| @@ -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); | |||
| @@ -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 { | |||
| @@ -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(); | |||
| @@ -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; | |||
| } | |||
| @@ -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("****************************************************"); | |||
| @@ -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 <opus/opus.h> | |||
| #include <opus/opus_custom.h> | |||
| 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 | |||
| @@ -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 { | |||
| @@ -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', | |||
| @@ -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']) | |||