0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【干货分享】基于QT和ffmpeg硬解码的多路摄像头取流

电子发烧友论坛 2025-07-29 08:05 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

目前QT和ffmpeg都已经安装好了,接下来将会使用QT和ffmpeg来进行编程

https://bbs.elecfans.com/jishu_2496310_1_1.html



前言

其实官方为我们已经提供了三个官方实例,我打开学习了一下,QT实例虽然也用到了信号槽,是点击按钮的信号槽,我觉的QT妙就妙在了信号槽和多线程,而且官方的是QT5;多路摄像头取流案例使用的是采流使用的是GStreamer,UI采用的是GTK库;所以我做了一个QT6+多线程+ffmpeg的多路摄像头取流案例供参考。


代码编写

test.pro 这个地方比较重要的改动主要在于添加ffmpeg要使用的库


QT += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += 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.0SOURCES += \ camera.cpp \ main.cpp \ mainwindow.cppHEADERS += \ camera.h \ mainwindow.hFORMS += \ mainwindow.uiLIBS += -lavdevice \-lavformat \-ldrm \-lavfilter \-lavcodec \-lavutil \-lswresample \-lswscale \-lm \-lrga \-lpthread \-lrt \-lrockchip_mpp \-lz# Default rules for deployment.qnx: target.path = /tmp/$${TARGET}/binelse: unix target.path = /opt/$${TARGET}/bin!isEmpty(target.path): INSTALLS += target



camera.h:采集线程的头文件,无需多言

#ifndefCAMERA_H#defineCAMERA_H// 显示画面的大小#defineDISPLAY_WIDTH 640#defineDISPLAY_HEIGHT 360#include#include#includeextern"C"{#include"libavdevice/avdevice.h"#include"libavcodec/avcodec.h"#include"libavdevice/avdevice.h"#include"libavfilter/avfilter.h"#include"libavfilter/buffersink.h"#include"libavfilter/buffersrc.h"#include"libavformat/avformat.h"#include"libavutil/pixdesc.h"#include"libavutil/opt.h"#include"libavutil/avassert.h"#include"libavutil/imgutils.h"#include"libavutil/avutil.h"#include"libavutil/audio_fifo.h"#include"libavutil/file.h"#include"libavutil/imgutils.h"#include"libavutil/mathematics.h"#include"libavutil/pixfmt.h"#include"libavutil/time.h"#include"libswscale/swscale.h"#include"libswresample/swresample.h"#include"libavutil/hwcontext.h"#include"libavutil/hwcontext_drm.h"#include#include#include#include#include}classCamera:publicQThread { Q_OBJECTpublic: Camera(QObject *parent =0); virtual~Camera(); voidset_para(intindex,constchar*rtsp_str);signals: voidupdate_video_label(int, QImage *);protected: virtualvoidrun();private: intindex; char*rtsp_str =nullptr; uint8_t*dst_data_main[4]; intdst_linesize_main[4]; QImage *p_image_main =nullptr;};#endif// CAMERA_H



camera.cpp这部分是代码的关键,是取流的线程,具体的解释已经写好在注释里

