EntryTask: 使用FFMpeg编写一个简单音视频播放器
开发环境
- 开发语言:C++
- IDE:QT Creator/XCode/VS
内容要求
- 编译、集成FFmpeg实现基础的视频播放器功能;【完成】
- 有一个简易的界面(QT),支持开始播放、暂停、继续、拖放、从文件中选择媒体文件,支持RTMP拉流;【完成】
- 声音,画面的播放,不能出现异常中断,实现基础的音画同步策略;【完成】
- 输出FFMpeg库的集成/使用文档;【完成】
播放器功能
播放器流程
fmpeg播放器流程图.png开源框架选取
- 窗口显示采用QT框架
- 音视频解码采用FFmpeg
- 音频播放采用SDL
上述三个开源组建都是跨平台的。
读取、解码、渲染设计
- 设计一个音视频的
packet
队列,作用是使读取线程和解码线程异步工作。packet
队列采用互斥锁同步。 - 读取文件子线程:线程作用是从本地或者网络文件流中不停地读取数据。打开文件后,循环调用
av_read_frame
读取文件中的packet
,将其塞入对应的音视频packet
队列中。 - 解码子线程:线程的作用是循环从音视频
packet
队列中读取相应的packet
数据,送入解码器内部进行解码操作。其中,视频解码子线程是程序创建,音频解码子线程是由SDL创建,程序通过注册回调来执行音频解码。视频解码线程会在解码后将数据转换为RGB图像数据,再通过回调,将数据传递到MainWindow
类内部。 - 主线程:
MainWindow
窗口渲染线程。接收到RGB数据后,通过QT的信号槽机制,将需要显示的数据emit到主线程。在MainWindow
窗口的paintEvent
事件回调中,将RGB数据转成QImage
,最后通过drawImage
将显示数据画到MainWindow
窗口上。
可以参考ffmpeg播放器流程图。
抽象接口设计
播放器和外部Window窗口解耦,给播放器传入一个外部接口指针。
主窗口MainWindow
继承一个VideoPlayerCallBack
的抽象类,VideoPlayerCallBack
类中声明需要回调的外部接口。在主窗口MainWindow
中实现这些接口。内部播放器在合适时机调用这些接口,将数据传给窗口处理。
音视频同步处理
音视频同步主要存在三种方式:音频同步到视频,视频同步到音频,音视频同步到外部时间。这里选择使用视频同步到音频的方案,主要是因为SDL在音频解码时已经做好了时间的同步。
- 音频解码回调线程中,将音频的pts记录下来。即:
m_audioCurPts = av_q2d(m_audioStream->time_base) * packet->pts;
- 在视频的解码线程中,处理同步。获取当前视频帧的pts,与前面获得到的音频的pts进行比较,如果视频pts大于音频的pts,表示视频快了,则让视频解码线程暂停一段时间,然后继续比较,直到视频pts小于等于音频的pts,则继续视频解码线程。
- 在无音频的码流中,则将视频的同步到外部时间。即在视频解码一开始时记录当前的时间戳,在解码当前视频帧时,计算当前时间与视频解码起始时间差值,如果视频pts大于这个差值,则暂停视频解码线程。
音视频暂停播放处理
暂停播放时,读取文件子线程停止读取数据到packet
队列,解码现场也暂停从packet
队列取数据解码。
FFmpeg音视频解码过程
- 初始化网络
avformat_network_init
- 打开文件
avformat_open_input
- 从文件中提取流信息
avformat_find_stream_info
- 在多个数据流中找到视频流(类型为
MEDIA_TYPE_VIDEO
)和音频流(AVMEDIA_TYPE_AUDIO
) - 查找相对应的解码器
avcodec_find_decoder
- 打开解码器
avcodec_open2
- 从数据流中读取数据到Packet中
av_read_frame
- 将数据送入到解码器内部
avcodec_send_packet
- 为解码帧分配内存
av_frame_alloc
- 接收解码帧
avcodec_receive_frame
- 解码完成后需要关闭
avformat_open_input
打开的输入流,avcodec_open2
打开的CODEC
FFmpeg格式转换过程
- 分配保存输出RGB数据的
AVFrame *rgbFrame
空间。av_frame_alloc
- 分配
rgbFrame
中data
域的内存空间。av_image_alloc
- 调用
sws_getContext
创建一个SwsContext
上下文 - 调用
sws_scale
进行数据格式转换 - 释放
SwsContext
上下文sws_freeContext
,渲染之后释放rgbFrame
中data
域的内存空间av_freep(&(rgbFrame->data[0]))
和rgbFrame
的空间av_free(rgbFrame)
网友评论