view game/SoundManager.java @ 110:3551b61b3c0b

Increase buffer size a bit.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 03 Mar 2011 23:26:12 +0200
parents dd896bc7352b
children 4c0dec72e2f0
line wrap: on
line source

/*
 * Ristipolku Game Engine
 * (C) Copyright 2011 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
 */
package game;

import java.util.*;
import java.io.*;
import game.*;
import javax.sound.sampled.*;


public class SoundManager extends ThreadGroup
{
    private boolean alive;
    private LinkedList queue;
    private int id;
    private static int poolID;

    private AudioFormat playbackFormat;
    private ThreadLocal localLine;
    private ThreadLocal localBuffer;
    private Object pausedLock;
    private boolean paused;


    public SoundManager(AudioFormat format)
    {
        this(format, getMaxSimultaneousSounds(format));
    }


    public SoundManager(AudioFormat format, int maxSounds)
    {
        super("SoundManagerPool-" + (poolID++));

        int numThreads = Math.min(maxSounds, getMaxSimultaneousSounds(playbackFormat));
        
        System.out.print("SMGR.SoundManager() initializing with " + numThreads +" max sounds\n");
        
        setDaemon(true);
        alive = true;

        playbackFormat = format;
        localLine = new ThreadLocal();
        localBuffer = new ThreadLocal();
        pausedLock = new Object();

        queue = new LinkedList();
        for (int i = 0; i < numThreads; i++)
            new PooledThread().start();

        synchronized (this)
        {
            notifyAll();
        }
    }


    public static int getMaxSimultaneousSounds(AudioFormat playbackFormat)
    {
        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);

        Mixer.Info[] info = AudioSystem.getMixerInfo();
        System.out.print("SMGR.getMaxSimultaneousSounds() mixer information:\n");
        Mixer.Info select = null;
        for (Mixer.Info i : info)
        {
            System.out.print("  - '"+i.getName()+"'\n");
            if (i.getName().equals("Java Sound Audio Engine"))
                select = i;
        }

        Mixer mixer = AudioSystem.getMixer(select);

        Mixer.Info i = mixer.getMixerInfo();
        System.out.print("  * selected='"+i.getName()+"'\n");

        int maxLines = mixer.getMaxLines(lineInfo);
        if (maxLines == AudioSystem.NOT_SPECIFIED)
            maxLines = 8;

        System.out.print("  * maxLines="+maxLines+"\n");

