@danything/arib 2.0.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 +40 -3
- package/dist/b24caption.js +4 -1
- package/dist/bits.d.ts +25 -0
- package/dist/bits.js +66 -0
- package/dist/eit.d.ts +14 -2
- package/dist/eit.js +56 -33
- 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 +20 -2
- package/dist/psi.js +41 -12
- 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,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **本物の HEVC と AAC を MMT/TLV に包んで、4K の局の放送を作る** (試験用。`synth-av.ts` の 4K の放送に当たる)。
|
|
3
|
+
*
|
|
4
|
+
* 素材は ffmpeg で作り、ここに base64 で持つ (E2E のイメージには本物の ffmpeg が無い)。
|
|
5
|
+
*
|
|
6
|
+
* - 映像: `testsrc2` の 64x64・59.94 コマ・16 コマを libx265 で HEVC Main10 にしたもの
|
|
7
|
+
* (keyint 8・B ピクチャ 3・閉じた GOP・AUD と VPS/SPS/PPS をキーフレームごとに)。`-f hevc` の Annex B
|
|
8
|
+
* - 音声: 440 Hz の正弦波を AAC LC (48 kHz・2 チャンネル) にした 13 フレームを `-f latm` (LOAS) で書いたもの
|
|
9
|
+
*
|
|
10
|
+
* どちらも 1 つの mp4 から書き出した。そのときの映像の PTS と DTS (180000 の単位、デコードの順) が `VIDEO_TIMES`。
|
|
11
|
+
* `SynthMmtAv` は映像の GOP (8 コマ) を 1 つの MPU にして繰り返し、時刻と番号を進めながら流し続ける
|
|
12
|
+
* (偽エージェントの 4K の局。`tests/fake/broadcast.ts`)
|
|
13
|
+
*/
|
|
14
|
+
import { concatBytes } from "./bytes.js";
|
|
15
|
+
import { compressedIp, mfuPackets, mptTable, mpuExtendedTimestampDescriptor, mpuTimestampDescriptor, paMessage, signalingPackets, } from "./synth-mmt.js";
|
|
16
|
+
/** 映像の素材 (HEVC Main10 の Annex B。base64) */
|
|
17
|
+
export const HEVC = "AAAAAUYBEAAAAAFAAQwB//8CIAAAAwCQAAADAAADAB6VlAkAAAABQgEBAiAAAAMAkAAAAwAAAwAeoCCBBNllZZJMrwFoCAAAH0gA" +
|
|
18
|
+
"B1MAQAAAAAFEAcFytCJAAAAAAUABDAH//wIgAAADAJAAAAMAAAMAHpWUCQAAAAFCAQECIAAAAwCQAAADAAADAB6gIIEE2WVlkkyv" +
|
|
19
|
+
"AWgIAAAfSAAHUwBAAAAAAUQBwXK0IkAAAAABKAGvCmDNemFXo2IW9LkqJncuWp/hWNz/hGO7S0ofuo5yV/mrEBCOdn5YTb/Qbipx" +
|
|
20
|
+
"TCzBpNYj6O5lLOi4UTSDO/9x/pCRcK76LeqAvGc7YNBG7swCTyPAt8LfyAccOo3UCcdA8lJ2yAqxsAC8i4wMuOfoL4yikP+HOSP+" +
|
|
21
|
+
"N92uxF7R/ajaCHFksGwzv1u+MKz/xNrxA4vgKhhB+6QYJOPKKkzDDZ/Bqf2rNO/X8NCSoYclStKQwqqWmcQXE2OVr3VR63HYEy6k" +
|
|
22
|
+
"3A0d4rovQc0QJAFMVKS2mqD2AxuTHjkKbROyiHHunW3RG/ATQc/QDc9rOcwwS9d31vkXegXJ6aStUJMMolnJ0D1d44s/QXp+Ygyo" +
|
|
23
|
+
"YJ9Ro6uIfKOiuCSPSDqaZK5OeXVFl/KPSEX62Kpx7OiTcCnq9YylL3qe9c9t7RH3/WZ1EXlRqzaklJp6oTauY1muushdQ7e/PyW1" +
|
|
24
|
+
"PvgSl9fxIsSlgCbU/Dh9elXxnpZ23/tCznPsGIgo7iMLJc82QpYjOzF1IFbjeFFPMHi0Bi3BC+o7WszasF839cG1Vvq/tBW2+wUt" +
|
|
25
|
+
"Q9qE2pMcMpjs3AI5RRlL1s0+hjgvUZj+ub9LQiNPFSh4lCHUVon56F9muK+XR9LJiUCLpAc3FE2o2DhC01QyzAS6wEIF9rIcEM0H" +
|
|
26
|
+
"jD3bbjwtSMZGJdlEgYqo7xazstugpzoKkJsPafc18pjaJtvNkODgXbRSz4Hk054n/KmyaW4Eucxwi0LBhtfUIb+Z0//I+/U+P9W+" +
|
|
27
|
+
"nxClstfiaj8S6xuUiJrjch1CirE3Yg09936uQyrC8ej8w1+mLmv/RecOD7gRL0WYPzfoC6egcVWQcXjQiIdhOl0x18QsMpZmX5tb" +
|
|
28
|
+
"o8T4nKiUdrSxkxbiwWACL1N/X98DIetZlOhIv0Uppipn4m7mXPAL1HPa5YlVQMRQQ8NagjQefc4i0oA22JyUBLYVNBnfBAwub/k5" +
|
|
29
|
+
"iXdV5kU62B9bYb1lPj7kb33auU/OF4C+BD2Xg47URDxexDCUiTVdYmoY2yEgoCcSx4OEqti+hzW6/AAAAAFGATAAAAABAgHQGV+E" +
|
|
30
|
+
"RhRAxP1OdNJMGX7HFvlnhRZ+pNScwfmxpYe0z+eOhbDOGGIjN+iAcqk/Xn9iHQywWH7S+JkwAAAAAUYBUAAAAAECAeBEl+GGEP7/" +
|
|
31
|
+
"AyPFPwAAAAFGAVAAAAABAAHgJP/6LDWA6YEAAAABRgEwAAAAAQIB0DiSdfcQwGFExNTew6M334f//rsUEjJXhxfUoWn0b47NLGax" +
|
|
32
|
+
"cDCND1xinyZFDKG79t6v87Ul8p8AAAABRgFQAAAAAQIB4KIl1X3GGED+wPA/sT/3P19GYzW3/+6oFZ7/uAQ8W0/s7sDEVjiA2x0p" +
|
|
33
|
+
"LfUQRUP8z9p/tQWD55gSjIAAAAABRgFQAAAAAQAB4Ib/X0iw1uuZuAAAAAFGAVAAAAABAAHgwi1/3GGs9kf////Dh////5znDnAA" +
|
|
34
|
+
"AAABRgEQAAAAAUABDAH//wIgAAADAJAAAAMAAAMAHpWUCQAAAAFCAQECIAAAAwCQAAADAAADAB6gIIEE2WVlkkyvAWgIAAAfSAAH" +
|
|
35
|
+
"UwBAAAAAAUQBwXK0IkAAAAABQAEMAf//AiAAAAMAkAAAAwAAAwAelZQJAAAAAUIBAQIgAAADAJAAAAMAAAMAHqAggQTZZWWSTK8B" +
|
|
36
|
+
"aAgAAB9IAAdTAEAAAAABRAHBcrQiQAAAAAEoAa8JIMr6cbM8NzW/IaO8R0P/L4HzUUVSKZm/YHELmBK+qjXNtel9/oKajlBe8Mih" +
|
|
37
|
+
"P+FvK/eB7xCs6TRnCv116AhpnkczG/Os/+rnYBgyzR5z3ACQwPbxEVbNKu7EyqmkygsU/wutc9Q8b2KA1TntYXw1JELZXHRmZmE8" +
|
|
38
|
+
"q7V7b7m6DHfYNlIRHKWXtQF2GSy3H1R9Yad9Hn3i4vidui09N4XGYSKK3rASKz6aYq54GqMwtlYc0NFGVsEng5cRieC6Ev13XwDb" +
|
|
39
|
+
"Aj3ZDpVWsqnb+oIfZc77mFzCo6IGhI2U1uYnIsDyrQxXcpGaieozuvoaQ4QakRQoAxcuPBfoMGrWDxsCnGYGn28ogKwJaaEAnDAv" +
|
|
40
|
+
"1osxte75K9WY0td5Hl49IeBec3ff4fAS4eQmNYJle6eF/GcojyfTBYJc5rP7Uf919D/EzMkuE4LfLRz9PDoM+Jlgm3YYZLllwl2k" +
|
|
41
|
+
"ndVK8YuO4qw9/cJ5bc3uLJ18BGEapnfWkIYEg5z4A13xqt4ZaYNCd3v+jMGyN/qvvCse07R9Z7QBEUk4ilx44lT42ptljT1epcn5" +
|
|
42
|
+
"yGdfQhCUh0xqBnoMoCupUDF+8GPYqKNaqg/7z6DFg8qhWrhhu2jvDz5zhzPe0A0i7MTie9/LS32kbxw85yNEwf3Ik0sh/lFXN66C" +
|
|
43
|
+
"NFbvoUREEOjQ6CRha2EHM4J4KpA9SVIY7eFl6srbHPU6mNFVoerYPJpiNNc665knprrGm5SNTiDrwMsaAIdufA8uvlrZf9KG7CM4" +
|
|
44
|
+
"LUHF5fXFyYOGmII9qHg8GcoIjioGE64T43spvAz5hn3lHV/PA/5ygr3tvJEwISB0W4tEHhwzTkVlEQg7FQJfUegqc735QcBEVPYL" +
|
|
45
|
+
"ajjFcqoSHQwZE50wU7EPnRaR3OP1KqYiSy/o8KP4UH4Lr3tnjXClRdeb91q4Jv1k3cpw2CYJlmnx24HKaiNShcncVoSvk+Dc50J2" +
|
|
46
|
+
"Y8QluYJ/KETvcH2KIgeMZkjFgYR8KPOwNu8YeOHIEQDA92SYwCKXL++doMFVpn23DZARikC4rhBdjstKQPJPZxcfycalYO1lQrbU" +
|
|
47
|
+
"pzeneekOHfxUIXN/reBozSZJAg5xcfXpuGwP1p3nLkB7uho1KFzoKZ1SdYsE9UVFBLxcASFnoRAsPiZiw2Sdp/zAAAAAAUYBMAAA" +
|
|
48
|
+
"AAECAdAZX4QxhTDEU5U1mgJKKWh0tzkJrL5fpf9GbmnqThELtrcdLW4//3G2wi1UwPcDrBiDVGkeAO5qnnue6AqaJMOtstI5+ciO" +
|
|
49
|
+
"XJV0baUu4u/Mzdr6t/DyPzQSDgAAAAFGAVAAAAABAgHgRJfhhhD+/wPyNy4AAAABRgFQAAAAAQAB4CT/+iw1gOmBAAAAAUYBMAAA" +
|
|
50
|
+
"AAECAdA4knX3EQGFEMOjuw/K0rN1OUS9fs9eri6NRHRAY1Rgi+Vx6HmD7ZHYay0uNfTdSNy9Rt2kM4HyjUA5q87I1P53V3Sk1ZHO" +
|
|
51
|
+
"F9hlCAAAAAFGAVAAAAABAgHgoiXVfcYYQOmwuX8AAAABRgFQAAAAAQAB4Ib/X0iw0vZH////e4f////kvfAAAAABRgFQAAAAAQAB" +
|
|
52
|
+
"4MItf9xhrMpA";
|
|
53
|
+
/** 音声の素材 (AAC LC の LOAS。base64) */
|
|
54
|
+
export const LOAS = "VuCHIAARkB/kBvAQAmMLsxmxiXGJyXGJgYgCEoD///8AUlJR2mIOieyvGtevpckuRaQ5Ikn1cAQLJq2nY7Ix35jUYLRTDkJQ2htL" +
|
|
55
|
+
"AwMDAwMDAwMbBgYGRIkRs2bBgZEiRGzZs2iRIpZZYoooooooqQb6bw6J7K8a16+lyS5FpDkiSLgAAAAAAAOAVuDf7pClNn8Pw/D8" +
|
|
56
|
+
"JiyV2WS6sl1ZTpv9/OrbnEv1/0/fzrXF61/T/p+/nWuNXfr/6fjrjWtasbU7NuueeYM7YUnhIlFCl/fyfgiWKhgwboH0oV9KB9Pp" +
|
|
57
|
+
"QYD6fTXQB9PpQYD6fTXroMAPp9Po3A2DgiDAkMjDqQMy77jmFI3YpEIYmfKJnyiZ8vlETB8vlETB8vlx4sHy5vlFFEAOPyf4kgAE" +
|
|
58
|
+
"qHLCfgPOLOBISEhIScCTgSEhISlamB2wd8HfB3xv9/OrbfEnr/p+/nWg5/6fv51oOP/p+OuNAAAAAAAAAADgAFbgebuQvGf///gA" +
|
|
59
|
+
"SjGKOmEOlK/b+v41rjzxOpwaS5HJa6nkgDpxxmgqyxvb4+PhvP7+8L/x8N5/f30H+PjMn394R8fDYz+/oen4bsdEaQxzlBvpxDoR" +
|
|
60
|
+
"GV+39fxrXHnidTg0lyOS11HDLAAAAAAAAmStd2JwAkTdV6kMrgBW4Hu8kIxn///4AD7ByQKNEQdGItEIh/r/9P9/9viVd3l5Uvd8" +
|
|
61
|
+
"6edzlvd3FAJzPxK92gB6OLe4zGiiiKJESC48olailRCBdIWnCw8gWQghVcoPsUs0RgtjIVPo30Xh0fi0XiH+v/0/3/2+JV3eXlS9" +
|
|
62
|
+
"3zp525bLgAAAAAAADgBW4IbCEIxn///4ADrR0hoxHoRDoxCzx+3/8X3//G5JM1VJJfJ9OVJamCOsKrEqo1D4o16WzP5CUqYJCQkJ" +
|
|
63
|
+
"wmWtcy14Tu67JHewsgmeuvy4bHzzoQ7LTvE4TjSFJowlmSy1m5rTRhY6jfR+PQ+HR+Fnj9v/4vv/+NySZqqSS+T6ciFAAAAAAAAO" +
|
|
64
|
+
"AFbgjMUQjGf///kAO9HKGiIOiEehEbK/8f8fj/7St9KSmpvjmPm5zMlVlwSMkxoq7mSLzgf4nUDtqMn2c08885556lEEeoBSRACs" +
|
|
65
|
+
"kDkMa1jj4nxxQGlc7Tc1BOUbImKs6by8VQpVOkE5oQzncb6IQ6Lx6Hxsr/x/x+P/tK30pKam+OY+bnMFAAAAAAAAAHAAVuCTyJCM" +
|
|
66
|
+
"Z///+QA52kmILRCHRKHQqHRiFPn9v/2+//t7XuS0czqu+j+veVpSmBihcI125I7ZeMRx9J0qU1iFmmI/3pee0s0vScksvWAE0PMT" +
|
|
67
|
+
"7YgMuc73/0ctxZTdIWXTpwqDVcC8AYtU24UrwIbTmN9EIdF4dD4dH4U+f2//b7/+3te5LRzOq76P695QUAAAAAAAAA4AVuCczRCM" +
|
|
68
|
+
"Z///+YA52dIaIzaEQ6EQn2/r/2/f/rq86oRl6yuH7O7c3e91QTKj6whbdBuO4zsY2/preh9WGGjPSPHsIF5zOvG7g7wLdxBKRbzW" +
|
|
69
|
+
"F99DH3T8aunBdpxh1kb6XYcS3ta8GulcjIRla9aJZrWn2BggsL2ISOY30Qi8mh8Oh8J9v6/9v3/66vOqEZesrh+zuw2oAAAAAAAA" +
|
|
70
|
+
"ADgAVuCZy5CMZ///+0A42dEiVBaExaFQ6Ew6Vm//j/j/b/Pmd645mq9dayRAN8ZHKUCyc1Wzoa8eJBSMNmRH5D3mpl3ZfJ0SIz9z" +
|
|
71
|
+
"0FLLKbNg8slSGLnU7cy91r99dxPbXRLmHqvsD5vWzU21fh1xCOY7l9nTq4jfQiLQiHQ+HSs3/8f8f7f58zvXHM1XrrWSIBvjIJQA" +
|
|
72
|
+
"AAAAAAAAADgAVuCezhCMZ///+EA42pG6RSaMQq5/8f+n+//lM9uZCXlqzT/vmGXVNjcWxdFdu5S7dMp5zdr1GNt7JTFAWKmUQnVI" +
|
|
73
|
+
"TMEiQmO0qJnHBOa46L4XblF7c75jf8beT9N/t6UyX2d0zbnFuDhTd9JIW0m5m5tgLjhWrTXRTx5CGWriN9IIfHo/Crn/x/6f7/+U" +
|
|
74
|
+
"z25kJeWrNP++YFUAAAAAAAAAcABW4KTREIxn///5wDrZyYhNEIkJoVDoRCZ/f/6ff/14VrLXhd0Py8XKrcrsOWFSX4T3/PDPsfkP" +
|
|
75
|
+
"q3vgbjM/V6znnOqcbD80y/UAJnCqW1GrHlua06oY5naifOW1jdZLY+zNCHxpl0Ugz3159hE72+4hn4Gvk9nvY50ygdjhcQ5UC5DE" +
|
|
76
|
+
"dRvohH49D4dD4TP7//T7/+vCtZa8Luh+XixtQAAAAAAAAAADgFbgqNMQjGf///lAOdHRAmYWiUOhIWhUOiUSc/+P/T8f/fVV7bjz" +
|
|
77
|
+
"6rSZ1j+1nLFqqwin2zsfAeroZQ8f0ba46ORTI1fifwNGjF3cZReMK+Mht/E1fhzMZGnOilbgS8JjglEdBxTRsUNKLG11ApJ3QU6L" +
|
|
78
|
+
"hFuVUGEnFm4scOcG6lPbZzG+iEOhEPh0Ph0XiTn/x/6fj/76qvbcefVaTOsf2s5BQAAAAAAAAABwAFbgstgQjGf////AUhMS7M5/" +
|
|
79
|
+
"P341clySJIiREJQPo889knnnnnnnnnDLwYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGB" +
|
|
80
|
+
"gYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgZSDzzszn8/fjVyXJIki" +
|
|
81
|
+
"JEAAAAAAAAAAAAAA4AA=";
|
|
82
|
+
/** ffprobe が読んだ映像の [PTS, DTS] (180000 の単位、デコードの順) */
|
|
83
|
+
export const VIDEO_TIMES = [
|
|
84
|
+
[0, -6006],
|
|
85
|
+
[9009, -3003],
|
|
86
|
+
[6006, 0],
|
|
87
|
+
[3003, 3003],
|
|
88
|
+
[21021, 6006],
|
|
89
|
+
[15015, 9009],
|
|
90
|
+
[12012, 12012],
|
|
91
|
+
[18018, 15015],
|
|
92
|
+
[24024, 18018],
|
|
93
|
+
[33033, 21021],
|
|
94
|
+
[30030, 24024],
|
|
95
|
+
[27027, 27027],
|
|
96
|
+
[45045, 30030],
|
|
97
|
+
[39039, 33033],
|
|
98
|
+
[36036, 36036],
|
|
99
|
+
[42042, 39039],
|
|
100
|
+
];
|
|
101
|
+
/** 映像の 1 GOP のコマ数 (keyint) */
|
|
102
|
+
export const GOP = 8;
|
|
103
|
+
/** 音声の素材のフレーム数 */
|
|
104
|
+
export const AUDIO_FRAMES = 13;
|
|
105
|
+
/** MPT の時刻の単位 (実機の BS4K と同じ) */
|
|
106
|
+
export const TIMESCALE = 180000;
|
|
107
|
+
/** 映像の 1 コマ (180000 の単位。59.94 コマ) */
|
|
108
|
+
const FRAME = 3003;
|
|
109
|
+
/** 音声の 1 フレーム (1024 標本 @48kHz を 180000 の単位で) */
|
|
110
|
+
const AUDIO_FRAME = 3840;
|
|
111
|
+
/** 音声の 1 MPU のフレーム数 (実機の BS4K と同じ) */
|
|
112
|
+
const AUDIO_PER_MPU = 4;
|
|
113
|
+
/** base64 をバイト列に */
|
|
114
|
+
export function base64Bytes(base64) {
|
|
115
|
+
const text = atob(base64);
|
|
116
|
+
const out = new Uint8Array(text.length);
|
|
117
|
+
for (let i = 0; i < text.length; i++)
|
|
118
|
+
out[i] = text.charCodeAt(i);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
/** HEVC の NAL ユニットの種類 */
|
|
122
|
+
function nalType(nal) {
|
|
123
|
+
return (nal[0] >> 1) & 0x3f;
|
|
124
|
+
}
|
|
125
|
+
/** Annex B を NAL ユニットに切り、AUD (35) ごとにコマにまとめる (デコードの順) */
|
|
126
|
+
export function hevcPictures(annexB) {
|
|
127
|
+
const starts = [];
|
|
128
|
+
for (let i = 0; i + 3 <= annexB.length; i++)
|
|
129
|
+
if (annexB[i] === 0 && annexB[i + 1] === 0 && annexB[i + 2] === 1)
|
|
130
|
+
starts.push(i + 3);
|
|
131
|
+
const nals = starts.map((start, k) => {
|
|
132
|
+
let end = k + 1 < starts.length ? starts[k + 1] - 3 : annexB.length;
|
|
133
|
+
while (end > start && annexB[end - 1] === 0)
|
|
134
|
+
end--;
|
|
135
|
+
return annexB.subarray(start, end);
|
|
136
|
+
});
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const nal of nals) {
|
|
139
|
+
if (nalType(nal) === 35 || out.length === 0)
|
|
140
|
+
out.push([]);
|
|
141
|
+
out.at(-1).push(nal);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
/** LOAS の同期のヘッダー (11 ビットの 0x2B7 と 13 ビットの長さ) を外し、AudioMuxElement を並べる */
|
|
146
|
+
export function latmElements(loas) {
|
|
147
|
+
const out = [];
|
|
148
|
+
for (let at = 0; at + 3 <= loas.length;) {
|
|
149
|
+
if (((loas[at] << 3) | (loas[at + 1] >> 5)) !== 0x2b7)
|
|
150
|
+
break;
|
|
151
|
+
const length = ((loas[at + 1] & 0x1f) << 8) | loas[at + 2];
|
|
152
|
+
out.push(loas.subarray(at + 3, at + 3 + length));
|
|
153
|
+
at += 3 + length;
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
const be32 = (n) => [
|
|
158
|
+
n >>> 24,
|
|
159
|
+
(n >> 16) & 0xff,
|
|
160
|
+
(n >> 8) & 0xff,
|
|
161
|
+
n & 0xff,
|
|
162
|
+
];
|
|
163
|
+
/** 字幕の文書を流す間隔 (映像の MPU の数。約 2 秒) */
|
|
164
|
+
const CAPTION_EVERY = 15;
|
|
165
|
+
/** 字幕の 1 つの文書が出ている長さ (秒) */
|
|
166
|
+
const CAPTION_SECONDS = 1.5;
|
|
167
|
+
/** NTP の時刻 (1900 年から) と UNIX 時刻 (1970 年から) の差 (秒) */
|
|
168
|
+
const NTP_UNIX = 2_208_988_800;
|
|
169
|
+
/** 字幕の TTML の文書 (B62 に似せたもの。1920x1080 の下の領域に中央寄せ) */
|
|
170
|
+
function captionDocument(text) {
|
|
171
|
+
return [
|
|
172
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
173
|
+
'<tt xml:lang="ja" xmlns="http://www.w3.org/ns/ttml" xmlns:tts="http://www.w3.org/ns/ttml#styling" tts:extent="1920px 1080px">',
|
|
174
|
+
'<head><styling><style xml:id="s" tts:fontSize="60px" tts:color="white"/></styling>',
|
|
175
|
+
'<layout><region xml:id="r" tts:origin="160px 840px" tts:extent="1600px 180px" tts:textAlign="center" tts:displayAlign="after"/></layout></head>',
|
|
176
|
+
`<body><div><p begin="0s" end="${CAPTION_SECONDS}s" region="r" style="s"><span>${text}</span></p></div></body></tt>`,
|
|
177
|
+
].join("\n");
|
|
178
|
+
}
|
|
179
|
+
/** 映像の 1 MPU の長さ (ミリ秒)。実時間で流すときの間隔 */
|
|
180
|
+
export const MPU_MS = (GOP * FRAME * 1000) / TIMESCALE;
|
|
181
|
+
/**
|
|
182
|
+
* **素材の映像と音声 (と字幕) を、MMT/TLV で流し続ける。** `next` を呼ぶたびに映像の 1 MPU (1 GOP・8 コマ・約 0.13 秒) と、
|
|
183
|
+
* そこまでに要る音声の MPU を、時刻と番号を進めて返す。どの MPU の前にも、これから来る MPU の時刻を並べた MPT を付ける
|
|
184
|
+
* (途中から読み始めても、次の MPU から読める)
|
|
185
|
+
*/
|
|
186
|
+
export class SynthMmtAv {
|
|
187
|
+
options;
|
|
188
|
+
video;
|
|
189
|
+
audio;
|
|
190
|
+
ids;
|
|
191
|
+
cid;
|
|
192
|
+
/** 映像の最初の MPU の提示時刻 (180000 の単位、NTP の始まりから) */
|
|
193
|
+
base;
|
|
194
|
+
/** 次に流す映像の MPU (0 から。mpu_sequence_number) */
|
|
195
|
+
k = 0;
|
|
196
|
+
/** 次に流す音声の MPU (0 から) */
|
|
197
|
+
j = 0;
|
|
198
|
+
/** packet_id ごとの、次の packet_sequence_number */
|
|
199
|
+
sequence = new Map();
|
|
200
|
+
constructor(options) {
|
|
201
|
+
this.options = options;
|
|
202
|
+
this.video = hevcPictures(base64Bytes(HEVC));
|
|
203
|
+
this.audio = latmElements(base64Bytes(LOAS));
|
|
204
|
+
this.ids = {
|
|
205
|
+
video: options.videoPacketId ?? 0xf100,
|
|
206
|
+
audio: options.audioPacketId ?? 0xf110,
|
|
207
|
+
caption: options.captionPacketId ?? 0xf130,
|
|
208
|
+
};
|
|
209
|
+
this.cid = options.cid ?? 1;
|
|
210
|
+
this.base = Math.round((options.startMs / 1000 + NTP_UNIX) * TIMESCALE);
|
|
211
|
+
}
|
|
212
|
+
/** 180000 の単位の時刻を NTP に */
|
|
213
|
+
ntp(ticks) {
|
|
214
|
+
const whole = Math.floor(ticks / TIMESCALE);
|
|
215
|
+
return [
|
|
216
|
+
whole >>> 0,
|
|
217
|
+
Math.round(((ticks - whole * TIMESCALE) / TIMESCALE) * 2 ** 32) >>> 0,
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
/** packet_id ごとに続き番号を振りながら MFU を作る */
|
|
221
|
+
mfu(packetId, mpu, units, options = {}) {
|
|
222
|
+
const sequenceNumber = this.sequence.get(packetId) ?? 0;
|
|
223
|
+
const made = mfuPackets(packetId, mpu, units, {
|
|
224
|
+
...options,
|
|
225
|
+
sequenceNumber,
|
|
226
|
+
});
|
|
227
|
+
this.sequence.set(packetId, sequenceNumber + made.length);
|
|
228
|
+
return made;
|
|
229
|
+
}
|
|
230
|
+
/** 映像の MPU `k` のコマの [PTS, DTS] (180000 の単位、NTP の始まりから。デコードの順) */
|
|
231
|
+
videoTimes(k) {
|
|
232
|
+
const gop = k % 2;
|
|
233
|
+
const shift = this.base + (k - gop) * GOP * FRAME;
|
|
234
|
+
return VIDEO_TIMES.slice(gop * GOP, (gop + 1) * GOP).map(([pts, dts]) => [
|
|
235
|
+
shift + pts,
|
|
236
|
+
shift + dts,
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
/** これから流す映像・音声・字幕の MPU の時刻を並べた MPT */
|
|
240
|
+
mpt() {
|
|
241
|
+
const videoMpus = [this.k, this.k + 1, this.k + 2].map((k) => {
|
|
242
|
+
const times = this.videoTimes(k);
|
|
243
|
+
const first = Math.min(...times.map(([pts]) => pts));
|
|
244
|
+
return {
|
|
245
|
+
sequence: k,
|
|
246
|
+
presentation: this.ntp(first),
|
|
247
|
+
decodingOffset: first - times[0][1],
|
|
248
|
+
dtsPtsOffsets: times.map(([pts, dts]) => pts - dts),
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
const audioMpus = [this.j, this.j + 1, this.j + 2, this.j + 3].map((j) => ({
|
|
252
|
+
sequence: j,
|
|
253
|
+
presentation: this.ntp(this.base + j * AUDIO_PER_MPU * AUDIO_FRAME),
|
|
254
|
+
decodingOffset: 0,
|
|
255
|
+
dtsPtsOffsets: new Array(AUDIO_PER_MPU).fill(0),
|
|
256
|
+
}));
|
|
257
|
+
const caption = Math.floor(this.k / CAPTION_EVERY);
|
|
258
|
+
const captionMpus = [caption, caption + 1].map((c) => ({
|
|
259
|
+
sequence: c,
|
|
260
|
+
presentation: this.ntp(this.base + c * CAPTION_EVERY * GOP * FRAME),
|
|
261
|
+
decodingOffset: 0,
|
|
262
|
+
dtsPtsOffsets: [0],
|
|
263
|
+
}));
|
|
264
|
+
const assets = [
|
|
265
|
+
{
|
|
266
|
+
type: "hev1",
|
|
267
|
+
packetId: this.ids.video,
|
|
268
|
+
descriptors: [
|
|
269
|
+
...mpuTimestampDescriptor(videoMpus.map((m) => [m.sequence, m.presentation])),
|
|
270
|
+
...mpuExtendedTimestampDescriptor(TIMESCALE, videoMpus, FRAME),
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
type: "mp4a",
|
|
275
|
+
packetId: this.ids.audio,
|
|
276
|
+
descriptors: [
|
|
277
|
+
...mpuTimestampDescriptor(audioMpus.map((m) => [m.sequence, m.presentation])),
|
|
278
|
+
...mpuExtendedTimestampDescriptor(TIMESCALE, audioMpus, AUDIO_FRAME),
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
...(this.options.caption === undefined
|
|
282
|
+
? []
|
|
283
|
+
: [
|
|
284
|
+
{
|
|
285
|
+
type: "stpp",
|
|
286
|
+
packetId: this.ids.caption,
|
|
287
|
+
descriptors: [
|
|
288
|
+
...mpuTimestampDescriptor(captionMpus.map((m) => [m.sequence, m.presentation])),
|
|
289
|
+
...mpuExtendedTimestampDescriptor(TIMESCALE, captionMpus, 0),
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
]),
|
|
293
|
+
];
|
|
294
|
+
return signalingPackets(this.options.mptPacketId, [
|
|
295
|
+
paMessage(mptTable({ packageId: this.options.serviceId, assets })),
|
|
296
|
+
]);
|
|
297
|
+
}
|
|
298
|
+
/** 映像の 1 MPU と、そこまでの音声 (と字幕) を TLV のバイト列で */
|
|
299
|
+
next() {
|
|
300
|
+
const k = this.k;
|
|
301
|
+
const packets = [...this.mpt()];
|
|
302
|
+
if (this.options.caption !== undefined && k % CAPTION_EVERY === 0)
|
|
303
|
+
packets.push(...this.mfu(this.ids.caption, k / CAPTION_EVERY, [
|
|
304
|
+
{
|
|
305
|
+
sample: 0,
|
|
306
|
+
data: new TextEncoder().encode(captionDocument(this.options.caption)),
|
|
307
|
+
},
|
|
308
|
+
]));
|
|
309
|
+
const gop = k % 2;
|
|
310
|
+
this.video.slice(gop * GOP, (gop + 1) * GOP).forEach((nals, sample) => {
|
|
311
|
+
// スライスの手前の小さな NAL ユニットは集約、スライスは分割して送る (実機と同じ運び方)
|
|
312
|
+
const unit = (nal) => ({
|
|
313
|
+
sample,
|
|
314
|
+
data: [...be32(nal.length), ...nal],
|
|
315
|
+
});
|
|
316
|
+
const small = nals.filter((n) => nalType(n) >= 32);
|
|
317
|
+
const slices = nals.filter((n) => nalType(n) < 32);
|
|
318
|
+
packets.push(...this.mfu(this.ids.video, k, small.map(unit), {
|
|
319
|
+
aggregate: true,
|
|
320
|
+
rap: sample === 0,
|
|
321
|
+
}), ...this.mfu(this.ids.video, k, slices.map(unit), { fragment: 1000 }));
|
|
322
|
+
});
|
|
323
|
+
this.k++;
|
|
324
|
+
// 音声は映像の終わりまでのぶんを流す
|
|
325
|
+
const until = (this.k * GOP * FRAME) / (AUDIO_PER_MPU * AUDIO_FRAME);
|
|
326
|
+
while (this.j < until) {
|
|
327
|
+
const j = this.j++;
|
|
328
|
+
const units = Array.from({ length: AUDIO_PER_MPU }, (_, sample) => ({
|
|
329
|
+
sample,
|
|
330
|
+
data: this.audio[(j * AUDIO_PER_MPU + sample) % this.audio.length],
|
|
331
|
+
}));
|
|
332
|
+
packets.push(...this.mfu(this.ids.audio, j, units));
|
|
333
|
+
}
|
|
334
|
+
return concatBytes(packets.map((p) => compressedIp(this.cid, p)));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -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;
|