@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.
- package/README.md +3 -70
- package/package.json +6 -4
- package/src/globals.h +5 -0
- package/src/projectrtpbuffer.cpp +30 -6
- package/src/projectrtpbuffer.h +14 -3
- package/src/projectrtpchannel.cpp +110 -56
- package/src/projectrtpchannel.h +6 -0
- package/src/projectrtpchannelmux.cpp +23 -15
- package/src/projectrtpchannelmux.h +4 -2
- package/src/projectrtpcodecx.cpp +72 -96
- package/src/projectrtpcodecx.h +18 -5
- package/src/projectrtppacket.cpp +16 -41
- package/src/projectrtppacket.h +0 -2
- package/src/projectrtprawsound.cpp +56 -75
- package/src/projectrtprawsound.h +3 -2
- package/src/projectrtpsoundfile.cpp +4 -1
- package/src/projectrtptonegen.cpp +26 -43
- package/test/interface/pcap.js +2 -2
- package/test/interface/pcaps/440hzinbackgroundg722.pcap +0 -0
- package/test/interface/projectrtpchannel.js +10 -10
- package/test/interface/projectrtpdtmf.js +88 -4
- package/test/interface/projectrtpmix.js +1 -2
- package/test/interface/transcode.js +821 -0
|
@@ -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
|
+
|