読者です 読者をやめる 読者になる 読者になる

Android MediaPlayer に関するメモ (NuPlayer 編)

前回のあらすじ

Android MediaPlayer は libmediaplayerservice から呼び出される AwesomePlayer と NuPlayer に実装されていることがわかった。

なんか箇条書きばっかりで見辛かったので、もうちょっと詳細に書こうと思います。

MediaPlayerFactory

media/libmediaplayerservice/MediaPlayerFactory.cpp - platform/frameworks/av - Git at Google

virtual sp<MediaPlayerBase> createPlayer() {
    ALOGV(" create NuPlayer");
    return new NuPlayerDriver;
}

NuPlayerFactory の createPlayer から NuPlayerDriver というのが呼び出されている。

NuPlayerDriver

mPlayer = new NuPlayer;
mLooper->registerHandler(mPlayer);
mPlayer->setDriver(this);

mPlayer という変数に NuPlayer インスタンスを保持していて、 NuPlayerDriver ではステート管理をしつつ NuPlayer に処理を渡している。

NuPlayer

void NuPlayer::prepareAsync() {
    (new AMessage(kWhatPrepare, id()))->post();
}

NuPlayerDriver から呼ばれたメソッドで内容を AMessage に入れて post している。メッセージは onMessageReceived で受信していて、 kWhatPrepare を処理する部分で mSource を読んでいる。

void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        // 中略 ...
        case kWhatPrepare:
        {
            mSource->prepareAsync();
            break;
        }

mSource の中身は setDataSourceAsync で作られていて、タイプによってインスタンス化するクラスが違う。 実装を読むと HLS ならば HTTPLiveSource 、RTSP なら RTSPSource 、それ以外は GenericSource が生成されている。

if (IsHTTPLiveURL(url)) {
    source = new HTTPLiveSource(notify, httpService, url, headers);
} else if (!strncasecmp(url, "rtsp://", 7)) {
    source = new RTSPSource(
            notify, httpService, url, headers, mUIDValid, mUID);
} else if ((!strncasecmp(url, "http://", 7)
            || !strncasecmp(url, "https://", 8))
                && ((len >= 4 && !strcasecmp(".sdp", &url[len - 4]))
                || strstr(url, ".sdp?"))) {
    source = new RTSPSource(
            notify, httpService, url, headers, mUIDValid, mUID, true);
} else {
    sp<GenericSource> genericSource =
            new GenericSource(notify, mUIDValid, mUID);
    // Don't set FLAG_SECURE on mSourceFlags here for widevine.
    // The correct flags will be updated in Source::kWhatFlagsChanged
    // handler when  GenericSource is prepared.
    status_t err = genericSource->setDataSource(httpService, url, headers);
    if (err == OK) {
        source = genericSource;
    } else {
        ALOGE("Failed to set data source!");
    }
}

ちなみに IsHTTPLiveURL() は NuPlayer で実装されている。処理としては URL から m3u8 (HLS) かどうかを判定している (と言うことは .m3u8 って拡張子以外で HLS を配信しても NuPlayer だと再生できないんだろうか) 。

static bool IsHTTPLiveURL(const char *url) {
    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 true;
        }
        if (strstr(url,"m3u8")) {
            return true;
        }
    }
    return false;
}

AMessage

実装は libstagefright 内にある。 "A" ってもしかして Awesome?

特定の Thread に作った Looper が処理をするために一旦メッセージングを通じてやりとりしている。

HTTPLiveSource

処理はだいたい LiveSession に渡している。 HTTPLiveSource#prepareAsync からは LiveSession#connectAsync を呼んでいる。

LiveSession

LiveSession の実装は libstagefright 内にある。

NuPlayer のように一旦 AMessage を通して非同期処理を行なっている。 connectAsync から kWhatConnect を指定し AMessage を通って、 onConnect が呼び出される。

onConnect は m3u8 ファイルを実際に取得し、 m3u8 ファイル内の bandwidth を保持して、 changeConfiguration を呼び出す。

まず onChangeConfiguration が呼ばれ、 onChangeConfiguration2 onChangeConfiguration3 という風に呼ばれていく (メソッド名はマジで謎) 。そしてプレイリスト内で指定された URL を更に PlaylistFetcher で非同期的に取得してくる。

そしてプレイリスト内の動画ファイルを取得するわけだが、これは AnotherPacketSource でやっていて、それを postMonitorQueue を通じて定期的に実行される。

HLS について

HLS は、 1 つ目のプレイリストには複数のプレイリストの URL が指定されている。これはクライアントの帯域別に最適なストリームを選ぶため。そして、最適なプレイリストを取得すると、その中に小間切れになった動画ファイルの URL 列が格納されている。

この小間切れの動画ファイルを次々と取得し、プレイヤーでシームレスに再生することで HLS のアダプティブストリーミングが実現されている。

途中で回線が切り替わって高速になったり、低速になったりしても、最初に取得した帯域別プレイリストを参照しなおし、帯域に応じたプレイリストを利用することで再生が止まったりすることが無く動画を切り替えることができる。

LiveSession#fetchPlaylist

fetchFile を呼び出し、実際に取得したファイルを元に M3UParser のインスタンスを作って返却する (クラス名は Parser だけど Playlist) 。

M3UParser

m3u8 ファイルをパースする実体。

PlaylistFetcher

m3u8 ファイルをダウンロードして M3UParser に食わせている。

AnotherPacketSource

GenericSource

Stagefright で実装されている各種 Extractor を使って NuPlayer のインタフェースを実装している感じ。もちろん Widevine も実装されている。

所感

  • NuPlayer で Streaming Media 以外を再生するようにしているのは、 Stagefright と言うよりは AwesomePlayer を捨てたいということなのだろうか
  • Android の HLS はシークが微妙という問題があるんだけど、これが NuPlayer になったら直るのか?! と思っていたが、もともと NuPlayer だしダメそう
  • MEPG2-TS は元々細切れにしてもエラーに強いファイル形式なので HLS で採用されているということなんだろうか
    • デジタル放送で利用されているというイメージはあるけど、何で HLS で使われているのか良く分かっていない