16

Android フレームワークの例を変更して、MediaCodec によって生成された基本的な AAC ストリームをスタンドアロンの .mp4 ファイルにパッケージ化しています。MediaMuxerインスタンスによって生成された 1 つの AAC トラックを含む単一のインスタンスを使用していMediaCodecます。

ただし、次の呼び出しで常にエラーメッセージが表示されますmMediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo)

E/MPEG4Writer﹕timestampUs 0 < lastTimestampUs XXXXX for Audio track

生の入力データをキューに入れるときmCodec.queueInputBuffer(...)、フレームワークの例に従ってタイムスタンプ値として 0 を指定します (単調に増加するタイムスタンプ値を使用しても同じ結果が得られました。これで生のカメラ フレームを h264/mp​​4 ファイルにエンコードすることに成功しました)同じ方法)。

完全なソースを確認してください

最も関連性の高いスニペット:

private static void testEncoder(String componentName, MediaFormat format, Context c) {
    int trackIndex = 0;
    boolean mMuxerStarted = false;
    File f = FileUtils.createTempFileInRootAppStorage(c, "aac_test_" + new Date().getTime() + ".mp4");
    MediaCodec codec = MediaCodec.createByCodecName(componentName);

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

    }

    codec.start();

    try {
        mMediaMuxer = new MediaMuxer(f.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
    } catch (IOException ioe) {
        throw new RuntimeException("MediaMuxer creation failed", ioe);
    }

    ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
    ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();

    int numBytesSubmitted = 0;
    boolean doneSubmittingInput = false;
    int numBytesDequeued = 0;

    while (true) {
        int index;

        if (!doneSubmittingInput) {
            index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);

            if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
                if (numBytesSubmitted >= kNumInputBytes) {
                    Log.i(TAG, "queueing EOS to inputBuffer");
                    codec.queueInputBuffer(
                            index,
                            0 /* offset */,
                            0 /* size */,
                            0 /* timeUs */,
                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);

                    if (VERBOSE) {
                        Log.d(TAG, "queued input EOS.");
                    }

                    doneSubmittingInput = true;
                } else {
                    int size = queueInputBuffer(
                            codec, codecInputBuffers, index);

                    numBytesSubmitted += size;

                    if (VERBOSE) {
                        Log.d(TAG, "queued " + size + " bytes of input data.");
                    }
                }
            }
        }

        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);

        if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
        } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat newFormat = codec.getOutputFormat();
            trackIndex = mMediaMuxer.addTrack(newFormat);
            mMediaMuxer.start();
            mMuxerStarted = true;
        } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            codecOutputBuffers = codec.getOutputBuffers();
        } else {
            // Write to muxer
            ByteBuffer encodedData = codecOutputBuffers[index];
            if (encodedData == null) {
                throw new RuntimeException("encoderOutputBuffer " + index +
                        " was null");
            }

            if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                // The codec config data was pulled out and fed to the muxer when we got
                // the INFO_OUTPUT_FORMAT_CHANGED status.  Ignore it.
                if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
                info.size = 0;
            }

            if (info.size != 0) {
                if (!mMuxerStarted) {
                    throw new RuntimeException("muxer hasn't started");
                }

                // adjust the ByteBuffer values to match BufferInfo (not needed?)
                encodedData.position(info.offset);
                encodedData.limit(info.offset + info.size);

                mMediaMuxer.writeSampleData(trackIndex, encodedData, info);
                if (VERBOSE) Log.d(TAG, "sent " + info.size + " audio bytes to muxer with pts " + info.presentationTimeUs);
            }

            codec.releaseOutputBuffer(index, false);

            // End write to muxer
            numBytesDequeued += info.size;

            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                if (VERBOSE) {
                    Log.d(TAG, "dequeued output EOS.");
                }
                break;
            }

            if (VERBOSE) {
                Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
            }
        }
    }

    if (VERBOSE) {
        Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
                + "dequeued " + numBytesDequeued + " bytes.");
    }

    int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
    int inBitrate = sampleRate * channelCount * 16;  // bit/sec
    int outBitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);

    float desiredRatio = (float)outBitrate / (float)inBitrate;
    float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted;

    if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) {
        Log.w(TAG, "desiredRatio = " + desiredRatio
                + ", actualRatio = " + actualRatio);
    }


    codec.release();
    mMediaMuxer.stop();
    mMediaMuxer.release();
    codec = null;
}

更新:内にあると思われる根本的な症状を見つけましたMediaCodec。:

に送信presentationTimeUs=1000しますが、電話してからqueueInputBuffer(...)受信します。fadden は、この動作に関連する有益なコメントを残しました。info.presentationTimeUs= 33219MediaCodec.dequeueOutputBuffer(info, timeoutUs)

4

3 に答える 3

7

fadden の助けのおかげで、Github で概念実証のオーディオ エンコーダービデオ + オーディオ エンコーダーを入手できました。要約すれば:

AudioRecordのサンプルをMediaCodec+MediaMuxerラッパーに送信します。AudioRecord の内部バッファがいっぱいにならないように頻繁にポーリングする場合 (read を呼び出した時間と AudioRecord がサンプルを記録した時間とのずれを避けるため)にシステム時間を使用することはaudioRecord.read(...)、オーディオ タイムスタンプとして十分に機能します。残念ながら、AudioRecord はタイムスタンプを直接通信しません...

// Setup AudioRecord
while (isRecording) {
    audioPresentationTimeNs = System.nanoTime();
    audioRecord.read(dataBuffer, 0, samplesPerFrame);
    hwEncoder.offerAudioEncoder(dataBuffer.clone(), audioPresentationTimeNs);
}

AudioRecordは16 ビット PCM サンプルのサポートのみを保証することに注意してください。toを渡すと、16 ビットのサンプルが 8 ビットに切り捨てられます。MediaCodec.queueInputBufferbyte[]byte[]audioRecord.read(dataBuffer,...)

この方法でのポーリングでもエラーが発生することがあることがわかったので、によって報告されたtimestampUs XXX < lastTimestampUs XXX for Audio trackを追跡し、必要に応じて を呼び出す前に調整するためのロジックを含めました。bufferInfo.presentationTimeUsmediaCodec.dequeueOutputBuffer(bufferInfo, timeoutMs)mediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo)

于 2013-09-23T18:35:30.343 に答える