目次
アダプティブ再生をサポートしていないコーデックの場合 (Surface にデコードしない場合を含む)
アダプティブ再生をサポートし、そのように構成されたデコーダの場合
元のソース: MediaCodec | Android Developers
1. MediaCodecとは何ですか
MediaCodec クラスは、低レベルのメディア コーデック、エンコーダ/デコーダ コンポーネントへのアクセスを提供します。これは、Android の基礎となるマルチメディア サポート インフラストラクチャの一部です (多くの場合、MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface、および AudioTrack とともに使用されます)。
大まかに言うと、コーデックは入力データを処理して出力データを生成します。データを非同期的に処理し、一連の入力バッファーと出力バッファーを使用します。単純なレベルでは、空の入力バッファを要求 (または受信) し、データを入力してコーデックに送信して処理します。コーデックのデータが不足し、空の出力バッファの 1 つに変換されました。最後に、いっぱいになった出力バッファを要求 (または受信) し、その内容を消費してコーデックに解放します。
2. ビデオエンコーディングの最低品質の最終ライン
Build.VERSION_CODES.S 以降、Android の Video MediaCodecs は最低品質基準を適用します。目的は、低品質のビデオ エンコーディングを排除することです。この品質ベースラインは、コーデックが可変ビットレート (VBR) モードの場合に適用されますが、コーデックが固定ビットレート (CBR) モードの場合は適用されません。品質ベースラインの適用も特定のサイズ範囲に制限されています。このサイズ範囲は現在、320x240 を超えて 1920x1080 までのビデオ解像度で使用できます。
この品質下限が有効な場合、コーデックおよびサポートするフレームワーク コードは、結果として得られるビデオが少なくとも「公正」または「良好」な品質であることを保証します。これらのターゲットを選択するために使用されるメトリックは VMAF (ビデオ マルチメソッド評価機能) で、選択したテスト シーケンスのターゲット スコアは 70 です。
一般的な影響は、一部のビデオが元の構成よりも高いビットレートを生成することです。これは、非常に低いビットレートで構成されたビデオで最も顕著です。コーデックは、「まあまあ」または「良好な」品質のビデオを生成する可能性が高いと判断されたビットレートを使用します。もう 1 つの状況は、ビデオに非常に複雑なコンテンツ (動きやディテールが多い) が含まれている場合です。この構成では、コーデックはコンテンツの細部がすべて失われないように、必要に応じて追加のビットレートを使用します。
この品質下限は、高ビットレートでキャプチャされたコンテンツには影響しません (これにより、すべての詳細をエンコードするのに十分な容量がコーデックにすでに与えられているはずです)。Quality Bottom は CBR エンコーディングでは動作しません。現在、品質ベースラインは 320x240 以下の解像度や 1920x1080 を超えるビデオでは利用できません。
3. データ型
コーデックは、圧縮データ、生のオーディオ データ、生のビデオ データの 3 種類のデータを処理します。3 種類のデータはすべて ByteBuffers を使用して処理できますが、コーデックのパフォーマンスを向上させるには、生のビデオ データには Surface を使用する必要があります。Surface は、ByteBuffers へのマッピングやコピーを行わずにネイティブ ビデオ バッファーを使用するため、はるかに効率的です。Surface を使用する場合、通常、生のビデオ データにはアクセスできませんが、ImageReader クラスを使用して、安全でないデコードされた (生の) ビデオ フレームにアクセスできます。一部のネイティブ バッファーは ByteBuffers に直接マップされる可能性があるため、これは ByteBuffers を使用するよりも効率的である可能性があります。ByteBuffer モードを使用する場合、Image クラスと getInput/OutputImage(int) を使用して生のビデオ フレームにアクセスできます。
圧縮バッファ
入力バッファ (デコーダ用) と出力バッファ (エンコーダ用) には、フォーマットの種類に応じて圧縮データが含まれます。ビデオ タイプの場合、これは通常、圧縮ビデオの単一フレームです。オーディオ データの場合、これは通常、単一のアクセス ユニット (エンコードされたオーディオのセグメント。通常、形式の種類によって指定される数ミリ秒のオーディオを含む) ですが、バッファにはエンコードされたオーディオの複数のアクセス ユニットが含まれる場合があるため、この要件はわずかに緩和されます。 。どちらの場合も、バッファーは、BUFFER_FLAG_PARTIAL_FRAME としてフラグが立てられていない限り、任意のバイト境界で開始または終了するのではなく、フレーム/アクセス ユニット境界で開始または終了します。
生のオーディオバッファ
RAW オーディオ バッファーには、PCM オーディオ データの完全なフレームがチャンネル順に含まれており、チャンネルごとに 1 つのサンプルが含まれます。各 PCM オーディオ サンプルは、ネイティブ バイト オーダーの 16 ビット符号付き整数または浮動小数点数です。浮動小数点 PCM エンコーディングの生のオーディオ バッファーは、MediaCodec configure(…) 中に MediaFormat の MediaFormat#KEY_PCM_FLOAT が AudioFormat#ENCODING_PCM_FLOAT に設定され、デコーダーの getOutputFormat() またはエンコーダーの getInputFormat() によって確認された場合にのみ可能です。MediaFormat でフローティング PCM をチェックする方法の例は次のとおりです。
static boolean isPcmFloat(MediaFormat format) {
return format.getInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
== AudioFormat.ENCODING_PCM_FLOAT;
}
短い配列の 16 ビット符号付き整数オーディオ データを含むバッファーの 1 つのチャネルを抽出するには、次のコードを使用できます。
// Assumes the buffer PCM encoding is 16 bit.
short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
MediaFormat format = codec.getOutputFormat(bufferId);
ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
int numChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
if (channelIx < 0 || channelIx >= numChannels) {
return null;
}
short[] res = new short[samples.remaining() / numChannels];
for (int i = 0; i < res.length; ++i) {
res[i] = samples.get(i * numChannels + channelIx);
}
return res;
}
生のビデオバッファ
ByteBuffer モードでは、ビデオ バッファーはカラー フォーマットに従ってレイアウトされます。サポートされているカラー形式は、getCodecInfo().getCapabilitiesForType(...).colorFormats から配列として取得できます。ビデオ コーデックは、次の 3 つのカラー フォーマットをサポートする場合があります。
- ネイティブ Raw ビデオ形式: これは、CodecCapabilities#COLOR_FormatSurface によってマークされ、入力または出力 Surface で使用できます。
- 柔軟な YUV バッファー (例: CodecCapabilities#COLOR_FormatYUV420Flexible): getInput/OutputImage(int) を使用することで、入出力 Surface および ByteBuffer モードで使用できます。
- その他の特定の形式: これらは通常、ByteBuffer モードでのみサポートされます。一部のカラー形式はベンダー固有です。その他は CodecCapabilities で定義されます。フレキシブル フォーマットと同等のカラー フォーマットの場合は、引き続き getInput/OutputImage(int) を使用できます。
Build.VERSION_CODES.LOLLIPOP_MR1 以降のすべてのビデオ コーデックは、柔軟な YUV 4:2:0 バッファーをサポートしています。
古いデバイスで生のビデオ バイト バッファにアクセスする
Build.VERSION_CODES.LOLLIPOP と Image がサポートされる前は、生の出力バッファーのレイアウトを知るために、MediaFormat#KEY_STRIDE および MediaFormat#KEY_SLICE_HEIGHT 出力形式の値を使用する必要がありました。
一部のデバイスでは、タイルの高さを 0 に設定することが推奨されることに注意してください。これは、スライスの高さがフレームの高さと同じであること、またはスライスの高さが何らかの値 (通常は 2) に合わせられたフレームの高さであることを意味する可能性があります。残念ながら、この場合、実際のスライスの高さを知るための標準的で簡単な方法はありません。また、平面形式の U プレーンの垂直ストライドは指定または定義されていませんが、通常はスライス高さの半分です。
MediaFormat#KEY_WIDTH キーと MediaFormat#KEY_HEIGHT キーはビデオ フレームのサイズを指定しますが、ほとんどのエンコードでは、ビデオ (画像) はビデオ フレームの一部のみを占めます。これは「切り抜き長方形」で表されます。
出力形式から元の出力画像の切り抜き四角形を取得するには、次のキーを使用する必要があります。これらのキーが存在しない場合、ビデオはビデオ フレーム全体を占めます。切り抜き長方形は、回転が適用される前に、出力フレームのコンテキストで理解されます。
フォーマットキー |
タイプ |
説明する |
「クロップ左」 |
整数 |
クリッピング四角形の左座標 (x) |
「クロップトップ」 |
整数 |
クリッピング四角形の上の座標 (y) |
「右に切り取る」 |
整数 |
クリッピング長方形の右座標 (x) マイナス 1 |
「右に切り取る」 |
整数 |
クリッピング四角形の下部の座標 (y) マイナス 1 |
右下の座標は、切り出された出力画像の最右端の有効列/最下端の有効行の座標として理解できる。
ビデオ フレームのサイズ (回転前) は次のように計算できます。
MediaFormat format = decoder.getOutputFormat(…);
int width = format.getInteger(MediaFormat.KEY_WIDTH);
if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
}
int height = format.getInteger(MediaFormat.KEY_HEIGHT);
if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
}
また、BufferInfo.offset の意味はデバイス間で一貫していないことにも注意してください。一部のデバイスでは、オフセットはトリミング四角形の左上のピクセルを指しますが、ほとんどのデバイスでは、オフセットはフレーム全体の左上のピクセルを指します。
4. ステータス
コーデックは、その存続期間中、概念的には、停止、実行、リリースの 3 つの状態のいずれかになります。停止状態は、実際には、未初期化、構成、およびエラーの 3 つの状態の集合です。一方、実行状態は、概念的には 3 つのサブ状態 (フラッシュ、実行中、およびストリームの終了) に分割されます。
ファクトリ メソッドのいずれかを使用してコーデックを作成すると、コーデックは初期化されていない状態になります。まず、configure(...) を使用して構成して Configured 状態にし、次に start() を呼び出して Executing 状態に移行する必要があります。この状態では、上記のバッファ キュー操作を通じてデータを処理できます。
Executing 状態には、Flushed、Running、End-of-Stream の 3 つのサブ状態があります。start() の直後、コーデックはすべてのバッファを保持するフラッシュされたサブ状態になります。最初の入力バッファがデキューされると、コーデックは実行サブステートに移行し、そこでほとんどの時間を費やします。ストリーム終了マーカーを使用して入力バッファをキューに入れると、コーデックはストリーム終了サブ状態に移行します。この状態では、コーデックはそれ以上入力バッファを受け入れませんが、出力でストリームの終わりに達するまで出力バッファを生成します。Executing 状態にあるときに flash() を使用すると、いつでも Flushed サブ状態に戻ることができます。
stop() を呼び出すと、コーデックが初期化されていない状態に戻り、その後、再度設定できるようになります。コーデックの使用が完了したら、release() を呼び出してコーデックを解放する必要があります。
まれに、コーデックでエラーが発生し、エラー状態になることがあります。これは、キューに入れられた操作からの無効な戻り値を使用して、または場合によっては例外を通じて伝達されます。コーデックを再び使用できるようにするには、reset() を呼び出します。どの状態からでも呼び出して、コーデックを初期化されていない状態に戻すことができます。それ以外の場合は、release() を呼び出して端末のリリース状態に移行します。
5.作成
MediaCodecList を使用して、特定の MediaFormat の MediaCodec を作成します。ファイルまたはストリームをデコードするときに、MediaExtractor.getTrackFormat から目的の形式を取得できます。MediaFormat.setFeatureEnabled を使用して、追加する特定の機能を挿入し、MediaCodecList.findDecoderForFormat を呼び出して、その特定のメディア形式を処理できるコーデックの名前を取得します。最後に、createByCodecName(String) を使用してコーデックが作成されます。
注: Build.VERSION_CODES.LOLLIPOP では、MediaCodecList.findDecoder/EncoderForFormat の形式にフレーム レートを含めることはできません。format.setString(MediaFormat.KEY_FRAME_RATE, null) を使用して、形式内の既存のフレーム レート設定をクリアします。
createDecoder/EncoderByType(java.lang.String) を使用して、特定の MIME タイプの優先コーデックを作成することもできます。ただし、これを使用して関数を挿入することはできず、特定の必要なメディア形式を処理できないコーデックが作成される可能性があります。
安全なデコーダを作成する
Build.VERSION_CODES.KITKAT_WATCH 以前では、セキュアなコーデックが MediaCodecList にリストされていない場合がありますが、システム上では引き続き使用できる場合があります。既存のセキュア コーデックは、通常のコーデックの名前に「.secure」を追加することによってのみ、名前でインスタンス化できます (すべてのセキュア コーデック名は「.secure」で終わる必要があります)。コーデックがシステム上に存在しない場合、createByCodecName(String) は IOException をスローします。
Build.VERSION_CODES.LOLLIPOP 以降では、メディア形式で CodecCapabilities#FEATURE_SecurePlayback 機能を使用して、安全なデコーダーを作成する必要があります。
6. 初期化
コーデックを作成した後、データを非同期に処理する場合は、setCallback を使用してコールバックを設定できます。次に、特定のメディア形式でコーデックを構成します。これは、ビデオ プロデューサー、つまり生のビデオ データを生成したコーデック (ビデオ コーデックなど) の出力サーフェスを指定できる場合です。このとき、安全なコーデックの復号パラメータを設定することもできます (MediaCrypto を参照)。最後に、一部のコーデックは複数のモードで動作できるため、デコーダとして動作させるかエンコーダとして動作させるかを指定する必要があります。
Build.VERSION_CODES.LOLLIPOP により、生成された入力および出力形式は構成済み状態でクエリできます。これを使用して、コーデックを開始する前に、カラー形式などの生成された設定を検証できます。
ビデオ コンシューマー (ビデオ エンコーダーなど、生のビデオ入力を処理するコーデック) を使用して生の入力ビデオ バッファーをネイティブに処理する場合は、構成後に createInputSurface() を使用して、入力データのターゲット サーフェスを作成します。あるいは、setInputSurface(Surface) を呼び出して、以前に作成した永続的な入力サーフェスを使用するようにコーデックを設定します。
コーデック固有のデータ
一部の形式、特に AAC オーディオ形式、MPEG4、H.264、および H.265 ビデオ形式では、実際のデータの前に、セットアップ データまたはコーデック固有のデータを含む複数のバッファを付ける必要があります。このような圧縮フォーマットを扱う場合、このデータは start() の後、フレーム データの前にコーデックに送信する必要があります。このようなデータには、queueInputBuffer を呼び出すときにフラグ BUFFER_FLAG_CODEC_CONFIG でフラグを立てる必要があります。
コーデック固有のデータは、キー「csd-0」、「csd-1」などを使用して、ByteBuffer エントリの構成に渡される形式に含めることもできます。これらのキーは、MediaExtractor から取得されるトラックの MediaFormat に常に含まれます。形式内のコーデック固有のデータは、start() でコーデックに自動的にコミットされます。このデータを明示的にコミットしないでください。フォーマットにコーデック固有のデータが含まれていない場合は、フォーマットの要求に応じて、指定された数のバッファを使用して正しい順序でデータを送信することを選択できます。H.264 AVC の場合、すべてのコーデック固有のデータを連結し、単一のコーデック構成バッファとして送信することもできます。
Android は、次のコーデック固有のデータ バッファーを使用します。MediaMuxer トラックを適切に構成するには、これらもトラック形式で設定する必要があります。(*) のマークが付いた各パラメータ セットおよびコーデック固有のデータ セクションは、開始コード「\x00\x00\x00\x01」で始まる必要があります。
フォーマット |
CSDバッファ#0 |
CSD バッファ #1 |
CSD バッファ #2 |
AAC |
ESDS* からのデコーダ固有の情報 |
使われたことがない |
使われたことがない |
ヴォビス |
IDヘッダー |
タイトルを設定する |
使われたことがない |
オーパス |
IDヘッダー |
ナノ秒単位のプリスキップ 整数。) |
ナノ秒でプリロールを検索 整数。) |
FLAC |
「fLaC」、ASCII 形式の FLAC ストリーム マーカー、 |
使われたことがない |
使われたことがない |
MPEG-4 |
ESDS* からのデコーダ固有の情報 |
使われたことがない |
使われたことがない |
H.264 AVC |
SPS (シーケンスパラメータセット*) |
PPS(ピクチャーパラメータセット*) |
使われたことがない |
H.265 HEVC |
VPS (ビデオパラメータセット*) + |
使われたことがない |
使われたことがない |
VP9 |
VP9コーデックプライベート データ(オプション) |
使われたことがない |
使われたことがない |
注:出力バッファまたは出力形式の変更が返される直前、または起動直後にコーデックをフラッシュする場合は、フラッシュ中にコーデック固有のデータが失われる可能性があるため、注意が必要です。コーデックが正しく動作することを確認するには、このようなフラッシュの後にBUFFER_FLAG_CODEC_CONFIGとマークされたバッファを使用してデータを再送信する必要があります。
エンコーダ (または圧縮データを生成するコーデック) は、codec-config フラグでマークされた出力バッファ内の有効な出力バッファよりも前に、コーデック固有のデータを作成して返します。コーデック固有のデータを含むバッファには、意味のあるタイムスタンプがありません。
7. データ処理
各コーデックは、API 呼び出しのバッファ ID によって参照される入力バッファと出力バッファのセットを維持します。start()の呼び出しが成功した後、クライアントは入力バッファも出力バッファも「所有」しません。同期モードでは、dequeueInput / OutputBuffer(...)を呼び出して、コーデックから入力バッファまたは出力バッファを取得 (所有権を取得) します。非同期モードでは、 MediaCodec.Callback.onInput / OutputBufferAvailable(…)コールバックを介して利用可能なバッファを自動的に受け取ります。
入力バッファを取得したら、それにデータを入力し、queueInputBuffer (復号化を使用する場合はqueueSecureInputBuffer)を使用してコーデックに送信します。同じタイムスタンプを持つ複数の入力バッファを送信しないでください (そのようにマークされているコーデック固有のデータでない限り)。
コーデックは、非同期モードではonOutputBufferAvailableコールバックを介して読み取り専用の出力バッファを返すか、同期モードではdequeueOutputBufferの呼び出しに応答します。出力バッファを処理した後、releaseOutputBufferメソッドの 1 つを呼び出してバッファをコーデックに返します。
コーデックにバッファをすぐに再送信/解放する必要はありませんが、入力バッファや出力バッファを保持するとコーデックが停止する可能性があり、この動作はデバイスに依存します。具体的には、コーデックは、すべての未処理のバッファが解放/再送信されるまで、出力バッファの生成を延期する場合があります。したがって、空きバッファをできるだけ少なくするようにしてください。
API バージョンに応じて、次の 3 つの方法でデータを処理できます。
加工方法 |
API バージョン <= 20 |
API バージョン >= 21 |
バッファ配列を使用した同期API |
サポートされている |
廃止された |
バッファを使用した同期 API |
利用不可 |
サポートされている |
バッファを使用した非同期 API |
利用不可 |
サポートされている |
バッファを使った非同期処理
Build.VERSION_CODES.LOLLIPOPがあるため、 configureを呼び出す前にコールバックを設定してデータを非同期的に処理する方法が推奨されます。非同期モードでは状態遷移が若干変更されます。これは、コーデックを Running サブ状態に遷移させ、入力バッファの受信を開始するには、flush()の後にstart () を呼び出す必要があるためです。同様に、コーデックへの最初の呼び出しでは、start は直接 Running サブステートに移動し、コールバックを介して利用可能な入力バッファーの受け渡しを開始します。
MediaCodec は通常、非同期モードで次のように使用されます。
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is equivalent to mOutputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
}
@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mOutputFormat = format; // option B
}
@Override
void onError(…) {
…
}
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start();
// wait for processing to complete
codec.stop();
codec.release();
バッファを使用した同期
Build.VERSION_CODES.LOLLIPOPのため、コーデックを同期モードで使用する場合でも、 getInput / OutputBuffer(int)および/またはgetInput / OutputImage(int)を使用して入力バッファと出力バッファを取得する必要があります。これにより、動的コンテンツを扱う場合など、フレームワークで特定の最適化を行うことができます。getInput / OutputBuffers()を呼び出すと、この最適化は無効になります。
注: バッファーメソッドとバッファー配列メソッドを同時に混合しないでください。具体的には、値が の出力バッファ ID をデキューした後、またはその直後にのみ getInput/ を呼び出します。OutputBuffers start() INFO_OUTPUT_FORMAT_CHANGED
MediaCodec は通常、同期モードで次のように使用されます。
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();
バッファ配列を使用した同期処理 (非推奨)
Build.VERSION_CODES.KITKAT_WATCH以前のバージョンでは、入力バッファーと出力バッファーのセットは ByteBuffer[] 配列で表されていました。start()の呼び出しが成功すると、getInput / OutputBuffers()を使用してバッファーの配列が取得されます。以下の例のように、バッファー ID をこれらの配列 (負でない場合) のインデックスとして使用します。配列サイズには上限がありますが、配列サイズとシステムが使用する入出力バッファーの数の間には本質的な相関関係がないことに注意してください。
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
codec.start();
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(…);
if (inputBufferId >= 0) {
// fill inputBuffers[inputBufferId] with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
// outputBuffers[outputBufferId] is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = codec.getOutputBuffers();
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
MediaFormat format = codec.getOutputFormat();
}
}
codec.stop();
codec.release();
ストリーム終了処理
入力データの終わりに達したら、呼び出しでBUFFER_FLAG_END_OF_STREAMフラグを指定して、入力データをコーデックqueueInputBufferに送信する必要があります。これは、最後の有効な入力バッファで行うことも、ストリームの終わりフラグを設定して追加の空の入力バッファを送信することによっても行うことができます。空のバッファが使用される場合、タイムスタンプは無視されます。
コーデックは、 dequeueOutputBufferに設定されたBufferInfo内の同じ終了ストリーム フラグを指定するか、return onOutputBufferAvailableを介して、最終的に出力ストリームの終了を通知するまで、出力バッファを返し続けます。これは、最後の有効な出力バッファ、または最後の有効な出力バッファの後の空のバッファに設定できます。このような空のバッファのタイムスタンプは無視する必要があります。
コーデックがフラッシュされるか、停止して再起動されるまでは、入力ストリームの終了を通知した後、追加の入力バッファをコミットしないでください。
出力サーフェスを使用する
出力Surfaceを使用する場合、データ処理は ByteBuffer モードとほぼ同じですが、出力バッファーにはアクセスできず、null 値として表されます。たとえば、getOutputBuffer / Image(int)は null を返し、getOutputBuffers() はnull のみを含む配列を返します。
出力Surfaceを使用する場合、 Surface上に各出力バッファをレンダリングするかどうかを選択できます。次の 3 つのオプションがあります。
- バッファをレンダリングしないでください。releaseOutputBuffer(bufferId, false)を呼び出します。
- デフォルトのタイムスタンプを使用してバッファをレンダリングします。releaseOutputBuffer(bufferId, true)を呼び出します。
- 特定のタイムスタンプを持つバッファをレンダリングします。releaseOutputBuffer(bufferId, timestamp)を呼び出します。
Build.VERSION_CODES.Mにより、デフォルトのタイムスタンプはバッファーのレンダリング タイムスタンプ(ナノ秒に変換) です。それ以前は定義されていませんでした。
Build.VERSION_CODES.Mに加えて、 setOutputSurfaceを使用できます。
出力をSurfaceにレンダリングする場合、Surface は多すぎるフレーム (Surface によって適時に消費されないフレーム) をドロップするように構成されている可能性があります。または、フレームをドロップしすぎないように設定することもできます。後者のモードでは、Surface が出力フレームを十分な速さで消費しない場合、最終的にデコーダがブロックされます。
Build.VERSION_CODES.Qまでは、ビュー サーフェス (SurfaceView または TextureView) が常に多すぎるフレームをドロップすることを除いて、正確な動作は未定義です。Build.VERSION_CODES.Qが原因で、デフォルトの動作ではフレームが多すぎます。アプリケーションは、 Build.VERSION_CODES.Qから SDK をターゲットにし、構成された形式でキーMediaFormat#KEY_ALLOW_FRAME_DROPを 0 に設定することで、非 View サーフェス (ImageReaders やサーフェス テクスチャなど) に対してこの動作をオプトインできます。
サーフェスにレンダリングするときの変換
コーデックが Surface モードで設定されている場合、クロップ四角形、回転、ビデオ スケーリング モードは、次の 1 つの例外を除いて自動的に適用されます。
Build.VERSION_CODES.Mのリリースより前は、Surface にレンダリングするときにソフトウェア コーデックに回転が適用されない場合があります。残念ながら、ソフトウェア コーデック、またはソフトウェア コーデックが回転を適用しているかどうかを識別するための標準的で簡単な方法は、試してみる以外にありません。
注意事項もいくつかあります。
Surface に出力を表示する場合、ピクセル アスペクト比は考慮されないことに注意してください。つまり、VIDEO_SCALING_MODE_SCALE_TO_FITモードを使用している場合は、最終的な表示アスペクト比が正しくなるように出力サーフェスを配置する必要があります。代わりに、正方形ピクセル (ピクセル アスペクト比または 1:1) のコンテンツにはVIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPINGモードのみを使用できます。
Build.VERSION_CODES.N のリリース時点では、 VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPINGモードは 90 度または 270 度回転したビデオに対して正しく動作しない可能性があることにも注意してください。
ビデオ スケーリング モードを設定する場合は、出力バッファを変更するたびにリセットする必要があることに注意してください。INFO_OUTPUT_BUFFERS_CHANGEDイベントは非推奨になっているため、出力形式を変更するたびにこれを実行できます。
入力サーフェスを使用する
入力 Surface を使用する場合、バッファーは入力 Surface からコーデックに自動的に渡されるため、アクセス可能な入力バッファーはありません。dequeueInputBufferを呼び出すとIllegalStateException がスローされ、getInputBuffers() は書き込みできない偽の ByteBuffer[] 配列を返します。
signalEndOfInputStream()を呼び出して、ストリームの終了を通知します。入力 Surface は、この呼び出しの直後にコーデックへのデータの送信を停止します。
シークおよび適応再生のサポート
ビデオ デコーダ (および一般に圧縮ビデオ データを使用するコーデック) は、適応再生をサポートし、そのように構成されているかどうかに関係なく、シークとフォーマットの変更に関して異なる動作をします。コーデックがアダプティブ再生をサポートしているかどうかは、 CodecCapabilities.isFeatureSupported(String)を通じて確認できます。ビデオ コーデックのアダプティブ再生サポートは、Surface にデコードするようにコーデックを構成した場合にのみ有効になります。
フロー境界とキーフレーム
適切なストリーム境界の後にstart()またはflush()する入力データが重要です。最初のフレームはキーフレームである必要があります。キーフレームは単独でデコードでき (ほとんどのコーデックでは、このキーフレームは I フレームです)、参照されたキーフレームの前に他のフレームは表示されません。
以下の表は、さまざまなビデオ形式で使用できるキーフレームをまとめたものです。
フォーマット |
適切なキーフレーム |
VP9/VP8 |
後続のフレームがその前のフレームを参照しない、適切なイントラフレーム。 |
H.265 HEVC |
IDRまたはCRA |
H.264 AVC |
IDR |
MPEG-4 |
後続のフレームがその前のフレームを参照しない適切な I フレーム。 |
アダプティブ再生をサポートしていないコーデックの場合 (Surface にデコードしない場合を含む)
以前にコミットされたデータに隣接していないデータのデコードを開始するには (つまり、シーク後)、デコーダをリフレッシュする必要があります。すべての出力バッファはフラッシュされるとすぐにフラッシュされるため、フラッシュを呼び出す前にシグナルを送り、ストリームの終わりを待つことをお勧めします。更新された入力データが適切なフロー境界/キーフレームで始まることが重要です。
注意: flush后提交的数据格式必须不能改变;flush()不支持不连的格式;为此,一个完整的stop()- configure(…)-start()生命周期是必要的。
另外请注意:如果start()后过早刷新编解码器,通常在收到第一个输出缓冲区或输出格式更改之前,您将需要重新提交编解码特定数据到编解码器。有关详细信息,请参阅编解码特定数据部分。
对于支持并配置为自适应播放的解码器
为了开始解码与先前提交的数据不相邻的数据(即在查找之后),刷新解码器不是必要的;但是,中断后的输入数据必须从合适的流边界/关键帧开始。
对于某些视频格式 - 即 H.264、H.265、VP8 和 VP9 - 还可以在中途更改图片大小或配置。为此,您必须将整个新的特定于编解码器的配置数据与关键帧一起打包到单个缓冲区(包括任何起始代码)中,并将其作为常规输入缓冲区提交。
您将在图片大小更改发生之后和具有新大小的任何帧返回之前收到INFO_OUTPUT_FORMAT_CHANGED来自dequeueOutputBuffer或onOutputFormatChanged回调的返回值。
注意:就像编解码器特定数据的情况一样,在调用flush()方法来改图片大小要小心 。如果您还没有收到更改图片大小的确认,您将需要重新请求新的图片大小。
错误处理
您必须捕获或声明放弃工厂方法createByCodecName和createDecoder/EncoderByType引发IOException失败。MediaCodec 会抛出IllegalStateException,当从不允许它的编解码器状态调用方法时;这通常是由于不正确的应用程序 API 使用造成的。涉及安全缓冲区的方法可能会抛出 CryptoException,其中包含可从CryptoException#getErrorCode.
内部编解码器错误会导致CodecException,这可能是由于媒体内容损坏、硬件故障、资源耗尽等,即使应用程序正确使用 API。当抛出CodecException 时,调用CodecException#isRecoverable和CodecException#isTransient来确定推荐的操作:
- 可恢复的错误:如果isRecoverable()返回true,则调用 stop(),configure(…)以及start()恢复。
- 瞬态错误:如果isTransient()返回 true,则资源暂时不可用,该方法可能会稍后重试。
- 致命的なエラー: isRecoverable() と isTransient() の両方が false を返した場合、CodecException は致命的であるため、コーデックをリセットまたは解放する必要があります。
isRecoverable() と isTransient() は同時に true を返すことはありません。