Slide 1

Slide 1 text

GIFから12倍速くする ImageFluxコース PIXIV SUMMER BOOT CAMP 2022 筑波大学 momeemt / 浅田睦葉 2022.9.28

Slide 2

Slide 2 text

2 自己紹介 ● 筑波大学 情報学群 情報科学類 1年次 ● Nim / FFmpeg ● 趣味: インディーズバンドとお笑い ○ 35.7とダウ90000が好きです ● Twitter: @momeemt momeemt ImageFluxコース

Slide 3

Slide 3 text

3 nontanさん usaさん terryさん hayaさん 配信技術部 メンター サブメンター

Slide 4

Slide 4 text

4 H.264エンコーダを利用して GIFアニメーションをmp4に変換する

Slide 5

Slide 5 text

5 ● GIFとは複数枚の画像をコマ送りにしてアニメーションにできる 画像ファイルフォーマットの こと ● Webブラウザでも標準的にサポート ● ImageFluxもサポートしている GIF? ▲こういうの

Slide 6

Slide 6 text

6 ● H.264が行うフレーム間予測というメチャクチャ賢い圧縮ができない ○ → ファイルサイズが大きくなるので配信コストが高い ● エンコードが遅い ○ どうして... GIFの問題点 地球も泣いている

Slide 7

Slide 7 text

● H.264とは動画圧縮規格の1つ ● 「.mp4」「.mov」「.m4v」などおなじみ(?)の拡張子を持つ動画は H.264コーデックによっ てエンコード(圧縮)されている ● 軽い!速い! 7 H.264?

Slide 8

Slide 8 text

8 ● FFmpegは映像や音声を処理できる伝統のライブラリ ● 膨大なコーデックやファイル形式、メディアをサポートしている ● コマンドラインツールとしては有名だが libavcodec等のAPIを直接叩くことも ○ 今回はこっち FFmpeg

Slide 9

Slide 9 text

9 ● nasm・yasm・libx264・FFmpegをインストール インストールバトル

Slide 10

Slide 10 text

10 ● ビルド時間がえらいことに(15分半) 長すぎ

Slide 11

Slide 11 text

11 ● H.264コーデックを手に入れる ● エンコードに必要な設定を行う ● 書き出し先である映像ストリームを準備する ● フレームをエンコードしてパケットに格納して書き出す H.264エンコーダを呼び出す

Slide 12

Slide 12 text

12 ● cgoはCとGoを相互運用する仕組み ○ FFmpegの各種APIはCで記述されているのでこれを使って呼び出す ○ GoのFFmpegラッパーではうまくいかなかったので途中で使うのをやめた cgo

Slide 13

Slide 13 text

13 あとは根気 ● これはもうC言語

Slide 14

Slide 14 text

14 ● VLCやブラウザで視聴可能なmp4ファイルを出力できた ● QuickTimeではブラックスクリーンになり見れない ○ 気難しい できた mp4になった地球 困る

Slide 15

Slide 15 text

15 ● ImageFlux上でGIFをmp4に変換する ● URLで f=mp4 というパラメータを与えるとmp4が返ってくるようにしたい ImageFluxでmp4変換をサポートする

Slide 16

Slide 16 text

16 ● これまでは書き出すために avio_open 関数を呼んでいた ○ URLを受け取って書き出し先を確立する ● しかし、ImageFluxではHTTPのレスポンスとして書き出す必要があるので、 io.Writer に書 き出さなければならない ● そこでカスタムな AVIOContext を作成して io.Writer を繋ぎ合わせる I/Oを繋ぎ合わせる

Slide 17

Slide 17 text

AVIOContext *allocIOContext(void *bridge) { int bufferSize = 4096; uint8_t *buffer = (uint8_t *)av_malloc(bufferSize); return avio_alloc_context( buffer, bufferSize, 1, bridge, 0, golibmp4BridgeWriterWrite, golibmp4BridgeSeek ); } void freeIOContextBuffer(AVIOContext *ctx) { av_free(ctx->buffer); } I/Oを繋ぎ合わせる C側から関数ポインタを渡す 17

Slide 18

Slide 18 text

● AVIOContextは書き込み関数だけでなくシーク関数を呼んでいる 18 I/Oを繋ぎ合わせる f544a9e55d239e42d74c9a16971e4b234da64441cd7bd 9e17c199b60b09fbe3741fda6110ee2fd16514d88f280b 62c1989204ba2db16d36d650419aeb04e4b5bc762b81 87f91c5d7845… 基本的には最初から順番に書き込んでいくが...

Slide 19

Slide 19 text

