Android—NuPlayer框架

举报
程思扬 发表于 2022/01/05 15:40:30 2022/01/05
【摘要】 在实现上NuPlayer和Awesomeplayer不同,NuPlayer基于StagefrightPlayer的基础类构建,利用了更底层的ALooper/AHandler机制来异步地处理请求,ALooper列队消息请求,AHandler中去处理,所以有更少的Mutex/Lock在NuPlayer中。Awesomeplayer中利用了omxcodec而NuPlayer中利用了Acodec

如标题所说,接下来讲的是NuPlayer,不知道对这个,大家了解多少呢。

Android2.3时引入流媒体框架,而流媒体框架的核心是NuPlayer。在之前的版本中一般认为Local
Playback就用Stagefrightplayer+Awesomeplayer,流媒体用NuPlayer。Android4.0之后HttpLive和RTSP协议开始使用NuPlayer播放器,Android5.0(L版本)之后本地播放也开始使用NuPlayer播放器。
Android7.0(N版本)则完全去掉了Awesomeplayer。
通俗点说,NuPlayer是AOSP中提供的多媒体播放框架,能够支持本地文件、HTTP(HLS)、RTSP等协议的播放,通常支持H.264、H.265/HEVC、AAC编码格式,支持MP4、MPEG-TS封装。
在实现上NuPlayer和Awesomeplayer不同,NuPlayer基于StagefrightPlayer的基础类构建,利用了更底层的ALooper/AHandler机制来异步地处理请求,ALooper列队消息请求,AHandler中去处理,所以有更少的Mutex/Lock在NuPlayer中。Awesomeplayer中利用了omxcodec而NuPlayer中利用了Acodec。

结构

NuPlayer 是从 MediaPlayerFactory构造出来的实例 NuPlayerFactory产生的,其结构关系图 如图5-1所示。
MediaPlayerFactory 通过工厂模式创建 StagefrightFactory 和 NuPlayerFactory,然后通过 NuPlayerFactory 创建 NuPlayerDriver,接着通过 NuPlayerDriver 构建一个 NuPlayer,NuPlayer 作为播放器,其中涉及数据解析、解码、渲染等过程。

下图为结构关系图
在这里插入图片描述
NuPlayer 主要用于处理流媒体播放,自然会涉及通过不同流媒体协议传输过来的数据,并有对应的解析和处理逻辑,下面看看NuPlayer的类关系图
在这里插入图片描述

Android层的多媒体框架,有多层实现,甚至有跨进程的调用。

  • NuPlayer::Source:解析模块(parser,功能类似FFmpeg的avformat)。其接口与MediaExtractor和MediaSource组合的接口差不多,同时提供了用于快速定位的seekTo接口。
  • NuPlayer::Decoder:解码模块(decoder,功能类似FFmpeg的avcodec),封装了用于AVC、AAC解码的接口,通过ACodec实现解码(包含OMX硬解码和软解码)。
  • NuPlayer::Render:渲染模块(render,功能类似声卡驱动和显卡驱动),主要用于音视频渲染和同步,与NativeWindow有关。

在接下来的文章呢,也会详细讲解下这三个模块。

构造

NuPlayer 的构建呢,是在上层调用 setDataSource函数后,到达 MediaPlayerService中的 setDataSource函数,通过getPlayerType函数获取播放器类型。

首先说一下播放器的类型枚举
在**/frameworks/av/include/media/MediaPlayerInterface.h**录下。

enum player_type {
    STAGEFRIGHT_PLAYER = 3,
    NU_PLAYER = 4,
    // Test players are available only in the 'test' and 'eng' builds.
    // The shared library with the test player is passed passed as an
    // argument to the 'test:' url in the setDataSource call.
    TEST_PLAYER = 5,
};
status_t MediaPlayerService::Client::setDataSource(
        const sp<IStreamSource> &source) {
    // create the right type of player
    player_type playerType = MediaPlayerFactory::getPlayerType(this, source);
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    if (p == NULL) {
        return NO_INIT;
   }

    // now set data source
   setDataSource_post(p, p->setDataSource(source));
   return mStatus;
}

