Upto: Table of Contents of full book "Programming and Using Linux Sound"

Java Sound

This chapter covers the essentials of programming sampled data using the Java Sound API

Introduction

Java Sound has been around since early days of Java. It deals with both sampled and MIDI data. There are many resources available for Java Sound:

Key Java Sound classes

Information about devices

Each device is represented by a Mixer object. Ask the AudioSystem for a list of these. Each mixer has a set of target (output) lines and source (input lines). Query each mixer about these separately. The program is DeviceInfo.java:

      


import javax.sound.sampled.*;

public class DeviceInfo {

    public static void main(String[] args) throws Exception {

	Mixer.Info[] minfoSet = AudioSystem.getMixerInfo();
	System.out.println("Mixers:");
	for (Mixer.Info minfo: minfoSet) {
	    System.out.println("   " + minfo.toString());

	    Mixer m = AudioSystem.getMixer(minfo);
	    System.out.println("    Mixer: " + m.toString());
	    System.out.println("      Source lines");
	    Line.Info[] slines = m.getSourceLineInfo();
	    for (Line.Info s: slines) {
		System.out.println("        " + s.toString());
	    }

	    Line.Info[] tlines = m.getTargetLineInfo();
	    System.out.println("      Target lines");
	    for (Line.Info t: tlines) {
		System.out.println("        " + t.toString());
	    }
	}
    }
}
      
    

A part of the output on my system is

	
Mixers:
   PulseAudio Mixer, version 0.02
      Source lines
        interface SourceDataLine supporting 42 audio formats, and buffers of 0 to 1000000 bytes
        interface Clip supporting 42 audio formats, and buffers of 0 to 1000000 bytes
      Target lines
        interface TargetDataLine supporting 42 audio formats, and buffers of 0 to 1000000 bytes
   default [default], version 1.0.24
      Source lines
        interface SourceDataLine supporting 512 audio formats, and buffers of at least 32 bytes
        interface Clip supporting 512 audio formats, and buffers of at least 32 bytes
      Target lines
        interface TargetDataLine supporting 512 audio formats, and buffers of at least 32 bytes
   PCH [plughw:0,0], version 1.0.24
      Source lines
        interface SourceDataLine supporting 24 audio formats, and buffers of at least 32 bytes
        interface Clip supporting 24 audio formats, and buffers of at least 32 bytes
      Target lines
        interface TargetDataLine supporting 24 audio formats, and buffers of at least 32 bytes
   NVidia [plughw:1,3], version 1.0.24
      Source lines
        interface SourceDataLine supporting 96 audio formats, and buffers of at least 32 bytes
        interface Clip supporting 96 audio formats, and buffers of at least 32 bytes
      Target lines
   NVidia [plughw:1,7], version 1.0.24
      Source lines
        interface SourceDataLine supporting 96 audio formats, and buffers of at least 32 bytes
        interface Clip supporting 96 audio formats, and buffers of at least 32 bytes
      Target lines
   NVidia [plughw:1,8], version 1.0.24
      Source lines
        interface SourceDataLine supporting 96 audio formats, and buffers of at least 32 bytes
        interface Clip supporting 96 audio formats, and buffers of at least 32 bytes
      Target lines
	
      

This shows both PulseAudio and ALSA mixers. Further queries could show what the supported formats are, for example.

Playing audio from a file

To play from a file, appropriate objects must be created for reading from the file and for writing to the output device. These are

Following these steps, data can then be read from the input stream and written to the dataline. The UML class diagram for the relevant classes is

      
      import java.io.File;
import java.io.IOException;
     
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
     
public class PlayAudioFile {
    /** Plays audio from given file names. */
    public static void main(String [] args) {
	// Check for given sound file names.
	if (args.length < 1) {
	    System.out.println("Usage: java Play <sound file names>*");
	    System.exit(0);
	}
     
	// Process arguments.
	for (int i = 0; i < args.length; i++)
	    playAudioFile(args[i]);
     
	// Must exit explicitly since audio creates non-daemon threads.
	System.exit(0);
    } // main
     
    public static void playAudioFile(String fileName) {
	File soundFile = new File(fileName);
     
	try {
	    // Create a stream from the given file.
	    // Throws IOException or UnsupportedAudioFileException
	    AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(soundFile);
	    // AudioSystem.getAudioInputStream(inputStream); // alternate audio stream from inputstream
	    playAudioStream(audioInputStream);
	} catch (Exception e) {
	    System.out.println("Problem with file " + fileName + ":");
	    e.printStackTrace();
	}
    } // playAudioFile
     
