Android: класс AudioRecord Проблема: обратный вызов никогда не вызывается - PullRequest
21 голосов
/ 15 февраля 2010

Моему Java-приложению Android необходимо записать аудиоданные в ОЗУ и обработать их. Вот почему я использую класс «AudioRecord», а не «MediaRecorder» (записи только в файл).

До сих пор я использовал опрос занятых циклов с «read ()» для аудиоданных. до сих пор это работало, но слишком сильно привязывает процессор. Между двумя опросами я усыпляю поток, чтобы избежать 100% загрузки процессора. Однако это не совсем чистое решение, так как время сна не гарантируется, и вы должны вычесть время безопасности, чтобы не потерять звук сниппеты. Это не оптимально для процессора. Мне нужно как можно больше свободных циклов ЦП для параллельный поток.

Теперь я реализовал запись, используя «OnRecordPositionUpdateListener». Это выглядит очень многообещающе и правильный способ сделать это в соответствии с SDK Docs. Кажется, все работает (открытие аудиоустройства, чтение () данных и т. Д.) но Listner никогда не вызывается.

Кто-нибудь знает почему?

Информация: Я работаю с реальным устройством, а не под эмулятором. Запись с использованием Busy Loop в основном работает (но не удовлетворяет). Только Callback Listener никогда не вызывается.

Вот фрагмент моего исходного кода:

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

  }
}

Ответы [ 4 ]

14 голосов
/ 29 сентября 2010

Я считаю, что проблема в том, что вам все еще нужно выполнить цикл чтения. Если вы настроите обратные вызовы, они сработают, когда вы прочитаете количество кадров, которое вы указали для обратных вызовов. Но вам все равно нужно сделать чтение. Я попробовал это, и обратные вызовы были вызваны просто отлично. Установка маркера вызывает обратный вызов, когда это количество кадров было прочитано с начала записи. Другими словами, вы можете установить маркер далеко в будущее, после многих чтений, и тогда он сработает. Вы можете установить период на большее количество кадров, и этот обратный вызов будет срабатывать каждый раз, когда будет прочитано такое количество кадров. Я думаю, что они делают это, так что вы можете выполнять низкоуровневую обработку необработанных данных в тесном цикле, тогда каждый раз ваш обратный вызов может выполнять обработку на суммарном уровне. Вы можете использовать маркер, чтобы легче было решить, когда остановить запись (вместо подсчета в цикле чтения).

6 голосов
/ 04 ноября 2011

Вот мой код, используемый для нахождения среднего шума. Обратите внимание, что он основан на уведомлениях слушателя, поэтому он сэкономит заряд батареи устройства Это определенно основано на примерах выше. Этот пример сэкономил мне много времени, спасибо.

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;
    }
}
5 голосов
/ 04 апреля 2013

Теперь я реализовал запись, используя "OnRecordPositionUpdateListener". Это выглядит очень многообещающе и правильный способ сделать это в соответствии с SDK Docs. Кажется, все работает (открытие аудиоустройства, чтение () данных и т. д.), но Listner никогда не звонил.

Кто-нибудь знает почему?

Я обнаружил, что OnRecordPositionUpdateListener игнорируется, пока вы не сделаете свой первый .read().

Другими словами, я обнаружил, что если я настрою все на документы, мой Слушатель никогда не будет вызван. Однако, если я сначала позвонил .read() сразу после выполнения моего начального .start(), тогда будет вызван Слушатель - при условии, что я делал .read() каждый раз, когда Слушатель вызывался.

Другими словами, кажется, что событие Listener подходит только один раз за .read() или что-то подобное.

Я также обнаружил, что если бы я запрашивал чтение сэмплов меньше, чем buffSize / 2, прослушиватель не вызывался бы. Таким образом, кажется, что слушатель называется только ПОСЛЕ .read() как минимум половины размера буфера. Чтобы продолжать использовать функцию обратного вызова Listener, необходимо вызывать read каждый раз, когда слушатель запускается. (Другими словами, поместите вызов для чтения в коде слушателя.)

Однако слушатель, кажется, вызывается в то время, когда данные еще не готовы, что вызывает блокировку.

Кроме того, если ваш период уведомления или время больше половины вашего bufferSize, они никогда не будут вызваны, кажется.

UPDATE:

Продолжая копать глубже, я обнаружил, что обратный вызов, кажется, вызывается ТОЛЬКО после завершения .read() ......!

Я не знаю, это ошибка или особенность. Моей первоначальной мыслью было бы, что я хочу перезвонить, когда пришло время читать. Но, возможно, у разработчиков Android возникла иная идея: вы просто помещаете while(1){xxxx.read(...)} в отдельный поток, и чтобы избавить вас от необходимости отслеживать каждый раз, когда read () завершается, обратный вызов может по существу сказать вам, когда чтение закончилось.

О. Может, меня осенило. И я думаю, что другие указывали на это здесь до меня, но это не отражалось: обратный вызов позиции или периода должен работать с байтами, которые уже прочитаны ...

Наверное, я застрял с использованием потоков.

В этом стиле поток будет непрерывно вызывать read (), как только он вернется, поскольку read блокируется и терпеливо ждет достаточного количества данных для возврата.

Затем, независимо, обратный вызов будет вызывать указанную вами функцию каждые x сэмплов.

0 голосов
/ 28 мая 2012

Для тех, кто записывает аудио через Intent Service, они могут также столкнуться с этой проблемой обратного вызова. Я работал над этой проблемой в последнее время, и я нашел простое решение, где вы вызываете свой метод записи в отдельном потоке. Это должно вызвать метод onPeriodicNotification во время записи без каких-либо проблем.

Примерно так:

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