#include"camera.h"Camera::Camera(QObject *parent) : QThread(parent) { // 创建显示图像的内存 av_image_alloc(dst_data_main, dst_linesize_main, DISPLAY_WIDTH, DISPLAY_HEIGHT,AV_PIX_FMT_RGB24,1); p_image_main = new QImage(dst_data_main[0], DISPLAY_WIDTH, DISPLAY_HEIGHT, QImage::Format_RGB888);}Camera::~Camera() {}voidCamera::set_para(intindex,constchar*rtsp_str) { // 配置线程的参数 this->index = index; this->rtsp_str = newchar[255]; strcpy(this->rtsp_str, rtsp_str);}voidCamera::run() { // 线程执行函数 intsrc_width, src_height; AVFormatContext*av_fmt_ctx =NULL; AVStream*av_stream =NULL; AVCodecContext* av_codec_ctx; // 设置RTSP连接参数 AVDictionary*options =NULL; av_dict_set(&options,"rtsp_transport","tcp",0); av_dict_set(&options,"buffer_size","1024000",0); av_dict_set(&options,"stimeout","2000000",0); av_dict_set(&options,"max_delay","500000",0); // 创建AVFormatContext av_fmt_ctx = avformat_alloc_context(); if(avformat_open_input(&av_fmt_ctx, rtsp_str,NULL, &options) !=0) { qDebug() << "Couldn't open input stream.\n";    }    if (avformat_find_stream_info(av_fmt_ctx, NULL) < 0) {        qDebug() << "Couldn't find stream information.\n";    }    // 选取视频流    for (int i = 0; i < av_fmt_ctx->nb_streams; i++) { if(av_fmt_ctx->streams[i]->codecpar->codec_type ==AVMEDIA_TYPE_VIDEO) { av_stream = av_fmt_ctx->streams[i]; break; } } if(av_stream ==NULL) { qDebug() << "Couldn't find stream information.\n";    }    // 第一句是使用的ffmpeg默认的软编码,第二句是使用的rkmpp硬编码//    const AVCodec *av_codec = avcodec_find_decoder(av_stream->codecpar->codec_id); constAVCodec*av_codec = avcodec_find_decoder_by_name("h264_rkmpp"); if(av_codec == nullptr) { qDebug() << "Couldn't find decoder codec.\n";        return;    }    // 创建CodecContext    av_codec_ctx = avcodec_alloc_context3(av_codec);    if (av_codec_ctx == nullptr) {        qDebug() << "Couldn't find alloc codec context.\n";    }    avcodec_parameters_to_context(av_codec_ctx, av_stream->codecpar); // 打开解码器 if(avcodec_open2(av_codec_ctx, av_codec,NULL) < 0) {        qDebug() << "Could not open codec.\n";    }    // 在调试栏显示使用的解码器    qDebug() << av_codec->long_name << " " << av_codec->name; src_width = av_codec_ctx->width; src_height = av_codec_ctx->height; // 创建AVFrame和AVPacket供解码使用 AVFrame*av_frame = av_frame_alloc(); AVPacket*av_packet = (AVPacket*)av_malloc(sizeof(AVPacket)); // 创建SwsContext,将解码出来的YUV格式,转换成供界面显示的RGB格式,同时对图像进行缩放,缩放成显示大小 structSwsContext* img_ctx = sws_getContext(src_width, src_height, av_codec_ctx->pix_fmt, DISPLAY_WIDTH, DISPLAY_HEIGHT,AV_PIX_FMT_RGB24, SWS_BILINEAR,0,0,0); while(true) { // 解码流程 if(av_read_frame(av_fmt_ctx, av_packet) >=0){ avcodec_send_packet(av_codec_ctx, av_packet); while(avcodec_receive_frame(av_codec_ctx, av_frame) ==0) { // 转换画面 sws_scale(img_ctx, (constuint8_t*const*)av_frame->data, av_frame->linesize,0, src_height, dst_data_main, dst_linesize_main); // 发送信号,将QImage图像地址发送到主线程,供更新界面 // 一般情况下,界面的更新都不允许在子线程进行 // 同时,一些耗时长的操作也一般不允许在主线程进行,例如网络操作,IO操作以及解码等,防止主线程卡死,画面卡顿 emit update_video_label(index, p_image_main); } av_frame_unref(av_frame); av_packet_unref(av_packet); }else{ msleep(10); } } return;}


mainwindow.h 这个文件也不必多说,button的点击事件是自动添加的,增加4个采集线程和4个状态。

#ifndefMAINWINDOW_H#defineMAINWINDOW_H#include#include"camera.h"QT_BEGIN_NAMESPACEnamespaceUi {classMainWindow; }QT_END_NAMESPACEclassMainWindow:publicQMainWindow{ Q_OBJECTpublic: MainWindow(QWidget *parent =nullptr); ~MainWindow();publicslots: voidupdate_video_label(intindex, QImage *image);privateslots: voidon_pushButton_1_clicked(); voidon_pushButton_2_clicked(); voidon_pushButton_3_clicked(); voidon_pushButton_4_clicked();private: Ui::MainWindow *ui; Camera **camera =newCamera*[4]; boolstatus[4] = {false};};#endif// MAINWINDOW_H



