@blockcast/mmt-container 0.1.0
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/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/loc.d.ts +100 -0
- package/dist/loc.d.ts.map +1 -0
- package/dist/loc.js +198 -0
- package/dist/loc.js.map +1 -0
- package/dist/mfu-reassembler.d.ts +92 -0
- package/dist/mfu-reassembler.d.ts.map +1 -0
- package/dist/mfu-reassembler.js +320 -0
- package/dist/mfu-reassembler.js.map +1 -0
- package/dist/mmtp.d.ts +73 -0
- package/dist/mmtp.d.ts.map +1 -0
- package/dist/mmtp.js +210 -0
- package/dist/mmtp.js.map +1 -0
- package/package.json +45 -0
- package/src/index.ts +44 -0
- package/src/loc.ts +218 -0
- package/src/mfu-reassembler.ts +416 -0
- package/src/mmtp.ts +277 -0
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blockcast/mmt-container",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MMTP/LOC container parsing for MMT over MoQ and SSM",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./mmtp": {
|
|
14
|
+
"import": "./dist/mmtp.js",
|
|
15
|
+
"types": "./dist/mmtp.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./mfu": {
|
|
18
|
+
"import": "./dist/mfu-reassembler.js",
|
|
19
|
+
"types": "./dist/mfu-reassembler.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./loc": {
|
|
22
|
+
"import": "./dist/loc.js",
|
|
23
|
+
"types": "./dist/loc.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": ["dist", "src"],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"dev": "tsc --watch",
|
|
30
|
+
"clean": "rm -rf dist",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "^5.3.0",
|
|
36
|
+
"vitest": "^1.0.0"
|
|
37
|
+
},
|
|
38
|
+
"keywords": ["mmt", "mmtp", "loc", "container", "streaming", "iso23008-1"],
|
|
39
|
+
"license": "Apache-2.0",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/Blockcast/libmmt.git",
|
|
43
|
+
"directory": "packages/container"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mmt/container - MMTP/LOC Container Parsing
|
|
3
|
+
*
|
|
4
|
+
* Shared container parsing library for MMT over MoQ and SSM.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// MMTP Parser
|
|
10
|
+
export {
|
|
11
|
+
type MMTPHeader,
|
|
12
|
+
type MPUHeader,
|
|
13
|
+
type MMTPPacket,
|
|
14
|
+
MMTP_HEADER_SIZE,
|
|
15
|
+
MPU_HEADER_SIZE,
|
|
16
|
+
MFU_DU_HEADER_SIZE,
|
|
17
|
+
parseMMTPHeader,
|
|
18
|
+
unwrapMMTP,
|
|
19
|
+
parseMPUHeader,
|
|
20
|
+
extractNALUnit,
|
|
21
|
+
extractTimestamp,
|
|
22
|
+
isFecRepairPacket,
|
|
23
|
+
hasFecPayloadId,
|
|
24
|
+
} from './mmtp.js'
|
|
25
|
+
|
|
26
|
+
// MFU Reassembler
|
|
27
|
+
export {
|
|
28
|
+
type MFUFragment,
|
|
29
|
+
type ReassembledMFU,
|
|
30
|
+
type ReassemblerStats,
|
|
31
|
+
type MFUReassemblerOptions,
|
|
32
|
+
MFUReassembler,
|
|
33
|
+
createFragment,
|
|
34
|
+
processMMTPWithReassembly,
|
|
35
|
+
} from './mfu-reassembler.js'
|
|
36
|
+
|
|
37
|
+
// LOC FEC Extensions
|
|
38
|
+
export {
|
|
39
|
+
LOCHeaderExtensionId,
|
|
40
|
+
FEC_PAYLOAD_ID_MAX,
|
|
41
|
+
fecPack,
|
|
42
|
+
fecUnpack,
|
|
43
|
+
FecPayloadId,
|
|
44
|
+
} from './loc.js'
|
package/src/loc.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mmt/container - LOC FEC Extensions
|
|
3
|
+
*
|
|
4
|
+
* FEC Payload ID helpers for LOC (Low Overhead Container) format
|
|
5
|
+
* Per draft-ramadan-moq-mmt Section 7.3
|
|
6
|
+
*
|
|
7
|
+
* The FEC Payload ID encodes Source Block Number (SBN) and
|
|
8
|
+
* Encoding Symbol ID (ESI) as a 48-bit value: (SBN << 24) | ESI
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* LOC Header Extension IDs
|
|
15
|
+
*/
|
|
16
|
+
export enum LOCHeaderExtensionId {
|
|
17
|
+
CaptureTimestamp = 2,
|
|
18
|
+
VideoFrameMarking = 4,
|
|
19
|
+
AudioLevel = 6,
|
|
20
|
+
VideoConfig = 13,
|
|
21
|
+
/** FEC Payload ID extension (draft-ramadan-moq-mmt Section 7.3) */
|
|
22
|
+
FecPayloadId = 0x10,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Maximum value for SBN and ESI (24 bits each) */
|
|
26
|
+
export const FEC_PAYLOAD_ID_MAX = 0xffffff
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pack Source Block Number (SBN) and Encoding Symbol ID (ESI) into a 48-bit value.
|
|
30
|
+
*
|
|
31
|
+
* Per draft-ramadan-moq-mmt Section 7.3, the FEC Payload ID is encoded as:
|
|
32
|
+
* `(SBN << 24) | ESI` where both SBN and ESI are 24 bits.
|
|
33
|
+
*
|
|
34
|
+
* @param sbn - Source Block Number (24 bits, 0-16777215)
|
|
35
|
+
* @param esi - Encoding Symbol ID (24 bits, 0-16777215)
|
|
36
|
+
* @returns Packed 48-bit value as bigint
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { fecPack } from '@mmt/container/loc'
|
|
41
|
+
* const packed = fecPack(100, 5) // 419430405n
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function fecPack(sbn: number, esi: number): bigint {
|
|
45
|
+
return (BigInt(sbn & 0xffffff) << 24n) | BigInt(esi & 0xffffff)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Unpack a 48-bit FEC Payload ID into Source Block Number and Encoding Symbol ID.
|
|
50
|
+
*
|
|
51
|
+
* Per draft-ramadan-moq-mmt Section 7.3, extracts:
|
|
52
|
+
* - SBN from upper 24 bits
|
|
53
|
+
* - ESI from lower 24 bits
|
|
54
|
+
*
|
|
55
|
+
* @param packed - Packed 48-bit FEC Payload ID value
|
|
56
|
+
* @returns Tuple of [sbn, esi] both as numbers
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { fecPack, fecUnpack } from '@mmt/container/loc'
|
|
61
|
+
* const packed = fecPack(100, 5)
|
|
62
|
+
* const [sbn, esi] = fecUnpack(packed) // [100, 5]
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function fecUnpack(packed: bigint): [sbn: number, esi: number] {
|
|
66
|
+
const sbn = Number((packed >> 24n) & 0xffffffn)
|
|
67
|
+
const esi = Number(packed & 0xffffffn)
|
|
68
|
+
return [sbn, esi]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* FEC Payload ID for LOC extension headers
|
|
73
|
+
*
|
|
74
|
+
* Encodes Source Block Number (SBN) and Encoding Symbol ID (ESI)
|
|
75
|
+
* for FEC recovery with LOC container format.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { FecPayloadId } from '@mmt/container/loc'
|
|
80
|
+
*
|
|
81
|
+
* // Create from SBN/ESI
|
|
82
|
+
* const fecId = new FecPayloadId(100, 5)
|
|
83
|
+
*
|
|
84
|
+
* // Get packed value for extension header
|
|
85
|
+
* const packed = fecId.packed // 419430405n
|
|
86
|
+
*
|
|
87
|
+
* // Create from packed value
|
|
88
|
+
* const fecId2 = FecPayloadId.fromPacked(packed)
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export class FecPayloadId {
|
|
92
|
+
static readonly TYPE = LOCHeaderExtensionId.FecPayloadId
|
|
93
|
+
|
|
94
|
+
constructor(
|
|
95
|
+
/** Source Block Number (24 bits, 0-16777215) */
|
|
96
|
+
public readonly sbn: number,
|
|
97
|
+
/** Encoding Symbol ID (24 bits, 0-16777215) */
|
|
98
|
+
public readonly esi: number,
|
|
99
|
+
) {
|
|
100
|
+
if (sbn < 0 || sbn > 0xffffff) {
|
|
101
|
+
throw new Error('SBN must be 0-16777215 (24 bits)')
|
|
102
|
+
}
|
|
103
|
+
if (esi < 0 || esi > 0xffffff) {
|
|
104
|
+
throw new Error('ESI must be 0-16777215 (24 bits)')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Get packed 48-bit value */
|
|
109
|
+
get packed(): bigint {
|
|
110
|
+
return fecPack(this.sbn, this.esi)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Create from packed 48-bit value */
|
|
114
|
+
static fromPacked(packed: bigint): FecPayloadId {
|
|
115
|
+
const [sbn, esi] = fecUnpack(packed)
|
|
116
|
+
return new FecPayloadId(sbn, esi)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Check if value is within valid 24-bit range */
|
|
120
|
+
static isValidSbn(sbn: number): boolean {
|
|
121
|
+
return sbn >= 0 && sbn <= 0xffffff
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Check if value is within valid 24-bit range */
|
|
125
|
+
static isValidEsi(esi: number): boolean {
|
|
126
|
+
return esi >= 0 && esi <= 0xffffff
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// =============================================================================
|
|
131
|
+
// Inline Tests (vitest)
|
|
132
|
+
// =============================================================================
|
|
133
|
+
|
|
134
|
+
if (import.meta.vitest) {
|
|
135
|
+
const { describe, test, expect } = import.meta.vitest
|
|
136
|
+
|
|
137
|
+
describe('FEC Helpers', () => {
|
|
138
|
+
test('fecPack/fecUnpack roundtrip', () => {
|
|
139
|
+
const sbn = 12345
|
|
140
|
+
const esi = 67890
|
|
141
|
+
const packed = fecPack(sbn, esi)
|
|
142
|
+
const [unpackedSbn, unpackedEsi] = fecUnpack(packed)
|
|
143
|
+
expect(unpackedSbn).toBe(sbn)
|
|
144
|
+
expect(unpackedEsi).toBe(esi)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('fecPack/fecUnpack max values', () => {
|
|
148
|
+
const sbn = FEC_PAYLOAD_ID_MAX
|
|
149
|
+
const esi = FEC_PAYLOAD_ID_MAX
|
|
150
|
+
const packed = fecPack(sbn, esi)
|
|
151
|
+
const [unpackedSbn, unpackedEsi] = fecUnpack(packed)
|
|
152
|
+
expect(unpackedSbn).toBe(sbn)
|
|
153
|
+
expect(unpackedEsi).toBe(esi)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('fecPack/fecUnpack zero', () => {
|
|
157
|
+
const packed = fecPack(0, 0)
|
|
158
|
+
expect(packed).toBe(0n)
|
|
159
|
+
const [sbn, esi] = fecUnpack(0n)
|
|
160
|
+
expect(sbn).toBe(0)
|
|
161
|
+
expect(esi).toBe(0)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('fecPack truncates overflow', () => {
|
|
165
|
+
const sbn = 0x1ffffff // 25 bits
|
|
166
|
+
const esi = 0x1ffffff // 25 bits
|
|
167
|
+
const packed = fecPack(sbn, esi)
|
|
168
|
+
const [unpackedSbn, unpackedEsi] = fecUnpack(packed)
|
|
169
|
+
expect(unpackedSbn).toBe(FEC_PAYLOAD_ID_MAX)
|
|
170
|
+
expect(unpackedEsi).toBe(FEC_PAYLOAD_ID_MAX)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('FecPayloadId', () => {
|
|
175
|
+
test('should create and roundtrip', () => {
|
|
176
|
+
const sbn = 12345
|
|
177
|
+
const esi = 67890
|
|
178
|
+
const fecId = new FecPayloadId(sbn, esi)
|
|
179
|
+
expect(fecId.sbn).toBe(sbn)
|
|
180
|
+
expect(fecId.esi).toBe(esi)
|
|
181
|
+
|
|
182
|
+
const fecId2 = FecPayloadId.fromPacked(fecId.packed)
|
|
183
|
+
expect(fecId2.sbn).toBe(sbn)
|
|
184
|
+
expect(fecId2.esi).toBe(esi)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('should handle max 24-bit values', () => {
|
|
188
|
+
const sbn = 0xffffff
|
|
189
|
+
const esi = 0xffffff
|
|
190
|
+
const fecId = new FecPayloadId(sbn, esi)
|
|
191
|
+
const fecId2 = FecPayloadId.fromPacked(fecId.packed)
|
|
192
|
+
expect(fecId2.sbn).toBe(sbn)
|
|
193
|
+
expect(fecId2.esi).toBe(esi)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('should reject invalid SBN', () => {
|
|
197
|
+
expect(() => new FecPayloadId(-1, 0)).toThrow()
|
|
198
|
+
expect(() => new FecPayloadId(0x1000000, 0)).toThrow()
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('should reject invalid ESI', () => {
|
|
202
|
+
expect(() => new FecPayloadId(0, -1)).toThrow()
|
|
203
|
+
expect(() => new FecPayloadId(0, 0x1000000)).toThrow()
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('isValidSbn/isValidEsi', () => {
|
|
207
|
+
expect(FecPayloadId.isValidSbn(0)).toBe(true)
|
|
208
|
+
expect(FecPayloadId.isValidSbn(0xffffff)).toBe(true)
|
|
209
|
+
expect(FecPayloadId.isValidSbn(-1)).toBe(false)
|
|
210
|
+
expect(FecPayloadId.isValidSbn(0x1000000)).toBe(false)
|
|
211
|
+
|
|
212
|
+
expect(FecPayloadId.isValidEsi(0)).toBe(true)
|
|
213
|
+
expect(FecPayloadId.isValidEsi(0xffffff)).toBe(true)
|
|
214
|
+
expect(FecPayloadId.isValidEsi(-1)).toBe(false)
|
|
215
|
+
expect(FecPayloadId.isValidEsi(0x1000000)).toBe(false)
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
}
|