Android MediaPlayer在seek视频时可能会黑屏卡顿好几秒且进度条不动但有声音播放的问题源码解析_android mvideoplayer视频播放进度条不对无页面-CSDN博客

Android MediaPlayer在seek视频时可能会黑屏卡顿好几秒且进度条不动但有声音播放的问题源码解析

根本原因:个别视频格式播放器【特别是video decoder软解码器】解码速度较慢导致的。
从源码分析原因就是:
一、当前视频支持seek到非关键帧,导致其seek位置前最近的一个关键帧开始到seek位置的视频帧数据将会被drop掉,并且延迟通知notifySeekCompleted事件给APP,直到解码数据到seek位置时才通知,但此时seek位置的视频帧播放时间已经错过了音频播放时间,延迟太长而被丢弃,一直解码出来的视频数据直到丢弃到当前音频播放时间点的4毫秒范围内,才认为是音视频同步播放数据,才可进行渲染,因此seek时遇到的黑屏卡顿是有两个阶段的卡顿原因:
1.1、seek位置前已解码的视频帧drop掉,直到seek位置才通知seek完成;
1.2、seek位置准备开始渲染但由于音视频渲染机制是以音频播放时间为基准,因此还是会丢弃延迟太长的视频帧,直到解码的视频帧时间符合音视频同步机制才可进行渲染播放。
二、而进度条不动的原因是:通知notifySeekCompleted事件的延迟导致上层每隔1秒调用getCurrentPosition方法时在NuPlayerDriver中会判断是否正在seeking,如果正在seeking就会直接返回seek的位置给到上层,直到该通知事件收到之后才会返回真正正在播放的时间点给到上层APP更新,也就导致了进度条不更新但声音的现象。

源码分析流程:【android 10.0版本】
1、seek调用流程为:
MediaPlayer.java的seekTo() ===> android_media_MediaPlayer.cpp的xxx_seekTo() ===> mediaplayer.cpp的seekTo() ===> NuPlayerDriver.cpp的seekTo() ===> NuPlayer.cpp的seekToAsync(),然后发送kWhatSeek事件给自己的工作线程的onMessageReceived去接收和处理,此事件调用的时候分两种情况处理:1、当前还未播放;2、当前正在播放。

此问题只分析当前正在播放时的流程,因为黑屏但有声音播放就是发生在正在播放流程中,并且其seek mode方式为设置了支持seek到非关键帧功能才会出现黑屏比较久。

2、源码分析:
2.1、第1小节中调用流程在NuPlayerDriver之前的处理均为透传调用,因此不分析,此处开始分析:

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp]
status_t NuPlayerDriver::seekTo(int msec, MediaPlayerSeekMode mode) {
   
    ALOGV("seekTo(%p) (%d ms, %d) at state %d", this, msec, mode, mState);
    Mutex::Autolock autoLock(mLock);

    int64_t seekTimeUs = msec * 1000LL;

    switch (mState) {
   
        case STATE_PREPARED:
        case STATE_STOPPED_AND_PREPARED:
        case STATE_PAUSED:
        case STATE_RUNNING:
        {
   
            // 设置此处的两个标记值
            mAtEOS = false;
            mSeekInProgress = true;
            // seeks can take a while, so we essentially paused
            // 注意这个通知从源码调用链分析可知对于上层APP是接收不到的,
            // 只是给到了MediaPlayer内部类TimeProvider去处理了
            notifyListener_l(MEDIA_PAUSED);
            // 调用NuPlayer.seekToAsync方法
            // 见第2.2小节分析
            mPlayer->seekToAsync(seekTimeUs, mode, true /* needNotify */);
            break;
        }

        default:
            return INVALID_OPERATION;
    }
    // 若seek执行了,则记录当前seek时间点
    mPositionUs = seekTimeUs;
    return OK;
}

// 此处先分析下seeking状态时上层APP获取播放进度不变化的处理:
status_t NuPlayerDriver::getCurrentPosition(int *msec) {
   
    int64_t tempUs = 0;
    {
   
        Mutex::Autolock autoLock(mLock);
        if (mSeekInProgress || (mState == STATE_PAUSED && !mAtEOS)) {
   
            // 由上面的seekTo方法处理可知,mSeekInProgress为true,
            // 进入此处处理即直接将上次seek的位置时间返回给了上层APP,
            // 而该标识是在seek完成通知事件处理中设为false的,
            // 因此造成了若seek完成通知事件延迟很久,那么上层APP获取到的进度值将不会改变。
            tempUs = (mPositionUs <= 0) ? 0 : mPositionUs;
            *msec = (int)divRound(tempUs, (int64_t)(1000));
            return OK;
        }
    }

    // 下面就是正常播放流程中获取当前播放位置的处理,此处不展开分析,可见后续MediaPlayer框架实现分析章节
    
    status_t ret = mPlayer->getCurrentPosition(&tempUs);

    Mutex::Autolock autoLock(mLock);
    // We need to check mSeekInProgress here because mPlayer->seekToAsync is an async call, which
    // means getCurrentPosition can be called before seek is completed. Iow, renderer may return a
    // position value that's different the seek to position.
    if (ret != OK) {
   
        tempUs = (mPositionUs <= 0) ? 0 : mPositionUs;
    } else {
   
        mPositionUs = tempUs;
    }
    *msec = (int)divRound(tempUs, (int64_t)(1000));
    return OK;
}

2.2、seekToAsync源码分析:

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::seekToAsync(int64_t seekTimeUs, MediaPlayerSeekMode mode, bool needNotify) {
   
    // 此处只是发送了kWhatSeek事件给自己Looper工作线程接收,见下面分析
    sp<AMessage> msg = new AMessage(kWhatSeek, this);
    msg->setInt64("seekTimeUs", seekTimeUs);
    msg->setInt32("mode", mode);
    msg->setInt32("needNotify", needNotify);
    msg->post();
}

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
   
    switch (msg->what()) {
   
        // 省略其它代码
        case kWhatSeek:
        {
   
            int64_t seekTimeUs;
            int32_t mode;
            int32_t needNotify;
            CHECK(msg->findInt64("seekTimeUs", &seekTimeUs));
            CHECK(msg->findInt32("mode", &mode));
            CHECK(msg->findInt32("needNotify", &needNotify));

            ALOGV("kWhatSeek seekTimeUs=%lld us, mode=%d, needNotify=%d",
                    (long long)seekTimeUs, mode, needNotify);

            // 分两种情况处理:1、当前还未播放;2、当前正在播放
            // 但我们此处只分析当前正在播放时的流程,因为黑屏但有声音播放就是发生在正在播放流程中,
            // 因为未播放流程中seek之后会立即暂停即基本不会出现声音播放现象
            
            if (!mStarted) {
   
                // 当前还未播放时
                // Seek before the player is started. In order to preview video,
                // need to start the player and pause it. This branch is called
                // only once if needed. After the player is started, any seek
                // operation will go through normal path.
                // Audio-only cases are handled separately.
                onStart(seekTimeUs, (MediaPlayerSeekMode)mode);
                if (mStarted) {
   
                    onPause();
                    mPausedByClient = true;
                }
                if (needNotify) {
   
                    notifyDriverSeekComplete();
                }
                break;
            }

            // 当前正在播放时,此处走了三个流程:
            // 1、先处理音视频解码器的flush刷新清空缓冲数据;【注意不会shutdown解码器,此时解码器只是暂停状态】
            // 2、decoder flush完成后执行seek定位操作;
            // 3、然后恢复解码器进行解码新seek的音视频数据进行播放。
            
            // 此处是将三个动作封装为三个待执行的Action操作类中,然后进行逐一执行
            
            // 见2.3小节分析
            mDeferredActions.push_back(
                    new FlushDecoderAction(FLUSH_CMD_FLUSH /* audio */,
                                           FLUSH_CMD_FLUSH /* video */));

            // 见2.4小节分析
            mDeferredActions.push_back(
                    new SeekAction(seekTimeUs, (MediaPlayerSeekMode)mode));

            // 见2.5小节分析
            // 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.
            mDeferredActions.push_back(
                    new ResumeDecoderAction(needNotify));

            // 此处很简单就是按顺序执行上面三个流程Action
            processDeferredActions();
            break;
        }
    }
}

2.3、FlushDecoderAction实现为:
【处理音视频解码器的flush刷新清空缓冲数据】

// TODO 该流程对于该问题可以不用分析,只需知道其的作用即可。在后续的MediaPlayer架构实现中会详细分析

2.4、SeekAction实现流程分析:
【decoder flush完成后执行seek定位操作】

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
struct NuPlayer::SeekAction : public Action {
   
    explicit SeekAction(int64_t seekTimeUs, MediaPlayerSeekMode mode)
        : mSeekTimeUs(seekTimeUs),
          mMode(mode) {
   
    }

    virtual void execute(NuPlayer *player) {
   
        // 最终会执行NuPlayer的该方法进行seek数据
        // 见下面的分析
        player->performSeek(mSeekTimeUs, mMode);
    }

private:
    int64_t mSeekTimeUs;
    MediaPlayerSeekMode mMode;

    DISALLOW_EVIL_CONSTRUCTORS(SeekAction);
};

// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp]
void NuPlayer::performSeek(int64_t seekTimeUs, MediaPlayerSeekMode mode) {
   
    ALOGV("performSeek seekTimeUs=%lld us (%.2f secs), mode=%d",
          (long long)seekTimeUs, seekTimeUs / 1E6, mode)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值