JackNetAdapter.cpp 19.9 KB
Newer Older
sletz's avatar
sletz committed
1
/*
moret's avatar
moret committed
2
Copyright (C) 2008 Romain Moret at Grame
sletz's avatar
sletz committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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 "JackNetAdapter.h"
moret's avatar
moret committed
20
#include "JackException.h"
21
#include "JackServerGlobals.h"
22
#include "JackEngineControl.h"
23
#include "JackArgParser.h"
sletz's avatar
sletz committed
24
#include <assert.h>
25

26
27
namespace Jack
{
moret's avatar
moret committed
28
    JackNetAdapter::JackNetAdapter ( jack_client_t* jack_client, jack_nframes_t buffer_size, jack_nframes_t sample_rate, const JSList* params )
moret's avatar
moret committed
29
            : JackAudioAdapterInterface ( buffer_size, sample_rate ), JackNetSlaveInterface(), fThread ( this )
30
31
    {
        jack_log ( "JackNetAdapter::JackNetAdapter" );
sletz's avatar
sletz committed
32

moret's avatar
moret committed
33
        //global parametering
moret's avatar
moret committed
34
35
36
        //we can't call JackNetSlaveInterface constructor with some parameters before
        //because we don't have full parametering right now
        //parameters will be parsed from the param list, and then JackNetSlaveInterface will be filled with proper values
37
        strcpy ( fMulticastIP, DEFAULT_MULTICAST_IP );
moret's avatar
moret committed
38
        uint port = DEFAULT_PORT;
39
40
        GetHostName ( fParams.fName, JACK_CLIENT_NAME_SIZE );
        fSocket.GetName ( fParams.fSlaveNetName );
41
        fParams.fMtu = DEFAULT_MTU;
moret's avatar
moret committed
42
        fParams.fTransportSync = 0;
43
44
45
46
        fParams.fSendAudioChannels = 2;
        fParams.fReturnAudioChannels = 2;
        fParams.fSendMidiChannels = 0;
        fParams.fReturnMidiChannels = 0;
moret's avatar
moret committed
47
48
        fParams.fSampleRate = sample_rate;
        fParams.fPeriodSize = buffer_size;
moret's avatar
Cleanup    
moret committed
49
        fParams.fSlaveSyncMode = 1;
50
        fParams.fNetworkMode = 's';
moret's avatar
moret committed
51
        fJackClient = jack_client;
sletz's avatar
sletz committed
52

moret's avatar
moret committed
53
        //options parsing
54
55
56
57
58
59
60
        const JSList* node;
        const jack_driver_param_t* param;
        for ( node = params; node; node = jack_slist_next ( node ) )
        {
            param = ( const jack_driver_param_t* ) node->data;
            switch ( param->character )
            {
moret's avatar
moret committed
61
                case 'a' :
62
63
                    if (strlen (param->value.str) < 32)
                        strcpy(fMulticastIP, param->value.str);
moret's avatar
moret committed
64
                    else
65
                        jack_error("Can't use multicast address %s, using default %s", param->value.ui, DEFAULT_MULTICAST_IP);
moret's avatar
moret committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
                    break;
                case 'p' :
                    fSocket.SetPort ( param->value.ui );
                    break;
                case 'M' :
                    fParams.fMtu = param->value.i;
                    break;
                case 'C' :
                    fParams.fSendAudioChannels = param->value.i;
                    break;
                case 'P' :
                    fParams.fReturnAudioChannels = param->value.i;
                    break;
                case 'n' :
                    strncpy ( fParams.fName, param->value.str, JACK_CLIENT_NAME_SIZE );
                    break;
                case 't' :
moret's avatar
moret committed
83
                    //fParams.fTransportSync = param->value.ui;
moret's avatar
moret committed
84
85
86
87
88
89
90
91
92
93
94
                    break;
                case 'm' :
                    if ( strcmp ( param->value.str, "normal" ) == 0 )
                        fParams.fNetworkMode = 'n';
                    else if ( strcmp ( param->value.str, "slow" ) == 0 )
                        fParams.fNetworkMode = 's';
                    else if ( strcmp ( param->value.str, "fast" ) == 0 )
                        fParams.fNetworkMode = 'f';
                    else
                        jack_error ( "Unknown network mode, using 'normal' mode." );
                    break;
95
96
97
                case 'q':
                    fQuality = param->value.ui;
                    break;
98
             }
99
        }
moret's avatar
moret committed
100

moret's avatar
moret committed
101
        //set the socket parameters
moret's avatar
moret committed
102
103
104
        fSocket.SetPort ( port );
        fSocket.SetAddress ( fMulticastIP, port );

moret's avatar
moret committed
105
        //set the audio adapter interface channel values
moret's avatar
moret committed
106
107
108
        SetInputs ( fParams.fSendAudioChannels );
        SetOutputs ( fParams.fReturnAudioChannels );

moret's avatar
moret committed
109
        //soft buffers will be allocated later (once network initialization done)
110
111
112
        fSoftCaptureBuffer = NULL;
        fSoftPlaybackBuffer = NULL;
    }
sletz's avatar
sletz committed
113

114
115
    JackNetAdapter::~JackNetAdapter()
    {
116
        jack_log ( "JackNetAdapter::~JackNetAdapter" );
moret's avatar
moret committed
117

moret's avatar
moret committed
118
        int port_index;
moret's avatar
moret committed
119
120
        if ( fSoftCaptureBuffer )
        {
121
122
123
124
            for ( port_index = 0; port_index < fCaptureChannels; port_index++ )
                delete[] fSoftCaptureBuffer[port_index];
            delete[] fSoftCaptureBuffer;
        }
moret's avatar
moret committed
125
126
        if ( fSoftPlaybackBuffer )
        {
127
128
129
130
            for ( port_index = 0; port_index < fPlaybackChannels; port_index++ )
                delete[] fSoftPlaybackBuffer[port_index];
            delete[] fSoftPlaybackBuffer;
        }
moret's avatar
moret committed
131
    }
sletz's avatar
sletz committed
132

moret's avatar
moret committed
133
//open/close--------------------------------------------------------------------------
134
135
    int JackNetAdapter::Open()
    {
moret's avatar
moret committed
136
        jack_log ( "JackNetAdapter::Open" );
moret's avatar
moret committed
137

138
        jack_info ( "NetAdapter started in %s mode %s Master's transport sync.",
moret's avatar
moret committed
139
                    ( fParams.fSlaveSyncMode ) ? "sync" : "async", ( fParams.fTransportSync ) ? "with" : "without" );
140

moret's avatar
moret committed
141
142
143
        if ( fThread.StartSync() < 0 )
        {
            jack_error ( "Cannot start netadapter thread" );
144
145
            return -1;
        }
moret's avatar
moret committed
146

147
        return 0;
moret's avatar
moret committed
148
    }
149
150
151

    int JackNetAdapter::Close()
    {
moret's avatar
moret committed
152
153
154
155
156
        jack_log ( "JackNetAdapter::Close" );

        switch ( fThread.GetStatus() )
        {
                // Kill the thread in Init phase
157
158
            case JackThread::kStarting:
            case JackThread::kIniting:
moret's avatar
moret committed
159
160
161
                if ( fThread.Kill() < 0 )
                {
                    jack_error ( "Cannot kill thread" );
162
163
164
                    return -1;
                }
                break;
moret's avatar
moret committed
165
                // Stop when the thread cycle is finished
166
                
167
            case JackThread::kRunning:
moret's avatar
moret committed
168
169
170
                if ( fThread.Stop() < 0 )
                {
                    jack_error ( "Cannot stop thread" );
171
172
173
                    return -1;
                }
                break;
174
                
175
176
177
            default:
                break;
        }
178
179
180
181
182
183
        fSocket.Close();
        return 0;
    }

    int JackNetAdapter::SetBufferSize ( jack_nframes_t buffer_size )
    {
184
        JackAudioAdapterInterface::SetHostBufferSize ( buffer_size );
185
186
187
        return 0;
    }

moret's avatar
moret committed
188
//thread------------------------------------------------------------------------------
189
190
    bool JackNetAdapter::Init()
    {
moret's avatar
moret committed
191
        jack_log ( "JackNetAdapter::Init" );
192

moret's avatar
moret committed
193
194
195
196
197
198
199
200
201
202
203
204
        int port_index;

        //init network connection
        if ( !JackNetSlaveInterface::Init() )
            return false;

        //then set global parameters
        SetParams();

        //set buffers
        fSoftCaptureBuffer = new sample_t*[fCaptureChannels];
        for ( port_index = 0; port_index < fCaptureChannels; port_index++ )
moret's avatar
moret committed
205
        {
moret's avatar
moret committed
206
207
            fSoftCaptureBuffer[port_index] = new sample_t[fParams.fPeriodSize];
            fNetAudioCaptureBuffer->SetBuffer ( port_index, fSoftCaptureBuffer[port_index] );
moret's avatar
moret committed
208
        }
moret's avatar
moret committed
209
210
211
212
213
214
215
        fSoftPlaybackBuffer = new sample_t*[fPlaybackChannels];
        for ( port_index = 0; port_index < fCaptureChannels; port_index++ )
        {
            fSoftPlaybackBuffer[port_index] = new sample_t[fParams.fPeriodSize];
            fNetAudioPlaybackBuffer->SetBuffer ( port_index, fSoftPlaybackBuffer[port_index] );
        }

216
        //set audio adapter parameters
217
218
        SetAdaptedBufferSize ( fParams.fPeriodSize );
        SetAdaptedSampleRate ( fParams.fSampleRate );
219
        
220
221
222
223
224
        // Will do "something" on OSX only...
        fThread.SetParams(JackServerGlobals::fInstance->GetEngineControl()->fPeriod, 
                        JackServerGlobals::fInstance->GetEngineControl()->fComputation, 
                        JackServerGlobals::fInstance->GetEngineControl()->fConstraint);
        
225
        if (fThread.AcquireRealTime ( JackServerGlobals::fInstance->GetEngineControl()->fClientPriority ) < 0) {
226
227
228
229
230
            jack_error("AcquireRealTime error");
        } else {
            set_threaded_log_function();
        }
  
moret's avatar
moret committed
231
232
        //init done, display parameters
        SessionParamsDisplay ( &fParams );
moret's avatar
moret committed
233
        return true;
234
235
    }

moret's avatar
moret committed
236
    bool JackNetAdapter::Execute()
moret's avatar
moret committed
237
238
239
240
241
242
243
244
245
246
247
248
249
250
    {
        try
        {
            // Keep running even in case of error
            while ( fThread.GetStatus() == JackThread::kRunning )
                if ( Process() == SOCKET_ERROR )
                    return false;
            return false;
        }
        catch ( JackNetException& e )
        {
            e.PrintMessage();
            jack_log ( "NetAdapter is restarted." );
            fThread.DropRealTime();
moret's avatar
moret committed
251
            fThread.SetStatus ( JackThread::kIniting );
moret's avatar
moret committed
252
253
254
255
256
257
258
259
260
261
            if ( Init() )
            {
                fThread.SetStatus ( JackThread::kRunning );
                return true;
            }
            else
                return false;
        }
    }

moret's avatar
moret committed
262
263
264
265
266
267
268
269
270
271
272
273
274
275
//transport---------------------------------------------------------------------------
    int JackNetAdapter::DecodeTransportData()
    {
        //TODO : we need here to get the actual timebase master to eventually release it from its duty (see JackNetDriver)

        //is there a new transport state ?
        if ( fSendTransportData.fNewState && ( fSendTransportData.fState != jack_transport_query ( fJackClient, NULL ) ) )
        {
            switch ( fSendTransportData.fState )
            {
                case JackTransportStopped :
                    jack_transport_stop ( fJackClient );
                    jack_info ( "NetMaster : transport stops." );
                    break;
276
                    
moret's avatar
moret committed
277
278
279
280
281
                case JackTransportStarting :
                    jack_transport_reposition ( fJackClient, &fSendTransportData.fPosition );
                    jack_transport_start ( fJackClient );
                    jack_info ( "NetMaster : transport starts." );
                    break;
282
                    
moret's avatar
moret committed
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
                case JackTransportRolling :
                    //TODO , we need to :
                    // - find a way to call TransportEngine->SetNetworkSync()
                    // - turn the transport state to JackTransportRolling
                    jack_info ( "NetMaster : transport rolls." );
                    break;
            }
        }

        return 0;
    }

    int JackNetAdapter::EncodeTransportData()
    {
        //is there a timebase master change ?
        int refnum = -1;
        bool conditional = 0;
        //TODO : get the actual timebase master
        if ( refnum != fLastTimebaseMaster )
        {
            //timebase master has released its function
            if ( refnum == -1 )
            {
                fReturnTransportData.fTimebaseMaster = RELEASE_TIMEBASEMASTER;
                jack_info ( "Sending a timebase master release request." );
            }
            //there is a new timebase master
            else
            {
                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 = jack_transport_query ( fJackClient, &fReturnTransportData.fPosition );

        //is it a new state (that the master need to know...) ?
        fReturnTransportData.fNewState = ( ( fReturnTransportData.fState != fLastTransportState ) &&
                                            ( fReturnTransportData.fState != fSendTransportData.fState ) );
        if ( fReturnTransportData.fNewState )
            jack_info ( "Sending transport state '%s'.", GetTransportState ( fReturnTransportData.fState ) );
        fLastTransportState = fReturnTransportData.fState;

        return 0;
    }

//read/write operations---------------------------------------------------------------
moret's avatar
moret committed
334
    int JackNetAdapter::Read()
335
    {
moret's avatar
moret committed
336
337
        //don't return -1 in case of sync recv failure
        //we need the process to continue for network error detection
moret's avatar
moret committed
338
        if ( SyncRecv() == SOCKET_ERROR )
moret's avatar
moret committed
339
            return 0;
moret's avatar
moret committed
340

moret's avatar
moret committed
341
342
343
        if ( DecodeSyncPacket() < 0 )
            return 0;

moret's avatar
moret committed
344
345
346
347
348
        return DataRecv();
    }

    int JackNetAdapter::Write()
    {
moret's avatar
moret committed
349
350
351
        if ( EncodeSyncPacket() < 0 )
            return 0;

moret's avatar
moret committed
352
        if ( SyncSend() == SOCKET_ERROR )
moret's avatar
moret committed
353
            return SOCKET_ERROR;
moret's avatar
moret committed
354

moret's avatar
moret committed
355
356
        return DataSend();
    }
moret's avatar
moret committed
357

moret's avatar
moret committed
358
//process-----------------------------------------------------------------------------
moret's avatar
moret committed
359
360
361
362
    int JackNetAdapter::Process()
    {
        bool failure = false;
        int port_index;
moret's avatar
moret committed
363

moret's avatar
moret committed
364
        //read data from the network
365
366
        //in case of fatal network error, stop the process
        if ( Read() == SOCKET_ERROR )
moret's avatar
moret committed
367
            return SOCKET_ERROR;
moret's avatar
moret committed
368

369
370
371
        //get the resample factor,
        jack_nframes_t time1, time2;
        ResampleFactor ( time1, time2 );
moret's avatar
moret committed
372

373
374
375
376
        //resample input data,
        for ( port_index = 0; port_index < fCaptureChannels; port_index++ )
        {
            fCaptureRingBuffer[port_index]->SetRatio ( time1, time2 );
377
            if ( fCaptureRingBuffer[port_index]->WriteResample ( fSoftCaptureBuffer[port_index], fAdaptedBufferSize ) < fAdaptedBufferSize )
378
379
                failure = true;
        }
380
        
381
382
383
384
        //and output data,
        for ( port_index = 0; port_index < fPlaybackChannels; port_index++ )
        {
            fPlaybackRingBuffer[port_index]->SetRatio ( time2, time1 );
385
            if ( fPlaybackRingBuffer[port_index]->ReadResample ( fSoftPlaybackBuffer[port_index], fAdaptedBufferSize ) < fAdaptedBufferSize )
386
                failure = true;
moret's avatar
moret committed
387
        }
moret's avatar
moret committed
388

moret's avatar
moret committed
389
        //then write data to network
390
        //in case of failure, stop process
moret's avatar
moret committed
391
        if ( Write() == SOCKET_ERROR )
moret's avatar
moret committed
392
            return SOCKET_ERROR;
moret's avatar
moret committed
393

moret's avatar
moret committed
394
        //if there was any ringbuffer failure during resampling, reset
moret's avatar
moret committed
395
396
397
398
399
400
        if ( failure )
        {
            jack_error ( "JackNetAdapter::Execute ringbuffer failure...reset." );
            ResetRingBuffers();
        }

401
        return 0;
402
    }
403
} // namespace Jack
sletz's avatar
sletz committed
404

moret's avatar
moret committed
405
//loader------------------------------------------------------------------------------
sletz's avatar
sletz committed
406
407
408
409
410
411
#ifdef __cplusplus
extern "C"
{
#endif

#include "driver_interface.h"
412
413
#include "JackAudioAdapter.h"

moret's avatar
moret committed
414
    using namespace Jack;
sletz's avatar
sletz committed
415

416
    SERVER_EXPORT jack_driver_desc_t* jack_get_descriptor()
sletz's avatar
sletz committed
417
    {
418
        jack_driver_desc_t* desc = ( jack_driver_desc_t* ) calloc ( 1, sizeof ( jack_driver_desc_t ) );
419
420
421
422
        
        strcpy(desc->name, "netadapter");                              // size MUST be less then JACK_DRIVER_NAME_MAX + 1
        strcpy(desc->desc, "netjack net <==> audio backend adapter");  // size MUST be less then JACK_DRIVER_PARAM_DESC + 1
       
423
        desc->nparams = 9;
424
425
426
427
428
429
430
431
432
433
434
435
436
437
        desc->params = ( jack_driver_param_desc_t* ) calloc ( desc->nparams, sizeof ( jack_driver_param_desc_t ) );

        int i = 0;
        strcpy ( desc->params[i].name, "multicast_ip" );
        desc->params[i].character = 'a';
        desc->params[i].type = JackDriverParamString;
        strcpy ( desc->params[i].value.str, DEFAULT_MULTICAST_IP );
        strcpy ( desc->params[i].short_desc, "Multicast Address" );
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );

        i++;
        strcpy ( desc->params[i].name, "udp_net_port" );
        desc->params[i].character = 'p';
        desc->params[i].type = JackDriverParamInt;
438
        desc->params[i].value.i = DEFAULT_PORT;
439
440
441
442
443
444
445
        strcpy ( desc->params[i].short_desc, "UDP port" );
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );

        i++;
        strcpy ( desc->params[i].name, "mtu" );
        desc->params[i].character = 'M';
        desc->params[i].type = JackDriverParamInt;
446
        desc->params[i].value.i = DEFAULT_MTU;
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
        strcpy ( desc->params[i].short_desc, "MTU to the master" );
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );

        i++;
        strcpy ( desc->params[i].name, "input_ports" );
        desc->params[i].character = 'C';
        desc->params[i].type = JackDriverParamInt;
        desc->params[i].value.i = 2;
        strcpy ( desc->params[i].short_desc, "Number of audio input ports" );
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );

        i++;
        strcpy ( desc->params[i].name, "output_ports" );
        desc->params[i].character = 'P';
        desc->params[i].type = JackDriverParamInt;
        desc->params[i].value.i = 2;
        strcpy ( desc->params[i].short_desc, "Number of audio output ports" );
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );

        i++;
        strcpy ( desc->params[i].name, "client_name" );
        desc->params[i].character = 'n';
        desc->params[i].type = JackDriverParamString;
        strcpy ( desc->params[i].value.str, "'hostname'" );
        strcpy ( desc->params[i].short_desc, "Name of the jack client" );
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );

        i++;
        strcpy ( desc->params[i].name, "transport_sync" );
        desc->params[i].character  = 't';
        desc->params[i].type = JackDriverParamUInt;
        desc->params[i].value.ui = 1U;
        strcpy ( desc->params[i].short_desc, "Sync transport with master's" );
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );
moret's avatar
moret committed
481

482
        i++;
moret's avatar
moret committed
483
484
        strcpy ( desc->params[i].name, "mode" );
        desc->params[i].character  = 'm';
485
        desc->params[i].type = JackDriverParamString;
486
        strcpy ( desc->params[i].value.str, "slow" );
moret's avatar
moret committed
487
        strcpy ( desc->params[i].short_desc, "Slow, Normal or Fast mode." );
488
        strcpy ( desc->params[i].long_desc, desc->params[i].short_desc );
489
490
491
492
493
494
495
496
        
        i++;
        strcpy(desc->params[i].name, "quality");
        desc->params[i].character = 'q';
        desc->params[i].type = JackDriverParamInt;
        desc->params[i].value.ui = 0;
        strcpy(desc->params[i].short_desc, "Resample algorithm quality (0 - 4)");
        strcpy(desc->params[i].long_desc, desc->params[i].short_desc);
moret's avatar
moret committed
497

sletz's avatar
sletz committed
498
499
500
        return desc;
    }

501
    SERVER_EXPORT int jack_internal_initialize ( jack_client_t* jack_client, const JSList* params )
sletz's avatar
sletz committed
502
    {
moret's avatar
moret committed
503
        jack_log ( "Loading netadapter" );
sletz's avatar
sletz committed
504
505

        Jack::JackAudioAdapter* adapter;
moret's avatar
moret committed
506
507
        jack_nframes_t buffer_size = jack_get_buffer_size ( jack_client );
        jack_nframes_t sample_rate = jack_get_sample_rate ( jack_client );
508
509
510
511
512
        
        try {
        
            adapter = new Jack::JackAudioAdapter ( jack_client, new Jack::JackNetAdapter ( jack_client, buffer_size, sample_rate, params ) );
            assert ( adapter );
sletz's avatar
sletz committed
513

514
515
516
517
518
519
520
521
522
            if ( adapter->Open() == 0 )
                return 0;
            else
            {
                delete adapter;
                return 1;
            }
            
        } catch (...) {
sletz's avatar
sletz committed
523
524
525
526
            return 1;
        }
    }

527
    SERVER_EXPORT int jack_initialize ( jack_client_t* jack_client, const char* load_init )
sletz's avatar
sletz committed
528
529
    {
        JSList* params = NULL;
530
531
532
533
534
535
536
537
538
539
540
541
        bool parse_params = true;
        int res = 1;
        jack_driver_desc_t* desc = jack_get_descriptor();

        Jack::JackArgParser parser ( load_init );
        if ( parser.GetArgc() > 0 )
            parse_params = parser.ParseParams ( desc, &params );

        if (parse_params) {
            res = jack_internal_initialize ( jack_client, params );
            parser.FreeParams ( params );
        }
sletz's avatar
sletz committed
542
        return res;
sletz's avatar
sletz committed
543
544
    }

545
    SERVER_EXPORT void jack_finish ( void* arg )
sletz's avatar
sletz committed
546
    {
moret's avatar
moret committed
547
        Jack::JackAudioAdapter* adapter = static_cast<Jack::JackAudioAdapter*> ( arg );
sletz's avatar
sletz committed
548

549
        if (adapter) {
moret's avatar
moret committed
550
            jack_log ( "Unloading netadapter" );
sletz's avatar
sletz committed
551
552
553
554
555
556
557
558
            adapter->Close();
            delete adapter;
        }
    }

#ifdef __cplusplus
}
#endif