NuPlayer源码分析一:播放器创建

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));
}

留下了三个比较重要的调用,先依次说一下它们的作用,然后再展开:

  1. MediaPlayerFactory::getPlayerType:该函数涉及Android底层媒体播放器的评分机制。通过评分,获得一个最优的播放器类型,具体怎么得到播放器类型,请阅:Android Framework层播放器评分机制,因为AndroidO只剩下了NuPlayerTestPlayer两种播放器,TestPlayer并未正式启用。所以,函数调用返回的是NuPlayer对应的播放器类型NU_PLAYER
  2. setDataSource_pre:该函数的作用,是根据前面获得的播放器类型创建播放器对象。
  3. setDataSource_post:将媒体资源设置给播放器,这才是真正的setDataSource操作。

接下来展开:setDataSource_presetDataSource_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播放器由NuPlayerDriverNuPlayer两部分组成,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逻辑外,最重要的就是以下三个操作了:

  1. mState(STATE_IDLE):将播放器状态设置为STATE_IDLE(空闲)。
  2. new NuPlayer(pid):创建一个NuPlayer对象,并让NuPlayerDriver持有NuPlayer的引用。这里稍后展开。
  3. setDriver(this):将NuPlayerDriver设置给NuPlayer,让NuPlayer持有NuPlayerDriver的引用。

第二和第三点,让NuPlayerDriverNuPlayer相互持有引用,目的是在后续的流程控制中,方便彼此回调,配合工作。

到这里,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。所以,来看一下NuPlayersetAudioSink

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;
        }

关于AMessagemsg->findObject等代码和为什么这么调用,可以去快速看一下Android媒体底层通信框架Native Handler(三):NuPlayer的总结部分。

可以看出,不管是NuPlayerDriver还是NuPlayersetAudioSink代码,都是将新建的AudioOutput对象存在对应的mAudioSink字段中了,方便以后播放音频做准备。

setDataSource

当前代码位置:

在这里插入图片描述

在前一个流程中,创建了NuPlayerNuPlayerDriver对象,并将NuPlayerDriver对象指针保存在了p中,接着,通过p调用了NuPlayerDriversetDataSource函数。

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:只是一个解封装格式的类,同样的类还有RTSPSourceHTTPLiveSource等,是媒体流信息的直接处理者。媒体源信息也将被设置到该对象中。这会在一下篇文章进行展开,这里就先留个疑问。
  • source->setDataSource:将媒体流(源)设置给解封装格式的解析器,这个也在下一篇文章中展开。
  • msg->post():通过NativeHandler机制,将函数执行结果,也就是新创建的source对象发送给下一个函数执行onMessageReceived,这个过程是异步的,当前函数执行到这里就会退栈。

创建了一个解封装格式的解析器后,将结果postNuPlayer::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流程执行完毕。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章