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

news2024/11/18 13:13:56

文章目录

  • 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 update
    sudo apt-get upgrade
    
    // 安装git
    sudo 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 libtool
    git clone --depth 1 https://github.com/mstorsjo/fdk-aac
    cd fdk-aac
    autoreconf -fiv
    ./configure --prefix=/usr --disable-shared
    make -j4
    make install
    
    /* 编译x264(若git不下来,可联系笔者获取已下好的x264源码包) */
    git clone https://git.videolan.org/git/x264.git
    cd x264
    ./configure --enable-shared --enable-static --enable-strip --disable-cli
    make -j4
    sudo make install

    // 下载ffmpeg源码
    wget https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2
    tar -jxvf ffmpeg-xxx.tar.bz2
    cd 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 -j4
    sudo make install
    
    // 生成的结果均在当前目录下build文件件内
    ls ./build

2.3 图像格式

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

    // 初始化 SwsContext,将 MJPEG 格式转换为 YUV420P
    sws_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_bundle

HEADERS += \
    avframequeue.h \
    avpacketqueue.h \
    decodethread.h \
    demuxthread.h \
    encodethread.h \
    queue.h \
    thread.h

SOURCES += \
        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-shared

    INCLUDEPATH += $$FFMPEG_PATH/include
    LIBS += -L$$FFMPEG_PATH/lib \
             -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale
}

