3

The play method below is from a class which, upon instantiation, reads a .wav file into a byte array called data, and stores the sound format in an AudioFormat object called format.

I have a program that calls play from a java.util.Timer. When I go into the folder with all the relevant .class files and I run the program using the command java MainClass, everything works as expected. However, when I put all the .class files in an executable .jar and run the program using the command java -jar MyProgram.jar, sounds played using the play method are cut off after something like 50 to 150 ms.

public void play() throws LineUnavailableException {
    final Clip clip = (Clip)AudioSystem.getLine(new DataLine.Info(Clip.class, format));
    clip.open(format, data, 0, data.length);
    new Thread() {
        public void run() {
            clip.start();
            try {
                Thread.sleep(300); // all sounds are less than 300 ms long
            } catch (InterruptedException ex) { /* i know, i know... */ }
            clip.close();
        }
    }.start();
}

A few comments:

  • I've tried increasing the sleep time in the play method up to 1000 ms, with no change in behavior.

  • Timing the Thread.sleep using System.nanoTime confirms that the thread is sleeping exactly as long as expected.

  • Since the sound file to be played is pre-loaded into memory, I don't think the act of extracting the sound resource from the .jar can be causing the problem.

  • I've tried running the program from both inside and outside the jar using the memory pool size options -Xms2m and -Xmx64m (separately), with no change in behavior.

I'm running OpenJDK Java 6 on Ubuntu 11.04. Any idea what's going on?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Vectornaut
  • 473
  • 5
  • 11
  • 2
    For better help sooner, post an [SSCCE](http://sscce.org/). It is likely the loading of the `byte[]` that is the problem. – Andrew Thompson Sep 04 '12 at 01:11
  • @AndrewThompson: You're totally right! There was a bug in the method that reads the .wav file into the byte array. When I ran the program using java MainClass, the input stream was able to read the .wav file in a single read call, so the bug wasn't apparent. When I ran the program from a .jar, multiple read calls were necessary, and the bug appeared. Thanks so much for your help! If you post your comment as an answer, I'd be glad to accept it. – Vectornaut Sep 22 '12 at 20:14
  • @AndrewThompson: Thanks! I'm so sorry it's taken me this long to accept your answer... – Vectornaut Dec 20 '12 at 07:19

3 Answers3

4

I don't know if this is directly related to the problem, but it is somewhat self-defeating to instantiate a new Clip every time you play that Clip. The point of a Clip is to be able to start it without having to load it first. As you are currently set up, your Clip won't even start playing until it has been fully loaded. (SourceDataLines start playing more quickly than Clips when you include the instantiation steps, as they don't wait for all the data to load before commencing.)

So, as a first step to solving this problem, I would suggest instantiating the various Clips prior to the play call. I believe if you do this right, and the clip start is in its own thread, it can be allowed to run its course with no need to mess with Thread.sleep. You just have to reposition the clip back to its starting frame prior to the next start. (Can be done in the play call just prior to the start.

Once your usage of Clip has become more conventional, it might be easier to figure out if there is something else going on.

I am also unclear why the source .wav files are being loaded into byte arrays as an intermediate step. Might as well load them directly into Clips. They will take up pretty much the same amount of space either way, given the preponderance of PCM data, and be ready to go when you call them.

Disabling error messages is also self-defeating. Java could be trying to give you a perfectly good diagnostic, maybe not in this particular sleep call (I know, I know) but if you do this often, who knows. :)

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41
  • I'm instantiating a new Clip every time I play the sound because I may need to play two copies of the same sound concurrently. See this answer for details: (http://stackoverflow.com/a/10000439/1644283). – Vectornaut Sep 22 '12 at 20:02
  • I've already checked that no exceptions are being thrown during this particular sleep call by writing to standard output. The catch block is silent because the way the sleep method is being called makes it difficult to send a message back to my program's error-handling system. (The program is a GUI application, so output to standard output will most likely just be thrown away during normal use.) – Vectornaut Sep 22 '12 at 20:07
  • Then preload two Clips into memory! Your play routine should only have to start them, not load them, otherwise, you are guaranteed to be performing worse than you would if you use SourceDataLines. Ah! Just saw trashgod's reply. Step one to fix this is to follow his pattern. He show's in code what I am talking about. – Phil Freihofner Sep 23 '12 at 01:21
4

Amplifying on @Anrew's and @Phil's answers, there's an outline of obtaining a Clipand playing it asynchronously below. There's a complete example here.

AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(...);
Clip clip = AudioSystem.getClip();
clip.open(audioInputStream);
...
// Play the sound in a separate thread.
private void playSound() {
    Runnable soundPlayer = new Runnable() {

        @Override
        public void run() {
            try {
                clip.setMicrosecondPosition(0);
                clip.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    new Thread(soundPlayer).start();
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thanks for writing out and citing good examples of efficient Clip usage! +1 – Phil Freihofner Sep 23 '12 at 01:24
  • Yeah.. I *liked* my comment (now upgraded to an answer), but now I like this answer better. @Phil was also spot on in the 1st detailed answer. OP - you should think very carefully about which way to go on selecting the 'accept' answer. – Andrew Thompson Sep 23 '12 at 01:32
  • trashgod and @PhilFreihofner --- I appreciate your detailed answers, and I hope other people will find them useful, but I can't accept them, because they didn't help me solve my problem. I'm using the convoluted approach described in the question because the conventional one you guys described didn't work for me. Maybe if I understood Java Sound and multithreading better, I would be able to see what I was doing wrong, but unfortunately that isn't a priority for me---especially now that I have working code. – Vectornaut Dec 20 '12 at 07:29
  • @Vectornaut: No explanation required; Andrew's answer is dispositive. I just didn't want to preempt Phil's answer. Glad you got it sorted. For later reference, note how this [example](http://stackoverflow.com/tags/javasound/info) loads and plays a `Clip` on the [*initial thread*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html), while the dialog runs on the EDT. – trashgod Dec 20 '12 at 07:47
  • I am here to HELP, and learn stuff, not game the system to accumulate points. And it is very gratifying to get the compliments from Andrew, trashgod, as I appreciate the expertise both of you have and the reassurance I'm not leading people astray (AND appreciate criticism if my answers are off-base). – Phil Freihofner Dec 20 '12 at 18:49
3

It is likely the loading of the byte[] that is the problem.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 1
    I have to think a `byte[]` is superfluous; more [here](http://stackoverflow.com/a/12548978/230513). – trashgod Sep 23 '12 at 01:13