diff --git a/lib/native/linux-64/libjnopus.so b/lib/native/linux-64/libjnopus.so index 5a73eaafb66e390c09a03b0e63c399b05e293982..045fa4c9380c3edd33a2f64ea52f38de125bca99 100755 Binary files a/lib/native/linux-64/libjnopus.so and b/lib/native/linux-64/libjnopus.so differ diff --git a/lib/native/linux/libjnopus.so b/lib/native/linux/libjnopus.so index c55810f143401615df5c95484546ef779b469fd7..6fa870980561270879197d815d78ca7928b8b7d9 100755 Binary files a/lib/native/linux/libjnopus.so and b/lib/native/linux/libjnopus.so differ diff --git a/lib/native/mac/libjnopus.jnilib b/lib/native/mac/libjnopus.jnilib index 7b58804f5b41e2c9dfb88a3ce0fa9d1be6a6ab7c..72550c2d067d739a2f1019366379b6bcff8d9049 100755 Binary files a/lib/native/mac/libjnopus.jnilib and b/lib/native/mac/libjnopus.jnilib differ diff --git a/lib/native/windows-64/jnopus.dll b/lib/native/windows-64/jnopus.dll index 02ddb34c378d3712b74771d49786b056e48e8bc5..903c582080ae1bb972f2cbe427514944f2cdf8e0 100755 Binary files a/lib/native/windows-64/jnopus.dll and b/lib/native/windows-64/jnopus.dll differ diff --git a/lib/native/windows/jnopus.dll b/lib/native/windows/jnopus.dll index 961fb5be44ddfe082a3dd9b8558ce7c69c2f8864..4e4e6d8f5816d5c622ab0d9e5adff39f6ce275b9 100755 Binary files a/lib/native/windows/jnopus.dll and b/lib/native/windows/jnopus.dll differ diff --git a/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.c b/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.c index 55058e782d74c31806bf0f2ca957fc155af6037b..0a07a0f000ef3e89ca548c44fbf896a06f335af9 100644 --- a/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.c +++ b/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.c @@ -66,12 +66,30 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encode return (jint) opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_SET_VBR(use_vbr)); } +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1get_1vbr + (JNIEnv *env, jclass clazz, jlong encoder) +{ + int x, ret; + ret = opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_GET_VBR(&x)); + if(ret<0) return ret; + return x; +} + JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1vbr_1constraint (JNIEnv *env, jclass clazz, jlong encoder, jint use_cvbr) { return (jint) opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_SET_VBR_CONSTRAINT(use_cvbr)); } +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1get_1vbr_1constraint + (JNIEnv *env, jclass clazz, jlong encoder) +{ + int x, ret; + ret = opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_GET_VBR_CONSTRAINT(&x)); + if(ret<0) return ret; + return x; +} + JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1complexity (JNIEnv *env, jclass clazz, jlong encoder, jint complexity) { @@ -96,6 +114,27 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encode return (jint) opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_SET_DTX(use_dtx)); } +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1get_1dtx + (JNIEnv *env, jclass clazz, jlong encoder) +{ + int x, ret; + ret = opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_GET_DTX(&x)); + if(ret<0) return ret; + return x; +} + +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1packet_1loss_1perc + (JNIEnv *env, jclass clazz, jlong encoder, jint percentage) +{ + return (jint) opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_SET_PACKET_LOSS_PERC(percentage)); +} + +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1max_1bandwidth + (JNIEnv *env, jclass clazz, jlong encoder, jint max_bandwidth) +{ + return (jint) opus_encoder_ctl((OpusEncoder *)(intptr_t)encoder, OPUS_SET_MAX_BANDWIDTH(max_bandwidth)); +} + JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encode (JNIEnv *env, jclass clazz, jlong encoder, jbyteArray input, jint inputOffset, jint frameSize, jbyteArray output, jint outputSize) { @@ -148,7 +187,7 @@ JNIEXPORT void JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_decode } JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_decode - (JNIEnv *env, jclass clazz, jlong decoder, jbyteArray input, jint inputOffset, jint inputSize, jbyteArray output, jint outputSize) + (JNIEnv *env, jclass clazz, jlong decoder, jbyteArray input, jint inputOffset, jint inputSize, jbyteArray output, jint outputSize, jint decodeFEC) { jbyte *inputPtr = (*env)->GetByteArrayElements(env, input, NULL); jbyte *outputPtr = (*env)->GetByteArrayElements(env, output, NULL); @@ -158,10 +197,10 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_decode { ret = opus_decode((OpusDecoder *)(intptr_t)decoder, (unsigned char *) ((char *)inputPtr + inputOffset), - (int) inputSize, + (int) inputSize, (opus_int16 *)outputPtr, - (int) outputSize, - 0); + (int) outputSize, + (int) decodeFEC); } else ret = 0; @@ -181,7 +220,7 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_packet jint bandwidth = 0; if(packetPtr){ bandwidth = (jint) opus_packet_get_bandwidth((unsigned char *)packetPtr+offset); - (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); + (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); } return bandwidth; } @@ -193,7 +232,7 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_packet jint channels = 0; if(packetPtr){ channels = (jint) opus_packet_get_nb_channels((unsigned char *)packetPtr+offset); - (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); + (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); } return channels; } @@ -205,7 +244,7 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_packet jint frames = 0; if(packetPtr){ frames = (jint) opus_packet_get_nb_frames((unsigned char *)packetPtr+offset, len); - (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); + (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); } return frames; } @@ -220,6 +259,6 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_decode samples = opus_decoder_get_nb_samples((OpusDecoder*)(intptr_t)decoder, (unsigned char *)packetPtr+offset, len); } if(packetPtr) - (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); + (*env)->ReleaseByteArrayElements(env, packet, packetPtr, JNI_ABORT); return samples; } diff --git a/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.h b/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.h index 57f053967070e5e897a74d5d86111cfa5725b592..d9b71c8a772a983e6ac19645cd161521613e6f17 100644 --- a/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.h +++ b/src/native/opus/org_jitsi_impl_neomedia_codec_audio_opus_Opus.h @@ -19,6 +19,8 @@ extern "C" { #define org_jitsi_impl_neomedia_codec_audio_opus_Opus_BANDWIDTH_FULLBAND 1105L #undef org_jitsi_impl_neomedia_codec_audio_opus_Opus_OPUS_OK #define org_jitsi_impl_neomedia_codec_audio_opus_Opus_OPUS_OK 0L +#undef org_jitsi_impl_neomedia_codec_audio_opus_Opus_OPUS_AUTO +#define org_jitsi_impl_neomedia_codec_audio_opus_Opus_OPUS_AUTO -1000L #undef org_jitsi_impl_neomedia_codec_audio_opus_Opus_INVALID_PACKET #define org_jitsi_impl_neomedia_codec_audio_opus_Opus_INVALID_PACKET -4L #undef org_jitsi_impl_neomedia_codec_audio_opus_Opus_MAX_PACKET @@ -87,6 +89,14 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encode JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1vbr (JNIEnv *, jclass, jlong, jint); +/* + * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus + * Method: encoder_get_vbr + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1get_1vbr + (JNIEnv *, jclass, jlong); + /* * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus * Method: encoder_set_vbr_constraint @@ -95,6 +105,14 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encode JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1vbr_1constraint (JNIEnv *, jclass, jlong, jint); +/* + * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus + * Method: encoder_get_vbr_constraint + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1get_1vbr_1constraint + (JNIEnv *, jclass, jlong); + /* * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus * Method: encoder_set_complexity @@ -127,6 +145,30 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encode JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1dtx (JNIEnv *, jclass, jlong, jint); +/* + * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus + * Method: encoder_get_dtx + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1get_1dtx + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus + * Method: encoder_set_packet_loss_perc + * Signature: (JI)I + */ +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1packet_1loss_1perc + (JNIEnv *, jclass, jlong, jint); + +/* + * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus + * Method: encoder_set_max_bandwidth + * Signature: (JI)I + */ +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_encoder_1set_1max_1bandwidth + (JNIEnv *, jclass, jlong, jint); + /* * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus * Method: encode @@ -162,10 +204,10 @@ JNIEXPORT void JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_decode /* * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus * Method: decode - * Signature: (J[BII[BI)I + * Signature: (J[BII[BII)I */ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_codec_audio_opus_Opus_decode - (JNIEnv *, jclass, jlong, jbyteArray, jint, jint, jbyteArray, jint); + (JNIEnv *, jclass, jlong, jbyteArray, jint, jint, jbyteArray, jint, jint); /* * Class: org_jitsi_impl_neomedia_codec_audio_opus_Opus diff --git a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java index 662adf1f98f2cad71a3dc9048b996aa14d0f3a47..a521d1126c4a3be848b6c808af94b1d6be6afcd6 100644 --- a/src/org/jitsi/impl/neomedia/MediaStreamImpl.java +++ b/src/org/jitsi/impl/neomedia/MediaStreamImpl.java @@ -202,6 +202,11 @@ else if (MediaDeviceSession.SSRC_LIST.equals(propertyName)) */ private long numberOfReceivedSenderReports = 0; + /** + * Number of received receiver reports. Used for logging and debugging only. + */ + private long numberOfReceivedReceiverReports = 0; + /** * The minimum inter arrival jitter value the other party has reported. */ @@ -229,6 +234,12 @@ else if (MediaDeviceSession.SSRC_LIST.equals(propertyName)) */ private PayloadTypeTransformEngine ptTransformEngine; + /** + * The <tt>PacketLossAwareEncoder</tt> in the encoding codec chain, which + * is to be notified of packet loss information received via RTCP. + */ + PacketLossAwareEncoder packetLossAwareEncoder = null; + /** * Initializes a new <tt>MediaStreamImpl</tt> instance which will use the * specified <tt>MediaDevice</tt> for both capture and playback of media. @@ -2495,15 +2506,22 @@ public void update(SessionEvent event) */ public void update(RemoteEvent remoteEvent) { - if(!logger.isInfoEnabled()) - return; - - if(remoteEvent instanceof SenderReportEvent) + if(remoteEvent instanceof SenderReportEvent || + remoteEvent instanceof ReceiverReportEvent) { - numberOfReceivedSenderReports++; - - SenderReport report = - ((SenderReportEvent)remoteEvent).getReport(); + Report report; + boolean senderReport = false; + if(remoteEvent instanceof SenderReportEvent) + { + numberOfReceivedSenderReports++; + report = ((SenderReportEvent)remoteEvent).getReport(); + senderReport = true; + } + else + { + numberOfReceivedReceiverReports++; + report = ((ReceiverReportEvent)remoteEvent).getReport(); + } Feedback feedback = null; long remoteJitter = -1; @@ -2522,36 +2540,55 @@ public void update(RemoteEvent remoteEvent) maxRemoteInterArrivalJitter = remoteJitter; } - // As sender reports are received on every 5 seconds - // print every 4th packet, on every 20 seconds - if(numberOfReceivedSenderReports%4 != 1) - return; + //Notify the encoder of the percentage of packets lost by the + //other side. See RFC3550 Section 6.4.1 for the interpretation of + //'fraction lost' + PacketLossAwareEncoder plae = getPacketLossAwareEncoder(); + if(plae != null) + plae.setExpectedPacketLoss( + (int) ((feedback.getFractionLost() * 100) / 256)); - StringBuilder buff - = new StringBuilder(StatisticsEngine.RTP_STAT_PREFIX); - MediaType mediaType = getMediaType(); - String mediaTypeStr - = (mediaType == null) ? "" : mediaType.toString(); - - buff.append("Received a report for ") - .append(mediaTypeStr) - .append(" stream SSRC:") - .append(getLocalSourceID()) - .append(" [packet count:") - .append(report.getSenderPacketCount()) - .append(", bytes:").append(report.getSenderByteCount()); + if(logger.isInfoEnabled()) + { + // As reports are received on every 5 seconds + // print every 4th packet, on every 20 seconds + if((numberOfReceivedSenderReports + + numberOfReceivedReceiverReports)%4 != 1) + return; + + StringBuilder buff + = new StringBuilder(StatisticsEngine.RTP_STAT_PREFIX); + MediaType mediaType = getMediaType(); + String mediaTypeStr + = (mediaType == null) ? "" : mediaType.toString(); + + buff.append("Received a ") + .append(senderReport ? "sender" : "receiver") + .append(" report for ") + .append(mediaTypeStr) + .append(" stream SSRC:") + .append(getLocalSourceID()) + .append(" ["); + if(senderReport) + { + buff.append("packet count:") + .append(((SenderReport) report).getSenderPacketCount()) + .append(", bytes:") + .append(((SenderReport) report).getSenderByteCount()); + } if(feedback != null) { buff.append(", interarrival jitter:") - .append(remoteJitter) - .append(", lost packets:").append(feedback.getNumLost()) - .append(", time since previous report:") - .append((int) (feedback.getDLSR() / 65.536)) - .append("ms"); + .append(remoteJitter) + .append(", lost packets:").append(feedback.getNumLost()) + .append(", time since previous report:") + .append((int) (feedback.getDLSR() / 65.536)) + .append("ms"); } - buff.append(" ]"); - logger.info(buff); + buff.append(" ]"); + logger.info(buff); + } } } @@ -2860,7 +2897,7 @@ public FECDecoderControl getFecDecoderControl(ReceiveStream receiveStream) /** * Adds an additional RTP payload mapping that will overriding one that - * we've set with {@link addDynamicRTPPayloadType(byte, MediaFormat)}. + * we've set with {@link #addDynamicRTPPayloadType(byte, MediaFormat)}. * This is necessary so that we can support the RFC3264 case where the * answerer has the right to declare what payload type mappings it wants to * receive RTP packets with even if they are different from those in the @@ -2880,4 +2917,37 @@ public void addDynamicRTPPayloadTypeOverride(byte originalPt, ptTransformEngine.addPTMappingOverride(originalPt, overloadPt); } } + + /** + * Gets this <tt>MediaDevice</tt>'s <tt>PacketLossAwareEncoder</tt> if any, + * <tt>null</tt> otherwise. To find such an instance, the codec chain + * contained in the <tt>DeviceSession</tt>'s processor is searched. + * + * @return this <tt>MediaDevice</tt>'s <tt>PacketLossAwareEncoder</tt> if + * any, <tt>null</tt> otherwise. + */ + public PacketLossAwareEncoder getPacketLossAwareEncoder() + { + if(packetLossAwareEncoder != null) + return packetLossAwareEncoder; + + MediaDeviceSession mediaDeviceSession = getDeviceSession(); + if(mediaDeviceSession == null) + return null; + Processor processor = mediaDeviceSession.getProcessor(); + if(processor == null) + return null; + + for(TrackControl tc : processor.getTrackControls()) + { + Object obj + = tc.getControl(PacketLossAwareEncoder.class.getName()); + if(obj instanceof PacketLossAwareEncoder) + { + packetLossAwareEncoder = (PacketLossAwareEncoder)obj; + return packetLossAwareEncoder; + } + } + return packetLossAwareEncoder; + } } diff --git a/src/org/jitsi/impl/neomedia/MediaUtils.java b/src/org/jitsi/impl/neomedia/MediaUtils.java index 3b539b9b5135844d78a9ead7fa9e6e7a6a93cc8d..fa5038da2fb5a8b8ddb12d3e5092d1a334b87afe 100644 --- a/src/org/jitsi/impl/neomedia/MediaUtils.java +++ b/src/org/jitsi/impl/neomedia/MediaUtils.java @@ -169,8 +169,7 @@ public class MediaUtils ConfigurationService cfg = LibJitsi.getConfigurationService(); boolean advertiseFEC - = cfg.getBoolean("net.java.sip.communicator.impl.neomedia" + - ".codec.audio.silk.ADVERTISE_FEC", false); + = cfg.getBoolean(Constants.PROP_SILK_ADVERSISE_FEC, false); Map<String,String> silkFormatParams = new HashMap<String, String>(); if(advertiseFEC) silkFormatParams.put("useinbandfec", "1"); diff --git a/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIDecoder.java b/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIDecoder.java index 1af80796104668b22a8da607f45774cba25b225a..40d1a50beea325be1d329180517457c4dbadef47 100644 --- a/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIDecoder.java +++ b/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIDecoder.java @@ -141,7 +141,7 @@ protected int doProcess(Buffer inputBuffer, Buffer outputBuffer) byte[] output = validateByteArraySize(outputBuffer, outputLength); int samplesCount = Opus.decode(decoder, input, inputOffset, inputLength, - output, outputLength); + output, outputLength, 0); if (samplesCount > 0) { diff --git a/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIEncoder.java b/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIEncoder.java index 9006edd44f4b40bd395e4c74f83b511c582875b1..013c9988dc204495d74ce53de5abaa0bea1844b0 100644 --- a/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIEncoder.java +++ b/src/org/jitsi/impl/neomedia/codec/audio/opus/JNIEncoder.java @@ -10,7 +10,13 @@ import javax.media.format.*; import net.sf.fmj.media.*; import org.jitsi.impl.neomedia.codec.*; +import org.jitsi.service.configuration.*; +import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.codec.*; +import org.jitsi.service.neomedia.control.*; +import org.jitsi.util.*; + +import java.awt.*; /** * Implements an opus encoder. @@ -19,6 +25,7 @@ */ public class JNIEncoder extends AbstractCodecExt + implements PacketLossAwareEncoder { /** * The list of <tt>Format</tt>s of audio data supported as input by @@ -44,6 +51,13 @@ public class JNIEncoder private static final Format[] SUPPORTED_OUTPUT_FORMATS = new Format[] { new AudioFormat(Constants.OPUS_RTP) }; + /** + * The <tt>Logger</tt> used by this <tt>JNIEncoder</tt> instance + * for logging output. + */ + private final Logger logger + = Logger.getLogger(JNIEncoder.class); + /** * Set the supported input formats. */ @@ -111,6 +125,11 @@ public class JNIEncoder */ private double frameSize = 20; + /** + * The minimum expected packet loss percentage to set to the encoder. + */ + private int minPacketLoss = 0; + /** * Returns the number of bytes that we need to read from the input buffer * in order ot fill a frame of <tt>frameSize</tt>. Depends on the input @@ -123,14 +142,14 @@ public class JNIEncoder private int inputFrameSize() { int fs = - (int) ( + (int) ( 2 /* sizeof(short) */ * channels * ((AudioFormat)getInputFormat()).getSampleRate() /* samples in 1s */ * frameSize /* milliseconds */ ) / 1000; - return fs; + return fs; } /** @@ -143,6 +162,8 @@ public JNIEncoder() SUPPORTED_OUTPUT_FORMATS); inputFormats = SUPPORTED_INPUT_FORMATS; + + addControl(this); } /** @@ -170,25 +191,61 @@ protected void doClose() protected void doOpen() throws ResourceUnavailableException { - - //TODO: make a ParametrizedAudioFormat (or something) class, and use it here - //to set the encoder parameters - AudioFormat inputFormat = (AudioFormat) getInputFormat(); - int sampleRate = (int)inputFormat.getSampleRate(); - channels = inputFormat.getChannels(); - - encoder = Opus.encoder_create(sampleRate, channels); - if (encoder == 0) - throw new ResourceUnavailableException("opus_encoder_create()"); - - //set default encoder settings - Opus.encoder_set_bitrate(encoder, 60000); - Opus.encoder_set_bandwidth(encoder, Opus.BANDWIDTH_FULLBAND); - Opus.encoder_set_vbr(encoder, 1); - Opus.encoder_set_complexity(encoder, 10); - Opus.encoder_set_inband_fec(encoder, 0); - Opus.encoder_set_dtx(encoder, 1); - Opus.encoder_set_force_channels(encoder, 1); + AudioFormat inputFormat = (AudioFormat) getInputFormat(); + int sampleRate = (int)inputFormat.getSampleRate(); + channels = inputFormat.getChannels(); + + encoder = Opus.encoder_create(sampleRate, channels); + if (encoder == 0) + throw new ResourceUnavailableException("opus_encoder_create()"); + + //Set encoder options according to user configuration and SDP parameters + ConfigurationService cfg = LibJitsi.getConfigurationService(); + + //configuration is in kilobits per second + int bitrate = 1000 * + cfg.getInt(Constants.PROP_OPUS_BITRATE, 32); + //TODO:update bitrate from SDP (maxaveragebitrate) + //Note: If the parameter "maxaveragebitrate" is below the range specified + //in Section 3.1.1 the session MUST be rejected. + if(bitrate < 500 && bitrate != Opus.OPUS_AUTO) + bitrate = 500; + if(bitrate > 512000 && bitrate != Opus.OPUS_AUTO) + bitrate = 512000; + + String bandwidthStr + = cfg.getString(Constants.PROP_OPUS_BANDWIDTH, "auto"); + int bandwidth = Opus.OPUS_AUTO; + if("fb".equals(bandwidthStr)) + bandwidth = Opus.BANDWIDTH_FULLBAND; + else if("swb".equals(bandwidthStr)) + bandwidth = Opus.BANDWIDTH_SUPERWIDEBAND; + else if("wb".equals(bandwidthStr)) + bandwidth = Opus.BANDWIDTH_WIDEBAND; + else if("mb".equals(bandwidthStr)) + bandwidth = Opus.BANDWIDTH_MEDIUMBAND; + else if("nb".equals(bandwidthStr)) + bandwidth = Opus.BANDWIDTH_NARROWBAND; + + int complexity = cfg.getInt(Constants.PROP_OPUS_COMPLEXITY, 10); + + boolean useFEC = cfg.getBoolean(Constants.PROP_OPUS_FEC, true); + //TODO:check SDP for useinbandfec + + minPacketLoss = cfg.getInt( + Constants.PROP_OPUS_MIN_EXPECTED_PACKET_LOSS, 1); + boolean useDTX = cfg.getBoolean(Constants.PROP_OPUS_DTX, true); + //TODO:check SDP parameters for usedtx + + //TODO:check SDP for maxcodedaudiobandwidth + //TODO: check {min,max,}ptime and adjust the frame size + + Opus.encoder_set_bitrate(encoder, bitrate); + Opus.encoder_set_bandwidth(encoder, bandwidth); + Opus.encoder_set_complexity(encoder, complexity); + Opus.encoder_set_inband_fec(encoder, useFEC ? 1 : 0); + Opus.encoder_set_packet_loss_perc(encoder, minPacketLoss); + Opus.encoder_set_dtx(encoder, useDTX ? 1 : 0); } /** @@ -211,14 +268,13 @@ protected int doProcess(Buffer inputBuffer, Buffer outputBuffer) if (null == setInputFormat(inputFormat)) return BUFFER_PROCESSED_FAILED; } - inputFormat = this.inputFormat; byte[] input = (byte[]) inputBuffer.getData(); int inputLength = inputBuffer.getLength(); int inputOffset = inputBuffer.getOffset(); - int inputBytesNeeded = inputFrameSize(); + int inputBytesNeeded = inputFrameSize(); if ((previousInput != null) && (previousInputLength > 0)) { @@ -299,7 +355,7 @@ else if (inputLength < inputBytesNeeded) byte[] output = validateByteArraySize(outputBuffer, Opus.MAX_PACKET); - int outputLength = Opus.encode(encoder, input, inputOffset, + int outputLength = Opus.encode(encoder, input, inputOffset, inputBytesNeeded / 2, output, Opus.MAX_PACKET); @@ -412,4 +468,31 @@ public Format setInputFormat(Format format) } return inputFormat; } + + /** + * Updates the encoder's expected packet loss percentage to the bigger of + * <tt>percentage</tt> and <tt>this.minPacketLoss</tt>. + * + * @param percentage the expected packet loss percentage to set + */ + public void setExpectedPacketLoss(int percentage) + { + if(opened) + Opus.encoder_set_packet_loss_perc(encoder, + (percentage > minPacketLoss) ? percentage : minPacketLoss); + + if(logger.isTraceEnabled()) + logger.trace("Updating expected packet loss: " + percentage + + " (minimum " + minPacketLoss + ")"); + } + + /** + * Stub. Only added in order to implement the + * <tt>PacketLossAwareEncoder</tt> interface. + * + * @return null + */ + public Component getControlComponent() { + return null; + } } diff --git a/src/org/jitsi/impl/neomedia/codec/audio/opus/Opus.java b/src/org/jitsi/impl/neomedia/codec/audio/opus/Opus.java index bf27ddaed3bf10ae4d3cdccdd39c52a02b2999ef..4115d208529126fd703a31dafe4f25772a53cdd5 100644 --- a/src/org/jitsi/impl/neomedia/codec/audio/opus/Opus.java +++ b/src/org/jitsi/impl/neomedia/codec/audio/opus/Opus.java @@ -8,6 +8,8 @@ /** * Implements an interface to the native opus library + * + * @author Boris Grozev */ public class Opus { @@ -41,6 +43,11 @@ public class Opus */ public static final int OPUS_OK = 0; + /** + * Constant used to set various settings to "automatic" + */ + public static final int OPUS_AUTO = -1000; + /** * Opus constant for an invalid packet */ @@ -139,6 +146,16 @@ public class Opus */ public static native int encoder_set_vbr(long encoder, int use_vbr); + /** + * Wrapper around the native <tt>opus_encoder_ctl</tt> function. Returns the + * current encoder VBR setting + * + * @param encoder The encoder to use + * + * @return The current encoder VBR setting. + */ + public static native int encoder_get_vbr(long encoder); + /** * Wrapper around the native <tt>opus_encoder_ctl</tt> function. Sets the * encoder VBR constraint setting @@ -151,6 +168,16 @@ public class Opus public static native int encoder_set_vbr_constraint(long encoder, int use_cvbr); + /** + * Wrapper around the native <tt>opus_encoder_ctl</tt> function. Returns + * the current VBR constraint encoder setting. + * + * @param encoder The encoder to use + * + * @return the current VBR constraint encoder setting. + */ + public static native int encoder_get_vbr_constraint(long encoder); + /** * Wrapper around the native <tt>opus_encoder_ctl</tt> function. Sets the * encoder complexity setting. @@ -197,6 +224,43 @@ public static native int encoder_set_force_channels(long encoder, */ public static native int encoder_set_dtx(long encoder, int use_dtx); + /** + * Wrapper around the native <tt>opus_encoder_ctl</tt> function. Returns + * the current DTX setting of the encoder. + * + * @param encoder The encoder to use + * + * @return the current DTX setting of the encoder. + */ + public static native int encoder_get_dtx(long encoder); + + /** + * Wrapper around the native <tt>opus_encoder_ctl</tt> function. Sets the + * encoder's expected packet loss percentage. + * + * @param encoder The encoder to use + * @param percentage 0 to turn DTX off, non-zero to turn it on + * + * @return OPUS_OK on success. + */ + public static native int encoder_set_packet_loss_perc(long encoder, + int percentage); + + /** + * Wrapper around the native <tt>opus_encoder_ctl</tt> function. Sets the + * maximum audio bandwidth to be used by the encoder. + * + * @param encoder The encoder to use + * @param maxBandwidth The maximum bandwidth to use, should be one of + * <tt>BANDWIDTH_FULLBAND</tt>, <tt>BANDWIDTH_MEDIUMBAND</tt>, + * <tt>BANDWIDTH_NARROWBAND</tt>, <tt>BANDWIDTH_SUPERWIDEBAND</tt> or + * <tt>BANDWIDTH_WIDEBAND</tt> + * + * @return <tt>OPUS_OK</tt> on success. + */ + public static native int encoder_set_max_bandwidth(long encoder, + int maxBandwidth); + /** * Encodes the input from <tt>input</tt> into an opus packet in * <tt>output</tt>. @@ -254,12 +318,14 @@ public static native int encode(long encoder, byte[] input, int offset, * @param inputSize Size of the packet in bytes. * @param output Output buffer where the output will be stored. * @param outputSize Size in bytes of the output buffer. + * @param decodeFEC 0 to decode the packet normally, 1 to decode the FEC + * data in the packet. * * @return Number of samples decoded */ public static native int decode(long decoder, byte[] input, int inputOffset, int inputSize, byte[] output, - int outputSize); + int outputSize, int decodeFEC); /** * Returns the audio bandwidth of an opus packet, one of diff --git a/src/org/jitsi/impl/neomedia/codec/audio/silk/JavaEncoder.java b/src/org/jitsi/impl/neomedia/codec/audio/silk/JavaEncoder.java index 3343af07588214585d232fc62c61551b921b159e..85073444e9e41b220ec3931c635b18b1cc9f11cf 100644 --- a/src/org/jitsi/impl/neomedia/codec/audio/silk/JavaEncoder.java +++ b/src/org/jitsi/impl/neomedia/codec/audio/silk/JavaEncoder.java @@ -13,15 +13,28 @@ import org.jitsi.service.configuration.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.codec.*; +import org.jitsi.service.neomedia.control.*; +import org.jitsi.util.*; + +import java.awt.*; /** * Implements the SILK encoder as an FMJ/JMF <tt>Codec</tt>. * * @author Dingxin Xu + * @author Boris Grozev */ public class JavaEncoder extends AbstractCodecExt + implements PacketLossAwareEncoder { + /** + * The <tt>Logger</tt> used by this <tt>JavaEncoder</tt> instance + * for logging output. + */ + private final Logger logger + = Logger.getLogger(JavaEncoder.class); + private static final int BITRATE = 40000; private static final int COMPLEXITY = 2; @@ -51,8 +64,34 @@ public class JavaEncoder private static final double[] SUPPORTED_SAMPLE_RATES = new double[] { 8000, 12000, 16000, 24000 }; + /** + * Default value for the use DTX setting + */ private static final boolean USE_DTX = false; + /** + * If <tt>alwaysExpectPacketLoss</tt> is <tt>true</tt> the expected + * packet loss will always be set at or above this threshold. + */ + private static final int MIN_PACKET_LOSS_PERCENTAGE = 3; + + /** + * Whether to use FEC or not. + */ + private boolean useFec; + + /** + * Whether to always assume packet loss and set the encoder's expected + * packet loss over <tt>MIN_PACKET_LOSS_PERCENTAGE</tt>. + */ + private boolean alwaysAssumePacketLoss = true; + + /** + * The actual expected packet loss. Defaults to 0 and can be updated by + * outside classes through <tt>PacketLossUpdater</tt> instances. + */ + private int expectedPacketLoss = 0; + /** * The duration an output <tt>Buffer</tt> produced by this <tt>Codec</tt> * in nanosecond. @@ -118,6 +157,26 @@ public JavaEncoder() super("SILK Encoder", AudioFormat.class, SUPPORTED_OUTPUT_FORMATS); inputFormats = SUPPORTED_INPUT_FORMATS; + + ConfigurationService cfg = LibJitsi.getConfigurationService(); + //TODO: we should have a default value dependent on the SDP parameters + //here. + useFec = cfg.getBoolean(Constants.PROP_SILK_FEC, true); + alwaysAssumePacketLoss + = cfg.getBoolean(Constants.PROP_SILK_ASSUME_PL, true); + + //Update the statically defined value for "speech activity threshold" + //according to our configuration + String satStr = cfg.getString(Constants.PROP_SILK_FEC_SAT, "0.5"); + float sat = Silk_define_FLP.LBRR_SPEECH_ACTIVITY_THRES; + try + { + sat = Float.parseFloat(satStr); + } + catch (Exception e){} + Silk_define_FLP.LBRR_SPEECH_ACTIVITY_THRES = sat; + + addControl(this); } protected void doClose() @@ -141,37 +200,16 @@ protected void doOpen() int channels = inputFormat.getChannels(); - ConfigurationService cfg = LibJitsi.getConfigurationService(); - //TODO: we should have a default value dependent on the SDP parameters - //here. - boolean useFEC = cfg.getBoolean("net.java.sip.communicator.impl." - +"neomedia.codec.audio.silk.encoder.USE_FEC", true); - boolean assumePacketLoss = cfg.getBoolean("net.java.sip.communicator." + - "impl.neomedia.codec.audio.silk.encoder." + - "ALWAYS_ASSUME_PACKET_LOSS", true); - - //Update the statically defined value for "speech activity threshold" - //according to our configuration - String satStr = cfg.getString("net.java.sip.communicator.impl.neomedia" - + ".codec.audio.silk.encoder.SPEECH_ACTIVITY_THRESHOLD", "0.5"); - float sat = Silk_define_FLP.LBRR_SPEECH_ACTIVITY_THRES; - try - { - sat = Float.parseFloat(satStr); - } - catch (Exception e){} - Silk_define_FLP.LBRR_SPEECH_ACTIVITY_THRES = sat; - encControl.API_sampleRate = (int) sampleRate; encControl.bitRate = BITRATE; encControl.complexity = COMPLEXITY; encControl.maxInternalSampleRate = encControl.API_sampleRate; - encControl.packetLossPercentage = assumePacketLoss ? 3 : 0; + setExpectedPacketLoss(0); encControl.packetSize = (int) ((JavaDecoder.FRAME_DURATION * sampleRate * channels) / 1000); encControl.useDTX = USE_DTX ? 1 : 0; - encControl.useInBandFEC = useFEC ? 1 : 0; + encControl.useInBandFEC = useFec ? 1 : 0; } protected int doProcess(Buffer inputBuffer, Buffer outputBuffer) @@ -324,4 +362,34 @@ public long computeDuration(long length) } return outputFormat; } + + /** + * Updates the encoder's packet loss percentage. Takes into account + * <tt>this.alwaysAssumePacketLoss</tt>. + * + * @param percentage the expected packet loss percentage to set. + */ + public void setExpectedPacketLoss(int percentage) + { + if(opened) + { + if(alwaysAssumePacketLoss && + MIN_PACKET_LOSS_PERCENTAGE >= percentage) + percentage = MIN_PACKET_LOSS_PERCENTAGE; + encControl.packetLossPercentage = percentage; + if(logger.isTraceEnabled()) + logger.trace("Setting expected packet loss to: " + + percentage); + } + } + + /** + * Stub. Only added in order to implement the + * <tt>PacketLossAwareEncoder</tt> interface. + * + * @return null + */ + public Component getControlComponent() { + return null; + } } diff --git a/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java b/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java index 3189bad1b0054208667c08c58630f45c23ce0d28..14c478cfcf7310110853b26cef0623cdc102f2e6 100644 --- a/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java +++ b/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java @@ -1026,7 +1026,7 @@ protected List<Player> getPlayers() * @return the JMF <tt>Processor</tt> which transcodes the * <tt>MediaDevice</tt> of this instance into the format of this instance */ - private Processor getProcessor() + public Processor getProcessor() { if (processor == null) processor = createProcessor(); diff --git a/src/org/jitsi/service/neomedia/codec/Constants.java b/src/org/jitsi/service/neomedia/codec/Constants.java index 7a842fe40444771424992e50118988cfcccaae26..a49ea44709a84d7ebb2b336abdcdd4d9e407fcbe 100644 --- a/src/org/jitsi/service/neomedia/codec/Constants.java +++ b/src/org/jitsi/service/neomedia/codec/Constants.java @@ -9,12 +9,13 @@ import org.jitsi.util.*; /** - * Allows start import of <tt>org.jitsi.impl.neomedia.codec</tt> - * in order to get the constants define in + * Allows star import of <tt>org.jitsi.service.neomedia.codec</tt> + * in order to get the constants defined in * <tt>org.jitsi.service.neomedia.codec.Constants</tt> without star * import of <tt>org.jitsi.impl.neomedia.codec</tt>. * * @author Lubomir Marinov + * @author Boris Grozev */ public class Constants { @@ -110,6 +111,83 @@ public class Constants */ public static final int VIDEO_HEIGHT; + /** + * The name of the property used to control whether FEC is enabled for SILK + */ + public static final String PROP_SILK_FEC + = "net.java.sip.communicator.impl.neomedia.codec.audio.silk." + + "encoder.USE_FEC"; + + /** + * The name of the property used to control the the + * 'always assume packet loss' setting for SILK + */ + public static final String PROP_SILK_ASSUME_PL + = "net.java.sip.communicator.impl.neomedia.codec.audio.silk." + + "encoder.AWLAYS_ASSUME_PACKET_LOSS"; + + /** + * The name of the property used to control the SILK + * 'speech activity threshold' + */ + public static final String PROP_SILK_FEC_SAT + = "net.java.sip.communicator.impl.neomedia.codec.audio.silk." + + "encoder.SPEECH_ACTIVITY_THRESHOLD"; + + /** + * The name of the property used to control whether FEC support is + * advertised for SILK + */ + public static final String PROP_SILK_ADVERSISE_FEC + = "net.java.sip.communicator.impl.neomedia.codec.audio.silk." + + "ADVERTISE_FEC"; + /** + * The name of the property used to control the Opus encoder + * "audio bandwidth" setting + */ + public static final String PROP_OPUS_BANDWIDTH + = "net.java.sip.communicator.impl.neomedia.codec.audio.opus." + + "encoder.AUDIO_BANDWIDTH"; + + /** + * The name of the property used to control the Opus encoder bitrate setting + */ + public static final String PROP_OPUS_BITRATE + = "net.java.sip.communicator.impl.neomedia.codec.audio.opus." + + "encoder.BITRATE"; + + /** + * The name of the property used to control the Opus encoder "DTX" setting + */ + public static final String PROP_OPUS_DTX + = "net.java.sip.communicator.impl.neomedia.codec.audio.opus." + + "encoder.DTX"; + + /** + * The name of the property used to control whether FEC is enabled for the + * Opus encoder + */ + public static final String PROP_OPUS_FEC + = "net.java.sip.communicator.impl.neomedia.codec.audio.opus." + + "encoder.FEC"; + + /** + * The name of the property used to control the Opus encoder + * "minimum expected packet loss" setting + */ + public static final String PROP_OPUS_MIN_EXPECTED_PACKET_LOSS + = "net.java.sip.communicator.impl.neomedia.codec.audio.opus." + + "encoder.MIN_EXPECTED_PACKET_LOSS"; + + /** + * The name of the property used to control the Opus encoder 'complexity' + * setting + */ + public static final String PROP_OPUS_COMPLEXITY + = "net.java.sip.communicator.impl.neomedia.codec.audio.opus." + + "encoder.COMPLEXITY"; + + static { diff --git a/src/org/jitsi/service/neomedia/control/PacketLossAwareEncoder.java b/src/org/jitsi/service/neomedia/control/PacketLossAwareEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..aa1c34ecc4e5792b933b7d25e79c71eb4ab4ae6d --- /dev/null +++ b/src/org/jitsi/service/neomedia/control/PacketLossAwareEncoder.java @@ -0,0 +1,24 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.service.neomedia.control; + +import javax.media.*; + +/** + * An interface used to notify encoders about the packet loss which is expected. + * + * @author Boris Grozev + */ +public interface PacketLossAwareEncoder extends Control +{ + /** + * Tells the encoder to expect <tt>percentage</tt> percent packet loss. + * + * @return the percentage of expected packet loss + */ + public void setExpectedPacketLoss(int percentage); +}