Muxing-AAC-audio mit Android MediaCodec und MediaMuxer

Ich bin ändern Android-Framework Beispiel Paket elementar-AAC-streams produziert von MediaCodec in ein standalone .mp4-Datei. Ich bin mit einem einzigen MediaMuxer Instanz, die einen AAC-track erzeugt durch einen MediaCodec Instanz.

Allerdings habe ich immer irgendwann eine Fehlermeldung, die auf einen Aufruf mMediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo):

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

Wenn ich die Warteschlange raw-input-Daten in mCodec.queueInputBuffer(...) ich 0 als der timestamp-Wert pro Rahmen-Beispiel (ich habe auch versucht, mit Hilfe monoton steigende timestamp-Werte mit dem gleichen Ergebnis. Ich habe erfolgreich codierte raw-Kamera-frames auf h264/mp4-Dateien mit der gleichen Methode).

Schauen Sie sich die vollständige Quelle

Meisten relevanten Ausschnitt:

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;
}

Update: ich habe ein root symptom ich denke, dass liegt in MediaCodec.:

Sende ich presentationTimeUs=1000 zu queueInputBuffer(...) erhalten aber info.presentationTimeUs= 33219 nach dem Aufruf MediaCodec.dequeueOutputBuffer(info, timeoutUs). fadden Links einen hilfreichen Kommentar zu diesem Verhalten.

Klingt wie MediaMuxer ist immer null und nicht-null-Zeitstempel. Haben Sie versucht, die Protokollierung der Inhalt info bei jedem writeSampleData aufrufen, um zu überprüfen, dass es die Werte, die Sie erwarten?
Ich protokolliert, der output und in der Tat, bevor der Fehler ausgelöst wird info enthält eine nicht-null -presentationTimeUs. Wie kann dieser Wert Verschieden von dem, was zur Verfügung gestellt queueInputBuffer(...)?
Ich weiß es nicht. Auch der Wert angezeigt, um einen festen offset vom vorherigen Wert, D. H. ist es der gleiche Wert jedes mal, aber wenn Sie passieren einen Konstanten, von null verschiedenen Wert für den Zeitstempel ändert es?
Ja, das unerklärliche timestamp unterscheidet sich immer von der ständigen timestamp, die ich durch einen festen Wert: 23219.
Beste Vermutung: der encoder ist etwas zu tun mit der Ausgabe -- vielleicht Aufspaltung einer Eingabe-Paket in zwei output-Pakete --, die es erfordert, zu synthetisieren, die einen Zeitstempel. Es nimmt den Zeitstempel der start des Pakets und fügt einen Wert basierend auf der bit-rate und Anzahl der bytes. Wenn Sie das erzeugen von Zeitstempeln, die einigermaßen korrekte Darstellung der Zeiten sollte man es nicht sehen, gehen Sie rückwärts, wenn die "in-zwischen" timestamp generiert wird.

InformationsquelleAutor dbro | 2013-09-17

Schreibe einen Kommentar