Androidマンになりたいおじさん

自分の備忘録的なのです。Androidを普及できたらいいなと思ったりしながら書きます

ExoPlayerのDecoder周りのお話

はじめに

こんにちは!!!!さっき任天堂からサイレント爆弾投下されて元気になっているぐみおじです!!!! 信者とかじゃないんですが、任天堂って人を喜ばせるのうまいな〜って思いつつ、見習いたいところですね

ということで、今日はDecoder周りについて少し触れたいと思います

ちなみにAndroidの低レベル層で提供されているMediaCodecについては今回は触れませんx(そのうち深堀りしていきたいですね)

Let's Nintendo

Codecについて

ここでいうCodecとは、動画や音声をEncodeしたり、Decodeしたりすることを指します。(画像なんかでも使われてますね)

生のデータは綺麗だけど、容量デカすぎ!なるべく人間に気付かれないように容量軽くなるように圧縮・変換しちゃおうぜ!的な捉え方で大丈夫です

  • 圧縮・変換 -> Encode
  • Encode済みデータを復元 -> Decode

この圧縮方法が色んなやり方があって、そりゃもう大変大変って感じ。

ExoPlayerでは何をサポートしているかが以下から確認できます! exoplayer.dev

多分皆さんも一度は耳にしたことがあると思います!

映像: H.264, H.265, MPEG-4...etc. 音声: AAC, MP3...etc.

これらのCodecをハードウェアとして提供してたり、ソフトウェアで実現したりって話もあったりします。もちろん性能はハードの方が優秀なんですが、色んなformatをハードで対応するのは正直きつそうという小並感な意見をもってます...

補足: 動画は音声と映像の2つから成り立ってるから、音声用のCodec, 映像用のCodecというふうに扱います。

AndroidのCodecについて

AndroidのCodecってどうなってるんだろ?自分ってどんなのが対応してるんだろ?とか思いませんか?

adb shellで自分の端末にアクセスしてみて、media_codecs.xmlがどこかに配置されてるので探してみてください! -> 下記サイトより: etc/, vendor/etc/, odm/etc/などに基本はあるみたいですよ!

medium.com

androidのcodec情報はxml宣言されてることがまず驚きですよね...。(自分はびっくりしました)

試しに自分のGalaxyS10+のvendo/etc/配下を見てみましょう!!

f:id:qurangumio:20200903234415p:plain
media_codecs

うっ。。。目が痛い

全部は見てられないので、代表的なmedia_codecs.xmlをチラ見してみます。

