NuPlayer源码分析一:播放器创建
文章目录
源码环境:Oreo 8.0.0_r4
需要编译 /frameworks/av/media/libmediaplayerservice,生成libmediaplayerservice.so
该系列播放将详细分析NuPlayer源码,经过该系列博客之后,你应该就可以对底层播放器有了一个具体而全面的认识,系列文章分为下面几个部分:
简介
看过或者没看过MediaPlayer源码分析一文的朋友,都应该知道,严格意义上讲,MediaPlayer并不是播放器本身,它只是Android框架层众多媒体播放器(包括ROM厂商自定义的播放器)的“壳“。
MediaPlayer的所有主要操作(包括但不限于播放、暂停、快进等)都将通过调用链条,到达框架层的播放器,如NuPlayer。那么,这里就来分析一下,Android框架层真正的播放器,NuPlayer。
MediaPlayer和NuPlayer的关系如图:
NuPlayer前情提要
既然MediaPlayer
只是NuPlayer
等底层播放器的壳,那么是不是new MediaPlayer()
的时候,底层就会new NuPlayer()呢?
结论是否定的,对于NuPlayer播放器来说,NuPlayer类中,实现了播放相关函数,但播放器的控制流程却是在NuPlayerDriver类中实现。
虽然如此,但这也不意味着new MediaPlayer()
会导致new NuPlayerDriver()
的函数调用。
不管是NuPlayer
还是NuPlayerDriver
实例的创建,都是在MediaPlayer
实例化后的setDataSource()
函数执行过程中实现的。
MediaPlayer
的调用代码如下:
player = new MediaPlayer();
player.setDataSource(path);
至于具体调用过程就不分析了, 有兴趣或者感到迷惑的同学可以去看一看MediaPlayer源码分析就全明白了。来看一下setDataSource
部分的时序图:
我们从最开始接触到的NuPlayer世界的代码开始,也就是status_t MediaPlayerService::Client::setDataSource(...)
函数。
status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length) {
// 略掉一些对资源的判断,剩下和可能和NuPlayer有关的部分
player_type playerType = MediaPlayerFactory::getPlayerType(this,
fd,
offset,
length);
sp<MediaPlayerBase> p = setDataSource_pre(playerType);
// now set data source
return mStatus = setDataSource_post(p, p->setDataSource(fd, offset, length));
}
留下了三个比较重要的调用,先依次说一下它们的作用,然后再展开:
MediaPlayerFactory::getPlayerType
:该函数涉及Android底层媒体播放器的评分机制。通过评分,获得一个最优的播放器类型,具体怎么得到播放器类型,请阅:Android Framework层播放器评分机制,因为AndroidO只剩下了NuPlayer
和TestPlayer
两种播放器,TestPlayer
并未正式启用。所以,函数调用返回的是NuPlayer
对应的播放器类型NU_PLAYER
。setDataSource_pre
:该函数的作用,是根据前面获得的播放器类型创建播放器对象。setDataSource_post
:将媒体资源设置给播放器,这才是真正的setDataSource
操作。
接下来展开:setDataSource_pre
和setDataSource_post
函数。
为了让小伙伴们看代码片段的时候,可以有效的形成上下文逻辑,每部分代码,都会配一张图,以说明当前代码所处位置,比如:现在我们在这里(注意红色箭头,表示当前函数位置)。
NuPlayer播放器创建
前面已经提到,NuPlayer的创建过程,是在setDataSource_pre
函数中实现,我们接下来就展开一下吧:
sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(
player_type playerType)
{
// create the right type of player
sp<MediaPlayerBase> p = createPlayer(playerType);
// 删掉了大量注册服务监听的代码,包括extractor、IOMX
if (!p->hardwareOutput()) { // 播放器音频是否通过硬件直接输出,NuPlayer是不需要的。
Mutex::Autolock l(mLock);
mAudioOutput = new AudioOutput(mAudioSessionId, IPCThreadState::self()->getCallingUid(), mPid, mAudioAttributes);
static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
}
return p;
}
createPlayer
sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)
{
// 检查当前进程,是否已经有一个播放器不同类型的播放器了,如果有,干掉它
sp<MediaPlayerBase> p = mPlayer;
if ((p != NULL) && (p->playerType() != playerType)) {
ALOGV("delete player");
p.clear();
}
if (p == NULL) { // 创建对应类型的播放器。
p = MediaPlayerFactory::createPlayer(playerType, this, notify, mPid);
}
if (p != NULL) {
p->setUID(mUid);
}
return p;
}
这个函数最重要的部分是 MediaPlayerFactory::createPlayer
:
sp<MediaPlayerBase> MediaPlayerFactory::createPlayer(
player_type playerType,
void* cookie,
notify_callback_f notifyFunc,
pid_t pid) {
sp<MediaPlayerBase> p;
IFactory* factory;
status_t init_result;
// 略掉一些非关键代码
factory = sFactoryMap.valueFor(playerType);
p = factory->createPlayer(pid);
// 略掉一些非关键代码
init_result = p->initCheck();
return p;
}
这个函数体实现的也比较简单,逻辑如下:
sFactoryMap.valueFor
:通过sFactoryMap和playerType获取播放器工厂对象。factory->createPlayer
:调用播放器工厂对象创建播放器对象。p->initCheck
:对播放器做初始化检查。
sFactoryMap.valueFor
sFactoryMap是个什么东西呢,看一下它的申明:
typedef KeyedVector<player_type, IFactory*> tFactoryMap;
static tFactoryMap sFactoryMap;
它是一个KeyedVector
的结构,以播放器类型为键,对应的播放器工厂为值。在MediaPlayerService
服务启动时,会通过MediaPlayerFactory::registerBuiltinFactories()
函数调用,将所有的播放器工厂添加到这个Map结构中。这部分逻辑,在Android Framework层播放器评分机制一文中的注册播放器工厂小节中详细分析过,就不再赘述了。
我们已经知道此时的播放器类型为NU_PLAYER,sFactoryMap.valueFor(playerType);
可以等价于:
sFactoryMap.valueFor(NU_PLAYER)
,所以,factory是NuPlayer播放器对应的工厂对象。简单看一下类图结构。
factory->createPlayer
通过类图,和前面的分析,到这里我们已经知道NuPlayer
的播放器工厂是NuPlayerFactory
类:
class NuPlayerFactory : public MediaPlayerFactory::IFactory {
public:
// 删掉了评分机制的代码
virtual sp<MediaPlayerBase> createPlayer(pid_t pid) {
ALOGV(" create NuPlayer");
return new NuPlayerDriver(pid);
}
};
说好的创建NuPlayer
播放器呢,怎么冒出来一个NuPlayerDriver
?
其实,虽然播放器叫NuPlayer
,但并意味着“播放器”只有NuPlayer
对象。实际上,NuPlayer
播放器由NuPlayerDriver
和NuPlayer
两部分组成,NuPlayer
对象负责播放、暂停等功能函数的实现,NuPlayerDriver
则负责功能的调度,和MediaPlayerSerivce
等外界沟通。
回到代码。
NuPlayerFactory::createPlayer
函数只new了一个NuPlayerDriver
,我们来看一下NuPlayerDriver
的初始化过程:
NuPlayerDriver::NuPlayerDriver(pid_t pid)
: mState(STATE_IDLE),
mLooper(new ALooper),
mPlayer(new NuPlayer(pid)),
mLooping(false),
mAutoLoop(false) {
mLooper->setName("NuPlayerDriver Looper");
mLooper->start(
false, /* runOnCallingThread */
true, /* canCallJava */
PRIORITY_AUDIO);
mLooper->registerHandler(mPlayer);
mPlayer->setDriver(this);
}
为了简洁,代码依然删掉了不少暂时并不重要的。这部分代码,其实我在Android媒体底层通信框架Native Handler(三):NuPlayer一文中已经讲过。
有所不同的是,当时侧重点放在媒体通信部分,也就是NativeHandler
逻辑部分。
NuPlayerDriver
的构造函数部分,除了NativeHandler
逻辑外,最重要的就是以下三个操作了:
mState(STATE_IDLE)
:将播放器状态设置为STATE_IDLE
(空闲)。new NuPlayer(pid)
:创建一个NuPlayer
对象,并让NuPlayerDriver
持有NuPlayer
的引用。这里稍后展开。setDriver(this)
:将NuPlayerDriver
设置给NuPlayer
,让NuPlayer
持有NuPlayerDriver
的引用。
第二和第三点,让NuPlayerDriver
和NuPlayer
相互持有引用,目的是在后续的流程控制中,方便彼此回调,配合工作。
到这里,NuPlayer
的创建过程,算是明白了。NuPlayer
的构造函数没什么好看的,就是给一堆成员赋初值的过程。
initCheck()
因为createPlayer
函数创建并返回的是NuPlayerDriver
对象,所以调用的是NuPlayerDriver::initCheck
函数:
status_t NuPlayerDriver::initCheck() {
return OK;
}
啥也没干,直接返回了OK
,有点浪费时间的感觉kugualian
代码到哪儿了?
setAudioSink
AudioOutput
对象是音频输出的抽象层,在不支持硬件驱动直接输出的接口下,需要手动设置音频输出的抽象层接口。
MediaPlayerBase
和它子类的结构图如下:
通过createPlayer
函数,返回的是NuPlayerDriver
对象。
void NuPlayerDriver::setAudioSink(const sp<AudioSink> &audioSink) {
mPlayer->setAudioSink(audioSink);
mAudioSink = audioSink;
}
这个调用的mPlayer
,在NuPlayerDriver
构造函数的初始化列表中,已经新建了一个NuPlayer
对象,并赋值给mPlayer
。所以,来看一下NuPlayer
的setAudioSink
:
void NuPlayer::setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink) {
sp<AMessage> msg = new AMessage(kWhatSetAudioSink, this);
msg->setObject("sink", sink);
msg->post();
}
case kWhatSetAudioSink:
{
sp<RefBase> obj;
CHECK(msg->findObject("sink", &obj));
mAudioSink = static_cast<MediaPlayerBase::AudioSink *>(obj.get());
break;
}
关于
AMessage
、msg->findObject
等代码和为什么这么调用,可以去快速看一下Android媒体底层通信框架Native Handler(三):NuPlayer的总结部分。
可以看出,不管是NuPlayerDriver
还是NuPlayer
的setAudioSink
代码,都是将新建的AudioOutput
对象存在对应的mAudioSink
字段中了,方便以后播放音频做准备。
setDataSource
当前代码位置:
在前一个流程中,创建了NuPlayer
和NuPlayerDriver
对象,并将NuPlayerDriver
对象指针保存在了p中,接着,通过p调用了NuPlayerDriver
的setDataSource
函数。
status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
if (mState != STATE_IDLE) { // NuPlayerDriver构造中mState被设置成了STATE_IDLE。
return INVALID_OPERATION;
}
mState = STATE_SET_DATASOURCE_PENDING; // 将播放器状态设置为STATE_SET_DATASOURCE_PENDING
mPlayer->setDataSourceAsync(fd, offset, length); // 调用NuPlayer,设置媒体源
while (mState == STATE_SET_DATASOURCE_PENDING) {
mCondition.wait(mLock); // 加锁,直到被通知唤醒
}
return mAsyncResult;
}
该函数主要作用:
- mState = STATE_SET_DATASOURCE_PENDING: 设置播放器状态,和流程控制有关,比较重要,后面很多流程都需要判断当前状态,上一个状态是
NuPlayerDriver
构造中设置的STATE_IDLE
状态。 - mPlayer->setDataSourceAsync:实际上
NuPlayerDriver
并没有处理资源的逻辑,前面也提到,它就是一层壳,需要将具体的动作交给NuPlayer
对象去做。 - while (mState == STATE_SET_DATASOURCE_PENDING):因为上一步的
setDataSourceAsync
流程中会用到NativeHandler
机制,是异步的,所以在while循环体中加了一个锁,让当前线程阻塞。直到setDataSourceAsync
流程执行完毕后,唤醒。
setDataSourceAsync
继续跟踪setDataSourceAsync
函数:
void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {
sp<AMessage> msg = new AMessage(kWhatSetDataSource, this); // 新建消息,这属于常规操作了
sp<AMessage> notify = new AMessage(kWhatSourceNotify, this); // 新建消息,用于和解封装模块通信,类似于一种listener的功能。
sp<GenericSource> source = new GenericSource(notify, mUIDValid, mUID); // 创建解封装器
status_t err = source->setDataSource(fd, offset, length); // 为GenericSource设置媒体源
msg->setObject("source", source);
msg->post(); // 将创建并设置好的setDataSource,post给下一个流程处理
mDataSourceType = DATA_SOURCE_TYPE_GENERIC_FD;
}
该函数主要逻辑如下:
- new AMessage:构建了两个消息对象,
msg
用于向下一个流程发送消息和当前函数执行的成果(source
)。notify
用于在构建GenericSource
的结果回调。 - new GenericSource:只是一个解封装格式的类,同样的类还有
RTSPSource
、HTTPLiveSource
等,是媒体流信息的直接处理者。媒体源信息也将被设置到该对象中。这会在一下篇文章进行展开,这里就先留个疑问。 - source->setDataSource:将媒体流(源)设置给解封装格式的解析器,这个也在下一篇文章中展开。
- msg->post():通过
NativeHandler
机制,将函数执行结果,也就是新创建的source
对象发送给下一个函数执行onMessageReceived
,这个过程是异步的,当前函数执行到这里就会退栈。
创建了一个解封装格式的解析器后,将结果post
到NuPlayer::onMessageReceived
函数处理:
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatSetDataSource:
{
status_t err = OK;
sp<RefBase> obj;
CHECK(msg->findObject("source", &obj));
if (obj != NULL) {
Mutex::Autolock autoLock(mSourceLock);
mSource = static_cast<Source *>(obj.get()); // 将新创建的GenericSource对象,赋值给mSource
} else {
err = UNKNOWN_ERROR;
}
sp<NuPlayerDriver> driver = mDriver.promote();
if (driver != NULL) {
driver->notifySetDataSourceCompleted(err); // 通知NuPlayerDriver,任务完成
}
break;
}
// 略去一万行代码
}
}
这段代码的重点在于:
- mSource =:将之前创建的GenericSource对象赋值给了mSource字段。
- driver->notifySetDataSourceCompleted:到这里,整个setDataSource的流程已经执行完毕,函数调用回到NuPlayerDriver中。
NuPlayerDriver::notifySetDataSourceCompleted
void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) { // err = OK;
CHECK_EQ(mState, STATE_SET_DATASOURCE_PENDING); // 当前mState为STATE_SET_DATASOURCE_PENDING
mAsyncResult = err;
mState = (err == OK) ? STATE_UNPREPARED : STATE_IDLE; // 将状态设置为STATE_UNPREPARED
mCondition.broadcast(); // 唤醒mCondition.wait(mLock);锁,完成setDataSource函数调用
}
如果没出以外,这里的入参值应该是OK
的。所以,该函数的主要操作有:
- 将当前状态设置成STATE_UNPREPARED。上一个状态未STATE_SET_DATASOURCE_PENDING。
- mCondition.broadcast():发出唤醒mCondition锁广播。
释放锁后,NuPlayerDriver::setDataSource
会将执行的结果mAsyncResult
返回给调用者。setDataSource
流程执行完毕。