@danything/arib 2.2.0 → 2.3.1

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 CHANGED
@@ -9,7 +9,8 @@
9
9
  - ARIB STD-B24 の 8 単位符号 (JIS X 0213 の 1 面・2 面まで) と ARIB 外字を文字列にする (`aribtext`、`aribtext-gaiji`)
10
10
  - 字幕は STD-B24 の字幕文を、字の置き場所まで決めた JSON にする (`b24caption`、形式は `caption-text`)
11
11
  - データ放送は DSM-CC のカルーセルからモジュールとファイルを組み立てる (`dsmcc`、`multipart`、`bml`、形式は `bml-protocol`)
12
- - Hybridcast の AIT を読む (`ait`)。1 つの局だけを抜き出す (`service-filter`)
12
+ - Hybridcast の AIT を読む (`ait`)
13
+ - 1 つの局だけを TS のまま抜き出す (`service-filter`)
13
14
  - 4K の放送 (MMT/TLV。ARIB STD-B32・B60) の土台として、TLV パケットの切り出し (`tlv`)、IP とヘッダー圧縮した IP から UDP の中身を取り出す処理 (`tlv-ip`)、MMTP のヘッダーとスクランブルの印、信号メッセージの分割と集約 (`mmtp`)
14
15
  - 4K の放送の表は、TLV-NIT・PLT・MPT・MH-SDT・MH-TOT (`mmt-si`)。局の一覧と、局ごとの CA_system_id (MPT のアクセス制御記述子) をそろえて読む `TlvServiceReader` もここ (TS の `psi` の `ServiceReader` に当たる)。番組表の MH-EIT は TS と同じ形式の番組にする (`mh-eit`)。1 つの局だけを TLV のまま抜き出す (`mmt-service-filter`)
15
16
  - 4K の放送の映像 (HEVC)・音声 (AAC)・字幕 (TTML) のアクセスユニットを、MPT の記述子から付けた PTS・DTS (90 kHz) と一緒に取り出す (`mmt-media`)。音声は LATM を外した AAC のフレームと AudioSpecificConfig、映像は NAL ユニットの並び。字幕の TTML は、TS の字幕と同じ字幕の文字の配置にする (`ttml`)。スクランブルの復号は無い
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * **字幕の外字 (DRCS) の画像を、送る前になめらかにする** ([b24caption.ts](b24caption.ts) が JSON に入れるところで使う)。
3
3
  *
4
- * 放送は置き換え表で字にできない外字を、16〜36 画素の白黒か 4 階調の画像で送ってくる。受け側はそれを字の枠いっぱい
4
+ * 放送は置き換え表で字にできない外字を、16〜72 画素の白黒か 4 階調・16 階調の画像で送ってくる。受け側はそれを字の枠いっぱい
5
5
  * (1920x1080 の全画面で 72 画素ほど) に引き伸ばすので、そのまま渡すと 1 点が 4x4 の階段になり、
6
6
  * なめらかに引き伸ばしても階段がぼやけて残る (スピーカー 🔊 の外字など)。
7
7
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * **字幕の外字 (DRCS) の画像を、送る前になめらかにする** ([b24caption.ts](b24caption.ts) が JSON に入れるところで使う)。
3
3
  *
4
- * 放送は置き換え表で字にできない外字を、16〜36 画素の白黒か 4 階調の画像で送ってくる。受け側はそれを字の枠いっぱい
4
+ * 放送は置き換え表で字にできない外字を、16〜72 画素の白黒か 4 階調・16 階調の画像で送ってくる。受け側はそれを字の枠いっぱい
5
5
  * (1920x1080 の全画面で 72 画素ほど) に引き伸ばすので、そのまま渡すと 1 点が 4x4 の階段になり、
6
6
  * なめらかに引き伸ばしても階段がぼやけて残る (スピーカー 🔊 の外字など)。
7
7
  *
@@ -20,8 +20,9 @@
20
20
  *
21
21
  * スクランブルされたパケットは数えて捨てる (`scrambled`)。復号はエージェントの仕事
22
22
  */
23
- /** 放送の時刻の物差し (TS の PTS と同じ) */
24
- export declare const CLOCK = 90000;
23
+ import { CLOCK } from "./pes.js";
24
+ /** 放送の時刻の物差し (TS の PTS と同じ。`pes.ts` の `CLOCK`) */
25
+ export { CLOCK };
25
26
  /** NTP の 64 ビットの時刻 (上位 32 ビットが秒、下位 32 ビットが端数) を 90 kHz に */
