承接上一章节分析:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 12】【01】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
推荐涉及到的知识点:
Binder机制实现原理:Android C++底层Binder通信机制原理分析总结【通俗易懂】
ALooper机制实现原理:Android native层媒体通信架构AHandler/ALooper机制实现源码分析
Binder异常关闭监听:Android native层DeathRecipient对关联进程(如相关Service服务进程)异常关闭通知事件的监听实现源码分析
【此章节小节编号就接着上一章节排列】
onDrainAudioQueue()实现分析:
执行消耗音频队列处理流程,并返回是否处理成功,处理失败则丢弃它
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
bool NuPlayer::Renderer::onDrainAudioQueue() {
// do not drain audio during teardown as queued buffers may be invalid.
// 备注:mAudioTornDown该标志位是音频offload模式时才使用的,
// 它是在暂停超过3秒后会进行AudioSink的卸载过程,然后标记该值为true。
// 也就是说AudioSink在卸载过程中不应该提交Buffer给它播放。
// 而offload模式是采用AudioSink通过回调callback方式来主动拉取音频队列数据的,
// 因此此处为软音频解码器推送数据给AudioSink的write,不属于callback方式,因此此处理论上一直为false。
if (mAudioTornDown) {
// 丢弃本次处理请求
return false;
}
// TODO: This call to getPosition checks if AudioTrack has been created
// in AudioSink before draining audio. If AudioTrack doesn't exist, then
// CHECKs on getPosition will fail.
// We still need to figure out why AudioTrack is not created when
// this function is called. One possible reason could be leftover
// audio. Another possible place is to check whether decoder
// has received INFO_FORMAT_CHANGED as the first buffer since
// AudioSink is opened there, and possible interactions with flush
// immediately after start. Investigate error message
// "vorbis_dsp_synthesis returned -135", along with RTSP.
// 记录已播放音频帧数
uint32_t numFramesPlayed;
// 备注:有前面章节可知,mAudioSink的open在输出格式变化时open AudioSink初始化了。
// 此处最终将获取到AudioTrack、AudioFlinger模块维护的DSP帧数据计算可得的音频播放进度值
if (mAudioSink->getPosition(&numFramesPlayed) != OK) {
// 获取音频播放进度失败时
// 当getPosition失败时,渲染器不会重新调度(消耗)输出音频队列数据,除非新样本音频队列数据被入队。
// 如果我们有未处理的EOS(或“EOS”标记表示不连续),我们需要现在发送输出它们,
// 因为NuPlayerDecoder可能正在等待它。
// When getPosition fails, renderer will not reschedule the draining
// unless new samples are queued.
// If we have pending EOS (or "eos" marker for discontinuities), we need
// to post these now as NuPlayerDecoder might be waiting for it.
// (消耗)输出音频队列数据直到最后EOS数据
// 见4.1.1小节分析
// 备注:该方法主要功能就是,在音频渲染队列中循环查找、处理在EOS状态buffer之前的item buffer
// 并直接发送它们的所有回复应答消息并丢弃drop这些音频队列数据样本,并可能重新打开AudioSink进行工作。
drainAudioQueueUntilLastEOS();
// 表明该流程为AudioSink未准备好的情况下发生的
ALOGW("onDrainAudioQueue(): audio sink is not ready");
// 丢弃本次处理请求
return false;
}
// 获取音频播放进度成功时
#if 0
// debug代码块,默认不开放
// 计算可写音频帧数:AudioSink可接受总帧数减去【已写入帧数减已播放渲染帧】差
// 备注:frameCount()该值最终是AudioFlinger来配置的。
// 并且注意该值其实际表示的AudioSink可接受Buffer的最大值(字节数),虽然名字取得有点偏差...
ssize_t numFramesAvailableToWrite =
mAudioSink->frameCount() - (mNumFramesWritten - numFramesPlayed);
if (numFramesAvailableToWrite == mAudioSink->frameCount()) {
// 若相等则表示当前AudioSink没有写入数据即数据不足情况【underrun】
ALOGI("audio sink underrun");
} else {
// 否则此处计算出AudioSink中还剩余待播放音频帧数
ALOGV("audio queue has %d frames left to play",
mAudioSink->frameCount() - numFramesAvailableToWrite);
}
#endif
// 记录(此前)已写入AudioSink的音频帧数
uint32_t prevFramesWritten = mNumFramesWritten;
while (!mAudioQueue.empty()) {
// 音频队列不为空时,从队列起始item处理
QueueEntry *entry = &*mAudioQueue.begin();
// 备注:类似【QueueEntry】的这种处理逻辑判断,
// 可以参考【QueueEntry】结构声明时的英文注释,其已经解释清楚不同case情况如何处理了。
if (entry->mBuffer == NULL) {
// 实际负载buffer为空时
if (entry->mNotifyConsumed != nullptr) {
// mNotifyConsumed不为空时
// 重新打开AudioSink处理
// TAG for re-open audio sink.
onChangeAudioFormat(entry->mMeta, entry->mNotifyConsumed);
// 移除该音频队列项item
mAudioQueue.erase(mAudioQueue.begin());
// 继续下一个item处理
continue;
}
// 进入到此处时就是EOS状态处理了
// EOS
if (mPaused) {
// 若已暂停时,则不要通知EOS事件,并且也没有将该item数据从队列中移除,
// 也就是说下面用户恢复播放时还会继续该item数据数据,也就不会有问题。
// 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");
// 返回false处理失败
return false;
}
// 播放中
// EOS消息发送延迟时长
int64_t postEOSDelayUs = 0;
if (mAudioSink->needsTrailingPadding()) {
// AudioSink模块需要结尾(数据)填充时
// 获取需要等待AudioSink模块音频播放完毕时长
// 见4.1.2小节分析
postEOSDelayUs = getPendingAudioPlayoutDurationUs(ALooper::GetNowUs());
}
// 通知EOS状态
// 关于EOS状态流程处理此前流程中有说过目前暂不分析 TODO 后续有时间再分析吧
// 简单说一下流程:也就是它将会发送【kWhatEOS】事件消息通知NuPlayer
// 并且清除MediaClock音视频同步时钟,来让视频可以单独播放
notifyEOS(true /* audio */, entry->mFinalResult, postEOSDelayUs);
// 缓存最后一次音频媒体时间戳即当前音频已写入帧数(样本数)的总时长
mLastAudioMediaTimeUs = getDurationUsIfPlayedAtSampleRate(mNumFramesWritten);
// 移除该item并赋值为null
mAudioQueue.erase(mAudioQueue.begin());
entry = NULL;
if (mAudioSink->needsTrailingPadding()) {
// AudioSink模块需要结尾(数据)填充时
// 注:如果我们不是在无间隙播放(即通过setNextPlayer),我们需要在这里停止AudioSink、AudioTrack模块,
// 因为这将播放文件末尾的最后一点bit数据。否则,短文件将无法播放。
// If we're not in gapless playback (i.e. through setNextPlayer), we
// need to stop the track here, because that will play out the last
// little bit at the end of the file. Otherwise short files won't play.
// 直接stop AudioSink、AudioTrack、AudioFlinger
mAudioSink->stop();
// 重置为0
mNumFramesWritten = 0;
}
// 返回false
return false;
}
// 实际负载音频数据存在时
// 【最后消耗的音频Buffer序列号】缓存当前队列项item的buffer序列号
mLastAudioBufferDrained = entry->mBufferOrdinal;
// 注:忽略0大小的Buffer,它可能是没有数据的EOS标记的数据
// ignore 0-sized buffer which could be EOS marker with no data
if (entry->mOffset == 0 && entry->mBuffer->size() > 0) {
// 有效音频负载数据时,即数据读取偏移量为0且负载buffer数据大小大于0
int64_t mediaTimeUs;
// 获取当前音频buffer的媒体时间戳
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
ALOGV("onDrainAudioQueue: rendering audio at media time %.2f secs",
mediaTimeUs / 1E6);
// 回调执行新音频buffer媒体时间戳处理流程
// 见4.1.3小节分析
onNewAudioMediaTime(mediaTimeUs);
}
// 计算有效负载可读数据【拷贝】大小
// 备注:数据单位为字节
size_t copy = entry->mBuffer->size() - entry->mOffset;
// 重要处理:此处将是主动推送音频队列数据给AudioSink写入:
// 传入数据为当前buffer读取位置(地址)指针,拷贝数据大小,非阻塞模式。
// 返回值为AudioSink本次已写入的音频数据大小
ssize_t written = mAudioSink->write(entry->mBuffer->data() + entry->mOffset,
copy, false /* blocking */);
if (written < 0) {
// 写入失败时,有可能是AudioSink没有正确打开
// An error in AudioSink write. Perhaps the AudioSink was not properly opened.
if (written == WOULD_BLOCK) {
// AudioSink写入数据时会阻塞
ALOGV("AudioSink write would block when writing %zu bytes", copy);
} else {
// AudioSink写入数据时发生错误
ALOGE("AudioSink write error(%zd) when writing %zu bytes", written, copy);
// 注:这只会在使用doNotReconnect标志被设置为true情况下AudioSink打开时发生,
// 在这种情况下,NuPlayer将处理重新连接AudioSink流程。
// This can only happen when AudioSink was opened with doNotReconnect flag set to
// true, in which case the NuPlayer will handle the reconnect.
// 该情况如前面已有阐述,暂不分析它 TODO
notifyAudioTearDown(kDueToError);
}
// 写入失败时直接退出
break;
}
// 更新可读数据偏移量:加上已读数据大小
entry->mOffset += written;
// 计算剩余可读数据大小
size_t remainder = entry->mBuffer->size() - entry->mOffset;
// 注意此处if判断逻辑处理的作用:也就是每一帧音频数据都被AudioSink(一次或可能多次)读取(快)结束时,
// 开始移除该帧buffer,进行下一次循环读取下一个新buffer的数据,
// 否则代表着该buffer还有可供AudioSink(一次或可能多次)读取的完整数据继续下一次循环读取。
// 备注:此处有个重要概念, frameSize表明意思是帧大小,实际在音频数据中表示每帧音频采样(多声道对应的总)字节数。
// 简单的总结就是:
// 对于线性存储的PCM数据,该值为每个采样点的多声道对应的总字节数据,计算为channelCount * (bit depth per channel / 8);
// 对于非线性存储格式时,该值默认为1字节。
if ((ssize_t)remainder < mAudioSink->frameSize()) {
// 剩余数据大小小于AudioSink每帧大小时
if (remainder > 0) {
// 大于0时,实际就是AudioSink本来可以一次性读取完整个Buffer的,
// 但它没读完,因此此处判断AudioSink可能认为这是损坏数据,
// 因此损坏的音频负载数据中有部分帧,丢弃这些损坏字节。
ALOGW("Corrupted audio buffer has fractional frames, discarding %zu bytes.",
remainder);
// 因此直接修改为数据被读取完毕情况
entry->mOffset += remainder;
copy -= remainder;
}
// 由前面流程可知,此处mNotifyConsumed参数为此前已消耗该Buffer完成通知事件消息
// 即NuPlayerDecoder接收的【kWhatRenderBuffer】已渲染Buffer事件应答消息。
// 直接发送该【已消耗完成通知】事件消息给NuPlayerDecoder
// 见4.1.4小节分析
entry->mNotifyConsumed->post();
// 移除音频已渲染Buffer
mAudioQueue.erase(mAudioQueue.begin());
// 赋值为null
entry = NULL;
}
// 计算已【拷贝】帧数
size_t copiedFrames = written / mAudioSink->frameSize();
// 更新已写入帧数
mNumFramesWritten += copiedFrames;
// 加锁代码块
{
Mutex::Autolock autoLock(mLock);
int64_t maxTimeMedia;
// mAnchorTimeMediaUs为当前最新音频锚点媒体时间。
// msecsPerFrame()该方法为AudioSink计算的一帧音频播放时长(毫秒)
// 计算当前(已写入AudioSink)音频最大媒体时间:mAnchorTimeMediaUs 再加上 当前新写入帧的总播放时长
maxTimeMedia =
mAnchorTimeMediaUs +
(int64_t)(max((long long)mNumFramesWritten - mAnchorNumFramesWritten, 0LL)
* 1000LL * mAudioSink->msecsPerFrame());
// 更新当前同步时钟中媒体最大时间,即当前(已写入AudioSink)音频最大媒体时间
// 见4.1.5小节分析
mMediaClock->updateMaxTimeMedia(maxTimeMedia);
// 判断通知媒体已渲染事件
// 见4.1.6小节分析
notifyIfMediaRenderingStarted_l();
}
if (written != (ssize_t)copy) {
// 若AudioSink本次已写入音频数据大小和当前Buffer有效负载数据实际大小不等时
// 此时表示AudioSink不能一次性读完当前Buffer中所有的音频帧数据。
// 注释就不一一翻译,请自行阅读
// A short count was received from AudioSink::write()
//
// AudioSink write is called in non-blocking mode.
// It may return with a short count when:
//
// 注:要复制的大小不是帧大小的倍数,部分帧被丢弃。
// 1) Size to be copied is not a multiple of the frame size. Fractional frames are
// discarded.
// 要复制的数据超出AudioSink中的可用Buffer的大小
// 2) The data to be copied exceeds the available buffer in AudioSink.
// 错误发生,数据被部分复制到AudioSink的Buffer中
// 3) An error occurs and data has been partially copied to the buffer in AudioSink.
// AudioSink是一个用于数据检索的AudioCache,并且超出了AudioCache的存储大小
// 4) AudioSink is an AudioCache for data retrieval, and the AudioCache is exceeded.
// (Case 1)
// 注:必须是帧大小的倍数。如果它不是帧大小的倍数,需要处理为失败,
// 因为我们不应该在调用之间携带部分帧(即帧的一部分),
// (注意此处的判断处理为取余运算符计算:它实际就是copy的值必须是frameSize的倍数,
// 否则都不应该发生,会直接抛出crash)。
// Must be a multiple of the frame size. If it is not a multiple of a frame size, it
// needs to fail, as we should not carry over fractional frames between calls.
CHECK_EQ(copy % mAudioSink->frameSize(), 0u);
// (Case 2, 3, 4)
// 提前返回给调用端(此处)
// Return early to the caller.
// 注意不要立即再次调用,因为如果你不小心的话,这可能会busy忙碌循环。
// Beware of calling immediately again as this may busy-loop if you are not careful.
ALOGV("AudioSink write short frame count %zd < %zu", written, copy);
// 退出写入数据
break;
}
}
// 本次写入数据结束
// calculate whether we need to reschedule another write.
// 计算是否需要重新安排(调度)另一次写入流程。
// 条件为:音频队列不为空,并且当未暂停或前后两次写入总帧数不相等时
// 备注:写入总帧数不相等表示本次写入了数据,若相等表明本次没写入一帧数据(可能发生错误了),
// 因此也就没有必要再次自动写入,但此处也会在暂停状态下允许填充音频Buffer。
bool reschedule = !mAudioQueue.empty()
&& (!mPaused
|| prevFramesWritten != mNumFramesWritten); // permit pause to fill buffers
//ALOGD("reschedule:%d empty:%d mPaused:%d prevFramesWritten:%u mNumFramesWritten:%u",
// reschedule, mAudioQueue.empty(), mPaused, prevFramesWritten, mNumFramesWritten);
// 返回值则表示为是否需要重新安排(调度)另一次写入音频数据处理流程,true则需要
return reschedule;
}
4.1.1、drainAudioQueueUntilLastEOS()实现分析:
(消耗)输出音频队列数据直到最后EOS数据
备注:该方法主要功能就是,在音频渲染队列中循环查找、处理在EOS状态buffer之前的item buffer并直接发送它们的所有回复应答消息并丢弃drop这些音频队列数据样本,并可能重新打开AudioSink进行工作。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::drainAudioQueueUntilLastEOS() {
// 音频渲染队列起始item
List<QueueEntry>::iterator it = mAudioQueue.begin(), itEOS = it;
bool foundEOS = false;
// while循环查找当前音频队列中是否有EOS状态Buffer数据
while (it != mAudioQueue.end()) {
// 还有数据时
int32_t eos;
// 注意此处的【&*it++】看起来很复杂唬人,其实际计算过程是这样的【(&(*it))++】
// 先计算 entry = &(*it); 然后 it++
// 即也就是先得到it未增加即当前it对象地址(即赋给指针)后,再让it增加即指向下一个item地址
QueueEntry *entry = &*it++;
if ((entry->mBuffer == nullptr && entry->mNotifyConsumed == nullptr)
|| (entry->mNotifyConsumed->findInt32("eos", &eos) && eos != 0)) {
// 若当前item没有负载buffer并且也米有【mNotifyConsumed】,或者mNotifyConsumed设置了eos状态且值不为0时
// 表明当前Buffer为EOS状态buffer
// 记录并标记它
itEOS = it;
foundEOS = true;
}
}
if (foundEOS) {
// 查询到EOS状态buffer时
// 在EOS状态buffer之前的item buffer,必须发送它们的所有回复消息并丢弃drop这些音频队列数据样本
// post all replies before EOS and drop the samples
// 循环处理在EOS状态buffer之前的item buffer
for (it = mAudioQueue.begin(); it != itEOS; it++) {
if (it->mBuffer == nullptr) {
// 该item buffer的实际音频负载数据为空时
if (it->mNotifyConsumed == nullptr) {
// mNotifyConsumed也为空时,表示此为eos状态的buffer
// 延迟时长并不重要,因为我们甚至都没有AudioTrack
// delay doesn't matter as we don't even have an AudioTrack
// 通知EOS状态
// 关于EOS状态流程处理此前流程中有说过目前暂不分析 TODO 后续有时间再分析吧
// 简单说一下流程:也就是它将会发送【kWhatEOS】事件消息通知NuPlayer
// 并且清除MediaClock音视频同步时钟,来让视频可以单独播放
notifyEOS(true /* audio */, it->mFinalResult);
} else {
// mNotifyConsumed不为空时,需要重打开AudioSink
// 由前面流程可知,此处mNotifyConsumed参数为Decoder接收的
// 【kWhatAudioOutputFormatChanged】音频输出格式改变处理完成事件通知消息。
// TAG for re-opening audio sink.
// 回调执行音频输出格式改变处理流程
// 见前面章节分析
onChangeAudioFormat(it->mMeta, it->mNotifyConsumed);
}
} else {
// 音频实际负载数据不为空时
// 由前面流程可知,此处mNotifyConsumed参数为此前已消耗该Buffer完成通知事件消息
// 即NuPlayerDecoder接收的【kWhatRenderBuffer】已渲染Buffer事件应答消息。
// 直接发送该【已消耗完成通知】事件消息给NuPlayerDecoder
// 见后续流程中将会分析到的
it->mNotifyConsumed->post();
}
}
// 移除中间已处理完的废弃item数据
mAudioQueue.erase(mAudioQueue.begin(), itEOS);
}
}
4.1.2、getPendingAudioPlayoutDurationUs(ALooper::GetNowUs())实现分析:
AudioSink模块需要结尾(数据)填充时,获取等待音频播放完毕时长
参数为当前系统已开机时长
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
// 计算在常规普通播放速率(如1.0)下的待播放音频帧样本总时长,也就是不考虑减速或加速播放情况
// Calculate duration of pending samples if played at normal rate (i.e., 1.0).
int64_t NuPlayer::Renderer::getPendingAudioPlayoutDurationUs(int64_t nowUs) {
// 获取使用普通采样率播放的时长:计算方式是采样率乘以已写入AudioSink帧数即样本数
// 音频已写入帧数(样本数)的总时长
// 见下面分析
int64_t writtenAudioDurationUs = getDurationUsIfPlayedAtSampleRate(mNumFramesWritten);
if (mUseVirtualAudioSink) {
// 使用虚拟AudioSink时,该标志是在后续【onNewAudioMediaTime】流程中设置的,见后续分析
// 备注:其实就是此时AudioSink中记录已播放时长不可用时需要Renderer此处自己计算已播放时长等
// 当前系统已开机时长
int64_t nowUs = ALooper::GetNowUs();
int64_t mediaUs;
// 获取当前音频已播放时长即媒体时间戳播放进度
// 备注:该方法实现分析将在这后面流程中分析
if (mMediaClock->getMediaTime(nowUs, &mediaUs) != OK) {
// 获取失败时返回0
return 0LL;
} else {
// 获取成功时,计算等待剩余音频数据播放完毕总时长:
// 音频已写入帧数(样本数)的总时长减去【当前媒体时间戳播放进度 减 音频第一次播放的锚点媒体时间戳】的差
// 注意此处为什么这么计算的原因是:这三个数值都只是在当次播放状态下有关联的相对时长,
// 因此必须这样计算相对播放时长来获取到【剩余音频数据播放完毕总时长】。
// 即(mediaUs - mAudioFirstAnchorTimeMediaUs)这个计算就是当次播放状态下本次音频播放时长,而不是播放进度的值。
// 而writtenAudioDurationUs的值也是当次播放状态下写入AudioSink帧数(样本数)的总时长。
return writtenAudioDurationUs - (mediaUs - mAudioFirstAnchorTimeMediaUs);
}
}
// 未使用虚拟AudioSink时
// 直接AudioSink来获取已播放时长,这个计算是在AudioSink、AudioTrack那边维护计算的。
const int64_t audioSinkPlayedUs = mAudioSink->getPlayedOutDurationUs(nowUs);
// 计算待播放完毕时长:音频已写入帧数(样本数)的总时长 减去 已播放帧时长
int64_t pendingUs = writtenAudioDurationUs - audioSinkPlayedUs;
if (pendingUs < 0) {
// 这不应该发生,除非这时间戳有误
// This shouldn't happen unless the timestamp is stale.
ALOGW("%s: pendingUs %lld < 0, clamping to zero, potential resume after pause "
"writtenAudioDurationUs: %lld, audioSinkPlayedUs: %lld",
__func__, (long long)pendingUs,
(long long)writtenAudioDurationUs, (long long)audioSinkPlayedUs);
// 重置为0
pendingUs = 0;
}
return pendingUs;
}
getDurationUsIfPlayedAtSampleRate(mNumFramesWritten)实现分析:
获取使用普通采样率播放的时长:计算方式是采样率乘以已写入AudioSink帧数即音频样本数
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
int64_t NuPlayer::Renderer::getDurationUsIfPlayedAtSampleRate(uint32_t numFrames) {
// 通过判断支持offload模式来获取不同配置信息中缓存的普通采样率
int32_t sampleRate = offloadingAudio() ?
mCurrentOffloadInfo.sample_rate : mCurrentPcmInfo.mSampleRate;
if (sampleRate == 0) {
// 为0采样率...
ALOGE("sampleRate is 0 in %s mode", offloadingAudio() ? "offload" : "non-offload");
return 0;
}
// 有效时
// 计算音频已写入帧数(样本数)的总时长:已写入帧数(样本数)除以采样率,并乘以1000000来转换单位为微妙
return (int64_t)(numFrames * 1000000LL / sampleRate);
}
4.1.3、onNewAudioMediaTime(mediaTimeUs)
回调执行新音频buffer媒体时间戳处理流程,其实际就是更新当前音视频同步机制MediaClock同步时钟的相关信息
参数为新音频buffer媒体时间
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::onNewAudioMediaTime(int64_t mediaTimeUs) {
// 加锁访问
Mutex::Autolock autoLock(mLock);
// 棘手问题:vorbis(ogg)解码器生成多个帧具有相同的时间戳,所以只更新给定时间戳的第一个帧
// TRICKY: vorbis decoder generates multiple frames with the same
// timestamp, so only update on the first frame with a given timestamp
if (mAnchorTimeMediaUs > 0 && mediaTimeUs == mAnchorTimeMediaUs) {
// 若全局锚点媒体时间有效并且和最新音频数据的媒体时间相同时则不更新MediaClock
return;
}
// 有必须的话设置音频第一个锚点媒体时间戳
// 见4.1.3.1小节分析
setAudioFirstAnchorTimeIfNeeded_l(mediaTimeUs);
// mNextAudioClockUpdateTimeUs下一帧音频时钟更新系统时间若为-1(无效)值时
// 需要等待AudioSink的启动工作
// 备注:该值是系统已开机时长基础上添加估算的等待下一帧时长,非音频媒体播放进度值
// mNextAudioClockUpdateTimeUs is -1 if we're waiting for audio sink to start
if (mNextAudioClockUpdateTimeUs == -1) {
// -1时 从AudioSink获取其计算的音频播放进度【当前帧媒体时间位置】时间信息
AudioTimestamp ts;
if (mAudioSink->getTimestamp(ts) == OK && ts.mPosition > 0) {
// 成功并大于0时
// 赋值为0,启动我们的【下一帧音频时钟更新系统时间】时钟(计时)更新
mNextAudioClockUpdateTimeUs = 0; // start our clock updates
}
}
// 当前系统已开机时长
int64_t nowUs = ALooper::GetNowUs();
if (mNextAudioClockUpdateTimeUs >= 0) {
// 该值大于等于0时
if (nowUs >= mNextAudioClockUpdateTimeUs) {
// 当前系统已开机时长大于等于该值时
// 计算音频当前已播放的媒体进度(位置)值:新音频buffer媒体时间 减去 【等待AudioSink模块音频播放完毕时长】
int64_t nowMediaUs = mediaTimeUs - getPendingAudioPlayoutDurationUs(nowUs);
// 更新音视频同步时钟锚点时间
// 见4.1.3.2小节分析
mMediaClock->updateAnchor(nowMediaUs, nowUs, mediaTimeUs);
// 缓存这些值
mAnchorTimeMediaUs = mediaTimeUs;
mAnchorNumFramesWritten = mNumFramesWritten;
// 使用虚拟AudioSink标记设置为false
mUseVirtualAudioSink = false;
// kMinimumAudioClockUpdatePeriodUs表示音频时钟更新间隔最小时间,默认为20毫秒
// 更新【下一帧音频时钟更新系统时间】:当前系统已开机时长 加上 20毫秒
mNextAudioClockUpdateTimeUs = nowUs + kMinimumAudioClockUpdatePeriodUs;
}
// 重要NOTE:这一点处理逻辑非常重要!
// 小于时将不处理,即不更新锚点时间信息,
// 也就是说正常情况下,锚点时间信息将会在20毫秒之后才更新一次
} else {
// mNextAudioClockUpdateTimeUs下一帧音频时钟更新系统时间若为无效(-1)值时
int64_t unused;
// 获取当前音频已播放时长即媒体时间戳播放进度【该值未被使用】
// 备注:该方法实现分析将在这后面流程中分析
if ((mMediaClock->getMediaTime(nowUs, &unused) != OK)
&& (getDurationUsIfPlayedAtSampleRate(mNumFramesWritten)
> kMaxAllowedAudioSinkDelayUs)) {
// kMaxAllowedAudioSinkDelayUs表示:从AudioSink中最大允许延迟时长,默认为1.5秒。
// 获取播放进度失败并且获取的当前音频已写入帧数(样本数)总时长大于1.5秒时
// 即当前同步时钟发生异常时
// 注:足够的音频数据已经发送到AudioSink,
// 但AudioSink还没有渲染任何数据。AudioSink发生异常有问题,
// 例如,音频输出设备没有连接到音频输出端。
// 切换到系统时钟来同步播放。这本质上使用getDurationUsIfPlayedAtSampleRated的初始延迟来创建一个虚拟AudioSink。
// 这个虚拟AudioSink渲染音频数据从第一个样本开始,它是由系统时钟决定的。
// Enough data has been sent to AudioSink, but AudioSink has not rendered
// any data yet. Something is wrong with AudioSink, e.g., the device is not
// connected to audio out.
// Switch to system clock. This essentially creates a virtual AudioSink with
// initial latenty of getDurationUsIfPlayedAtSampleRate(mNumFramesWritten).
// This virtual AudioSink renders audio data starting from the very first sample
// and it's paced by system clock.
ALOGW("AudioSink stuck. ARE YOU CONNECTED TO AUDIO OUT? Switching to system clock.");
// 再次执行更新同步时钟锚点时间信息
// 注意和上面不同的是第一个参数,此处为【本次开始播放的音频第一个锚点媒体时间戳】,
// 而上面为【音频当前已播放的媒体进度(位置)值】
mMediaClock->updateAnchor(mAudioFirstAnchorTimeMediaUs, nowUs, mediaTimeUs);
// 缓存值
mAnchorTimeMediaUs = mediaTimeUs;
mAnchorNumFramesWritten = mNumFramesWritten;
// 使用虚拟AudioSink标记设置为true
mUseVirtualAudioSink = true;
}
}
}
4.1.3.1、setAudioFirstAnchorTimeIfNeeded_l(mediaTimeUs)实现分析:
有必须的话设置本次开始播放的音频第一个锚点媒体时间戳
参数为新音频buffer媒体时间
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::setAudioFirstAnchorTimeIfNeeded_l(int64_t mediaUs) {
if (mAudioFirstAnchorTimeMediaUs == -1) {
// 此前为-1即无效值时,将会初始化该值记录为本次开始播放的音频第一个锚点媒体时间戳
mAudioFirstAnchorTimeMediaUs = mediaUs;
// 也将该值设置给音视频同步时钟MediaClock去记录它
mMediaClock->setStartingTimeMedia(mediaUs);
}
}
mMediaClock->setStartingTimeMedia(mediaUs)实现分析:
将【本次开始播放的音频第一个锚点媒体时间戳】设置给音视频同步时钟MediaClock去记录它
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::setStartingTimeMedia(int64_t startingTimeMediaUs) {
// 加锁并缓存到【mStartingTimeMediaUs】开始播放媒体时间全局变量中
Mutex::Autolock autoLock(mLock);
mStartingTimeMediaUs = startingTimeMediaUs;
}
4.1.3.2、mMediaClock->updateAnchor(nowMediaUs, nowUs, mediaTimeUs):
更新音视频同步时钟锚点时间信息
anchorTimeMediaUs锚点媒体时间戳:为计算得到的音频当前最新已播放的媒体进度(位置)值
anchorTimeRealUs锚点真实时间戳:当前系统已开机时长
maxTimeMediaUs当前媒体最大时间:即当前最新音频Buffer数据的PTS媒体时间戳
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::updateAnchor(
int64_t anchorTimeMediaUs,
int64_t anchorTimeRealUs,
int64_t maxTimeMediaUs) {
if (anchorTimeMediaUs < 0 || anchorTimeRealUs < 0) {
// 若这两个值有一个为0,则无效
ALOGW("reject anchor time since it is negative.");
return;
}
// 有效值时
// 加锁访问
Mutex::Autolock autoLock(mLock);
// 当前系统已开机时长
int64_t nowUs = ALooper::GetNowUs();
// 再次计算音频当前已播放的媒体进度(位置)值
// 注意该值和传入参数【anchorTimeMediaUs】这个值意思表示一样的,
// 但其不同之处在于,此处的【nowMediaUs】加上了不同速率播放的从上一个系统开机时长
// 减去当前系统已开机时长的播放速率乘积,也就是在上一个媒体进度值进入此处执行时
// 重新加上了这中间的系统时间流逝偏差,并考虑了播放速率处理。
int64_t nowMediaUs =
anchorTimeMediaUs + (nowUs - anchorTimeRealUs) * (double)mPlaybackRate;
if (nowMediaUs < 0) {
// 无效时
ALOGW("reject anchor time since it leads to negative media time.");
return;
}
if (maxTimeMediaUs != -1) {
// 当前媒体最大时间不为-1时,缓存到全局变量中
// 备注:也就是说【mMaxTimeMediaUs】全局变量记录的就是 当前最新音频Buffer数据的PTS媒体时间戳
mMaxTimeMediaUs = maxTimeMediaUs;
}
if (mAnchorTimeRealUs != -1) {
// 全局变量锚点真实时间戳此前不为-1时【也就是记录的当时系统已开机时长】
// 计算旧的【音频当前已播放的媒体进度(位置)值】
// 也就是说此处计算是通过此前MediaClock全局缓存的锚点时间信息来计算得到当前音频应该预期的已播放进度值。
int64_t oldNowMediaUs =
mAnchorTimeMediaUs + (nowUs - mAnchorTimeRealUs) * (double)mPlaybackRate;
// kAnchorFluctuationAllowedUs常量表示锚点时间波动允许时长,
// 即从锚点更新之后,时间向后的最大允许时间。如果大于这个阈值,就被视为音频帧不连续,默认为10毫秒。
// 备注:也就是说若音频前后两帧时间戳PTS大于10毫秒,则认为是不连续帧,若在10毫秒内则认为是连续帧。
// 该判断处理流程非常重要,若该if条件不成立,那么if之后的处理将会引起视频以音频时间戳为同步时
// 应该需要渲染视频同步问题,即将会放弃上一个视频帧定时渲染事件。
if (nowMediaUs < oldNowMediaUs + kAnchorFluctuationAllowedUs
&& nowMediaUs > oldNowMediaUs - kAnchorFluctuationAllowedUs) {
// 执行此处表示,当前最新音频前后两帧为连续帧,因此不需要更新同步时钟锚点信息
// 备注:大多数理想情况下都将会进入此处,若音频数据有损坏则将进行该if后面的更新锚点信息处理
return;
}
// 非连续音频帧时
}
// mAnchorTimeRealUs即全局变量锚点真实时间戳此前为-1时,也需要更新锚点信息处理
// 更新锚点时间信息和播放速率处理
// 见下面分析
// 备注:其实该处理流程在早前章节中有过分析,此处再分析下吧
updateAnchorTimesAndPlaybackRate_l(nowMediaUs, nowUs, mPlaybackRate);
// MediaClock内部全局代数值递增1,也就是使当前正要执行的视频渲染事件放弃当次渲染请求
++mGeneration;
// 执行视频渲染请求事件定时器
// 该处理流程将放在后面视频输出渲染流程中分析
processTimers_l();
}
updateAnchorTimesAndPlaybackRate_l(nowMediaUs, nowUs, mPlaybackRate)实现分析:
更新锚点时间信息和播放速率处理
备注:其实该处理流程在早前章节中有过分析,此处再分析下吧
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::updateAnchorTimesAndPlaybackRate_l(int64_t anchorTimeMediaUs,
int64_t anchorTimeRealUs, float playbackRate) {
// 也就是这几个参数对应的全局缓存变量若有不相同的值才允许更新,否则都相同时不更新处理
if (mAnchorTimeMediaUs != anchorTimeMediaUs
|| mAnchorTimeRealUs != anchorTimeRealUs
|| mPlaybackRate != playbackRate) {
// 有不相同的值缓存更新锚点时间
mAnchorTimeMediaUs = anchorTimeMediaUs;
mAnchorTimeRealUs = anchorTimeRealUs;
mPlaybackRate = playbackRate;
// 通知该非连续帧事件
notifyDiscontinuity_l();
}
}
notifyDiscontinuity_l()实现分析:
通知该非连续帧事件
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::notifyDiscontinuity_l() {
if (mNotify != nullptr) {
// 有早前初始化章节可知,mNotify该通知消息为NuPlayer接收的【kWhatMediaClockNotify】事件消息
sp<AMessage> msg = mNotify->dup();
// 传递非连续帧事件的新锚点信息
msg->setInt64("anchor-media-us", mAnchorTimeMediaUs);
msg->setInt64("anchor-real-us", mAnchorTimeRealUs);
msg->setFloat("playback-rate", mPlaybackRate);
msg->post();
}
}
NuPlayer接收的【kWhatMediaClockNotify】事件消息处理:
下面实现很简单,就是将参数封装到【Parcel】对象中,最终通知NuPlayerDriver等上层模块该事件
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatMediaClockNotify:
{
ALOGV("kWhatMediaClockNotify");
int64_t anchorMediaUs, anchorRealUs;
float playbackRate;
CHECK(msg->findInt64("anchor-media-us", &anchorMediaUs));
CHECK(msg->findInt64("anchor-real-us", &anchorRealUs));
CHECK(msg->findFloat("playback-rate", &playbackRate));
Parcel in;
in.writeInt64(anchorMediaUs);
in.writeInt64(anchorRealUs);
in.writeFloat(playbackRate);
notifyListener(MEDIA_TIME_DISCONTINUITY, 0, 0, &in);
break;
}
}
}
4.1.4、已消耗该Buffer完成通知事件消息接收处理:
NuPlayerDecoder接收的【kWhatRenderBuffer】已渲染Buffer完成事件应答消息
备注:此时此处的已渲染Buffer指的是音频Buffer,而当该流程处理视频Buffer时视频帧却还未真正渲染,因此此流程就是处理视频帧渲染流程。
因此看此处处理逻辑时,建议先看完上面音频处理流程和后面【4.2】小节的视频消耗渲染处理流程后,再来统一看此处处理。
由于该部分分析较长,因此单独拎出后续章节分析,请查看:
【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 13】
4.1.5、mMediaClock->updateMaxTimeMedia(maxTimeMedia)实现分析:
更新当前同步时钟中媒体最大时间,即当前(已写入AudioSink)音频最大媒体时间
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::updateMaxTimeMedia(int64_t maxTimeMediaUs) {
Mutex::Autolock autoLock(mLock);
mMaxTimeMediaUs = maxTimeMediaUs;
}
4.1.6、notifyIfMediaRenderingStarted_l()实现分析:
判断通知媒体已渲染事件
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::notifyIfMediaRenderingStarted_l() {
// 由前面流程可知,在准备播放之前,这两种消耗(渲染)代数值都会被置为相同值,即为了准备播放而设置的
if (mVideoRenderingStartGeneration == mVideoDrainGeneration &&
mAudioRenderingStartGeneration == mAudioDrainGeneration) {
// 相同时
// 设置已递交渲染数据标识为true
mRenderingDataDelivered = true;
if (mPaused) {
// 暂停状态时则不再往下处理,即先不通知
return;
}
// 播放过程中将其设置为-1,避免后续重复上报该媒体渲染开始事件
mVideoRenderingStartGeneration = -1;
mAudioRenderingStartGeneration = -1;
// 根据早前章节分析,mNotify该通知消息为NuPlayer接收的【kWhatRendererNotify】渲染通知事件消息
sp<AMessage> notify = mNotify->dup();
// 设置子事件类型为媒体渲染开始事件【kWhatMediaRenderingStart】
notify->setInt32("what", kWhatMediaRenderingStart);
notify->post();
}
}
NuPlayer接收【kWhatRendererNotify】渲染通知事件消息处理:
其实【kWhatRendererNotify】该事件此前分析过它的另一个子事件类型。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatRendererNotify:
{
// 判断当前Renderer的代数值是否一致,不一致则忽略它的请求
int32_t requesterGeneration = mRendererGeneration - 1;
CHECK(msg->findInt32("generation", &requesterGeneration));
if (requesterGeneration != mRendererGeneration) {
ALOGV("got message from old renderer, generation(%d:%d)",
requesterGeneration, mRendererGeneration);
return;
}
int32_t what;
CHECK(msg->findInt32("what", &what));
if (what == Renderer::kWhatEOS) {
} else if (what == Renderer::kWhatMediaRenderingStart) {
// 此处最终将会通知上层APP媒体渲染开始事件,不再展开分析
ALOGV("media rendering started");
notifyListener(MEDIA_STARTED, 0, 0);
}
}
}
}
4.2、postDrainVideoQueue()实现分析:
执行消耗视频队列处理
由于本章节接下来内容篇幅过长,因此必须放入另一章节分析,请查看:
【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 12】【03】