5

Android MediaCodec を使用してビデオ ストリームをデコードし、出力画像を使用してネイティブ コードでさらに画像処理を行うことを目指しています。

プラットフォーム: ASUS tf700t アンドロイド 4.1.1。テスト ストリーム: H.264 フル HD @ 24 frm/s

内部に Tegra-3 SoC があるため、ビデオ デコードのハードウェア サポートを期待しています。機能的には、私のアプリケーションは期待どおりに動作します。実際、デコーダー イメージにアクセスして適切に処理できます。ただし、デコーダの CPU 負荷が非常に高くなります。

以下の実験では、プロセス/スレッドの負荷は、adb シェルの「top -m 32 -t」によって測定されます。「top」から信頼できる出力を得るために、最低の優先度で永遠にループするいくつかのスレッドを実行することにより、4 つの CPU コアすべてを強制的にアクティブにします。これは、「cat /sys/devices/system/cpu/cpu[0-3]/online」を繰り返し実行することで確認できます。物事を単純にするために、ビデオのデコードのみがあり、オーディオはありません。タイミング制御がないため、デコーダーは可能な限り高速に実行されます。

最初の実験: JNI 処理関数を呼び出してアプリケーションを実行しますが、それ以降のすべての処理呼び出しはコメントアウトされています。結果:

  • スループット: 25 frm/s
  • アプリケーションのスレッド VideoDecoder の 1% の負荷
  • プロセス /system/bin/mediaserver のスレッド Binder_3 の 24% の負荷

デコード速度はCPUに制限されているようです(クアッドコアCPUの25%)...出力処理を有効にすると、デコードされた画像は正しく、アプリケーションは動作します。唯一の問題: デコードのための CPU 負荷が高すぎる。

たくさんの実験の後、MediaCodec に結果を描画するためのサーフェスを与えることを検討しました。他のすべての面では、コードは同じです。結果:

  • スループット 55 frm/s (いいね!!)
  • アプリケーションのスレッド VideoDecoder の 2% の負荷
  • プロセス /system/bin/mediaserver のスレッド mediaserver の 1% の負荷

実際、ビデオは提供された Surface に表示されます。CPU 負荷がほとんどないため、これはハードウェア アクセラレーションである必要があります...

Surface が提供されている場合、de MediaCodec はハードウェア アクセラレーションのみを使用しているようです。

ここまでは順調ですね。私はすでに Surface を回避策として使用する傾向がありました (必須ではありませんが、あると便利な場合もあります)。しかし、表面が提供されている場合、出力画像にアクセスできません! 結果は、ネイティブ コードでのアクセス違反です。

これは本当に私を困惑させます!アクセス制限の概念や、ドキュメントhttp://developer.android.com/reference/android/media/MediaCodec.htmlには何も表示されませんでした。また、Google I/O プレゼンテーションhttp://www.youtube.com/watch?v=RQws6vsoav8では、この方向については何も言及されていませんでした。

では、ハードウェア アクセラレータの Android MediaCodec デコーダを使用し、ネイティブ コードで画像にアクセスするにはどうすればよいでしょうか。アクセス違反を回避するには?どんな助けでも大歓迎です!また、説明やヒント。

アプリケーションは正常に機能するため (Surface を提供しない限り)、MediaExtractor と MediaCodec が適切に使用されていると確信しています。これはまだかなり実験的なものであり、優れた API 設計は todo リストにあります ;-)

2 つの実験の唯一の違いは変数 mSurface: null または "mDecoder.configure(mediaFormat, mSurface, null, 0);" の実際の Surface であることに注意してください。

初期化コード:

mExtractor = new MediaExtractor();
mExtractor.setDataSource(mPath);

// Locate first video stream
for (int i = 0; i < mExtractor.getTrackCount(); i++) {
    mediaFormat = mExtractor.getTrackFormat(i);
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
    Log.i(TAG, String.format("Stream %d/%d %s", i, mExtractor.getTrackCount(), mime));
    if (streamId == -1 && mime.startsWith("video/")) {
        streamId = i;
    }
}

if (streamId == -1) {
    Log.e(TAG, "Can't find video info in " + mPath);
    return;
}

mExtractor.selectTrack(streamId);
mediaFormat = mExtractor.getTrackFormat(streamId);

mDecoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
mDecoder.configure(mediaFormat, mSurface, null, 0);

width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
Log.i(TAG, String.format("Image size: %dx%d format: %s", width, height, mediaFormat.toString()));
JniGlue.decoutStart(width, height);

デコーダー ループ (別のスレッドで実行):

ByteBuffer[] inputBuffers = mDecoder.getInputBuffers();
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers();

while (!isEOS && !Thread.interrupted()) {
    int inIndex = mDecoder.dequeueInputBuffer(10000);
    if (inIndex >= 0) {
        // Valid buffer returned
        int sampleSize = mExtractor.readSampleData(inputBuffers[inIndex], 0);
        if (sampleSize < 0) {
            Log.i(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
            mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            isEOS = true;
        } else {
            mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
            mExtractor.advance();
        }
    }

    int outIndex = mDecoder.dequeueOutputBuffer(info, 10000);
    if (outIndex >= 0) {
        // Valid buffer returned
        ByteBuffer buffer = outputBuffers[outIndex];
        JniGlue.decoutFrame(buffer, info.offset, info.size);
        mDecoder.releaseOutputBuffer(outIndex, true);
    } else {
        // Some INFO_* value returned
        switch (outIndex) {
        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            Log.i(TAG, "RunDecoder: INFO_OUTPUT_BUFFERS_CHANGED");
            outputBuffers = mDecoder.getOutputBuffers();
            break;
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            Log.i(TAG, "RunDecoder: New format " + mDecoder.getOutputFormat());
            break;
        case MediaCodec.INFO_TRY_AGAIN_LATER:
            // Timeout - simply ignore
            break;
        default:
            // Some other value, simply ignore
            break;
        }
    }

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        Log.d(TAG, "RunDecoder: OutputBuffer BUFFER_FLAG_END_OF_STREAM");
        isEOS = true;
    }
}
4

2 に答える 2

4

出力サーフェスを構成すると、デコードされたデータがグラフィック バッファーに書き込まれ、OpenGL ES テクスチャーとして使用できます (「外部テクスチャー」拡張を介して)。ハードウェアのさまざまなビットが、好きな形式でデータを処理できるようになり、CPU がデータをコピーする必要がなくなります。

Surface を構成しない場合、出力はjava.nio.ByteBuffer. MediaCodec に割り当てられたバッファから にデータを取得するためのバッファ コピーが少なくとも 1 つありByteByffer、おそらくデータを JNI コードに戻すための別のコピーがあります。あなたが見ているのは、ソフトウェアのデコード コストではなくオーバーヘッド コストだと思います。

出力を に送信し、FBO または pbuffer にレンダリングし、 を使用してデータを抽出することで、問題を改善できる場合があります。ネイティブ コードから「直接」または呼び出しを読み取ると、JNI のオーバーヘッドが削減されます。このアプローチの欠点は、データが YCbCr ではなく RGB になることです。(OTOH、必要な変換を GLES 2.0 フラグメント シェーダーで表現できる場合は、CPU の代わりに GPU に作業をさせることができます。)SurfaceTextureglReadPixelsByteBufferglReadPixels

別の回答で述べたように、さまざまなデバイスのデコーダーはByteBufferさまざまな形式でデータを出力するため、移植性が重要な場合、ソフトウェアでデータを解釈することは実行できない場合があります。

編集: Grafikaには、GPU を使用して画像処理を行う例があります。ここでデモビデオを見ることができます。

于 2013-03-30T00:12:33.863 に答える
0

nexus 4 で mediacodec api を使用し、QOMX_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka の出力カラー フォーマットを取得します。このフォーマットは一種のハードウェア フォーマットであり、ハードウェア レンダリングでしかレンダリングできないと思います。興味深いことに、null と実際の Surface を使用して MediaCodec のサーフェスを構成すると、出力バッファーの長さがそれぞれ実際の値と 0 に変更されることがわかりました。どうしてか分かりません。より多くの結果を得るために、さまざまなデバイスでいくつかの実験を行うことができると思います。ハードウェア アクセラレーションについては、 http://www.saschahlusiak.de/2012/10/hardware-acceleration-on-sgs2-with-android-4-0/を参照してください。

于 2013-03-29T20:50:03.207 に答える