【问题标题】:Reliably playing a short sound in Java在 Java 中可靠地播放短声音
【发布时间】:2016-01-19 04:13:25
【问题描述】:

我很想写一些基本上只是播放一个短的 .wav 文件的 Java 代码——“短”是指几分之一秒。 (对于那些使用 Ubuntu 的人,我使用的文件位于 /usr/share/sounds/generic.wav。)

问题是,我似乎无法弄清楚如何可靠地播放该样本,也就是说,在我所有的尝试中,我都可以让我的程序以五分之四的方式播放声音次左右,但绝不是 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 附加到剪辑以等待 STOP 事件

加上一些随机试验,但到目前为止,我还没有设法创建每次都有效的代码

我也知道以下错误:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4434125 但我使用的是 Java 1.6,并且报告声称 Java 1.5 或更高版本应该没问题。

有什么想法吗?是 PulseAudio 吗?

【问题讨论】:

    标签: java audio


    【解决方案1】:

    BASS 音频库让我很幸运。

    它是原生编写的,因此它打破了一次写入、随处运行,但它可以在 Windows、OS/X 和 Linux 上运行,这足以满足我的需求。

    【讨论】:

    • 谢谢!一个外部库可能是一种可行的方法——不过,如果可能的话,我宁愿保持全 Java。
    【解决方案2】:

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

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

    然后每次我按 enter 时都会正确播放短声音。

    【讨论】:

      【解决方案3】:

      这里最直接的方法可能是以毫秒为单位获取确切的剪辑长度(四舍五入)并使用它来休眠播放声音的线程持续时间。确保使用 synchronized 以避免 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();
       }
      

      【讨论】:

        【解决方案4】:

        我很幸运在应用程序中使用了以下代码(即使它使用了 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 开始:从控制结构到对象,第 4 版,作者 Tony Gaddis,Addison Wesley,ISBN-13 978-0-13-608020-6

        【讨论】:

          【解决方案5】:

          您重新运行呼叫以播放剪辑的速度有多快?我正在制作一个“风铃”,其中有六个作为剪辑加载的 .wav 文件,并且在调用播放声音失败时遇到了一些并发问题。我想出了一个方案,它实际上在每个触发器的新线程上制作了一个新剪辑,而不是尝试重新运行现有剪辑并且有效,但我认为它本质上是低效的。 (如果无论如何都要费心制作新线程,那么也许您最好运行流并避免在播放之前加载整个线程的开销。我必须测试这个理论。)顺便说一句:我能够运行接近如果我没记错的话,一次100个线程。单独的线程方法允许 wav 文件完成和“重叠”,而不是相互切断。在 JProfiler 上观看很有趣!

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

          【讨论】:

          • 我不是要播放多个声音,只是一个长度很短(不到一秒)的波文件。上面的代码几乎就是我想做的所有事情——我不确定我是否尝试在额外的线程中加载剪辑以避免竞争条件,但实际上可能是这样。不过,与此同时,我已经着手解决其他问题。
          【解决方案6】:

          这是我一直在使用的一些代码。我编辑了它,因为这里有很多其他你不需要的东西,如果有点乱,请见谅。

          打电话

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

          【讨论】:

            猜你喜欢
            • 2012-05-06
            • 1970-01-01
            • 2012-12-10
            • 1970-01-01
            • 1970-01-01
            • 2011-05-25
            • 1970-01-01
            • 2012-06-21
            • 2010-09-06
            相关资源
            最近更新 更多