        return maxLines;
    }


    protected void cleanUp()
    {
        System.out.print("SMGR.cleanUp()\n");
        // signal to unpause
        setPaused(false);

        System.out.print("SMGR.cleanUp(): closing mixer\n");

        // close the mixer (stops any running sounds)
        Mixer mixer = AudioSystem.getMixer(null);
        if (mixer.isOpen())
            mixer.close();

        System.out.print("SMGR.cleanUp(): leaving\n");
    }


    public void setPaused(boolean paused)
    {
        if (this.paused != paused)
        {
            synchronized (pausedLock)
            {
                this.paused = paused;
                if (!paused)
                {
                    // restart sounds
                    pausedLock.notifyAll();
                }
            }
        }
    }


    public boolean isPaused()
    {
        return paused;
    }


    public Sound getSound(String filename)
    {
        return getSound(getAudioInputStream(filename));
    }


    public Sound getSound(InputStream is)
    {
        return getSound(getAudioInputStream(is));
    }


    public Sound getSound(AudioInputStream audioStream)
    {
        if (audioStream == null)
            return null;

        // get the number of bytes to read
        int length = (int)(audioStream.getFrameLength() * audioStream.getFormat().getFrameSize());

        // read the entire stream
        byte[] samples = new byte[length];
        DataInputStream is = new DataInputStream(audioStream);
        try {
            is.readFully(samples);
            is.close();
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }

        // return the samples
        return new Sound(samples);
    }


    public AudioInputStream getAudioInputStream(String filename)
    {
        ResourceLoader res = new ResourceLoader(filename);
        if (res == null || res.getStream() == null)
        {
            System.out.print("Could not load audio resource '"+ filename +"'.\n");
            return null;
        }
        try {
            return getAudioInputStream(res.getStream());
        }
        catch (Exception ex)
        {
            System.out.print("Could not get AudioInputStream for '"+ filename +"'\n");
            return null;
        }
    }


    public AudioInputStream getAudioInputStream(InputStream is)
    {
        try {
            if (!is.markSupported())
                is = new BufferedInputStream(is);

            // open the source stream
            AudioInputStream source = AudioSystem.getAudioInputStream(is);

            // convert to playback format
            return AudioSystem.getAudioInputStream(playbackFormat, source);
        }

        catch (UnsupportedAudioFileException ex) {
            ex.printStackTrace();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
        catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }

        return null;
    }


    public InputStream play(Sound sound)
    {
        InputStream is;
        if (sound != null)
        {
            is = new ByteArrayInputStream(sound.getSamples());
            return play(is);
        }
        return null;
    }


    public InputStream play(InputStream is)
    {
        System.out.print("SMGR.play(is="+is+")\n");
        if (is != null)
        {
            runTask(new SoundPlayer(is));
        }
        return is;
    }


    protected void threadStarted()
    {
        synchronized (this)
        {
            try {
                wait();
            }
            catch (InterruptedException ex) { }
        }


        // use a short, 100ms (1/10th sec) buffer for filters that
        // change in real-time
        int bufferSize = playbackFormat.getFrameSize() * Math.round(playbackFormat.getSampleRate() / 10);

        // create, open, and start the line
        SourceDataLine line;
        DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, playbackFormat);
        
        System.out.print("SMGR.threadStarted(): "+lineInfo.toString()+"\n");

        try {
            line = (SourceDataLine) AudioSystem.getLine(lineInfo);
            line.open(playbackFormat, bufferSize);
        }
        catch (LineUnavailableException ex)
        {
            System.out.print("SMGR.threadStarted() line unavailable!\n");
            // the line is unavailable - signal to end this thread
            Thread.currentThread().interrupt();
            return;
        }
        
        // Change volume
        Control.Type ct = FloatControl.Type.MASTER_GAIN;
        if (line.isControlSupported(ct))
        {
            FloatControl c = (FloatControl) line.getControl(ct);
            c.setValue(-20f);
        }

        line.start();

        // create the buffer
        byte[] buffer = new byte[bufferSize];

        // set this thread's locals
        localLine.set(line);
        localBuffer.set(buffer);
    }


    protected void threadStopped()
    {
        System.out.print("SMGR.threadStopped()\n");
        SourceDataLine line = (SourceDataLine) localLine.get();
        if (line != null)
        {
            line.drain();
            line.close();
        }
    }


    protected class SoundPlayer implements Runnable
    {
        private InputStream source;

        public SoundPlayer(InputStream source)
        {
            this.source = source;
        }

        public void run()
        {
            // get line and buffer from ThreadLocals
            SourceDataLine line = (SourceDataLine) localLine.get();
            byte[] buffer = (byte[])localBuffer.get();
            
            if (line == null || buffer == null)
                return;

            // copy data to the line
            try {
                boolean playing = true;
                while (playing) {
                    // if paused, wait until unpaused
                    synchronized (pausedLock)
                    {
                        if (paused) {
                            try {
                                pausedLock.wait();
                            }
                            catch (InterruptedException ex) {
                                return;
                            }
                        }
                    }
                    
                    // copy data
                    int bufPos = 0;
                    while (bufPos < buffer.length && playing)
                    {
                        int res = source.read(buffer, bufPos, buffer.length - bufPos);
                        if (res != -1)
                            bufPos += res;
                        else
                            playing = false;
                    }
                    if (playing)
                        line.write(buffer, 0, bufPos);
                }
            }

            catch (IOException ex) {
                ex.printStackTrace();
            }

        }
    }

    public synchronized void runTask(Runnable task)
    {
        if (!alive)
        {
            throw new IllegalStateException();
        }
        if (task != null)
        {
            queue.add(task);
            notify();
        }

    }

    protected synchronized Runnable getTask() throws InterruptedException
    {
        while (queue.size() == 0)
        {
            if (!alive)
                return null;

            wait();
        }
        return (Runnable) queue.removeFirst();
    }


    public synchronized void close()
    {
        System.out.print("SMGR.close()\n");

        if (alive)
        {
            System.out.print("SMGR.close(): alive queue clear\n");
            // Clear queue
            alive = false;
            queue.clear();
            interrupt();
        }

        cleanUp();
        System.out.print("SMGR.close(): leaving\n");
    }


    public void join()
    {
        System.out.print("SMGR.join()\n");
        cleanUp();

        synchronized (this)
        {
            alive = false;
            notifyAll();
        }

        Thread[] threads = new Thread[activeCount()];
        int count = enumerate(threads);
        for (int i = 0; i < count; i++)
        {
            try {
                threads[i].join();
            }
            catch (InterruptedException ex)
            {
            }
        }
    }


    private class PooledThread extends Thread
    {
        public PooledThread()
        {
            super(SoundManager.this, "SoundManagerPool-" + (id++));
        }

        public void run()
        {
            threadStarted();

            while (!isInterrupted()) {
                Runnable task = null;
                try {
                    task = getTask();
                }
                catch (InterruptedException ex)
                {
                }

                if (task == null)
                    break;

                try {
                    task.run();
                }
                catch (Throwable t) {
                    uncaughtException(this, t);
                }
            }
            threadStopped();
        }
    }
}