MediaCodec作品
アンドロイドMediaCodecクラスは、低レベルのAndroidマルチメディアアーキテクチャの一部であるマルチメディアエンコーダ/デコーダのインタフェースのための低レベルのアクセスを提供し、それは多くの場合、そのようなコーデックとして、MediaExtractor、MediaMuxer、AudioTrackと共に使用されるH.264、H.265、AACすることができ、3GPおよび他の一般的なオーディオおよびビデオフォーマット。大まかに言えば、動作原理MediaCodecは、農産物の出力データに入力データを処理しています。具体的には、MediaCodecは、符号化中にデータの同期または非同期処理への入力/出力バッファのセットを使用して復号化する:まず、クライアントデータがコーデック入力バッファに書き込まれ、コーデックが取得しますコーデックに提出され、コーデックは、エンコーダバッファの出力へ移動して処理されるが、入力バッファの回復クライアント所有してから、リードバッファへのコーデックの出力から取得されたクライアント良いデータは、コード上で処理された出力バッファのコーデッククライアントの所有権を回復するために処理します。全体のプロセスは、エンコーダが働いて停止するか予期せず終了するまで繰り返されます。
図からわかるように、効果は、データ入力処理で出力データを生成mediacodec。コーデックに設けられたバッファに、データを第1の入力データバッファを生成し、コーデックは非同期これらの入力は、出力バッファは、消費者がバッファを過ごした後、消費者に充填されるプロセスデータに使用されますコーデックに戻りました。
MediaCodec符号化処理
停止として符号化および復号化プロセス全体を通して、MediaCodec使用体験構成し、データ処理は、いくつかのプロセスを解放し、停止している開始、対応する状態を要約することができる(停止)、実行(実行)と放出(リリース)は、3つの状態、停止状態と初期化されていない(初期化されていない)、コンフィギュレーション(構成)に細分することができる、(エラー)、異常、状態を実行すると(フラッシュ)さ書き込みデータに分割することができる、(実行)を実行し、ストリームの終了(エンド・オブ・ストリーム)。次のように構造のMediaCodec全体の状態です。
図からわかる後、MediaCodecが初期化されていない状態になりますときに作成され、設定情報は、通話をセットアップするには()を開始し、MediaCodecが実行状態に入り、読み取りと書き込みのデータができ始めます。エラーは、この過程で発生した場合、MediaCodecが停止状態になります、私たちは、コーデックをリセットするためにリセットメソッドを使用している、またはMediaCodecは、リソースが最終的に解放されます開催しました。着信呼の停止と解除方法は、コーデックを使用している間もちろん、MediaCodec通常の使用ならば、我々はまた、EOSコーデックに指示を送ることができます。
(1)を作成するエンコーダ/デコーダ
MediaCodec主createEncoderByType(文字列型)、二つの方法のcreateDecoderByType(文字列型)がコーデックを作成するために提供する、これらのマルチメディアフォーマットのMIMEタイプを通過するすべての必要性。次のようなマルチメディアフォーマットの共通MIMEタイプがある:
● "ビデオ/ X - vnd.on2.vp8" - VP8ビデオ(IEの.webmにおけるビデオ)
● "ビデオ/ Xは- vnd.on2.vp9" - IEでVP9ビデオ(ビデオ。 WEBM)
● "ビデオ/ AVC" - H.264 / AVCビデオ
● "ビデオ/ MP4V-ES" - MPEG4ビデオ
● "ビデオ/ 3GPP" - H.263ビデオ
● "オーディオ/ 3GPP" - AMRナローバンドオーディオ
●「オーディオ/ AMR-WB " - AMR広帯域オーディオ
●"オーディオ/ MPEG " - MPEG1 / 2オーディオレイヤーIII
●"オーディオ/ MP4A-1気圧" - AACオーディオ(注、このIS RAW AACパケット、LATMにパッケージングされません!)
●"オーディオ/ Vorbisの" -オーディオVorbisの
●"オーディオ/ G711-ALAW " -オーディオALAW G.711
●"オーディオ/「G711-mlaw - G.711オーディオはuLaw
もちろん、MediaCodecもコーデックを作成するためのコンポーネントの特定の名前の使用をサポートし、createByCodecName(文字列名)方法を提供します。しかし、この方法は、いくつかの問題を使用しており、関係者はMediaCodecListは、すべての利用可能なコーデックを記録しているためMediaCodecListを使用して最善の方法があります。もちろん、我々はまた、パラメータのこのタイプを使用することができますコーデックのmineTypeタイプをサポートするかどうかをMediaCodecに一致するように、minmeTypeの裁判官を通過しました。以下のように、一例として、MIMEタイプ「ビデオ/ AVC」を指定するには:
private static MediaCodecInfo selectCodec(String mimeType) {
// 获取所有支持编解码器数量
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
// 编解码器相关性信息存储在MediaCodecInfo中
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
// 判断是否为编码器
if (!codecInfo.isEncoder()) {
continue;
}
// 获取编码器支持的MIME类型,并进行匹配
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(mimeType)) {
return codecInfo;
}
}
}
return null;
}
(2)設定は、エンコーダ/デコーダ開始
コーデック設定native_configure設定作業のためのデータが最初に抽出された地図MediaFormatに格納されている構成MediaCodec方法、及びローカルメソッド呼び出しに使用されるコーデック。設定する場合、設定方法は、フォーマットを渡す必要があり、表面、暗号、flagsパラメータ、フォーマットは、マルチメディアデータの形式で格納されている「キー値」キーフォーマット情報を使用する、MediaFormatの一例である。デコーダを示す表面表面へのデータソースから、暗号を指定したメディアデータのセキュリティを復号するためMediaCryptoオブジェクト、フラグは、エンコーダコンフィギュレーション(CONFIGURE_FLAG_ENCODE)ことを示しています。
MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480); // 创建MediaFormat
mFormat.setInteger(MediaFormat.KEY_BIT_RATE,600); // 指定比特率
mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30); // 指定帧率
mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat); // 指定编码器颜色格式
mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定关键帧时间间隔
mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
上記のコードは、H.264符号化方式、createVideoFormat(「ビデオ/ AVC」に配置されている 、640、480) 「ビデオ/ AVC」タイプとして(すなわち、H.264)MediaFormatオブジェクトエンコーダは、映像データの幅を指定する必要があります高い符号化する場合、オーディオデータの復号、createAudioFormatコール(文字列MIME、INTは、sampleRate、INTのMediaFormat channelCount) 方法。加えて、ビデオフレームレート、オーディオサンプルレートおよび他のパラメータのようないくつかのビデオ設定、ビデオエンコーダ用のカラーフォーマットを示すMediaFormat.KEY_COLOR_FORMAT構成属性、入力色フォーマット及び選択されたデータソースの特定の色を説明強調されるべきですフォーマットについて。例えば、我々はすべて知っているカメラプレビュー画像ストリームは、通常、NV21またはYV12、適切なカラーフォーマットを指定するために、エンコーダの必要性、または符号化データが得られるの取得はHuaping、ゴースト、色の歪みなどに表示されることがあります。。:MediaCodecInfo.CodecCapabilities格納エンコーダは、色のすべての形式を、カラー共通フォーマットマッピングは以下の通りであるサポート
生データエンコーダ
NV12(yuv420sp)---> COLOR_FormatYUV420PackedSemiPlanar
NV21 ----> COLOR_FormatYUV420SemiPlanar
YV12(I420)----> COLOR_FormatYUV420Planar
コーデックコンフィグレーションが完了したら、エンコーダを起動するために、低レベルのnative_start()メソッドを呼び出すMediaCodecのstart()メソッドを呼び出すことができ、そして、出力の入力系列を開くために、低レベルの方法のByteBuffer [] getBuffers(入力)を呼び出しますバッファ。次のように()メソッドのソースコードを起動します。
public final void start() {
native_start();
synchronized(mBufferLock) {
cacheBuffers(true /* input */);
cacheBuffers(false /* input */);
}
}
(3)データ処理
MediaCodecコーデックは、2つのモード、すなわち同期同期をサポートし、非同期非同期、入出力コーデックデータが同期されていることを、いわゆる同期モード手段は、コーデック処理の出力が終了し、再び受信入力されます取引;非同期入出力コーデックが非同期データであり、コーデックが再び処理された入力データの出力データを受信する前に待機していません。このように、我々は以上であるので、ここでは、次の主要な同期コーデックを紹介します。私たちは、コーデックが開始されたときに、各コーデックは、入力および出力バッファのセットを持っていることを知っているが、バッファのみdequeueInputBuffer / dequeueOutputBuffer方法のMediaCodec入力と出力バッファによる承認を取得するために、一時的に使用することはできません、 IDは、これらのバッファを動作させるために戻りました。私たちは、公式、拡張分析により提供される次のコードを採用しました。
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();
上記のコードから、場合コーデック開始し、連続的なバッファを実装するために無限ループである(;;)ループのため入る入力バッファから取得データであるコーデックプールを含んでいます、バッファプールから、その後の良好なコーデック出力データ出力を取得しました。
コーデック入力バッファを取得し、データが書き込まれる●
最初、呼び出しMediaCodec dequeueInputBuffer(長いtimeoutUs)メソッドは、エンコーダバッファのセット入力からの入力バッファを取得し、キャッシュインデックスのインデックスを返す場合、およびインデックス= -1使用可能な命令の一時的なキャッシュ、timeoutUs = 0がdequeueInputBufferはすぐに戻ります。次いで、ローカル索引方法getBufferに渡されるコールMediaCodec getInputBuffer(int index)指定され、(真/ *入力* /インデックス) のByteBufferバッファを返し、そして得られたBufferMapへのByteBufferオブジェクトとそのインデックスを格納されています入力バッファの最後のリリースプロセスのためのオブジェクト、コーデックへ戻ります。getInputBuffer(int型のインデックス)元以下のように:
@Nullable
public ByteBuffer getInputBuffer(int index) {
ByteBuffer newBuffer = getBuffer(true /* input */, index);
synchronized(mBufferLock) {
invalidateByteBuffer(mCachedInputBuffers, index);
// mDequeuedInputBuffers是BufferMap的实例
mDequeuedInputBuffers.put(index, newBuffer);
}
return newBuffer;
}
入力バッファはバックコーデックに解放されている間、その後、入力バッファを得た後、データおよび用途にデータが処理queueInputBufferコーデックを提出します。queueInputBufferのソースコードは次のよう:
public final void queueInputBuffer(
int index,
int offset, int size, long presentationTimeUs, int flags)
throws CryptoException {
synchronized(mBufferLock) {
invalidateByteBuffer(mCachedInputBuffers, index);
// 移除输入缓存区
mDequeuedInputBuffers.remove(index);
}
try {
native_queueInputBuffer(
index, offset, size, presentationTimeUs, flags);
} catch (CryptoException | IllegalStateException e) {
revalidateByteBuffer(mCachedInputBuffers, index);
throw e;
}
}
インデックスは、コーデックのインデックス入力バッファは、バッファの位置を見つけるためにインデックスを介して行われている5つのパラメータを渡す必要があるメソッドを呼び出すことによって、上記のコード、queueInputBuffer native_queueInputBuffer主に低レベルから、オフセットが有効データでありますオフセットバッファと、バッファのpresentationTimeUsプレゼンテーションタイムスタンプ、典型的には0;サイズは、元の入力データのサイズが有効であるフラグ入力バッファフラグは通常BUFFER_FLAG_END_OF_STREAMに設定されています。
●コーデック取得した出力バッファ、データが読み出される
第一、及び上記dequeueInputBuffer getInputBuffer同様のバッファ入力を取得することにより、MediaCodec dequeueOutputBufferはまた、方法を提供し、ヘルプを私たちはgetOutputBufferコーデック出力バッファを得ること。しかし、違いはdequeueInputBufferで、dequeueOutputBufferもMediaCodec.BufferInfoオブジェクトを渡す必要があるということです。MediaCodec.BufferInfo MediaCodecは、出力バッファ領域にデータコーデック良いオフセット及びサイズを記録する内部クラスです。
public final static class BufferInfo {
public void set(
int newOffset, int newSize, long newTimeUs, @BufferFlag int newFlags) {
offset = newOffset;
size = newSize;
presentationTimeUs = newTimeUs;
flags = newFlags;
}
public int offset // 偏移量
public int size; // 缓存区有效数据大小
public long presentationTimeUs; // 显示时间戳
public int flags; // 缓存区标志
@NonNull
public BufferInfo dup() {
BufferInfo copy = new BufferInfo();
copy.set(offset, size, presentationTimeUs, flags);
return copy;
}
};
次いで、dequeueOutputBufferが理解源は、場合戻り値dequeueOutputBuffer> = 0、データ出力バッファが有効です。ローカルメソッド呼び出しがnative_dequeueOutputBuffer INFO_OUTPUT_BUFFERS_CHANGEDを返す場合、cacheBuffersメソッド呼び出しがmCachedOutputBuffersバッファ出力のセットを取得(のByteBuffer [])。これは、我々はコーデック出力バッファを取得する方法のgetOutputBuffersを(API21が代わりに使用getOutputBuffer(インデックス)の後に廃棄された)を使用する場合、それは戻り値MediaCodec場合、戻り値はdequeueOutputBuffer決定されたコールする必要があると説明しています。 INFO_OUTPUT_BUFFERS_CHANGED、あなたは再取得の集合出力バッファにする必要があります。また、ここでは、二つの追加の戻り値dequeueOutputBuffer:MediaCodec.INFO_TRY_AGAIN_LATER、MediaCodec.INFO_OUTPUT_FORMAT_CHANGED、前者取り出しコーデック出力バッファタイムアウト、コーデックデータが出力フォーマットを変更し、新たなデータの出力を使用して表しますフォーマット。したがって、我々はdequeueOutputBufferは、戻り値がINFO_OUTPUT_FORMAT_CHANGED、getOutputFormatのMediaCodecによる再設定された目標MediaFormtに必要であるかどうかを判断呼び出す必要があります。
public final int dequeueOutputBuffer(
@NonNull BufferInfo info, long timeoutUs) {
int res = native_dequeueOutputBuffer(info, timeoutUs);
synchronized(mBufferLock) {
if (res == INFO_OUTPUT_BUFFERS_CHANGED) {
// 将会调用getBuffers()底层方法
cacheBuffers(false /* input */);
} else if (res >= 0) {
validateOutputByteBuffer(mCachedOutputBuffers, res, info);
if (mHasSurface) {
mDequeuedOutputInfos.put(res, info.dup());
}
}
}
return res;
}
最後に、データが処理され、出力バッファは、呼び出しMediaCodec releaseOutputBuffer出力バッファによって解放され、コーデックに戻され、出力バッファはdequeueOutputBufferて次の使用可能になるまで使用されません。releaseOutputBuffer方法は、2つのパラメータを受信する:インデックスは、レンダリングインデックスは、インデックスバッファ出力である請求項を、レンダリング面はエンコーダが構成されている場合、それはtrueに設定されなければならない指定され表し、データ出力バッファは、表面に転写されます。ソースとして、次のとおりです。
public final void releaseOutputBuffer(int index, boolean render) {
BufferInfo info = null;
synchronized(mBufferLock) {
invalidateByteBuffer(mCachedOutputBuffers, index);
mDequeuedOutputBuffers.remove(index);
if (mHasSurface) {
info = mDequeuedOutputInfos.remove(index);
}
}
releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
}