私はこれをAndroid 4.1(アップグレードされたAsus Transformerタブレットを使用)で動作させようとしています。 以前の質問に対するアレックスの応答 のおかげで、私はすでにできました生のH.264データをファイルに書き込むには、このファイルはffplay -f h264
、およびフレームレートに関するすべての情報が失われているようです(非常に高速な再生)。また、色空間が正しくないように見えます(エンコーダー側のカメラのデフォルトを使用するATM)。
public class AvcEncoder {
private MediaCodec mediaCodec;
private BufferedOutputStream outputStream;
public AvcEncoder() {
File f = new File(Environment.getExternalStorageDirectory(), "Download/video_encoded.264");
touch (f);
try {
outputStream = new BufferedOutputStream(new FileOutputStream(f));
Log.i("AvcEncoder", "outputStream initialized");
} catch (Exception e){
e.printStackTrace();
}
mediaCodec = MediaCodec.createEncoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
}
public void close() {
try {
mediaCodec.stop();
mediaCodec.release();
outputStream.flush();
outputStream.close();
} catch (Exception e){
e.printStackTrace();
}
}
// called from Camera.setPreviewCallbackWithBuffer(...) in other class
public void offerEncoder(byte[] input) {
try {
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
outputStream.write(outData, 0, outData.length);
Log.i("AvcEncoder", outData.length + " bytes written");
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
エンコーダタイプを「video/mp4」に変更すると、フレームレートの問題は明らかに解決されますが、主な目標はストリーミングサービスを作成することであるため、これは良い解決策ではありません。
SPSとPPS NALU'sを考慮してAlexのコードの一部を削除したことは承知していますが、その情報はoutData
からも来ているので、これは必要ないことを望んでいました。エンコーダがこれを正しくフォーマットすると想定しましたが、そうでない場合は、ファイル/ストリームにさまざまな種類のNALUをどのように配置する必要がありますか?
だから、有効なH.264ストリームを作成するためにここで何が欠けていますか?また、カメラの色空間とエンコーダーの色空間を一致させるには、どの設定を使用する必要がありますか?
これは、Android/MediaCodecのトピックというよりも、H.264関連の質問だと感じています。または、まだMediaCodec APIを正しく使用していませんか?
前もって感謝します。
高速再生-フレームレートの問題については、ここで行う必要はありません。これはストリーミングソリューションであるため、相手側には事前にフレームレートまたは各フレームのタイムスタンプを通知する必要があります。これらは両方ともエレメンタリストリームの一部ではありません。事前に決定されたフレームレートが選択されるか、sdpまたはそのようなものを渡すか、rtspなどの既存のプロトコルを使用します。 2番目の場合、タイムスタンプはrtpのような形式で送信されるストリームの一部です。次に、クライアントはRTPストリームを遅延させ、それをbaclで再生する必要があります。これがエレメンタリーストリーミングの仕組みです。 [固定レートエンコーダーがある場合はフレームレートを修正するか、タイムスタンプを指定します]
ローカルPCの再生は、fpsを認識しないため、高速になります。入力前にfpsパラメーターを指定することにより、例えば
ffplay -fps 30 in.264
pCで再生を制御できます。
ファイルが再生可能でないことについて:SPSとPPSがありますか。また、NALヘッダーを有効にする必要があります-別館b形式。 Androidについてはあまり知りませんが、これはh.264エレメンタリストリームがコンテナ内になく、後でダンプして再生する必要がある場合に再生可能にするための要件です。 Androidデフォルトはmp4ですが、デフォルトのannexbヘッダーはオフになるので、有効にするスイッチがあるかもしれません。フレームごとにデータを取得する場合は、自分で追加してください。
色の形式については、デフォルトが機能するはずです。設定しないでください。そうでない場合は、422 PlanarまたはUVYV/VYUYインターリーブ形式を試してください。通常、カメラはそれらの1つです。 (必ずしも必要ではありませんが、これらは私がより頻繁に遭遇したものかもしれません)。
Android 4.3(API 18)は簡単なソリューションを提供します。 MediaCodec
クラスはSurfacesからの入力を受け入れるようになりました。つまり、カメラのSurfaceプレビューをエンコーダーに接続し、すべての奇妙なYUV形式の問題を回避できます。
また、生のH.264ストリームを.mp4ファイルに変換する(オプションでオーディオストリームにブレンドする)新しい MediaMuxerクラス があります。
これを正確に行う例については、 CameraToMpegTest source を参照してください。 (また、OpenGL ESフラグメントシェーダーを使用して、録画中にビデオの簡単な編集を実行することも示しています。)
プレビューカラースペースをYV12に設定している場合は、次のようにカラースペースを変換できます。
public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final byte[] output, final int width, final int height) {
/*
* COLOR_TI_FormatYUV420PackedSemiPlanar is NV12
* We convert by putting the corresponding U and V bytes together (interleaved).
*/
final int frameSize = width * height;
final int qFrameSize = frameSize/4;
System.arraycopy(input, 0, output, 0, frameSize); // Y
for (int i = 0; i < qFrameSize; i++) {
output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U)
output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V)
}
return output;
}
または
public static byte[] YV12toYUV420Planar(byte[] input, byte[] output, int width, int height) {
/*
* COLOR_FormatYUV420Planar is I420 which is like YV12, but with U and V reversed.
* So we just have to reverse U and V.
*/
final int frameSize = width * height;
final int qFrameSize = frameSize/4;
System.arraycopy(input, 0, output, 0, frameSize); // Y
System.arraycopy(input, frameSize, output, frameSize + qFrameSize, qFrameSize); // Cr (V)
System.arraycopy(input, frameSize + qFrameSize, output, frameSize, qFrameSize); // Cb (U)
return output;
}
サポートされているビットマップ形式についてMediaCodecを照会し、プレビューを照会できます。問題は、一部のMediaCodecsは、プレビューから取得できない独自の圧縮YUV形式のみをサポートしていることです。特に2130706688 = 0x7F000100 = COLOR_TI_FormatYUV420PackedSemiPlanar。プレビューのデフォルト形式は17 = NV21 = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar = YCbCr 420 Semi Planarです
別のピクセル形式を明示的に要求しなかった場合、カメラプレビューバッファーは、 NV21 として知られるYUV 420形式で到着し、そのために COLOR_FormatYCrYCb MediaCodecと同等です。
残念ながら、このページの他の回答にあるように、お使いのデバイスでAVCエンコーダーがこの形式をサポートしているという保証はありません。 NV21をサポートしない奇妙なデバイスがいくつか存在することに注意してください。しかし、API 16にアップグレードできるものは知りません(したがって、MediaCodecがあります)。
Googleのドキュメントでは、 YV12 平面YUVは、API> = 12を備えたすべてのデバイスのカメラプレビュー形式としてサポートする必要があると主張しています。 COLOR_FormatYUV420Planar コードスニペットで使用します)。
Update:Andrew Cottrellが私に思い出させたように、YV12はまだCOLOR_FormatYUV420Planarになるためにクロマスワッピングが必要です。