Android 13 - Media框架(5)- NuPlayerDriver

前面的章节中我们了解到上层调用setDataSource后,MediaPlayerService::Client(IMediaPlayer)会调用MediaPlayerFactory创建MediaPlayerBase。Android为我们提供了默认的播放器实现NuPlayer,NuPlayerDriver实现了MediaPlayerBase接口,内部调用并管理有NuPlayer,起着承上启下的作用。

本节代码参考:
frameworks/av/media/libmediaplayerservice/include/MediaPlayerInterface.h
frameworks/av/media/libmediaplayerservice/nuplayer

1、MediaPlayerBase

MediaPlayerBase 是一个抽象类,定义了播放器需要的基本接口,并给出了一些方法的默认实现,如果我们想要实现播放器并且接入MediaPlayer,那么必须要继承于 MediaPlayerBase。

MediaPlayerBase::Listener 是一个内部抽象类,通过它可以将事件上抛给上层 MediaPlayerService::Client,MediaPlayerService::Client 中有 Listener 的具体实现。

MediaPlayerBase::AudioSink 同样是内部抽象类,定义了 Audio Output 所需要的基本接口,这个类用于走AudioTrack / AudioFlinger 进行软件合成输出声音,MediaPlayerService::AudioOutput 是 software output 的具体实现。

MediaPlayerInterface 继承于 MediaPlayerBase,实现了 hardwareOutput 方法,它走的是 software output,需要用到上面提到的 AudioOutput。我们接下来要看的 NuPlayerDriver 继承自于这个类,所以声音走的软件合成。

MediaPlayerHWInterface 也是继承于 MediaPlayerBase,从名字上就可以看出它走的是 hardware output,是没有setAudioSink接口的,具体如何进行硬件合成需要 vendor 自己来实现。

通常来说,我们实现的播放器需要继承于 MediaPlayerInterface / MediaPlayerHWInterface,确定声音走软件还是硬件合成。如果我们的播放器需要两者都支持,也可以直接继承于MediaPlayerBase,或者继承于MediaPlayerInterface。

MediaPlayerBase 提供有基础而全面的播放接口,但是如果我们实现的播放器还有更多的功能,可以使用它提供的 invoke 函数来实现自定义接口的调用。


2、NuPlayerDriver

NuPlayerDriver 会对上层下发的指令进行处理,根据当前的状态调用 NuPlayer 对应的功能。

2.1、NuPlayerDriver调用机制的理解

NuPlayer 内部使用的是 AMessage - AHandler - ALooper 这一套异步消息处理机制,从观察来看播放控制接口,例如 setDataSource、prepare、start、pause、reset 这类播控制接口都是异步调用的(NuPlayer 没有 stop);设定或获取一些参数的接口,例如 getTrackInfo、getSelectedTrack 这类都是同步调用的。

回到 NuPlayerDriver 中来,通过搜索 mCondition.wait 来看哪些接口是同步调用的,查找到 setDataSource、setVideoSurfaceTexture、prepare 和 reset 这四个接口是同步调用的。为什么他们四个是同步调用的,而其他的 start、pause、stop 是异步调用的呢?

我的理解是这样:异步调用的接口会依赖同步调用的4个接口所创建的对象,例如 setDataSource 会创建出 NuPlayer 中的 Source 对象,如果这步是异步的,Source 还没有创建出来就调用 prepare,那就会出现空指针的错误了;又比如 reset 会销毁 MediaPlayerClient 及其内部的对象,如果是异步的,reset处理过程中重新调用其他接口,也是很有可能出现空指针的问题。所以这类会创建或者销毁成员对象的接口必须要进行同步调用!

而 start、pause、stop 这类接口并不会创建或者销毁某些成员变量,不会对前后调用产生影响,调用后会在 ALooper 中按照调用顺序执行,所以异步处理不会有问题。

2.2、NuPlayerDriver中的状态

我们再看下 NuPlayer 中的基本播放控制方法,总共有 setDataSourceAsyncprepareAsyncstartpauseresetAsync 这5个,是没有stop 的,NuPlayer 的 stop 就是用的 pause 来假装的。

