代码之家  ›  专栏  ›  技术社区  ›  Thomas

在Java中可靠播放短声音

  •  4
  • Thomas  · 技术社区  · 14 年前

    我正在编写一些Java代码,这些代码基本上只是播放一个简短的wav文件——我的意思是“短”。(对于使用ubuntu的用户,我使用的文件是/usr/share/sounds/generic.wav。)

    问题是,我似乎不知道如何播放这个示例 可靠地 也就是说,在我的所有尝试中,我可以让我的程序在5次左右的4次中播放声音,但绝不能100%。

    作为一个独立的程序,这是最有效的:

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

    (注意,代码甚至不调用clip.stop())但是即使使用了那个代码,如果我连续运行它几次,迟早会有一次没有任何声音播放的运行,但也没有例外。

    我尝试过的变化:

    1)将音频文件加载到字节数组中并将其传递给clip.open

    2)将LineListener附加到剪辑以等待停止事件

    再加上一些随机的尝试,但是到目前为止我还没有创建出有效的代码 每一次 .

    我还注意到以下错误: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4434125 但是我使用Java 1.6,报告声称Java 1.5或更晚的事情应该是好的。

    有什么想法吗?是Pulseaudio吗?

    6 回复  |  直到 8 年前
        1
  •  2
  •   Dean J    14 年前

    我在贝斯音箱里运气很好。

    它是以本机方式编写的,所以它可以中断一次写操作,在任何地方运行,但是它可以在windows、os/x和linux上运行,这已经足够满足我的需要了。

        2
  •  2
  •   Matthew    12 年前

    这里最直接的方法可能是以毫秒为单位获得准确的cliplength(四舍五入),并使用它让播放声音的线程睡眠。确保使用synchronized以避免非法监视器状态异常。

    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();
     }
    
        3
  •  2
  •   randers    8 年前

    我在应用程序中使用了以下代码(尽管它使用了applet的newAudioClip()方法),但运气不错:

    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();
    

    我从Java开始:从控制结构到对象,第四版由Tony Gaddis,Addison Wesley,ISBN-13983-013-608020-6

        4
  •  1
  •   Phil Freihofner    13 年前

    你要多久才能重新运行你的通话来播放这个片段?我正忙着制作一个“风铃”,其中有六个bell.wav文件作为剪辑加载,并且在播放失败的声音时遇到了一些并发问题。我想出了一个方案,实际上是用每个触发器在一个新线程上创建一个新的剪辑,而不是尝试重新运行现有的剪辑,这是可行的,但我认为它本质上是低效的。(如果一个人很难创建新的线程,那么你最好运行流,避免在播放之前加载整个线程的开销。我必须测试这个理论。)顺便说一句:如果我没记错的话,我一次可以运行近100个线程。独立线程方法允许wav文件完成并“重叠”,而不是相互切断。在JProfiler上观看很有趣!

    有些命令可以停止声音并将起点移回起点。你在这么做吗?这可能允许在剪辑完成之前调用它的情况下重用。

        5
  •  0
  •   user489041    14 年前

    这是我一直在使用的一些代码。我编辑它是因为这里有很多你不需要的东西,所以很抱歉,如果它有点乱。

    呼叫

    
    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); } } } }
        6
  •  0
  •   Thomas    13 年前

    我现在怀疑我的测试程序失败的原因是时间问题。要么我尝试在样本完全加载之前播放短声音,要么程序终止得太快。产生这种怀疑的原因是,如果我稍微像这样更改上面的代码:

    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();
    }
    

    然后每次我击球时,短音都会正确播放 进入 .