1

MediaCodec を使用して、(カメラまたはデコーダーによって) フレームをビデオにエンコードしようとしています。dequeueOutputBuffer() によってエンコーダー出力を処理するとき、戻りインデックス = MediaCodec.INFO_OUTPUT_FORMAT_CHANGED を受け取ることが期待されるため、getOutputFormat() を呼び出して、現在使用されている ffmpeg マルチプレクサーの入力としてエンコーダー出力形式を取得できます。

Android バージョン 4.1 ~ 4.3 でいくつかのパッド/電話デバイスをテストしました。それらはすべて、少なくとも 1 つのハードウェア ビデオ AVC エンコーダーを備えており、テストで使用されます。バージョン 4.3 のデバイスでは、エンコーダーはエンコードされたデータを期待どおりに書き込む前に MediaCodec.INFO_OUTPUT_FORMAT_CHANGED を提供し、getOutputFormat() から返された出力形式をマルチプレクサーで正しく使用できます。4.2.2 以前のデバイスでは、エンコーダーは MediaCodec.INFO_OUTPUT_FORMAT_CHANGED を提供しませんが、エンコードされたエレメンタリー ストリームを出力できますが、マルチプレクサは正確な出力形式を認識できません。

次の質問をしたいです。

  1. エンコーダーの動作 (エンコードされたデータを出力する前に MediaCodec.INFO_OUTPUT_FORMAT_CHANGED を与えるかどうか) は、Android API レベルまたは個々のデバイスのチップに依存しますか?
  2. MediaCodec.INFO_OUTPUT_FORMAT_CHANGED が表示される前にエンコーダーがデータを書き込む場合、エンコードされたデータの出力形式を取得する方法はありますか?
  3. エンコーダーは、エンコードされたデータの前に、デバイスのコーデック構成データ (フラグ MediaCodec.BUFFER_FLAG_CODEC_CONFIG を使用) を引き続き出力します。主にデコーダーの構成に使用されますが、コーデック構成データから出力形式を導き出すことはできますか?

