承接上一章节分析:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 12】【02】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
推荐涉及到的知识点:
Binder机制实现原理:Android C++底层Binder通信机制原理分析总结【通俗易懂】
ALooper机制实现原理:Android native层媒体通信架构AHandler/ALooper机制实现源码分析
Binder异常关闭监听:Android native层DeathRecipient对关联进程(如相关Service服务进程)异常关闭通知事件的监听实现源码分析
【此章节小节编号就接着上一章节排列】
4.2、postDrainVideoQueue()实现分析:
执行消耗视频队列处理
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
// 该方法调用时未加锁
// Called without mLock acquired.
void NuPlayer::Renderer::postDrainVideoQueue() {
// 【mDrainVideoQueuePending】标志位表示当前是否已有正在执行该流程的处理,
// 有的话将忽略此处处理,因为不需要多次发起视频buffer消费。
// getSyncQueues()该方法返回mSyncQueues,始终为false。
// 已暂停状态并且已接受处理了至少一帧视频时也不需要多次发起消耗请求。
if (mDrainVideoQueuePending
|| getSyncQueues()
|| (mPaused && mVideoSampleReceived)) {
// 不需要多次发起视频buffer消费事件处理
return;
}
// 需要发起消耗请求
if (mVideoQueue.empty()) {
// 视频渲染队列为空
return;
}
// 取起始视频队列项item
QueueEntry &entry = *mVideoQueue.begin();
// 创建【kWhatDrainVideoQueue】消耗视频队列数据事件,并设置当前视频消费代数值
sp<AMessage> msg = new AMessage(kWhatDrainVideoQueue, this);
msg->setInt32("drainGeneration", getDrainGeneration(false /* audio */));
if (entry.mBuffer == NULL) {
// 实际视频负载数据为空时,表示EOS不携带时间戳的空Buffer
// EOS doesn't carry a timestamp.
// 发送该视频消耗渲染事件消息,见后续涉及流程中分析
msg->post();
// 标记当前【kWhatDrainVideoQueue】消耗视频队列数据事件流程已发起处理
mDrainVideoQueuePending = true;
return;
}
// 实际视频负载数据不为空时
// 注:当我们准备好发送消息事件去消费视频buffer时,立即通知preroll视频预滚完成,
// 这样NuPlayer可以尽早地唤醒渲染器去恢复AudioSink,因为AudioSink恢复工作有延迟。
// notify preroll completed immediately when we are ready to post msg to drain video buf, so that
// NuPlayer could wake up renderer early to resume AudioSink since audio sink resume has latency
// 备注:此处就是处理此前提到过的NuPlayer创建音视频解码器后,
// 若此时视频帧还未接收到,则将会立即暂停Render(即暂停AudioSink)来等待视频接收到第一帧数据时的唤醒通知事件。
// 另外主要:当时阐述过该流程会有一个bug问题,此处不再阐述
if (mPaused && !mVideoSampleReceived) {
// 已暂停并且此前还未接收到视频帧时
// 根据早前章节分析,mNotify该通知消息为NuPlayer接收的【kWhatRendererNotify】渲染通知事件消息
sp<AMessage> notify = mNotify->dup();
// 设置子事件即视频预滚完成唤醒Renderer通知事件
notify->setInt32("what", kWhatVideoPrerollComplete);
ALOGI("NOTE: notifying video preroll complete");
// 发送该通知唤醒事件
// TODO:目前暂不分析该事件,实际上它只是执行【mRenderer->resume()】即唤醒Renderer重新进行渲染工作
// 也会唤醒AudioSink工作。 后续有时间考虑吧
notify->post();
}
// 系统时间
int64_t nowUs = ALooper::GetNowUs();
// mFlags该标记位实际上就是标记是否为offload音频播放模式
if (mFlags & FLAG_REAL_TIME) {
// 非offload模式时,将采用(系统流逝)真实(世界)时间来计算
int64_t realTimeUs;
CHECK(entry.mBuffer->meta()->findInt64("timeUs", &realTimeUs));
// 关于mVideoScheduler视频帧渲染调度实现类,早前章节分析过一些,
// 它主要作用就是结合屏幕刷新率及其屏幕VSYNC同步信号来提高视频渲染流畅度。
// 目前暂不分析它 TODO 以后有时间再考虑
// 备注:关于屏幕VSYNC刷新同步信号可自行查资料了解
realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
// 计算两个屏幕VSYNC刷新同步信号周期时长
// 备注:其实际它的值默认以60fps为基础的计算的帧更新周期,该值默认为1秒
// 也就是此次计算将得到twoVsyncsUs为2秒 即 2000 000 毫秒
int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
// 计算该帧渲染(实际)延迟时长,当前视频帧PTS真实时间 减去 当前系统时间,得到该帧需要多久渲染的时长
int64_t delayUs = realTimeUs - nowUs;
// 若大于500毫秒,则表示异常高延迟时间
ALOGW_IF(delayUs > 500000, "unusually high delayUs: %lld", (long long)delayUs);
// post 2 display refreshes before rendering is due
// 延迟两个屏幕VSYNC刷新同步信号周期时长,才渲染该视频帧
// 若当前该帧渲染延迟时长大于两个屏幕VSYNC刷新同步信号周期时长时,
// 则该视频帧将在渲染PTS时间提前 两个屏幕VSYNC刷新同步信号周期时长 发送该渲染事件处理,
// 否则立即发送该帧渲染事件处理。
// 注意处理非常重要:它是用来增强视频渲染流畅度的手段!
// 该渲染事件处理将在后续流程中统一展开分析
msg->post(delayUs > twoVsyncsUs ? delayUs - twoVsyncsUs : 0);
// 标记当前渲染事件正在执行过程中
mDrainVideoQueuePending = true;
return;
}
// offload模式时将采用MediaClock音视频同步时钟来同步视频帧渲染
int64_t mediaTimeUs;
// 获取当前视频帧PTS媒体显示时间
CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
// 加锁访问代码块
{
Mutex::Autolock autoLock(mLock);
// mNeedVideoClearAnchor该标志位表示在音频EOS或flush、或者没有音频时
// 需要视频播放清除MediaClock音视频同步时钟的锚点时间信息,默认为false。
if (mNeedVideoClearAnchor && !mHasAudio) {
// 需要清除锚点信息并且没有音频时
// 设置为false
mNeedVideoClearAnchor = false;
// 执行清除锚点时间信息
// 见4.2.1小节分析
clearAnchorTime();
}
if (mAnchorTimeMediaUs < 0) {
// 当前锚点媒体时间无效时,即可能只有视频需要播放,也可能是音频还没写入第一帧
if (!mVideoSampleReceived && mHasAudio) {
// 视频帧还未接收到并且有音频时
// 注:这是第一个视频帧Buffer将被消耗渲染,并且我们知道有音频Track存在。
// 既然音频启动有不可避免的延迟,我们等待音频一段时间,给音频更新锚点时间的机会。
// 视频这次不更新锚点,以缓解音视频同步问题。
// this is the first video buffer to be drained, and we know there is audio track
// exist. sicne audio start has inevitable latency, we wait audio for a while, give
// audio a chance to update anchor time. video doesn't update anchor this time to
// alleviate a/v sync issue
// 计算音频开始播放延迟时长:AudioSink延迟时长 减去 【当前AudioSink中已有帧数全部播放完毕所需总时长】
auto audioStartLatency = 1000 * (mAudioSink->latency()
- (1000 * mAudioSink->frameCount() / mAudioSink->getSampleRate()));
ALOGV("First video buffer, wait audio for a while due to audio start latency(%zuus)",
audioStartLatency);
// 延迟时长后执行该事件消息
msg->post(audioStartLatency)