1:音视频数据都有一个list,用于存放解码后的数据;
List mFilledBuffers;
2:解码后的音视频数据不断的往list中存放,不做音视频同步方面的时间上控制
mFilledBuffers.push_back(i);
3:音视频同步主要表现在从list中取数据进行视频的显示和音频的输出;
4:其中audio数据在线程函数threadLoop中调用AudioPlayer的回调函数循环读取,不做时间上的控制;
4:视频数据正常情况下按照每10ms的时间取一次,如果有音视频不同步的情况,就可能需要做以下三种情况的处理
1:如果视频数据过快,超过音频时间10ms,则显示当前帧后,将读取数据的事件延时(慢的时间-10ms)后放入队列;
2:如果视频数据过慢,慢于音频时间40ms,则丢弃当前帧,然后再去取数据;
3:如果视频数据过慢,慢于音频时间500ms,则丢弃当前帧,并且需要丢弃一段视频帧,直接对video部分(SEEK_VIDEO_ONLY)seek到音频当前帧的时间戳;
PS:上面的三个时间参数可能会微调;
6:视频当前时间是指视频当前帧携带的时间戳,是通过该帧的sampleIndex从stts表中读取出来的;
int64_t timeUs;
CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
7:音频的时间戳通过以下方式计算:
result + diffUs - realtime_us + mediatime_us = -mLatencyUs + ALooper::GetNowUs() - mNumFramesPlayedSysTimeUs + mediatime_us
mNumFramesPlayedSysTimeUs = ALooper::GetNowUs() 和前面那个函数有一定的时间差
8:循环读取数据的机制
android中循环读取数据都是通过Thread类的threadLoop函数实现,这个函数的介绍如下:
Derived class must implement threadLoop(). The thread starts its life here. There are two ways of using the Thread object:
1) loop: if threadLoop() returns true, it will be called again if
requestExit() wasn‘t called.
2) once: if threadLoop() returns false, the thread will exit upon return.
virtual bool threadLoop() = 0;
如果函数返回true,且没有调用requestExit()函数,则一直循环执行 ----------正常播放情况
如果函数返回true,但调用requestExit()函数,则停止执行 ----------停止播放情况
如果函数返回false,执行一次后返回
9:正常情况下音视频对应的threadLoop函数都在循环执行,并没有时间上的控制,音频数据确实如此,但是视频数据通过下面的机制控制读取
读取视频数据被封装为一个个事件,这些事件都会携带一个什么执行的时间戳,这些事件按照执行时间的先后顺序放入一个队列;
threadLoop函数循环执行的时候,会判断当前时间和队头事件的时间戳
前者大于等于后者,取出时间执行,return true;
前者小于后者,做下面的操作:
int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
int64_t nowUs = GetNowUs();
if (whenUs > nowUs) {
int64_t delayUs = whenUs - nowUs;
mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);
return true;
event = *mEventQueue.begin();
mEventQueue.erase(mEventQueue.begin());
gLooperRoster.deliverMessage(event.mMessage);
libstagefright 音视频同步方案