hls.js の tsdemuxer.js のコードを読む

個人用メモ。

hls.jsがやっていることをかなり大雑把に書いておくと、JavaScriptでプレイリスト(.m3u8)を取得・パースし、プレイリストに書かれたセグメントファイル(.ts、MPEG2-TS)を取得・パースし、画像と音声のデータ(コーデックは MPEG-4 AVCだったり、MPEG-4 AAC)を取り出して、ごにょごにょした後(ぼかし)にMediaSourceExtensionを使ってvideoタグで再生する、といったかんじ。
JavaScriptで画像・音声データのバイナリをパースしたり操作する処理はヘビィなので、UIスレッドではなく、WebWorkerで別スレッドでおこなうようにしている。
MediaSourceExtensionsが何者か、どういうふうに使うかは、Media Source Extensionsを使ってみた (WebM編) - Qiita が分かりやすい。

今日は、上の「セグメントファイル(.ts、MPEG2-TS)を取得・パースし」のパースの部分を読んでみた。

おもむろにコードを読んでも意味不明なので、デジタル映像の「アーカイブ&デリバリー」に関する技術情報サイト|mpeg.co.jp > VIDEO-ITを取り巻く市場と技術MPEG-2システム - Wikipedia を読んで、簡単にMPEG2-TSがどういったデータ形式になっているか、要素の意味を事前に理解した。(本当はちゃんとした規格のドキュメントを読んだほうがいいんだろうけど、厳密に書かれている分細部に囚われてしまうので、取っ掛かりとしてはこれでいいんじゃないかな...)

セグメントファイルのパースは、 tsdemuxer.js でおこなっている。ひとつの入力(パケットの並び)を複数の出力に分離しているので、demuxer。

セグメントファイルは、188バイト固定長のトランスポートパケットがつながったもの。それぞれのトランスポートパケットに、分割された音声・画像データやプログラム(チャンネル)の情報が含まれている。トランスポートパケットをループで順になめているのが hls.js/tsdemuxer.js at 9ccbc9bc7f629a693fa77863e91d4abd59c1a4da · video-dev/hls.js · GitHub 。次の行で同期ワード( 0x47 )をみて、トランスポートパケットの先頭であるかチェックしている。

トランスポートパケットの識別

PIDは、パケットが扱っているデータが画像か音声かプログラムに関するものか、識別するためのもの( hls.js/tsdemuxer.js at 9ccbc9bc7f629a693fa77863e91d4abd59c1a4da · video-dev/hls.js · GitHub)。

パケットは(おそらく)以下のPAT, PMT, 音声パケット or 画像パケットの順にパースしていく。

PIDが0で固定なのが、PAT。プログラム(チャンネル)の一覧を格納しているもの。hls.jsの場合はひとつで十分なので、parsePAT 関数が返した値を PMT のIDとしている(PMTについては以下で説明)。(hls.js/tsdemuxer.js at 9ccbc9bc7f629a693fa77863e91d4abd59c1a4da · video-dev/hls.js · GitHub)

PMTは、特定のプログラムに含まれる画像や音声それぞれのPIDを格納しているもの。hls.jsでは、それをパースして、画像のPIDは、変数 avcId、音声のPIDは 変数 aacId に代入している。( hls.js/tsdemuxer.js at 9ccbc9bc7f629a693fa77863e91d4abd59c1a4da · video-dev/hls.js · GitHub)

あとは後続のパケットで、PIDが avcId、aacId と一致するか調べて、PES(符号化された画像・音声データを分割してパケット化したやつ)のパースをおこなっている(ぼかし)。

識別に関してはこれくらい。最近、この手のバイナリをパースするJavaScriptのコードを読む機会が多いんですが、大分慣れてきた。次回は、PESのパースまわりを読んでみようと思います。

色々なプロジェクトの utils のコードを読む

最近、時間のあるときに Vue.js や hls.js のソースコードを読んでいるのだけど、この2つのプロジェクトには utils というディレクトリがあって、ロガーやポリフィルのコードが置かれている(utils って名前はどうなんだ、とか、そこに色々置かれているのはどうなんだ、とかそういう話はちょっとスルー)。

これらのプロジェクトに限らず、ある程度大きい規模のプロジェクトは、汎用的な関数がなにかしらの形でまとめられている。これらの関数は大きさも程よく、なにより自分のプロジェクトの参考にもなる。特にコードリーディングしたいものがない場合は、通勤するときとか、土日ベッドでごろごろしているときに普段自分が使っているライブラリの utils のコードを読むと良いんでないかな、と思ったのでした。