    /** Plays audio from the given audio input stream. */
    public static void playAudioStream(AudioInputStream audioInputStream) {
	// Audio format provides information like sample rate, size, channels.
	AudioFormat audioFormat = audioInputStream.getFormat();
	System.out.println("Play input audio format=" + audioFormat);
     
	// Open a data line to play our type of sampled audio.
	// Use SourceDataLine for play and TargetDataLine for record.
	DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
	if (!AudioSystem.isLineSupported(info)) {
	    System.out.println("Play.playAudioStream does not handle this type of audio on this system.");
	    return;
	}
     
	try {
	    // Create a SourceDataLine for play back (throws LineUnavailableException).
	    SourceDataLine dataLine = (SourceDataLine) AudioSystem.getLine(info);
	    // System.out.println("SourceDataLine class=" + dataLine.getClass());
     
	    // The line acquires system resources (throws LineAvailableException).
	    dataLine.open(audioFormat);
     
	    // Adjust the volume on the output line.
	    if(dataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
		FloatControl volume = (FloatControl) dataLine.getControl(FloatControl.Type.MASTER_GAIN);
		volume.setValue(6.0F);
	    }
     
	    // Allows the line to move data in and out to a port.
	    dataLine.start();
     
	    // Create a buffer for moving data from the audio stream to the line.
	    int bufferSize = (int) audioFormat.getSampleRate() * audioFormat.getFrameSize();
	    byte [] buffer = new byte[ bufferSize ];
     
	    // Move the data until done or there is an error.
	    try {
		int bytesRead = 0;
		while (bytesRead >= 0) {
		    bytesRead = audioInputStream.read(buffer, 0, buffer.length);
		    if (bytesRead >= 0) {
			// System.out.println("Play.playAudioStream bytes read=" + bytesRead +
			// ", frame size=" + audioFormat.getFrameSize() + ", frames read=" + bytesRead / audioFormat.getFrameSize());
			// Odd sized sounds throw an exception if we don't write the same amount.
			int framesWritten = dataLine.write(buffer, 0, bytesRead);
		    }
		} // while
	    } catch (IOException e) {
		e.printStackTrace();
	    }
     
	    System.out.println("Play.playAudioStream draining line.");
	    // Continues data line I/O until its buffer is drained.
	    dataLine.drain();
     
	    System.out.println("Play.playAudioStream closing line.");
	    // Closes the data line, freeing any resources such as the audio device.
	    dataLine.close();
	} catch (LineUnavailableException e) {
	    e.printStackTrace();
	}
    } // playAudioStream
} // PlayAudioFile


      
    

Recording audio to a file

Most of the work to do this is in preparation of an audio input stream. Once that is done the method write of AudioSystem will copy input from the audio input stream to the output file.

To prepare the audio input stream:

The output is just a Java File.

Then use the AudioSystem function write() to copy the stream to the file. The UML class diagram is

The program is:

      
      
import javax.sound.sampled.*;
import java.io.File;

/**
 * Sample audio recorder
 */
public class Recorder extends Thread
{
    /**
     * The TargetDataLine that we’ll use to read data from
     */
    private TargetDataLine line;

    /**
     * The audio format type that we’ll encode the audio data with
     */
    private AudioFileFormat.Type targetType = AudioFileFormat.Type.WAVE;

    /**
     * The AudioInputStream that we’ll read the audio data from
     */
    private AudioInputStream inputStream;

    /**
     * The file that we’re going to write data out to
     */
    private File file;

    /**
     * Creates a new Audio Recorder
     */
    public Recorder(String outputFilename)
    {
	try {
	    // Create an AudioFormat that specifies how the recording will be performed
	    // In this example we’ll 44.1Khz, 16-bit, stereo
	    AudioFormat audioFormat = new AudioFormat(
						      AudioFormat.Encoding.PCM_SIGNED,    // Encoding technique
						      44100.0F,                           // Sample Rate
						      16,                                 // Number of bits in each channel
						      2,                                  // Number of channels (2=stereo)
						      4,                                  // Number of bytes in each frame
						      44100.0F,                            // Number of frames per second
						      false);                            // Big-endian (true) or little-
	    // endian (false)

	    // Create our TargetDataLine that will be used to read audio data by first 
	    // creating a DataLine instance for our audio format type
	    DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat);

	    // Next we ask the AudioSystem to retrieve a line that matches the 
	    // DataLine Info
	    this.line = (TargetDataLine)AudioSystem.getLine(info);

	    // Open the TargetDataLine with the specified format
	    this.line.open(audioFormat);

	    // Create an AudioInputStream that we can use to read from the line
	    this.inputStream = new AudioInputStream(this.line);

	    // Create the output file
	    this.file = new File(outputFilename);
	}
	catch(Exception e) {
	    e.printStackTrace();
	}
    }

    public void startRecording() {
        // Start the TargetDataLine
        this.line.start();

        // Start our thread
        start();
    }

    public void stopRecording() {
        // Stop and close the TargetDataLine
        this.line.stop();
        this.line.close();
    }

    public void run() {
        try {
	    // Ask the AudioSystem class to write audio data from the audio input stream
	    // to our file in the specified data type (PCM 44.1Khz, 16-bit, stereo)
	    AudioSystem.write(this.inputStream, this.targetType, this.file);
	}
        catch(Exception e) {
	    e.printStackTrace();
	}
    }

    public static void main(String[] args) {
        if (args.length == 0) {
	    System.out.println("Usage: Recorder <filename>");
	    System.exit(0);
	}

        try {
	    // Create a recorder that writes WAVE data to the specified filename
	    Recorder r = new Recorder(args[0]);
	    System.out.println("Press ENTER to start recording");
	    System.in.read();

	    // Start the recorder
	    r.startRecording();

	    System.out.println("Press ENTER to stop recording");
	    System.in.read();

	    // Stop the recorder
	    r.stopRecording();

	    System.out.println("Recording complete");
	}
        catch(Exception e) {
	    e.printStackTrace();
	}
    }

}
      
    

