Search code examples
javajavasoundtarsosdsp

Issues with SourceDataLine format support


I have an application written in Java in which I need to play audio. I used OpenAL (with java-openal library) for the task however I would like to use WSOLA which is not supported by OpenAL directly. I found a nice java-native library called TarsosDSP which has support for WSOLA.

The library uses standard Java APIs for audio output. The issue occurs during the SourceDataLine setup:

IllegalArgumentException: No line matching interface SourceDataLine supporting format PCM_UNSIGNED 16000.0 Hz, 16 bit, mono, 2 bytes/frame, little-endian is supported.

I made sure the issue is not caused by the lack of permissions (ran it as root on Linux + tried it on Windows 10) and there are no other SourceDataLines used in the project.

After tinkering with the format I found out that the format is accepted when it's changed from PCM_UNSIGNED to PCM_SIGNED. It seems like a minor issue since only moving the byte range form unsigned to signed should be pretty easy. However it's weird that it's not supported natively.

So, is there some solution in which I wouldn't have to modify the source data?

Thanks, Jan


Solution

  • You don't have to move the byte range by hand. After you've created an AudioInputStream, you create another AudioInputStream, with a signed format and that is connected to the first unsigned stream. If you then read the data using the signed stream, the Sound API automatically converts the format. This way you don't need to modify the source data.

    File fileWithUnsignedFormat;
    
    AudioInputStream sourceInputStream;
    AudioInputStream targetInputStream;
    
    AudioFormat sourceFormat;
    AudioFormat targetFormat;
    
    SourceDataLine sourceDataLine;
    
    sourceInputStream = AudioSystem.getAudioInputStream(fileWithUnsignedFormat);
    sourceFormat = sourceInputStream.getFormat();
    
    targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 
        sourceFormat.getSampleRate(), 
        sourceFormat.getSampleSizeInBits(), 
        sourceFormat.getChannels(), 
        sourceFormat.getFrameSize(), 
        sourceFormat.getFrameRate(), 
        false);
    
    targetInputStream = AudioSystem.getAudioInputStream(targetFormat, sourceInputStream);
    
    DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, targetFormat);
    sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
    
    sourceDataLine.open(targetFormat);
    sourceLine.start();
    
    
    // schematic
    targetInputStream.read(byteArray, 0, byteArray.length);
    sourceDataLine.write(byteArray, 0, byteArray.length);