@danything/arib 2.1.0 → 2.2.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/README.md +39 -2
- package/dist/bits.d.ts +25 -0
- package/dist/bits.js +66 -0
- package/dist/eit.d.ts +14 -2
- package/dist/eit.js +46 -31
- package/dist/mh-eit.d.ts +43 -0
- package/dist/mh-eit.js +275 -0
- package/dist/mmt-media.d.ts +168 -0
- package/dist/mmt-media.js +536 -0
- package/dist/mmt-service-filter.d.ts +44 -0
- package/dist/mmt-service-filter.js +165 -0
- package/dist/mmt-si.d.ts +183 -0
- package/dist/mmt-si.js +456 -0
- package/dist/mmtp.d.ts +84 -0
- package/dist/mmtp.js +232 -0
- package/dist/psi.d.ts +2 -0
- package/dist/psi.js +1 -1
- package/dist/synth-mmt-av.d.ts +83 -0
- package/dist/synth-mmt-av.js +336 -0
- package/dist/synth-mmt.d.ts +176 -0
- package/dist/synth-mmt.js +476 -0
- package/dist/synth.d.ts +4 -0
- package/dist/synth.js +2 -2
- package/dist/tlv-ip.d.ts +44 -0
- package/dist/tlv-ip.js +118 -0
- package/dist/tlv.d.ts +43 -0
- package/dist/tlv.js +119 -0
- package/dist/ttml.d.ts +25 -0
- package/dist/ttml.js +455 -0
- package/package.json +5 -3
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 試験用の TLV のバイト列 (TLV・IP・MMTP・MMT-SI) を組み立てる。TS の `synth.ts` と同じ役で、
|
|
3
|
+
* 読む側 (`tlv.ts`・`tlv-ip.ts`・`mmtp.ts`・`mmt-si.ts`・`mh-eit.ts`・`mmt-service-filter.ts`・`mmt-media.ts`) と対になる書く側。
|
|
4
|
+
*
|
|
5
|
+
* スクランブルは印 (ヘッダー拡張) だけを付け、中身は暗号にしない。
|
|
6
|
+
*/
|
|
7
|
+
import type { Scrambling } from "./mmtp.js";
|
|
8
|
+
/** TLV のパケット 1 つ */
|
|
9
|
+
export declare function tlvPacket(type: number, payload: Uint8Array | number[]): Uint8Array;
|
|
10
|
+
/** 宛先と送り元。実機の BS4K と同じ値を既定にする */
|
|
11
|
+
export interface SynthAddress {
|
|
12
|
+
source?: string;
|
|
13
|
+
destination?: string;
|
|
14
|
+
sourcePort?: number;
|
|
15
|
+
destinationPort?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* ヘッダー圧縮した IP の TLV パケット。`address` を渡せば全部のヘッダー (圧縮の種類 0x60)、
|
|
19
|
+
* 渡さなければヘッダー無し (0x61)
|
|
20
|
+
*/
|
|
21
|
+
export declare function compressedIp(cid: number, payload: Uint8Array, address?: SynthAddress, sequence?: number): Uint8Array;
|
|
22
|
+
/** 素の IPv6 の UDP の TLV パケット (NTP など) */
|
|
23
|
+
export declare function ipv6Udp(destination: string, destinationPort: number, payload: Uint8Array): Uint8Array;
|
|
24
|
+
/** TLV-SI の TLV パケット。セクションを並べ、残りは詰めない */
|
|
25
|
+
export declare function tlvSi(...sections: Uint8Array[]): Uint8Array;
|
|
26
|
+
export interface SynthMmtp {
|
|
27
|
+
packetId: number;
|
|
28
|
+
/** 0 は MPU、2 は信号メッセージ */
|
|
29
|
+
payloadType: number;
|
|
30
|
+
sequenceNumber?: number;
|
|
31
|
+
rap?: boolean;
|
|
32
|
+
/** 付ければスクランブルの印のヘッダー拡張を付ける (中身は暗号にしない) */
|
|
33
|
+
scrambling?: Scrambling;
|
|
34
|
+
payload: Uint8Array | number[];
|
|
35
|
+
}
|
|
36
|
+
/** MMTP のパケット 1 つ (UDP の中身) */
|
|
37
|
+
export declare function mmtpPacket(packet: SynthMmtp): Uint8Array;
|
|
38
|
+
/** MPU のパケットの中身 (ペイロードヘッダー 8 バイトと、その後ろ) */
|
|
39
|
+
export declare function mpuPayload(data: Uint8Array | number[], mpuSequenceNumber?: number): Uint8Array;
|
|
40
|
+
/** MFU のデータユニット 1 つ。同じ `sample` のものが 1 つのアクセスユニット */
|
|
41
|
+
export interface SynthDataUnit {
|
|
42
|
+
sample: number;
|
|
43
|
+
data: Uint8Array | number[];
|
|
44
|
+
}
|
|
45
|
+
/** MFU の運び方 */
|
|
46
|
+
export interface SynthMfuOptions {
|
|
47
|
+
/** 最初のパケットの packet_sequence_number */
|
|
48
|
+
sequenceNumber?: number;
|
|
49
|
+
/** データユニットを 1 つのパケットに集約する */
|
|
50
|
+
aggregate?: boolean;
|
|
51
|
+
/** データユニットを分割するときの、1 つのパケットに載せる中身の上限 (バイト)。省くと分割しない */
|
|
52
|
+
fragment?: number;
|
|
53
|
+
/** 最初のパケットに RAP_flag を立てる */
|
|
54
|
+
rap?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* MPU の中身 (MFU) を運ぶ MMTP のパケットの並び。データユニットは時刻のあるもの (映像・音声・字幕)。
|
|
58
|
+
* 分割したデータユニットは、断片ごとにヘッダーを付ける
|
|
59
|
+
*/
|
|
60
|
+
export declare function mfuPackets(packetId: number, mpuSequenceNumber: number, units: SynthDataUnit[], options?: SynthMfuOptions): Uint8Array[];
|
|
61
|
+
/** NTP の 64 ビットの時刻 `[秒, 端数 (1/2^32 秒)]` */
|
|
62
|
+
export type SynthNtp = [number, number];
|
|
63
|
+
/** MPU タイムスタンプ記述子。MPU ごとに `[mpu_sequence_number, 最初の提示時刻]` */
|
|
64
|
+
export declare function mpuTimestampDescriptor(mpus: [number, SynthNtp][]): number[];
|
|
65
|
+
export interface SynthExtendedMpu {
|
|
66
|
+
sequence: number;
|
|
67
|
+
/** mpu_decoding_time_offset */
|
|
68
|
+
decodingOffset: number;
|
|
69
|
+
/** アクセスユニットごとの dts_pts_offset (デコードの順) */
|
|
70
|
+
dtsPtsOffsets: number[];
|
|
71
|
+
/** アクセスユニットごとの pts_offset。`duration` を渡さないときに書く (種類 2) */
|
|
72
|
+
durations?: number[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* MPU 拡張タイムスタンプ記述子。`duration` を渡せば pts_offset をどれも同じ値で書き (種類 1。実機の BS4K はこれ)、
|
|
76
|
+
* 渡さなければアクセスユニットごとに書く (種類 2)
|
|
77
|
+
*/
|
|
78
|
+
export declare function mpuExtendedTimestampDescriptor(timescale: number, mpus: SynthExtendedMpu[], duration?: number): number[];
|
|
79
|
+
/**
|
|
80
|
+
* LATM の AudioMuxElement (muxConfigPresent = 1。LOAS の同期のヘッダーは付けない)。
|
|
81
|
+
* `audioSpecificConfig` を渡せば StreamMuxConfig を書き、渡さなければ「前と同じ」にする。
|
|
82
|
+
* AudioSpecificConfig は、長さがバイトの区切りに揃うもの (AAC LC の 2 バイトなど) を渡す
|
|
83
|
+
*/
|
|
84
|
+
export declare function latmElement(frame: Uint8Array | number[], audioSpecificConfig?: Uint8Array | number[]): Uint8Array;
|
|
85
|
+
/** PA メッセージ。表 (MPT・PLT) を並べる */
|
|
86
|
+
export declare function paMessage(...tables: Uint8Array[]): Uint8Array;
|
|
87
|
+
/** M2 セクションメッセージ。セクション (MH-EIT・MH-SDT・ECM など) を 1 つ運ぶ */
|
|
88
|
+
export declare function m2SectionMessage(section: Uint8Array): Uint8Array;
|
|
89
|
+
/** 信号メッセージを MMTP のパケットに載せる仕方 */
|
|
90
|
+
export interface SignalingOptions {
|
|
91
|
+
/** 最初のパケットの packet_sequence_number */
|
|
92
|
+
sequenceNumber?: number;
|
|
93
|
+
/** 1 つのパケットに長さを付けて集約する。`long` なら長さを 32 ビットで書く */
|
|
94
|
+
aggregate?: "short" | "long";
|
|
95
|
+
/** 分割するときの、1 つのパケットに載せる中身の上限 (バイト)。省くと分割しない */
|
|
96
|
+
fragment?: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 信号メッセージを運ぶ MMTP のパケットの並び。集約するときは全部を 1 つに、分割するときは 1 つのメッセージを
|
|
100
|
+
* いくつものパケットに分ける。fragment_counter は実機と同じく 0 のまま
|
|
101
|
+
*/
|
|
102
|
+
export declare function signalingPackets(packetId: number, messages: Uint8Array[], options?: SignalingOptions): Uint8Array[];
|
|
103
|
+
/** PLT。package ごとに `[package_id, MPT の packet_id]` */
|
|
104
|
+
export declare function pltTable(packages: [number, number][], version?: number): Uint8Array;
|
|
105
|
+
/** MMT の記述子。タグが 0xF000 以上なら長さは 2 バイト */
|
|
106
|
+
export declare function mmtDescriptor(tag: number, body: number[]): number[];
|
|
107
|
+
export interface SynthAsset {
|
|
108
|
+
/** `hev1`・`mp4a`・`stpp` など */
|
|
109
|
+
type: string;
|
|
110
|
+
packetId: number;
|
|
111
|
+
componentTag?: number;
|
|
112
|
+
/** ほかに載せる記述子 (`mpuTimestampDescriptor` など) */
|
|
113
|
+
descriptors?: number[];
|
|
114
|
+
}
|
|
115
|
+
export interface SynthMpt {
|
|
116
|
+
/** = service_id */
|
|
117
|
+
packageId: number;
|
|
118
|
+
version?: number;
|
|
119
|
+
/** 付ければアクセス制御記述子を載せる */
|
|
120
|
+
access?: {
|
|
121
|
+
caSystemId: number;
|
|
122
|
+
ecmPacketId: number;
|
|
123
|
+
};
|
|
124
|
+
assets: SynthAsset[];
|
|
125
|
+
}
|
|
126
|
+
/** MPT。アセットはどれも同じ IP の並びの packet_id (在りかの種類 0) に置く */
|
|
127
|
+
export declare function mptTable(mpt: SynthMpt): Uint8Array;
|
|
128
|
+
export interface SynthMhService {
|
|
129
|
+
serviceId: number;
|
|
130
|
+
serviceType?: number;
|
|
131
|
+
name: string;
|
|
132
|
+
providerName?: string;
|
|
133
|
+
}
|
|
134
|
+
/** MH-SDT。`other` なら MH-SDT[other] (0xA0) */
|
|
135
|
+
export declare function mhSdtSection(tlvStreamId: number, originalNetworkId: number, services: SynthMhService[], other?: boolean): Uint8Array;
|
|
136
|
+
/** TLV-NIT。TLV のストリームごとに `[tlv_stream_id, original_network_id, [service_id, 種類][]]` */
|
|
137
|
+
export declare function tlvNitSection(networkId: number, name: string, streams: [number, number, [number, number][]][]): Uint8Array;
|
|
138
|
+
/** MH-TOT。放送の現在時刻 */
|
|
139
|
+
export declare function mhTotSection(unixMs: number): Uint8Array;
|
|
140
|
+
export interface SynthMhEvent {
|
|
141
|
+
eventId: number;
|
|
142
|
+
/** epoch ms */
|
|
143
|
+
startAt: number;
|
|
144
|
+
/** ms */
|
|
145
|
+
duration: number;
|
|
146
|
+
name: string;
|
|
147
|
+
description?: string;
|
|
148
|
+
/**
|
|
149
|
+
* 見出しと本文の組。本文を `split` バイトごとに区切り、2 つ目からは見出しを空にして続きとして書く
|
|
150
|
+
* (UTF-8 の 1 文字の途中で区切れることもある)。記述子は組ごとに 1 つ
|
|
151
|
+
*/
|
|
152
|
+
extended?: [string, string][];
|
|
153
|
+
split?: number;
|
|
154
|
+
genres?: [number, number][];
|
|
155
|
+
/** 映像コンポーネント記述子の video_resolution (6 は 2160) */
|
|
156
|
+
resolution?: number;
|
|
157
|
+
/** コンテンツコピー制御記述子の digital_recording_control_data (3 はコピー禁止) */
|
|
158
|
+
copyControl?: number;
|
|
159
|
+
isFree?: boolean;
|
|
160
|
+
}
|
|
161
|
+
export interface SynthMhEit {
|
|
162
|
+
/** 0x8B は p/f、0x8C〜 は予定の基本、0x94〜 は詳細 */
|
|
163
|
+
tableId: number;
|
|
164
|
+
serviceId: number;
|
|
165
|
+
tlvStreamId: number;
|
|
166
|
+
originalNetworkId: number;
|
|
167
|
+
version?: number;
|
|
168
|
+
sectionNumber?: number;
|
|
169
|
+
lastSectionNumber?: number;
|
|
170
|
+
lastTableId?: number;
|
|
171
|
+
/** 4 は放送中 */
|
|
172
|
+
runningStatus?: number;
|
|
173
|
+
events: SynthMhEvent[];
|
|
174
|
+
}
|
|
175
|
+
/** MH-EIT */
|
|
176
|
+
export declare function mhEitSection(options: SynthMhEit): Uint8Array;
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 試験用の TLV のバイト列 (TLV・IP・MMTP・MMT-SI) を組み立てる。TS の `synth.ts` と同じ役で、
|
|
3
|
+
* 読む側 (`tlv.ts`・`tlv-ip.ts`・`mmtp.ts`・`mmt-si.ts`・`mh-eit.ts`・`mmt-service-filter.ts`・`mmt-media.ts`) と対になる書く側。
|
|
4
|
+
*
|
|
5
|
+
* スクランブルは印 (ヘッダー拡張) だけを付け、中身は暗号にしない。
|
|
6
|
+
*/
|
|
7
|
+
import { concatBytes } from "./bytes.js";
|
|
8
|
+
import { withCrc } from "./psi.js";
|
|
9
|
+
import { bcdDuration, mjdTime } from "./synth.js";
|
|
10
|
+
import { TLV_COMPRESSED_IP, TLV_IPV6, TLV_SIGNALING } from "./tlv.js";
|
|
11
|
+
const be = (value) => [(value >> 8) & 0xff, value & 0xff];
|
|
12
|
+
const be32 = (value) => [...be(value >>> 16), ...be(value & 0xffff)];
|
|
13
|
+
const utf8 = (text) => [...new TextEncoder().encode(text)];
|
|
14
|
+
const JPN = [0x6a, 0x70, 0x6e];
|
|
15
|
+
/** TLV のパケット 1 つ */
|
|
16
|
+
export function tlvPacket(type, payload) {
|
|
17
|
+
const body = Uint8Array.from(payload);
|
|
18
|
+
return Uint8Array.from([0x7f, type, ...be(body.length), ...body]);
|
|
19
|
+
}
|
|
20
|
+
/** IPv6 のアドレスを 16 バイトに (`ff3e::a000:1000` のような縮めた形も読む) */
|
|
21
|
+
function ipv6Bytes(text) {
|
|
22
|
+
const [head = "", tail] = text.split("::");
|
|
23
|
+
const groups = (part) => part === "" ? [] : part.split(":").map((g) => parseInt(g, 16));
|
|
24
|
+
const front = groups(head);
|
|
25
|
+
const back = tail === undefined ? [] : groups(tail);
|
|
26
|
+
const zeros = new Array(8 - front.length - back.length).fill(0);
|
|
27
|
+
return [...front, ...zeros, ...back].flatMap(be);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* ヘッダー圧縮した IP の TLV パケット。`address` を渡せば全部のヘッダー (圧縮の種類 0x60)、
|
|
31
|
+
* 渡さなければヘッダー無し (0x61)
|
|
32
|
+
*/
|
|
33
|
+
export function compressedIp(cid, payload, address, sequence = 0) {
|
|
34
|
+
const head = [(cid >> 4) & 0xff, ((cid & 0x0f) << 4) | (sequence & 0x0f)];
|
|
35
|
+
if (address === undefined)
|
|
36
|
+
return tlvPacket(TLV_COMPRESSED_IP, [...head, 0x61, ...payload]);
|
|
37
|
+
const ip = [
|
|
38
|
+
0x60,
|
|
39
|
+
0x00,
|
|
40
|
+
0x00,
|
|
41
|
+
0x00, // version・traffic class・flow label
|
|
42
|
+
17, // next header (UDP)
|
|
43
|
+
0x40, // hop limit
|
|
44
|
+
...ipv6Bytes(address.source ?? "2401:dbc0:1003::b111"),
|
|
45
|
+
...ipv6Bytes(address.destination ?? "ff3e::a000:1000"),
|
|
46
|
+
...be(address.sourcePort ?? 50000),
|
|
47
|
+
...be(address.destinationPort ?? 51216),
|
|
48
|
+
];
|
|
49
|
+
return tlvPacket(TLV_COMPRESSED_IP, [...head, 0x60, ...ip, ...payload]);
|
|
50
|
+
}
|
|
51
|
+
/** 素の IPv6 の UDP の TLV パケット (NTP など) */
|
|
52
|
+
export function ipv6Udp(destination, destinationPort, payload) {
|
|
53
|
+
const udp = [
|
|
54
|
+
...be(destinationPort),
|
|
55
|
+
...be(destinationPort),
|
|
56
|
+
...be(8 + payload.length),
|
|
57
|
+
0x00,
|
|
58
|
+
0x00,
|
|
59
|
+
...payload,
|
|
60
|
+
];
|
|
61
|
+
return tlvPacket(TLV_IPV6, [
|
|
62
|
+
0x60,
|
|
63
|
+
0x00,
|
|
64
|
+
0x00,
|
|
65
|
+
0x00,
|
|
66
|
+
...be(udp.length),
|
|
67
|
+
17,
|
|
68
|
+
0x01,
|
|
69
|
+
...ipv6Bytes("fe80::1"),
|
|
70
|
+
...ipv6Bytes(destination),
|
|
71
|
+
...udp,
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
/** TLV-SI の TLV パケット。セクションを並べ、残りは詰めない */
|
|
75
|
+
export function tlvSi(...sections) {
|
|
76
|
+
return tlvPacket(TLV_SIGNALING, concatBytes(sections));
|
|
77
|
+
}
|
|
78
|
+
const SCRAMBLING_BITS = {
|
|
79
|
+
none: 0,
|
|
80
|
+
even: 2,
|
|
81
|
+
odd: 3,
|
|
82
|
+
};
|
|
83
|
+
/** MMTP のパケット 1 つ (UDP の中身) */
|
|
84
|
+
export function mmtpPacket(packet) {
|
|
85
|
+
const extension = packet.scrambling === undefined
|
|
86
|
+
? []
|
|
87
|
+
: [
|
|
88
|
+
// 種類がいくつも入る拡張 (0x0000) の中に、スクランブルの印 (種類 0x0001・長さ 1。上位 1 ビットは「これで終わり」)
|
|
89
|
+
...be(0x0000),
|
|
90
|
+
...be(5),
|
|
91
|
+
...be(0x8001),
|
|
92
|
+
...be(1),
|
|
93
|
+
(SCRAMBLING_BITS[packet.scrambling] << 6) | 0x3f,
|
|
94
|
+
];
|
|
95
|
+
return Uint8Array.from([
|
|
96
|
+
// version 0、packet_counter_flag 0、FEC 0、extension_flag、RAP_flag
|
|
97
|
+
(extension.length > 0 ? 0x02 : 0) | (packet.rap === true ? 0x01 : 0),
|
|
98
|
+
packet.payloadType & 0x3f,
|
|
99
|
+
...be(packet.packetId),
|
|
100
|
+
...be32(0), // delivery_timestamp
|
|
101
|
+
...be32(packet.sequenceNumber ?? 0),
|
|
102
|
+
...extension,
|
|
103
|
+
...packet.payload,
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
/** MPU のパケットの中身 (ペイロードヘッダー 8 バイトと、その後ろ) */
|
|
107
|
+
export function mpuPayload(data, mpuSequenceNumber = 0) {
|
|
108
|
+
const body = [
|
|
109
|
+
0x28, // fragment_type 2 (MFU)、timed、分割なし、集約なし
|
|
110
|
+
0x00,
|
|
111
|
+
...be32(mpuSequenceNumber),
|
|
112
|
+
...data,
|
|
113
|
+
];
|
|
114
|
+
return Uint8Array.from([...be(body.length), ...body]);
|
|
115
|
+
}
|
|
116
|
+
/** 時刻のあるデータユニットのヘッダー (14 バイト。offset はサンプルの中の位置) */
|
|
117
|
+
function timedHeader(sample, offset) {
|
|
118
|
+
return [...be32(0), ...be32(sample), ...be32(offset), 0x00, 0x00];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* MPU の中身 (MFU) を運ぶ MMTP のパケットの並び。データユニットは時刻のあるもの (映像・音声・字幕)。
|
|
122
|
+
* 分割したデータユニットは、断片ごとにヘッダーを付ける
|
|
123
|
+
*/
|
|
124
|
+
export function mfuPackets(packetId, mpuSequenceNumber, units, options = {}) {
|
|
125
|
+
const start = options.sequenceNumber ?? 0;
|
|
126
|
+
let sequenceNumber = start;
|
|
127
|
+
const packet = (indicator, aggregated, body) => {
|
|
128
|
+
const payload = [
|
|
129
|
+
// fragment_type 2 (MFU)、timed、分割の印、集約の印
|
|
130
|
+
0x28 | (indicator << 1) | (aggregated ? 0x01 : 0),
|
|
131
|
+
0x00,
|
|
132
|
+
...be32(mpuSequenceNumber),
|
|
133
|
+
...body,
|
|
134
|
+
];
|
|
135
|
+
return mmtpPacket({
|
|
136
|
+
packetId,
|
|
137
|
+
payloadType: 0,
|
|
138
|
+
rap: sequenceNumber === start && options.rap === true,
|
|
139
|
+
sequenceNumber: sequenceNumber++,
|
|
140
|
+
payload: [...be(payload.length), ...payload],
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
if (options.aggregate === true) {
|
|
144
|
+
const body = units.flatMap((unit) => {
|
|
145
|
+
const du = [...timedHeader(unit.sample, 0), ...unit.data];
|
|
146
|
+
return [...be(du.length), ...du];
|
|
147
|
+
});
|
|
148
|
+
return [packet(0, true, body)];
|
|
149
|
+
}
|
|
150
|
+
const out = [];
|
|
151
|
+
for (const unit of units) {
|
|
152
|
+
const data = [...unit.data];
|
|
153
|
+
const size = options.fragment ?? data.length;
|
|
154
|
+
if (data.length <= size) {
|
|
155
|
+
out.push(packet(0, false, [...timedHeader(unit.sample, 0), ...data]));
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
for (let at = 0; at < data.length; at += size) {
|
|
159
|
+
const indicator = at === 0 ? 1 : at + size >= data.length ? 3 : 2;
|
|
160
|
+
out.push(packet(indicator, false, [
|
|
161
|
+
...timedHeader(unit.sample, at),
|
|
162
|
+
...data.slice(at, at + size),
|
|
163
|
+
]));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
/** MPU タイムスタンプ記述子。MPU ごとに `[mpu_sequence_number, 最初の提示時刻]` */
|
|
169
|
+
export function mpuTimestampDescriptor(mpus) {
|
|
170
|
+
return mmtDescriptor(0x0001, mpus.flatMap(([sequence, [seconds, fraction]]) => [
|
|
171
|
+
...be32(sequence),
|
|
172
|
+
...be32(seconds),
|
|
173
|
+
...be32(fraction),
|
|
174
|
+
]));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* MPU 拡張タイムスタンプ記述子。`duration` を渡せば pts_offset をどれも同じ値で書き (種類 1。実機の BS4K はこれ)、
|
|
178
|
+
* 渡さなければアクセスユニットごとに書く (種類 2)
|
|
179
|
+
*/
|
|
180
|
+
export function mpuExtendedTimestampDescriptor(timescale, mpus, duration) {
|
|
181
|
+
const type = duration === undefined ? 2 : 1;
|
|
182
|
+
return mmtDescriptor(0x8026, [
|
|
183
|
+
0xf8 | (type << 1) | 0x01,
|
|
184
|
+
...be32(timescale),
|
|
185
|
+
...(duration === undefined ? [] : be(duration)),
|
|
186
|
+
...mpus.flatMap((mpu) => [
|
|
187
|
+
...be32(mpu.sequence),
|
|
188
|
+
0x3f,
|
|
189
|
+
...be(mpu.decodingOffset),
|
|
190
|
+
mpu.dtsPtsOffsets.length,
|
|
191
|
+
...mpu.dtsPtsOffsets.flatMap((offset, i) => [
|
|
192
|
+
...be(offset),
|
|
193
|
+
...(type === 2 ? be(mpu.durations?.[i] ?? 0) : []),
|
|
194
|
+
]),
|
|
195
|
+
]),
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
/** ビットを詰めていく (LATM を書くため) */
|
|
199
|
+
class BitWriter {
|
|
200
|
+
bytes = [];
|
|
201
|
+
at = 0;
|
|
202
|
+
put(value, count) {
|
|
203
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
204
|
+
if (this.at % 8 === 0)
|
|
205
|
+
this.bytes.push(0);
|
|
206
|
+
if (Math.floor(value / 2 ** i) % 2)
|
|
207
|
+
this.bytes[this.bytes.length - 1] |= 0x80 >> (this.at % 8);
|
|
208
|
+
this.at++;
|
|
209
|
+
}
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
get done() {
|
|
213
|
+
return Uint8Array.from(this.bytes);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* LATM の AudioMuxElement (muxConfigPresent = 1。LOAS の同期のヘッダーは付けない)。
|
|
218
|
+
* `audioSpecificConfig` を渡せば StreamMuxConfig を書き、渡さなければ「前と同じ」にする。
|
|
219
|
+
* AudioSpecificConfig は、長さがバイトの区切りに揃うもの (AAC LC の 2 バイトなど) を渡す
|
|
220
|
+
*/
|
|
221
|
+
export function latmElement(frame, audioSpecificConfig) {
|
|
222
|
+
const bits = new BitWriter();
|
|
223
|
+
bits.put(audioSpecificConfig === undefined ? 1 : 0, 1);
|
|
224
|
+
if (audioSpecificConfig !== undefined) {
|
|
225
|
+
// audioMuxVersion 0、allStreamsSameTimeFraming 1、サブフレーム・番組・層は 1 つずつ
|
|
226
|
+
bits.put(0, 1).put(1, 1).put(0, 6).put(0, 4).put(0, 3);
|
|
227
|
+
for (const byte of audioSpecificConfig)
|
|
228
|
+
bits.put(byte, 8);
|
|
229
|
+
// frameLengthType 0、latmBufferFullness 0xFF、otherData なし、CRC なし
|
|
230
|
+
bits.put(0, 3).put(0xff, 8).put(0, 1).put(0, 1);
|
|
231
|
+
}
|
|
232
|
+
let left = frame.length;
|
|
233
|
+
for (; left >= 255; left -= 255)
|
|
234
|
+
bits.put(255, 8);
|
|
235
|
+
bits.put(left, 8);
|
|
236
|
+
for (const byte of frame)
|
|
237
|
+
bits.put(byte, 8);
|
|
238
|
+
return bits.done;
|
|
239
|
+
}
|
|
240
|
+
/** PA メッセージ。表 (MPT・PLT) を並べる */
|
|
241
|
+
export function paMessage(...tables) {
|
|
242
|
+
const body = [
|
|
243
|
+
tables.length,
|
|
244
|
+
...tables.flatMap((table) => [table[0], table[1], table[2], table[3]]),
|
|
245
|
+
...tables.flatMap((table) => [...table]),
|
|
246
|
+
];
|
|
247
|
+
return Uint8Array.from([...be(0x0000), 0x00, ...be32(body.length), ...body]);
|
|
248
|
+
}
|
|
249
|
+
/** M2 セクションメッセージ。セクション (MH-EIT・MH-SDT・ECM など) を 1 つ運ぶ */
|
|
250
|
+
export function m2SectionMessage(section) {
|
|
251
|
+
return Uint8Array.from([
|
|
252
|
+
...be(0x8000),
|
|
253
|
+
0x00,
|
|
254
|
+
...be(section.length),
|
|
255
|
+
...section,
|
|
256
|
+
]);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 信号メッセージを運ぶ MMTP のパケットの並び。集約するときは全部を 1 つに、分割するときは 1 つのメッセージを
|
|
260
|
+
* いくつものパケットに分ける。fragment_counter は実機と同じく 0 のまま
|
|
261
|
+
*/
|
|
262
|
+
export function signalingPackets(packetId, messages, options = {}) {
|
|
263
|
+
let sequenceNumber = options.sequenceNumber ?? 0;
|
|
264
|
+
const packet = (indicator, flags, body) => mmtpPacket({
|
|
265
|
+
packetId,
|
|
266
|
+
payloadType: 2,
|
|
267
|
+
sequenceNumber: sequenceNumber++,
|
|
268
|
+
payload: [(indicator << 6) | flags, 0x00, ...body],
|
|
269
|
+
});
|
|
270
|
+
if (options.aggregate !== undefined) {
|
|
271
|
+
const long = options.aggregate === "long";
|
|
272
|
+
const body = messages.flatMap((message) => [
|
|
273
|
+
...(long ? be32(message.length) : be(message.length)),
|
|
274
|
+
...message,
|
|
275
|
+
]);
|
|
276
|
+
return [packet(0, (long ? 0x02 : 0) | 0x01, body)];
|
|
277
|
+
}
|
|
278
|
+
const out = [];
|
|
279
|
+
for (const message of messages) {
|
|
280
|
+
const size = options.fragment ?? message.length;
|
|
281
|
+
if (message.length <= size) {
|
|
282
|
+
out.push(packet(0, 0, [...message]));
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
for (let at = 0; at < message.length; at += size) {
|
|
286
|
+
const last = at + size >= message.length;
|
|
287
|
+
const indicator = at === 0 ? 1 : last ? 3 : 2;
|
|
288
|
+
out.push(packet(indicator, 0, [...message.subarray(at, at + size)]));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
/** PLT。package ごとに `[package_id, MPT の packet_id]` */
|
|
294
|
+
export function pltTable(packages, version = 0) {
|
|
295
|
+
const body = [
|
|
296
|
+
packages.length,
|
|
297
|
+
...packages.flatMap(([packageId, packetId]) => [
|
|
298
|
+
2,
|
|
299
|
+
...be(packageId),
|
|
300
|
+
0x00,
|
|
301
|
+
...be(packetId),
|
|
302
|
+
]),
|
|
303
|
+
0x00, // IP_delivery の数
|
|
304
|
+
];
|
|
305
|
+
return Uint8Array.from([0x80, version, ...be(body.length), ...body]);
|
|
306
|
+
}
|
|
307
|
+
/** MMT の記述子。タグが 0xF000 以上なら長さは 2 バイト */
|
|
308
|
+
export function mmtDescriptor(tag, body) {
|
|
309
|
+
return tag >= 0xf000
|
|
310
|
+
? [...be(tag), ...be(body.length), ...body]
|
|
311
|
+
: [...be(tag), body.length, ...body];
|
|
312
|
+
}
|
|
313
|
+
/** MPT。アセットはどれも同じ IP の並びの packet_id (在りかの種類 0) に置く */
|
|
314
|
+
export function mptTable(mpt) {
|
|
315
|
+
const descriptors = mpt.access === undefined
|
|
316
|
+
? []
|
|
317
|
+
: mmtDescriptor(0x8004, [
|
|
318
|
+
...be(mpt.access.caSystemId),
|
|
319
|
+
0x00,
|
|
320
|
+
...be(mpt.access.ecmPacketId),
|
|
321
|
+
]);
|
|
322
|
+
const assets = mpt.assets.flatMap((asset) => {
|
|
323
|
+
const assetDescriptors = [
|
|
324
|
+
...(asset.componentTag === undefined
|
|
325
|
+
? []
|
|
326
|
+
: mmtDescriptor(0x8011, be(asset.componentTag))),
|
|
327
|
+
...(asset.descriptors ?? []),
|
|
328
|
+
];
|
|
329
|
+
return [
|
|
330
|
+
0x00, // identifier_type
|
|
331
|
+
...be32(0), // asset_id_scheme
|
|
332
|
+
2,
|
|
333
|
+
...be(asset.packetId), // asset_id
|
|
334
|
+
...[...asset.type].map((c) => c.charCodeAt(0)),
|
|
335
|
+
0xfe, // asset_clock_relation_flag 0
|
|
336
|
+
1, // location_count
|
|
337
|
+
0x00,
|
|
338
|
+
...be(asset.packetId),
|
|
339
|
+
...be(assetDescriptors.length),
|
|
340
|
+
...assetDescriptors,
|
|
341
|
+
];
|
|
342
|
+
});
|
|
343
|
+
const body = [
|
|
344
|
+
0xfc, // MPT_mode 0
|
|
345
|
+
2,
|
|
346
|
+
...be(mpt.packageId),
|
|
347
|
+
...be(descriptors.length),
|
|
348
|
+
...descriptors,
|
|
349
|
+
mpt.assets.length,
|
|
350
|
+
...assets,
|
|
351
|
+
];
|
|
352
|
+
return Uint8Array.from([0x20, mpt.version ?? 0, ...be(body.length), ...body]);
|
|
353
|
+
}
|
|
354
|
+
/** MMT の表のセクションの頭 (table_id・長さ・拡張・バージョン・セクション番号)。長さは `withCrc` が埋める */
|
|
355
|
+
function head(tableId, extension, version = 0) {
|
|
356
|
+
return [tableId, 0x00, 0x00, ...be(extension), 0xc1 | (version << 1), 0, 0];
|
|
357
|
+
}
|
|
358
|
+
/** MH-SDT。`other` なら MH-SDT[other] (0xA0) */
|
|
359
|
+
export function mhSdtSection(tlvStreamId, originalNetworkId, services, other = false) {
|
|
360
|
+
const body = [
|
|
361
|
+
...head(other ? 0xa0 : 0x9f, tlvStreamId),
|
|
362
|
+
...be(originalNetworkId),
|
|
363
|
+
0xff,
|
|
364
|
+
];
|
|
365
|
+
for (const service of services) {
|
|
366
|
+
const provider = utf8(service.providerName ?? "");
|
|
367
|
+
const name = utf8(service.name);
|
|
368
|
+
const descriptor = mmtDescriptor(0x8019, [
|
|
369
|
+
service.serviceType ?? 0x01,
|
|
370
|
+
provider.length,
|
|
371
|
+
...provider,
|
|
372
|
+
name.length,
|
|
373
|
+
...name,
|
|
374
|
+
]);
|
|
375
|
+
body.push(...be(service.serviceId), 0xe3, // EIT の予定と p/f がある
|
|
376
|
+
0x80 | ((descriptor.length >> 8) & 0x0f), // running_status 4、free_CA_mode 0
|
|
377
|
+
descriptor.length & 0xff, ...descriptor);
|
|
378
|
+
}
|
|
379
|
+
return withCrc(body);
|
|
380
|
+
}
|
|
381
|
+
/** TLV-NIT。TLV のストリームごとに `[tlv_stream_id, original_network_id, [service_id, 種類][]]` */
|
|
382
|
+
export function tlvNitSection(networkId, name, streams) {
|
|
383
|
+
const nameBytes = utf8(name);
|
|
384
|
+
const network = [0x40, nameBytes.length, ...nameBytes];
|
|
385
|
+
const loop = streams.flatMap(([tlvStreamId, onid, services]) => {
|
|
386
|
+
const list = services.flatMap(([serviceId, type]) => [
|
|
387
|
+
...be(serviceId),
|
|
388
|
+
type,
|
|
389
|
+
]);
|
|
390
|
+
const descriptors = [0x41, list.length, ...list];
|
|
391
|
+
return [
|
|
392
|
+
...be(tlvStreamId),
|
|
393
|
+
...be(onid),
|
|
394
|
+
...be(0xf000 | descriptors.length),
|
|
395
|
+
...descriptors,
|
|
396
|
+
];
|
|
397
|
+
});
|
|
398
|
+
return withCrc([
|
|
399
|
+
...head(0x40, networkId),
|
|
400
|
+
...be(0xf000 | network.length),
|
|
401
|
+
...network,
|
|
402
|
+
...be(0xf000 | loop.length),
|
|
403
|
+
...loop,
|
|
404
|
+
]);
|
|
405
|
+
}
|
|
406
|
+
/** MH-TOT。放送の現在時刻 */
|
|
407
|
+
export function mhTotSection(unixMs) {
|
|
408
|
+
return withCrc([0xa1, 0x00, 0x00, ...mjdTime(unixMs), 0xf0, 0x00]);
|
|
409
|
+
}
|
|
410
|
+
function eventDescriptors(event) {
|
|
411
|
+
const name = utf8(event.name);
|
|
412
|
+
const text = utf8(event.description ?? "");
|
|
413
|
+
const out = mmtDescriptor(0xf001, [
|
|
414
|
+
...JPN,
|
|
415
|
+
name.length,
|
|
416
|
+
...name,
|
|
417
|
+
...be(text.length),
|
|
418
|
+
...text,
|
|
419
|
+
]);
|
|
420
|
+
for (const [heading, body] of event.extended ?? []) {
|
|
421
|
+
const bytes = utf8(body);
|
|
422
|
+
const size = event.split ?? bytes.length;
|
|
423
|
+
const items = [];
|
|
424
|
+
for (let at = 0; at < bytes.length; at += size) {
|
|
425
|
+
const title = at === 0 ? utf8(heading) : [];
|
|
426
|
+
const part = bytes.slice(at, at + size);
|
|
427
|
+
items.push(title.length, ...title, ...be(part.length), ...part);
|
|
428
|
+
}
|
|
429
|
+
out.push(...mmtDescriptor(0xf002, [
|
|
430
|
+
0x00,
|
|
431
|
+
...JPN,
|
|
432
|
+
...be(items.length),
|
|
433
|
+
...items,
|
|
434
|
+
0x00,
|
|
435
|
+
0x00,
|
|
436
|
+
]));
|
|
437
|
+
}
|
|
438
|
+
if (event.genres !== undefined)
|
|
439
|
+
out.push(...mmtDescriptor(0x8012, event.genres.flatMap(([lv1, lv2]) => [(lv1 << 4) | lv2, 0xff])));
|
|
440
|
+
if (event.resolution !== undefined)
|
|
441
|
+
// 16:9、プログレッシブ、59.94 コマ、component_tag 0、言語
|
|
442
|
+
out.push(...mmtDescriptor(0x8010, [
|
|
443
|
+
(event.resolution << 4) | 0x03,
|
|
444
|
+
0xe8,
|
|
445
|
+
0x00,
|
|
446
|
+
0x00,
|
|
447
|
+
0x5f,
|
|
448
|
+
...JPN,
|
|
449
|
+
]));
|
|
450
|
+
if (event.copyControl !== undefined)
|
|
451
|
+
out.push(...mmtDescriptor(0x8038, [(event.copyControl << 6) | 0x0f, 0xff]));
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
454
|
+
/** MH-EIT */
|
|
455
|
+
export function mhEitSection(options) {
|
|
456
|
+
const body = [
|
|
457
|
+
options.tableId,
|
|
458
|
+
0x00,
|
|
459
|
+
0x00,
|
|
460
|
+
...be(options.serviceId),
|
|
461
|
+
0xc1 | ((options.version ?? 0) << 1),
|
|
462
|
+
options.sectionNumber ?? 0,
|
|
463
|
+
options.lastSectionNumber ?? 0,
|
|
464
|
+
...be(options.tlvStreamId),
|
|
465
|
+
...be(options.originalNetworkId),
|
|
466
|
+
options.lastSectionNumber ?? 0,
|
|
467
|
+
options.lastTableId ?? options.tableId,
|
|
468
|
+
];
|
|
469
|
+
for (const event of options.events) {
|
|
470
|
+
const descriptors = eventDescriptors(event);
|
|
471
|
+
body.push(...be(event.eventId), ...mjdTime(event.startAt), ...bcdDuration(event.duration), ((options.runningStatus ?? 0) << 5) |
|
|
472
|
+
(event.isFree === false ? 0x10 : 0) |
|
|
473
|
+
((descriptors.length >> 8) & 0x0f), descriptors.length & 0xff, ...descriptors);
|
|
474
|
+
}
|
|
475
|
+
return withCrc(body);
|
|
476
|
+
}
|
package/dist/synth.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export declare function sdtSection(transportStreamId: number, originalNetworkId:
|
|
|
27
27
|
options?: SectionPart & {
|
|
28
28
|
other?: boolean;
|
|
29
29
|
}): Uint8Array;
|
|
30
|
+
/** epoch ms → MJD + BCD の 5 バイト。放送の時刻は日本時間で書く */
|
|
31
|
+
export declare function mjdTime(at: number): number[];
|
|
32
|
+
/** ms → BCD 3 バイトの尺 */
|
|
33
|
+
export declare function bcdDuration(ms: number): number[];
|
|
30
34
|
export interface SynthEvent {
|
|
31
35
|
eventId: number;
|
|
32
36
|
/** epoch ms */
|
package/dist/synth.js
CHANGED
|
@@ -120,7 +120,7 @@ options = {}) {
|
|
|
120
120
|
/** 2 桁の 10 進を BCD の 1 バイトに */
|
|
121
121
|
const bcd = (value) => ((Math.floor(value / 10) << 4) | (value % 10)) & 0xff;
|
|
122
122
|
/** epoch ms → MJD + BCD の 5 バイト。放送の時刻は日本時間で書く */
|
|
123
|
-
function mjdTime(at) {
|
|
123
|
+
export function mjdTime(at) {
|
|
124
124
|
const jst = Math.floor(at / 1000) + 9 * 3600;
|
|
125
125
|
const mjd = Math.floor(jst / 86400) + 40587;
|
|
126
126
|
const rest = ((jst % 86400) + 86400) % 86400;
|
|
@@ -132,7 +132,7 @@ function mjdTime(at) {
|
|
|
132
132
|
];
|
|
133
133
|
}
|
|
134
134
|
/** ms → BCD 3 バイトの尺 */
|
|
135
|
-
function bcdDuration(ms) {
|
|
135
|
+
export function bcdDuration(ms) {
|
|
136
136
|
const total = Math.floor(ms / 1000);
|
|
137
137
|
return [
|
|
138
138
|
bcd(Math.floor(total / 3600)),
|