web-dev-qa-db-ja.com

Android MediaCodecを使用してAAC ADTSエレメンタリーストリームを生成する方法

私がやろうとしていること:AndroidのMediaCodecを使用して、未加工のPCMオーディオサンプルを未加工のAACファイルにエンコードします。

私が抱えている問題:FFMPEGを使用して生成された未加工のAACファイルをM4Aコンテナーにパックすると、FFMPEGはファイル内のコーデックパラメーターが欠落していると文句を言います。

詳細:

出力AACファイルを生成するオーディオエンコーダーのMediaCodecサンプルコードが見つからないため、ビデオエンコーダーをオーディオエンコーダーに変更しようとしました。元のコードは次のとおりです: source_code

私はこのようにオーディオエンコーダを設定しました:

    mEncoderFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", (int)mAudioSampleRate, 2);

    // redundant?
    mEncoderFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
    mEncoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, 
                      MediaCodecInfo.CodecProfileLevel.AACObjectELD);
    mEncoderFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, kSampleRates);
    mEncoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates);
    mEncoderFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
    testEncoderWithFormat("audio/mp4a-latm", mEncoderFormat);

    try {
        codec.configure(
                mEncoderFormat,
                null /* surface */,
                null /* crypto */,
                MediaCodec.CONFIGURE_FLAG_ENCODE);
    } catch (IllegalStateException e) {
        Log.e(TAG, "codec '" + componentName + "' failed configuration.");
        return;
    }
    Log.d(TAG, "  testEncoder configured with format = " + format);

次に、エンコーダーにフレームあたり10ms相当のPCMサンプルを供給します。エンコーダーは各フレームを受け取り、ビットストリームのフレームを生成し、ビットストリームをFileOutputStreamに書き込みます。ループは入力ファイルの終わりまで続きます。

コードは最後まで実行されます。 「adb pull」を実行して、生成されたAACファイルをデバイスからPCに取得し、FFMPEGを使用してそれを読み取ります。以下は、コマンドとエラーFFMPEGが吐き出すエラーです。

$ ffmpeg -f aac -i BlessedNoColor_nexus7_api18.aac
ffmpeg version N-45739-g04bf2e7 Copyright (c) 2000-2012 the FFmpeg developers
  built on Oct 20 2012 00:20:36 with gcc 4.7.2 (GCC)
  configuration: --enable-gpl --enable-version3 --disable-pthreads --enable-runt
ime-cpudetect --enable-avisynth --enable-bzlib --enable-frei0r --enable-libass -
-enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libfreetype --enab
le-libgsm --enable-libmp3lame --enable-libnut --enable-libopenjpeg --enable-libo
pus --enable-librtmp --enable-libschroedinger --enable-libspeex --enable-libtheo
ra --enable-libutvideo --enable-libvo-aacenc --enable-libvo-amrwbenc --enable-li
bvorbis --enable-libvpx --enable-libx264 --enable-libxavs --enable-libxvid --ena
ble-zlib
  libavutil      51. 76.100 / 51. 76.100
  libavcodec     54. 67.100 / 54. 67.100
  libavformat    54. 33.100 / 54. 33.100
  libavdevice    54.  3.100 / 54.  3.100
  libavfilter     3. 19.103 /  3. 19.103
  libswscale      2.  1.101 /  2.  1.101
  libswresample   0. 16.100 /  0. 16.100
  libpostproc    52.  1.100 / 52.  1.100
[aac @ 00000000002efae0] channel element 2.0 is not allocated
[aac @ 00000000003cf520] decoding for stream 0 failed
[aac @ 00000000003cf520] Could not find codec parameters for stream 0 (Audio: aac, 0 channels, s16): unspecified sample rate
Consider increasing the value for the 'analyzeduration' and 'probesize' options
[aac @ 00000000003cf520] Estimating duration from bitrate, this may be inaccurate

BlessedNoColor_nexus7_api18.aac: could not find codec parameters

