前文中,我们基于 FFmpeg 利用 OpenGL ES 和 OpenSL ES 分别实现了对解码后视频和音频的渲染,本文将实现播放器的最后一个重要功能:音视频同步。
老人们经常说,播放器对音频和视频的播放没有绝对的静态的同步,只有相对的动态的同步,实际上音视频同步就是一个“你追我赶”的过程。
音视频的同步方式有 3 种,即:音视频向系统时钟同步、音频向视频同步及视频向音频同步。
音视频解码器结构
在实现音视频同步之前,我们先简单说下本文播放器的大致结构,方便后面实现不同的音视频同步方式。
如上图所示,音频解码和视频解码分别占用一个独立线程,线程里有一个解码循环,解码循环里不断对音视频编码数据进行解码,音视频解码帧不设置缓存 Buffer , 进行实时渲染,极大地方便了音视频同步的实现。
音视频解码线程独立分离的播放器模式,简单灵活,代码量小,面向初学者,可以很方便实现音视频同步。
音视和视频解码流程非常相似,所以我们可以将二者的解码器抽象为一个基类:
class DecoderBase : public Decoder {
public:
DecoderBase()
{};
virtual~ DecoderBase()
{};
//开始播放
virtual void Start();
//暂停播放
virtual void Pause();
//停止
virtual void Stop();
//获取时长
virtual float GetDuration()
{
//ms to s
return m_Duration * 1.0f / 1000;
}
//seek 到某个时间点播放
virtual void SeekToPosition(float position);
//当前播放的位置,用于更新进度条和音视频同步
virtual float GetCurrentPosition();
virtual void ClearCache()
{};
virtual void SetMessageCallback(void* context, MessageCallback callback)
{
m_MsgContext = context;
m_MsgCallback = callback;
}
//设置音视频同步的回调
virtual void SetAVSyncCallback(void* context, AVSyncCallback callback)
{
m_AVDecoderContext = context;
m_AudioSyncCallback = callback;
}
protected:
void * m_MsgContext = nullptr;
MessageCallback m_MsgCallback = nullptr;
virtual int Init(const char *url, AVMediaType mediaType);
virtual void UnInit();
virtual void OnDecoderReady() = 0;
virtual void OnDecoderDone() = 0;
//解码数据的回调
virtual void OnFr