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.
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
Du musst angemeldet sein, um einen Kommentar abzugeben.
Dank fadden ' s Hilfe habe ich eine proof-of-concept audio-encoder und video+audio encoder auf Github. Zusammenfassend:
Senden
AudioRecord
's Proben zu einemMediaCodec
+MediaMuxer
wrapper. Mit der system-Zeit beiaudioRecord.read(...)
funktioniert ausreichend gut als audio-Zeitstempel, sofern Sie Umfrage oft genug, um zu vermeiden Füllung bis AudioRecord den internen Puffer (zur Vermeidung von drift zwischen der Zeit, die Sie aufrufen, Lesen und die Zeit AudioRecord aufgenommenen samples). Schade, AudioRecord nicht direkt kommunizieren Zeitstempel...Beachten Sie, dass AudioRecord garantiert nur die Unterstützung für 16-bit-PCM-samples, obwohl
MediaCodec.queueInputBuffer
nimmt die Eingabe alsbyte[]
. Die übergabe einesbyte[]
zuaudioRecord.read(dataBuffer,...)
wirdtruncateteilen Sie die 16-bit-Proben in 8 bit für Sie.Fand ich, dass polling in dieser Art und Weise noch gelegentlich erzeugt eine
timestampUs XXX < lastTimestampUs XXX for Audio track
Fehler, so dass ich enthalten eine gewisse Logik zu verfolgen, diebufferInfo.presentationTimeUs
berichtet vonmediaCodec.dequeueOutputBuffer(bufferInfo, timeoutMs)
und passen Sie wenn nötig vor dem AufrufmediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo)
.Habe ich eine neue Frage zu diesem Thema mit mehr details hier: stackoverflow.com/questions/19361770/...
Die Proben nicht gekürzt. Abgeschnitten wäre jedes 16-bit-Rahmen ist verkürzt, in ein 8-bit-Rahmen, der nicht ist, was passiert, jedes 16-bit-frame ist aufgeteilt in zwei bytes, aber das ist wohl nur Semantik.
Ich gemischte audio-und video erfolgreich mit MediaMuxer und MediaCodec, und die mp4-video-Datei abgespielt werden kann, aber es ist etwas falsch. Zum Beispiel habe ich ein video aufgenommen für 12 Sekunden, wenn ich spielen Sie das video mit system-player, die player zeigen die video-Dauer 12 Sekunden, das ist richtig, aber Es nimmt den Spieler mit 10 Sekunden zu Ende spielen. Implementieren Sie die Mischung erfolgreich ohne Fehler? Wie hast du es machen?@dbro
Mein Ergebnis: github.com/kickflip/kickflip-android-sdk. Siehe AVRecorder.java, CameraEncoder.java, MicrophoneEncoder.java
InformationsquelleAutor dbro
Den code von oben beantworten https://stackoverflow.com/a/18966374/6463821 bietet auch
timestampUs XXX < lastTimestampUs XXX for Audio track
Fehler, weil, wenn Sie Lesen, von AudioRecord ist Puffer dann schneller benötigen, Dauer zwischen den generierten timstamps wird kleiner als die Reale Dauer zwischen audio-samples.Also meine Lösung für dieses Problem ist das generieren der ersten timstamp und jeder nächste Probe erhöhen timestamp nach der Dauer Ihrer Probe (hängt davon ab, bitrate, audio format, Kanal-config).
...
...
Lesen von AudioRecord sollte in einem separaten thread, und alle lese-Puffer aufgenommen werden sollen, Warteschlange, ohne zu warten, für die Codierung oder andere Aktionen mit Ihnen, um zu verhindern, dass Verlust von audio-samples.
Ach ja, ich vergaß, um anzugeben, array-Typ. Die Antwort schon aktualisiert, danke @AlexandruCircus!
InformationsquelleAutor Oleh Sokolov
Problem ist aufgetreten, weil Sie den Empfangspuffer ungeordnet :
Versuchen Sie, fügen Sie den folgenden test :
InformationsquelleAutor KRiadh