# Linux 平台设置
unix {
    INCLUDEPATH += /home/pi/Desktop/FFmpeg-master/build/include
    LIBS += /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 格式转换为 YUV420P
    sws_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格式的AVFrame
        yuv_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.coloradmin.cn/o/2242828.html

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

相关文章

排序算法(基础)大全

一、排序算法的作用&#xff1a; 排序算法的主要作用是将一组数据按照特定的顺序进行排列&#xff0c;使得数据更加有序和有组织。 1. 查找效率&#xff1a;通过将数据进行排序&#xff0c;可以提高查找算法的效率。在有序的数据中&#xff0c;可以使用更加高效的查找算法&…

计算机网络:运输层 —— TCP 的拥塞控制

文章目录 TCP的拥塞控制拥塞控制的基本方法流量控制与拥塞控制的区别拥塞控制分类闭环拥塞控制算法 TCP的四种拥塞控制方法&#xff08;算法&#xff09;窗口慢开始门限慢开始算法拥塞避免算法快重传算法快恢复算法 TCP拥塞控制的流程TCP拥塞控制与网际层拥塞控制的关系 TCP的拥…

如何在Mysql中生成0-23完整的小时数据

目录 1. 创建表2. 插入0-23小时的数据3. 查询并合并数据 在数据分析中&#xff0c;我们经常需要对特定时间段内的数据进行统计和分析。 例如&#xff0c;在名片进线的场景中&#xff0c;我们可能需要了解一天内每小时的名片进线数量。 然而&#xff0c;由于某些时间点可能没有数…

【GeekBand】C++设计模式笔记12_Singleton_单件模式

1. “对象性能” 模式 面向对象很好地解决了 “抽象” 的问题&#xff0c; 但是必不可免地要付出一定的代价。对于通常情况来讲&#xff0c;面向对象的成本大都可以忽略不计。但是某些情况&#xff0c;面向对象所带来的成本必须谨慎处理。典型模式 SingletonFlyweight 2. Si…

架构篇(理解架构的模式2)

目录 一、管理和监控 大使模式&#xff1a;创建代表消费者服务或应用程序发送网络请求的帮助服务 反腐模式&#xff1a;在现代应用程序和遗留系统之间实现装饰或适配器层 外部配置存储&#xff1a;将应用程序部署包中的配置信息移动到中心化的位置 网关聚合模式&#xff1…

20241116解决在WIN11和ubuntu20.04通过samba共享时出现局域网千兆带宽拉满的情况

20241116解决在WIN11和ubuntu20.04通过samba共享时出现局域网千兆带宽拉满的情况 2024/11/16 13:42 缘起&#xff1a;最近需要通过iperf3打流&#xff0c;因此在ubuntu20.04服务器上常开sudo nethogs监控流量。 但是发现一个异常&#xff0c;ubuntu20.04服务器上发送的流量过大…

DevOps工程技术价值流:打造卓越项目协作的优化宝典

一、引言 解锁项目协作的无限潜力&#xff0c;覆盖全链路实现流畅高效。 在当今瞬息万变的商业环境中&#xff0c;项目协作的效率和效果直接关系到企业的竞争力和市场响应速度。DevOps工程技术价值流中的项目协作优化&#xff0c;不仅是技术层面的革新&#xff0c;更是团队协…

WSL--无需安装虚拟机和docker可以直接在Windows操作系统上使用Linux操作系统

安装WSL命令 管理员打开PowerShell或Windows命令提示符&#xff0c;输入wsl --install&#xff0c;然后回车 注意&#xff1a;此命令将启用运行 WSL 和安装 Linux 的 Ubuntu 发行版所需的功能。 注意&#xff1a;默认安装最新的Ubuntu发行版。 注意&#xff1a;默认安装路径是…

⾃动化运维利器Ansible-基础

Ansible基础 一、工作原理二、快速入门2.1 测试所有资产的网络连通性2.2 发布文件到被管理节点(资产) 三、资产(被管理节点)3.1 静态资产3.1.1 自定义资产3.1.2 自定义资产的使用3.1.3 资产选择器 四、Ansible Ad-Hoc 命令4.1 模块类型4.1.1 command & shell 模块4.1.2 cop…

简易实现自动签到并发送通知邮件

环境准备 Windows操作系统adbshell1.0.40pyhon3.7visual stdio code stableandroid手机数据线&#xff0c;并配置环境变量 打卡程序 需要定位屏幕坐标 import os import timea0os.popen("adb shell input keyevent 26") ##ba0.read() ##print(b) time.sleep(5) …

机器学习(贝叶斯算法,决策树)

朴素贝叶斯分类 贝叶斯分类理论 假设现有两个数据集&#xff0c;分为两类 我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率&#xff0c;用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率&#xff0c;那么对于一个新数据点(x,y)…

[ACTF2020]Upload 1--详细解析

信息收集 题目告诉我们是一道upload&#xff0c;也就是文件上传漏洞题目。 进入界面&#xff0c;是一个灯泡&#xff0c;将鼠标放在图标上就会出现文件上传的相应位置&#xff1a; 思路 文件上传漏洞&#xff0c;先看看有没有前端校验。 在js源码中找到了前端校验&#xff…

网络常用特殊地址-127.0.0.1

借用Medium博客的一张图 经常在问题解答群里留意到如下关于127.0.0.1的消息 ”如果单机版&#xff0c;不需要配置IP&#xff0c;所有配置IP的地方都写死127.0.0.1就可以” “ip: 根据实际情况填写&#xff08;在 xxx-init.conf 里可以给一个默认值 127.0.0.1 &#xff0c;方便…

Scala-字符串(拼接、printf格式化输出等)-用法详解

Scala 一、 使用 号连接字符串 在 Scala 中&#xff0c; 运算符实际上会调用 String 类的 concat 方法或者使用字符串的加法操作&#xff0c;生成一个新的字符串。 字符串是不可变的&#xff0c;每次拼接都会创建一个新的字符串。 Mr. yuTips&#xff1a; 性能相对较差&…

软考教材重点内容 信息安全工程师 第 4 章 网络安全体系与网络安全模型

4,1 网络安全体系的主要特征: (1)整体性。网络安全体系从全局、长远的角度实现安全保障&#xff0c;网络安全单元按照一定的规则&#xff0c;相互依赖、相互约束、相互作用而形成人机物一体化的网络安全保护方式。 (2)协同性。网络安全体系依赖于多种安全机制&#xff0c;通过各…

【数据库】如何保证数据库迁移过程中数据的一致性?

在数据库迁移过程中&#xff0c;保证数据的一致性是非常重要的&#xff0c;尤其是在涉及到多个表、多个数据库或分布式系统的情况下。以下是一些确保数据一致性的最佳实践和方法&#xff1a; 1. 备份数据 在开始迁移之前&#xff0c;进行全面的数据备份是确保数据一致性的第…

github 模型下载方法

github 模型权重&#xff0c;如果是项目下载&#xff0c;pth文件有时下载后只有1kb 本人测试ok下载方法&#xff1a; 点击view raw&#xff0c;然后可以下载模型权重文件了。

spring-data-elasticsearch 3.2.4 实现桶bucket排序去重,实现指定字段的聚合搜索

一、背景 es索引有一个文档CourseIndex&#xff0c;下面是示意: creatorIdgradesubjectnameno1002270英语听力课程一N00232DS91004380数学口算课程N00209DK71003480物理竞赛课程N00642XS21002280英语听力课程二N00432WS31002290英语听力课程三N002312DP5 在搜索的时候&#…

QQ 小程序已发布,但无法被搜索的解决方案

前言 我的 QQ 小程序在 2024 年 8 月就已经审核通过&#xff0c;上架后却一直无法被搜索到。打开后&#xff0c;再在 QQ 上下拉查看 “最近使用”&#xff0c;发现他出现一下又马上消失。 上线是按正常流程走的&#xff0c;开发、备案、审核&#xff0c;没有任何违规&#xf…

快速搭建Android开发环境:Docker部署docker-android并实现远程连接

目录 前言 1. 虚拟化环境检查 2. Android 模拟器部署 3. Ubuntu安装Cpolar 4. 配置公网地址 5. 远程访问 小结 6. 固定Cpolar公网地址 7. 固定地址访问 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊快速搭建Android开发环境&#x…