<MediaCodecs>
    <Settings>
        <Setting name="max-video-encoder-input-buffers" value="11" />
    </Settings>
    <Encoders>
        <!-- Video Hardware  -->
        <MediaCodec name="OMX.qcom.video.encoder.avc" type="video/avc" >
            <Quirk name="requires-allocate-on-input-ports" />
            <Quirk name="requires-allocate-on-output-ports" />
            <Quirk name="requires-loaded-to-idle-after-allocation" />
            <Limit name="size" min="96x96" max="4096x2304" />
            <Limit name="alignment" value="2x2" />
            <Limit name="block-size" value="16x16" />
            <Limit name="blocks-per-second" min="36" max="1958400" />
            <Limit name="bitrate" range="1-160000000" />
            <Limit name="frame-rate" range="1-480" />
            <Limit name="concurrent-instances" max="16" />
            <Limit name="performance-point-4096x2304" value="56" />
            <Limit name="performance-point-4096x2160" value="60" />
            <Limit name="performance-point-3840x2160" value="60" />
            <Limit name="performance-point-1920x1080" value="240" />
            <Limit name="performance-point-1280x720" value="480" />
        </MediaCodec>
        <!-- Video Software -->
        <MediaCodec name="OMX.qcom.video.encoder.h263sw" type="video/3gpp" >
            <Quirk name="requires-allocate-on-input-ports" />
            <Quirk name="requires-allocate-on-output-ports" />
            <Quirk name="requires-loaded-to-idle-after-allocation" />
            <Limit name="size" min="96x96" max="864x480" />
            <Limit name="alignment" value="4x4" />
            <Limit name="block-size" value="16x16" />
            <Limit name="blocks-per-second" min="36" max="48600" />
            <Limit name="bitrate" range="1-2000000" />
            <Limit name="frame-rate" range="1-30" />
            <Limit name="concurrent-instances" max="16" />
            <Limit name="performance-point-720x480" value="30" />
        </MediaCodec>
    </Encoders>
    <Decoders>
       <!-- Video Hardware  -->
        <MediaCodec name="OMX.qcom.video.decoder.avc" type="video/avc" >
            <Quirk name="requires-allocate-on-input-ports" />
            <Quirk name="requires-allocate-on-output-ports" />
            <Limit name="size" min="96x96" max="8192x4320" />
            <Limit name="alignment" value="2x2" />
            <Limit name="block-size" value="16x16" />
            <Limit name="blocks-per-second" min="36" max="3916800" />
            <Limit name="bitrate" range="1-220000000" />
            <Limit name="frame-rate" range="1-480" />
            <Feature name="adaptive-playback" />
            <Limit name="concurrent-instances" max="16" />
            <Limit name="performance-point-4096x2304" value="60" />
            <Limit name="performance-point-4096x2160" value="96" />
            <Limit name="performance-point-3840x2160" value="120" />
            <Limit name="performance-point-1920x1088" range="480" />
            <Limit name="performance-point-1920x1088" range="240" />
            <Limit name="performance-point-1280x720" value="480" />
        </MediaCodec>
        <!-- Video Software -->
        <MediaCodec name="OMX.qti.video.decoder.h263sw" type="video/3gpp" >
            <Quirk name="requires-allocate-on-input-ports" />
            <Quirk name="requires-allocate-on-output-ports" />
            <Limit name="size" min="96x96" max="864x480" />
            <Limit name="alignment" value="4x4" />
            <Limit name="block-size" value="16x16" />
            <Limit name="blocks-per-second" min="36" max="48600" />
            <Limit name="bitrate" range="1-16000000" />
            <Limit name="frame-rate" range="1-30" />
            <Feature name="adaptive-playback" />
            <Limit name="concurrent-instances" max="16" />
            <Limit name="performance-point-720x480" value="30" />
        </MediaCodec>
    </Decoders>

    <!-- JAPAN models only have mpeg2 decoder -->
    <Include href="media_codecs_qcom_video_mpeg2.xml" />
</MediaCodecs>

かなり長いので、一部の抜粋となっています。

EncoderとDecoderがあり、コメントでHardware, Softwareがあり、各Codecには細かな設定が宣言されてることがわかりますね。

androd.media.MediaCodecListを使っても簡単に覗くことも可能ですo

MediaCodecList(MediaCodecList.ALL_CODECS).codecInfos.forEach {
      Log.d("MediaCodec", it.name))
}

ExoPlayerのCodecについて

ExoPlayerではMediaCodecRenderでcodecの名前を一意に指定して、MediaCodecを生成して、Decode処理を試みようとします。

try {
 codecInitializingTimestamp = SystemClock.elapsedRealtime();
 TraceUtil.beginSection("createCodec:" + codecName);
 codec = MediaCodec.createByCodecName(codecName);
 TraceUtil.endSection();
 TraceUtil.beginSection("configureCodec");
 configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate);
 TraceUtil.endSection();
 TraceUtil.beginSection("startCodec");
 codec.start();
 TraceUtil.endSection();
 codecInitializedTimestamp = SystemClock.elapsedRealtime();
 getCodecBuffers(codec);
} ・・・

このMediaCodecRenderを継承してるのが、MediaCodecAudio/VideoRendererになっているわけですね。

この内部のgetDecoderInfosでdecoderの情報を取得するようになっているんですが、これを担っているのがMediaCodecSelectorになっています。

MediaCodecSelectorRenderesFactoryで実装したものが指定できます。更にRenderesFactorySimpleExoPlayerを生成するときに指定することができます。

何が嬉しいか

何が嬉しいか話をする前にMediaCodecRenderで選ばれたdecoderInfos達がどのように扱われてるかを知ってる前提で話す必要があるのでそれをさせてください。

取得したdecoderInfosの取り扱い

MediaCodecRender内部であれやこれと処理が進んで、Decodeの準備するかーって段階で maybeInitCodecWithFallback が呼ばれます。

maybeInitCodecWithFallback ではallAvailableCodecInfosとしてMediaCodecSelectorで実装したcodecInfosがMediaAudio/VideoRenderでsortなどの加工がされて流れてきます。

注) ここの加工部分に関しては今回省略しています

この後に以下のような実装がされています

if (enableDecoderFallback) {
 availableCodecInfos.addAll(allAvailableCodecInfos);
} else if (!allAvailableCodecInfos.isEmpty()) {
 availableCodecInfos.add(allAvailableCodecInfos.get(0));
}

このenableDecoderFallbackはdefaultではfalseになっているので、sortされたcodecInfosの一番最初のものが利用されることになります。この時点でcodecInfosは優先度の高い順に並んでます。

このcodecがもしコンテンツと相性がよくなかったり、Codecが対応していないとかで初期化に失敗したら、DecoderInitializationException が投げられ、動画は再生されません。

最適化されたCodec順に並んでて、一番最初に失敗するなんてあるんか???って思うじゃないですか....あるんです。

その大半の理由を占めるのがベンダー依存のhardware codecです。

仮にdecodeに成功しても映像が微妙とかいうパターンもあります。Errorすら検知できないですね、、、

なので、ここでMediaCodecSelectorを使って、MediaCodecSelector.DEFAULTで取得する情報を自分の都合のいいように変えちゃうわけですね。

MediaCodecInfoにはCodecに関しての色んなフィールドがあるので、把握しとくといいですよ!

今回でいうとsoftwareOnlyがあるので、これを使ってsoftwareOnlyなcodecだけを抽出して返してあげればよさそうですねb

decoder fallbackについて

ちなみにenableDecoderFallbackってあったじゃないですか?

あれ一応RenderesFactory.setEnableDecoderFallbackでtrueにできます。

trueにすると、先程のallAvailableCodecInfosに優先度の高い順に並んだcodecがすべて格納されます。なので、最初のcodecがdecodeに失敗しても次の候補でdecodeしてみるという動きに変わるわけですね。

この機能をonにしとけば、何も怖くない!!!とはならないですね

仮にdecodeに成功しても映像が微妙とかいうパターンもあります。Errorすら検知できないですね、、、

こういうパターンはやっぱり弾けないので、、、、

でも個人的な意見ですが、このfallback機能はonにしててもいいと思っています。ExoPlayer的にdefaultでoffにしている理由があるんだろうけど、、、 -> 今度聞いてみます

おまけ

decoder周りで何かを調査したい時にAnalyticsListenerに以下のDecoder周りの有益な情報があるので分析などをしてみたい場合は使ってみるとよいかも(?)ですよ!

  /**
   * Called when an audio or video decoder has been enabled.
   *
   * @param eventTime The event time.
   * @param trackType The track type of the enabled decoder. Either {@link C#TRACK_TYPE_AUDIO} or
   *     {@link C#TRACK_TYPE_VIDEO}.
   * @param decoderCounters The accumulated event counters associated with this decoder.
   */
  default void onDecoderEnabled(EventTime eventTime, int trackType, DecoderCounters decoderCounters) {}

  /**
   * Called when an audio or video decoder has been initialized.
   *
   * @param eventTime The event time.
   * @param trackType The track type of the initialized decoder. Either {@link C#TRACK_TYPE_AUDIO}
   *     or {@link C#TRACK_TYPE_VIDEO}.
   * @param decoderName The decoder that was created.
   * @param initializationDurationMs Time taken to initialize the decoder, in milliseconds.
   */
  default void onDecoderInitialized(EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {}

  /**
   * Called when an audio or video decoder input format changed.
   *
   * @param eventTime The event time.
   * @param trackType The track type of the decoder whose format changed. Either {@link
   *     C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}.
   * @param format The new input format for the decoder.
   */
  default void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {}

  /**
   * Called when an audio or video decoder has been disabled.
   *
   * @param eventTime The event time.
   * @param trackType The track type of the disabled decoder. Either {@link C#TRACK_TYPE_AUDIO} or
   *     {@link C#TRACK_TYPE_VIDEO}.
   * @param decoderCounters The accumulated event counters associated with this decoder.
   */
  default void onDecoderDisabled(EventTime eventTime, int trackType, DecoderCounters decoderCounters) {}

さいごに

いかがだったでしょうか! 調べれば、調べるほど、ExoPlayerって本当に実装とか設計が綺麗だなーってなって感動しちゃいます。。。。

Codec周りはまだまだ学ぶことがありふれているので、その辺りを次は学んでいきたいですね

それでは皆さん、よいExo lifeを!!!!!!