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
- media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp - platform/frameworks/av - Git at Google
- media/libmediaplayerservice/nuplayer/NuPlayerDriver.h - platform/frameworks/av - Git at Google
mPlayer = new NuPlayer; mLooper->registerHandler(mPlayer); mPlayer->setDriver(this);
mPlayer という変数に NuPlayer インスタンスを保持していて、 NuPlayerDriver ではステート管理をしつつ NuPlayer に処理を渡している。
NuPlayer
- media/libmediaplayerservice/nuplayer/NuPlayer.cpp - platform/frameworks/av - Git at Google
- media/libmediaplayerservice/nuplayer/NuPlayer.h - platform/frameworks/av - Git at Google
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?
- media/libstagefright/foundation/AMessage.cpp - platform/frameworks/av - Git at Google
- include/media/stagefright/foundation/AMessage.h - platform/frameworks/av - Git at Google
特定の Thread に作った Looper が処理をするために一旦メッセージングを通じてやりとりしている。
HTTPLiveSource
- media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp - platform/frameworks/av - Git at Google
- media/libmediaplayerservice/nuplayer/HTTPLiveSource.h - platform/frameworks/av - Git at Google
処理はだいたい LiveSession
に渡している。 HTTPLiveSource#prepareAsync
からは LiveSession#connectAsync
を呼んでいる。
LiveSession
LiveSession の実装は libstagefright 内にある。
- media/libstagefright/httplive/LiveSession.cpp - platform/frameworks/av - Git at Google
- media/libstagefright/httplive/LiveSession.h - platform/frameworks/av - Git at Google
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 で使われているのか良く分かっていない