一、前言

从代码层面以及自由度来说,用ffmpeg来写全能播放组件是最佳方案(跨平台最好最多、编解码能力最强),尽管已经有优秀的vlc/mpv等方案可以直接用,但是vlc/mpv对标主要是播放器应用层面,其他层面比如视频监控行业领域就比较鸡肋,所以还是从底层一点一滴做解码编码会让自己更熟练。关于网上很多ffmpeg的示例,尤其是播放的示例,数不胜数,比较适合用来入门学习,问题是随着ffmpeg官方不断的迭代更新,很多代码都不可用,因为api变了,尤其是最近5年迭代的特别快,从2017年开始直接猛飚版本,现在直接干到了ffmpeg6版本,一般在安排取消或者改动某些api接口前几个版本,都会打上对应的标记,既有新方法,也兼容旧的api,一般会放在下一个大版本将旧的api接口移除,以便减轻历史包袱,在核心功能编解码这块,一直是兼容的,不会说新版本不兼容以前旧版本的一些编解码格式。

编写这个全能播放组件,面对用户各种各样的需求,当然需要从ffmpeg2兼容到ffmpeg6以及后续的版本,现在用的最多的还是ffmpeg4版本,目测三五年后会陆续切换到ffmpeg5/ffmpeg6,主要是支持的格式多了,尤其是某些新标准的编解码的效率更高。在ffmpeg提供的头文件接口中,并没有提供ffmpeg的大版本号,只提供了字符串版本,所以需要通过子库的主版本号来定义一个ffmpeg的版本号,比如编解码库LIBAVCODEC_VERSION_MAJOR,56=ffmpeg2/57=ffmpeg3/58=ffmpeg4/59=ffmpeg5/60=ffmpeg6,这个编解码库就是ffmpeg的核心,看家的本领都在里面,个人觉得ffmpeg最牛逼的就是编解码和滤镜。在兼容各个版本的这条路上,大致整理了以下几条:

  1. 以前AVStream带了解码器参数,stream->codec,现在去掉了,对应放在stream->codecpar中。
  2. 拷贝上下文参数以前是avcodec_copy_context,现在对应avcodec_parameters_copy。
  3. 参数拷贝以前是avcodec_copy_context,现在对应avcodec_parameters_from_context/avcodec_parameters_to_context。
  4. 编码以前是avcodec_encode_video2/avcodec_encode_audio2,现在对应avcodec_send_frame后avcodec_receive_packet(视音频步骤一样)。
  5. 解码以前是avcodec_decode_video2/avcodec_decode_audio4,现在对应avcodec_send_packet后avcodec_receive_frame(视音频步骤一样)。
  6. 打印解码编码器名称以前是av_codec_next,现在对应av_codec_iterate。
  7. 获取和设置旋转角度以前是av_dict_get(stream->metadata, "rotate", NULL, 0)/av_dict_set(&stream->metadata, "rotate", "90", 0),现在对应av_stream_get_side_data后av_display_rotation_get/av_stream_new_side_data后av_display_rotation_set。

二、效果图

三、体验地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_demo。

四、相关代码

