Android AudioRecord class - быстро запускает живой микрофон, настраивает функцию обратного вызова

Я хочу записать звук с микрофона и получить доступ к нему для возможного воспроизведения почти в режиме реального времени. Я не уверен, как использовать класс 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" упорядоченной и упорядоченной в другом месте? Кажется, этот код может читаться слишком часто или слишком редко, в зависимости от того, сколько времени занимает чтение и письмо.

Сайт doc также говорит, что класс 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 почти в режиме реального времени. Где я могу найти некоторые конкретные примеры?

Ответ 1

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

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, на которых я его пробовал.

Ответ 2

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

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

Ответ 3

Вот код, который вы должны использовать OnRecordPositionUpdateListener и Notification Period.

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

О detectAfterEvery:

Размер detectEvery должен быть достаточно большим, чтобы удерживать только то количество данных, которое вы хотите. Таким образом, для этого примера мы имеем частоту дискретизации 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);
        }

Ответ 4

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 мс.