/*
* 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.MixDataSource;
import com.sun.voip.Recorder;
import com.sun.voip.RtpPacket;
import com.sun.voip.TreatmentManager;
import com.sun.voip.TreatmentDoneListener;
import com.sun.voip.Util;
import java.util.ArrayList;
import java.io.IOException;
import java.text.ParseException;
public class WhisperGroup implements MixDataSource, TreatmentDoneListener {
private String id;
private static double defaultAttenuation = .13;
private static boolean commonMixDefault = true;
private double attenuation;
private boolean isTransient = false;
private boolean isLocked = false;
private boolean noCommonMix = false;
private ArrayList members = new ArrayList(); // members in group
private ArrayList whisperers = new ArrayList(); // members whispering
private int[] linearMixBuffer;
private int[] doNotRecordMix;
private MediaInfo mediaInfo;
public WhisperGroup(String id, double attenuation, MediaInfo mediaInfo) {
this.id = id;
this.attenuation = attenuation;
this.mediaInfo = mediaInfo;
if (commonMixDefault == false) {
noCommonMix = true;
}
Logger.writeFile("New Whisper group: " + toString());
}
public static void setCommonMixDefault(boolean commonMixDefault) {
WhisperGroup.commonMixDefault = commonMixDefault;
}
public static boolean getCommonMixDefault() {
return commonMixDefault;
}
public void setMediaInfo(MediaInfo mediaInfo) {
this.mediaInfo = mediaInfo;
setAttenuation(attenuation); // reset attenuation
}
public String getId() {
return id;
}
public double getAttenuation() {
return attenuation;
}
public static double getDefaultAttenuation() {
return defaultAttenuation;
}
public static void setDefaultAttenuation(double defaultAttenuation) {
WhisperGroup.defaultAttenuation = defaultAttenuation;
}
public ArrayList getMembers() {
return members;
}
public int whisperCount() {
return whisperers.size();
}
public boolean isMember(ConferenceMember member) {
return members.contains(member);
}
private void removeWhisperer(ConferenceMember member) {
whisperers.remove(member);
}
/*
* For debugging
*/
public String checkMember(ConferenceMember member) {
String s = "";
if (members.contains(member) == false) {
s += "\t**** Not a member of the whisper group! ****\n";
}
if (whisperers.contains(member) == false) {
s += "\t**** Not in whisperers! ****\n";
}
return s;
}
public void addCall(ConferenceMember member) {
if (members.contains(member) == true) {
Logger.println(member + " is already in whisper group " + id);
return;
}
members.add(member);
}
public void removeCall(ConferenceMember member) {
if (members.contains(member) == false) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(member + " is not in whisper group " + id);
}
return;
}
members.remove(member);
}
public void setWhispering(boolean isWhispering, ConferenceMember member) {
if (isMember(member) == false) {
Logger.println("Call " + member
+ " is not a member of whisper group " + id);
return;
}
if (isWhispering) {
if (whisperers.contains(member)) {
return; // already whispering
}
whisperers.add(member);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + member + " started whispering to "
+ id);
}
return;
}
if (whisperers.contains(member) == false) {
Logger.println("Call " + member + " is not in whisperers!");
return;
}
removeWhisperer(member);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + member + " stopped whispering to " + id);
}
}
public void setTransient(boolean isTransient) {
this.isTransient = isTransient;
}
public boolean isTransient() {
return isTransient;
}
public void setLocked(boolean isLocked) {
this.isLocked = isLocked;
}
public boolean isLocked() {
return isLocked;
}
public void setAttenuation(double attenuation) {
this.attenuation = attenuation;
}
public boolean hasCommonMix() {
return noCommonMix == false;
}
public void setNoCommonMix(boolean noCommonMix) {
this.noCommonMix = noCommonMix;
}
public void forwardDtmf(MemberReceiver memberReceiver, String dtmfKeys) {
synchronized (members) {
for (int i = 0; i < members.size(); i++) {
ConferenceMember member = (ConferenceMember) members.get(i);
if (member.getMemberReceiver() != memberReceiver) {
member.getMemberSender().setDtmfKeyToSend(dtmfKeys);
}
}
}
}
/*
* This is called when data is received from a conference member
* The member has already converted its contribution to linear.
*/
public void addToLinearDataMix(int[] contribution, boolean doNotRecord)
{
if (doNotRecord) {
if (doNotRecordMix == null) {
doNotRecordMix = new int[contribution.length];
System.arraycopy(contribution, 0, doNotRecordMix, 0,
contribution.length);
return;
}
mixData(contribution, doNotRecordMix, true);
return;
}
if (linearMixBuffer == null) {
linearMixBuffer = new int[contribution.length];
System.arraycopy(contribution, 0, linearMixBuffer, 0,
contribution.length);
return;
}
mixData(contribution, linearMixBuffer, true);
}
public static void mixData(int[] inData, int[] mixData, boolean add)
{
//Logger.println("mixData - inData length " + inData.length + " mixData length " + mixData.length + " add " + add);
try {
if (add) {
if (inData.length <= mixData.length)
{
for (int i = 0; i < inData.length; i++) {
mixData[i] = mixData[i] + inData[i];
}
} else {
for (int i = 0; i < mixData.length; i++) {
mixData[i] = mixData[i] + inData[i];
}
}
} else {
if (inData.length <= mixData.length)
{
for (int i = 0; i < inData.length; i++) {
mixData[i] = mixData[i] - inData[i];
}
} else {
for (int i = 0; i < mixData.length; i++) {
mixData[i] = mixData[i] - inData[i];
}
}
}
} catch (IndexOutOfBoundsException e) {
Logger.println("Exception! inData length " + inData.length
+ " mixData length " + mixData.length + " add " + add);
e.printStackTrace();
}
}
public static void mixData(int[] conferenceData, int[] memberData, int[] outData)
{
//Logger.println("mixData - conferenceData length " + conferenceData.length +" memberData.length " + memberData.length + " outData length " + outData.length);
try {
if (outData.length <= memberData.length)
{
for (int i = 0; i < outData.length; i ++) {
outData[i] = conferenceData[i] - memberData[i];
}
} else {
for (int i = 0; i < memberData.length; i ++) {
outData[i] = conferenceData[i] - memberData[i];
}
}
} catch (IndexOutOfBoundsException e) {
Logger.println("mixData Exception! conferenceData length " + conferenceData.length +" memberData.length " + memberData.length + " outData length " + outData.length);
e.printStackTrace();
}
}
private int[] previousContribution;
private int[] currentContribution;
public String getSourceId() {
return id;
}
public boolean contributionIsInCommonMix() {
return hasCommonMix();
}
public int[] getPreviousContribution() {
return previousContribution;
}
public int[] getCurrentContribution() {
previousContribution = currentContribution;
return currentContribution;
}
public void saveCurrentContribution() {
currentContribution = linearMixBuffer;
linearMixBuffer = null;
if (currentTreatment != null) {
synchronized (conferenceTreatments) {
currentTreatment.saveCurrentContribution();
int[] treatmentData = currentTreatment.getCurrentContribution();
if (treatmentDone) {
conferenceTreatments.remove(currentTreatment);
startNextTreatment();
}
if (treatmentData != null) {
if (currentContribution == null) {
currentContribution = treatmentData;
} else {
mixData(treatmentData, currentContribution, true);
}
}
}
}
if (currentContribution != null) {
recordAudio(currentContribution, currentContribution.length);
}
if (doNotRecordMix != null) {
mixData(doNotRecordMix, currentContribution, true);
doNotRecordMix = null;
}
}
ArrayList conferenceTreatments = new ArrayList();
TreatmentManager currentTreatment;
boolean treatmentDone;
public void addTreatment(TreatmentManager treatmentManager) {
synchronized (conferenceTreatments) {
conferenceTreatments.add(treatmentManager);
if (currentTreatment == null) {
startNextTreatment();
}
}
}
public void pauseTreatment(String treatment, boolean isPaused) {
synchronized(conferenceTreatments) {
for (int i = 0; i < conferenceTreatments.size(); i++) {
TreatmentManager treatmentManager = (TreatmentManager)
conferenceTreatments.get(i);
if (treatmentManager.getId().equals(treatment)) {
treatmentManager.pause(isPaused);
break;
}
}
}
}
public void removeTreatment(String treatment) {
synchronized(conferenceTreatments) {
for (int i = 0; i < conferenceTreatments.size(); i++) {
TreatmentManager treatmentManager = (TreatmentManager)
conferenceTreatments.get(i);
if (treatmentManager.getId().equals(treatment)) {
if (currentTreatment == treatmentManager) {
treatmentManager.stopTreatment();
} else {
conferenceTreatments.remove(treatmentManager);
}
break;
}
}
}
}
public void treatmentDoneNotification(TreatmentManager treatmentManager) {
/*
* We cannot start the next treatment just yet because we got called
* by saveCurrentContribution() above and we still need to use
* current contribution to get the last chunk of data.
*/
treatmentDone = true;
}
private void startNextTreatment() {
treatmentDone = false;
if (conferenceTreatments.size() == 0) {
currentTreatment = null;
return;
}
currentTreatment = (TreatmentManager) conferenceTreatments.get(0);
currentTreatment.addTreatmentDoneListener(this);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("whisper group " + id
+ " Starting first treatment " + currentTreatment.getId());
}
}
private Recorder audioRecorder;
private String recordingFile;
private Integer recordingLock = new Integer(0);
public void recordConference(boolean enabled, String recordingFile,
String recordingType) throws IOException {
if (enabled == false) {
synchronized (recordingLock) {
if (audioRecorder != null) {
audioRecorder.done();
audioRecorder = null;
}
this.recordingFile = null;
}
return;
}
if (audioRecorder == null) {
synchronized (recordingLock) {
audioRecorder = new Recorder(recordingFile, "au",
mediaInfo);
Logger.println("starting conference recorder for "
+ recordingFile + " " + mediaInfo);
this.recordingFile = recordingFile;
}
}
}
public String getRecordingFile() {
return recordingFile;
}
private void recordAudio(int[] data, int length) {
synchronized (recordingLock) {
if (audioRecorder == null) {
return;
}
/*
* We have linear data to record.
* If the mediaInfo is PCMU, convert to PCMU before recording.
*/
try {
if (mediaInfo.getEncoding() != RtpPacket.PCMU_ENCODING) {
audioRecorder.write(data, 0, length);
} else {
byte[] ulawData = new byte[data.length];
AudioConversion.linearToUlaw(data, ulawData, 0);
audioRecorder.write(ulawData, 0, ulawData.length);
}
} catch (IOException e) {
Logger.println("Unable to record data " + e.getMessage());
audioRecorder = null;
}
}
}
public String toAbbreviatedString() {
return toAbbreviatedString(false);
}
public String toAbbreviatedString(boolean showMembers) {
String id = this.id;
if (id.length() >= 14) {
id = id.substring(0, 13);
}
String s = id + ":";
s += (Math.round(attenuation * 1000) / 1000D);
if (isTransient) {
s += " Transient";
}
if (isLocked) {
s += " Locked";
}
if (noCommonMix) {
s += " NoCommonMix";
}
if (showMembers == false) {
return s;
}
s += " ";
for (int j = 0; j < members.size(); j++) {
ConferenceMember member = (ConferenceMember) members.get(j);
CallParticipant cp = member.getCallParticipant();
id = cp.toConsiseString();
s += "'" + id;
if (whisperers.contains(member)) {
s += "+";
}
s += "' ";
}
return s;
}
public String toString() {
String s = id + ":";
s += mediaInfo + " ";
s += (Math.round(attenuation * 1000) / 1000D);
if (isTransient) {
s += " Transient";
}
if (isLocked) {
s += " Locked";
}
s += " ";
for (int i = 0; i < members.size(); i++) {
ConferenceMember member =
(ConferenceMember)members.get(i);
CallParticipant cp = member.getCallParticipant();
s += cp.getCallId();
if (whisperers.contains(member)) {
s += "+";
}
s += " ";
}
return s;
}
}