承接上一章节分析:【六】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);
// 标记当前渲染事件正在执行过程中
mDrainVideoQueuePending = true;
return;
}
// 视频帧此前已接收过或者无音频时
// 更新音视频同步时钟锚点时间
// 见前面分析
// 备注:kDefaultVideoFrameIntervalUs表示默认视频帧间隔时长,默认为100毫秒,
// 其作用为:当只有视频时,默认的视频帧显示时间。用于设置MediaClock的最大媒体时间。
// 第三个参数说明:
// 由前面可知它表示当前媒体最大时间即当前最新音频Buffer数据的PTS媒体时间戳
// 而此处有音频时则设置为-1,无音频时则为当前最新视频帧PTS显示时间 加上 视频帧间隔时长100毫秒。
// 因为有音频时会由音频渲染来更新
mMediaClock->updateAnchor(mediaTimeUs, nowUs,
(mHasAudio ? -1 : mediaTimeUs + kDefaultVideoFrameIntervalUs));
// 更新当前最新锚点媒体时间为当前最新视频帧PTS显示时间
mAnchorTimeMediaUs = mediaTimeUs;
}
}
// 下一个视频媒体时间记录为当前最新视频帧PTS显示时间
mNextVideoTimeMediaUs = mediaTimeUs;
if (!mHasAudio) {
// 无音频时
// 平滑视频大于等于10帧率
// smooth out videos >= 10fps
// 更新当前同步时钟中媒体最大时间:当前最新视频帧PTS显示时间 加上 视频帧间隔时长100毫秒,见前面分析
// 备注:这100毫秒就是刚好等于10帧率的一帧时间
mMediaClock->updateMaxTimeMedia(mediaTimeUs + kDefaultVideoFrameIntervalUs);
}
if (!mVideoSampleReceived || mediaTimeUs < mAudioFirstAnchorTimeMediaUs || getVideoLateByUs() > 40000) {
// 视频帧还未接收到 或者 当前最新视频帧PTS小于音频第一帧锚点时间 或者 当前视频延迟时长大于40毫秒 时,进入此处
// 将会直接立即发送该视频消耗渲染事件消息
// 该事件处理将在后续流程中统一展开分析
msg->post();
} else {
// 否则当前视频还未到达应该执行消耗渲染的处理
// 计算两个屏幕VSYNC刷新同步信号周期时长
int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
// post 2 display refreshes before rendering is due
// 延迟两个屏幕VSYNC刷新同步信号周期时长,才渲染该视频帧
// 和上面已有分析一致处理
// 添加视频帧渲染事件任务定时器
// 见4.2.2小节分析
mMediaClock->addTimer(msg, mediaTimeUs, -twoVsyncsUs);
}
// 标记当前渲染事件正在执行过程中
mDrainVideoQueuePending = true;
}
4.2.1、clearAnchorTime()实现分析:
执行清除锚点时间信息
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
// Called on renderer looper.
void NuPlayer::Renderer::clearAnchorTime() {
// 清除MediaClock的锚点时间信息
mMediaClock->clearAnchor();
// 重置当前锚点媒体时间(无效)【已写入AudioSink的帧总时长,包括还未播放的】
mAnchorTimeMediaUs = -1;
// 当前已写入AudioSink的帧总数
mAnchorNumFramesWritten = -1;
}
mMediaClock->clearAnchor()实现分析:
清除MediaClock的锚点时间信息
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::clearAnchor() {
// 加锁
Mutex::Autolock autoLock(mLock);
// 重置锚点时间信息(无效)和播放速率,见此前分析
updateAnchorTimesAndPlaybackRate_l(-1, -1, mPlaybackRate);
}
4.2.2、mMediaClock->addTimer(msg, mediaTimeUs, -twoVsyncsUs)实现分析:
添加视频帧渲染事件任务定时器,延迟两个屏幕VSYNC刷新同步信号周期时长,才渲染该视频帧
参数说明:
notify:【kWhatDrainVideoQueue】消耗视频队列数据事件消息通知
mediaTimeUs:当前最新视频帧PTS
adjustRealUs(负值)作为调整真实时间的因子:两个屏幕VSYNC刷新同步信号周期时长的负值,(比如默认值为2秒周期)
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::addTimer(const sp<AMessage> ¬ify, int64_t mediaTimeUs,
int64_t adjustRealUs) {
// 加锁
Mutex::Autolock autoLock(mLock);
// 是否更新定时器(即是否更新视频帧渲染事件定时任务)
// 由前面可知播放速率为1,因此每次进入时均为true
bool updateTimer = (mPlaybackRate != 0.0);
if (updateTimer) {
// true时
// 定义:std::list<Timer> mTimers; 可知是Timer定时器即定时任务结构对象队列
// Timer结构定义见下面分析
// 视频帧渲染事件队列起始item定时器
auto it = mTimers.begin();
while (it != mTimers.end()) {
// 队列未到末尾时
// 判断是否应该更新定时器(及其同步代数值)
// 加号的前一个值我们可以认为是固定的值即默认会为0,它是计算真实时间适配的波动差值,
// 若无播放的话则默认差值为0,再乘以播放速率得到波动差值的缩放倍数。
// 加号后一个值,则计算的当前队列中靠前渲染帧PTS和本次最新视频帧PTS的差值,
// 理论上会为负值即后面的帧PTS大于前面帧,但也不一定哦,因为若存在B帧时候,
// 解码器解除的帧PTS时间将不是按照PTS时间递增来排序的,而是不规则顺序排序的,
// 即后面的B帧可能比前面的参考帧P帧的PTS时间小。
// 因此在这种情况下就不需要【更新定时器(及其同步代数值)】
if (((it->mAdjustRealUs - (double)adjustRealUs) * (double)mPlaybackRate
+ (it->mMediaTimeUs - mediaTimeUs)) <= 0) {
// 不需要【更新定时器(及其同步代数值)】
updateTimer = false;
break;
}
// 继续下一个item判断
++it;
}
}
// 添加新视频帧渲染事件定时器到队列中
mTimers.emplace_back(notify, mediaTimeUs, adjustRealUs);
if (updateTimer) {
// 需要【更新定时器(及其同步代数值)】
// 递增该视频帧渲染同步代数值
++mGeneration;
// 执行定时器更新
// 见下面分析
processTimers_l();
}
}
Timer结构定义:
// [frameworks/av/media/libstagefright/include/media/stagefright/MediaClock.h]
struct MediaClock : public AHandler {
struct Timer {
// 可知该结构定时器就是用来缓存记录【视频帧渲染处理事件消息通知、该帧PTS、调整真实时间(负值)】
Timer(const sp<AMessage> ¬ify, int64_t mediaTimeUs, int64_t adjustRealUs);
const sp<AMessage> mNotify;
int64_t mMediaTimeUs;
int64_t mAdjustRealUs;
};
}
Timer结构构造函数定义:缓存值
// [frameworks/av/media/libstagefright/MediaClock.cpp]
MediaClock::Timer::Timer(const sp<AMessage> ¬ify, int64_t mediaTimeUs, int64_t adjustRealUs)
: mNotify(notify),
mMediaTimeUs(mediaTimeUs),
mAdjustRealUs(adjustRealUs) {
}
processTimers_l()实现分析:
执行定时器更新
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::processTimers_l() {
int64_t nowMediaTimeUs;
// 获取当前媒体时间即已播放进度值
// 见4.2.2.1小节分析
status_t status = getMediaTime_l(
ALooper::GetNowUs(), &nowMediaTimeUs, false /* allowPastMaxTime */);
if (status != OK) {
// 错误时不处理
return;
}
// 下一个即下一帧时间流逝时长,默认64位Int最大值
int64_t nextLapseRealUs = INT64_MAX;
// C++标准库map对象,记录的是 需要执行渲染处理事件的视频帧定时器 队列
// 备注:multimap 是关联容器,含有键值对的已排序列表,同时容许多个元素拥有同一键。
// 按照应用到键的比较函数 Compare 排序。搜索、插入和移除操作拥有对数复杂度。
// 拥有等价键的键值对的顺序就是插入顺序,且不会更改。
std::multimap<int64_t, Timer> notifyList;
// 视频帧渲染事件队列起始item定时器
auto it = mTimers.begin();
while (it != mTimers.end()) {
// 队列未到末尾时
// 时刻记住mAdjustRealUs这是个负值,为什么负值比较好,
// 因为可以直接使用加号或乘号,加上这个数就相当于减去这个时长时间。
// 计算该帧距离PTS的媒体时间时差:该帧距离PTS显示时间点的时长 减去 两个屏幕VSYNC刷新同步信号周期时长
// 【即也就是需要提前两个屏幕VSYNC刷新同步信号周期时长进行渲染处理流程,提前处理渲染流程降低卡顿或丢帧】
double diff = it->mAdjustRealUs * (double)mPlaybackRate
+ it->mMediaTimeUs - nowMediaTimeUs;
// 记录该帧距离PTS的媒体时间差值
int64_t diffMediaUs;
if (diff > (double)INT64_MAX) {
// 太大溢出,则初始化为最大【这种情况实际上很少存在】
diffMediaUs = INT64_MAX;
} else if (diff < (double)INT64_MIN) {
// 太小溢出,也很少存在
diffMediaUs = INT64_MIN;
} else {
// 正常未溢出情况下,赋值
diffMediaUs = diff;
}
if (diffMediaUs <= 0) {
// 小于等于0时 表示当前该帧已快到达或已延迟显示渲染时间了,也就是该帧需要立即执行渲染处理了
// 添加插入【待渲染差值时长和定时器】映射item
notifyList.emplace(diffMediaUs, *it);
// 从原队列中移除,并返回下一个item项
it = mTimers.erase(it);
} else {
// 大于0时
if (mPlaybackRate != 0.0
&& (double)diffMediaUs < INT64_MAX * (double)mPlaybackRate) {
// 播放速率不为0 并且 差值小于最大Int和播放速率乘积(也就是差值有效) 时
// 根据播放速率将该延迟渲染媒体时间进行缩放,得到最终目标真实时长
int64_t targetRealUs = diffMediaUs / (double)mPlaybackRate;
if (targetRealUs < nextLapseRealUs) {
// 小于 下一帧时间流逝时长 时 更新它
// 备注:也就是说【nextLapseRealUs】将会是所有未到达PTS时间的视频帧中的
// 最小PTS时间那个帧的距离显示渲染处理时间点的差值。
nextLapseRealUs = targetRealUs;
}
}
++it;
}
}
// 视频帧渲染事件队列渲染时间最早的item定时器的map item
auto itNotify = notifyList.begin();
// while循环处理每个执行渲染处理事件的视频帧定时器
while (itNotify != notifyList.end()) {
// 设置通知原因,渲染处理事件定时器时间已到达渲染处理时间【TIMER_REASON_REACHED】
itNotify->second.mNotify->setInt32("reason", TIMER_REASON_REACHED);
// 立即发送视频帧渲染处理消息即【kWhatDrainVideoQueue】消耗视频队列数据事件消息通知
// 见4.2.2.2小节分析
itNotify->second.mNotify->post();
// 从原队列中移除,并返回下一个item项
itNotify = notifyList.erase(itNotify);
}
if (mTimers.empty() || mPlaybackRate == 0.0 || mAnchorTimeMediaUs < 0
|| nextLapseRealUs == INT64_MAX) {
// mTimers队列为空即等待渲染帧任务 或者 播放速率为0 或者
// mAnchorTimeMediaUs无效 或者 nextLapseRealUs也为无效时
// 不继续处理
return;
}
// 当前定时器队列任务有效时
// 发送【kWhatTimeIsUp】事件消息携带同步代数值【mGeneration】,
// 延迟【nextLapseRealUs】多秒处理该事件。
// 见4.2.2.3小节分析
// 备注:【nextLapseRealUs】该值延迟执行的意义非常重要,避免过早重复(无意义)的执行该流程
sp<AMessage> msg = new AMessage(kWhatTimeIsUp, this);
msg->setInt32("generation", mGeneration);
msg->post(nextLapseRealUs);
}
4.2.2.1、getMediaTime_l(ALooper::GetNowUs(), &nowMediaTimeUs, false /* allowPastMaxTime */)实现分析:
获取当前媒体时间即已播放进度值
参数说明:
realUs:当前系统已开机时长
outMediaUs:输出返回当前媒体时间即已播放进度值
allowPastMaxTime:是否允许跳过当前媒体最大时间【最大时间此前讲述过它表示当前已写入AudioSink的最新音频PTS】
// [frameworks/av/media/libstagefright/MediaClock.cpp]
status_t MediaClock::getMediaTime_l(
int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) const {
// 判断此前全局变量锚点真实时间【也就是记录的上一次最新连续音频帧更新锚点时的系统已开机时长,
// 注意非连续帧时将会更新该值】
if (mAnchorTimeRealUs == -1) {
// 返回未初始化状态码,不处理
return NO_INIT;
}
// 计算当前媒体时间即已播放进度值:当前最新音频帧PTS 加上 系统流逝时长和播放速率的乘积
int64_t mediaUs = mAnchorTimeMediaUs
+ (realUs - mAnchorTimeRealUs) * (double)mPlaybackRate;
if (mediaUs > mMaxTimeMediaUs && !allowPastMaxTime) {
// 若大于【当前(已写入AudioSink)音频最大媒体时间】 并且 不允许跳过最大媒体时间时
// 重置它为当前最大媒体时间
mediaUs = mMaxTimeMediaUs;
}
if (mediaUs < mStartingTimeMediaUs) {
// 当前媒体播放时间 小于 开始播放媒体时间 时
// 也重置它为开始播放媒体时间
mediaUs = mStartingTimeMediaUs;
}
if (mediaUs < 0) {
// 小于0,重置为0
mediaUs = 0;
}
// 输出并返回OK
*outMediaUs = mediaUs;
return OK;
}
4.2.2.2、视频帧渲染处理消息即【kWhatDrainVideoQueue】消耗视频队列数据事件消息通知处理:
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatDrainVideoQueue:
{
int32_t generation;
// 取出此前设置的该视频帧消耗事件代数值
CHECK(msg->findInt32("drainGeneration", &generation));
// 若和当前全局消费代数值不同时则忽略该事件处理
if (generation != getDrainGeneration(false /* audio */)) {
break;
}
// 标记该事件已得到处理,因此设置为false,以便下一次再次发起渲染请求
mDrainVideoQueuePending = false;
// 回调消耗视频帧队列处理
// 见下面分析
onDrainVideoQueue();
// 再次循环发起渲染请求
// 备注:视频帧渲染将会自动循环发起渲染请求
postDrainVideoQueue();
break;
}
}
}
onDrainVideoQueue()实现分析:
回调消耗视频帧队列处理
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::onDrainVideoQueue() {
if (mVideoQueue.empty()) {
// 视频帧队列为空
return;
}
// 从第一个item开始处理
QueueEntry *entry = &*mVideoQueue.begin();
if (entry->mBuffer == NULL) {
// EOS
// 实际负载buffer为空时 即视频EOS状态
// 通知视频EOS状态处理
// 关于EOS状态流程处理此前流程中有说过目前暂不分析 TODO 后续有时间再分析吧
notifyEOS(false /* audio */, entry->mFinalResult);
// 移除
mVideoQueue.erase(mVideoQueue.begin());
entry = NULL;
// eos时设置视频延迟时长为0
// 见下面分析
setVideoLateByUs(0);
return;
}
// // 实际负载buffer不为空时即非EOS时
// 当前系统已开机时长
int64_t nowUs = ALooper::GetNowUs();
// 该帧使用(系统)真实时间表示的PTS
int64_t realTimeUs;
// 该帧
int64_t mediaTimeUs = -1;
if (mFlags & FLAG_REAL_TIME) {
// 非offload模式时,获取视频帧渲染显示真实时间
// 备注:此处获取到的值是在系统时间上计算得到的【相当于该帧使用(系统)真实时间表示的PTS】,
// 也就是说它不是真正的媒体播放时间,它并不是PTS概念所述的那种媒体显示时间
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &realTimeUs));
} else {
// offload模式时,获取视频帧PTS
// 备注:此处获取到的PTS值是媒体播放时间戳,而非(系统)真实显示时间的PTS
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
// 因此此处必须获取真实时间【相当于该帧使用(系统)真实时间表示的PTS】
// 见下面分析
realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
}
// 目前暂不分析它 TODO 以后有时间再考虑
realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
// 视频帧是否延迟标志位,默认为false
bool tooLate = false;
if (!mPaused) {
// 正在播放时
// 设置该视频帧延迟时长,计算为:当前系统时间 减去 【该帧使用(系统)真实时间表示的PTS】
setVideoLateByUs(nowUs - realTimeUs);
// 然后比较该视频帧延迟时长,大于40毫秒时则为true表示该帧延迟时长大于40毫秒
// 备注:也就是说视频帧延迟时长小于等于40毫秒及其负值时将会认为是有效帧,
// 因为下面处理延迟帧将丢弃不会被渲染
tooLate = (mVideoLateByUs > 40000);
if (tooLate) {
// 延迟时
ALOGV("video late by %lld us (%.2f secs)",
(long long)mVideoLateByUs, mVideoLateByUs / 1E6);
} else {
// 有效帧时
int64_t mediaUs = 0;
// 获取当前音频已播放时长即媒体时间播放进度【因为视频以音频时间戳来同步】
// 见下面分析
mMediaClock->getMediaTime(realTimeUs, &mediaUs);
ALOGV("rendering video at media time %.2f secs",
(mFlags & FLAG_REAL_TIME ? realTimeUs :
mediaUs) / 1E6);
if (!(mFlags & FLAG_REAL_TIME)
&& mLastAudioMediaTimeUs != -1
&& mediaTimeUs > mLastAudioMediaTimeUs) {
// offload模式 并且 最后音频帧媒体时间PTS有效(非-1) 并且 当前视频帧PTS大于该音频PTS 时
// 注:如果音频在视频之前结束,视频将继续驱动(音视频)媒体同步时钟。
// 同时平滑视频帧大于等于10帧率,也就是平滑视频帧到下一帧被允许播放时间,
// 以此来继续驱动MediaClock锚点时间正确同步工作,也就让视频能够独立完成最后的播放。
// If audio ends before video, video continues to drive media clock.
// Also smooth out videos >= 10fps.
// 更新当前同步时钟中媒体最大时间:当前最新视频帧PTS显示时间 加上 视频帧间隔时长100毫秒,见前面分析
// 备注:这100毫秒就是刚好等于10帧率的一帧时间
mMediaClock->updateMaxTimeMedia(mediaTimeUs + kDefaultVideoFrameIntervalUs);
}
}
} else {
// 暂停时
// 重置视频延迟时长为0
setVideoLateByUs(0);
if (!mVideoSampleReceived && !mHasAudio) {
// 视频帧还未接收到并且无音频时
// 注:这将确保第一帧在flush后不会在渲染器处于暂停状态时用作锚点,因为在seek之后的任何时候都可能发生resume。
// This will ensure that the first frame after a flush won't be used as anchor
// when renderer is in paused state, because resume can happen any time after seek.
// 清除锚点时间信息
clearAnchorTime();
}
}
// Always render the first video frame while keeping stats on A/V sync.
// 始终渲染第一帧视频,同时保持数据在A/V同步机制上
if (!mVideoSampleReceived) {
// 视频帧还未接收到一帧时
// 重置这两值,也就是说第一帧数据始终被渲染显示
realTimeUs = nowUs;
tooLate = false;
}
// 设置【该帧使用(系统)真实时间戳表示的PTS】,单位纳秒
entry->mNotifyConsumed->setInt64("timestampNs", realTimeUs * 1000LL);
// 设置标志位,是否该帧应该被渲染,true时表示需要被渲染处理
entry->mNotifyConsumed->setInt32("render", !tooLate);
// 由前面流程可知,此处mNotifyConsumed参数为此前已消耗该Buffer完成通知事件消息
// 即NuPlayerDecoder接收的【kWhatRenderBuffer】已渲染Buffer事件应答消息。
// 直接发送该【已消耗完成通知】事件消息给NuPlayerDecoder
// 见前面4.1.4小节分析
entry->mNotifyConsumed->post();
// 移除该帧数据
mVideoQueue.erase(mVideoQueue.begin());
entry = NULL;
// 设置标志位已接收到视频帧为true
mVideoSampleReceived = true;
if (!mPaused) {
// 未暂停时即播放中时
// [mVideoRenderingStarted]该值初始化为false
if (!mVideoRenderingStarted) {
// false时标记为true
mVideoRenderingStarted = true;
// 通知视频渲染已开始事件
// 见下面分析
notifyVideoRenderingStart();
}
// 加锁访问
Mutex::Autolock autoLock(mLock);
// 判断通知媒体已渲染事件
// 见前面4.1.6小节分析
notifyIfMediaRenderingStarted_l();
}
}
setVideoLateByUs(0)实现分析:
设置当前视频延迟时长,可看到加锁更新到【mVideoLateByUs 】全局变量记录
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::setVideoLateByUs(int64_t lateUs) {
Mutex::Autolock autoLock(mLock);
mVideoLateByUs = lateUs;
}
getRealTimeUs(mediaTimeUs, nowUs)实现分析:
获取真实时间【相当于该帧使用(系统)真实时间表示的PTS】
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
int64_t NuPlayer::Renderer::getRealTimeUs(int64_t mediaTimeUs, int64_t nowUs) {
int64_t realUs;
// 从MediaClock中计算获取 系统真实时间表示的PTS
if (mMediaClock->getRealTimeFor(mediaTimeUs, &realUs) != OK) {
// 获取失败时,直接返回当前系统时间
// If failed to get current position, e.g. due to audio clock is
// not ready, then just play out video immediately without delay.
return nowUs;
}
// 成功返回
return realUs;
}
mMediaClock->getRealTimeFor(mediaTimeUs, &realUs)实现分析:
通过【targetMediaUs】目标媒体时间PTS来获取其真实时间【相当于该帧使用(系统)真实时间表示的PTS】
该方法将会获得一个合适的【该帧媒体时间PTS相对于使用(系统)真实时间表示的PTS】
// [frameworks/av/media/libstagefright/MediaClock.cpp]
status_t MediaClock::getRealTimeFor(
int64_t targetMediaUs, int64_t *outRealUs) const {
if (outRealUs == NULL) {
return BAD_VALUE;
}
// 加锁
Mutex::Autolock autoLock(mLock);
// 播放速率必须不能为空
if (mPlaybackRate == 0.0) {
return NO_INIT;
}
// 系统时间
int64_t nowUs = ALooper::GetNowUs();
// 获取当前媒体时间即已播放进度,并允许超出当前最大媒体时间
int64_t nowMediaUs;
status_t status =
getMediaTime_l(nowUs, &nowMediaUs, true /* allowPastMaxTime */);
if (status != OK) {
return status;
}
// 成功时
// 计算真实时间PTS输出值:当前系统时间 加上 【目标(该帧)媒体时间 减去 当前播放媒体时间,然后差值进行播放速率缩放】
// 备注:如此计算就得到了一个合适的【该帧使用(系统)真实时间表示的PTS】
*outRealUs = (targetMediaUs - nowMediaUs) / (double)mPlaybackRate + nowUs;
return OK;
}
mMediaClock->getMediaTime(realTimeUs, &mediaUs)实现分析:
获取当前音频已播放时长即媒体时间播放进度【因为视频以音频时间戳来同步】
// [frameworks/av/media/libstagefright/MediaClock.cpp]
status_t MediaClock::getMediaTime(
int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) const {
if (outMediaUs == NULL) {
return BAD_VALUE;
}
// 其就是加锁访问[getMediaTime_l]来计算获取的
Mutex::Autolock autoLock(mLock);
return getMediaTime_l(realUs, outMediaUs, allowPastMaxTime);
}
notifyVideoRenderingStart()实现分析:
通知视频渲染已开始事件
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::notifyVideoRenderingStart() {
// 根据早前章节分析,mNotify该通知消息为NuPlayer接收的【kWhatRendererNotify】渲染通知事件消息
sp<AMessage> notify = mNotify->dup();
// 设置子事件类型为视频渲染开始事件【kWhatVideoRenderingStart】
notify->setInt32("what", kWhatVideoRenderingStart);
notify->post();
}
NuPlayer接收【kWhatRendererNotify】渲染通知事件消息的子事件【kWhatVideoRenderingStart】处理:
其实【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::kWhatVideoRenderingStart) {
// 此处最终将会通知上层APP媒体渲染开始事件【MEDIA_INFO_RENDERING_START】,不再展开分析
notifyListener(MEDIA_INFO, MEDIA_INFO_RENDERING_START, 0);
}
}
}
}
4.2.2.3、接收处理【kWhatTimeIsUp】事件消息:
// [frameworks/av/media/libstagefright/MediaClock.cpp]
void MediaClock::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatTimeIsUp:
{
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
// 加锁访问
Mutex::Autolock autoLock(mLock);
// 判断当前同步代数值是否已改变,不一致时表明当前事件消息已时旧消息了,因此忽略它不处理
if (generation != mGeneration) {
break;
}
// 再次执行更新定时器处理
processTimers_l();
break;
}
default:
TRESPASS();
break;
}
}
本章结束