Commit 2f20b26c authored by Dele Olajide's avatar Dele Olajide Committed by dele

Rayo plugin - Implemented OPUS decoding/encoding for SIP calls

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13802 b35dd754-fafc-0310-a699-88a17e54d16e
parent de9cab2e
...@@ -124,17 +124,15 @@ if (false) { ...@@ -124,17 +124,15 @@ if (false) {
(byte)125, RtpPacket.SPEEX_ENCODING, 32000, 2, false)); (byte)125, RtpPacket.SPEEX_ENCODING, 32000, 2, false));
} }
public MediaInfo(byte payload , int encoding, int sampleRate, public MediaInfo(byte payload , int encoding, int sampleRate, int channels, boolean isTelephoneEventPayload)
int channels, boolean isTelephoneEventPayload) { {
this.payload = payload; this.payload = payload;
this.encoding = encoding; this.encoding = encoding;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.channels = channels; this.channels = channels;
this.isTelephoneEventPayload = isTelephoneEventPayload; this.isTelephoneEventPayload = isTelephoneEventPayload;
samplesPerPacket = samplesPerPacket = sampleRate * channels / (1000 / RtpPacket.PACKET_PERIOD);
sampleRate * channels / (1000 / RtpPacket.PACKET_PERIOD);
} }
public static MediaInfo findMediaInfo(int encoding, int sampleRate, public static MediaInfo findMediaInfo(int encoding, int sampleRate,
......
...@@ -224,8 +224,7 @@ public class SdpInfo { ...@@ -224,8 +224,7 @@ public class SdpInfo {
+ encoding + "/" + sampleRate + "/" + channels); + encoding + "/" + sampleRate + "/" + channels);
} }
public MediaInfo findBestMediaInfo(Vector otherSupportedMedia, public MediaInfo findBestMediaInfo(Vector otherSupportedMedia, MediaInfo otherMediaPreference) throws IOException {
MediaInfo otherMediaPreference) throws IOException {
MediaInfo best = null; MediaInfo best = null;
......
...@@ -124,15 +124,12 @@ public class SdpManager { ...@@ -124,15 +124,12 @@ public class SdpManager {
for (int i = 0; i < supportedMedia.size(); i++) { for (int i = 0; i < supportedMedia.size(); i++) {
MediaInfo mediaInfo = (MediaInfo) supportedMedia.elementAt(i); MediaInfo mediaInfo = (MediaInfo) supportedMedia.elementAt(i);
if (mediaInfo.getSampleRate() > maxSampleRate || if (mediaInfo.getSampleRate() > maxSampleRate || mediaInfo.getChannels() > maxChannels) {
mediaInfo.getChannels() > maxChannels) {
continue; continue;
} }
if (useTelephoneEvent == false && if (useTelephoneEvent == false && mediaInfo.isTelephoneEventPayload())
mediaInfo.isTelephoneEventPayload()) { {
continue; continue;
} }
...@@ -153,13 +150,12 @@ public class SdpManager { ...@@ -153,13 +150,12 @@ public class SdpManager {
private String getRtpmaps() { private String getRtpmaps() {
String rtpmaps = ""; String rtpmaps = "";
for (int i = 0; i < supportedMedia.size(); i++) { for (int i = 0; i < supportedMedia.size(); i++)
MediaInfo mediaInfo = (MediaInfo) {
supportedMedia.elementAt(i); MediaInfo mediaInfo = (MediaInfo) supportedMedia.elementAt(i);
if (mediaInfo.getSampleRate() > maxSampleRate ||
mediaInfo.getChannels() > maxChannels) {
if (mediaInfo.getSampleRate() > maxSampleRate || mediaInfo.getChannels() > maxChannels)
{
continue; continue;
} }
...@@ -267,25 +263,23 @@ public class SdpManager { ...@@ -267,25 +263,23 @@ public class SdpManager {
return sdp; return sdp;
} }
public String generateSdp(String name, InetSocketAddress isa, public String generateSdp(String name, InetSocketAddress isa, SdpInfo remoteSdpInfo) throws IOException
SdpInfo remoteSdpInfo) throws IOException { {
MediaInfo mediaInfo = null; MediaInfo mediaInfo = null;
if (localMediaPreference != null) { if (localMediaPreference != null)
{
if (remoteSdpInfo.isSupported(localMediaPreference)) { if (remoteSdpInfo.isSupported(localMediaPreference)) {
mediaInfo = localMediaPreference; mediaInfo = localMediaPreference;
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Using local media preference: " + mediaInfo); Logger.println("Using local media preference: " + mediaInfo);
} }
} }
}
/* /*
* Try remote media preference * Try remote media preference
*/ */
if (mediaInfo == null && remoteSdpInfo.preferredMediaSpecified()) { if (remoteSdpInfo.preferredMediaSpecified())
{
MediaInfo remoteMediaPreference = remoteSdpInfo.getMediaInfo(); MediaInfo remoteMediaPreference = remoteSdpInfo.getMediaInfo();
if (remoteMediaPreference.getSampleRate() <= maxSampleRate && if (remoteMediaPreference.getSampleRate() <= maxSampleRate &&
...@@ -295,11 +289,8 @@ public class SdpManager { ...@@ -295,11 +289,8 @@ public class SdpManager {
* See if remote media preference is supported * See if remote media preference is supported
*/ */
try { try {
mediaInfo = mediaInfo = findMediaInfo(remoteMediaPreference.getPayload());
findMediaInfo(remoteMediaPreference.getPayload()); Logger.println("Using remote media preference: " + mediaInfo);
Logger.println("Using remote media preference: "
+ mediaInfo);
} catch (ParseException e) { } catch (ParseException e) {
} }
} }
...@@ -309,8 +300,7 @@ public class SdpManager { ...@@ -309,8 +300,7 @@ public class SdpManager {
/* /*
* default to 8000/1 ulaw * default to 8000/1 ulaw
*/ */
mediaInfo = remoteSdpInfo.findBestMediaInfo(supportedMedia, mediaInfo = remoteSdpInfo.findBestMediaInfo(supportedMedia, localMediaPreference);
localMediaPreference);
Logger.println("Using best media " + mediaInfo); Logger.println("Using best media " + mediaInfo);
} }
...@@ -329,8 +319,7 @@ public class SdpManager { ...@@ -329,8 +319,7 @@ public class SdpManager {
payloads += " " + telephoneEventPayload; payloads += " " + telephoneEventPayload;
telephoneEvent += generateRtpmap(m) + "\r\n"; telephoneEvent += generateRtpmap(m) + "\r\n";
} catch (ParseException e) { } catch (ParseException e) {
Logger.println("Failed to add rtpmap for telephone event " Logger.println("Failed to add rtpmap for telephone event " + telephoneEventPayload);
+ telephoneEventPayload);
} }
} }
......
...@@ -172,7 +172,7 @@ public class SdpParser { ...@@ -172,7 +172,7 @@ public class SdpParser {
+ payloads[i], 0); + payloads[i], 0);
} }
if (payload != 0 && payload < 96 || payload > 127) { if (payload != 0 && (payload < 96 || payload > 127)) {
/* /*
* Not one we can deal with * Not one we can deal with
*/ */
......
...@@ -323,10 +323,9 @@ public abstract class CallHandler extends Thread { ...@@ -323,10 +323,9 @@ public abstract class CallHandler extends Thread {
* Send indication when a dtmf key is pressed * Send indication when a dtmf key is pressed
*/ */
public void dtmfKeys(String dtmfKeys) { public void dtmfKeys(String dtmfKeys) {
if (Logger.logLevel >= Logger.LOG_MOREINFO) { //if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println(cp + " got dtmf keys " + dtmfKeys + " " Logger.println(cp + " got dtmf keys " + dtmfKeys + " " + cp.dtmfDetection());
+ cp.dtmfDetection()); //}
}
if (isCallEstablished()) { if (isCallEstablished()) {
if (cp.dtmfDetection()) { if (cp.dtmfDetection()) {
......
...@@ -97,13 +97,21 @@ public class IncomingCallHandler extends CallHandler ...@@ -97,13 +97,21 @@ public class IncomingCallHandler extends CallHandler
addCallEventListener(this); addCallEventListener(this);
if (cp.getConferenceId() == null || cp.getConferenceId().length() == 0)
{
if (directConferencing) if (directConferencing)
{
if (cp.getConferenceId() == null || cp.getConferenceId().length() == 0)
{ {
System.out.println("Don't have conf, using default...."); System.out.println("Don't have conf, using default....");
cp.setConferenceId(defaultIncomingConferenceId); // wait in lobby cp.setConferenceId(defaultIncomingConferenceId); // wait in lobby
} else {
Logger.println("Have conf " + cp.getConferenceId());
haveIncomingConferenceId = true; // goto your conference
}
start();
} else { } else {
System.out.println("Incoming SIP, call " + cp); System.out.println("Incoming SIP, call " + cp);
...@@ -111,6 +119,7 @@ public class IncomingCallHandler extends CallHandler ...@@ -111,6 +119,7 @@ public class IncomingCallHandler extends CallHandler
if (RayoComponent.self.routeIncomingSIP(cp)) if (RayoComponent.self.routeIncomingSIP(cp))
{ {
haveIncomingConferenceId = true; haveIncomingConferenceId = true;
start();
} else { } else {
// conf bridge // conf bridge
...@@ -118,10 +127,12 @@ public class IncomingCallHandler extends CallHandler ...@@ -118,10 +127,12 @@ public class IncomingCallHandler extends CallHandler
if (Config.getInstance().getConferenceExten().equals(cp.getToPhoneNumber())) if (Config.getInstance().getConferenceExten().equals(cp.getToPhoneNumber()))
{ {
incomingConferenceHandler = new IncomingConferenceHandler(this, cp.getToPhoneNumber()); incomingConferenceHandler = new IncomingConferenceHandler(this, cp.getToPhoneNumber());
start();
} else if (Config.getInstance().getConferenceByPhone(cp.getToPhoneNumber()) != null) { } else if (Config.getInstance().getConferenceByPhone(cp.getToPhoneNumber()) != null) {
incomingConferenceHandler = new IncomingConferenceHandler(this, cp.getToPhoneNumber()); incomingConferenceHandler = new IncomingConferenceHandler(this, cp.getToPhoneNumber());
start();
} else { } else {
cancelRequest(cp.getToPhoneNumber() + " is not a valid endpoint"); // reject call cancelRequest(cp.getToPhoneNumber() + " is not a valid endpoint"); // reject call
...@@ -129,14 +140,6 @@ public class IncomingCallHandler extends CallHandler ...@@ -129,14 +140,6 @@ public class IncomingCallHandler extends CallHandler
} }
} }
} else {
Logger.println("Have conf " + cp.getConferenceId());
haveIncomingConferenceId = true; // goto your conference
}
start();
} }
public static void setDirectConferencing(boolean directConferencing) { public static void setDirectConferencing(boolean directConferencing) {
...@@ -350,6 +353,9 @@ public class IncomingCallHandler extends CallHandler ...@@ -350,6 +353,9 @@ public class IncomingCallHandler extends CallHandler
public void callEventNotification(CallEvent callEvent) { public void callEventNotification(CallEvent callEvent) {
Logger.println("IncomingCallHandler " + callEvent.toString());
if (callEvent.equals(callEvent.STATE_CHANGED) && if (callEvent.equals(callEvent.STATE_CHANGED) &&
callEvent.getCallState().equals(CallState.ESTABLISHED)) { callEvent.getCallState().equals(CallState.ESTABLISHED)) {
...@@ -461,6 +467,8 @@ public class IncomingCallHandler extends CallHandler ...@@ -461,6 +467,8 @@ public class IncomingCallHandler extends CallHandler
public ConferenceManager transferCall(String conferenceId) public ConferenceManager transferCall(String conferenceId)
throws IOException { throws IOException {
System.out.println("transferCall " + conferenceId);
ConferenceManager conferenceManager = transferCall(this, conferenceId); ConferenceManager conferenceManager = transferCall(this, conferenceId);
String s = getNumberOfCallsAsTreatment(conferenceManager.getNumberOfMembers()); String s = getNumberOfCallsAsTreatment(conferenceManager.getNumberOfMembers());
......
...@@ -84,6 +84,8 @@ public class IncomingConferenceHandler extends Thread ...@@ -84,6 +84,8 @@ public class IncomingConferenceHandler extends Thread
this.phoneNo = phoneNo; this.phoneNo = phoneNo;
incomingCallHandler.addCallEventListener(this); incomingCallHandler.addCallEventListener(this);
Logger.println("IncomingConferenceHandler: " + phoneNo);
} }
private String lastMessagePlayed; private String lastMessagePlayed;
...@@ -121,9 +123,9 @@ public class IncomingConferenceHandler extends Thread ...@@ -121,9 +123,9 @@ public class IncomingConferenceHandler extends Thread
* Called when status for an incoming call changes. * Called when status for an incoming call changes.
*/ */
public void callEventNotification(CallEvent callEvent) { public void callEventNotification(CallEvent callEvent) {
if (Logger.logLevel >= Logger.LOG_INFO) { //if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(callEvent.toString()); Logger.println("IncomingConferenceHandler " + callEvent.toString());
} //}
if (callEvent.equals(CallEvent.STATE_CHANGED) && if (callEvent.equals(CallEvent.STATE_CHANGED) &&
callEvent.getCallState().equals(CallState.ESTABLISHED)) { callEvent.getCallState().equals(CallState.ESTABLISHED)) {
...@@ -132,18 +134,19 @@ public class IncomingConferenceHandler extends Thread ...@@ -132,18 +134,19 @@ public class IncomingConferenceHandler extends Thread
* New incoming call * New incoming call
*/ */
if (callEvent.getInfo() != null) { if (callEvent.getInfo() != null) {
Logger.println("IncomingConferenceHandler: " Logger.println("IncomingConferenceHandler: " + callEvent.getInfo());
+ callEvent.getInfo());
} }
if (Config.getInstance().getMeetingCode(phoneNo) != null) if (Config.getInstance().getMeetingCode(phoneNo) != null)
{ {
meetingCode = Config.getInstance().getMeetingCode(phoneNo); meetingCode = Config.getInstance().getMeetingCode(phoneNo);
Logger.println("IncomingConferenceHandler: meeting code " + meetingCode);
if (Config.getInstance().getPassCode(meetingCode, phoneNo) == null) if (Config.getInstance().getPassCode(meetingCode, phoneNo) == null)
{ {
try { try {
incomingCallHandler.transferCall(Config.getInstance().getMeetingCode(phoneNo)); incomingCallHandler.transferCall(meetingCode);
state = IN_MEETING; state = IN_MEETING;
} catch (IOException e) { } catch (IOException e) {
......
...@@ -71,6 +71,7 @@ import java.awt.Point; ...@@ -71,6 +71,7 @@ import java.awt.Point;
import org.ifsoft.*; import org.ifsoft.*;
import org.ifsoft.rtp.*; import org.ifsoft.rtp.*;
import org.jitsi.impl.neomedia.codec.audio.opus.Opus;
/** /**
* Receive RTP data for this ConferenceMember, add it to the mix * Receive RTP data for this ConferenceMember, add it to the mix
...@@ -116,6 +117,15 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener { ...@@ -116,6 +117,15 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener {
private SpeexDecoder speexDecoder; private SpeexDecoder speexDecoder;
private long opusDecoder = 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 int dropPackets; private int dropPackets;
private boolean done = false; private boolean done = false;
...@@ -451,6 +461,23 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener { ...@@ -451,6 +461,23 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener {
callHandler.cancelRequest(e.getMessage()); callHandler.cancelRequest(e.getMessage());
return; return;
} }
} else if (myMediaInfo.getEncoding() == RtpPacket.PCM_ENCODING) {
try {
opusDecoder = Opus.decoder_create(opusSampleRate, opusChannels);
if (opusDecoder == 0)
{
Logger.println("Call " + cp + " OPUS decoder creation error ");
callHandler.cancelRequest("OPUS decoder creation error ");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
} }
if (cp.getJoinConfirmationTimeout() == 0) { if (cp.getJoinConfirmationTimeout() == 0) {
...@@ -1170,9 +1197,7 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener { ...@@ -1170,9 +1197,7 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener {
callHandler.getMember().adjustVolume(data, inputVolume); callHandler.getMember().adjustVolume(data, inputVolume);
} }
//Logger.println("Call " + cp //Logger.println("Call " + cp + " receiveMedia length " + length + " decoded int length " + data.length);
// + " receiveMedia length " + length + " decoded int length "
// + data.length);
int numberOfSamples = data.length; int numberOfSamples = data.length;
...@@ -1246,18 +1271,20 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener { ...@@ -1246,18 +1271,20 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener {
return numberOfSamples; return numberOfSamples;
} }
private int[] decodeToLinear(byte[] receivedData, int length) private int[] decodeToLinear(byte[] receivedData, int length) throws SpeexException
throws SpeexException { {
/* /*
* receivedData has the 12 byte RTP header. * receivedData has the 12 byte RTP header.
*/ */
int[] data = new int[myMediaInfo.getSamplesPerPacket()]; int[] data = new int[myMediaInfo.getSamplesPerPacket()];
long start = 0; long start = 0;
if (myMediaInfo.getEncoding() == RtpPacket.PCMU_ENCODING) { if (myMediaInfo.getEncoding() == RtpPacket.PCMU_ENCODING)
if (traceCall || Logger.logLevel == -1) { {
if (traceCall || Logger.logLevel == -1)
{
start = System.nanoTime(); start = System.nanoTime();
} }
...@@ -1268,32 +1295,47 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener { ...@@ -1268,32 +1295,47 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener {
* If the incoming packet is shorter, than we expect, * If the incoming packet is shorter, than we expect,
* the rest of <data> will be filled with 0 * which is PCM_SILENCE. * the rest of <data> will be filled with 0 * which is PCM_SILENCE.
*/ */
AudioConversion.ulawToLinear(receivedData, RtpPacket.HEADER_SIZE,
length - RtpPacket.HEADER_SIZE, data); AudioConversion.ulawToLinear(receivedData, RtpPacket.HEADER_SIZE, length - RtpPacket.HEADER_SIZE, data);
if (length < 172 && Logger.logLevel >= Logger.LOG_DETAIL) { if (length < 172 && Logger.logLevel >= Logger.LOG_DETAIL) {
Logger.println("Call " + cp + " received short packet " Logger.println("Call " + cp + " received short packet " + length);
+ length);
} }
if (traceCall || Logger.logLevel == -1) { if (traceCall || Logger.logLevel == -1) {
Logger.println("Call " + cp + " ulawToLinear time " Logger.println("Call " + cp + " ulawToLinear time " + ((System.nanoTime() - start) / 1000000000.) + " seconds");
+ ((System.nanoTime() - start) / 1000000000.) }
+ " seconds");
} else if (myMediaInfo.getEncoding() == RtpPacket.PCM_ENCODING) {
int inputOffset = RtpPacket.HEADER_SIZE;
int inputLength = length - RtpPacket.HEADER_SIZE;
int frameSizeInSamplesPerChannel = Opus.decoder_get_nb_samples(opusDecoder, receivedData, inputOffset, inputLength);
if (frameSizeInSamplesPerChannel > 1)
{
int frameSizeInBytes = outputFrameSize * opusChannels * frameSizeInSamplesPerChannel;
byte[] output = new byte[frameSizeInBytes];
frameSizeInSamplesPerChannel = Opus.decode(opusDecoder, receivedData, inputOffset, inputLength, output, 0, frameSizeInSamplesPerChannel, 0);
data = AudioConversion.bytesToLittleEndianInts(output);
} }
} else if (myMediaInfo.getEncoding() == RtpPacket.SPEEX_ENCODING) { } else if (myMediaInfo.getEncoding() == RtpPacket.SPEEX_ENCODING) {
if (traceCall || Logger.logLevel == -1) { if (traceCall || Logger.logLevel == -1) {
start = System.nanoTime(); start = System.nanoTime();
} }
data = speexDecoder.decodeToIntArray(receivedData, data = speexDecoder.decodeToIntArray(receivedData, RtpPacket.HEADER_SIZE, length - RtpPacket.HEADER_SIZE);
RtpPacket.HEADER_SIZE, length - RtpPacket.HEADER_SIZE);
if (traceCall || Logger.logLevel == -1) { if (traceCall || Logger.logLevel == -1)
Logger.println("Call " + cp + " speex decode time " {
+ ((System.nanoTime() - start) / 1000000000.) Logger.println("Call " + cp + " speex decode time " + ((System.nanoTime() - start) / 1000000000.) + " seconds");
+ " seconds");
} }
} else { } else {
AudioConversion.bytesToInts(receivedData, RtpPacket.HEADER_SIZE, AudioConversion.bytesToInts(receivedData, RtpPacket.HEADER_SIZE,
length - RtpPacket.HEADER_SIZE, data); length - RtpPacket.HEADER_SIZE, data);
...@@ -1877,6 +1919,12 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener { ...@@ -1877,6 +1919,12 @@ public class MemberReceiver implements MixDataSource, TreatmentDoneListener {
} }
} }
} }
if (opusDecoder != 0)
{
Opus.decoder_destroy(opusDecoder);
opusDecoder = 0;
}
} }
public void printStatistics() { public void printStatistics() {
......
...@@ -55,6 +55,8 @@ import org.xmpp.jnodes.RelayChannel; ...@@ -55,6 +55,8 @@ import org.xmpp.jnodes.RelayChannel;
import org.ifsoft.*; import org.ifsoft.*;
import org.ifsoft.rtp.*; import org.ifsoft.rtp.*;
import org.jitsi.impl.neomedia.codec.audio.opus.Opus;
/** /**
* Send RTP data to this ConferenceMember, * Send RTP data to this ConferenceMember,
*/ */
...@@ -73,6 +75,14 @@ public class MemberSender { ...@@ -73,6 +75,14 @@ public class MemberSender {
private RtpSenderPacket senderPacket; private RtpSenderPacket senderPacket;
private SpeexEncoder speexEncoder; 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 InetSocketAddress memberAddress;
private boolean done = false; private boolean done = false;
...@@ -265,6 +275,23 @@ public class MemberSender { ...@@ -265,6 +275,23 @@ public class MemberSender {
} }
} }
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; initializationDone = true;
...@@ -411,6 +438,8 @@ public class MemberSender { ...@@ -411,6 +438,8 @@ public class MemberSender {
//Logger.println("Call " + cp + " Sending data..."); //Logger.println("Call " + cp + " Sending data...");
byte[] opusBytes = null;
if (myMediaInfo.getEncoding() == RtpPacket.PCMU_ENCODING) { if (myMediaInfo.getEncoding() == RtpPacket.PCMU_ENCODING) {
/* /*
* Convert to ulaw * Convert to ulaw
...@@ -433,13 +462,27 @@ public class MemberSender { ...@@ -433,13 +462,27 @@ public class MemberSender {
Logger.println("Call " + this + ": " + e.getMessage()); Logger.println("Call " + this + ": " + e.getMessage());
return false; return false;
} }
} else if (myMediaInfo.getEncoding() == RtpPacket.PCM_ENCODING) {
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 { } else {
AudioConversion.intsToBytes(dataToSend, rtpData, RtpPacket.HEADER_SIZE); AudioConversion.intsToBytes(dataToSend, rtpData, RtpPacket.HEADER_SIZE);
} }
recordPacket(rtpData, senderPacket.getLength()); recordPacket(rtpData, senderPacket.getLength());
recordAudio(rtpData, RtpPacket.HEADER_SIZE, recordAudio(rtpData, RtpPacket.HEADER_SIZE, senderPacket.getLength() - RtpPacket.HEADER_SIZE);
senderPacket.getLength() - RtpPacket.HEADER_SIZE);
/* /*
* Encrypt data if required * Encrypt data if required
...@@ -469,14 +512,12 @@ public class MemberSender { ...@@ -469,14 +512,12 @@ public class MemberSender {
try { try {
senderPacket.setSocketAddress(memberAddress); senderPacket.setSocketAddress(memberAddress);
datagramChannel.send( datagramChannel.send(ByteBuffer.wrap(senderPacket.getData(), 0, senderPacket.getLength()), memberAddress);
ByteBuffer.wrap(senderPacket.getData(), 0,
senderPacket.getLength()), memberAddress);
if (Logger.logLevel >= Logger.LOG_MOREDETAIL) { if (Logger.logLevel >= Logger.LOG_MOREDETAIL) {
Logger.writeFile("Call " + cp + " back from sending data"); Logger.writeFile("Call " + cp + " back from sending data");
} }
} catch (IOException e) { } catch (Exception e) {
if (!done) { if (!done) {
Logger.error("Call " + cp + " sendData " + e.getMessage()); Logger.error("Call " + cp + " sendData " + e.getMessage());
e.printStackTrace(); e.printStackTrace();
...@@ -488,7 +529,8 @@ public class MemberSender { ...@@ -488,7 +529,8 @@ public class MemberSender {
} else { } else {
try { try {
getWebRTCParticipant().pushAudio(rtpData, dataToSend);
getWebRTCParticipant().pushAudio(senderPacket.getData(), opusBytes);
} catch (Exception e) { } catch (Exception e) {
...@@ -758,8 +800,7 @@ public class MemberSender { ...@@ -758,8 +800,7 @@ public class MemberSender {
senderPacket.setSocketAddress(memberAddress); senderPacket.setSocketAddress(memberAddress);
try { try {
datagramChannel.send( datagramChannel.send(ByteBuffer.wrap(senderPacket.getData()), memberAddress);
ByteBuffer.wrap(senderPacket.getData()), memberAddress);
} catch (IOException e) { } catch (IOException e) {
if (!done) { if (!done) {
Logger.println("Call " + cp + " sendComfortNoisePayload " Logger.println("Call " + cp + " sendComfortNoisePayload "
...@@ -864,6 +905,12 @@ public class MemberSender { ...@@ -864,6 +905,12 @@ public class MemberSender {
recorder = null; recorder = null;
} }
} }
if (opusEncoder != 0)
{
Opus.encoder_destroy(opusEncoder);
opusEncoder = 0;
}
} }
public void printStatistics() { public void printStatistics() {
......
...@@ -508,8 +508,11 @@ public class MixManager { ...@@ -508,8 +508,11 @@ public class MixManager {
memberMixDescriptor.getMixDataSource().getCurrentContribution(); memberMixDescriptor.getMixDataSource().getCurrentContribution();
if (memberContribution == null) { if (memberContribution == null) {
System.arraycopy(conferenceMixContribution, 0, outData, 0,
conferenceMixContribution.length); if (outData.length <= conferenceMixContribution.length)
System.arraycopy(conferenceMixContribution, 0, outData, 0, outData.length);
else
System.arraycopy(conferenceMixContribution, 0, outData, 0, conferenceMixContribution.length);
if (Logger.logLevel == -39) { if (Logger.logLevel == -39) {
checkData(outData, useFastMix); checkData(outData, useFastMix);
...@@ -519,8 +522,7 @@ public class MixManager { ...@@ -519,8 +522,7 @@ public class MixManager {
return outData; return outData;
} }
WhisperGroup.mixData(conferenceMixContribution, memberContribution, WhisperGroup.mixData(conferenceMixContribution, memberContribution, outData);
outData);
if (Logger.logLevel == -39) { if (Logger.logLevel == -39) {
checkData(outData, useFastMix); checkData(outData, useFastMix);
......
...@@ -74,8 +74,7 @@ public class SipIncomingCallAgent extends CallSetupAgent implements SipListener ...@@ -74,8 +74,7 @@ public class SipIncomingCallAgent extends CallSetupAgent implements SipListener
sipServerCallback = SipServer.getSipServerCallback(); sipServerCallback = SipServer.getSipServerCallback();
MediaInfo mixerMediaPreference = MediaInfo mixerMediaPreference = callHandler.getConferenceManager().getMediaInfo();
callHandler.getConferenceManager().getMediaInfo();
sipUtil = new SipUtil(mixerMediaPreference); sipUtil = new SipUtil(mixerMediaPreference);
......
...@@ -87,8 +87,7 @@ public class SipTPCCallAgent extends CallSetupAgent implements SipListener { ...@@ -87,8 +87,7 @@ public class SipTPCCallAgent extends CallSetupAgent implements SipListener {
public SipTPCCallAgent(CallHandler callHandler) { public SipTPCCallAgent(CallHandler callHandler) {
super(callHandler); super(callHandler);
MediaInfo mixerMediaPreference = MediaInfo mixerMediaPreference = callHandler.getConferenceManager().getMediaInfo();
callHandler.getConferenceManager().getMediaInfo();
sipUtil = new SipUtil(mixerMediaPreference); sipUtil = new SipUtil(mixerMediaPreference);
} }
......
...@@ -74,21 +74,23 @@ public class SipUtil { ...@@ -74,21 +74,23 @@ public class SipUtil {
this(null); this(null);
} }
public SipUtil(MediaInfo mediaInfo) { public SipUtil(MediaInfo mediaInfo)
{
if (!initialized) { if (!initialized) {
initialize(); initialize();
} }
sdpManager = new SdpManager(); sdpManager = new SdpManager();
if (mediaInfo == null) { if (mediaInfo == null)
{
try { try {
mediaInfo = sdpManager.findMediaInfo(RtpPacket.PCMU_ENCODING, mediaInfo = sdpManager.findMediaInfo(RtpPacket.PCMU_ENCODING, 8000, 1);
8000, 1);
Logger.println("SipUtil: Preference default media " + mediaInfo);
} catch (ParseException e) { } catch (ParseException e) {
Logger.println( Logger.println("SipUtil: Invalid media info, can't set preference" + e.getMessage());
"SipUtil: Invalid media info, can't set preference"
+ e.getMessage());
} }
} }
...@@ -1042,9 +1044,8 @@ if (false) { ...@@ -1042,9 +1044,8 @@ if (false) {
return getSdpInfo(sdpBody, true); return getSdpInfo(sdpBody, true);
} }
public SdpInfo getSdpInfo(String sdpBody, boolean isRequest) public SdpInfo getSdpInfo(String sdpBody, boolean isRequest) throws ParseException
throws ParseException { {
SdpInfo remoteSdpInfo = sdpManager.parseSdp(sdpBody); SdpInfo remoteSdpInfo = sdpManager.parseSdp(sdpBody);
MediaInfo myPreferredMediaInfo = sdpManager.getPreferredMediaInfo(); MediaInfo myPreferredMediaInfo = sdpManager.getPreferredMediaInfo();
...@@ -1065,18 +1066,16 @@ if (false) { ...@@ -1065,18 +1066,16 @@ if (false) {
Logger.println("My preferred payload being used " + payload); Logger.println("My preferred payload being used " + payload);
} else { } else {
if (isRequest) { if (isRequest) {
Logger.writeFile("My preferred media " Logger.writeFile("My preferred media " + myPreferredMediaInfo + " not supported...");
+ myPreferredMediaInfo + " not supported...");
} }
try { try {
payload = remoteSdpInfo.getMediaInfo().getPayload(); payload = remoteSdpInfo.getMediaInfo().getPayload();
remoteSdpInfo.setMediaInfo(sdpManager.findMediaInfo(payload)); remoteSdpInfo.setMediaInfo(sdpManager.findMediaInfo(payload));
Logger.writeFile("media setting is " Logger.writeFile("media setting is " + remoteSdpInfo.getMediaInfo());
+ remoteSdpInfo.getMediaInfo());
} catch (ParseException e) { } catch (ParseException e) {
throw new ParseException("Unsupported media " remoteSdpInfo.setMediaInfo(new MediaInfo((byte)0, RtpPacket.PCMU_ENCODING, 8000, 1, false));
+ remoteSdpInfo.getMediaInfo(), 0);
} }
} }
return remoteSdpInfo; return remoteSdpInfo;
......
...@@ -233,7 +233,8 @@ public class WhisperGroup implements MixDataSource, TreatmentDoneListener { ...@@ -233,7 +233,8 @@ public class WhisperGroup implements MixDataSource, TreatmentDoneListener {
* This is called when data is received from a conference member * This is called when data is received from a conference member
* The member has already converted its contribution to linear. * The member has already converted its contribution to linear.
*/ */
public void addToLinearDataMix(int[] contribution, boolean doNotRecord) { public void addToLinearDataMix(int[] contribution, boolean doNotRecord)
{
if (doNotRecord) { if (doNotRecord) {
if (doNotRecordMix == null) { if (doNotRecordMix == null) {
doNotRecordMix = new int[contribution.length]; doNotRecordMix = new int[contribution.length];
...@@ -258,17 +259,40 @@ public class WhisperGroup implements MixDataSource, TreatmentDoneListener { ...@@ -258,17 +259,40 @@ public class WhisperGroup implements MixDataSource, TreatmentDoneListener {
mixData(contribution, linearMixBuffer, true); mixData(contribution, linearMixBuffer, true);
} }
public static void mixData(int[] inData, int[] mixData, boolean add) { public static void mixData(int[] inData, int[] mixData, boolean add)
{
//Logger.println("mixData - inData length " + inData.length + " mixData length " + mixData.length + " add " + add);
try { try {
if (add) { if (add) {
if (inData.length <= mixData.length)
{
for (int i = 0; i < inData.length; i++) { for (int i = 0; i < inData.length; i++) {
mixData[i] = mixData[i] + inData[i]; mixData[i] = mixData[i] + inData[i];
} }
} else { } 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++) { for (int i = 0; i < inData.length; i++) {
mixData[i] = mixData[i] - inData[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) { } catch (IndexOutOfBoundsException e) {
Logger.println("Exception! inData length " + inData.length Logger.println("Exception! inData length " + inData.length
+ " mixData length " + mixData.length + " add " + add); + " mixData length " + mixData.length + " add " + add);
...@@ -277,17 +301,25 @@ public class WhisperGroup implements MixDataSource, TreatmentDoneListener { ...@@ -277,17 +301,25 @@ public class WhisperGroup implements MixDataSource, TreatmentDoneListener {
} }
} }
public static void mixData(int[] conferenceData, int[] memberData, public static void mixData(int[] conferenceData, int[] memberData, int[] outData)
int[] outData) { {
//Logger.println("mixData - conferenceData length " + conferenceData.length +" memberData.length " + memberData.length + " outData length " + outData.length);
try { try {
if (outData.length <= memberData.length)
{
for (int i = 0; i < outData.length; i ++) { for (int i = 0; i < outData.length; i ++) {
outData[i] = conferenceData[i] - memberData[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) { } catch (IndexOutOfBoundsException e) {
Logger.println("Exception! conferenceData length " Logger.println("mixData Exception! conferenceData length " + conferenceData.length +" memberData.length " + memberData.length + " outData length " + outData.length);
+ conferenceData.length +" memberData.length "
+ memberData.length + " outData length " + outData.length);
e.printStackTrace(); e.printStackTrace();
} }
......
...@@ -558,7 +558,6 @@ public class RayoComponent extends AbstractComponent ...@@ -558,7 +558,6 @@ public class RayoComponent extends AbstractComponent
{ {
if (channel == null) if (channel == null)
{ {
cp.setMediaPreference("PCMU/8000/1");
cp.setPhoneNumber(handset.sipuri); cp.setPhoneNumber(handset.sipuri);
cp.setAutoAnswer(true); cp.setAutoAnswer(true);
cp.setProtocol("SIP"); cp.setProtocol("SIP");
...@@ -1037,7 +1036,6 @@ public class RayoComponent extends AbstractComponent ...@@ -1037,7 +1036,6 @@ public class RayoComponent extends AbstractComponent
CallParticipant cp = new CallParticipant(); CallParticipant cp = new CallParticipant();
cp.setVoiceDetection(true); cp.setVoiceDetection(true);
cp.setCallOwner(handsetId); cp.setCallOwner(handsetId);
cp.setMediaPreference("PCMU/8000/1");
cp.setProtocol("SIP"); cp.setProtocol("SIP");
cp.setDisplayName(callerName); cp.setDisplayName(callerName);
cp.setPhoneNumber(to); cp.setPhoneNumber(to);
...@@ -1063,6 +1061,7 @@ public class RayoComponent extends AbstractComponent ...@@ -1063,6 +1061,7 @@ public class RayoComponent extends AbstractComponent
CallParticipant hp = handsetHandler.getCallParticipant(); CallParticipant hp = handsetHandler.getCallParticipant();
headers.put("mixer_name", hp.getConferenceId()); headers.put("mixer_name", hp.getConferenceId());
headers.put("codec_name", "PCM/48000/2".equals(hp.getMediaPreference()) ? "OPUS" : "PCMU");
try { try {
ConferenceManager conferenceManager = ConferenceManager.findConferenceManager(hp.getConferenceId()); ConferenceManager conferenceManager = ConferenceManager.findConferenceManager(hp.getConferenceId());
...@@ -1225,6 +1224,7 @@ public class RayoComponent extends AbstractComponent ...@@ -1225,6 +1224,7 @@ public class RayoComponent extends AbstractComponent
ConferenceManager conferenceManager = ConferenceManager.findConferenceManager(mixer); ConferenceManager conferenceManager = ConferenceManager.findConferenceManager(mixer);
cp.setConferenceId(mixer); cp.setConferenceId(mixer);
cp.setCallId(mixer); cp.setCallId(mixer);
cp.setMediaPreference(hp.getMediaPreference());
conferenceManager.setCallId(mixer); conferenceManager.setCallId(mixer);
conferenceManager.setTransferCall(transferCall); conferenceManager.setTransferCall(transferCall);
...@@ -1761,15 +1761,33 @@ public class RayoComponent extends AbstractComponent ...@@ -1761,15 +1761,33 @@ public class RayoComponent extends AbstractComponent
public boolean routeIncomingSIP(CallParticipant cp) public boolean routeIncomingSIP(CallParticipant cp)
{ {
Log.info("Incoming SIP, call route to user " + cp.getToPhoneNumber()); boolean canRoute = false;
Group group = null;
JID foundUser = findUser(cp.getToPhoneNumber()); JID foundUser = findUser(cp.getToPhoneNumber());
if (foundUser != null)
canRoute = true;
else {
try {
group = GroupManager.getInstance().getGroup(cp.getToPhoneNumber());
canRoute = true;
} catch (GroupNotFoundException e) {
}
}
Log.info("Incoming SIP, call route to entity " + cp.getToPhoneNumber() + " " + canRoute);
if (canRoute)
{
String callId = "rayo-incoming-" + System.currentTimeMillis(); String callId = "rayo-incoming-" + System.currentTimeMillis();
cp.setCallId(callId); cp.setCallId(callId);
cp.setMediaPreference("PCMU/8000/1");
cp.setConferenceId(callId); cp.setConferenceId(callId);
if (cp.getMediaPreference() == null) cp.setMediaPreference("PCMU/8000/1"); // regular phone
ConferenceManager conferenceManager = ConferenceManager.getConference(callId, cp.getMediaPreference(), cp.getToPhoneNumber(), false); ConferenceManager conferenceManager = ConferenceManager.getConference(callId, cp.getMediaPreference(), cp.getToPhoneNumber(), false);
conferenceManager.setCallId(callId); conferenceManager.setCallId(callId);
...@@ -1777,17 +1795,15 @@ public class RayoComponent extends AbstractComponent ...@@ -1777,17 +1795,15 @@ public class RayoComponent extends AbstractComponent
Map<String, String> headers = cp.getHeaders(); Map<String, String> headers = cp.getHeaders();
headers.put("mixer_name", callId); headers.put("mixer_name", callId);
headers.put("call_protocol", "SIP"); headers.put("call_protocol", "SIP");
headers.put("codec_name", "PCM/48000/2".equals(cp.getMediaPreference()) ? "OPUS" : "PCMU");
headers.put("group_name", cp.getToPhoneNumber()); headers.put("group_name", cp.getToPhoneNumber());
if (foundUser != null) // send this call to specific user if (foundUser != null) // send this call to specific user
{ {
cp.setCallOwner(foundUser.toString()); cp.setCallOwner(foundUser.toString());
routeSIPCall(foundUser, cp, callId, headers); routeSIPCall(foundUser, cp, callId, headers);
return true;
}
try { } else {
Group group = GroupManager.getInstance().getGroup(cp.getToPhoneNumber());
conferenceManager.setGroupName(cp.getToPhoneNumber()); conferenceManager.setGroupName(cp.getToPhoneNumber());
...@@ -1800,29 +1816,10 @@ public class RayoComponent extends AbstractComponent ...@@ -1800,29 +1816,10 @@ public class RayoComponent extends AbstractComponent
routeSIPCall(session.getAddress(), cp, callId, headers); routeSIPCall(session.getAddress(), cp, callId, headers);
} }
} }
return true;
} catch (GroupNotFoundException e) {
// Group not found
if (XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService("conference").hasChatRoom(cp.getToPhoneNumber())) {
MUCRoom room = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService("conference").getChatRoom(cp.getToPhoneNumber());
if (room != null)
{
for (MUCRole role : room.getOccupants())
{
routeSIPCall(role.getUserAddress(), cp, callId, headers);
} }
} }
return true;
} else { return canRoute;
return false;
}
}
} }
public void routeSIPCall(JID callee, CallParticipant cp, String callId, Map<String, String> headers) public void routeSIPCall(JID callee, CallParticipant cp, String callId, Map<String, String> headers)
......
...@@ -91,7 +91,6 @@ public class RelayChannel { ...@@ -91,7 +91,6 @@ public class RelayChannel {
private Integer lastAudioTimestamp = new Integer((int)0); private Integer lastAudioTimestamp = new Integer((int)0);
private long decoder = 0; private long decoder = 0;
private long encoder = 0;
private final int sampleRate = 48000; private final int sampleRate = 48000;
private final int frameSizeInMillis = 20; private final int frameSizeInMillis = 20;
private final int outputFrameSize = 2; private final int outputFrameSize = 2;
...@@ -297,22 +296,13 @@ public class RelayChannel { ...@@ -297,22 +296,13 @@ public class RelayChannel {
encryptor2 = new Encryptor(SDPCryptoSuite.getEncryptionMode(handset.cryptoSuite), remoteCryptoKey, remoteCryptoSalt, localCryptoKey, localCryptoSalt); encryptor2 = new Encryptor(SDPCryptoSuite.getEncryptionMode(handset.cryptoSuite), remoteCryptoKey, remoteCryptoSalt, localCryptoKey, localCryptoSalt);
decoder = Opus.decoder_create(sampleRate, channels); decoder = Opus.decoder_create(sampleRate, channels);
encoder = Opus.encoder_create(sampleRate, channels);
//Opus.encoder_set_bandwidth(encoder, Opus.OPUS_AUTO);
//Opus.encoder_set_bitrate(encoder, 32000);
//Opus.encoder_set_complexity(encoder, 10);
//Opus.encoder_set_inband_fec(encoder, 1);
//Opus.encoder_set_packet_loss_perc(encoder, 1);
//Opus.encoder_set_dtx(encoder, 1);
if (decoder == 0) Log.error( "Opus decoder creation error "); if (decoder == 0) Log.error( "Opus decoder creation error ");
if (encoder == 0) Log.error( "Opus encoder creation error ");
if (decoder == 0 || encoder == 0) if (decoder == 0)
{ {
handset.codec = "PCMU"; handset.codec = "PCMU";
Log.warn( "Opus encoder/decoder creation failure, PCMU will be used in default"); Log.warn( "Opus decoder creation failure, PCMU will be used in default");
} }
} catch (Exception e) { } catch (Exception e) {
...@@ -361,12 +351,6 @@ public class RelayChannel { ...@@ -361,12 +351,6 @@ public class RelayChannel {
decoder = 0; decoder = 0;
} }
if (encoder != 0)
{
Opus.encoder_destroy(encoder);
encoder = 0;
}
SayCompleteEvent complete = new SayCompleteEvent(); SayCompleteEvent complete = new SayCompleteEvent();
complete.setReason(SayCompleteEvent.Reason.valueOf("SUCCESS")); complete.setReason(SayCompleteEvent.Reason.valueOf("SUCCESS"));
...@@ -445,7 +429,7 @@ public class RelayChannel { ...@@ -445,7 +429,7 @@ public class RelayChannel {
return new Long((new Integer(timestamp.intValue())).longValue()); return new Long((new Integer(timestamp.intValue())).longValue());
} }
public synchronized void pushAudio(byte[] rtpData, int[] in) public synchronized void pushAudio(byte[] rtpData, byte[] opus)
{ {
try { try {
...@@ -454,15 +438,9 @@ public class RelayChannel { ...@@ -454,15 +438,9 @@ public class RelayChannel {
RTPPacket newPacket = RTPPacket.parseBytes(BitAssistant.bytesToArray(rtpData)); RTPPacket newPacket = RTPPacket.parseBytes(BitAssistant.bytesToArray(rtpData));
RTPPacket packet = RTPPacket.parseBytes(lastAudioPacket.getBytes()); RTPPacket packet = RTPPacket.parseBytes(lastAudioPacket.getBytes());
if (handset.codec == null || "OPUS".equals(handset.codec)) if (opus != null)
{ {
byte[] input = AudioConversion.littleEndianIntsToBytes(in); packet.setPayload(BitAssistant.bytesToArray(opus));
byte[] output = new byte[Opus.MAX_PACKET];
int outLength = Opus.encode(encoder, input, 0, frameSizeInSamplesPerChannel, output, 0, output.length);
byte[] compressedBytes = new byte[outLength];
System.arraycopy(output, 0, compressedBytes, 0, outLength);
packet.setPayload(BitAssistant.bytesToArray(compressedBytes));
packet.setTimestamp(getNextAudioTimestamp(Long.valueOf(48000))); packet.setTimestamp(getNextAudioTimestamp(Long.valueOf(48000)));
} else { // ULAW } else { // ULAW
...@@ -484,8 +462,8 @@ public class RelayChannel { ...@@ -484,8 +462,8 @@ public class RelayChannel {
kt++; kt++;
if ( kt < 10 ) { if ( kt < 20 ) {
Log.info( "+++ " + in ); Log.info( "+++ " + packet.getPayload().length );
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment