Класс Android AudioRecord - быстрая обработка звука с живого микрофона, настройка функции обратного вызова - PullRequest
64 голосов
/ 24 декабря 2010

Я хочу записать звук с микрофона и получить доступ к нему для возможного воспроизведения практически в реальном времени.Я не уверен, как использовать класс Android AudioRecord для записи звука с микрофона и быстрого доступа к нему.

Для класса AudioRecord официальный сайт сообщает, что «приложение опрашивает объект AudioRecord во времени», а «размер заполняемого буфера определяет продолжительность записи перед перезапуском непрочитанных данных».,Позже предлагается использовать больший буфер при опросе реже.Они никогда не показывают пример в коде.

Один пример, который я видел в книге, использует класс AudioRecord для непрерывного чтения буфера, недавно заполненного звуком с живого микрофона, а затем приложение записывает эти данные в файл SD.Псевдокод выглядит примерно так:

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();

Каким образом этот код синхронизирует свое чтение с частотой записи, неясно - правильно ли секвенируется логическое значение «isRecording» в других местах?Кажется, этот код может либо читать слишком часто, либо слишком редко, в зависимости от того, сколько времени занимает чтение и запись.

Документ сайта также говорит, что класс AudioRecord имеет вложенный класс с именем OnRecordPositionUpdateListener, который определен как интерфейс.Информация предполагает, что каким-то образом вы указываете период, который вы хотите получать, уведомляя о ходе записи, и имя вашего обработчика событий, и ваш обработчик событий автоматически совершает вызов с указанной частотой.Я думаю, что структура в псевдокоде будет выглядеть примерно так:

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
)

Мне нужно найти какой-то конкретный код, который позволит мне захватывать и обрабатывать звук с микрофона с задержкой менее 500 мс.Android предлагает еще один класс под названием MediaRecorder, но он не поддерживает потоковую передачу, и я могу захотеть транслировать звук с живого микрофона через сеть Wi-Fi в режиме реального времени.Где я могу найти конкретные примеры?

Ответы [ 4 ]

33 голосов
/ 28 января 2011

После экспериментов с уведомлениями и множеством других методов я остановился на этом коде:

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

    }

Пока что он работает довольно надежно на полдюжине телефонов Android, на которых я его пробовал.

20 голосов
/ 29 января 2011

Интересно, могли бы вы объединить эти ответы следующим образом ...

Используйте setPositionNotificationPeriod (160) перед циклом while.Это должно вызывать обратный вызов каждый раз, когда читается 160 кадров.Вместо вызова процесса (буфера) внутри потока, который выполняет цикл чтения, вызовите процесс (буфер) из обратного вызова.Используйте переменную для отслеживания последнего прочитанного буфера, чтобы обработать правильный.Как и сейчас, вы блокируете чтение, а затем не читаете во время обработки.Я думаю, что было бы лучше разделить эти два.

11 голосов
/ 27 января 2012

Вот код, который вам необходим для использования OnRecordPositionUpdateListener и периода уведомления.

Я заметил, что на практике оно не отправляет уведомление последовательно в одно и то же точное время, я хочу, но оно достаточно близко.

О detectAfterEvery:

Размер Detevevery должен быть достаточно большим, чтобы вместить только тот объем данных, который вы хотите.Таким образом, для этого примера у нас есть частота выборки 44100 Гц, что означает, что мы хотим 44100 выборок в секунду.Установив setPositionNotificationPeriod равным 44100, код сообщает Android для обратного вызова после того, как он записал 44100 выборок, что составляет примерно 1 секунду.

Полный код здесь :

        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);
        }
2 голосов
/ 26 июля 2012
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();

}

Воспроизведение записанного звука с задержкой менее 100 мс.

...