米尔电子
直播中

dutong0321

3年用户 589经验值
擅长:模拟技术 嵌入式技术 接口/总线/驱动 光电显示 控制/MCU RF/无线
私信 关注
[技术]

【米尔NXP i.MX 93开发板试用评测】02.使用QT开发推流器

编译FFMPEG库

在编译FFMPEG库前,我们还需要先进行编译x264的库。
首先需要切换到root用户下进行:

sudo -s
. /opt/aarch64/environment-setup-armv8a-poky-linux
git clone https://code.videolan.org/videolan/x264.git
sudo mkdir /opt/arm
sudo mkdir /opt/arm/x264
cd x264
./configure --prefix=/opt/arm/x264 --enable-shared --enable-static --disable-opencl --enable-pic --disable-asm --host=aarch64-linux --cross-prefix=aarch64-poky-linux-
make -j4
make install

引入环境,然后使用GIT把X264代码拉下来,创建安装的目录,然后通过configure进行配置,例如配置安装的目录,我的安装目录是/opt/arm/x264,然后开启编译动态库,静态库,关闭一些没用的,最后的--host是交叉编译必须加的,还需要把交叉编译配置一下,完成后只需要编译,编译完成后安装即可了。

然后接下来就可以编译FFMPEG库了。

wget https://ffmpeg.org/releases/ffmpeg-6.0.tar.xz
xz -d ffmpeg-6.0.tar.xz
tar -xvf ffmpeg-6.0.tar
cd ffmpeg-6.0
sudo mkdir /opt/arm/ffmpeg
export PKG_CONFIG_PATH=/opt/arm/x264/lib/pkgconfig
./configure  --pkg-config="pkg-config --static"  --target-os=linux --arch=arm64  --cross-prefix=aarch64-poky-linux- --prefix=/opt/arm/ffmpeg --enable-version3 --enable-libx264 --enable-nonfree --enable-gpl --disable-x86asm  --extra-cflags="-I/opt/arm/x264/include" --extra-ldflags="-L/opt/arm/x264/lib"  --enable-cross-compile --sysroot=/opt/aarch64/sysroots/armv8a-poky-linux/
make
make install

接下来的东西基本和上面一样,首先使用wget从ffmpeg官网把源代码下载下来,然后使用xz解压ffmpeg,解压后还是一个tar包,所以再使用tar把ffmpeg的tar包再次解压了。然后一样创建安装的目录,需要配置一下编译环境,接下来通过configure进行配置,一样配置安装的目录,然后开启编译动态库、静态库,配置x264的环境,关闭一些没用的,开启交叉编译环境,除了上面的哪些交叉编译环境,最后还要设置一下sysroot,完成后只需要编译,编译完成后安装即可了,可以直接参照一下我上面的命令即可!

代码编写

然后我们开始写程序的代码。
首先,我们修改一下显示界面,拉进去3个label,2个输入框和1个按钮,删去没用的菜单栏和状态栏,调整一下位置,改一下名称就可以看到如下的界面了。
1016.png

接下来,创建一个基于QThread的类:
ffmpegthread.h

#ifndef FFMPEGTHREAD_H
#define FFMPEGTHREAD_H

#include <QThread>
#include <QDebug>

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavutil/avutil.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/mathematics.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
}

class FfmpegThread : public QThread {
    Q_OBJECT

public:
    FfmpegThread(QObject *parent = 0);
    virtual ~FfmpegThread();

signals:
    void main_window_signal(int);

protected:
    virtual void run();

private:
    const char input_link[255] = "rtsp://admin:a1234567@192.168.1.65:554/h264/ch1/sub/av_stream";
    const char output_link[255] = "rtmp://192.168.1.1:8910/rtmplive/cctv";
    const int width = 640, height = 480, fps = 10;
};

#endif // FFMPEGTHREAD_H

ffmpegthread.cpp

#include "ffmpegthread.h"

FfmpegThread::FfmpegThread(QObject *parent) : QThread(parent) {
    //
}

FfmpegThread::~FfmpegThread() { }

