ライブ動画変換におけるRust活用事例

saturday06氏(以下、saturday06):ライブ動画変換でのRust言語活用事例についてピクシブ株式会社の茂木が発表させていただきます。よろしくお願いします。

さっそく自己紹介させてください。茂木勇と言います。ピクシブ株式会社でImageFlux事業部という部署でエンジニアをさせていただいております。ソーシャルアカウントはこちらのとおりになってます。

まず導入事例のシステムの紹介をさせていただきます。ピクシブ株式会社が提供するWebサービス、pixiv Sketch。

こちらにはライブ機能というのがあります。クリエイターのみなさまがお絵かきとか3Dモデル作成などの創作活動の様子をライブ動画配信できます。もちろん無料です。

最大4人まで同時配信可能で、特別な配信機材やアプリなども不要でブラウザだけで配信が開始できるのが特徴となります。

そのバックエンドのシステムがこんな感じになっていて。配信者さんがWebRTCでデータを送って、それが巡り巡ってMessagePackでTranscoderというのに入るんですけれども。

今回Rustで組み上げさせていただいたのはこちら。赤いところに挟まったやつです。

MessagePackのデータの中に入っているH.264とかOpusという音楽のフォーマットをトランスコードして配信に適した形式にします。その部分をRustで書きました。

最初にRustで作ると決めたのは、実は自分の前任者の方でした。当時の担当者にRustを採用した理由を聞いたら「ノリで採用しました」とおっしゃっていました。

なのでどうやってRustをねじ込んだか? みたいなことに興味がある方が多いって聞いてきたんですけれども、ちょっとそういうソーシャルエンジニアリング的な話はほかの発表にお願いしたいんです(笑)。

ただ事情があってRustのコードは1から書き直す必要があったので、このタイミングでプログラミング言語の選定を再度行うことになりました。

選定、採用の経緯がこちらです。

今回、とにかく多くのCのネイティブライブラリに依存する開発というのがすごくわかっていたんですね。1番依存しているのがNVIDIAのVideo Codec SDK、こちらでH.264のトランスコードなどを行うことになりました。

あとは音声のlibopus、あとmp3のLAMEですね。あとはffmpegなどのライブラリ、その他もろもろ、大量に依存することになります。

これらを使うときにプログラミング言語の選定として優先度が高いのは、ともかくネイティブライブラリの関数をまず呼び出せないといけない。次に、できればネイティブライブラリの関数を呼び出すときに呼び出しの定義のコードを自分で書く必要がない。

.NETとかRubyのdlを使ったことがある方はわかると思いますが、Cのヘッダファイルの内容をその言語に読み替えて定義しないとうまくCのコードが呼び出せないということがよくあります。そういうのはできればやめたいです。

あとは、できればネイティブライブラリからのデータをそのまま受け取れたり、そのまま渡せたりみたいなことができるといいです。

そこで、プログラム言語が最終的に3つに絞られました。

まず、C++なんですが、「社内のC++プロダクトのメンテが辛い」「Rustで書き直したい」という話がけっこう出てました。これはダメだなということで見送りました。

あとはGoですね。Goのcgoを使って、Cの関数はいきなり呼べたりするんですが、変数のやりとり、とくにポインタのやりとりが非常に厳しい。GoのポインタのポインタをC側に渡すことはできないとか、そういう制限が非常に厳しくて見送りました。

Rustです。Rustはbindgenというrust-lang-nurseryが作っているcrateを使うことによってCの関数をいきなり呼べるようになるんですね。

あと、Cとの変数のやりとりのとき、ヒープ、スタックみたいなそういう常識がそのまま通用してすごく楽ですね。

使ってみてよかったところ

実際に使ってみてのよかったところの感想なんですけども。とくにネイティブ開発用のエコシステムがそのまま使える。valgrindによるメモリーエラー検知ができる。gdb、lldbによるデバッガのアタッチ、デタッチができる。

straceで余計な出力がない。あとコアを吐いて、そのコアのスタックがCとRustでまぜこぜで取れるみたいな。すごくよかったですね。

あとはC++のあれがほしいというときにだいたいあるということと。社内の人にアピールするときに「Rubyのあれが欲しいというときにだいたいありますよ」っていう。Scalaのアピールをしたときもそんな感じでした。

難しかったところ、はまったところっていうのもやっぱりありました。所有権とかライフタイムがきつかったです。僕はともかく全部RefCellで書いてみて、削れるよねってなったらやめる。そこでNLLですね。non-lexical lifetimeが来たらちょっと楽になるのかなと思ってます。

futures-rsが厳しいというのもあって、これもasync/awaitが来るまで我慢になります。

この2つはみなさんはまってると思います。今回の開発で特有のはまり方をしたのは、3つ目と4つ目ですね。

moveされうる変数のポインタという話と、bindgenがきついという話。この2つがとくに今回話せることかなと思います。

ポインタではまった件。まずデータがあるかもしれない、ないかもしれないっていう変数を作ります。このコードはデータがある場合。

そのデータをCのネイティブライブラリに渡したい。そういうときにポインタを取る必要があるんですね。下のmay_data_ptrという変数にそのポインタを入れたいという事例がありました。

こんなコードを書いたんですよ。

どこがやばいかわかります? 僕はわかりませんでした。これ、3日くらいはまりましたね~。

なんとmay_data_ptrには無効なアドレスが入ってるんですよ。

Optionのmapって中身のデータをmoveするんですよ。なのでクロージャ内のスタックにデータが移って、そのデータのポインタを取っている。外側の変数へ代入する頃にはそのクロージャは終了しているので、そのクロージャ内のスタックに置かれたデータへのポインタはもう無効なんですね。

再現コードはこんな感じで。

さっきのコードと同じことを書いてます。これをcargo runします。僕はprintln!で0 1 2って出てほしいんですよ。1番目のデータが1番下で出てほしかった。でもなんと、3 4 5が出るんですね。というはまり方をしました。

あとはbindgenというCのヘッダファイルを読んでRustのバインディングを出すやつなんですけれども。

細かいところがあって、3行目のCreateWindowWとか見たことある人いるかもしれないんですけども。こういうプリプロセッサでマクロ定義されているものはさすがにRustから呼べないので、そこはちょっとはまりましたね。

あとはCのenumの型がなんか変とか。あと既存のバインディングがあんまり品質が良くなくて、今回mp3のエンコードにlame-sysとか使ったんですけどあんまりメンテされてなかったりでけっこう厳しかったですね。

使い方は簡単なのでチュートリアル見ながら自分で書いたほうがいい場合が多いという知見を得ました。

Rustを導入してよかったこと

まとめになります。ライブ動画変換でRustを導入してみてよかったところは、C/C++のライブラリと相互運用性がすごくよかった。言語としてあれが欲しい、これが欲しいというのはあんまりなかった。

悪かったのは、やっぱり先ほど説明させていただいた変数がmoveするときがあるためポインタの扱いはちょっと危険かな。あと所有権とかfutures-rsの学習コストが高いというのはありました。

そして、ここからが本題です。ピクシブ株式会社では一緒に働いてくれる仲間を募集しております!

とくにRustを使いたいなら、画像配信サービスImageFluxエンジニアで応募お願いします。

開発中のImageFlux Live streamingでさっきと同じものが動いています。

もちろん新卒採用もやっています!  新卒って書いてあるんですけども第二新卒、あるいは義務教育が終わっていればオッケーです。

以上になります。ご静聴ありがとうございました。

(会場拍手)

司会者:ありがとうございました。土曜日さんに盛大な拍手をお送りください。ありがとうございました。

(会場拍手)