/*
* Copyright (C) 2004 Keith Stribley <tech@thanlwinsoft.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.thanlwinsoft.languagetest.sound;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Line;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer.Info;
import java.util.Vector;
/**
*
* @author keith
*/
public class LineController implements Runnable
{
private static final float MAX_SAMPLE_RATE = 44100;
private static final int MAX_BITS = 16;
private static final int MAX_CHANNELS = 2;
private TargetDataLine targetDataLine = null;
private SourceDataLine sourceDataLine = null;
private boolean linesOpen = false;
// temporary format used when testing for availability
private AudioFormat testFormat = null;
private AudioFormat recFormat = null;
private AudioFormat playFormat = null;
private Mixer recMixer = null;
private Mixer playMixer = null;
private DataLine.Info recLineInfo = null;
private DataLine.Info playLineInfo = null;
private Vector<Info> recMixers = null;
private Vector<Info> playMixers = null;
private int playIndex = -1;
private int recIndex = -1;
public final static int PLAY_MODE = 1;
public final static int REC_MODE = 2;
public final static int DUPLEX_MODE = 3;
private int mode;
private float maxSampleRate = AudioSystem.NOT_SPECIFIED;
private String error = null;
/** Creates a new instance of LineController */
public LineController(int mode)
{
maxSampleRate = MAX_SAMPLE_RATE; // could be configurable in future
new Thread(this).start();
recMixers = new Vector<Info>();
playMixers = new Vector<Info>();
this.mode = mode;
}
public Vector<Mixer.Info> getPlayMixers()
{
return playMixers;
}
public Vector<Mixer.Info> getRecMixers()
{
return recMixers;
}
public void run()
{
int count = 0;
boolean foundLines = false;
while (!foundLines)
{
count++;
Mixer mixer = null;
Mixer.Info [] mixers = AudioSystem.getMixerInfo();
int i = 0; // mixer index
playIndex = -1;
recIndex = -1;
testFormat = null;
recFormat = null;
playFormat = null;
if (count < 2)
System.out.println("Getting info for " + mixers.length + " mixers...");
// only loop over mixers once, if we already have a play back line
// skip this step
for (i=0; (i<mixers.length); i++)
{
if (count < 2) System.out.println("\n" + mixers[i].getName() + " (" +
mixers[i].getDescription() + ")");
mixer = AudioSystem.getMixer(mixers[i]);
try
{
DataLine.Info testLi = null;
// check for play lines
Line.Info [] li = mixer.getSourceLineInfo();
if ((li != null)&&(sourceDataLine == null))
{
for (int l=0; l<li.length; l++)
{
try
{
Line line = mixer.getLine(li[l]);
if (line instanceof SourceDataLine)
{
testLi = getLineInfo(line);
System.out.println("Play Line: " + testLi);
if (testLi != null)
{
// compare with existing recFormat to see
// whether we prefer the one from this line
playFormat = compareFormats(playFormat, testFormat);
if (playFormat == testFormat)
{
System.out.println("chose FR" + testFormat.getSampleRate() +
" Ch" + testFormat.getChannels());
playIndex = i;
playLineInfo = testLi;
playMixer = AudioSystem.getMixer(mixers[playIndex]);
}
}
}
}
catch (IllegalArgumentException e)
{
System.out.println(e);
}
}
if (li.length > 0) playMixers.add(mixer.getMixerInfo());
}
// check for recording lines
li = mixer.getTargetLineInfo();
if (li != null)
{
if (li.length == 0 && count < 2)
System.out.println("No target lines for recording on " + mixers[i].getDescription());
for (int l=0; l<li.length; l++)
{
try
{
Line line = mixer.getLine(li[l]);
if (line instanceof TargetDataLine)
{
testLi = getLineInfo(line);
System.out.println("Rec Line: " + testLi);
if (testLi != null)
{
// compare with existing recFormat to see
// whether we prefer the one from this line
recFormat = compareFormats(recFormat, testFormat);
if (recFormat == testFormat)
{
recIndex = i;
recLineInfo = testLi;
recMixer = AudioSystem.getMixer(mixers[recIndex]);
System.out.println("chose " + testFormat + " on " + mixers[i].getDescription());
}
}
}
}
catch (IllegalArgumentException e)
{
System.out.println(e);
}
}
if (li.length > 0) recMixers.add(mixer.getMixerInfo());
}
// it is best to use the same mixer for recording and
// playback, so if that is the case break now
if (playIndex == i && recIndex == i)
{
//break;
}
}
catch (LineUnavailableException e)
{
System.out.println(e.getLocalizedMessage());
error = e.getLocalizedMessage();
}
} // end of loop over mixers
if (playIndex > -1 || recIndex > -1)
{
System.out.println("Found lines in " + count + " loop");
if (playMixer != null)
{
System.out.println(playMixer.getMixerInfo().getName());
System.out.println(playLineInfo);
}
if (recMixer != null)
{
System.out.println(recMixer.getMixerInfo().getName());
System.out.println(recLineInfo);
}
foundLines = true;
}
else
{
if (count++ < 2)
{
System.out.println("No lines available at present");
error = "No lines available at present";
}
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
break;
}
}
}
}
public boolean openLines()
{
int openLines = 0;
try
{
if ((playMixer != null) && (mode & PLAY_MODE) > 0)
{
synchronized (this)
{
sourceDataLine =
(SourceDataLine) playMixer.getLine(playLineInfo);
if (sourceDataLine != null)
openLines |= PLAY_MODE;
}
System.out.println("" +
playMixer.getMixerInfo().getName() + "\n" + playLineInfo + "\n" +
playFormat);
}
if ((recMixer != null) && (mode & REC_MODE) > 0)
{
synchronized (this)
{
targetDataLine =
(TargetDataLine) recMixer.getLine(recLineInfo);
if (targetDataLine != null)
openLines |= REC_MODE;
}
System.out.println("" +
recMixer.getMixerInfo().getName() + "\n" + recLineInfo + "\n" +
recFormat);
System.out.println("Lines available");
}
}
catch (LineUnavailableException e)
{
System.out.println(e.getLocalizedMessage());
error = e.getLocalizedMessage();
return false;
}
synchronized (this)
{
linesOpen = (mode == openLines);
}
return linesOpen;
}
public void closeLines()
{
try
{
if (sourceDataLine != null && sourceDataLine.isOpen())
{
sourceDataLine.close();
sourceDataLine = null;
}
if (targetDataLine != null && targetDataLine.isOpen())
{
targetDataLine.close();
targetDataLine = null;
}
linesOpen = false;
}
catch (IllegalArgumentException e)
{
System.out.println(e);
}
}
public synchronized void setPlayMixer(Mixer.Info mi)
{
playMixer = AudioSystem.getMixer(mi);
Line.Info [] li = playMixer.getSourceLineInfo();
Line.Info testLi, playLineInfo = null;
if (sourceDataLine != null && sourceDataLine.isOpen())
{
sourceDataLine.close();
sourceDataLine = null;
}
if (li != null)
{
playFormat = null;
for (int l=0; l<li.length; l++)
{
try
{
Line line = playMixer.getLine(li[l]);
if (line instanceof SourceDataLine)
{
testLi = getLineInfo(line);
playFormat = compareFormats(playFormat, testFormat);
playLineInfo = testLi;
}
}
catch (IllegalArgumentException e)
{
System.out.println(e);
}
catch (LineUnavailableException e)
{
System.out.println(e);
}
}
try
{
sourceDataLine =
(SourceDataLine) playMixer.getLine(playLineInfo);
}
catch (LineUnavailableException e)
{
System.out.println(e);
playMixer = null;
}
}
else sourceDataLine = null;
}
public synchronized void setRecMixer(Mixer.Info mi)
{
recMixer = AudioSystem.getMixer(mi);
Line.Info [] li = recMixer.getTargetLineInfo();
Line.Info testLi, recLineInfo = null;
if (targetDataLine != null && targetDataLine.isOpen())
{
targetDataLine.close();
targetDataLine = null;
}
if (li != null)
{
recFormat = null;
for (int l=0; l<li.length; l++)
{
try
{
Line line = recMixer.getLine(li[l]);
if (line instanceof TargetDataLine)
{
testLi = getLineInfo(line);
recFormat = compareFormats(recFormat, testFormat);
recLineInfo = testLi;
}
}
catch (IllegalArgumentException e)
{
System.out.println(e);
}
catch (LineUnavailableException e)
{
System.out.println(e);
}
}
try
{
targetDataLine =
(TargetDataLine) recMixer.getLine(recLineInfo);
}
catch (LineUnavailableException e)
{
System.out.println(e);
recMixer = null;
}
}
else targetDataLine = null;
}
protected AudioFormat compareFormats(AudioFormat bestSoFar, AudioFormat test)
{
if (test.getSampleSizeInBits() <= MAX_BITS && (bestSoFar == null ||
test.getSampleSizeInBits() > bestSoFar.getSampleSizeInBits() ||
(test.getSampleRate() >
bestSoFar.getSampleRate()) ||
(test.getChannels() >
bestSoFar.getChannels())
))
{
System.out.println("rejected " + bestSoFar);
bestSoFar = test;
System.out.println("chose " + test);
}
else
{
System.out.println("rejected " + test);
}
return bestSoFar;
}
/**
* Examines the formats available for the given line and chooses the one
* closest to what we want.
*
*/
protected DataLine.Info getLineInfo(Line line)
throws LineUnavailableException
{
AudioFormat refFormat = null;
DataLine.Info supportedInfo = null;
//Line.Info classInfo = line.getLineInfo();
DataLine dl = (DataLine)line;
AudioFormat [] formats =
((DataLine.Info)dl.getLineInfo()).getFormats();
if (formats.length > 0) refFormat = formats[0];
for (int i=0; i<formats.length; i++)
{
// does it meet the target format
if (formats[i].getSampleSizeInBits() == MAX_BITS &&
formats[i].getChannels() == 2 &&
formats[i].getEncoding() == AudioFormat.Encoding.PCM_SIGNED &&
formats[i].isBigEndian())
{
System.out.println(formats[i]);
//break;// good enough
}
// skip quickly over rejects
if (formats[i].getSampleSizeInBits() > MAX_BITS ||
formats[i].getSampleSizeInBits() < refFormat.getSampleSizeInBits())
continue;
if (formats[i].getChannels() > MAX_CHANNELS ||
formats[i].getChannels() < refFormat.getChannels())
continue;
if (formats[i].getSampleRate() != AudioSystem.NOT_SPECIFIED &&
(formats[i].getSampleRate() < refFormat.getSampleRate() ||
formats[i].getSampleRate() > MAX_SAMPLE_RATE))
continue;
// prefer PCM_SIGNED, big endian
if (formats[i].getEncoding() != AudioFormat.Encoding.PCM_SIGNED)
continue;
if ((formats[i].isBigEndian() == false) &&
(refFormat.getEncoding() == AudioFormat.Encoding.PCM_SIGNED))
continue;
// unless the ref is null, it must have the same or more channels
// as ref, a higher or equal sample rate and be big endian,
// PCM_SIGNED
refFormat = formats[i];
System.out.println(i + "Candidate " + refFormat);
}
// if the sample rate is not specified then specify it now
if (refFormat != null)
{
System.out.println(">>>" + refFormat);
if (refFormat.getSampleRate() == AudioSystem.NOT_SPECIFIED)
{
float frameRate = maxSampleRate *
(refFormat.getSampleSizeInBits() *
refFormat.getChannels()) /
(refFormat.getFrameSize() * 8);
refFormat = new AudioFormat(
refFormat.getEncoding(),
maxSampleRate,
refFormat.getSampleSizeInBits(),
refFormat.getChannels(),
refFormat.getFrameSize(),
frameRate,
refFormat.isBigEndian());
}
if (line instanceof TargetDataLine)
{
supportedInfo = new DataLine.Info(TargetDataLine.class,
refFormat);
testFormat = refFormat;
}
else if (line instanceof SourceDataLine)
{
supportedInfo = new DataLine.Info(SourceDataLine.class,
refFormat);
testFormat = refFormat;
}
}
return supportedInfo;
}
public synchronized boolean linesAreAvailable(){return linesOpen;}
public synchronized SourceDataLine getSourceDataLine()
{
return sourceDataLine;
}
public synchronized TargetDataLine getTargetDataLine()
{
return targetDataLine;
}
public AudioFormat getRecLineFormat()
{
return recFormat;
}
public AudioFormat getPlayLineFormat()
{
return playFormat;
}
public Mixer getRecMixer()
{
return recMixer;
}
public Mixer getPlayMixer()
{
return playMixer;
}
public boolean lineError()
{
return (error == null) ? false : true;
}
public String getError()
{
return error;
}
public void clearError()
{
error = null;
}
}