Play microphone to speaker

This is a combination of the previous two programs. An AudioInputStream is prepared for reading from the microphone. A SourceDataLine is prepared for writing to the speaker. The data is copied from the first to the second by reading from the audio input stream and writing to the source data line. The UML class diagram is

The program is:

      
      
import java.io.File;
import java.io.IOException;

     
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.Line.Info;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

public class PlayMicrophone {
    private static final int FRAMES_PER_BUFFER = 1024;

    public static void main(String[] args) throws Exception {
	/*
	 *	We check that there is exactely one command-line
	 *	argument. If not, we display the usage message and
	 *	exit.
	 */

	new PlayMicrophone().playAudio();
    }



    private void out(String strMessage)
    {
	System.out.println(strMessage);
    }

  //This method creates and returns an
  // AudioFormat object for a given set of format
  // parameters.  If these parameters don't work
  // well for you, try some of the other
  // allowable parameter values, which are shown
  // in comments following the declarations.
  private  AudioFormat getAudioFormat(){
    float sampleRate = 44100.0F;
    //8000,11025,16000,22050,44100
    int sampleSizeInBits = 16;
    //8,16
    int channels = 1;
    //1,2
    boolean signed = true;
    //true,false
    boolean bigEndian = false;
    //true,false
    return new AudioFormat(sampleRate,
                           sampleSizeInBits,
                           channels,
                           signed,
                           bigEndian);
  }//end getAudioFormat

    public void playAudio() throws Exception {
	AudioFormat audioFormat;
	TargetDataLine targetDataLine;
	
	audioFormat = getAudioFormat();
	DataLine.Info dataLineInfo =
	    new DataLine.Info(
			      TargetDataLine.class,
			      audioFormat);
	targetDataLine = (TargetDataLine)
	    AudioSystem.getLine(dataLineInfo);

	/*
	Line.Info lines[] = AudioSystem.getTargetLineInfo(dataLineInfo);
	for (int n = 0; n < lines.length; n++) {
	    System.out.println("Target " + lines[n].toString() + " " + lines[n].getLineClass());
	}
	targetDataLine = (TargetDataLine)
	    AudioSystem.getLine(lines[0]);
	*/
	
	targetDataLine.open(audioFormat, 
			    audioFormat.getFrameSize() * FRAMES_PER_BUFFER);
	targetDataLine.start();
	
	playAudioStream(new AudioInputStream(targetDataLine));

	/*
	File soundFile = new File( fileName );
     
	try {
	    // Create a stream from the given file.
	    // Throws IOException or UnsupportedAudioFileException
	    AudioInputStream audioInputStream = AudioSystem.getAudioInputStream( soundFile );
	    // AudioSystem.getAudioInputStream( inputStream ); // alternate audio stream from inputstream
	    playAudioStream( audioInputStream );
	} catch ( Exception e ) {
	    System.out.println( "Problem with file " + fileName + ":" );
	    e.printStackTrace();
	}
	*/
    } // playAudioFile
     
