mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-10 09:13:47 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b717cbf68c | ||
|
|
7cc26edacb | ||
|
|
de9a4bbc23 | ||
|
|
4fae45ec40 | ||
|
|
417c85d8ed | ||
|
|
612642c5ad | ||
|
|
2298f4a8b3 | ||
|
|
a613a870c8 | ||
|
|
b61b2b326f | ||
|
|
cacbc2a399 |
+7
-4
@@ -24,7 +24,7 @@
|
||||
chunked: true,
|
||||
chunkSize: 2**20
|
||||
});
|
||||
const outputFormat = new Mediabunny.Mp4OutputFormat({});
|
||||
const outputFormat = new Mediabunny.WavOutputFormat({});
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.textContent = 'Cancel';
|
||||
@@ -100,8 +100,9 @@
|
||||
},
|
||||
*/
|
||||
video: () => ({
|
||||
forceTranscode: true,
|
||||
allowRotationMetadata: false,
|
||||
width: 320,
|
||||
//forceTranscode: true,
|
||||
//allowRotationMetadata: false,
|
||||
//width: 720,
|
||||
//frameRate: 30,
|
||||
//bitrate: Mediabunny.QUALITY_VERY_LOW,
|
||||
@@ -181,7 +182,9 @@
|
||||
}
|
||||
},
|
||||
trim: {
|
||||
start: 0,
|
||||
start: -2,
|
||||
end: 5,
|
||||
//start: 0,
|
||||
//end: 4
|
||||
},
|
||||
});
|
||||
|
||||
@@ -42,6 +42,12 @@ export default withMermaid({
|
||||
{ text: 'Examples', link: '/examples', activeMatch: '/examples' },
|
||||
{ text: 'Sponsors', link: '/#sponsors', activeMatch: '/#sponsors' },
|
||||
{ text: 'License', link: 'https://github.com/Vanilagy/mediabunny#license' },
|
||||
{
|
||||
text: 'More',
|
||||
items: [
|
||||
{ text: 'Codec Registry', link: '/codec-registry/overview' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
@@ -93,6 +99,40 @@ export default withMermaid({
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
||||
'/api': apiRoutes as any,
|
||||
|
||||
'/codec-registry': [
|
||||
{
|
||||
text: 'Codec registry',
|
||||
items: [
|
||||
{ text: 'Overview', link: '/codec-registry/overview' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Video',
|
||||
items: [
|
||||
{ text: 'AVC (H.264)', link: '/codec-registry/avc' },
|
||||
{ text: 'HEVC (H.265)', link: '/codec-registry/hevc' },
|
||||
{ text: 'VP8', link: '/codec-registry/vp8' },
|
||||
{ text: 'VP9', link: '/codec-registry/vp9' },
|
||||
{ text: 'AV1', link: '/codec-registry/av1' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Audio',
|
||||
items: [
|
||||
{ text: 'AAC', link: '/codec-registry/aac' },
|
||||
{ text: 'Opus', link: '/codec-registry/opus' },
|
||||
{ text: 'MP3', link: '/codec-registry/mp3' },
|
||||
{ text: 'Vorbis', link: '/codec-registry/vorbis' },
|
||||
{ text: 'FLAC', link: '/codec-registry/flac' },
|
||||
{ text: 'AC-3', link: '/codec-registry/ac3' },
|
||||
{ text: 'E-AC-3', link: '/codec-registry/eac3' },
|
||||
{ text: 'Linear PCM', link: '/codec-registry/pcm' },
|
||||
{ text: 'μ-law PCM', link: '/codec-registry/ulaw' },
|
||||
{ text: 'A-law PCM', link: '/codec-registry/alaw' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
socialLinks: [
|
||||
|
||||
+53
-45
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 141 KiB |
@@ -0,0 +1,50 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# AAC codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The Advanced Audio Coding (AAC) audio codec, specified in [ISO/IEC 14496-3](https://www.iso.org/standard/76383.html).
|
||||
|
||||
An AAC bitstream can have either of two formats:
|
||||
- _AAC_ (raw), where packets contain raw AAC frames (syntax element `raw_data_block()`) as defined in [ISO/IEC 14496-3](https://www.iso.org/standard/76383.html) Section 4.4.2.1. Here, codec metadata is provided out-of-band.
|
||||
- _ADTS_, where packets contain ADTS frames as defined in [ISO/IEC 14496-3](https://www.iso.org/standard/76383.html) Section 1.A.3.2. Here, codec metadata is provided in-band in each ADTS frame header.
|
||||
|
||||
All packets within the bitstream must have the same format.
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'aac'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
If the bitstream is in the _AAC_ (raw) format, the packet's data must be a raw AAC frame (syntax element `raw_data_block()`) as defined in [ISO/IEC 14496-3](https://www.iso.org/standard/76383.html) Section 4.4.2.1.
|
||||
|
||||
If the bitstream is in the _ADTS_ format, the packet's data must be an ADTS frame as defined in [ISO/IEC 14496-3](https://www.iso.org/standard/76383.html) Section 1.A.3.2.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
The following fully qualified codec strings are recognized:
|
||||
|
||||
- `'mp4a.40.2'` — MPEG-4 AAC-LC
|
||||
- `'mp4a.40.02'` — MPEG-4 AAC-LC (leading zero for Aud-OTI compatibility)
|
||||
- `'mp4a.40.5'` — MPEG-4 HE-AAC v1 (AAC-LC + SBR)
|
||||
- `'mp4a.40.05'` — MPEG-4 HE-AAC v1 (leading zero for Aud-OTI compatibility)
|
||||
- `'mp4a.40.29'` — MPEG-4 HE-AAC v2 (AAC-LC + SBR + PS)
|
||||
- `'mp4a.67'` — MPEG-2 AAC-LC
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
If the bitstream is in the _AAC_ (raw) format, `description` must be an `AudioSpecificConfig` as defined in [ISO/IEC 14496-3](https://www.iso.org/standard/76383.html) Section 1.6.2.1.
|
||||
|
||||
If the bitstream is in the _ADTS_ format, `description` must be undefined.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# AC-3 codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The Dolby Digital (AC-3) audio codec, specified in [ETSI TS 102 366](https://www.etsi.org/deliver/etsi_ts/102300_102399/102366/01.04.01_60/ts_102366v010401p.pdf).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'ac3'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a sync frame (syntactic element `syncframe()`) as defined in Section 4.3 of [ETSI TS 102 366](https://www.etsi.org/deliver/etsi_ts/102300_102399/102366/01.04.01_60/ts_102366v010401p.pdf), beginning with the sync word `0x0B77`.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'ac-3'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# A-law PCM codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The A-law companded PCM audio codec, specified in [ITU-T G.711](https://www.itu.int/rec/T-REC-G.711) Tables 1a and 1b.
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'alaw'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a sequence of bytes of arbitrary length (divisible by the channel count), where each byte is an A-law encoded PCM sample. For multichannel audio, samples from different channels are interleaved.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'alaw'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Video codec" />
|
||||
|
||||
# AV1 codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The AOMedia Video 1 (AV1) video codec, specified in the [AV1 Bitstream & Decoding Process Specification](https://aomediacodec.github.io/av1-spec/).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'av1'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must comply with the low-overhead bitstream format as defined in Section 5 of the [AV1 Bitstream & Decoding Process Specification](https://aomediacodec.github.io/av1-spec/).
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
If the packet's type is `'key'`, then the packet is expected to contain a frame with `frame_type` of `KEY_FRAME`, as defined in Section 6.8.2 of the [AV1 Bitstream & Decoding Process Specification](https://aomediacodec.github.io/av1-spec/).
|
||||
|
||||
## `VideoDecoderConfig` codec string
|
||||
|
||||
The full codec string begins with the prefix `'av01.'`, with a variable-length suffix as specified in Section 5 of the [AV1 Codec ISO Media File Format Binding](https://aomediacodec.github.io/av1-isobmff/).
|
||||
|
||||
## `VideoDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Video codec" />
|
||||
|
||||
# AVC (H.264) codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The Advanced Video Coding (H.264) video codec, specified in [Rec. ITU-T H.264](https://www.itu.int/rec/T-REC-H.264) / [ISO/IEC 14496-10](https://www.iso.org/standard/87574.html).
|
||||
|
||||
An AVC bitstream can have either of two formats:
|
||||
- _Canonical_ (length-prefixed), as defined in [ISO/IEC 14496-15](https://www.iso.org/standard/89118.html) Section 5.3.2. Here, video parameter sets (SPS/PPS) are provided out-of-band.
|
||||
- _Annex B_, as defined in [Rec. ITU-T H.264](https://www.itu.int/rec/T-REC-H.264) Annex B. Here, video parameter sets (SPS/PPS) must be provided in-band in the respective NALUs.
|
||||
|
||||
All packets within the bitstream must have the same format.
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'avc'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be an access unit as defined in [Rec. ITU-T H.264](https://www.itu.int/rec/T-REC-H.264) Section 7.4.1.2, in either _canonical_ or _Annex B_ format.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
If the packet's type is `'key'`, then the packet is expected to contain a primary coded picture from which decoding can begin. Additionally, if the bitstream's format is _Annex B_, then this packet is also expected to contain the necessary video parameter sets to initialize the decoder.
|
||||
|
||||
## `VideoDecoderConfig` codec string
|
||||
|
||||
The full codec string begins with the prefix `'avc1.'` or `'avc3.'`, with a suffix of 6 characters as described respectively in Section 3.4 of [RFC 6381](https://www.rfc-editor.org/rfc/rfc6381) and Section 5.4.1 of [ISO/IEC 14496-15](https://www.iso.org/standard/89118.html).
|
||||
|
||||
## `VideoDecoderConfig` description
|
||||
|
||||
If the bitstream is in the _canonical_ (length-prefixed) format, `description` must be an `AVCDecoderConfigurationRecord` as defined in [ISO/IEC 14496-15](https://www.iso.org/standard/89118.html) Section 5.3.3.1.
|
||||
|
||||
If the bitstream is in the _Annex B_ format, `description` must be undefined.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# E-AC-3 codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The Dolby Digital Plus (E-AC-3) audio codec, specified in [ETSI TS 102 366](https://www.etsi.org/deliver/etsi_ts/102300_102399/102366/01.04.01_60/ts_102366v010401p.pdf).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'eac3'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a sync frame as defined in Section E.1.2.0 of [ETSI TS 102 366](https://www.etsi.org/deliver/etsi_ts/102300_102399/102366/01.04.01_60/ts_102366v010401p.pdf), beginning with the sync word `0x0B77`.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'ec-3'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# FLAC codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The Free Lossless Audio Codec (FLAC), specified in the [FLAC Format Specification](https://xiph.org/flac/format.html).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'flac'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a FLAC frame as described in the [FLAC Format Specification](https://xiph.org/flac/format.html).
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'flac'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` must contain the bytes `0x66 0x4C 0x61 0x43` (the ASCII string `'fLaC'`), followed by a `STREAMINFO` metadata block as defined in Section 7 of the [FLAC Format Specification](https://xiph.org/flac/format.html).
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Video codec" />
|
||||
|
||||
# HEVC (H.265) codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The High Efficiency Video Coding (H.265) video codec, specified in [Rec. ITU-T H.265](https://www.itu.int/rec/T-REC-H.265) / [ISO/IEC 23008-2](https://www.iso.org/standard/75484.html).
|
||||
|
||||
An HEVC bitstream can have either of two formats:
|
||||
- _Canonical_ (length-prefixed), as defined in [ISO/IEC 14496-15](https://www.iso.org/standard/89118.html) Section 8.3.2. Here, video parameter sets (VPS/SPS/PPS) are provided out-of-band.
|
||||
- _Annex B_, as defined in [Rec. ITU-T H.265](https://www.itu.int/rec/T-REC-H.265) Annex B. Here, video parameter sets (VPS/SPS/PPS) must be provided in-band in the respective NALUs.
|
||||
|
||||
All packets within the bitstream must have the same format.
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'hevc'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be an access unit as defined in [Rec. ITU-T H.265](https://www.itu.int/rec/T-REC-H.265) Section 7.4.2.4, containing exactly one base layer coded picture, in either _canonical_ or _Annex B_ format.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
If the packet's type is `'key'`, then the packet is expected to contain an IDR, CRA, or BLA picture. Additionally, if the bitstream's format is _Annex B_, then this packet is also expected to contain the necessary video parameter sets to initialize the decoder.
|
||||
|
||||
## `VideoDecoderConfig` codec string
|
||||
|
||||
The full codec string begins with the prefix `'hev1.'` or `'hvc1.'`, with a variable-length suffix of four dot-separated fields as specified in Section E.3 of [ISO/IEC 14496-15](https://www.iso.org/standard/89118.html).
|
||||
|
||||
## `VideoDecoderConfig` description
|
||||
|
||||
If the bitstream is in the _canonical_ (length-prefixed) format, `description` must be an `HEVCDecoderConfigurationRecord` as defined in [ISO/IEC 14496-15](https://www.iso.org/standard/89118.html) Section 8.3.3.1.
|
||||
|
||||
If the bitstream is in the _Annex B_ format, `description` must be undefined.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# MP3 codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The MP3 audio codec (MPEG-1/2 Audio Layer III), specified in [ISO/IEC 13818-3](https://www.iso.org/standard/26797.html).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'mp3'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be an MP3 frame as described in Section 2.4.2.2 of [ISO/IEC 13818-3](https://www.iso.org/standard/26797.html).
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'mp3'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# Opus codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The Opus audio codec, specified in [RFC 6716](https://www.rfc-editor.org/rfc/rfc6716).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'opus'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be an Opus packet as described in Section 3 of [RFC 6716](https://www.rfc-editor.org/rfc/rfc6716).
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'opus'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
If present, `description` must be an Identification Header as defined in Section 5.1 of [RFC 7845](https://www.rfc-editor.org/rfc/rfc7845).
|
||||
@@ -0,0 +1,26 @@
|
||||
# Mediabunny Codec Registry
|
||||
|
||||
The Mediabunny Codec Registry formalizes the precise definitions of all video and audio codecs supported by Mediabunny. More specifically, for any given codec, it describes the format that `EncodedPacket`, `VideoDecoderConfig` and `AudioDecoderConfig` must adhere to. All packets coming out of or going into Mediabunny are expected to adhere to this registry.
|
||||
|
||||
The registry is an extension of the [WebCodecs Codec Registry](https://www.w3.org/TR/webcodecs-codec-registry/). Mediabunny's registry matches that of WebCodecs for all codecs supported by both.
|
||||
|
||||
## Video codecs
|
||||
|
||||
- [AVC (H.264)](./avc)
|
||||
- [HEVC (H.265)](./hevc)
|
||||
- [VP8](./vp8)
|
||||
- [VP9](./vp9)
|
||||
- [AV1](./av1)
|
||||
|
||||
## Audio codecs
|
||||
|
||||
- [AAC](./aac)
|
||||
- [Opus](./opus)
|
||||
- [MP3](./mp3)
|
||||
- [Vorbis](./vorbis)
|
||||
- [FLAC](./flac)
|
||||
- [AC-3](./ac3)
|
||||
- [E-AC-3](./eac3)
|
||||
- [Linear PCM](./pcm)
|
||||
- [μ-law PCM](./ulaw)
|
||||
- [A-law PCM](./alaw)
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# Linear PCM codecs registration
|
||||
|
||||
## Description
|
||||
|
||||
A family of linear pulse-code modulation (PCM) audio codecs of various bit depths and byte orders.
|
||||
|
||||
## Codec IDs
|
||||
|
||||
| Codec ID | Description |
|
||||
| --- | --- |
|
||||
| `'pcm-u8'` | Unsigned 8-bit integer |
|
||||
| `'pcm-s8'` | Signed 8-bit integer |
|
||||
| `'pcm-s16'` | Signed 16-bit integer, little-endian |
|
||||
| `'pcm-s16be'` | Signed 16-bit integer, big-endian |
|
||||
| `'pcm-s24'` | Signed 24-bit integer, little-endian |
|
||||
| `'pcm-s24be'` | Signed 24-bit integer, big-endian |
|
||||
| `'pcm-s32'` | Signed 32-bit integer, little-endian |
|
||||
| `'pcm-s32be'` | Signed 32-bit integer, big-endian |
|
||||
| `'pcm-f32'` | 32-bit float, little-endian |
|
||||
| `'pcm-f32be'` | 32-bit float, big-endian |
|
||||
| `'pcm-f64'` | 64-bit float, little-endian |
|
||||
| `'pcm-f64be'` | 64-bit float, big-endian |
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a sequence of bytes of arbitrary length (divisible by the size of one frame), with each sample occupying the number of bits defined by the codec ID. For multichannel audio, samples from different channels are interleaved.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
The codec string is the same as the codec ID (e.g. `'pcm-s16'`, `'pcm-f32'`).
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# μ-law PCM codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The μ-law companded PCM audio codec, specified in [ITU-T G.711](https://www.itu.int/rec/T-REC-G.711) Tables 2a and 2b.
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'ulaw'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a sequence of bytes of arbitrary length (divisible by the channel count), where each byte is a μ-law encoded PCM sample. For multichannel audio, samples from different channels are interleaved.
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'ulaw'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Audio codec" />
|
||||
|
||||
# Vorbis codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The Vorbis audio codec, specified in the [Vorbis I Specification](https://xiph.org/vorbis/doc/Vorbis_I_spec.html).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'vorbis'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be an audio packet as described in Section 4.3 of the [Vorbis I Specification](https://xiph.org/vorbis/doc/Vorbis_I_spec.html).
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
The packet's type is always `'key'`.
|
||||
|
||||
## `AudioDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'vorbis'
|
||||
```
|
||||
|
||||
## `AudioDecoderConfig` description
|
||||
|
||||
`description` must contain Vorbis codec setup data in Xiph extradata format: the `page_segments` field, followed by the `segment_table` field, followed by the three Vorbis header packets (identification header, comments header, and setup header) as defined in Section 4.2 of the [Vorbis I Specification](https://xiph.org/vorbis/doc/Vorbis_I_spec.html).
|
||||
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Video codec" />
|
||||
|
||||
# VP8 codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The VP8 video codec, specified in [RFC 6386](https://www.rfc-editor.org/rfc/rfc6386).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'vp8'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a frame as described in Section 4 and Annex A of [RFC 6386](https://www.rfc-editor.org/rfc/rfc6386).
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
If the packet's type is `'key'`, then the packet is expected to contain a frame where `key_frame` is `true`, as defined in Section 19.1 of [RFC 6386](https://www.rfc-editor.org/rfc/rfc6386).
|
||||
|
||||
## `VideoDecoderConfig` codec string
|
||||
|
||||
```ts
|
||||
'vp8'
|
||||
```
|
||||
|
||||
## `VideoDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import { VPBadge } from 'vitepress/theme'
|
||||
</script>
|
||||
|
||||
<VPBadge type="info" text="Video codec" />
|
||||
|
||||
# VP9 codec registration
|
||||
|
||||
## Description
|
||||
|
||||
The VP9 video codec, specified in the [VP9 Bitstream & Decoding Process Specification](https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf).
|
||||
|
||||
## Codec ID
|
||||
|
||||
```ts
|
||||
'vp9'
|
||||
```
|
||||
|
||||
## `EncodedPacket` data
|
||||
|
||||
The packet's data must be a frame as described in Section 6 of the [VP9 Bitstream & Decoding Process Specification](https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf).
|
||||
|
||||
## `EncodedPacket` type
|
||||
|
||||
If the packet's type is `'key'`, then the packet is expected to contain a frame with `frame_type` of `KEY_FRAME`, as defined in Section 7.2 of the [VP9 Bitstream & Decoding Process Specification](https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf).
|
||||
|
||||
## `VideoDecoderConfig` codec string
|
||||
|
||||
The full codec string begins with the prefix `'vp09.'`, with a variable-length suffix as specified in the [VP Codec ISO Media File Format Binding](https://www.webmproject.org/vp9/mp4/).
|
||||
|
||||
## `VideoDecoderConfig` description
|
||||
|
||||
`description` is not used for this codec.
|
||||
@@ -367,6 +367,19 @@ If only `start` is set, the clip will run until the end of the input file. If on
|
||||
|
||||
Note that when using the trimming defaults, the resulting media file will always begin at timestamp 0. If your input file has a start time offset (like is common with MPEG-TS files) and you want to retain that, use `trim: { start: 0 }` to ensure timestamps don't get shifted.
|
||||
|
||||
---
|
||||
|
||||
You can even use negative trimming values to offset the start of the media:
|
||||
```ts
|
||||
const conversion = await Conversion.init({
|
||||
// ...
|
||||
trim: {
|
||||
start: -2, // Two seconds of no media data (freeze frame / silence) at the start
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Metadata tags
|
||||
|
||||
By default, any [descriptive metadata tags](../api/MetadataTags.md) of the input will be copied to the output. If you want to further control the metadata tags written to the output, you can use the `tags` options:
|
||||
|
||||
@@ -74,7 +74,7 @@ type VideoEncodingConfig = {
|
||||
- `bitrateMode`: Can be used to control constant vs. variable bitrate.
|
||||
- `latencyMode`: The latency mode as specified by the WebCodecs API. Browsers default to `quality`. Media stream-driven video sources will automatically use the `realtime` setting.
|
||||
- `keyFrameInterval`: The maximum interval in seconds between two adjacent key frames. Defaults to 5 seconds. More frequent key frames improve seeking behavior but increase file size. When using multiple video tracks, this value should be set to the same value for all tracks.
|
||||
- `fullCodecString`: Allows you to optionally specify the full codec string used by the video encoder, as specified in the [WebCodecs Codec Registry](https://www.w3.org/TR/webcodecs-codec-registry/). For example, you may set it to `'avc1.42001f'` when using AVC. Keep in mind that the codec string must still match the codec specified in `codec`. If you don't set this field, a codec string will be generated automatically.
|
||||
- `fullCodecString`: Allows you to optionally specify the full codec string used by the video encoder, as specified in the [Mediabunny Codec Registry](/codec-registry/overview). For example, you may set it to `'avc1.42001f'` when using AVC. Keep in mind that the codec string must still match the codec specified in `codec`. If you don't set this field, a codec string will be generated automatically.
|
||||
- `hardwareAcceleration`: A hint that configures the hardware acceleration method of this codec. This is best left on `'no-preference'`.
|
||||
- `scalabilityMode`: An encoding scalability mode identifier as defined by [WebRTC-SVC](https://w3c.github.io/webrtc-svc/#scalabilitymodes*).
|
||||
- `contentHint`: An encoding video content hint as defined by [mst-content-hint](https://w3c.github.io/mst-content-hint/#video-content-hints).
|
||||
@@ -104,7 +104,7 @@ type AudioEncodingConfig = {
|
||||
- `codec`: The [audio codec](./supported-formats-and-codecs#audio-codecs) used for encoding. Can be omitted for uncompressed PCM codecs.
|
||||
- `bitrate`: The target number of bits per second. Alternatively, this can be a [subjective quality](#subjective-qualities).
|
||||
- `bitrateMode`: Can be used to control constant vs. variable bitrate.
|
||||
- `fullCodecString`: Allows you to optionally specify the full codec string used by the audio encoder, as specified in the [WebCodecs Codec Registry](https://www.w3.org/TR/webcodecs-codec-registry/). For example, you may set it to `'mp4a.40.2'` when using AAC. Keep in mind that the codec string must still match the codec specified in `codec`. If you don't set this field, a codec string will be generated automatically.
|
||||
- `fullCodecString`: Allows you to optionally specify the full codec string used by the audio encoder, as specified in the [Mediabunny Codec Registry](/codec-registry/overview). For example, you may set it to `'mp4a.40.2'` when using AAC. Keep in mind that the codec string must still match the codec specified in `codec`. If you don't set this field, a codec string will be generated automatically.
|
||||
- `onEncodedPacket`: Called for each successfully encoded packet. Useful for determining encoding progress.
|
||||
- `onEncoderConfig`: Called when the internal encoder config, as used by the WebCodecs API, is created. You can use this to introspect the full codec string.
|
||||
|
||||
@@ -240,7 +240,7 @@ await packetSource.add(firstPacket, {
|
||||
});
|
||||
```
|
||||
|
||||
`codec`, `codedWidth`, and `codedHeight` are required for all codecs, whereas `description` is required for some codecs. Additional fields, such as `colorSpace`, are optional. The [WebCodecs Codec Registry](https://www.w3.org/TR/webcodecs-codec-registry/) specifies the formats of `codec` and `description` for each video codec, which you must adhere to.
|
||||
`codec`, `codedWidth`, and `codedHeight` are required for all codecs, whereas `description` is required for some codecs. Additional fields, such as `colorSpace`, are optional. The [Mediabunny Codec Registry](/codec-registry/overview) specifies the formats of `codec` and `description` for each video codec, which you **must** adhere to.
|
||||
|
||||
#### B-frames
|
||||
|
||||
@@ -397,7 +397,7 @@ await packetSource.add(firstPacket, {
|
||||
});
|
||||
```
|
||||
|
||||
`codec`, `numberOfChannels`, and `sampleRate` are required for all codecs, whereas `description` is required for some codecs. The [WebCodecs Codec Registry](https://www.w3.org/TR/webcodecs-codec-registry/) specifies the formats of `codec` and `description` for each audio codec, which you must adhere to.
|
||||
`codec`, `numberOfChannels`, and `sampleRate` are required for all codecs, whereas `description` is required for some codecs. The [Mediabunny Codec Registry](/codec-registry/overview) specifies the formats of `codec` and `description` for each audio codec, which you must adhere to.
|
||||
|
||||
## Subtitle sources
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ constructor(
|
||||
);
|
||||
```
|
||||
|
||||
When creating a packet for a given codec, you *must* adhere to the data format specified in the [Mediabunny Codec Registry](/codec-registry/overview).
|
||||
|
||||
::: info
|
||||
You probably won't ever need to set `sequenceNumber` or `byteLength` in the constructor.
|
||||
:::
|
||||
|
||||
@@ -129,7 +129,7 @@ track.codec; // => MediaCodec | null
|
||||
```
|
||||
This field is `null` when the track's codec couldn't be recognized or is not supported by Mediabunny. See [Codecs](./supported-formats-and-codecs#codecs) for the full list of supported codecs. When Mediabunny doesn't recognize the format, you can still use the `internalCodecId` field to figure out the codec of the track, although its format depends on the container format used and is not homogenized by Mediabunny.
|
||||
|
||||
You can also extract the full codec parameter string from the track, as specified in the [WebCodecs Codec Registry](https://www.w3.org/TR/webcodecs-codec-registry/):
|
||||
You can also extract the full codec parameter string from the track, as specified in the [Mediabunny Codec Registry](/codec-registry/overview):
|
||||
```ts
|
||||
await track.getCodecParameterString(); // => 'avc1.42001f'
|
||||
```
|
||||
|
||||
@@ -21,6 +21,8 @@ Mediabunny supports a wide range of video, audio, and subtitle codecs. More spec
|
||||
|
||||
The availability of the codecs provided by the WebCodecs API depends on the browser and thus cannot be guaranteed by this library. Mediabunny provides [special utility functions](#querying-codec-encodability) to check which codecs are able to be encoded. You can also specify [custom coders](#custom-coders) to provide your own encoder/decoder implementation if the browser doesn't support the codec natively.
|
||||
|
||||
For precise definitions of each codec including the corresponding packet format, please refer to the [Mediabunny Codec Registry](/codec-registry/overview).
|
||||
|
||||
::: info
|
||||
Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, meaning they are always supported.
|
||||
:::
|
||||
@@ -40,6 +42,8 @@ Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, m
|
||||
- `'mp3'` - MP3
|
||||
- `'vorbis'` - Vorbis
|
||||
- `'flac'` - Free Lossless Audio Codec (FLAC)
|
||||
- `'ac3'` - Dolby Digital (AC-3) [^1]
|
||||
- `'eac3'` - Dolby Digital Plus (E-AC-3) [^1]
|
||||
- `'pcm-u8'` - 8-bit unsigned PCM
|
||||
- `'pcm-s8'` - 8-bit signed PCM
|
||||
- `'pcm-s16'` - 16-bit little-endian signed PCM
|
||||
@@ -55,6 +59,8 @@ Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, m
|
||||
- `'ulaw'` - μ-law PCM
|
||||
- `'alaw'` - A-law PCM
|
||||
|
||||
[^1]: AC-3 and E-AC-3 are not natively supported by WebCodecs. To encode or decode these codecs, you must provide a [custom coder](#custom-coders).
|
||||
|
||||
### Subtitle codecs
|
||||
|
||||
- `'webvtt'` - WebVTT
|
||||
@@ -63,7 +69,7 @@ Mediabunny ships with built-in decoders and encoders for all audio PCM codecs, m
|
||||
|
||||
Not all codecs can be used with all containers. The following table specifies the supported codec-container combinations:
|
||||
|
||||
| | .mp4 | .mov | .mkv | .webm[^1] | .ogg | .mp3 | .wav | .aac | .flac | .ts |
|
||||
| | .mp4 | .mov | .mkv | .webm[^2] | .ogg | .mp3 | .wav | .aac | .flac | .ts |
|
||||
|:--------------:|:--------:|:-----:|:-----:|:---------:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|
|
||||
| `'avc'` | ✓ | ✓ | ✓ | | | | | | | ✓ |
|
||||
| `'hevc'` | ✓ | ✓ | ✓ | | | | | | | ✓ |
|
||||
@@ -75,6 +81,8 @@ Not all codecs can be used with all containers. The following table specifies th
|
||||
| `'mp3'` | ✓ | ✓ | ✓ | | | ✓ | | | | ✓ |
|
||||
| `'vorbis'` | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | |
|
||||
| `'flac'` | ✓ | ✓ | ✓ | | | | | | ✓ | |
|
||||
| `'ac3'` | ✓ | ✓ | ✓ | | | | | | | ✓ |
|
||||
| `'eac3'` | ✓ | ✓ | ✓ | | | | | | | ✓ |
|
||||
| `'pcm-u8'` | | ✓ | ✓ | | | | ✓ | | | |
|
||||
| `'pcm-s8'` | | ✓ | | | | | | | | |
|
||||
| `'pcm-s16'` | ✓ | ✓ | ✓ | | | | ✓ | | | |
|
||||
@@ -89,11 +97,11 @@ Not all codecs can be used with all containers. The following table specifies th
|
||||
| `'pcm-f64be'` | ✓ | ✓ | | | | | | | | |
|
||||
| `'ulaw'` | | ✓ | | | | | ✓ | | | |
|
||||
| `'alaw'` | | ✓ | | | | | ✓ | | | |
|
||||
| `'webvtt'`[^2] | (✓) | | (✓) | (✓) | | | | | | |
|
||||
| `'webvtt'`[^3] | (✓) | | (✓) | (✓) | | | | | | |
|
||||
|
||||
|
||||
[^1]: WebM only supports a small subset of the codecs supported by Matroska. However, this library can technically read all codecs from a WebM that are supported by Matroska.
|
||||
[^2]: WebVTT can only be written, not read.
|
||||
[^2]: WebM only supports a small subset of the codecs supported by Matroska. However, this library can technically read all codecs from a WebM that are supported by Matroska.
|
||||
[^3]: WebVTT can only be written, not read.
|
||||
|
||||
## Querying codec encodability
|
||||
|
||||
|
||||
+1
-1
@@ -124,6 +124,7 @@ const sponsors = {
|
||||
{ image: 'https://avatars.githubusercontent.com/u/1836701', name: 'Bean Deng', url: 'https://github.com/HADB' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/255616819', name: 'cronischarles-del', url: 'https://github.com/cronischarles-del' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/37973863', name: 'Gomi', url: 'https://github.com/gxy5202' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/36898190', name: 'jepcd', url: 'https://github.com/jepcd' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/63088713', name: 'taf2000', url: 'https://github.com/taf2000' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/91711202', name: 'ihasq', url: 'https://github.com/ihasq' },
|
||||
@@ -132,7 +133,6 @@ const sponsors = {
|
||||
{ image: 'https://avatars.githubusercontent.com/u/3709646', name: 'Rodrigo Belfiore', url: 'https://github.com/roprgm' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/31102694', name: 'Aiden Liu', url: 'https://github.com/aidenlx' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/41021374', name: 'arthco', url: 'https://github.com/arthtyagi' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/36898190', name: 'alakhpc', url: 'https://github.com/alakhpc' },
|
||||
{ image: 'https://avatars.githubusercontent.com/u/5907357', name: 'Harvey Zhao', url: 'https://github.com/zhw2590582' },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="5.33333" fill="#DAFF99"/>
|
||||
<path d="M14.3273 9.29964C14.5206 8.97719 14.9878 8.97719 15.1811 9.29964L17.7213 13.5375C17.7634 13.6077 17.8221 13.6664 17.8923 13.7085L22.1301 16.2487C22.4526 16.4419 22.4526 16.9092 22.1301 17.1025L17.8923 19.6427C17.8221 19.6848 17.7634 19.7435 17.7213 19.8137L15.1811 24.0515C14.9878 24.374 14.5206 24.374 14.3273 24.0515L11.7871 19.8137C11.745 19.7435 11.6863 19.6848 11.6161 19.6427L7.37825 17.1025C7.0558 16.9092 7.0558 16.4419 7.37825 16.2487L11.6161 13.7085C11.6863 13.6664 11.745 13.6077 11.7871 13.5375L14.3273 9.29964Z" fill="#2D2D2D"/>
|
||||
<path d="M22.7374 7.51156C22.7857 7.43094 22.9026 7.43094 22.9509 7.51156L23.7608 8.86276C23.7713 8.88032 23.786 8.895 23.8035 8.90552L25.1548 9.71544C25.2354 9.76376 25.2354 9.88058 25.1548 9.9289L23.8035 10.7388C23.786 10.7493 23.7713 10.764 23.7608 10.7816L22.9509 12.1328C22.9026 12.2134 22.7857 12.2134 22.7374 12.1328L21.9275 10.7816C21.917 10.764 21.9023 10.7493 21.8847 10.7388L20.5335 9.9289C20.4529 9.88058 20.4529 9.76376 20.5335 9.71544L21.8847 8.90552C21.9023 8.895 21.917 8.88032 21.9275 8.86276L22.7374 7.51156Z" fill="#2D2D2D"/>
|
||||
<g clip-path="url(#clip0_3664_2482)">
|
||||
<rect width="32" height="32" rx="4" fill="#DAFF99"/>
|
||||
<path d="M15.736 9.07175C15.8492 8.8828 16.123 8.8828 16.2362 9.07175L18.9703 13.6355C18.9949 13.6766 19.0293 13.711 19.0705 13.7357L23.6322 16.4714C23.821 16.5846 23.821 16.8582 23.6322 16.9715L19.0705 19.7072C19.0293 19.7318 18.9949 19.7662 18.9703 19.8074L16.2362 24.3711C16.123 24.5601 15.8492 24.5601 15.736 24.3711L13.0019 19.8074C12.9772 19.7662 12.9428 19.7318 12.9017 19.7072L8.34001 16.9715C8.15121 16.8582 8.15121 16.5846 8.34001 16.4714L12.9017 13.7357C12.9428 13.711 12.9772 13.6766 13.0019 13.6355L15.736 9.07175Z" fill="#2D2D2D"/>
|
||||
<g filter="url(#filter0_d_3664_2482)">
|
||||
<path d="M23.8321 7.41198C23.8604 7.36474 23.9288 7.36474 23.9571 7.41198L24.815 8.84391C24.8212 8.85419 24.8298 8.86279 24.84 8.86896L26.2713 9.72731C26.3185 9.75561 26.3185 9.82401 26.2713 9.85232L24.84 10.7107C24.8298 10.7168 24.8212 10.7254 24.815 10.7357L23.9571 12.1676C23.9288 12.2149 23.8604 12.2149 23.8321 12.1676L22.9742 10.7357C22.9681 10.7254 22.9595 10.7168 22.9492 10.7107L21.5179 9.85232C21.4707 9.82401 21.4707 9.75561 21.5179 9.72731L22.9492 8.86896C22.9595 8.86279 22.9681 8.85419 22.9742 8.84391L23.8321 7.41198Z" fill="#2D2D2D"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_3664_2482" x="-56.5175" y="-70.623" width="160.824" height="160.826" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="39"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3664_2482"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3664_2482" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_3664_2482">
|
||||
<rect width="32" height="32" rx="4" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.1 KiB |
Generated
+6
-6
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mediabunny",
|
||||
"version": "1.32.1",
|
||||
"version": "1.33.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mediabunny",
|
||||
"version": "1.32.1",
|
||||
"version": "1.33.0",
|
||||
"license": "MPL-2.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
@@ -7739,9 +7739,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mediabunny": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.32.0.tgz",
|
||||
"integrity": "sha512-wMWynAQqZkmsLm+d2+fovcBal3zBQu/bot3u+yV4bDTfNxPeEDHv1wjiz6yjyKcXBuxfaQK96gImqLcKLfJG7w==",
|
||||
"version": "1.32.2",
|
||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.32.2.tgz",
|
||||
"integrity": "sha512-cxzGriei0JAYaYApO7xsN8ITq1npEnQPBAE43Z3vnGHhh8UVm8Cgb5o06wrufDVYlFg58I2sF6d/xWH39uXuEw==",
|
||||
"license": "MPL-2.0",
|
||||
"peer": true,
|
||||
"workspaces": [
|
||||
@@ -12065,7 +12065,7 @@
|
||||
},
|
||||
"packages/mp3-encoder": {
|
||||
"name": "@mediabunny/mp3-encoder",
|
||||
"version": "1.32.1",
|
||||
"version": "1.33.0",
|
||||
"license": "MPL-2.0",
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "^1.40.1"
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mediabunny",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.32.1",
|
||||
"version": "1.33.0",
|
||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@mediabunny/mp3-encoder",
|
||||
"author": "Vanilagy",
|
||||
"version": "1.32.1",
|
||||
"version": "1.33.0",
|
||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||
|
||||
@@ -62,10 +62,7 @@ export class AdtsMuxer extends Muxer {
|
||||
|
||||
const description = meta?.decoderConfig?.description;
|
||||
|
||||
// From the WebCodecs Codec Registry:
|
||||
// "If description is present, it is assumed to a AudioSpecificConfig as defined in [iso14496-3] section
|
||||
// 1.6.2.1, Table 1.15, and the bitstream is assumed to be in aac.
|
||||
// If the description is not present, the bitstream is assumed to be in adts format."
|
||||
// Follows from the Mediabunny Codec Registry:
|
||||
this.inputIsAdts = !description;
|
||||
|
||||
if (!this.inputIsAdts) {
|
||||
|
||||
@@ -2643,3 +2643,367 @@ export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags
|
||||
|
||||
return commentHeader;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// AC-3 / E-AC-3 Parsing
|
||||
// Reference: ETSI TS 102 366 V1.4.1
|
||||
// ============================================================================
|
||||
|
||||
/** Sample rates indexed by fscod (Table 4.1) */
|
||||
export const AC3_SAMPLE_RATES = [48000, 44100, 32000] as const;
|
||||
|
||||
/**
|
||||
* Channel counts indexed by acmod (Table 4.3).
|
||||
* Does NOT include LFE - add lfeon to get total channel count.
|
||||
*/
|
||||
export const AC3_ACMOD_CHANNEL_COUNTS = [2, 1, 2, 3, 3, 4, 4, 5] as const;
|
||||
|
||||
export interface Ac3FrameInfo {
|
||||
/** Sample rate code */
|
||||
fscod: number;
|
||||
/** Bitstream ID */
|
||||
bsid: number;
|
||||
/** Bitstream mode */
|
||||
bsmod: number;
|
||||
/** Audio coding mode */
|
||||
acmod: number;
|
||||
/** LFE channel on */
|
||||
lfeon: number;
|
||||
/** Bit rate code (0-18, maps to bitrate via Table F.4.1) */
|
||||
bitRateCode: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an AC-3 syncframe to extract BSI (Bit Stream Information) fields.
|
||||
* Section 4.3
|
||||
*/
|
||||
export const parseAc3SyncFrame = (data: Uint8Array): Ac3FrameInfo | null => {
|
||||
if (data.length < 7) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check sync word (0x0B77)
|
||||
if (data[0] !== 0x0B || data[1] !== 0x77) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bitstream = new Bitstream(data);
|
||||
bitstream.skipBits(16); // sync word
|
||||
bitstream.skipBits(16); // crc1
|
||||
|
||||
const fscod = bitstream.readBits(2);
|
||||
if (fscod === 3) {
|
||||
return null; // Reserved, invalid
|
||||
}
|
||||
|
||||
const frmsizecod = bitstream.readBits(6);
|
||||
const bsid = bitstream.readBits(5);
|
||||
|
||||
// Verify this is AC-3
|
||||
if (bsid > 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bsmod = bitstream.readBits(3);
|
||||
const acmod = bitstream.readBits(3);
|
||||
|
||||
// Skip cmixlev (center downmix level) if three front channels are in use (L, C, R).
|
||||
if ((acmod & 0x1) !== 0 && acmod !== 0x1) {
|
||||
bitstream.skipBits(2);
|
||||
}
|
||||
|
||||
// Skip surmixlev (surround downmix level) if surround channels are in use.
|
||||
if ((acmod & 0x4) !== 0) {
|
||||
bitstream.skipBits(2);
|
||||
}
|
||||
|
||||
// Skip dsurmod if stereo (acmod === 2)
|
||||
if (acmod === 0x2) {
|
||||
bitstream.skipBits(2);
|
||||
}
|
||||
|
||||
const lfeon = bitstream.readBits(1);
|
||||
const bitRateCode = Math.floor(frmsizecod / 2);
|
||||
|
||||
return { fscod, bsid, bsmod, acmod, lfeon, bitRateCode };
|
||||
};
|
||||
|
||||
/**
|
||||
* AC-3 frame sizes in bytes, indexed by [3 * frmsizecod + fscod].
|
||||
* fscod: 0=48kHz, 1=44.1kHz, 2=32kHz
|
||||
* Values are 16-bit words * 2 (to convert to bytes).
|
||||
* Table 4.13
|
||||
*/
|
||||
export const AC3_FRAME_SIZES = [
|
||||
// frmsizecod, [48kHz, 44.1kHz, 32kHz] in bytes
|
||||
64 * 2, 69 * 2, 96 * 2,
|
||||
64 * 2, 70 * 2, 96 * 2,
|
||||
80 * 2, 87 * 2, 120 * 2,
|
||||
80 * 2, 88 * 2, 120 * 2,
|
||||
96 * 2, 104 * 2, 144 * 2,
|
||||
96 * 2, 105 * 2, 144 * 2,
|
||||
112 * 2, 121 * 2, 168 * 2,
|
||||
112 * 2, 122 * 2, 168 * 2,
|
||||
128 * 2, 139 * 2, 192 * 2,
|
||||
128 * 2, 140 * 2, 192 * 2,
|
||||
160 * 2, 174 * 2, 240 * 2,
|
||||
160 * 2, 175 * 2, 240 * 2,
|
||||
192 * 2, 208 * 2, 288 * 2,
|
||||
192 * 2, 209 * 2, 288 * 2,
|
||||
224 * 2, 243 * 2, 336 * 2,
|
||||
224 * 2, 244 * 2, 336 * 2,
|
||||
256 * 2, 278 * 2, 384 * 2,
|
||||
256 * 2, 279 * 2, 384 * 2,
|
||||
320 * 2, 348 * 2, 480 * 2,
|
||||
320 * 2, 349 * 2, 480 * 2,
|
||||
384 * 2, 417 * 2, 576 * 2,
|
||||
384 * 2, 418 * 2, 576 * 2,
|
||||
448 * 2, 487 * 2, 672 * 2,
|
||||
448 * 2, 488 * 2, 672 * 2,
|
||||
512 * 2, 557 * 2, 768 * 2,
|
||||
512 * 2, 558 * 2, 768 * 2,
|
||||
640 * 2, 696 * 2, 960 * 2,
|
||||
640 * 2, 697 * 2, 960 * 2,
|
||||
768 * 2, 835 * 2, 1152 * 2,
|
||||
768 * 2, 836 * 2, 1152 * 2,
|
||||
896 * 2, 975 * 2, 1344 * 2,
|
||||
896 * 2, 976 * 2, 1344 * 2,
|
||||
1024 * 2, 1114 * 2, 1536 * 2,
|
||||
1024 * 2, 1115 * 2, 1536 * 2,
|
||||
1152 * 2, 1253 * 2, 1728 * 2,
|
||||
1152 * 2, 1254 * 2, 1728 * 2,
|
||||
1280 * 2, 1393 * 2, 1920 * 2,
|
||||
1280 * 2, 1394 * 2, 1920 * 2,
|
||||
];
|
||||
|
||||
/** Number of samples per AC-3 syncframe (always 1536) */
|
||||
export const AC3_SAMPLES_PER_FRAME = 1536;
|
||||
|
||||
/**
|
||||
* AC-3 registration_descriptor for MPEG-TS.
|
||||
* Section A.2.3
|
||||
*/
|
||||
export const AC3_REGISTRATION_DESCRIPTOR = new Uint8Array([0x05, 0x04, 0x41, 0x43, 0x2d, 0x33]);
|
||||
|
||||
/** E-AC-3 registration_descriptor for MPEG-TS/ */
|
||||
export const EAC3_REGISTRATION_DESCRIPTOR = new Uint8Array([0x05, 0x04, 0x45, 0x41, 0x43, 0x33]);
|
||||
|
||||
/** E-AC-3 reduced sample rates for fscod2 per ATSC A/52:2018 */
|
||||
const EAC3_REDUCED_SAMPLE_RATES = [24000, 22050, 16000] as const;
|
||||
|
||||
/** Number of audio blocks per syncframe, indexed by numblkscod */
|
||||
export const EAC3_NUMBLKS_TABLE = [1, 2, 3, 6] as const;
|
||||
|
||||
/**
|
||||
* E-AC-3 independent substream info.
|
||||
* Each independent substream represents a separate audio program.
|
||||
*/
|
||||
export interface Eac3SubstreamInfo {
|
||||
/** Sample rate code */
|
||||
fscod: number;
|
||||
/** Sample rate code 2 (ATSC A/52:2018) */
|
||||
fscod2: number | null;
|
||||
/** Bitstream ID */
|
||||
bsid: number;
|
||||
/** Bitstream mode */
|
||||
bsmod: number;
|
||||
/** Audio coding mode */
|
||||
acmod: number;
|
||||
/** LFE channel on */
|
||||
lfeon: number;
|
||||
/** Number of dependent substreams */
|
||||
numDepSub: number;
|
||||
/** Channel locations for dependent substreams */
|
||||
chanLoc: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* E-AC-3 decoder configuration (dec3 box contents).
|
||||
*/
|
||||
export interface Eac3FrameInfo {
|
||||
/** Data rate in kbps */
|
||||
dataRate: number;
|
||||
/** Independent substreams */
|
||||
substreams: Eac3SubstreamInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an E-AC-3 syncframe to extract BSI fields.
|
||||
* Section E.1.2
|
||||
*/
|
||||
export const parseEac3SyncFrame = (data: Uint8Array): Eac3FrameInfo | null => {
|
||||
if (data.length < 6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check sync word (0x0B77)
|
||||
if (data[0] !== 0x0B || data[1] !== 0x77) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bitstream = new Bitstream(data);
|
||||
bitstream.skipBits(16); // sync word
|
||||
|
||||
const strmtyp = bitstream.readBits(2);
|
||||
bitstream.skipBits(3); // substreamid
|
||||
|
||||
// Only parse independent substreams (strmtyp 0 or 2)
|
||||
if (strmtyp !== 0 && strmtyp !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frmsiz = bitstream.readBits(11);
|
||||
const fscod = bitstream.readBits(2);
|
||||
|
||||
let fscod2 = 0;
|
||||
let numblkscod: number;
|
||||
|
||||
if (fscod === 3) {
|
||||
// fscod2 enables reduced sample rates (24/22.05/16 kHz) per ATSC A/52:2018
|
||||
fscod2 = bitstream.readBits(2);
|
||||
numblkscod = 3; // Implicitly 6 blocks when fscod=3
|
||||
} else {
|
||||
numblkscod = bitstream.readBits(2);
|
||||
}
|
||||
|
||||
const acmod = bitstream.readBits(3);
|
||||
const lfeon = bitstream.readBits(1);
|
||||
const bsid = bitstream.readBits(5);
|
||||
|
||||
// Verify this is E-AC-3
|
||||
if (bsid < 11 || bsid > 16) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate data rate: ((frmsiz + 1) * fs) / (numblks * 16)
|
||||
const numblks = EAC3_NUMBLKS_TABLE[numblkscod]!;
|
||||
let fs: number;
|
||||
if (fscod < 3) {
|
||||
fs = AC3_SAMPLE_RATES[fscod]! / 1000;
|
||||
} else {
|
||||
fs = EAC3_REDUCED_SAMPLE_RATES[fscod2]! / 1000;
|
||||
}
|
||||
const dataRate = Math.round(((frmsiz + 1) * fs) / (numblks * 16));
|
||||
|
||||
// These fields require parsing beyond the first frame.
|
||||
// Defaults are correct for almost all content.
|
||||
const bsmod = 0;
|
||||
const numDepSub = 0;
|
||||
const chanLoc = 0;
|
||||
|
||||
const substream: Eac3SubstreamInfo = {
|
||||
fscod,
|
||||
fscod2,
|
||||
bsid,
|
||||
bsmod,
|
||||
acmod,
|
||||
lfeon,
|
||||
numDepSub,
|
||||
chanLoc,
|
||||
};
|
||||
|
||||
return {
|
||||
dataRate,
|
||||
substreams: [substream],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a dec3 box to extract E-AC-3 parameters.
|
||||
* Section F.6
|
||||
*/
|
||||
export const parseEac3Config = (data: Uint8Array): Eac3FrameInfo | null => {
|
||||
if (data.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bitstream = new Bitstream(data);
|
||||
|
||||
const dataRate = bitstream.readBits(13);
|
||||
const numIndSub = bitstream.readBits(3);
|
||||
|
||||
const substreams: Eac3SubstreamInfo[] = [];
|
||||
|
||||
for (let i = 0; i <= numIndSub; i++) {
|
||||
// Check we have enough data for this substream
|
||||
// Each substream needs at least 24 bits (3 bytes) without dependent subs
|
||||
if (Math.ceil(bitstream.pos / 8) + 3 > data.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
const fscod = bitstream.readBits(2);
|
||||
const bsid = bitstream.readBits(5);
|
||||
bitstream.skipBits(1); // reserved
|
||||
bitstream.skipBits(1); // asvc
|
||||
const bsmod = bitstream.readBits(3);
|
||||
const acmod = bitstream.readBits(3);
|
||||
const lfeon = bitstream.readBits(1);
|
||||
bitstream.skipBits(3); // reserved
|
||||
const numDepSub = bitstream.readBits(4);
|
||||
|
||||
let chanLoc = 0;
|
||||
|
||||
if (numDepSub > 0) {
|
||||
chanLoc = bitstream.readBits(9);
|
||||
} else {
|
||||
bitstream.skipBits(1); // reserved
|
||||
}
|
||||
|
||||
substreams.push({
|
||||
fscod,
|
||||
fscod2: null,
|
||||
bsid,
|
||||
bsmod,
|
||||
acmod,
|
||||
lfeon,
|
||||
numDepSub,
|
||||
chanLoc,
|
||||
});
|
||||
}
|
||||
|
||||
if (substreams.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { dataRate, substreams };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get sample rate from E-AC-3 config.
|
||||
* See ATSC A/52:2018 for handling fscod2.
|
||||
*/
|
||||
export const getEac3SampleRate = (config: Eac3FrameInfo): number | null => {
|
||||
const sub = config.substreams[0];
|
||||
assert(sub);
|
||||
|
||||
if (sub.fscod < 3) {
|
||||
return AC3_SAMPLE_RATES[sub.fscod]!;
|
||||
} else if (sub.fscod2 !== null && sub.fscod2 < 3) {
|
||||
return EAC3_REDUCED_SAMPLE_RATES[sub.fscod2]!;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get channel count from E-AC-3 config (first independent substream only).
|
||||
*/
|
||||
export const getEac3ChannelCount = (config: Eac3FrameInfo): number => {
|
||||
const sub = config.substreams[0];
|
||||
assert(sub);
|
||||
|
||||
let channels = AC3_ACMOD_CHANNEL_COUNTS[sub.acmod]! + sub.lfeon;
|
||||
|
||||
// Add channels from dependent substreams
|
||||
if (sub.numDepSub > 0) {
|
||||
const CHAN_LOC_COUNTS = [2, 2, 1, 1, 2, 2, 2, 1, 1];
|
||||
|
||||
for (let bit = 0; bit < 9; bit++) {
|
||||
if (sub.chanLoc & (1 << (8 - bit))) {
|
||||
channels += CHAN_LOC_COUNTS[bit]!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
};
|
||||
|
||||
+31
-3
@@ -70,6 +70,8 @@ export const NON_PCM_AUDIO_CODECS = [
|
||||
'mp3',
|
||||
'vorbis',
|
||||
'flac',
|
||||
'ac3',
|
||||
'eac3',
|
||||
] as const;
|
||||
/**
|
||||
* List of known audio codecs, ordered by encoding preference.
|
||||
@@ -528,6 +530,10 @@ export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: numbe
|
||||
return 'vorbis';
|
||||
} else if (codec === 'flac') {
|
||||
return 'flac';
|
||||
} else if (codec === 'ac3') {
|
||||
return 'ac-3';
|
||||
} else if (codec === 'eac3') {
|
||||
return 'ec-3';
|
||||
} else if ((PCM_AUDIO_CODECS as readonly string[]).includes(codec)) {
|
||||
return codec;
|
||||
}
|
||||
@@ -573,6 +579,10 @@ export const extractAudioCodecString = (trackInfo: {
|
||||
return 'vorbis';
|
||||
} else if (codec === 'flac') {
|
||||
return 'flac';
|
||||
} else if (codec === 'ac3') {
|
||||
return 'ac-3';
|
||||
} else if (codec === 'eac3') {
|
||||
return 'ec-3';
|
||||
} else if (codec && (PCM_AUDIO_CODECS as readonly string[]).includes(codec)) {
|
||||
return codec;
|
||||
}
|
||||
@@ -742,6 +752,10 @@ export const inferCodecFromCodecString = (codecString: string): MediaCodec | nul
|
||||
return 'vorbis';
|
||||
} else if (codecString === 'flac') {
|
||||
return 'flac';
|
||||
} else if (codecString === 'ac-3' || codecString === 'ac3') {
|
||||
return 'ac3';
|
||||
} else if (codecString === 'ec-3' || codecString === 'eac3') {
|
||||
return 'eac3';
|
||||
} else if (codecString === 'ulaw') {
|
||||
return 'ulaw';
|
||||
} else if (codecString === 'alaw') {
|
||||
@@ -819,7 +833,7 @@ export const validateVideoChunkMetadata = (metadata: EncodedVideoChunkMetadata |
|
||||
if (!VALID_VIDEO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
|
||||
throw new TypeError(
|
||||
'Video chunk metadata decoder configuration codec string must be a valid video codec string as specified in'
|
||||
+ ' the WebCodecs Codec Registry.',
|
||||
+ ' the Mediabunny Codec Registry.',
|
||||
);
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth! <= 0) {
|
||||
@@ -933,7 +947,9 @@ export const validateVideoChunkMetadata = (metadata: EncodedVideoChunkMetadata |
|
||||
}
|
||||
};
|
||||
|
||||
const VALID_AUDIO_CODEC_STRING_PREFIXES = ['mp4a', 'mp3', 'opus', 'vorbis', 'flac', 'ulaw', 'alaw', 'pcm'];
|
||||
const VALID_AUDIO_CODEC_STRING_PREFIXES = [
|
||||
'mp4a', 'mp3', 'opus', 'vorbis', 'flac', 'ulaw', 'alaw', 'pcm', 'ac-3', 'ec-3',
|
||||
];
|
||||
|
||||
export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata | undefined) => {
|
||||
if (!metadata) {
|
||||
@@ -954,7 +970,7 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
|
||||
if (!VALID_AUDIO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
|
||||
throw new TypeError(
|
||||
'Audio chunk metadata decoder configuration codec string must be a valid audio codec string as specified in'
|
||||
+ ' the WebCodecs Codec Registry.',
|
||||
+ ' the Mediabunny Codec Registry.',
|
||||
);
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) {
|
||||
@@ -1051,6 +1067,18 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
|
||||
+ ' adhere to the format described in https://www.w3.org/TR/webcodecs-flac-codec-registration/.',
|
||||
);
|
||||
}
|
||||
} else if (metadata.decoderConfig.codec.startsWith('ac-3') || metadata.decoderConfig.codec.startsWith('ac3')) {
|
||||
// AC3-specific validation
|
||||
|
||||
if (metadata.decoderConfig.codec !== 'ac-3') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for AC-3 must be "ac-3".');
|
||||
}
|
||||
} else if (metadata.decoderConfig.codec.startsWith('ec-3') || metadata.decoderConfig.codec.startsWith('eac3')) {
|
||||
// EAC3-specific validation
|
||||
|
||||
if (metadata.decoderConfig.codec !== 'ec-3') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for EC-3 must be "ec-3".');
|
||||
}
|
||||
} else if (
|
||||
metadata.decoderConfig.codec.startsWith('pcm')
|
||||
|| metadata.decoderConfig.codec.startsWith('ulaw')
|
||||
|
||||
+7
-6
@@ -564,11 +564,11 @@ export class Conversion {
|
||||
if (options.trim !== undefined && (!options.trim || typeof options.trim !== 'object')) {
|
||||
throw new TypeError('options.trim, when provided, must be an object.');
|
||||
}
|
||||
if (options.trim?.start !== undefined && (!Number.isFinite(options.trim.start) || options.trim.start < 0)) {
|
||||
throw new TypeError('options.trim.start, when provided, must be a non-negative number.');
|
||||
if (options.trim?.start !== undefined && (!Number.isFinite(options.trim.start))) {
|
||||
throw new TypeError('options.trim.start, when provided, must be a finite number.');
|
||||
}
|
||||
if (options.trim?.end !== undefined && (!Number.isFinite(options.trim.end) || options.trim.end < 0)) {
|
||||
throw new TypeError('options.trim.end, when provided, must be a non-negative number.');
|
||||
if (options.trim?.end !== undefined && (!Number.isFinite(options.trim.end))) {
|
||||
throw new TypeError('options.trim.end, when provided, must be a finite number.');
|
||||
}
|
||||
if (
|
||||
options.trim?.start !== undefined
|
||||
@@ -607,7 +607,7 @@ export class Conversion {
|
||||
// those out by default.
|
||||
0,
|
||||
);
|
||||
this._endTimestamp = this._options.trim?.end ?? Infinity;
|
||||
this._endTimestamp = Math.max(this._options.trim?.end ?? Infinity, this._startTimestamp);
|
||||
|
||||
const inputTracks = await this.input.getTracks();
|
||||
const outputTrackCounts = this.output.format.getSupportedTrackCounts();
|
||||
@@ -1326,7 +1326,8 @@ export class Conversion {
|
||||
let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
|
||||
let needsResample = numberOfChannels !== originalNumberOfChannels
|
||||
|| sampleRate !== originalSampleRate
|
||||
|| firstTimestamp < this._startTimestamp;
|
||||
|| firstTimestamp < this._startTimestamp
|
||||
|| (firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData);
|
||||
|
||||
let audioCodecs = this.output.format.getSupportedAudioCodecs();
|
||||
if (
|
||||
|
||||
+4
-2
@@ -127,7 +127,7 @@ export type VideoEncodingAdditionalOptions = {
|
||||
*/
|
||||
latencyMode?: 'quality' | 'realtime';
|
||||
/**
|
||||
* The full codec string as specified in the WebCodecs Codec Registry. This string must match the codec
|
||||
* The full codec string as specified in the Mediabunny Codec Registry. This string must match the codec
|
||||
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
|
||||
*/
|
||||
fullCodecString?: string;
|
||||
@@ -280,7 +280,7 @@ export type AudioEncodingAdditionalOptions = {
|
||||
/** Configures the bitrate mode. */
|
||||
bitrateMode?: 'constant' | 'variable';
|
||||
/**
|
||||
* The full codec string as specified in the WebCodecs Codec Registry. This string must match the codec
|
||||
* The full codec string as specified in the Mediabunny Codec Registry. This string must match the codec
|
||||
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
|
||||
*/
|
||||
fullCodecString?: string;
|
||||
@@ -375,6 +375,8 @@ export class Quality {
|
||||
opus: 64000, // 64kbps base for Opus
|
||||
mp3: 160000, // 160kbps base for MP3
|
||||
vorbis: 64000, // 64kbps base for Vorbis
|
||||
ac3: 384000, // 384kbps base for AC-3
|
||||
eac3: 192000, // 192kbps base for E-AC-3
|
||||
};
|
||||
|
||||
const baseBitrate = baseRates[codec as keyof typeof baseRates];
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
UNDETERMINED_LANGUAGE,
|
||||
assertNever,
|
||||
keyValueIterator,
|
||||
Bitstream,
|
||||
} from '../misc';
|
||||
import {
|
||||
AudioCodec,
|
||||
@@ -43,7 +44,7 @@ import {
|
||||
IsobmffVideoTrackData,
|
||||
Sample,
|
||||
} from './isobmff-muxer';
|
||||
import { parseOpusIdentificationHeader } from '../codec-data';
|
||||
import { parseAc3SyncFrame, parseEac3SyncFrame, parseOpusIdentificationHeader } from '../codec-data';
|
||||
import { MetadataTags, RichImageData } from '../metadata';
|
||||
|
||||
export class IsobmffBoxWriter {
|
||||
@@ -907,6 +908,79 @@ const pcmC = (trackData: IsobmffAudioTrackData) => {
|
||||
]);
|
||||
};
|
||||
|
||||
/** AC3SpecificBox */
|
||||
const dac3 = (trackData: IsobmffAudioTrackData) => {
|
||||
const frameInfo = parseAc3SyncFrame(trackData.info.firstPacket.data);
|
||||
if (!frameInfo) {
|
||||
throw new Error(
|
||||
'Couldn\'t extract AC-3 frame info from the audio packet. '
|
||||
+ 'Ensure the packets contain valid AC-3 sync frames (as specified in ETSI TS 102 366).',
|
||||
);
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(3);
|
||||
const bitstream = new Bitstream(bytes);
|
||||
|
||||
bitstream.writeBits(2, frameInfo.fscod);
|
||||
bitstream.writeBits(5, frameInfo.bsid);
|
||||
bitstream.writeBits(3, frameInfo.bsmod);
|
||||
bitstream.writeBits(3, frameInfo.acmod);
|
||||
bitstream.writeBits(1, frameInfo.lfeon);
|
||||
bitstream.writeBits(5, frameInfo.bitRateCode);
|
||||
bitstream.writeBits(5, 0); // reserved
|
||||
|
||||
return box('dac3', [...bytes]);
|
||||
};
|
||||
|
||||
/** EC3SpecificBox */
|
||||
const dec3 = (trackData: IsobmffAudioTrackData) => {
|
||||
const frameInfo = parseEac3SyncFrame(trackData.info.firstPacket.data);
|
||||
if (!frameInfo) {
|
||||
throw new Error(
|
||||
'Couldn\'t extract E-AC-3 frame info from the audio packet. '
|
||||
+ 'Ensure the packets contain valid E-AC-3 sync frames (as specified in ETSI TS 102 366).',
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate size
|
||||
let totalBits = 16; // header: data_rate (13) + num_ind_sub (3)
|
||||
for (const sub of frameInfo.substreams) {
|
||||
totalBits += 23; // fixed fields per substream
|
||||
if (sub.numDepSub > 0) {
|
||||
totalBits += 9; // chan_loc
|
||||
} else {
|
||||
totalBits += 1; // reserved
|
||||
}
|
||||
}
|
||||
const size = Math.ceil(totalBits / 8);
|
||||
|
||||
const bytes = new Uint8Array(size);
|
||||
const bitstream = new Bitstream(bytes);
|
||||
|
||||
bitstream.writeBits(13, frameInfo.dataRate);
|
||||
bitstream.writeBits(3, frameInfo.substreams.length - 1); // num_ind_sub
|
||||
|
||||
for (const sub of frameInfo.substreams) {
|
||||
bitstream.writeBits(2, sub.fscod);
|
||||
bitstream.writeBits(5, sub.bsid);
|
||||
bitstream.writeBits(1, 0); // reserved
|
||||
bitstream.writeBits(1, 0); // asvc = 0
|
||||
bitstream.writeBits(3, sub.bsmod);
|
||||
bitstream.writeBits(3, sub.acmod);
|
||||
bitstream.writeBits(1, sub.lfeon);
|
||||
bitstream.writeBits(3, 0); // reserved
|
||||
bitstream.writeBits(4, sub.numDepSub);
|
||||
|
||||
if (sub.numDepSub > 0) {
|
||||
bitstream.writeBits(9, sub.chanLoc);
|
||||
} else {
|
||||
bitstream.writeBits(1, 0); // reserved
|
||||
}
|
||||
}
|
||||
|
||||
return box('dec3', [...bytes]);
|
||||
};
|
||||
|
||||
export const subtitleSampleDescription = (
|
||||
compressionType: string,
|
||||
trackData: IsobmffSubtitleTrackData,
|
||||
@@ -1606,6 +1680,8 @@ const audioCodecToBoxName = (codec: AudioCodec, isQuickTime: boolean): string =>
|
||||
case 'alaw': return 'alaw';
|
||||
case 'pcm-u8': return 'raw ';
|
||||
case 'pcm-s8': return 'sowt';
|
||||
case 'ac3': return 'ac-3';
|
||||
case 'eac3': return 'ec-3';
|
||||
}
|
||||
|
||||
// Logic diverges here
|
||||
@@ -1645,6 +1721,8 @@ const audioCodecToConfigurationBox = (codec: AudioCodec, isQuickTime: boolean) =
|
||||
case 'opus': return dOps;
|
||||
case 'vorbis': return esds;
|
||||
case 'flac': return dfLa;
|
||||
case 'ac3': return dac3;
|
||||
case 'eac3': return dec3;
|
||||
}
|
||||
|
||||
// Logic diverges here
|
||||
|
||||
@@ -27,6 +27,11 @@ import {
|
||||
FlacBlockType,
|
||||
HevcDecoderConfigurationRecord,
|
||||
Vp9CodecInfo,
|
||||
parseEac3Config,
|
||||
getEac3SampleRate,
|
||||
getEac3ChannelCount,
|
||||
AC3_SAMPLE_RATES,
|
||||
AC3_ACMOD_CHANNEL_COUNTS,
|
||||
} from '../codec-data';
|
||||
import { Demuxer } from '../demuxer';
|
||||
import { Input } from '../input';
|
||||
@@ -952,6 +957,10 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
track.info.codec = 'ulaw';
|
||||
} else if (lowercaseBoxName === 'alaw') {
|
||||
track.info.codec = 'alaw';
|
||||
} else if (lowercaseBoxName === 'ac-3') {
|
||||
track.info.codec = 'ac3';
|
||||
} else if (lowercaseBoxName === 'ec-3') {
|
||||
track.info.codec = 'eac3';
|
||||
} else {
|
||||
console.warn(`Unsupported audio codec (sample entry type '${sampleBoxInfo.name}').`);
|
||||
}
|
||||
@@ -1463,6 +1472,51 @@ export class IsobmffDemuxer extends Demuxer {
|
||||
track.info.codecDescription = description;
|
||||
}; break;
|
||||
|
||||
case 'dac3': { // AC3SpecificBox
|
||||
const track = this.currentTrack;
|
||||
if (!track) {
|
||||
break;
|
||||
}
|
||||
assert(track.info?.type === 'audio');
|
||||
|
||||
const bytes = readBytes(slice, 3);
|
||||
const bitstream = new Bitstream(bytes);
|
||||
|
||||
const fscod = bitstream.readBits(2);
|
||||
bitstream.skipBits(5 + 3); // Skip bsid and bsmod
|
||||
const acmod = bitstream.readBits(3);
|
||||
const lfeon = bitstream.readBits(1);
|
||||
|
||||
if (fscod < 3) {
|
||||
track.info.sampleRate = AC3_SAMPLE_RATES[fscod]!;
|
||||
}
|
||||
|
||||
track.info.numberOfChannels = AC3_ACMOD_CHANNEL_COUNTS[acmod]! + lfeon;
|
||||
}; break;
|
||||
|
||||
case 'dec3': { // EC3SpecificBox
|
||||
const track = this.currentTrack;
|
||||
if (!track) {
|
||||
break;
|
||||
}
|
||||
assert(track.info?.type === 'audio');
|
||||
|
||||
const bytes = readBytes(slice, boxInfo.contentSize);
|
||||
const config = parseEac3Config(bytes);
|
||||
|
||||
if (!config) {
|
||||
console.warn('Invalid dec3 box contents, ignoring.');
|
||||
break;
|
||||
}
|
||||
|
||||
const sampleRate = getEac3SampleRate(config);
|
||||
if (sampleRate !== null) {
|
||||
track.info.sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
track.info.numberOfChannels = getEac3ChannelCount(config);
|
||||
}; break;
|
||||
|
||||
case 'stts': {
|
||||
const track = this.currentTrack;
|
||||
if (!track) {
|
||||
|
||||
@@ -111,6 +111,7 @@ export type IsobmffTrackData = {
|
||||
* ADTS-wrapped data.
|
||||
*/
|
||||
requiresAdtsStripping: boolean;
|
||||
firstPacket: EncodedPacket;
|
||||
};
|
||||
} | {
|
||||
track: OutputSubtitleTrack;
|
||||
@@ -429,8 +430,9 @@ export class IsobmffMuxer extends Muxer {
|
||||
!this.isFragmented
|
||||
&& (PCM_AUDIO_CODECS as readonly string[]).includes(track.source._codec),
|
||||
requiresAdtsStripping,
|
||||
firstPacket: packet,
|
||||
},
|
||||
timescale: meta.decoderConfig.sampleRate,
|
||||
timescale: decoderConfig.sampleRate,
|
||||
samples: [],
|
||||
sampleQueue: [],
|
||||
timestampProcessingQueue: [],
|
||||
|
||||
@@ -744,6 +744,8 @@ export const CODEC_STRING_MAP: Partial<Record<MediaCodec, string>> = {
|
||||
'opus': 'A_OPUS',
|
||||
'vorbis': 'A_VORBIS',
|
||||
'flac': 'A_FLAC',
|
||||
'ac3': 'A_AC3',
|
||||
'eac3': 'A_EAC3',
|
||||
'pcm-u8': 'A_PCM/INT/LIT',
|
||||
'pcm-s16': 'A_PCM/INT/LIT',
|
||||
'pcm-s16be': 'A_PCM/INT/BIG',
|
||||
|
||||
@@ -1084,6 +1084,12 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
} else if (codecIdWithoutSuffix === CODEC_STRING_MAP.flac) {
|
||||
this.currentTrack.info.codec = 'flac';
|
||||
this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
|
||||
} else if (codecIdWithoutSuffix === CODEC_STRING_MAP.ac3) {
|
||||
this.currentTrack.info.codec = 'ac3';
|
||||
this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
|
||||
} else if (codecIdWithoutSuffix === CODEC_STRING_MAP.eac3) {
|
||||
this.currentTrack.info.codec = 'eac3';
|
||||
this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
|
||||
} else if (this.currentTrack.codecId === 'A_PCM/INT/LIT') {
|
||||
if (this.currentTrack.info.bitDepth === 8) {
|
||||
this.currentTrack.info.codec = 'pcm-u8';
|
||||
|
||||
@@ -19,16 +19,25 @@ import {
|
||||
VideoCodec,
|
||||
} from '../codec';
|
||||
import {
|
||||
AC3_ACMOD_CHANNEL_COUNTS,
|
||||
AC3_SAMPLE_RATES,
|
||||
AC3_SAMPLES_PER_FRAME,
|
||||
AvcDecoderConfigurationRecord,
|
||||
AvcNalUnitType,
|
||||
extractAvcDecoderConfigurationRecord,
|
||||
extractHevcDecoderConfigurationRecord,
|
||||
extractNalUnitTypeForAvc,
|
||||
extractNalUnitTypeForHevc,
|
||||
EAC3_NUMBLKS_TABLE,
|
||||
getEac3ChannelCount,
|
||||
getEac3SampleRate,
|
||||
HevcDecoderConfigurationRecord,
|
||||
HevcNalUnitType,
|
||||
parseAc3SyncFrame,
|
||||
parseAvcSps,
|
||||
parseEac3SyncFrame,
|
||||
parseHevcSps,
|
||||
AC3_FRAME_SIZES,
|
||||
} from '../codec-data';
|
||||
import { Demuxer } from '../demuxer';
|
||||
import { Input } from '../input';
|
||||
@@ -266,7 +275,20 @@ export class MpegTsDemuxer extends Demuxer {
|
||||
bitstream.skipBits(6);
|
||||
const esInfoLength = bitstream.readBits(10);
|
||||
|
||||
bitstream.skipBits(8 * esInfoLength);
|
||||
// Check ES descriptors to detect AC-3/E-AC-3 in System B
|
||||
const esInfoEndPos = bitstream.pos + 8 * esInfoLength;
|
||||
let hasAc3Descriptor = false;
|
||||
let hasEac3Descriptor = false;
|
||||
while (bitstream.pos < esInfoEndPos) {
|
||||
const descriptorTag = bitstream.readBits(8);
|
||||
const descriptorLength = bitstream.readBits(8);
|
||||
if (descriptorTag === 0x6a) {
|
||||
hasAc3Descriptor = true;
|
||||
} else if (descriptorTag === 0x7a || descriptorTag === 0xcc) {
|
||||
hasEac3Descriptor = true;
|
||||
}
|
||||
bitstream.skipBits(8 * descriptorLength);
|
||||
}
|
||||
|
||||
let info: ElementaryStream['info'] | null = null;
|
||||
|
||||
@@ -306,6 +328,46 @@ export class MpegTsDemuxer extends Demuxer {
|
||||
};
|
||||
}; break;
|
||||
|
||||
case MpegTsStreamType.AC3_SYSTEM_A: {
|
||||
info = {
|
||||
type: 'audio',
|
||||
codec: 'ac3',
|
||||
aacCodecInfo: null,
|
||||
numberOfChannels: -1,
|
||||
sampleRate: -1,
|
||||
};
|
||||
}; break;
|
||||
|
||||
case MpegTsStreamType.EAC3_SYSTEM_A: {
|
||||
info = {
|
||||
type: 'audio',
|
||||
codec: 'eac3',
|
||||
aacCodecInfo: null,
|
||||
numberOfChannels: -1,
|
||||
sampleRate: -1,
|
||||
};
|
||||
}; break;
|
||||
|
||||
case MpegTsStreamType.PRIVATE_DATA: {
|
||||
if (hasEac3Descriptor) {
|
||||
info = {
|
||||
type: 'audio',
|
||||
codec: 'eac3',
|
||||
aacCodecInfo: null,
|
||||
numberOfChannels: -1,
|
||||
sampleRate: -1,
|
||||
};
|
||||
} else if (hasAc3Descriptor) {
|
||||
info = {
|
||||
type: 'audio',
|
||||
codec: 'ac3',
|
||||
aacCodecInfo: null,
|
||||
numberOfChannels: -1,
|
||||
sampleRate: -1,
|
||||
};
|
||||
}
|
||||
}; break;
|
||||
|
||||
default: {
|
||||
// If we don't recognize the codec, we don't surface the track at all. This is because
|
||||
// we can't determine its metadata and also have no idea how to packetize its data.
|
||||
@@ -436,6 +498,44 @@ export class MpegTsDemuxer extends Demuxer {
|
||||
elementaryStream.info.numberOfChannels = result.header.channel === 3 ? 1 : 2;
|
||||
elementaryStream.info.sampleRate = result.header.sampleRate;
|
||||
|
||||
elementaryStream.initialized = true;
|
||||
} else if (elementaryStream.info.codec === 'ac3') {
|
||||
const frameInfo = parseAc3SyncFrame(pesPacket.data);
|
||||
if (!frameInfo) {
|
||||
throw new Error(
|
||||
'Invalid AC-3 audio stream; could not read sync frame from first packet.',
|
||||
);
|
||||
}
|
||||
|
||||
if (frameInfo.fscod === 3) {
|
||||
throw new Error(
|
||||
'Invalid AC-3 audio stream; reserved sample rate code found in first packet.',
|
||||
);
|
||||
}
|
||||
|
||||
elementaryStream.info.numberOfChannels
|
||||
= AC3_ACMOD_CHANNEL_COUNTS[frameInfo.acmod]! + frameInfo.lfeon;
|
||||
elementaryStream.info.sampleRate = AC3_SAMPLE_RATES[frameInfo.fscod]!;
|
||||
|
||||
elementaryStream.initialized = true;
|
||||
} else if (elementaryStream.info.codec === 'eac3') {
|
||||
const frameInfo = parseEac3SyncFrame(pesPacket.data);
|
||||
if (!frameInfo) {
|
||||
throw new Error(
|
||||
'Invalid E-AC-3 audio stream; could not read sync frame from first packet.',
|
||||
);
|
||||
}
|
||||
|
||||
const sampleRate = getEac3SampleRate(frameInfo);
|
||||
if (sampleRate === null) {
|
||||
throw new Error(
|
||||
'Invalid E-AC-3 audio stream; reserved sample rate code found in first packet.',
|
||||
);
|
||||
}
|
||||
|
||||
elementaryStream.info.numberOfChannels = getEac3ChannelCount(frameInfo);
|
||||
elementaryStream.info.sampleRate = sampleRate;
|
||||
|
||||
elementaryStream.initialized = true;
|
||||
} else {
|
||||
throw new Error('Unhandled.');
|
||||
@@ -1686,6 +1786,91 @@ const markNextPacket = async (context: PacketReadingContext) => {
|
||||
} else {
|
||||
context.seekTo(possibleHeaderStartPos + 1);
|
||||
}
|
||||
} else if (codec === 'ac3') {
|
||||
if (byte !== 0x0b) {
|
||||
continue;
|
||||
}
|
||||
|
||||
context.skip(-1);
|
||||
const possibleSyncPos = context.currentPos;
|
||||
|
||||
// Need at least 5 bytes for sync word + CRC + fscod/frmsizecod
|
||||
let remaining = context.ensureBuffered(5);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
|
||||
if (remaining < 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
const headerBytes = context.readBytes(5);
|
||||
|
||||
// Verify sync word (0x0B77)
|
||||
if (headerBytes[0] !== 0x0b || headerBytes[1] !== 0x77) {
|
||||
context.seekTo(possibleSyncPos + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const fscod = headerBytes[4]! >> 6;
|
||||
const frmsizecod = headerBytes[4]! & 0x3f;
|
||||
|
||||
if (fscod === 3 || frmsizecod > 37) {
|
||||
// Invalid
|
||||
context.seekTo(possibleSyncPos + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const frameSize = AC3_FRAME_SIZES[3 * frmsizecod + fscod];
|
||||
assert(frameSize !== undefined);
|
||||
|
||||
context.seekTo(possibleSyncPos);
|
||||
|
||||
remaining = context.ensureBuffered(frameSize);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
|
||||
const duration = Math.round(
|
||||
AC3_SAMPLES_PER_FRAME * TIMESCALE / elementaryStream.info.sampleRate,
|
||||
);
|
||||
return context.supplyPacket(remaining, duration);
|
||||
} else if (codec === 'eac3') {
|
||||
if (byte !== 0x0b) {
|
||||
continue;
|
||||
}
|
||||
|
||||
context.skip(-1);
|
||||
const possibleSyncPos = context.currentPos;
|
||||
|
||||
// Need at least 5 bytes for E-AC-3 header parsing (sync word + frmsiz + fscod/numblkscod)
|
||||
let remaining = context.ensureBuffered(5);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
|
||||
if (remaining < 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
const headerBytes = context.readBytes(5);
|
||||
|
||||
if (headerBytes[0] !== 0x0b || headerBytes[1] !== 0x77) {
|
||||
context.seekTo(possibleSyncPos + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const frmsiz = ((headerBytes[2]! & 0x07) << 8) | headerBytes[3]!;
|
||||
const frameSize = (frmsiz + 1) * 2;
|
||||
const fscod = headerBytes[4]! >> 6;
|
||||
const numblkscod = fscod === 3 ? 3 : (headerBytes[4]! >> 4) & 0x03;
|
||||
const numblks = EAC3_NUMBLKS_TABLE[numblkscod]!;
|
||||
|
||||
context.seekTo(possibleSyncPos);
|
||||
|
||||
remaining = context.ensureBuffered(frameSize);
|
||||
if (remaining instanceof Promise) remaining = await remaining;
|
||||
|
||||
// Duration = numblks * 256 samples per block
|
||||
const samplesPerFrame = numblks * 256;
|
||||
const duration = Math.round(
|
||||
samplesPerFrame * TIMESCALE / elementaryStream.info.sampleRate,
|
||||
);
|
||||
return context.supplyPacket(remaining, duration);
|
||||
} else {
|
||||
throw new Error('Unhandled.');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ export const enum MpegTsStreamType {
|
||||
MP3_MPEG1 = 0x03,
|
||||
MP3_MPEG2 = 0x04,
|
||||
AAC = 0x0f,
|
||||
AC3_SYSTEM_A = 0x81,
|
||||
EAC3_SYSTEM_A = 0x87,
|
||||
PRIVATE_DATA = 0x06,
|
||||
AVC = 0x1b,
|
||||
HEVC = 0x24,
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
import { parseAacAudioSpecificConfig, validateAudioChunkMetadata, validateVideoChunkMetadata } from '../codec';
|
||||
import { buildAdtsHeaderTemplate, writeAdtsFrameLength } from '../adts/adts-misc';
|
||||
import {
|
||||
AC3_REGISTRATION_DESCRIPTOR,
|
||||
AvcDecoderConfigurationRecord,
|
||||
AvcNalUnitType,
|
||||
concatNalUnitsInAnnexB,
|
||||
deserializeAvcDecoderConfigurationRecord,
|
||||
deserializeHevcDecoderConfigurationRecord,
|
||||
EAC3_REGISTRATION_DESCRIPTOR,
|
||||
extractNalUnitTypeForAvc,
|
||||
extractNalUnitTypeForHevc,
|
||||
HevcDecoderConfigurationRecord,
|
||||
@@ -151,11 +153,34 @@ export class MpegTsMuxer extends Muxer {
|
||||
assert(meta?.decoderConfig);
|
||||
|
||||
const codec = track.source._codec;
|
||||
assert(codec === 'aac' || codec === 'mp3');
|
||||
assert(codec === 'aac' || codec === 'mp3' || codec === 'ac3' || codec === 'eac3');
|
||||
|
||||
let streamType: MpegTsStreamType;
|
||||
let streamId: number;
|
||||
|
||||
switch (codec) {
|
||||
case 'aac': {
|
||||
streamType = MpegTsStreamType.AAC;
|
||||
streamId = AUDIO_STREAM_ID_BASE + this.audioTrackIndex++;
|
||||
}; break;
|
||||
|
||||
case 'mp3': {
|
||||
streamType = MpegTsStreamType.MP3_MPEG1;
|
||||
streamId = AUDIO_STREAM_ID_BASE + this.audioTrackIndex++;
|
||||
}; break;
|
||||
|
||||
case 'ac3': {
|
||||
streamType = MpegTsStreamType.AC3_SYSTEM_A;
|
||||
streamId = 0xbd;
|
||||
}; break;
|
||||
|
||||
case 'eac3': {
|
||||
streamType = MpegTsStreamType.EAC3_SYSTEM_A;
|
||||
streamId = 0xbd;
|
||||
}; break;
|
||||
}
|
||||
|
||||
const streamType = codec === 'aac' ? MpegTsStreamType.AAC : MpegTsStreamType.MP3_MPEG1;
|
||||
const pid = FIRST_TRACK_PID + this.trackDatas.length;
|
||||
const streamId = AUDIO_STREAM_ID_BASE + this.audioTrackIndex++;
|
||||
|
||||
const newTrackData: MpegTsTrackData = {
|
||||
track,
|
||||
@@ -373,7 +398,7 @@ export class MpegTsMuxer extends Muxer {
|
||||
): Uint8Array {
|
||||
const codec = (trackData.track as OutputAudioTrack).source._codec;
|
||||
|
||||
if (codec === 'mp3') {
|
||||
if (codec === 'mp3' || codec === 'ac3' || codec === 'eac3') {
|
||||
// We're good
|
||||
return packet.data;
|
||||
}
|
||||
@@ -695,7 +720,18 @@ const PAT_SECTION = new Uint8Array(16);
|
||||
}
|
||||
|
||||
const buildPmt = (trackDatas: MpegTsTrackData[]) => {
|
||||
const sectionLength = 9 + trackDatas.length * 5 + 4;
|
||||
let totalEsBytes = 0;
|
||||
for (const trackData of trackDatas) {
|
||||
totalEsBytes += 5;
|
||||
|
||||
if (trackData.streamType === MpegTsStreamType.AC3_SYSTEM_A) {
|
||||
totalEsBytes += AC3_REGISTRATION_DESCRIPTOR.length;
|
||||
} else if (trackData.streamType === MpegTsStreamType.EAC3_SYSTEM_A) {
|
||||
totalEsBytes += EAC3_REGISTRATION_DESCRIPTOR.length;
|
||||
}
|
||||
}
|
||||
|
||||
const sectionLength = 9 + totalEsBytes + 4;
|
||||
const section = new Uint8Array(3 + sectionLength - 4);
|
||||
const view = toDataView(section);
|
||||
|
||||
@@ -714,8 +750,21 @@ const buildPmt = (trackDatas: MpegTsTrackData[]) => {
|
||||
section[offset++] = trackData.streamType; // stream_type
|
||||
view.setUint16(offset, 0xE000 | (trackData.pid & 0x1FFF), false); // reserved=111, elementary_PID
|
||||
offset += 2;
|
||||
view.setUint16(offset, 0xF000, false); // reserved=1111, ES_info_length=0
|
||||
offset += 2;
|
||||
|
||||
if (trackData.streamType === MpegTsStreamType.AC3_SYSTEM_A) {
|
||||
view.setUint16(offset, 0xF000 | AC3_REGISTRATION_DESCRIPTOR.length, false);
|
||||
offset += 2;
|
||||
section.set(AC3_REGISTRATION_DESCRIPTOR, offset);
|
||||
offset += AC3_REGISTRATION_DESCRIPTOR.length;
|
||||
} else if (trackData.streamType === MpegTsStreamType.EAC3_SYSTEM_A) {
|
||||
view.setUint16(offset, 0xF000 | EAC3_REGISTRATION_DESCRIPTOR.length, false);
|
||||
offset += 2;
|
||||
section.set(EAC3_REGISTRATION_DESCRIPTOR, offset);
|
||||
offset += EAC3_REGISTRATION_DESCRIPTOR.length;
|
||||
} else {
|
||||
view.setUint16(offset, 0xF000, false); // reserved=1111, ES_info_length=0
|
||||
offset += 2;
|
||||
}
|
||||
}
|
||||
|
||||
const crc = computeMpegTsCrc32(section);
|
||||
|
||||
+43
-2
@@ -74,6 +74,13 @@ export abstract class OutputFormat {
|
||||
abstract getSupportedTrackCounts(): TrackCountLimits;
|
||||
/** Whether this output format supports video rotation metadata. */
|
||||
abstract get supportsVideoRotationMetadata(): boolean;
|
||||
/**
|
||||
* Whether this output format's tracks store timestamped media data. When `true`, the timestamps of added packets
|
||||
* will be respected, allowing things like gaps in media data or non-zero start times. When `false`, the format's
|
||||
* media data implicitly starts at zero and follows an implicit sequential timing from there, using the intrinsic
|
||||
* durations of the media data.
|
||||
*/
|
||||
abstract get supportsTimestampedMediaData(): boolean;
|
||||
|
||||
/** Returns a list of video codecs that this output format can contain. */
|
||||
getSupportedVideoCodecs() {
|
||||
@@ -258,6 +265,10 @@ export abstract class IsobmffOutputFormat extends OutputFormat {
|
||||
return true;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_createMuxer(output: Output) {
|
||||
return new IsobmffMuxer(output, this);
|
||||
@@ -292,7 +303,8 @@ export class Mp4OutputFormat extends IsobmffOutputFormat {
|
||||
return [
|
||||
...VIDEO_CODECS,
|
||||
...NON_PCM_AUDIO_CODECS,
|
||||
// These are supported via ISO/IEC 23003-5
|
||||
|
||||
// These are supported via ISO/IEC 23003-5:
|
||||
'pcm-s16',
|
||||
'pcm-s16be',
|
||||
'pcm-s24',
|
||||
@@ -303,6 +315,7 @@ export class Mp4OutputFormat extends IsobmffOutputFormat {
|
||||
'pcm-f32be',
|
||||
'pcm-f64',
|
||||
'pcm-f64be',
|
||||
|
||||
...SUBTITLE_CODECS,
|
||||
];
|
||||
}
|
||||
@@ -488,6 +501,10 @@ export class MkvOutputFormat extends OutputFormat {
|
||||
// While it technically does support it with ProjectionPoseRoll, many players appear to ignore this value
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,6 +641,10 @@ export class Mp3OutputFormat extends OutputFormat {
|
||||
get supportsVideoRotationMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -722,6 +743,10 @@ export class WavOutputFormat extends OutputFormat {
|
||||
get supportsVideoRotationMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -813,6 +838,10 @@ export class OggOutputFormat extends OutputFormat {
|
||||
get supportsVideoRotationMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -887,6 +916,10 @@ export class AdtsOutputFormat extends OutputFormat {
|
||||
get supportsVideoRotationMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -958,6 +991,10 @@ export class FlacOutputFormat extends OutputFormat {
|
||||
get supportsVideoRotationMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1032,11 +1069,15 @@ export class MpegTsOutputFormat extends OutputFormat {
|
||||
getSupportedCodecs(): MediaCodec[] {
|
||||
return [
|
||||
...VIDEO_CODECS.filter(codec => ['avc', 'hevc'].includes(codec)),
|
||||
...AUDIO_CODECS.filter(codec => ['aac', 'mp3'].includes(codec)),
|
||||
...AUDIO_CODECS.filter(codec => ['aac', 'mp3', 'ac3', 'eac3'].includes(codec)),
|
||||
];
|
||||
}
|
||||
|
||||
get supportsVideoRotationMetadata() {
|
||||
return false;
|
||||
}
|
||||
|
||||
get supportsTimestampedMediaData() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -56,7 +56,10 @@ export class EncodedPacket {
|
||||
|
||||
/** Creates a new {@link EncodedPacket} from raw bytes and timing information. */
|
||||
constructor(
|
||||
/** The encoded data of this packet. */
|
||||
/**
|
||||
* The encoded data of this packet. For any given codec, this data must adhere to the format specified in the
|
||||
* Mediabunny Codec Registry.
|
||||
*/
|
||||
public readonly data: Uint8Array,
|
||||
/** The type of this packet. */
|
||||
public readonly type: PacketType,
|
||||
|
||||
@@ -425,9 +425,7 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
|
||||
packetIndex: number,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
if (packetIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
assert(packetIndex >= 0);
|
||||
|
||||
assert(this.demuxer.audioInfo);
|
||||
const startOffset = packetIndex * PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes;
|
||||
@@ -493,6 +491,9 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
|
||||
timestamp * this.demuxer.audioInfo.sampleRate / PACKET_SIZE_IN_FRAMES,
|
||||
(this.demuxer.dataSize - 1) / (PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes),
|
||||
));
|
||||
if (packetIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const packet = await this.getPacketAtIndex(packetIndex, options);
|
||||
if (packet) {
|
||||
|
||||
@@ -16,7 +16,7 @@ test('MPEG-TS output format', async () => {
|
||||
expect(format.mimeType).toBe('video/MP2T');
|
||||
expect(format.fileExtension).toBe('.ts');
|
||||
expect(format.supportsVideoRotationMetadata).toBe(false);
|
||||
expect(format.getSupportedCodecs()).toEqual(['avc', 'hevc', 'aac', 'mp3']);
|
||||
expect(format.getSupportedCodecs()).toEqual(['avc', 'hevc', 'aac', 'mp3', 'ac3', 'eac3']);
|
||||
expect(format.getSupportedTrackCounts()).toEqual({
|
||||
video: { min: 0, max: 16 },
|
||||
audio: { min: 0, max: 32 },
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { Input } from '../../src/input.js';
|
||||
import { BufferSource, FilePathSource } from '../../src/source.js';
|
||||
import path from 'node:path';
|
||||
import { ALL_FORMATS } from '../../src/input-format.js';
|
||||
import { Output } from '../../src/output.js';
|
||||
import { MpegTsOutputFormat } from '../../src/output-format.js';
|
||||
import { BufferTarget } from '../../src/target.js';
|
||||
import { Conversion } from '../../src/conversion.js';
|
||||
import { AC3_REGISTRATION_DESCRIPTOR, EAC3_REGISTRATION_DESCRIPTOR } from '../../src/codec-data.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
test('reads AC-3 from MP4', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/ac3.mp4')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = (await input.getPrimaryAudioTrack())!;
|
||||
const decoderConfig = (await audioTrack.getDecoderConfig())!;
|
||||
|
||||
expect(audioTrack.codec).toBe('ac3');
|
||||
expect(decoderConfig.description).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads E-AC-3 from MP4', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/eac3.mp4')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = (await input.getPrimaryAudioTrack())!;
|
||||
const decoderConfig = (await audioTrack.getDecoderConfig())!;
|
||||
|
||||
expect(audioTrack.codec).toBe('eac3');
|
||||
expect(decoderConfig.description).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads and writes AC-3 in MPEG-TS', async () => {
|
||||
using originalInput = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/ac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const originalAudioTrack = (await originalInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(originalAudioTrack.codec).toBe('ac3');
|
||||
expect(originalAudioTrack.internalCodecId).toBe(0x81);
|
||||
|
||||
const output = new Output({
|
||||
format: new MpegTsOutputFormat(),
|
||||
target: new BufferTarget(),
|
||||
});
|
||||
|
||||
const conversion = await Conversion.init({ input: originalInput, output });
|
||||
await conversion.execute();
|
||||
|
||||
using newInput = new Input({
|
||||
source: new BufferSource(output.target.buffer!),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const newAudioTrack = (await newInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(newAudioTrack.codec).toBe('ac3');
|
||||
expect(newAudioTrack.internalCodecId).toBe(0x81);
|
||||
expect(newAudioTrack.numberOfChannels).toBe(originalAudioTrack.numberOfChannels);
|
||||
expect(newAudioTrack.sampleRate).toBe(originalAudioTrack.sampleRate);
|
||||
|
||||
// Verify registration_descriptor is present
|
||||
const buffer = new Uint8Array(output.target.buffer!);
|
||||
let found = false;
|
||||
for (let i = 0; i < buffer.length - AC3_REGISTRATION_DESCRIPTOR.length; i++) {
|
||||
if (AC3_REGISTRATION_DESCRIPTOR.every((byte, j) => buffer[i + j] === byte)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(found).toBe(true);
|
||||
});
|
||||
|
||||
test('reads and writes E-AC-3 in MPEG-TS', async () => {
|
||||
using originalInput = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '..', 'public/eac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const originalAudioTrack = (await originalInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(originalAudioTrack.codec).toBe('eac3');
|
||||
expect(originalAudioTrack.internalCodecId).toBe(0x87);
|
||||
|
||||
const output = new Output({
|
||||
format: new MpegTsOutputFormat(),
|
||||
target: new BufferTarget(),
|
||||
});
|
||||
|
||||
const conversion = await Conversion.init({ input: originalInput, output });
|
||||
await conversion.execute();
|
||||
|
||||
using newInput = new Input({
|
||||
source: new BufferSource(output.target.buffer!),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const newAudioTrack = (await newInput.getPrimaryAudioTrack())!;
|
||||
|
||||
expect(newAudioTrack.codec).toBe('eac3');
|
||||
expect(newAudioTrack.internalCodecId).toBe(0x87);
|
||||
expect(newAudioTrack.numberOfChannels).toBe(originalAudioTrack.numberOfChannels);
|
||||
expect(newAudioTrack.sampleRate).toBe(originalAudioTrack.sampleRate);
|
||||
|
||||
// Verify registration_descriptor is present
|
||||
const buffer = new Uint8Array(output.target.buffer!);
|
||||
let found = false;
|
||||
for (let i = 0; i < buffer.length - EAC3_REGISTRATION_DESCRIPTOR.length; i++) {
|
||||
if (EAC3_REGISTRATION_DESCRIPTOR.every((byte, j) => buffer[i + j] === byte)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(found).toBe(true);
|
||||
});
|
||||
@@ -9,6 +9,7 @@ import { assert } from '../../src/misc.js';
|
||||
import { EncodedPacketSink } from '../../src/media-sink.js';
|
||||
import { EncodedPacket } from '../../src/packet.js';
|
||||
import { MpegTsDemuxer } from '../../src/mpeg-ts/mpeg-ts-demuxer.js';
|
||||
import { MpegTsStreamType } from '../../src/mpeg-ts/mpeg-ts-misc.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
@@ -612,6 +613,146 @@ test('MPEG-TS with MP3 audio', async () => {
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with AC-3 audio (System A)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/ac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('ac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.AC3_SYSTEM_A);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ac-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with AC-3 audio (System B)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/ac3-system-b.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('ac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.PRIVATE_DATA);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ac-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with E-AC-3 audio (System A)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/eac3.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('eac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.EAC3_SYSTEM_A);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ec-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS with E-AC-3 audio (System B)', async () => {
|
||||
using input = new Input({
|
||||
source: new FilePathSource(path.join(__dirname, '../public/eac3-system-b.ts')),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
assert(audioTrack);
|
||||
|
||||
expect(audioTrack.codec).toBe('eac3');
|
||||
expect(audioTrack.internalCodecId).toBe(MpegTsStreamType.PRIVATE_DATA);
|
||||
|
||||
const audioDecoderConfig = await audioTrack.getDecoderConfig();
|
||||
expect(audioDecoderConfig).toEqual({
|
||||
codec: 'ec-3',
|
||||
numberOfChannels: 6,
|
||||
sampleRate: 48000,
|
||||
});
|
||||
|
||||
const sink = new EncodedPacketSink(audioTrack);
|
||||
|
||||
const firstPacket = await sink.getFirstPacket();
|
||||
assert(firstPacket);
|
||||
|
||||
let count = 0;
|
||||
for await (const packet of sink.packets()) {
|
||||
expect(packet.data[0]).toBe(0x0b);
|
||||
expect(packet.data[1]).toBe(0x77);
|
||||
expect(packet.type).toBe('key');
|
||||
count++;
|
||||
}
|
||||
|
||||
expect(count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('MPEG-TS partial reading', async () => {
|
||||
const fullPath = path.join(__dirname, '../public/193039199_mp4_h264_aac_fhd_7.ts');
|
||||
const buffer = await fs.promises.readFile(fullPath);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user