Android媒体底层通信框架Native Handler(三):NuPlayer

Native Handler实例分析(NuPlayer)

该系列文章,会分为三个部分:

Native Handler的使用,在Android multimudia框架中有很多使用,原因是,多媒体处理数据通常都需要很多线程来处理对媒体帧数据。但这些线程又需要保持**happens-before **关系,处理好同步问题。

优秀的同步方案很多,但Android设计者还是选择了Native Handler的方案,和java层Handler机制保持了一致。

下面就以NuPlayer的创建过程为例,简单分析一下,Native Handler在其中的作用。

NuPlayerDriver

NuPlayer的创建过程,其实没那么简单,总入口在MediaPlayer中,中间又需要通过MediaPlayerService、打分机制来选择是否使用NuPlayer播放器、NuPlayerDriver这些结构体或者过程。

如果对MediaPlayer感兴趣的,可以进传送门:MediaPlayer源码分析,在这里,你可以了解到,NuPlayer的创建,是如何一步步从MediaPlayer走来的。

因为本文主旨是分析NativeHandler的使用,所以,就直接从NuPlayerDriver开始了。对NuPlayerDriver感兴趣的大兄弟,可以去看看我另一篇文章:传送门 TODO:将于近期更新

先看看NuPlayerDriver的头文件:

struct NuPlayerDriver : public MediaPlayerInterface {
	explicit NuPlayerDriver(pid_t pid);
    sp<ALooper> mLooper;
    const sp<NuPlayer> mPlayer;
};

头文件里边的mLooper字段够醒目的,我们可以简单通过原理图回顾一下ALooper是干什么的:
在这里插入图片描述

  • 它维护了一个消息队列mEventQueue。
  • 它有一个线程,不断地循环mEvnetQueue队列,找到符合发送条件的消息,交给AHandler去处理。

接下来具体看一下NuPlayerDriver的构造:

NuPlayerDriver::NuPlayerDriver(pid_t pid)
    : mState(STATE_IDLE),
      mLooper(new ALooper),
      mPlayer(new NuPlayer(pid)),
      mLooping(false),
      mAutoLoop(false) {
    ALOGD("NuPlayerDriver(%p) created, clientPid(%d)", this, pid);
    mLooper->setName("NuPlayerDriver Looper"); // 设置looper的名称
    mLooper->start(
            false, /* runOnCallingThread */
            true,  /* canCallJava */
            PRIORITY_AUDIO); // 启动looper中的线程。
    mLooper->registerHandler(mPlayer); 
    mPlayer->setDriver(this);
}

可以看到,整个构造函数,基本都在为mLooper(ALooper)和mPlayer(NuPlayer)的初始化。先不管省略掉的代码,依次看看构造函数中都做了什么。

  1. 默认初始化列表中new ALooper,为mLooper初始化。
  2. 默认初始化列表中new NuPlayer,为mPlayer初始化。
  3. 在代码块中,设置looper的名称。
  4. 调用looper->start启动线程,开始循环消息队列。有疑问的同志,ALooperstart一节回顾一下。
  5. 为mLooper注册Handler,参数为mPlayer。从这里,我们知道,NuPlayer一定继承了AHandler。
  6. mPlyaer设置Driver,这对本文来说,看起来似乎没啥用。

这里,着重强调一下第4步和第5步。

从第4步开始ALooper中的线程就已经开始运行,不断地循环ALooper中的loop函数了。而loop函数的作用是从消息队列中去消息,找到合适的时间“发送”出去。但现在消息队列里什么都没有,线程会进入锁等待,直到被唤醒。

第5步,registerHandler将mLooper绑定在mPlayer(也就是AHandler)中,添加到了一个ALooperRoster的花名册中去。详细请看ALooperregisterHandler&unregisterHandler一节。

小结NuPlayerDriver构造

NuPlayerDriver构造的构造函数中,新建了一个ALooper对象,一个AHandler对象(new NuPlayer)。并让ALooper中的循环线程跑起来。

NuPlayer的onMessageReceived

看过AHandler一文或者多少知道java层Handler机制的朋友,一定都知道,继承AHandler(java层是Handler)类,需要实现纯虚函数onMessageReceived(java层是onHandleMessage)。

