Nuplayer 音视频同步学习笔记
目录
1. 处理解码之后的数据
(1) handleAnOutputBuffer
NuPlayerDecoder调用handleAnOutputBuffer处理解码之后的音视频数据. 函数最终会调用NuPlayerDecoderRenderer::queueBuffer处理这个Buffer
bool NuPlayer::Decoder::handleAnOutputBuffer(size_t index, size_t offset, size_t size,
int64_t timeUs, int32_t flags) {
sp<MediaCodecBuffer> buffer;
//根据Index从MediaCodec获取Buffer
mCodec->getOutputBuffer(index, &buffer);
mOutputBuffers.editItemAt(index) = buffer;
// 把offset size timeUs信息添加到buffer中
// 其中timeUs是buffer的媒体时间(Buffer在媒体文件的位置)
buffer->setRange(offset, size);
buffer->meta()->clear();
buffer->meta()->setInt64("timeUs", timeUs);
// 创建一个消息kWhatRenderBuffer, 消息会被传入到NuplayerRenderer,
// 当Buffer被Renderer处理完成后,就会发送这个消息消息
sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this);
reply->setSize("buffer-ix", index);
reply->setInt32("generation", mBufferGeneration);
// 调用NuplayerRenderer 的 queueBuffer
mRenderer->queueBuffer(mIsAudio, buffer, reply);
(2). queueBuffer
把音视频的buffer添加到Render的队列 mAudioQueue(audio) mVideoQueue(!audio)
<1> queueBuffer发送消息kWhatQueueBuffer调用Renderer::onQueueBuffer
void NuPlayer::Renderer::queueBuffer(bool audio, const sp<MediaCodecBuffer> &buffer, const sp<AMessage> ¬ifyConsumed) {
int64_t mediaTimeUs = -1;
buffer->meta()->findInt64("timeUs", &mediaTimeUs);
// 发送消息kWhatQueueBuffer, 调用onQueueBuffer
// 把Decoder传入的消息notifyConsumed放入到新创建的msg
sp<AMessage> msg = new AMessage(kWhatQueueBuffer, this);
msg->setInt32("queueGeneration", getQueueGeneration(audio));
msg->setInt32("audio", static_cast<int32_t>(audio));
msg->setObject("buffer", buffer);
msg->setMessage("notifyConsumed", notifyConsumed);
msg->post();
<2> 把音视频Buffer添加到队列mAudioQueue, mVideoQueue。调用postDrainAudioQueue_l, postDrainVideoQueue处理各自队列中的Buffer
void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg)
// 判断是Audio数据还是Video的数据
if (audio) mHasAudio = true;
else mHasVideo = true;
//创建并初始化一个mVideoScheduler
if (mHasVideo)
if (mVideoScheduler == NULL)
mVideoScheduler = new VideoFrameScheduler();
mVideoScheduler->init();
//创建一个队列的节点用来保存buffer的信息。
CHECK(msg->findMessage("notifyConsumed", ¬ifyConsumed));
QueueEntry entry;
entry.mBuffer = buffer;
// Decoder传入的消息kWhatRenderBuffer被放入到节点
entry.mNotifyConsumed = notifyConsumed;
entry.mOffset = 0;
entry.mFinalResult = OK;
entry.mBufferOrdinal = ++mTotalBuffersQueued;
// 把创建的节点push到音频数据的队列和视频数据的队列 mAudioQueue mVideoQueue
// 调用postDrainAudioQueue_l 或 postDrainVideoQueue,清空音视频数据的队列
if (audio) {
mAudioQueue.push_back(entry);
postDrainAudioQueue_l();
} else {
mVideoQueue.push_back(entry);
postDrainVideoQueue();
}
2. AudioBuffer的处理
(1) postDrainAudioQueue_l
调用onDrainAudioQueue来处理Auido队列的Buffer
void NuPlayer::Renderer::postDrainAudioQueue_l(int64_t delayUs)
//发送消息 kWhatDrainAudioQueue, 调用
sp<AMessage> msg = new AMessage(kWhatDrainAudioQueue, this);
msg->setInt32("drainGeneration", mAudioDrainGeneration);
msg->post(delayUs);
case kWhatDrainAudioQueue
// 接下来主要的工作在onDrainAudioQueue中完成
if (onDrainAudioQueue()) {
// 函数onDrainAudioQueue,当AudioQueue的数据没有处理完的情况下,会返回true
// 返回true的情况下,需要延时delayUs再次调用onDrainAudioQueue处理AudioQueue
// 1) 调用AudioTrack的getPosition获得Audio已经播放的数据帧的个数.
uint32_t numFramesPlayed;
mAudioSink->getPosition(&numFramesPlayed)
// 2) 已经写入到Audio但是还没有播放的数据(主要是在Audio侧Buffer中的数据)
uint32_t numFramesPendingPlayout =mNumFramesWritten - numFramesPlayed;
// 3) delayUs: 需要延时delayUs之后,最新写入Audio的数据才会开始播放
int64_t delayUs = mAudioSink->msecsPerFrame()
* numFramesPendingPlayout * 1000ll;
// 4) 根据播放的速率 调整delayUs
if (mPlaybackRate > 1.0f) delayUs /= mPlaybackRate;
// 5) 调整delayUs,防止Audio Buffer的数据被清空
delayUs /= 2;
// 6) 调用postDrainAudioQueue_l处理AudioQueue的数据
// 传入 delayUs
postDrainAudioQueue_l(delayUs);
(2) onDrainAudioQueue
更新锚点时间, 把AudioBuffer传给AudioSink播放
1) 循环处理mAudioQueue中的节点, 直到mAudioQueue中的buffer被清空
2) 尝试更新锚点时间 onNewAudioMediaTime()
3) 把数据写入到AudioSink
4) Buffer被处理完, 通知Decoder
5) 更新MediaClock中的maxTimeMedia
6) 判断mAudioQueue是否还有数据
bool NuPlayer::Renderer::onDrainAudioQueue()
uint32_t prevFramesWritten = mNumFramesWritten;
// 1) 循环处理mAudioQueue中的节点, 直到mAudioQueue中的buffer被清空
while (!mAudioQueue.empty()) {
// 处理当前的头节点
QueueEntry *entry = &*mAudioQueue.begin();
// Buffer为空的情况
if (entry->mBuffer == NULL) {
// EOS
}
// 2) 尝试更新锚点时间,之后会对锚点时间以及如何更新详细介绍
// 第一次处理这个节点, 如果需要, 尝试通过媒体时间更新锚点时间
// mOffset Buffer中已经有mOffset大小的数据被处理
if (entry->mOffset == 0 && entry->mBuffer->size() > 0) {
int64_t mediaTimeUs;
// 从Buffer的数据中, 获得媒体的时间(当前Buffer在媒体文件中的位置)
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
// 尝试更新锚点时间
onNewAudioMediaTime(mediaTimeUs);
}
// 3) 把数据写入到AudioSink
// 需要写入copy大小的的数据
size_t copy = entry->mBuffer->size() - entry->mOffset;
// 调用AudioSink的Write把数据写到AudioTrack
ssize_t written = mAudioSink->write(entry->mBuffer->data() + entry->mOffset,
copy, false /* blocking */);
// 根据写入的数据, 更新Offset
entry->mOffset += written;
// Buffer中还有remainder的数据需要处理
size_t remainder = entry->mBuffer->size() - entry->mOffset;
// 4) Buffer被处理完, 通知Decoder
// 剩余的数据小于一帧(一个Audio的采样点), 当前Buffer已经被处理完
if ((ssize_t)remainder < mAudioSink->frameSize()) {
// 通知Decoder当前buffer已经被处理完
// mNotifyConsumed: Decoder传入的消息kWhatRenderBuffer
entry->mNotifyConsumed->post();
mAudioQueue.erase(mAudioQueue.begin());
entry = NULL;
// 更新copiedFrames:已经写入到Audio的数据
size_t copiedFrames = written / mAudioSink->frameSize();
mNumFramesWritten += copiedFrames;
// 5) 更新MediaClock中的maxTimeMedia
// (上面写入的Buffer的最后一帧的媒体时间)
// 媒体时间, Buffer在媒体文件的位置, 可以理解为进度条的时间
// 锚点媒体时间戳加上新写入帧数对应的时长,即为媒体时间戳最大值
// mAnchorTimeMediaUs +
// 锚点媒体时间, 最新的锚点对应的buffer的MediaTime
// (mNumFramesWritten - mAnchorNumFramesWritten)
// 上次锚点之后,新写入的帧数
// * mAudioSink->msecsPerFrame() * 1000LL
// 每一帧播放的时间(delay)
int64_t maxTimeMedia;
maxTimeMedia = mAnchorTimeMediaUs +
(int64_t)(max((long long)mNumFramesWritten - mAnchorNumFramesWritten, 0LL)
* 1000LL * mAudioSink->msecsPerFrame());
mMediaClock->updateMaxTimeMedia(maxTimeMedia);
// 6) 如果mAudioQueue还有数据没有处理返回true, 需要重新调用onDrainAudioQueue处理
bool reschedule = !mAudioQueue.empty()
&& (!mPaused || prevFramesWritten != mNumFramesWritten);
return reschedule;
3. Video Buffer的处理
(1) postDrainVideoQueue
计算数据应该在什么时间显示, 根据这个时间延时发送kWhatDrainVideoQueue消息, 调用onDrainVideoQueue
1) 根据Buffer的媒体时间,获得Buffer显示的系统时间(数据应该在这个时间显示)
2) 计算出合适的发送kWhatDrainVideoQueue消息的延时时间
3) 发送消息kWhatDrainVideoQueue
void NuPlayer::Renderer::postDrainVideoQueue()
QueueEntry &entry = *mVideoQueue.begin();
// 准备发送消息kWhatDrainVideoQueue,
sp<AMessage> msg = new AMessage(kWhatDrainVideoQueue, this);
msg->setInt32("drainGeneration", getDrainGeneration(false /* audio */));
// 1) 根据Buffer的媒体时间,获得Buffer显示的系统时间(数据应该在这个时间显示)
// 获得当前系统的时间
int64_t nowUs = ALooper::GetNowUs();
// 获取当前Buffer的媒体时间(当前Buffer在媒体文件的位置)
entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs);
// 获取当前Buffer应该在什么时间显示(数据显示的系统的时间)
// 主要根据mediaTimeUs来计算, 需要依赖Audio侧更新的锚点时间
// 如果获得realTimeUs和delayUs有问题
// 通常需要检查Audio侧更新的锚点时间和Audio getTimestamp或getPosition的返回值
realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
// 2) 计算出合适的发送kWhatDrainVideoQueue消息的延时时间,
// delayUs, 当前Buffer在delayUs之后显示
delayUs = realTimeUs - nowUs;
// Video的Buffer来的太早, 或锚点时间有问题,延时重新调用postDrainVideoQueue
if (delayUs > 500000) {
postDelayUs = 500000;
if (postDelayUs >= 0) {
msg->setWhat(kWhatPostDrainVideoQueue);
msg->post(postDelayUs);
mVideoScheduler->restart();
// 利用VideoScheduler更新realTimeUs和delayUs
realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
delayUs = realTimeUs - nowUs;
// 3) 发送消息kWhatDrainVideoQueue调用onDrainVideoQueue
//如果 delayUs大于2倍的Vsync, 延时delayUs减去2倍的Vsync的时间发送kWhatDrainVideoQueue
//否则立即发送kWhatDrainVideoQueue, 立即发送kWhatDrainVideoQueue,处理buffer
msg->post(delayUs > twoVsyncsUs ? delayUs - twoVsyncsUs : 0);
(2) onDrainVideoQueue
重新计算buffer显示的系统时间realTimeUs, 通知Decoder Buffer已经处理完
发送realTimeUs 和 tooLate的信息
void NuPlayer::Renderer::onDrainVideoQueue() {
// 取出第一个Buffer
QueueEntry *entry = &*mVideoQueue.begin();
// 当前Real系统的时间
int64_t nowUs = ALooper::GetNowUs();
// 获取媒体时间
entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs);
// 显示的Real系统时间
realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
bool tooLate = false;
if (!mPaused) {
// 视频的数据来晚了nowUs - realTimeUs的时间
// mVideoLateByUs = nowUs - realTimeUs
setVideoLateByUs(nowUs - realTimeUs);
// 视频晚了40ms
tooLate = (mVideoLateByUs > 40000);
// 通知Decoder当前buffer已经被处理完, 发送realTimeUs和tooLate
entry->mNotifyConsumed->setInt64("timestampNs", realTimeUs * 1000ll);
entry->mNotifyConsumed->setInt32("render", !tooLate);
entry->mNotifyConsumed->post();
mVideoQueue.erase(mVideoQueue.begin());
entry = NULL;
4. AVsync Audio更新锚点时间
(1) AVsync原理
系统时间和媒体时间应该是线性关系: (mediaTimeUs - anchorTimeMediaUs) = PlaybackRate*(nowUs - anchorTimeMediaUs)。
所以理论上,我们可以根据锚点, 计算出任意一点的媒体时间对应的系统时间(Buffer应该播放的时间). AVsync 的进本机理就是通过Audio每隔一段时间更新锚点, Video的Buffer根据锚点和媒体时间计算出应该播放的时(系统时间)
更新锚点时间 anchorTimeMediaUs anchorTimeRealUs
anchorTimeRealUs 更新锚点时的 系统时间
anchorTimeMediaUs 更新锚点时的 媒体时间(正在播放的媒体的时间)
下面我们主要来看一下Audi如何更新锚点
(2) Audi如何更新锚点
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();
// 获得当前正在播放的媒体时间 nowMediaUs
int64_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;
上一篇: 用circular_buffer实现的播放缓存队列
下一篇: 爱闹腾的儿童搞笑图片集锦