全新系列文章已更新:
- Android Media Framework - 开篇
- Android Media Framework(一)OpenMAX 框架简介
- Android Media Framework(二)OpenMAX 类型阅读与分析
- Android Media Framework(三)OpenMAX API阅读与分析
- Android Media Framework(四)Non-Tunneled组件的状态转换与buffer分配过程分析
- Android Media Framework(五)Tunnel Mode
- Android Media Framework(六)插件式编程与OMXStore
- Android Media Framework(七)MediaCodecService
- Android Media Framework(八)OMXNodeInstance - Ⅰ
- Android Media Framework(九)OMXNodeInstance - Ⅱ
- Android Media Framework(十)OMXNodeInstance - Ⅲ
- Android Media Framework(十一)OMXNodeInstance - Ⅳ
- Android Media Framework(十二)OMXNodeInstance - Ⅴ
- Android Media Framework(十三)ACodec - Ⅰ
- Android Media Framework(十四)ACodec - Ⅱ
- Android Media Framework(十五)ACodec - Ⅲ
- Android Media Framework(十六)ACodec - Ⅳ
这一节我们来了解 NuPlayer Renderer 是如何工作,avsync 机制是如何运行的。
1、创建 Renderer
void NuPlayer::onStart(int64_t startPositionUs, MediaPlayerSeekMode mode) {
if (mSource->isRealTime()) {
flags |= Renderer::FLAG_REAL_TIME;
}
......
if (mOffloadAudio) {
flags |= Renderer::FLAG_OFFLOAD_AUDIO;
}
......
sp<AMessage> notify = new AMessage(kWhatRendererNotify, this);
++mRendererGeneration;
notify->setInt32("generation", mRendererGeneration);
mRenderer = new Renderer(mAudioSink, mMediaClock, notify, flags);
mRendererLooper = new ALooper;
mRendererLooper->setName("NuPlayerRenderer");
mRendererLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
mRendererLooper->registerHandler(mRenderer);
}
调用 NuPlayer start 方法后会创建 Renderer,传入参数为 callback message、AudioSink、MediaClock 以及 flags。可以看到 NuPlayer 中也用一个 generation 来管理 Renderer 的状态,如不了解 generation 是如何使用的,可以看前面一篇笔记。
接下来分别解释几个参数的意义:
AudioSink
:它是一个基类,实际传入的是他的子类对象AudioOutput
,实现在 MediaPlayerService.cpp 当中,AudioOutput 中封装的是AudioTrack
,如果想了解 AudioTrack 如何使用,可以参考 AudioOutput。回到这里,在 Renderer 中,decoder 解出 audio data 后会直接将数据写到 AudioOutput 中;MediaClock
:它是一个系统时钟,用于记录系统时间戳;flags
:创建 Renderer 之前会分析它所使用的 flag,首先判断 Source 是否是 RealTime,Source.isRealTime 默认返回值是 false,只有 RTSPSource 返回 true,这里就可以猜测,如果是直播源,那么 avsync 流程应该会有不一样的地方;接着会判断当前的音频流是否支持 offload mode,offload mode 指的是将 audio compress data 直接写入到 AudioTrack 中,直接解码播放,而普通模式需要将 audio compress data 送到 audio decoder 中解出 PCM 数据,然后再写入到 AudioTrack 中。如果走 offload mode,audio Decoder 将使用NuPlayerDecoderPassThrough
,因此 Renderer 中写入 audio data 的流程也需要做些改变。
status_t NuPlayer::instantiateDecoder(bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
if (audio) {
if (checkAudioModeChange) {
// 判断是否需要开启 offload mode
determineAudioModeChange(format);
}
if (mOffloadAudio) {
mSource->setOffloadAudio(true /* offload */);
const bool hasVideo = (mSource->getFormat(false /*audio */) != NULL);
format->setInt32("has-video", hasVideo);
*decoder = new DecoderPassThrough(notify, mSource, mRenderer);
ALOGV("instantiateDecoder audio DecoderPassThrough hasVideo: %d", hasVideo);
} else {
*decoder = new Decoder(notify, mSource, mPID, mUID, mRenderer);
ALOGV("instantiateDecoder audio Decoder");
}
mAudioDecoderError = false;
}
}
void NuPlayer::determineAudioModeChange(const sp<AMessage> &audioFormat) {
if (canOffload) {
if (!mOffloadAudio) {
mRenderer->signalEnableOffloadAudio();
}
// open audio sink early under offload mode.
tryOpenAudioSinkForOffload(audioFormat, audioMeta, hasVideo);
} else {
if (mOffloadAudio) {
mRenderer->signalDisableOffloadAudio();
mOffloadAudio = false;
}
}
}
创建 AudioDecoder 时会调用 determineAudioModeChange 再次判断是否支持 offload mode(第一次判断在 NuPlayer 章节中已经简单提过了),如果支持就会优先使用 offload mode(节约性能),调用 Renderer 的 openAudioSink
方法尝试打开 audio hal,并配置 offload mode;如果不支持 offload mode,则暂时不会创建 AudioTrack。
上一篇 Decoder 中我们提到,Decoder 收到Audio Output Format Changed 事件后会调用 changeAudioFormat
方法,如果不是 offload mode,这里会调用 openAudioSink 创建普通的 AudioTrack。也就是说,普通模式下只有真正解出 audio 数据后 AudioTrack 才会被创建。
ps:如果想了解 AudioTrack 普通模式以及 offload mode 如何使用,可以参考 NuPlayer、Renderer 以及 NuPlayerDecoderPassThrough。
接下来的内容我们暂时只看 AudioTrack 普通模式。
2、queueBuffer
Renderer 没有 start 方法,调用 queueBuffer 把 ouput buffer 写入到 Renderer 时 Avsync 就自动开始了。
void NuPlayer::Renderer::queueBuffer(
bool audio,
const sp<MediaCodecBuffer> &buffer,
const sp<AMessage> ¬ifyConsumed) {
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();
}
Renderer 中同样也使用了 generation trick,传进来的参数会被封装到新的 AMessage 送到 ALooper 中,最后通过 onQueueBuffer 处理消息:
void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) {
int32_t audio;
CHECK(msg->findInt32("audio", &audio));
// 判断 buffer 是否因为 generation变化要 drop
if (dropBufferIfStale(audio, msg)) {
return;
}
if (audio) {
mHasAudio = true;
} else {
mHasVideo = true;
}
// 如果是 video 则需要创建 VideoFrameScheduler,这是用于获取 vsync,这里不做研究
if (mHasVideo) {
if (mVideoScheduler == NULL) {
mVideoScheduler = new VideoFrameScheduler();
mVideoScheduler->init();
}
}
sp<RefBase> obj;
CHECK(msg->findObject("buffer", &obj));
sp<MediaCodecBuffer> buffer = static_cast<MediaCodecBuffer *>(obj.get());
sp<AMessage> notifyConsumed;
CHECK(msg->findMessage("notifyConsumed", ¬ifyConsumed));
// 将 Message 中的内容重新封装到 QueueEntry
QueueEntry entry;
entry.mBuffer = buffer;
entry.mNotifyConsumed = notifyConsumed;
entry.mOffset = 0;
entry.mFinalResult = OK;
entry.mBufferOrdinal = ++mTotalBuffersQueued;
// 发消息处理 Queue 中的 entry
if (audio) {
Mutex::Autolock autoLock(mLock);
mAudioQueue.push_back(entry);
postDrainAudioQueue_l();
} else {
mVideoQueue.push_back(entry);
postDrainVideoQueue();
}
// SyncQueue
Mutex::Autolock autoLock(mLock);
if (!mSyncQueues || mAudioQueue.empty() || mVideoQueue.empty()) {
return;
}
sp<MediaCodecBuffer> firstAudioBuffer = (*mAudioQueue.begin()).mBuffer;
sp<MediaCodecBuffer> firstVideoBuffer = (*mVideoQueue.begin()).mBuffer;
if (firstAudioBuffer == NULL || firstVideoBuffer == NULL) {
// EOS signalled on either queue.
syncQueuesDone_l();
return;
}
int64_t firstAudioTimeUs;
int64_t firstVideoTimeUs;
CHECK(firstAudioBuffer->meta()
->findInt64("timeUs", &firstAudioTimeUs));
CHECK(firstVideoBuffer->meta()
->findInt64("timeUs", &firstVideoTimeUs));
int64_t diff = firstVideoTimeUs - firstAudioTimeUs;
ALOGV("queueDiff = %.2f secs", diff / 1E6);
if (diff > 100000LL) {
// Audio data starts More than 0.1 secs before video.
// Drop some audio.
(*mAudioQueue.begin()).mNotifyConsumed->post();
mAudioQueue.erase(mAudioQueue.begin());
return;
}
syncQueuesDone_l();
}
onQueueBuffer 看起来很长,但是做的内容却并不多:
- 将 Message 中的内容重新封装成 QueueEntry,添加到对应的 List 中;
- 调用 postDrainAudioQueue_l / postDrainVideoQueue 发送消息处理 List 中的 Entry;
- 判断是否需要 SyncQueue;
2.1、SyncQueue
先说 SyncQueue 机制,它应该做起播同步用的,如果起播时 audio pts 和 video pts 差距过大,则通过该机制来 drop output data。
SyncQueue 机制在 Renderer 中尚未启用,功能也没有做完,为什么这么说呢?因为 mSyncQueues
并不会在代码中置 true,另外除了起播时要做 SyncQueue 外,我觉得 flush 之后也需要做一下同步。
SyncQueue 机制的主要思路是当 Video 和 Audio 数据都到达时,判断两队队首元素的 pts,如果 Video 的时间比 Audio 晚,那么就把 Audio 先 drop,同步之后可以将 SyncQueue flag 置为 false。
我们上面说到 onQueueBuffer 会先调用 postDrainAudioQueue_l / postDrainVideoQueue 发送消息,但是如果实际看代码就会发现,一进入这两个方法就会判断是否需要做 SyncQueue,如果需要是不会执行接下来的内容的。
2.2、postDrainAudioQueue_l
void NuPlayer::Renderer::postDrainAudioQueue_l(int64_t delayUs) {
// 暂停、syncqueue、offload 直接退出
if (mDrainAudioQueuePending || mSyncQueues || mUseAudioCallback) {
return;
}
if (mAudioQueue.empty()) {
return;
}
// FIXME: if paused, wait until AudioTrack stop() is complete before delivering data.
if (mPaused) {
const int64_t diffUs = mPauseDrainAudioAllowedUs - ALooper::GetNowUs();
if (diffUs > delayUs) {
delayUs = diffUs;
}
}
// 如果暂停了就延时写入audioTrack
mDrainAudioQueuePending = true;
sp<AMessage> msg = new AMessage(kWhatDrainAudioQueue, this);
msg->setInt32("drainGeneration", mAudioDrainGeneration);
msg->post(delayUs);
}
使用 postDrainAudioQueue_l 发送消息前会先做判断,再决定是否要发送:
- 如果调用了暂停,那么需要等待 AudioTrack 完全停止才能做写入操作,所以消息需要做延时;
- 如果处在暂停延时或重写处理过程中、或者在 SyncQueue 处理过程中,又或者使用的 offload mode,将直接退出不再发送消息;
void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatDrainAudioQueue:
{
mDrainAudioQueuePending = false;
// 检查 generation
int32_t generation;
CHECK(msg->findInt32("drainGeneration", &generation));
if (generation != getDrainGeneration(true /* audio */)) {
break;
}
// 写入 audiotrack
if (onDrainAudioQueue()) {
uint32_t numFramesPlayed;
CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed),
(status_t)OK);
// ......
// 重写处理
postDrainAudioQueue_l(delayUs);
}
break;
}
}
}
kWhatDrainAudioQueue 的主要步骤如下:
- 检查 generation,判断是否需要停止写入数据到 AudioTrack;
- 调用 onDrainAudioQueue 将 List 中的数据全部写入到 AudioTrack;
- 如果 List 中的数据没有全部写完(ring buffer 写满),那么计算延时时间,调用 postDrainAudioQueue_l 重新发送延时消息等待写入。
bool NuPlayer::Renderer::onDrainAudioQueue() {
// do not drain audio during teardown as queued buffers may be invalid.
if (mAudioTornDown) {
return false;
}
// 获取当前已经播放的帧数
uint32_t numFramesPlayed;
mAudioSink->getPosition(&numFramesPlayed);
uint32_t prevFramesWritten = mNumFramesWritten;
while (!mAudioQueue.empty()) {
QueueEntry *entry = &*mAudioQueue.begin();
if (entry->mBuffer == NULL) {
// buffer 等于 null 会有两种情况,一种是 mNotifyConsumed 不等 null,另一种是 等于 null
if (entry->mNotifyConsumed != nullptr) {
// TAG for re-open audio sink.
onChangeAudioFormat(entry->mMeta, entry->mNotifyConsumed);
mAudioQueue.erase(mAudioQueue.begin());
continue;
}
// EOS
if (mPaused) {
// Do not notify EOS when paused.
// This is needed to avoid switch to next clip while in pause.
ALOGV("onDrainAudioQueue(): Do not notify EOS when paused");
return false;
}
int64_t postEOSDelayUs = 0;
if (mAudioSink->needsTrailingPadding()) {
postEOSDelayUs = getPendingAudioPlayoutDurationUs(ALooper::GetNowUs());
}
notifyEOS(true /* audio */, entry->mFinalResult, postEOSDelayUs);
mLastAudioMediaTimeUs = getDurationUsIfPlayedAtSampleRate(mNumFramesWritten);
mAudioQueue.erase(mAudioQueue.begin());
entry = NULL;
if (mAudioSink->needsTrailingPadding()) {
mAudioSink->stop();
mNumFramesWritten = 0;
}
return false;
}
mLastAudioBufferDrained = entry->mBufferOrdinal;
// 如果偏移量为 0,说明是一个全新的ouput buffer
if (entry->mOffset == 0 && entry->mBuffer->size() > 0) {
int64_t mediaTimeUs;
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
// 更新 AudioMediaTime
onNewAudioMediaTime(mediaTimeUs);
}
size_t copy = entry->mBuffer->size() - entry->mOffset;
// 将数据写入到 AudioTrack
ssize_t written = mAudioSink->write(entry->mBuffer->data() + entry->mOffset,
copy, false /* blocking */);
// 计算写入长度,并且做一些判断
entry->mOffset += written;
size_t remainder = entry->mBuffer->size() - entry->mOffset;
if ((ssize_t)remainder < mAudioSink->frameSize()) {
// 如果剩余的数据大于0,并且小于一帧音频的大小,那么就丢弃剩下的数据
if (remainder > 0) {
ALOGW("Corrupted audio buffer has fractional frames, discarding %zu bytes.",
remainder);
entry->mOffset += remainder;
copy -= remainder;
}
entry->mNotifyConsumed->post();
mAudioQueue.erase(mAudioQueue.begin());
entry = NULL;
}
// 记录写入的帧数
size_t copiedFrames = written / mAudioSink->frameSize();
mNumFramesWritten += copiedFrames;
{
// 计算最大可播放时间
Mutex::Autolock autoLock(mLock);
int64_t maxTimeMedia;
maxTimeMedia =
mAnchorTimeMediaUs +
(int64_t)(max((long long)mNumFramesWritten - mAnchorNumFramesWritten, 0LL)
* 1000LL * mAudioSink->msecsPerFrame());
mMediaClock->updateMaxTimeMedia(maxTimeMedia);
notifyIfMediaRenderingStarted_l();
}
if (written != (ssize_t)copy) {
CHECK_EQ(copy % mAudioSink->frameSize(), 0u);
ALOGV("AudioSink write short frame count %zd < %zu", written, copy);
break;
}
}
// calculate whether we need to reschedule another write.
// 当 List 数据不为空,并且 没有暂停或者是AudioTrack还没写满,尝试再次调用 postDrainAudioQueue_l 写入
bool reschedule = !mAudioQueue.empty()
&& (!mPaused
|| prevFramesWritten != mNumFramesWritten);
return reschedule;
}
这里把 onDrainAudioQueue 做的事情一一列举:
- 调用 AudioSink.getPosition 获取当前已经播放的 Audio 帧数,利用这个帧数与当前写入的帧数可以计算出现在可以写入多少帧音频数据;
- 如果 QueueEntry 中的 Buffer 为 NULL,说明收到了 EOS:
- 2.1. 如果 mNotifyConsumed 不为 NULL,说明是收到了码流不连续时间,需要用新的 format 重新启动 AudioTrack;
- 2.2. 如果 mNotifyConsumed 为 NULL,说明是上层调用了 queueEOS,这时候需要计算音频剩余可以播放多长时间,然后发送一条延时消息将 EOS 通知到 NuPlayer;
- 如果 Buffer 不为NULL 则要将数据拷贝到 AudioTrack,这里又分为两种情况:
- 4.1. 如果 Buffer 的 offset 不为 0,说明是上次没有拷贝完,这里要接着拷贝;
- 4.2. 如果 offset 为 0,说明是一块新的 output buffer,需要调用
onNewAudioMediaTime
用新的 timestamp 更新一些内容;
- 调用 AudioSink.write 将数据写入到 AudioTrack,如果剩余数据小于一帧音频数据的大小则剩余数据直接 drop,否则留到下次再拷贝;
- 更新 MediaClock 的最大媒体时长,并且发送开始 render 的事件 kWhatMediaRenderingStart 给 NuPlayer;
- 如果 List 不为空,并且没有暂停或者 AudioTrack的数据还没有写满,返回 true,尝试重新写入。
2.3、onNewAudioMediaTime
原文阅读:
Android Media Framework(十一)NuPlayer::Decoder
扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。