全新系列文章已更新:
- Android Media Framework - 开篇
- Android Media Framework(一)OpenMAX 框架简介
- Android Media Framework(二)OpenMAX 类型阅读与分析
- Android Media Framework(三)OpenMAX API阅读与分析
- Android Media Framework(四)Non-Tunneled组件的状态转换与buffer分配过程分析
- Android Media Framework(五)Tunnel Mode
- Android Media Framework(六)插件式编程与OMXStore
- Android Media Framework(七)MediaCodecService
- Android Media Framework(八)OMXNodeInstance - Ⅰ
- Android Media Framework(九)OMXNodeInstance - Ⅱ
- Android Media Framework(十)OMXNodeInstance - Ⅲ
- Android Media Framework(十一)OMXNodeInstance - Ⅳ
- Android Media Framework(十二)OMXNodeInstance - Ⅴ
- Android Media Framework(十三)ACodec - Ⅰ
- Android Media Framework(十四)ACodec - Ⅱ
- Android Media Framework(十五)ACodec - Ⅲ
- Android Media Framework(十六)ACodec - Ⅳ
前面的章节中我们了解到上层调用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 中的基本播放控制方法,总共有 setDataSourceAsync
、prepareAsync
、start
、pause
和 resetAsync
这5个,是没有stop 的,NuPlayer 的 stop 就是用的 pause 来假装的。
接下来再看 NuPlayer 有哪些状态,状态如下:
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
};
STATE_IDLE
:MediaPlayerService::Client 调用 setDataSource 刚刚创建 NuPlayerDriver 时状态为 STATE_IDLE,reset 之后状态也变成 STATE_IDLE;STATE_SET_DATASOURCE_PENDING
:调用 MediaPlayerBase setDataSource 后状态变成 STATE_SET_DATASOURCE_PENDING,这个过程可能会比较耗时,所以在等待过程中设置了这个中间状态,如果有人错误使用了多线程调用MediaPlayer,那么这个中间状态将会阻止这个错误调用;STATE_UNPREPARED
:setDataSource 之后的状态置为 STATE_UNPREPARED;STATE_PREPARING
:同样的,prepare过程可能会比较耗时,所以也设置了这个中间状态;prepare 有同步和异步两个版本,同步版本的作用和 SET_DATASOURCE_PENDING 作用相同;异步的版本会标记当前的状态为 preparing,当 prepareAsync 过程中调用 reset 销毁对象时,会直接退出 STATE_PREPARING 状态,进入到 reset 的状态中;STATE_RUNNING
:正在播放的状态,这个状态下 isPlaying 接口返回值为 true;STATE_PAUSED
:暂停状态为STATE_PAUSED,播放结束的状态也是STATE_PAUSED;STATE_STOPPED
:停止播放状态;STATE_RESET_IN_PROGRESS
:reset 的处理过程会将状态置为 STATE_RESET_IN_PROGRESS;STATE_STOPPED_AND_PREPARING
:stop 之后需要重新 prepare 才能继续播放,stop 时资源都没有释放,所以是直接 seek 到文件起始位置播放;但是这里的 preparing 并不会影响 reset 的动作。STATE_STOPPED_AND_PREPARED
:stop 之后 prepare 调用完成,状态置为 STATE_STOPPED_AND_PREPARED。
2.3、NuPlayerDriver中的状态切换
2.3.1、start
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种:
- 当前状态为 STATE_UNPREPARED 调用 start:这种情况应该来说是不会出现的,因为在 MediaPlayer Native 层已经进行了状态处理;
- 当前状态为 STATE_PAUSED、STATE_STOPPED_AND_PREPARED、STATE_PREPARED:这三种状态下直接调用 start 即可;如果调用之前出现错误,则seek到0的位置;
- 当前状态为 STATE_RUNNING:不做处理;
- 其他状态调用 start 返回 INVALID_OPERATION,MediaPlayer Native 层抛出 Error;
2.3.2、pause
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种:
- 状态为 STATE_PAUSED、STATE_PREPARED:不做处理;
- 状态为 STATE_RUNNING,调用 pause 方法;
2.3.3、stop
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;
}
- 状态为 STATE_RUNNING:调用 pause 方法,并将状态置为 STATE_STOPPED;
- 状态为 STATE_PAUSED:直接将状态置为 STATE_STOPPED;
- 状态为 STATE_PREPARED、STATE_STOPPED、STATE_STOPPED_AND_PREPARING、STATE_STOPPED_AND_PREPARED:这些状态下本就没有开始播放,所以直接将状态置为 STATE_STOPPED;
- 其他状态返回 INVALID_OPERATION;
2.3.4、reset
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种:
- 状态为 STATE_IDLE:直接返回,不需要处理;
- 状态为 STATE_SET_DATASOURCE_PENDING、STATE_RESET_IN_PROGRESS:正在 reset 或者说正在初始化,同样也直接返回;
- 状态为 STATE_PREPARING:这时候正在 prepare,这时候直接回调上层
MEDIA_PREPARED
,如果当前状态不是 stop则还要回调上层当前的状态为 STATE_STOPPED,所以我们上层应用在 prepareAsync、stop 的回调事件中应该判断当前 reset 是否被调用,如果被调用了则不应该 call start 方法,防止快切时出现问题;最后调用resetAsync,等待 reset 完成。
我们要注意的是 MediaPlayerService::Client 和 NuPlayerDriver 的 reset 方法并不会销毁对象,只有等MediaPlayer Native 的 disconnect 调用完成 MediaPlayerService 中的对象才会销毁。
原文阅读:
Android Media Framework(六)NuPlayerDriver
扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。