Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


Muxing audio AAC con MediaCodec de Android y MediaMuxer

Estoy modificando un ejemplo de Framework Android para empaquetar los flujos AAC elementales producidos por MediaCodec en un archivo .mp4 independiente. Estoy utilizando una sola instancia de MediaMuxer contiene una pista AAC generada por una instancia de MediaCodec .

Sin embargo siempre obtienes un mensaje de error en una llamada a mMediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo) :

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

Cuando hago cola de los datos de entrada sin procesar en mCodec.queueInputBuffer(...) Proporciono 0 como el valor de marca de tiempo por el ejemplo del marco de trabajo (también he intentado usar valores de timestamp monotonically creciente con el mismo resultado. Frames a archivos h264 / mp4 con este mismo método).

Echa un vistazo a la fuente completa

Snippet más relevante:

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

Actualización: He encontrado un síntoma raíz que creo que se encuentra dentro de MediaCodec .

Puedo enviar presentationTimeUs=1000 a queueInputBuffer(...) pero recibir info.presentationTimeUs= 33219 después de llamar a MediaCodec.dequeueOutputBuffer(info, timeoutUs) . Fadden dejó un comentario útil relacionado con este comportamiento.

3 Solutions collect form web for “Muxing audio AAC con MediaCodec de Android y MediaMuxer”

Gracias a la ayuda de fadden tengo un codificador de audio de prueba de concepto y un codificador de video + audio en Github. En resumen:

Envíe las muestras de AudioRecord a un contenedor MediaCodec + MediaMuxer . Utilizar la hora del sistema en audioRecord.read(...) funciona suficientemente bien como una marca de tiempo de audio, siempre que se realice sondeos con suficiente frecuencia para evitar llenar el búfer interno de AudioRecord (para evitar el desplazamiento entre el tiempo de llamada y el tiempo que AudioRecord grabó las muestras ). Demasiado malo AudioRecord no comunica directamente timestamps …

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

Tenga en cuenta que AudioRecord sólo garantiza el soporte para muestras PCM de 16 bits , aunque MediaCodec.queueInputBuffer toma la entrada como byte[] . Pasar un byte[] a audioRecord.read(dataBuffer,...) truncará dividir las muestras de 16 bits en 8 bits para usted.

He encontrado que la encuesta de esta manera todavía ocasionalmente generado un timestampUs XXX < lastTimestampUs XXX for Audio track error de timestampUs XXX < lastTimestampUs XXX for Audio track , por lo que bufferInfo.presentationTimeUs algunos lógica para realizar un seguimiento de la bufferInfo.presentationTimeUs informado por mediaCodec.dequeueOutputBuffer(bufferInfo, timeoutMs) y ajustar si es necesario antes de llamar mediaMuxer.writeSampleData(trackIndex, encodedData, bufferInfo) .

El código de la respuesta anterior https://stackoverflow.com/a/18966374/6463821 también proporciona timestampUs XXX < lastTimestampUs XXX for Audio track error de timestampUs XXX < lastTimestampUs XXX for Audio track , ya que si usted lee desde el buffer de AudioRecord más rápido entonces necesita, la duración entre timstamps generados será menor que Duración real entre muestras de audio.

Así que mi solución para este problema es generar primero timstamp y cada muestra siguiente aumentar timestamp por duración de su muestra ( depende de la tasa de bits, formato de audio, configuración de canal) .

 BUFFER_DURATION_US = 1_000_000 * (ARR_SIZE / AUDIO_CHANNELS) / SAMPLE_AUDIO_RATE_IN_HZ; 

 long firstPresentationTimeUs = System.nanoTime() / 1000; 

 audioRecord.read(shortBuffer, OFFSET, ARR_SIZE); long presentationTimeUs = count++ * BUFFER_DURATION + firstPresentationTimeUs; 

La lectura de AudioRecord debe estar en un subproceso separado y todos los búferes de lectura deben agregarse a la cola sin esperar a que la codificación o cualquier otra acción con ellos, para evitar la pérdida de muestras de audio.

 worker = new Thread() { @Override public void run() { try { AudioFrameReader reader = new AudioFrameReader(audioRecord); while (!isInterrupted()) { Thread.sleep(10); addToQueue( reader .read()); } } catch (InterruptedException e) { Log.w(TAG, "run: ", e); } } }; 

Problema ocurrido porque recibe buffers desordenadamente: Intente agregar la siguiente prueba:

 if(lastAudioPresentationTime == -1) { lastAudioPresentationTime = bufferInfo.presentationTimeUs; } else if (lastAudioPresentationTime < bufferInfo.presentationTimeUs) { lastAudioPresentationTime = bufferInfo.presentationTimeUs; } if ((bufferInfo.size != 0) && (lastAudioPresentationTime <= bufferInfo.presentationTimeUs)) { if (!mMuxerStarted) { throw new RuntimeException("muxer hasn't started"); } // adjust the ByteBuffer values to match BufferInfo (not needed?) encodedData.position(bufferInfo.offset); encodedData.limit(bufferInfo.offset + bufferInfo.size); mMuxer.writeSampleData(trackIndex.index, encodedData, bufferInfo); } encoder.releaseOutputBuffer(encoderStatus, false); 
FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.