Android10.0MediaPlayerService中选择player的评分机制

前言

我们知道MediaPlayer播放的时候,最终会调到native层的MediaPlayerService中,在MediaPlayerService中会创建NuPlayer和TestPlayer,那么这俩Player是如何选择的呢?就涉及到了选择player的得分机制。

正文

先看下MediaPlayerService创建player的过程,首先MediaPlayerService在启动的时候

MediaPlayerService::MediaPlayerService()
{
    ALOGV("MediaPlayerService created");
    mNextConnId = 1;

    MediaPlayerFactory::registerBuiltinFactories();
}

会调用到MediaPlayerFactory的registerBuiltinFactories,我们看下registerBuiltinFactories这个函数

void MediaPlayerFactory::registerBuiltinFactories() {
    Mutex::Autolock lock_(&sLock);
	//这是一个flage,标志此函数只能调用一次。
    if (sInitComplete)
        return;

    IFactory* factory = new NuPlayerFactory();
    if (registerFactory_l(factory, NU_PLAYER) != OK)
        delete factory;
    factory = new TestPlayerFactory();
    if (registerFactory_l(factory, TEST_PLAYER) != OK)
        delete factory;

    sInitComplete = true;
}

分别new了两个factory,NuPlayerFactory和TestPlayerFactory,并分别调用了registerFactory_l,那么继续看下registerFactory_l

status_t MediaPlayerFactory::registerFactory_l(IFactory* factory,
                                               player_type type) {
    if (NULL == factory) {
        ALOGE("Failed to register MediaPlayerFactory of type %d, factory is"
              " NULL.", type);
        return BAD_VALUE;
    }

    if (sFactoryMap.indexOfKey(type) >= 0) {
        ALOGE("Failed to register MediaPlayerFactory of type %d, type is"
              " already registered.", type);
        return ALREADY_EXISTS;
    }

    if (sFactoryMap.add(type, factory) < 0) {
        ALOGE("Failed to register MediaPlayerFactory of type %d, failed to add"
              " to map.", type);
        return UNKNOWN_ERROR;
    }

    return OK;
}

将NuPlayerFactory和TestPlayerFacory加入到容器sFactoryMap中。到此MediaPlayerService就初始化完成了。简单总结一下就是MediaPlayerService初始化的时候通过MediaPlayerFactory分别创建了NuPlayerFactory和TestPlayerFacory,并将两个factory加入到容器sFactoryMap中。
那么我们是在什么时候选取的player呢,其实是在我们SetDataSource的时候,我们知道MediaPlayer的java层Api暴露的SetDataSource的接口很多,我粗略看了下不管是hide还是systemapi的一共11个,其实这么多对应到native层就三个分别是

status_t MediaPlayer::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url, const KeyedVector<String8, String8> *headers)
status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)

status_t MediaPlayer::setDataSource(const sp<IDataSource> &source)

我们使用最多的是第一种和第二种,分别播放的是uri和path,在native的mediaplayer中setDataSource会调用的MediaPlayerService的

status_t MediaPlayerService::Client::setDataSource(
        const sp<IDataSource> &source) {
    sp<DataSource> dataSource = CreateDataSourceFromIDataSource(source);
    player_type playerType = MediaPlayerFactory::getPlayerType(this, dataSource);
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    if (p == NULL) {
        return NO_INIT;
    }
    // now set data source
    return mStatus = setDataSource_post(p, p->setDataSource(dataSource));
}

这里简单列出了一个Media Player Service中的setDataSource的源码,其实不管哪个最终调用的都是MediaPlayerFactory的getPlayerType

player_type MediaPlayerFactory::getPlayerType(const sp<IMediaPlayer>& client,
                                              const char* url) {
    GET_PLAYER_TYPE_IMPL(client, url);
}

player_type MediaPlayerFactory::getPlayerType(const sp<IMediaPlayer>& client,
                                              int fd,
                                              int64_t offset,
                                              int64_t length) {
    GET_PLAYER_TYPE_IMPL(client, fd, offset, length);
}

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

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

选取PlayerType的时候代码如下

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

我们先看下playerType的定义


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

