@lumen5/beamcoder 0.0.1

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 (88) hide show
  1. package/.circleci/config.yml +41 -0
  2. package/.circleci/images/testbeam10-4.1/Dockerfile +12 -0
  3. package/.circleci/test_image/Dockerfile +14 -0
  4. package/.circleci/test_image/build.md +13 -0
  5. package/.eslintrc.js +27 -0
  6. package/.github/workflows/publish-npm.yml +33 -0
  7. package/LICENSE +674 -0
  8. package/README.md +1221 -0
  9. package/beamstreams.js +692 -0
  10. package/binding.gyp +103 -0
  11. package/examples/encode_h264.js +92 -0
  12. package/examples/jpeg_app.js +55 -0
  13. package/examples/jpeg_filter_app.js +101 -0
  14. package/examples/make_mp4.js +123 -0
  15. package/images/beamcoder_small.jpg +0 -0
  16. package/index.d.ts +83 -0
  17. package/index.js +44 -0
  18. package/install_ffmpeg.js +240 -0
  19. package/package.json +45 -0
  20. package/scratch/decode_aac.js +38 -0
  21. package/scratch/decode_avci.js +50 -0
  22. package/scratch/decode_hevc.js +38 -0
  23. package/scratch/decode_pcm.js +39 -0
  24. package/scratch/make_a_mux.js +68 -0
  25. package/scratch/muxer.js +74 -0
  26. package/scratch/read_wav.js +35 -0
  27. package/scratch/simple_mux.js +39 -0
  28. package/scratch/stream_avci.js +127 -0
  29. package/scratch/stream_mp4.js +78 -0
  30. package/scratch/stream_mux.js +47 -0
  31. package/scratch/stream_pcm.js +82 -0
  32. package/scratch/stream_wav.js +62 -0
  33. package/scripts/install_beamcoder_dependencies.sh +25 -0
  34. package/src/adaptor.h +202 -0
  35. package/src/beamcoder.cc +937 -0
  36. package/src/beamcoder_util.cc +1129 -0
  37. package/src/beamcoder_util.h +206 -0
  38. package/src/codec.cc +7386 -0
  39. package/src/codec.h +44 -0
  40. package/src/codec_par.cc +1818 -0
  41. package/src/codec_par.h +40 -0
  42. package/src/decode.cc +569 -0
  43. package/src/decode.h +75 -0
  44. package/src/demux.cc +584 -0
  45. package/src/demux.h +88 -0
  46. package/src/encode.cc +496 -0
  47. package/src/encode.h +72 -0
  48. package/src/filter.cc +1888 -0
  49. package/src/filter.h +30 -0
  50. package/src/format.cc +5287 -0
  51. package/src/format.h +77 -0
  52. package/src/frame.cc +2681 -0
  53. package/src/frame.h +52 -0
  54. package/src/governor.cc +286 -0
  55. package/src/governor.h +30 -0
  56. package/src/hwcontext.cc +378 -0
  57. package/src/hwcontext.h +35 -0
  58. package/src/log.cc +186 -0
  59. package/src/log.h +20 -0
  60. package/src/mux.cc +834 -0
  61. package/src/mux.h +106 -0
  62. package/src/packet.cc +762 -0
  63. package/src/packet.h +49 -0
  64. package/test/codecParamsSpec.js +148 -0
  65. package/test/decoderSpec.js +56 -0
  66. package/test/demuxerSpec.js +41 -0
  67. package/test/encoderSpec.js +69 -0
  68. package/test/filtererSpec.js +47 -0
  69. package/test/formatSpec.js +343 -0
  70. package/test/frameSpec.js +145 -0
  71. package/test/introspectionSpec.js +73 -0
  72. package/test/muxerSpec.js +34 -0
  73. package/test/packetSpec.js +122 -0
  74. package/types/Beamstreams.d.ts +98 -0
  75. package/types/Codec.d.ts +123 -0
  76. package/types/CodecContext.d.ts +555 -0
  77. package/types/CodecPar.d.ts +108 -0
  78. package/types/Decoder.d.ts +137 -0
  79. package/types/Demuxer.d.ts +113 -0
  80. package/types/Encoder.d.ts +94 -0
  81. package/types/Filter.d.ts +324 -0
  82. package/types/FormatContext.d.ts +380 -0
  83. package/types/Frame.d.ts +295 -0
  84. package/types/HWContext.d.ts +62 -0
  85. package/types/Muxer.d.ts +121 -0
  86. package/types/Packet.d.ts +82 -0
  87. package/types/PrivClass.d.ts +25 -0
  88. package/types/Stream.d.ts +165 -0
