Search code examples
javaaudioraspberry-pijavasound

Getting sound clip position is extremely slow on Raspberry Pi


I'm running Java 21 on a Raspberry Pi 3B (64-bit OS). Calling javax.sound.sampled.Clip#getFramePosition() on a running audio clip takes up to 3 seconds per call, whereas it takes less than a millisecond on my PC.

I've come back to this over the years, trying:

  • Different JDKs (8, 17, 21)
  • Different Raspberry Pis
  • Different Pi operating systems (32 and 64 bit)
  • Different audio formats (my actual use case uses MP3 files, but WAV makes for a simpler reproduction case)
  • An external sound card
  • Running the loop on a separate thread

But I've never been able to get sensible performance out of this call. Monitoring with top while the code runs shows that the java process never consumes more than 2% of CPU or 5% of RAM.

Avenues I've considered for a solution:

  • Am I missing something obvious?
  • Can the audio config (or audio driver) be changed?
  • Is there another way to get the progress of a Clip? I don't think SourceDataLine is a good fit for my use case.
  • Can I raise a bug report somewhere? (Not sure whose jurisdiction this would fall under: the JDK, the Pi team, or someone else).
  • How can I figure out where the performance bottleneck is?

Here's a reproducible example (requires an external test.wav file in the working directory):

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Date;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;

public class Main {

    public static void main(String[] args) throws Exception {
        byte[] fileBytes = Files.readAllBytes(new File("./test.wav").toPath());
        InputStream bais = new ByteArrayInputStream(fileBytes);
        AudioInputStream ais = AudioSystem.getAudioInputStream(bais);

        Clip clip = AudioSystem.getClip();
        clip.open(ais);
        clip.start();

        for (int i = 0; i < 100; i++) {
            long start = new Date().getTime();
            long pos = clip.getFramePosition();
            long duration = new Date().getTime() - start;
            System.out.println("Pos: " + pos + ", duration: " + duration);
            Thread.sleep(10);
        }
    }
}

Here is some sample output:

Pos: 0, duration: 0
Pos: 32778, duration: 645
Pos: 164690, duration: 2980
Pos: 262573, duration: 2207
Pos: 426196, duration: 3698
Pos: 458820, duration: 728
Pos: 524493, duration: 1479
Pos: 557127, duration: 729
Pos: 590678, duration: 749
Pos: 656352, duration: 1478
Pos: 721684, duration: 1469
Pos: 754290, duration: 728
Pos: 786924, duration: 729
Pos: 813348, duration: 588

Solution

  • A couple ideas. For one, I'm curious how AudioCue-maven might work on the Raspberry PI. It operates like a Clip but is based upon a SourceDataLine and might have the best aspects of each for your use case.

    Another idea, with SourceDataLine, you can count frames as they elapse. There's a code example in the audio tutorials, in the section "Reading Audio Files" where the number of frames being read is held. With a little modification you could make the variable readable by other threads, e.g., designate it as volatile.

    It might be helpful to experiment with different buffer settings when you open the Clip. I'm curious also if you have a data example of the output from your laptop to show in contrast to the Raspberry output you show.