tutorial example audiorecorder android audio callback recording android-audiorecord

example - Android: problema de la clase AudioRecord: nunca se llama a la devolución de llamada



android audiorecorder tutorial (4)

Mi aplicación Java para Android necesita grabar datos de audio en la memoria RAM y procesarlos. Es por eso que utilizo la clase "AudioRecord" y no la "MediaRecorder" (graba solo en el archivo).

Hasta ahora, utilicé un sondeo de bucle ocupado con "read ()" para los datos de audio. esto ha estado funcionando hasta ahora, pero bloquea demasiado la CPU. Entre dos encuestas, puse el hilo en modo de suspensión para evitar el uso de la CPU al 100%. Sin embargo, esta no es realmente una solución limpia, ya que el tiempo de suspensión no está garantizado y debe restar un tiempo de seguridad para no perder fragmentos de audio. Esto no es óptimo para la CPU. Necesito tantos ciclos de CPU libres como sea posible para un hilo de ejecución paralela.

Ahora implementé la grabación usando "OnRecordPositionUpdateListener". Esto parece muy prometedor y la forma correcta de hacerlo de acuerdo con los SDK Docs. Todo parece funcionar (abrir el dispositivo de audio, leer () los datos, etc.) pero nunca se llama al Listner.

¿Alguien sabe por qué?

Información: Estoy trabajando con un dispositivo real, no debajo del emulador. La grabación utilizando un bucle de ocupado funciona básicamente (sin embargo, no saturando). Solo el Callback Listener nunca se llama.

Aquí hay un fragmento de mi Sourcecode:

public class myApplication extends Activity { /* audio recording */ private static final int AUDIO_SAMPLE_FREQ = 16000; private static final int AUDIO_BUFFER_BYTESIZE = AUDIO_SAMPLE_FREQ * 2 * 3; // = 3000ms private static final int AUDIO_BUFFER_SAMPLEREAD_SIZE = AUDIO_SAMPLE_FREQ / 10 * 2; // = 200ms private short[] mAudioBuffer = null; // audio buffer private int mSamplesRead; // how many samples are recently read private AudioRecord mAudioRecorder; // Audio Recorder ... private OnRecordPositionUpdateListener mRecordListener = new OnRecordPositionUpdateListener() { public void onPeriodicNotification(AudioRecord recorder) { mSamplesRead = recorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE); if (mSamplesRead > 0) { // do something here... } } public void onMarkerReached(AudioRecord recorder) { Error("What? Hu!? Where am I?"); } }; ... public void onCreate(Bundle savedInstanceState) { try { mAudioRecorder = new AudioRecord( android.media.MediaRecorder.AudioSource.MIC, AUDIO_SAMPLE_FREQ, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, AUDIO_BUFFER_BYTESIZE); } catch (Exception e) { Error("Unable to init audio recording!"); } mAudioBuffer = new short[AUDIO_BUFFER_SAMPLEREAD_SIZE]; mAudioRecorder.setPositionNotificationPeriod(AUDIO_BUFFER_SAMPLEREAD_SIZE); mAudioRecorder.setRecordPositionUpdateListener(mRecordListener); mAudioRecorder.startRecording(); /* test if I can read anything at all... (and yes, this here works!) */ mSamplesRead = mAudioRecorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE); } }


Ahora implementé la grabación usando "OnRecordPositionUpdateListener". Esto parece muy prometedor y la forma correcta de hacerlo de acuerdo con los SDK Docs. Todo parece funcionar (abrir el dispositivo de audio, leer () los datos, etc.) pero nunca se llama al Listner.

¿Alguien sabe por qué?

Descubrí que OnRecordPositionUpdateListener se ignora hasta que realiza su primer .read() .

En otras palabras, descubrí que si configuraba todo según los documentos, mi Listener nunca recibía llamadas. Sin embargo, si primero llamé a .read() justo después de hacer mi inicial .start() entonces el Listener sería llamado, siempre que hiciera un .read() cada vez que se llamara al Listener.

En otras palabras, casi parece que el evento Listener solo sirve para una vez por .read() o algo así.

También encontré que si pedía que se leyeran muestras menos de buffSize / 2, no se llamaría al Listener. Así que parece que el oyente solo se llama DESPUÉS de un .read() de al menos la mitad del tamaño del búfer. Para seguir utilizando la devolución de llamada del Listener, se debe llamar a la lectura cada vez que se ejecuta el detector. (En otras palabras, ponga la llamada para leer en el código de Listener).

Sin embargo, el oyente parece ser llamado en un momento en que los datos aún no están listos, lo que causa bloqueo.

Además, si su período de notificación o su tiempo son mayores que la mitad de su bufferSize, nunca serán llamados, parece.

ACTUALIZAR:

A medida que continúo profundizando, he encontrado que la devolución de llamada parece SÓLO ser llamada cuando termina un .read() ......!

No sé si eso es un error o una característica. Mi primer pensamiento sería que quiero una devolución de llamada cuando sea el momento de leer. Pero tal vez los desarrolladores de Android tuvieron la idea al revés, donde simplemente pondrían un while(1){xxxx.read(...)} en un hilo por sí mismos, y para evitar tener que hacer un seguimiento de cada vez que read () finaliza, la devolución de llamada puede indicarle cuándo finalizó una lectura.

Oh. Tal vez me di cuenta. Y creo que otros han señalado esto antes que yo aquí, pero no se hundió: la devolución de llamada de posición o período debe operar en bytes que ya se han leído ...

Creo que tal vez estoy atascado con el uso de hilos.

En este estilo, uno tendría un hilo continuamente llamando a read () tan pronto como regresara, ya que read está bloqueando y espera pacientemente a que regresen suficientes datos.

Entonces, de forma independiente, la devolución de llamada llamaría a su función especificada cada x número de muestras.


Aquí está mi código usado para encontrar ruido promedio. Tenga en cuenta que se basa en las notificaciones del oyente, por lo que se ahorrará batería del dispositivo. Definitivamente se basa en los ejemplos anteriores. Esos ejemplos me ahorraron mucho tiempo, gracias.

private AudioRecord recorder; private boolean recorderStarted; private Thread recordingThread; private int bufferSize = 800; private short[][] buffers = new short[256][bufferSize]; private int[] averages = new int[256]; private int lastBuffer = 0; protected void startListenToMicrophone() { if (!recorderStarted) { recordingThread = new Thread() { @Override public void run() { int minBufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT); recorder = new AudioRecord(AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 10); recorder.setPositionNotificationPeriod(bufferSize); recorder.setRecordPositionUpdateListener(new OnRecordPositionUpdateListener() { @Override public void onPeriodicNotification(AudioRecord recorder) { short[] buffer = buffers[++lastBuffer % buffers.length]; recorder.read(buffer, 0, bufferSize); long sum = 0; for (int i = 0; i < bufferSize; ++i) { sum += Math.abs(buffer[i]); } averages[lastBuffer % buffers.length] = (int) (sum / bufferSize); lastBuffer = lastBuffer % buffers.length; } @Override public void onMarkerReached(AudioRecord recorder) { } }); recorder.startRecording(); short[] buffer = buffers[lastBuffer % buffers.length]; recorder.read(buffer, 0, bufferSize); while (true) { if (isInterrupted()) { recorder.stop(); recorder.release(); break; } } } }; recordingThread.start(); recorderStarted = true; } } private void stopListenToMicrophone() { if (recorderStarted) { if (recordingThread != null && recordingThread.isAlive() && !recordingThread.isInterrupted()) { recordingThread.interrupt(); } recorderStarted = false; } }


