Qt-FFmpeg开发-打开本地摄像头录制视频(7)
创始人
2024-03-19 22:59:21

Qt-FFmpeg开发-打开本地摄像头录制视频【软解码+ OpenGL显示YUV】

文章目录

  • Qt-FFmpeg开发-打开本地摄像头录制视频【软解码+ OpenGL显示YUV】
    • 1、概述
    • 2、实现效果
    • 3、FFmpeg录制视频编码流程
    • 4、主要代码
    • 5、完整源代码

更多精彩内容
👉个人内容分类汇总 👈
👉音视频开发 👈

1、概述

  • 最近研究了一下FFmpeg开发,功能实在是太强大了,网上ffmpeg3、4的文章还是很多的,但是学习嘛,最新的还是不能放过,就选了一个最新的ffmpeg n5.1.2版本,和3、4版本api变化还是挺大的;
  • 在这个Demo里主要使用Qt + FFmpeg开发一个摄像头【录像机】,这里主要使用的是【软解码】,需要使用硬解码的可以看之前的文章;
  • 在之前的文章中使用了QPainter进行绘制显示,也讲了使用OpenGL显示RGB、YUV图像方式;
  • 由于FFmpeg解码得到的像素格式为YUVJ422P,将YUVJ422P转换为RGB或者YUV420p都很麻烦,并且会消耗CPU资源,所以这里直接使用OpenGL显示YUVJ422P图像,(将YUVJ422P转RGB的步骤放到了GPU中进行)。
  • 关于打开摄像头部分请看上一章,整理不重复说明,这里主要讲述录像功能。

开发环境说明

  • 系统:Windows10、Ubuntu20.04
  • Qt版本:V5.12.5
  • 编译器:MSVC2017-64、GCC/G++64
  • FFmpeg版本:n5.1.2
    • 官方下载
    • 我使用的库

2、实现效果

  1. 使用ffmpeg音视频库【软解码】打开本地摄像头【录制视频】保存到本地;
  2. 采用【OpenGL显示YUV】图像,支持自适应窗口缩放,支持使用QOpenGLWidget、QOpenGLWindow显示;
  3. 将YUV转RGB的步骤由CPU转换改为使用GPU转换,降低CPU占用率;
  4. 支持Windows、Linux打开本地摄像头;
  5. 支持使用【静态帧率】、【动态帧率】录制视频;
  6. 视频解码、线程控制、显示各部分功能分离,低耦合度。
  7. 采用最新的5.1.2版本ffmpeg库进行开发,超详细注释信息,将所有踩过的坑、解决办法、注意事项都得很写清楚。

在这里插入图片描述

  • 使用CPU软解码 + OpenGL绘制 + CPU软编码录制

在这里插入图片描述

3、FFmpeg录制视频编码流程

  • 白色部分主要为创建、设置信息步骤,蓝色部分主要为写入数据步骤。

在这里插入图片描述

