@babblevoice/projectrtp 2.4.17 → 2.5.22

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.
@@ -0,0 +1,821 @@
1
+ /* if we want to see what is going on - use nodeplotlib instead of our placeholder */
2
+ const npl = require( "nodeplotlib" )
3
+ // eslint-disable-next-line no-unused-vars
4
+ //const npl = { plot: ( /** @type {any} */ a ) => {} }
5
+
6
+
7
+ const fft = require( "fft-js" ).fft
8
+ const projectrtp = require( "../../index" ).projectrtp
9
+ const expect = require( "chai" ).expect
10
+ const dgram = require( "dgram" )
11
+ const fs = require( "fs" )
12
+ const pcap = require( "./pcap" )
13
+
14
+
15
+ /*
16
+ So that we do not have to impliment g722 or other codecs in JS, we create 2 channels, and mix them.
17
+ On one end, we UDP echo back - which means, for example, g722 will be echoed back, then on the other end,
18
+ we generate a signal (tone) and check we receive that signal on the end end.
19
+ */
20
+
21
+ const datalength = 8192 /* 1 second of data */
22
+ const frequency = 400
23
+
24
+
25
+ const magnitude = ( Math.pow( 2, 16 ) / 2 ) - ( 65536 / 4 )
26
+
27
+ /**
28
+ * Generates time series signal with one sinewave component @ hz
29
+ * @param { number } hz
30
+ * @returns { Int16Array }
31
+ */
32
+ function gensignal( hz ) {
33
+
34
+ const y = new Int16Array( datalength )
35
+
36
+ for( let i = 0; i < datalength; i ++ ) {
37
+ y[ i ] = Math.sin( i * ( Math.PI * 2 * ( 1 / 8000 ) ) * hz ) * magnitude
38
+ }
39
+
40
+ /*
41
+ npl.plot( [ {
42
+ y: Array.from( y ),
43
+ type: "scatter"
44
+ } ] )
45
+ */
46
+
47
+ return y
48
+ }
49
+
50
+ /**
51
+ *
52
+ * @param { Array } arr
53
+ * @returns
54
+ */
55
+ function truncatetopoweroftwo( arr ) {
56
+ const newsize = Math.pow( 2, Math.floor( Math.log2( arr.length ) ) )
57
+ return arr.slice( 0, newsize )
58
+ }
59
+
60
+ /**
61
+ *
62
+ * @param { Array< Array< number > > } c - array of complex numbers as returned by fft
63
+ * @returns { Array< number > }
64
+ */
65
+ function amplitude( c ) {
66
+ const out = []
67
+
68
+ for( let k = 0; k < c.length; k++ ) {
69
+ const complex = c[ k ]
70
+ const r = complex[ 0 ]
71
+ const i = complex[ 1 ]
72
+ out.push( Math.sqrt( ( r * r ) + ( i * i ) ) )
73
+ }
74
+
75
+ return out
76
+ }
77
+
78
+ /**
79
+ *
80
+ * @param { Array< number > } inarr
81
+ * @param { number } startpos
82
+ * @param { number } endpos
83
+ */
84
+ function sum( inarr, startpos, endpos ) {
85
+ let oursum = 0
86
+ for( let i = startpos; i < endpos; i++ ) oursum += inarr[ i ]
87
+ return oursum
88
+ }
89
+
90
+ /**
91
+ *
92
+ * @param { Int16Array } signal
93
+ * @returns { Array< number > }
94
+ */
95
+ function ampbyfrequency( signal ) {
96
+ const pow2signal = truncatetopoweroftwo( Array.from( signal ) )
97
+ const ourfft = fft( pow2signal )
98
+ const amps = amplitude( ourfft )
99
+
100
+ /*
101
+ npl.plot( [ {
102
+ y: amps
103
+ } ] )
104
+ */
105
+
106
+ return amps
107
+ }
108
+
109
+ /**
110
+ * Checks fft of signal to see if we have a signal at hz present
111
+ * @param { Array< number > } amps
112
+ * @param { number } hz
113
+ * @param { number } threshold
114
+ */
115
+ function has( amps , hz, threshold ) {
116
+ return sum( amps, hz - 20, hz + 20 ) > threshold
117
+ }
118
+
119
+ /**
120
+ *
121
+ * @param { Array< number > } inarray
122
+ * @returns { Array< number > }
123
+ */
124
+ function lineartopcma( inarray ) {
125
+ const out = []
126
+ for( let i = 0; i < inarray.length; i++ )
127
+ out.push( projectrtp.codecx.linear162pcma( inarray[ i ] ) )
128
+ return out
129
+ }
130
+
131
+ /**
132
+ *
133
+ * @param { Array< number > } inarray
134
+ * @returns { Int16Array }
135
+ */
136
+ function pcmatolinear( inarray ) {
137
+ const out = new Int16Array( inarray.length )
138
+ for( let i = 0; i < inarray.length; i++ ) {
139
+ out[ i ] = projectrtp.codecx.pcma2linear16( inarray[ i ] )
140
+ }
141
+
142
+ return out
143
+ }
144
+
145
+ /**
146
+ *
147
+ * @param { Array< number > } inarray
148
+ * @returns { Array< number > }
149
+ */
150
+ function lineartopcmu( inarray ) {
151
+ const out = []
152
+ for( let i = 0; i < inarray.length; i++ )
153
+ out.push( projectrtp.codecx.linear162pcmu( inarray[ i ] ) )
154
+ return out
155
+ }
156
+
157
+ /**
158
+ *
159
+ * @param { Array< number > } inarray
160
+ * @returns { Int16Array }
161
+ */
162
+ function pcmutolinear( inarray ) {
163
+ const out = new Int16Array( inarray.length )
164
+ for( let i = 0; i < inarray.length; i++ ) {
165
+ out[ i ] = projectrtp.codecx.pcmu2linear16( inarray[ i ] )
166
+ }
167
+
168
+ return out
169
+ }
170
+
171
+ /**
172
+ * Send Buffer to server at required time
173
+ * @param { number } sendtime
174
+ * @param { Buffer } pk
175
+ * @param { number } dstport
176
+ * @param { dgram.Socket } server
177
+ * @returns
178
+ */
179
+ function sendpayload( sendtime, pk, dstport, server ) {
180
+ return setTimeout( () => {
181
+ server.send( pk, dstport, "localhost" )
182
+ }, sendtime )
183
+ }
184
+
185
+ /**
186
+ *
187
+ * @param { number } sn - should start from 0 which we use to index into the supplied data buffer
188
+ * @param { number } dstport
189
+ * @param { object } server
190
+ * @param { number } pt - payload type
191
+ * @param { number } ssrc - a unique payload type
192
+ * @param { Array< number > } payload
193
+ * @param { number } [ snoffset = 0 ] - if we want to have an offset
194
+ * @param { function } [ cb ] - callback when sent
195
+ * @returns
196
+ */
197
+ function sendpk( sn, dstport, server, pt = 0, ssrc, payload, snoffset=0, cb ) {
198
+
199
+ if( !ssrc ) ssrc = 25
200
+ const ts = sn * 160
201
+ const sendtime = sn * 20
202
+
203
+ const uint8pl = new Uint8Array( payload.slice( sn , sn + 160 ) )
204
+
205
+ return setTimeout( () => {
206
+ const subheader = Buffer.alloc( 10 )
207
+
208
+ subheader.writeUInt16BE( ( sn + snoffset ) % ( 2**16 ) )
209
+ subheader.writeUInt32BE( ts, 2 )
210
+ subheader.writeUInt32BE( ssrc, 6 )
211
+
212
+ const rtppacket = Buffer.concat( [
213
+ Buffer.from( [ 0x80, pt ] ),
214
+ subheader,
215
+ uint8pl ] )
216
+
217
+ server.send( rtppacket, dstport, "localhost" )
218
+ if( cb ) cb( { rtppacket, dstport } )
219
+ }, sendtime )
220
+ }
221
+
222
+ /**
223
+ * Limitation of not parsing ccrc.
224
+ * @param { Buffer } packet
225
+ * @return { object }
226
+ */
227
+ function parsepk( packet ) {
228
+ return {
229
+ sn: packet.readUInt16BE( 2 ),
230
+ ts: packet.readUInt32BE( 4 ),
231
+ pt: packet.readUInt8( 1 ) & 0x7f,
232
+ ssrc: packet.readUInt32BE( 8 ),
233
+ payload: new Uint8Array( packet.slice( 12 ) )
234
+ }
235
+ }
236
+
237
+ /**
238
+ * @callback encodefunction
239
+ * @param { Array< number > } inarray
240
+ * @returns { Array< number > }
241
+ */
242
+
243
+ /**
244
+ * @callback decodefunction
245
+ * @param { Array< number > } inarray
246
+ * @returns { Int16Array }
247
+ */
248
+
249
+ /**
250
+ * Run a loop test: generate signal - endcode pass to a channel, mix with second channal
251
+ * receive this rtp and loop back and finally recieve and test for signal in sound.
252
+ * This tests the full audio loop with codec conversion.
253
+ * The encode and decode functions must match the bcodec, i.e. bcodec tells
254
+ * projectrtp what codec to accept on that channel, the functions are what takes
255
+ * our linear16 and encodes and decodes into the payload.
256
+ * @param { number } acodec
257
+ * @param { number } bcodec
258
+ * @param { encodefunction } encode
259
+ * @param { decodefunction } decode
260
+ * @param { number } [ ilbcpt = -1 ] if acodec is ilbc then set the dynamic pt
261
+ */
262
+ async function looptest( acodec, bcodec, encode, decode, ilbcpt = -1 ) {
263
+ const a = dgram.createSocket( "udp4" )
264
+ const b = dgram.createSocket( "udp4" )
265
+
266
+ a.bind()
267
+ await new Promise( resolve => a.on( "listening", resolve ) )
268
+ b.bind()
269
+ await new Promise( resolve => b.on( "listening", resolve ) )
270
+
271
+ let done
272
+ const finished = new Promise( ( r ) => { done = r } )
273
+
274
+ const channeladef = { "id": "4", "remote": { "address": "localhost", "port": a.address().port, "codec": acodec } }
275
+ if( 97 == acodec && -1 != ilbcpt ) channeladef.remote.ilbcpt = ilbcpt
276
+
277
+ const achannel = await projectrtp.openchannel( channeladef, function( d ) {
278
+ if( "close" === d.action ) {
279
+ a.close()
280
+ b.close()
281
+ bchannel.close()
282
+ }
283
+ } )
284
+
285
+ const bchannel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": b.address().port, "codec": bcodec } }, function( d ) {
286
+ if( "close" === d.action ) done()
287
+ } )
288
+
289
+ bchannel.mix( achannel )
290
+
291
+ /* echo straight back */
292
+ a.on( "message", function( msg ) {
293
+ const rtppk = parsepk( msg )
294
+ if( -1 != ilbcpt ) expect( rtppk.pt ).to.equal( ilbcpt )
295
+ a.send( msg, achannel.local.port, "localhost" )
296
+ } )
297
+
298
+ let received = Buffer.alloc( 0 )
299
+ let ondonereceiving, recvcount = 0
300
+ const receiveuntil = new Promise( resolve => ondonereceiving = resolve )
301
+ b.on( "message", function( msg ) {
302
+ const pk = parsepk( msg )
303
+ received = Buffer.concat( [ received, pk.payload ] )
304
+ if( 50 < recvcount++ ) ondonereceiving()
305
+ } )
306
+
307
+ const y = gensignal( frequency )
308
+ const encoded = encode( Array.from( y ) )
309
+
310
+ for( let i = 0; 60 > i; i ++ ) {
311
+ sendpk( i, bchannel.local.port, b, bcodec, 44, encoded )
312
+ }
313
+
314
+ await receiveuntil
315
+ const y2 = decode( Array.from( received ) )
316
+
317
+ achannel.close()
318
+ await finished
319
+
320
+ npl.plot( [ {
321
+ y: Array.from( y ),
322
+ type: "scatter"
323
+ } ] )
324
+
325
+ npl.plot( [ {
326
+ y: Array.from( y2 ),
327
+ type: "scatter"
328
+ } ] )
329
+
330
+ const amps = ampbyfrequency( y2 )
331
+ expect( has( amps, frequency - 100, 25000000 ) ).to.be.false
332
+ expect( has( amps, frequency, 25000000 ) ).to.be.true
333
+ expect( has( amps, frequency + 100, 25000000 ) ).to.be.false
334
+ }
335
+
336
+ /**
337
+ * Test to check we receive all packets. ALso check basic codecs to make sure
338
+ * no duff packets come through (i,.e. memory is cleared out). It will only work
339
+ * with non-lossy CODECS
340
+ * @param { number } acodec
341
+ * @param { number } bcodec
342
+ * @param { encodefunction } encode
343
+ * @param { decodefunction } decode
344
+ * @param { number } [ expectedval ] what value we expect after the round trip (i.e. ulaw - alawy and back again might not be the same value)
345
+ */
346
+ async function loopcounttest( acodec, bcodec, encode, decode, expectedval = 0 ) {
347
+
348
+ const a = dgram.createSocket( "udp4" )
349
+ const b = dgram.createSocket( "udp4" )
350
+
351
+ a.bind()
352
+ await new Promise( resolve => a.on( "listening", resolve ) )
353
+ b.bind()
354
+ await new Promise( resolve => b.on( "listening", resolve ) )
355
+
356
+ let done
357
+ const finished = new Promise( ( r ) => { done = r } )
358
+
359
+ const allstats = {
360
+ a: {
361
+ recv:{ count: 0 },
362
+ send:{ count: 0 },
363
+ port: a.address().port
364
+ },
365
+ b: {
366
+ recv:{ count: 0 },
367
+ send:{ count: 0 },
368
+ srcport: b.address().port,
369
+ dstport: 0
370
+ },
371
+ notcorrect: 0
372
+ }
373
+
374
+ const achannel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": a.address().port, "codec": acodec } }, function( d ) {
375
+ if( "close" === d.action ) {
376
+ a.close()
377
+ b.close()
378
+ bchannel.close()
379
+ allstats.achannel = { stats: d.stats }
380
+ }
381
+ } )
382
+
383
+ const bchannel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": b.address().port, "codec": bcodec } }, function( d ) {
384
+ if( "close" === d.action ) {
385
+ allstats.bchannel = { stats: d.stats }
386
+ done()
387
+ }
388
+ } )
389
+
390
+ allstats.b.dstport = bchannel.local.port
391
+
392
+ /* echo straight back */
393
+ a.on( "message", function( msg ) {
394
+ a.send( msg, achannel.local.port, "localhost" )
395
+ allstats.a.recv.count++
396
+ allstats.a.send.count++
397
+ } )
398
+
399
+ bchannel.mix( achannel )
400
+
401
+ b.on( "message", function( msg ) {
402
+ allstats.b.recv.count++
403
+ const pk = parsepk( msg )
404
+ const decoded = decode( Array.from( pk.payload ) )
405
+ for( let i = 0; i < decoded.length; i++ ) {
406
+ if( expectedval != decoded[ i ] ) allstats.notcorrect++
407
+ }
408
+ } )
409
+
410
+ const y = new Int16Array( datalength ).fill( 0 )
411
+ const encoded = encode( Array.from( y ) )
412
+
413
+ for( let i = 0; 60 > i; i ++ ) {
414
+ sendpk( i, bchannel.local.port, b, bcodec, 44, encoded, 0, () => { allstats.b.send.count++ } )
415
+ }
416
+
417
+ const bufferdelay = 350
418
+ const errormarin = 500
419
+ const packettime = 20 * 60
420
+ const totaltimerequired = packettime + bufferdelay + errormarin
421
+ await new Promise( resolve => setTimeout( resolve, totaltimerequired ) )
422
+
423
+ achannel.close()
424
+ await finished
425
+
426
+ return allstats
427
+ }
428
+
429
+
430
+
431
+ describe( "Transcode", function() {
432
+
433
+ this.slow( 3000 )
434
+ this.timeout( 5000 )
435
+
436
+ it( "basic count test and data check trancode pcmu <==> pcmu", async function() {
437
+ /* 2 seconds is important, it should be below 60 * 20mS + JT = 1200 + 300 + 300 = 1800mS - we are taking 1820 */
438
+ this.timeout( 4000 )
439
+ this.slow( 2000 )
440
+
441
+ const all = []
442
+ for( let i = 0; 50 > i; i++) {
443
+ all.push( loopcounttest( 0, 0, lineartopcmu, pcmutolinear ) )
444
+ }
445
+ const results = await Promise.all( all )
446
+ results.forEach( ( i ) => {
447
+ expect( i.a.recv.count ).to.equal( 60 )
448
+ expect( i.b.recv.count ).to.equal( 60 )
449
+ } )
450
+ } )
451
+
452
+ it( "basic count test and data check trancode pcma <==> pcma", async function() {
453
+ this.timeout( 3000 )
454
+ this.slow( 2500 )
455
+ const result = await loopcounttest( 0, 0, lineartopcma, pcmatolinear, 8 )
456
+ expect( result.notcorrect ).to.equal( 0 )
457
+ } )
458
+
459
+ it( "basic count test and data check trancode pcmu <==> pcma", async function() {
460
+ this.timeout( 3000 )
461
+ this.slow( 2500 )
462
+ const result = await loopcounttest( 8, 0, lineartopcmu, pcmutolinear, 8 )
463
+ expect( result.notcorrect ).to.equal( 0 )
464
+ } )
465
+
466
+ it( "basic count test and data check trancode pcma <==> pcmu", async function() {
467
+ this.timeout( 3000 )
468
+ this.slow( 2500 )
469
+ const result = await loopcounttest( 0, 8, lineartopcma, pcmatolinear, 8 )
470
+ expect( result.notcorrect ).to.equal( 0 )
471
+ } )
472
+
473
+ it( "Test our linear to pcma converting routines", async function() {
474
+
475
+ const y = gensignal( frequency )
476
+
477
+ const pcma = lineartopcma( Array.from( y ) )
478
+ const y2 = pcmatolinear( pcma )
479
+
480
+ npl.plot( [ {
481
+ y: y2,
482
+ type: "scatter"
483
+ } ] )
484
+
485
+ const amps = ampbyfrequency( y )
486
+ expect( has( amps, 300, 25000000 ) ).to.be.false
487
+ expect( has( amps, 400, 25000000 ) ).to.be.true
488
+ expect( has( amps, 500, 25000000 ) ).to.be.false
489
+ } )
490
+
491
+ it( "trancode pcmu <==> ilbc static pt", async function() {
492
+ await looptest( 97, 0, lineartopcmu, pcmutolinear )
493
+ } )
494
+
495
+ it( "trancode pcmu <==> ilbc with dynamic pt", async function() {
496
+ await looptest( 97, 0, lineartopcmu, pcmutolinear, 123 )
497
+ } )
498
+
499
+ it( "trancode pcmu <==> g722", async function() {
500
+ await looptest( 9, 0, lineartopcmu, pcmutolinear )
501
+ } )
502
+
503
+ it( "trancode pcmu <==> pcma", async function() {
504
+ await looptest( 8, 0, lineartopcmu, pcmutolinear )
505
+ } )
506
+
507
+ it( "trancode pcma <==> ilbc", async function() {
508
+ await looptest( 97, 8, lineartopcma, pcmatolinear )
509
+ } )
510
+
511
+ it( "trancode pcma <==> g722", async function() {
512
+ await looptest( 9, 8, lineartopcma, pcmatolinear )
513
+ } )
514
+
515
+ it( "trancode pcma <==> pcmu", async function() {
516
+ await looptest( 0, 8, lineartopcma, pcmatolinear )
517
+ } )
518
+
519
+ it( "trancode pcma <==> pcma", async function() {
520
+ await looptest( 8, 8, lineartopcma, pcmatolinear )
521
+ } )
522
+
523
+ it( "trancode pcmu <==> pcmu", async function() {
524
+ await looptest( 0, 0, lineartopcmu, pcmutolinear )
525
+ } )
526
+
527
+ it( "simulate an xfer with multiple mix then test new path pcma <==> g722", async function() {
528
+
529
+ this.timeout( 8000 )
530
+ this.slow( 7000 )
531
+
532
+ /* make sure we have some tone to play */
533
+ projectrtp.tone.generate( "300*0.5:2000", "/tmp/tone.wav" )
534
+ projectrtp.tone.generate( "800*0.5:2000", "/tmp/hightone.wav" )
535
+
536
+ /**
537
+ * a and b is the 2 phone legs, c is the transfered channel
538
+ */
539
+ const acodec = 8
540
+ const bcodec = 0
541
+ const ccodec = 9
542
+
543
+ /*
544
+ a = 8, b = 0, c = 0 missing tones on a
545
+ a = 8, b = 9, c = 0 all works
546
+ a = 8, b = 0, c = 9, missing tones and missing c leg back on a
547
+ */
548
+
549
+ const a = dgram.createSocket( "udp4" )
550
+ const b = dgram.createSocket( "udp4" )
551
+ const c = dgram.createSocket( "udp4" )
552
+
553
+ a.bind()
554
+ await new Promise( resolve => a.on( "listening", resolve ) )
555
+ b.bind()
556
+ await new Promise( resolve => b.on( "listening", resolve ) )
557
+ c.bind()
558
+ await new Promise( resolve => c.on( "listening", resolve ) )
559
+
560
+ /* echo straight back */
561
+ c.on( "message", function( msg ) {
562
+ c.send( msg, cchannel.local.port, "localhost" )
563
+ } )
564
+
565
+ b.on( "message", function( msg ) {
566
+ b.send( msg, bchannel.local.port, "localhost" )
567
+ } )
568
+
569
+ let received = Buffer.alloc( 0 )
570
+ let ondonereceiving, recvcount = 0
571
+ const receiveuntil = new Promise( resolve => ondonereceiving = resolve )
572
+ a.on( "message", function( msg ) {
573
+ const pk = parsepk( msg )
574
+ received = Buffer.concat( [ received, pk.payload ] )
575
+ if( 100 < recvcount++ ) ondonereceiving()
576
+ } )
577
+
578
+ let done
579
+ const finished = new Promise( ( resolve ) => { done = resolve } )
580
+
581
+ let unmixresolve
582
+ const unmixdone = new Promise( resolve => unmixresolve = resolve )
583
+
584
+ /* This channel reflects the outbound channel */
585
+ const achannel = await projectrtp.openchannel( { "id": "4" }, function( d ) {
586
+ if( "close" === d.action ) {
587
+ a.close()
588
+ b.close()
589
+ c.close()
590
+ cchannel.close()
591
+ }
592
+
593
+ if( "mix" === d.action && "finished" === d.event ) unmixresolve()
594
+ } )
595
+
596
+ await new Promise( resolve => setTimeout( resolve, 800 ) )
597
+ achannel.remote( { "address": "localhost", "port": a.address().port, "codec": acodec } )
598
+
599
+ /* This channel reflects the originator */
600
+ const bchannel = await projectrtp.openchannel( { "id": "5", "remote": { "address": "localhost", "port": b.address().port, "codec": bcodec } }, function( /*d*/ ) {
601
+ } )
602
+
603
+ achannel.mix( bchannel )
604
+ await new Promise( resolve => setTimeout( resolve, 500 ) ) // we really should wait for the mix start events
605
+
606
+ /* for some reason in our lib this gets sent again */
607
+ achannel.remote( { "address": "localhost", "port": a.address().port, "codec": acodec } )
608
+ bchannel.remote( { "address": "localhost", "port": b.address().port, "codec": bcodec } )
609
+
610
+ achannel.mix( bchannel )
611
+ bchannel.record( { file: "/tmp/test.wav", numchannels: 2, mp3: true } )
612
+
613
+ const y = gensignal( 100 )
614
+ const encoded = lineartopcma( Array.from( y ) )
615
+
616
+ for( let i = 0 ; 120 > i; i ++ ) {
617
+ sendpk( i, achannel.local.port, a, acodec, 44, encoded, 6300 )
618
+ }
619
+
620
+ await new Promise( resolve => setTimeout( resolve, 1200 ) )
621
+
622
+ /* Now our blind xfer happens */
623
+ achannel.unmix()
624
+ bchannel.unmix()
625
+ await unmixdone
626
+
627
+ /* moh followed by ringing tone */
628
+ achannel.play( { "loop": true, "files": [ { "wav": "/tmp/tone.wav" } ] } )
629
+ await new Promise( resolve => setTimeout( resolve, 200 ) )
630
+ achannel.play( { "loop": true, "files": [ { "wav": "/tmp/hightone.wav" } ] } )
631
+
632
+ await new Promise( resolve => setTimeout( resolve, 200 ) )
633
+
634
+ /* now open our new leg */
635
+ const cchannel = await projectrtp.openchannel( { "id": "6" }, function( d ) {
636
+ if( "close" === d.action ) done()
637
+ } )
638
+
639
+ bchannel.close()
640
+ cchannel.remote( { "address": "localhost", "port": c.address().port, "codec": ccodec } )
641
+ await new Promise( resolve => setTimeout( resolve, 40 ) )
642
+ cchannel.mix( achannel )
643
+
644
+ /* now we have one way audio in real life */
645
+ await receiveuntil
646
+ const y2 = pcmatolinear( Array.from( received ) )
647
+
648
+ await new Promise( resolve => setTimeout( resolve, 1000 ) )
649
+ achannel.close()
650
+ await finished
651
+
652
+ npl.plot( [ {
653
+ y: Array.from( y ),
654
+ type: "scatter"
655
+ } ] )
656
+
657
+ npl.plot( [ {
658
+ y: Array.from( y2 ),
659
+ type: "scatter"
660
+ } ] )
661
+
662
+ /* TODO this currently doesn't test the c leg as this is teh same frequency as the a leg*/
663
+ const amps = ampbyfrequency( y2 )
664
+ expect( has( amps, 100, 25000000 ) ).to.be.true
665
+ expect( has( amps, 300, 25000000 ) ).to.be.true
666
+ expect( has( amps, 800, 25000000 ) ).to.be.true
667
+ expect( has( amps, 500, 25000000 ) ).to.be.false
668
+
669
+ await fs.promises.unlink( "/tmp/ukringing.wav" ).catch( () => {} )
670
+ } )
671
+
672
+ it( "replay captured g722 from poly", async () => {
673
+
674
+ const g722endpoint = dgram.createSocket( "udp4" )
675
+ g722endpoint.on( "message", function() {} )
676
+
677
+ const pcmuendpoint = dgram.createSocket( "udp4" )
678
+ let receivedpcmu = []
679
+ pcmuendpoint.on( "message", function( msg ) {
680
+ pcmuendpoint.send( msg, pcmuchannel.local.port, "localhost" )
681
+
682
+ receivedpcmu = [ ...receivedpcmu, ...Array.from( pcmutolinear( parsepk( msg ).payload ) ) ]
683
+ } )
684
+
685
+ g722endpoint.bind()
686
+ await new Promise( resolve => g722endpoint.on( "listening", resolve ) )
687
+ pcmuendpoint.bind()
688
+ await new Promise( resolve => pcmuendpoint.on( "listening", resolve ) )
689
+
690
+ const allstats = {}
691
+
692
+ const g722channel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": g722endpoint.address().port, "codec": 9 } }, function( d ) {
693
+ if( "close" === d.action ) {
694
+ g722endpoint.close()
695
+ pcmuendpoint.close()
696
+ pcmuchannel.close()
697
+ allstats.achannel = { stats: d.stats }
698
+ }
699
+ } )
700
+
701
+ let done
702
+ const allclose = new Promise( resolve => done = resolve )
703
+ const pcmuchannel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": pcmuendpoint.address().port, "codec": 0 } }, function( d ) {
704
+ if( "close" === d.action ) {
705
+ allstats.bchannel = { stats: d.stats }
706
+ done()
707
+ }
708
+ } )
709
+
710
+ const ourpcap = ( await pcap.readpcap( "test/interface/pcaps/440hzinbackgroundg722.pcap" ) ).slice( 0, 50 )
711
+
712
+ g722channel.mix( pcmuchannel )
713
+
714
+ const offset = 0
715
+ ourpcap.forEach( ( packet ) => {
716
+ if( packet.ipv4 && packet.ipv4.udp && 10018 == packet.ipv4.udp.dstport ) {
717
+ sendpayload( ( 1000 * packet.ts_sec_offset ) - offset, packet.ipv4.udp.data, g722channel.local.port, g722endpoint )
718
+ }
719
+ } )
720
+
721
+ await new Promise( resolve => setTimeout( resolve, 1400 ) )
722
+ g722channel.close()
723
+ await allclose
724
+
725
+ npl.plot( [ {
726
+ y: Array.from( receivedpcmu ),
727
+ type: "scatter"
728
+ } ] )
729
+
730
+ const amps = ampbyfrequency( Int16Array.from( receivedpcmu ) )
731
+ const bin = 225
732
+ expect( 20000 < amps[ bin ] ).to.be.true
733
+
734
+ npl.plot( [ {
735
+ y: Array.from( amps ),
736
+ type: "scatter"
737
+ } ] )
738
+
739
+ } )
740
+
741
+ it( "replay captured g722 no transcode from poly 3 way mix", async () => {
742
+
743
+ const g722endpoint = dgram.createSocket( "udp4" )
744
+ g722endpoint.on( "message", function() {} )
745
+
746
+ const pcmuendpoint = dgram.createSocket( "udp4" )
747
+ let receivedpcmu = []
748
+
749
+ pcmuendpoint.on( "message", function( msg ) {
750
+ pcmuendpoint.send( msg, pcmuchannel.local.port, "localhost" )
751
+
752
+ receivedpcmu = [ ...receivedpcmu, ...Array.from( pcmutolinear( parsepk( msg ).payload ) ) ]
753
+ } )
754
+
755
+ g722endpoint.bind()
756
+ await new Promise( resolve => g722endpoint.on( "listening", resolve ) )
757
+ pcmuendpoint.bind()
758
+ await new Promise( resolve => pcmuendpoint.on( "listening", resolve ) )
759
+
760
+ const allstats = {}
761
+
762
+ const g722channel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": g722endpoint.address().port, "codec": 9 } }, function( d ) {
763
+ if( "close" === d.action ) {
764
+ g722endpoint.close()
765
+ pcmuendpoint.close()
766
+ pcmuchannel.close()
767
+ secondg722.close()
768
+ allstats.achannel = { stats: d.stats }
769
+ }
770
+ } )
771
+
772
+ let done
773
+ const allclose = new Promise( resolve => done = resolve )
774
+ const pcmuchannel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": pcmuendpoint.address().port, "codec": 0 } }, function( d ) {
775
+ if( "close" === d.action ) {
776
+ allstats.bchannel = { stats: d.stats }
777
+ done()
778
+ }
779
+ } )
780
+
781
+ const secondg722 = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": 9990, "codec": 9 } }, function( d ) {
782
+ if( "close" === d.action ) {
783
+ allstats.bchannel = { stats: d.stats }
784
+ done()
785
+ }
786
+ } )
787
+
788
+ const ourpcap = ( await pcap.readpcap( "test/interface/pcaps/440hzinbackgroundg722.pcap" ) ).slice( 0, 50 )
789
+
790
+ g722channel.mix( pcmuchannel )
791
+ g722channel.mix( secondg722 )
792
+
793
+ const offset = 0
794
+ ourpcap.forEach( ( packet ) => {
795
+ if( packet.ipv4 && packet.ipv4.udp && 10018 == packet.ipv4.udp.dstport ) {
796
+ sendpayload( ( 1000 * packet.ts_sec_offset ) - offset, packet.ipv4.udp.data, g722channel.local.port, g722endpoint )
797
+ }
798
+ } )
799
+
800
+ await new Promise( resolve => setTimeout( resolve, 1400 ) )
801
+ g722channel.close()
802
+ await allclose
803
+
804
+ npl.plot( [ {
805
+ y: Array.from( receivedpcmu ),
806
+ type: "scatter"
807
+ } ] )
808
+
809
+ const amps = ampbyfrequency( Int16Array.from( receivedpcmu ) )
810
+
811
+ npl.plot( [ {
812
+ y: Array.from( amps ),
813
+ type: "scatter"
814
+ } ] )
815
+
816
+ const bin = 430
817
+ expect( 20000 < amps[ bin ] ).to.be.true
818
+
819
+ } )
820
+ } )
821
+