diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 01390f56a..da5d2689e 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -89,6 +89,11 @@ ilbc ${project.parent.version} + + org.restcomm.media.codecs.opus + opus-java + ${project.version} + diff --git a/bootstrap/src/main/assembly/descriptor.xml b/bootstrap/src/main/assembly/descriptor.xml index ae6668de6..e55039607 100644 --- a/bootstrap/src/main/assembly/descriptor.xml +++ b/bootstrap/src/main/assembly/descriptor.xml @@ -19,6 +19,22 @@ /lib false runtime + + *:so + *:dylib + + + + lib/native + ${artifact.artifactId}.${artifact.extension} + false + runtime + false + false + + *:so + *:dylib + diff --git a/bootstrap/src/main/config/autoconfig/mediaserver.conf b/bootstrap/src/main/config/autoconfig/mediaserver.conf index b598be78c..3f9f0c660 100644 --- a/bootstrap/src/main/config/autoconfig/mediaserver.conf +++ b/bootstrap/src/main/config/autoconfig/mediaserver.conf @@ -15,7 +15,7 @@ MEDIA_MAX_DURATION=14440 MEDIA_LOW_PORT=64534 MEDIA_HIGH_PORT=65534 MEDIA_JITTER_SIZE=50 -MEDIA_CODECS=pcmu,pcma,telephone-event +MEDIA_CODECS=opus,pcmu,pcma,telephone-event # Resources AUDIO_CACHE_SIZE=100 @@ -42,4 +42,4 @@ DTLS_ALGORITHM=ecdsa #ASR_DRIVER_SOMEPROVIDER_PROPERTY_PARAMETER2_VALUE=parameter2_value # Java -MS_OPTS="-Xms3400m -Xmx3400m -XX:+UseG1GC -XX:ParallelGCThreads=8 -XX:ConcGCThreads=8 -XX:G1RSetUpdatingPauseTimePercent=10 -XX:+ParallelRefProcEnabled -XX:G1HeapRegionSize=4m -XX:G1HeapWastePercent=5 -XX:InitiatingHeapOccupancyPercent=85 -XX:+UnlockExperimentalVMOptions -XX:G1MixedGCLiveThresholdPercent=85 -XX:+AlwaysPreTouch -XX:+UseCompressedOops -Djava.net.preferIPv4Stack=true -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dhttp.keepAlive=false" \ No newline at end of file +MS_OPTS="-Xms3400m -Xmx3400m -XX:+UseG1GC -XX:ParallelGCThreads=8 -XX:ConcGCThreads=8 -XX:G1RSetUpdatingPauseTimePercent=10 -XX:+ParallelRefProcEnabled -XX:G1HeapRegionSize=4m -XX:G1HeapWastePercent=5 -XX:InitiatingHeapOccupancyPercent=85 -XX:+UnlockExperimentalVMOptions -XX:G1MixedGCLiveThresholdPercent=85 -XX:+AlwaysPreTouch -XX:+UseCompressedOops -Djava.net.preferIPv4Stack=true -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dhttp.keepAlive=false" diff --git a/bootstrap/src/main/config/mediaserver.xml b/bootstrap/src/main/config/mediaserver.xml index 9829c908a..c1d700b40 100644 --- a/bootstrap/src/main/config/mediaserver.xml +++ b/bootstrap/src/main/config/mediaserver.xml @@ -39,6 +39,7 @@ + - \ No newline at end of file + diff --git a/bootstrap/src/main/config/run.sh b/bootstrap/src/main/config/run.sh index fa948d815..6e1b6a47b 100755 --- a/bootstrap/src/main/config/run.sh +++ b/bootstrap/src/main/config/run.sh @@ -50,9 +50,14 @@ esac # Force IPv4 on Linux systems since IPv6 doesn't work correctly with jdk5 and lower if [ "$linux" = "true" ]; then - JAVA_OPTS="$JAVA_OPTS -Djava.net.preferIPv4Stack=true" + JAVA_OPTS="$JAVA_OPTS -Djava.net.preferIPv4Stack=true -Djava.library.path=/usr/lib/x86_64-linux-gnu:../lib/native -Drestcomm.opus.library=opus_jni_linux" fi +if [ "$darwin" = "true" ]; then + JAVA_OPTS="$JAVA_OPTS -Djava.library.path=../lib/native -Drestcomm.opus.library=opus_jni_macos" +fi + + # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$MMS_HOME" ] && diff --git a/bootstrap/src/test/resources/mediaserver.xml b/bootstrap/src/test/resources/mediaserver.xml index 91ff74b28..f3a080646 100644 --- a/bootstrap/src/test/resources/mediaserver.xml +++ b/bootstrap/src/test/resources/mediaserver.xml @@ -38,6 +38,7 @@ + @@ -73,4 +74,4 @@ - \ No newline at end of file + diff --git a/codecs/opus/opus-java/pom.xml b/codecs/opus/opus-java/pom.xml new file mode 100644 index 000000000..09e4b077e --- /dev/null +++ b/codecs/opus/opus-java/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + jar + + + org.restcomm.media.codecs + opus + 7.0.0-SNAPSHOT + + + org.restcomm.media.codecs.opus + opus-java + Opus Java + + + + linux-profile + + + Linux + unix + + + + libopus_jni_linux + so + linux + opus_jni_linux + + + + + macosx-profile + + + mac + + + + libopus_jni_macos + dylib + macosx + opus_jni_macos + + + + + + + org.restcomm.media + spi + ${project.version} + + + org.restcomm.media.codecs.opus + ${libopus.artifactId} + ${project.version} + ${libopus.packaging} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Djava.library.path=../opus-native/${libopus.distro}/target -Drestcomm.opus.library=${libopus.libName} + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + restcomm-mediaserver-codecs-opus-${project.version} + + + diff --git a/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/Decoder.java b/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/Decoder.java new file mode 100644 index 000000000..2e8fcfb31 --- /dev/null +++ b/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/Decoder.java @@ -0,0 +1,91 @@ +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2017, Telestax Inc and individual contributors + * by the @authors tag. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.restcomm.media.codec.opus; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.apache.log4j.Logger; +import org.restcomm.media.spi.dsp.Codec; +import org.restcomm.media.spi.format.Format; +import org.restcomm.media.spi.format.FormatFactory; +import org.restcomm.media.spi.memory.Frame; +import org.restcomm.media.spi.memory.Memory; + +/** + * Implements Opus decoder. + * + * @author Vladimir Morosev (vladimir.morosev@telestax.com) + * + */ +public class Decoder implements Codec { + + private final static Logger log = Logger.getLogger(Encoder.class); + + private final static Format opus = FormatFactory.createAudioFormat("opus", 48000, 8, 2); + private final static Format linear = FormatFactory.createAudioFormat("linear", 8000, 16, 1); + + private long decoderAddress; + + private final int OPUS_SAMPLE_RATE = 8000; + + public Decoder() { + decoderAddress = OpusJni.createDecoderNative(OPUS_SAMPLE_RATE, 1); + } + + @Override + protected void finalize() throws Throwable { + if (decoderAddress != 0) OpusJni.releaseDecoderNative(decoderAddress); + super.finalize(); + } + + @Override + public Format getSupportedInputFormat() { + return opus; + } + + @Override + public Format getSupportedOutputFormat() { + return linear; + } + + @Override + public Frame process(Frame frame) { + + short[] decodedData = OpusJni.decodeNative(decoderAddress, frame.getData()); + byte[] output = new byte[2 * decodedData.length]; + ByteBuffer.wrap(output).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(decodedData); + + Frame res = Memory.allocate(output.length); + System.arraycopy(output, 0, res.getData(), 0, output.length); + + res.setOffset(0); + res.setLength(output.length); + res.setTimestamp(frame.getTimestamp()); + res.setDuration(frame.getDuration()); + res.setSequenceNumber(frame.getSequenceNumber()); + res.setEOM(frame.isEOM()); + res.setFormat(linear); + res.setHeader(frame.getHeader()); + return res; + } +} diff --git a/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/Encoder.java b/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/Encoder.java new file mode 100644 index 000000000..9f26488b9 --- /dev/null +++ b/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/Encoder.java @@ -0,0 +1,93 @@ +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2017, Telestax Inc and individual contributors + * by the @authors tag. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.restcomm.media.codec.opus; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.apache.log4j.Logger; +import org.restcomm.media.spi.dsp.Codec; +import org.restcomm.media.spi.format.Format; +import org.restcomm.media.spi.format.FormatFactory; +import org.restcomm.media.spi.memory.Frame; +import org.restcomm.media.spi.memory.Memory; + +/** + * Implements Opus encoder. + * + * @author Vladimir Morosev (vladimir.morosev@telestax.com) + * + */ +public class Encoder implements Codec { + + private final static Logger log = Logger.getLogger(Encoder.class); + + private final static Format opus = FormatFactory.createAudioFormat("opus", 48000, 8, 2); + private final static Format linear = FormatFactory.createAudioFormat("linear", 8000, 16, 1); + + private long encoderAddress; + + private final int OPUS_SAMPLE_RATE = 8000; + private final int OPUS_BITRATE = 20000; + + public Encoder() { + encoderAddress = OpusJni.createEncoderNative(OPUS_SAMPLE_RATE, 1, OpusJni.OPUS_APPLICATION_VOIP, OPUS_BITRATE); + } + + @Override + protected void finalize() throws Throwable { + OpusJni.releaseEncoderNative(encoderAddress); + super.finalize(); + } + + @Override + public Format getSupportedInputFormat() { + return linear; + } + + @Override + public Format getSupportedOutputFormat() { + return opus; + } + + @Override + public Frame process(Frame frame) { + + byte[] input = frame.getData(); + short[] inputData = new short[frame.getLength() / 2]; + ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(inputData); + byte[] encodedData = OpusJni.encodeNative(encoderAddress, inputData); + + Frame res = Memory.allocate(encodedData.length); + System.arraycopy(encodedData, 0, res.getData(), 0, encodedData.length); + + res.setOffset(0); + res.setLength(encodedData.length); + res.setFormat(opus); + res.setTimestamp(frame.getTimestamp()); + res.setDuration(frame.getDuration()); + res.setEOM(frame.isEOM()); + res.setSequenceNumber(frame.getSequenceNumber()); + + return res; + } +} diff --git a/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/OpusJni.java b/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/OpusJni.java new file mode 100644 index 000000000..424a3a0f6 --- /dev/null +++ b/codecs/opus/opus-java/src/main/java/org/restcomm/media/codec/opus/OpusJni.java @@ -0,0 +1,68 @@ +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2017, Telestax Inc and individual contributors + * by the @authors tag. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.restcomm.media.codec.opus; + +import org.apache.log4j.Logger; + +/** + * Implements access to JNI layer for native Opus library. + * + * @author Vladimir Morosev (vladimir.morosev@telestax.com) + * + */ +public class OpusJni { + + private static final Logger log = Logger.getLogger(OpusJni.class); + + public final static int OPUS_APPLICATION_VOIP = 2048; + public final static int OPUS_APPLICATION_AUDIO = 2049; + public final static int OPUS_APPLICATION_RESTRICTED_LOWDELAY = 2051; + + public static interface Observer { + public void onHello(); + } + + static { + String libraryName = System.getProperty("restcomm.opus.library"); + if (libraryName != null) { + try { + System.loadLibrary(libraryName); + } catch (UnsatisfiedLinkError e) { + log.error("Failed to load native Opus library. " + e.getMessage()); + throw e; + } + } else { + log.error("Native Opus library parameter has not been defined (restcomm.opus.library)."); + } + } + + public static native long createEncoderNative(int sampleRate, int channels, int application, int bitRate); + public static native long createDecoderNative(int sampleRate, int channels); + public static native void releaseEncoderNative(long encoderAddress); + public static native void releaseDecoderNative(long decoderAddress); + public static native byte[] encodeNative(long encoderAddress, short[] pcmData); + public static native short[] decodeNative(long decoderAddress, byte[] opusData); + + public native void sayHelloNative(); + public native void setOpusObserverNative(Observer observer); + public native void unsetOpusObserverNative(); +} diff --git a/codecs/opus/opus-java/src/test/java/org/restcomm/media/codec/opus/OpusCodecTest.java b/codecs/opus/opus-java/src/test/java/org/restcomm/media/codec/opus/OpusCodecTest.java new file mode 100644 index 000000000..fe6a0eadf --- /dev/null +++ b/codecs/opus/opus-java/src/test/java/org/restcomm/media/codec/opus/OpusCodecTest.java @@ -0,0 +1,146 @@ +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2017, Telestax Inc and individual contributors + * by the @authors tag. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.restcomm.media.codec.opus; + +import org.apache.log4j.Logger; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.restcomm.media.codec.opus.Decoder; +import org.restcomm.media.codec.opus.Encoder; +import org.restcomm.media.codec.opus.OpusJni; +import org.restcomm.media.spi.memory.Frame; +import org.restcomm.media.spi.memory.Memory; + + +/** + * Opus codec test class + * + * @author Vladimir Morosev (vladimir.morosev@telestax.com) + * + */ +public class OpusCodecTest { + + private static final Logger log = Logger.getLogger(OpusCodecTest.class); + + private Frame buffer = Memory.allocate(512); + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + public OpusCodecTest() { + } + + @Before + public void setUp() throws Exception { + buffer.setLength(512); + } + + @After + public void tearDown() throws Exception { + } + + /** + * Test of process method, for Encoder and Decoder. + */ + @Test + public void testCodec() throws Exception { + + boolean testPassed = false; + + try { + final int packetSize = 480; + File outputFile = File.createTempFile("opustest", ".tmp"); + URL inputFileUrl = this.getClass().getResource("/test_sound_mono_48.pcm"); + Encoder encoder = new Encoder(); + Decoder decoder = new Decoder(); + try (FileInputStream inputStream = new FileInputStream(inputFileUrl.getFile()); + FileOutputStream outputStream = new FileOutputStream(outputFile, false)) { + byte[] input = new byte[2 * packetSize]; + short[] inputData = new short[packetSize]; + while (inputStream.read(input) == 2 * packetSize) { + Frame inputFrame = Memory.allocate(2 * packetSize); + inputFrame.setOffset(0); + inputFrame.setLength(2 * packetSize); + inputFrame.setFormat(encoder.getSupportedInputFormat()); + inputFrame.setTimestamp(System.currentTimeMillis()); + inputFrame.setDuration(10); + System.arraycopy(input, 0, inputFrame.getData(), 0, 2 * packetSize); + Frame encodedFrame = encoder.process(inputFrame); + Frame decodedFrame = decoder.process(encodedFrame); + outputStream.write(decodedFrame.getData()); + } + testPassed = true; + } finally { + outputFile.delete(); + } + + outputFile.delete(); + } catch (IOException exc) { + log.error("IOException: " + exc.getMessage()); + fail("Opus test file access error"); + } + + assertTrue(testPassed); + } + + /** + * Test for observer. + */ + @Test + public void testObserver() throws Exception { + + // given + final OpusJni.Observer observer = mock(OpusJni.Observer.class); + + // when + OpusJni opus = new OpusJni(); + opus.setOpusObserverNative(observer); + opus.sayHelloNative(); + opus.unsetOpusObserverNative(); + + // then + verify(observer, times(1)).onHello(); + } +} diff --git a/codecs/opus/opus-java/src/test/resources/test_sound_mono_48.pcm b/codecs/opus/opus-java/src/test/resources/test_sound_mono_48.pcm new file mode 100644 index 000000000..7619a5019 Binary files /dev/null and b/codecs/opus/opus-java/src/test/resources/test_sound_mono_48.pcm differ diff --git a/codecs/opus/opus-native/README.md b/codecs/opus/opus-native/README.md new file mode 100644 index 000000000..c09063eda --- /dev/null +++ b/codecs/opus/opus-native/README.md @@ -0,0 +1 @@ +c++ opus_jni.cpp -std=c++11 -I/usr/lib/jvm/jdk1.7.0_80/include -I/usr/lib/jvm/jdk1.7.0_80/include/linux -I/usr/include/opus -lopus -Wall -fPIC -shared -o libopus_jni.so diff --git a/codecs/opus/opus-native/linux/pom.xml b/codecs/opus/opus-native/linux/pom.xml new file mode 100644 index 000000000..788e88e89 --- /dev/null +++ b/codecs/opus/opus-native/linux/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + so + + + org.restcomm.media.codecs.opus + opus-native + 7.0.0-SNAPSHOT + + + org.restcomm.media.codecs.opus + libopus_jni_linux + Opus Native Linux + + + + + org.codehaus.mojo + native-maven-plugin + true + + + generic + + -std=c++11 -I${java.home}/../include -I${java.home}/../include/linux -I/usr/include/opus -Wall -fPIC + + + + -shared + + + -lopus + + ${artifactId} + + + + ../src + + opus_jni.cpp + + + + + + + + + diff --git a/codecs/opus/opus-native/macosx/pom.xml b/codecs/opus/opus-native/macosx/pom.xml new file mode 100644 index 000000000..ae06fe84a --- /dev/null +++ b/codecs/opus/opus-native/macosx/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + dylib + + + org.restcomm.media.codecs.opus + opus-native + 7.0.0-SNAPSHOT + + + org.restcomm.media.codecs.opus + libopus_jni_macos + Opus Native Mac OSX + + + + + org.codehaus.mojo + native-maven-plugin + true + + + generic + + -std=c++11 -I${java.home}/../include -I${java.home}/../include/darwin -I/usr/local/include/opus -Wall -fPIC + + + + -shared -lopus + + ${artifactId} + + + + ../src + + opus_jni.cpp + + + + + + + + + + diff --git a/codecs/opus/opus-native/pom.xml b/codecs/opus/opus-native/pom.xml new file mode 100644 index 000000000..40b190763 --- /dev/null +++ b/codecs/opus/opus-native/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + pom + + + org.restcomm.media.codecs + opus + 7.0.0-SNAPSHOT + + + org.restcomm.media.codecs.opus + opus-native + Opus Native + + + + + linux-profile + + + Linux + unix + + + + linux + + + + + macosx-profile + + + mac + + + + macosx + + + + + + diff --git a/codecs/opus/opus-native/src/opus_jni.cpp b/codecs/opus/opus-native/src/opus_jni.cpp new file mode 100644 index 000000000..8199fb684 --- /dev/null +++ b/codecs/opus/opus-native/src/opus_jni.cpp @@ -0,0 +1,212 @@ +/* +* TeleStax, Open Source Cloud Communications +* Copyright 2011-2017, Telestax Inc and individual contributors +* by the @authors tag. +* +* This is free software; you can redistribute it and/or modify it +* under the terms of the GNU Lesser General Public License as +* published by the Free Software Foundation; either version 2.1 of +* the License, or (at your option) any later version. +* +* This software 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this software; if not, write to the Free +* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +* 02110-1301 USA, or see the FSF site: http://www.fsf.org. +* +* @author morosev +* +*/ + +#if defined(_WIN32) +#define OPUS_EXPORT __declspec(dllimport) +#endif +#include "opus.h" + +#include "jni.h" + +#include +#include +#include +#include + +#define MAX_FRAME_SIZE 6*80 +#define MAX_PACKET_SIZE 320 + +JavaVM* gJvm; +jobject gOpusObserver; + +extern "C" { + + JNIEXPORT jlong JNICALL Java_org_restcomm_media_codec_opus_OpusJni_createEncoderNative(JNIEnv *, jobject, + jint, jint, jint, jint); + + JNIEXPORT jlong JNICALL Java_org_restcomm_media_codec_opus_OpusJni_createDecoderNative(JNIEnv *, jobject, + jint, jint); + + JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_releaseEncoderNative(JNIEnv *, jobject, + jlong); + + JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_releaseDecoderNative(JNIEnv *, jobject, + jlong); + + JNIEXPORT jbyteArray JNICALL Java_org_restcomm_media_codec_opus_OpusJni_encodeNative( + JNIEnv *jni, jobject, jlong, jshortArray); + + JNIEXPORT jshortArray JNICALL Java_org_restcomm_media_codec_opus_OpusJni_decodeNative( + JNIEnv *jni, jobject, jlong, jbyteArray); + + JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_sayHelloNative(JNIEnv *, jobject); + + JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_setOpusObserverNative(JNIEnv *, jobject, jobject); + + JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_unsetOpusObserverNative(JNIEnv *, jobject); +} + +JNIEXPORT jlong JNICALL Java_org_restcomm_media_codec_opus_OpusJni_createEncoderNative(JNIEnv *env, jobject, + jint jSampleRate, jint jChannels, jint jApplicationType, jint jBitrate) { + + int err; + + OpusEncoder *encoder = opus_encoder_create(jSampleRate, jChannels, jApplicationType, &err); + if (err < 0) { + fprintf(stderr, "Failed to create an encoder: %s\n", opus_strerror(err)); + return 0; + } + + err = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(jBitrate)); + if (err < 0) { + fprintf(stderr, "Failed to set bitrate: %s\n", opus_strerror(err)); + return 0; + } + + return (jlong)encoder; +} + +JNIEXPORT jlong JNICALL Java_org_restcomm_media_codec_opus_OpusJni_createDecoderNative(JNIEnv *env, jobject, + jint jSampleRate, jint jChannels) { + + int err; + + OpusDecoder *decoder = opus_decoder_create(jSampleRate, jChannels, &err); + if (err < 0) { + fprintf(stderr, "Failed to create decoder: %s\n", opus_strerror(err)); + return 0; + } + + return (jlong)decoder; +} + +JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_releaseEncoderNative(JNIEnv *env, jobject, + jlong jEncoderAddress) { + + OpusEncoder *encoder = (OpusEncoder *)jEncoderAddress; + + opus_encoder_destroy(encoder); +} + +JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_releaseDecoderNative(JNIEnv *env, jobject, + jlong jDecoderAddress) { + + OpusDecoder *decoder = (OpusDecoder *)jDecoderAddress; + + opus_decoder_destroy(decoder); +} + +JNIEXPORT jbyteArray JNICALL Java_org_restcomm_media_codec_opus_OpusJni_encodeNative( + JNIEnv *env, jobject, jlong jEncoderAddress, jshortArray jPcmData) { + + jshort *pcmData = env->GetShortArrayElements(jPcmData, NULL); + jsize pcmLen = env->GetArrayLength(jPcmData); + + OpusEncoder *encoder = (OpusEncoder *)jEncoderAddress; + + unsigned char encoded[MAX_PACKET_SIZE]; + + int packetSize; + packetSize = opus_encode(encoder, pcmData, pcmLen, encoded, MAX_PACKET_SIZE); + if (packetSize < 0) { + fprintf(stderr, "Encode failed: %s\n", opus_strerror(packetSize)); + return nullptr; + } + + env->ReleaseShortArrayElements(jPcmData, pcmData, 0); + + jbyteArray jOpusData = env->NewByteArray(packetSize); + env->SetByteArrayRegion(jOpusData, 0, packetSize, (jbyte *)encoded); + + return jOpusData; +} + +JNIEXPORT jshortArray JNICALL Java_org_restcomm_media_codec_opus_OpusJni_decodeNative( + JNIEnv *env, jobject, jlong jDecoderAddress, jbyteArray jOpusData) { + + jbyte *opusData = env->GetByteArrayElements(jOpusData, NULL); + jsize opusLen = env->GetArrayLength(jOpusData); + + OpusDecoder *decoder = (OpusDecoder *)jDecoderAddress; + + short decoded[MAX_FRAME_SIZE]; + + int frameSize; + frameSize = opus_decode(decoder, (unsigned char *)opusData, opusLen, decoded, MAX_FRAME_SIZE, 0); + if (frameSize < 0) { + fprintf(stderr, "Decoder failed: %s\n", opus_strerror(frameSize)); + return nullptr; + } + + env->ReleaseByteArrayElements(jOpusData, opusData, 0); + + jshortArray jPcmData = env->NewShortArray(frameSize); + env->SetShortArrayRegion(jPcmData, 0, frameSize, decoded); + + return jPcmData; +} + +void OnHello() { + void* env = nullptr; + jint status = gJvm->GetEnv(&env, JNI_VERSION_1_4); + if (status != JNI_OK) + return; + JNIEnv* jni = reinterpret_cast(env); + jmethodID jOnHelloMid = jni->GetMethodID( + jni->GetObjectClass(gOpusObserver), "onHello", "()V"); + jni->CallVoidMethod(gOpusObserver, jOnHelloMid); +} + +JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_sayHelloNative(JNIEnv *, jobject) { + printf("Hello World - native!\n"); + OnHello(); +} + +JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_setOpusObserverNative( + JNIEnv *env, jobject, jobject jObserver) { + gOpusObserver = env->NewGlobalRef(jObserver); +} + +JNIEXPORT void JNICALL Java_org_restcomm_media_codec_opus_OpusJni_unsetOpusObserverNative( + JNIEnv *env, jobject) { + env->DeleteGlobalRef(gOpusObserver); +} + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { + + if (!vm) { + printf("No Java Virtual Machine pointer"); + return -1; + } + + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast (&env), JNI_VERSION_1_4) != JNI_OK) { + printf("Cannot obtain JNI environment"); + return -1; + } + + gJvm = vm; + + return JNI_VERSION_1_4; +} diff --git a/codecs/opus/pom.xml b/codecs/opus/pom.xml new file mode 100644 index 000000000..50be36c1a --- /dev/null +++ b/codecs/opus/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + pom + + + org.restcomm.media + codecs + 7.0.0-SNAPSHOT + + + org.restcomm.media.codecs + opus + Opus + + + opus-java + opus-native + + diff --git a/codecs/pom.xml b/codecs/pom.xml index ffce430fb..3a732a899 100644 --- a/codecs/pom.xml +++ b/codecs/pom.xml @@ -17,7 +17,8 @@ g711 gsm g729 - l16 - ilbc + l16 + ilbc + opus diff --git a/controls/mgcp2/src/main/java/org/restcomm/media/control/mgcp/network/netty/MgcpChannelInitializer.java b/controls/mgcp2/src/main/java/org/restcomm/media/control/mgcp/network/netty/MgcpChannelInitializer.java index 5b810bfc2..a84b94ff5 100644 --- a/controls/mgcp2/src/main/java/org/restcomm/media/control/mgcp/network/netty/MgcpChannelInitializer.java +++ b/controls/mgcp2/src/main/java/org/restcomm/media/control/mgcp/network/netty/MgcpChannelInitializer.java @@ -36,7 +36,7 @@ */ public class MgcpChannelInitializer extends ChannelInitializer { - private static final int BUFFER_SIZE = 2300; + private static final int BUFFER_SIZE = 5000; private static final ChannelHandler[] NO_HANDLERS = new ChannelHandler[0]; private final ChannelHandler[] handlers; diff --git a/core/pom.xml b/core/pom.xml index d350c484b..ef6ba5fc5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -54,6 +54,11 @@ g711 ${project.version} + + org.restcomm.media.codecs.opus + opus-java + ${project.version} + org.restcomm.media rtp diff --git a/core/src/main/java/org/restcomm/media/core/configuration/CodecType.java b/core/src/main/java/org/restcomm/media/core/configuration/CodecType.java index 5a4634ad7..c3ec13bcb 100644 --- a/core/src/main/java/org/restcomm/media/core/configuration/CodecType.java +++ b/core/src/main/java/org/restcomm/media/core/configuration/CodecType.java @@ -35,6 +35,7 @@ public enum CodecType { L16(97, "l16", org.restcomm.media.codec.l16.Encoder.class.getName(), org.restcomm.media.codec.l16.Decoder.class.getName()), G729(18, "g729", org.restcomm.media.codec.g729.Encoder.class.getName(), org.restcomm.media.codec.g729.Decoder.class.getName()), ILBC(102, "ilbc", org.restcomm.media.codec.ilbc.Encoder.class.getName(), org.restcomm.media.codec.ilbc.Decoder.class.getName()), + OPUS(111, "opus", org.restcomm.media.codec.opus.Encoder.class.getName(), org.restcomm.media.codec.opus.Decoder.class.getName()), DTMF(101, "telephone-event", "", ""); private final int payloadType; diff --git a/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Configuring_the_Media_Server.adoc b/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Configuring_the_Media_Server.adoc index 8b04acd3d..d168d178b 100644 --- a/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Configuring_the_Media_Server.adoc +++ b/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Configuring_the_Media_Server.adoc @@ -174,6 +174,7 @@ The media configuration contains definitions that have an impact on the media ch + @@ -224,7 +225,7 @@ In case of 180 or 183 without SDP response , intermediate MDCX is not required. ==== Codecs -Currently media server supports five codecs : G711 A/U, Linear PCM Raw, GSM, ILBC and G.729. +Currently media server supports six codecs : G711 A/U, Linear PCM Raw, GSM, ILBC, Opus and G.729. .G.729 usage WARNING: Please note that a valid license is required to use G.729 , therefore you should purchase a license prior to enabling this codec. @@ -236,6 +237,30 @@ This is useful only in case of a one way activity. NOTE: L16 codec is useful only in server to server communication where you have enough network bandwidth. It is not recommended to allow L16 codec for UA – server connections, this can lead to degradation of the signal quality due to increased jitter and packet loss. +.Opus usage +NOTE: Opus codec processes data internally at 8kHz as mono signal. This sample rate and number of channels are limiting factors for sound quality when this codec is used. Bitrate is around 20 kbps. Payload type is fixed to value 111. It won't work with clients with other payload type values assigned to Opus codec. + +==== Opus Codec Configuration + +http://opus-codec.org/[Opus Codec] is open, royalty-free, highly versatile audio codec. + +Prerequisite library for Opus codec is `libopus` (or `libopus-dev` if the project is compiled from sources). + +[source,shell] +---- +# CentOS/RHEL +yum install libopus + +# Ubuntu/Debian +apt-get install libopus + +# macOS +brew install opus +---- + +The location of libopus library and name of compiled JNI library used by media server are specified by command line parameters. The values of the parameters are defined in file `bin/run.sh`. + + === Resources Configuration In the current Media Server release, a global pool of resources is used to decrease garbage collection and allow for faster resource allocation. diff --git a/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Introduction_to_the_Media_Server.adoc b/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Introduction_to_the_Media_Server.adoc index 399915129..ee499f6b4 100644 --- a/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Introduction_to_the_Media_Server.adoc +++ b/docs/sources-asciidoc/src/main/asciidoc/concept-chapter-Introduction_to_the_Media_Server.adoc @@ -54,6 +54,7 @@ The Restcomm Media Server is capable of ** GSM ** Linear PCM(L16) ** G729 +** Opus ** DTMF(RFC 2833, INBAND) * Media Files : diff --git a/sdp/src/main/java/org/restcomm/media/sdp/format/AVProfile.java b/sdp/src/main/java/org/restcomm/media/sdp/format/AVProfile.java index e264d6051..d85b2a89c 100644 --- a/sdp/src/main/java/org/restcomm/media/sdp/format/AVProfile.java +++ b/sdp/src/main/java/org/restcomm/media/sdp/format/AVProfile.java @@ -52,6 +52,7 @@ public class AVProfile { private final static RTPFormat dtmf = new RTPFormat(telephoneEventsID, telephoneEvent, 8000); private final static RTPFormat dtmf126 = new RTPFormat(telephoneEvent126, telephoneEvent, 8000); private final static RTPFormat ilbc = new RTPFormat(102, FormatFactory.createAudioFormat("ilbc", 8000, 16, 1), 8000); + private final static RTPFormat opus = new RTPFormat(111, FormatFactory.createAudioFormat("opus", 48000, 16, 2), 48000); private final static RTPFormat linear = new RTPFormat(150, FormatFactory.createAudioFormat("linear", 8000, 16, 1), 8000); private final static RTPFormat H261 = new RTPFormat(45, FormatFactory.createVideoFormat("h261")); @@ -65,6 +66,7 @@ public class AVProfile { audio.add(g729); audio.add(l16); audio.add(ilbc); + audio.add(opus); audio.add(dtmf); audio.add(dtmf126); }