日記...
静かなインターネットを始めてそっちにもたまに書いているが、はてな.....。
続きを読む日記、やっぱり書いたほうがいいなとなるきっかけがあったので漠然とやっていく。
この本、バイナリが好きなので読んだ。
雑な感想として:
全体的に隠蔽しているところを、なるべくほぐそうという気持ちが感じられてよかった。
emscriptenの提供するAPIに依存するところはしているが、まあそこはemscriptenを追えばいいのだろう...。自分のやりたいことには近づけた感がある。
次はこれを読んでいるところ。
wasmよりwebpackの理解に難儀している........これも感想を残したい。
あまりにも個人の日記を書いていないので、某所にただのメモとしておいてあるやつを再構成したりしてお茶をにごす。
例えばこのツイート... ポスト。
#スイカゲーム #NintendoSwitch pic.twitter.com/ukIwEVZm1t
— Uchio Kondo🐝 (@udzura) 2023年10月2日
動画を再生しようとするとき、最初に LS8h5YVnzTCKxCEM.m3u8
というファイルにアクセスした。
これはHLSのplaylist(マニフェスト)で、中身はこう:
#EXTM3U #EXT-X-VERSION:6 #EXT-X-INDEPENDENT-SEGMENTS #EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=501854,BANDWIDTH=639498,RESOLUTION=1280x720,CODECS="mp4a.40.2,avc1.640020" /ext_tw_video/1708859564446429185/pu/pl/avc1/1280x720/_u3fHmjJPHlXgv3W.m3u8?container=fmp4 #EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=205381,BANDWIDTH=264621,RESOLUTION=640x360,CODECS="mp4a.40.2,avc1.4d001f" /ext_tw_video/1708859564446429185/pu/pl/avc1/640x360/emjUVd3t94tT8j-1.m3u8?container=fmp4 #EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=91859,BANDWIDTH=116120,RESOLUTION=480x270,CODECS="mp4a.40.2,avc1.4d001e" /ext_tw_video/1708859564446429185/pu/pl/avc1/480x270/IhZW9A8k91V6lbX3.m3u8?container=fmp4
container=fmp4
とのことだが、ここを例えば mpeg2tsに変えても無視される気がする...このうち一つ IhZW9A8k91V6lbX3.m3u8
の中身はこう。こちらはメディアマニフェストとか言うらしい。
#EXTM3U #EXT-X-VERSION:6 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-TARGETDURATION:3 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-MAP:URI="/ext_tw_video/1708859564446429185/pu/vid/avc1/0/0/480x270/BEWYQPCMCXWMJLNs.mp4" #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/0/3000/480x270/SjaCI0mG8RqXyZZw.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/3000/6000/480x270/AEOm4mdlYZWCo9GB.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/6000/9000/480x270/DN4R2KT6gfCdACrh.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/9000/12000/480x270/atCTyDRFdq0I9JWQ.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/12000/15000/480x270/5AwGszExXRlmwlmD.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/15000/18000/480x270/Qq9t_qPDrmuLQNjN.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/18000/21000/480x270/8AviU-V8vo2GuJTe.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/21000/24000/480x270/jSQu_WPDX28DAOtc.m4s #EXTINF:3.000, /ext_tw_video/1708859564446429185/pu/vid/avc1/24000/27000/480x270/Ng8Ba10HbqG9oLD9.m4s #EXTINF:2.600, /ext_tw_video/1708859564446429185/pu/vid/avc1/27000/29600/480x270/sdqh8euFgH6OtRKe.m4s #EXT-X-ENDLIST
*.mp4
のファイルにアクセスする必要がある*.m4s
ファイルをダウンロードして映像・音声のデータを取り出し再生MP4のファイル(MPEG-4 Part 14)はおおまかに言えば Box という単位のデータの区切りの塊で、データ本体の他にメディアのメタデータなんかを含んでおり、Box同士はツリー構造になって格納されている。
気持ち的にはhtmlのタグの入れ子みたいに認識している(実際XMLでダンプするツールなんかもある)。
まず、最初に BEWYQPCMCXWMJLNs.mp4
の構造を眺めてみる。abema/go-mp4 に含まれる mp4tool というコマンドを使っている。
[ftyp] Size=24 MajorBrand="iso5" MinorVersion=512 CompatibleBrands=[{CompatibleBrand="iso6"}, {CompatibleBrand="mp41"}] [moov] Size=1106 [mvhd] Size=108 ... (use "-full mvhd" to show all) [trak] Size=418 [tkhd] Size=92 ... (use "-full tkhd" to show all) [mdia] Size=318 [mdhd] Size=32 Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=48000 DurationV0=0 Language="und" PreDefined=0 [hdlr] Size=51 Version=0 Flags=0x000000 PreDefined=0 HandlerType="soun" Name="Twitter-vork muxer" [minf] Size=227 [smhd] Size=16 Version=0 Flags=0x000000 Balance=0 [dinf] Size=36 [dref] Size=28 Version=0 Flags=0x000000 EntryCount=1 [url ] Size=12 Version=0 Flags=0x000001 [stbl] Size=167 [stsd] Size=91 Version=0 Flags=0x000000 EntryCount=1 [mp4a] Size=75 DataReferenceIndex=1 EntryVersion=0 ChannelCount=1 SampleSize=16 PreDefined=0 SampleRate=48000 [esds] Size=39 ... (use "-full esds" to show all) [stts] Size=16 Version=0 Flags=0x000000 EntryCount=0 Entries=[] [stsc] Size=16 Version=0 Flags=0x000000 EntryCount=0 Entries=[] [stsz] Size=20 Version=0 Flags=0x000000 SampleSize=0 SampleCount=0 EntrySize=[] [stco] Size=16 Version=0 Flags=0x000000 EntryCount=0 ChunkOffset=[] [trak] Size=500 [tkhd] Size=92 ... (use "-full tkhd" to show all) [mdia] Size=400 [mdhd] Size=32 Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=900000 DurationV0=0 Language="und" PreDefined=0 [hdlr] Size=51 Version=0 Flags=0x000000 PreDefined=0 HandlerType="vide" Name="Twitter-vork muxer" [minf] Size=309 [vmhd] Size=20 Version=0 Flags=0x000001 Graphicsmode=0 Opcolor=[0, 0, 0] [dinf] Size=36 [dref] Size=28 Version=0 Flags=0x000000 EntryCount=1 [url ] Size=12 Version=0 Flags=0x000001 [stbl] Size=245 [stsd] Size=169 Version=0 Flags=0x000000 EntryCount=1 [avc1] Size=153 ... (use "-full avc1" to show all) [avcC] Size=51 ... (use "-full avcC" to show all) [pasp] Size=16 HSpacing=1 VSpacing=1 [stts] Size=16 Version=0 Flags=0x000000 EntryCount=0 Entries=[] [stsc] Size=16 Version=0 Flags=0x000000 EntryCount=0 Entries=[] [stsz] Size=20 Version=0 Flags=0x000000 SampleSize=0 SampleCount=0 EntrySize=[] [stco] Size=16 Version=0 Flags=0x000000 EntryCount=0 ChunkOffset=[] [mvex] Size=72 [trex] Size=32 Version=0 Flags=0x000000 TrackID=1 DefaultSampleDescriptionIndex=1 DefaultSampleDuration=0 DefaultSampleSize=0 DefaultSampleFlags=0x10000000 [trex] Size=32 Version=0 Flags=0x000000 TrackID=2 DefaultSampleDescriptionIndex=1 DefaultSampleDuration=0 DefaultSampleSize=0 DefaultSampleFlags=0x10000
最初にこのファイルを参照しないといけないのは、 ftyp
というBoxでファイル種類を判定する必要があるのと、 moov
というBoxに動画の重要な情報(映像、音声各ストリームのコーデック情報、サンプルレートなど)が含まれるため。一方、ストリーム自体のコンテンツデータは mdat
というBoxにあるのだが、それはこのファイルにはなさそう。なのでファイルとしては小さい。
次にセグメントを一つ落として見てみる。 SjaCI0mG8RqXyZZw.m4s
:
[styp] Size=24 MajorBrand="iso5" MinorVersion=512 CompatibleBrands=[{CompatibleBrand="iso6"}, {CompatibleBrand="mp41"}] [moof] Size=2736 [mfhd] Size=16 Version=0 Flags=0x000000 SequenceNumber=2 [traf] Size=1192 [tfhd] Size=16 Version=0 Flags=0x020000 TrackID=1 [tfdt] Size=20 Version=1 Flags=0x000000 BaseMediaDecodeTimeV1=0 [trun] Size=1148 ... (use "-full trun" to show all) [traf] Size=1520 [tfhd] Size=16 Version=0 Flags=0x020000 TrackID=2 [tfdt] Size=20 Version=1 Flags=0x000000 BaseMediaDecodeTimeV1=0 [trun] Size=1476 ... (use "-full trun" to show all) [mdat] Size=33789 Data=[...] (use "-full mdat" to show all)
styp
、 moof
などのBoxがあり、セグメント単位のメタデータを持っている。ちなみにメタデータから実際の映像・音声のデータへの参照は trun
というBoxに記述されている。
[trun] Size=1148 Version=1 Flags=0x000301 SampleCount=141 DataOffset=2744 Entries=[{SampleDuration=3118 SampleSize=85}, {SampleDuration=1024 SampleSize=85}, {SampleDuration=1024 SampleSize=86}, {SampleDuration=1024 SampleSize=125}, {SampleDuration=1024 SampleSize=135}, ...
DataOffset=2744
というのが、上のデータで言えば moof 2736 bytes + mdatのヘッダ 8 bytes で計算できるとわかる。その後はSampleSize分のデータが順番に並んでいると考えられる。
なお、この辺りのBoxは、FragmentedでないMP4には登場しない。
そしてmdatが小さいとわかる。なので、動画がめちゃ長の場合でも、初回再生や、あるいはシークした場合でも、ちょっとだけ落としてすぐに再生を再開できる仕組みになっている。
言い訳を最後に置いておくと、公開されたファイルにしかアクセスしていないが、若干お行儀の悪い印象を受けるかもしれない。あと僕の理解が間違ってるところがあればこっそり教えてください...。
手作りの温かみのあるバイナリです。
そろそろRustで書くか、と思ってRustで書いた。
bytes というcrateが今回の用途にめちゃくちゃマッチしていた。数値をBig Endianで書き込めればいいので、こういう感じで。
pub fn write(&self, mut out: impl io::Write) -> Result<(), io::Error> { let b = &self.binary_header; let mut buf = bytes::BytesMut::new(); buf.put_slice(&b.ident); buf.put_slice(&b.major_version); buf.put_slice(&b.minor_version); let size_pos = buf.len(); buf.put_i32(-1); buf.put_slice(&b.compiler_name); buf.put_slice(&b.compiler_version); for section in self.sections.iter() { match section.header.ident { rite::markers::irep => { let h = §ion.header; let i = section.body_irep.as_ref().unwrap(); buf.put_slice(&h.ident); //.... buf.put_u16(i.pool.len() as u16); for pi in i.pool.iter() { match pi { rite::PoolItem::Str(s) => { buf.put_u8(0); // IREP_TT_STR // ... } _ => { todo!("unsupported") } } } buf.put_u16(i.syms.len() as u16); for s in i.syms.iter() { buf.put_u16(s.name.len() as u16); buf.put_slice(&s.name); buf.put_u8(0); } for _ in i.children.iter() { // ... } } rite::markers::end => { let h = §ion.header; buf.put_slice(&h.ident); buf.put_i32(h.size); } _ => { unreachable!("unknown section ident") } } } // finally write back total binsize let size = buf.len(); buf[size_pos] = ((size >> 24) & 0xff) as u8; buf[size_pos + 1] = ((size >> 16) & 0xff) as u8; buf[size_pos + 2] = ((size >> 8) & 0xff) as u8; buf[size_pos + 3] = (size & 0xff) as u8; let freeze = buf.freeze(); out.write(&freeze[..])?; out.flush()?; Ok(()) }
あとはサイズの検知の仕方とかをこなれた感じにするかなー。
できたバイナリ、compiler name が MATZ
と違っていてもちゃんと実行できる!
$ xxd /opt/ghq/github.com/udzura/mrbasm/sample.mrb 00000000: 5249 5445 3033 3030 0000 0056 4d41 534d RITE0300...VMASM 00000010: 3030 3030 4952 4550 0000 003a 3033 3030 0000IREP...:0300 00000020: 0000 002e 0001 0004 0000 0000 0000 000a ................ 00000030: 5102 002d 0100 0138 0169 0001 0000 0548 Q..-...8.i.....H 00000040: 656c 6c6f 0000 0100 0470 7574 7300 454e ello.....puts.EN 00000050: 4400 0000 0008 D..... $ ./bin/mruby /opt/ghq/github.com/udzura/mrbasm/sample.mrb Hello
色々ラフですが。一旦上記に置いた。
リポジトリ名の通り、最終的にはmrubyの命令を直接記述できるアセンブリ言語を作ろうとしている。まだ何も設計できていないが、多分こういう感じ。
!binformat mruby 3.2 !begin irep main: !rootirep LOADI_2 R1 (2) MOVE R2 R1 LOADI_1 R3 (1) GT R2 R3 JMPNOT R2 else1 STRING R3 "big" SSEND R2 :puts 1 JMP end1 else1: STRING R3 "small" SSEND R2 :puts 1 end1: RETURN R2 STOP !end !begin irep sub: LOADI_2 R1 (2) MOVE R2 R1 ... !end
ただまあ、このasmは正直やろうとしていることに必要だから作っていて、ゴールでもなんでもない。作りたいのはもうちょっと別のもの。
ということでRubyKaigi お疲れ様です。みなさんと沖縄で会えるといいっすね。