树莓派4B Qt+FFMPEG 多线程录制USB相机mjpeg数据流“h264_omx“硬件编码的MP4文件

文章目录

  • 1 前言
  • 2 一些问题说明
    • 2.0 树莓派4b系统版本
    • 2.1 Qt
    • 2.2 FFMPEG
    • 2.3 图像格式
  • 3 核心代码
    • 3.0 代码逻辑
    • 3.1 pro文件
    • 3.2 avframequeue.cpp
    • 3.3 decodethread.cpp
  • 4 资源下载


1 前言

  本项目为在树莓派4B开发板上,通过Qt+FFMPEG以多线程分别解码、编码USB摄像头视频数据。其中USB摄像头视频输入格式为MJPEG。通过树莓派的硬件编码器“h264_omx”进行硬件编码封装成mp4文件。


2 一些问题说明

2.0 树莓派4b系统版本

  本项目中系统用的是树莓派的raspbain 10 代号 buster。
  本人尝试过用raspbain 11 代号bullseye的系统,在结合ffmpeg编译h264_omx硬件编码器接口的时候报错,无法正常生成h264_omx编码器。网上一些帖子说需要基于openMax的库,在/opt/vc这个目录下,但是我发现bullseye没有这个目录,暂时放弃,具体原因后续再排查。

2.1 Qt

  通过sudo apt-get install 安装。

2.2 FFMPEG

  需要下载x264、FFMPEG源码,按照下述步骤编译。

    // 更新源sudo apt-get updatesudo apt-get upgrade// 安装gitsudo apt-get install git// 安装依赖sudo apt-get -y install autoconf automake build-essential cmake git-core libass-dev libfreetype6-dev libgnutls28-dev libsdl2-dev libtool libva-dev libvorbis-dev meson ninja-build pkg-config texinfo wget yasm zlib1g-dev nasm libaom-dev libmp3lame-dev libopus-dev libx264-dev libvpx-dev libavfilter-dev// 安装omx依赖sudo apt-get install libomxil-bellagio-dev/* 编译fdk-aac编码器(可不执行) */sudo apt-get install libtoolgit clone --depth 1 https://github.com/mstorsjo/fdk-aaccd fdk-aacautoreconf -fiv./configure --prefix=/usr --disable-sharedmake -j4make install/* 编译x264(若git不下来,可联系笔者获取已下好的x264源码包) */git clone https://git.videolan.org/git/x264.gitcd x264./configure --enable-shared --enable-static --enable-strip --disable-climake -j4sudo make install// 下载ffmpeg源码wget https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2tar -jxvf ffmpeg-xxx.tar.bz2cd ffmpeg-xxx.tar.bz2// 配置mkdir build./configure --prefix=$PWD/build --enable-gpl --enable-version3 --enable-nonfree --enable-static --enable-shared --disable-opencl --disable-thumb --disable-pic --disable-stripping --enable-small --enable-ffmpeg --enable-ffplay --disable-doc --disable-htmlpages --disable-podpages --disable-txtpages --disable-manpages --disable-everything --enable-libx264 --enable-encoder=libx264 --enable-decoder=h264 --enable-encoder=aac --enable-decoder=aac --enable-encoder=ac3 --enable-decoder=ac3 --enable-encoder=rawvideo --enable-decoder=rawvideo --enable-encoder=mjpeg --enable-decoder=mjpeg --enable-demuxer=concat --enable-muxer=flv --enable-demuxer=flv --enable-demuxer=live_flv --enable-muxer=hls --enable-muxer=segment --enable-muxer=stream_segment --enable-muxer=mov --enable-demuxer=mov --enable-muxer=mp4 --enable-muxer=mpegts --enable-demuxer=mpegts --enable-demuxer=mpegvideo --enable-muxer=matroska --enable-demuxer=matroska --enable-muxer=wav --enable-demuxer=wav --enable-muxer=pcm* --enable-demuxer=pcm* --enable-muxer=rawvideo --enable-demuxer=rawvideo --enable-muxer=rtsp --enable-demuxer=rtsp --enable-muxer=rtsp --enable-demuxer=sdp --enable-muxer=fifo --enable-muxer=tee --enable-parser=h264 --enable-parser=aac --enable-protocol=file --enable-protocol=tcp --enable-protocol=rtmp --enable-protocol=cache --enable-protocol=pipe --enable-filter=aresample --enable-filter=allyuv --enable-filter=scale --enable-libfreetype --enable-indev=v4l2 --enable-indev=alsa --enable-omx --enable-omx-rpi --enable-encoder=h264_omx --enable-mmal --enable-hwaccel=h264_mmal --enable-decoder=h264_mmal// 编译(慎用4线程,若树莓派内存小请慎用)make -j4sudo make install// 生成的结果均在当前目录下build文件件内ls ./build