26
27
  export declare function ntpToClock(data: Uint8Array, at: number): number;
27
28
  /** MPU タイムスタンプ記述子を読む。`[mpu_sequence_number, 最初の提示時刻 (90 kHz)]` の並び */
@@ -41,7 +42,7 @@ export interface MpuExtendedTimestamp {
41
42
  * MPU 拡張タイムスタンプ記述子を読む。次のものは DTS を進められないので、空を返す (その MPU は時刻が分からないものとして捨てる)
42
43
  *
43
44
  * - timescale を持たないもの (MPU のメタデータの mdhd を見る、の意味)。放送ではメタデータを送らない
44
- * - pts_offset を持たないもの (pts_offset_type 0)。コマの長さが分からない
45
+ * - pts_offset を持たないもの (pts_offset_type 0) と、予約の種類 3。コマの長さが分からない
45
46
  */
46
47
  export declare function parseMpuExtendedTimestamps(body: Uint8Array): MpuExtendedTimestamp[];
47
48
  /** アクセスユニットの時刻 (90 kHz) */
@@ -143,6 +144,8 @@ export declare class MmtMediaReader {
143
144
  private tracks;
144
145
  /** 選んだアセットの並び (変わったかを見る) */
145
146
  private selection;
147
+ /** 選んだアセットの MMTP のパケット (MPU) の数。スクランブルされていたもの (`scrambled`) も数える */
148
+ media: number;
146
149
  /** スクランブルされていて読めなかった MMTP のパケットの数 */
147
150
  scrambled: number;
148
151
  /** 時刻が分からずに捨てたアクセスユニットの数 */
@@ -165,4 +168,3 @@ export declare class MmtMediaReader {
165
168
  /** 組み立て中のアクセスユニットを閉じて返す */
166
169
  private finish;
167
170
  }
168
- export {};
package/dist/mmt-media.js CHANGED
@@ -24,11 +24,11 @@ import { BitReader } from "./bits.js";
24
24
  import { concatBytes, u16, u32 } from "./bytes.js";
25
25
  import { mmtDescriptors, parseMpt, TABLE_MPT } from "./mmt-si.js";
26
26
  import { MESSAGE_PA, messageTables, parseMessage, parseMmtp, parseMpu, PAYLOAD_MPU, PAYLOAD_SIGNALING, SignalingAssembler, } from "./mmtp.js";
27
- import { ADTS_RATES } from "./pes.js";
27
+ import { ADTS_RATES, CLOCK } from "./pes.js";
28
28
  import { TlvIpReader } from "./tlv-ip.js";
29
29
  import { TLV_COMPRESSED_IP, TlvStream } from "./tlv.js";
30
- /** 放送の時刻の物差し (TS の PTS と同じ) */
31
- export const CLOCK = 90_000;
30
+ /** 放送の時刻の物差し (TS の PTS と同じ。`pes.ts` の `CLOCK`) */
31
+ export { CLOCK };
32
32
  const DESC_MPU_TIMESTAMP = 0x0001;
33
33
  const DESC_MPU_EXTENDED_TIMESTAMP = 0x8026;
34
34
  /** MPU のペイロードヘッダーの fragment_type。2 は MFU (中身)。0・1 はメタデータで、放送では使わない */
@@ -59,13 +59,13 @@ export function parseMpuTimestamps(body) {
59
59
  * MPU 拡張タイムスタンプ記述子を読む。次のものは DTS を進められないので、空を返す (その MPU は時刻が分からないものとして捨てる)
60
60
  *
61
61
  * - timescale を持たないもの (MPU のメタデータの mdhd を見る、の意味)。放送ではメタデータを送らない
62
- * - pts_offset を持たないもの (pts_offset_type 0)。コマの長さが分からない
62
+ * - pts_offset を持たないもの (pts_offset_type 0) と、予約の種類 3。コマの長さが分からない
63
63
  */
64
64
  export function parseMpuExtendedTimestamps(body) {
65
65
  if (body.length < 5 || (body[0] & 0x01) === 0)
66
66
  return [];
67
67
  const offsetType = (body[0] >> 1) & 0x03;
68
- if (offsetType === 0)
68
+ if (offsetType !== 1 && offsetType !== 2)
69
69
  return [];
70
70
  const timescale = u32(body, 1);
71
71
  let at = 5;
@@ -312,6 +312,8 @@ export class MmtMediaReader {
312
312
  tracks = new Map();
313
313
  /** 選んだアセットの並び (変わったかを見る) */
314
314
  selection = "";
315
+ /** 選んだアセットの MMTP のパケット (MPU) の数。スクランブルされていたもの (`scrambled`) も数える */
316
+ media = 0;
315
317
  /** スクランブルされていて読めなかった MMTP のパケットの数 */
316
318
  scrambled = 0;
317
319
  /** 時刻が分からずに捨てたアクセスユニットの数 */
@@ -355,6 +357,7 @@ export class MmtMediaReader {
355
357
  const track = this.tracks.get(`${datagram.flow}/${mmtp.packetId}`);
356
358
  if (track === undefined)
357
359
  continue;
360
+ this.media++;
358
361
  // パケットが欠けたら、組み立て中のアクセスユニットはもう組めない
359
362
  if (track.sequence !== null &&
360
363
  mmtp.sequenceNumber !== (track.sequence + 1) >>> 0) {
@@ -453,11 +456,15 @@ export class MmtMediaReader {
453
456
  // 時刻の無いもの (MPU 全体で 1 つ) は sample_number が無いので 0 とする
454
457
  const sample = timed ? u32(unit, 4) : 0;
455
458
  const body = unit.subarray(timed ? TIMED_HEADER : ITEM_HEADER);
456
- if (fragmentation === WHOLE || fragmentation === FIRST) {
457
- if (track.sample !== null && sample !== track.sample)
458
- yield* this.finish(track);
459
- track.sample = sample;
459
+ /*
460
+ * sample_number が変わったら、途中の断片でも前のアクセスユニットを閉じる。先頭の断片が欠けたアクセスユニットも
461
+ * 1 つと数えないと、MPU の何番目か (`index`) がずれて、その MPU の残りのコマに違う時刻が付く
462
+ */
463
+ if (track.sample !== null && sample !== track.sample) {
464
+ yield* this.finish(track);
465
+ track.fragment = null;
460
466
  }
467
+ track.sample = sample;
461
468
  // 中身は届いたチャンクの一部を指すので、持ち越すものはコピーする (`TlvStream`)
462
469
  if (fragmentation === WHOLE) {
463
470
  this.keep(track, body.slice());
@@ -65,9 +65,8 @@ export class MmtServiceFilter {
65
65
  if (datagram === null || mmtp === null)
66
66
  continue;
67
67
  const flow = this.flow(datagram.flow);
68
- if (flow.foreign)
69
- continue;
70
68
  const packetId = mmtp.packetId;
69
+ // ほかの局だけの並びでも信号は読み続ける (後の PLT がこの局を挙げたら出し始める)。出すかは読んだあとで決める
71
70
  if (mmtp.payloadType === PAYLOAD_SIGNALING) {
72
71
  const before = flow.own;
73
72
  for (const raw of this.signaling.feed(datagram.flow, mmtp))
@@ -80,6 +79,8 @@ export class MmtServiceFilter {
80
79
  this.release(out);
81
80
  continue;
82
81
  }
82
+ if (flow.foreign)
83
+ continue;
83
84
  if (flow.own === null)
84
85
  this.hold(datagram.flow, packetId, packet.bytes);
85
86
  else if (flow.own.has(packetId))
package/dist/psi.d.ts CHANGED
@@ -32,8 +32,8 @@ export declare function crc32(data: Uint8Array): number;
32
32
  /**
33
33
  * セクション本体に CRC32 を付けて仕上げる。`section_length` も実長に直す。
34
34
  *
35
- * **書く側が 2 つある。** PAT を書き直して流す `service-filter.ts` と、
36
- * 偽の放送を組み立てる `synth.ts`。長さの直し方を間違えると読む側が黙って
35
+ * **書く側が 3 つある。** PAT を書き直して流す `service-filter.ts` と、
36
+ * 偽の放送を組み立てる `synth.ts`・`synth-mmt.ts`。長さの直し方を間違えると読む側が黙って
37
37
  * 捨てるだけなので、1 箇所に置く。
38
38
  */
39
39
  export declare function withCrc(body: number[]): Uint8Array;
package/dist/psi.js CHANGED
@@ -66,8 +66,8 @@ export function crc32(data) {
66
66
  /**
67
67
  * セクション本体に CRC32 を付けて仕上げる。`section_length` も実長に直す。
68
68
  *
69
- * **書く側が 2 つある。** PAT を書き直して流す `service-filter.ts` と、
70
- * 偽の放送を組み立てる `synth.ts`。長さの直し方を間違えると読む側が黙って
69
+ * **書く側が 3 つある。** PAT を書き直して流す `service-filter.ts` と、
70
+ * 偽の放送を組み立てる `synth.ts`・`synth-mmt.ts`。長さの直し方を間違えると読む側が黙って
71
71
  * 捨てるだけなので、1 箇所に置く。
72
72
  */
73
73
  export function withCrc(body) {
@@ -204,12 +204,13 @@ export class ServiceFilter {
204
204
  const pmtPid = parsePat(section).get(this.serviceId);
205
205
  if (pmtPid === undefined)
206
206
  continue;
207
- if (pmtPid === this.pmtPid)
208
- return;
209
- // 選局し直した・PAT が入れ替わった。PMT も読み直す
210
- this.pmtPid = pmtPid;
211
- this.pmt = new SectionAssembler(pmtPid);
212
- this.own.clear();
207
+ if (pmtPid !== this.pmtPid) {
208
+ // 選局し直した・PAT が入れ替わった。PMT も読み直す
209
+ this.pmtPid = pmtPid;
210
+ this.pmt = new SectionAssembler(pmtPid);
211
+ this.own.clear();
212
+ }
213
+ // PMT の PID が同じでも、バージョンと TSID は届いた PAT に合わせる
213
214
  const version = (section[5] >> 1) & 0x1f;
214
215
  this.rewritten = buildPat(this.tsid, version, this.serviceId, pmtPid);
215
216
  return;
package/dist/synth.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * TS のセクションを組み立てる。
3
3
  *
4
4
  * 実チューナーが無いので、解析のテストにも偽エージェントにも「それらしい TS」が要る。
5
- * 読む側 (psi.ts・eit.ts・dsmcc.ts・ait.ts、denpa の logo.ts など) と対になる書く側で、仕様を書き写しているのはここだけ。
5
+ * 読む側 (psi.ts・eit.ts・dsmcc.ts・ait.ts、denpa の logo.ts など) と対になる書く側で、TS の PSI・SI の仕様を書き写しているのはここだけ。
6
6
  */
7
7
  import { withCrc } from "./psi.js";
8
8
  export { withCrc };
package/dist/synth.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * TS のセクションを組み立てる。
3
3
  *
4
4
  * 実チューナーが無いので、解析のテストにも偽エージェントにも「それらしい TS」が要る。
5
- * 読む側 (psi.ts・eit.ts・dsmcc.ts・ait.ts、denpa の logo.ts など) と対になる書く側で、仕様を書き写しているのはここだけ。
5
+ * 読む側 (psi.ts・eit.ts・dsmcc.ts・ait.ts、denpa の logo.ts など) と対になる書く側で、TS の PSI・SI の仕様を書き写しているのはここだけ。
6
6
  */
7
7
  import { joinBytes } from "./bytes.js";
8
8
  import { PACKET, SYNC, withCrc } from "./psi.js";
@@ -410,7 +410,7 @@ export function bxmlDescriptor(options = {}) {
410
410
  const body = [
411
411
  ...be(dataComponentId),
412
412
  // 伝送方式 00 / 入口 / 自動起動 / 解像度 0011 (960x540)
413
- ...(entryPoint ? [0x33, 0x70] : [0x00, 0xff]),
413
+ ...(entryPoint ? [0x33, 0x70] : [0x00]),
414
414
  0xf8, // dataEventId 0xF / event_section_flag
415
415
  0xa0, // 即時取得あり / 蓄積不可 / 起動優先
416
416
  ];
package/dist/ttml.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * 縦書き・アニメーション (set) は読まない
11
11
  */
12
12
  import { CAPTION_TEXT_VERSION, } from "./caption-text.js";
13
- import { CLOCK } from "./mmt-media.js";
13
+ import { CLOCK } from "./pes.js";
14
14
  const ENTITIES = {
15
15
  lt: "<",
16
16
  gt: ">",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danything/arib",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "日本のデジタル放送 (ARIB) の TS と MMT/TLV を読む: PSI・PES・EIT・ARIB の文字・字幕・データ放送、TLV・MMTP・MMT-SI・MH-EIT",
5
5
  "license": "MIT",
6
6
  "repository": {