@blockcast/mmt-render 0.1.0-main.c0e9e40265a1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -0
- package/dist/.build-stamp +0 -0
- package/dist/audio/audio-buffer-plan.d.ts +27 -0
- package/dist/audio/audio-buffer-plan.d.ts.map +1 -0
- package/dist/audio/audio-render-pipeline.d.ts +83 -0
- package/dist/audio/audio-render-pipeline.d.ts.map +1 -0
- package/dist/audio/audio-render-pipeline.js +372 -0
- package/dist/audio/audio-render-pipeline.js.map +7 -0
- package/dist/audio/audio-ring-buffer.d.ts +55 -0
- package/dist/audio/audio-ring-buffer.d.ts.map +1 -0
- package/dist/audio/audio-ring-buffer.js +255 -0
- package/dist/audio/audio-ring-buffer.js.map +7 -0
- package/dist/audio/index.d.ts +9 -0
- package/dist/audio/index.d.ts.map +1 -0
- package/dist/audio/index.js +640 -0
- package/dist/audio/index.js.map +7 -0
- package/dist/audio/render-messages.d.ts +94 -0
- package/dist/audio/render-messages.d.ts.map +1 -0
- package/dist/audio/render-messages.js +1 -0
- package/dist/audio/render-messages.js.map +7 -0
- package/dist/audio/render-worklet.d.ts +13 -0
- package/dist/audio/render-worklet.d.ts.map +1 -0
- package/dist/audio/worklet-url.d.ts +25 -0
- package/dist/audio/worklet-url.d.ts.map +1 -0
- package/dist/audio/worklet-url.js +11 -0
- package/dist/audio/worklet-url.js.map +7 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/pacing/au-duration.d.ts +18 -0
- package/dist/pacing/au-duration.d.ts.map +1 -0
- package/dist/pacing/au-duration.js +23 -0
- package/dist/pacing/au-duration.js.map +7 -0
- package/dist/pacing/frame-pacer.d.ts +71 -0
- package/dist/pacing/frame-pacer.d.ts.map +1 -0
- package/dist/pacing/frame-pacer.js +89 -0
- package/dist/pacing/frame-pacer.js.map +7 -0
- package/dist/pacing/index.d.ts +6 -0
- package/dist/pacing/index.d.ts.map +1 -0
- package/dist/pacing/index.js +166 -0
- package/dist/pacing/index.js.map +7 -0
- package/dist/pacing/interleave-timing.d.ts +64 -0
- package/dist/pacing/interleave-timing.d.ts.map +1 -0
- package/dist/pacing/interleave-timing.js +58 -0
- package/dist/pacing/interleave-timing.js.map +7 -0
- package/dist/video/index.d.ts +4 -0
- package/dist/video/index.d.ts.map +1 -0
- package/dist/video/index.js +247 -0
- package/dist/video/index.js.map +7 -0
- package/dist/video/keyframe-gate.d.ts +21 -0
- package/dist/video/keyframe-gate.d.ts.map +1 -0
- package/dist/video/keyframe-gate.js +39 -0
- package/dist/video/keyframe-gate.js.map +7 -0
- package/dist/video/video-render-pipeline.d.ts +60 -0
- package/dist/video/video-render-pipeline.d.ts.map +1 -0
- package/dist/video/video-render-pipeline.js +247 -0
- package/dist/video/video-render-pipeline.js.map +7 -0
- package/dist/worklet/render-worklet.js +403 -0
- package/dist/worklet/render-worklet.js.map +7 -0
- package/package.json +101 -0
- package/src/audio/audio-buffer-plan.ts +62 -0
- package/src/audio/audio-render-pipeline.ts +313 -0
- package/src/audio/audio-ring-buffer.ts +281 -0
- package/src/audio/index.ts +14 -0
- package/src/audio/render-messages.ts +110 -0
- package/src/audio/render-worklet.ts +161 -0
- package/src/audio/worklet-url.ts +30 -0
- package/src/index.ts +13 -0
- package/src/pacing/au-duration.ts +34 -0
- package/src/pacing/frame-pacer.ts +129 -0
- package/src/pacing/index.ts +5 -0
- package/src/pacing/interleave-timing.ts +95 -0
- package/src/video/index.ts +7 -0
- package/src/video/keyframe-gate.ts +35 -0
- package/src/video/video-render-pipeline.ts +179 -0
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# @blockcast/mmt-render
|
|
2
|
+
|
|
3
|
+
Shared render pipeline for MMT players: FEC-aware wall-clock pacing, per-AU presentation-time reconstruction under FEC interleave, an `AudioWorkletProcessor` with an exact-timestamp PCM FIFO, and a keyframe-gated `VideoDecoder` driver. The package is the canonical substrate for moqtail, `hang-mmt-fec`, and Shaka integrations so they do not independently invent timing, buffering, or sample-correction policy. It is *not* a "reference renderer + diagnostics UI" — there is no DOM, chart, or catalog browser. Diagnostics are structured snapshots and worklet telemetry messages that callers can render however they like.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @blockcast/mmt-render
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer-installs through normal deps: `@blockcast/mmt-container` (provides `MmtpDecodePipeline` and the FEC-aware MFU reassembler) and `@blockcast/mmt-fec` (provides `FecBlockMapper`, which carries `pacingThresholdMs`, `K`, `D`, `symbolsPerGroup`).
|
|
12
|
+
|
|
13
|
+
## Quick start (audio)
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { AudioRenderPipeline } from "@blockcast/mmt-render/audio"
|
|
17
|
+
import { FecBlockMapper } from "@blockcast/mmt-fec"
|
|
18
|
+
|
|
19
|
+
const ctx = new AudioContext({ sampleRate: 48000 })
|
|
20
|
+
const mapper = new FecBlockMapper({ interleaveDepthMs: 66, gopDurationMs: 21.33, k: 32 })
|
|
21
|
+
|
|
22
|
+
const audio = new AudioRenderPipeline({
|
|
23
|
+
context: ctx,
|
|
24
|
+
codec: "mp4a.40.2",
|
|
25
|
+
packetId: 0x0011,
|
|
26
|
+
sampleRate: 48000,
|
|
27
|
+
channels: 2,
|
|
28
|
+
description: aacEsdsDescription, // from catalog / init segment
|
|
29
|
+
mapper,
|
|
30
|
+
reassemblyPolicy: { transport: "reliable-moq" },
|
|
31
|
+
latencyMs: mapper.interleaveDepthMs, // one catalog FEC block
|
|
32
|
+
diag: true,
|
|
33
|
+
onDiag: (d) => console.log("ring", d),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
await audio.ready()
|
|
37
|
+
audio.rootNode.connect(ctx.destination)
|
|
38
|
+
|
|
39
|
+
for await (const obj of moqSubscription) {
|
|
40
|
+
audio.feedMoqObject(obj.payload)
|
|
41
|
+
const sleep = audio.paceSleepMs()
|
|
42
|
+
if (sleep > 0) await new Promise((r) => setTimeout(r, sleep))
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick start (video)
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { VideoRenderPipeline } from "@blockcast/mmt-render/video"
|
|
50
|
+
|
|
51
|
+
const video = new VideoRenderPipeline({
|
|
52
|
+
codec: "hev1.1.6.L120.90",
|
|
53
|
+
packetId: 0x0010,
|
|
54
|
+
description: hvccBox,
|
|
55
|
+
width: 1920,
|
|
56
|
+
height: 1080,
|
|
57
|
+
framerate: 30,
|
|
58
|
+
mapper,
|
|
59
|
+
reassemblyPolicy: { transport: "reliable-moq" },
|
|
60
|
+
onFrame: (frame) => {
|
|
61
|
+
canvas.getContext("2d")?.drawImage(frame, 0, 0)
|
|
62
|
+
frame.close()
|
|
63
|
+
},
|
|
64
|
+
onError: (e) => console.warn(e),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
for await (const obj of moqSubscription) {
|
|
68
|
+
video.feedMoqObject(obj.payload)
|
|
69
|
+
const sleep = video.paceSleepMs()
|
|
70
|
+
if (sleep > 0) await new Promise((r) => setTimeout(r, sleep))
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Exports
|
|
75
|
+
|
|
76
|
+
Per `package.json`:
|
|
77
|
+
|
|
78
|
+
| Subpath | Symbols |
|
|
79
|
+
|---------|---------|
|
|
80
|
+
| `@blockcast/mmt-render` | re-exports of `./pacing`, `./audio`, `./video` |
|
|
81
|
+
| `@blockcast/mmt-render/pacing` | `FrameCountPacer`, `InterleaveTiming`, `auDurationMsFromCodec`, `pictureDurationMs` |
|
|
82
|
+
| `@blockcast/mmt-render/pacing/frame-pacer` | `FrameCountPacer` only |
|
|
83
|
+
| `@blockcast/mmt-render/pacing/interleave-timing` | `InterleaveTiming` only |
|
|
84
|
+
| `@blockcast/mmt-render/audio` | `AudioRenderPipeline`, `AudioRingBuffer`, message types, `WORKLET_PROCESSOR_NAME`, `workletModuleUrl` |
|
|
85
|
+
| `@blockcast/mmt-render/audio/ring-buffer` | `AudioRingBuffer` only |
|
|
86
|
+
| `@blockcast/mmt-render/audio/render-messages` | `FromMain` / `ToMain` message types |
|
|
87
|
+
| `@blockcast/mmt-render/audio/render-pipeline` | `AudioRenderPipeline` only |
|
|
88
|
+
| `@blockcast/mmt-render/audio/worklet-url` | `workletModuleUrl()`, `WORKLET_PROCESSOR_NAME` |
|
|
89
|
+
| `@blockcast/mmt-render/audio/worklet.js` | Prebuilt self-contained worklet IIFE — pass to `audioWorklet.addModule()` |
|
|
90
|
+
| `@blockcast/mmt-render/video` | `VideoRenderPipeline`, `KeyframeGate`, frame meta types |
|
|
91
|
+
| `@blockcast/mmt-render/video/keyframe-gate` | `KeyframeGate` only |
|
|
92
|
+
| `@blockcast/mmt-render/video/render-pipeline` | `VideoRenderPipeline` only |
|
|
93
|
+
|
|
94
|
+
The fine-grained subpaths exist so a lean consumer (e.g. moqtail's `player.ts`) can import just the pacer + AU-duration helpers without pulling in `@blockcast/mmt-container` (see `src/audio/index.ts:5-7`).
|
|
95
|
+
|
|
96
|
+
## Components
|
|
97
|
+
|
|
98
|
+
### `FrameCountPacer` (`src/pacing/frame-pacer.ts`)
|
|
99
|
+
|
|
100
|
+
Media-agnostic wall-clock pacer. Anchors on the first `notifyFrame()` call; computes media time as `frameCount × frameDurationMs`. Sleeps the read loop when the decoder runs ahead of wall-clock by more than `mapper.pacingThresholdMs`. Returns `0` for `NullBlockMapper` (pacing disabled). Why not derive media time from MPU timestamps? Under FEC interleaving, encoders pack multiple AUs per MMTP MPU with a single `mpu_timestamp`; the observed delta is a fraction of the real per-AU duration, so timestamp-driven pacing never sleeps and lets burst delivery saturate the decoder. See the file-level comment for the Sony libatsc3 reference.
|
|
101
|
+
|
|
102
|
+
`PacerSnapshot` exposes `frameCount`, `mediaMs`, `wallMs`, `aheadMs`, `thresholdMs`, `anchored` for diagnostics overlays.
|
|
103
|
+
|
|
104
|
+
### `InterleaveTiming` (`src/pacing/interleave-timing.ts`)
|
|
105
|
+
|
|
106
|
+
Per-AU presentation-time reconstruction from FEC coordinates. Implements `draft-ramadan-moq-fec-00 §8.3`:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
au_index_in_block = groupInBlock × symbolsPerGroup + objectId
|
|
110
|
+
ssId = sbn × K + au_index_in_block
|
|
111
|
+
presentationUs = anchorMpuUs + ssId × frameDurationUs
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Same answer whether the AU arrived via MoQ unicast (group_id/object_id) or multicast UDP (SS_ID from the FEC Payload ID). Methods: `presentationUs(groupId, objectId)`, `presentationUsFromSsId(ssId)`, `frameAtOffset(offsetUs)` (inverse), `blockTimeRangeUs(sbn)`. Consumers: CMAF segment cut points, debug overlays, cross-path FEC combining.
|
|
115
|
+
|
|
116
|
+
### `auDurationMsFromCodec` / `pictureDurationMs` (`src/pacing/au-duration.ts`)
|
|
117
|
+
|
|
118
|
+
Nominal AU duration helpers. AAC-LC = 1024 samples; Opus default = 960; FLAC = 4096. Throws on unknown codec — no silent fallback. Lives in the pacing subpath specifically to avoid pulling in `mmt-container`.
|
|
119
|
+
|
|
120
|
+
### `AudioRingBuffer` (`src/audio/audio-ring-buffer.ts`)
|
|
121
|
+
|
|
122
|
+
Timestamp-anchored PCM FIFO. Decoder output stays in emission order, and every decoded chunk adds an exact MMTP timestamp anchor for playout reporting. Timestamps never reposition samples, guess discontinuities, or trigger sample correction. Startup waits for the full caller-supplied, catalog-derived capacity. Underflow re-stalls until that capacity is full again. Source changes use an explicit `reset`; there is no elapsed-time heuristic. The compatibility `skips` and `dups` telemetry fields remain present but are always zero.
|
|
123
|
+
|
|
124
|
+
Optional telemetry via `getDiagAndReset()`: `reads`, `avgFill`, `capacity`, `skips`, `dups`, `overflowSamples`.
|
|
125
|
+
|
|
126
|
+
### `AudioRenderPipeline` (`src/audio/audio-render-pipeline.ts`)
|
|
127
|
+
|
|
128
|
+
End-to-end glue: `MoQ object → MmtpDecodePipeline → WebCodecs AudioDecoder → AudioWorkletNode (ring buffer)`. Owns the worklet, decoder, decode pipeline, and pacer. Audio uses `reorderWindow: 0` because AAC/Opus frames are self-contained.
|
|
129
|
+
|
|
130
|
+
API:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
await pipeline.ready() // load worklet + create decoder
|
|
134
|
+
pipeline.feedMoqObject(packet) // hot path
|
|
135
|
+
pipeline.paceSleepMs() // throttle read loop
|
|
136
|
+
pipeline.rootNode.connect(destination) // connect to AudioContext
|
|
137
|
+
pipeline.resetPacer() // on source switch (MoQ ↔ multicast)
|
|
138
|
+
pipeline.resetPlayout() // drop queued PCM + old timestamp epoch
|
|
139
|
+
pipeline.close() // teardown
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`onState` and `onDiag` callbacks surface worklet messages (`StateMessage`, `DiagMessage`) for player UIs.
|
|
143
|
+
|
|
144
|
+
### `KeyframeGate` (`src/video/keyframe-gate.ts`)
|
|
145
|
+
|
|
146
|
+
Tiny state machine. Drops delta frames until the first keyframe arrives, then opens permanently. `reset()` re-arms after a decoder error so the next IDR reopens the gate. Required for mid-stream subscribe — relays may deliver P/B frames from the middle of a GOP before the first IDR, which yields "key frame required" decoder errors otherwise.
|
|
147
|
+
|
|
148
|
+
### `VideoRenderPipeline` (`src/video/video-render-pipeline.ts`)
|
|
149
|
+
|
|
150
|
+
End-to-end glue for video: `MoQ object → MmtpDecodePipeline → KeyframeGate → WebCodecs VideoDecoder → onFrame(VideoFrame)`. Datagram delivery uses a catalog-derived frame reorder window and explicit catalog/in-band deadline. Reliable MoQ uses protocol-verified sequential groups with no frame window or timeout. Decoder errors trigger an internal `reset()` that closes the gate, resets the pacer, and reconfigures the decoder so the next keyframe re-opens playback.
|
|
151
|
+
|
|
152
|
+
This replaces the `decodeQueueSize > 10 drop-deltas` workaround in moqtail's `webcodecs-backend.ts` and the `reorderWindow = 0 // TODO` in hang-mmt-fec's `video/decoder.ts` (see file header at `src/video/video-render-pipeline.ts:1-15`).
|
|
153
|
+
|
|
154
|
+
## AudioWorklet bundle
|
|
155
|
+
|
|
156
|
+
`AudioWorkletGlobalScope` cannot resolve bare specifiers, so the worklet is shipped as a self-contained IIFE at `dist/worklet/render-worklet.js`. Two ways to load it:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// 1. Vite-style static URL (recommended for bundled apps)
|
|
160
|
+
import workletUrl from "@blockcast/mmt-render/audio/worklet.js?url"
|
|
161
|
+
await ctx.audioWorklet.addModule(workletUrl)
|
|
162
|
+
|
|
163
|
+
// 2. import.meta.url resolution (no bundler)
|
|
164
|
+
import { workletModuleUrl } from "@blockcast/mmt-render/audio/worklet-url"
|
|
165
|
+
await ctx.audioWorklet.addModule(workletModuleUrl())
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`AudioRenderPipeline.ready()` uses option (2) internally. Processor name is exported as `WORKLET_PROCESSOR_NAME = "mmt-render"`.
|
|
169
|
+
|
|
170
|
+
## Diagnostics surface
|
|
171
|
+
|
|
172
|
+
The package does not render its own UI. It exposes typed data the host can render:
|
|
173
|
+
|
|
174
|
+
- `FrameCountPacer.snapshot` → `PacerSnapshot` (frameCount, mediaMs, wallMs, aheadMs, thresholdMs, anchored)
|
|
175
|
+
- `AudioRingBuffer.getDiagAndReset()` → `AudioRingBufferDiag` (reads, avgFill, capacity, skips, dups, overflowSamples)
|
|
176
|
+
- Worklet `StateMessage` (exact timestamp, stalled, bufferedFrames) every ~13 ms
|
|
177
|
+
- Worklet `DiagMessage` (fill/overflow/underflow telemetry; skip/dup remain zero) when `diag: true` was passed at init
|
|
178
|
+
- `audio underflow: <ms>ms` `console.warn` when the worklet runs dry (`src/audio/render-worklet.ts:58`)
|
|
179
|
+
|
|
180
|
+
There is **no CMCD `x-bc-*` emission, no catalog (CMSF) inspector, and no MPU/MFU packet inspector** in this package. CMCD lives in `@blockcast/mmt-container/cmcd-producer`; catalog parsing lives in `@blockcast/mmt-container/carp`. This package is purely the render side.
|
|
181
|
+
|
|
182
|
+
## Use cases
|
|
183
|
+
|
|
184
|
+
- **Player implementations.** moqtail and hang-mmt-fec both consume this — adding a third player only requires wiring a transport into `feedMoqObject()`.
|
|
185
|
+
- **Debugging multicast losses.** `PacerSnapshot.aheadMs` going negative + ring-buffer underflow warns indicate the decoder can't keep up; large positive `aheadMs` indicates burst delivery and pacing is throttling correctly.
|
|
186
|
+
- **Customer demos / overlays.** `onState` + `onDiag` callbacks feed real-time HUDs without the host having to instrument the pipeline.
|
|
187
|
+
- **Source-switch testing.** `resetPlayout()` / `VideoRenderPipeline.reset()` clear the old epoch when flipping between MoQ unicast and SSM multicast.
|
|
188
|
+
|
|
189
|
+
## Related packages
|
|
190
|
+
|
|
191
|
+
- `@blockcast/mmt-container` — `MmtpDecodePipeline` (consumed for AU framing), MFU reassembly, catalog parser
|
|
192
|
+
- `@blockcast/mmt-fec` — `FecBlockMapper` (provides `pacingThresholdMs`, `K`, `D`, `symbolsPerGroup`); `NullBlockMapper` disables pacing
|
|
193
|
+
- `@blockcast/mmt-transport` — produces the MoQ objects that feed `feedMoqObject()`
|
|
194
|
+
|
|
195
|
+
## Testing
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pnpm test # vitest, runs src/__tests__/*.test.ts
|
|
199
|
+
pnpm test:watch
|
|
200
|
+
pnpm build # tsc --emitDeclarationOnly + esbuild bundles (incl. worklet IIFE)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Existing suites: `frame-pacer.test.ts`, `interleave-timing.test.ts`, `audio-ring-buffer.test.ts`, `keyframe-gate.test.ts`. The pacer suite uses `vi.spyOn(performance, "now")` to drive deterministic wall time. There is no DOM / `AudioContext` integration test — the worklet is exercised in-browser by the consuming players' E2E suites.
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
Apache-2.0 — see `LICENSE`. (libmmt npm packages are all Apache-2.0; the Rust crates are dual `MIT OR Apache-2.0`.)
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface AudioBurstBufferPlanInput {
|
|
2
|
+
sampleRate: number;
|
|
3
|
+
chunkFrames: number;
|
|
4
|
+
/** Catalog media-group duration. Integer milliseconds may be rounded. */
|
|
5
|
+
groupDurationMs: number;
|
|
6
|
+
/** Optional catalog-derived datagram/FEC delivery span. */
|
|
7
|
+
deliveryWindowMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AudioBurstBufferPlan {
|
|
10
|
+
/** PCM required before playout starts or resumes. */
|
|
11
|
+
startupFrames: number;
|
|
12
|
+
/** Maximum retained PCM, including the next complete delivery burst. */
|
|
13
|
+
capacityFrames: number;
|
|
14
|
+
/** Maximum PCM frames delivered as one catalog-defined burst. */
|
|
15
|
+
deliveryFrames: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Convert catalog delivery timing into exact codec-frame FIFO geometry.
|
|
19
|
+
*
|
|
20
|
+
* `groupDurationMs` is commonly rounded to an integer, so media-group units
|
|
21
|
+
* use nearest-unit rounding. FEC delivery windows are upper bounds and use
|
|
22
|
+
* ceiling. Storage holds the startup burst plus one complete successor burst;
|
|
23
|
+
* this prevents an atomic MoQ group from overwriting PCM while the worklet is
|
|
24
|
+
* draining the preceding group without adding a guessed time margin.
|
|
25
|
+
*/
|
|
26
|
+
export declare function deriveAudioBurstBufferPlan(input: AudioBurstBufferPlanInput): AudioBurstBufferPlan;
|
|
27
|
+
//# sourceMappingURL=audio-buffer-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-buffer-plan.d.ts","sourceRoot":"","sources":["../../src/audio/audio-buffer-plan.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,eAAe,EAAE,MAAM,CAAA;IACvB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAA;IACtB,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAA;CACvB;AAOD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,yBAAyB,GAC/B,oBAAoB,CA2BtB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioRenderPipeline — end-to-end audio render for MoQ/MMT players.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline: MoQ object → MmtpDecodePipeline → WebCodecs AudioDecoder
|
|
5
|
+
* → AudioWorkletNode (exact-timestamp FIFO)
|
|
6
|
+
*
|
|
7
|
+
* The consumer:
|
|
8
|
+
* 1. constructs with codec/sampleRate/channels/mapper from catalog
|
|
9
|
+
* 2. awaits ready()
|
|
10
|
+
* 3. calls feedMoqObject(packet) for each arriving MoQ object
|
|
11
|
+
* 4. between objects, awaits paceSleepMs() to throttle burst delivery
|
|
12
|
+
* 5. connects rootNode to an AudioContext destination (or a GainNode)
|
|
13
|
+
* 6. close() on teardown
|
|
14
|
+
*
|
|
15
|
+
* Pacing uses FrameCountPacer (anchor on first decoded AU, frame count × AU
|
|
16
|
+
* duration) with threshold = mapper.pacingThresholdMs — NOT MPU timestamps
|
|
17
|
+
* (ambiguous under FEC interleave).
|
|
18
|
+
*/
|
|
19
|
+
import { type PipelineFecClient } from '@blockcast/mmt-container/mmtp-decode-pipeline';
|
|
20
|
+
import type { MfuReassemblyPolicy } from '@blockcast/mmt-container/mfu-reassembly-policy';
|
|
21
|
+
import type { FecBlockMapper } from '@blockcast/mmt-fec';
|
|
22
|
+
export interface AudioRenderPipelineOpts {
|
|
23
|
+
context: AudioContext;
|
|
24
|
+
codec: string;
|
|
25
|
+
/** MMTP packet_id for this audio track — from catalog. */
|
|
26
|
+
packetId?: number;
|
|
27
|
+
sampleRate: number;
|
|
28
|
+
channels: number;
|
|
29
|
+
description?: Uint8Array;
|
|
30
|
+
mapper: FecBlockMapper;
|
|
31
|
+
fecClient?: PipelineFecClient;
|
|
32
|
+
/** Reliable MoQ is clock-free; datagram deadlines come from catalog/in-band timing. */
|
|
33
|
+
reassemblyPolicy: MfuReassemblyPolicy;
|
|
34
|
+
/** Catalog-derived PCM startup buffer in milliseconds. */
|
|
35
|
+
latencyMs: number;
|
|
36
|
+
/** Exact PCM startup threshold when catalog grouping is codec-aligned. */
|
|
37
|
+
startupFrames?: number;
|
|
38
|
+
/** Exact PCM storage ceiling, including one successor delivery burst. */
|
|
39
|
+
capacityFrames?: number;
|
|
40
|
+
/** Enable ring-buffer telemetry messages. Default false. */
|
|
41
|
+
diag?: boolean;
|
|
42
|
+
/** Called when worklet reports state (playback timestamp + exact buffer accounting). Optional. */
|
|
43
|
+
onState?: (state: {
|
|
44
|
+
timestampUs: number | undefined;
|
|
45
|
+
stalled: boolean;
|
|
46
|
+
bufferedFrames: number;
|
|
47
|
+
writtenFrames: number;
|
|
48
|
+
}) => void;
|
|
49
|
+
/** Called when worklet reports ring-buffer diag (telemetry). Optional. */
|
|
50
|
+
onDiag?: (diag: {
|
|
51
|
+
reads: number;
|
|
52
|
+
avgFill: number;
|
|
53
|
+
capacity: number;
|
|
54
|
+
skipsPerSec: number;
|
|
55
|
+
dupsPerSec: number;
|
|
56
|
+
overflowSamplesPerSec: number;
|
|
57
|
+
underflowSamplesPerSec: number;
|
|
58
|
+
}) => void;
|
|
59
|
+
/** Warmup frames before pacing engages. Default 0 (audio tolerates instant pacing). */
|
|
60
|
+
warmupFrames?: number;
|
|
61
|
+
/** Audio underflow / config event logger. Optional. */
|
|
62
|
+
onError?: (err: unknown) => void;
|
|
63
|
+
}
|
|
64
|
+
export declare class AudioRenderPipeline {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(opts: AudioRenderPipelineOpts);
|
|
67
|
+
/** Load worklet module + create decoder + pipeline. Call once. */
|
|
68
|
+
ready(): Promise<void>;
|
|
69
|
+
/** Feed one MoQ object (raw MMTP packet). */
|
|
70
|
+
feedMoqObject(packet: Uint8Array): void;
|
|
71
|
+
/** ms to sleep before the next object read. 0 when not anchored / not ahead. */
|
|
72
|
+
paceSleepMs(): number;
|
|
73
|
+
/** Pacer snapshot (for diagnostics / UI). */
|
|
74
|
+
get pacerSnapshot(): import("../pacing/frame-pacer.js").PacerSnapshot;
|
|
75
|
+
/** Reset pacer on source switch (MoQ ↔ multicast). */
|
|
76
|
+
resetPacer(): void;
|
|
77
|
+
/** Drop queued PCM and its timestamp epoch at an explicit source boundary. */
|
|
78
|
+
resetPlayout(startUnstalled?: boolean): void;
|
|
79
|
+
/** Node to connect to destination / gain. */
|
|
80
|
+
get rootNode(): AudioNode;
|
|
81
|
+
close(): void;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=audio-render-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-render-pipeline.d.ts","sourceRoot":"","sources":["../../src/audio/audio-render-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,+CAA+C,CAAA;AAC1G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gDAAgD,CAAA;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAYxD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,UAAU,CAAA;IACxB,MAAM,EAAE,cAAc,CAAA;IACtB,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,uFAAuF;IACvF,gBAAgB,EAAE,mBAAmB,CAAA;IACrC,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,kGAAkG;IAClG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;QAC/B,OAAO,EAAE,OAAO,CAAA;QAChB,cAAc,EAAE,MAAM,CAAA;QACtB,aAAa,EAAE,MAAM,CAAA;KACtB,KAAK,IAAI,CAAA;IACV,0EAA0E;IAC1E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE;QACd,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;QAChD,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,qBAAqB,EAAE,MAAM,CAAA;QACtE,sBAAsB,EAAE,MAAM,CAAA;KAC/B,KAAK,IAAI,CAAA;IACV,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,uDAAuD;IACvD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACjC;AAED,qBAAa,mBAAmB;;gBA6BlB,IAAI,EAAE,uBAAuB;IA+BzC,kEAAkE;IAC5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuG5B,6CAA6C;IAC7C,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIvC,gFAAgF;IAChF,WAAW,IAAI,MAAM;IAIrB,6CAA6C;IAC7C,IAAI,aAAa,qDAEhB;IAED,sDAAsD;IACtD,UAAU,IAAI,IAAI;IAIlB,8EAA8E;IAC9E,YAAY,CAAC,cAAc,UAAQ,GAAG,IAAI;IAa1C,6CAA6C;IAC7C,IAAI,QAAQ,IAAI,SAAS,CAGxB;IAED,KAAK,IAAI,IAAI;CAgCd"}
|