@babblevoice/projectrtp 2.5.26 → 2.5.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,7 +60,7 @@ If you wish to build outsode of a Docker image, there are npm target scripts for
60
60
 
61
61
  ```bash
62
62
  docker buildx prune
63
- docker buildx build --platform linux/amd64,linux/arm64 -t tinpotnick/projectrtp:2.5.23_beta4 . --push
63
+ docker buildx build --platform linux/amd64,linux/arm64 -t tinpotnick/projectrtp:2.5.27 . --push
64
64
  ```
65
65
 
66
66
  ## Example scripts
package/lib/server.js CHANGED
@@ -4,9 +4,10 @@ const net = require( "net" )
4
4
  const { v4: uuidv4 } = require( "uuid" )
5
5
 
6
6
  const EventEmitter = require( "events" )
7
-
8
7
  const message = require( "./message" )
9
8
 
9
+ const opentimeout = 5000
10
+
10
11
  /**
11
12
  * Node host address and port object
12
13
  * @typedef { object } nodehost
@@ -105,12 +106,36 @@ class channel {
105
106
  */
106
107
  openresolve
107
108
 
109
+ /**
110
+ * @private
111
+ * @type { function }
112
+ */
113
+ closeresolve
114
+
108
115
  /**
109
116
  * @private
110
117
  * @type { function }
111
118
  */
112
119
  openreject
113
120
 
121
+ /**
122
+ * @private
123
+ * @type { function }
124
+ */
125
+ closereject
126
+
127
+ /**
128
+ * @private
129
+ * @type { NodeJS.Timeout }
130
+ */
131
+ opentimer
132
+
133
+ /**
134
+ * @private
135
+ * @type { NodeJS.Timeout }
136
+ */
137
+ closetimer
138
+
114
139
  /**
115
140
  * Array of messages sent to and from the remote end.
116
141
  * @type { Array< any > }
@@ -206,14 +231,15 @@ class channel {
206
231
  }
207
232
 
208
233
  /**
209
- * Creates a new channel which connects to a remote rtp node.
234
+ * Connects to a RTP server then creates channel
210
235
  * @param { object } options
211
236
  * @param { channelcallback } cb
212
237
  * @param { function } openresolve
238
+ * @param { function } openreject
213
239
  * @returns { Promise< channel > } - resolves when the connection is connected
214
240
  * @internal
215
241
  */
216
- static _createforconnect( options, cb, openresolve /* openreject do something with me */ ) {
242
+ static _createforconnect( options, cb, openresolve, openreject ) {
217
243
 
218
244
  const newchannel = new channel( options.id )
219
245
  if( cb ) newchannel.em.on( "all", cb )
@@ -228,6 +254,9 @@ class channel {
228
254
  "type": nodeconnectiontype.connect
229
255
  }
230
256
 
257
+ newchannel.openresolve = openresolve
258
+ newchannel.openreject = openreject
259
+
231
260
  newchannel.connection.sock.setKeepAlive( true )
232
261
  newchannel.channels = [ newchannel ]
233
262
 
@@ -236,6 +265,15 @@ class channel {
236
265
  if ( connectresolve ) connectresolve( newchannel )
237
266
  } )
238
267
 
268
+ newchannel.opentimer = setTimeout( () => {
269
+ if( newchannel.openreject ) {
270
+ newchannel.openreject()
271
+ delete newchannel.openresolve
272
+ delete newchannel.openreject
273
+ delete newchannel.opentimer
274
+ }
275
+ }, opentimeout )
276
+
239
277
  const state = message.newstate()
240
278
  newchannel.connection.sock.on( "data", ( data ) => {
241
279
  message.parsemessage( state, data, ( /** @type { object } */ receivedmsg ) => {
@@ -248,8 +286,6 @@ class channel {
248
286
  if( receivedmsg && receivedmsg.status && receivedmsg.status.instance ) {
249
287
  newchannel.connection.instance = receivedmsg.status.instance
250
288
  }
251
-
252
- if ( openresolve ) openresolve( newchannel )
253
289
  } )
254
290
  } )
255
291
 
@@ -259,6 +295,16 @@ class channel {
259
295
  } )
260
296
 
261
297
  newchannel.connection.sock.on( "close", () => {
298
+ newchannel.channels.forEach( chnl => {
299
+ channels.delete( chnl.id )
300
+ if( chnl.openreject ) {
301
+ chnl.openreject()
302
+ clearTimeout( chnl.opentimer )
303
+ delete chnl.openresolve
304
+ delete chnl.openreject
305
+ delete chnl.opentimer
306
+ }
307
+ } )
262
308
  } )
263
309
  } )
264
310
  }
@@ -294,7 +340,17 @@ class channel {
294
340
  newchannel.channels = this.channels
295
341
  newchannel.channels.push( newchannel )
296
342
  newchannel.openresolve = resolve
343
+ newchannel.openreject = reject
297
344
  newchannel._write( options )
345
+
346
+ newchannel.opentimer = setTimeout( () => {
347
+ if( newchannel.openreject ) {
348
+ newchannel.openreject()
349
+ delete newchannel.openresolve
350
+ delete newchannel.openreject
351
+ delete newchannel.opentimer
352
+ }
353
+ }, opentimeout )
298
354
  } )
299
355
 
300
356
  return resolvepromise
@@ -314,12 +370,24 @@ class channel {
314
370
  }
315
371
 
316
372
  /**
317
- * @summary Close the channel
373
+ * Closes the channel. It might throw a timeout error if the channel is not closed within 5 seconds.
374
+ * @param { object } [ options ]
375
+ * @param { number } [ options.timeout ] - different timeout to wait
376
+ * @returns { Promise< object > }
318
377
  */
