4. AVsync Audio更新锚点时间
(1) AVsync原理
系统时间和媒体时间应该是线性关系: (mediaTimeUs - anchorTimeMediaUs) = PlaybackRate*(nowUs - anchorTimeRealUs)。
所以理论上,我们可以根据锚点, 计算出任意一点的媒体时间对应的系统时间(Buffer应该播放的时间). AVsync 的进本机理就是通过Audio每隔一段时间更新锚点, Video的Buffer根据锚点和媒体时间计算出应该播放的时(系统时间)
更新锚点时间 anchorTimeMediaUs anchorTimeRealUs
anchorTimeRealUs 更新锚点时的 系统时间
anchorTimeMediaUs 更新锚点时的 媒体时间(正在播放的媒体的时间)
下面我们主要来看一下Audio如何更新锚点
(2) Audio如何更新锚点
anchorTimeRealUs = nowUs
锚点系统时间被设置为当前系统时间, 所以 anchorTimeMediaUs 就应该当前正在播放的媒体时间,接下来重点是如何确定anchorTimeMediaUs.
<1> 如何计算当前正在播放的媒体时间
当前正在播放的媒体时间 = 正在写入的媒体时间 - 已经写入但是没有播放的数据需要播放的时间
pendingAudioPlayoutDurationUs: 已经写入但是没有播放的数据需要播放的时间(主要是在AudioBuffer里面的数据)
anchorTimeMediaUs = mediaTimeUs - pendingAudioPlayoutDurationUs;
<2> 计算已经写入但是没有播放的数据需要播放的时间
writtenAudioDurationUs: 已经写入的数据的持续时间
PlayedOutDurationUs: 当前已经播放的数据的持续时间
两者相减就是pendingAudioPlayoutDurationUs
pendingAudioPlayoutDurationUs = writtenAudioDurationUs - PlayedOutDurationUs
<3> 已经写入的数据的持续时间
mNumFramesWritten 已经写入的数据的帧的个数 * 每一帧多少时间(1000000LL / sampleRate)
writtenAudioDurationUs = mNumFramesWritten * (1000000LL / sampleRate)
<4> 计算当前已经播放的数据的持续时间
ts.mPosition * 1000000LL/mSampleRateHz 在 ts.mTime时间已经播放的数据的时间
因为nowUs与ts.mTime不相等, 最后需要根据nowUs进行调整
PlayedOutDurationUs = ts.mPosition * 1000000LL/mSampleRateHz + nowUs - ts.mTime
void NuPlayer::Renderer::onNewAudioMediaTime(int64_t mediaTimeUs)// 设置初始锚点媒体时间setAudioFirstAnchorTimeIfNeeded_l(mediaTimeUs);int64_t nowUs = ALooper::GetNowUs();if (mNextAudioClockUpdateTimeUs >= 0)//是否需要更新锚点时间, 根据kMinimumAudioClockUpdatePeriodUs的时间if (nowUs >= mNextAudioClockUpdateTimeUs) // (1) nowMediaUs: 当前正在播放的媒体时间// mediaTimeUs: 当前正在写入到Audio的数据的媒体时间// getPendingAudioPlayoutDurationUs // 已经写入到Audio但是还没有播放的数据持续时间int64_t nowMediaUs = mediaTimeUs - getPendingAudioPlayoutDurationUs(nowUs);// 根据nowMediaUs和mediaTimeUs更新锚点时间mMediaClock->updateAnchor(nowMediaUs, nowUs, mediaTimeUs);mNextAudioClockUpdateTimeUs = nowUs + kMinimumAudioClockUpdatePeriodUs;// (2) 需要分析一下getPendingAudioPlayoutDurationUs
// 计算方法使用 writtenAudioDurationUs - PlayedOutDurationUs
int64_t NuPlayer::Renderer::getPendingAudioPlayoutDurationUs(int64_t nowUs)// (3) writtenAudioDurationUs 已经写入的数据的持续时间// mNumFramesWritten * (1000000LL / sampleRate)// 写入的帧的个数 * 每一帧的持续时间(us)// (1000000LL / sampleRate): sampleRate一秒的采样数, 取倒数每一个采样的持续时间int64_t writtenAudioDurationUs = getDurationUsIfPlayedAtSampleRate(mNumFramesWritten);// PlayedOutDurationUs 已经播放时间, 需要从Audio侧获取return writtenAudioDurationUs - mAudioSink->getPlayedOutDurationUs(nowUs);//(4) 当前已经播放的数据的持续时间
int64_t MediaPlayerService::AudioOutput::getPlayedOutDurationUs(int64_t nowUs)// 从Audio侧获取已经获取当前播放的帧,和对应的系统时间// 注意ts.mPosition并不是正在播放的帧的位置, // 应该是ts.mTime这个系统时间点正在播放的帧的位置// ts.mTime与当前时间nowUs并不相等,会有ms级的差别status_t res = mTrack->getTimestamp(ts);if (res == OK)numFramesPlayed = ts.mPosition;numFramesPlayedAt = ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000;// numFramesPlayed * 1000000LL / mSampleRateHz: ts.mTime时间点已经播放的时间// 最后计算的时候需要考虑ts.mTime与当前时间nowUs之间的差异// durationUs nowUs时间点已经播放的时间(正在播放的媒体时间)int64_t durationUs = (int64_t)((int32_t)numFramesPlayed * 1000000LL / mSampleRateHz) + nowUs - numFramesPlayedAt;// (5) 调用MediaClock::updateAnchor更新锚点
// 传入参数:
// anchorTimeMediaUs 正在播放的媒体时间
// anchorTimeRealUs anchorTimeMediaUs对应的系统时间
void MediaClock::updateAnchor(int64_t anchorTimeMediaUs, int64_t anchorTimeRealUs, int64_t maxTimeMediaUs)// 获得当前的系统时间, 可能与anchorTimeRealUs有差别int64_t nowUs = ALooper::GetNowUs();// 获得当前正在播放的媒体时间 nowMediaUsint64_t nowMediaUs =anchorTimeMediaUs + (nowUs - anchorTimeRealUs) * (double)mPlaybackRate;// 更新当前播放的媒体时间为锚点媒体时间// 更新当前系统时间为锚点系统时间mAnchorTimeRealUs = nowUs;mAnchorTimeMediaUs = nowMediaUs;
5. AVsync Video 获取显示的时间(系统时间)
AVsync的目的是获得Buffer显示的时间(buffer的系统时间).我们可以根据媒体时间和系统时间的线性关系计算出显示的时间
(mediaTimeUs - anchorTimeMediaUs) = PlaybackRate*(nowUs - anchorTimeMediaUs)
(1) nowMediaUs 当前播放媒体时间
根据当前的系统时间和锚点时间计算出当前播放媒体时间
nowMediaUs = mAnchorTimeMediaUs + (realUs - mAnchorTimeRealUs) * mPlaybackRate
(2) outRealUs Buffer的显示时间
根据当前播放的媒体时间和系统时间, 计算出Buffer的显示时间(系统时间)
outRealUs = (targetMediaUs - nowMediaUs) / (double)mPlaybackRate + nowUs
// outRealUs 获得当前Buffer播放的系统时间(应该在这个时间点播放)
// 传入参数 targetMediaUs. 当前Buffer的媒体时间
status_t MediaClock::getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs)int64_t nowUs = ALooper::GetNowUs();// (1) nowMediaUs video正在播放的媒体时间, nowUs对应的媒体时间getMediaTime_l(nowUs, &nowMediaUs, true /* allowPastMaxTime */);// (2) 计算出Buffer的显示时间*outRealUs = (targetMediaUs - nowMediaUs) / (double)mPlaybackRate + nowUs;// outMediaUs video正在播放的媒体时间, nowUs对应的媒体时间
// realUs 当前系统时间
status_t MediaClock::getMediaTime_l(int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime)//mediaUs 当前Audio正在播放的媒体时间, 对应video正在播放的媒体时间int64_t mediaUs = mAnchorTimeMediaUs+ (realUs - mAnchorTimeRealUs) * (double)mPlaybackRate;*outMediaUs = mediaUs;
摘自:Nuplayer 音视频同步学习笔记_ondrainaudioqueue-CSDN博客