我的Android Java应用程序需要将音频数据记录到RAM中并进行处理。 这就是我使用“AudioRecord”类而不是“MediaRecorder”(仅记录到文件)的原因。
直到现在,我使用繁忙的循环轮询“read()”来表示音频数据。到目前为止,这一直在起作用,但它过多地占用了CPU。 在两次民意调查之间,我将线程置于休眠状态以避免100%的CPU使用率。 然而,这并不是一个干净的解决方案,因为睡眠的时间是 不保证,您必须减去安全时间,以免音频松动 片段。这不是CPU的最佳选择。我需要尽可能多的空闲CPU周期 并行运行的线程。
现在我使用“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);
}
}
答案 0 :(得分:14)
我认为问题是你仍然需要进行读取循环。如果设置回调,则在读取为回调指定的帧数时将触发。但是你仍然需要进行读取。我试过这个并且回调被调用就好了。设置标记会在记录开始后读取该帧数时产生回调。换句话说,在你的许多读取之后,你可以将标记设置为远期,然后它将会触发。您可以将周期设置为更大的帧数,并且每次读取该帧数时都会触发回调。我认为他们这样做是为了你可以在紧密循环中对原始数据进行低级处理,然后你的回调经常会进行汇总级处理。您可以使用标记来更轻松地决定何时停止录制(而不是在读取循环中计数)。
答案 1 :(得分: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;
}
}
答案 2 :(得分:5)
现在我用the实现了录音 “OnRecordPositionUpdateListener”。这看起来很有希望和 根据SDK Docs正确的方法。一切似乎都有效 (打开音频设备,读取()数据等)但Listner是 从未打过电话。
有人知道为什么吗?
我发现OnRecordPositionUpdateListener会被忽略,直到您执行第一次.read()
。
换句话说,我发现如果按照文档设置所有内容,我的监听器就不会被调用。但是,如果我在完成初始.read()
之后第一次调用.start()
,那么监听器将被调用 - 前提是每次调用监听器时我都会.read()
。
换句话说,看起来每个.read()
或者其他一些事件,听众事件只有一次。
我还发现,如果我要求读取的buffSize / 2样本少于一个,则不会调用监听器。因此,似乎只在缓冲区大小的一半.read()
之后调用侦听器。要继续使用监听器回调,每次运行监听器时都必须调用read。 (换句话说,将调用放在Listener代码中。)
然而,似乎在数据尚未准备好的时候调用了侦听器,导致阻塞。
此外,如果您的通知期限或时间大于您的bufferSize的一半,它们将永远不会被调用,似乎。
更新:
随着我继续深入挖掘,我发现只有在.read()
完成后才会调用回调......!
我不知道这是一个错误还是一个功能。我最初的想法是,当我需要阅读时,我想要回调。但也许Android开发人员有相反的想法,你只需要在一个线程中放置一个while(1){xxxx.read(...)}
,并避免每次read()完成时都要跟踪回调,回调基本上可以告诉你读取完成的时间。
喔。也许它只是在我身上恍然大悟。我认为其他人已经在我面前指出了这一点,但它没有陷入:位置或周期回调必须对已经读过的字节进行操作......
我想也许我会坚持使用线程。
在这种风格中,一旦线程返回,就会有一个线程不断地调用read(),因为读取是阻塞的,并且耐心地等待返回足够的数据。
然后,独立地,回调将每x个样本调用指定的函数。
答案 3 :(得分: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
}
}
}