//通过avcode版本定义对应主版本
#if (LIBAVCODEC_VERSION_MAJOR == 56)
#define FFMPEG_VERSION_MAJOR 2
#elif (LIBAVCODEC_VERSION_MAJOR == 57)
#define FFMPEG_VERSION_MAJOR 3
#elif (LIBAVCODEC_VERSION_MAJOR == 58)
#define FFMPEG_VERSION_MAJOR 4
#elif (LIBAVCODEC_VERSION_MAJOR == 59)
#define FFMPEG_VERSION_MAJOR 5
#elif (LIBAVCODEC_VERSION_MAJOR == 60)
#define FFMPEG_VERSION_MAJOR 6
#endif int FFmpegHelper::getRotate(AVStream *stream)
{
int rotate = 0;
//测试发现ffmpeg2不支持旋转滤镜
#if (FFMPEG_VERSION_MAJOR < 3)
return rotate;
#endif #if (FFMPEG_VERSION_MAJOR < 5)
AVDictionaryEntry *tag = NULL;
tag = av_dict_get(stream->metadata, "rotate", NULL, 0);
if (tag) {
rotate = atoi(tag->value);
}
#else
//从ffplay源码中找到的方法
double theta = 0;
quint8 *displaymatrix = av_stream_get_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, NULL);
if (displaymatrix) {
theta = -av_display_rotation_get((qint32 *) displaymatrix);
theta -= 360 * floor(theta / 360 + 0.9 / 360);
rotate = theta;
}
#endif
return rotate;
} void FFmpegHelper::setRotate(AVStream *stream, int rotate)
{
#if (FFMPEG_VERSION_MAJOR < 5)
av_dict_set(&stream->metadata, "rotate", QString::number(rotate).toUtf8().constData(), 0);
#else
quint8 *sidedata = av_stream_new_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX, sizeof(qint32) * 9);
if (sidedata) {
av_display_rotation_set((qint32 *)sidedata, rotate);
}
#endif
} void FFmpegHelper::getStreamInfo(AVStream *stream, int &id, int &width, int &height, qint64 &bitrate, int &sampleRate, int &channelCount, int &profile)
{
#if (FFMPEG_VERSION_MAJOR < 3)
id = stream->codec->codec_id;
width = stream->codec->width;
height = stream->codec->height;
bitrate = stream->codec->bit_rate;
sampleRate = stream->codec->sample_rate;
channelCount = stream->codec->channels;
profile = stream->codec->profile;
#else
id = stream->codecpar->codec_id;
width = stream->codecpar->width;
height = stream->codecpar->height;
bitrate = stream->codecpar->bit_rate;
sampleRate = stream->codecpar->sample_rate;
channelCount = stream->codecpar->channels;
profile = stream->codecpar->profile;
#endif
} int FFmpegHelper::copyContext(AVStream *streamIn, AVStream *streamOut)
{
int result = -1;
//设置 codec_tag = 0 这个很关键(不加保存的数据可能不正确)
#if (FFMPEG_VERSION_MAJOR < 3)
result = avcodec_copy_context(streamOut->codec, streamIn->codec);
streamOut->codec->codec_tag = 0;
#else
result = avcodec_parameters_copy(streamOut->codecpar, streamIn->codecpar);
streamOut->codecpar->codec_tag = 0;
#endif
return result;
} int FFmpegHelper::copyContext(AVCodecContext *avctx, AVStream *stream, bool from)
{
int result = -1;
#if (FFMPEG_VERSION_MAJOR < 3)
if (from) {
result = avcodec_copy_context(stream->codec, avctx);
} else {
result = avcodec_copy_context(avctx, stream->codec);
}
#else
if (from) {
result = avcodec_parameters_from_context(stream->codecpar, avctx);
} else {
result = avcodec_parameters_to_context(avctx, stream->codecpar);
}
#endif
return result;
} int FFmpegHelper::decode(FFmpegThread *thread, AVCodecContext *avctx, AVPacket *packet, AVFrame *frame, bool video)
{
int result = -1;
#ifdef videoffmpeg
QString flag = video ? "视频解码" : "音频解码";
#if (FFMPEG_VERSION_MAJOR < 3)
if (video) {
avcodec_decode_video2(avctx, frame, &result, packet);
if (result < 0) {
thread->debug(result, flag, "avcodec_decode_video2");
return result;
}
} else {
avcodec_decode_audio4(avctx, frame, &result, packet);
if (result < 0) {
thread->debug(result, flag, "avcodec_decode_audio4");
return result;
}
}
goto end;
#else
result = avcodec_send_packet(avctx, packet);
if (result < 0 && (result != AVERROR(EAGAIN)) && (result != AVERROR_EOF)) {
//if (result < 0) {
thread->debug(result, flag, "avcodec_send_packet");
return result;
} while (result >= 0) {
result = avcodec_receive_frame(avctx, frame);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
break;
} else if (result < 0) {
thread->debug(result, flag, "avcodec_receive_frame");
break;
}
goto end;
}
#endif
return result; end:
//调用线程处理解码后的数据
if (video) {
thread->decodeVideo2(packet);
} else {
thread->decodeAudio2(packet);
}
#endif
return result;
} int FFmpegHelper::encode(FFmpegSave *thread, AVCodecContext *avctx, AVPacket *packet, AVFrame *frame, bool video)
{
int result = -1;
#ifdef videosave
QString flag = video ? "视频编码" : "音频编码";
#if (FFMPEG_VERSION_MAJOR < 3)
if (video) {
avcodec_encode_video2(avctx, packet, frame, &result);
if (result < 0) {
thread->debug(result, flag, "avcodec_encode_video2");
return result;
}
} else {
avcodec_encode_audio2(avctx, packet, frame, &result);
if (result < 0) {
thread->debug(result, flag, "avcodec_encode_audio2");
return result;
}
}
goto end;
#else
result = avcodec_send_frame(avctx, frame);
if (result < 0) {
thread->debug(result, flag, "avcodec_send_frame");
return result;
} while (result >= 0) {
result = avcodec_receive_packet(avctx, packet);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
break;
} else if (result < 0) {
thread->debug(result, flag, "avcodec_receive_packet");
break;
}
goto end;
}
#endif
return result; end:
thread->writePacket2(packet, video);
#endif
return result;
}

