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;
}
}