Подтвердить что ты не робот

Android: проблема класса AudioRecord: Callback никогда не вызывается

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

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

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

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

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

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

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

  }
}
4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

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

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

UPDATE:

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

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

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

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

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

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

Ответ 4

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

Что-то вроде этого:

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