Any supported sound formats for Java on Windows 7?

3.8k views Asked by At

We'll, I've been beating my head against a wall trying to get Java to play some simple wav files without any luck. I've tried this code:

Clip clip = AudioSystem.getClip();
AudioInputStream inputStream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(soundBytes));
clip.open(inputStream);
clip.start();

This fails on "clip.open(...)" with the Exception:

javax.sound.sampled.LineUnavailableException: line with format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported.

And I've tried the more complicated (streaming version):

int BUFFER_SIZE = 128000;
AudioInputStream audioStream = null;
AudioFormat audioFormat;
SourceDataLine sourceLine = null;

try {
    audioStream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(soundBytes));
} catch (Exception e){
    e.printStackTrace();
}

audioFormat = audioStream.getFormat();

DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
try {
    sourceLine = (SourceDataLine) AudioSystem.getLine(info);
    sourceLine.open(audioFormat);
} catch (LineUnavailableException e) {
    e.printStackTrace();
} catch (Exception e) {
    e.printStackTrace();
}

sourceLine.start();

int nBytesRead = 0;
byte[] abData = new byte[BUFFER_SIZE];
while (nBytesRead != -1) {
    try {
        nBytesRead = audioStream.read(abData, 0, abData.length);
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (nBytesRead >= 0) {
       @SuppressWarnings("unused")
       int nBytesWritten = sourceLine.write(abData, 0, nBytesRead);
    }
}

sourceLine.drain();
sourceLine.close();

This also fails on "sourceLine.open(...)" with the Exception:

javax.sound.sampled.LineUnavailableException: line with format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported.

I've tried two different wav files, including the venerable "tada.wav" that comes in C:\Windows\Media.

I also used GoldWave to change one of the files to unsigned 8 bit mono, but that only changed the error message to:

javax.sound.sampled.LineUnavailableException: line with format PCM_UNSIGNED 44100.0 Hz, 8 bit, mono, 1 bytes/frame,  not supported.

Any thoughts on where I might be going wrong? Seems like playing a simple wave file should be simple, so I'm guessing I've gotten off in the weeds somewhere.

Thanks in advance.

UPDATE

So, the plot thickens. The code works fine if we move it into a separate stand alone java program. Something in our application must be cooking the Java's ability to play sounds.

Here is the stack trace for the above errors:

javax.sound.sampled.LineUnavailableException: line with format PCM_UNSIGNED 44100.0 Hz, 8 bit, mono, 1 bytes/frame,  not supported.
at com.sun.media.sound.DirectAudioDevice$DirectDL.implOpen(DirectAudioDevice.java:492)
at com.sun.media.sound.AbstractDataLine.open(AbstractDataLine.java:107)
at com.sun.media.sound.AbstractDataLine.open(AbstractDataLine.java:139)
at com.hcs.orc.detail.SoundAddEdit.playButtonActionPerformed(SoundAddEdit.java:315)
at com.hcs.orc.detail.SoundAddEdit.access$100(SoundAddEdit.java:40)
at com.hcs.orc.detail.SoundAddEdit$2.actionPerformed(SoundAddEdit.java:225)

UPDATE 2

More interesting finds. It seems that we have a conflict when loading DLLs. We have our own DLL to help us do things (such as find a reliable and usable MAC Address). If you play a sound (which loads the sound related DLLs) before you load our DLL, then both work. However, if you our DLL and then try to play a sound, the sound gives you the errors reported above.

Anyone have any insight into why a seemingly unrelated DLL would cause another DLL to load incorrectly later?

As an entry for really lame and bad workarounds, we can play a fraction of a second of silence on start up before we look up the MAC address. This is bad form for several reasons including that many of our clients don't use sound at all.

UPDATE 3

Digging into our library, it seems the problem is caused by a call to RegisterClassEx(...). We do this so we can display an embedded IE window with our HTML help file.

2

There are 2 answers

0
Stephen M -on strike- On BEST ANSWER

After figuring out it was a problem with our JNI code doing this:

rc=CoInitialize(NULL);
rc=OleInitialize(NULL);
{
     WNDCLASSEX     wc; 
     // Register the class of our window to host the browser. 'WindowProc' is our message handler
 // and 'ClassName' is the class name. You can choose any class name you want.
 ZeroMemory(&wc, sizeof(WNDCLASSEX));
 wc.cbSize = sizeof(WNDCLASSEX);
 wc.hInstance = hinstance;
 wc.lpfnWndProc = WindowProc;
 wc.lpszClassName = &ClassName[0];
 rc=RegisterClassEx(&wc);
}

It was a problem because we were calling it in the wrong place in dllMain(...). Instead, we moved it into the place where it is only called once, right before it is actually needed to open an embedded browser.

This resolved our problem.

2
Michael Berry On

I've run into a similar issue before (though not in relation to loading DLLs.) Javasound works underneath by using 1 or more mixers, each with 1 or more lines. Each of those lines has a number of formats that it says it supports, but this doesn't mean it won't spontaneously combust when given that format to play (essentially, there's nothing stopping it from boasting about playing formats it can't.)

When you use AudioSystem.getLine(), it will iterate over all these lines from all these mixers, and essentially return the first one it comes across that says it can handle that format. If that line is a big fat liar, then it won't look for others - it'll just go with it and produce the error that you're seeing.

Now the important thing to remember here is that the order it iterates over these lines is compeltely arbitrary. So anything could cause it to change, including something that seems seemingly irrelevant, such as loading a DLL. I can see one of two possibilities here, the DLL is somehow providing another audio line that's causing the issue, or loading the DLL just causes the arbitrary order to change, and when you do that it comes across the suspect line first for some reason.

The workaround isn't nice, but it's nicer than playing a sound and waiting a bit, you essentially have to test the line to see if it's telling the truth:

SourceDataLine dataline = null;
for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
    try {
        Mixer mixer = AudioSystem.getMixer(mixerInfo);
        dataline = (SourceDataLine)mixer.getLine(info);
        if(dataline==null) {
            continue; //Doesn't support this format
        }
        dataline.open(audioFormat);
        dataline.start();
    }
    catch (Exception ex) {
        //If we get here it's a buggered line, so loop round again
        continue;
    }
    try {
        dataline.close();
    }
    catch (Exception ex) {
        ex.printStackTrace(); //Shouldn't get here
    }
}


if(dataline==null) {
    //No dataline capable of *really* playing the stream
}
else {
    //We have a non-lying dataline!
}

This way takes a bit longer, but it's essentially a double check - we loop through each data line, and if it says it can play the format, we check if it really can - and only in that case do we then determine if it's safe to use.