319
- close() {
378
+ close( options ) {
320
379
 
321
- /* any outstanding to remove where unmix was not called first? */
322
- //this._removebridge()
380
+ let timeout = opentimeout
381
+ if( options && options.timeout ) timeout = options.timeout
382
+
383
+ this.closetimer = setTimeout( () => {
384
+ if( this.closereject ) {
385
+ this.closereject()
386
+ delete this.closereject
387
+ delete this.closeresolve
388
+ delete this.closetimer
389
+ }
390
+ }, timeout )
323
391
 
324
392
  /* close us */
325
393
  this._write( {
@@ -327,11 +395,18 @@ class channel {
327
395
  "uuid": this.uuid
328
396
  } )
329
397
 
330
- if( this.openresolve ) {
331
- this.openresolve()
398
+ if( this.openreject ) {
399
+ this.openreject()
400
+ clearTimeout( this.opentimer )
332
401
  delete this.openresolve
333
402
  delete this.openreject
403
+ delete this.opentimer
334
404
  }
405
+
406
+ return new Promise( ( resolve, reject ) => {
407
+ this.closeresolve = resolve
408
+ this.closereject = reject
409
+ } )
335
410
  }
336
411
 
337
412
  /**
@@ -354,142 +429,6 @@ class channel {
354
429
  return true
355
430
  }
356
431
 
357
- /**
358
- * Add _bridge object to both channel
359
- * @param { object } other - other channel
360
- * @returns { void }
361
- */
362
- _createbridges( other ) {
363
-
364
- if( !this._bridge && !other._bridge ) {
365
- this._bridge = other._bridge = {
366
- "bridges": [],
367
- "main": this.connection.instance
368
- }
369
- }
370
-
371
- if( !this._bridge && other._bridge ) this._bridge = other._bridge
372
- if( this._bridge && !other._bridge ) other._bridge = this._bridge
373
-
374
- if( this._bridge !== other._bridge )
375
- throw new Error( "some complex mixing occured and this function needs finishing" )
376
- }
377
-
378
- /**
379
- * Any channels which have been mixed we create an
380
- * object common to both channels so we can keep track of channels which
381
- * require tidyin up. We try to maintain a star shaped network.
382
- * In the example diagram, the out mix method mixes between phone1 and this
383
- * and phone2 and other.
384
- * Markdown (mermaid)
385
- graph TD
386
- this -->|rtp - our created bridge to link the 2 remote channels| other
387
- other --> this
388
-
389
- phone1 --> this
390
- this --> phone1
391
-
392
- phone2 --> other
393
- other --> phone2
394
- * @private
395
- * @param { object } other - other channel
396
- * @returns { Promise< boolean > }
397
- */
398
- async _addbridge( other ) {
399
-
400
- let leftid = this.connection.instance
401
- const rightid = other.connection.instance
402
-
403
- this._createbridges( other )
404
-
405
- /* nothing more to do if we are on the same node */
406
- if( rightid === leftid ) return false
407
-
408
- /* It might have changed now we have linked them up */
409
- leftid = this._bridge.main
410
-
411
- let found = false
412
- for( const bridge of this._bridge.bridges ) {
413
- if( ( bridge.left.connection.instance === leftid || bridge.right.connection.instance === leftid ) &&
414
- ( bridge.left.connection.instance === rightid || bridge.right.connection.instance === rightid ) ) {
415
- found = true
416
- bridge.channels.add( this )
417
- bridge.channels.add( other )
418
-
419
- this.mix( bridge.left )
420
- other.mix( bridge.right )
421
- }
422
- }
423
-
424
- if( found ) return
425
-
426
- const bridge = {
427
- /* TODO: this will need reworking with to support reverse connection types */
428
- "left": await serverinterface.get().openchannel( { "nodeinstance": leftid } ),
429
- "right": await serverinterface.get().openchannel( { "nodeinstance": rightid } ),
430
- "channels": new Set( [ this, other ] )
431
- }
432
-
433
- const addressleft = bridge.left.local.privateaddress || bridge.left.local.address
434
- const addressright = bridge.right.local.privateaddress || bridge.right.local.address
435
- const g722 = 9
436
-
437
- bridge.left.remote( { "address": addressright, "port": bridge.right.local.port, "codec": g722 } )
438
- bridge.right.remote( { "address": addressleft, "port": bridge.left.local.port, "codec": g722 } )
439
-
440
- this._bridge.bridges.push( bridge )
441
-
442
- this._write( {
443
- "channel": "mix",
444
- "other": {
445
- "id": bridge.left.id,
446
- "uuid": bridge.left.uuid
447
- }
448
- } )
449
-
450
- other._write( {
451
- "channel": "mix",
452
- "other": {
453
- "id": bridge.right.id,
454
- "uuid": bridge.right.uuid
455
- }
456
- } )
457
-
458
- return true
459
- }
460
-
461
- /**
462
- * We check if we are a remote node (i.e.. not the centre of the star)
463
- * then we close down the link between the two.
464
- * @private
465
- * @returns { boolean }
466
- */
467
- _removebridge() {
468
-
469
- /* no remote bridges have been created */
470
- if( !this._bridge || !this._bridge.main ) return
471
-
472
- const usid = this.connection.instance
473
-
474
- /* The centre of star cannot remove from the network until last man standing */
475
- if( usid == this._bridge.main ) return
476
-
477
- this._bridge.bridges = this._bridge.bridges.filter( ( bridge ) => {
478
- bridge.channels.delete( this )
479
-
480
- if( 2 > bridge.channels.size ) {
481
-
482
- bridge.left._write( { "channel": "unmix" } )
483
- bridge.left._write( { "channel": "unmix" } )
484
-
485
- bridge.left.close()
486
- bridge.right.close()
487
- return false /* remove */
488
- }
489
- return true /* keep */
490
- } )
491
- }
492
-
493
432
  /**
494
433
  @summary Removes us from an existing mix
495
434
  @returns { boolean }
@@ -592,6 +531,7 @@ class channel {
592
531
  }
593
532
 
594
533
  /**
534
+ * We have received teh open confirm message so we should action it
595
535
  * @private
596
536
  * @param { object } msg
597
537
  * @returns { boolean } - is further processing required
@@ -605,33 +545,50 @@ class channel {
605
545
 
606
546
  if( !this.openresolve ) return true
607
547
  this.openresolve( this )
548
+ clearTimeout( this.opentimer )
608
549
  delete this.openresolve
609
550
  delete this.openreject
551
+ delete this.opentimer
610
552
 
611
553
  return true
612
554
  }
613
555
 
614
556
  /**
615
- *
557
+ * We have received a close message so we should action it
558
+ * @param { object } msg
616
559
  * @returns { void }
617
560
  * @private
618
561
  */
619
- _runclose() {
620
- if( undefined !== this.openresolve ) {
621
- this.openresolve()
562
+ _runclose( msg ) {
563
+ if( this.openreject ) {
564
+ this.openreject()
565
+ clearTimeout( this.opentimer )
622
566
  delete this.openresolve
623
567
  delete this.openreject
568
+ delete this.opentimer
569
+ }
570
+
571
+ if( this.closeresolve ) {
572
+ this.closeresolve( msg )
573
+ clearTimeout( this.closetimer )
574
+ delete this.closeresolve
575
+ delete this.closereject
576
+ delete this.closetimer
624
577
  }
625
578
 
579
+ this.#cleanupsharedchannels()
580
+ channels.delete( this.id )
581
+ }
582
+
583
+ #cleanupsharedchannels() {
626
584
  if( this.channels ) {
627
585
  // adjust with filter
628
586
  const index = this.channels.indexOf( this )
629
587
  if( -1 < index ) this.channels.splice( index, 1 )
630
- if( 0 === this.channels.length && this.connection.sock ) this.connection.sock.destroy()
588
+ if( 0 === this.channels.length && this.connection.sock ) {
589
+ this.connection.sock.destroy()
590
+ }
631
591
  }
632
-
633
-
634
- channels.delete( this.id )
635
592
  }
636
593
 
637
594
  /**
@@ -647,7 +604,7 @@ class channel {
647
604
  this.em.emit( "all", msg )
648
605
  this.em.emit( msg.action, msg )
649
606
 
650
- if( "close" == msg.action ) this._runclose()
607
+ if( "close" == msg.action ) this._runclose( msg )
651
608
  }
652
609
 
653
610
  /**
@@ -772,9 +729,9 @@ class rtpserver {
772
729
  request.channel = "open"
773
730
 
774
731
  const node = randomenode( options.nodeinstance )
775
-
732
+
776
733
  if ( 0 < nodes.size ) channel._createforlisten( request, node, callback, resolve, reject )
777
- else channel._createforconnect( request, callback, resolve )
734
+ else channel._createforconnect( request, callback, resolve, reject )
778
735
 
779
736
  } )
780
737
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babblevoice/projectrtp",
3
- "version": "2.5.26",
3
+ "version": "2.5.28",
4
4
  "description": "A scalable Node addon RTP server",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -53,7 +53,21 @@ Returns the next packet in order - does not modify our structure.
53
53
  rtppacket* rtpbuffer::peek( void ) {
54
54
  if( nullptr != this->peekedpopped ) return this->peekedpopped;
55
55
  uint16_t bufindex = this->outsn % this->buffercount;
56
- this->peekedpopped = this->orderedrtpdata.at( bufindex );
56
+ rtppacket *out = this->orderedrtpdata.at( bufindex );
57
+
58
+ if( out == nullptr ) {
59
+ this->outsn++;
60
+ return nullptr;
61
+ }
62
+
63
+ if( out->getsequencenumber() != this->outsn ) {
64
+ this->orderedrtpdata.at( this->outsn % this->buffercount ) = nullptr;
65
+ this->availablertpdata.push( out );
66
+ this->badsn++;
67
+ return nullptr;
68
+ }
69
+
70
+ this->peekedpopped = out;
57
71
  return this->peekedpopped;
58
72
  }
59
73
 
@@ -72,7 +86,7 @@ rtppacket* rtpbuffer::poppeeked( void ) {
72
86
  this->availablertpdata.push( out );
73
87
  }
74
88
 
75
- if( out->getsequencenumber() != oldsn ) return nullptr;
89
+ this->popped++;
76
90
  return out;
77
91
  }
78
92
 
@@ -81,25 +95,9 @@ Returns the next packet in order.
81
95
  */
82
96
  rtppacket* rtpbuffer::pop( void ) {
83
97
  rtppacket *out = this->peek();
84
- uint16_t oldsn = this->outsn;
85
- this->outsn++;
86
-
87
- this->peekedpopped = nullptr;
88
98
  if( nullptr == out ) return nullptr;
99
+ this->poppeeked();
89
100
 
90
- uint16_t bufindex = oldsn % this->buffercount;
91
- if( nullptr != this->orderedrtpdata.at( bufindex ) ) {
92
- this->orderedrtpdata.at( bufindex ) = nullptr;
93
- this->availablertpdata.push( out );
94
- }
95
-
96
- auto outsn = out->getsequencenumber();
97
- if( outsn != oldsn ) {
98
- this->badsn++;
99
- return nullptr;
100
- }
101
-
102
- this->popped++;
103
101
  return out;
104
102
  }
105
103
 
@@ -48,6 +48,7 @@ public:
48
48
  int waterlevel = BUFFERPACKETCAP /* the level we build up before allowing a read */ );
49
49
 
50
50
  rtppacket* peek( void );
51
+ rtppacket* peeked( void ) { return this->peekedpopped; }
51
52
  rtppacket* poppeeked( void );
52
53
  rtppacket* pop( void );
53
54
  void push( void );
@@ -19,7 +19,49 @@
19
19
  extern boost::asio::io_context workercontext;
20
20
  std::queue < unsigned short >availableports;
21
21
  std::atomic_bool availableportslock( false );
22
- uint32_t channelscreated = 0;
22
+
23
+ std::atomic< std::uint32_t > channelscreated{ 0 };
24
+
25
+ /**
26
+ * Get the next available port.
27
+ */
28
+ unsigned short getavailableport( void ) {
29
+ AQUIRESPINLOCK( availableportslock );
30
+ auto ourport = availableports.front();
31
+ availableports.pop();
32
+ RELEASESPINLOCK( availableportslock );
33
+
34
+ channelscreated.fetch_add( 1 );
35
+
36
+ return ourport;
37
+ }
38
+
39
+ /**
40
+ * Returns the number of ports we still have available.
41
+ * @returns { size_t }
42
+ */
43
+ auto getvailableportsize() {
44
+ AQUIRESPINLOCK( availableportslock );
45
+ auto availableportssize = availableports.size();
46
+ RELEASESPINLOCK( availableportslock );
47
+
48
+ return availableportssize;
49
+ }
50
+
51
+ /**
52
+ * Return a port number to the available list (to the back) and clear our value
53
+ * so we cannot return it twice.
54
+ */
55
+ void projectrtpchannel::returnavailableport( void ) {
56
+
57
+ if( 0 == this->port ) return;
58
+ AQUIRESPINLOCK( availableportslock );
59
+ availableports.push( this->port );
60
+ this->port = 0;
61
+ RELEASESPINLOCK( availableportslock );
62
+
63
+ channelscreated.fetch_sub( 1 );
64
+ }
23
65
 
24
66
  /* useful for random string generation */
25
67
  const char alphanumsecret[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
@@ -53,6 +95,7 @@ projectrtpchannel::projectrtpchannel( unsigned short port ):
53
95
  totalticktime( 0 ),
54
96
  totaltickcount( 0 ),
55
97
  tickswithnortpcount( 0 ),
98
+ hardtickswithnortpcount( 0 ),
56
99
  outpkwritecount( 0 ),
57
100
  outpkcount( 0 ),
58
101
  outpkskipcount( 0 ),
@@ -107,8 +150,6 @@ projectrtpchannel::projectrtpchannel( unsigned short port ):
107
150
  lastdtmfsn( 0 ),
108
151
  tickstarttime() {
109
152
 
110
- channelscreated++;
111
-
112
153
  gnutls_rnd( GNUTLS_RND_RANDOM, &this->ssrcout, 4 );
113
154
 
114
155
  char localicepwd[ 25 ];
@@ -241,6 +282,7 @@ void projectrtpchannel::handleremoteresolve (
241
282
 
242
283
  this->confirmedrtpsenderendpoint = *it;
243
284
  this->remoteconfirmed = true;
285
+ this->hardtickswithnortpcount = 0;
244
286
 
245
287
  /* allow us to re-auto correct */
246
288
  this->autoadjust = true;
@@ -264,29 +306,63 @@ uint32_t projectrtpchannel::requestopen( void ) {
264
306
  return this->ssrcout;
265
307
  }
266
308
 
309
+ /**
310
+ * handle errors on socket open
311
+ */
312
+ void projectrtpchannel::badsocketopen( const char *err ) {
313
+
314
+ fprintf( stderr, "Socket issue - refusing a new channel: %s\n", err );
315
+
316
+ if( this->rtpsocket.is_open() ) {
317
+ this->rtpsocket.close();
318
+ }
319
+
320
+ if( !this->rtcpsocket.is_open() ) {
321
+ this->rtcpsocket.close();
322
+ }
323
+
324
+ this->returnavailableport();
325
+
326
+ postdatabacktojsfromthread( shared_from_this(), "close", "error.nosocket" );
327
+ }
328
+
267
329
  /**
268
330
  * Must be called in the workercontext.
269
331
  */
270
332
  void projectrtpchannel::doopen( void ) {
271
333
 
334
+ boost::system::error_code ec;
335
+
272
336
  this->outcodec.reset();
273
337
  this->incodec.reset();
274
338
 
275
- boost::asio::socket_base::reuse_address reuseoption( true );
276
- this->rtpsocket.open( boost::asio::ip::udp::v4() );
277
- this->rtpsocket.set_option( reuseoption );
339
+ this->rtpsocket.open( boost::asio::ip::udp::v4(), ec );
340
+
341
+ if( ec ) {
342
+ this->badsocketopen( "failed to open rtp socket" );
343
+ return;
344
+ }
345
+
278
346
  this->rtpsocket.bind( boost::asio::ip::udp::endpoint(
279
- boost::asio::ip::udp::v4(), this->port ) );
347
+ boost::asio::ip::udp::v4(), this->port ), ec );
348
+
349
+ if( ec ) {
350
+ this->badsocketopen( "failed to bind rtp socket" );
351
+ return;
352
+ }
353
+
354
+ this->rtcpsocket.open( boost::asio::ip::udp::v4(), ec );
355
+
356
+ if( ec ) {
357
+ this->badsocketopen( "failed to open rtcp socket" );
358
+ return;
359
+ }
280
360
 
281
- this->rtcpsocket.open( boost::asio::ip::udp::v4() );
282
- this->rtcpsocket.set_option( reuseoption );
283
361
  this->rtcpsocket.bind( boost::asio::ip::udp::endpoint(
284
- boost::asio::ip::udp::v4(), this->port + 1 ) );
362
+ boost::asio::ip::udp::v4(), this->port + 1 ), ec );
285
363
 
286
- if( !this->rtpsocket.is_open() || !this->rtcpsocket.is_open() ) {
287
- fprintf( stderr, "No more sockets available - refusing a new channel\n" );
288
- this->requestclose( "error.nosocket" );
289
- this->doclose();
364
+ if( ec ) {
365
+ this->badsocketopen( "failed to bind rtcp socket" );
290
366
  return;
291
367
  }
292
368
 
@@ -333,11 +409,15 @@ void projectrtpchannel::requestecho( bool e ) {
333
409
  this->doecho = e;
334
410
  }
335
411
 
412
+ /**
413
+ * Worker thread to perform teh close. It will only run if the channel has
414
+ * been active (i.e. the sockets have been opened sucsessfully)
415
+ */
336
416
  void projectrtpchannel::doclose( void ) {
337
417
 
338
418
  if( !this->active ) return;
339
-
340
419
  this->active = false;
420
+
341
421
  this->tick.cancel();
342
422
 
343
423
  if( nullptr != this->player ) {
@@ -365,25 +445,30 @@ void projectrtpchannel::doclose( void ) {
365
445
  this->rtpsocket.close();
366
446
  this->rtcpsocket.close();
367
447
 
368
- AQUIRESPINLOCK( availableportslock );
369
- availableports.push( this->getport() );
370
- RELEASESPINLOCK( availableportslock );
371
-
372
- channelscreated--;
448
+ this->returnavailableport();
373
449
 
374
450
  postdatabacktojsfromthread( shared_from_this(), "close", this->closereason );
375
451
  }
376
452
 
377
453
  bool projectrtpchannel::checkidlerecv( void ) {
378
454
  if( this->recv && this->active ) {
379
- this->tickswithnortpcount++;
380
- if( this->tickswithnortpcount > ( 50 * 20 ) ) { /* 50 (@20mS ptime)/S = 20S */
455
+
456
+ this->hardtickswithnortpcount++;
457
+ if( this->remoteconfirmed ) {
458
+
459
+ this->tickswithnortpcount++;
460
+ if( this->tickswithnortpcount > ( 50 * 20 ) ) { /* 50 (@20mS ptime)/S = 20S */
461
+ this->closereason = "idle";
462
+ this->doclose();
463
+ return true;
464
+ }
465
+
466
+ } else if( this->hardtickswithnortpcount > ( 50 * 60 * 60 ) ) { /* 1hr hard timeout */
381
467
  this->closereason = "idle";
382
468
  this->doclose();
383
469
  return true;
384
470
  }
385
471
  }
386
-
387
472
  return false;
388
473
  }
389
474
 
@@ -600,20 +685,27 @@ void projectrtpchannel::removeoldrecorders( void ) {
600
685
 
601
686
  if( rec->requestfinish ) {
602
687
  rec->completed = true;
688
+ rec->maxsincestartpower = 0;
603
689
  postdatabacktojsfromthread( shared_from_this(), "record", rec->file, "finished.requested" );
604
690
  continue;
605
691
  }
606
692
 
607
- if( rec->finishbelowpower > 0 ) {
608
- if( rec->lastpowercalc < rec->finishbelowpower ) {
609
- rec->completed = true;
610
- postdatabacktojsfromthread( shared_from_this(), "record", rec->file, "finished.belowpower" );
611
- continue;
612
- }
693
+ if( rec->lastpowercalc > rec->maxsincestartpower ) {
694
+ rec->maxsincestartpower = rec->lastpowercalc;
695
+ }
696
+
697
+ if( rec->finishbelowpower > 0 &&
698
+ rec->maxsincestartpower > rec->finishbelowpower &&
699
+ rec->lastpowercalc < rec->finishbelowpower ) {
700
+ rec->completed = true;
701
+ rec->maxsincestartpower = 0;
702
+ postdatabacktojsfromthread( shared_from_this(), "record", rec->file, "finished.belowpower" );
703
+ continue;
613
704
  }
614
705
 
615
706
  if( 0 != rec->maxduration && diff.total_milliseconds() > rec->maxduration ) {
616
707
  rec->completed = true;
708
+ rec->maxsincestartpower = 0;
617
709
  postdatabacktojsfromthread( shared_from_this(), "record", rec->file, "finished.timeout" );
618
710
  continue;
619
711
  }
@@ -802,6 +894,7 @@ void projectrtpchannel::correctaddress( void ) {
802
894
  if( this->autoadjust ) {
803
895
  this->confirmedrtpsenderendpoint = this->rtpsenderendpoint;
804
896
  this->remoteconfirmed = true;
897
+ this->hardtickswithnortpcount = 0;
805
898
  this->autoadjust = false;
806
899
  }
807
900
  }
@@ -905,6 +998,7 @@ void projectrtpchannel::readsomertp( void ) {
905
998
  */
906
999
 
907
1000
  this->tickswithnortpcount = 0;
1001
+ this->hardtickswithnortpcount = 0;
908
1002
 
909
1003
  /* dynamic payload types */
910
1004
  auto pt = buf->getpayloadtype();
@@ -1168,15 +1262,18 @@ void projectrtpchannel::senddtmf( void ) {
1168
1262
  return;
1169
1263
  }
1170
1264
 
1265
+ const char volume = 15;
1266
+ const char endofevent = 0x80;
1267
+
1171
1268
  rtppacket *dst = this->gettempoutbuf();
1172
1269
  dst->setpayloadtype( this->rfc2833pt );
1173
- dst->setmarker();
1174
1270
  dst->setpayloadlength( 4 );
1175
1271
  uint8_t *pl = dst->getpayload();
1176
1272
  pl[ 0 ] = tosend;
1177
- pl[ 1 ] = 10; /* end of event & reserved & volume */
1178
- pl[ 2 ] = 0; /* event duration high */
1179
- pl[ 3 ] = 160; /* event duration */
1273
+ pl[ 1 ] = volume; /* end of event & reserved & volume */
1274
+ uint16_t *tmp = ( uint16_t * ) &pl[ 2 ]; /* event duration */
1275
+ *tmp = htons( 160 );
1276
+
1180
1277
  this->writepacket( dst );
1181
1278
 
1182
1279
  dst = this->gettempoutbuf();
@@ -1184,9 +1281,10 @@ void projectrtpchannel::senddtmf( void ) {
1184
1281
  dst->setpayloadlength( 4 );
1185
1282
  pl = dst->getpayload();
1186
1283
  pl[ 0 ] = tosend;
1187
- pl[ 1 ] = 10; /* end of event & reserved & volume */
1188
- pl[ 2 ] = 0; /* event duration high */
1189
- pl[ 3 ] = 160; /* event duration */
1284
+ pl[ 1 ] = volume; /* end of event & reserved & volume */
1285
+ tmp = ( uint16_t * ) &pl[ 2 ]; /* event duration */
1286
+ *tmp = htons( 320 );
1287
+
1190
1288
  this->writepacket( dst );
1191
1289
 
1192
1290
  dst = this->gettempoutbuf();
@@ -1194,9 +1292,44 @@ void projectrtpchannel::senddtmf( void ) {
1194
1292
  dst->setpayloadlength( 4 );
1195
1293
  pl = dst->getpayload();
1196
1294
  pl[ 0 ] = tosend;
1197
- pl[ 1 ] = 0x80 | 10; /* end of event & reserved & volume */
1198
- pl[ 2 ] = 0; /* event duration high */
1199
- pl[ 3 ] = 160; /* event duration */
1295
+ pl[ 1 ] = volume; /* end of event & reserved & volume */
1296
+ tmp = ( uint16_t * ) &pl[ 2 ]; /* event duration */
1297
+ *tmp = htons( 480 );
1298
+
1299
+ this->writepacket( dst );
1300
+
1301
+ dst = this->gettempoutbuf();
1302
+ dst->setpayloadtype( this->rfc2833pt );
1303
+ dst->setmarker();
1304
+ dst->setpayloadlength( 4 );
1305
+ pl = dst->getpayload();
1306
+ pl[ 0 ] = tosend;
1307
+ pl[ 1 ] = endofevent | volume; /* end of event & reserved & volume */
1308
+ tmp = ( uint16_t * ) &pl[ 2 ]; /* event duration */
1309
+ *tmp = htons( 640 );
1310
+
1311
+ this->writepacket( dst );
1312
+
1313
+ dst = this->gettempoutbuf();
1314
+ dst->setpayloadtype( this->rfc2833pt );
1315
+ dst->setpayloadlength( 4 );
1316
+ pl = dst->getpayload();
1317
+ pl[ 0 ] = tosend;
1318
+ pl[ 1 ] = endofevent | volume; /* end of event & reserved & volume */
1319
+ tmp = ( uint16_t * ) &pl[ 2 ]; /* event duration */
1320
+ *tmp = htons( 640 );
1321
+
1322
+ this->writepacket( dst );
1323
+
1324
+ dst = this->gettempoutbuf();
1325
+ dst->setpayloadtype( this->rfc2833pt );
1326
+ dst->setpayloadlength( 4 );
1327
+ pl = dst->getpayload();
1328
+ pl[ 0 ] = tosend;
1329
+ pl[ 1 ] = endofevent | volume; /* end of event & reserved & volume */
1330
+ tmp = ( uint16_t * ) &pl[ 2 ]; /* event duration */
1331
+ *tmp = htons( 640 );
1332
+
1200
1333
  this->writepacket( dst );
1201
1334
 
1202
1335
  this->lastdtmfsn = this->snout;
@@ -1928,10 +2061,6 @@ static napi_value channelcreate( napi_env env, napi_callback_info info ) {
1928
2061
  napi_value nremote;
1929
2062
 
1930
2063
  napi_value result;
1931
- AQUIRESPINLOCK( availableportslock );
1932
- auto ourport = availableports.front();
1933
- availableports.pop();
1934
- RELEASESPINLOCK( availableportslock );
1935
2064
 
1936
2065
  size_t bytescopied;
1937
2066
 
@@ -1949,7 +2078,7 @@ static napi_value channelcreate( napi_env env, napi_callback_info info ) {
1949
2078
  return NULL;
1950
2079
  }
1951
2080
 
1952
- projectrtpchannel::pointer p = projectrtpchannel::create( ourport );
2081
+ projectrtpchannel::pointer p = projectrtpchannel::create( getavailableport() );
1953
2082
 
1954
2083
  /* optional - remote */
1955
2084
  dtlssession::mode dtlsmode = dtlssession::none;
@@ -2196,7 +2325,7 @@ nodtls:
2196
2325
  if( napi_ok != napi_create_object( env, &nlocal ) ||
2197
2326
  napi_ok != napi_set_named_property( env, result, "local", nlocal ) ||
2198
2327
 
2199
- napi_ok != napi_create_int32( env, ourport, &nourport ) ||
2328
+ napi_ok != napi_create_int32( env, p->getport(), &nourport ) ||
2200
2329
  napi_ok != napi_set_named_property( env, nlocal, "port", nourport ) ||
2201
2330
 
2202
2331
  napi_ok != napi_create_uint32( env, ssrc, &nssrc ) ||
@@ -2227,12 +2356,8 @@ void getchannelstats( napi_env env, napi_value &result ) {
2227
2356
 
2228
2357
  napi_value av, chcount;
2229
2358
 
2230
- AQUIRESPINLOCK( availableportslock );
2231
- auto availableportssize = availableports.size();
2232
- RELEASESPINLOCK( availableportslock );
2233
-
2234
- if( napi_ok != napi_create_double( env, availableportssize, &av ) ) return;
2235
- if( napi_ok != napi_create_double( env, channelscreated, &chcount ) ) return;
2359
+ if( napi_ok != napi_create_double( env, getvailableportsize(), &av ) ) return;
2360
+ if( napi_ok != napi_create_double( env, channelscreated.load(), &chcount ) ) return;
2236
2361
 
2237
2362
  if( napi_ok != napi_set_named_property( env, channel, "available", av ) ) return;
2238
2363
  if( napi_ok != napi_set_named_property( env, channel, "current", chcount ) ) return;
@@ -133,6 +133,7 @@ public:
133
133
  std::atomic_uint64_t totalticktime;
134
134
  std::atomic_uint64_t totaltickcount;
135
135
  std::atomic_uint16_t tickswithnortpcount;
136
+ std::atomic_uint16_t hardtickswithnortpcount;
136
137
 
137
138
  std::atomic_uint64_t outpkwritecount;
138
139
  std::atomic_uint64_t outpkcount;
@@ -220,6 +221,9 @@ private:
220
221
  static bool recordercompleted( const channelrecorder::pointer& value );
221
222
  void dounmix( void );
222
223
 
224
+ void badsocketopen( const char *err );
225
+ void returnavailableport( void );
226
+
223
227
  std::atomic_bool mixerlock;
224
228
  projectchannelmuxptr mixer;
225
229
  std::atomic_bool mixing;
@@ -60,6 +60,8 @@ void projectchannelmux::mixall( void ) {
60
60
  src = chan->inbuff->peek();
61
61
  RELEASESPINLOCK( chan->rtpbufferlock );
62
62
 
63
+ if( nullptr == src ) break;
64
+
63
65
  AQUIRESPINLOCK( chan->rtpdtlslock );
64
66
  dtlssession::pointer currentdtlssession = chan->rtpdtls;
65
67
  RELEASESPINLOCK( chan->rtpdtlslock );
@@ -68,6 +70,11 @@ void projectchannelmux::mixall( void ) {
68
70
  if( !currentdtlssession->unprotect( src ) ) {
69
71
  chan->receivedpkskip++;
70
72
  src = nullptr;
73
+
74
+ AQUIRESPINLOCK( chan->rtpbufferlock );
75
+ chan->inbuff->poppeeked();
76
+ RELEASESPINLOCK( chan->rtpbufferlock );
77
+ break;
71
78
  }
72
79
  }
73
80
 
@@ -93,7 +100,12 @@ void projectchannelmux::mixall( void ) {
93
100
 
94
101
  /* Now we subtract this channel to send to this channel. */
95
102
  for( auto& chan: this->channels ) {
96
- if( !chan->send ) continue;
103
+ if( !chan->send ) {
104
+ AQUIRESPINLOCK( chan->rtpbufferlock );
105
+ chan->inbuff->poppeeked();
106
+ RELEASESPINLOCK( chan->rtpbufferlock );
107
+ continue;
108
+ }
97
109
 
98
110
  rtppacket *dst = chan->gettempoutbuf();
99
111
 
@@ -102,7 +114,7 @@ void projectchannelmux::mixall( void ) {
102
114
 
103
115
  if( chan->recv ) {
104
116
  AQUIRESPINLOCK( chan->rtpbufferlock );
105
- rtppacket *src = chan->inbuff->peek();
117
+ rtppacket *src = chan->inbuff->peeked();
106
118
  RELEASESPINLOCK( chan->rtpbufferlock );
107
119
  if( nullptr != src ) {
108
120
  this->subtracted -= chan->incodec;
@@ -136,6 +148,8 @@ void projectchannelmux::mix2( void ) {
136
148
  src = chan1->inbuff->pop();
137
149
  RELEASESPINLOCK( chan1->rtpbufferlock );
138
150
 
151
+ if( nullptr == src ) break;
152
+
139
153
  AQUIRESPINLOCK( chan1->rtpdtlslock );
140
154
  dtlssession::pointer currentdtlssession = chan1->rtpdtls;
141
155
  RELEASESPINLOCK( chan1->rtpdtlslock );
@@ -144,6 +158,7 @@ void projectchannelmux::mix2( void ) {
144
158
  if( !currentdtlssession->unprotect( src ) ) {
145
159
  chan1->receivedpkskip++;
146
160
  src = nullptr;
161
+ break;
147
162
  }
148
163
  }
149
164
 
@@ -157,6 +172,8 @@ void projectchannelmux::mix2( void ) {
157
172
  src = chan2->inbuff->pop();
158
173
  RELEASESPINLOCK( chan2->rtpbufferlock );
159
174
 
175
+ if( nullptr == src ) break;
176
+
160
177
  AQUIRESPINLOCK( chan2->rtpdtlslock );
161
178
  dtlssession::pointer currentdtlssession = chan2->rtpdtls;
162
179
  RELEASESPINLOCK( chan2->rtpdtlslock );
@@ -165,6 +182,7 @@ void projectchannelmux::mix2( void ) {
165
182
  if( !currentdtlssession->unprotect( src ) ) {
166
183
  chan2->receivedpkskip++;
167
184
  src = nullptr;
185
+ break;
168
186
  }
169
187
  }
170
188
 
@@ -12,6 +12,7 @@ channelrecorder::channelrecorder( std::string file ) :
12
12
  file( file ),
13
13
  poweraveragepackets( 50 ),
14
14
  startabovepower( 0 ),
15
+ maxsincestartpower( 0 ),
15
16
  finishbelowpower( 0 ),
16
17
  minduration( 0 ),
17
18
  maxduration( 0 ),
@@ -30,6 +30,7 @@ public:
30
30
  /* must have started for this to kick in */
31
31
  uint16_t startabovepower;
32
32
  /* must have started for this to kick in */
33
+ uint16_t maxsincestartpower;
33
34
  uint16_t finishbelowpower;
34
35
  /* used in conjunction with finishbelowpower */
35
36
  uint32_t minduration; /* mSeconds */
@@ -69,13 +69,14 @@ static const uint8_t alaw_to_ulaw_table[256] =
69
69
  };
70
70
 
71
71
  void gen711convertdata( void ) {
72
- for( int32_t i = 0; i != 65535; i++ ) {
72
+ int32_t i;
73
+ for( i = 0; i != 65536; i++ ) {
73
74
  int16_t l16val = i - 32768;
74
75
  _l16topcmu[ i ] = linear_to_ulaw( l16val );
75
76
  _l16topcma[ i ] = linear_to_alaw( l16val );
76
77
  }
77
78
 
78
- for( uint8_t i = 0; i != 255; i++ ) {
79
+ for( i = 0; i != 256; i++ ) {
79
80
  _pcmatol16[ i ] = alaw_to_linear( i );
80
81
  _pcmutol16[ i ] = ulaw_to_linear( i );
81
82
  }
@@ -149,7 +149,7 @@ describe( "dtmf", function() {
149
149
 
150
150
 
151
151
  it( "single channel and request rtp server to send 2833", function( done ) {
152
-
152
+ /* TODO - check tre fuller structure of an RTP event (duration, volume, etc) */
153
153
  /* create our RTP/UDP endpoint */
154
154
  const server = dgram.createSocket( "udp4" )
155
155
  let dtmfpkcount = 0
@@ -182,7 +182,7 @@ describe( "dtmf", function() {
182
182
 
183
183
  if( "close" === d.action ) {
184
184
  server.close()
185
- expect( dtmfpkcount ).to.equal( 2*3 )
185
+ expect( dtmfpkcount ).to.equal( 2*6 )
186
186
  done()
187
187
  }
188
188
  } )
@@ -263,7 +263,7 @@ describe( "dtmf", function() {
263
263
  clienta.close()
264
264
  clientb.close()
265
265
 
266
- expect( dtmfpkcount ).to.equal( 3*3 )
266
+ expect( dtmfpkcount ).to.equal( 3*6 )
267
267
  } )
268
268
 
269
269
  it( "2 channels mixing and request rtp server to send 2833 to one with dynamic payloadtype", async function() {
@@ -331,7 +331,7 @@ describe( "dtmf", function() {
331
331
  clienta.close()
332
332
  clientb.close()
333
333
 
334
- expect( dtmfpkcount ).to.equal( 3*3 )
334
+ expect( dtmfpkcount ).to.equal( 3*6 )
335
335
  } )
336
336
 
337
337
  it( "3 channels mixing and request rtp server to send 2833 to one", async function() {
@@ -414,7 +414,7 @@ describe( "dtmf", function() {
414
414
  clientb.close()
415
415
  clientc.close()
416
416
 
417
- expect( dtmfpkcount ).to.equal( 5*3 )
417
+ expect( dtmfpkcount ).to.equal( 5*6 )
418
418
 
419
419
  await finished
420
420
  } )
@@ -971,11 +971,11 @@ describe( "channel mix", function() {
971
971
  endpointb.close()
972
972
  endpointc.close()
973
973
 
974
- expect( endpointapkcountzero ).to.be.within( 60, 75 )
975
- expect( endpointbpkcountzero ).to.be.within( 60, 75 )
976
- expect( endpointcpkcountzero ).to.be.within( 60, 75 )
977
- expect( endpointapkcountnotzero ).to.be.within( 4, 12 )
978
- expect( endpointbpkcountnotzero ).to.be.within( 4, 12 )
974
+ expect( endpointapkcountzero ).to.be.within( 55, 75 )
975
+ expect( endpointbpkcountzero ).to.be.within( 55, 75 )
976
+ expect( endpointcpkcountzero ).to.be.within( 55, 75 )
977
+ expect( endpointapkcountnotzero ).to.be.within( 4, 18 )
978
+ expect( endpointbpkcountnotzero ).to.be.within( 4, 18 )
979
979
  expect( endpointcpkcountnotzero ).to.be.below( 2 )
980
980
 
981
981
  await finished
@@ -67,11 +67,13 @@ describe( "record", function() {
67
67
  this.timeout( 1500 )
68
68
  this.slow( 1200 )
69
69
 
70
+ let endclose = 0
70
71
  server.bind()
71
72
  server.on( "listening", async function() {
72
73
 
73
74
  channel = await prtp.projectrtp.openchannel( { "remote": { "address": "localhost", "port": server.address().port, "codec": 0 } }, function( d ) {
74
75
  if( "close" === d.action ) {
76
+ endclose = Date.now()
75
77
  server.close()
76
78
  }
77
79
  } )
@@ -89,6 +91,7 @@ describe( "record", function() {
89
91
  } )
90
92
 
91
93
  await new Promise( ( resolve ) => { setTimeout( () => resolve(), 1300 ) } )
94
+ const startclose = Date.now()
92
95
  channel.close()
93
96
  await new Promise( resolve => { server.on( "close", resolve ) } )
94
97
 
@@ -102,6 +105,7 @@ describe( "record", function() {
102
105
  expect( wavinfo.chunksize ).to.be.within( 28000, 33000 )
103
106
  expect( wavinfo.fmtchunksize ).to.equal( 16 )
104
107
  expect( wavinfo.subchunksize ).to.be.within( 28000, 33000 )
108
+ expect( endclose - startclose ).to.be.below( 100 )
105
109
 
106
110
  const ourfile = await fspromises.open( "/tmp/ourrecording.wav", "r" )
107
111
  const buffer = Buffer.alloc( 28204 )