void FfmpegThread::run() {
    int	i_video_output_stream = -1;
    int64_t i_video_frame = 0;

    avformat_network_init();

    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    AVDictionary* options = NULL;
    av_dict_set(&options, "max_delay", "200000", 0);
    av_dict_set(&options, "stimeout", "1000000", 0);  //设置超时断开连接时间
    av_dict_set(&options, "rtsp_transport", "tcp", 0);  //tcp方式打开

    AVFormatContext *p_video_input_format_ctx = avformat_alloc_context();
    AVStream *p_video_input_stream = NULL;
    if (avformat_open_input(&p_video_input_format_ctx, input_link, NULL, &options) != 0) {
        qDebug() << "error: input stream open fail!";
        emit main_window_signal(-1);
        return;
    }
    if (avformat_find_stream_info(p_video_input_format_ctx, NULL) < 0) {
        qDebug() << "error: couldn't find stream information.\n";
        emit main_window_signal(-1);
        return;
    }
    for (unsigned int i = 0; i < p_video_input_format_ctx->nb_streams; i++) {
        if (p_video_input_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            p_video_input_stream = p_video_input_format_ctx->streams[i];
            break;
        }
    }
    if (p_video_input_stream == NULL) {
        qDebug() << "error: couldn't find video stream.\n";
        emit main_window_signal(-1);
        return;
    }

    AVFormatContext *p_output_format_ctx;
    if (avformat_alloc_output_context2(&p_output_format_ctx, 0, "flv", output_link) != 0) {
        qDebug() << "error: avformat_alloc_output_context2!\n";
        emit main_window_signal(-1);
        return;
    }

    //添加视频流
    AVStream *p_video_output_stream = avformat_new_stream(p_output_format_ctx, NULL);
    if (!p_video_output_stream) {
        qDebug() << "error: avformat_new_stream failed!\n";
        emit main_window_signal(-1);
        return;
    }
    if (avcodec_parameters_copy(p_video_output_stream->codecpar, p_video_input_stream->codecpar) < 0) {
        qDebug() << "error: avformat_new_stream failed!\n";
        emit main_window_signal(-1);
        return;
    }
    //附加标志,这个一定要设置
    p_video_output_stream->codecpar->codec_tag = 0;

    ///打开rtmp 的网络输出IO  AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
    if (avio_open(&p_output_format_ctx->pb, output_link, AVIO_FLAG_WRITE) != 0) {
        qDebug() << "error: avio_open failed!\n";
        emit main_window_signal(-1);
        return;
    }
    //写入封装头
    if (avformat_write_header(p_output_format_ctx, NULL) != 0) {
        qDebug() << "error: avformat_write_header failed!\n";
        emit main_window_signal(-1);
        return;
    }
    for (unsigned int i = 0; i < p_output_format_ctx->nb_streams; i++) {
        if (p_output_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            i_video_output_stream = i;
        }
    }

    while (1) {
        if (av_read_frame(p_video_input_format_ctx, packet) >= 0){
            packet->stream_index = i_video_output_stream;
            packet->pts = av_rescale_q_rnd(packet->pts, p_video_input_stream->time_base, p_video_output_stream->time_base, AV_ROUND_NEAR_INF);
            packet->dts = av_rescale_q_rnd(packet->dts, p_video_input_stream->time_base, p_video_output_stream->time_base, AV_ROUND_NEAR_INF);
            packet->duration = av_rescale_q(packet->duration, p_video_input_stream->time_base, p_video_output_stream->time_base);
            packet->pos = -1;

            if (av_interleaved_write_frame(p_output_format_ctx, packet) < 0) {
                qDebug() << "error: av_interleaved_write_frame failed!\n";
            }
            av_packet_unref(packet);
            i_video_frame++;
        }
        usleep(10000);
    }
}

main.c函数不用动,不过我们需要修改一下mainwindow类:
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "ffmpegthread.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();
    void ffmpeg_thread_slot(int code);

private:
    Ui::MainWindow *ui;
    FfmpegThread *ffmpeg_thread = nullptr;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow) {
    ui->setupUi(this);
    ffmpeg_thread = new FfmpegThread();
    connect(ffmpeg_thread, SIGNAL(main_window_signal(int)), this, SLOT(ffmpeg_thread_slot(int)));
}

MainWindow::~MainWindow() {
    delete ui;
}

void MainWindow::on_pushButton_clicked() {
    ui->pushButton->setEnabled(false);
    ffmpeg_thread->start();
}

void MainWindow::ffmpeg_thread_slot(int code) {
    if (code == -1) {
        ui->pushButton->setEnabled(true);
    }
}

在.pro的文件中也需要修改:

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    ffmpegthread.cpp \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    ffmpegthread.h \
    mainwindow.h

FORMS += \
    mainwindow.ui

LIBS += -lavformat \
-lavfilter \
-lavcodec \
-lavutil \
-lswresample \
-lswscale \
-lm \
-lpthread \
-lrt

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

修改的关键点是增加LIBS,把FFMPEG的内容添加进去,然后进行编译即可。
1018.jpg
然后我们输入进去输入链接和输出链接,然后点击Start进行转换,就可以看到状态栏开始转换开了,并且显示已经转换了多少帧了,按钮也变成了Stop按钮。
然后我们打开VLC就可以看到输出链接的运行图了:
1017.png

FFMPEG的另外一种方法

如果不想编译ffmpeg有没有现成的使用方法呢?
其实还有两种,第二种稍后再说,先说第一种。
打开ffmpeg的官网,点击Download,不过我们不可以选择Linux的小企鹅,而是要选择Windows的窗口,然后选择第二个By Btbn的链接,就可以打开github的release页面了。
1019.png

打开以后就可以看到各种版本已经为我们编译好的各种版本的ffmpeg库了,我们要选择的是ffmpeg-n6.1.2-linuxarm64-lgpl-shared-6.1.tar.xz,或者我们也可以选择ffmpeg7.0的版本也可以,linuxarm64代表的是我们的系统和架构,LGPL代表的是我们的协议,因为我们要进行开发所以要选择shared的这种版本。

1020.png

更多回帖

发帖
×
20
完善资料,
赚取积分