【发布时间】:2014-09-02 21:42:18
【问题描述】:
对于我的应用程序,我需要复制心率监测器的蜂鸣声,也就是说,它每分钟播放 N 次声音。问题是它似乎滞后了大约 5ms。我知道由于操作系统上的上下文切换和其他开销,我无法期望实时性能。 BPM 为 80 时,我得到以下日志输出:
MainActivity: Beeping every 946ms
MainActivity: java.lang.InterruptedException
MainActivity: Beep 0: 951
MainActivity: Beep 1: 951
MainActivity: Beep 2: 951
MainActivity: Beep 3: 951
MainActivity: Beep 4: 951
MainActivity: Beep 5: 951
MainActivity: Beep 6: 952
MainActivity: Beep 7: 951
MainActivity: Beep 8: 954
MainActivity: Beep 9: 951
MainActivity: Beep 10: 953
MainActivity: Beep 11: 952
MainActivity: Beep 12: 951
MainActivity: Beep 13: 951
这是我用来播放哔声的Thread:
mBeepThread = new Thread(new Runnable() {
@Override
public void run() {
ArrayList<Long> beepTimes = new ArrayList<Long>(5000);
try {
AssetFileDescriptor afd;
afd = getAssets().openFd("audio/heart_beep.ogg");
SoundPool sp = new SoundPool(10,AudioManager.STREAM_MUSIC,0);
int soundId = sp.load(afd, 1);
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength());
long duration = mp.getDuration();
long waitTime = (60000/bpmVal) - duration;
mp.release();
Log.i(TAG,"Beeping every "+waitTime+"ms");
while(true) {
beepTimes.add(SystemClock.elapsedRealtime());
sp.play(soundId,1,1,1,0,1);
Thread.sleep(waitTime);
}
} catch (IllegalStateException | IOException | InterruptedException e) {
Log.e(TAG,e.getMessage(),e);
Iterator<Long> it = beepTimes.iterator();
int count = 0;
Long oldTime = it.next();
while(it.hasNext()) {
long newTime = it.next();
long diff = newTime - oldTime;
oldTime = newTime;
Log.i(TAG,String.format("Beep %d: %d",count++,diff));
}
return;
}
}
});
mBeepThread.start();
我能做些什么来让它按照设定的 BPM 播放,还是实际上每 946 毫秒播放一次,而其他 5 个只是播放声音和记录的开销?
我知道我可以从waitTime 缩短 5 毫秒,但这感觉像是在作弊,并没有解决实际问题。
【问题讨论】:
-
调用 play 可能会有一些开销,但更大的问题是
Thread.sleep没有任何实时保证。它受调度程序的突发奇想。 -
@jaket 那么你如何建议我每
nms 实现一次哔声呢? -
如果是我,我会同步到音频时钟而不是系统时钟。我的意思是,你能想象一个带有 Thread.sleeps 的鼓机或 DAW 吗?不幸的是,唯一正确的方法是将一堆零输出到输出,直到您需要播放哔声时的确切样本,然后输出。
-
听起来你仍然在忙着等待,即:
while(true)if(getTime() - lastPlay >= waitTime) {audioTrack.write(soundBuffer);lastPlay = getTime();} else audioTrack.write(0); -
不一定。大多数 API 要么使用回调,要么具有阻塞调用。忙碌的等待不是问题。如果您想准确地为音频事件计时,正如您在实验中看到的那样,睡眠不会做到这一点。