简体   繁体   中英

Unable to play sounds simultaneously

Im trying to play six audio tracks simultaneously on the click of a jbutton, but upon click it plays the first track and waits until it finishes to play the second track, and so on. Here is my code

 button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == button) {
                System.out.println("Button Pressed");
                AudioPlayerExample2 player1 = new AudioPlayerExample2();
                AudioPlayerExample2 player2 = new AudioPlayerExample2();
                AudioPlayerExample2 player3 = new AudioPlayerExample2();
                AudioPlayerExample2 player4 = new AudioPlayerExample2();
                AudioPlayerExample2 player5 = new AudioPlayerExample2();
                AudioPlayerExample2 player6 = new AudioPlayerExample2();
                player1.play(track1);
                player2.play(track2);
                player3.play(track3);
                player4.play(track4);
                player5.play(track5);
                player6.play(track6);
            }
        }
    });

and the audio player imported

public class AudioPlayerExample2 {

private static final int BUFFER_SIZE = 4096;


public void play(String audioFilePath) {
    File audioFile = new File(audioFilePath);
    try {
        AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile);

        AudioFormat format = audioStream.getFormat();

        DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

        SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);

        audioLine.open(format);

        audioLine.start();

        System.out.println("Playback started.");

        byte[] bytesBuffer = new byte[BUFFER_SIZE];
        int bytesRead = -1;

        while ((bytesRead = audioStream.read(bytesBuffer)) != -1) {
            audioLine.write(bytesBuffer, 0, bytesRead);
        }

        audioLine.drain();
        audioLine.close();
        audioStream.close();

        System.out.println("Playback completed.");

    } catch (UnsupportedAudioFileException ex) {
        System.out.println("The specified audio file is not supported.");
        ex.printStackTrace();
    } catch (LineUnavailableException ex) {
        System.out.println("Audio line for playing back is unavailable.");
        ex.printStackTrace();
    } catch (IOException ex) {
        System.out.println("Error playing the audio file.");
        ex.printStackTrace();
    }
}

public static void main(String[] args) {
    String audioFilePath = "";
    AudioPlayerExample2 player = new AudioPlayerExample2();
    player.play(audioFilePath);
}}

While the track is playing, the button also remains clicked, so I am unable to use my volume jslider as well. Thanks for the help!

The way you've written the play method it will block until a stream has completely played - meaning the streams will play one after the other. One option is to fork a new thread for each stream. This will avoid the blocking problem but introduces another problem and that is the threads will all be in a race to startup. This means the streams will not all necessarily start at the exact same time (although you could use a signal to get them pretty close to synchronized).

A better approach I think is to use read from all of the files and write to one SourceDataLine all in a single thread. This means you have to manually mix the signals together yourself. Assuming all of your files have the same sample rate and bit depth it is not too difficult. I've assumed 16-bit samples. If your files are different then you can figure out how to deal with that.

public void play(String[] audioFilePath) {
    int numStreams = audioFilePath.length;

    // Open all of the file streams
    AudioInputStream[] audioStream = new AudioInputStream[numStreams];
    for (int i = 0; i < numStreams; i++)
    audioStream[i] = AudioSystem.getAudioInputStream(audioFile);

    // Open the audio line.
    AudioFormat format = audioStream[0].getFormat();
    DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
    SourceDataLine audioLine = (SourceDataLine) AudioSystem.getLine(info);
    audioLine.open(format);
    audioLine.start();

    while (true) {
        // Read a buffer from each stream and mix into an array of
        // doubles.
        byte[] bytesBuffer = new byte[BUFFER_SIZE];
        double[] mixBuffer = new double[BUFFER_SIZE/2];
        int maxSamplesRead = -1;
        for (int i = 0 ; i < numStreams; i++)
        {
            int bytesRead = audioStream.read(bytesBuffer);
            if (bytesRead != -1) {
                int samplesRead = bytesRead/2;
                if (samplesRead > maxSamplesRead) {
                    maxSamplesRead = samplesRead;
                }
                for (int j = 0 ; j < bytesRead/2 ; j++) {
                    double sample = ((bytesBuffer[j*2] << 8) | bytesBuffer[j*2+1]) / 32768.0;
                    mixBuffer[j] += sample;
                }
            }
        }

        // Convert the mixed samples back into a byte array and play.
        if (maxSamplesRead > 0) {
            for (int i = 0; i < maxSamplesRead; i++) {
                // rescale data between -1 and 1
                mixBuffer[i] /= numStreams;

                // and now back to 16-bit
                short sample16 = (short)(mixBuffer * 32768);

                // and back to bytes
                bytesBuffer[i*2]   = (byte)(sample16 >> 8);
                bytesBuffer[i*2+1] = (byte)(sample16);
            }
            audioLine.write(bytesBuffer, 0, maxSamplesRead*2);
        }
        else {
            // All of the streams are empty so cleanup.
            audioLine.drain();
            audioLine.close();
            for (int i = 0 ; i < numStreams; i++)
                audioStream[i].close();
            break;
        }
    }
}

And call it by passing an array of filenames (which I would recommend replacing track1, track2, etc... with)

button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == button) {
                System.out.println("Button Pressed");
                AudioPlayerExample2 player = new AudioPlayerExample2(allTracks);
            }
        }
    });

A third alternative that might be even better is to derive a class from InputStream that supports multiple files and does the mixing internally. With this approach you could use most of your existing AudioPlayerExample2 class but there would be only one instance of it. It would be a bit more involved than I care to get right now.

PS I haven't tried compiling any of this. I'm just trying to get across the idea.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM