LoginSignup
7
4

More than 3 years have passed since last update.

[Android]ExoPlayer でハードウェアのデコーダーを利用しないようにする

Posted at

はじめに

ExoPlayer ではメディアにあわせて一番良いデコーダーを再生時に自動的に選択してくれるので、
ExoPlayer を利用する時は、デコーダー周りは特に何も気にしないで良いことになっています。

そのはずだったのですが 端末に搭載されるハードウェアのデコーダーの不具合?なのか
再生時に動画が途切れたり、上手くループ再生できなくなるなどの現象に遭遇しました。

その時に ExoPlayer に対してはハードウェアのデコーダーを利用しないように
設定を施したのでその方法を共有したいと思います。

動作を確認するためのサンプルアプリを作る

本記事では次のような構成のアプリを作成していると仮定して設定を進めていきます。

Nov-29-2019 23-20-18.gif

ExoPlayer をインストールする

build.gradle(app)
android {
    compileOptions {
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'com.google.android.exoplayer:exoplayer:2.10.8'
        
}

インターネットにアクセスするのでパーミッションを許可しておく

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="kaleidot725.exoplayersample">
    <uses-permission android:name="android.permission.INTERNET"/></manifest>

ExoPlayer の PlayerView をレイアウトする

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/player_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:show_buffering="when_playing"
        app:show_shuffle_button="true"/>

</FrameLayout>

ExoPlayer をセットアップする

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val view = findViewById<PlayerView>(R.id.player_view)
        val player = ExoPlayerFactory.newSimpleInstance(this).apply {
            val uri = Uri.parse("https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_1MB.mp4")
            val dataSource = DefaultDataSourceFactory(this@MainActivity, Util.getUserAgent(this@MainActivity, "ExoPlayerSample"))
            val videoSource = ProgressiveMediaSource.Factory(dataSource).createMediaSource(uri)
            prepare(videoSource)
        }
        view.player = player
    }
}

デバイスに搭載されているハードウェアのデコーダーを探す

次のコードでデバイスに搭載されているエンコーダーとデコーダーを確認します。

    MediaCodecList(MediaCodecList.ALL_CODECS).codecInfos.forEach { codecInfo ->
        Log.d("CODEC_INFO", "media ${codecInfo.name} ")
    }

次のような感じでメディアごとのデコーダーとエンコーダーが出力されます。
これは Pixel3 XL で実行したものですが c2.android.*OMX.google.* と2種類のエンコーダーとデコーダーがあります。

2019-11-30 10:21:01.741 6207-6207/kaleidot725.exoplayersample D/CODEC_INFO: media c2.android.avc.decoder 
2019-11-30 10:21:01.741 6207-6207/kaleidot725.exoplayersample D/CODEC_INFO: media OMX.google.h264.decoder 
2019-11-30 10:21:01.741 6207-6207/kaleidot725.exoplayersample D/CODEC_INFO: media c2.android.avc.encoder 
2019-11-30 10:21:01.741 6207-6207/kaleidot725.exoplayersample D/CODEC_INFO: media OMX.google.h264.encoder

調べたところc2.android.* が Pixel3 に搭載されたハードウェアのエンコーダーとデコーダー、
OMX.google.* が Android OS で実装されたソフトウェアのエンコーダーとデコーダーを指しているようです。
今回はハードウェアのデコーダーを利用したくないので、c2.android.avc.decoder が対象になります。

ハードウェアのデコーダーを利用しないようにする

ExoPlayer では MediaCodecSelector クラスを通して、どんなデコーダーが ExoPlayer で利用できるかを取得してます。
通常は MediaCodecSelector.DEFAULT の実装を利用しており、端末に搭載される全てのデコーダーを返すようになっています。

MediaCodecSelector.java
  MediaCodecSelector DEFAULT =
      new MediaCodecSelector() {
        @Override
        public List<MediaCodecInfo> getDecoderInfos(
            String mimeType, boolean requiresSecureDecoder, boolean requiresTunnelingDecoder)
            throws DecoderQueryException {
          return MediaCodecUtil.getDecoderInfos(
              mimeType, requiresSecureDecoder, requiresTunnelingDecoder);
        }

        @Override
        public @Nullable MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException {
          return MediaCodecUtil.getPassthroughDecoderInfo();
        }
      };

今回はこの MediaCodecSelector クラスをカスタマイズすることで
ハードウェアのデコーダーを ExoPlayer で利用しないようにします。

次のように getDecoderInfo で利用できるデコーダーを取得する処理に
namec2.android.*で始まるものを取り除く処理を追加します。

        val customCodecSelector = object : MediaCodecSelector {
            override fun getDecoderInfos(mimeType: String?, requiresSecureDecoder: Boolean, requiresTunnelingDecoder: Boolean): MutableList<MediaCodecInfo> {
                return MediaCodecUtil.getDecoderInfos(mimeType, requiresSecureDecoder, requiresTunnelingDecoder).toMutableList().filter {
                    !it.name.startsWith("c2.android.")
                }.toMutableList()
            }

            override fun getPassthroughDecoderInfo(): MediaCodecInfo? {
                return MediaCodecUtil.getPassthroughDecoderInfo()
            }
        }

そして ExoPlayer を生成するときにこのクラスのインスタンスを設定すれば、
ExoPlayer では c2.android.* 以外のデコーダーを利用して動画を再生するようになります。

        val view = findViewById<PlayerView>(R.id.player_view)
        val trackSelector = DefaultTrackSelector()
        val renderersFactory = DefaultRenderersFactory(this).apply {
            setMediaCodecSelector(customCodecSelector)
        }
        val player = ExoPlayerFactory.newSimpleInstance(this, renderersFactory, trackSelector).apply {
            val uri = Uri.parse("https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_1MB.mp4")
            val dataSource = DefaultDataSourceFactory(this@MainActivity, Util.getUserAgent(this@MainActivity, "ExoPlayerSample"))
            val videoSource = ProgressiveMediaSource.Factory(dataSource).createMediaSource(uri)
            prepare(videoSource)
        }
        view.player = player

うまく設定できていれば指定したハードウェアを利用せずに動画を再生していることがわかるような次のログが出でてきます。

2019-11-30 11:58:49.588 3862-3941/kaleidot725.exoplayersample I/MediaCodec: [OMX.google.h264.decoder] setting surface generation to 3954689

おわりに

ハードウェアのデコーダーを利用しないようにしましたが、
ソフトウェアのデコーダーを利用するとCPU使用率が上昇するので注意が必要だったりします。
そのため本来であれは ExoPlayer にデコーダーの選択など全てを任せるのが安心できます。

ですがデバイスだとうまくデコードできないという場面に直面した場合は本実装が有効なので、
デバイス依存の問題には直面してしまったときはこういった実装でしのげるかなと思います。

参考記事

本記事は Can we tweak hardware/software decoding in Exoplayer? を参考にしました、
詳しくはこっちで議論されているので確認してみてください。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4