For my application I need to replicate the beeping of a heart rate monitor, that is, it plays a sound N times every minute. The problem is that it seems to lag by about 5ms. I know I can't expect realtime performance because of context switching on the OS and other overhead. With a BPM of 80, I get the following log output:
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
Here is my Thread
that I use to play the beep sound:
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();
Is there anything I can do to make it play true to the set BPM or is it actually playing every 946ms and the other 5 are just overhead from making the sound play and logging?
I know I could take off 5ms from waitTime
, but that feels like cheating and not solving the actual problem.
The question is similar in nature to many that have been asked before concerning implementation of a metronome.
THe only way to achieve this accurately is to render your own PCM stream. Since the system (wall) clock is not synchronous with the audio clock, you cannot reliably use this to insert your periodic events - none of the available ways of doing this (e.g sleeping a dedicated thread, using timers) are accurate enough as they all rely on accurate scheduling and/or the run loop to not be congested.
Instead you insert the event after every N samples are rendered into the PCM stream, where N is a function of the period and sample-rate.