@babblevoice/projectrtp 2.5.22 → 2.5.26
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 +1 -1
- package/lib/server.js +9 -1
- package/package.json +1 -1
- package/src/projectrtpchannel.cpp +19 -5
- package/src/projectrtpchannelmux.cpp +2 -2
- package/src/projectrtpcodecx.cpp +15 -5
- package/src/projectrtpsoundfile.cpp +62 -52
- package/src/projectrtpsoundfile.h +1 -0
- package/test/interface/projectrtpchannel.js +4 -9
- package/test/interface/projectrtpdtmf.js +145 -4
- package/test/interface/projectrtpmix.js +77 -6
- package/test/interface/projectrtpsound.js +145 -91
- package/test/interface/transcode.js +2 -2
- package/test/util/rtp.js +17 -0
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.
|
|
63
|
+
docker buildx build --platform linux/amd64,linux/arm64 -t tinpotnick/projectrtp:2.5.23_beta4 . --push
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
## Example scripts
|
package/lib/server.js
CHANGED
|
@@ -277,7 +277,15 @@ class channel {
|
|
|
277
277
|
|
|
278
278
|
options.channel = "open"
|
|
279
279
|
|
|
280
|
-
const resolvepromise = new Promise( ( resolve ) => {
|
|
280
|
+
const resolvepromise = new Promise( ( resolve, reject ) => {
|
|
281
|
+
|
|
282
|
+
if ( 0 < nodes.size ) {
|
|
283
|
+
const node = randomenode( options.nodeinstance )
|
|
284
|
+
const request = JSON.parse( JSON.stringify( options ) )
|
|
285
|
+
request.channel = "open"
|
|
286
|
+
channel._createforlisten( request, node, cb, resolve, reject )
|
|
287
|
+
return
|
|
288
|
+
}
|
|
281
289
|
|
|
282
290
|
const newchannel = new channel()
|
|
283
291
|
if( cb ) newchannel.em.on( "all", cb )
|
package/package.json
CHANGED
|
@@ -655,7 +655,7 @@ bool projectrtpchannel::checkfordtmf( rtppacket *src ) {
|
|
|
655
655
|
uint16_t sn = src->getsequencenumber();
|
|
656
656
|
|
|
657
657
|
if( 0 != this->rfc2833pt &&
|
|
658
|
-
src->getpayloadtype() ==
|
|
658
|
+
src->getpayloadtype() == RFC2833PAYLOADTYPE ) {
|
|
659
659
|
|
|
660
660
|
if( src->getpayloadlength() >= 4 ) {
|
|
661
661
|
/* We have to look for DTMF events handling issues like missing events - such as the marker or end bit */
|
|
@@ -811,7 +811,8 @@ void projectrtpchannel::correctaddress( void ) {
|
|
|
811
811
|
*/
|
|
812
812
|
void projectrtpchannel::correctssrc( uint32_t ssrc ) {
|
|
813
813
|
if( !this->receivedrtp ) {
|
|
814
|
-
|
|
814
|
+
/* allow settling */
|
|
815
|
+
if( this->inbuff->getpushed() > 5 )this->receivedrtp = true;
|
|
815
816
|
this->ssrcin = ssrc;
|
|
816
817
|
}
|
|
817
818
|
}
|
|
@@ -871,15 +872,24 @@ void projectrtpchannel::readsomertp( void ) {
|
|
|
871
872
|
goto readsomemore;
|
|
872
873
|
}
|
|
873
874
|
|
|
874
|
-
|
|
875
|
+
/* TODO ZRTP? */
|
|
876
|
+
if( buf->getpacketextension() ) {
|
|
875
877
|
this->receivedpkskip++;
|
|
876
878
|
goto readsomemore;
|
|
877
879
|
}
|
|
878
880
|
|
|
879
|
-
this->tickswithnortpcount = 0;
|
|
880
|
-
|
|
881
881
|
this->correctaddress();
|
|
882
|
+
|
|
883
|
+
if( !this->recv ) {
|
|
884
|
+
this->receivedpkskip++;
|
|
885
|
+
goto readsomemore;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/*
|
|
889
|
+
after speaking with Magrathea - more streams are changing ssrc without notice mid-stream
|
|
890
|
+
for now do not check.
|
|
882
891
|
this->correctssrc( buf->getssrc() );
|
|
892
|
+
*/
|
|
883
893
|
|
|
884
894
|
if( this->confirmedrtpsenderendpoint != this->rtpsenderendpoint ) {
|
|
885
895
|
/* After the first packet - we only accept data from the verified source */
|
|
@@ -887,10 +897,14 @@ void projectrtpchannel::readsomertp( void ) {
|
|
|
887
897
|
goto readsomemore;
|
|
888
898
|
}
|
|
889
899
|
|
|
900
|
+
/*
|
|
890
901
|
if( buf->getssrc() != this->ssrcin ) {
|
|
891
902
|
this->receivedpkskip++;
|
|
892
903
|
goto readsomemore;
|
|
893
904
|
}
|
|
905
|
+
*/
|
|
906
|
+
|
|
907
|
+
this->tickswithnortpcount = 0;
|
|
894
908
|
|
|
895
909
|
/* dynamic payload types */
|
|
896
910
|
auto pt = buf->getpayloadtype();
|
|
@@ -325,8 +325,8 @@ void projectchannelmux::postrtpdata( projectrtpchannelptr srcchan, projectrtpcha
|
|
|
325
325
|
return;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
if( src->getpayloadtype() ==
|
|
329
|
-
dst->setpayloadtype(
|
|
328
|
+
if( src->getpayloadtype() == RFC2833PAYLOADTYPE ) {
|
|
329
|
+
dst->setpayloadtype( dstchan->rfc2833pt );
|
|
330
330
|
dst->copy( src );
|
|
331
331
|
} else {
|
|
332
332
|
srcchan->incodec << codecx::next;
|
package/src/projectrtpcodecx.cpp
CHANGED
|
@@ -438,10 +438,16 @@ Search for the relevent data and convert as necessary.
|
|
|
438
438
|
*/
|
|
439
439
|
bool codecx::requirewideband( void ) {
|
|
440
440
|
if( 0 != this->l1616kref.size() && !this->l1616kref.isdirty() ) return true;
|
|
441
|
+
|
|
442
|
+
if( 0 != this->l168kref.size() && !this->l168kref.isdirty() ) {
|
|
443
|
+
return this->l16lowtowideband();
|
|
444
|
+
}
|
|
445
|
+
|
|
441
446
|
if( this->g722tol16() ) return true;
|
|
442
|
-
if( !this->g711tol16() )
|
|
443
|
-
|
|
444
|
-
|
|
447
|
+
if( !this->g711tol16() ) {
|
|
448
|
+
if( !this->ilbctol16() ) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
445
451
|
}
|
|
446
452
|
|
|
447
453
|
return this->l16lowtowideband();
|
|
@@ -477,9 +483,13 @@ bool codecx::l16widetonarrowband( void ) {
|
|
|
477
483
|
## requirenarrowband
|
|
478
484
|
Search for the relevent data and convert as necessary.
|
|
479
485
|
*/
|
|
480
|
-
bool codecx::requirenarrowband( void )
|
|
481
|
-
{
|
|
486
|
+
bool codecx::requirenarrowband( void ) {
|
|
482
487
|
if( 0 != this->l168kref.size() && !this->l168kref.isdirty() ) return true;
|
|
488
|
+
|
|
489
|
+
if( 0 != this->l1616kref.size() && !this->l1616kref.isdirty() ) {
|
|
490
|
+
return this->l16widetonarrowband();
|
|
491
|
+
}
|
|
492
|
+
|
|
483
493
|
if( this->g711tol16() ) return true;
|
|
484
494
|
if( this->ilbctol16() ) return true;
|
|
485
495
|
this->g722tol16();
|
|
@@ -101,6 +101,7 @@ soundfilereader::soundfilereader( std::string &url ) :
|
|
|
101
101
|
blocksize( L16NARROWBANDBYTES ),
|
|
102
102
|
badheader( false ),
|
|
103
103
|
headerread( false ),
|
|
104
|
+
bodyread( false ),
|
|
104
105
|
initseekmseconds( 0 ),
|
|
105
106
|
ploadtype( L168KPAYLOADTYPE ) {
|
|
106
107
|
|
|
@@ -127,15 +128,13 @@ soundfilereader::soundfilereader( std::string &url ) :
|
|
|
127
128
|
/* read */
|
|
128
129
|
if ( aio_read( &this->cbwavheader ) == -1 ) {
|
|
129
130
|
fprintf( stderr, "aio_read read of header failed in soundfile\n" );
|
|
130
|
-
|
|
131
|
-
this->file = -1;
|
|
131
|
+
this->badheader = true;
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if ( aio_read( &this->cbwavblock[ this->currentcbindex ] ) == -1 ) {
|
|
136
136
|
fprintf( stderr, "aio_read read of block failed in soundfile" );
|
|
137
|
-
|
|
138
|
-
this->file = -1;
|
|
137
|
+
this->bodyread = true;
|
|
139
138
|
return;
|
|
140
139
|
}
|
|
141
140
|
|
|
@@ -153,8 +152,7 @@ soundfilereader::~soundfilereader() {
|
|
|
153
152
|
# create
|
|
154
153
|
Shared pointer version of us.
|
|
155
154
|
*/
|
|
156
|
-
soundfilereader::pointer soundfilereader::create( std::string url )
|
|
157
|
-
{
|
|
155
|
+
soundfilereader::pointer soundfilereader::create( std::string url ) {
|
|
158
156
|
return pointer( new soundfilereader( url ) );
|
|
159
157
|
}
|
|
160
158
|
|
|
@@ -177,16 +175,17 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
177
175
|
|
|
178
176
|
if( this->badheader ) {
|
|
179
177
|
fprintf( stderr, "Bad wav file\n" );
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if( !this->headerread &&
|
|
184
|
-
aio_error( &this->cbwavheader ) == EINPROGRESS ) {
|
|
185
|
-
fprintf( stderr, "Read of soundfile wav header has not completed\n" );
|
|
186
|
-
return false;
|
|
178
|
+
out.zero();
|
|
179
|
+
return true;
|
|
187
180
|
}
|
|
188
181
|
|
|
189
182
|
if( !this->headerread ) {
|
|
183
|
+
if( aio_error( &this->cbwavheader ) == EINPROGRESS ) {
|
|
184
|
+
fprintf( stderr, "Read of soundfile wav header has not completed\n" );
|
|
185
|
+
out.zero();
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
190
189
|
if( 'W' != this->ourwavheader.wave_header[ 0 ] ) {
|
|
191
190
|
this->badheader = true;
|
|
192
191
|
}
|
|
@@ -197,7 +196,9 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
197
196
|
break;
|
|
198
197
|
default:
|
|
199
198
|
fprintf( stderr, "Bad sample rate in wav\n" );
|
|
200
|
-
|
|
199
|
+
this->badheader = true;
|
|
200
|
+
out.zero();
|
|
201
|
+
return true;
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
this->ploadtype = L168KPAYLOADTYPE;
|
|
@@ -211,7 +212,9 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
211
212
|
this->ploadtype = L1616KPAYLOADTYPE;
|
|
212
213
|
this->blocksize = L16WIDEBANDBYTES;
|
|
213
214
|
} else {
|
|
214
|
-
|
|
215
|
+
this->badheader = true;
|
|
216
|
+
out.zero();
|
|
217
|
+
return true;
|
|
215
218
|
}
|
|
216
219
|
break;
|
|
217
220
|
}
|
|
@@ -236,8 +239,10 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
236
239
|
break;
|
|
237
240
|
}
|
|
238
241
|
default: {
|
|
242
|
+
this->badheader = true;
|
|
243
|
+
out.zero();
|
|
239
244
|
fprintf( stderr, "Bad audio format in wav\n" );
|
|
240
|
-
return
|
|
245
|
+
return true;
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
this->headerread = true;
|
|
@@ -245,12 +250,15 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
245
250
|
|
|
246
251
|
if( this->initseekmseconds > 0 ) {
|
|
247
252
|
this->setposition( this->initseekmseconds );
|
|
253
|
+
out.zero();
|
|
248
254
|
return true;
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
if( aio_error( &this->cbwavblock[ this->currentcbindex ] ) == EINPROGRESS ) {
|
|
252
258
|
fprintf( stderr, "Read of soundfile wav block has not completed\n" );
|
|
253
|
-
return
|
|
259
|
+
/* return some silence */
|
|
260
|
+
out.zero();
|
|
261
|
+
return true;
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
/* success? */
|
|
@@ -258,12 +266,20 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
258
266
|
|
|
259
267
|
if( -1 == numbytes ) {
|
|
260
268
|
fprintf( stderr, "Bad call to aio_return\n" );
|
|
261
|
-
|
|
269
|
+
this->bodyread = true;
|
|
270
|
+
out.zero();
|
|
271
|
+
return true;
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
uint8_t *current = ( uint8_t * ) this->cbwavblock[ this->currentcbindex ].aio_buf;
|
|
265
275
|
out = rawsound( current, this->blocksize, this->ploadtype, this->ourwavheader.sample_rate );
|
|
266
276
|
|
|
277
|
+
if( numbytes < this->blocksize ) {
|
|
278
|
+
this->bodyread = true;
|
|
279
|
+
/* TODO if we get a partial read we probably should not zero that part */
|
|
280
|
+
out.zero();
|
|
281
|
+
}
|
|
282
|
+
|
|
267
283
|
/* Get the next block reading */
|
|
268
284
|
auto lastreadoffset = this->cbwavblock[ this->currentcbindex ].aio_offset;
|
|
269
285
|
this->currentcbindex = ( this->currentcbindex + 1 ) % SOUNDFILENUMBUFFERS;
|
|
@@ -277,10 +293,10 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
277
293
|
this->cbwavblock[ this->currentcbindex ].aio_nbytes = this->blocksize;
|
|
278
294
|
|
|
279
295
|
/* read next block */
|
|
280
|
-
if ( aio_read( &this->cbwavblock[ this->currentcbindex ] ) == -1 ) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return
|
|
296
|
+
if ( !this->bodyread && aio_read( &this->cbwavblock[ this->currentcbindex ] ) == -1 ) {
|
|
297
|
+
this->bodyread = true;
|
|
298
|
+
out.zero();
|
|
299
|
+
return true;
|
|
284
300
|
}
|
|
285
301
|
|
|
286
302
|
return true;
|
|
@@ -291,11 +307,10 @@ bool soundfilereader::read( rawsound &out ) {
|
|
|
291
307
|
Have we completed reading the file.
|
|
292
308
|
*/
|
|
293
309
|
bool soundfilereader::complete( void ) {
|
|
294
|
-
if( this->badheader
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return ( this->cbwavblock[ this->currentcbindex ].aio_offset + this->blocksize ) > this->ourwavheader.chunksize;
|
|
310
|
+
if( this->badheader ) return true;
|
|
311
|
+
if( !this->headerread ) return false;
|
|
312
|
+
if( -1 == this->file ) return true;
|
|
313
|
+
return this->bodyread;
|
|
299
314
|
}
|
|
300
315
|
|
|
301
316
|
/*!md
|
|
@@ -315,35 +330,31 @@ void soundfilereader::setposition( long mseconds ) {
|
|
|
315
330
|
this->headerread = true;
|
|
316
331
|
}
|
|
317
332
|
|
|
333
|
+
if( !this->headerread ) {
|
|
334
|
+
this->initseekmseconds = mseconds;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
318
337
|
|
|
319
|
-
|
|
320
|
-
|
|
338
|
+
this->bodyread = false;
|
|
339
|
+
while( AIO_NOTCANCELED == aio_cancel( this->file, NULL ) );
|
|
321
340
|
|
|
322
|
-
|
|
341
|
+
this->currentcbindex = ( this->currentcbindex + 1 ) % SOUNDFILENUMBUFFERS;
|
|
323
342
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
343
|
+
off_t our_aio_offset = ( this->ourwavheader.bit_depth /*16*/ / 8 ) * ( this->ourwavheader.sample_rate / 1000 ) * mseconds; /* bytes per sample */
|
|
344
|
+
our_aio_offset = ( our_aio_offset / this->blocksize ) * this->blocksize; /* realign to the nearest block */
|
|
345
|
+
our_aio_offset += sizeof( wavheader );
|
|
327
346
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this->cbwavblock[ i ].aio_fildes = this->file;
|
|
332
|
-
this->cbwavblock[ i ].aio_offset = sizeof( wavheader ) + our_aio_offset + ( i * L16WIDEBANDBYTES );
|
|
333
|
-
this->cbwavblock[ i ].aio_buf = this->buffer + ( i * L16WIDEBANDBYTES );
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/* read ahead */
|
|
337
|
-
this->currentcbindex = 0;
|
|
338
|
-
if ( aio_read( &this->cbwavblock[ this->currentcbindex ] ) == -1 ) {
|
|
339
|
-
close( this->file );
|
|
340
|
-
this->file = -1;
|
|
341
|
-
}
|
|
347
|
+
for( auto i = 0; i < SOUNDFILENUMBUFFERS; i++ ) {
|
|
348
|
+
this->cbwavblock[ ( this->currentcbindex + i ) % SOUNDFILENUMBUFFERS ].aio_offset = our_aio_offset + ( i * L16WIDEBANDBYTES );
|
|
349
|
+
}
|
|
342
350
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
this->
|
|
351
|
+
/* read ahead */
|
|
352
|
+
if ( aio_read( &this->cbwavblock[ this->currentcbindex ] ) == -1 ) {
|
|
353
|
+
this->bodyread = true;
|
|
346
354
|
}
|
|
355
|
+
|
|
356
|
+
this->initseekmseconds = 0;
|
|
357
|
+
|
|
347
358
|
}
|
|
348
359
|
|
|
349
360
|
long soundfilereader::offtomsecs( void ) {
|
|
@@ -423,8 +434,7 @@ soundfilewriter::~soundfilewriter() {
|
|
|
423
434
|
# create
|
|
424
435
|
Shared pointer for writing.
|
|
425
436
|
*/
|
|
426
|
-
soundfilewriter::pointer soundfilewriter::create( std::string &url, int16_t numchannels, int32_t samplerate )
|
|
427
|
-
{
|
|
437
|
+
soundfilewriter::pointer soundfilewriter::create( std::string &url, int16_t numchannels, int32_t samplerate ) {
|
|
428
438
|
return pointer( new soundfilewriter( url, numchannels, samplerate ) );
|
|
429
439
|
}
|
|
430
440
|
|
|
@@ -5,6 +5,7 @@ const dgram = require( "dgram" )
|
|
|
5
5
|
const projectrtp = require( "../../index" ).projectrtp
|
|
6
6
|
const node = require( "../../lib/node" ).interface
|
|
7
7
|
const server = require( "../../lib/server" ).interface
|
|
8
|
+
const rtputil = require( "../util/rtp" )
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Common channel tester
|
|
@@ -360,15 +361,9 @@ describe( "rtpchannel", function() {
|
|
|
360
361
|
let totalsndiff = 0
|
|
361
362
|
let totaltsdiff = 0
|
|
362
363
|
server.on( "message", function( msg ) {
|
|
363
|
-
|
|
364
|
-
sn =
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
let ts = 0
|
|
368
|
-
ts = msg[ 4 ] << 24
|
|
369
|
-
ts = ts | ( msg[ 5 ] << 16 )
|
|
370
|
-
ts = ts | ( msg[ 6 ] << 8 )
|
|
371
|
-
ts = ts | msg[ 7 ]
|
|
364
|
+
const pk = rtputil.parsepk( msg )
|
|
365
|
+
const sn = pk.sn
|
|
366
|
+
const ts = pk.ts
|
|
372
367
|
|
|
373
368
|
if( -1 !== lastsn ) {
|
|
374
369
|
totalsndiff += sn - lastsn - 1
|
|
@@ -66,16 +66,14 @@ function sendpk( sn, ts, sendtime, dstport, server, pt = 0, ssrc ) {
|
|
|
66
66
|
/*
|
|
67
67
|
|
|
68
68
|
*/
|
|
69
|
-
function senddtmf( sn, ts, sendtime, dstport, server, endofevent, ev ) {
|
|
69
|
+
function senddtmf( sn, ts, sendtime, dstport, server, endofevent, ev, pt = 101 ) {
|
|
70
70
|
|
|
71
71
|
return setTimeout( () => {
|
|
72
72
|
const ssrc = 25
|
|
73
|
-
const pklength =
|
|
73
|
+
const pklength = 16
|
|
74
74
|
|
|
75
75
|
const header = Buffer.alloc( pklength )
|
|
76
76
|
|
|
77
|
-
const pt = 101
|
|
78
|
-
|
|
79
77
|
header.writeUInt8( 0x80 )
|
|
80
78
|
header.writeUInt8( pt, 1 ) // payload type
|
|
81
79
|
header.writeUInt16BE( ( sn ) % ( 2**16 ), 2 )
|
|
@@ -715,6 +713,149 @@ describe( "dtmf", function() {
|
|
|
715
713
|
} )
|
|
716
714
|
|
|
717
715
|
|
|
716
|
+
it( "mix 2 channels - pcmu <-> pcma and send DTMF different 2833 pt", async function() {
|
|
717
|
+
|
|
718
|
+
/*
|
|
719
|
+
When mixing 2 channels, we expect the second leg to receive the 2833 packets
|
|
720
|
+
and our server to emit events indicating the DTMF on the first channel.
|
|
721
|
+
*/
|
|
722
|
+
this.timeout( 3000 )
|
|
723
|
+
this.slow( 2000 )
|
|
724
|
+
|
|
725
|
+
const endpointa = dgram.createSocket( "udp4" )
|
|
726
|
+
const endpointb = dgram.createSocket( "udp4" )
|
|
727
|
+
|
|
728
|
+
const receivedmessages = []
|
|
729
|
+
|
|
730
|
+
let endpointapkcount = 0
|
|
731
|
+
let endpointbpkcount = 0
|
|
732
|
+
let dtmfpkcount = 0
|
|
733
|
+
|
|
734
|
+
endpointa.on( "message", function( msg ) {
|
|
735
|
+
endpointapkcount++
|
|
736
|
+
expect( msg.length ).to.equal( 172 )
|
|
737
|
+
expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
|
|
738
|
+
} )
|
|
739
|
+
|
|
740
|
+
endpointb.on( "message", function( msg ) {
|
|
741
|
+
endpointbpkcount++
|
|
742
|
+
if( 101 == ( 0x7f & msg [ 1 ] ) ) {
|
|
743
|
+
dtmfpkcount++
|
|
744
|
+
} else {
|
|
745
|
+
expect( msg.length ).to.equal( 172 )
|
|
746
|
+
expect( 0x7f & msg [ 1 ] ).to.equal( 8 )
|
|
747
|
+
endpointb.send( msg, channelb.local.port, "localhost" )
|
|
748
|
+
}
|
|
749
|
+
} )
|
|
750
|
+
|
|
751
|
+
endpointa.bind()
|
|
752
|
+
await new Promise( ( resolve ) => { endpointa.on( "listening", function() { resolve() } ) } )
|
|
753
|
+
|
|
754
|
+
endpointb.bind()
|
|
755
|
+
await new Promise( ( resolve ) => { endpointb.on( "listening", function() { resolve() } ) } )
|
|
756
|
+
|
|
757
|
+
let done
|
|
758
|
+
const finished = new Promise( ( r ) => { done = r } )
|
|
759
|
+
|
|
760
|
+
const channela = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": endpointa.address().port, "codec": 0, "rfc2833pt": 127 } }, function( d ) {
|
|
761
|
+
receivedmessages.push( d )
|
|
762
|
+
|
|
763
|
+
if( "close" === d.action ) channelb.close()
|
|
764
|
+
} )
|
|
765
|
+
|
|
766
|
+
const channelb = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": endpointb.address().port, "codec": 8, "rfc2833pt": 101 } }, function( d ) {
|
|
767
|
+
if( "close" === d.action ) done()
|
|
768
|
+
} )
|
|
769
|
+
|
|
770
|
+
/* mix */
|
|
771
|
+
expect( channela.mix( channelb ) ).to.be.true
|
|
772
|
+
|
|
773
|
+
/* send a packet every 20mS x 50 */
|
|
774
|
+
sendpk( 0, 0, 0, channela.local.port, endpointa )
|
|
775
|
+
sendpk( 1, 1*160, 1*20, channela.local.port, endpointa )
|
|
776
|
+
sendpk( 2, 2*160, 2*20, channela.local.port, endpointa )
|
|
777
|
+
sendpk( 3, 3*160, 3*20, channela.local.port, endpointa )
|
|
778
|
+
sendpk( 4, 4*160, 4*20, channela.local.port, endpointa )
|
|
779
|
+
sendpk( 5, 5*160, 5*20, channela.local.port, endpointa )
|
|
780
|
+
sendpk( 6, 6*160, 6*20, channela.local.port, endpointa )
|
|
781
|
+
sendpk( 7, 7*160, 7*20, channela.local.port, endpointa )
|
|
782
|
+
sendpk( 8, 8*160, 8*20, channela.local.port, endpointa )
|
|
783
|
+
sendpk( 9, 9*160, 9*20, channela.local.port, endpointa )
|
|
784
|
+
sendpk( 10, 10*160, 10*20, channela.local.port, endpointa )
|
|
785
|
+
sendpk( 11, 11*160, 11*20, channela.local.port, endpointa )
|
|
786
|
+
sendpk( 12, 12*160, 12*20, channela.local.port, endpointa )
|
|
787
|
+
|
|
788
|
+
senddtmf( 13, 13*160, 13*20, channela.local.port, endpointa, false, "4", 127 )
|
|
789
|
+
sendpk( 14, 13*160, 13*20, channela.local.port, endpointa, 0 )
|
|
790
|
+
sendpk( 15, 14*160, 14*20, channela.local.port, endpointa, 0 )
|
|
791
|
+
senddtmf( 16, (15*160)+10, (15*20)+10, channela.local.port, endpointa, false, "4", 127 )
|
|
792
|
+
sendpk( 17, 15*160, 15*20, channela.local.port, endpointa, 0 )
|
|
793
|
+
sendpk( 18, 16*160, 16*20, channela.local.port, endpointa, 0 )
|
|
794
|
+
senddtmf( 19, (17*160)+20, (17*20)+20, channela.local.port, endpointa, true, "4", 127 )
|
|
795
|
+
sendpk( 20, 17*160, 17*20, channela.local.port, endpointa, 0 )
|
|
796
|
+
sendpk( 21, 18*160, 18*20, channela.local.port, endpointa, 0 )
|
|
797
|
+
senddtmf( 22, (18*160)+30, (18*20)+30, channela.local.port, endpointa, true, "4", 127 )
|
|
798
|
+
sendpk( 23, 19*160, 19*20, channela.local.port, endpointa, 0 )
|
|
799
|
+
sendpk( 24, 20*160, 20*20, channela.local.port, endpointa, 0 )
|
|
800
|
+
sendpk( 25, 21*160, 21*20, channela.local.port, endpointa, 0 )
|
|
801
|
+
sendpk( 26, 22*160, 22*20, channela.local.port, endpointa, 0 )
|
|
802
|
+
sendpk( 27, 23*160, 23*20, channela.local.port, endpointa, 0 )
|
|
803
|
+
sendpk( 28, 24*160, 24*20, channela.local.port, endpointa, 0 )
|
|
804
|
+
sendpk( 29, 25*160, 25*20, channela.local.port, endpointa, 0 )
|
|
805
|
+
|
|
806
|
+
senddtmf( 30, 26*160, 26*20, channela.local.port, endpointa, false, "5", 127 )
|
|
807
|
+
sendpk( 31, 26*160, 26*20, channela.local.port, endpointa, 0 )
|
|
808
|
+
sendpk( 32, 27*160, 27*20, channela.local.port, endpointa, 0 )
|
|
809
|
+
senddtmf( 33, (27*160)+10, (27*20)+10, channela.local.port, endpointa, false, "5", 127 )
|
|
810
|
+
sendpk( 34, 28*160, 28*20, channela.local.port, endpointa, 0 )
|
|
811
|
+
sendpk( 35, 29*160, 28*20, channela.local.port, endpointa, 0 )
|
|
812
|
+
senddtmf( 36, (28*160)+20, (28*20)+20, channela.local.port, endpointa, true, "5", 127 )
|
|
813
|
+
sendpk( 37, 30*160, 29*20, channela.local.port, endpointa, 0 )
|
|
814
|
+
sendpk( 38, 31*160, 30*20, channela.local.port, endpointa, 0 )
|
|
815
|
+
|
|
816
|
+
senddtmf( 39, (38*160)+30, (30*20)+30, channela.local.port, endpointa, true, "5", 127 )
|
|
817
|
+
sendpk( 40, 32*160, 31*20, channela.local.port, endpointa, 0 )
|
|
818
|
+
sendpk( 41, 33*160, 32*20, channela.local.port, endpointa, 0 )
|
|
819
|
+
|
|
820
|
+
sendpk( 42, 34*160, 33*20, channela.local.port, endpointa, 0 )
|
|
821
|
+
sendpk( 43, 35*160, 34*20, channela.local.port, endpointa, 0 )
|
|
822
|
+
sendpk( 44, 36*160, 35*20, channela.local.port, endpointa, 0 )
|
|
823
|
+
sendpk( 45, 37*160, 36*20, channela.local.port, endpointa, 0 )
|
|
824
|
+
sendpk( 46, 38*160, 37*20, channela.local.port, endpointa, 0 )
|
|
825
|
+
sendpk( 47, 39*160, 38*20, channela.local.port, endpointa, 0 )
|
|
826
|
+
sendpk( 48, 40*160, 39*20, channela.local.port, endpointa, 0 )
|
|
827
|
+
sendpk( 49, 51*160, 40*20, channela.local.port, endpointa, 0 )
|
|
828
|
+
|
|
829
|
+
await new Promise( ( r ) => { setTimeout( () => r(), 1400 ) } )
|
|
830
|
+
|
|
831
|
+
channela.close()
|
|
832
|
+
endpointa.close()
|
|
833
|
+
endpointb.close()
|
|
834
|
+
|
|
835
|
+
await finished
|
|
836
|
+
|
|
837
|
+
expect( endpointapkcount ).to.be.within( 30, 51 )
|
|
838
|
+
expect( endpointbpkcount ).to.be.within( 30, 51 )
|
|
839
|
+
|
|
840
|
+
expect( receivedmessages.length ).to.equal( 5 )
|
|
841
|
+
|
|
842
|
+
expect( dtmfpkcount ).to.be.within( 4, 8 )
|
|
843
|
+
|
|
844
|
+
expect( receivedmessages[ 0 ].action ).to.equal( "mix" )
|
|
845
|
+
expect( receivedmessages[ 1 ].action ).to.equal( "telephone-event" )
|
|
846
|
+
expect( receivedmessages[ 2 ].action ).to.equal( "telephone-event" )
|
|
847
|
+
expect( receivedmessages[ 3 ].action ).to.equal( "mix" )
|
|
848
|
+
expect( receivedmessages[ 3 ].event ).to.equal( "finished" )
|
|
849
|
+
expect( receivedmessages[ 4 ].action ).to.equal( "close" )
|
|
850
|
+
|
|
851
|
+
expect( receivedmessages[ 0 ].event ).to.equal( "start" )
|
|
852
|
+
expect( receivedmessages[ 1 ].event ).to.equal( "4" )
|
|
853
|
+
expect( receivedmessages[ 2 ].event ).to.equal( "5" )
|
|
854
|
+
expect( receivedmessages[ 3 ].event ).to.equal( "finished" )
|
|
855
|
+
|
|
856
|
+
} )
|
|
857
|
+
|
|
858
|
+
|
|
718
859
|
it( "mix 3 channels - pcmu <-> pcma and ilbc and send DTMF", async function() {
|
|
719
860
|
|
|
720
861
|
this.timeout( 3000 )
|
|
@@ -5,9 +5,8 @@ const expect = require( "chai" ).expect
|
|
|
5
5
|
const dgram = require( "dgram" )
|
|
6
6
|
const projectrtp = require( "../../index.js" ).projectrtp
|
|
7
7
|
|
|
8
|
-
function sendpk( sn, sendtime, dstport, server, data = undefined ) {
|
|
8
|
+
function sendpk( sn, sendtime, dstport, server, data = undefined, pt = 0, ssrc = 25 ) {
|
|
9
9
|
|
|
10
|
-
const ssrc = 25
|
|
11
10
|
const pklength = 172
|
|
12
11
|
|
|
13
12
|
return setTimeout( () => {
|
|
@@ -27,8 +26,11 @@ function sendpk( sn, sendtime, dstport, server, data = undefined ) {
|
|
|
27
26
|
subheader.writeUInt32BE( ts, 2 )
|
|
28
27
|
subheader.writeUInt32BE( ssrc, 6 )
|
|
29
28
|
|
|
29
|
+
const header = Buffer.from( [ 0x80, 0x00 ] )
|
|
30
|
+
header.writeUInt8( pt, 1 ) // payload type
|
|
31
|
+
|
|
30
32
|
const rtppacket = Buffer.concat( [
|
|
31
|
-
|
|
33
|
+
header,
|
|
32
34
|
subheader,
|
|
33
35
|
payload ] )
|
|
34
36
|
|
|
@@ -101,6 +103,75 @@ describe( "channel mix", function() {
|
|
|
101
103
|
|
|
102
104
|
} )
|
|
103
105
|
|
|
106
|
+
it( "basic mix 2 channels with start 2 packets wrong payload type", async function() {
|
|
107
|
+
|
|
108
|
+
this.timeout( 3000 )
|
|
109
|
+
this.slow( 2000 )
|
|
110
|
+
|
|
111
|
+
const endpointa = dgram.createSocket( "udp4" )
|
|
112
|
+
const endpointb = dgram.createSocket( "udp4" )
|
|
113
|
+
|
|
114
|
+
let endpointapkcount = 0
|
|
115
|
+
let endpointbpkcount = 0
|
|
116
|
+
|
|
117
|
+
endpointa.on( "message", function( msg ) {
|
|
118
|
+
endpointapkcount++
|
|
119
|
+
expect( msg.length ).to.equal( 172 )
|
|
120
|
+
expect( 0x7f & msg [ 1 ] ).to.equal( 8 )
|
|
121
|
+
} )
|
|
122
|
+
|
|
123
|
+
endpointb.on( "message", function( msg ) {
|
|
124
|
+
endpointbpkcount++
|
|
125
|
+
|
|
126
|
+
expect( msg.length ).to.equal( 172 )
|
|
127
|
+
expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
|
|
128
|
+
endpointb.send( msg, channelb.local.port, "localhost" )
|
|
129
|
+
} )
|
|
130
|
+
|
|
131
|
+
endpointa.bind()
|
|
132
|
+
await new Promise( ( r ) => { endpointa.on( "listening", function() { r() } ) } )
|
|
133
|
+
|
|
134
|
+
endpointb.bind()
|
|
135
|
+
await new Promise( ( r ) => { endpointb.on( "listening", function() { r() } ) } )
|
|
136
|
+
|
|
137
|
+
let done
|
|
138
|
+
const finished = new Promise( ( r ) => { done = r } )
|
|
139
|
+
|
|
140
|
+
const channela = await projectrtp.openchannel( {}, function( d ) {
|
|
141
|
+
if( "close" === d.action ) channelb.close()
|
|
142
|
+
} )
|
|
143
|
+
|
|
144
|
+
const channelb = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": endpointb.address().port, "codec": 0 } }, function( d ) {
|
|
145
|
+
if( "close" === d.action ) done()
|
|
146
|
+
} )
|
|
147
|
+
|
|
148
|
+
/* mix */
|
|
149
|
+
expect( channela.mix( channelb ) ).to.be.true
|
|
150
|
+
|
|
151
|
+
/* a problem was highlighted that would remote would be called before rtp stream was updated */
|
|
152
|
+
channela.remote( { "address": "localhost", "port": endpointa.address().port, "codec": 8 } )
|
|
153
|
+
|
|
154
|
+
sendpk( 0, 0, channela.local.port, endpointa )
|
|
155
|
+
sendpk( 1, 1, channela.local.port, endpointa )
|
|
156
|
+
|
|
157
|
+
/* Now, when we send UDP on endpointb it passes through our mix then arrives at endpointa */
|
|
158
|
+
for( let i = 2; 50 > i; i ++ ) {
|
|
159
|
+
sendpk( i, i, channela.local.port, endpointa, undefined, 8, 27 )
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await new Promise( ( resolve ) => { setTimeout( () => resolve(), 1300 ) } )
|
|
163
|
+
|
|
164
|
+
channela.close()
|
|
165
|
+
endpointa.close()
|
|
166
|
+
endpointb.close()
|
|
167
|
+
|
|
168
|
+
expect( endpointapkcount ).to.be.within( 30, 51 )
|
|
169
|
+
expect( endpointbpkcount ).to.be.within( 30, 51 )
|
|
170
|
+
|
|
171
|
+
await finished
|
|
172
|
+
|
|
173
|
+
} )
|
|
174
|
+
|
|
104
175
|
|
|
105
176
|
it( "mix 2 channels then unmix", async function() {
|
|
106
177
|
|
|
@@ -900,9 +971,9 @@ describe( "channel mix", function() {
|
|
|
900
971
|
endpointb.close()
|
|
901
972
|
endpointc.close()
|
|
902
973
|
|
|
903
|
-
expect( endpointapkcountzero ).to.be.within(
|
|
904
|
-
expect( endpointbpkcountzero ).to.be.within(
|
|
905
|
-
expect( endpointcpkcountzero ).to.be.within(
|
|
974
|
+
expect( endpointapkcountzero ).to.be.within( 60, 75 )
|
|
975
|
+
expect( endpointbpkcountzero ).to.be.within( 60, 75 )
|
|
976
|
+
expect( endpointcpkcountzero ).to.be.within( 60, 75 )
|
|
906
977
|
expect( endpointapkcountnotzero ).to.be.within( 4, 12 )
|
|
907
978
|
expect( endpointbpkcountnotzero ).to.be.within( 4, 12 )
|
|
908
979
|
expect( endpointcpkcountnotzero ).to.be.below( 2 )
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
|
|
2
|
+
|
|
3
|
+
//const npl = require( "nodeplotlib" )
|
|
4
|
+
// eslint-disable-next-line no-unused-vars
|
|
5
|
+
const npl = { plot: ( /** @type {any} */ a ) => {} }
|
|
6
|
+
|
|
2
7
|
/*
|
|
3
8
|
Note, timing in Node doesn't appear that accurate. This requires more work if
|
|
4
9
|
we can measure jitter. The pcap traces show 0.01 mS jitter but we are getting 4mS
|
|
@@ -8,7 +13,48 @@ when playing in node. For now, leave checking of timing.
|
|
|
8
13
|
const expect = require( "chai" ).expect
|
|
9
14
|
const fs = require( "fs" )
|
|
10
15
|
const dgram = require( "dgram" )
|
|
11
|
-
const prtp = require( "../../index
|
|
16
|
+
const prtp = require( "../../index" )
|
|
17
|
+
const rtputils = require( "../util/rtp" )
|
|
18
|
+
|
|
19
|
+
function gensignal( hz, datalength, magnitude ) {
|
|
20
|
+
|
|
21
|
+
const y = Buffer.alloc( datalength * 2 )
|
|
22
|
+
|
|
23
|
+
for( let i = 0; i < datalength; i ++ ) {
|
|
24
|
+
y.writeInt16LE( Math.sin( i * ( Math.PI * 2 * ( 1 / 8000 ) ) * hz ) * magnitude, i * 2 )
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return y
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param { Array< number > } inarray
|
|
33
|
+
* @returns { Int16Array }
|
|
34
|
+
*/
|
|
35
|
+
function pcmutolinear( inarray ) {
|
|
36
|
+
const out = new Int16Array( inarray.length )
|
|
37
|
+
for( let i = 0; i < inarray.length; i++ ) {
|
|
38
|
+
out[ i ] = prtp.projectrtp.codecx.pcmu2linear16( inarray[ i ] )
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return out
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Limitation of not parsing ccrc.
|
|
46
|
+
* @param { Buffer } packet
|
|
47
|
+
* @return { object }
|
|
48
|
+
*/
|
|
49
|
+
function parsertppk( packet ) {
|
|
50
|
+
return {
|
|
51
|
+
sn: packet.readUInt16BE( 2 ),
|
|
52
|
+
ts: packet.readUInt32BE( 4 ),
|
|
53
|
+
pt: packet.readUInt8( 1 ) & 0x7f,
|
|
54
|
+
ssrc: packet.readUInt32BE( 8 ),
|
|
55
|
+
payload: new Uint8Array( packet.slice( 12 ) )
|
|
56
|
+
}
|
|
57
|
+
}
|
|
12
58
|
|
|
13
59
|
/**
|
|
14
60
|
* @param { number } samples
|
|
@@ -75,13 +121,16 @@ describe( "rtpsound", function() {
|
|
|
75
121
|
/* create our RTP/UDP endpoint */
|
|
76
122
|
const server = dgram.createSocket( "udp4" )
|
|
77
123
|
let receviedpkcount = 0
|
|
124
|
+
let receivedcorrectvalue = 0
|
|
78
125
|
|
|
79
126
|
/** @type { prtp.channel } */
|
|
80
127
|
let channel
|
|
81
128
|
|
|
82
129
|
server.on( "message", function( msg ) {
|
|
83
130
|
/* This is PCMA encoded data from our flat file */
|
|
84
|
-
|
|
131
|
+
const pk = parsertppk( msg )
|
|
132
|
+
/* we can also receive silence */
|
|
133
|
+
if( 228 == pk.payload[ 10 ] ) receivedcorrectvalue++
|
|
85
134
|
receviedpkcount++
|
|
86
135
|
} )
|
|
87
136
|
|
|
@@ -114,10 +163,11 @@ describe( "rtpsound", function() {
|
|
|
114
163
|
|
|
115
164
|
await finished
|
|
116
165
|
|
|
117
|
-
expect( receviedpkcount ).to.be.within( 48,
|
|
166
|
+
expect( receviedpkcount ).to.be.within( 48, 60 )
|
|
167
|
+
expect( receivedcorrectvalue ).to.be.within( 48, 60 )
|
|
118
168
|
} )
|
|
119
169
|
|
|
120
|
-
it( "loop in soundsoup and check udp data", function(
|
|
170
|
+
it( "loop in soundsoup and check udp data", async function() {
|
|
121
171
|
|
|
122
172
|
this.timeout( 6000 )
|
|
123
173
|
this.slow( 5000 )
|
|
@@ -125,45 +175,52 @@ describe( "rtpsound", function() {
|
|
|
125
175
|
/* create our RTP/UDP endpoint */
|
|
126
176
|
const server = dgram.createSocket( "udp4" )
|
|
127
177
|
let receviedpkcount = 0
|
|
128
|
-
let
|
|
178
|
+
let correctvalcount = 0
|
|
179
|
+
let done, cleanedup
|
|
180
|
+
const completpromise = new Promise( resolve => done = resolve )
|
|
181
|
+
const cleanedpromise = new Promise( resolve => cleanedup = resolve )
|
|
129
182
|
|
|
130
183
|
server.on( "message", function( msg ) {
|
|
131
184
|
|
|
132
185
|
/* This is PCMA encoded data from our flat file */
|
|
133
|
-
|
|
186
|
+
if( 228 == msg[ 16 ] ) correctvalcount++
|
|
134
187
|
receviedpkcount++
|
|
135
188
|
/*
|
|
136
189
|
flat.wav has 1 S of audio (8000 samples). 160 per packet compressed = 320 PCM.
|
|
137
190
|
200 packets is 64000 samples so this must have looped to work.
|
|
138
191
|
*/
|
|
139
192
|
if( 220 < receviedpkcount ) {
|
|
140
|
-
|
|
193
|
+
done()
|
|
141
194
|
}
|
|
142
195
|
} )
|
|
143
196
|
|
|
144
197
|
server.bind()
|
|
145
|
-
server.on( "listening",
|
|
198
|
+
await new Promise( resolve => server.on( "listening", resolve ) )
|
|
146
199
|
|
|
147
|
-
|
|
200
|
+
const ourport = server.address().port
|
|
148
201
|
|
|
149
|
-
|
|
202
|
+
const channel = await prtp.projectrtp.openchannel( { "remote": { "address": "localhost", "port": ourport, "codec": 0 } }, function( d ) {
|
|
203
|
+
if( "close" == d.action ) cleanedup()
|
|
204
|
+
} )
|
|
150
205
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
206
|
+
expect( channel.play( {
|
|
207
|
+
"loop": true,
|
|
208
|
+
"files": [
|
|
209
|
+
{ "wav": "/tmp/flat.wav" }
|
|
210
|
+
]
|
|
211
|
+
} ) ).to.be.true
|
|
156
212
|
|
|
157
|
-
|
|
158
|
-
"loop": true,
|
|
159
|
-
"files": [
|
|
160
|
-
{ "wav": "/tmp/flat.wav" }
|
|
161
|
-
]
|
|
162
|
-
} ) ).to.be.true
|
|
213
|
+
await completpromise
|
|
163
214
|
|
|
164
|
-
|
|
215
|
+
channel.close()
|
|
216
|
+
server.close()
|
|
217
|
+
|
|
218
|
+
await cleanedpromise
|
|
219
|
+
expect( correctvalcount ).to.be.above( 210 )
|
|
165
220
|
} )
|
|
166
221
|
|
|
222
|
+
/* I have also used this test to play with file codec conversion to check it is working properly */
|
|
223
|
+
|
|
167
224
|
it( "loop in soundsoup file and check udp data", function( done ) {
|
|
168
225
|
|
|
169
226
|
this.timeout( 6000 )
|
|
@@ -174,10 +231,12 @@ describe( "rtpsound", function() {
|
|
|
174
231
|
let receviedpkcount = 0
|
|
175
232
|
let channel
|
|
176
233
|
|
|
234
|
+
let receivedpcmu = []
|
|
177
235
|
server.on( "message", function( msg ) {
|
|
236
|
+
receivedpcmu = [ ...receivedpcmu, ...Array.from( pcmutolinear( rtputils.parsepk( msg ).payload ) ) ]
|
|
178
237
|
|
|
179
238
|
/* This is PCMA encoded data from our flat file */
|
|
180
|
-
expect( msg[ 16 ] ).to.equal( 0x99 )
|
|
239
|
+
//expect( msg[ 16 ] ).to.equal( 0x99 )
|
|
181
240
|
|
|
182
241
|
receviedpkcount++
|
|
183
242
|
/*
|
|
@@ -199,13 +258,20 @@ describe( "rtpsound", function() {
|
|
|
199
258
|
|
|
200
259
|
if( "close" === d.action ) {
|
|
201
260
|
server.close()
|
|
261
|
+
|
|
262
|
+
npl.plot( [ {
|
|
263
|
+
y: receivedpcmu,
|
|
264
|
+
type: "scatter"
|
|
265
|
+
} ] )
|
|
266
|
+
|
|
267
|
+
|
|
202
268
|
done()
|
|
203
269
|
}
|
|
204
270
|
} )
|
|
205
271
|
|
|
206
272
|
expect( channel.play( {
|
|
207
273
|
"files": [
|
|
208
|
-
{ "loop": true, "wav": "/tmp/
|
|
274
|
+
{ "loop": true, "wav": "/tmp/440sine.wav" }
|
|
209
275
|
]
|
|
210
276
|
} ) ).to.be.true
|
|
211
277
|
|
|
@@ -220,6 +286,7 @@ describe( "rtpsound", function() {
|
|
|
220
286
|
/* create our RTP/UDP endpoint */
|
|
221
287
|
const server = dgram.createSocket( "udp4" )
|
|
222
288
|
let receviedpkcount = 0
|
|
289
|
+
let receviedgoodcount = 0
|
|
223
290
|
|
|
224
291
|
/** @type { prtp.channel } */
|
|
225
292
|
let channel
|
|
@@ -232,8 +299,7 @@ describe( "rtpsound", function() {
|
|
|
232
299
|
receviedpkcount++
|
|
233
300
|
|
|
234
301
|
/* This is PCMA encoded data from our flat file */
|
|
235
|
-
|
|
236
|
-
expect( msg[ 150 ] ).to.equal( 153 /* 0x99 */ )
|
|
302
|
+
if( 228 == msg[ 17 ] ) receviedgoodcount++
|
|
237
303
|
} )
|
|
238
304
|
|
|
239
305
|
server.bind()
|
|
@@ -258,7 +324,8 @@ describe( "rtpsound", function() {
|
|
|
258
324
|
8000 samples looped twice. 100 packets (50 packets/S).
|
|
259
325
|
*/
|
|
260
326
|
|
|
261
|
-
expect( receviedpkcount ).to.
|
|
327
|
+
expect( receviedpkcount ).to.above( 90 )
|
|
328
|
+
expect( receviedgoodcount ).to.be.above( 90 )
|
|
262
329
|
} )
|
|
263
330
|
|
|
264
331
|
it( "slightly more complex soundsoup file and check udp data", async function() {
|
|
@@ -270,73 +337,61 @@ describe( "rtpsound", function() {
|
|
|
270
337
|
const server = dgram.createSocket( "udp4" )
|
|
271
338
|
let receviedpkcount = 0
|
|
272
339
|
|
|
273
|
-
|
|
274
|
-
|
|
340
|
+
let enoughpackets
|
|
341
|
+
const enoughpacketspromise = new Promise( resolve => enoughpackets = resolve )
|
|
275
342
|
|
|
276
343
|
let done
|
|
277
344
|
const finished = new Promise( ( r ) => { done = r } )
|
|
278
345
|
|
|
346
|
+
// eslint-disable-next-line complexity
|
|
279
347
|
server.on( "message", function( msg ) {
|
|
280
348
|
|
|
281
349
|
receviedpkcount++
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
expect( msg[ 150 ] ).to.equal( 153 /* 0x99 */ )
|
|
295
|
-
} else if( 125 == receviedpkcount ) {
|
|
296
|
-
/* flat2.wav */
|
|
297
|
-
expect( msg[ 17 ] ).to.equal( 3 )
|
|
298
|
-
expect( msg[ 150 ] ).to.equal( 3 )
|
|
299
|
-
} else if( 175 == receviedpkcount ) {
|
|
300
|
-
/* flat.wav */
|
|
301
|
-
expect( msg[ 17 ] ).to.equal( 153 /* 0x99 */ )
|
|
302
|
-
expect( msg[ 150 ] ).to.equal( 153 /* 0x99 */ )
|
|
303
|
-
} else if( 250 == receviedpkcount ) {
|
|
304
|
-
/* flat.wav */
|
|
305
|
-
expect( msg[ 17 ] ).to.equal( 153 )
|
|
306
|
-
expect( msg[ 150 ] ).to.equal( 153 )
|
|
307
|
-
} else if( 325 == receviedpkcount ) {
|
|
350
|
+
const secondouterloopoffset = 205
|
|
351
|
+
|
|
352
|
+
/* This is PCMA encoded data from our soundsoup */
|
|
353
|
+
if( 25 == receviedpkcount || ( 25 + secondouterloopoffset ) == receviedpkcount ) {
|
|
354
|
+
/* flat.wav first loop */
|
|
355
|
+
expect( msg[ 17 ] ).to.equal( 228 )
|
|
356
|
+
expect( msg[ 150 ] ).to.equal( 228 )
|
|
357
|
+
} else if( 75 == receviedpkcount || ( 75 + secondouterloopoffset ) == receviedpkcount ) {
|
|
358
|
+
/* flat.wav second loop */
|
|
359
|
+
expect( msg[ 17 ] ).to.equal( 228 )
|
|
360
|
+
expect( msg[ 150 ] ).to.equal( 228 )
|
|
361
|
+
} else if( 125 == receviedpkcount || ( 125 + secondouterloopoffset ) == receviedpkcount ) {
|
|
308
362
|
/* flat2.wav */
|
|
309
|
-
expect( msg[ 17 ] ).to.equal(
|
|
310
|
-
expect( msg[ 150 ] ).to.equal(
|
|
311
|
-
} else if(
|
|
363
|
+
expect( msg[ 17 ] ).to.equal( 223 )
|
|
364
|
+
expect( msg[ 150 ] ).to.equal( 223 )
|
|
365
|
+
} else if( 175 == receviedpkcount || ( 175 + secondouterloopoffset ) == receviedpkcount ) {
|
|
312
366
|
/* flat.wav */
|
|
313
|
-
expect( msg[ 17 ] ).to.equal(
|
|
314
|
-
expect( msg[ 150 ] ).to.equal(
|
|
315
|
-
} else if(
|
|
367
|
+
expect( msg[ 17 ] ).to.equal( 228 )
|
|
368
|
+
expect( msg[ 150 ] ).to.equal( 228 )
|
|
369
|
+
} else if( 206 == receviedpkcount || 413 == receviedpkcount ) {
|
|
316
370
|
/* flat3.wav */
|
|
317
|
-
expect( msg[ 17 ] ).to.equal(
|
|
318
|
-
expect( msg[ 150 ] ).to.equal(
|
|
371
|
+
expect( msg[ 17 ] ).to.equal( 220 )
|
|
372
|
+
expect( msg[ 150 ] ).to.equal( 220 )
|
|
373
|
+
} else if( ( 207 * 2 ) == receviedpkcount ) {
|
|
374
|
+
enoughpackets()
|
|
319
375
|
}
|
|
320
376
|
} )
|
|
321
377
|
|
|
322
378
|
server.bind()
|
|
323
|
-
server.on( "listening",
|
|
324
|
-
|
|
325
|
-
const ourport = server.address().port
|
|
379
|
+
await new Promise( resolve => server.on( "listening", resolve ) )
|
|
326
380
|
|
|
327
|
-
|
|
328
|
-
if( "close" === d.action ) done()
|
|
329
|
-
} )
|
|
381
|
+
const ourport = server.address().port
|
|
330
382
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
{ "wav": "/tmp/flat2.wav" },
|
|
334
|
-
{ "wav": "/tmp/flat.wav" },
|
|
335
|
-
{ "wav": "/tmp/flat3.wav", "start": 40, "stop": 60 }, /* should be 2 packets */
|
|
336
|
-
] } ) ).to.be.true
|
|
383
|
+
const channel = await prtp.projectrtp.openchannel( { "remote": { "address": "localhost", "port": ourport, "codec": 0 } }, function( d ) {
|
|
384
|
+
if( "close" === d.action ) done()
|
|
337
385
|
} )
|
|
338
386
|
|
|
339
|
-
|
|
387
|
+
expect( channel.play( { "loop": 2, "files": [
|
|
388
|
+
{ "wav": "/tmp/flat.wav", "loop": 2 },
|
|
389
|
+
{ "wav": "/tmp/flat2.wav" },
|
|
390
|
+
{ "wav": "/tmp/flat.wav" },
|
|
391
|
+
{ "wav": "/tmp/flat3.wav", "start": 40, "stop": 100 }, /* should be 4 packets */
|
|
392
|
+
] } ) ).to.be.true
|
|
393
|
+
|
|
394
|
+
await enoughpacketspromise
|
|
340
395
|
channel.close()
|
|
341
396
|
server.close()
|
|
342
397
|
|
|
@@ -345,7 +400,7 @@ describe( "rtpsound", function() {
|
|
|
345
400
|
/*
|
|
346
401
|
8000 samples looped twice with 3 sections to play. 400 packets (50 packets/S).
|
|
347
402
|
*/
|
|
348
|
-
expect( receviedpkcount ).to.be.
|
|
403
|
+
expect( receviedpkcount ).to.be.above( 400 )
|
|
349
404
|
} )
|
|
350
405
|
|
|
351
406
|
before( async () => {
|
|
@@ -355,35 +410,34 @@ describe( "rtpsound", function() {
|
|
|
355
410
|
const wavheader = createwavheader( samples )
|
|
356
411
|
const values = Buffer.alloc( samples * bytespersample )
|
|
357
412
|
for( let i = 0; i < samples; i++ ) {
|
|
358
|
-
values.
|
|
413
|
+
values.writeUInt16LE( 300, i * 2 )
|
|
359
414
|
}
|
|
360
415
|
/* Put a marker at the start of the file */
|
|
361
|
-
values.
|
|
416
|
+
values.writeUInt16LE( 1000, 50 )
|
|
362
417
|
|
|
363
|
-
await fs.writeFile( "/tmp/flat.wav", Buffer.concat( [ wavheader, values ] )
|
|
418
|
+
await fs.promises.writeFile( "/tmp/flat.wav", Buffer.concat( [ wavheader, values ] ) )
|
|
364
419
|
|
|
365
420
|
for( let i = 0; i < samples; i++ ) {
|
|
366
|
-
values.
|
|
421
|
+
values.writeUInt16LE( 400, i * 2 )
|
|
367
422
|
}
|
|
368
423
|
|
|
369
|
-
await fs.writeFile( "/tmp/flat2.wav", Buffer.concat( [ wavheader, values ] )
|
|
424
|
+
await fs.promises.writeFile( "/tmp/flat2.wav", Buffer.concat( [ wavheader, values ] ) )
|
|
370
425
|
|
|
371
426
|
for( let i = 0; i < samples; i++ ) {
|
|
372
|
-
values.
|
|
427
|
+
values.writeUInt16LE( 500, i * 2 )
|
|
373
428
|
}
|
|
374
429
|
|
|
430
|
+
await fs.promises.writeFile( "/tmp/flat3.wav", Buffer.concat( [ wavheader, values ] ) )
|
|
375
431
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
await fs.writeFile( "/tmp/flat3.wav", Buffer.concat( [ wavheader, values ] ), function() {} )
|
|
432
|
+
const sig = gensignal( 440, 8000, 1000 )
|
|
433
|
+
await fs.promises.writeFile( "/tmp/440sine.wav", Buffer.concat( [ wavheader, sig ] ) )
|
|
381
434
|
|
|
382
435
|
} )
|
|
383
436
|
|
|
384
437
|
after( async () => {
|
|
385
|
-
await
|
|
386
|
-
await
|
|
387
|
-
await
|
|
438
|
+
await fs.promises.unlink( "/tmp/flat.wav" )
|
|
439
|
+
await fs.promises.unlink( "/tmp/flat2.wav" )
|
|
440
|
+
await fs.promises.unlink( "/tmp/flat3.wav" )
|
|
441
|
+
await fs.promises.unlink( "/tmp/440sine.wav" )
|
|
388
442
|
} )
|
|
389
443
|
} )
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* if we want to see what is going on - use nodeplotlib instead of our placeholder */
|
|
2
|
-
const npl = require( "nodeplotlib" )
|
|
2
|
+
//const npl = require( "nodeplotlib" )
|
|
3
3
|
// eslint-disable-next-line no-unused-vars
|
|
4
|
-
|
|
4
|
+
const npl = { plot: ( /** @type {any} */ a ) => {} }
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
const fft = require( "fft-js" ).fft
|
package/test/util/rtp.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Limitation of not parsing ccrc.
|
|
6
|
+
* @param { Buffer } packet
|
|
7
|
+
* @return { object }
|
|
8
|
+
*/
|
|
9
|
+
module.exports.parsepk = function( packet ) {
|
|
10
|
+
return {
|
|
11
|
+
sn: packet.readUInt16BE( 2 ),
|
|
12
|
+
ts: packet.readUInt32BE( 4 ),
|
|
13
|
+
pt: packet.readUInt8( 1 ) & 0x7f,
|
|
14
|
+
ssrc: packet.readUInt32BE( 8 ),
|
|
15
|
+
payload: new Uint8Array( packet.slice( 12 ) )
|
|
16
|
+
}
|
|
17
|
+
}
|