Android AudioRecord类 – 快速处理现场麦克风audio,设置回拨function
我想录制来自麦克风的audio并以近实时的方式访问它以便可能的播放。 我不确定如何使用Android AudioRecord类录制一些麦克风audio并快速访问它。
对于AudioRecord类,官方网站称“该应用程序在时间上轮询AudioRecord对象”,并且“正在填充的缓冲区的大小决定了在未读取数据溢出之前logging的时间长度”。 稍后有人build议在不频繁轮询时应该使用更大的缓冲区。 他们从来没有实际上在代码中显示的例子
我在书中看到的一个例子是使用AudioRecord类来连续读取新鲜填充了现场麦克风audio的缓冲区,然后应用程序将此数据写入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();
这段代码如何使其读数与logging速率同步尚不清楚 – 布尔型“isRecording”是在其他地方正确地开启和closures的? 看起来这个代码可能会读取太频繁或者很less,取决于读取和写入需要多长时间。
站点文档还说,AudioRecord类有一个名为OnRecordPositionUpdateListener的嵌套类,它被定义为一个接口。 这些信息表明,不pipe怎样,你都要指定你想要通知录制进程的时间,以及你的事件处理程序的名称,并且以指定的频率自动对你的事件处理程序进行调用。 我认为在伪代码中的结构将是类似的 –
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 )
我需要find一些特定的代码,使我能够捕捉和处理延迟小于500毫秒左右的麦克风audio。 Android提供了另一个名为MediaRecorder的类,但它不支持stream式传输,我可能想要通过Wi-Finetworking实时stream式传输现场麦克风audio。 我在哪里可以find一些具体的例子?
在用通知和其他一些技巧进行实验之后,我决定使用这个代码:
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手机上它的工作非常强劲。
我想知道你是否可以通过以下方式来结合这些答案…
在while循环之前使用setPositionNotificationPeriod(160)。 这应该导致每次读取160帧时调用callback。 不是在执行读取循环的线程内调用进程(缓冲区),而是从callback中调用进程(缓冲区)。 使用一个variables来跟踪最后一个读取缓冲区,以便处理正确的一个。 就像现在,你阻止了阅读,那么当你正在处理时你没有阅读。 我认为将这两者分开可能会更好。
这是您需要使用OnRecordPositionUpdateListener和通知期间的代码。
我注意到,在实践中,我不是一直在同一时间发送通知,我想,但是已经足够接近了。
关于detectAfterEvery
:
detectEvery的大小需要足够大,以保存您想要的数据量。 所以对于这个例子,我们有一个44100赫兹的采样率,这意味着我们需要每秒44100个采样。 通过将setPositionNotificationPeriod
设置为44100,代码告诉Android在回收了44100个样本之后callback,大约每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); }
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(); }
它播放录制的audioless于100毫秒的延迟。