package/beamstreams.js ADDED
@@ -0,0 +1,692 @@
1
+ /*
2
+ Aerostat Beam Coder - Node.js native bindings to FFmpeg
3
+ Copyright (C) 2019 Streampunk Media Ltd.
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ https://www.streampunk.media/ mailto:furnace@streampunk.media
19
+ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K.
20
+ */
21
+
22
+ const beamcoder = require('bindings')('beamcoder');
23
+ const { Writable, Readable, Transform } = require('stream');
24
+
25
+ const doTimings = false;
26
+ const timings = [];
27
+
28
+ function frameDicer(encoder, isAudio) {
29
+ let sampleBytes = 4; // Assume floating point 4 byte samples for now...
30
+ const numChannels = encoder.channels;
31
+ const dstNumSamples = encoder.frame_size;
32
+ let dstFrmBytes = dstNumSamples * sampleBytes;
33
+ const doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE;
34
+
35
+ let lastFrm = null;
36
+ let lastBuf = [];
37
+ const nullBuf = [];
38
+ for (let b = 0; b < numChannels; ++b)
39
+ nullBuf.push(Buffer.alloc(0));
40
+
41
+ const addFrame = srcFrm => {
42
+ let result = [];
43
+ let dstFrm;
44
+ let curStart = 0;
45
+ if (!lastFrm) {
46
+ lastFrm = beamcoder.frame(srcFrm.toJSON());
47
+ lastBuf = nullBuf;
48
+ dstFrmBytes = dstNumSamples * sampleBytes;
49
+ }
50
+
51
+ if (lastBuf[0].length > 0)
52
+ dstFrm = beamcoder.frame(lastFrm.toJSON());
53
+ else
54
+ dstFrm = beamcoder.frame(srcFrm.toJSON());
55
+ dstFrm.nb_samples = dstNumSamples;
56
+ dstFrm.pkt_duration = dstNumSamples;
57
+
58
+ while (curStart + dstFrmBytes - lastBuf[0].length <= srcFrm.nb_samples * sampleBytes) {
59
+ const resFrm = beamcoder.frame(dstFrm.toJSON());
60
+ resFrm.data = lastBuf.map((d, i) =>
61
+ Buffer.concat([
62
+ d, srcFrm.data[i].slice(curStart, curStart + dstFrmBytes - d.length)],
63
+ dstFrmBytes));
64
+ result.push(resFrm);
65
+
66
+ dstFrm.pts += dstNumSamples;
67
+ dstFrm.pkt_dts += dstNumSamples;
68
+ curStart += dstFrmBytes - lastBuf[0].length;
69
+ lastFrm.pts = 0;
70
+ lastFrm.pkt_dts = 0;
71
+ lastBuf = nullBuf;
72
+ }
73
+
74
+ lastFrm.pts = dstFrm.pts;
75
+ lastFrm.pkt_dts = dstFrm.pkt_dts;
76
+ lastBuf = srcFrm.data.map(d => d.slice(curStart, srcFrm.nb_samples * sampleBytes));
77
+
78
+ return result;
79
+ };
80
+
81
+ const getLast = () => {
82
+ let result = [];
83
+ if (lastBuf[0].length > 0) {
84
+ const resFrm = beamcoder.frame(lastFrm.toJSON());
85
+ resFrm.data = lastBuf.map(d => d.slice(0));
86
+ resFrm.nb_samples = lastBuf[0].length / sampleBytes;
87
+ resFrm.pkt_duration = resFrm.nb_samples;
88
+ lastFrm.pts = 0;
89
+ lastBuf = nullBuf;
90
+ result.push(resFrm);
91
+ }
92
+ return result;
93
+ };
94
+
95
+ this.dice = (frames, flush = false) => {
96
+ if (isAudio && doDice) {
97
+ let result = frames.reduce((muxFrms, frm) => {
98
+ addFrame(frm).forEach(f => muxFrms.push(f));
99
+ return muxFrms;
100
+ }, []);
101
+
102
+ if (flush)
103
+ getLast().forEach(f => result.push(f));
104
+
105
+ return result;
106
+ }
107
+
108
+ return frames;
109
+ };
110
+ }
111
+
112
+ function serialBalancer(numStreams) {
113
+ let pending = [];
114
+ // initialise with negative ts and no pkt
115
+ // - there should be no output until each stream has sent its first packet
116
+ for (let s = 0; s < numStreams; ++s)
117
+ pending.push({ ts: -Number.MAX_VALUE, streamIndex: s });
118
+
119
+ const adjustTS = (pkt, srcTB, dstTB) => {
120
+ const adj = (srcTB[0] * dstTB[1]) / (srcTB[1] * dstTB[0]);
121
+ pkt.pts = Math.round(pkt.pts * adj);
122
+ pkt.dts = Math.round(pkt.dts * adj);
123
+ pkt.duration > 0 ? Math.round(pkt.duration * adj) : Math.round(adj);
124
+ };
125
+
126
+ const pullPkts = (pkt, streamIndex, ts) => {
127
+ return new Promise(resolve => {
128
+ Object.assign(pending[streamIndex], { pkt: pkt, ts: ts, resolve: resolve });
129
+ const minTS = pending.reduce((acc, pend) => Math.min(acc, pend.ts), Number.MAX_VALUE);
130
+ // console.log(streamIndex, pending.map(p => p.ts), minTS);
131
+ const nextPend = pending.find(pend => pend.pkt && (pend.ts === minTS));
132
+ if (nextPend) nextPend.resolve(nextPend.pkt);
133
+ if (!pkt) resolve();
134
+ });
135
+ };
136
+
137
+ this.writePkts = (packets, srcStream, dstStream, writeFn, final = false) => {
138
+ if (packets && packets.packets.length) {
139
+ return packets.packets.reduce(async (promise, pkt) => {
140
+ await promise;
141
+ pkt.stream_index = dstStream.index;
142
+ adjustTS(pkt, srcStream.time_base, dstStream.time_base);
143
+ const pktTS = pkt.pts * dstStream.time_base[0] / dstStream.time_base[1];
144
+ return writeFn(await pullPkts(pkt, dstStream.index, pktTS));
145
+ }, Promise.resolve());
146
+ } else if (final)
147
+ return pullPkts(null, dstStream.index, Number.MAX_VALUE);
148
+ };
149
+ }
150
+
151
+ function parallelBalancer(params, streamType, numStreams) {
152
+ let resolveGet = null;
153
+ const tag = 'video' === streamType ? 'v' : 'a';
154
+ const pending = [];
155
+ // initialise with negative ts and no pkt
156
+ // - there should be no output until each stream has sent its first packet
157
+ for (let s = 0; s < numStreams; ++s)
158
+ pending.push({ ts: -Number.MAX_VALUE, streamIndex: s });
159
+
160
+ const makeSet = resolve => {
161
+ if (resolve) {
162
+ // console.log('makeSet', pending.map(p => p.ts));
163
+ const nextPends = pending.every(pend => pend.pkt) ? pending : null;
164
+ const final = pending.filter(pend => true === pend.final);
165
+ if (nextPends) {
166
+ nextPends.forEach(pend => pend.resolve());
167
+ resolve({
168
+ value: nextPends.map(pend => {
169
+ return { name: `in${pend.streamIndex}:${tag}`, frames: [ pend.pkt ] }; }),
170
+ done: false });
171
+ resolveGet = null;
172
+ pending.forEach(pend => Object.assign(pend, { pkt: null, ts: Number.MAX_VALUE }));
173
+ } else if (final.length > 0) {
174
+ final.forEach(f => f.resolve());
175
+ resolve({ done: true });
176
+ } else {
177
+ resolveGet = resolve;
178
+ }
179
+ }
180
+ };
181
+
182
+ const pushPkt = async (pkt, streamIndex, ts) =>
183
+ new Promise(resolve => {
184
+ Object.assign(pending[streamIndex], { pkt: pkt, ts: ts, final: pkt ? false : true, resolve: resolve });
185
+ makeSet(resolveGet);
186
+ });
187
+
188
+ const pullSet = async () => new Promise(resolve => makeSet(resolve));
189
+
190
+ const readStream = new Readable({
191
+ objectMode: true,
192
+ highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4,
193
+ read() {
194
+ (async () => {
195
+ const start = process.hrtime();
196
+ const reqTime = start[0] * 1e3 + start[1] / 1e6;
197
+ const result = await pullSet();
198
+ if (result.done)
199
+ this.push(null);
200
+ else {
201
+ result.value.timings = result.value[0].frames[0].timings;
202
+ result.value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 };
203
+ this.push(result.value);
204
+ }
205
+ })();
206
+ },
207
+ });
208
+
209
+ readStream.pushPkts = (packets, stream, streamIndex, final = false) => {
210
+ if (packets && packets.frames.length) {
211
+ return packets.frames.reduce(async (promise, pkt) => {
212
+ await promise;
213
+ const ts = pkt.pts * stream.time_base[0] / stream.time_base[1];
214
+ pkt.timings = packets.timings;
215
+ return pushPkt(pkt, streamIndex, ts);
216
+ }, Promise.resolve());
217
+ } else if (final) {
218
+ return pushPkt(null, streamIndex, Number.MAX_VALUE);
219
+ }
220
+ };
221
+
222
+ return readStream;
223
+ }
224
+
225
+ function teeBalancer(params, numStreams) {
226
+ let resolvePush = null;
227
+ const pending = [];
228
+ for (let s = 0; s < numStreams; ++s)
229
+ pending.push({ frames: null, resolve: null, final: false });
230
+
231
+ const pullFrame = async index => {
232
+ return new Promise(resolve => {
233
+ if (pending[index].frames) {
234
+ resolve({ value: pending[index].frames, done: false });
235
+ Object.assign(pending[index], { frames: null, resolve: null });
236
+ } else if (pending[index].final)
237
+ resolve({ done: true });
238
+ else
239
+ pending[index].resolve = resolve;
240
+
241
+ if (resolvePush && pending.every(p => null === p.frames)) {
242
+ resolvePush();
243
+ resolvePush = null;
244
+ }
245
+ });
246
+ };
247
+
248
+ const readStreams = [];
249
+ for (let s = 0; s < numStreams; ++s)
250
+ readStreams.push(new Readable({
251
+ objectMode: true,
252
+ highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4,
253
+ read() {
254
+ (async () => {
255
+ const start = process.hrtime();
256
+ const reqTime = start[0] * 1e3 + start[1] / 1e6;
257
+ const result = await pullFrame(s);
258
+ if (result.done)
259
+ this.push(null);
260
+ else {
261
+ result.value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 };
262
+ this.push(result.value);
263
+ }
264
+ })();
265
+ },
266
+ }));
267
+
268
+ readStreams.pushFrames = frames => {
269
+ return new Promise(resolve => {
270
+ pending.forEach((p, index) => {
271
+ if (frames.length)
272
+ p.frames = frames[index].frames;
273
+ else
274
+ p.final = true;
275
+ });
276
+
277
+ pending.forEach(p => {
278
+ if (p.resolve) {
279
+ if (p.frames) {
280
+ p.frames.timings = frames.timings;
281
+ p.resolve({ value: p.frames, done: false });
282
+ } else if (p.final)
283
+ p.resolve({ done: true });
284
+ }
285
+ Object.assign(p, { frames: null, resolve: null });
286
+ });
287
+ resolvePush = resolve;
288
+ });
289
+ };
290
+
291
+ return readStreams;
292
+ }
293
+
294
+ function transformStream(params, processFn, flushFn, reject) {
295
+ return new Transform({
296
+ objectMode: true,
297
+ highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4,
298
+ transform(val, encoding, cb) {
299
+ (async () => {
300
+ const start = process.hrtime();
301
+ const reqTime = start[0] * 1e3 + start[1] / 1e6;
302
+ const result = await processFn(val);
303
+ result.timings = val.timings;
304
+ if (result.timings)
305
+ result.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 };
306
+ cb(null, result);
307
+ })().catch(cb);
308
+ },
309
+ flush(cb) {
310
+ (async () => {
311
+ const result = flushFn ? await flushFn() : null;
312
+ if (result) result.timings = {};
313
+ cb(null, result);
314
+ })().catch(cb);
315
+ }
316
+ }).on('error', err => reject(err));
317
+ }
318
+
319
+ const calcStats = (arr, elem, prop) => {
320
+ const mean = arr.reduce((acc, cur) => cur[elem] ? acc + cur[elem][prop] : acc, 0) / arr.length;
321
+ const stdDev = Math.pow(arr.reduce((acc, cur) => cur[elem] ? acc + Math.pow(cur[elem][prop] - mean, 2) : acc, 0) / arr.length, 0.5);
322
+ const max = arr.reduce((acc, cur) => cur[elem] ? Math.max(cur[elem][prop], acc) : acc, 0);
323
+ const min = arr.reduce((acc, cur) => cur[elem] ? Math.min(cur[elem][prop], acc) : acc, Number.MAX_VALUE);
324
+ return { mean: mean, stdDev: stdDev, max: max, min: min };
325
+ };
326
+
327
+ function writeStream(params, processFn, finalFn, reject) {
328
+ return new Writable({
329
+ objectMode: true,
330
+ highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4,
331
+ write(val, encoding, cb) {
332
+ (async () => {
333
+ const start = process.hrtime();
334
+ const reqTime = start[0] * 1e3 + start[1] / 1e6;
335
+ const result = await processFn(val);
336
+ if ('mux' === params.name) {
337
+ const pktTimings = val.timings;
338
+ pktTimings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 };
339
+ if (doTimings)
340
+ timings.push(pktTimings);
341
+ }
342
+ cb(null, result);
343
+ })().catch(cb);
344
+ },
345
+ final(cb) {
346
+ (async () => {
347
+ const result = finalFn ? await finalFn() : null;
348
+ if (doTimings && ('mux' === params.name)) {
349
+ const elapsedStats = {};
350
+ Object.keys(timings[0]).forEach(k => elapsedStats[k] = calcStats(timings.slice(10, -10), k, 'elapsed'));
351
+ console.log('elapsed:');
352
+ console.table(elapsedStats);
353
+
354
+ const absArr = timings.map(t => {
355
+ const absDelays = {};
356
+ const keys = Object.keys(t);
357
+ keys.forEach((k, i) => absDelays[k] = { reqDelta: i > 0 ? t[k].reqTime - t[keys[i-1]].reqTime : 0 });
358
+ return absDelays;
359
+ });
360
+ const absStats = {};
361
+ Object.keys(absArr[0]).forEach(k => absStats[k] = calcStats(absArr.slice(10, -10), k, 'reqDelta'));
362
+ console.log('request time delta:');
363
+ console.table(absStats);
364
+
365
+ const totalsArr = timings.map(t => {
366
+ const total = (t.mux && t.read) ? t.mux.reqTime - t.read.reqTime + t.mux.elapsed : 0;
367
+ return { total: { total: total }};
368
+ });
369
+ console.log('total time:');
370
+ console.table(calcStats(totalsArr.slice(10, -10), 'total', 'total'));
371
+ }
372
+ cb(null, result);
373
+ })().catch(cb);
374
+ }
375
+ }).on('error', err => reject(err));
376
+ }
377
+
378
+ function readStream(params, demuxer, ms, index) {
379
+ const time_base = demuxer.streams[index].time_base;
380
+ const end_pts = ms ? ms.end * time_base[1] / time_base[0] : Number.MAX_SAFE_INTEGER;
381
+ async function getPacket() {
382
+ let packet = null;
383
+ do { packet = await demuxer.read(); }
384
+ while (packet && packet.stream_index !== index);
385
+ return packet;
386
+ }
387
+
388
+ return new Readable({
389
+ objectMode: true,
390
+ highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4,
391
+ read() {
392
+ (async () => {
393
+ const start = process.hrtime();
394
+ const reqTime = start[0] * 1e3 + start[1] / 1e6;
395
+ const packet = await getPacket();
396
+ if (packet && (packet.pts < end_pts)) {
397
+ packet.timings = {};
398
+ packet.timings.read = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 };
399
+ this.push(packet);
400
+ } else
401
+ this.push(null);
402
+ })();
403
+ }
404
+ });
405
+ }
406
+
407
+ function createBeamWritableStream(params, governor) {
408
+ const beamStream = new Writable({
409
+ highWaterMark: params.highwaterMark || 16384,
410
+ write: (chunk, encoding, cb) => {
411
+ (async () => {
412
+ await governor.write(chunk);
413
+ cb();
414
+ })();
415
+ }
416
+ });
417
+ return beamStream;
418
+ }
419
+
420
+ function demuxerStream(params) {
421
+ const governor = new beamcoder.governor({});
422
+ const stream = createBeamWritableStream(params, governor);
423
+ stream.on('finish', () => governor.finish());
424
+ stream.on('error', console.error);
425
+ stream.demuxer = options => {
426
+ options.governor = governor;
427
+ // delay initialisation of demuxer until stream has been written to - avoids lock-up
428
+ return new Promise(resolve => setTimeout(async () => resolve(await beamcoder.demuxer(options)), 20));
429
+ };
430
+ return stream;
431
+ }
432
+
433
+ function createBeamReadableStream(params, governor) {
434
+ const beamStream = new Readable({
435
+ highWaterMark: params.highwaterMark || 16384,
436
+ read: size => {
437
+ (async () => {
438
+ const chunk = await governor.read(size);
439
+ if (0 === chunk.length)
440
+ beamStream.push(null);
441
+ else
442
+ beamStream.push(chunk);
443
+ })();
444
+ }
445
+ });
446
+ return beamStream;
447
+ }
448
+
449
+ function muxerStream(params) {
450
+ const governor = new beamcoder.governor({ highWaterMark: 1 });
451
+ const stream = createBeamReadableStream(params, governor);
452
+ stream.on('end', () => governor.finish());
453
+ stream.on('error', console.error);
454
+ stream.muxer = options => {
455
+ options.governor = governor;
456
+ return beamcoder.muxer(options);
457
+ };
458
+ return stream;
459
+ }
460
+
461
+ async function makeSources(params) {
462
+ if (!params.video) params.video = [];
463
+ if (!params.audio) params.audio = [];
464
+
465
+ params.video.forEach(p => p.sources.forEach(src => {
466
+ if (src.input_stream) {
467
+ const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 });
468
+ src.input_stream.pipe(demuxerStream);
469
+ src.format = demuxerStream.demuxer({ iformat: src.iformat, options: src.options });
470
+ } else
471
+ src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options });
472
+ }));
473
+ params.audio.forEach(p => p.sources.forEach(src => {
474
+ if (src.input_stream) {
475
+ const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 });
476
+ src.input_stream.pipe(demuxerStream);
477
+ src.format = demuxerStream.demuxer({ iformat: src.iformat, options: src.options });
478
+ } else
479
+ src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options });
480
+ }));
481
+
482
+ await params.video.reduce(async (promise, p) => {
483
+ await promise;
484
+ return p.sources.reduce(async (promise, src) => {
485
+ await promise;
486
+ src.format = await src.format;
487
+ if (src.ms && !src.input_stream)
488
+ src.format.seek({ time: src.ms.start });
489
+ return src.format;
490
+ }, Promise.resolve());
491
+ }, Promise.resolve());
492
+ await params.audio.reduce(async (promise, p) => {
493
+ await promise;
494
+ return p.sources.reduce(async (promise, src) => {
495
+ await promise;
496
+ src.format = await src.format;
497
+ if (src.ms && !src.input_stream)
498
+ src.format.seek({ time: src.ms.start });
499
+ return src.format;
500
+ }, Promise.resolve());
501
+ }, Promise.resolve());
502
+
503
+ params.video.forEach(p => p.sources.forEach(src =>
504
+ src.stream = readStream({ highWaterMark : 1 }, src.format, src.ms, src.streamIndex)));
505
+ params.audio.forEach(p => p.sources.forEach(src =>
506
+ src.stream = readStream({ highWaterMark : 1 }, src.format, src.ms, src.streamIndex)));
507
+ }
508
+
509
+ function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) {
510
+ return new Promise((resolve, reject) => {
511
+ if (!sources.length)
512
+ return resolve();
513
+
514
+ const timeBaseStream = sources[0].format.streams[sources[0].streamIndex];
515
+ const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark : 1 }, streamType, sources.length);
516
+
517
+ sources.forEach((src, srcIndex) => {
518
+ const decStream = transformStream({ name: 'decode', highWaterMark : 1 },
519
+ pkts => src.decoder.decode(pkts), () => src.decoder.flush(), reject);
520
+ const filterSource = writeStream({ name: 'filterSource', highWaterMark : 1 },
521
+ pkts => filterBalancer.pushPkts(pkts, src.format.streams[src.streamIndex], srcIndex),
522
+ () => filterBalancer.pushPkts(null, src.format.streams[src.streamIndex], srcIndex, true), reject);
523
+
524
+ src.stream.pipe(decStream).pipe(filterSource);
525
+ });
526
+
527
+ const streamTee = teeBalancer({ name: 'streamTee', highWaterMark : 1 }, streams.length);
528
+ const filtStream = transformStream({ name: 'filter', highWaterMark : 1 }, frms => {
529
+ if (filterer.cb) filterer.cb(frms[0].frames[0].pts);
530
+ return filterer.filter(frms);
531
+ }, () => {}, reject);
532
+ const streamSource = writeStream({ name: 'streamSource', highWaterMark : 1 },
533
+ frms => streamTee.pushFrames(frms), () => streamTee.pushFrames([], true), reject);
534
+
535
+ filterBalancer.pipe(filtStream).pipe(streamSource);
536
+
537
+ streams.forEach((str, i) => {
538
+ const dicer = new frameDicer(str.encoder, 'audio' === streamType);
539
+ const diceStream = transformStream({ name: 'dice', highWaterMark : 1 },
540
+ frms => dicer.dice(frms), () => dicer.dice([], true), reject);
541
+ const encStream = transformStream({ name: 'encode', highWaterMark : 1 },
542
+ frms => str.encoder.encode(frms), () => str.encoder.flush(), reject);
543
+ const muxStream = writeStream({ name: 'mux', highWaterMark : 1 },
544
+ pkts => muxBalancer.writePkts(pkts, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)),
545
+ () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), reject);
546
+ muxStream.on('finish', resolve);
547
+
548
+ streamTee[i].pipe(diceStream).pipe(encStream).pipe(muxStream);
549
+ });
550
+ });
551
+ }
552
+
553
+ async function makeStreams(params) {
554
+ params.video.forEach(p => {
555
+ p.sources.forEach(src =>
556
+ src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex }));
557
+ });
558
+ params.audio.forEach(p => {
559
+ p.sources.forEach(src =>
560
+ src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex }));
561
+ });
562
+
563
+ params.video.forEach(p => {
564
+ p.filter = beamcoder.filterer({
565
+ filterType: 'video',
566
+ inputParams: p.sources.map((src, i) => {
567
+ const stream = src.format.streams[src.streamIndex];
568
+ return {
569
+ name: `in${i}:v`,
570
+ width: stream.codecpar.width,
571
+ height: stream.codecpar.height,
572
+ pixelFormat: stream.codecpar.format,
573
+ timeBase: stream.time_base,
574
+ pixelAspect: stream.sample_aspect_ratio };
575
+ }),
576
+ outputParams: p.streams.map((str, i) => { return { name: `out${i}:v`, pixelFormat: str.codecpar.format }; }),
577
+ filterSpec: p.filterSpec });
578
+ });
579
+ const vidFilts = await Promise.all(params.video.map(p => p.filter));
580
+ params.video.forEach((p, i) => p.filter = vidFilts[i]);
581
+ // params.video.forEach(p => console.log(p.filter.graph.dump()));
582
+
583
+ params.audio.forEach(p => {
584
+ p.filter = beamcoder.filterer({
585
+ filterType: 'audio',
586
+ inputParams: p.sources.map((src, i) => {
587
+ const stream = src.format.streams[src.streamIndex];
588
+ return {
589
+ name: `in${i}:a`,
590
+ sampleRate: src.decoder.sample_rate,
591
+ sampleFormat: src.decoder.sample_fmt,
592
+ channelLayout: src.decoder.channel_layout,
593
+ timeBase: stream.time_base };
594
+ }),
595
+ outputParams: p.streams.map((str, i) => {
596
+ return {
597
+ name: `out${i}:a`,
598
+ sampleRate: str.codecpar.sample_rate,
599
+ sampleFormat: str.codecpar.format,
600
+ channelLayout: str.codecpar.channel_layout }; }),
601
+ filterSpec: p.filterSpec });
602
+ });
603
+ const audFilts = await Promise.all(params.audio.map(p => p.filter));
604
+ params.audio.forEach((p, i) => p.filter = audFilts[i]);
605
+ // params.audio.forEach(p => console.log(p.filter.graph.dump()));
606
+
607
+ let mux;
608
+ if (params.out.output_stream) {
609
+ let muxerStream = beamcoder.muxerStream({ highwaterMark: 1024 });
610
+ muxerStream.pipe(params.out.output_stream);
611
+ mux = muxerStream.muxer({ format_name: params.out.formatName });
612
+ } else
613
+ mux = beamcoder.muxer({ format_name: params.out.formatName });
614
+
615
+ params.video.forEach(p => {
616
+ p.streams.forEach((str, i) => {
617
+ const encParams = p.filter.graph.filters.find(f => f.name === `out${i}:v`).inputs[0];
618
+ str.encoder = beamcoder.encoder({
619
+ name: str.name,
620
+ width: encParams.w,
621
+ height: encParams.h,
622
+ pix_fmt: encParams.format,
623
+ sample_aspect_ratio: encParams.sample_aspect_ratio,
624
+ time_base: encParams.time_base,
625
+ // framerate: [encParams.time_base[1], encParams.time_base[0]],
626
+ // bit_rate: 2000000,
627
+ // gop_size: 10,
628
+ // max_b_frames: 1,
629
+ // priv_data: { preset: 'slow' }
630
+ priv_data: { crf: 23 } }); // ... more required ...
631
+ });
632
+ });
633
+
634
+ params.audio.forEach(p => {
635
+ p.streams.forEach((str, i) => {
636
+ const encParams = p.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0];
637
+ str.encoder = beamcoder.encoder({
638
+ name: str.name,
639
+ sample_fmt: encParams.format,
640
+ sample_rate: encParams.sample_rate,
641
+ channel_layout: encParams.channel_layout,
642
+ flags: { GLOBAL_HEADER: mux.oformat.flags.GLOBALHEADER } });
643
+
644
+ str.codecpar.frame_size = str.encoder.frame_size;
645
+ });
646
+ });
647
+
648
+ params.video.forEach(p => {
649
+ p.streams.forEach(str => {
650
+ str.stream = mux.newStream({
651
+ name: str.name,
652
+ time_base: str.time_base,
653
+ interleaved: true }); // Set to false for manual interleaving, true for automatic
654
+ Object.assign(str.stream.codecpar, str.codecpar);
655
+ });
656
+ });
657
+
658
+ params.audio.forEach(p => {
659
+ p.streams.forEach(str => {
660
+ str.stream = mux.newStream({
661
+ name: str.name,
662
+ time_base: str.time_base,
663
+ interleaved: true }); // Set to false for manual interleaving, true for automatic
664
+ Object.assign(str.stream.codecpar, str.codecpar);
665
+ });
666
+ });
667
+
668
+ return {
669
+ run: async () => {
670
+ await mux.openIO({
671
+ url: params.out.url ? params.out.url : '',
672
+ flags: params.out.flags ? params.out.flags : {}
673
+ });
674
+ await mux.writeHeader({ options: params.out.options ? params.out.options : {} });
675
+
676
+ const muxBalancer = new serialBalancer(mux.streams.length);
677
+ const muxStreamPromises = [];
678
+ params.video.forEach(p => muxStreamPromises.push(runStreams('video', p.sources, p.filter, p.streams, mux, muxBalancer)));
679
+ params.audio.forEach(p => muxStreamPromises.push(runStreams('audio', p.sources, p.filter, p.streams, mux, muxBalancer)));
680
+ await Promise.all(muxStreamPromises);
681
+
682
+ await mux.writeTrailer();
683
+ }
684
+ };
685
+ }
686
+
687
+ module.exports = {
688
+ demuxerStream,
689
+ muxerStream,
690
+ makeSources,
691
+ makeStreams
692
+ };