2.3 图像格式

  编码器对输入图片格式有要求,本项目中的h264_omx硬件编码器输入图像格式必须为yuv420p。而摄像头视频数据解封装、解码之后是MJPEG格式,即yuvj422p。需要完成编码,需要将yuvj422p转换成yuv420p。这里需要通过SwsContext上下文,进行sws_scale操作。
  在int DecodeThread::Init(AVCodecParameters *par)函数中需要添加下述内容:

    // 初始化 SwsContext,将 MJPEG 格式转换为 YUV420Psws_ctx_ = sws_getContext(codec_ctx_->width, codec_ctx_->height, codec_ctx_->pix_fmt, // 源格式codec_ctx_->width, codec_ctx_->height, AV_PIX_FMT_YUV420P, // 目标格式SWS_BILINEAR, NULL, NULL, NULL);

  在void DecodeThread::Run()函数中需要添加下述内容:

ret = sws_scale(sws_ctx_,frame->data, frame->linesize, 0, codec_ctx_->height,yuv_frame->data, yuv_frame->linesize);
yuv_frame->pts = frame->pts;

  并且,需要同步AVFramepts。因为sws_scale仅仅操作了AVFrame.data字段的内容。

3 核心代码

3.0 代码逻辑

  本项目下的文件层级如下图所示.
项目文件结构
  本项目采用多线程的方式对视频数据流解封装、解码、编码保存。通过demuxthread、decodethread、encodethread,三个子线程实现上述不同操作。三个线程继承于thread.h
  此外,通过AVFrameQueue以及AVPacketQueue两个Queue来实现线程之间的数据共享

3.1 pro文件

TEMPLATE = app
CONFIG += console c++11
CONFIG -= app_bundleHEADERS += \avframequeue.h \avpacketqueue.h \decodethread.h \demuxthread.h \encodethread.h \queue.h \thread.hSOURCES += \avframequeue.cpp \avpacketqueue.cpp \decodethread.cpp \demuxthread.cpp \encodethread.cpp \main.cpp# 检查平台
win32: CONFIG += windows
unix: CONFIG += linux# Windows 平台设置
win32 {FFMPEG_PATH = E:/FFMPEG/ffmpeg-master-latest-win64-gpl-shared/ffmpeg-master-latest-win64-gpl-sharedINCLUDEPATH += $$FFMPEG_PATH/includeLIBS += -L$$FFMPEG_PATH/lib \-lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
}# Linux 平台设置
unix {INCLUDEPATH += /home/pi/Desktop/FFmpeg-master/build/includeLIBS += /home/pi/Desktop/FFmpeg-master/build/lib/libavcodec.so  \/home/pi/Desktop/FFmpeg-master/build/lib/libavdevice.so   \/home/pi/Desktop/FFmpeg-master/build/lib/libavfilter.so   \/home/pi/Desktop/FFmpeg-master/build/lib/libavformat.so  \/home/pi/Desktop/FFmpeg-master/build/lib/libavutil.so   \/home/pi/Desktop/FFmpeg-master/build/lib/libpostproc.so  \/home/pi/Desktop/FFmpeg-master/build/lib/libswresample.so   \/home/pi/Desktop/FFmpeg-master/build/lib/libswscale.so}

3.2 avframequeue.cpp

#include "avframequeue.h"
#include <QDebug>AVFrameQueue::AVFrameQueue()
{}AVFrameQueue::~AVFrameQueue()
{}void AVFrameQueue::Abort()
{release();queue_.Abort();
}int AVFrameQueue::Push(AVFrame *val)
{AVFrame *tmp_frame = av_frame_alloc();av_frame_move_ref(tmp_frame, val);return queue_.Push(tmp_frame);
}AVFrame *AVFrameQueue::Pop(const int timeout)
{AVFrame *tmp_frame = NULL;int ret = queue_.Pop(tmp_frame, timeout);if(ret<0){if(ret == -1)qDebug("AVFrameQueue::Pop failed");}return tmp_frame;
}AVFrame *AVFrameQueue::Front()
{AVFrame *tmp_frame = NULL;int ret = queue_.Front(tmp_frame);if(ret<0){if(ret == -1)qDebug("AVFrameQueue::Front failed");}return tmp_frame;
}int AVFrameQueue::Size()
{return queue_.Size();
}void AVFrameQueue::release()
{while(true){AVFrame *frame = NULL;int  ret = queue_.Pop(frame, 1);if(ret<0){break;}else{av_frame_free(&frame);continue;}}
}