接下来再看 NuPlayer 有哪些状态,状态如下:

cpp 复制代码
    enum State {
        STATE_IDLE,
        STATE_SET_DATASOURCE_PENDING,
        STATE_UNPREPARED,
        STATE_PREPARING,
        STATE_PREPARED,
        STATE_RUNNING,
        STATE_PAUSED,
        STATE_RESET_IN_PROGRESS,
        STATE_STOPPED,                  // equivalent to PAUSED
        STATE_STOPPED_AND_PREPARING,    // equivalent to PAUSED, but seeking
        STATE_STOPPED_AND_PREPARED,     // equivalent to PAUSED, but seek complete
    };
  1. STATE_IDLE:MediaPlayerService::Client 调用 setDataSource 刚刚创建 NuPlayerDriver 时状态为 STATE_IDLE,reset 之后状态也变成 STATE_IDLE;
  2. STATE_SET_DATASOURCE_PENDING:调用 MediaPlayerBase setDataSource 后状态变成 STATE_SET_DATASOURCE_PENDING,这个过程可能会比较耗时,所以在等待过程中设置了这个中间状态,如果有人错误使用了多线程调用MediaPlayer,那么这个中间状态将会阻止这个错误调用;
  3. STATE_UNPREPARED:setDataSource 之后的状态置为 STATE_UNPREPARED;
  4. STATE_PREPARING:同样的,prepare过程可能会比较耗时,所以也设置了这个中间状态;prepare 有同步和异步两个版本,同步版本的作用和 SET_DATASOURCE_PENDING 作用相同;异步的版本会标记当前的状态为 preparing,当 prepareAsync 过程中调用 reset 销毁对象时,会直接退出 STATE_PREPARING 状态,进入到 reset 的状态中;
  5. STATE_RUNNING:正在播放的状态,这个状态下 isPlaying 接口返回值为 true;
  6. STATE_PAUSED:暂停状态为STATE_PAUSED,播放结束的状态也是STATE_PAUSED;
  7. STATE_STOPPED:停止播放状态;
  8. STATE_RESET_IN_PROGRESS:reset 的处理过程会将状态置为 STATE_RESET_IN_PROGRESS;
  9. STATE_STOPPED_AND_PREPARING:stop 之后需要重新 prepare 才能继续播放,stop 时资源都没有释放,所以是直接 seek 到文件起始位置播放;但是这里的 preparing 并不会影响 reset 的动作。
  10. STATE_STOPPED_AND_PREPARED:stop 之后 prepare 调用完成,状态置为 STATE_STOPPED_AND_PREPARED。

2.3、NuPlayerDriver中的状态切换

2.3.1、start

cpp 复制代码
status_t NuPlayerDriver::start_l() {
    switch (mState) {
    	// 1、
        case STATE_UNPREPARED:
        {
            status_t err = prepare_l();
            CHECK_EQ(mState, STATE_PREPARED);
            FALLTHROUGH_INTENDED;
        }
        // 2
        case STATE_PAUSED:
        case STATE_STOPPED_AND_PREPARED:
        case STATE_PREPARED:
        {
            mPlayer->start();
            FALLTHROUGH_INTENDED;
        }
        // 3
        case STATE_RUNNING:
        {
            if (mAtEOS) {
                mPlayer->seekToAsync(0);
                mAtEOS = false;
                mPositionUs = -1;
            }
            break;
        }
        default:
            return INVALID_OPERATION;
    }
    mState = STATE_RUNNING;
    return OK;
}

从以上代码可以看出,start 的处理情况有4种:

  1. 当前状态为 STATE_UNPREPARED 调用 start:这种情况应该来说是不会出现的,因为在 MediaPlayer Native 层已经进行了状态处理;
  2. 当前状态为 STATE_PAUSED、STATE_STOPPED_AND_PREPARED、STATE_PREPARED:这三种状态下直接调用 start 即可;如果调用之前出现错误,则seek到0的位置;
  3. 当前状态为 STATE_RUNNING:不做处理;
  4. 其他状态调用 start 返回 INVALID_OPERATION,MediaPlayer Native 层抛出 Error;

