@mediabunny/server 1.53.1 → 1.55.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mediabunny/server",
3
3
  "author": "Vanilagy",
4
- "version": "1.53.1",
4
+ "version": "1.55.1",
5
5
  "description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
6
6
  "main": "./dist/bundles/mediabunny-server.cjs",
7
7
  "module": "./dist/bundles/mediabunny-server.mjs",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "node-av": "^6.0.0",
38
- "@mediabunny/prores": "^1.53.1"
38
+ "@mediabunny/prores": "^1.55.1"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "mediabunny": "^1.45.0"
@@ -25,7 +25,8 @@ export class NodeAvAudioDecoder extends CustomAudioDecoder {
25
25
  || codec === 'vorbis'
26
26
  || codec === 'flac'
27
27
  || codec === 'ac3'
28
- || codec === 'eac3';
28
+ || codec === 'eac3'
29
+ || codec === 'dts';
29
30
  }
30
31
 
31
32
  async init(): Promise<void> {
@@ -15,7 +15,7 @@ import {
15
15
  EncodedPacket,
16
16
  } from 'mediabunny';
17
17
  import * as NodeAv from 'node-av';
18
- import { CODEC_TO_CODEC_ID, getChannelLayout } from './misc';
18
+ import { CODEC_TO_CODEC_ID, getChannelLayout, getDtsChannelLayout } from './misc';
19
19
  import { assert, toUint8Array } from '../../../src/misc';
20
20
  import { copyAudioSampleToAvFrame, AvFrameAudioSampleResource } from './audio-sample';
21
21
  import {
@@ -23,6 +23,7 @@ import {
23
23
  buildAdtsHeaderTemplate,
24
24
  parseAacAudioSpecificConfig,
25
25
  } from '../../../shared/aac-misc';
26
+ import { DTS_CHANNEL_COUNTS, DTS_SAMPLE_RATES, dtsBitrateFits } from '../../../shared/dts-misc';
26
27
 
27
28
  const AAC_SAMPLE_RATES
28
29
  = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
@@ -72,6 +73,10 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
72
73
  ) || (
73
74
  codec === 'eac3' && numberOfChannels >= 1 && numberOfChannels <= 16
74
75
  && AC3_SAMPLE_RATES.includes(sampleRate)
76
+ ) || (
77
+ codec === 'dts' && DTS_CHANNEL_COUNTS.includes(numberOfChannels)
78
+ && DTS_SAMPLE_RATES.includes(sampleRate)
79
+ && dtsBitrateFits(resolveBitrate(config, codec), sampleRate, numberOfChannels)
75
80
  );
76
81
  }
77
82
 
@@ -107,21 +112,26 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
107
112
  }
108
113
 
109
114
  codecContext.sampleRate = this.config.sampleRate;
110
- codecContext.channelLayout = getChannelLayout(this.config.numberOfChannels);
115
+ codecContext.channelLayout = this.codec === 'dts'
116
+ ? getDtsChannelLayout(this.config.numberOfChannels)
117
+ : getChannelLayout(this.config.numberOfChannels);
111
118
  codecContext.codecType = NodeAv.AVMEDIA_TYPE_AUDIO;
112
119
  codecContext.codecId = CODEC_TO_CODEC_ID[this.codec]!;
113
120
  codecContext.sampleFormat = sampleFormat;
114
121
  codecContext.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
115
- codecContext.bitRate = BigInt(
116
- this.config.bitrate ?? new Quality('medium')._toAudioBitrate(this.codec) ?? 0,
117
- );
122
+ codecContext.bitRate = BigInt(resolveBitrate(this.config, this.codec));
118
123
 
119
124
  if (this.config.bitrateMode === 'constant') {
120
125
  codecContext.rcMinRate = codecContext.bitRate;
121
126
  codecContext.rcMaxRate = codecContext.bitRate;
122
127
  }
123
128
 
124
- const ret = await codecContext.open2();
129
+ // libav's DTS encoder is marked experimental, so it refuses to open at the default compliance level
130
+ const options = this.codec === 'dts'
131
+ ? NodeAv.Dictionary.fromObject({ strict: -2 })
132
+ : null;
133
+
134
+ const ret = await codecContext.open2(this.avCodec, options);
125
135
  NodeAv.FFmpegError.throwIfError(ret, 'Open codec context');
126
136
 
127
137
  this.codecContext = codecContext;
@@ -172,7 +182,9 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
172
182
  this.resampler = new NodeAv.SoftwareResampleContext();
173
183
  this.resamplerInputSampleRate = this.frame.sampleRate;
174
184
 
175
- const outLayout = getChannelLayout(this.codecContext.channels);
185
+ // Resample straight to whatever layout the encoder was opened with, since not every codec accepts
186
+ // the layout that getChannelLayout hands out for a given channel count
187
+ const outLayout = this.codecContext.channelLayout;
176
188
  const inLayout = getChannelLayout(this.frame.channels);
177
189
 
178
190
  const ret = this.resampler.allocSetOpts2(
@@ -400,3 +412,7 @@ export class NodeAvAudioEncoder extends CustomAudioEncoder {
400
412
  this.resampler?.free();
401
413
  }
402
414
  }
415
+
416
+ const resolveBitrate = (config: AudioEncoderConfig, codec: AudioCodec) => {
417
+ return config.bitrate ?? new Quality('medium')._toAudioBitrate(codec) ?? 0;
418
+ };
package/src/misc.ts CHANGED
@@ -25,6 +25,7 @@ export const CODEC_TO_CODEC_ID: Partial<Record<MediaCodec, NodeAv.AVCodecID>> =
25
25
  flac: NodeAv.AV_CODEC_ID_FLAC,
26
26
  ac3: NodeAv.AV_CODEC_ID_AC3,
27
27
  eac3: NodeAv.AV_CODEC_ID_EAC3,
28
+ dts: NodeAv.AV_CODEC_ID_DTS,
28
29
  };
29
30
 
30
31
  let cachedHardwareContext: NodeAv.HardwareContext | null | undefined = undefined;
@@ -267,3 +268,21 @@ export const getChannelLayout = (numChannels: number): NodeAv.ChannelLayout => {
267
268
  default: return { nbChannels: numChannels, order: NodeAv.AV_CHANNEL_ORDER_UNSPEC, mask: 0n };
268
269
  }
269
270
  };
271
+
272
+ /** FL + FR + SL + SR, which libav has no constant for. */
273
+ const QUAD_SIDE_MASK = 0x603n;
274
+
275
+ /**
276
+ * DTS insists on the side-based surround layouts and rejects the back-based ones that {@link getChannelLayout}
277
+ * hands out, so it gets its own mapping.
278
+ */
279
+ export const getDtsChannelLayout = (numChannels: number): NodeAv.ChannelLayout => {
280
+ switch (numChannels) {
281
+ case 1: return NodeAv.AV_CHANNEL_LAYOUT_MONO;
282
+ case 2: return NodeAv.AV_CHANNEL_LAYOUT_STEREO;
283
+ case 4: return { nbChannels: 4, order: NodeAv.AV_CHANNEL_ORDER_NATIVE, mask: QUAD_SIDE_MASK };
284
+ case 5: return NodeAv.AV_CHANNEL_LAYOUT_5POINT0;
285
+ case 6: return NodeAv.AV_CHANNEL_LAYOUT_5POINT1;
286
+ default: return { nbChannels: numChannels, order: NodeAv.AV_CHANNEL_ORDER_UNSPEC, mask: 0n };
287
+ }
288
+ };