package net.sourceforge.gjtapi.raw.javasound.desktopAgent;
import java.util.HashMap;
import net.sourceforge.gjtapi.raw.javasound.JavaSoundCallId;
import net.sourceforge.gjtapi.raw.javasound.JavaSoundProvider;
import net.sourceforge.gjtapi.util.SizedPipedInputStream;
import javax.telephony.ConnectionEvent;
import javax.telephony.Event;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.DataLine;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.Timer;
import javax.sound.sampled.AudioInputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
/**
* <p>Title: Desktop Agent</p>
*
* <p>Description: </p>
*
* <p>Copyright: Copyright (c) 2008</p>
*
* <p>Company: L2F | INESC-ID</p>
*
* @author D�rio Marcelino
* @version 1.0
*/
public class DesktopAgent {
public final int BUFFER_SIZE = 1024;
//public final int BUFFER_SIZE = 1280;
//public final int BUFFER_SIZE = 160;
public final AudioFormat DEFAULT_FORMAT = new AudioFormat(AudioFormat.
Encoding.PCM_SIGNED, 8000.0F, 16, 1, 2, 8000.0F, false);
private String address;
private HashMap<String, Mixer> playbackMixers;
private HashMap<String, Mixer> captureMixers;
private JavaSoundProvider provider;
//Selected mixers
private Mixer selPlaybackMixer;
private Mixer selCaptureMixer;
//GUI
private DesktopAgentGUI gui;
/** Id from current call (It's assumed an agent can only handle one call at a time) */
private JavaSoundCallId callID = null;
//destinatary addr - Saves the address of an outgoing call
private String destAddress;
//media stop
private boolean play = false;
private boolean record = false;
private boolean playing = false;
private boolean recording = false;
//Audio format
//int buffer = 4096;
private AudioFormat format;
private DataLine.Info playbackLineInfo;
private DataLine.Info captureLineInfo;
public DesktopAgent(String address, HashMap<String, Mixer> playbackMixers,
HashMap<String, Mixer> captureMixers, JavaSoundProvider provider) {
this.address = address;
this.playbackMixers = playbackMixers;
this.captureMixers = captureMixers;
this.provider = provider;
gui = new DesktopAgentGUI(this);
gui.run();
}
/**
* DesktopAgent
*
* @param daProps DesktopAgentProps
* @param playbackMixers HashMap
* @param captureMixers HashMap
* @param provider JavaSoundProvider
*/
public DesktopAgent(DesktopAgentProps daProps, HashMap<String, Mixer>
playbackMixers,
HashMap<String, Mixer> captureMixers, JavaSoundProvider provider) {
this.address = daProps.getAddress();
this.playbackMixers = playbackMixers;
this.captureMixers = captureMixers;
this.provider = provider;
if ((format = daProps.getFormat()) != null) {
playbackLineInfo = new DataLine.Info(SourceDataLine.class, format,
AudioSystem.NOT_SPECIFIED);
captureLineInfo = new DataLine.Info(TargetDataLine.class, format,
AudioSystem.NOT_SPECIFIED);
} else {
playbackLineInfo = new DataLine.Info(SourceDataLine.class, DEFAULT_FORMAT,
AudioSystem.NOT_SPECIFIED);
captureLineInfo = new DataLine.Info(TargetDataLine.class, DEFAULT_FORMAT,
AudioSystem.NOT_SPECIFIED);
}
this.selectPlaybackMixer(daProps.getPlaybackDevice());
this.selectCaptureMixer(daProps.getCaptureDevice());
gui = new DesktopAgentGUI(this);
gui.run();
}
public void setJavaSoundCallId(JavaSoundCallId id) {
callID = id;
}
public JavaSoundCallId getJavaSoundCallId() {
return callID;
}
/**
* Accept incoming call
*/
public void accept() {
gui.accepted();
provider.connectionConnected(callID, destAddress,
ConnectionEvent.CAUSE_NORMAL);
provider.callActive(callID, Event.CAUSE_NORMAL);
}
/**
* Hangup current call
*/
public void hangup() {
gui.hangup();
provider.connectionDisconnected(callID, address, Event.CAUSE_NORMAL);
callID = null;
}
public void call(JavaSoundCallId callID, String address) {
this.callID = callID;
destAddress = address;
gui.call();
/** When an outgoing call is remotly ringing */
provider.terminalConnectionCreated(callID, address,
address,
ConnectionEvent.CAUSE_NORMAL);
provider.connectionInProgress(callID, address,
Event.CAUSE_NORMAL);
provider.connectionAlerting(callID, address,
ConnectionEvent.CAUSE_NORMAL);
}
/**
* Play from an InputStream
*
* @param is InputStream
*/
AudioInputStream ais;
InputStream rtpStream;
public void play(InputStream inStream, long duration) {
play = true;
playing = true;
DataLine.Info pbLineInfo = playbackLineInfo;
rtpStream = inStream;
Timer timer;
if (duration != javax.telephony.media.ResourceConstants.v_Forever) {
timer = new Timer("PlayDuration");
timer.schedule(new StopPlayTask(this), duration);
//System.out.println("Play: Duration set to: " + duration);
}
SourceDataLine source_line;
AudioInputStream convertedStream = null;
byte[] b;
boolean conversion = false; //flag indicating audio conversion will occur
if (!selPlaybackMixer.isLineSupported(pbLineInfo)) {
pbLineInfo = new DataLine.Info(SourceDataLine.class, DEFAULT_FORMAT,
AudioSystem.NOT_SPECIFIED);
if (selPlaybackMixer.isLineSupported(pbLineInfo)) {
//System.out.println("AudioLine not supported by this Mixer, using conversion.");
conversion = true;
} else {
System.err.println("ERROR: AudioLine not supported by this Mixer!");
}
}
try {
source_line = (SourceDataLine) selPlaybackMixer.getLine(
pbLineInfo);
source_line.open();
b = new byte[BUFFER_SIZE];
//System.out.println("Buffer length: " + b.length);
source_line.start();
if (conversion) {
try {
//From 'format' to 'DEFAULT_FORMAT'
convertedStream = AudioSystem.getAudioInputStream(
DEFAULT_FORMAT,
new AudioInputStream(inStream, format, -1));
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (convertedStream == null) {
throw new RuntimeException("null converted stream");
}
}
ais = convertedStream;
}
else
ais = new AudioInputStream(inStream, format, -1);
//cicle
int a, c;
while ((a = ais.read(b)) != -1 /*&& isPlaying()*/) {
c = source_line.write(b, 0, a);
//System.out.println(a + " bytes read @ play");
//System.out.println(c + " bytes actually written");
if (a == 0) {
Thread.currentThread().sleep(15);
}
}
System.out.println("DesktopAgent: ais.read(b) = -1");
/*if (play == true){ // if there was no stopPlay()
ais.close();
}*/
source_line.drain();
source_line.stop();
source_line.close();
play = false;
playing = false;
} catch (IOException e) {
System.err.println(
"IOException: Pipe closed?!?!?!?!");
e.printStackTrace();
} catch (LineUnavailableException e) {
System.err.println(
"ERROR: LineUnavailableException at AudioSender()");
e.printStackTrace();
} catch (InterruptedException ex) {
}
}
/**
* Record to an OutputStream
*
* @param os OutputStream
*/
public void record(OutputStream outStream, long duration) {
record = true;
recording = true;
DataLine.Info cLineInfo = captureLineInfo;
Timer timer;
if (duration != javax.telephony.media.ResourceConstants.v_Forever) {
timer = new Timer("RecordDuration");
timer.schedule(new StopRecordTask(this), duration);
System.out.println("Record: Duration set to: " + duration);
}
TargetDataLine target_line;
OutputStream os = outStream;
byte[] b;
boolean conversion = false; //flag indicating audio conversion will occur
if (!selCaptureMixer.isLineSupported(captureLineInfo)) {
cLineInfo = new DataLine.Info(TargetDataLine.class, DEFAULT_FORMAT,
AudioSystem.NOT_SPECIFIED);
if (selCaptureMixer.isLineSupported(cLineInfo)) {
System.out.println(
"AudioLine not supported by this Mixer, using conversion.");
conversion = true;
} else {
System.err.println(
"ERROR: AudioLine not supported by this Mixer.");
}
}
try {
target_line = (TargetDataLine) selCaptureMixer.getLine(
cLineInfo);
target_line.open();
b = new byte[BUFFER_SIZE];
//System.out.println("Buffer length: " + b.length);
target_line.start();
if (conversion) {
PipedOutputStream pos = null;
// Use the SizedPipedInputStream, which is a backported version
// of the Java 6 PipedInputStream.
// This can be moved back once Java 6 is the norm for GJTAPI
PipedInputStream pis = new SizedPipedInputStream((int) (format.
getChannels() * format.getSampleSizeInBits() / 8 *
format.getSampleRate()));
try {
pos = new PipedOutputStream(pis);
} catch (IOException ex) {
ex.printStackTrace();
}
AudioInputStream convertedStream = AudioSystem.
getAudioInputStream(format,
new AudioInputStream(pis,
DEFAULT_FORMAT, -1));
int frameSize = convertedStream.getFormat().getFrameSize();
//cicle
int a;
while (isRecording()) {
//read from speaker
a = target_line.read(b, 0, b.length);
//System.out.println(a + " bytes read from mic @ record");
//conversion
pos.write(b, 0, a);
a = convertedStream.available();
//System.out.println(a + " available in convertedStream @ record");
float b_size = (Math.min(a,
b.length * frameSize) * ((float)format.getSampleSizeInBits() / (float)DEFAULT_FORMAT.getSampleSizeInBits()));
byte[] convBuffer = new byte[(int)b_size];
a = convertedStream.read(convBuffer);
//System.out.println(a + " bytes read from convertedStream @ record");
//write to stream
if (a != 0) {
os.write(convBuffer, 0, a);
//System.out.println(a + " bytes wrote @ record");
} else {
Thread.currentThread().sleep(5);
}
}
} else {
//cicle
int a;
while (isRecording()) {
a = target_line.read(b, 0, b.length);
os.write(b, 0, a);
//System.out.println(a + " bytes read @ record");
if (a == 0) {
Thread.currentThread().sleep(5);
}
}
}
//When it's finished
os.close();
target_line.stop();
target_line.close();
record = false;
recording = false;
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
System.err.println(
"ERROR: LineUnavailableException at AudioSender()");
e.printStackTrace();
} catch (InterruptedException ex) {
}
}
/**
* Stop Play and Recording
*/
public void stop() {
stopPlay();
stopRecord();
}
/**
* stopPlay
*/
public void stopPlay() {
System.out.println("DesktopAgent: StopPlay");
play = false;
try {
long time = System.currentTimeMillis();
//rtpStream.close();
if (ais != null) ais.close();
System.out.println(">>>>>>>>>>>>> StopPlay, close >>>>>>>>>>>>> " + (System.currentTimeMillis() - time)/1000 + "s.");
} catch (IOException ex1) {
}
while (playing == true) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException ex) {
}
}
System.out.println("DesktopAgent: PlayStopped!");
}
/**
* stopRecord
*/
public void stopRecord() {
System.out.println("DesktopAgent: StopRecord");
record = false;
while (recording == true) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException ex) {
}
}
}
/**
* isPlaying
*/
public boolean isPlaying() {
return play;
}
/**
* isRecording
*/
public boolean isRecording() {
//System.out.println("DesktopAgent: Record" + record);
return record;
}
//------------ GUI related methods -----------------------------------------
/**
* Returns the agent Address
*
* @return String
*/
public String getAddress() {
return address;
}
/**
* Returns a string with the Playback Mixers
*
* @return String[]
*/
public String[] getPlaybackMixers() {
String[] ret = new String[playbackMixers.size()];
int i = 0;
for (String st : playbackMixers.keySet()) {
ret[i] = st;
i++;
}
return ret;
}
/**
* Returns a string with the Capture Mixers
*
* @return String[]
*/
public String[] getCaptureMixers() {
String[] ret = new String[captureMixers.size()];
int i = 0;
for (String st : captureMixers.keySet()) {
ret[i] = st;
i++;
}
return ret;
}
/**
* Incoming call from the simulated outside client
*/
public void incomingCall() {
callID = new JavaSoundCallId();
provider.terminalConnectionRinging(callID, address,
address,
ConnectionEvent.CAUSE_NORMAL);
provider.connectionInProgress(callID, address,
Event.CAUSE_NORMAL);
provider.connectionAlerting(callID, address,
ConnectionEvent.CAUSE_NORMAL);
}
/**
* Hang up by the simulated outside client
*/
public void remoteHangup() {
/** When a call has been locally or remotely closed */
provider.connectionDisconnected(callID, address, Event.CAUSE_NORMAL);
callID = null;
}
/**
* selectPlaybackMixer
*
* @param string String
*/
public void selectPlaybackMixer(String selectedMixer) {
selPlaybackMixer = playbackMixers.get(selectedMixer);
if (selPlaybackMixer == null) {
selPlaybackMixer = (Mixer) playbackMixers.values().toArray()[0];
}
}
public String getSelPlaybackMixer() {
return selPlaybackMixer.getMixerInfo().getName();
}
/**
* Selects de Capture Mixer
*
* @param string selectedMixer
*/
public void selectCaptureMixer(String selectedMixer) {
selCaptureMixer = captureMixers.get(selectedMixer);
if (selCaptureMixer == null) {
selCaptureMixer = (Mixer) captureMixers.values().toArray()[0];
}
}
public String getSelCaptureMixer() {
return selCaptureMixer.getMixerInfo().getName();
}
/**
* CAll accepted by the simulated outside client
*/
public void accepted() {
/** When an outgoing call has been accepted */
provider.connectionConnected(callID, destAddress,
ConnectionEvent.CAUSE_NORMAL);
provider.callActive(callID, Event.CAUSE_NORMAL);
}
/**
* DesktopAgent shutdown
*/
public void shutdown() {
if (callID != null) {
remoteHangup();
}
provider.removeDesktopAgent(address);
}
}