    /** Plays audio from the given audio input stream. */
    public void playAudioStream( AudioInputStream audioInputStream ) {
	// Audio format provides information like sample rate, size, channels.
	AudioFormat audioFormat = audioInputStream.getFormat();
	System.out.println( "Play input audio format=" + audioFormat );
     
	// Open a data line to play our type of sampled audio.
	// Use SourceDataLine for play and TargetDataLine for record.
	DataLine.Info info = new DataLine.Info( SourceDataLine.class, audioFormat );

	Line.Info lines[] = AudioSystem.getSourceLineInfo(info);
	for (int n = 0; n < lines.length; n++) {
	    System.out.println("Source " + lines[n].toString() + " " + lines[n].getLineClass());
	}

	if ( !AudioSystem.isLineSupported( info ) ) {
	    System.out.println( "Play.playAudioStream does not handle this type of audio on this system." );
	    return;
	}
     
	try {
	    // Create a SourceDataLine for play back (throws LineUnavailableException).
	    SourceDataLine dataLine = (SourceDataLine) AudioSystem.getLine( info );
	    // System.out.println( "SourceDataLine class=" + dataLine.getClass() );
     
	    // The line acquires system resources (throws LineAvailableException).
	    dataLine.open( audioFormat,
			   audioFormat.getFrameSize() * FRAMES_PER_BUFFER);
     
	    // Adjust the volume on the output line.
	    if( dataLine.isControlSupported( FloatControl.Type.MASTER_GAIN ) ) {
		FloatControl volume = (FloatControl) dataLine.getControl( FloatControl.Type.MASTER_GAIN );
		volume.setValue( 6.0F );
	    }
     
	    // Allows the line to move data in and out to a port.
	    dataLine.start();
     
	    // Create a buffer for moving data from the audio stream to the line.
	    int bufferSize = (int) audioFormat.getSampleRate() * audioFormat.getFrameSize();
	    bufferSize =  audioFormat.getFrameSize() * FRAMES_PER_BUFFER;
	    System.out.println("Buffer size: " + bufferSize);
	    byte [] buffer = new byte[ bufferSize ];
     
	    // Move the data until done or there is an error.
	    try {
		int bytesRead = 0;
		while ( bytesRead >= 0 ) {
		    bytesRead = audioInputStream.read( buffer, 0, buffer.length );
		    if ( bytesRead >= 0 ) {
			System.out.println( "Play.playAudioStream bytes read=" + bytesRead +
			", frame size=" + audioFormat.getFrameSize() + ", frames read=" + bytesRead / audioFormat.getFrameSize() );
			// Odd sized sounds throw an exception if we don't write the same amount.
			int framesWritten = dataLine.write( buffer, 0, bytesRead );
		    }
		} // while
	    } catch ( IOException e ) {
		e.printStackTrace();
	    }
     
	    System.out.println( "Play.playAudioStream draining line." );
	    // Continues data line I/O until its buffer is drained.
	    dataLine.drain();
     
	    System.out.println( "Play.playAudioStream closing line." );
	    // Closes the data line, freeing any resources such as the audio device.
	    dataLine.close();
	} catch ( LineUnavailableException e ) {
	    e.printStackTrace();
	}
    } // playAudioStream

}



      
    

Where does javaSound get its devices from?

The first program in this chapter showed a list of mixer devices and their attributes. How does Java get this information? In this section we look at JDK 1.7 - OpenJDK will probably be similar. You will need the Java source from Oracle to track through this. Alternatively, move on...

The file jre/lib/resources.jar contains a list of resources used by the JRE runtime. This is a zip file, and contains the file META-INF/services/javax.sound.sampled.spi.MixerProvider. On my system the contents of this file are

	
# last mixer is default mixer
com.sun.media.sound.PortMixerProvider
com.sun.media.sound.DirectAudioDeviceProvider
	
      

The class com.sun.media.sound.PortMixerProvider is in the file java/media/src/share/native/com/sun/media/sound/PortMixerProvider.java on my system. It extends MixerProvider and implements methods such as Mixer.Info[] getMixerInfo. This class stores the device information.

The bulk of the work done done by this class is actually performed by native methods in the C file java/media/src/share/native/com/sun/media/sound/PortMixerProvider.c which implements the two methods nGetNumDevices and nNewPortMixerInfo used by the PortMixerProvider class. Unfortunately, there's ot much joy to be found in this C file, as it just makes calls to the C functions PORT_GetPortMixerCount and PORT_GetPortMixerDescription.

There are three files containing these functions

	
java/media/src/windows/native/com/sun/media/sound/PLATFORM_API_WinOS_Ports.c
java/media/src/solaris/native/com/sun/media/sound/PLATFORM_API_SolarisOS_Ports.c
java/media/src/solaris/native/com/sun/media/sound/PLATFORM_API_LinuxOS_ALSA_Ports.c
	
      

In the file PLATFORM_API_LinuxOS_ALSA_Ports.c you will see the function calls to ALSA as described in the ALSA chapter. These calls fill in information about the ALSA devices for use by JavaSound.

Conclusion

The Java Sound API is well documented. We have shown four simple programs here, but more complex ones are possible. The link to the underlying sound system is briefly discussed.



Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
"Programming and Using Linux Sound - in depth" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/LinuxSound/ .

If you like this book, please contribute using PayPal

Or Flattr me:
Flattr this book