Qt音视频开发6-ffmpeg解码处理

2020-08-10 10:46:16 浏览数 (1)

一、前言

采用ffmpeg解码,是所有视频监控开发人员必备的技能,绕不过去的一个玩意,甚至可以说是所有音视频开发人员的必备技能。FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它包括了目前领先的音/视频编码库libavcodec。 FFmpeg是在 Linux 下开发出来的,但它可以在包括 Windows 在内的大多数操作系统中编译。这个项目是由 Fabrice Bellard 发起的,现在由 Michael Niedermayer 主持。可以轻易地实现多种视频格式之间的相互转换,例如可以将摄录下的视频avi等转成现在视频网站所采用的flv格式。

关于ffmpeg解码,网上搜索到的代码绝对是一大堆一大堆,而且很多都讲得很详细,解码的函数流程图非常清晰,关于ffmpeg这块的学习本人推荐雷神的博客,分析的相当细致,我在很久以前刚用Qt ffmpeg解码的时候,参考的就是雷神的例子,当然这些demo其实在ffmpeg的开发包dev下的examples也是非常详细的,只不过没有什么分析过程,参考雷神的博客可以看到很多分析过程。

本人总结的解码过程:

  1. 注册解码库相关(av_register_all、avformat_network_init等)
  2. 初始化各种参数比如缓存大小等(av_dict_set)
  3. 打开视频流或者文件(avformat_alloc_context、avformat_open_input)
  4. 获取流信息(avformat_find_stream_info)
  5. 获取视频流并初始化视频解码器(av_find_best_stream、avcodec_find_decoder)
  6. 获取音频流并初始化音频解码器(av_find_best_stream、avcodec_find_decoder、avcodec_open2)
  7. 预分配帧内存(av_frame_alloc)
  8. 循环读取音视频帧(av_read_frame、av_packet_unref)
  9. 解码视频(avcodec_decode_video2或者avcodec_send_packet、avcodec_receive_frame)
  10. 解码音频(avcodec_decode_audio4)
  11. 处理结束释放资源(sws_freeContext、av_frame_free、av_free)

二、功能特点

  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. 自定义顶部悬浮条,发送单击信号通知,可设置是否启用。
  18. 支持qsv、dxva2、d3d11va等硬解码。
  19. 支持opengl绘制视频数据,极低CPU占用。
  20. 支持嵌入式linux。

三、效果图

QQ截图20200806130519.jpgQQ截图20200806130519.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复制
bool FFmpegThread::init()
{
    //判断该摄像机是否能联通
    if (checkConn && isRtsp) {
        if (!checkUrl(url, checkTime)) {
            return false;
        }
    }

    //初始化参数
    this->initOption();
    //初始化输入
    if (!initInput()) {
        return false;
    }
    //初始化视频
    if (!initVideo()) {
        return false;
    }
    //初始化音频
    if (!initAudio()) {
        return false;
    }
    //初始化帧
    this->initFrame();

    //输出视频信息
    //av_dump_format(formatCtx, 0, url.toStdString().data(), 0);
    //qDebug() << TIMEMS << "init ffmpeg finsh";
    return true;
}

void FFmpegThread::initOption()
{
    //在打开码流前指定各种参数比如:探测时间/超时时间/最大延时等
    //设置缓存大小,1080p可将值调大
    av_dict_set(&options, "buffer_size", "8192000", 0);
    //以tcp方式打开,如果以udp方式打开将tcp替换为udp
    av_dict_set(&options, "rtsp_transport", transport.toLatin1().constData(), 0);
    //设置超时断开连接时间,单位微秒,3000000表示3秒
    av_dict_set(&options, "stimeout", "3000000", 0);
    //设置最大时延,单位微秒,1000000表示1秒
    av_dict_set(&options, "max_delay", "1000000", 0);
    //自动开启线程数
    av_dict_set(&options, "threads", "auto", 0);

    //设置USB摄像机分辨率
    if (url.startsWith("video")) {
        QString size = QString("%1x%2").arg(videoWidth).arg(videoHeight);
        av_dict_set(&options, "video_size", size.toLatin1().constData(), 0);
    }
}

bool FFmpegThread::initInput()
{
    //实例化格式处理上下文
    formatCtx = avformat_alloc_context();

    //先判断是否是本地设备(video=设备名字符串),打开的方式不一样
    int result = -1;
    if (url.startsWith("video")) {
#if defined(Q_OS_WIN)
        AVInputFormat *ifmt = av_find_input_format("dshow");
#elif defined(Q_OS_LINUX)
        AVInputFormat *ifmt = av_find_input_format("video4linux2");
#elif defined(Q_OS_MAC)
        AVInputFormat *ifmt = av_find_input_format("avfoundation");
#endif
        result = avformat_open_input(&formatCtx, url.toStdString().data(), ifmt, &options);
    } else {
        result = avformat_open_input(&formatCtx, url.toStdString().data(), NULL, &options);
    }

    if (result < 0) {
        qDebug() << TIMEMS << "open input error" << url;
        return false;
    }

    //释放设置参数
    if (options != NULL) {
        av_dict_free(&options);
    }

    //获取流信息
    result = avformat_find_stream_info(formatCtx, NULL);
    if (result < 0) {
        qDebug() << TIMEMS << "find stream info error";
        return false;
    }

    return true;
}

0 人点赞