Android MediaPlayer播放器暂停3秒后恢复播放时视频会倒退几秒才开始播放问题源码分析和解决方案

先写结论:是安卓高版本上视频在暂停播放处理时加上了audio offload超时机制【降低系统功耗】优化功能导致的,offload该处理是使用音频硬解码,不是软解,其音频解码速度更快,而在PAUSE暂停时设置超时是为了降低功耗提升系统性能。
跳帧【回退几秒】原因:是重新执行了seek操作,而该操作在安卓原生播放器中不支持seek到非关键帧。

而之所以只有某些视频会出现比较明显的回退几秒现象,是由于该视频中视频数据GOP时长问题即视频编码问题,如若该视频在10秒时是一个关键帧,而之后是25秒后才是关键帧,因此结合安卓原生播放器seek到关键帧的处理,在播放进度在10秒到25秒之间暂停3秒后就会出现该bug现象产生。

备注:若是用户直接快进seek到这个非关键帧时间段内,也会有该问题,但该问题在安卓原生播放器实现目前不考虑优化的。

该3秒延迟值可以通过系统构建mk配置文件中设置的系统属性值进行修改,
在【vendor/qcom/opensource/audio-hal/primary-hal/configs/平台名/平台名.mk】中配置的,
属性值名称为【“audio.sys.offload.pstimeout.secs”】,目前系统设置为3秒。

目前考虑的可行优化方案:
1、去掉offload功能;【不推荐】 ===》可通过系统属性配置去关闭
2、将超时时长3秒扩大到10秒后等; ===》可通过系统属性配置去修改
3、修改安卓底层解码输出端让其支持seek到非关键帧;

代码分析过程:【android 10.0版本】
1、暂停流程最终会调用到NuPlayer中的该方法:

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
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.
        // video输出标识位增加1
        ++mVideoDrainGeneration;
        prepareForMediaRenderingStart_l();
        // 置为暂停状态
        mPaused = true;
        mMediaClock->setPlaybackRate(0.0);
    }

    // 将音视频输出标志设置为false
    mDrainAudioQueuePending = false;
    mDrainVideoQueuePending = false;
    mVideoRenderingStarted = false; // force-notify NOTE_INFO MEDIA_INFO_RENDERING_START after resume

    // 通知AudioSink暂停处理
    // Note: audio data may not have been decoded, and the AudioSink may not be opened.
    mAudioSink->pause();
    
    // 关键点:Audio offload关闭Audio相关加载模块的暂停超时流程处理
    // 该功能就是导致3秒后恢复播放异常原因
    // 见第2小节分析
    startAudioOffloadPauseTimeout();

    ALOGV("now paused audio queue has %zu entries, video has %zu entries",
          mAudioQueue.size(), mVideoQueue.size());
}

