package jass.render;
import javax.sound.sampled.*;
/**
Utility class to read audio in real-time. Use appropriate constructor to use JavaSound or native sound support
@author Kees van den Doel (kvdoel@cs.ubc.ca)
*/
public class RTAudioIn extends Thread {
private float srate;
private int bitsPerFrame;
private int nchannels;
private boolean signed;
private static final boolean bigEndian = false;
private AudioFormat audioFormat;
private TargetDataLine targetDataLine;
private int scratchBufsz; // byte buffer size
private byte[] bbuf;
private int bufferSize; // JavaSound buffersize
private String preferredMixer = null;
private Mixer mixer;
// for native sound
private boolean useNative = false; // can use native audio
private long nativeObjectPointer = 0;
/** Initialize native sound for RtAudio
This is a native method and needs librtaudio.so (LINUX) or rtaudio.dll (Windows)
@param nchannels number of audio channels.
@param srate sampling rate in Hertz.
@param bufferSizeJass jass buffersize
@param numRtAudioBuffers affectiing latency, 0 gives best latency
@return long representing C++ object pointer
*/
public native long initNativeSound(int nchannels,int srate,int bufferSizeJass,int numRtAudioBuffers);
/** Read a buffer of floats using native sound.
This is a native method and needs librtaudio.so (LINUX) or rtaudio.dll (Windows)
@param nativeObjectPointer representing C++ object pointer.
@param buf array of float with sound buffer.
@param buflen length of buffer.
*/
public native void readNativeSoundFloat(long nativeObjectPointer,float[] buf,int buflen);
/** Close native sound
This is a native method and needs librtaudio.so (LINUX) or rtaudio.dll (Windows)
@param nativeObjectPointer representing C++ object pointer
*/
public native void closeNativeSound(long nativeObjectPointer);
private void findMixer() {
Mixer.Info[] mixerinfo = AudioSystem.getMixerInfo();
int mixerIndex = 0;
if(preferredMixer != null) {
for(int i=0;i<mixerinfo.length;i++) {
// list the mixers
System.out.println("mixer "+i+": ");
//System.out.println("description:"+mixerinfo[i].getDescription());
String name = mixerinfo[i].getName();
System.out.println("name:"+name);
if(name.equalsIgnoreCase(preferredMixer)) {
mixerIndex = i;
}
//System.out.println("vendor:"+mixerinfo[i].getVendor());
//System.out.println("version:"+mixerinfo[i].getVersion());
//System.out.println("string:"+mixerinfo[i].toString());
}
}
System.out.println("CHOSEN INPUT MIXER: "+mixerinfo[mixerIndex].getName());
mixer = AudioSystem.getMixer(mixerinfo[mixerIndex]);
}
/* num_fragments must equal bufferSize of JASS patch whern using RtAudio on LINUX, it is what its name
indicates when using DirectSound
*/
private void initAudioNative(float srate,int nchannels,int num_fragments) {
int numRtAudioBuffers = 0;
initAudioNative(srate,nchannels,num_fragments,numRtAudioBuffers);
}
private void initAudioNative(float srate,int nchannels,int buffersizeJass,int numRtAudioBuffers) {
this.srate = srate;
this.nchannels = nchannels;
nativeObjectPointer = initNativeSound(nchannels,(int)srate,buffersizeJass,numRtAudioBuffers);
//This will ensure that close() gets called before the program exits
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.out.println("The shutdown hook in RTAudioIn is executing");
close();
try{ //wait for the DLL to do its destruction before terminating!
sleep(100);
} catch(Exception e) {
System.out.print("The sleep function is broken");
}
}
});
}
private void initAudio(int bufferSize, float srate,int bitsPerFrame,int nchannels,boolean signed) {
this.srate = srate;
this.bufferSize = bufferSize;
this.bitsPerFrame = bitsPerFrame;
this.nchannels = nchannels;
this.signed = signed;
findMixer();
audioFormat = new AudioFormat(srate,bitsPerFrame,nchannels,signed,bigEndian);
DataLine.Info info;
if(bufferSize == 0) {
info = new DataLine.Info(TargetDataLine.class, audioFormat);
} else {
info = new DataLine.Info(TargetDataLine.class, audioFormat,bufferSize);
}
if (!AudioSystem.isLineSupported(info)) {
System.out.println(getClass().getName()+" : Error gettting targetDataLine: not supported\n");
}
// BUG
if (!mixer.isLineSupported(info)) {
// this may be a bogus message, e.g. on Tritonus this is not reliable information
System.out.println(getClass().getName()
+" : Error: sourcedataline: not supported (this may be a bogus message under Tritonus)\n");
}
try {
// <BUG>
// For some reason can't get a line from the mixer, but I can get it from AudioSystem
//
//targetDataLine = (TargetDataLine) mixer.getLine(info); // should do this but fails??
targetDataLine = (TargetDataLine) AudioSystem.getLine(info); // workaround, ignore preferred mixer
// </BUG>
if(bufferSize == 0) {
targetDataLine.open(audioFormat);
} else {
targetDataLine.open(audioFormat,bufferSize);
}
targetDataLine.start();
} catch (LineUnavailableException ex) {
System.out.println(getClass().getName()+" : Error getting line, trying again in 1 sec\n");
// sometimes it works the second time
try {
sleep(1000);
} catch(Exception e3) {
}
try {
targetDataLine = (TargetDataLine) mixer.getLine(info);
if(bufferSize == 0) {
targetDataLine.open(audioFormat);
} else {
targetDataLine.open(audioFormat,bufferSize);
}
targetDataLine.start();
} catch (LineUnavailableException ex2) {
System.out.println(getClass().getName()+" : Error getting line 2nd time, giving up\n");
}
}
scratchBufsz = 4;
bbuf = new byte[scratchBufsz];
}
/** Constructor. Uses JavaSound with default mixer
@param bufferSize Buffer size used by JavaSound.
@param srate sampling rate in Hertz.
@param bitsPerFrame number of bits per audio frame.
@param nchannels number of audio channels.
@param signed true if signed format, false otherwise.
*/
public RTAudioIn(int buffersize, float srate,int bitsPerFrame,int nchannels,boolean signed) {
initAudio(buffersize,srate,bitsPerFrame,nchannels,signed);
}
/** Constructor. Uses JavaSound and can set mixer to use (e.g. "Esd Mixer")
@param bufferSize Buffer size used by JavaSound.
@param srate sampling rate in Hertz.
@param bitsPerFrame number of bits per audio frame.
@param nchannels number of audio channels.
@param signed true if signed format, false otherwise.
@param prefMixer preferred mixer as name string
*/
public RTAudioIn(int buffersize, float srate,int bitsPerFrame,int nchannels,boolean signed,String prefMixer) {
this.preferredMixer = prefMixer;
initAudio(buffersize,srate,bitsPerFrame,nchannels,signed);
}
/** Constructor. Uses default high latency JavaSound buffersize and default mixer
@param srate sampling rate in Hertz.
@param bitsPerFrame number of bits per audio frame.
@param nchannels number of audio channels.
@param signed true if signed format, false otherwise.
*/
public RTAudioIn(float srate,int bitsPerFrame,int nchannels,boolean signed) {
initAudio(0,srate,bitsPerFrame,nchannels,signed);
}
/** Constructor. Uses native sound capture.
This is a native method and needs librtaudio.so (LINUX) or rtaudio.dll (Windows)
@param srate sampling rate in Hertz.
@param nchannels number of audio channels.
@param bufferSizeJass jass bufsz
@param numRtAudioBuffers rtaudio parameter related to latency. 0 lowest
*/
public RTAudioIn(float srate,int nchannels,int bufferSizeJass,int numRtAudioBuffers) {
useNative = true;
// load shared library with native sound implementations
try {
System.loadLibrary("rtaudio");
} catch(UnsatisfiedLinkError e) {
System.out.println("Could not load shared library rtaudio: "+e);
}
initAudioNative(srate,nchannels,bufferSizeJass,numRtAudioBuffers);
}
/** Constructor. Uses native sound capture. Uses default (0) for numRtAudioBuffers
This is a native method and needs librtaudio.so (LINUX) or rtaudio.dll (Windows)
@param srate sampling rate in Hertz.
@param nchannels number of audio channels.
@param bufferSizeJass jass bufsz
*/
public RTAudioIn(float srate,int nchannels,int bufferSizeJass) {
useNative = true;
int numRtAudioBuffers = 0; // use lowest possible
// load shared library with native sound implementations
try {
System.loadLibrary("rtaudio");
} catch(UnsatisfiedLinkError e) {
System.out.println("Could not load shared library rtaudio: "+e);
}
initAudioNative(srate,nchannels,bufferSizeJass,numRtAudioBuffers);
}
/** Read audio buffer from input queue and block if queue is empty.
@param y buffer to write to.
@param nsamples number of samples required.
*/
public void read(float [] y,int nsamples) {
if(useNative) {
readNativeSoundFloat(nativeObjectPointer, y, nsamples);
} else {
if(2*nsamples > scratchBufsz) {
scratchBufsz = 2 * nsamples;
bbuf = new byte[scratchBufsz];
}
targetDataLine.read(bbuf,0,2*nsamples);
FormatUtils.byteToFloat(y,bbuf,nsamples);
}
}
//** Close resources */
public void close() {
if(useNative) {
if(nativeObjectPointer != 0) {
closeNativeSound(nativeObjectPointer);
nativeObjectPointer = 0;
}
} else {
targetDataLine.stop();
targetDataLine.close();
}
}
}