(自分は、 hls.js のロガーVue.js のhtmlパーサーを面白く読むことができた)

アルキメデスの大戦 を3巻まで読んだ

読んだ。

昭和に入り、大戦の足音が後ろから近づいてきた頃の戦艦主戦論 vs 航空主戦論の争いを題材にした作品。主人公は数学の天才で、図体がでかいだけであまり役に立たない(と航空主戦論者が思っている)巨大戦艦の製造を阻止するために、数学的な根拠で鋭く切り込む。

所々「???」と思うところもあるのだけど、テンポの良さとブッコミ発言で総じて面白い作品。

アンゴルモア 元寇合戦記(6) を読んだ

読んだ (以下、ネタバレっぽいので注意)

これまでミスすることがなかった迅三郎が初の失点? と、見せかけて、次巻では、気づいていないフリをしていましたー、みたいなかんじになりそうなのが、この男の食えない所。

console.timeStampのメモ

Console APIにtimeStampというのがあって、実行すると、DevToolsのTimelineパネルにイベントを記録してくれる。

console.timeStamp('foo');

分かりづらいが、オレンジのバーが出ているのが、console.timeStamp が実行されたとき。

gyazo.com

Event Logにも記録される。

gyazo.com

パフォーマンス上のボトルネックになっている可能性のあるイベントを特定するために、JavaScriptのコードのどの処理がきっかけになっているか探すときに便利。JavaScriptの関数呼び出しもFunction CallとしてEvent Logに記録されているが、複雑に入り組んだ関数を扱うときもあるので、やはりこういう「目印」は付けることができたらうれしい。

複雑に入り組んだ関数、と書いて思ったのは、メンテンナスのし易さに加えて、パフォーマンスの分析のし易さ、という視点で普段からコードを書く、整理する、ということの大切さ。この視点がないと、いざ、分析しようとするとなかなかに苦労する。

複数のgitリポジトリを履歴を残したまま統合する

引き継いだプロジェクトが、foo_pc, foo_sp, foo_commonみたいなかんじでリポジトリが分かれていて、同じ機能の開発やっているのにそれぞれにPullReqだしたり、リリースノートを書いたりするのがしんどいので、統合した。以下に統合した時の手順をまとめておく。

まず、新しくリポジトリを用意して、以下のように統合したいリポジトリ毎にディレクトリを作成して(.gitkeepとか用意して)、コミットする。

foo
├── foo_common
├── foo_pc
└── foo_sp

次のようなスクリプトを実行する。git 2.9 から無関係なヒストリもってるブランチ同士をマージするときは --allow-unrelated-histories つけないとエラーになるのがハマりどころ。

for repo in foo_pc foo_sp foo_common; do
git remote add ${repo} ~/${repo}
git merge -s ours --no-commit --allow-unrelated-histories ${repo}/master
git read-tree --prefix=${repo} -u ${repo}/master
git commit -am "Merge in ${repo}."
done

webpagetest-api を使おうとしたときのメモ

個人用メモ。

ウェブサイトのパフォーマンス計測ができる WebPagetest - Website Performance and Optimization Test というサイトがあって、Speed Indexという独自の指標( アルゴリズムwebpagetest-doc-ja/index.md at master · t32k/webpagetest-doc-ja · GitHub が詳しい )や、その他各種計測値・統計値を出してくれる。

最近、関わっている案件のひとつで、継続的なウェブサイトのパフォーマンスの改善に取り組んでいて、WebPagetestが提供しているREST APIを定期的に叩きたくなった。webpagetest-apiというAPIラッパー・コマンドがnpmパッケージがあるので、手軽に始めるにはそれを使うとよさそう。

と、思っていたんだけど、数週間前にWebPagetestがhttpsに移行した影響でエラーになったり(既にPullReqは出ているがスルーされている。が、コマンドのオプションでFQDNを変更できる)、計測のAPIを呼び出すときにAPIキーが必須になっていたり(キーはここから取得できる WebPagetest - Get API Key)、ドキュメントが古くて、ちょいちょいハマった。

いまいま、コマンドで試す場合はこんなかんじになる。

webpagetest test --server='https://www.webpagetest.org' --key='YOUR_API_KEY' 'https://twitter.com/kitak'