Java Audio SourceDataLine does not support PCM_FLOAT

2.4k views Asked by At

I am trying to play a buffer of audio using Java on Linux.

I am getting the following exception when attempting to open the line (not when I write the audio to it)...

Exception in thread "main" java.lang.IllegalArgumentException: No line matching interface SourceDataLine supporting format PCM_FLOAT 44100.0 Hz, 16 bit, mono, 2 bytes/frame,  is supported.

    public boolean open()
{
    try {

        int smpSizeInBits = bytesPerSmp * 8;
        int frameSize = bytesPerSmp * channels; // just an fyi, frameSize does not always == bytesPerSmp * channels for non PCM encodings
        int frameRate = (int)smpRate; // again this might not be the case for non PCM encodings.
        boolean isBigEndian = false;

        AudioFormat af = new AudioFormat(AudioFormat.Encoding.PCM_FLOAT , smpRate, smpSizeInBits, channels, frameSize, frameRate, isBigEndian);

        DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);
        int bufferSizeInBytes = bufferSizeInFrames * channels * bytesPerSmp;
        line = (SourceDataLine) AudioSystem.getLine(info);
        line.open(af, bufferSizeInBytes);
        open = true;
    }
    catch(LineUnavailableException e) {
        System.out.println("PcmFloatPlayer: Unable to open, line unavailble.");
    }

    return open;
}

I am wondering if my assumptions about what PCM_FLOAT encoding is, are actually incorrect.

I have some code that reads in a wav file. The wavfile is mono, 16bit, uncompressed format. I then convert the audio to floats in range of -1.0 to 1.0 for processing.

I assumed the PCM_FLOAT encoding is just raw PCM data that has been converted to float values between -1.0 and 1.0. Is this correct?

I then assumed that the SourceDataLine would convert the float audio to the appropriate format based on my passed format info (mono, 16bit, 2bytes/frame). Again is this assumption incorrect?

Must I convert my float -1.0 to 1.0 audio back to my desired output format, and set the SourceDataLine to PCM_SIGNED (assuming that is my desired format)?

EDIT:

In addition, when I called AudioSystem.getTargetEncodings(), with PCM_FLOAT, it returns three encodings. Does that mean that it will accept PCM_FLOAT, and be capable to converting to the returned encodings, based on what the underlying audio system supports?

        AudioFormat.Encoding[] encodings = AudioSystem.getTargetEncodings(AudioFormat.Encoding.PCM_FLOAT);
    for(AudioFormat.Encoding e : encodings)
        System.out.println(e);

results in...

PCM_SIGNED PCM_UNSIGNED PCM_FLOAT

2

There are 2 answers

0
Phil Freihofner On

I don't know that I'll be able to answer your direct questions. But maybe the code I can show you, which I know works (including on Linux), will help you arrive at a workable solution. I have programs that generate audio signals via incoming cues, but also custom-made Synths, and I do all the mixing and effects with PCM floats in the range -1 to 1. To output, I convert the floats to a standard "CD Quality" format that Java supports.

Here is the format I use for the outputting SourceDataLine:

AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);

You'll probably want to make this mono instead of stereo. But I should say, it seems to me that if you are able to read an incoming wav file with a different format, you should be able to play back that same format, assuming you reverse all the steps taken to convert the incoming data to PCM.

For the standard "CD Quality" format, to go from pcm signed floats to bytes, there is an intermediate step of inflating to the range of a signed short (-32768 to 32767).

public static byte[] fromBufferToAudioBytes(byte[] audioBytes, float[] buffer)
{
    for (int i = 0, n = buffer.length; i < n; i++)
    {
        buffer[i] *= 32767;
        audioBytes[i*2] = (byte) buffer[i];
        audioBytes[i*2 + 1] = (byte)((int)buffer[i] >> 8 );
    }
    return audioBytes;
}

This is taken from the AudioCue library that I wrote and posted on github.

I find it reduces headaches to just deal with the one AudioFormat, to make conversions with Audacity to the one format, and not try make provisions for multiple formats. But that is just a personal preference, and I don't know if that strategy would work for your situation or not.

Hope there is something here that helps!

0
Roman Obednyak On
public class Main {    
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread2();
        t1.start(); 
        Thread t2 = new thread3();  
        t2.start();
        Thread.sleep(5000);  
    }  
}

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Thread2 extends Thread implements Runnable {

   @Override 
   public void run() { 
      playWav("C:/Windows/Media/feel_good_x.wav");
    }

   private static void playWav(String soundFilePath) {
      File sFile = new File(soundFilePath); 
      if (!sFile.exists()) {

         String ls = System.lineSeparator();
         System.err.println("немає в директорії»+
               ls + "(" + soundFilePath + ")" + ls);
         return;
      }

      try {
         Clip clip;

         try (AudioInputStream audioInputStream = AudioSystem.

               getAudioInputStream(sFile.getAbsoluteFile())) { 
            clip = AudioSystem.getClip();
            clip.setFramePosition(0);
            clip.open(audioInputStream);
         }
         clip.start();
      }

      catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {

         Logger.getLogger("playWav()").log(Level.SEVERE, null, ex);
      }
   }


}