?同步播放音视频的时候,《FFmpeg开发实战:从零基础到短视频上线》一书第10章的示例程序playsync.c采取一边遍历一边播放的方式,在源文件的音频流和视频流交错读取的情况下,该方式可以很好地实现同步播放功能。
但个别格式的音频流和视频流是分开存储的,前面一大段放了所有的音频帧,后面一大段放了所有的视频帧,并非音频帧与视频帧交错存储的模式。对于这种格式,playsync.c播放时先放完所有的声音,这期间画面是空白的;再快速放完所有的视频画面,这期间没有声音,显然播放过程是有问题的。
若想纠正playsync.c的播放问题,就得重新设计音视频的同步播放机制,不能采取一边遍历一边播放的方式,而要先把音频帧和视频帧都读到缓存队列中,再依次检查音频与视频的时间戳,从而决定在哪个时刻才播放对应时间戳的音视频。具体到代码实现上,需要补充下列几点改造。
1、除了已有的视频处理线程和视频包队列之外,还要增加声明音频处理线程和音频包队列,当然音频包队列配套的队列锁也要补充声明。增补后的声明代码如下所示:
2、在程序初始化的时候,不但要创建视频处理线程和视频队列的互斥锁,还要创建音频处理线程和音频队列的互斥锁。修改后的初始化代码如下所示:
3、对音视频文件遍历数据包时,不能立即渲染音频,而要把音频包加入音频队列,把视频包加入视频队列,由两个处理线程根据时间戳来调度具体的播放进度。另外,在所有数据包都遍历完之后,视频包队列可能还有剩余的数据,所以程序末尾得轮询视频包队列,直至所有视频帧都渲染结束才算完成播放。据此修改音视频文件的遍历与轮询代码如下所示:
除了上述的三大块改造,尚有下面四个函数要补充修改:
thread_work_audio函数:这是音频处理线程新增的工作函数,主要从音频包队列取数据,然后解码为音频帧再重采样,并将重采样的结果数据送给扬声器。
thread_work_video函数:这是视频处理线程原有的工作函数,除了给视频包队列及其对应的互斥锁改名之外,其他代码照搬即可。
play_video_frame函数:这是播放视频画面的新增函数,就是把原来SDL渲染画面的代码块重新包装成独立的函数,方便多次调用罢了。
release函数:这是释放音视频资源的函数,与之前的释放代码相比,主要增加了音频处理线程的等待操作,以及音频队列锁的销毁操作。
上述修改后的代码已经附在了《FFmpeg开发实战:从零基础到短视频上线》一书第10章的源码chapter10/playsync2.c,这个c代码是playsync.c的改进版,能够正常播放音频流和视频流分开存储的视频文件。
接着执行下面的编译命令。
编译完成后执行以下命令启动测试程序,期望播放视频文件fuzhou.mp4。
程序运行完毕,发现控制台输出以下的日志信息。
同时弹出SDL窗口播放视频画面,并且扬声器传来了阵阵歌声,表示上述代码正确实现了同步播放音视频的功能。
?