PCM -> AAC (codificador) -> PCM (decodificador) en tiempo real con la optimización correcta

Estoy tratando de implementar

AudioRecord (MIC) -> PCM -> AAC Encoder AAC -> PCM Decode -> AudioTrack?? (SPEAKER) 

con MediaCodec en Android 4.1+ (API16).

En primer lugar, implementé PCM -> AAC Encoder por MediaCodec éxito (pero no estoy seguro de que se haya optimizado correctamente) como se MediaCodec continuación.

 private boolean setEncoder(int rate) { encoder = MediaCodec.createEncoderByType("audio/mp4a-latm"); MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm"); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100); format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);//AAC-HE 64kbps format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE); encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); return true; } 

ENTRADA: PCM Bitrate = 44100 (Hz) x 16 (bit) x 1 (Monoral) = 705600 bit / s

SALIDA: Velocidad de bits AAC-HE = 64 x 1024 (bit) = 65536 bit / s

Entonces, el tamaño de los datos se comprime aproximadamente x11 , y confirmé que esto funcionaba al observar un registro

  • AudioRecoder: 4096 bytes leídos
  • AudioEncoder: 369 bytes codificados

el tamaño de los datos se comprime aproximadamente x11 , hasta ahora todo bien.

Ahora, tengo un servidor UDP para recibir los datos codificados, luego decodificarlos.

El perfil del decodificador se establece de la siguiente manera:

 private boolean setDecoder(int rate) { decoder = MediaCodec.createDecoderByType("audio/mp4a-latm"); MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm"); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100); format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);//AAC-HE 64kbps format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE); decoder.configure(format, null, null, 0); return true; } 

Dado que el tamaño del búfer del paquete UDPserver es 1024

  • UDPserver: 1024 bytes recibidos

y dado que estos son los datos AAC comprimidos, esperaría que el tamaño de deencoding sea

aproximadamente 1024 x11 , sin embargo, el resultado real es

  • AudioDecoder: 8192 bytes decodificados

Es aproximadamente x8 , y siento algo mal.

El código del decodificador es el siguiente:

  IOudpPlayer = new Thread(new Runnable() { public void run() { SocketAddress sockAddress; String address; int len = 1024; byte[] buffer2 = new byte[len]; DatagramPacket packet; byte[] data; ByteBuffer[] inputBuffers; ByteBuffer[] outputBuffers; ByteBuffer inputBuffer; ByteBuffer outputBuffer; MediaCodec.BufferInfo bufferInfo; int inputBufferIndex; int outputBufferIndex; byte[] outData; try { decoder.start(); isPlaying = true; while (isPlaying) { try { packet = new DatagramPacket(buffer2, len); ds.receive(packet); sockAddress = packet.getSocketAddress(); address = sockAddress.toString(); Log.d("UDP Receiver"," received !!! from " + address); data = new byte[packet.getLength()]; System.arraycopy(packet.getData(), packet.getOffset(), data, 0, packet.getLength()); Log.d("UDP Receiver", data.length + " bytes received"); //=========== inputBuffers = decoder.getInputBuffers(); outputBuffers = decoder.getOutputBuffers(); inputBufferIndex = decoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(data); decoder.queueInputBuffer(inputBufferIndex, 0, data.length, 0, 0); } bufferInfo = new MediaCodec.BufferInfo(); outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { outputBuffer = outputBuffers[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); outData = new byte[bufferInfo.size]; outputBuffer.get(outData); Log.d("AudioDecoder", outData.length + " bytes decoded"); decoder.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0); } //=========== } catch (IOException e) { } } decoder.stop(); } catch (Exception e) { } } }); 

el código completo:

https://gist.github.com/kenokabe/9029256

también necesita permiso:

    

Un miembro de Fadden que trabaja para Google me dijo

Parece que no estoy estableciendo posición y límite en el buffer de salida.

He leído VP8 Encoding Nexus 5 devuelve Empty / 0-Frames , pero no estoy seguro de cómo implementarlo correctamente.


ACTUALIZACIÓN: entendí de algún modo dónde modificar para

Parece que no estoy estableciendo posición y límite en el buffer de salida.

, entonces agregue 2 líneas dentro del ciclo while de Encoder y Decoder de la siguiente manera:

  outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); 

https://gist.github.com/kenokabe/9029256/revisions

Sin embargo, el resultado es el mismo.

y ahora, creo, los errores: W/SoftAAC2﹕ AAC decoder returned error 16388, substituting silence. indica que este decodificador falla completamente desde el principio. Una vez más, the data is not seekable problema the data is not seekable . Buscar en transmisiones de AAC en Android Es muy decepcionante que el decodificador de AAC no pueda manejar los datos de transmisión de esta manera, sino solo agregando un encabezado.


ACTUALIZACIÓN2: el receptor UDP hizo mal, así que modificó

https://gist.github.com/kenokabe/9029256

Ahora, el error

W/SoftAAC2﹕ AAC decoder returned error 16388, substituting silence. desapareció !!

Por lo tanto, indica que el decodificador funciona sin un error, al menos,

sin embargo, este es el registro de 1 ciclo:

 D/AudioRecoder﹕ 4096 bytes read D/AudioEncoder﹕ 360 bytes encoded D/UDP Receiver﹕ received !!! from /127.0.0.1:39000 D/UDP Receiver﹕ 360 bytes received D/AudioDecoder﹕ 8192 bytes decoded 

PCM (4096) -> AAC codificado (360) -> UDP-AAC (360) -> (supuestamente) PCM (8192)

El resultado final es aproximadamente 2 veces el tamaño del PCM original, algo todavía está mal.


Entonces mi pregunta aquí sería

  1. ¿Se puede optimizar correctamente mi código de muestra para que funcione correctamente?

  2. ¿Es una forma correcta de utilizar AudioTrack API para reproducir los datos sin formato PCM descodificados sobre la marcha, y puede mostrarme la forma correcta de hacerlo? Se aprecia un código de ejemplo.

Gracias.

PD. Mis objectives de proyecto en Android4.1 + (API16), he leído que las cosas son más fáciles en API18 (Andeoid 4.3+), pero por razones de compatibilidad obvias, desafortunadamente, tengo que omitir MediaMuxer, etc. aquí …

Después de las pruebas, esto es lo que surgió al modificar tu código:

  package com.example.app; import android.app.Activity; import android.media.AudioManager; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Bundle; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaCodec; import android.media.MediaRecorder.AudioSource; import android.util.Log; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; public class MainActivity extends Activity { private AudioRecord recorder; private AudioTrack player; private MediaCodec encoder; private MediaCodec decoder; private short audioFormat = AudioFormat.ENCODING_PCM_16BIT; private short channelConfig = AudioFormat.CHANNEL_IN_MONO; private int bufferSize; private boolean isRecording; private boolean isPlaying; private Thread IOrecorder; private Thread IOudpPlayer; private DatagramSocket ds; private final int localPort = 39000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); IOrecorder = new Thread(new Runnable() { public void run() { int read; byte[] buffer1 = new byte[bufferSize]; ByteBuffer[] inputBuffers; ByteBuffer[] outputBuffers; ByteBuffer inputBuffer; ByteBuffer outputBuffer; MediaCodec.BufferInfo bufferInfo; int inputBufferIndex; int outputBufferIndex; byte[] outData; DatagramPacket packet; try { encoder.start(); recorder.startRecording(); isRecording = true; while (isRecording) { read = recorder.read(buffer1, 0, bufferSize); // Log.d("AudioRecoder", read + " bytes read"); //------------------------ inputBuffers = encoder.getInputBuffers(); outputBuffers = encoder.getOutputBuffers(); inputBufferIndex = encoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(buffer1); encoder.queueInputBuffer(inputBufferIndex, 0, buffer1.length, 0, 0); } bufferInfo = new MediaCodec.BufferInfo(); outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { outputBuffer = outputBuffers[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); outData = new byte[bufferInfo.size]; outputBuffer.get(outData); // Log.d("AudioEncoder ", outData.length + " bytes encoded"); //------------- packet = new DatagramPacket(outData, outData.length, InetAddress.getByName("127.0.0.1"), localPort); ds.send(packet); //------------ encoder.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0); } // ----------------------; } encoder.stop(); recorder.stop(); } catch (Exception e) { e.printStackTrace(); } } }); IOudpPlayer = new Thread(new Runnable() { public void run() { SocketAddress sockAddress; String address; int len = 2048 byte[] buffer2 = new byte[len]; DatagramPacket packet; byte[] data; ByteBuffer[] inputBuffers; ByteBuffer[] outputBuffers; ByteBuffer inputBuffer; ByteBuffer outputBuffer; MediaCodec.BufferInfo bufferInfo; int inputBufferIndex; int outputBufferIndex; byte[] outData; try { player.play(); decoder.start(); isPlaying = true; while (isPlaying) { try { packet = new DatagramPacket(buffer2, len); ds.receive(packet); sockAddress = packet.getSocketAddress(); address = sockAddress.toString(); // Log.d("UDP Receiver"," received !!! from " + address); data = new byte[packet.getLength()]; System.arraycopy(packet.getData(), packet.getOffset(), data, 0, packet.getLength()); // Log.d("UDP Receiver", data.length + " bytes received"); //=========== inputBuffers = decoder.getInputBuffers(); outputBuffers = decoder.getOutputBuffers(); inputBufferIndex = decoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(data); decoder.queueInputBuffer(inputBufferIndex, 0, data.length, 0, 0); } bufferInfo = new MediaCodec.BufferInfo(); outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { outputBuffer = outputBuffers[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); outData = new byte[bufferInfo.size]; outputBuffer.get(outData); // Log.d("AudioDecoder", outData.length + " bytes decoded"); player.write(outData, 0, outData.length); decoder.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0.. 

Self Answer, aquí está mi mejor esfuerzo hasta el momento

 package com.example.app; import android.app.Activity; import android.media.AudioManager; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.os.Bundle; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.AudioTrack; import android.media.MediaCodec; import android.media.MediaRecorder.AudioSource; import android.util.Log; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; public class MainActivity extends Activity { private AudioRecord recorder; private AudioTrack player; private MediaCodec encoder; private MediaCodec decoder; private short audioFormat = AudioFormat.ENCODING_PCM_16BIT; private short channelConfig = AudioFormat.CHANNEL_IN_MONO; private int bufferSize; private boolean isRecording; private boolean isPlaying; private Thread IOrecorder; private Thread IOudpPlayer; private DatagramSocket ds; private final int localPort = 39000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); IOrecorder = new Thread(new Runnable() { public void run() { int read; byte[] buffer1 = new byte[bufferSize]; ByteBuffer[] inputBuffers; ByteBuffer[] outputBuffers; ByteBuffer inputBuffer; ByteBuffer outputBuffer; MediaCodec.BufferInfo bufferInfo; int inputBufferIndex; int outputBufferIndex; byte[] outData; DatagramPacket packet; try { encoder.start(); recorder.startRecording(); isRecording = true; while (isRecording) { read = recorder.read(buffer1, 0, bufferSize); // Log.d("AudioRecoder", read + " bytes read"); //------------------------ inputBuffers = encoder.getInputBuffers(); outputBuffers = encoder.getOutputBuffers(); inputBufferIndex = encoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(buffer1); encoder.queueInputBuffer(inputBufferIndex, 0, buffer1.length, 0, 0); } bufferInfo = new MediaCodec.BufferInfo(); outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { outputBuffer = outputBuffers[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); outData = new byte[bufferInfo.size]; outputBuffer.get(outData); // Log.d("AudioEncoder", outData.length + " bytes encoded"); //------------- packet = new DatagramPacket(outData, outData.length, InetAddress.getByName("127.0.0.1"), localPort); ds.send(packet); //------------ encoder.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0); } // ----------------------; } encoder.stop(); recorder.stop(); } catch (Exception e) { e.printStackTrace(); } } }); IOudpPlayer = new Thread(new Runnable() { public void run() { SocketAddress sockAddress; String address; int len = 1024; byte[] buffer2 = new byte[len]; DatagramPacket packet; byte[] data; ByteBuffer[] inputBuffers; ByteBuffer[] outputBuffers; ByteBuffer inputBuffer; ByteBuffer outputBuffer; MediaCodec.BufferInfo bufferInfo; int inputBufferIndex; int outputBufferIndex; byte[] outData; try { player.play(); decoder.start(); isPlaying = true; while (isPlaying) { try { packet = new DatagramPacket(buffer2, len); ds.receive(packet); sockAddress = packet.getSocketAddress(); address = sockAddress.toString(); // Log.d("UDP Receiver"," received !!! from " + address); data = new byte[packet.getLength()]; System.arraycopy(packet.getData(), packet.getOffset(), data, 0, packet.getLength()); // Log.d("UDP Receiver", data.length + " bytes received"); //=========== inputBuffers = decoder.getInputBuffers(); outputBuffers = decoder.getOutputBuffers(); inputBufferIndex = decoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(data); decoder.queueInputBuffer(inputBufferIndex, 0, data.length, 0, 0); } bufferInfo = new MediaCodec.BufferInfo(); outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { outputBuffer = outputBuffers[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); outData = new byte[bufferInfo.size]; outputBuffer.get(outData); // Log.d("AudioDecoder", outData.length + " bytes decoded"); player.write(outData, 0, outData.length); decoder.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 0); } //=========== } catch (IOException e) { } } decoder.stop(); player.stop(); } catch (Exception e) { } } }); //=========================================================== int rate = findAudioRecord(); if (rate != -1) { Log.v("=========media ", "ready: " + rate); Log.v("=========media channel ", "ready: " + channelConfig); boolean encoderReady = setEncoder(rate); Log.v("=========encoder ", "ready: " + encoderReady); if (encoderReady) { boolean decoderReady = setDecoder(rate); Log.v("=========decoder ", "ready: " + decoderReady); if (decoderReady) { Log.d("=======bufferSize========", "" + bufferSize); try { setPlayer(rate); ds = new DatagramSocket(localPort); IOudpPlayer.start(); IOrecorder.start(); } catch (SocketException e) { e.printStackTrace(); } } } } } protected void onDestroy() { recorder.release(); player.release(); encoder.release(); decoder.release(); } /* protected void onResume() { // isRecording = true; } protected void onPause() { isRecording = false; } */ private int findAudioRecord() { for (int rate : new int[]{44100}) { try { Log.v("===========Attempting rate ", rate + "Hz, bits: " + audioFormat + ", channel: " + channelConfig); bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat); if (bufferSize != AudioRecord.ERROR_BAD_VALUE) { // check if we can instantiate and have a success recorder = new AudioRecord(AudioSource.MIC, rate, channelConfig, audioFormat, bufferSize); if (recorder.getState() == AudioRecord.STATE_INITIALIZED) { Log.v("===========final rate ", rate + "Hz, bits: " + audioFormat + ", channel: " + channelConfig); return rate; } } } catch (Exception e) { Log.v("error", "" + rate); } } return -1; } private boolean setEncoder(int rate) { encoder = MediaCodec.createEncoderByType("audio/mp4a-latm"); MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm"); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate); format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);//AAC-HE 64kbps format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE); encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); return true; } private boolean setDecoder(int rate) { decoder = MediaCodec.createDecoderByType("audio/mp4a-latm"); MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm"); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate); format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);//AAC-HE 64kbps format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE); decoder.configure(format, null, null, 0); return true; } private boolean setPlayer(int rate) { int bufferSizePlayer = AudioTrack.getMinBufferSize(rate, AudioFormat.CHANNEL_OUT_MONO, audioFormat); Log.d("====buffer Size player ", String.valueOf(bufferSizePlayer)); player= new AudioTrack(AudioManager.STREAM_MUSIC, rate, AudioFormat.CHANNEL_OUT_MONO, audioFormat, bufferSizePlayer, AudioTrack.MODE_STREAM); if (player.getState() == AudioTrack.STATE_INITIALIZED) { return true; } else { return false; } } } 

He intentado con el código anterior y no funcionó correctamente. Recibí mucho silencio inyectado en la salida decodificada. El problema no era establecer el valor “csd” adecuado para el decodificador.

Entonces, si ve “silencio” en el registro o error de lanzamiento del decodificador, asegúrese de haber agregado lo siguiente a su formato de decodificador de medios.

 int profile = 2; //AAC LC int freqIdx = 11; //8KHz int chanCfg = 1; //Mono ByteBuffer csd = ByteBuffer.allocate(2); csd.put(0, (byte) (profile << 3 | freqIdx >> 1)); csd.put(1, (byte)((freqIdx & 0x01) << 7 | chanCfg << 3)); mediaFormat.setByteBuffer("csd-0", csd); 

Su código de red está combinando datos. Obtuvo 369 bytes de datos comprimidos, pero en el extremo receptor terminó con 1024 bytes. Esos 1024 bytes consisten en dos cuadros enteros y uno parcial. Los dos marcos completos se decodifican nuevamente a 4096 bytes, para un total de 8192 bytes que usted vio. El cuadro parcial restante probablemente se decodificará una vez que envíe suficientes datos al decodificador, pero generalmente debe enviar solo cuadros enteros al decodificador.

Además, MediaCodec.dequeueOutputBuffer() no solo devuelve índices de buffer (positivos), sino también códigos de estado (negativos). Uno de los códigos posibles es MediaCodec.INFO_OUTPUT_FORMAT_CHANGED , que indica que debe llamar a MediaCodec.getOutputFormat() para obtener el formato de los datos de audio. Es posible que vea el estéreo de salida del códec incluso si la entrada fue mono. El código que publicó simplemente sale del bucle cuando recibe uno de estos códigos de estado.

He probado con tu souce hay algunos puntos

  1. La tasa de bits es un número natural de K, pero no de la computadora K. 64k = 64000, pero no 64 * 1024

  2. No se recomienda escribir un código largo que comparta algunas variables. A. Subproceso de codificador y subproceso de decodificador separados en 2 clases independientes. B. El emisor y el receptor comparten el DatagramSocket, no es bueno.

  3. Enumerate Audio Format necesita más valores. es decir, las tasas de muestreo se deben recoger de: 8000, 11025, 22050, 44100

D / AudioRecoder: 4096 bytes leer D / AudioEncoder: 360 bytes codificados D / UDP Receiver: recibido !!! desde /127.0.0.1:39000 Receptor D / UDP: 360 bytes recibidos D / AudioDecoder: 8192 bytes decodificados

Esto se debe a que el decodificador acc siempre decodifica canales estéreo, incluso si los datos codificados son MONO. entonces, si su lado de encoding está configurado en canales estéreo, será como:

D / AudioRecoder: 8192 bytes leer D / AudioEncoder: 360 bytes codificados D / UDP Receiver: ¡¡¡recibido !!! desde /127.0.0.1:39000 Receptor D / UDP: 360 bytes recibidos D / AudioDecoder: 8192 bytes decodificados

Intereting Posts