/* * BasicPlayer. * * JavaZOOM : jlgui@javazoom.net * http://www.javazoom.net * *----------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *---------------------------------------------------------------------- */ package javazoom.jlgui.basicplayer; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Control; import javax.sound.sampled.DataLine; import javax.sound.sampled.FloatControl; import javax.sound.sampled.Line; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.UnsupportedAudioFileException; import javazoom.spi.PropertiesContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tritonus.share.sampled.TAudioFormat; import org.tritonus.share.sampled.file.TAudioFileFormat; /** * BasicPlayer is a threaded simple player class based on JavaSound API. * It has been successfully tested under J2SE 1.3.x, 1.4.x and 1.5.x. */ public class BasicPlayer implements BasicController, Runnable { public static int EXTERNAL_BUFFER_SIZE = 4000 * 4; public static int SKIP_INACCURACY_SIZE = 1200; protected Thread m_thread = null; protected Object m_dataSource; protected AudioInputStream m_encodedaudioInputStream; protected int encodedLength = -1; protected AudioInputStream m_audioInputStream; protected AudioFileFormat m_audioFileFormat; protected SourceDataLine m_line; protected FloatControl m_gainControl; protected FloatControl m_panControl; protected String m_mixerName = null; private int m_lineCurrentBufferSize = -1; private int lineBufferSize = -1; private long threadSleep = -1; private static Logger log = LoggerFactory.getLogger(BasicPlayer.class); /** * These variables are used to distinguish stopped, paused, playing states. * We need them to control Thread. */ public static final int UNKNOWN = -1; public static final int PLAYING = 0; public static final int PAUSED = 1; public static final int STOPPED = 2; public static final int OPENED = 3; public static final int SEEKING = 4; private int m_status = UNKNOWN; // Listeners to be notified. private Collection m_listeners = null; private Map empty_map = new HashMap(); /** * Constructs a Basic Player. */ public BasicPlayer() { m_dataSource = null; m_listeners = new ArrayList(); reset(); } protected void reset() { m_status = UNKNOWN; if (m_audioInputStream != null) { synchronized (m_audioInputStream) { closeStream(); } } m_audioInputStream = null; m_audioFileFormat = null; m_encodedaudioInputStream = null; encodedLength = -1; if (m_line != null) { m_line.stop(); m_line.close(); m_line = null; } m_gainControl = null; m_panControl = null; } /** * Add listener to be notified. * @param bpl */ public void addBasicPlayerListener(BasicPlayerListener bpl) { m_listeners.add(bpl); } /** * Return registered listeners. * @return */ public Collection getListeners() { return m_listeners; } /** * Remove registered listener. * @param bpl */ public void removeBasicPlayerListener(BasicPlayerListener bpl) { if (m_listeners != null) { m_listeners.remove(bpl); } } /** * Set SourceDataLine buffer size. It affects audio latency. * (the delay between line.write(data) and real sound). * Minimum value should be over 10000 bytes. * @param size -1 means maximum buffer size available. */ public void setLineBufferSize(int size) { lineBufferSize = size; } /** * Return SourceDataLine buffer size. * @return -1 maximum buffer size. */ public int getLineBufferSize() { return lineBufferSize; } /** * Return SourceDataLine current buffer size. * @return */ public int getLineCurrentBufferSize() { return m_lineCurrentBufferSize; } /** * Set thread sleep time. * Default is -1 (no sleep time). * @param time in milliseconds. */ public void setSleepTime(long time) { threadSleep = time; } /** * Return thread sleep time in milliseconds. * @return -1 means no sleep time. */ public long getSleepTime() { return threadSleep; } /** * Returns BasicPlayer status. * @return status */ public int getStatus() { return m_status; } /** * Open file to play. */ public void open(File file) throws BasicPlayerException { log.info("open(" + file + ")"); if (file != null) { m_dataSource = file; initAudioInputStream(); } } /** * Open URL to play. */ public void open(URL url) throws BasicPlayerException { log.info("open(" + url + ")"); if (url != null) { m_dataSource = url; initAudioInputStream(); } } /** * Open inputstream to play. */ public void open(InputStream inputStream) throws BasicPlayerException { log.info("open(" + inputStream + ")"); if (inputStream != null) { m_dataSource = inputStream; initAudioInputStream(); } } /** * Inits AudioInputStream and AudioFileFormat from the data source. * @throws BasicPlayerException */ protected void initAudioInputStream() throws BasicPlayerException { try { reset(); notifyEvent(BasicPlayerEvent.OPENING, getEncodedStreamPosition(), -1, m_dataSource); if (m_dataSource instanceof URL) { initAudioInputStream((URL) m_dataSource); } else if (m_dataSource instanceof File) { initAudioInputStream((File) m_dataSource); } else if (m_dataSource instanceof InputStream) { initAudioInputStream((InputStream) m_dataSource); } createLine(); // Notify listeners with AudioFileFormat properties. Map properties = null; if (m_audioFileFormat instanceof TAudioFileFormat) { // Tritonus SPI compliant audio file format. properties = ((TAudioFileFormat) m_audioFileFormat).properties(); // Clone the Map because it is not mutable. properties = deepCopy(properties); } else properties = new HashMap(); // Add JavaSound properties. if (m_audioFileFormat.getByteLength() > 0) properties.put("audio.length.bytes", new Integer(m_audioFileFormat.getByteLength())); if (m_audioFileFormat.getFrameLength() > 0) properties.put("audio.length.frames", new Integer(m_audioFileFormat.getFrameLength())); if (m_audioFileFormat.getType() != null) properties.put("audio.type", (m_audioFileFormat.getType().toString())); // Audio format. AudioFormat audioFormat = m_audioFileFormat.getFormat(); if (audioFormat.getFrameRate() > 0) properties.put("audio.framerate.fps", new Float(audioFormat.getFrameRate())); if (audioFormat.getFrameSize() > 0) properties.put("audio.framesize.bytes", new Integer(audioFormat.getFrameSize())); if (audioFormat.getSampleRate() > 0) properties.put("audio.samplerate.hz", new Float(audioFormat.getSampleRate())); if (audioFormat.getSampleSizeInBits() > 0) properties.put("audio.samplesize.bits", new Integer(audioFormat.getSampleSizeInBits())); if (audioFormat.getChannels() > 0) properties.put("audio.channels", new Integer(audioFormat.getChannels())); if (audioFormat instanceof TAudioFormat) { // Tritonus SPI compliant audio format. Map addproperties = ((TAudioFormat) audioFormat).properties(); properties.putAll(addproperties); } // Add SourceDataLine properties.put("basicplayer.sourcedataline", m_line); Iterator it = m_listeners.iterator(); while (it.hasNext()) { BasicPlayerListener bpl = (BasicPlayerListener) it.next(); bpl.opened(m_dataSource, properties); } m_status = OPENED; notifyEvent(BasicPlayerEvent.OPENED, getEncodedStreamPosition(), -1, null); } catch (LineUnavailableException e) { throw new BasicPlayerException(e); } catch (UnsupportedAudioFileException e) { throw new BasicPlayerException(e); } catch (IOException e) { throw new BasicPlayerException(e); } } /** * Inits Audio ressources from file. */ protected void initAudioInputStream(File file) throws UnsupportedAudioFileException, IOException { m_audioInputStream = AudioSystem.getAudioInputStream(file); m_audioFileFormat = AudioSystem.getAudioFileFormat(file); } /** * Inits Audio ressources from URL. */ protected void initAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException { m_audioInputStream = AudioSystem.getAudioInputStream(url); m_audioFileFormat = AudioSystem.getAudioFileFormat(url); } /** * Inits Audio ressources from InputStream. */ protected void initAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException { m_audioInputStream = AudioSystem.getAudioInputStream(inputStream); m_audioFileFormat = AudioSystem.getAudioFileFormat(inputStream); } /** * Inits Audio ressources from AudioSystem.<br> */ protected void initLine() throws LineUnavailableException { log.info("initLine()"); if (m_line == null) createLine(); if (!m_line.isOpen()) { openLine(); } else { AudioFormat lineAudioFormat = m_line.getFormat(); AudioFormat audioInputStreamFormat = m_audioInputStream == null ? null : m_audioInputStream.getFormat(); if (!lineAudioFormat.equals(audioInputStreamFormat)) { m_line.close(); openLine(); } } } /** * Inits a DateLine.<br> * * We check if the line supports Gain and Pan controls. * * From the AudioInputStream, i.e. from the sound file, we * fetch information about the format of the audio data. These * information include the sampling frequency, the number of * channels and the size of the samples. There information * are needed to ask JavaSound for a suitable output line * for this audio file. * Furthermore, we have to give JavaSound a hint about how * big the internal buffer for the line should be. Here, * we say AudioSystem.NOT_SPECIFIED, signaling that we don't * care about the exact size. JavaSound will use some default * value for the buffer size. */ protected void createLine() throws LineUnavailableException { log.info("Create Line"); if (m_line == null) { AudioFormat sourceFormat = m_audioInputStream.getFormat(); log.info("Create Line : Source format : " + sourceFormat.toString()); int nSampleSizeInBits = sourceFormat.getSampleSizeInBits(); if (nSampleSizeInBits <= 0) nSampleSizeInBits = 16; if ((sourceFormat.getEncoding() == AudioFormat.Encoding.ULAW) || (sourceFormat.getEncoding() == AudioFormat.Encoding.ALAW)) nSampleSizeInBits = 16; if (nSampleSizeInBits != 8) nSampleSizeInBits = 16; AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sourceFormat.getSampleRate(), nSampleSizeInBits, sourceFormat.getChannels(), sourceFormat.getChannels() * (nSampleSizeInBits / 8), sourceFormat.getSampleRate(), false); log.info("Create Line : Target format: " + targetFormat); // Keep a reference on encoded stream to progress notification. m_encodedaudioInputStream = m_audioInputStream; try { // Get total length in bytes of the encoded stream. encodedLength = m_encodedaudioInputStream.available(); } catch (IOException e) { log.error("Cannot get m_encodedaudioInputStream.available()", e); } // Create decoded stream. m_audioInputStream = AudioSystem.getAudioInputStream(targetFormat, m_audioInputStream); AudioFormat audioFormat = m_audioInputStream.getFormat(); DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED); Mixer mixer = getMixer(m_mixerName); if (mixer != null) { log.info("Mixer : "+mixer.getMixerInfo().toString()); m_line = (SourceDataLine) mixer.getLine(info); } else { m_line = (SourceDataLine) AudioSystem.getLine(info); m_mixerName = null; } log.info("Line : " + m_line.toString()); log.debug("Line Info : " + m_line.getLineInfo().toString()); log.debug("Line AudioFormat: " + m_line.getFormat().toString()); } } /** * Opens the line. */ protected void openLine() throws LineUnavailableException { if (m_line != null) { AudioFormat audioFormat = m_audioInputStream.getFormat(); int buffersize = lineBufferSize; if (buffersize <= 0) buffersize = m_line.getBufferSize(); m_lineCurrentBufferSize = buffersize; m_line.open(audioFormat, buffersize); log.info("Open Line : BufferSize=" + buffersize); /*-- Display supported controls --*/ Control[] c = m_line.getControls(); for (int p = 0; p < c.length; p++) { log.debug("Controls : " + c[p].toString()); } /*-- Is Gain Control supported ? --*/ if (m_line.isControlSupported(FloatControl.Type.MASTER_GAIN)) { m_gainControl = (FloatControl) m_line.getControl(FloatControl.Type.MASTER_GAIN); log.info("Master Gain Control : [" + m_gainControl.getMinimum() + "," + m_gainControl.getMaximum() + "] " + m_gainControl.getPrecision()); } /*-- Is Pan control supported ? --*/ if (m_line.isControlSupported(FloatControl.Type.PAN)) { m_panControl = (FloatControl) m_line.getControl(FloatControl.Type.PAN); log.info("Pan Control : [" + m_panControl.getMinimum() + "," + m_panControl.getMaximum() + "] " + m_panControl.getPrecision()); } } } /** * Stops the playback.<br> * * Player Status = STOPPED.<br> * Thread should free Audio ressources. */ protected void stopPlayback() { if ((m_status == PLAYING) || (m_status == PAUSED)) { if (m_line != null) { m_line.flush(); m_line.stop(); } m_status = STOPPED; notifyEvent(BasicPlayerEvent.STOPPED, getEncodedStreamPosition(), -1, null); synchronized (m_audioInputStream) { closeStream(); } log.info("stopPlayback() completed"); } } /** * Pauses the playback.<br> * * Player Status = PAUSED. */ protected void pausePlayback() { if (m_line != null) { if (m_status == PLAYING) { m_line.flush(); m_line.stop(); m_status = PAUSED; log.info("pausePlayback() completed"); notifyEvent(BasicPlayerEvent.PAUSED, getEncodedStreamPosition(), -1, null); } } } /** * Resumes the playback.<br> * * Player Status = PLAYING. */ protected void resumePlayback() { if (m_line != null) { if (m_status == PAUSED) { m_line.start(); m_status = PLAYING; log.info("resumePlayback() completed"); notifyEvent(BasicPlayerEvent.RESUMED, getEncodedStreamPosition(), -1, null); } } } /** * Starts playback. */ protected void startPlayback() throws BasicPlayerException { if (m_status == STOPPED) initAudioInputStream(); if (m_status == OPENED) { log.info("startPlayback called"); if (!(m_thread == null || !m_thread.isAlive())) { log.info("WARNING: old thread still running!!"); int cnt = 0; while (m_status != OPENED) { try { if (m_thread != null) { log.info("Waiting ... " + cnt); cnt++; Thread.sleep(1000); if (cnt > 2) { m_thread.interrupt(); } } } catch (InterruptedException e) { throw new BasicPlayerException(BasicPlayerException.WAITERROR, e); } } } // Open SourceDataLine. try { initLine(); } catch (LineUnavailableException e) { throw new BasicPlayerException(BasicPlayerException.CANNOTINITLINE, e); } log.info("Creating new thread"); m_thread = new Thread(this, "BasicPlayer"); m_thread.start(); if (m_line != null) { m_line.start(); m_status = PLAYING; notifyEvent(BasicPlayerEvent.PLAYING, getEncodedStreamPosition(), -1, null); } } } /** * Main loop. * * Player Status == STOPPED || SEEKING => End of Thread + Freeing Audio Ressources.<br> * Player Status == PLAYING => Audio stream data sent to Audio line.<br> * Player Status == PAUSED => Waiting for another status. */ public void run() { log.info("Thread Running"); int nBytesRead = 1; byte[] abData = new byte[EXTERNAL_BUFFER_SIZE]; // Lock stream while playing. synchronized (m_audioInputStream) { // Main play/pause loop. while ((nBytesRead != -1) && (m_status != STOPPED) && (m_status != SEEKING) && (m_status != UNKNOWN)) { if (m_status == PLAYING) { // Play. try { nBytesRead = m_audioInputStream.read(abData, 0, abData.length); if (nBytesRead >= 0) { byte[] pcm = new byte[nBytesRead]; System.arraycopy(abData, 0, pcm, 0, nBytesRead); if (m_line.available() >= m_line.getBufferSize()) log.debug("Underrun : "+m_line.available()+"/"+m_line.getBufferSize()); int nBytesWritten = m_line.write(abData, 0, nBytesRead); // Compute position in bytes in encoded stream. int nEncodedBytes = getEncodedStreamPosition(); // Notify listeners Iterator it = m_listeners.iterator(); while (it.hasNext()) { BasicPlayerListener bpl = (BasicPlayerListener) it.next(); if (m_audioInputStream instanceof PropertiesContainer) { // Pass audio parameters such as instant bitrate, ... Map properties = ((PropertiesContainer) m_audioInputStream).properties(); bpl.progress(nEncodedBytes, m_line.getMicrosecondPosition(), pcm, properties); } else bpl.progress(nEncodedBytes, m_line.getMicrosecondPosition(), pcm, empty_map); } } } catch (IOException e) { log.error("Thread cannot run()", e); m_status = STOPPED; notifyEvent(BasicPlayerEvent.STOPPED, getEncodedStreamPosition(), -1, null); } // Nice CPU usage. if (threadSleep > 0) { try { Thread.sleep(threadSleep); } catch (InterruptedException e) { log.error("Thread cannot sleep(" + threadSleep + ")", e); } } } else { // Pause try { Thread.sleep(1000); } catch (InterruptedException e) { log.error("Thread cannot sleep(1000)", e); } } } // Free audio resources. if (m_line != null) { m_line.drain(); m_line.stop(); m_line.close(); m_line = null; } // Notification of "End Of Media" if (nBytesRead == -1) { notifyEvent(BasicPlayerEvent.EOM, getEncodedStreamPosition(), -1, null); } // Close stream. closeStream(); } m_status = STOPPED; notifyEvent(BasicPlayerEvent.STOPPED, getEncodedStreamPosition(), -1, null); log.info("Thread completed"); } /** * Skip bytes in the File inputstream. * It will skip N frames matching to bytes, so it will never skip given bytes length exactly. * @param bytes * @return value>0 for File and value=0 for URL and InputStream * @throws BasicPlayerException */ protected long skipBytes(long bytes) throws BasicPlayerException { long totalSkipped = 0; if (m_dataSource instanceof File) { log.info("Bytes to skip : " + bytes); int previousStatus = m_status; m_status = SEEKING; long skipped = 0; try { synchronized (m_audioInputStream) { notifyEvent(BasicPlayerEvent.SEEKING, getEncodedStreamPosition(), -1, null); initAudioInputStream(); if (m_audioInputStream != null) { // Loop until bytes are really skipped. while (totalSkipped < (bytes - SKIP_INACCURACY_SIZE)) { skipped = m_audioInputStream.skip(bytes - totalSkipped); if (skipped == 0) break; totalSkipped = totalSkipped + skipped; log.info("Skipped : " + totalSkipped + "/" + bytes); if (totalSkipped == -1) throw new BasicPlayerException(BasicPlayerException.SKIPNOTSUPPORTED); } } } notifyEvent(BasicPlayerEvent.SEEKED, getEncodedStreamPosition(), -1, null); m_status = OPENED; if (previousStatus == PLAYING) startPlayback(); else if (previousStatus == PAUSED) { startPlayback(); pausePlayback(); } } catch (IOException e) { throw new BasicPlayerException(e); } } return totalSkipped; } /** * Notify listeners about a BasicPlayerEvent. * @param code event code. * @param position in the stream when the event occurs. */ protected void notifyEvent(int code, int position, double value, Object description) { BasicPlayerEventLauncher trigger = new BasicPlayerEventLauncher(code, position, value, description, new ArrayList(m_listeners), this); trigger.start(); } protected int getEncodedStreamPosition() { int nEncodedBytes = -1; if (m_dataSource instanceof File) { try { if (m_encodedaudioInputStream != null) { nEncodedBytes = encodedLength - m_encodedaudioInputStream.available(); } } catch (IOException e) { //log.debug("Cannot get m_encodedaudioInputStream.available()",e); } } return nEncodedBytes; } protected void closeStream() { // Close stream. try { if (m_audioInputStream != null) { m_audioInputStream.close(); log.info("Stream closed"); } } catch (IOException e) { log.info("Cannot close stream", e); } } /** * Returns true if Gain control is supported. */ public boolean hasGainControl() { if (m_gainControl == null) { // Try to get Gain control again (to support J2SE 1.5) if ( (m_line != null) && (m_line.isControlSupported(FloatControl.Type.MASTER_GAIN))) m_gainControl = (FloatControl) m_line.getControl(FloatControl.Type.MASTER_GAIN); } return m_gainControl != null; } /** * Returns Gain value. */ public float getGainValue() { if (hasGainControl()) { return m_gainControl.getValue(); } else { return 0.0F; } } /** * Gets max Gain value. */ public float getMaximumGain() { if (hasGainControl()) { return m_gainControl.getMaximum(); } else { return 0.0F; } } /** * Gets min Gain value. */ public float getMinimumGain() { if (hasGainControl()) { return m_gainControl.getMinimum(); } else { return 0.0F; } } /** * Returns true if Pan control is supported. */ public boolean hasPanControl() { if (m_panControl == null) { // Try to get Pan control again (to support J2SE 1.5) if ((m_line != null)&& (m_line.isControlSupported(FloatControl.Type.PAN))) m_panControl = (FloatControl) m_line.getControl(FloatControl.Type.PAN); } return m_panControl != null; } /** * Returns Pan precision. */ public float getPrecision() { if (hasPanControl()) { return m_panControl.getPrecision(); } else { return 0.0F; } } /** * Returns Pan value. */ public float getPan() { if (hasPanControl()) { return m_panControl.getValue(); } else { return 0.0F; } } /** * Deep copy of a Map. * @param src * @return */ protected Map deepCopy(Map src) { HashMap map = new HashMap(); if (src != null) { Iterator it = src.keySet().iterator(); while (it.hasNext()) { Object key = it.next(); Object value = src.get(key); map.put(key, value); } } return map; } /** * @see javazoom.jlgui.basicplayer.BasicController#seek(long) */ public long seek(long bytes) throws BasicPlayerException { return skipBytes(bytes); } /** * @see javazoom.jlgui.basicplayer.BasicController#play() */ public void play() throws BasicPlayerException { startPlayback(); } /** * @see javazoom.jlgui.basicplayer.BasicController#stop() */ public void stop() throws BasicPlayerException { stopPlayback(); } /** * @see javazoom.jlgui.basicplayer.BasicController#pause() */ public void pause() throws BasicPlayerException { pausePlayback(); } /** * @see javazoom.jlgui.basicplayer.BasicController#resume() */ public void resume() throws BasicPlayerException { resumePlayback(); } /** * Sets Pan value. * Line should be opened before calling this method. * Linear scale : -1.0 <--> +1.0 */ public void setPan(double fPan) throws BasicPlayerException { if (hasPanControl()) { log.debug("Pan : " + fPan); m_panControl.setValue((float) fPan); notifyEvent(BasicPlayerEvent.PAN, getEncodedStreamPosition(), fPan, null); } else throw new BasicPlayerException(BasicPlayerException.PANCONTROLNOTSUPPORTED); } /** * Sets Gain value. * Line should be opened before calling this method. * Linear scale 0.0 <--> 1.0 * Threshold Coef. : 1/2 to avoid saturation. */ public void setGain(double fGain) throws BasicPlayerException { if (hasGainControl()) { double minGainDB = getMinimumGain(); double ampGainDB = ((10.0f / 20.0f) * getMaximumGain()) - getMinimumGain(); double cste = Math.log(10.0) / 20; double valueDB = minGainDB + (1 / cste) * Math.log(1 + (Math.exp(cste * ampGainDB) - 1) * fGain); log.debug("Gain : " + valueDB); m_gainControl.setValue((float) valueDB); notifyEvent(BasicPlayerEvent.GAIN, getEncodedStreamPosition(), fGain, null); } else throw new BasicPlayerException(BasicPlayerException.GAINCONTROLNOTSUPPORTED); } public List getMixers() { ArrayList mixers = new ArrayList(); Mixer.Info[] mInfos = AudioSystem.getMixerInfo(); if (mInfos != null) { for (int i = 0; i < mInfos.length; i++) { Line.Info lineInfo = new Line.Info(SourceDataLine.class); Mixer mixer = AudioSystem.getMixer(mInfos[i]); if (mixer.isLineSupported(lineInfo)) { mixers.add(mInfos[i].getName()); } } } return mixers; } public Mixer getMixer(String name) { Mixer mixer = null; if (name != null) { Mixer.Info[] mInfos = AudioSystem.getMixerInfo(); if (mInfos != null) { for (int i = 0; i < mInfos.length; i++) { if (mInfos[i].getName().equals(name)) { mixer = AudioSystem.getMixer(mInfos[i]); break; } } } } return mixer; } public String getMixerName() { return m_mixerName; } public void setMixerName(String name) { m_mixerName = name; } }