/* Copyright (C) 2008-2011 Romain Moret at Grame This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "JackCompilerDeps.h" #include "driver_interface.h" #include "JackNetDriver.h" #include "JackEngineControl.h" #include "JackLockedEngine.h" #include "JackWaitThreadedDriver.h" using namespace std; 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, int opus_encoding, bool auto_save) : JackWaiterDriver(name, alias, engine, table), JackNetSlaveInterface(ip, udp_port) { jack_log("JackNetDriver::JackNetDriver ip %s, port %d", ip, udp_port); // Use the hostname if no name parameter was given if (strcmp(net_name, "") == 0) { GetHostName(net_name, JACK_CLIENT_NAME_SIZE); } fParams.fMtu = mtu; fWantedMIDICaptureChannels = midi_input_ports; fWantedMIDIPlaybackChannels = midi_output_ports; 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; } strcpy(fParams.fName, net_name); fSocket.GetName(fParams.fSlaveNetName); fParams.fTransportSync = transport_sync; fParams.fNetworkLatency = network_latency; fSendTransportData.fState = -1; fReturnTransportData.fState = -1; fLastTransportState = -1; fLastTimebaseMaster = -1; fMidiCapturePortList = NULL; fMidiPlaybackPortList = NULL; fWantedAudioCaptureChannels = -1; fWantedAudioPlaybackChannels = -1; fAutoSave = auto_save; #ifdef JACK_MONITOR fNetTimeMon = NULL; fRcvSyncUst = 0; #endif } JackNetDriver::~JackNetDriver() { delete[] fMidiCapturePortList; delete[] fMidiPlaybackPortList; #ifdef JACK_MONITOR delete fNetTimeMon; #endif } //open, close, attach and detach------------------------------------------------------ int JackNetDriver::Open(jack_nframes_t buffer_size, jack_nframes_t samplerate, bool capturing, bool playing, int inchannels, int outchannels, bool monitor, const char* capture_driver_name, const char* playback_driver_name, jack_nframes_t capture_latency, jack_nframes_t playback_latency) { // Keep initial wanted values fWantedAudioCaptureChannels = inchannels; fWantedAudioPlaybackChannels = outchannels; return JackWaiterDriver::Open(buffer_size, samplerate, capturing, playing, inchannels, outchannels, monitor, capture_driver_name, playback_driver_name, capture_latency, playback_latency); } int JackNetDriver::Close() { #ifdef JACK_MONITOR if (fNetTimeMon) { fNetTimeMon->Save(); } #endif FreeAll(); return JackWaiterDriver::Close(); } // Attach and Detach are defined as empty methods: port allocation is done when driver actually start (that is in Init) int JackNetDriver::Attach() { return 0; } int JackNetDriver::Detach() { return 0; } //init and restart-------------------------------------------------------------------- /* JackNetDriver is wrapped in a JackWaitThreadedDriver decorator that behaves as a "dummy driver, until Init method returns. */ bool JackNetDriver::Initialize() { jack_log("JackNetDriver::Initialize"); if (fAutoSave) { SaveConnections(0); } FreePorts(); // New loading, but existing socket, restart the driver if (fSocket.IsSocket()) { jack_info("Restarting driver..."); FreeAll(); } // Set the parameters to send fParams.fSendAudioChannels = fWantedAudioCaptureChannels; fParams.fReturnAudioChannels = fWantedAudioPlaybackChannels; fParams.fSendMidiChannels = fWantedMIDICaptureChannels; fParams.fReturnMidiChannels = fWantedMIDIPlaybackChannels; fParams.fSlaveSyncMode = fEngineControl->fSyncMode; // Display some additional infos jack_info("NetDriver started in %s mode %s Master's transport sync.", (fParams.fSlaveSyncMode) ? "sync" : "async", (fParams.fTransportSync) ? "with" : "without"); // Init network if (!JackNetSlaveInterface::Init()) { jack_error("Starting network fails..."); return false; } // Set global parameters if (!SetParams()) { jack_error("SetParams error..."); return false; } // If -1 at connection time for audio, in/out audio channels count is sent by the master fCaptureChannels = fParams.fSendAudioChannels; fPlaybackChannels = fParams.fReturnAudioChannels; // If -1 at connection time for MIDI, in/out MIDI channels count is sent by the master (in fParams struct) // Allocate midi ports lists delete[] fMidiCapturePortList; delete[] fMidiPlaybackPortList; if (fParams.fSendMidiChannels > 0) { fMidiCapturePortList = new jack_port_id_t [fParams.fSendMidiChannels]; assert(fMidiCapturePortList); for (int midi_port_index = 0; midi_port_index < fParams.fSendMidiChannels; midi_port_index++) { fMidiCapturePortList[midi_port_index] = 0; } } if (fParams.fReturnMidiChannels > 0) { fMidiPlaybackPortList = new jack_port_id_t [fParams.fReturnMidiChannels]; assert(fMidiPlaybackPortList); for (int midi_port_index = 0; midi_port_index < fParams.fReturnMidiChannels; midi_port_index++) { fMidiPlaybackPortList[midi_port_index] = 0; } } // Register jack ports if (AllocPorts() != 0) { jack_error("Can't allocate ports."); return false; } // Init done, display parameters SessionParamsDisplay(&fParams); // Monitor #ifdef JACK_MONITOR string plot_name; // NetTimeMon plot_name = string(fParams.fName); plot_name += string("_slave"); plot_name += (fEngineControl->fSyncMode) ? string("_sync") : string("_async"); plot_name += string("_latency"); fNetTimeMon = new JackGnuPlotMonitor(128, 5, plot_name); string net_time_mon_fields[] = { string("sync decoded"), string("end of read"), string("start of write"), string("sync send"), string("end of write") }; string net_time_mon_options[] = { string("set xlabel \"audio cycles\""), string("set ylabel \"% of audio cycle\"") }; fNetTimeMon->SetPlotFile(net_time_mon_options, 2, net_time_mon_fields, 5); #endif // Driver parametering JackTimedDriver::SetBufferSize(fParams.fPeriodSize); JackTimedDriver::SetSampleRate(fParams.fSampleRate); JackDriver::NotifyBufferSize(fParams.fPeriodSize); JackDriver::NotifySampleRate(fParams.fSampleRate); // Transport engine parametering fEngineControl->fTransport.SetNetworkSync(fParams.fTransportSync); if (fAutoSave) { LoadConnections(0); } return true; } void JackNetDriver::FreeAll() { FreePorts(); delete[] fTxBuffer; delete[] fRxBuffer; delete fNetAudioCaptureBuffer; delete fNetAudioPlaybackBuffer; delete fNetMidiCaptureBuffer; delete fNetMidiPlaybackBuffer; delete[] fMidiCapturePortList; delete[] fMidiPlaybackPortList; fTxBuffer = NULL; fRxBuffer = NULL; fNetAudioCaptureBuffer = NULL; fNetAudioPlaybackBuffer = NULL; fNetMidiCaptureBuffer = NULL; fNetMidiPlaybackBuffer = NULL; fMidiCapturePortList = NULL; fMidiPlaybackPortList = NULL; #ifdef JACK_MONITOR delete fNetTimeMon; fNetTimeMon = NULL; #endif } void JackNetDriver::UpdateLatencies() { jack_latency_range_t input_range; jack_latency_range_t output_range; jack_latency_range_t monitor_range; for (int i = 0; i < fCaptureChannels; i++) { input_range.max = input_range.min = float(fParams.fNetworkLatency * fEngineControl->fBufferSize) / 2.f; fGraphManager->GetPort(fCapturePortList[i])->SetLatencyRange(JackCaptureLatency, &input_range); } for (int i = 0; i < fPlaybackChannels; i++) { output_range.max = output_range.min = float(fParams.fNetworkLatency * fEngineControl->fBufferSize) / 2.f; if (!fEngineControl->fSyncMode) { output_range.max = output_range.min += fEngineControl->fBufferSize; } fGraphManager->GetPort(fPlaybackPortList[i])->SetLatencyRange(JackPlaybackLatency, &output_range); if (fWithMonitorPorts) { monitor_range.min = monitor_range.max = 0; fGraphManager->GetPort(fMonitorPortList[i])->SetLatencyRange(JackCaptureLatency, &monitor_range); } } } //jack ports and buffers-------------------------------------------------------------- int JackNetDriver::AllocPorts() { jack_log("JackNetDriver::AllocPorts fBufferSize = %ld fSampleRate = %ld", fEngineControl->fBufferSize, fEngineControl->fSampleRate); /* fNetAudioCaptureBuffer fNetAudioPlaybackBuffer fSendAudioChannels fReturnAudioChannels fCapturePortList fPlaybackPortList fCaptureChannels ==> SLAVE ==> fPlaybackChannels "capture_" "playback_" */ JackPort* port; jack_port_id_t port_index; char name[REAL_JACK_PORT_NAME_SIZE+1]; char alias[REAL_JACK_PORT_NAME_SIZE+1]; int audio_port_index; int midi_port_index; //audio for (audio_port_index = 0; audio_port_index < fCaptureChannels; audio_port_index++) { snprintf(alias, sizeof(alias), "%s:%s:out%d", fAliasName, fCaptureDriverName, audio_port_index + 1); snprintf(name, sizeof(name), "%s:capture_%d", fClientControl.fName, audio_port_index + 1); if (fEngine->PortRegister(fClientControl.fRefNum, name, JACK_DEFAULT_AUDIO_TYPE, CaptureDriverFlags, fEngineControl->fBufferSize, &port_index) < 0) { jack_error("driver: cannot register port for %s", name); return -1; } port = fGraphManager->GetPort(port_index); port->SetAlias(alias); fCapturePortList[audio_port_index] = port_index; jack_log("JackNetDriver::AllocPorts() fCapturePortList[%d] audio_port_index = %ld fPortLatency = %ld", audio_port_index, port_index, port->GetLatency()); } for (audio_port_index = 0; audio_port_index < fPlaybackChannels; audio_port_index++) { snprintf(alias, sizeof(alias), "%s:%s:in%d", fAliasName, fPlaybackDriverName, audio_port_index + 1); snprintf(name, sizeof(name), "%s:playback_%d",fClientControl.fName, audio_port_index + 1); if (fEngine->PortRegister(fClientControl.fRefNum, name, JACK_DEFAULT_AUDIO_TYPE, PlaybackDriverFlags, fEngineControl->fBufferSize, &port_index) < 0) { jack_error("driver: cannot register port for %s", name); return -1; } port = fGraphManager->GetPort(port_index); port->SetAlias(alias); fPlaybackPortList[audio_port_index] = port_index; jack_log("JackNetDriver::AllocPorts() fPlaybackPortList[%d] audio_port_index = %ld fPortLatency = %ld", audio_port_index, port_index, port->GetLatency()); } //midi for (midi_port_index = 0; midi_port_index < fParams.fSendMidiChannels; midi_port_index++) { snprintf(alias, sizeof(alias), "%s:%s:out%d", fAliasName, fCaptureDriverName, midi_port_index + 1); snprintf(name, sizeof (name), "%s:midi_capture_%d", fClientControl.fName, midi_port_index + 1); if (fEngine->PortRegister(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, CaptureDriverFlags, fEngineControl->fBufferSize, &port_index) < 0) { jack_error("driver: cannot register port for %s", name); return -1; } port = fGraphManager->GetPort(port_index); fMidiCapturePortList[midi_port_index] = port_index; jack_log("JackNetDriver::AllocPorts() fMidiCapturePortList[%d] midi_port_index = %ld fPortLatency = %ld", midi_port_index, port_index, port->GetLatency()); } for (midi_port_index = 0; midi_port_index < fParams.fReturnMidiChannels; midi_port_index++) { snprintf(alias, sizeof(alias), "%s:%s:in%d", fAliasName, fPlaybackDriverName, midi_port_index + 1); snprintf(name, sizeof(name), "%s:midi_playback_%d", fClientControl.fName, midi_port_index + 1); if (fEngine->PortRegister(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, PlaybackDriverFlags, fEngineControl->fBufferSize, &port_index) < 0) { jack_error("driver: cannot register port for %s", name); return -1; } port = fGraphManager->GetPort(port_index); fMidiPlaybackPortList[midi_port_index] = port_index; jack_log("JackNetDriver::AllocPorts() fMidiPlaybackPortList[%d] midi_port_index = %ld fPortLatency = %ld", midi_port_index, port_index, port->GetLatency()); } UpdateLatencies(); return 0; } int JackNetDriver::FreePorts() { jack_log("JackNetDriver::FreePorts"); for (int audio_port_index = 0; audio_port_index < fCaptureChannels; audio_port_index++) { if (fCapturePortList[audio_port_index] > 0) { fEngine->PortUnRegister(fClientControl.fRefNum, fCapturePortList[audio_port_index]); fCapturePortList[audio_port_index] = 0; } } for (int audio_port_index = 0; audio_port_index < fPlaybackChannels; audio_port_index++) { if (fPlaybackPortList[audio_port_index] > 0) { fEngine->PortUnRegister(fClientControl.fRefNum, fPlaybackPortList[audio_port_index]); fPlaybackPortList[audio_port_index] = 0; } } for (int midi_port_index = 0; midi_port_index < fParams.fSendMidiChannels; midi_port_index++) { if (fMidiCapturePortList && fMidiCapturePortList[midi_port_index] > 0) { fGraphManager->ReleasePort(fClientControl.fRefNum, fMidiCapturePortList[midi_port_index]); fMidiCapturePortList[midi_port_index] = 0; } } for (int midi_port_index = 0; midi_port_index < fParams.fReturnMidiChannels; midi_port_index++) { if (fMidiPlaybackPortList && fMidiPlaybackPortList[midi_port_index] > 0) { fEngine->PortUnRegister(fClientControl.fRefNum, fMidiPlaybackPortList[midi_port_index]); fMidiPlaybackPortList[midi_port_index] = 0; } } return 0; } void JackNetDriver::SaveConnections(int alias) { JackDriver::SaveConnections(alias); const char** connections; if (fMidiCapturePortList) { for (int i = 0; i < fParams.fSendMidiChannels; ++i) { if (fMidiCapturePortList[i] && (connections = fGraphManager->GetConnections(fMidiCapturePortList[i])) != 0) { for (int j = 0; connections[j]; j++) { JackPort* port_id = fGraphManager->GetPort(fGraphManager->GetPort(connections[j])); fConnections.push_back(make_pair(port_id->GetType(), make_pair(fGraphManager->GetPort(fMidiCapturePortList[i])->GetName(), connections[j]))); jack_info("Save connection: %s %s", fGraphManager->GetPort(fMidiCapturePortList[i])->GetName(), connections[j]); } free(connections); } } } if (fMidiPlaybackPortList) { for (int i = 0; i < fParams.fReturnMidiChannels; ++i) { if (fMidiPlaybackPortList[i] && (connections = fGraphManager->GetConnections(fMidiPlaybackPortList[i])) != 0) { for (int j = 0; connections[j]; j++) { JackPort* port_id = fGraphManager->GetPort(fGraphManager->GetPort(connections[j])); fConnections.push_back(make_pair(port_id->GetType(), make_pair(connections[j], fGraphManager->GetPort(fMidiPlaybackPortList[i])->GetName()))); jack_info("Save connection: %s %s", connections[j], fGraphManager->GetPort(fMidiPlaybackPortList[i])->GetName()); } free(connections); } } } } JackMidiBuffer* JackNetDriver::GetMidiInputBuffer(int port_index) { return static_cast(fGraphManager->GetBuffer(fMidiCapturePortList[port_index], fEngineControl->fBufferSize)); } JackMidiBuffer* JackNetDriver::GetMidiOutputBuffer(int port_index) { return static_cast(fGraphManager->GetBuffer(fMidiPlaybackPortList[port_index], fEngineControl->fBufferSize)); } //transport--------------------------------------------------------------------------- void JackNetDriver::DecodeTransportData() { //is there a new timebase master on the net master ? // - release timebase master only if it's a non-conditional request // - no change or no request : don't do anything // - conditional request : don't change anything too, the master will know if this slave is actually the timebase master int refnum; bool conditional; if (fSendTransportData.fTimebaseMaster == TIMEBASEMASTER) { fEngineControl->fTransport.GetTimebaseMaster(refnum, conditional); if (refnum != -1) { fEngineControl->fTransport.ResetTimebase(refnum); } jack_info("The NetMaster is now the new timebase master."); } //is there a transport state change to handle ? if (fSendTransportData.fNewState &&(fSendTransportData.fState != fEngineControl->fTransport.GetState())) { switch (fSendTransportData.fState) { case JackTransportStopped : fEngineControl->fTransport.SetCommand(TransportCommandStop); jack_info("Master stops transport."); break; case JackTransportStarting : fEngineControl->fTransport.RequestNewPos(&fSendTransportData.fPosition); fEngineControl->fTransport.SetCommand(TransportCommandStart); jack_info("Master starts transport frame = %d", fSendTransportData.fPosition.frame); break; case JackTransportRolling : //fEngineControl->fTransport.SetCommand(TransportCommandStart); fEngineControl->fTransport.SetState(JackTransportRolling); jack_info("Master is rolling."); break; } } } void JackNetDriver::EncodeTransportData() { // is there a timebase master change ? int refnum; bool conditional; fEngineControl->fTransport.GetTimebaseMaster(refnum, conditional); if (refnum != fLastTimebaseMaster) { // timebase master has released its function if (refnum == -1) { fReturnTransportData.fTimebaseMaster = RELEASE_TIMEBASEMASTER; jack_info("Sending a timebase master release request."); } else { // there is a new timebase master fReturnTransportData.fTimebaseMaster = (conditional) ? CONDITIONAL_TIMEBASEMASTER : TIMEBASEMASTER; jack_info("Sending a %s timebase master request.", (conditional) ? "conditional" : "non-conditional"); } fLastTimebaseMaster = refnum; } else { fReturnTransportData.fTimebaseMaster = NO_CHANGE; } // update transport state and position fReturnTransportData.fState = fEngineControl->fTransport.Query(&fReturnTransportData.fPosition); // is it a new state (that the master need to know...) ? fReturnTransportData.fNewState = ((fReturnTransportData.fState == JackTransportNetStarting) && (fReturnTransportData.fState != fLastTransportState) && (fReturnTransportData.fState != fSendTransportData.fState)); if (fReturnTransportData.fNewState) { jack_info("Sending '%s'.", GetTransportState(fReturnTransportData.fState)); } fLastTransportState = fReturnTransportData.fState; } //driver processes-------------------------------------------------------------------- int JackNetDriver::Read() { // buffers for (int midi_port_index = 0; midi_port_index < fParams.fSendMidiChannels; midi_port_index++) { fNetMidiCaptureBuffer->SetBuffer(midi_port_index, GetMidiInputBuffer(midi_port_index)); } for (int audio_port_index = 0; audio_port_index < fParams.fSendAudioChannels; audio_port_index++) { #ifdef OPTIMIZED_PROTOCOL if (fGraphManager->GetConnectionsNum(fCapturePortList[audio_port_index]) > 0) { fNetAudioCaptureBuffer->SetBuffer(audio_port_index, GetInputBuffer(audio_port_index)); } else { fNetAudioCaptureBuffer->SetBuffer(audio_port_index, NULL); } #else fNetAudioCaptureBuffer->SetBuffer(audio_port_index, GetInputBuffer(audio_port_index)); #endif } #ifdef JACK_MONITOR fNetTimeMon->New(); #endif switch (SyncRecv()) { case SOCKET_ERROR: return SOCKET_ERROR; case SYNC_PACKET_ERROR: // since sync packet is incorrect, don't decode it and continue with data break; default: // decode sync int unused_frames; DecodeSyncPacket(unused_frames); break; } #ifdef JACK_MONITOR // For timing fRcvSyncUst = GetMicroSeconds(); #endif #ifdef JACK_MONITOR fNetTimeMon->Add(float(GetMicroSeconds() - fRcvSyncUst) / float(fEngineControl->fPeriodUsecs) * 100.f); #endif // audio, midi or sync if driver is late switch (DataRecv()) { case SOCKET_ERROR: return SOCKET_ERROR; case DATA_PACKET_ERROR: jack_time_t cur_time = GetMicroSeconds(); NotifyXRun(cur_time, float(cur_time - fBeginDateUst)); // Better this value than nothing... break; } // take the time at the beginning of the cycle JackDriver::CycleTakeBeginTime(); #ifdef JACK_MONITOR fNetTimeMon->Add(float(GetMicroSeconds() - fRcvSyncUst) / float(fEngineControl->fPeriodUsecs) * 100.f); #endif return 0; } int JackNetDriver::Write() { // buffers for (int midi_port_index = 0; midi_port_index < fParams.fReturnMidiChannels; midi_port_index++) { fNetMidiPlaybackBuffer->SetBuffer(midi_port_index, GetMidiOutputBuffer(midi_port_index)); } for (int audio_port_index = 0; audio_port_index < fPlaybackChannels; audio_port_index++) { #ifdef OPTIMIZED_PROTOCOL // Port is connected on other side... if (fNetAudioPlaybackBuffer->GetConnected(audio_port_index) && (fGraphManager->GetConnectionsNum(fPlaybackPortList[audio_port_index]) > 0)) { fNetAudioPlaybackBuffer->SetBuffer(audio_port_index, GetOutputBuffer(audio_port_index)); } else { fNetAudioPlaybackBuffer->SetBuffer(audio_port_index, NULL); } #else fNetAudioPlaybackBuffer->SetBuffer(audio_port_index, GetOutputBuffer(audio_port_index)); #endif } #ifdef JACK_MONITOR fNetTimeMon->AddLast(float(GetMicroSeconds() - fRcvSyncUst) / float(fEngineControl->fPeriodUsecs) * 100.f); #endif EncodeSyncPacket(); // send sync if (SyncSend() == SOCKET_ERROR) { return SOCKET_ERROR; } #ifdef JACK_MONITOR fNetTimeMon->Add(((float)(GetMicroSeconds() - fRcvSyncUst) / (float)fEngineControl->fPeriodUsecs) * 100.f); #endif // send data if (DataSend() == SOCKET_ERROR) { return SOCKET_ERROR; } #ifdef JACK_MONITOR fNetTimeMon->AddLast(((float)(GetMicroSeconds() - fRcvSyncUst) / (float)fEngineControl->fPeriodUsecs) * 100.f); #endif return 0; } //driver loader----------------------------------------------------------------------- #ifdef __cplusplus extern "C" { #endif SERVER_EXPORT jack_driver_desc_t* driver_get_descriptor() { jack_driver_desc_t * desc; jack_driver_desc_filler_t filler; jack_driver_param_value_t value; desc = jack_driver_descriptor_construct("net", JackDriverMaster, "netjack slave backend component", &filler); strcpy(value.str, DEFAULT_MULTICAST_IP); jack_driver_descriptor_add_parameter(desc, &filler, "multicast-ip", 'a', JackDriverParamString, &value, NULL, "Multicast address, or explicit IP of the master", NULL); value.i = DEFAULT_PORT; jack_driver_descriptor_add_parameter(desc, &filler, "udp-net-port", 'p', JackDriverParamInt, &value, NULL, "UDP port", NULL); value.i = DEFAULT_MTU; jack_driver_descriptor_add_parameter(desc, &filler, "mtu", 'M', JackDriverParamInt, &value, NULL, "MTU to the master", NULL); value.i = -1; jack_driver_descriptor_add_parameter(desc, &filler, "input-ports", 'C', JackDriverParamInt, &value, NULL, "Number of audio input ports", "Number of audio input ports. If -1, audio physical input from the master"); jack_driver_descriptor_add_parameter(desc, &filler, "output-ports", 'P', JackDriverParamInt, &value, NULL, "Number of audio output ports", "Number of audio output ports. If -1, audio physical output from the master"); value.i = -1; jack_driver_descriptor_add_parameter(desc, &filler, "midi-in-ports", 'i', JackDriverParamInt, &value, NULL, "Number of midi input ports", "Number of MIDI input ports. If -1, MIDI physical input from the master"); jack_driver_descriptor_add_parameter(desc, &filler, "midi-out-ports", 'o', JackDriverParamInt, &value, NULL, "Number of midi output ports", "Number of MIDI output ports. If -1, MIDI physical output from the master"); #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); value.i = false; jack_driver_descriptor_add_parameter(desc, &filler, "auto-save", 's', JackDriverParamBool, &value, NULL, "Save/restore connection state when restarting", NULL); /* Deactivated for now.. value.ui = 0U; jack_driver_descriptor_add_parameter(desc, &filler, "transport-sync", 't', JackDriverParamUInt, &value, NULL, "Sync transport with master's", NULL); */ value.ui = 5U; jack_driver_descriptor_add_parameter(desc, &filler, "latency", 'l', JackDriverParamUInt, &value, NULL, "Network latency", NULL); return desc; } SERVER_EXPORT Jack::JackDriverClientInterface* driver_initialize(Jack::JackLockedEngine* engine, Jack::JackSynchro* table, const JSList* params) { char multicast_ip[32]; char net_name[JACK_CLIENT_NAME_SIZE+1] = {0}; int udp_port; int mtu = DEFAULT_MTU; // Deactivated for now... uint transport_sync = 0; jack_nframes_t period_size = 1024; // to be used while waiting for master period_size jack_nframes_t sample_rate = 48000; // to be used while waiting for master sample_rate int audio_capture_ports = -1; int audio_playback_ports = -1; int midi_input_ports = -1; int midi_output_ports = -1; int celt_encoding = -1; int opus_encoding = -1; bool monitor = false; int network_latency = 5; const JSList* node; const jack_driver_param_t* param; bool auto_save = false; // Possibly use env variable for UDP port const char* default_udp_port = getenv("JACK_NETJACK_PORT"); udp_port = (default_udp_port) ? atoi(default_udp_port) : DEFAULT_PORT; // Possibly use env variable for multicast IP const char* default_multicast_ip = getenv("JACK_NETJACK_MULTICAST"); strcpy(multicast_ip, (default_multicast_ip) ? default_multicast_ip : DEFAULT_MULTICAST_IP); for (node = params; node; node = jack_slist_next(node)) { param = (const jack_driver_param_t*) node->data; switch (param->character) { case 'a' : assert(strlen(param->value.str) < 32); strcpy(multicast_ip, param->value.str); break; case 'p': udp_port = param->value.ui; break; case 'M': mtu = param->value.i; break; case 'C': audio_capture_ports = param->value.i; break; case 'P': audio_playback_ports = param->value.i; break; case 'i': midi_input_ports = param->value.i; break; case 'o': midi_output_ports = param->value.i; break; #if HAVE_CELT case 'c': 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; case 's': auto_save = true; break; /* Deactivated for now.. case 't' : transport_sync = param->value.ui; break; */ case 'l' : network_latency = param->value.ui; if (network_latency > NETWORK_MAX_LATENCY) { printf("Error : network latency is limited to %d\n", NETWORK_MAX_LATENCY); return NULL; } break; } } try { Jack::JackDriverClientInterface* driver = new Jack::JackWaitThreadedDriver( 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, opus_encoding, auto_save)); 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 { delete driver; return NULL; } } catch (...) { return NULL; } } #ifdef __cplusplus } #endif }