2.3.2、pause

cpp 复制代码
status_t NuPlayerDriver::pause() {
    ALOGD("pause(%p)", this);
    int unused;
    getCurrentPosition(&unused);

    Mutex::Autolock autoLock(mLock);

    switch (mState) {
    	// 1
        case STATE_PAUSED:
        case STATE_PREPARED:
            return OK;
		// 2
        case STATE_RUNNING:
            mState = STATE_PAUSED;
            notifyListener_l(MEDIA_PAUSED);
            mPlayer->pause();
            break;

        default:
            return INVALID_OPERATION;
    }

    return OK;
}

pause 的处理情况有3种:

  1. 状态为 STATE_PAUSED、STATE_PREPARED:不做处理;
  2. 状态为 STATE_RUNNING,调用 pause 方法;

2.3.3、stop

cpp 复制代码
status_t NuPlayerDriver::stop() {
    ALOGD("stop(%p)", this);
    Mutex::Autolock autoLock(mLock);

    switch (mState) {
    	// 1
        case STATE_RUNNING:
            mPlayer->pause();
            FALLTHROUGH_INTENDED;
		// 2
        case STATE_PAUSED:
            mState = STATE_STOPPED;
            notifyListener_l(MEDIA_STOPPED);
            break;
		// 3
        case STATE_PREPARED:
        case STATE_STOPPED:
        case STATE_STOPPED_AND_PREPARING:
        case STATE_STOPPED_AND_PREPARED:
            mState = STATE_STOPPED;
            break;
		// 4
        default:
            return INVALID_OPERATION;
    }
    return OK;
}
  1. 状态为 STATE_RUNNING:调用 pause 方法,并将状态置为 STATE_STOPPED;
  2. 状态为 STATE_PAUSED:直接将状态置为 STATE_STOPPED;
  3. 状态为 STATE_PREPARED、STATE_STOPPED、STATE_STOPPED_AND_PREPARING、STATE_STOPPED_AND_PREPARED:这些状态下本就没有开始播放,所以直接将状态置为 STATE_STOPPED;
  4. 其他状态返回 INVALID_OPERATION;

2.3.4、reset

cpp 复制代码
status_t NuPlayerDriver::reset() {
    ALOGD("reset(%p) at state %d", this, mState);

    updateMetrics("reset");
    logMetrics("reset");

    Mutex::Autolock autoLock(mLock);

    switch (mState) {
    	// 1
        case STATE_IDLE:
            return OK;
		// 2
        case STATE_SET_DATASOURCE_PENDING:
        case STATE_RESET_IN_PROGRESS:
            return INVALID_OPERATION;
		// 3
        case STATE_PREPARING:
        {
            CHECK(mIsAsyncPrepare);

            notifyListener_l(MEDIA_PREPARED);
            break;
        }

        default:
            break;
    }

    if (mState != STATE_STOPPED) {
        notifyListener_l(MEDIA_STOPPED);
    }

    mState = STATE_RESET_IN_PROGRESS;
    mPlayer->resetAsync();

    while (mState == STATE_RESET_IN_PROGRESS) {
        mCondition.wait(mLock);
    }
    return OK;
}

reset 的处理情况有3种:

  1. 状态为 STATE_IDLE:直接返回,不需要处理;
  2. 状态为 STATE_SET_DATASOURCE_PENDING、STATE_RESET_IN_PROGRESS:正在 reset 或者说正在初始化,同样也直接返回;
  3. 状态为 STATE_PREPARING:这时候正在 prepare,这时候直接回调上层 MEDIA_PREPARED,如果当前状态不是 stop则还要回调上层当前的状态为 STATE_STOPPED,所以我们上层应用在 prepareAsync、stop 的回调事件中应该判断当前 reset 是否被调用,如果被调用了则不应该 call start 方法,防止快切时出现问题;最后调用resetAsync,等待 reset 完成。

我们要注意的是 MediaPlayerService::Client 和 NuPlayerDriver 的 reset 方法并不会销毁对象,只有等MediaPlayer Native 的 disconnect 调用完成 MediaPlayerService 中的对象才会销毁。

