Reliable short sound reproduction in Java

I use tyring to write Java code that basically just plays a short .wav file - with β€œshort” I mean a split second. (The file I'm using is located in /usr/share/sounds/generic.wav for those of you using Ubuntu.)

The problem is that I cannot figure out how to reproduce this sample correctly, i.e. in all my attempts, I can make my program play sound 4 out of 5 times or so, but not 100%.

This is what worked best in a standalone program:

File soundFile = new File("/usr/share/sounds/generic.wav"); Clip clip = AudioSystem.getClip(); AudioInputStream inputStream = AudioSystem.getAudioInputStream(soundFile); clip.open(inputStream); clip.start(); 

(Note that the code does not even call clip.stop ()). But even with this, if I run it a couple of times in a row, sooner or later it will be executed without any sound, but there are no exceptions.

Variations I tried:

1) Uploading an audio file to a byte array and transferring it to clip.open

2) Attaching a LineListener to a clip to wait for STOP events

plus a few random attempts, but so far I have not been able to create code that works every time.

I also know the following error: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4434125 , but I use Java 1.6, and the report states that everything should be fine with Java 1.5 or later.

Any ideas? Is this PulseAudio?

+4
source share
6 answers

I suspect the reason my test program failed was a time issue. Either I tried to play a short sound before the samples were fully loaded, or the program ended too quickly. The reason for this suspicion is that if I change the above code a little like this:

 File soundFile = new File("/usr/share/sounds/generic.wav"); Clip clip = AudioSystem.getClip(); AudioInputStream inputStream = AudioSystem.getAudioInputStream(soundFile); clip.open(inputStream); while (System.in.read() == '\n') { clip.stop(); clip.setFramePosition(0); clip.start(); } 

then a short sound plays correctly every time I press enter .

0
source

I got lucky with the BASS Audio Library.

It was originally written, so it splits once per record, runs anywhere, but it will work on Windows, OS / X and Linux, which is enough for my needs.

+2
source

I was fortunate enough to use the following code in the application (although it uses the Applet newAudioClip () method):

 AudioClip clip; File fileClip = new File("/usr/share/sounds/generic.wav"); URL url = null; try { URI uri = fileClip.toURI(); url = uri.toURL(); clip = Applet.newAudioClip(url); } catch (MalformedURLException e){} clip.play(); 

I got this method: Starting with Java: from Control Structures Through Objects, 4th Edition Tony Gaddis, Addison Wesley, ISBN-13 978-0-13-608020-6

+2
source

The easiest approach is to simply get the exact clip length in milliseconds (rounding) and use this to sleep the stream reproducing the sound for the duration. Make sure you use synchronization to avoid IllegalMonitorStateExceptions.

 synchronized(clip){ clip.start(); try{ double clipLength = audioParams.getFrameLength() / audioParams.getFormat().getFrameRate(); clip.wait(java.lang.Math.round(clipLength +.5)*1000); } catch (InterruptedException e) { System.out.println( e.getMessage() ); } c.stop(); } 
+2
source

How fast do you repeat your calls to play a clip? I was busy creating a "wind sound" in which there were six .wav files loaded as Clips and there were problems with concurrency with calls to play sounds. I came up with a scheme that actually created a new clip on a new thread with each trigger, rather than trying to restart the existing clips, and it worked, but I believe that it is essentially ineffective. (If you have a problem creating new threads anyway, you might also be able to run threads and avoid the overhead of downloading everything before the game. I need to check this theory.) By the way: I managed to get close to 100 threads at a time if I remember correctly. A separate approach to the stream allows wav files to complete and "overlap" rather than shrinking each other. Have fun looking at JProfiler!

There are commands that stop the sound and move the starting point back to the beginning. You do it? This may allow reuse in situations where a clip is called before it is completed.

+1
source

Here is the code I used. I edited it because there was a lot of other stuff you didn't need, so sorry if it's a little dirty.

Call

 Wav player = new Wav("sound.wav"); player.playAudio(player.getBytes());