mainwindow.cpp 对采集线程进行初始化并绑定更新视图槽

#include"mainwindow.h"#include"ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(newUi::MainWindow) { ui->setupUi(this); // 对采集线程进行初始化并绑定更新视图槽 camera[0] =newCamera(); camera[1] =newCamera(); camera[2] =newCamera(); camera[3] =newCamera(); connect(camera[0],SIGNAL(update_video_label(int, QImage *)),this,SLOT(update_video_label(int, QImage *))); connect(camera[1],SIGNAL(update_video_label(int, QImage *)),this,SLOT(update_video_label(int, QImage *))); connect(camera[2],SIGNAL(update_video_label(int, QImage *)),this,SLOT(update_video_label(int, QImage *))); connect(camera[3],SIGNAL(update_video_label(int, QImage *)),this,SLOT(update_video_label(int, QImage *)));}MainWindow::~MainWindow() { deleteui;}voidMainWindow::on_pushButton_1_clicked(){ // 如果已经开始播放,那么就不必继续了,后期可以添加先停止原链接采集再开启新链接采集 if(status[0]) { return; } status[0] =true; camera[0]->set_para(0, ui->lineEdit_1->text().toStdString().c_str()); // 启动采集 camera[0]->start();}voidMainWindow::on_pushButton_2_clicked(){ // 同button1的点击事件处理 if(status[1]) { return; } status[1] =true; camera[1]->set_para(1, ui->lineEdit_2->text().toStdString().c_str()); camera[1]->start();}voidMainWindow::on_pushButton_3_clicked(){ // 同button1的点击事件处理 if(status[2]) { return; } status[2] =true; camera[2]->set_para(2, ui->lineEdit_3->text().toStdString().c_str()); camera[2]->start();}voidMainWindow::on_pushButton_4_clicked(){ // 同button1的点击事件处理 if(status[3]) { return; } status[3] =true; camera[3]->set_para(3, ui->lineEdit_4->text().toStdString().c_str()); camera[3]->start();}voidMainWindow::update_video_label(intcode, QImage *image){ // 将采集回来的画面显示到对应的界面 switch(code) { case0: ui->label_video_1->setPixmap(QPixmap::fromImage(*image)); break; case1: ui->label_video_2->setPixmap(QPixmap::fromImage(*image)); break; case2: ui->label_video_3->setPixmap(QPixmap::fromImage(*image)); break; case3: ui->label_video_4->setPixmap(QPixmap::fromImage(*image)); break; default: break; }}


还需要更改的地方

首先第一个更改的地方是吧QT编译的线程降低一些,线程太多容易搞死机,我这里改成了1,可以像我这样修改,就可以了。

c9e83ea6-6c0f-11f0-9080-92fbcf53809c.png
第二个问题,让我整整花费了一整天进行修改,我在安装自己编译的ffmpeg前就已经测试过ffmpeg板子没有安装,所以我就默认ffmpeg的库也没有安装,等到我编译完成后总是无法找到h264_rkmpp的解码器,但是偏偏使用ffmpeg来查看时,却拥有h264_rkmpp的解码器,我整整找了一天的问题关键所在。首先,我怀疑的是不是ffmpeg需要开启 --enable-shared 进行动态编译,于是按照猜想执行

./configure --prefix=/usr --enable-gpl --enable-version3 --enable-libdrm --enable-rkmpp --enable-rkrga --enable-sharedmake -j 6sudo make install

但是执行完毕后,依旧没有解决问题,直到花了一天的时间后,我再次使用ldd test的时候突然发现一个问题,明明我的安装路径是在/usr,理论上动态连接库应该是在/usr/lib当中,但是为何是在/lib/aarch64-linux-gnu/下面呢?于是我怀疑虽然没有安装ffmpeg,但是很有可能把ffmpeg的库已经安装上了,所以使用apt search ffmpeg搜索,然后就看到avformat avcodec avutil等库都已经安装好了,所以有时候不要想当然,当把该排除的都排除了,那可能就是最简单的原因,简单到压根都想不起来的程度,发现问题那就解决问题吧。

第一种解决办法,把自带的ffmpeg卸载掉,缺点是依赖比较多,卸载起来还是比较麻烦,但是强烈推荐。

