Android-NuPlayer音視頻同步之安卓Q新功能

概述:

       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();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章