● AVIOContextは書き込み関数だけでなくシーク関数を呼んでいる 19 I/Oを繋ぎ合わせる f544a9e55d239e42d74c9a16971e4b234da64441cd7bd 9e17c199b60b09fbe3741cd50…fda6110ee2fd16514d8 8f280b62c1989204ba2db16d36d650419aeb04e4b5bc7 62b8187f91c5d7845 ファイルの途中に巻き戻って書き出すこともある

Slide 20

Slide 20 text

● ImageFluxは io.Writer を受け取る関数が処理の基本単位になっている ○ io.Writer はシークできることが保証されていないのでシークできない ● エンコードする時にシーク可能なバッファに書き込んで、まとめて書き出す ○ メンターのnontanさんが seekbuf を実装してくださった 実装しておきました 20 I/Oを繋ぎ合わせる ありがとうございます🙏

Slide 21

Slide 21 text

21 動いた ~/c!/f=mp4/testdata/images/rotating_earth.gif

Slide 22

Slide 22 text

22 ● 利用しやすいOpenH264に差し替える ● libopenh264をビルドしてインストール(pkg-configファイルの生成) ● libopenh264が生成した共有ライブラリを削除して Ciscoがビルドして頒布している共有ラ イブラリに差し替える ● OpenH264を有効にしてFFmpegをビルド OpenH264への差し替え

Slide 23

Slide 23 text

23 ● GIFよりどれくらい速くなったのか ● GIFエンコードとmp4エンコードのベンチマークを書いて計測する 計測! 1回の実行に561ミリ秒 GIFエンコード

Slide 24

Slide 24 text

● GIFよりどれくらい速くなったのか ● GIFエンコードとmp4エンコードのベンチマークを書いて計測する 24 もう速い mp4エンコード 1回の実行に69ミリ秒 6.3倍

Slide 25

Slide 25 text

25 お得 ファイルサイズも半分少しになっている

Slide 26

Slide 26 text

26 ● しかしメモリ確保を43倍やっているのでもう少し減らしたい 富豪すぎた mp4エンコード 1回の実行に176万回もメモリを確保している

Slide 27

Slide 27 text

27 ● NRGBAをYUV420Pに変換する時にメモリアロケーション回数が跳ね上がっていた エンコード最適化

Slide 28

Slide 28 text

28 ● color.NRGBA 型が color.Color 型にBoxingされる際にメモリアロケーションがされまくっ ていた ○ → image.NRGBA 型にアサーションすることでアロケーションを回避 エンコード最適化

Slide 29

Slide 29 text

● 元のGIFエンコードよりも 12.2倍 高速になった ● また、メモリ確保回数はGIFエンコードと比べて 0.013% になった 29 結果 1回の実行に46ミリ秒 爆速

Slide 30

Slide 30 text

もっと! 30 ※ 最終日はずっとチューニングしていた

Slide 31

Slide 31 text

● 色空間の変換で毎フレーム確保されていたバッファを使い回す ● 元のGIFエンコードよりも 14.8倍 高速になった 31 NRGBA32→YUV420P変換 1回の実行に38ミリ秒

Slide 32

Slide 32 text

● 色空間の変換で23ms、フレームの書き込みで15msかかっている ● どちらかを改善したい 32 速度的なボトルネック 変換部分のありなしで 2桁も変わる

Slide 33

Slide 33 text

● 自前の色空間変換を捨てて libswscale を使ってみる ○ SWS_FAST_BILINEAR フラグで変換するとかなり速かった(約 17ms) ● 元のGIFエンコードよりも 19.1倍 高速になった 33 libswscaleを使う 1回の実行に29ミリ秒

Slide 34

Slide 34 text

● なったが、かなり変換時間にはブレがある ○ 自前の場合は安定して37ms〜42msかかっていた ○ swscaleでは27〜32msかかることは多いが、たまに60msになったりする 34 libswscaleを使う 1回の実行に29ミリ秒

Slide 35

Slide 35 text

35 感想 ● 複数の環境で問題なく動作すること、いずれ本番環境に乗せることを見据えて品質を保 つことなどを強く意識させられた 8日間でした ● FFmpegを延々と触っていられて楽しかったです ○ 実装のためにドキュメントを普段よりじっくりと読めたので解像度がなんとなく上がった 気がします ● シェルが書けないとこんなに辛いんだなと思いました ○ 8日間で一番苦労したのはシェルスクリプトでした

Slide 36

Slide 36 text

36 8日間ありがとうございました!!!
 最後に メンターのnontanさん、人事のkamikoさん、配信技術部のみなさま、
 お世話になりました、ありがとうございました!