4、主要代码

  • 啥也不说了,直接上代码,一切有注释

  • videosave.h文件

    /******************************************************************************* @文件名     videosave.h* @功能       将视频编码后保存到文件中** @开发者     mhf* @邮箱       1603291350@qq.com* @时间       2022/11/29* @备注*****************************************************************************/
    #ifndef VIDEOSAVE_H
    #define VIDEOSAVE_H#include 
    #include struct AVCodecParameters;
    struct AVFormatContext;
    struct AVCodecContext;
    struct AVStream;
    struct AVFrame;
    struct AVPacket;
    struct AVOutputFormat;class VideoSave
    {
    public:VideoSave();~VideoSave();bool open(AVStream *inStream, const QString& fileName);void write(AVFrame* frame);void close();private:void showError(int err);private:AVFormatContext* m_formatContext = nullptr;AVCodecContext * m_codecContext  = nullptr;    // 编码器上下文AVStream       * m_videoStream   = nullptr;AVPacket       * m_packet        = nullptr;    // 数据包int m_index = 0;bool             m_writeHeader   = false;      // 是否写入头QMutex           m_mutex;
    };#endif // VIDEOSAVE_H
    
  • videosave.cpp文件

    #include "videosave.h"
    #include extern "C" {        // 用C规则编译指定的代码
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/avutil.h"
    #include "libswscale/swscale.h"
    #include "libavutil/imgutils.h"
    #include "libavdevice/avdevice.h"
    }#define ERROR_LEN 1024  // 异常信息数组长度
    #define PRINT_LOG 1
    #define USE_H264 0      // 使用H264编码器VideoSave::VideoSave()
    {
    }VideoSave::~VideoSave()
    {close();
    }/*** @brief        显示ffmpeg函数调用异常信息* @param err*/
    void VideoSave::showError(int err)
    {
    #if PRINT_LOGstatic char  m_error[ERROR_LEN];         // 保存异常信息memset(m_error, 0, ERROR_LEN);           // 将数组置零av_strerror(err, m_error, ERROR_LEN);qWarning() << "VideoSave Error:" << m_error;
    #elseQ_UNUSED(err)
    #endif
    }bool VideoSave::open(AVStream *inStream, const QString &fileName)
    {if(!inStream || fileName.isEmpty()) return false;// 通过输出文件名为输出格式分配AVFormatContext。
    #if USE_H264int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, "h264", fileName.toStdString().data());
    #else/*** 摄像头打开使用的是mjpeg编码器;* MJPEG压缩技术可以获取清晰度很高的视频图像,可以【动态调整帧率】适合保存摄像头视频、分辨率。但由于没有考虑到帧间变化,造成大量冗余信息被重复存储,因此单帧视频的占用空间较大;* 如果采用其它编码器,由于摄像头曝光时间长度不一定,所以录像时帧率一直在变,编码器指定固定帧率会导致视频一会快一会慢,效果很不好,适用于录制固定帧率的视频(当然其它编码器应该是有处理办法,不过我还不清楚);*/QString strName = avcodec_find_encoder(inStream->codecpar->codec_id)->name;    // 获取编码器名称int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, strName.toStdString().data(), fileName.toStdString().data());  // 这里使用和解码一样的编码器,防止保存的图像颜色出问题
    #endifif(ret < 0){close();showError(ret);return false;}// 创建并初始化AVIOContext以访问url所指示的资源。ret = avio_open(&m_formatContext->pb, fileName.toStdString().data(), AVIO_FLAG_WRITE);if(ret < 0){close();showError(ret);return false;}// 查询编码器const AVCodec* codec = avcodec_find_encoder(m_formatContext->oformat->video_codec);if(!codec){close();showError(AVERROR(ENOMEM));return false;}// 分配AVCodecContext并将其字段设置为默认值。m_codecContext = avcodec_alloc_context3(codec);if(!m_codecContext){close();showError(AVERROR(ENOMEM));return false;}// 设置编码器上下文参数m_codecContext->width = inStream->codecpar->width;                          // 图片宽度/高度m_codecContext->height = inStream->codecpar->height;
    #if USE_H264m_codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
    #elsem_codecContext->pix_fmt = AVPixelFormat(inStream->codecpar->format);        // 像素格式,也可以使用codec->pix_fmts[0]或AV_PIX_FMT_YUVJ422P(【注意】摄像头解码的图像格式为yuvj422p,如果这里不一样可能保存会出问题,或者后面进行格式转换)
    #endifm_codecContext->time_base = {1, 10};                   //设置时间基,20为分母,1为分子,表示以1/20秒时间间隔播放一帧图像m_codecContext->framerate = {10, 1};m_codecContext->bit_rate = 4000000;                    // 目标的码率,即采样的码率;显然,采样码率越大,视频大小越大,画质越高m_codecContext->gop_size = 10;                         // I帧间隔m_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    //    m_codecContext->max_b_frames = 1;                      // 非B帧之间的最大B帧数(有些格式不支持)
    //    m_codecContext->qmin = 1;
    //    m_codecContext->qmax = 5;
    //    m_codecContext->colorspace = AVCOL_SPC_BT470BG;
    //    m_codecContext->color_range = AVCOL_RANGE_JPEG;
    //    m_codecContext->color_primaries = AVCOL_PRI_BT709;
    //    m_codecContext->bits_per_coded_sample = 24;
    //    m_codecContext->bits_per_raw_sample = 8;
    //    av_opt_set(m_codecContext->priv_data, "preset", "placebo", 0);
    //    qDebug() << m_codecContext->pix_fmt;// 打开编码器ret = avcodec_open2(m_codecContext, nullptr, nullptr);
    #if USE_H264ret = avcodec_open2(m_codecContext, codec, nullptr);      // 使用h264时第一次打不开,第二次可以打卡,不知道什么原因
    #endifif(ret < 0){close();showError(ret);return false;}// 向媒体文件添加新流m_videoStream = avformat_new_stream(m_formatContext, nullptr);if(!m_videoStream){close();showError(AVERROR(ENOMEM));return false;}//拷贝一些参数,给codecpar赋值ret = avcodec_parameters_from_context(m_videoStream->codecpar,m_codecContext);if(ret < 0){close();showError(ret);return false;}// 写入文件头ret = avformat_write_header(m_formatContext, nullptr);if(ret < 0){close();showError(ret);return false;}m_writeHeader = true;// 分配一个AVPacketm_packet = av_packet_alloc();if(!m_packet){close();showError(AVERROR(ENOMEM));return false;}qDebug() << "开始录制视频!";return true;
    }/*** @brief        写入数据* @param frame*/
    void VideoSave::write(AVFrame *frame)
    {QMutexLocker locker(&m_mutex);if(!m_packet){return;}if(frame){frame->pts = m_index;    // 注意:每一帧视频显示时间从0递增,否则录制的视频显示/时长会不对m_index++;}// 将图像传入编码器avcodec_send_frame(m_codecContext, frame);// 循环读取所有编码完的帧while (true){// 从编码器中读取图像帧int ret = avcodec_receive_packet(m_codecContext, m_packet);if(ret < 0){break;}// 将数据包中的有效时间字段(时间戳/持续时间)从一个时基转换为 输出流的时间av_packet_rescale_ts(m_packet, m_codecContext->time_base, m_videoStream->time_base);av_write_frame(m_formatContext, m_packet);   // 将数据包写入输出媒体文件av_packet_unref(m_packet);}
    }/*** @brief 关闭保存数据*/
    void VideoSave::close()
    {write(nullptr);   // 传入空帧,读取所有编码数据QMutexLocker locker(&m_mutex);    // 如果不加锁可能在点击关闭时,write函数正在写入数据,导致崩溃if(m_formatContext){// 写入文件尾if(m_writeHeader){m_writeHeader = false;int ret = av_write_trailer(m_formatContext);if(ret < 0){showError(ret);return;}}int ret = avio_close(m_formatContext->pb);if(ret < 0){showError(ret);return;}avformat_free_context(m_formatContext);m_formatContext = nullptr;}// 释放编解码器上下文并置空if(m_codecContext){avcodec_free_context(&m_codecContext);}if(m_packet){av_packet_free(&m_packet);qDebug() << "停止录制视频!";}
    }
    

5、完整源代码

  • github
  • gitee

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...