アプリケーションの観点から Android MediaCodec を理解する


序文

マルチメディア産業の発展に伴い、携帯電話のビデオ デコーディング パフォーマンスに対する要件はますます高くなっています。CPU をデコードに使用すると、多くの CPU リソースが消費されます。現在主流のアプローチは、ビデオのデコードに携帯電話の GPU リソースを使用することです。Android システムは、Android4.0 (API 16) に MediaCodec を追加します。これにより、アプリが Java インターフェイスを呼び出し、基盤となるハードウェアのオーディオとビデオのエンコードおよびデコード機能を使用できるようになります。Android ndk は、対応するネイティブ メソッドを Android 5.0 (API21) で提供します。機能はほとんど同じです。MediaCodec はエンコードとデコードを処理でき、オーディオとビデオを処理でき、ソフト ソリューション (CPU) とハード ソリューション (GPU) があります。特定の携帯電話の Android システムは、通常、media_codecs.xml に記述されます。異なる携帯電話の場所は異なります。私の経験によると、ほとんどの携帯電話は /system/etc/ ディレクトリの下にあります。ここでは主にビデオのデコードについて説明します。

1. Android MediaCodec 動作モード

MediaCodec 内には 2 つのバッファーがあり、1 つは InputBuffer で、もう 1 つは OutputBuffer です。2 つのバッファーのサイズは、通常、基盤となるハードウェアのコードによって決まります。デコード プロセス中、クライアントは InputBuffer と OutputBuffer を継続的にクエリする必要があります。InputBuffer が空いている場合は、対応するコード ストリームを配置する必要があります。OutputBuffer に出力がある場合は、ビデオ フレームを時間内に消費して解放する必要があります。
MediaCodec データ フロー

コーデックは、InputBuffer と OutputBuffer を継続的にクエリする内部自己開始スレッドです。OutputBuffer が空いていて、未処理の InputBuffer がある場合は、フレームをデコードします。そうでない場合は、ハングします。

2. Android MediaCodec の起動処理

1. Android ランタイムのバージョンを確認する

ndk インターフェイスは Android 5.0 以上でしか利用できないため、まず Android のバージョンを判別し、バージョン番号が 5.0 以上の場合は Ndk インターフェイスを使用し、そうでない場合は java の逆調整方法を使用します。

2. デコーダーを作成する

MediaCodec は、デコーダを作成する 2 つの方法を提供します. 比較的簡単な方法は、MIME を介して直接デコーダを作成することです. MIME はデコーダーのタイプです。たとえば、264 デコーダーを作成するには、次の関数を呼び出すだけです。

AMediaCodec_createDecoderByType("video/avc")		//硬解

電話に複数の 264 デコーダーがある場合 (通常、ハード デコーダーと Google ソフト デコーダーがあります)、MediaCodec はデフォルトの順序で 1 つを選択します。もちろん、この順序は変更できます。通常の状況では、携帯電話はデフォルトでハード ソリューションを使用します。作成するデコーダーを正確に選択したい場合は、名前で作成できます。

AMediaCodec_createCodecByName("OMX.video.decoder.avc")		//软解

3. デコーダーを構成する

AMediaCodec_configure(handle,format,surface, crypto, flag)

この関数について注意すべき点が 2 つあります。1 つは MediaFormat で、もう 1 つは surface です。crypto は暗号化に関連しており、ここで毎日使用しています。フラグは符号化時に注意すべきパラメータで、通常は復号化のために0を埋めます。MediaFormat は、幅、高さ、sps、pps など、クライアントが事前にデコーダーに伝える必要があるいくつかのパラメーター形式です。例えば:

AMediaFormat* videoFormat = AMediaFormat_new();
AMediaFormat_setString(videoFormat, "mime", "video/avc");
AMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_WIDTH, width); // 视频宽度 
AMediaFormat_setInt32(videoFormat, AMEDIAFORMAT_KEY_HEIGHT, height); // 视频高度 
AMediaFormat_setBuffer(videoFormat, "csd-0", sps, spsSize); // sps 
AMediaFormat_setBuffer(videoFormat, "csd-1", pps, ppsSize); // pps

