
2025.03.07
メール対応担当の8割以上が「カスハラ被害」に クレームのハード化・長期化を防ぐ4つの対策
カメラの映像をリアルタイムで送信するための部品をZig言語で書く(Part2)(全1記事)
リンクをコピー
記事をブックマーク
小林哲之氏:tetsu_kobaと申します。今日は、「カメラの映像をリアルタイムで送信するための部品をZig言語で書く(Part2)」というタイトルで、発表したいと思います。
概要です。Zig言語はOSのシステムコールの呼び出しや、C言語で書かれたライブラリを利用するのがとても容易です。Video for Linux 2でカメラの映像をキャプチャし、それをエンコード、デコードして、画面に表示するまでの一連のプログラムをパーツとしてZig言語で書きました。これは5月22日に別の勉強会で話した内容の続きなのでPart2です。
なぜこのようなことをしたかというと、映像の配信に必要な各プロセスをきちんと自分で理解するためです。実験のために再利用しやすいパーツを自分で作ります。将来的には新しいコーデックや通信方式を試したいので、そのための土台を作っておくということになります。要するに車輪の再発明ですが、とにかくZig言語でなにかを作ってみたかったということになります。
「とにかくシンプルに」ということで、今回はいろいろな制約を付けています。まず、映像と音声の同期を今回は行いません。さらに今回は映像だけを扱っています。プロセス間のデータの受け渡しにはパイプを使いました。非常にシンプルなプロセス間通信です。パイプの中には画像のデータのみが流れます。映像のサイズは決め打ちで、コマンドラインから指定するようにしました。
ネットワーク間のデータ送信には、TCP socketを使用しました。LAN環境のみなので、ネットワークがひどく混雑している状況は想定していません。今回はとにかく正常系を優先して実装しています。異常が起きたら基本的にはそこで終了するということにしています。必要に応じてエラーリカバリを追加しています。
前回発表時までに作ったものは(スライドを示して)以下のものです。主に送信側を中心に作りました。
今回は以下のようなものを追加しています。jpegのデコーダをエンハンスして、Motion JPEGに対応しました。受信側に使うものはVP8のデコーダ、それからTCPのソケットをlistenして (待ち受けて)、受けた内容をstdoutに出力する。今回はこれをGo言語で書きました。それからI420のローデータのビューアを、libsdl2を使って作りました。
V4L2 captureは、前回作ったものと同じです。
jpegのデコーダです。USBカメラでは、高解像度・高フレームレートの画像はmjpegのフォーマットになるので、今回このjpegのデコーダでmjpegを扱えるようにしました。これによって1,280×720の画像サイズを扱えるようになっています。
mjpegからjpegを切り出すために、jpegの先頭と最後に付いているマーカーを検出しているわけですが、このコードを書く時にいくつかTipsを見つけたので、(スライドを示して)このブログに書いています。jpegのデコードそのものには、libturbojpegのライブラリを使っています。
Convert to I420。これはさまざまな基本的な映像フォーマットをI420に変換するものです。
VP8用のエンコーダとデコーダを作りました。libvpxを使用しています。なぜVP8を利用したかというと、このエンコード済みのフォーマットがIVFになるからです。IVFはH.264やH.265で使われているNALフォーマットよりもずっとシンプルで扱いやすいです。
AV1もraw video formatとしてIVFを採用しているので、それの足掛かりとして、まずはIVFを使っているVP8でやってみました。エンコーダはVP8専用に作りましたが、デコーダはVP8とVP9の両対応にしています。
次にI420 Viewerです。これはlibsdl2を利用しています。フレームレートの制御はしていないので、データが来たタイミングで映像の更新をしています。画面に表示するためにはRGBへ変換する必要がありますが、変換はlibsdl2の内部に任せてあります。
作ったこれらのパーツを組み合わせて使用します。送信側はLinuxで動かすわけですが、V4L2 captureからmjpegのデータを取り込んでそれをデコードして、その結果をさらにI420の形式に変換します。それをVP8でエンコードしてTCP送信。それぞれのパーツをパイプでつなげています。
同様に受信側はLinux、あるいはmacで動かすようになっています。TCPで受信したものをVP8でデコードして画面に表示する。この3つがつながっています。
具体的に使用したシェルスクリプトです。(スライドを示して)このようにパラメーターをシェル変数に入れて呼んでいて、それぞれの入力・出力はファイルで指定するようになっていますが、/dev/stdin、/dev/stdoutを指定することによって、標準入力から得て標準出力に出すということにして、パイプでつなげていくことができるようになっています。
(スライドを示して)同様に、これは受信側です。前回は受信側を作っていなかったので「ffplay」にやらせていましたが、今回は受信側も自分で作りました。
パイプの効率化ですが、パイプのバッファサイズはLinuxの場合、デフォルトで64KBです。これはビデオデータを送るにはけっこう小さいので、何回もシステムコールを呼ばなければいけません。なのでパイプのバッファサイズの変更をしたり、パイプへの書き込みに対してwriteをシステムコールじゃなくvmspliceを使うことによってコピーの回数を1回減らしています。そういった工夫に関してはブログに書いたので、そちらをご覧ください。
Future workです。まだこれは土台なので、これからいろいろなことをやっていきたいと思っています。まずは各ライブラリをZigのパッケージにしたいと思います。それによってパイプでつなげるのではなく、もっとメモリで受け渡すとか、もっと効率の良いやり方で実用化に近いものに変更していけばいいと思います。最終的にはハードウェアアクセラレータによる動画のエンコードやデコードまでやってみたいと思っています。
まとめです。このように単純ではありますが、最低限の映像の送信と受信を行うことはできました。このレイヤーのソフトウェアを実装するのにZig言語はとても有用だと思います。特にC言語で書かれているCのABIのライブラリを使う上では、非常にスマートに使えるので、いいなと思いました。今日紹介したプログラムのソースコードはGitHubに公開しているので、ご覧ください。ブログにもいろいろなことを書いています。
このあと簡単にですが、デモをしたいと思います。2つの画面がありますが、上のほうが送信側。これはArmの評価ボードのJetson AGX OrinにSSHしているところです。下のほうは、M1 Macのターミナルです。動かすシェルスクリプトは(スライドを示して)このとおりです。
まず、下のレシーブ側から動かします。続いて送信側。こんな感じです。音に比べてそれほど遅延がないことがわかります。
以上です。終わります。ありがとうございました。
2025.03.04
チームが協力しないのはマネジメントの問題 “協働意識”を高めるマネージャーの特徴とは?
2025.03.03
大企業で成功したマネージャーが中小企業で苦戦する理由 “指示待ち”部下を主体的に動かす方法
2025.01.28
適応障害→ニート→起業して1年で年収1,000万円を達成できたわけ “統計のお姉さん”サトマイ氏が語る、予想外の成功をつかめたポイント
2025.03.07
部下へのフィードバックで最初に伝える一言 何度も指摘せずに済むマネジメントの秘訣
2025.03.03
2064年もエンジニアで稼ぎ続けるには Python歴20年のベテランが語る、生成AI時代のキャリア戦略
2025.03.05
「はい、わかりました」と返事をした部下が“かたちだけ動く”理由 主体性を引き出すマネジメントの鍵
2025.02.28
40歳以降の人間関係は「年下」との交流が肝 『移動する人はうまくいく』著者が語る、職場で評価される秘訣
2025.03.05
「一人前のエンジニア」になるために必要なこと 未経験からフルスタックエンジニアへの道筋
2025.01.07
1月から始めたい「日記」を書く習慣 ビジネスパーソンにおすすめな3つの理由
2025.03.04
PMが陥りがちなプロダクト開発の失敗パターン 判断を歪める意思決定バイアスとは