3.3 decodethread.cpp

#include "decodethread.h"
#include <QDebug>DecodeThread::DecodeThread(AVPacketQueue *packet_queue, AVFrameQueue *frame_queue): packet_queue_(packet_queue), frame_queue_(frame_queue)
{
}DecodeThread::~DecodeThread()
{if (thread_){Stop();}if (codec_ctx_){avcodec_close(codec_ctx_);}if (sws_ctx_){sws_freeContext(sws_ctx_);}
}int DecodeThread::Init(AVCodecParameters *par)
{if (!par){qDebug("Init par is null!");return -1;}codec_ctx_ = avcodec_alloc_context3(NULL);int ret = avcodec_parameters_to_context(codec_ctx_, par);if (ret < 0){av_strerror(ret, err2str, sizeof(err2str));qDebug("avcodec_parameters_to_context failed, ret:%d, err2str:%s", ret, err2str);return -1;}const AVCodec *codec = avcodec_find_decoder(codec_ctx_->codec_id);if (!codec){qDebug("avcodec_find_decoder failed");return -1;}ret = avcodec_open2(codec_ctx_, codec, NULL);if (ret < 0){av_strerror(ret, err2str, sizeof(err2str));qDebug("avcodec_open2 failed, ret:%d, err2str:%s", ret, err2str);return -1;}// 初始化 SwsContext,将 MJPEG 格式转换为 YUV420Psws_ctx_ = sws_getContext(codec_ctx_->width, codec_ctx_->height, codec_ctx_->pix_fmt, // 源格式codec_ctx_->width, codec_ctx_->height, AV_PIX_FMT_YUV420P, // 目标格式SWS_BILINEAR, NULL, NULL, NULL);if (!sws_ctx_){qDebug("sws_getContext failed");return -1;}qDebug("Init finish!");return 0;
}int DecodeThread::Start()
{thread_ = new std::thread(&DecodeThread::Run, this);if (!thread_){qDebug("new std::thread(&DecodeThread::Run, this) failed");return -1;}return 0;
}int DecodeThread::Stop()
{Thread::Stop();
}void DecodeThread::Run()
{AVFrame *frame = av_frame_alloc();qDebug("DecodeThread::Run into DecodeThread::run");while (abort_ != 1){AVFrame *yuv_frame = av_frame_alloc();// 分配YUV420P格式的AVFrameyuv_frame->format = AV_PIX_FMT_YUV420P;yuv_frame->width = codec_ctx_->width;yuv_frame->height = codec_ctx_->height;if (av_frame_get_buffer(yuv_frame, 32) < 0){qDebug() << "Could not allocate buffer for yuv_frame";return;}if (!yuv_frame->data[0]){qDebug() << "yuv_frame->data[0] is nullptr after av_frame_get_buffer";return;}if (frame_queue_->Size() > 10){std::this_thread::sleep_for(std::chrono::milliseconds(10));continue;}AVPacket *pkt = packet_queue_->Pop(10);if (pkt){int ret = avcodec_send_packet(codec_ctx_, pkt);av_packet_free(&pkt);//            qDebug("ret=%d", ret);if (ret < 0){av_strerror(ret, err2str, sizeof(err2str));qDebug("avcodec_send_packet failed, ret:%d, err2str:%s", ret, err2str);break;}while (true){ret = avcodec_receive_frame(codec_ctx_, frame);if (ret == 0){//                    qDebug()<<"Decoded frame info:";
//                    qDebug()<<"Width: "<<frame->width<<"Height: "<<frame->height;
//                    qDebug()<<"Pixel Format: "<<av_get_pix_fmt_name((AVPixelFormat)frame->format);
//                    qDebug()<<"Data[0]"<<frame->data[0];
//                    qDebug()<<"Linesize[0]: "<<frame->linesize[0];
//                    qDebug()<<"yuv_frame->data[0]: "<<yuv_frame->data[0];// 检查数据有效性if (!frame->data[0] || !yuv_frame->data[0]){qDebug("Invalid frame data, skipping frame");continue;}// 打印数据和行大小
//                    qDebug() << "Source frame linesize:" << frame->linesize[0];
//                    qDebug() << "Destination YUV frame linesize:" << yuv_frame->linesize[0];// 使用sws_scale将frame从MJPEG转换为YUV420P格式ret = sws_scale(sws_ctx_,frame->data, frame->linesize, 0, codec_ctx_->height,yuv_frame->data, yuv_frame->linesize);yuv_frame->pts = frame->pts;if (ret < 0){char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));qDebug("sws_scale failed, ret: %d, error: %s", ret, errbuf);continue;}// 将转换后的YUV420P格式的帧推入frame_queue_frame_queue_->Push(yuv_frame);
//                    qDebug("%s frame queue size %d", codec_ctx_->codec->name, frame_queue_->Size());continue;}else if (AVERROR(EAGAIN) == ret){break;}else{abort_ = 1;av_strerror(ret, err2str, sizeof(err2str));qDebug("avcodec_receive_frame failed, ret:%d, err2str:%s", ret, err2str);break;}}}else{
//            qDebug("Not got packet");}}av_frame_free(&frame);qDebug("DecodeThread::Run finish");
}

4 资源下载

本案例中涉及到的工程文件到此处下载https://download.csdn.net/download/wang_chao118/89990656

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/59770.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

i春秋-Hash(__wakeup沉默、序列化)

练习平台地址 竞赛中心 题目描述 题目内容 啥也没有就一个标签跳转 点击后的确发生了跳转 观察到url中有key和hash两个值&#xff0c;猜测hash是key的hash 查看源代码发现确实是 $hashmd5($sign.$key);the length of $sign is 8 解密得到$sign应该为kkkkkk01 构造122的hash i…

【C语言指南】C语言内存管理 深度解析

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C语言指南》 期待您的关注 引言 C语言是一种强大而灵活的编程语言&#xff0c;为程序员提供了对内存的直接控制能力。这种对内存…

解决vue3+ts打包项目时会生成map文件

在正常未配置的情况下使用npm run build 命令打包&#xff0c;会生成很多的js和map文件,map文件是为了方便我们在生产环境进行更友好的代码调试&#xff0c;但是这样就存一个安全问题&#xff1b;容易被攻击&#xff1b; 解决方法&#xff1a;在package.json文件&#xff0c;重…

MySQL:表设计

表的设计 从需求中获得类&#xff0c;类对应到数据库中的实体&#xff0c;实体在数据库中表现为一张一张的表&#xff0c;类中的属性就对应着表中的字段&#xff08;也就是表中的列&#xff09; 表设计的三大范式&#xff1a; 在数据库设计中&#xff0c;三大范式&#xff0…

使用 Azure OpenAI 服务对数据进行联合 SharePoint 搜索

作者&#xff1a;来自 Elastic Gustavo Llermaly 使用 Azure OpenAI 服务处理你的数据&#xff0c;并使用 Elastic 作为向量数据库。 在本文中&#xff0c;我们将探索 Azure OpenAI 服务 “On Your Data”&#xff0c;使用 Elasticsearch 作为数据源。我们将使用 Elastic Shar…

chat2db调用ollama实现数据库的操作。

只试了mysql的调用。 其它的我也不用&#xff0c;本来想充钱算了。最后一看单位是美刀。就放弃了这分心。于是折腾了一下。 本地运行chat2db 及chat2db ui https://gitee.com/ooooinfo/Chat2DB clone 后运行起来 chat2db的java端&#xff0c;我现在搞不清这一个项目是有没有…

【搜狐简单AI-注册/登录安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被机器执行自动化程序攻击&#xff0c;存在如下风险&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露&#xff0c;不符合国家等级保护的要求。短信盗刷带来的拒绝服务风险 &#xff0c;造成用户无法登陆、注册&#xff0c;大量收到垃圾短信的…

微服务day09

DSL查询 快速入门 GET /items/_search {"query": {"match_all": {}} } 叶子查询 GET /items/_search {"query": {"match_all": {}} }GET /items/_search {"query": {"multi_match": {"query": "脱…

Linux驱动开发第2步_“物理内存”和“虚拟内存”的映射

“新字符设备的GPIO驱动”和“设备树下的GPIO驱动”都要用到寄存器地址&#xff0c;使用“物理内存”和“虚拟内存”映射时&#xff0c;非常不方便&#xff0c;而pinctrl和gpio子系统的GPIO驱动&#xff0c;非常简化。因此&#xff0c;要重点学习pinctrl和gpio子系统下的GPIO驱…

force stop和pm clear的区别

前言&#xff1a;因为工作中遇到force stop和pm clear进程后&#xff0c;进程不能再次挂起&#xff0c;谷歌系统共性问题&#xff0c;服务类应用经清缓存后当下服务就会挂掉&#xff0c;需要系统重启才能恢复。为了更好的“丢锅”&#xff0c;需要进一步学习force stop和pm cle…

【大数据学习 | flume】flume Sink Processors与拦截器Interceptor

1. Failover Sink Processor 故障转移处理器可以同时指定多个sink输出&#xff0c;按照优先级高低进行数据的分发&#xff0c;并具有故障转移能力。 需要修改第一台服务器agent a1.sourcesr1 a1.sinksk1 k2 a1.channelsc1 a1.sources.r1.typenetcat a1.sources.r1.bindworker…

如何从头开始构建神经网络?(附教程)

随着流行的深度学习框架的出现&#xff0c;如 TensorFlow、Keras、PyTorch 以及其他类似库&#xff0c;学习神经网络对于新手来说变得更加便捷。虽然这些框架可以让你在几分钟内解决最复杂的计算任务&#xff0c;但它们并不要求你理解背后所有需求的核心概念和直觉。如果你知道…

Conda安装与使用中的若干问题记录

Conda安装与使用中的若干问题记录 1.Anaconda 安装失败1.1.问题复述1.2.问题解决&#xff08;安装建议&#xff09; 2.虚拟环境pip install未安装至本虚拟环境2.1.问题复述2.2.问题解决 3.待补充 最近由于工作上的原因&#xff0c;要使用到Conda进行虚拟环境的管理&#xff0c;…

『OpenCV-Python』视频的读取和保存

点赞 + 关注 + 收藏 = 学会了 推荐关注 《OpenCV-Python专栏》 上一讲介绍了 OpenCV 的读取图片的方法,这一讲简单聊聊 OpenCV 读取和保存视频。 视频的来源主要有2种,一种是本地视频文件,另一种是实时视频流,比如手机和电脑的摄像头。 要读取这两种视频的方法都是一样的…

字节青训-字符串字符类型排序问题、小C点菜问题

目录 一、字符串字符类型排序问题 题目 样例 输入&#xff1a; 输出&#xff1a; 输入&#xff1a; 输出&#xff1a; 输入&#xff1a; 输出&#xff1a; 解题思路&#xff1a; 问题理解 数据结构选择 算法步骤 最终代码&#xff1a; 运行结果&#xff1a; ​…

深入理解接口测试:实用指南与最佳实践5.0(二)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

CSS基础知识05(弹性盒子、布局详解,动画,3D转换,calc)

目录 0、弹性盒子、布局 0.1.弹性盒子的基本概念 0.2.弹性盒子的主轴和交叉轴 0.3.弹性盒子的属性 flex-direction row row-reverse column column-reverse flex-wrap nowrap wrap wrap-reverse flex-dirction和flex-wrap的组合简写模式 justify-content flex-s…

任务调度工具Spring Test

Spring Task 是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 作用&#xff1a;定时自动执行某段Java代码 应用场景&#xff1a; 信用卡每月还款提醒 银行贷款每月还款提醒 火车票售票系统处理未支付订单 入职纪念日为用户发送通知 一.…

嵌入式硬件杂谈(二)-芯片输入接入0.1uf电容的本质(退耦电容)

引言&#xff1a;对于嵌入式硬件这个庞大的知识体系而言&#xff0c;太多离散的知识点很容易疏漏&#xff0c;因此对于这些容易忘记甚至不明白的知识点做成一个梳理&#xff0c;供大家参考以及学习&#xff0c;本文主要针对芯片输入接入0.1uf电容的本质的知识点的进行学习。 目…

数据结构(单向链表——c语言实现)

链式存储的优缺点&#xff1a; 优点&#xff1a; 1、动态分配内存&#xff1a; 链式存储不需要在数据插入之前分配固定大小的数组或内存块&#xff0c;因此它更适合存储动态变化的数据 2、高效的插入和删除操作&#xff1a; 在链表中插入或删除元素只需要调整相邻节点的指…