sps と pps が最初の I フレームの前に直接配置され、フォーマットが設定されていない場合、デコードも成功する可能性があることがわかりました。事前に設定されている場合、configure関数は事前にパラメーターを確認できるはずであり、パラメーターがサポートされていない場合、事前に返されません。surface パラメーターは、デコーダーの動作を直接決定します。NativeWindow を渡すと、デコーダーが接続された後の AImage は、Release メソッドを介して直接サーフェスにレンダリングされ、画像が表示されます。これにより、GPU から CPU へ、次に CPU から GPU へのイメージのコピーが節約され、より効率的になります; nullptr を渡す場合は、インターフェイスを介してイメージ アドレスを取得する必要があります。これには、要件を満たすために一部の CPU 画像処理を後で接続してから、画像レンダリングを行うことができるという利点があります。

4. デコーダーを起動します

これは比較的単純です。Start インターフェイスを呼び出すだけです。configure が存在しない場合、失敗します。

AMediaCodec_start();

3. Android MediaCodec のデータ フロー

開始後、データの送信を開始し、デコードのためにデータをフェッチします。前述の一般的な構造の説明によると、データ フローは基本的に 2 つのステップに分けられ、データの送信は主に InputBuffer を中心に展開し、データのフェッチは主に OutputBuffer を中心に展開します。ベスト プラクティスを実現するには、2 つのスレッドを使用してこれら 2 つのプロセスを別々に処理し、互いに干渉して効率を低下させないようにすることが最善であることがわかりました。

1.データを送る

データの送信は 3 つのステップに分かれています. 最初のステップは、InputBufferIndex を取得することです. このステップの主な目的は、InputBuffer がいっぱいかどうかを確認することです. InputBuffer がいっぱいの場合、アップストリームは対応するデータ キャッシュ操作を実行する必要があります。

MediaCodec_dequeueInputBuffer(handle, 2000);

2 番目のステップは、InputBuffer アドレスを取得し、データを入力することです。

AMediaCodec_getInputBuffer(handle, idx, &size);

3 番目のステップは、データが入力されたことを MediaCodec に伝えることです。

AMediaCodec_queueInputBuffer(handle, idx, 0, bufferSize, pts, 0);

ここで言及されていない特定のパラメータもあります。詳細は Android Developer で説明されています。ここで質問があります。InputBuffer アドレスを取得してからデータを入力し、入力するように指示する必要があるのはなぜですか。そのため、getInputBuffer と queueInputBuffer の 2 つの関数が必要です。代わりに関数 SendDataToInputBuffer を直接使用する方がよいのではないでしょうか? ここで言及しておくべきことの 1 つは、Android のハード デコードは、AnnexB 形式のコード ストリーム、つまり 00 00 00 01 で始まるコード ストリームのみをサポートすることです。avcc バイト長で始まるコード ストリームの場合は、前進。

2. データを取得する

データのフェッチは、データの送信よりも複雑です. 最初のステップは、デコードされたフレームがあるかどうかを確認するためのインデックスを取得することです:

AMediaCodec_dequeueOutputBuffer(handle, &info, 2000);

その場合は、フレームを取ります。表面が nullptr で満たされている場合、データ アドレスはインターフェイスを介して取得できます。

AMediaCodec_getOutputBuffer(handle, idx, &outsize);

サーフェスが値で満たされている場合は、リリース インターフェイスを介してイメージをサーフェスに直接レンダリングできます。

AMediaCodec_releaseOutputBuffer(handle, idx, bRender);

データをフェッチする際に注意すべきことは、getOutputBuffer で一部負の値が取得される可能性があることです。そして、これらの負の値は非常に意味があります。たとえば、AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED は、出力形式が変更されたことなどを示します。この情報に注意を払い、デコーダーの出力形式を適時に更新する必要があります。

4. ビジネスルートのハードデコード

1.ソフトデコードではなくハードデコード

最も簡単な方法は、構成中に Surface を null で埋めてから、デコードされたデータをコピーすることです。これは少し明白です。つまり、基本的に以前のソフト ソリューション ロジックと同じであり、外側をあまり変更する必要はなく、以前の VideoProcess も使用でき、レンダリングの連携は必要ありません。エンジン、およびカプセル化は良好です。欠点は、デコーダ メモリのコピーがもう 1 つ、それ自体のメモリに存在することです。

2. デコーダキャッシュを利用する

