## 一、前言

用ffmpeg来实现自己的播放器,这是一直以来的一个目标,之前的难点卡在音视频同步以及如何播放声音这两点(尽管之前已经进行过不少的尝试和探索,但是问题还是挺多,比如音视频同步不完美,有些文件正常而有些文件不准,声音播放采用的sdl总感觉多了个依赖怪怪的,而且很多初学者也反映希望采用Qt自身的类来播放),近期正好把这两个难点一一攻破了,音视频同步采用的外部时钟同步,声音播放采用的Qt自带的QAudioOutput(并没有采用sdl,省去学习sdl开源库的成本),播放器的demo如期进行。有时候做项目,如果将各个难点击破以后,接下来都是顺理成章水到渠成的事情,速度会非常快,这也是我经常用的策略。

最简单基本播放器具备的功能:

1. 播放、关闭、暂停、继续。

2. 音量调节、静音设置。

3. 进度调节、定位播放。

4. 总时长、已播放时长。

5. 音频、视频、本地文件、视频流。

前面几篇文章写了音视频同步、音频播放、音量设置、静音设置,这里就差一个进度调节、定位播放的处理了,ffmpeg内置了av_seek_frame函数负责定位播放帧,总共4个参数,含义分别如下:

1. 参数1 AVFormatContext *s 表示处理媒体对象的上下文。

2. 参数2 int stream_index 表示流的索引,填-1表示自动默认流索引。

3. 参数3 int64_t timestamp 表示要定位的时间,单位是微妙,如果传入的是秒则需要 * AV_TIME_BASE。

4. 参数4 int flags 表示如何定位和查找使用的策略,建议选择AVSEEK_FLAG_BACKWARD,其余参数容易花屏。

5. 返回值 >= 0 表示成功。

## 二、功能特点

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. 音视频完美同步,采用外部时钟同步策略。

19. 支持seek定位播放位置。

20. 支持qsv、dxva2、d3d11va等硬解码。

21. 支持opengl绘制视频数据,极低CPU占用。

22. 支持安卓和嵌入式linux,交叉编译即可。

## 三、效果图

qt音乐播放器源码(Qt音开发26-ffmpeg播放器)(1)

## 四、相关站点

1. 国内站点:[https://gitee.com/feiyangqingyun/QWidgetDemo](https://gitee.com/feiyangqingyun/QWidgetDemo)

2. 国际站点:[https://github.com/feiyangqingyun/QWidgetDemo](https://github.com/feiyangqingyun/QWidgetDemo)

3. 个人主页:[https://blog.csdn.net/feiyangqingyun](https://blog.csdn.net/feiyangqingyun)

4. 知乎主页:[https://www.zhihu.com/people/feiyangqingyun/](https://www.zhihu.com/people/feiyangqingyun/)

5. 体验地址:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)

## 五、核心代码

uint FFmpegThread::getLength() { return duration * 1000; } uint FFmpegThread::getPosition() { return 0; } void FFmpegThread::setPosition(int position) { if (this->isRunning() && !isRtsp && !isUsbCamera) { pause(); QThread::msleep(100); videoSync->clear(); audioSync->clear(); int64_t timestamp = ((double)position / 1000.0) * AV_TIME_BASE; av_seek_frame(formatCtx, -1, timestamp, AVSEEK_FLAG_BACKWARD); next(); } } void FFmpegThread::play() { //通过标志位让线程执行初始化 isPlay = true; isPause = false; } void FFmpegThread::pause() { //只对本地文件起作用 playAudio = false; if (!isRtsp && !isUsbCamera && !isPause) { isPause = true; } } void FFmpegThread::next() { //只对本地文件起作用 playAudio = true; if (!isRtsp && !isUsbCamera && isPause) { isPause = false; videoSync->reset(); audioSync->reset(); } } void FFmpegThread::stop() { //通过标志位让线程停止 stopped = true; } void FFmpegThread::snap() { //通过标志位来截图 句柄模式才需要 if (!callback) { isSnap = true; } }

,