@pexip/media-processor 16.7.1 → 17.1.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -7
  3. package/dist/main/audio.d.ts +123 -0
  4. package/dist/main/audio.js +653 -0
  5. package/dist/main/benchUtils.d.ts +12 -0
  6. package/dist/main/benchUtils.js +34 -0
  7. package/dist/main/generator.d.ts +4 -0
  8. package/dist/main/generator.js +4 -0
  9. package/dist/main/index.d.ts +17 -0
  10. package/dist/main/index.js +17 -0
  11. package/dist/main/math.d.ts +41 -0
  12. package/dist/main/math.js +57 -0
  13. package/dist/main/path.d.ts +102 -0
  14. package/dist/main/path.js +103 -0
  15. package/dist/main/process.d.ts +239 -0
  16. package/dist/main/process.js +364 -0
  17. package/dist/main/processor.d.ts +5 -0
  18. package/dist/main/processor.js +4 -0
  19. package/dist/main/transformer.d.ts +1 -0
  20. package/dist/main/transformer.js +4 -0
  21. package/dist/main/tsconfig.tsbuildinfo +1 -0
  22. package/dist/main/typeGuards.d.ts +5 -0
  23. package/dist/main/typeGuards.js +24 -0
  24. package/dist/main/types.d.ts +342 -0
  25. package/dist/main/types.js +1 -0
  26. package/dist/main/utils.d.ts +173 -0
  27. package/dist/main/utils.js +364 -0
  28. package/dist/main/video/canvasRenderUtils.d.ts +22 -0
  29. package/dist/main/video/canvasRenderUtils.js +198 -0
  30. package/dist/main/video/canvasTransform.d.ts +8 -0
  31. package/dist/main/video/canvasTransform.js +173 -0
  32. package/dist/main/video/constants.d.ts +10 -0
  33. package/dist/main/video/constants.js +12 -0
  34. package/dist/main/video/index.d.ts +9 -0
  35. package/dist/main/video/index.js +9 -0
  36. package/dist/main/video/load.d.ts +17 -0
  37. package/dist/main/video/load.js +47 -0
  38. package/dist/main/video/segmenters/index.d.ts +1 -0
  39. package/dist/main/video/segmenters/index.js +1 -0
  40. package/dist/main/video/segmenters/mediapipe.d.ts +13 -0
  41. package/dist/main/video/segmenters/mediapipe.js +85 -0
  42. package/dist/main/video/transformer.d.ts +10 -0
  43. package/dist/main/video/transformer.js +56 -0
  44. package/dist/main/video/typeGuards.d.ts +2 -0
  45. package/dist/main/video/typeGuards.js +13 -0
  46. package/dist/main/video/types.d.ts +98 -0
  47. package/dist/main/video/types.js +20 -0
  48. package/dist/main/video/utils.d.ts +102 -0
  49. package/dist/main/video/utils.js +476 -0
  50. package/dist/main/video/video.d.ts +19 -0
  51. package/dist/main/video/video.js +48 -0
  52. package/dist/main/video/videoStreamTrackProcessor.d.ts +14 -0
  53. package/dist/main/video/videoStreamTrackProcessor.js +82 -0
  54. package/dist/main/visual.d.ts +80 -0
  55. package/dist/main/visual.js +135 -0
  56. package/dist/main/workletNodes.d.ts +2 -0
  57. package/dist/main/workletNodes.js +3 -0
  58. package/dist/workers/index.d.ts +0 -0
  59. package/dist/workers/index.js +1 -0
  60. package/dist/workers/tsconfig.tsbuildinfo +1 -0
  61. package/dist/worklets/denoise.worklet.d.ts +1 -0
  62. package/dist/worklets/denoise.worklet.js +1 -2
  63. package/dist/worklets/tsconfig.tsbuildinfo +1 -0
  64. package/dist/worklets/types.d.ts +52 -0
  65. package/dist/worklets/types.js +0 -0
  66. package/package.json +11 -9
  67. package/dist/index.d.ts +0 -1129
  68. package/dist/index.mjs +0 -2441
  69. 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,5 @@
1
+ /// <reference types="dom-webcodecs" />
2
+ export declare const createMediaStreamTrackProcessor: {
3
+ (init: MediaStreamAudioTrackProcessorInit): MediaStreamTrackProcessor<AudioData>;
4
+ (init: MediaStreamVideoTrackProcessorInit): MediaStreamTrackProcessor<VideoFrame>;
5
+ };
@@ -0,0 +1,4 @@
1
+ export const createMediaStreamTrackProcessor = init => {
2
+ const processor = new MediaStreamTrackProcessor(init);
3
+ return processor;
4
+ };
@@ -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>;
@@ -0,0 +1,4 @@
1
+ export const createStreamTransformer = (transformer, writableStrategy, readableStrategy) => {
2
+ const transformStream = new TransformStream(transformer, writableStrategy, readableStrategy);
3
+ return transformStream;
4
+ };