I know Android is known for having audio latency issues but I have done everything I know of to reduce it and I still have noticeable latency in my music application between pressing the button and hearing the sound.
I first made the app using MediaPlayer which was way too slow so I switched to SoundPool but that was still to slow. So I had to go much lower level and use AudioRecord and AudioTrack to record and play raw PCM audio data. I store the raw audio data in a hash map related to a file name. I also make a WAV file for permanent storage but I'm not actually playing the audio from that. I have even tried chunking the audio data when I send it to the AudioTrack object with AudioTrack.write and have tried many different chunk sizes, settling on 1024 for now. I create one AudioTrack object instance per button and keep alive for that button for the life cycle of the app and just reuse it. This way there is a separate thread and AudioTrack object handling the playback for all the different sounds. I also switched to using onTouch ACTION_DOWN to trigger rather than onClick. Nothing seems to help much.
Even then I still have noticeable lag. Haven't measured it yet but it seems like it's around 300ms or so. Definitely better than with SoundPool but still not usable for a live music making application. The app plays drum beats with recorded sounds so it has to be almost instantaneous or it's useless.
I know it's possible to make a live music making android app with very low latency because I have apps that do that like Mini Piano Lite, Caustic etc.
Here's the class that handles the audio and below is the most relevant method that actually plays the sound.
public static void recordPlay(String fileName) {
Log.d("AudioTrack", "in recordPlay fileName " + fileName);
byte[] bytes = soundNameDataHashMap.get(fileName); // Get the data from the hashmap based on file name
if (bytes != null) { // There is audio data for this file name
Log.d("AudioTrack", "in recordPlay bytes was not null, fileName " + fileName);
// Check if there is an AudioTrack for this fileName
AudioTrack audioTrack = soundNameAudioTrackHashMap.get(fileName);
if (audioTrack == null){
// If there isn't one yet, make one
audioTrack = createAudioTrack();
soundNameAudioTrackHashMap.put(fileName, audioTrack); // Add the new AudioTrack to the hash map for this file name
} else {
// If there is an existing one, we just need to stop it to get ready to play again
// Check if it's playing currently
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING){ // TODO this check is only necessary when chunking the audioTrack.write below which doesn't even really seem to help
audioTrack.stop();
audioTrack.flush();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Play the AudioTrack and write the data to it
Log.d("recordPlay", "recordPlay: right before audioTrack.play()!");
audioTrack.play();
Log.d("recordPlay", "recordPlay: right before audioTrack.write(bytes)!");
int chunkSize = 1024; // 1024 seems good
for (int i = 0; i < bytes.length; i += chunkSize){
int blockSize = Math.min(chunkSize, bytes.length - i);
audioTrack.write(bytes, i, blockSize);
}
Log.d("recordPlay", "recordPlay: At the end of recordPlay!");
} else {
Log.d("AudioTrack", "No sound recorded on that button yet!");
}
}