Scopus is a Scala interface to the Opus 1.3.1 and g.711u codecs. There is also a PCM "codec" which is effectively a NULL codec and is useful in testing.
It is light and thin by design and gives programmers access to the bulk of the functionality to the codecs. It uses JNI to handle the native code and works for Linux (amd64 and i386) and OSX. Benchmarks performed on the encoder show that it is 10% slower than a native C implementation. For example, on a 3.5Ghz i5, the Opus coder runs at 360 times real time (complexity factor set to 2). The Opus decoder runs at around 1600 times real time. A native benchmark shows the encoder to run at 400 times real time. The LLVM C compiler (clang v3.4) pips GCC by about 5%.
The sources for Opus can be downloaded here.
Scopus by David Weber. Released under the Apache-2.0 license.
Clone the sources and do a sbt test
. Package and deploy as you see fit.
The native libraries can be built using the makefiles in the src/native/opus directory
. You do need to have the Opus libraries and headers installed and
copied to the appropriate native directories in the resource
directory. You
may have to customise the paths to the include file (jni.h) a little. The
libraries libjni-opus.{so,dylib} and libopus.{so.0,0.dylib} should be placed
in the appropriate resources/native/
directory. Some resources to understanding
this process are:
- Benson Margulies' JNI test bed shows how to avoid
LD_LIBRARY_PATH
hell. - Read the
ld
man page, especially the bit about -rpath=$ORIGIN (Linux) and @loader_path (OSX). It makes the magic work. - For OSX,
otool
andinstall_name_tool
are your friends.
This library explicitly copies the dynamic libraries out of the resources
directory in the jar file to a unique location (something like
/tmp/scopus_418af7c0b63
on Linux systems) from where it loads them. The
temporary files are deleted when the JVM exits. Note that if you have existing
installations of Opus, the linker will likely find that library rather than
the bundled one (demonstrated for Linux). This may cause the test cases to
fail.
The Opus codec performance improves if compiled with clang
. Configure, build
and test it as follows:
CC=clang CFLAGS=-O3 ./configure
make
make check
Scopus is available from the Sonatype Maven repo with builds for Scala 2.12 and 2.13. Add the following dependency to your sbt build:
libaryDependencies += "za.co.monadic" %% "scopus" % "0.5.0"
Encoding a stream is pretty simple. Return types are Scala are wrapped in a Try[_]
so it is up to you to manage errors reported by the decoder or the encoder.
// Variable bit rate Opus encoder with discontinuous transmission
val enc = OpusEncoder(Sf8000, 1, Audio).setUseDtx(1).setVbr(1)
// Corresponding decoder
val dec = OpusDecoder(Sf8000, 1)
val coded: Try[Array[Byte]] = enc(new Array[Short](160))
// Transmit
// On receive end
val decoded: Try[Array[Short]] = dec(coded.get)
// Send decoded packet off
There is now support for a G711 u-law encoder. The standard applies to 8kHz signals but this codec will upsample and downsample the signal to achieve particular sampling rate goals. If the encoder is set to decode to a 48kHz sampling rate, it expects an 8kHz u-Law codec signal which it first decodes to a linear signal then upsamples it to reach 48kHz. Similarly, a decoder configured for 48kHz will first downsample the signal to 8kHz then u-Law encode it. Stereo is not supported for this mode.
There are restrictions on the size of the input buffer. For Opus, audio frames may be one of the following durations: 2.5, 5, 10, 20, 40 or 60 ms. Smaller values obviously give less delay but at the expense of slightly less efficient compression. Note that Java is big endian while most raw audio data are little endian (at least on Intel Architectures). This means you may have to do some byte swapping when reading audio streams from external sources.
Individual instances of encoders and decoders are not thread safe as they have to be called sequentially to maintain the state of the encoder/decoder. The Opus codec instances use a shared decode buffer which is not thread safe. In practice, if you are calling decode on the same instance of the codec, you are managing the stream incorrectly.
Scala does not seem to have a convention for error handling. I went with Try. If this is not how you think it should be done, read the link and make a case. Try can be flatmapped which is important in my application.
Future plans include adding codecs and abstracting the codec layer so they are more pluggable. Pull requests welcome.
To build native libraries located under src/main/resources/native
folder, go to src/main/native/opus
folder
and run the following commands:
make -f Makefile.[platform]
make -f Makefile.[platform] install
make -f Makefile.[platform] clean
this will produce the appropriate files and locate them under resources/native
folder.