前面我们说到,NuPlayer继承了AHandler。那么来找找NuPlayer的onMessageReceived是怎么实现的吧。

void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatSetDataSource:
        {
            ALOGV("kWhatSetDataSource");
        }
        case kWhatGetDefaultBufferingSettings:
	}
    // ......
}

代码不贴多了,意思一下就行。NuPlayer严重依靠NativeHandler机制传递消息,所以它的onMessageReceived函数需要处理的消息很多,case也很多。我们简单体会一下,onMessageReceived是怎么处理的就行了。

ALooper和AHandler在NuPlayerDriver的构造中就已经创建了,那么AMessage呢?

NuPlayer中的AMessage

我们来看一个最简单的使用AMessage的函数NuPlayer::start():

void NuPlayer::start() {
    (new AMessage(kWhatStart, this))->post();
}

函数短小精悍,一如你……

我们分成两步来看:

  1. new AMessage(kWhatStart, this):

    看起来就一步,但是啊,和很多少男少女约会一样,毛手毛脚的,小动作很多。还是弄一个图来说明一下吧。
    在这里插入图片描述
    创建Message时,会将通过setTarge函数,和handler(此处为NuPlayer对象)为该条Message的mTarget、mHandler、mLooper 字段赋值。这在后面有大用

    再说另一个入参kWhatStart,对AMessage来说,这个也很重要,用于onMessageReceived中区分不同的case。

  2. post():

    AMessage中,如果post函数调用时,无参,将使用默认初始化参数。代码如下:

     status_t post(int64_t delayUs = 0);
    

    所以,它最终调用的还是有参构造。

    还是用途来看一下:
    在这里插入图片描述
    新创建的AMessage对象,调用AMessage::post函数,将一默认的delayUs时间传递给looper执行。这个looper是通过new AMessage的过程中初始化的mLooper中获取的。

    流程进入到ALooper控制。执行ALooper::post函数,该函数将delayUs时间和AMessage对象本身封装到一个Event结构中,最后加入到mEventQueue消息队列中去。

    同时释放信号,将ALooper中等待的线程唤醒。执行LooperThread::threadLoop函数,该函数又调用mLooper->loop();,掉用到了ALooper中的loop函数中去。loop将消息队列中的消息取出,调用消息的deliver()函数,直到AHandler子类的onMessageReceived函数处理。整个流程才执行完毕。

好了,实例分析到此结束,因为前面做了不少知识铺垫,所以这里讲的就比较简单,如果有不明白的,可以把该系列前面的文章拿来研究研究。

总结NativeHandler的用法

  1. 首先要创建一个ALooper和一个AHandler(NuPlayer继承了AHandler实现了onMessageReceived,所以new NuPlayer就算是创建了一个AHandler)。并通过ALooper::registerHandler函数,将AHandler对象和ALooper对象绑定。
  2. 通过mLooper->start函数,启动消息队列的循环线程。
  3. 在需要发送消息的地方,直接创建AMessage对象,并将新的AMessage对象post的出去。
  4. AMessage对象被post后,继承了AHandler对象的类,会通过实现的onMessageReceived函数,接收到该AMessage对象,做进一步的处理。

完了,就是这么简单。现在问题来了,原理分析完了,实例也不缺。

系统分析Native Handler的初衷,是为了看懂Android底层MultiMedia框架部分关于消息传递的代码。现在来说,就很简单了,只要紧盯AMessage创建时,设置的消息tag是什么,在实现了onMessageReceived函数的地方,找到从AMeesage对象通过what()函数取出的tag所对应的case就可以了。

当然,虽然自己实现一个底层播放器的机会比较少,但不排除有人去探索的。该系列文章也可以为自己实现播放器,需要处理多线程消息传递的哥们提供参考。需要使用哪些头文件啊,怎么实现之类的,去隔壁NuPlayer抄作业就好。

那么,就这样吧。

源码相关路径

Android底层代码,一般*.h文件和.cpp*文件都存放在不同路径下。

头文件

/frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.h

.cpp文件

/frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值