五、功能特点

  1. 支持各种音视频文件、本地摄像头设备,各种视频流网络流。
  2. 支持开始播放、暂停播放、继续播放、停止播放、设置播放进度、倍速播放。
  3. 可设置音量、静音切换、抓拍图片、录像存储。
  4. 自动提取专辑信息比如标题、艺术家、专辑、专辑封面,自动显示专辑封面。
  5. 完美支持音视频同步和倍速播放。
  6. 解码策略支持速度优先、质量优先、均衡处理、最快速度。
  7. 支持手机视频旋转角度显示,比如一般手机拍摄的视频是旋转了90度的,解码显示的时候需要重新旋转90度才是正的。
  8. 自动转换yuv420格式,比如本地摄像头是yuyv422格式,有些视频文件是xx格式,统一将非yuv420格式转换,然后再进行处理。
  9. 支持硬解码dxva2、d3d11va等,性能极高尤其是大分辨率比如4K视频。
  10. 视频响应极低延迟0.2s左右,极速响应打开视频流0.5s左右,专门做了优化处理。
  11. 硬解码和GPU绘制组合,极低CPU占用,比海康大华等客户端更优。
  12. 支持视频流中的各种音频格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支持,推荐选择AAC兼容性跨平台性最好。
  13. 视频存储支持yuv、h264、mp4多种格式,音频存储支持pcm、wav、aac多种格式。默认视频mp4格式、音频aac格式。
  14. 支持分开存储音频视频文件,也支持合并到一个mp4文件,默认策略是无论何种音视频文件格式存储,最终都转成mp4及aac格式,然后合并成音视频一起的mp4文件。
  15. 支持本地摄像头实时视频显示带音频输入输出,音视频录制合并到一个mp4文件。
  16. 支持H264/H265编码(现在越来越多的监控摄像头是H265视频流格式)生成视频文件,内部自动识别切换编码格式。
  17. 自动识别视频流动态分辨率改动,重新打开视频流。
  18. 支持用户信息中包含特殊字符(比如用户信息中包含+#@等字符)的视频流播放,内置解析转义处理。
  19. 纯qt+ffmpeg解码,非sdl等第三方绘制播放依赖,gpu绘制采用qopenglwidget,音频播放采用qaudiooutput。
  20. 同时支持ffmpeg2、ffmpeg3、ffmpeg4、ffmpeg5、ffmpeg6以及后续版本,全部做了兼容处理。如果需要支持xp需要选用ffmpeg3或ffmpeg2。
  21. 支持滤镜,源头带各种水印及图形效果,可以将OSD标签信息和各种图形信息写入到MP4文件。

Qt/C++编写全能播放组件(支持ffmpeg2/3/4/5/6/Qt4/5/6)的更多相关文章

  1. Qt高仿Excel表格组件-支持冻结列、冻结行、内容自适应和合并单元格

    目录 一.概述 二.效果展示 三.实现思路 1.冻结行.冻结列 2.行高自适应 3.蚂蚁线 四.测试代码 1.添加表格数据 2.设置冻结行.列 3.行高.列宽 4.单元格背景色 5.单元格文字 6.其 ...

  2. Qt实现表格树控件-支持多级表头

    目录 一.概述 二.效果展示 三.实现方式 四.多级表头 1.数据源 2.表格 3.QStyledItemDelegate绘制代理 五.测试代码 六.相关文章 原文链接:Qt实现表格树控件-支持多级表 ...

  3. C#编写媒体播放器--Microsoft的Directx提供的DirectShow组件,该组件的程序集QuartzTypeLib.dll.

    使用C#编写媒体播放器时,需要用到Microsoft的Directx提供的DirectShow组件.用该组件前需要先注册程序集QuartzTypeLib.dll. 1.用QuartzTypeLib.d ...

  4. Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例

    Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例以及错误总结 ok,前面铺垫了那么多,现在来写一个开发实例,我会把其中隐藏的坑和陷阱简单谈谈,并在文章最后总结. 不愿意看长篇大论的 ...

  5. QT | 聊聊QT与直播流播放——从QMediaPlayer到Qt-AV

    [原创文章,转载请注明来源,方便查看本文更新] 这段时间需要用QT开发一个播放直播流的功能,能够播放各种格式的直播流,并且CPU占用率不要太高(可以占用GPU),这些是我们的目标. 直播流推流的技术进 ...

  6. DirectShow多媒体流捕获播放组件

    英文原文地址:https://msdn.microsoft.com/en-us/library/windows/desktop/dd390351(v=vs.85).aspx Microsoft®Dir ...

  7. artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口

    artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口 自适应内容 artDialog的特殊UI框架能够适应内容变化,甚至连外部程序动态插入的内容它仍然能自适应 ...

  8. JavaScript 是如何工作:Shadow DOM 的内部结构 + 如何编写独立的组件!

    这是专门探索 JavaScript 及其所构建的组件的系列文章的第 17 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...

  9. vue中引入awesomeswiper的方法以及编写轮播组件

    1.先安装less-loader npm install less less-loader --save 2.再安装css-loader npm install css-loader --save 3 ...

  10. 利用Eric+Qt Designer编写倒计时时钟

    [前言]前几日通过编写命令行通讯录,掌握了Python的基本语法结构,于是开始向更高水平冲击,利用Eric与Qt Designer 编写一个带界面的小程序.本次实操中也确实遇到了不少问题,通过学习也都 ...

随机推荐

  1. 浅谈 K-D Tree 及其进阶应用

    前言 \(\text{K-D Tree (K-Dimension Tree)}\) 是一种可以有效处理高维信息的数据结构. 在一般信息学竞赛题目中 \(k = 2\),此时它又称 \(\text{2- ...

  2. 如果读完这几百 Python 的书籍,能成编程大佬吗? #Python 入门 #编程 #编程学习 #知识分享

  3. 常见CDN绕过姿势

    CDN绕过: 1.子域名 子域名查询: 在一些网站中有可能只加速了主站,而一些其它子域名和主站在同一个C段或者同服务器 利用子域名查询工具: http://tool.chinaz.com/subdom ...

  4. burpsuit无法启动

    在安装burpsuit时,最难的并不是找带有注册机的burpsuit安装包. 而是因为一些底层的原因,无法打开 burpsuit. 提示 Your JRE appears to be version ...

  5. chrome浏览器自定义安装方法

    chrome浏览器有很多比较好的方面,也是很多人首选的浏览器.对于想自定义安装chrome浏览器的小伙伴来说,关于chrome浏览器默认安装到C盘让人很无奈.网上有说直接将C盘的文件夹放到想安装的文件 ...

  6. NSScrollView 内容显示不正常问题

    NSScrollView 内容显示不正常,顶部没有对齐已经后边有空隙,说明Layout的方式错误,采用了Automatic导致的.需要采用如下布局方式才可以.

  7. 配置NVIDIA Container Runtime和容器运行GPUStack教程

    GPUStack 是一个设计用于运行大模型的开源 GPU 集群管理器,提供私有部署的大模型服务,支持大语言模型.Embedding 文本嵌入模型.Reranker 重排序模型.Vision 多模态模型 ...

  8. Hibernate二级缓存 ---- 最佳实践

    2010年11月7号,立冬,星期天.北京外面风好大,躲在家里整理一下这篇文章,发出来与大家分享,对大家有帮助是我最高兴的事儿. 不要想当然的认为使用了Hibernate的二级缓存就一定能够提高应用程序 ...

  9. 想学习建个网站?WAMP Server助你在Windows上快速搭建PHP集成环境

    我想只要爬过几天网的同学都会知道PHP吧,异次元的新版本就是基于PHP的WordPress程序制造出来的,还有国内绝大部分论坛都是PHP的哦.据我所知很多同学都想要试着学习一下PHP,无奈要在Wind ...

  10. pikachu平台暴力破解详解

    声明:文章只是起演示作用,所有涉及的网站和内容,仅供大家学习交流,如有任何违法行为,均和本人无关,切勿触碰法律底线. 文章来自个人csdn博客,感兴趣的可以关注一下,https://blog.csdn ...