树莓派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.rhkb.cn/news/473157.html

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

相关文章

Cartographer激光雷达slam -20241116

Cartographer Cartographer代码结构 cartographer&#xff1a;负责处理来自雷达、IMU和里程计的数据并基于这些数据进行地图的构建&#xff0c;是cartographer理论的底层实现cartographer_ros&#xff1a;基于ros的通信机制获取传感器的数据并将它们转换成cartographer中定义…

Scratch 014生日贺卡(上)

知识回顾&#xff1a; 1、“面向鼠标指针”积木块 2、“重复执行直到”积木块 本次分享制作生日贺卡引入广播模块 案列效果&#xff1a; 生日贺卡上案例效果-CSDN直播 步骤拆解&#xff1a; 1、添加背景和角色 2、编辑贺卡造型添加名字 3、流程图的组成和画法 4、…

外网访问 WebDav 服务

从外部网络环境&#xff08;比如异地和家中网络&#xff09;来访问公司内网的 WebDav 服务&#xff08;基于 IIS &#xff09;并映射成本地虚拟磁盘。 步骤如下 第一步 在公司内网的电脑上设置 webDav。 1&#xff0c;找到【控制面板】&#xff0c;双击进入。 2&#xff0c…

渑池县中药材产业党委莅临河南广宇企业管理集团有限公司参观交流

11月14日&#xff0c;渑池县人大副主任、工商联主席杨航率县中药材产业党委代表团一行13人&#xff0c;莅临河南广宇集团参观交流。河南广宇集团总经理王峰、副总经理王培等领导热情接待并陪同参观、座谈。 代表团一行首先参观了集团旗下郑州美信中医院&#xff08;庚贤堂中医药…

WP网站如何增加文章/页面的自定义模板

通过Wordpress我们后台在发布文章或者页面的时候其实可以看到有些主题 他有选择使用的页面模板&#xff0c;可以自定义模板&#xff0c;但是有些主题却没有选择主题这个功能&#xff0c;那这个自定义模板的功能是如何实现的呢&#xff1f;以下分两种情况&#xff1a;Page页面和…

FFmpeg 4.3 音视频-多路H265监控录放C++开发十四,总结编码过程,从摄像头获得数据后,转成AVFrame,然后再次转成AVPacket,

也就是将摄像头采集到的YUV 的数据换成 AVFrame&#xff0c;然后再次转成 AVPacket&#xff0c;那么这AVPakcet数据要怎么办呢&#xff1f;分为三种情况&#xff1a; 一种是将AVPacket存储成h264文件&#xff0c;由于h264编码器在将avframe变成avpacket的时候就是按照h264的格…

SQL Server 查询设置 - LIKE/DISTINCT/HAVING/排序

目录 背景 一、LIKE - 模糊查询 1. 通配符 % 2. 占位符 _ 3. 指定集合 [] 3.1 表示否定 ^ 3.2 表示范围 - 4. 否定 NOT 二、DISTINCT - 去重查询 三、HAVING - 过滤查询 四、小的查询设置 1. ASC|DESC - 排序 2. TOP - 限制 3. 子查询 4. not in - 取补集&…

动态规划-完全背包问题——322.零钱兑换

1.题目解析 题目来源 322.零钱兑换——力扣 测试用例 2.算法原理 1.状态表示 这里需要寻找硬币使总面值等于一个值求出所需硬币的最小个数&#xff0c;所以不妨设置一个二维dp表&#xff0c;即dp[i][j]&#xff1a;在[1,i]个硬币中选择的硬币总面值完全等于j时所需要的最小硬…

从零到一:利用 AI 开发 iOS App 《震感》的编程之旅

在网上看到一篇关于使用AI开发的编程经历&#xff0c;分享给大家 作者是如何在没有 iOS 开发经验的情况下&#xff0c;借助 AI&#xff08;如 Claude 3 模型&#xff09;成功开发并发布《震感》iOS 应用。 正文开始 2022 年 11 月&#xff0c;ChatGPT 诞生并迅速引发全球关注。…

【Linux庖丁解牛】—Linux基本指令(下)!

目录 1、grep指令 2、zip/unzip指令 3、sz/rz指令 4、tar指令 ​编辑 5、scp指令 6、bc指令 7、uname –r指令 8、重要的几个热键 9、关机 10、完结撒花 1、grep指令 grep是文本过滤器&#xff0c;其作用是在指定的文件中过滤出包含你指定字符串的内容&#xff0c;…