2.3.5、notifyPrepareCompleted

如果在 prepareAsync 的状态下调用 reset,prepare 事件处理完成后会调用回调事件 notifyPrepareCompleted,这里面会怎么处理呢?

cpp 复制代码
void NuPlayerDriver::notifyPrepareCompleted(status_t err) {
    ALOGV("notifyPrepareCompleted %d", err);

    Mutex::Autolock autoLock(mLock);

    if (mState != STATE_PREPARING) {
    	// 直接退出
        CHECK(mState == STATE_RESET_IN_PROGRESS || mState == STATE_IDLE);
        return;
    }

    CHECK_EQ(mState, STATE_PREPARING);

    mAsyncResult = err;

    if (err == OK) {
        mState = STATE_PREPARED;
        if (mIsAsyncPrepare) {
            notifyListener_l(MEDIA_PREPARED);
        }
    } else {
        mState = STATE_UNPREPARED;
        if (mIsAsyncPrepare) {
            notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
        }
    }

    sp<MetaData> meta = mPlayer->getFileMeta();
    int32_t loop;
    if (meta != NULL
            && meta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
        mAutoLoop = true;
    }

    mCondition.broadcast();
}

从代码里可以看到,如果 prepareAsync 完成时,则直接退出回调方法,不会向上 Callback。

2.3.6、MEDIA_PLAYBACK_COMPLETE

视频播放结束后,NuPlayer 会调用 Callback 上抛 MEDIA_PLAYBACK_COMPLETE 事件,如果设置了 Loop 则自动 seek 到开头位置开始播放,否则调用 pause 方法进入暂停的状态,并且将 mAtEOS 设置为 true,最终将 MEDIA_PLAYBACK_COMPLETE 回调给上层。

cpp 复制代码
void NuPlayerDriver::notifyListener_l(
        int msg, int ext1, int ext2, const Parcel *in) {
    switch (msg) {
        case MEDIA_PLAYBACK_COMPLETE:
        {
            if (mState != STATE_RESET_IN_PROGRESS) {
                if (mAutoLoop) {
                    audio_stream_type_t streamType = AUDIO_STREAM_MUSIC;
                    if (mAudioSink != NULL) {
                        streamType = mAudioSink->getAudioStreamType();
                    }
                    if (streamType == AUDIO_STREAM_NOTIFICATION) {
                        mAutoLoop = false;
                    }
                }
                // 1、如果有循环播放则seek到开始位置,然后直接返回
                if (mLooping || mAutoLoop) {
                    mPlayer->seekToAsync(0);
                    if (mAudioSink != NULL) {
                        mAudioSink->start();
                    }
                    return;
                }
				// 2、否则进入暂停状态
                mPlayer->pause();
                mState = STATE_PAUSED;
            }
            FALLTHROUGH_INTENDED;
        }

        case MEDIA_ERROR:
        {
            if (msg == MEDIA_ERROR) {
                Mutex::Autolock autoLock(mMetricsLock);
                if (mMetricsItem != NULL) {
                    mMetricsItem->setInt32(kPlayerError, ext1);
                    if (ext2 != 0) {
                        mMetricsItem->setInt32(kPlayerErrorCode, ext2);
                    }
                    mMetricsItem->setCString(kPlayerErrorState, stateString(mState).c_str());
                }
            }
            // 3、将 mAtEOS 设置为true,标记当前状态
            mAtEOS = true;
            break;
        }

        default:
            break;
    }

    mLock.unlock();
    // 4、callback到上层
    sendEvent(msg, ext1, ext2, in);
    mLock.lock();
}
相关推荐
xiangpanf1 天前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx1 天前
安卓线程相关
android
消失的旧时光-19431 天前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon1 天前
VSYNC 信号流程分析 (Android 14)
android
dalancon1 天前
VSYNC 信号完整流程2
android
dalancon1 天前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013841 天前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android1 天前
Android 刷新一帧流程trace拆解
android
墨狂之逸才1 天前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶1 天前
记一次Gradle环境的编译问题与解决
android·前端·gradle