はじめに
組み込みLinuxで動画を再生するとき、ハードウェアデコーダーをGStreamerから利用できる環境があります。では、そのデコーダーをChromiumからも利用するにはどうすればよいでしょうか。GStreamerで再生できることと、Chromiumの動画デコード処理から呼び出せることの間には、もう一段の接続が必要です。
その橋渡しをする自由なソフトウェアがv4l-gstです。クリアコードでは、お客様のご要望に応じた業務として、v4l-gstの機能追加、不具合修正、保守を行っています。
v4l-gstは、もともとIgel社によって開発されたソフトウェアです。クリアコードではこれをフォークし、独自に開発・保守を続けています。
改良の出発点となった実装は、V4L2とGStreamerを接続する可能性を示すPoC(概念実証)の段階にあるように見えました。クリアコードでは、この設計を生かしながら不具合修正を重ね、対象環境でプロダクション利用に耐える水準へと整えてきました。
今回は、v4l-gstの仕組みと、実運用に向けた改良例を紹介します1。
V4L2の向こう側をGStreamerにする
Linuxには、動画デバイスなどを扱うV4L2というAPIがあります。動画のデコードでも、圧縮データを入力し、復号した画像を受け取る「メモリー間(mem-to-mem)」のインターフェースを利用できます。Chromiumには、このV4L2を利用する動画デコードの実装があります。
一方、GStreamerでは、パーサーやデコーダーといった処理部品をつないでパイプラインを構成します。ハードウェアデコーダーを利用する部品が提供されていれば、パイプラインに組み込んで使えます。
v4l-gstは、この2つのインターフェースをつなぎます。v4l-utilsに含まれるライブラリーであるlibv4l2のプラグインとして動作し、V4L2の呼び出しをGStreamerの操作に対応付けます。アプリケーション側にはV4L2のメモリー間デバイスのように見えますが、その先ではGStreamerのパイプラインが動きます。
図の灰色の矢印は呼び出しや操作の関係、緑色の矢印はパイプライン内のデータの流れです。描画の経路やプロセスの配置は省略しています。
設定ファイルでは、例えば次のようにパイプラインを指定します。
[H264]
pipeline=h264parse ! omxh264dec
この場合、v4l-gstは前後にappsrcとappsinkを補い、次の構成を作ります。
appsrc → h264parse → omxh264dec → appsink
appsrcは外部からデータを投入する入口、appsinkは処理結果を外部で受け取る出口です。h264parseはH.264の圧縮データを解析し、omxh264decはOpenMAX ILを利用するデコーダーです。後者はgst-omxのエレメントで、利用には対応するOpenMAX IL実装などが必要です。
GStreamerからV4L2デバイスを使う仕組みとは逆に、V4L2を使うアプリケーションからGStreamerを利用するところが特徴です。この橋渡しの基本設計は、オリジナルのv4l-gstに既に備わっていました。
クリアコードでは、実際に利用する上で支障となる様々な不具合を修正してきました。ここからは、そのうち2点を紹介します。
バッファの番号と実体を対応付ける
一点目は、バッファの番号と実体の対応付けに関する修正です。症状としては、映像がときどきカクつくことがありました。発生条件やタイミングが一定せず、原因を特定しづらい不具合でした。
まず、V4L2とGStreamerでバッファをどう管理するかを見てみましょう。
V4L2のデコーダーでは、圧縮データを渡す側をOUTPUT、復号した画像を受け取る側をCAPTUREと呼びます。OUTPUTは画面への出力という意味ではなく、アプリケーションからデバイスへ渡す方向を表します。
アプリケーションは、番号の付いたバッファをQBUFでキューに入れ、処理を終えたバッファをDQBUFで受け取ります。CAPTUREでは、受け取った画像を使い終えた後に再びQBUFし、そのバッファを再利用できるようにします。
GStreamer側では、GstBufferというオブジェクトと参照カウントを使ってバッファの寿命を管理します。デコードは非同期に進むため、QBUFの呼び出しが戻ったからといって、その入力データをすぐ再利用できるわけではありません。v4l-gstは、入力用GstBufferの解放通知やappsinkのコールバックを使い、両者の使用状況を対応付けています。
ここに、もう一つ難しさがあります。GstBufferというオブジェクトと、画像データを保持するメモリーの対応が、常に固定とは限らないことです。
Linuxには、ドライバーなどの間でバッファを共有するDMA-BUFという仕組みがあります。v4l-gstは、GStreamer側のバッファがDMA-BUFを持つ場合、VIDIOC_EXPBUFでそのファイル記述子(FD)をアプリケーションへ渡せます。アプリケーションは、V4L2のバッファ番号と、そこで得たDMA-BUFの関係を使い続けます。
ところが、対応付けを修正したコミットに記載されているように、gst-omxでは、パイプライン内に残っているデータを破棄するflushの後に、FDを閉じずにGstBufferとDMA-BUFの対応が変わることがあります。GstBufferだけを目印にしていると、アプリケーションが覚えている対応と食い違ってしまいます。
図は説明用の模式例です。X・Yは同じDMA-BUFを識別するためのラベルであり、実際のFD番号ではありません。
この問題に対して、export時に内部のFDを記録し、CAPTUREのDQBUF時に現在のGstBufferが参照するFDと照合する処理を加えました。必要に応じて、V4L2の番号とGstBufferの対応を入れ替えます。対象は、アプリケーションが使用権を返したバッファです。
画像をコピーしてつじつまを合わせるのではなく、両側が認識しているバッファの関係を整える修正です。「同じバッファ」という言葉でも、番号、オブジェクト、共有メモリーのどれを指すかを区別する必要があります。
最後の1フレームをどう見分けるか
二点目は、デコードが完了した最後のフレームを正しく返すための修正です。プレーヤーが動画の終端を判別できないため、繰り返し再生が止まったり、再生・一時停止ボタンの状態が切り替わらなかったりしていました。
V4L2のstatefulデコーダーでは、アプリケーションが最後の入力を渡した後、V4L2_DEC_CMD_STOPで残っているデータの処理を要求できます。その時点でデコーダー内にある画像も出力し、最後のCAPTUREバッファにはV4L2_BUF_FLAG_LASTを付けて返す、という流れです。これは、入力を受け付けた時点と、その入力に由来する出力がそろう時点が異なるためです。
GStreamerにも、データの終端を示すEOS(End of Stream)があります。v4l-gstではSTOPを受けたらappsrcへEOSを送り、パイプラインを流れてappsinkへ届くのを待ちます。最後の圧縮データを渡した直後にLASTを付けると、まだデコード中の画像を取りこぼす可能性があります。
そこで、デコード出力を1フレーム保持する処理を加えました。動作を簡略化すると、次のようになります。
| 到着したもの | アプリケーションへ返せるもの | 保留するもの |
|---|---|---|
| フレームA | まだ返さない | A |
| フレームB | A | B |
| EOS | BをLAST付きで返す | なし |
Bが届けばAは最後ではないと分かります。Bを保持したままEOSが届けば、Bが最後だと判断できます。終端を判定するため、デコード出力を1フレーム分保留する設計です。
ただし、フレームの到着、アプリケーションによるDQBUF、EOSの到着は同じタイミングで実行されるとは限りません。キューに残っている数だけを見ると、最後のフレームをEOS到着前に返してしまうおそれがあります。このため、EOS状態とキューの判定に関する競合修正や、EOSまでは最後の1フレームを返さないための修正も行っています。
データの受け渡しに加えて、処理が「終わった」という意味も両側で一致させる必要があるわけです。
Chromiumから利用するための条件
ここまでの仕組みをChromiumから使うには、Chromium側にも対応が必要です。通常のioctlを直接呼ぶ経路ではv4l-gstへ届かないため、デバイスを開く処理やioctl、mmap、closeなどをlibv4l2経由にする必要があります。libv4l2側にも、プラグインへmmapを渡す拡張が必要です。
この接続に用いるChromium向けのパッチは、JeffyCN/meta-rockchipのパッチ群をベースに、クリアコードで対象のChromiumバージョンやv4l-gstとの接続に合わせた調整を加えています。このパッチ群には、Igel社の開発者によるlibv4l2プラグイン対応なども含まれており、複数の開発者の成果を生かしています。
また、v4l-gst自身がハードウェアを駆動するわけではありません。ハードウェアデコードには、対応するGStreamerのデコーダー、ドライバー、ハードウェアなどがそろっている必要があります。Chromiumの対象バージョンやビルド設定にも依存するため、v4l-gstを導入するだけで任意のChromium環境にハードウェアデコードが有効になるとは限りません。DMA-BUFを受け渡せることと、画面への表示までコピーを省けることも別の条件です。
アップストリームへの報告と還元
クリアコードでは、自由なソフトウェアを利用する中で得た知見や修正を、アップストリームへ還元することも重視しています。v4l-gstについては、活動報告を目的としたプルリクエストを送り、フォークでの改良内容を開発元に共有しています。
また、動画再生に関する調査では、MesaのGPUドライバーであるPanfrostにも不具合を見つけました。画像データのレイアウト変換時にバッファの容量が不足する問題で、提出した修正はMesaのアップストリームに取り込まれています。
まとめ
v4l-gstの面白さは、既存のインターフェースを生かして、GStreamerの処理部品をV4L2を使うアプリケーションへ接続するところにあります。その接続を保つには、異なる実装の前提を読み解き、両者の動作を合わせていく作業が欠かせません。
クリアコードでは、お客様のご要望に合わせて、既存の自由なソフトウェアの設計や実装を調査し、実際に利用するために必要な改良と保守を行っています。最近のv4l-gstの開発では、単体テストの追加やデバッグなどにAIも活用しています。
-
本記事のv4l-gstの説明はコミット
a6eaccf44283のコードを基準としています。Chromiumとの接続は、公開されているChromium 132向けのパッチを参照しています。特定環境への導入手順や性能評価を扱う記事ではありません。 ↩