出力形式を取得するためにこれらのソリューションを試しましたが、失敗しました:

  • エンコード プロセス全体で頻繁に getOutputFormat() を呼び出します。ただし、それらはすべて、MediaCodec.INFO_OUTPUT_FORMAT_CHANGED を表示せずに IllegalStateException をスローします。
  • 例のように、最初の MediaFormat の使用を使用して、最初にエンコーダーを構成します。

    m_init_encode_format = MediaFormat.createVideoFormat(m_encode_video_mime, m_frame_width, m_frame_height);
    int encode_bit_rate = 3000000;
    int encode_frame_rate = 15;
    int encode_iframe_interval = 2;
    
    m_init_encode_format.setInteger(MediaFormat.KEY_COLOR_FORMAT, m_encode_color_format);
    m_init_encode_format.setInteger(MediaFormat.KEY_BIT_RATE, encode_bit_rate);
    m_init_encode_format.setInteger(MediaFormat.KEY_FRAME_RATE, encode_frame_rate);
    m_init_encode_format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, encode_iframe_interval);
    
    m_encoder = MediaCodec.createByCodecName(m_video_encoder_codec_info.getName());
    m_encoder.configure(m_init_encode_format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    
    // Assume m_init_encode_format is the output format of the encoder
    

    ただし、エンコーダーの出力形式がまだ最初のものから「変更」されているため、失敗します。

エンコーダーの動作を理解するのを手伝ってください。必要な MediaCodec.INFO_OUTPUT_FORMAT_CHANGED が欠落している場合、出力形式を照会する解決策があれば教えてください。


出力形式とコーデック構成データを比較すると、欠落しているフィールドは csd-0、csd-1、および値 = 1869968451 の「what」フィールドです。 (「what」フィールドはわかりません。定数であり、必須ではありません. 誰かがその意味について教えてもらえますか?)

コーデック構成データを csd-1 フィールド (最後の 8 バイト) および csd-0 フィールド (残りのバイト) として解析すると、マルチプレクサは正しく動作し、すべてのテスト デバイスで再生可能なビデオを出力できるようです。(しかし、私は質問したい: この 8 バイトの仮定は正しいですか、それともデータを解析するためのより信頼できる方法はありますか?)

しかし、もう一度 Android MediaCodec でビデオをデコードすると、dequeueOutputBuffer() で取得される BufferInfo.presentationTimeUs の値が、デコードされたフレームのほとんどで 0 になるという別の問題が発生しました。最後の数フレームだけが正しい時間です。MediaExtractor.getSampleTime() で取得したサンプル時間は正しく、エンコーダー/マルチプレクサーに設定した値とまったく同じですが、デコードされたフレーム時間は異なります。この問題は、4.2.2 以前のデバイスでのみ発生します。

フレーム時間が正しくないのは奇妙ですが、ビデオはデバイスで正しい速度で再生できます。(私がテストした 4.2.2 以前のほとんどのデバイスには、ビデオ AVC デコーダーが 1 つしかありません。) プレゼンテーション時間に影響を与える可能性のある他のフィールドを設定する必要がありますか?

4

3 に答える 3

2

クラスMediaCodecの導入に対応するために、Android 4.3 でエンコーダの動作が変更されました。MediaMuxerAndroid 4.3 では、常にINFO_OUTPUT_FORMAT_CHANGEDエンコーダーから受信します。以前のリリースでは、そうではありません。(関連する FAQ エントリを更新しました。)

のエンコーダーにクエリを実行する方法はありませんMediaFormat

私は ffmpeg ベースのマルチプレクサを使用したことがないので、必要な情報がわかりません。csd-0 / csd-1 キーを探している場合は、CODEC_CONFIGパケットからそれらを抽出できます (SPS / PPS 値を解析して別のキーに配置する必要があると思います)。4.3 デバイスで の内容を調べると、MediaFormat不足しているフィールドがわかります。

于 2014-03-11T15:09:25.130 に答える
0

ビデオのffmpegマルチプレクサを正しく初期化するには、次を使用します:

int outputBufferIndex = videoCodec.dequeueOutputBuffer(bufferInfo, -1);
if (MediaCodec.BUFFER_FLAG_CODEC_CONFIG == bufferInfo.flags) {
    ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
    headerData = new byte[bufferInfo.size];
    outputBuffer.get(headerData);

    // jni call
    WriteVideoHeader(headerData, headerData.length);

    videoCodec.releaseOutputBuffer(outputBufferIndex, false);   
}

jni では、次のようなものを使用します。

jint Java_com_an_FileWriterEx_WriteVideoHeader(JNIEnv * env, jobject this, jbyteArray data, jint datasize)
{
    jboolean isCopy;
    jbyte* rawjBytes = (*env)->GetByteArrayElements(env, data, &isCopy);    
    WriteVideoHeaderInternal(env, m_pFormatCtx, m_pVideoStream, rawjBytes, datasize);   
    (*env)->ReleaseByteArrayElements(env, data, rawjBytes, 0);
    return 0;
}

jint WriteVideoHeaderInternal(JNIEnv * env, AVFormatContext* pFormatCtx, AVStream*     pVideoStream, jbyte* data, jint datasize)
{
  jboolean bNoError = JNI_TRUE;

  jbyte* pExtDataBuffer = av_malloc(datasize);
  if(!pExtDataBuffer) 
  {
    LOGI("av alloc error\n");
    bNoError = JNI_FALSE;
  }

  if (bNoError)
  {
    memcpy(pExtDataBuffer, data, datasize * sizeof(jbyte));

    pVideoStream->codec->extradata = pExtDataBuffer;
    pVideoStream->codec->extradata_size = datasize;
  } 
}
于 2014-03-12T07:58:42.777 に答える