私の質問:

  1. Codec.start()を呼び出す前にエンコーダーを設定しました。生成されたAACファイルにコーデックパラメータがないのはなぜですか?
  2. 元のビデオコーデックの例では、パラメーター「csd-0」がエンコーダーからデコーダーに渡されますが、ビットストリームファイルに明示的に書き込まれません。それらを明示的にAACファイルに書き込む必要がありますか?
  3. 入力PCMサンプルをフレームあたり10ミリ秒に分割します。これにより、完全な出力パケットが生成されるとは限りません。入力フレームごとに、エンコーダーが出力するものをファイルに書き込むだけです。それは心配の種ですか?

どんな助けも深く感謝されます。ここで私がやろうとしていることを実行するサンプルプロジェクトがあれば、すばらしいでしょう。私のソースコードがあなたを助けることができるなら、私はそれを投稿します。片付けが必要です。ありがとう!

編集:タイトルを「MediaCodec欠落コーデックパラメータによって生成された基本AACファイル」から「Android MediaCodec」

20
hubeir

AndroidデバイスとWindowsホストコンピュータの両方で再生できるAACファイルを最終的に生成しました。他の人の役に立つことを願って、ここに私のソリューションを投稿しています。

まず、Android MediaCodecエンコーダーがエレメンタリーAACストリームを生成するという私の以前の仮定は正確ではありませんでした。MediaCodecエンコーダーは生のAACストリームを生成します。そのため、ファイルを再生できませんでした。生のAACストリーム [〜#〜] adts [〜#〜] ストリームなどの再生可能な形式に変換する必要があります。新しい理解を反映させるために、この投稿のタイトルを変更しました。 別の投稿 似たような質問をして、素晴らしい答えが得られましたが、初心者は必ずしも簡単な説明を理解していない可能性があります。その投稿を初めて読んだときは、あまり理解できませんでした。

したがって、メディアプレーヤーで再生できるAACビットストリームを生成するために、私は彼の最初のコメントでfaddenによって提供されたEncoderTestの例から始めましたが、元のコードを変更して、出力フレーム(アクセスユニット)ごとにADTSヘッダーを追加しました。結果のストリームをファイルに書き込むには(元のコードの248〜267行目を次のコードスニペットに置き換えます):

if (index >= 0) {
    int outBitsSize   = info.size;
    int outPacketSize = outBitsSize + 7;    // 7 is ADTS size
    ByteBuffer outBuf = codecOutputBuffers[index];

    outBuf.position(info.offset);
    outBuf.limit(info.offset + outBitsSize);
    try {
        byte[] data = new byte[outPacketSize];  //space for ADTS header included
        addADTStoPacket(data, outPacketSize);
        outBuf.get(data, 7, outBitsSize);
        outBuf.position(info.offset);
        mFileStream.write(data, 0, outPacketSize);  //open FileOutputStream beforehand
    } catch (IOException e) {
        Log.e(TAG, "failed writing bitstream data to file");
        e.printStackTrace();
    }

    numBytesDequeued += info.size;

    outBuf.clear();
    codec.releaseOutputBuffer(index, false /* render */);
    Log.d(TAG, "  dequeued " + outBitsSize + " bytes of output data.");
    Log.d(TAG, "  wrote " + outPacketSize + " bytes into output file.");
}
else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
}
else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    codecOutputBuffers = codec.getOutputBuffers();
}

ループの外で、関数addADTStoPacketを次のように定義しました。

/**
 *  Add ADTS header at the beginning of each and every AAC packet.
 *  This is needed as MediaCodec encoder generates a packet of raw
 *  AAC data.
 *
 *  Note the packetLen must count in the ADTS header itself.
 **/
private void addADTStoPacket(byte[] packet, int packetLen) {
    int profile = 2;  //AAC LC
                      //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
    int freqIdx = 4;  //44.1KHz
    int chanCfg = 2;  //CPE

    // fill in ADTS data
    packet[0] = (byte)0xFF;
    packet[1] = (byte)0xF9;
    packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
    packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
    packet[4] = (byte)((packetLen&0x7FF) >> 3);
    packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
    packet[6] = (byte)0xFC;
}

AAC ADTSストリームの生成を停止する方法を制御するコードも追加しましたが、それはアプリケーション固有なので、ここでは詳しく説明しません。これらすべての変更により、生成されたAACファイルはAndroidデバイス、私のWindows PCで再生でき、ffmpegはそれらに満足しています。

40
hubeir