2、startAudioOffloadPauseTimeout实现分析:

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::startAudioOffloadPauseTimeout() {
    
    
    if (offloadingAudio()) {
    
    
        // 若支持offload mode模式
        // 该值为系统属性值,系统默认为3秒,最大值为10秒【kOffloadPauseMaxUs】
        int64_t pauseTimeOutDuration = property_get_int64(
            "audio.sys.offload.pstimeout.secs",(kOffloadPauseMaxUs/1000000)/*default*/);
        mWakeLock->acquire();
        // 发送一个【kWhatAudioOffloadPauseTimeout】超时消息
        // 见下面的分析
        sp<AMessage> msg = new AMessage(kWhatAudioOffloadPauseTimeout, this);
        msg->setInt32("drainGeneration", mAudioOffloadPauseTimeoutGeneration);
        msg->post(pauseTimeOutDuration*1000000);
    }
}

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) {
    
    
    switch(){
    
    
         case kWhatAudioOffloadPauseTimeout:
        {
    
    
            int32_t generation;
            CHECK(msg->findInt32("drainGeneration", &generation));
            if (generation != mAudioOffloadPauseTimeoutGeneration) {
    
    
                // 若时间到之后发现当前offload超时事件记录发生变化则跳过该处理,如3秒内进行了恢复播放
                break;
            }
            ALOGV("Audio Offload tear down due to pause timeout.");
            // 见下面的分析
            onAudioTearDown(kDueToTimeout);
            mWakeLock->release();
            break;
        }
    }
}

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp]
void NuPlayer::Renderer::onAudioTearDown(AudioTearDownReason reason) {
    
    
    if (mAudioTornDown) {
    
    
        return;
    }

    // TimeoutWhenPaused is only for offload mode.
    if (reason == kDueToTimeout && !offloadingAudio()) {
    
    
        return;
    }

    mAudioTornDown = true;

    int64_t currentPositionUs;
    sp<AMessage> notify = mNotify->dup();
    if (getCurrentPosition(&currentPositionUs) == OK) {
    
    
        // 将当前播放时间点发送过去
        notify->setInt64("positionUs", currentPositionUs);
    }

    mAudioSink->stop();
    mAudioSink->flush();

    notify->setInt32("what", kWhatAudioTearDown);
    notify->setInt32("reason", reason);
    // 给NuPlayer模块发送【kWhatAudioTearDown】通知
    // 见下面的分析
    notify->post();
}

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
    
    
    switch (msg->what()) {
    
    
        case kWhatRendererNotify:
        {
    
    
            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::kWhatAudioTearDown) {
    
    
                int32_t reason;
                CHECK(msg->findInt32("reason", &reason));
                ALOGV("Tear down audio with reason %d.", reason);
                if (reason == Renderer::kDueToTimeout && !(mPaused && mOffloadAudio)) {
    
    
                    // TimeoutWhenPaused is only for offload mode.
                    ALOGW("Received a stale message for teardown, mPaused(%d), mOffloadAudio(%d)",
                          mPaused, mOffloadAudio);
                    break;
                }
                // 获取当前播放时间点
                int64_t positionUs;
                if (!msg->findInt64("positionUs", &positionUs)) {
    
    
                    positionUs = mPreviousSeekTimeUs;
                }

                // 重启audio相关模块,此时reason值为kDueToTimeout
                // 见下面到的分析
                restartAudio(
                        positionUs, reason == Renderer::kForceNonOffload /* forceNonOffload */,
                        reason != Renderer::kDueToTimeout /* needsToCreateAudioDecoder */);
            }
            break;
        }
    }
}

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::restartAudio(
        int64_t currentPositionUs, bool forceNonOffload, bool needsToCreateAudioDecoder) {
    
    
    ALOGD("restartAudio timeUs(%lld), dontOffload(%d), createDecoder(%d)",
          (long long)currentPositionUs, forceNonOffload, needsToCreateAudioDecoder);
    if (mAudioDecoder != NULL) {
    
    
        mAudioDecoder->pause();
        Mutex::Autolock autoLock(mDecoderLock);
        mAudioDecoder.clear();
        mAudioDecoderError = false;
        ++mAudioDecoderGeneration;
    }
    if (mFlushingAudio == FLUSHING_DECODER) {
    
    
        mFlushComplete[1 /* audio */][1 /* isDecoder */] = true;
        mFlushingAudio = FLUSHED;
        finishFlushIfPossible();
    } else if (mFlushingAudio == FLUSHING_DECODER_SHUTDOWN
            || mFlushingAudio == SHUTTING_DOWN_DECODER) {
    
    
        mFlushComplete[1 /* audio */][1 /* isDecoder */] = true;
        mFlushingAudio = SHUT_DOWN;
        finishFlushIfPossible();
        needsToCreateAudioDecoder = false;
    }
    if (mRenderer == NULL) {
    
    
        return;
    }
    closeAudioSink();
    mRenderer->flush(true /* audio */, false /* notifyComplete */);
    if (mVideoDecoder != NULL) {
    
    
        // 此处就是关键处理处
        // 会记录三个延迟操作放入list操作表中,然后调用processDeferredActions取出操作指令去执行
        // flush video data请求操作
        mDeferredActions.push_back(
                new FlushDecoderAction(FLUSH_CMD_NONE /* audio */,
                                       FLUSH_CMD_FLUSH /* video */));
        // seek video请求操作
        mDeferredActions.push_back(
                new SeekAction(currentPositionUs,
                MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC /* mode */));
        // After a flush without shutdown, decoder is paused.
        // Don't resume it until source seek is done, otherwise it could
        // start pulling stale data too soon.
        // 然后进行恢复video视频位置操作,
        // 但是由于前面进行了flush和seek操作,导致resume seek的position时
        // 会去寻找当前seek时间点前的关键帧【安卓底层NuPlayer不支持seek到非关键帧】,
        // 因此最终导致了该问题的产生
        mDeferredActions.push_back(new ResumeDecoderAction(false));
        processDeferredActions();
    } else {
    
    
        performSeek(currentPositionUs, MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC /* mode */);
    }

    if (forceNonOffload) {
    
    
        mRenderer->signalDisableOffloadAudio();
        mOffloadAudio = false;
    }

    if (needsToCreateAudioDecoder) {
    
    
         mDeferredActions.push_back(
            new InstantiateDecoderAction(true /* audio */, &mAudioDecoder, !forceNonOffload));
    }
    processDeferredActions();

}

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
// 取出操作指令去执行
void NuPlayer::processDeferredActions() {
    
    
    while (!mDeferredActions.empty()) {
    
    
        // We won't execute any deferred actions until we're no longer in
        // an intermediate state, i.e. one more more decoders are currently
        // flushing or shutting down.

        if (mFlushingAudio != NONE || mFlushingVideo != NONE) {
    
    
            // We're currently flushing, postpone the reset until that's
            // completed.

            ALOGV("postponing action mFlushingAudio=%d, mFlushingVideo=%d",
                  mFlushingAudio, mFlushingVideo);

            break;
        }

        sp<Action> action = *mDeferredActions.begin();
        mDeferredActions.erase(mDeferredActions.begin());

        action->execute(this);
    }
}

猜你喜欢

转载自blog.csdn.net/u012430727/article/details/112258632