Clase Android AudioRecord: procesa el audio del micrófono en vivo rápidamente, configura la función de callback

Quiero grabar audio desde el micrófono y acceder a él para una posible reproducción casi en tiempo real. No estoy seguro de cómo usar la clase Android AudioRecord para grabar algunos micrófonos de audio y acceder rápidamente a ellos.

Para la clase AudioRecord, el sitio oficial dice ‘la aplicación sondea el objeto AudioRecord a tiempo’, y ‘el tamaño del buffer que se está rellenando determina el tiempo de duración de la grabación antes de sobrepasar los datos no leídos’. Más tarde se sugiere que se use un buffer más grande al sondear con menos frecuencia. Nunca muestran un ejemplo en el código.

Un ejemplo que he visto en un libro usa la clase AudioRecord para leer continuamente un búfer recién poblado con audio de micrófono en vivo, y luego la aplicación escribe estos datos en un archivo SD. El pseudo-código se ve algo así como –

set up AudioRecord object with buffer size and recording format info set up a file and an output stream myAudioRecord.startRecording(); while(isRecording) { // myBuffer is being filled with fresh audio read audio data into myBuffer send contents of myBuffer to SD file } myAudioRecord.stop(); 

No está claro cómo este código sincroniza su lectura con la velocidad de grabación: ¿el booleano “isRecording” está secuenciado correctamente y en otro lugar? Parece que este código podría leerse con demasiada frecuencia o con poca frecuencia, según el tiempo que tarden en leer y escribir.

El documento del sitio también dice que la clase AudioRecord tiene una clase anidada llamada OnRecordPositionUpdateListener que se define como una interfaz. La información sugiere que de alguna manera, especifique el período que desea para que se le notifique el progreso de la grabación, y el nombre de su controlador de eventos, y se realiza una llamada automáticamente a su controlador de eventos en la frecuencia especificada. Creo que la estructura, en pseudo-código sería algo así como …

 set target of period update message = myListener set period to be about every 250 ms other code myListener() { if(record button was recently tapped) handle message that another 250 ms of fresh audio is available ie, read it and send it somewhere ) 

Necesito encontrar un código específico que me permita capturar y procesar el audio del micro con un retraso de menos de 500 ms. Android ofrece otra clase llamada MediaRecorder, pero no es compatible con la transmisión, y es posible que desee transmitir audio de micrófono en vivo a través de una red Wi-Fi casi en tiempo real. ¿Dónde puedo encontrar algunos ejemplos específicos?

Después de experimentar con las notificaciones y un montón de otras técnicas, decidí este código:

 private class AudioIn extends Thread { private boolean stopped = false; private AudioIn() { start(); } @Override public void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); AudioRecord recorder = null; short[][] buffers = new short[256][160]; int ix = 0; try { // ... initialise int N = AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT); recorder = new AudioRecord(AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, N*10); recorder.startRecording(); // ... loop while(!stopped) { short[] buffer = buffers[ix++ % buffers.length]; N = recorder.read(buffer,0,buffer.length); //process is what you will do with the data...not defined here process(buffer); } } catch(Throwable x) { Log.w(TAG,"Error reading voice audio",x); } finally { close(); } } private void close() { stopped = true; } } 

Hasta el momento, funciona bastante bien en la media docena de teléfonos Android en los que lo probé.

Me pregunto si podrías combinar estas respuestas de la siguiente manera …

Use setPositionNotificationPeriod (160) antes del ciclo while. Esto debería hacer que se llame a la callback cada vez que se lean 160 fotogtwigs. En lugar de llamar al proceso (buffer) dentro del hilo que está haciendo el ciclo de lectura, llame al proceso (buffer) desde la callback. Utilice una variable para realizar un seguimiento del último búfer de lectura para que procese el correcto. Tal como está ahora, bloqueas la lectura y luego no estás leyendo mientras procesas. Creo que sería mejor separar esos dos.

Aquí está el código que necesita para usar el OnRecordPositionUpdateListener y el Período de notificación.

Noté que en la práctica no envía la notificación de forma consistente en el mismo momento exacto, quiero, pero está lo suficientemente cerca.

Acerca de detectAfterEvery :

El tamaño de detectEvery debe ser lo suficientemente grande como para contener solo la cantidad de datos que desee. Por lo tanto, para este ejemplo, tenemos una frecuencia de muestreo de 44100 Hz, lo que significa que queremos 44100 muestras por segundo. Al configurar setPositionNotificationPeriod para que sea 44100, el código le dice a Android que setPositionNotificationPeriod callback después de haber grabado 44100 muestras, que es aproximadamente cada 1 segundo.

El código completo está aquí :

  final int sampleRate = 44100; int bufferSize = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); //aim for 1 second int detectAfterEvery = (int)((float)sampleRate * 1.0f); if (detectAfterEvery > bufferSize) { Log.w(TAG, "Increasing buffer to hold enough samples " + detectAfterEvery + " was: " + bufferSize); bufferSize = detectAfterEvery; } recorder = new AudioRecord(AudioSource.MIC, sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); recorder.setPositionNotificationPeriod(detectAfterEvery); final short[] audioData = new short[bufferSize]; final int finalBufferSize = bufferSize; OnRecordPositionUpdateListener positionUpdater = new OnRecordPositionUpdateListener() { @Override public void onPeriodicNotification(AudioRecord recorder) { Date d = new Date(); //it should be every 1 second, but it is actually, "about every 1 second" //like 1073, 919, 1001, 1185, 1204 milliseconds of time. Log.d(TAG, "periodic notification " + d.toLocaleString() + " mili " + d.getTime()); recorder.read(audioData, 0, finalBufferSize); //do something amazing with audio data } @Override public void onMarkerReached(AudioRecord recorder) { Log.d(TAG, "marker reached"); } }; recorder.setRecordPositionUpdateListener(positionUpdater); Log.d(TAG, "start recording, bufferSize: " + bufferSize); recorder.startRecording(); //remember to still have a read loop otherwise the listener won't trigger while (continueRecording) { recorder.read(audioData, 0, bufferSize); } 
 private int freq =8000; private AudioRecord audioRecord = null; private Thread Rthread = null; private AudioManager audioManager=null; private AudioTrack audioTrack=null; byte[] buffer = new byte[freq]; //call this method at start button protected void Start() { loopback(); } protected void loopback() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); final int bufferSize = AudioRecord.getMinBufferSize(freq, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, freq, AudioFormat.CHANNEL_CONFIGURATION_MONO, MediaRecorder.AudioEncoder.AMR_NB, bufferSize); audioTrack = new AudioTrack(AudioManager.ROUTE_HEADSET, freq, AudioFormat.CHANNEL_CONFIGURATION_MONO, MediaRecorder.AudioEncoder.AMR_NB, bufferSize, AudioTrack.MODE_STREAM); audioTrack.setPlaybackRate(freq); final byte[] buffer = new byte[bufferSize]; audioRecord.startRecording(); Log.i(LOG_TAG, "Audio Recording started"); audioTrack.play(); Log.i(LOG_TAG, "Audio Playing started"); Rthread = new Thread(new Runnable() { public void run() { while (true) { try { audioRecord.read(buffer, 0, bufferSize); audioTrack.write(buffer, 0, buffer.length); } catch (Throwable t) { Log.e("Error", "Read write failed"); t.printStackTrace(); } } } }); Rthread.start(); } 

Reproduce el audio grabado con menos de 100 ms de retraso.