Qt音视频开发9-ffmpeg录像存储

2020-08-12 14:26:15 浏览数 (1)

一、前言

上一篇文章写道直接将视频流保存裸流到文件,尽管裸流文件有一定的好处,但是 毕竟大部分用户需要的不是裸流而是MP4视频文件,所以需要将视频流保存成MP4文件,毕竟电脑上的播放器包括默认的播放器,可以直接播放MP4文件,而未必能播放裸流文件,裸流文件需要安装K-Lite解码器才行,关于ffmpeg解码保存成MP4文件,有两种处理方式,一种是先保存成裸流,然后开个后台线程,当裸流文件保存完成以后,自动触发H264转MP4的命令执行,也可以很快的完成转换,另外一种方法就是直接解码的时候保存成MP4文件,两种方法都可以,一般建议后者。

保存成MP4文件流程:

  1. 调用avformat_alloc_output_context2开辟一个格式上下文AVFormatContext用来处理视频流输出。
  2. 调用avformat_new_stream开辟一个视频流AVStream用来输出MP4文件。
  3. 重新设置输出视频流的各种参数。
  4. 调用avio_open打开输出文件。
  5. 调用avformat_write_header写入头部标识。
  6. 循环解码后调用av_write_frame写入数据到文件。
  7. 结束后调用av_write_trailer写入结束标识。
  8. 关闭解码输出,关闭文件,释放资源,

二、功能特点

  1. 多线程实时播放视频流 本地视频 USB摄像头等。
  2. 支持windows linux mac,支持ffmpeg3和ffmpeg4,支持32位和64位。
  3. 多线程显示图像,不卡主界面。
  4. 自动重连网络摄像头。
  5. 可设置边框大小即偏移量和边框颜色。
  6. 可设置是否绘制OSD标签即标签文本或图片和标签位置。
  7. 可设置两种OSD位置和风格。
  8. 可设置是否保存到文件以及文件名。
  9. 可直接拖曳文件到ffmpegwidget控件播放。
  10. 支持h265视频流 rtmp等常见视频流。
  11. 可暂停播放和继续播放。
  12. 支持存储单个视频文件和定时存储视频文件。
  13. 自定义顶部悬浮条,发送单击信号通知,可设置是否启用。
  14. 可设置画面拉伸填充或者等比例填充。
  15. 可设置解码是速度优先、质量优先、均衡处理。
  16. 可对视频进行截图(原始图片)和截屏。
  17. 录像文件存储支持裸流和MP4文件。
  18. 支持qsv、dxva2、d3d11va等硬解码。
  19. 支持opengl绘制视频数据,极低CPU占用。
  20. 支持嵌入式linux,交叉编译即可。

三、效果图

QQ截图20200806132326.jpgQQ截图20200806132326.jpg

四、相关站点

  1. 国内站点:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 国际站点:https://github.com/feiyangqingyun/QWidgetDemo
  3. 个人主页:https://blog.csdn.net/feiyangqingyun
  4. 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
  5. 体验地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

五、核心代码

代码语言:txt复制
void FFmpegThread::saveVideoMp4(const QString &fileName)
{
    QMutexLocker locker(&mutex);
    closeVideo();
    if (videoStreamIndex < 0 || !isRtsp) {
        return;
    }

    //转换文件字符串
    const char *filename = fileName.toStdString().data();
    //开辟一个格式上下文用来处理视频流输出
    avformat_alloc_output_context2(&formatOut, NULL, NULL, filename);
    //开辟一个视频流用来输出MP4文件
    AVStream *streamOut = avformat_new_stream(formatOut, NULL);
    AVStream *streamIn = formatCtx->streams[videoStreamIndex];

    //重新设置输出视频流的各种参数
    AVCodecContext *codec = streamOut->codec;
    codec->bit_rate = 400000;
    codec->codec_id = streamIn->codec->codec_id;
    codec->codec_type = streamIn->codec->codec_type;
    codec->time_base.num = streamIn->time_base.num;
    codec->time_base.den = streamIn->time_base.den;
    codec->width = streamIn->codec->width;
    codec->height = streamIn->codec->height;
    codec->pix_fmt = streamIn->codec->pix_fmt;
    codec->flags = streamIn->codec->flags;
    codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    codec->me_range = streamIn->codec->me_range;
    codec->max_qdiff = streamIn->codec->max_qdiff;
    codec->qmin = streamIn->codec->qmin;
    codec->qmax = streamIn->codec->qmax;
    codec->qcompress = streamIn->codec->qcompress;

    //打开输出文件并写入头部标识
    if (avio_open(&formatOut->pb, filename, AVIO_FLAG_WRITE) >= 0) {
        if (avformat_write_header(formatOut, NULL) >= 0) {
            initSaveOk = true;
        }
    }
}

void FFmpegThread::closeVideo()
{
    if (!saveFile) {
        return;
    }

    if (saveMp4) {
        if (formatOut != NULL) {
            //写入结束标识
            av_write_trailer(formatOut);
            avcodec_close(formatOut->streams[0]->codec);
            av_freep(&formatOut->streams[0]->codec);
            av_freep(&formatOut->streams[0]);
            avio_close(formatOut->pb);
            av_free(formatOut);
            initSaveOk = false;
            formatOut = NULL;
        }
    } else {
        if (fileVideo.isOpen()) {
            fileVideo.close();
        }

        if (fileAudio.isOpen()) {
            fileAudio.close();
        }
    }
}

//解码后的数据直接写入文件即可
av_write_frame(formatOut, videoPacket);

0 人点赞