diff --git a/common/JackNetAdapter.cpp b/common/JackNetAdapter.cpp index fca2dab0..7cc8f053 100644 --- a/common/JackNetAdapter.cpp +++ b/common/JackNetAdapter.cpp @@ -101,6 +101,14 @@ namespace Jack fParams.fKBps = param->value.i; } break; + #endif + #if HAVE_OPUS + case 'O': + if (param->value.i > 0) { + fParams.fSampleEncoder = JackOpusEncoder; + fParams.fKBps = param->value.i; + } + break; #endif case 'l' : fParams.fNetworkLatency = param->value.i; @@ -421,6 +429,11 @@ extern "C" jack_driver_descriptor_add_parameter(desc, &filler, "celt", 'c', JackDriverParamInt, &value, NULL, "Set CELT encoding and number of kBits per channel", NULL); #endif + #if HAVE_OPUS + value.i = -1; + jack_driver_descriptor_add_parameter(desc, &filler, "opus", 'O', JackDriverParamInt, &value, NULL, "Set Opus encoding and number of kBits per channel", NULL); + #endif + strcpy(value.str, "'hostname'"); jack_driver_descriptor_add_parameter(desc, &filler, "client-name", 'n', JackDriverParamString, &value, NULL, "Name of the jack client", NULL); diff --git a/common/JackNetDriver.cpp b/common/JackNetDriver.cpp index 1f3681f4..12019ba0 100644 --- a/common/JackNetDriver.cpp +++ b/common/JackNetDriver.cpp @@ -29,7 +29,7 @@ namespace Jack { JackNetDriver::JackNetDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, const char* ip, int udp_port, int mtu, int midi_input_ports, int midi_output_ports, - char* net_name, uint transport_sync, int network_latency, int celt_encoding) + char* net_name, uint transport_sync, int network_latency, int celt_encoding, int opus_encoding) : JackWaiterDriver(name, alias, engine, table), JackNetSlaveInterface(ip, udp_port) { jack_log("JackNetDriver::JackNetDriver ip %s, port %d", ip, udp_port); @@ -45,6 +45,9 @@ namespace Jack if (celt_encoding > 0) { fParams.fSampleEncoder = JackCeltEncoder; fParams.fKBps = celt_encoding; + } else if (opus_encoding > 0) { + fParams.fSampleEncoder = JackOpusEncoder; + fParams.fKBps = opus_encoding; } else { fParams.fSampleEncoder = JackFloatEncoder; //fParams.fSampleEncoder = JackIntEncoder; @@ -619,6 +622,10 @@ namespace Jack #if HAVE_CELT value.i = -1; jack_driver_descriptor_add_parameter(desc, &filler, "celt", 'c', JackDriverParamInt, &value, NULL, "Set CELT encoding and number of kBits per channel", NULL); +#endif +#if HAVE_OPUS + value.i = -1; + jack_driver_descriptor_add_parameter(desc, &filler, "opus", 'O', JackDriverParamInt, &value, NULL, "Set Opus encoding and number of kBits per channel", NULL); #endif strcpy(value.str, "'hostname'"); jack_driver_descriptor_add_parameter(desc, &filler, "client-name", 'n', JackDriverParamString, &value, NULL, "Name of the jack client", NULL); @@ -650,6 +657,7 @@ Deactivated for now.. int midi_input_ports = 0; int midi_output_ports = 0; int celt_encoding = -1; + int opus_encoding = -1; bool monitor = false; int network_latency = 5; const JSList* node; @@ -699,6 +707,11 @@ Deactivated for now.. celt_encoding = param->value.i; break; #endif + #if HAVE_OPUS + case 'O': + opus_encoding = param->value.i; + break; + #endif case 'n' : strncpy(net_name, param->value.str, JACK_CLIENT_NAME_SIZE); break; @@ -724,7 +737,7 @@ Deactivated for now.. new Jack::JackNetDriver("system", "net_pcm", engine, table, multicast_ip, udp_port, mtu, midi_input_ports, midi_output_ports, net_name, transport_sync, - network_latency, celt_encoding)); + network_latency, celt_encoding, opus_encoding)); if (driver->Open(period_size, sample_rate, 1, 1, audio_capture_ports, audio_playback_ports, monitor, "from_master_", "to_master_", 0, 0) == 0) { return driver; } else { diff --git a/common/JackNetDriver.h b/common/JackNetDriver.h index 48a733bf..245a431b 100644 --- a/common/JackNetDriver.h +++ b/common/JackNetDriver.h @@ -69,7 +69,7 @@ namespace Jack JackNetDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table, const char* ip, int port, int mtu, int midi_input_ports, int midi_output_ports, - char* net_name, uint transport_sync, int network_latency, int celt_encoding); + char* net_name, uint transport_sync, int network_latency, int celt_encoding, int opus_encoding); virtual ~JackNetDriver(); int Close(); diff --git a/common/JackNetInterface.cpp b/common/JackNetInterface.cpp index 4ad731c5..6a045507 100644 --- a/common/JackNetInterface.cpp +++ b/common/JackNetInterface.cpp @@ -243,6 +243,10 @@ namespace Jack case JackCeltEncoder: return new NetCeltAudioBuffer(&fParams, nports, buffer, fParams.fKBps); #endif + #if HAVE_OPUS + case JackOpusEncoder: + return new NetOpusAudioBuffer(&fParams, nports, buffer, fParams.fKBps); + #endif } return NULL; } diff --git a/common/JackNetTool.cpp b/common/JackNetTool.cpp index 275a0a2f..eb30cc18 100644 --- a/common/JackNetTool.cpp +++ b/common/JackNetTool.cpp @@ -709,6 +709,210 @@ namespace Jack #endif +#if HAVE_OPUS + + NetOpusAudioBuffer::NetOpusAudioBuffer(session_params_t* params, uint32_t nports, char* net_buffer, int kbps) + :NetAudioBuffer(params, nports, net_buffer) + { + fOpusMode = new OpusCustomMode *[fNPorts]; + fOpusEncoder = new OpusCustomEncoder *[fNPorts]; + fOpusDecoder = new OpusCustomDecoder *[fNPorts]; + + memset(fOpusMode, 0, fNPorts * sizeof(OpusCustomMode*)); + memset(fOpusEncoder, 0, fNPorts * sizeof(OpusCustomEncoder*)); + memset(fOpusDecoder, 0, fNPorts * sizeof(OpusCustomDecoder*)); + + int error = OPUS_OK; + + for (int i = 0; i < fNPorts; i++) { + /* Allocate en/decoders */ + fOpusMode[i] = opus_custom_mode_create( + params->fSampleRate, params->fPeriodSize, &error); + if (error != OPUS_OK) { + goto error; + } + + fOpusEncoder[i] = opus_custom_encoder_create(fOpusMode[i], 1,&error); + if (error != OPUS_OK) { + goto error; + } + + fOpusDecoder[i] = opus_custom_decoder_create(fOpusMode[i], 1, &error); + if (error != OPUS_OK) { + goto error; + } + + opus_custom_encoder_ctl(fOpusEncoder[i], OPUS_SET_BITRATE(kbps*1024)); // bits per second + opus_custom_encoder_ctl(fOpusEncoder[i], OPUS_SET_COMPLEXITY(10)); + opus_custom_encoder_ctl(fOpusEncoder[i], OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)); + opus_custom_encoder_ctl(fOpusEncoder[i], OPUS_SET_SIGNAL(OPUS_APPLICATION_RESTRICTED_LOWDELAY)); + + /* initilize decoders */ + error = opus_custom_encoder_init(fOpusEncoder[i], fOpusMode[i], 1); + error = opus_custom_decoder_init(fOpusDecoder[i], fOpusMode[i], 1); + } + + { + fPeriodSize = params->fPeriodSize; + fCompressedSizeByte = (kbps * params->fPeriodSize * 1024) / (params->fSampleRate * 8); + jack_log("NetOpusAudioBuffer fCompressedSizeByte %d", fCompressedSizeByte); + + fCompressedBuffer = new unsigned char* [fNPorts]; + for (int port_index = 0; port_index < fNPorts; port_index++) { + fCompressedBuffer[port_index] = new unsigned char[fCompressedSizeByte]; + memset(fCompressedBuffer[port_index], 0, fCompressedSizeByte * sizeof(char)); + } + + int res1 = (fNPorts * fCompressedSizeByte) % PACKET_AVAILABLE_SIZE(params); + int res2 = (fNPorts * fCompressedSizeByte) / PACKET_AVAILABLE_SIZE(params); + + fNumPackets = (res1) ? (res2 + 1) : res2; + + jack_log("NetOpusAudioBuffer res1 = %d res2 = %d", res1, res2); + + fSubPeriodBytesSize = fCompressedSizeByte / fNumPackets; + fLastSubPeriodBytesSize = fSubPeriodBytesSize + fCompressedSizeByte % fNumPackets; + + jack_log("NetOpusAudioBuffer fNumPackets = %d fSubPeriodBytesSize = %d, fLastSubPeriodBytesSize = %d", fNumPackets, fSubPeriodBytesSize, fLastSubPeriodBytesSize); + + fCycleDuration = float(fSubPeriodBytesSize / sizeof(sample_t)) / float(params->fSampleRate); + fCycleBytesSize = params->fMtu * fNumPackets; + + fLastSubCycle = -1; + return; + } + + error: + + FreeOpus(); + throw std::bad_alloc(); + } + + NetOpusAudioBuffer::~NetOpusAudioBuffer() + { + FreeOpus(); + + for (int port_index = 0; port_index < fNPorts; port_index++) { + delete [] fCompressedBuffer[port_index]; + } + + delete [] fCompressedBuffer; + } + + void NetOpusAudioBuffer::FreeOpus() + { + for (int i = 0; i < fNPorts; i++) { + if (fOpusEncoder[i]) { + opus_custom_encoder_destroy(fOpusEncoder[i]); + fOpusEncoder[i]=0; + } + if (fOpusDecoder[i]) { + opus_custom_decoder_destroy(fOpusDecoder[i]); + fOpusDecoder[i]=0; + } + if (fOpusMode[i]) { + opus_custom_mode_destroy(fOpusMode[i]); + fOpusMode[i]=0; + } + } + + delete [] fOpusEncoder; + delete [] fOpusDecoder; + delete [] fOpusMode; + } + + size_t NetOpusAudioBuffer::GetCycleSize() + { + return fCycleBytesSize; + } + + float NetOpusAudioBuffer::GetCycleDuration() + { + return fCycleDuration; + } + + int NetOpusAudioBuffer::GetNumPackets(int active_ports) + { + return fNumPackets; + } + + int NetOpusAudioBuffer::RenderFromJackPorts() + { + float buffer[BUFFER_SIZE_MAX]; + + for (int port_index = 0; port_index < fNPorts; port_index++) { + if (fPortBuffer[port_index]) { + memcpy(buffer, fPortBuffer[port_index], fPeriodSize * sizeof(sample_t)); + } else { + memset(buffer, 0, fPeriodSize * sizeof(sample_t)); + } + int res = opus_custom_encode_float(fOpusEncoder[port_index], buffer, fPeriodSize, fCompressedBuffer[port_index], fCompressedSizeByte); + if (res != fCompressedSizeByte) { + jack_error("opus_encode_float error fCompressedSizeByte = %d res = %d", fCompressedSizeByte, res); + } + } + + // All ports active + return fNPorts; + } + + void NetOpusAudioBuffer::RenderToJackPorts() + { + for (int port_index = 0; port_index < fNPorts; port_index++) { + if (fPortBuffer[port_index]) { + int res = opus_custom_decode_float(fOpusDecoder[port_index], fCompressedBuffer[port_index], fCompressedSizeByte, fPortBuffer[port_index], fPeriodSize); + if (res != OPUS_OK) { + jack_error("opus_decode_float error fCompressedSizeByte = %d res = %d", fCompressedSizeByte, res); + } + } + } + + NextCycle(); + } + + //network<->buffer + int NetOpusAudioBuffer::RenderFromNetwork(int cycle, int sub_cycle, uint32_t port_num) + { + // Cleanup all JACK ports at the beginning of the cycle + if (sub_cycle == 0) { + Cleanup(); + } + + if (port_num > 0) { + // Last packet of the cycle + if (sub_cycle == fNumPackets - 1) { + for (int port_index = 0; port_index < fNPorts; port_index++) { + memcpy(fCompressedBuffer[port_index] + sub_cycle * fSubPeriodBytesSize, fNetBuffer + port_index * fLastSubPeriodBytesSize, fLastSubPeriodBytesSize); + } + } else { + for (int port_index = 0; port_index < fNPorts; port_index++) { + memcpy(fCompressedBuffer[port_index] + sub_cycle * fSubPeriodBytesSize, fNetBuffer + port_index * fSubPeriodBytesSize, fSubPeriodBytesSize); + } + } + } + + return CheckPacket(cycle, sub_cycle); + } + + int NetOpusAudioBuffer::RenderToNetwork(int sub_cycle, uint32_t port_num) + { + // Last packet of the cycle + if (sub_cycle == fNumPackets - 1) { + for (int port_index = 0; port_index < fNPorts; port_index++) { + memcpy(fNetBuffer + port_index * fLastSubPeriodBytesSize, fCompressedBuffer[port_index] + sub_cycle * fSubPeriodBytesSize, fLastSubPeriodBytesSize); + } + return fNPorts * fLastSubPeriodBytesSize; + } else { + for (int port_index = 0; port_index < fNPorts; port_index++) { + memcpy(fNetBuffer + port_index * fSubPeriodBytesSize, fCompressedBuffer[port_index] + sub_cycle * fSubPeriodBytesSize, fSubPeriodBytesSize); + } + return fNPorts * fSubPeriodBytesSize; + } + } + +#endif + + NetIntAudioBuffer::NetIntAudioBuffer(session_params_t* params, uint32_t nports, char* net_buffer) : NetAudioBuffer(params, nports, net_buffer) { @@ -891,6 +1095,9 @@ namespace Jack case JackCeltEncoder: strcpy(encoder, "CELT"); break; + case JackOpusEncoder: + strcpy(encoder, "OPUS"); + break; } jack_info("**************** Network parameters ****************"); @@ -917,6 +1124,10 @@ namespace Jack jack_info("SampleEncoder : %s", "CELT"); jack_info("kBits : %d", params->fKBps); break; + case (JackOpusEncoder): + jack_info("SampleEncoder : %s", "OPUS"); + jack_info("kBits : %d", params->fKBps); + break; }; jack_info("Slave mode : %s", (params->fSlaveSyncMode) ? "sync" : "async"); jack_info("****************************************************"); diff --git a/common/JackNetTool.h b/common/JackNetTool.h index f8362c54..9e54c7b4 100644 --- a/common/JackNetTool.h +++ b/common/JackNetTool.h @@ -62,6 +62,7 @@ namespace Jack JackFloatEncoder = 0, JackIntEncoder = 1, JackCeltEncoder = 2, + JackOpusEncoder = 3, }; //session params ****************************************************************************** @@ -403,6 +404,50 @@ namespace Jack int RenderToNetwork(int sub_cycle, uint32_t port_num); }; +#endif + +#if HAVE_OPUS + +#include +#include + + class SERVER_EXPORT NetOpusAudioBuffer : public NetAudioBuffer + { + private: + + OpusCustomMode** fOpusMode; + OpusCustomEncoder** fOpusEncoder; + OpusCustomDecoder** fOpusDecoder; + + int fCompressedSizeByte; + int fNumPackets; + + size_t fLastSubPeriodBytesSize; + + unsigned char** fCompressedBuffer; + void FreeOpus(); + + public: + + NetOpusAudioBuffer(session_params_t* params, uint32_t nports, char* net_buffer, int kbps); + virtual ~NetOpusAudioBuffer(); + + // needed size in bytes for an entire cycle + size_t GetCycleSize(); + + // cycle duration in sec + float GetCycleDuration(); + int GetNumPackets(int active_ports); + + //jack<->buffer + int RenderFromJackPorts(); + void RenderToJackPorts(); + + //network<->buffer + int RenderFromNetwork(int cycle, int sub_cycle, uint32_t port_num); + int RenderToNetwork(int sub_cycle, uint32_t port_num); + }; + #endif class SERVER_EXPORT NetIntAudioBuffer : public NetAudioBuffer diff --git a/common/jack/net.h b/common/jack/net.h index 4709b477..ad7eb8ce 100644 --- a/common/jack/net.h +++ b/common/jack/net.h @@ -40,6 +40,7 @@ enum JackNetEncoder { JackFloatEncoder = 0, // samples are transmitted as float JackIntEncoder = 1, // samples are transmitted as 16 bits integer JackCeltEncoder = 2, // samples are transmitted using CELT codec (http://www.celt-codec.org/) + JackOpusEncoder = 2, // samples are transmitted using OPUS codec (http://www.opus-codec.org/) }; typedef struct { diff --git a/common/wscript b/common/wscript index 807e5e25..c3894a5e 100644 --- a/common/wscript +++ b/common/wscript @@ -68,7 +68,7 @@ def build(bld): ] includes = ['.', './jack', '..'] - uselib = ["PTHREAD", "CELT"] + uselib = ["PTHREAD", "CELT", "OPUS"] if bld.env['IS_LINUX']: common_libsources += [ @@ -193,7 +193,7 @@ def build(bld): netlib.includes = includes netlib.name = 'netlib' netlib.target = 'jacknet' - netlib.use = ['SAMPLERATE', 'CELT', 'PTHREAD' , 'RT'] + netlib.use = ['SAMPLERATE', 'CELT', 'OPUS', 'PTHREAD' , 'RT'] netlib.install_path = '${LIBDIR}' netlib.source = [ 'JackNetAPI.cpp', diff --git a/wscript b/wscript index e8d9a8bd..4fa9f5ba 100644 --- a/wscript +++ b/wscript @@ -173,6 +173,13 @@ def configure(conf): conf.define('HAVE_CELT_API_0_7', 0) conf.define('HAVE_CELT_API_0_5', 0) + conf.env['WITH_OPUS'] = False + if conf.check_cfg(package='opus', atleast_version='0.9.0' , args='--cflags --libs', mandatory=False): + if conf.check_cc(header_name='opus/opus_custom.h', mandatory=False): + conf.define('HAVE_OPUS', 1) + conf.env['WITH_OPUS'] = True + + conf.env['LIB_PTHREAD'] = ['pthread'] conf.env['LIB_DL'] = ['dl'] conf.env['LIB_RT'] = ['rt'] @@ -264,6 +271,7 @@ def configure(conf): display_msg('C++ compiler flags', repr(conf.env['CXXFLAGS'])) display_msg('Linker flags', repr(conf.env['LINKFLAGS'])) display_feature('Build doxygen documentation', conf.env['BUILD_DOXYGEN_DOCS']) + display_feature('Build Opus netjack2', conf.env['WITH_OPUS']) display_feature('Build with engine profiling', conf.env['BUILD_WITH_PROFILE']) display_feature('Build with 32/64 bits mixed mode', conf.env['BUILD_WITH_32_64'])