@pexip/media-processor 16.7.1 → 17.0.0
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/CHANGELOG.md +16 -0
- package/README.md +1 -7
- package/dist/main/audio.d.ts +123 -0
- package/dist/main/audio.js +653 -0
- package/dist/main/benchUtils.d.ts +12 -0
- package/dist/main/benchUtils.js +34 -0
- package/dist/main/generator.d.ts +4 -0
- package/dist/main/generator.js +4 -0
- package/dist/main/index.d.ts +17 -0
- package/dist/main/index.js +17 -0
- package/dist/main/math.d.ts +41 -0
- package/dist/main/math.js +57 -0
- package/dist/main/path.d.ts +102 -0
- package/dist/main/path.js +103 -0
- package/dist/main/process.d.ts +239 -0
- package/dist/main/process.js +364 -0
- package/dist/main/processor.d.ts +5 -0
- package/dist/main/processor.js +4 -0
- package/dist/main/transformer.d.ts +1 -0
- package/dist/main/transformer.js +4 -0
- package/dist/main/tsconfig.tsbuildinfo +1 -0
- package/dist/main/typeGuards.d.ts +5 -0
- package/dist/main/typeGuards.js +24 -0
- package/dist/main/types.d.ts +342 -0
- package/dist/main/types.js +1 -0
- package/dist/main/utils.d.ts +173 -0
- package/dist/main/utils.js +364 -0
- package/dist/main/video/canvasRenderUtils.d.ts +22 -0
- package/dist/main/video/canvasRenderUtils.js +198 -0
- package/dist/main/video/canvasTransform.d.ts +8 -0
- package/dist/main/video/canvasTransform.js +173 -0
- package/dist/main/video/constants.d.ts +10 -0
- package/dist/main/video/constants.js +12 -0
- package/dist/main/video/index.d.ts +9 -0
- package/dist/main/video/index.js +9 -0
- package/dist/main/video/load.d.ts +17 -0
- package/dist/main/video/load.js +47 -0
- package/dist/main/video/segmenters/index.d.ts +1 -0
- package/dist/main/video/segmenters/index.js +1 -0
- package/dist/main/video/segmenters/mediapipe.d.ts +13 -0
- package/dist/main/video/segmenters/mediapipe.js +85 -0
- package/dist/main/video/transformer.d.ts +10 -0
- package/dist/main/video/transformer.js +56 -0
- package/dist/main/video/typeGuards.d.ts +2 -0
- package/dist/main/video/typeGuards.js +13 -0
- package/dist/main/video/types.d.ts +98 -0
- package/dist/main/video/types.js +20 -0
- package/dist/main/video/utils.d.ts +102 -0
- package/dist/main/video/utils.js +476 -0
- package/dist/main/video/video.d.ts +19 -0
- package/dist/main/video/video.js +48 -0
- package/dist/main/video/videoStreamTrackProcessor.d.ts +14 -0
- package/dist/main/video/videoStreamTrackProcessor.js +82 -0
- package/dist/main/visual.d.ts +80 -0
- package/dist/main/visual.js +135 -0
- package/dist/main/workletNodes.d.ts +2 -0
- package/dist/main/workletNodes.js +3 -0
- package/dist/workers/index.d.ts +0 -0
- package/dist/workers/index.js +1 -0
- package/dist/workers/tsconfig.tsbuildinfo +1 -0
- package/dist/worklets/denoise.worklet.d.ts +1 -0
- package/dist/worklets/denoise.worklet.js +1 -2
- package/dist/worklets/tsconfig.tsbuildinfo +1 -0
- package/dist/worklets/types.d.ts +52 -0
- package/dist/worklets/types.js +0 -0
- package/package.json +10 -8
- package/dist/index.d.ts +0 -1129
- package/dist/index.mjs +0 -2441
- package/dist/worklets/denoise.worklet.js.map +0 -7
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { rms, round } from './math';
|
|
2
|
+
import { throttleProcess } from './utils';
|
|
3
|
+
// Constants
|
|
4
|
+
/**
|
|
5
|
+
* Default silent threshold
|
|
6
|
+
* At least one LSB 16-bit data (compare is on absolute value).
|
|
7
|
+
*/
|
|
8
|
+
export const SILENT_THRESHOLD = 1.0 / 32767;
|
|
9
|
+
/**
|
|
10
|
+
* Default mono detection threshold
|
|
11
|
+
* Data must be identical within one LSB 16-bit to be identified as mono.
|
|
12
|
+
*/
|
|
13
|
+
export const MONO_THRESHOLD = 1.0 / 65536;
|
|
14
|
+
/**
|
|
15
|
+
* Default low volume detection threshold
|
|
16
|
+
*/
|
|
17
|
+
export const LOW_VOLUME_THRESHOLD = -60; // dB
|
|
18
|
+
/**
|
|
19
|
+
* Default clipping detection threshold
|
|
20
|
+
*/
|
|
21
|
+
export const CLIP_THRESHOLD = 0.98;
|
|
22
|
+
/**
|
|
23
|
+
* Default Voice probability threshold
|
|
24
|
+
*/
|
|
25
|
+
export const VOICE_PROBABILITY_THRESHOLD = 0.3;
|
|
26
|
+
/**
|
|
27
|
+
* Default clipping count threshold
|
|
28
|
+
* Number of consecutive clipThreshold level samples that indicate clipping.
|
|
29
|
+
*/
|
|
30
|
+
export const CLIP_COUNT_THRESHOLD = 6.0;
|
|
31
|
+
/**
|
|
32
|
+
* AudioStats builder
|
|
33
|
+
*
|
|
34
|
+
* @param stats - overwrite the default attributes
|
|
35
|
+
* @param options - `silentThreshold`, `lowVolumeThreshold` and
|
|
36
|
+
* `clipCountThreshold`
|
|
37
|
+
*/
|
|
38
|
+
export const createAudioStats = (stats = {}, { silentThreshold, lowVolumeThreshold, clipCountThreshold, } = {}) => {
|
|
39
|
+
return {
|
|
40
|
+
peak: stats.peak ?? 0,
|
|
41
|
+
maxRms: stats.maxRms ?? 0,
|
|
42
|
+
maxClipCount: stats.maxClipCount ?? 0,
|
|
43
|
+
sumSquare: stats.sumSquare ?? 0,
|
|
44
|
+
sumLength: stats.sumLength ?? 0,
|
|
45
|
+
get silent() {
|
|
46
|
+
return isSilent([this.peak], silentThreshold);
|
|
47
|
+
},
|
|
48
|
+
get clipping() {
|
|
49
|
+
return isClipping(this.maxClipCount, clipCountThreshold);
|
|
50
|
+
},
|
|
51
|
+
set clipping(value) {
|
|
52
|
+
this.clipping = value;
|
|
53
|
+
},
|
|
54
|
+
get rms() {
|
|
55
|
+
return this.sumLength && Math.sqrt(this.sumSquare / this.sumLength);
|
|
56
|
+
},
|
|
57
|
+
get lowVolume() {
|
|
58
|
+
return this.rms === undefined
|
|
59
|
+
? false
|
|
60
|
+
: isLowVolume(this.rms, lowVolumeThreshold);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Convert a byte to float, according to web audio spec
|
|
66
|
+
*
|
|
67
|
+
* Floating point audio sample number is defined as: non-interleaved IEEE754
|
|
68
|
+
* 32-bit linear PCM with a nominal range between -1 and +1, that is, 32bits
|
|
69
|
+
* floating point buffer, with each samples between -1.0 and 1.0
|
|
70
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer
|
|
71
|
+
*
|
|
72
|
+
* Byte samples are represented as follows:
|
|
73
|
+
* 128 is silence, 0 is negative max, 256 is positive max
|
|
74
|
+
*
|
|
75
|
+
* @param value - The byte value to convert to float
|
|
76
|
+
*
|
|
77
|
+
* @remarks
|
|
78
|
+
* Ref. https://www.w3.org/TR/webaudio/#dom-analysernode-getbytetimedomaindata
|
|
79
|
+
*/
|
|
80
|
+
export const fromByteToFloat = (value) => (value - 128.0) / 128.0;
|
|
81
|
+
/**
|
|
82
|
+
* Convert a float to byte, according to web audio spec
|
|
83
|
+
*
|
|
84
|
+
* Floating point audio sample number is defined as: non-interleaved IEEE754
|
|
85
|
+
* 32-bit linear PCM with a nominal range between -1 and +1, that is, 32bits
|
|
86
|
+
* floating point buffer, with each samples between -1.0 and 1.0
|
|
87
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer
|
|
88
|
+
*
|
|
89
|
+
* Byte samples are represented as follows:
|
|
90
|
+
* 128 is silence, 0 is negative max, 256 is positive max
|
|
91
|
+
*
|
|
92
|
+
* @param value - The float value to convert to byte
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Ref. https://www.w3.org/TR/webaudio/#dom-analysernode-getbytetimedomaindata
|
|
96
|
+
*/
|
|
97
|
+
export const fromFloatToByte = (value) => round(value * 128.0 + 128.0);
|
|
98
|
+
/**
|
|
99
|
+
* Copy data from Uint8Array buffer to Float32Array buffer with byte to float conversion
|
|
100
|
+
*
|
|
101
|
+
* @param bytes - The source Byte buffer
|
|
102
|
+
* @param floats - The destination buffer
|
|
103
|
+
*/
|
|
104
|
+
export const copyByteBufferToFloatBuffer = (bytes, floats) => {
|
|
105
|
+
bytes.forEach((value, idx) => {
|
|
106
|
+
floats[idx] = fromByteToFloat(value);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Convert a floating point gain value into a dB representation without any
|
|
111
|
+
* reference, dBFS, https://en.wikipedia.org/wiki/DBFS
|
|
112
|
+
*
|
|
113
|
+
* See https://www.w3.org/TR/webaudio#conversion-to-db
|
|
114
|
+
*
|
|
115
|
+
* @param amplitude - Expected a value in (0, 1]
|
|
116
|
+
*/
|
|
117
|
+
export const toDecibel = (gain) => 20 * Math.log10(Math.abs(gain));
|
|
118
|
+
/**
|
|
119
|
+
* Calculate the averaged volume using Root Mean Square, assuming the data is in
|
|
120
|
+
* float form
|
|
121
|
+
*
|
|
122
|
+
* @param data - Audio Frequency data
|
|
123
|
+
*
|
|
124
|
+
* @alpha
|
|
125
|
+
*/
|
|
126
|
+
export const processAverageVolume = (data) => data.length ? rms(data) : 0;
|
|
127
|
+
/**
|
|
128
|
+
* Simple silent detection to only check the first and last bit from the sample
|
|
129
|
+
*
|
|
130
|
+
* @param samples - Audio sample data, this could be in a form of floating number
|
|
131
|
+
* of a byte number as long as the `threshold` value is given accordingly.
|
|
132
|
+
* @param threshold - Silent threshold
|
|
133
|
+
*
|
|
134
|
+
* @defaultValue
|
|
135
|
+
* `1.0 / 32767` assuming the sample is float value
|
|
136
|
+
*
|
|
137
|
+
* @returns
|
|
138
|
+
* `true` when it is silent
|
|
139
|
+
*/
|
|
140
|
+
export const isSilent = (samples, threshold = SILENT_THRESHOLD) => samples.length === 0 ||
|
|
141
|
+
(getFirstSample(samples) <= threshold &&
|
|
142
|
+
getLastSample(samples) <= threshold);
|
|
143
|
+
function getFirstSample(samples) {
|
|
144
|
+
if (samples[0] !== undefined) {
|
|
145
|
+
return Math.abs(samples[0]);
|
|
146
|
+
}
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
function getLastSample(samples) {
|
|
150
|
+
const last = samples[samples.length - 1];
|
|
151
|
+
if (last) {
|
|
152
|
+
return Math.abs(last);
|
|
153
|
+
}
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if the provided gain above the low volume threshold, which is
|
|
158
|
+
* considered as low volume.
|
|
159
|
+
*
|
|
160
|
+
* @param gain - Floating point representation of the gain number
|
|
161
|
+
*
|
|
162
|
+
* @returns
|
|
163
|
+
* `true` if the `gain` is lower than the threshold
|
|
164
|
+
*/
|
|
165
|
+
export const isLowVolume = (gain, threshold = LOW_VOLUME_THRESHOLD) => toDecibel(gain) < threshold;
|
|
166
|
+
/**
|
|
167
|
+
* Check if there is clipping
|
|
168
|
+
*
|
|
169
|
+
* @param clipCount - Number of consecutive clip
|
|
170
|
+
*
|
|
171
|
+
* @returns
|
|
172
|
+
* `true` if the `clipCount` is above the threshold, aka clipping
|
|
173
|
+
*/
|
|
174
|
+
export const isClipping = (clipCount, threshold = CLIP_COUNT_THRESHOLD) => clipCount > threshold;
|
|
175
|
+
/**
|
|
176
|
+
* Check if provided channels are mono or stereo
|
|
177
|
+
*
|
|
178
|
+
* @param channels - Audio channels and assuming the inputs are in floating
|
|
179
|
+
* point form
|
|
180
|
+
* @param threshold - Mono detection threshold, default to floating point form
|
|
181
|
+
*
|
|
182
|
+
* @defaultValue
|
|
183
|
+
* `1.0 / 32767`
|
|
184
|
+
*
|
|
185
|
+
* @returns
|
|
186
|
+
* `true` if they are mono, otherwise stereo
|
|
187
|
+
*/
|
|
188
|
+
export const isMono = (channels, threshold = MONO_THRESHOLD) => {
|
|
189
|
+
let sampleDiffCount = 0;
|
|
190
|
+
if (channels.length < 2 ||
|
|
191
|
+
channels.filter(channel => !isSilent(channel)).length < 2) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
if (channels[0]?.length === channels[1]?.length) {
|
|
195
|
+
channels[0]?.forEach((l, idx) => {
|
|
196
|
+
const r = channels[1]?.[idx];
|
|
197
|
+
if (r !== undefined && Math.abs(l - r) > threshold) {
|
|
198
|
+
sampleDiffCount++;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
sampleDiffCount++;
|
|
204
|
+
}
|
|
205
|
+
return sampleDiffCount === 0;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Calculate the audio stats, expected the samples are in float form
|
|
209
|
+
*
|
|
210
|
+
* @param options - See StatsOptions
|
|
211
|
+
*
|
|
212
|
+
* @remarks
|
|
213
|
+
* http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
|
|
214
|
+
*/
|
|
215
|
+
export const getAudioStats = ({ samples, baseStats, clipThreshold = CLIP_THRESHOLD, }) => {
|
|
216
|
+
let rms = 0;
|
|
217
|
+
let clipCount = 0;
|
|
218
|
+
let maxClipCount = 0;
|
|
219
|
+
let peak = 0;
|
|
220
|
+
const stats = baseStats || createAudioStats();
|
|
221
|
+
samples.forEach((s) => {
|
|
222
|
+
const absS = Math.abs(s);
|
|
223
|
+
peak = Math.max(peak, absS);
|
|
224
|
+
if (absS >= clipThreshold) {
|
|
225
|
+
clipCount += 1;
|
|
226
|
+
maxClipCount = Math.max(clipCount, maxClipCount);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
clipCount = 0;
|
|
230
|
+
}
|
|
231
|
+
rms += absS * absS;
|
|
232
|
+
});
|
|
233
|
+
stats.peak = Math.max(stats.peak ?? 0, peak);
|
|
234
|
+
stats.sumSquare += rms;
|
|
235
|
+
stats.sumLength += samples.length;
|
|
236
|
+
rms = samples.length ? Math.sqrt(rms / samples.length) : 0;
|
|
237
|
+
stats.maxRms = Math.max(stats.maxRms ?? 0, rms);
|
|
238
|
+
stats.maxClipCount = Math.max(maxClipCount, stats.maxClipCount ?? 0);
|
|
239
|
+
return stats;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* A Naive Voice activity detection
|
|
243
|
+
*
|
|
244
|
+
* @param options - See `VAOptions`
|
|
245
|
+
*
|
|
246
|
+
* @returns `(volume: number) => boolean`, `true` if there is voice
|
|
247
|
+
*/
|
|
248
|
+
export const isVoiceActivity = ({ volumeThreshold = 0.05, VADTimeThreshold = 500, clock = performance, } = {}) => {
|
|
249
|
+
let lastVADTime = 0;
|
|
250
|
+
return (volume) => {
|
|
251
|
+
if (volume >= volumeThreshold) {
|
|
252
|
+
const now = clock.now();
|
|
253
|
+
if (!lastVADTime) {
|
|
254
|
+
lastVADTime = now;
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (now - lastVADTime >= VADTimeThreshold) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (lastVADTime) {
|
|
263
|
+
lastVADTime = 0;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Compare the provided width and height to see if they are the same
|
|
270
|
+
*
|
|
271
|
+
* @param widthA - The width of A
|
|
272
|
+
* @param heightA - The height of A
|
|
273
|
+
* @param widthB - The width of B
|
|
274
|
+
* @param heightB - The height of B
|
|
275
|
+
*/
|
|
276
|
+
export const isEqualSize = (widthA, heightA, widthB, heightB) => widthA === widthB && heightA === heightB;
|
|
277
|
+
/**
|
|
278
|
+
* Convert the source size to destination size when necessary based on the
|
|
279
|
+
* height
|
|
280
|
+
*
|
|
281
|
+
* @param sw - Source width
|
|
282
|
+
* @param sh - Source height
|
|
283
|
+
* @param dw - destination width
|
|
284
|
+
* @param dh - destination height
|
|
285
|
+
*/
|
|
286
|
+
export const fitDestinationSize = (sw, sh, dw, dh) => {
|
|
287
|
+
if (!sw || !sh || !dw || !dh) {
|
|
288
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
289
|
+
}
|
|
290
|
+
if (isEqualSize(sw, sh, dw, dh)) {
|
|
291
|
+
return { x: 0, y: 0, width: sw, height: sh };
|
|
292
|
+
}
|
|
293
|
+
const height = Math.floor(dw * (sh / sw));
|
|
294
|
+
const y = Math.floor((dh - height) / 2);
|
|
295
|
+
return { x: 0, y, width: dw, height };
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* A function to check provided time series data is considered as voice activity
|
|
299
|
+
*
|
|
300
|
+
* @param options - @see VAOptions
|
|
301
|
+
*/
|
|
302
|
+
export const createVoiceDetectorFromTimeData = (options = {}) => {
|
|
303
|
+
const isVoice = isVoiceActivity(options);
|
|
304
|
+
return timeData => isVoice(rms(timeData));
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* A function to check the provided probability is considered as voice activity
|
|
308
|
+
*
|
|
309
|
+
* @param voiceThreshold - A threshold of the probability to be considered as
|
|
310
|
+
* voice activity
|
|
311
|
+
*/
|
|
312
|
+
export const createVoiceDetectorFromProbability = (voiceThreshold = VOICE_PROBABILITY_THRESHOLD) => probability => probability >= voiceThreshold;
|
|
313
|
+
/**
|
|
314
|
+
* Create a voice detector based on provided params
|
|
315
|
+
*
|
|
316
|
+
* @param onDetected - When there is voice activity, this callback will be called
|
|
317
|
+
* @param shouldDetect - When return `true`, voice activity will function, otherwise, not function
|
|
318
|
+
* @param options - @see ThrottleOptions
|
|
319
|
+
*/
|
|
320
|
+
export const createVADetector = (onDetected, shouldDetect, options) => (isVoice) => {
|
|
321
|
+
const throttledTrigger = throttleProcess(onDetected, options?.throttleMs, options?.clock);
|
|
322
|
+
const process = (data) => {
|
|
323
|
+
if (!shouldDetect()) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (isVoice(data)) {
|
|
327
|
+
throttledTrigger();
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
return process;
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* Create a function to process the AudioStats and check if silent
|
|
334
|
+
* `onSignalDetected` callback is called under 2 situations:
|
|
335
|
+
*
|
|
336
|
+
* ```
|
|
337
|
+
* Logic
|
|
338
|
+
* lastCheck | silent | should call onSignalDetected
|
|
339
|
+
* 0 | 0 | 0
|
|
340
|
+
* 0 | 1 | 1
|
|
341
|
+
* 1 | 0 | 1
|
|
342
|
+
* 1 | 1 | 0
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
export const createAudioSignalDetector = (shouldDetect, onDetected) => (buffer, threshold) => {
|
|
346
|
+
const props = { silent: false, lastCheck: false };
|
|
347
|
+
return (samples) => {
|
|
348
|
+
if (!shouldDetect()) {
|
|
349
|
+
buffer.empty();
|
|
350
|
+
props.silent = false;
|
|
351
|
+
props.lastCheck = false;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (buffer.enqueue(samples) >= buffer.maxSize) {
|
|
355
|
+
props.lastCheck = props.silent;
|
|
356
|
+
props.silent = buffer
|
|
357
|
+
.dequeueAll()
|
|
358
|
+
.every(samples => isSilent(samples, threshold));
|
|
359
|
+
if (props.lastCheck !== props.silent) {
|
|
360
|
+
onDetected(props.silent);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createStreamTransformer: <I, O>(transformer?: Transformer<I, O> | undefined, writableStrategy?: QueuingStrategy<I> | undefined, readableStrategy?: QueuingStrategy<O> | undefined) => TransformStream<I, O>;
|