Creo que el problema es que todavía necesita hacer el ciclo de lectura. Si configura las devoluciones de llamada, se activarán cuando haya leído la cantidad de fotogramas que especifique para las devoluciones de llamada. Pero aún necesitas hacer las lecturas. Intenté esto y las devoluciones de llamada se llaman bien. La configuración de un marcador causa una devolución de llamada cuando se ha leído ese número de fotogramas desde el inicio de la grabación. En otras palabras, puede configurar el marcador en el futuro, después de muchas de sus lecturas, y luego se disparará. Puede establecer el período en un número mayor de fotogramas y esa devolución de llamada se activará cada vez que se haya leído esa cantidad de fotogramas. Creo que lo hacen para que pueda hacer un procesamiento de bajo nivel de los datos brutos en un ciclo cerrado, y de vez en cuando su devolución de llamada puede hacer un procesamiento de nivel de resumen. Puede usar el marcador para que sea más fácil decidir cuándo dejar de grabar (en lugar de contar en el ciclo de lectura).


Para aquellos que están grabando audio a través de un servicio intencionado, también pueden experimentar este problema de devolución de llamada. He estado trabajando en este tema últimamente y se me ocurrió una solución simple en la que llama a su método de grabación en un hilo separado. Esto debería invocar el método PeriodicNotification durante la grabación sin ningún problema.

Algo como esto:

public class TalkService extends IntentService { ... @Override protected void onHandleIntent(Intent intent) { Context context = getApplicationContext(); tRecord = new Thread(new recordAudio()); tRecord.start(); ... while (tRecord.isAlive()) { if (getIsDone()) { if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) { socketConnection(context, host, port); } } } } ... class recordAudio implements Runnable { public void run() { try { OutputStream osFile = new FileOutputStream(file); BufferedOutputStream bosFile = new BufferedOutputStream(osFile); DataOutputStream dosFile = new DataOutputStream(bosFile); aRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelInMode, encodingMode, bufferSize); data = new short[bufferSize]; aRecorder.setPositionNotificationPeriod(sampleRate); aRecorder .setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() { int count = 1; @Override public void onPeriodicNotification( AudioRecord recorder) { Log.e(WiFiDirect.TAG, "Period notf: " + count++); if (getRecording() == false) { aRecorder.stop(); aRecorder.release(); setIsDone(true); Log.d(WiFiDirect.TAG, "Recorder stopped and released prematurely"); } } @Override public void onMarkerReached(AudioRecord recorder) { // TODO Auto-generated method stub } }); aRecorder.startRecording(); Log.d(WiFiDirect.TAG, "start Recording"); aRecorder.read(data, 0, bufferSize); for (int i = 0; i < data.length; i++) { dosFile.writeShort(data[i]); } if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { aRecorder.stop(); aRecorder.release(); setIsDone(true); Log.d(WiFiDirect.TAG, "Recorder stopped and released"); } } catch (Exception e) { // TODO: handle exception } } }