sudoapt remove libavformat-dev libopencv-dev libgstreamer-plugins-bad1.0-dev libcheese-dev libcheese-gtk-dev libopencv-highgui-dev libopencv-contrib-dev libopencv-features2d-dev libopencv-objdetect-dev libopencv-calib3d-dev libopencv-stitching-dev libopencv-videostab-dev libavcodec-dev libavutil-dev libavdevice-dev libswresample-dev libswscale-dev libswresample4 libswscale6 libavutil57 libavformat59 libavcodec59 libchromaprint1 libfreerdp2-2libopencv-videoio406 gstreamer1.0-plugins-bad libopencv-superres406 libopencv-videoio-dev libopencv-videostab406 libweston-10-0gnome-video-effects gstreamer1.0-plugins-bad-apps gstreamer1.0-plugins-bad-dbgsym libcheese8 libgstrtspserver-1.0-0 libopencv-superres-dev libweston-10-0-dbgsym libweston-10-dev weston cheese gir1.2-cheese-3.0gir1.2-gst-rtsp-server-1.0 gstreamer1.0-plugins-bad-apps-dbgsym libcheese-gtk25 libcheese8-dbgsym libgstrtspserver-1.0-dev weston-dbgsym cheese-dbgsym libcheese-gtk25-dbgsym


如果你需要里面的很多库,比如像测试官方的案例,那么就试试第二种解决办法:
首先,我们不能让qt编译时选择默认的include和lib路径,这也就是为什么在.pro中没有写路径也可以找到ffmpeg的头文件和库文件,我们需要添加路径,修改test.pro文件如下:

QT += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += 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.0SOURCES += \ camera.cpp \ main.cpp \ mainwindow.cppHEADERS += \ camera.h \ mainwindow.hFORMS += \ mainwindow.uiINCLUDEPATH += /usr/includeLIBS += -L/usr/lib \-lavdevice \-lavformat \-ldrm \-lavfilter \-lavcodec \-lavutil \-lswresample \-lswscale \-lm \-lrga \-lpthread \-lrt \-lrockchip_mpp \-lz# Default rules for deployment.qnx: target.path = /tmp/$${TARGET}/binelse: unix target.path = /opt/$${TARGET}/bin!isEmpty(target.path): INSTALLS += target

这样我们就强制让qt使用我们指定的ffmpeg的头文件和库文件了,但是仅仅有这些还不够,我们还需要在编译后,运行时指定动态链接库:

LD_LIBRARY_PATH=/usr/lib ./test


运行,对比及分析

首先我们尝试使用软解码来看看接入4路1080P 25FPS RTSP流,显示画面如下:

c9f904e8-6c0f-11f0-9080-92fbcf53809c.png
CPU占用:
ca2e9b4e-6c0f-11f0-9080-92fbcf53809c.png

然后我们看看用硬解码接入相同的码流,甚至清晰度还要好过软解码:
ca425f9e-6c0f-11f0-9080-92fbcf53809c.png

CPU占用:
ca552fd4-6c0f-11f0-9080-92fbcf53809c.png


可以看到硬解码接入后直接降低至少一般的CPU占用率,其实降低的要远远超过一半,因为从GPU把解码后的视频数据拷贝到内存当中、对图像进行缩放以及显示图像都是CPU占用率极高的地方,所以这也是后期优化的方向。


