Надежно воспроизводит короткий звук на Java - PullRequest
4 голосов
/ 25 февраля 2010

Я пытаюсь написать некоторый код Java, который в основном просто воспроизводит короткий WAV-файл - с «коротким» я подразумеваю доли секунды. (Файл, который я использую, находится по адресу /usr/share/sounds/generic.wav для тех, кто использует Ubuntu.)

Проблема в том, что я не могу понять, как воспроизвести этот семпл надежно , т. Е. При всех моих попытках я могу заставить свою программу воспроизводить звук в 4 из 5 раз или около того, но никогда не 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?

Ответы [ 6 ]

2 голосов
/ 25 июня 2012

Самый простой подход здесь, вероятно, состоит в том, чтобы просто получить точную длину клипа в миллисекундах (округление в большую сторону) и использовать ее, чтобы усыпить поток, воспроизводящий звук в течение продолжительного времени. Убедитесь, что вы используете синхронизированный, чтобы избежать 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 голосов
/ 01 декабря 2010

Мне повезло, используя следующий код в приложении (даже если он использует метод 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-е издание Тони Гаддиса, Эддисон Уэсли, ISBN-13 978-0-13-608020-6

2 голосов
/ 25 февраля 2010

Мне очень повезло с аудио библиотекой BASS.

Он написан изначально, поэтому он прерывает однократную запись, запускается где угодно, но он будет работать в Windows, OS / X и Linux, чего достаточно для моих нужд.

1 голос
/ 12 июня 2011

Теперь я подозреваю, что причина, по которой моя тестовая программа потерпела неудачу, была проблема синхронизации. Либо я пытался воспроизвести короткий звук до полной загрузки сэмплов, либо программа слишком быстро остановилась. Причиной такого подозрения является то, что если я немного изменю приведенный выше код примерно так:

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

тогда короткий звук воспроизводится правильно каждый раз, когда я нажимаю ввод .

1 голос
/ 26 апреля 2011

Как быстро вы перезапускаете свои звонки, чтобы воспроизвести клип? Я возился с созданием «перезвона ветра», в котором было загружено шесть файлов WAV-колоколов в виде клипов, и у меня были некоторые проблемы с параллелизмом при вызовах для воспроизведения сбойных звуков. Я придумал схему, которая фактически создала новый клип в новом потоке с каждым триггером, а не пытался перезапустить существующие клипы, и это сработало, но я думаю, что это по своей сути неэффективно. (Если у вас все равно возникнут проблемы с созданием новых потоков, то, возможно, вы могли бы с таким же успехом запускать потоки и избегать непроизводительных расходов на загрузку всего перед игрой. Я должен проверить эту теорию.) Кстати: я смог работать близко к 100 потоков одновременно, если я правильно помню. Подход с использованием отдельных потоков позволяет wav-файлам завершать и «перекрывать», а не обрезать друг друга. Весело смотреть на JProfiler!

Существуют команды, которые останавливают звук и перемещают начальную точку обратно в начало. Ты это делаешь? Это может позволить повторное использование в ситуациях, когда клип вызывается до его завершения.

0 голосов
/ 01 декабря 2010

Вот код, который я использовал. Я отредактировал его, потому что здесь было много других вещей, которые вам не нужны, так что извините, если это немного грязно.

Вызов


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 = "<br>";
        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);
            }
        }
    }
}


Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...