ビジネス1のコピーを最適化し、コピーを減らすとしたら、これが第2のビジネスルートです。出力ストレージにデコーダーのキャッシュを利用できます。つまり、ouputBuffer を呼び出した後、出力バッファー インデックスを取得し、急いで画像をコピーする必要はありません。代わりに、レンダリング時間まで待ち、GetOutputBuffer を呼び出してイメージ ポインターを取得してから、Image2D を呼び出して GPU テクスチャを生成します。

3. GPU イメージを使用して直接レンダリングする

サーフェスを渡すように構成すると、GPU 転送メソッドを介して直接レンダリングできます。これにより、GPU <-> CPU 間のメモリ コピーを減らすことができます。構成時に最初にサーフェスを渡します.ouputBuffer を呼び出した後、出力バッファ インデックスを取得します.レンダリング時には、直接 releaseOutputBuffer(handle, idx, true) を呼び出し、デコーダのイメージをサーフェス イメージに直接レンダリングします.
効率は高いですが、欠点も明らかで、まず、画像の後処理ができません。第 2 に、この方式はデコーダ キャッシュに依存しているため、問題が発生する可能性があります。デコーダが途中で破棄されると、キャッシュの内容が失われます。または、一部の再生ビジネス ロジックでは、デコーダーのバッファリング (逆再生など) がさらに必要になりますが、これは実行できません。

4. GPU イメージ、SurfaceTexture クラスを使用して OpenGL パイプラインにレンダリングする

ビジネスルート 3 については、Android システムもこの問題を考慮して、妥協点として解決策を提供します。最初に独自の OpenGL 環境を作成し、次にテクスチャを作成し、テクスチャを介して SurfaceTexture を作成し、構成するサーフェスを取り出します。このようにして、MediaCodec の Release が SurfaceTexture クラスにレンダリングされます。次に、Update メソッドを呼び出して、OpenGL テクスチャに同期します。その後、さまざまな後処理を接続して、swapbuffer を表示することができます。
このようにして、基本的にすべてのビジネス ロジックを満たすことができます。しかし、流暢さの欠如という小さな問題があります。具体的には、サーフェスが出力され、OpenGL がまだサーフェスを消費していない場合、デコード出力はブロックされます。つまり、outputBuffer のサーフェスと OpenGL cosume はシリアルに実行する必要があります。並列の場合、カバレッジの問題が発生します。
そのため、小さな調整を行うことができます。OpenGL で取得したテクスチャをコピーします (GPU->GPU コピー、テクスチャ コピー)。このようにして、OpenGL はデコードされた出力をブロックしません。しかし、コストはコピーのパフォーマンスの低下をもたらします。

5.流暢さを高めるためのマルチチャンネル同期

Android 6.0 (API23) は、新しいインターフェース - setOutputSurface を追加します。名前が示すように、これは出力サーフェスを動的に設定できます。これにより、上記の問題が完全に解決されます。具体的には、事前に複数のテクスチャを作成し、OutputBuffer 内のアイドル状態のテクスチャに出力をループしてデータでマークすることができます.OpenGL が画像を消費した後、テクスチャをアイドル状態に戻します. これは、OutputBuffer と OpenGL の消費の間にテクスチャ バッファを確立することと同じです。マルチスレッド並列処理の要件を満たすことができます。
欠点は明らかに Android 6.0 をサポートする必要があることですが、Android 統計パネル https://developer.android.com/about/dashboards/ を見ると、
ほとんどの携帯電話が Android 6.0 を超えていることがわかります。

5.文学

Android MediaCodec に関する Google の公式ドキュメントは、依然として非常に詳細です。私たちが発見するのを待っている多くの隠された属性があるはずです. より多くの公式ドキュメント マニュアルを確認する必要があります。

Java ドキュメント: https://developer.android.com/reference/android/media/MediaCodec

ndk ドキュメント: https://developer.android.com/ndk/reference/group/media

同時に、Android Samples には参照用のサンプル コードがあります。

https://github.com/googlesamples/android-BasicMediaDecoder/

ffmpeg にも関連パッケージがあり、具体的なファイルは次のとおりです: /libavcodec/mediacodecdec.c

おすすめ

転載: blog.csdn.net/qq_38750519/article/details/123215126