/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip.server;
import com.sun.voip.AudioConversion;
import com.sun.voip.CallParticipant;
import com.sun.voip.Logger;
import com.sun.voip.MediaInfo;
import com.sun.voip.Recorder;
import com.sun.voip.RtpPacket;
import com.sun.voip.RtpSenderPacket;
import com.sun.voip.SampleRateConverter;
import com.sun.voip.SdpManager;
import com.sun.voip.SpeexEncoder;
import com.sun.voip.SpeexException;
import com.sun.voip.TreatmentManager;
import com.sun.voip.Util;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.ArrayList;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.text.ParseException;
import org.xmpp.jnodes.IChannel;
import org.ifsoft.*;
import org.ifsoft.rtp.*;
import org.jitsi.impl.neomedia.codec.audio.opus.Opus;
import com.jcumulus.server.rtmfp.packet.AudioPacket;
/**
* Send RTP data to this ConferenceMember,
*/
public class MemberSender {
private ConferenceManager conferenceManager;
private CallHandler callHandler;
private CallParticipant cp; // caller parameters
private boolean traceCall = false;
private byte telephoneEventPayload;
private MediaInfo myMediaInfo;
private MediaInfo conferenceMediaInfo;
private double outputVolume = 1.0;
private boolean mustSetMarkBit = true; // true for first packet
private String dtmfKeyToSend;
private int dtmfSendSequence;
private RtpSenderPacket senderPacket;
private SpeexEncoder speexEncoder;
private long opusEncoder = 0;
private final int opusSampleRate = 48000;
private final int frameSizeInMillis = 20;
private final int outputFrameSize = 2;
private final int opusChannels = 2;
private int frameSizeInSamplesPerChannel = (opusSampleRate * frameSizeInMillis) / 1000;
private int frameSizeInBytes = outputFrameSize * opusChannels * frameSizeInSamplesPerChannel;
private InetSocketAddress memberAddress;
private boolean done = false;
/*
* Statistics
*/
private int packetsSent = 0;
private double totalTimeToGetData;
private int comfortPayloadsSent = 0;
private Cipher encryptCipher;
private String encryptionKey;
private String encryptionAlgorithm;
private int mySamplesPerPacket;
private SampleRateConverter outSampleRateConverter;
private int outSampleRate;
private int outChannels;
private DatagramChannel datagramChannel;
private boolean initializationDone = false;
private IChannel relayChannel;
private long startTime = 0;
public MemberSender(CallParticipant cp, DatagramChannel datagramChannel) throws IOException
{
this.cp = cp;
this.datagramChannel = datagramChannel;
encryptionKey = cp.getEncryptionKey();
encryptionAlgorithm = cp.getEncryptionAlgorithm();
if (encryptionKey != null) {
try {
if (encryptionKey.length() < 8) {
encryptionKey +=
String.valueOf(System.currentTimeMillis());
}
if (encryptionKey.length() > 8 &&
encryptionAlgorithm.equals("DES")) {
encryptionKey = encryptionKey.substring(0, 8);
}
byte[] keyBytes = encryptionKey.getBytes();
SecretKeySpec secretKey = new SecretKeySpec(keyBytes,
encryptionAlgorithm);
encryptCipher = Cipher.getInstance(encryptionAlgorithm);
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey);
Logger.println("Call " + cp + " Voice data will be encrypted "
+ "using " + encryptionAlgorithm);
} catch (Exception e) {
Logger.println("Call " + cp
+ " Crypto initialization failed " + e.getMessage());
throw new IOException(" Crypto initialization failed "
+ e.getMessage());
}
}
startTime = System.currentTimeMillis();
}
public void setChannel(IChannel relayChannel)
{
this.relayChannel = relayChannel;
}
public IChannel getChannel()
{
return this.relayChannel;
}
public InetSocketAddress getSendAddress() {
return memberAddress;
}
public void setSendAddress(InetSocketAddress sendAddress) {
if (memberAddress == null || memberAddress.equals(sendAddress)) {
return;
}
/* BAO not sure why this is here
if (memberAddress.getAddress().equals(sendAddress.getAddress()) == false) {
Logger.println("Call " + cp
+ " Attempt to change remote IP Address "
+ memberAddress.getAddress() + " to "
+ sendAddress.getAddress() + " rejected!");
return;
}
*/
Logger.println("Call " + cp + " member address changed from " + memberAddress + " to " + sendAddress);
memberAddress = sendAddress;
}
/*
* For debugging.
*/
public void traceCall(boolean traceCall) {
this.traceCall = traceCall;
}
public boolean traceCall() {
return traceCall;
}
public String getMemberState() {
if (initializationDone == false) {
return "";
}
String s = "";
s += "\tAddress to send data to call " + memberAddress + "\n";
s += "\tSending Speex encoded data " + cp.speexEncode() + "\n";
s += "\tOutput volume " + outputVolume;
s += "\n";
return s;
}
/**
* Initialize this member. The call has been established and
* we now know the port at which the member (CallParticipant)
* listens for data.
*/
public void initialize(ConferenceManager conferenceManager,
CallHandler callHandler, InetSocketAddress memberAddress,
byte mediaPayload, byte telephoneEventPayload) {
this.conferenceManager = conferenceManager;
this.memberAddress = memberAddress;
this.telephoneEventPayload = telephoneEventPayload;
this.callHandler = callHandler;
Logger.writeFile("Call " + cp + " MemberSender initialization started ..." + cp.getProtocol());
conferenceMediaInfo = conferenceManager.getMediaInfo();
outSampleRate = conferenceMediaInfo.getSampleRate();
outChannels = conferenceMediaInfo.getChannels();
try {
myMediaInfo = SdpManager.findMediaInfo(mediaPayload);
} catch (ParseException e) {
Logger.println("Call " + cp + " Invalid mediaPayload "
+ mediaPayload);
callHandler.cancelRequest("Invalid mediaPayload " + mediaPayload);
return;
}
int inSampleRate = myMediaInfo.getSampleRate();
int inChannels = myMediaInfo.getChannels();
/*
* No data is ever sent to an input treatment unless it's a recorder
*/
if (cp.getInputTreatment() == null || cp.isRecorder() == true) {
if (inSampleRate != outSampleRate || inChannels != outChannels) {
Logger.println("Call " + cp
+ " resample data to send from " + inSampleRate + "/"
+ inChannels + " to " + outSampleRate
+ "/" + outChannels);
try {
outSampleRateConverter = new SampleRateConverter(
this.toString(), outSampleRate, outChannels,
inSampleRate, inChannels);
} catch (IOException e) {
callHandler.cancelRequest(e.getMessage());
return;
}
}
}
senderPacket = new RtpSenderPacket(myMediaInfo.getEncoding(), inSampleRate, inChannels);
if (myMediaInfo.getEncoding() == RtpPacket.SPEEX_ENCODING) {
try {
speexEncoder = new SpeexEncoder(inSampleRate, inChannels);
Logger.println("Call " + cp + " created SpeexEncoder");
} catch (SpeexException e) {
Logger.println("Call " + cp
+ " Speex initialization for encoding failed: "
+ e.getMessage());
callHandler.cancelRequest(e.getMessage());
return;
}
}
if (myMediaInfo.getEncoding() == RtpPacket.PCM_ENCODING) {
try {
opusEncoder = Opus.encoder_create(opusSampleRate, opusChannels);
if (opusEncoder == 0)
{
Logger.println("Call " + cp + " OPUS encoder creation error ");
callHandler.cancelRequest("OPUS encoder creation error ");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
initializationDone = true;
Logger.writeFile("Call " + cp + " MemberSender initialization done...");
}
public MediaInfo getMediaInfo() {
return myMediaInfo;
}
public void setOutputVolume(double outputVolume) {
this.outputVolume = outputVolume;
}
public double getOutputVolume() {
return outputVolume;
}
private long timePreviousPacketSent;
public synchronized void handleVP8Video(RTPPacket videoPacket)
{
try {
//getWebRTCParticipant().pushVideo(videoPacket);
} catch (Exception e) {
}
}
public synchronized boolean sendData(int[] dataToSend)
{
if (dtmfKeyToSend != null) {
if (telephoneEventPayload != 0) {
sendDtmfKey();
return true;
} else {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp
+ " Telephone event payload not supported. "
+ "Can't send " + dtmfKeyToSend);
}
dtmfKeyToSend = null;
}
}
long start = System.nanoTime();
if (dataToSend == null)
{
if (Logger.logLevel == -77)
{
Logger.println("Call " + cp + " no data to send");
}
if (comfortNoiseType == CN_USE_PAYLOAD) {
if (senderPacket.getRtpPayload() != RtpPacket.COMFORT_PAYLOAD)
{
if (Logger.logLevel == -77) {
Logger.println("Call " + cp + " sending comfort payload");
}
if (relayChannel == null)
{
if (cp.getRtmfpSendStream() == null && "SIP".equals(cp.getProtocol()))
sendComfortNoisePayload();
} else relayChannel.sendComfortNoisePayload();
}
}
mustSetMarkBit = true;
return false;
}
if (senderPacket.getRtpPayload() == RtpPacket.COMFORT_PAYLOAD) {
senderPacket.adjustRtpTimestamp(); // account for pause
}
senderPacket.setRtpPayload(myMediaInfo.getPayload());
if (mustSetMarkBit) {
if (Logger.logLevel == -77 ||
Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Setting MARK for " + cp);
}
if (Logger.logLevel != -77) {
/*
* Adjust RTP timestamp to account for long pause
*/
if (timePreviousPacketSent != 0) {
senderPacket.adjustRtpTimestamp(
System.currentTimeMillis() - timePreviousPacketSent);
}
}
senderPacket.setMark(); /* Set MARK_BIT */
mustSetMarkBit = false;
}
if (outputVolume != 1.0) {
callHandler.getMember().adjustVolume(dataToSend, outputVolume);
}
dataToSend = normalize(dataToSend);
try {
/*
* Resample if needed
*/
if (outSampleRateConverter != null) {
dataToSend = outSampleRateConverter.resample(dataToSend);
}
} catch (IOException e) {
Logger.println("Call " + cp + " can't resample data to send! "
+ e.getMessage());
callHandler.cancelRequest("Call " + cp
+ " can't resample data to send! " + e.getMessage());
return false;
}
byte[] rtpData = senderPacket.getData();
if (Logger.logLevel == -37) {
boolean silence = true;
for (int i = RtpPacket.HEADER_SIZE;
i < rtpData.length - RtpPacket.HEADER_SIZE; i++) {
if (rtpData[i] != 0) {
silence = false;
break;
}
}
if (silence) {
//Logger.println("Call " + cp + " sending silence");
return false;
}
}
//Util.dump("Call " + cp + " sending data " + dataToSend.length,
// dataToSend, 0, 8);
//Logger.println("Call " + cp + " Sending data...");
byte[] opusBytes = null;
if (myMediaInfo.getEncoding() == RtpPacket.PCMU_ENCODING) {
/*
* Convert to ulaw
*/
AudioConversion.linearToUlaw(dataToSend, rtpData, RtpPacket.HEADER_SIZE);
senderPacket.setLength(rtpData.length);
//Util.dump("Call " + cp + " sending ulaw data " + rtpData.length,
// rtpData, 0, 16);
} else if (myMediaInfo.getEncoding() == RtpPacket.SPEEX_ENCODING) {
try {
if (Logger.logLevel >= Logger.LOG_MOREDETAIL) {
Logger.writeFile("Call " + cp + " speex encoding data ");
}
int length = speexEncoder.encode(dataToSend, rtpData, RtpPacket.HEADER_SIZE);
senderPacket.setLength(length + RtpPacket.HEADER_SIZE);
} catch (SpeexException e) {
Logger.println("Call " + this + ": " + e.getMessage());
return false;
}
} else if (myMediaInfo.getEncoding() == RtpPacket.PCM_ENCODING) {
if (relayChannel != null && relayChannel.encode())
{
byte[] input = AudioConversion.littleEndianIntsToBytes(dataToSend);
byte[] output = new byte[Opus.MAX_PACKET];
int outLength = Opus.encode(opusEncoder, input, 0, frameSizeInSamplesPerChannel, output, 0, output.length);
opusBytes = new byte[outLength];
System.arraycopy(output, 0, opusBytes, 0, outLength);
System.arraycopy(output, 0, rtpData, RtpPacket.HEADER_SIZE, outLength);
senderPacket.setLength(outLength + RtpPacket.HEADER_SIZE);
//Logger.println("RtpPacket.PCM_ENCODING " + outLength);
}
} else {
AudioConversion.intsToBytes(dataToSend, rtpData, RtpPacket.HEADER_SIZE);
}
recordPacket(rtpData, senderPacket.getLength());
recordAudio(rtpData, RtpPacket.HEADER_SIZE, senderPacket.getLength() - RtpPacket.HEADER_SIZE);
/*
* Encrypt data if required
*/
if (needToEncrypt()) {
encrypt(rtpData, senderPacket.getLength());
}
if (Logger.logLevel == -78) {
Logger.println("Call " + cp + " sending data from socket "
+ datagramChannel.socket().getLocalAddress()
+ ":" + datagramChannel.socket().getLocalPort()
+ " to " + senderPacket.getSocketAddress());
}
/*
* If this is an input treatment, the only reason we're
* here is to record the data.
* There is no need to send data to the call.
* In fact the send and receive addresses are the same
* so that if we sent data, it would also be received!
*/
if (relayChannel == null)
{
if (cp.getRtmfpSendStream() != null) // RTMFP
{
if (RtmfpCallAgent.publishHandlers.containsKey(cp.getRtmfpSendStream()) )
{
int ts = (int)(System.currentTimeMillis() - startTime);
byte[] rtmfp = new byte[rtpData.length + 1 - RtpPacket.HEADER_SIZE];
rtmfp[0] = (byte) 130;
System.arraycopy(rtpData, RtpPacket.HEADER_SIZE, rtmfp, 1, rtmfp.length - 1);
RtmfpCallAgent.publishHandlers.get(cp.getRtmfpSendStream()).B(ts, new AudioPacket(rtmfp, rtmfp.length), 0);
}
} else if ("SIP".equals(cp.getProtocol())) { // SIP
if (cp.getInputTreatment() == null) {
try {
senderPacket.setSocketAddress(memberAddress);
datagramChannel.send(ByteBuffer.wrap(senderPacket.getData(), 0, senderPacket.getLength()), memberAddress);
if (Logger.logLevel >= Logger.LOG_MOREDETAIL) {
Logger.writeFile("Call " + cp + " back from sending data");
}
} catch (Exception e) {
if (!done) {
Logger.error("Call " + cp + " sendData " + e.getMessage());
e.printStackTrace();
}
return false;
}
}
} else { //do nothing;
return true;
}
} else { // WebRTC
try {
if (relayChannel.encode())
relayChannel.pushAudio(senderPacket.getData(), opusBytes);
else
relayChannel.pushAudio(dataToSend);
} catch (Exception e) {
return false;
}
}
senderPacket.setBuffer(rtpData);
if (Logger.logLevel >= Logger.LOG_DEBUG) {
log(true);
}
timePreviousPacketSent = System.currentTimeMillis();
if (Logger.logLevel >= Logger.LOG_MOREDETAIL) {
Logger.println("Call " + cp + " sendLength " + rtpData.length);
}
totalTimeToGetData += (System.nanoTime() - start);
packetsSent++;
senderPacket.updateRtpHeader(rtpData.length);
return true;
}
public static int[] normalize(int[] audio)
{
int length = audio.length;
// Scan for max peak value here
float peak = 0;
for (int n = 0; n < length; n++) {
float val = Math.abs(audio[n]);
if (val > peak) {
peak = val;
}
}
// Peak is now the loudest point, calculate ratio
float r1 = 32768 / peak;
// Don't increase by over 500% to prevent loud background noise, and
// normalize to 90%
float ratio = Math.min(r1, 5) * .90f;
for (int n = 0; n < length; n++) {
audio[n] *= ratio;
}
return audio;
}
/*
* It takes 12 packets to generate a dtmf key!
* The first 3 are silence packets with the MARK bit set.
* All the rest have the same RTP timestamp.
* Next 3 packets have the MARK bit set and a duration of 0.
* The next 3 packets have the MARK bit clear and a duration of
* 400, 800, and 1200, respectively.
* The last 3 packets have the MARK bit clear, the END bit set,
* and a duration of 1304.
*
* The next packet sent has the MARK bit set.
*
* This is what we receive if a dtmf key is pressed on the lucent phone.
* So the assumption is that this is what we should generate for dtmf keys.
*/
private void sendDtmfKey() {
dtmfSendSequence++;
if (dtmfSendSequence == 1) {
Logger.println("Sending dtmf key " + dtmfKeyToSend
+ " to " + cp + " sequence " + dtmfSendSequence);
} else {
Logger.writeFile("Sending dtmf key " + dtmfKeyToSend
+ " to " + cp + " sequence " + dtmfSendSequence);
}
byte[] data = senderPacket.getData();
if (dtmfSendSequence <= 3) {
/*
* Send Silence with the MARK BIT
*/
int size = RtpPacket.getDataSize(myMediaInfo.getEncoding(),
myMediaInfo.getSampleRate(), myMediaInfo.getChannels());
size += RtpPacket.HEADER_SIZE;
int silence = AudioConversion.PCMU_SILENCE;
if (myMediaInfo.getEncoding() == RtpPacket.PCM_ENCODING) {
silence = AudioConversion.PCM_SILENCE;
}
for (int i = RtpPacket.HEADER_SIZE; i < size; i++) {
data[i] = AudioConversion.PCMU_SILENCE;
}
senderPacket.setLength(size);
senderPacket.setMark();
sendPacket();
senderPacket.incrementRtpSequenceNumber();
Logger.writeFile("Sending silence with MARK set");
return;
}
senderPacket.setRtpPayload(telephoneEventPayload);
senderPacket.setLength(RtpPacket.DATA + 4);
data[RtpPacket.DATA + 0] = getTelephoneEvent(dtmfKeyToSend);
data[RtpPacket.DATA + 1] = (byte)6; // volume level
if (dtmfSendSequence <= 6) {
/*
* These 3 packets have MARK bit set and duration of 0
*/
senderPacket.setMark();
data[RtpPacket.DATA + 2] = (byte)0;
data[RtpPacket.DATA + 3] = (byte)0;
sendPacket();
senderPacket.incrementRtpSequenceNumber();
Logger.writeFile("Sending MARK duration 0");
return;
}
/*
* Next 3 packets have MARK bit clear, duration of 400, 800,
* and 1200.
*/
if (dtmfSendSequence == 7) {
senderPacket.clearMark();
data[RtpPacket.DATA + 2] = (byte)((400 >> 8) & 0xff);
data[RtpPacket.DATA + 3] = (byte)(400 & 0xff);
sendPacket();
senderPacket.incrementRtpSequenceNumber();
Logger.writeFile("Sending duration 400");
return;
}
if (dtmfSendSequence == 8) {
senderPacket.clearMark();
data[RtpPacket.DATA + 2] = (byte)((800 >> 8) & 0xff);
data[RtpPacket.DATA + 3] = (byte)(800 & 0xff);
sendPacket();
senderPacket.incrementRtpSequenceNumber();
Logger.writeFile("Sending duration 800");
return;
}
if (dtmfSendSequence == 9) {
senderPacket.clearMark();
data[RtpPacket.DATA + 2] = (byte)((1200 >> 8) & 0xff);
data[RtpPacket.DATA + 3] = (byte)(1200 & 0xff);
sendPacket();
senderPacket.incrementRtpSequenceNumber();
Logger.writeFile("Sending duration 1200");
return;
}
/*
* Last 3 packets have MARK bit clear, END bit set, and a
* duration of 1304.
*/
if (dtmfSendSequence <= 12) {
senderPacket.clearMark();
data[RtpPacket.DATA + 1] |= (byte)0x80; // end
data[RtpPacket.DATA + 2] = (byte)((1304 >> 8) & 0xff);
data[RtpPacket.DATA + 3] = (byte)(1304 & 0xff);
sendPacket();
senderPacket.incrementRtpSequenceNumber();
Logger.writeFile("Sending END set duration 1304");
return;
}
Logger.writeFile("Done sending dtmf key...");
if (dtmfKeyToSend != null) {
if (dtmfKeyToSend.length() == 1) {
dtmfKeyToSend = null;
} else {
dtmfKeyToSend = dtmfKeyToSend.substring(1);
}
}
dtmfSendSequence = 0;
senderPacket.adjustRtpTimestamp(2400);
}
private byte getTelephoneEvent(String dtmfKeyToSend) {
byte dtmfKey = (byte) -1;
try {
dtmfKey = (byte) Integer.parseInt(dtmfKeyToSend);
} catch (NumberFormatException e) {
}
if (dtmfKey >= 0 && dtmfKey <= 9) {
return dtmfKey;
}
if (dtmfKeyToSend.equals("*")) {
return 10;
}
if (dtmfKeyToSend.equals("#")) {
return 11;
}
if (dtmfKey >= 12 && dtmfKey <= 15) {
return dtmfKey;
}
return 15;
}
private void sendPacket() {
try {
senderPacket.setSocketAddress(memberAddress);
datagramChannel.send(
ByteBuffer.wrap(senderPacket.getData()), memberAddress);
} catch (IOException e) {
if (!done) {
Logger.error("Call " + cp + " sendPacket: "
+ e.getMessage());
}
}
}
private static final int CN_DISABLE = 0; // disable comfort noise
private static final int CN_ADD_NOISE = 1; // add noise to every packet
private static final int CN_USE_PAYLOAD = 2; // use cn payload change
private static int comfortNoiseType = CN_USE_PAYLOAD;
public static void setComfortNoiseType(int comfortNoiseType) {
MemberSender.comfortNoiseType = comfortNoiseType;
}
public static int getComfortNoiseType() {
return comfortNoiseType;
}
public boolean sendComfortNoisePayload() {
/*
* Set payload and packet size
*/
senderPacket.setComfortPayload();
int len = senderPacket.getLength();
/*
* A packet with COMFORT_PAYLOAD has one byte of data to
* indicate the noise volume level to generate.
*/
senderPacket.setComfortNoiseLevel(RtpPacket.comfortNoiseLevel);
byte[] data = senderPacket.getData();
if (needToEncrypt()) {
senderPacket.setLength(RtpPacket.DATA + 1);
encrypt(data, senderPacket.getLength());
}
senderPacket.setSocketAddress(memberAddress);
try {
datagramChannel.send(ByteBuffer.wrap(senderPacket.getData()), memberAddress);
} catch (IOException e) {
if (!done) {
Logger.println("Call " + cp + " sendComfortNoisePayload "
+ e.getMessage());
e.printStackTrace();
}
return false;
}
senderPacket.setBuffer(data);
senderPacket.updateRtpHeader(len);
if (Logger.logLevel >= Logger.LOG_DETAIL) {
Logger.println("Call " + cp + " Sent comfort noise payload "
+ "with level " + RtpPacket.comfortNoiseLevel);
}
if (Logger.logLevel >= Logger.LOG_DEBUG) {
log(false);
}
comfortPayloadsSent++;
return true;
}
/*
* For debugging
*/
long previousSendTime;
private void log(boolean contributedToTheMix) {
long sendTimeChange;
long now = System.currentTimeMillis();
if (previousSendTime == 0) {
sendTimeChange = RtpPacket.PACKET_PERIOD;
} else {
sendTimeChange = now - previousSendTime;
}
previousSendTime = now;
String summary = "";
String flags = "";
String badTime = " ";
String badTimestamp = " ";
if (senderPacket.isMarkSet()) {
flags = "MARK ";
} else {
if (sendTimeChange < 15) {
badTime = "-";
summary = "!";
} else if (sendTimeChange > 25) {
badTime = "+";
summary = "!";
}
}
if (senderPacket.getRtpPayload() == RtpPacket.COMFORT_PAYLOAD) {
flags += "COMFORT ";
}
String timestamp = Integer.toHexString(
(int)(senderPacket.getRtpTimestamp() & 0xffffffff));
if (timestamp.length() != 8) {
timestamp += " "; // for alignment
}
/*
* The rtpTimestampChange is always the same since
* we send fixed size RTP Packets. To save time logging,
* we use the constant a0 (160 bytes).
*/
Logger.writeFile("S "
+ " " + sendTimeChange + badTime
+ "\ta0"
+ badTimestamp
+ "\t"
+ Integer.toHexString(senderPacket.getRtpSequenceNumber() & 0xffff)
+ "\t" + timestamp
+ "\t" + flags + cp
+ " " + memberAddress);
}
/**
* Member is leaving a conference. Print statistics for the member.
*/
public void end() {
if (done) {
return;
}
done = true;
synchronized (recordingLock) {
if (recorder != null) {
recorder.done();
recorder = null;
}
}
if (opusEncoder != 0)
{
Opus.encoder_destroy(opusEncoder);
opusEncoder = 0;
}
}
public void printStatistics() {
if (conferenceManager == null) {
return;
}
synchronized (conferenceManager) {
if (packetsSent == 0) {
return;
}
Logger.writeFile("Call " + cp + ": " + packetsSent
+ " packets sent");
Logger.writeFile("Call " + cp + ": " + comfortPayloadsSent
+ " comfort payloads sent");
if (packetsSent != 0) {
Logger.writeFile("Call " + cp + ": "
+ ((float)totalTimeToGetData / 1000000000. / packetsSent)
+ " average seconds to get data to send");
}
Logger.writeFile("Call " + cp + ": "
+ encryptCount + " packets encrypted");
if (encryptCount != 0) {
Logger.writeFile("Call " + cp + ": "
+ (((float)encryptTime / (float)encryptCount) / 1000)
+ " milliseconds average per encrypt");
}
if (speexEncoder != null) {
int encodes = speexEncoder.getEncodes();
if (encodes > 0) {
long encodeTime = speexEncoder.getEncodeTime();
int bytesEncoded = speexEncoder.getBytesEncoded();
Logger.writeFile("Average Speex Encode time " +
(((float)encodeTime / encodes) / 1000000000.)
+ " seconds");
if (bytesEncoded > 0) {
Logger.writeFile("Average compression ratio " +
((encodes * speexEncoder.getPcmPacketSize()) / bytesEncoded)
+ " to 1");
}
}
}
if (outSampleRateConverter != null) {
outSampleRateConverter.printStatistics();
}
Logger.writeFile("");
Logger.flush();
}
}
public boolean memberIsReadyForSenderData() {
return initializationDone;
}
/**
* Get CallParticipant for this member
*/
public CallParticipant getCallParticipant() {
return cp;
}
public boolean mustSetMarkBit() {
return mustSetMarkBit;
}
public void mustSetMarkBit(boolean mustSetMarkBit) {
this.mustSetMarkBit = mustSetMarkBit;
}
private Recorder recorder;
private Integer recordingLock = new Integer(0);
private boolean recordRtp;
private void recordPacket(byte[] data, int length) {
if (cp.getToRecordingFile() == null) {
return;
}
if (recordRtp == false) {
return;
}
synchronized(recordingLock) {
if (recorder == null) {
return;
}
try {
recorder.writePacket(data, 0, length);
} catch (IOException e) {
Logger.println("Unable to record data " + e.getMessage());
cp.setToRecordingFile(null);
recorder = null;
}
}
}
private void recordAudio(byte[] data, int offset, int length) {
if (cp.getToRecordingFile() == null) {
return;
}
if (recordRtp == true) {
return;
}
synchronized(recordingLock) {
if (recorder == null) {
return;
}
try {
recorder.write(data, offset, length);
} catch (IOException e) {
Logger.println("Unable to record data " + e.getMessage());
cp.setToRecordingFile(null);
recorder = null;
}
}
}
public void setRecordToMember(boolean enabled, String recordingFile,
String recordingType) throws IOException {
synchronized (recordingLock) {
if (enabled == false) {
if (recorder != null) {
recorder.done();
}
cp.setToRecordingFile(null);
return;
}
if (recordingType == null) {
recordingType = "";
}
recordRtp = false;
if (recordingType.equalsIgnoreCase("Rtp")) {
recordRtp = true;
}
recorder = new Recorder(cp.getRecordDirectory(),
recordingFile, recordingType, myMediaInfo);
cp.setToRecordingFile(recordingFile);
cp.setToRecordingType(recordingType);
}
}
public void setDtmfKeyToSend(String dtmfKeyToSend) {
if (telephoneEventPayload == 0) {
String treatment;
if (dtmfKeyToSend.equals("*")) {
treatment = "dtmfStar.au";
} else if (dtmfKeyToSend.equals("#")) {
treatment = "dtmfPound.au";
} else {
treatment = "dtmf" + dtmfKeyToSend + ".au";
}
try {
TreatmentManager tm = new TreatmentManager(treatment,
0, conferenceMediaInfo.getSampleRate(),
conferenceMediaInfo.getChannels());
callHandler.getMember().addTreatment(tm);
} catch (IOException e) {
Logger.println("Unable to play dtmf key " + dtmfKeyToSend
+ " " + e.getMessage());
}
return;
}
if (dtmfKeyToSend != null) {
if (this.dtmfKeyToSend != null) {
this.dtmfKeyToSend += dtmfKeyToSend;
return;
}
}
this.dtmfKeyToSend = dtmfKeyToSend;
dtmfSendSequence = 0;
}
public void speexEncode(int[] intData, byte[] byteData)
throws SpeexException {
if (speexEncoder == null) {
speexEncoder = new SpeexEncoder(myMediaInfo.getSampleRate(),
myMediaInfo.getChannels());
Logger.println("Call " + cp + " created SpeexEncoder");
}
speexEncoder.encode(intData, byteData, RtpPacket.HEADER_SIZE);
}
private long encryptCount;
private long encryptTime;
public boolean needToEncrypt() {
return encryptCipher != null;
}
public void encrypt(byte[] data, int length) {
try {
encryptCount++;
long start = System.currentTimeMillis();
byte[] cipherText = encryptCipher.doFinal(data, 0, length);
encryptTime += (System.currentTimeMillis() - start);
senderPacket.setBuffer(cipherText);
} catch (Exception e) {
Logger.println("Call " + cp + " Encryption failed, length "
+ data.length + ": " + e.getMessage());
callHandler.cancelRequest("Encryption failed: " +
e.getMessage());
}
}
public String toString() {
return cp.toString();
}
public String toAbbreviatedString() {
String callId = cp.getCallId();
if (callId.length() < 14) {
return callId;
}
return cp.getCallId().substring(0, 13);
}
}