现在再看一下 getPlayerType 方法

player_type MediaPlayerFactory::getPlayerType(const sp<IMediaPlayer>& client,
                                              const sp<IStreamSource> &source) {
    GET_PLAYER_TYPE_IMPL(client, source);
}

接下来再往下可以看到一个宏函数,简单介绍一下 #define 的作用

在C或C++语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。

在C或C++语言中,“宏”分为有参数和无参数两种。

#define GET_PLAYER_TYPE_IMPL(a...)                      \
    Mutex::Autolock lock_(&sLock);                      \
                                                        \
    player_type ret = STAGEFRIGHT_PLAYER;               \
    float bestScore = 0.0;                              \
                                                        \
    for (size_t i = 0; i < sFactoryMap.size(); ++i) {   \
                                                        \
        IFactory* v = sFactoryMap.valueAt(i);           \
        float thisScore;                                \
        CHECK(v != NULL);                               \
        thisScore = v->scoreFactory(a, bestScore);      \
        if (thisScore > bestScore) {                    \
            ret = sFactoryMap.keyAt(i);                 \
            bestScore = thisScore;                      \
        }                                               \
    }                                                   \
                                                        \
    if (0.0 == bestScore) {                             \
        ret = getDefaultPlayerType();                   \
    }                                                   \
                                                        \
    return ret;

这个宏函数的标示遍历map中存放的播放器工厂类,调用 scoreFactory 可以得到播放器的播放能力。这时候根据前面 StagefrightPlayerFactory 中的 if判断逻辑,thisScore>bestScore条 件不成立,所以得到 thisScore是0.0,而如果是NuPlayer,默认就会有一个0.8的值,所以返回的 ret 就是 NuPlayerFactory 对象。如果得到的值是 0.0,就会进入 getDefaultPlayerType函 数,代码如下:

static player_type getDefaultPlayerType() {
   char value[PROPERTY_VALUE_MAX];
   if (property_get("media.stagefright.use-awesome", value, NULL)
           && (!strcmp("1", value) || !strcasecmp("true", value))) {
       return STAGEFRIGHT_PLAYER;
   }

   return NU_PLAYER;
}

也就是如果设置了 property 是 media.stagefright.use-awesome,才会走到 StagefrightPlayer- Factory, 默认是 NuPlayerFactory。可见 Google 已经逐步替换掉 StagefrightPlayer 而使用 NuPlayer了。
针对 StagefrightPlayerFactory 会创建 StagefrightPlayer,而针对 NuPlayerFactory 不会直接创 建NuPlayer,而是在NuPlayerDriver的构造函数中创建一个NuPlayerDriver:

NuPlayerDriver::NuPlayerDriver(pid_t pid)
    : mState(STATE_IDLE),
      mIsAsyncPrepare(false),
      mAsyncResult(UNKNOWN_ERROR),
      mSetSurfaceInProgress(false),
      mDurationUs(-1),
      mPositionUs(-1),
      mSeekInProgress(false),
      mLooper(new ALooper),
      mPlayerFlags(0),
      mAtEOS(false),
      mLooping(false),
      mAutoLoop(false),
      mStartupSeekTimeUs(-1) {
    ALOGV("NuPlayerDriver(%p)", this);
    mLooper->setName("NuPlayerDriver Looper");

    mLooper->start(
            false, /* runOnCallingThread */
            true,  /* canCallJava */
            PRIORITY_AUDIO);

    mPlayer = new NuPlayer(pid);
    mLooper->registerHandler(mPlayer);

    mPlayer->setDriver(this);
}

NuPlayer 继承自 AHandler,并且引入了 AMessage,通过 ALooper 来处理消息,如NuPlayerDriver 调用 NuPlayer 的 prepareAsync 函数:

void NuPlayer::prepareAsync() {
    (new AMessage(kWhatPrepare, this))->post();
}
void NuPlayer::onMessageReceived(const sp<AMessage> &msg)(
switch (msg->what()){
//省略部分代码
case kWhatPrepare:
mSource->prepareAsync();
break;
}
//部分省略
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。