ca63141e-6c0f-11f0-9080-92fbcf53809c.png声明:本文由电子发烧友社区发布,转载请注明以上来源。如需平台(包括:试用+专栏+企业号+学院+技术直播+共建社区)合作及入群交流,请咨询18925255684(微信同号:elecfans123),谢谢!

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 摄像头
    +关注

    关注

    61

    文章

    5058

    浏览量

    102415
  • Qt
    Qt
    +关注

    关注

    2

    文章

    318

    浏览量

    40283
  • ffmpeg
    +关注

    关注

    0

    文章

    49

    浏览量

    7859
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    基于FFMPEG采集摄像头图像编码MP4视频+时间水印

    基于FFMPEG采集摄像头图像编码MP4视频+时间水印
    的头像 发表于 09-29 15:46 4766次阅读
    基于<b class='flag-5'>FFMPEG</b>采集<b class='flag-5'>摄像头</b>图像编码MP4视频+时间水印

    《深入理解FFmpeg阅读体验》FFmpeg摄像头测试

    better, run \'man ffmpeg\' OK的。 二、连接摄像头 我用的是手头的海康720P USB摄像头。 插进去后系统自动识别。 [ 1812.860609] uvcvideo
    发表于 04-17 19:06

    【EASY EAI Orin Nano开发板试用体验】05-基于QTffmpeg解码多路摄像头

    使用的是GStreamer,UI采用的是GTK库;所以我做了一个QT6+多线程+ffmpeg多路摄像头
    发表于 07-25 19:24

    LabVIEW获取网络摄像头方法

    。 *****************************************************************************************2015年9月28日 更新如果摄像头的数据是http,那就
    发表于 04-14 23:36

    nanopi m1最新rom支持摄像头模块CAM500A

    `http://wiki.friendlyarm.com/wiki/index.php/NanoPi_M1/zhDebian和Ubuntu-Core with Qt-Embedded系统支持摄像头
    发表于 09-08 16:09

    ESM6802支持Qt摄像头应用

    版本。使用Qt的multimedia模块可以方便快捷的进行摄像头应用的开发,本文使用Qt源码中提供的camera例程进行展示,例程代码可从Qt源码中获取或者向我们工程师索要。  我们使
    发表于 10-20 10:33

    安防摄像头的过保护

    ,分配给摄像头负载。无论是开关电源还是火牛电源供电,都是一个公用电源的多路供电系统,这是目前普遍采用的方法。 而在这种供电系统中,会存在一个致命的问题,那就是当其中某个摄像头负载有短路或过
    发表于 12-29 14:10

    labview如何连接多路网络摄像头采集视频

    想知道怎么实现labview连接多路网络摄像头采集视频,怎么实现通讯都不太清楚。
    发表于 12-26 23:39

    在RK3288主板Debian 9.13系统上如何调用CPU解进行网络摄像头视频解码

    在RK3288主板Debian 9.13系统上如何调用CPU解进行网络摄像头视频解码
    发表于 03-03 06:47

    Android解码如何去实现呢

    很简单,右下角是摄像头的预览窗口,中间的经过硬编解码显示出来的数据。logcat显示:从logcat可以看出1280*720的preview数据长度是1280*720*3/2=1382400;编码后数据长度是et 大概在500
    发表于 04-11 14:39

    RV1126通过v4l2拉usb摄像头mjpeg解码耗时很长如何解决

    在rv1126上拉usb摄像头1080p分辨率的视频的时候,默认情况下拉的是yuv格式的,并且拉1080p的时候帧率是5fps,如果
    发表于 11-23 16:32

    QT上构建ffmpeg环境实现音频的解码

    QT上构建ffmpeg环境,实现音频的解码
    发表于 06-09 09:05 1668次阅读
    在<b class='flag-5'>QT</b>上构建<b class='flag-5'>ffmpeg</b>环境实现音频的<b class='flag-5'>解码</b>

    嵌入式Qt-简易网络监控摄像头

    本篇介绍了如何用Qt实现一个网络摄像头功能,通过服务端将USB摄像头转换为一个IP摄像头,Linux板子中的客户端来连接服务器,将摄像头的实
    的头像 发表于 09-14 08:52 2608次阅读
    嵌入式<b class='flag-5'>Qt</b>-简易网络监控<b class='flag-5'>摄像头</b>

    基于RV1126开发板实现多路网络摄像头方案

    在RV1126上实现多路网络摄像头方案
    的头像 发表于 04-11 15:57 925次阅读
    基于RV1126开发板实现<b class='flag-5'>多路</b>网络<b class='flag-5'>摄像头</b><b class='flag-5'>取</b><b class='flag-5'>流</b>方案

    基于RV1126开发板实现多路网络摄像头方案

    在RV1126上实现多路网络摄像头方案
    的头像 发表于 04-21 14:39 49次阅读
    基于RV1126开发板实现<b class='flag-5'>多路</b>网络<b class='flag-5'>摄像头</b><b class='flag-5'>取</b><b class='flag-5'>流</b>方案