概述:
NuPlayer在播放視頻時,當收到kWhatScanSources消息,即有數據需要播放時,會先把render置成paused狀態。因爲audio解碼比video慢,audio已經有數據需要顯示時,video可能還沒解碼出來數據,所以在開始解碼前先把render置成paused狀態,當video有第一幀數據需要顯示時再把render置成resume狀態。vidoe audio同時開始播放。
具體代碼:
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
case kWhatScanSources:
{
if (!mHadAnySourcesBefore
&& (mAudioDecoder != NULL || mVideoDecoder != NULL)) {
// This is the first time we've found anything playable.
if (mSourceFlags & Source::FLAG_DYNAMIC_DURATION) {
schedulePollDuration();
}
// Pause the renderer till video queue pre-rolls
if (!mPaused && mVideoDecoder != NULL && mAudioDecoder != NULL) {
ALOGI("NOTE: Pausing Renderer after decoders instantiated..");
mRenderer->pause(); // 在此讓render pause
// wake up renderer if timed out
sp<AMessage> msg = new AMessage(kWhatWakeupRendererFromPreroll, this);
msg->post(kDefaultVideoPrerollMaxUs); // 2s之後發送消息wake up render。
}
}
}
}
}
void NuPlayer::Renderer::onPause() {
if (mPaused) {
return;
}
{
Mutex::Autolock autoLock(mLock);
// we do not increment audio drain generation so that we fill audio buffer during pause.
++mVideoDrainGeneration;
prepareForMediaRenderingStart_l();
mPaused = true; // 將mPaused設置爲true
mMediaClock->setPlaybackRate(0.0);
}
mDrainAudioQueuePending = false;
mDrainVideoQueuePending = false;
mVideoRenderingStarted = false; // force-notify NOTE_INFO MEDIA_INFO_RENDERING_START after resume
// Note: audio data may not have been decoded, and the AudioSink may not be opened.
mAudioSink->pause(); // 讓audiosink pause
startAudioOffloadPauseTimeout(); // 設置超時
ALOGV("now paused audio queue has %zu entries, video has %zu entries",
mAudioQueue.size(), mVideoQueue.size());
}
void NuPlayer::Renderer::startAudioOffloadPauseTimeout() {
if (offloadingAudio()) {
int64_t pauseTimeOutDuration = property_get_int64(
"audio.sys.offload.pstimeout.secs",(kOffloadPauseMaxUs/1000000)/*default*/);
mWakeLock->acquire();
sp<AMessage> msg = new AMessage(kWhatAudioOffloadPauseTimeout, this); // 發送timeout消息
msg->setInt32("drainGeneration", mAudioOffloadPauseTimeoutGeneration);
msg->post(pauseTimeOutDuration*1000000);
}
}
void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatAudioOffloadPauseTimeout:
{
int32_t generation;
CHECK(msg->findInt32("drainGeneration", &generation));
if (generation != mAudioOffloadPauseTimeoutGeneration) {
break;
}
ALOGV("Audio Offload tear down due to pause timeout.");
onAudioTearDown(kDueToTimeout);
mWakeLock->release();
break;
}
}
}
上面就是整個pause的邏輯,但是pause之後怎麼resume的呢?
當omx解碼完一幀,會調用NuPlayer::Renderer::onDrainVideoQueue()進行渲染,在此函數中如果收到第一幀解碼後的數據就會把render設置成resume狀態.
void NuPlayer::Renderer::onDrainVideoQueue() {
if (mVideoQueue.empty()) {
return;
}
// 前面進行一系列音視頻對齊的操作,略
if (mPaused && !mVideoSampleReceived) { // 如果當前是paused狀態並且是收到的第一幀數據,發送消息kWhatVideoPrerollComplete
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatVideoPrerollComplete);
ALOGI("NOTE: notifying video preroll complete");
notify->post();
}
mVideoSampleReceived = true;
if (!mPaused) {
if (!mVideoRenderingStarted) {
mVideoRenderingStarted = true;
notifyVideoRenderingStart();
}
Mutex::Autolock autoLock(mLock);
notifyIfMediaRenderingStarted_l();
}
}
NuPlayer收到消息kWhatVideoPrerollComplete時會調用mRenderer->resume();
void NuPlayer::Renderer::onResume() {
if (!mPaused) {
return;
}
// 略
{
Mutex::Autolock autoLock(mLock);
mPaused = false;
// rendering started message may have been delayed if we were paused.
if (mRenderingDataDelivered) {
notifyIfMediaRenderingStarted_l();
}
// configure audiosink as we did not do it when pausing
if (mAudioSink != NULL && mAudioSink->ready()) {
mAudioSink->setPlaybackRate(mPlaybackSettings);
}
mMediaClock->setPlaybackRate(mPlaybackRate);
if (!mAudioQueue.empty()) {
postDrainAudioQueue_l();
}
}
if (!mVideoQueue.empty()) {
postDrainVideoQueue(); // 調用postDrainVideoQueue,發送消息kWhatDrainVideoQueue。
// 收到此消息會調用onDrainVideoQueue和postDrainVideoQueue.
// postDrainVideoQueue就是一個回調函數,發出去消息之後還是會調用自己。
}
}
上面就是整個新功能的邏輯。看起來挺完整的。但是,並不完美。
問題:
考慮一種情況: video解碼特別快,在nuplayerrender響應pause之前,已經有解碼完的數據,並且已經render過了。
當第一幀video數據來的時候,處理如下:
void NuPlayer::Renderer::onDrainVideoQueue() {
if (mVideoQueue.empty()) {
return;
}
// 前面進行一系列音視頻對齊的操作,略
if (mPaused && !mVideoSampleReceived) { // 此處還未執行pause,所以下面的都不會執行
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatVideoPrerollComplete);
ALOGI("NOTE: notifying video preroll complete");
notify->post();
}
mVideoSampleReceived = true; // 將mVideoSampleReceived設成true,表示已經有過解碼的數據了
if (!mPaused) { // paused爲false,將數據送去render
if (!mVideoRenderingStarted) {
mVideoRenderingStarted = true;
notifyVideoRenderingStart();
}
Mutex::Autolock autoLock(mLock);
notifyIfMediaRenderingStarted_l();
}
}
顯示完之後render開始響應pause。將mpaused設置爲true。
然後,後面的video幀解碼完送給render,render操作如下。
void NuPlayer::Renderer::onDrainVideoQueue() {
if (mVideoQueue.empty()) {
return;
}
// 前面進行一系列音視頻對齊的操作,略
if (mPaused && !mVideoSampleReceived) { // 此處mPaused爲true,但是mVideoSampleReceived也爲true。所以不執行下面的代碼。
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatVideoPrerollComplete);
ALOGI("NOTE: notifying video preroll complete");
notify->post();
}
mVideoSampleReceived = true; // 將mVideoSampleReceived設成true,表示已經有過解碼的數據了
if (!mPaused) { // paused爲true,不送去render。
if (!mVideoRenderingStarted) {
mVideoRenderingStarted = true;
notifyVideoRenderingStart();
}
Mutex::Autolock autoLock(mLock);
notifyIfMediaRenderingStarted_l();
}
}
然後,後面的video解碼幀都這樣處理,界面一直不會刷新。等到所有的buffer都卡到render的時候,解碼器hang住,系統hang住,不會再自己退出。
修復方法:
增加一個判斷變量mVideoPrerollMesSync,當初始化時將其設置爲false,在onDrainVideoQueue中根據這個變量做一些處理.
void NuPlayer::Renderer::onDrainVideoQueue() {
if (mVideoQueue.empty()) {
return;
}
// 前面進行一系列音視頻對齊的操作,略
if (mPaused && !mVideoSampleReceived) { // 如果pause,且此幀數據爲第一幀則發送消息kWhatVideoPrerollComplete,將render置爲resume。
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatVideoPrerollComplete);
ALOGI("NOTE: notifying video preroll complete");
notify->post();
mVideoPrerollMesSync = true ; // 將mVideoPrerollMesSync置爲true。
}
if (mPaused && mVideoSampleReceived && !mVideoPrerollMesSync) { // 當前面已有數據render過然後再pause,且是第一次pause,則發送消息
// kWhatVideoPrerollComplete,並將render置爲resume。
mVideoPrerollMesSync = true ; // 將mVideoPrerollMesSync置爲true。
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatVideoPrerollComplete);
ALOGI("NOTE: notifying video preroll complete -- mVideoPrerollMesSync");
notify->post();
}
mVideoSampleReceived = true;
if (!mPaused) {
if (!mVideoRenderingStarted) {
mVideoRenderingStarted = true;
notifyVideoRenderingStart();
}
Mutex::Autolock autoLock(mLock);
notifyIfMediaRenderingStarted_l();
}
}