小程序19-微信小程序的样式和组件介绍

在小程序中不能使用 HTML 标签&#xff0c;也就没有 DOM 和 BOM&#xff0c;CSS 也仅支持部分选择器 小程序提供了 WXML 进行页面结构的编写&#xff0c;WXSS 进行页面的样式编写 WXML 提供了 view、text、image、navigator等标签构建页面结构&#xff0c;小程序中标签称为组件…

VMD + CEEMDAN 二次分解,CNN-LSTM预测模型

往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较 全是干货 | 数据集、学习资料、建模资源分享&#xff01; EMD变体分解效果最好算法——CEEMDAN&#xff08;五&#xff09;-CSDN博客 拒绝信息泄露&#xff01;VMD滚动分…

《生成式 AI》课程 第3講 CODE TASK 任务3:自定义任务的机器人

课程 《生成式 AI》课程 第3講&#xff1a;訓練不了人工智慧嗎&#xff1f;你可以訓練你自己-CSDN博客 我们希望你创建一个定制的服务机器人。 您可以想出任何您希望机器人执行的任务&#xff0c;例如&#xff0c;一个可以解决简单的数学问题的机器人0 一个机器人&#xff0c…

SOLIDWORKS Toolbox:一键自动化,让紧固件与零部件管理更高效

紧固件广泛应用于从手机到火箭的各种产品中。在SOLIDWORKS设计时&#xff0c;通过使用实际的CAD模型来包含和跟踪紧固件是最简便和全面的方法&#xff0c;这有助于理解设计的整体&#xff0c;并自动管理零件数据和设计文档&#xff0c;如工程图和物料清单(BOM)。 在SOLIDWORKS…

串口DMA接收不定长数据

STM32F767—&#xff1e;串口通信接收不定长数据的处理方法_stm32串口超时中断-CSDN博客 STM32-HAL库串口DMA空闲中断的正确使用方式解析SBUS信号_stm32 hal usart2 dma-CSDN博客 #define USART1_RxBuffSize 100 extern DMA_HandleTypeDef hdma_usart1_rx; //此处声明的变量在…

git简介和本地仓库创建,并提交修改。git config init status add commit

一、Git简介和本地仓库组成 1.1 git简介 视频教程在这 git简介&#xff0c;版本控制系统&#xff0c;工作区&#xff0c;暂存区&#xff0c;本地仓库_哔哩哔哩_bilibili 如下图&#xff0c;比如我们写毕业论文&#xff0c;要经常修改和完善&#xff0c;得靠自己保存&#x…

鸿蒙学习生态应用开发能力全景图-赋能套件(1)

文章目录 赋能套件鸿蒙生态应用开发能力全景图 赋能套件 鸿蒙生态白皮书: 全面阐释了鸿蒙生态下应用开发核心理念、关键能力以及创新体验,旨在帮助开发者快速、准确、全面的了解鸿蒙开发套件给开发者提供的能力全景和未来的愿景。 视频课程: 基于真实的开发场景,提供向导式…

vue+svg圆形进度条组件

vuesvg圆形进度条组件 一、实现思路二、ProgressCircle.vue三、父组件使用四、实现效果 一、实现思路 使用svg的circle元素画两个圆形&#xff0c;一个圆形控制进度&#xff0c;一个绘制底色 二、ProgressCircle.vue 代码示例&#xff1a; <template><!-- 圆形进度…

软件测试 —— 自动化基础

目录 前言 一、Web 自动化测试 1.什么是 Web 自动化测试 2.驱动 3.安装驱动管理 二、Selenium 1.简单 web 自动化测试示例 2.工作原理 三、元素定位 1.cssSelector 2.XPath 四、操作测试对象 1.点击/提交对象 2.模拟按键输入 3.清除文本内容 4.获取文本信息 5.…

基于SpringBoot的旅游网站(程序+数据库+报告)

基于SpringBoot的旅游网站&#xff0c;系统包含两种角色&#xff1a;管理员、用户,系统分为前台和后台两大模块&#xff0c;主要功能如下。 【前台】&#xff1a; - 首页&#xff1a;展示旅游网站的核心内容&#xff0c;包括推荐的旅游线路、最新的旅游资讯等。 - 旅游线路&am…