只有3个,1和2可能是早期的版本使用的,后来被移除了。STAGEFRIGHT_PLAYER这里基本也不用了,虽然这里playerType默认STAGEFRIGHT_PLAYER,但真正选择默认player的时候,是NU_PLAYER。我们继续看这个函数bestScore 是最终得分,thisScore是当前的分数,我们分析MediaPlayer初始化的时候知道了sFactoryMap其实只存储了两个值,分别是NU_PLAYER和TEST_PLAYER。那么我们就分别看下这俩fatory的scoreFactory

class NuPlayerFactory : public MediaPlayerFactory::IFactory {
  public:
    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               const char* url,
                               float curScore) {
        static const float kOurScore = 0.8;

        if (kOurScore <= curScore)
            return 0.0;

        if (!strncasecmp("http://", url, 7)
                || !strncasecmp("https://", url, 8)
                || !strncasecmp("file://", url, 7)) {
            size_t len = strlen(url);
            if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) {
                return kOurScore;
            }

            if (strstr(url,"m3u8")) {
                return kOurScore;
            }

            if ((len >= 4 && !strcasecmp(".sdp", &url[len - 4])) || strstr(url, ".sdp?")) {
                return kOurScore;
            }
        }

        if (!strncasecmp("rtsp://", url, 7)) {
            return kOurScore;
        }

        return 0.0;
    }

    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               const sp<IStreamSource>& /*source*/,
                               float /*curScore*/) {
        return 1.0;
    }

    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               const sp<DataSource>& /*source*/,
                               float /*curScore*/) {
        // Only NuPlayer supports setting a DataSource source directly.
        return 1.0;
    }

对于url的的得分计算还算不错,大概根据url来处理,是0.8分还是0.0分的。对于剩下两种情况就比较敷衍了,直接得分1.0.我们在看下TestPlayerFactory 的得分机制

class TestPlayerFactory : public MediaPlayerFactory::IFactory {
  public:
    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               const char* url,
                               float /*curScore*/) {
        if (TestPlayerStub::canBeUsed(url)) {
            return 1.0;
        }

        return 0.0;
    }

如果canBeUsed则得1.0分否则得0.0分,我们看下canBeUsed这个函数

/* static */ bool TestPlayerStub::canBeUsed(const char *url)
{
    return isTestBuild() && isTestUrl(url);
}

其中isTestBuild

// @return true if the current build is 'eng' or 'test'.
bool isTestBuild()
{
    char prop[PROPERTY_VALUE_MAX] = { '\0', };

    property_get(kBuildTypePropName, prop, "\0");
    return strcmp(prop, kEngBuild) == 0 || strcmp(prop, kTestBuild) == 0;
}

注释说的已经很清楚了,我们build的是一个eng或者test的版本,并且isTestUrl的时候我们就使用TestPlayer,那么什么样的url是testUrl呢

// @return true if the url scheme is 'test:'
bool isTestUrl(const char *url)
{
    return url && strncmp(url, kTestUrlScheme, strlen(kTestUrlScheme)) == 0;
}

原来是以’test:'这种开头的url。到此我们大概就明白了什么时候选取什么样的player了。
回到最初得分的那个函数里,如果我们thisScore > bestScore,那么最终得分bestScore = thisScore ,如果一顿操作猛如虎后,bestScore仍是0.0,那么就要使用getDefaultPlayerType()了。

static player_type getDefaultPlayerType() {
    return NU_PLAYER;
}

感觉像是被耍了一样,直接return NU_PLAYER不好吗?为何还要在开头搞个player_type ret = STAGEFRIGHT_PLAYER,来吓唬人。
到此拿到player_type后,后面的就是创建对应的player了。这里就不在分析了。

总结

对于Android的早期版本关注不是很多,或许早期版本的player有多个以及复杂的得分机制。但是最近几个版本,包括我们此次分析的Android10.0.对于mediaPlayer的选取以及得分的机制都简单了很多,或许谷歌只是想提供基本的播放和测试,剩下的就让大家随便定制的。
总结下来,我们player的得分机制是在setDataSource的时候,通过MediaPlayerFactory选取对应的player_type 。最终创建需要的player。
(以上,如有问题,欢迎大家执政交流)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻量级LZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值