Wav player = new Wav("sound.wav"); player.playAudio(player.getBytes()); 
 import java.applet.Applet; import java.applet.AudioClip; import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; import java.io.*; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import javax.sound.sampled.*; /** * This class handles the reading, writing, and playing of wav files. It is * also capable of converting the file to its raw byte [] form. * * based on code by Evan Merz */ public class Wav { ByteArrayOutputStream byteArrayOutputStream; AudioFormat audioFormat; TargetDataLine targetDataLine; AudioInputStream audioInputStream; SourceDataLine sourceDataLine; float frequency = 8000.0F; //8000,11025,16000,22050,44100 int samplesize = 16; private String myPath; private long myChunkSize; private long mySubChunk1Size; private int myFormat; private long myChannels; private long mySampleRate; private long myByteRate; private int myBlockAlign; private int myBitsPerSample; private long myDataSize; // I made this public so that you can toss whatever you want in here // maybe a recorded buffer, maybe just whatever you want public byte[] myData; public Wav() { myPath = ""; } // constructor takes a wav path public Wav(String tmpPath) { myPath = tmpPath; } // get set for the Path property public String getPath() { return myPath; } public void setPath(String newPath) { myPath = newPath; } // read a wav file into this class public boolean read() { DataInputStream inFile = null; myData = null; byte[] tmpLong = new byte[4]; byte[] tmpInt = new byte[2]; try { inFile = new DataInputStream(new FileInputStream(myPath)); //System.out.println("Reading wav file...\n"); // for debugging only String chunkID = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); inFile.read(tmpLong); // read the ChunkSize myChunkSize = byteArrayToLong(tmpLong); String format = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); // print what we've read so far //System.out.println("chunkID:" + chunkID + " chunk1Size:" + myChunkSize + " format:" + format); // for debugging only String subChunk1ID = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); inFile.read(tmpLong); // read the SubChunk1Size mySubChunk1Size = byteArrayToLong(tmpLong); inFile.read(tmpInt); // read the audio format. This should be 1 for PCM myFormat = byteArrayToInt(tmpInt); inFile.read(tmpInt); // read the # of channels (1 or 2) myChannels = byteArrayToInt(tmpInt); inFile.read(tmpLong); // read the samplerate mySampleRate = byteArrayToLong(tmpLong); inFile.read(tmpLong); // read the byterate myByteRate = byteArrayToLong(tmpLong); inFile.read(tmpInt); // read the blockalign myBlockAlign = byteArrayToInt(tmpInt); inFile.read(tmpInt); // read the bitspersample myBitsPerSample = byteArrayToInt(tmpInt); // print what we've read so far //System.out.println("SubChunk1ID:" + subChunk1ID + " SubChunk1Size:" + mySubChunk1Size + " AudioFormat:" + myFormat + " Channels:" + myChannels + " SampleRate:" + mySampleRate); // read the data chunk header - reading this IS necessary, because not all wav files will have the data chunk here - for now, we're just assuming that the data chunk is here String dataChunkID = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); inFile.read(tmpLong); // read the size of the data myDataSize = byteArrayToLong(tmpLong); // read the data chunk myData = new byte[(int) myDataSize]; inFile.read(myData); // close the input stream inFile.close(); } catch (Exception e) { return false; } return true; // this should probably be something more descriptive } // return a printable summary of the wav file public String getSummary() { //String newline = System.getProperty("line.separator"); String newline = " 
"; String summary = "Format: " + myFormat + newline + "Channels: " + myChannels + newline + "SampleRate: " + mySampleRate + newline + "ByteRate: " + myByteRate + newline + "BlockAlign: " + myBlockAlign + newline + "BitsPerSample: " + myBitsPerSample + newline + "DataSize: " + myDataSize + ""; return summary; } public byte[] getBytes() { read(); return myData; } /** * Plays back audio stored in the byte array using an audio format given by * freq, sample rate, ect. * @param data The byte array to play */ public void playAudio(byte[] data) { try { byte audioData[] = data; //Get an input stream on the byte array containing the data InputStream byteArrayInputStream = new ByteArrayInputStream(audioData); AudioFormat audioFormat = getAudioFormat(); audioInputStream = new AudioInputStream(byteArrayInputStream, audioFormat, audioData.length / audioFormat.getFrameSize()); DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat); sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo); sourceDataLine.open(audioFormat); sourceDataLine.start(); //Create a thread to play back the data and start it running. It will run \ //until all the data has been played back. Thread playThread = new Thread(new PlayThread()); playThread.start(); } catch (Exception e) { System.out.println(e); } } /** * 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. * @return */ private AudioFormat getAudioFormat() { float sampleRate = frequency; //8000,11025,16000,22050,44100 int sampleSizeInBits = samplesize; //8,16 int channels = 1; //1,2 boolean signed = true; //true,false boolean bigEndian = false; //true,false //return new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, 8000.0f, 8, 1, 1, //8000.0f, false ); return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian); } // =========================== // CONVERT BYTES TO JAVA TYPES // =========================== // these two routines convert a byte array to a unsigned short public static int byteArrayToInt(byte[] b) { int start = 0; int low = b[start] & 0xff; int high = b[start + 1] & 0xff; return (int) (high > 8) & 0x000000FF); b[2] = (byte) ((i >> 16) & 0x000000FF); b[3] = (byte) ((i >> 24) & 0x000000FF); return b; } // convert a short to a byte array public static byte[] shortToByteArray(short data) { return new byte[]{(byte) (data & 0xff), (byte) ((data >>> 8) & 0xff)}; } /** * Inner class to play back the data that was saved */ class PlayThread extends Thread { byte tempBuffer[] = new byte[10000]; public void run() { try { int cnt; //Keep looping until the input // read method returns -1 for // empty stream. while ((cnt = audioInputStream.read(tempBuffer, 0, tempBuffer.length)) != -1) { if (cnt > 0) { //Write data to the internal // buffer of the data line // where it will be delivered // to the speaker. sourceDataLine.write(tempBuffer, 0, cnt); } } //Block and wait for internal // buffer of the data line to // empty. sourceDataLine.drain(); sourceDataLine.close(); } catch (Exception e) { System.out.println(e); System.exit(0); } } } }
import java.applet.Applet; import java.applet.AudioClip; import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; import java.io.*; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import javax.sound.sampled.*; /** * This class handles the reading, writing, and playing of wav files. It is * also capable of converting the file to its raw byte [] form. * * based on code by Evan Merz */ public class Wav { ByteArrayOutputStream byteArrayOutputStream; AudioFormat audioFormat; TargetDataLine targetDataLine; AudioInputStream audioInputStream; SourceDataLine sourceDataLine; float frequency = 8000.0F; //8000,11025,16000,22050,44100 int samplesize = 16; private String myPath; private long myChunkSize; private long mySubChunk1Size; private int myFormat; private long myChannels; private long mySampleRate; private long myByteRate; private int myBlockAlign; private int myBitsPerSample; private long myDataSize; // I made this public so that you can toss whatever you want in here // maybe a recorded buffer, maybe just whatever you want public byte[] myData; public Wav() { myPath = ""; } // constructor takes a wav path public Wav(String tmpPath) { myPath = tmpPath; } // get set for the Path property public String getPath() { return myPath; } public void setPath(String newPath) { myPath = newPath; } // read a wav file into this class public boolean read() { DataInputStream inFile = null; myData = null; byte[] tmpLong = new byte[4]; byte[] tmpInt = new byte[2]; try { inFile = new DataInputStream(new FileInputStream(myPath)); //System.out.println("Reading wav file...\n"); // for debugging only String chunkID = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); inFile.read(tmpLong); // read the ChunkSize myChunkSize = byteArrayToLong(tmpLong); String format = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); // print what we've read so far //System.out.println("chunkID:" + chunkID + " chunk1Size:" + myChunkSize + " format:" + format); // for debugging only String subChunk1ID = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); inFile.read(tmpLong); // read the SubChunk1Size mySubChunk1Size = byteArrayToLong(tmpLong); inFile.read(tmpInt); // read the audio format. This should be 1 for PCM myFormat = byteArrayToInt(tmpInt); inFile.read(tmpInt); // read the # of channels (1 or 2) myChannels = byteArrayToInt(tmpInt); inFile.read(tmpLong); // read the samplerate mySampleRate = byteArrayToLong(tmpLong); inFile.read(tmpLong); // read the byterate myByteRate = byteArrayToLong(tmpLong); inFile.read(tmpInt); // read the blockalign myBlockAlign = byteArrayToInt(tmpInt); inFile.read(tmpInt); // read the bitspersample myBitsPerSample = byteArrayToInt(tmpInt); // print what we've read so far //System.out.println("SubChunk1ID:" + subChunk1ID + " SubChunk1Size:" + mySubChunk1Size + " AudioFormat:" + myFormat + " Channels:" + myChannels + " SampleRate:" + mySampleRate); // read the data chunk header - reading this IS necessary, because not all wav files will have the data chunk here - for now, we're just assuming that the data chunk is here String dataChunkID = "" + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte() + (char) inFile.readByte(); inFile.read(tmpLong); // read the size of the data myDataSize = byteArrayToLong(tmpLong); // read the data chunk myData = new byte[(int) myDataSize]; inFile.read(myData); // close the input stream inFile.close(); } catch (Exception e) { return false; } return true; // this should probably be something more descriptive } // return a printable summary of the wav file public String getSummary() { //String newline = System.getProperty("line.separator"); String newline = "
"; String summary = "Format: " + myFormat + newline + "Channels: " + myChannels + newline + "SampleRate: " + mySampleRate + newline + "ByteRate: " + myByteRate + newline + "BlockAlign: " + myBlockAlign + newline + "BitsPerSample: " + myBitsPerSample + newline + "DataSize: " + myDataSize + ""; return summary; } public byte[] getBytes() { read(); return myData; } /** * Plays back audio stored in the byte array using an audio format given by * freq, sample rate, ect. * @param data The byte array to play */ public void playAudio(byte[] data) { try { byte audioData[] = data; //Get an input stream on the byte array containing the data InputStream byteArrayInputStream = new ByteArrayInputStream(audioData); AudioFormat audioFormat = getAudioFormat(); audioInputStream = new AudioInputStream(byteArrayInputStream, audioFormat, audioData.length / audioFormat.getFrameSize()); DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat); sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo); sourceDataLine.open(audioFormat); sourceDataLine.start(); //Create a thread to play back the data and start it running. It will run \ //until all the data has been played back. Thread playThread = new Thread(new PlayThread()); playThread.start(); } catch (Exception e) { System.out.println(e); } } /** * 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. * @return */ private AudioFormat getAudioFormat() { float sampleRate = frequency; //8000,11025,16000,22050,44100 int sampleSizeInBits = samplesize; //8,16 int channels = 1; //1,2 boolean signed = true; //true,false boolean bigEndian = false; //true,false //return new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, 8000.0f, 8, 1, 1, //8000.0f, false ); return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian); } // =========================== // CONVERT BYTES TO JAVA TYPES // =========================== // these two routines convert a byte array to a unsigned short public static int byteArrayToInt(byte[] b) { int start = 0; int low = b[start] & 0xff; int high = b[start + 1] & 0xff; return (int) (high > 8) & 0x000000FF); b[2] = (byte) ((i >> 16) & 0x000000FF); b[3] = (byte) ((i >> 24) & 0x000000FF); return b; } // convert a short to a byte array public static byte[] shortToByteArray(short data) { return new byte[]{(byte) (data & 0xff), (byte) ((data >>> 8) & 0xff)}; } /** * Inner class to play back the data that was saved */ class PlayThread extends Thread { byte tempBuffer[] = new byte[10000]; public void run() { try { int cnt; //Keep looping until the input // read method returns -1 for // empty stream. while ((cnt = audioInputStream.read(tempBuffer, 0, tempBuffer.length)) != -1) { if (cnt > 0) { //Write data to the internal // buffer of the data line // where it will be delivered // to the speaker. sourceDataLine.write(tempBuffer, 0, cnt); } } //Block and wait for internal // buffer of the data line to // empty. sourceDataLine.drain(); sourceDataLine.close(); } catch (Exception e) { System.out.println(e); System.exit(0); } } } }
0
source

Source: https://habr.com/ru/post/1302384/


All Articles