22

我的 Android Java 应用程序需要将音频数据记录到 RAM 中并进行处理。这就是我使用“AudioRecord”类而不是“MediaRecorder”(仅记录到文件)的原因。

到现在为止,我对音频数据使用了带有“read()”的繁忙循环轮询。到目前为止,这一直有效,但它过多地与 CPU 挂钩。在两次轮询之间,我将线程置于睡眠状态以避免 100% 的 CPU 使用率。但是,这并不是一个真正干净的解决方案,因为不能保证睡眠时间,您必须减去安全时间才能不丢失音频片段。这不是 CPU 最优的。对于并行运行的线程,我需要尽可能多的空闲 CPU 周期。

现在我使用“OnRecordPositionUpdateListener”实现了录制。根据 SDK Docs,这看起来很有希望,也是正确的方法。一切似乎都正常(打开音频设备,读取()数据等),但从未调用过 Listner。

有人知道为什么吗?

信息:我正在使用真正的设备,而不是在模拟器下。使用忙碌循环的录音基本上可以工作(但不能令人满意)。只有回调监听器永远不会被调用。

这是我的源代码中的一个片段:

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

4 回答 4

14

我相信问题是您仍然需要执行读取循环。如果您设置回调,它们将在您读取为回调指定的帧数时触发。但是您仍然需要进行读取。我已经尝试过了,回调被调用得很好。当自记录开始以来读取了该数量的帧时,设置标记会导致回调。换句话说,你可以将标记设置在很远的未来,在你多次阅读之后,它就会触发。您可以将周期设置为更大的帧数,并且每次读取该帧数时都会触发回调。我认为他们这样做是为了让您可以在紧密循环中对原始数据进行低级处理,然后您的回调可以经常进行摘要级处理。

于 2010-09-28T23:35:20.870 回答
6

这是我用于查找平均噪声的代码。请注意,它基于侦听器通知,因此可以节省设备电池。这绝对是基于上面的例子。这些例子为我节省了很多时间,谢谢。

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;
    }
}
于 2011-11-04T09:15:27.510 回答
5

现在我使用“OnRecordPositionUpdateListener”实现了录制。根据 SDK Docs,这看起来很有希望,也是正确的方法。一切似乎都正常(打开音频设备,读取()数据等),但从未调用过 Listner。

有人知道为什么吗?

我发现 OnRecordPositionUpdateListener 在您执行第一个.read().

换句话说,我发现如果我按照文档设置所有内容,我的监听器永远不会被调用。.read()但是,如果我在执行初始操作后第一次调用 a ,.start()那么 Listener 就会被调用——前提是我.read()每次调用 Listener 时都调用了 a 。

换句话说,Listener 事件似乎只对每次.read()或某些此类事件有效。

我还发现,如果我请求读取的样本数少于 buffSize/2,则不会调用 Listener。.read()因此,似乎只有在缓冲区大小至少一半的情况下才调用侦听器。要继续使用侦听器回调,必须在每次运行侦听器时调用 read。(换句话说,将调用 read 放入 Listener 代码中。)

但是,似乎在数据尚未准备好时调用了侦听器,从而导致阻塞。

此外,如果您的通知期限或时间大于缓冲区大小的一半,它们似乎永远不会被调用。

更新:

随着我继续深入挖掘,我发现回调似乎只在 a.read()完成时被调用......!

我不知道这是一个错误还是一个功能。我最初的想法是,在阅读的时候我想要一个回调。但也许 android 开发人员有相反的想法,你只需将 a 单独放入while(1){xxxx.read(...)} 一个线程中,并且为了让你不必跟踪每次 read() 完成的时间,回调基本上可以告诉你什么时候阅读已完成。

哦。也许我刚刚才恍然大悟。而且我认为其他人在这里已经在我之前指出了这一点,但它并没有陷入:位置或周期回调必须对已经读取的字节进行操作......

我想也许我坚持使用线程。

在这种风格中,一旦它返回,就会有一个线程连续调用 read(),因为 read 是阻塞的并且耐心地等待足够的数据返回。

然后,独立地,回调将每 x 个样本调用您指定的函数。

于 2013-04-04T06:40:13.153 回答
0

对于通过 Intent 服务录制音频的人,他们也可能会遇到此回调问题。我最近一直在研究这个问题,我想出了一个简单的解决方案,您可以在单独的线程中调用您的录制方法。这应该在录制时调用 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
            }
        }
    }
于 2012-05-28T15:19:16.823 回答