@blockcast/mmt-render 0.1.0 → 0.3.0-main.36c5f6abe867
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 +5 -4
- package/dist/.build-stamp +0 -0
- package/dist/audio/audio-render-pipeline.js +217 -248
- package/dist/audio/audio-render-pipeline.js.map +2 -2
- package/dist/audio/audio-ring-buffer.js +93 -127
- package/dist/audio/audio-ring-buffer.js.map +1 -1
- package/dist/audio/index.js +313 -361
- package/dist/audio/index.js.map +3 -3
- package/dist/pacing/frame-pacer.d.ts +22 -1
- package/dist/pacing/frame-pacer.d.ts.map +1 -1
- package/dist/pacing/frame-pacer.js +43 -39
- package/dist/pacing/frame-pacer.js.map +2 -2
- package/dist/pacing/index.js +58 -43
- package/dist/pacing/index.js.map +2 -2
- package/dist/pacing/interleave-timing.d.ts +10 -1
- package/dist/pacing/interleave-timing.d.ts.map +1 -1
- package/dist/pacing/interleave-timing.js +15 -4
- package/dist/pacing/interleave-timing.js.map +2 -2
- package/dist/video/index.js +129 -139
- package/dist/video/index.js.map +2 -2
- package/dist/video/keyframe-gate.js +5 -17
- package/dist/video/keyframe-gate.js.map +1 -1
- package/dist/video/video-render-pipeline.js +129 -139
- package/dist/video/video-render-pipeline.js.map +2 -2
- package/dist/worklet/render-worklet.js +171 -219
- package/dist/worklet/render-worklet.js.map +2 -2
- package/package.json +3 -3
- package/src/pacing/frame-pacer.ts +28 -1
- package/src/pacing/interleave-timing.ts +32 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockcast/mmt-render",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-main.36c5f6abe867",
|
|
4
4
|
"description": "Shared MMT render pipeline (FEC-aware pacing, interleave timing, audio worklet) for MoQ players",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"test:watch": "vitest"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@blockcast/mmt-container": "
|
|
77
|
-
"@blockcast/mmt-fec": "
|
|
76
|
+
"@blockcast/mmt-container": "0.3.0-main.36c5f6abe867",
|
|
77
|
+
"@blockcast/mmt-fec": "0.3.0-main.36c5f6abe867"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@types/audioworklet": "^0.0.77",
|
|
@@ -83,13 +83,40 @@ export class FrameCountPacer {
|
|
|
83
83
|
* - pacing is disabled (NullBlockMapper → threshold=0)
|
|
84
84
|
* - the anchor is not yet set
|
|
85
85
|
* - still in warmup
|
|
86
|
+
* - the caller reports a standing backlog (see `observedDelayMs`)
|
|
86
87
|
* - decoder is not ahead of wall clock by more than threshold
|
|
87
88
|
* Otherwise returns aheadMs - thresholdMs/2 (leaves half the threshold as headroom).
|
|
89
|
+
*
|
|
90
|
+
* `observedDelayMs` is how far this frame is behind the producer's own clock,
|
|
91
|
+
* measured by the caller against a producer-supplied sending instant (for
|
|
92
|
+
* MMTP, the header send-wall timestamp). It is optional because the anchored
|
|
93
|
+
* rate below is blind to it: `#anchorWallMs` is the wall time of the FIRST
|
|
94
|
+
* frame read, so `aheadMs` measures consumption against the join instant and
|
|
95
|
+
* not against the producer. Once `aheadMs` reaches the threshold the loop
|
|
96
|
+
* consumes at exactly the source rate, and recovering latency present at join
|
|
97
|
+
* requires consuming FASTER than the source rate. So without this argument a
|
|
98
|
+
* consumer that joined a live stream mid-block keeps that join latency for the
|
|
99
|
+
* life of the subscription.
|
|
100
|
+
*
|
|
101
|
+
* A caller that can measure the real distance from the producer passes it and
|
|
102
|
+
* gets a drain: while the backlog exceeds the threshold there is nothing to
|
|
103
|
+
* pace against, so return 0 and let the transport set the rate. At the live
|
|
104
|
+
* edge the anchored-rate behavior below applies unchanged.
|
|
105
|
+
*
|
|
106
|
+
* Callers with no producer clock omit it and keep the pure ATSC3-style
|
|
107
|
+
* anchored pacing (see the class doc), where a first-frame anchor is correct
|
|
108
|
+
* because a broadcast push has no live edge to catch up to.
|
|
88
109
|
*/
|
|
89
|
-
computeSleepMs(): number {
|
|
110
|
+
computeSleepMs(observedDelayMs?: number): number {
|
|
90
111
|
if (this.thresholdMs <= 0) return 0
|
|
91
112
|
if (!this.#anchored) return 0
|
|
92
113
|
if (this.#frameCount <= this.warmupFrames) return 0
|
|
114
|
+
// Only a finite measurement can license the drain. NaN/Infinity mean the
|
|
115
|
+
// caller could not measure the distance, which is not the same claim as
|
|
116
|
+
// "the consumer is at the edge", so fall through to anchored pacing.
|
|
117
|
+
if (observedDelayMs !== undefined && Number.isFinite(observedDelayMs) && observedDelayMs > this.thresholdMs) {
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
93
120
|
|
|
94
121
|
const mediaMs = this.#frameCount * this.frameDurationMs
|
|
95
122
|
const wallMs = now() - this.#anchorWallMs
|
|
@@ -18,12 +18,22 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Consumers: CMAF segment assembler (block boundary → segment cut points),
|
|
20
20
|
* debug overlays, cross-path FEC combining, sync diagnostics.
|
|
21
|
+
*
|
|
22
|
+
* This is the `k-padded-v1` K-stride pacing model and nothing more: it is
|
|
23
|
+
* presentation-time arithmetic for a stream whose blocks are K symbols and D
|
|
24
|
+
* media groups long, NOT an FEC block locator. FecBlockMapper no longer maps
|
|
25
|
+
* media groups to blocks (BLO-35597), so the stride math lives here, and a
|
|
26
|
+
* stream under any other `fec.blockProfile` (an `iso-ssbg1-v1` block's length
|
|
27
|
+
* is stated per block on the wire, so it has no stride) is refused.
|
|
21
28
|
*/
|
|
22
29
|
|
|
23
30
|
import type { FecBlockMapper } from '@blockcast/mmt-fec'
|
|
24
31
|
|
|
32
|
+
/** 2^32: SS_ID is a 32-bit serial number (ISO/IEC 23008-1 C.5.2). */
|
|
33
|
+
const SS_ID_SPAN = 0x1_0000_0000
|
|
34
|
+
|
|
25
35
|
export interface InterleaveTimingOpts {
|
|
26
|
-
/** Track's FEC mapper (provides K
|
|
36
|
+
/** Track's FEC mapper (provides K and D); must be `k-padded-v1`. */
|
|
27
37
|
mapper: FecBlockMapper
|
|
28
38
|
/** Nominal AU duration in microseconds. */
|
|
29
39
|
frameDurationUs: number
|
|
@@ -35,6 +45,8 @@ export class InterleaveTiming {
|
|
|
35
45
|
readonly mapper: FecBlockMapper
|
|
36
46
|
readonly frameDurationUs: number
|
|
37
47
|
readonly anchorMpuUs: number
|
|
48
|
+
/** Source symbols contributed by each media group: ceil(K / D). */
|
|
49
|
+
readonly symbolsPerGroup: number
|
|
38
50
|
|
|
39
51
|
constructor(opts: InterleaveTimingOpts) {
|
|
40
52
|
if (!(opts.frameDurationUs > 0)) {
|
|
@@ -43,7 +55,14 @@ export class InterleaveTiming {
|
|
|
43
55
|
if (!Number.isFinite(opts.anchorMpuUs)) {
|
|
44
56
|
throw new Error('anchorMpuUs must be finite')
|
|
45
57
|
}
|
|
58
|
+
if (opts.mapper.blockProfile !== 'k-padded-v1') {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`InterleaveTiming is the k-padded-v1 K-stride pacing model; a ${opts.mapper.blockProfile ?? 'non-FEC'} ` +
|
|
61
|
+
'stream has no K-stride block geometry',
|
|
62
|
+
)
|
|
63
|
+
}
|
|
46
64
|
this.mapper = opts.mapper
|
|
65
|
+
this.symbolsPerGroup = Math.ceil(opts.mapper.k / opts.mapper.d)
|
|
47
66
|
this.frameDurationUs = opts.frameDurationUs
|
|
48
67
|
this.anchorMpuUs = opts.anchorMpuUs
|
|
49
68
|
}
|
|
@@ -53,8 +72,14 @@ export class InterleaveTiming {
|
|
|
53
72
|
* Per draft §8.3 + Sony anchor.
|
|
54
73
|
*/
|
|
55
74
|
presentationUs(groupId: number, objectId: number): number {
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
// §8.3 under k-padded-v1: SBN = floor(G / D), ESI = groupInBlock ×
|
|
76
|
+
// symbolsPerGroup + O, SS_ID = SBN × K + ESI (a 32-bit serial number;
|
|
77
|
+
// reduce SBN first so the product stays exact).
|
|
78
|
+
const { k, d } = this.mapper
|
|
79
|
+
const sbn = Math.floor(groupId / d)
|
|
80
|
+
const esi = (groupId - sbn * d) * this.symbolsPerGroup + objectId
|
|
81
|
+
const ssId = ((sbn % SS_ID_SPAN) * k + esi) % SS_ID_SPAN
|
|
82
|
+
return this.anchorMpuUs + ssId * this.frameDurationUs
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
/**
|
|
@@ -71,9 +96,11 @@ export class InterleaveTiming {
|
|
|
71
96
|
*/
|
|
72
97
|
frameAtOffset(offsetUs: number): { groupId: number; objectId: number; ssId: number } {
|
|
73
98
|
const rawSsId = Math.max(0, Math.floor(offsetUs / this.frameDurationUs))
|
|
74
|
-
|
|
99
|
+
// k-padded-v1: SBN = floor(SS_ID / K), ESI = SS_ID mod K.
|
|
100
|
+
const sbn = Math.floor(rawSsId / this.mapper.k)
|
|
101
|
+
const esi = rawSsId % this.mapper.k
|
|
75
102
|
// Invert §8.3: esi = groupInBlock * symbolsPerGroup + objectId
|
|
76
|
-
const spg = this.
|
|
103
|
+
const spg = this.symbolsPerGroup
|
|
77
104
|
const groupInBlock = Math.floor(esi / spg)
|
|
78
105
|
const objectId = esi - groupInBlock * spg
|
|
79
106
|
const groupId = sbn * this.mapper.d + groupInBlock
|