aptpodでは、複数のCANバスのデータを時刻同期して取得できるSynchronized CAN Transceiverに続き、このデバイスと時刻同期し、かつ複数のカメラ映像をフレーム単位で同期させて録画できるカメラデバイスの開発を行なっています。
このデバイスには、TI社製のAM5728というSoC(System on a Chip)の採用を検討しています。このSoCには様々なコアが搭載されており、その中でもマルチメディア処理を担当するIVA(ハードウェアアクセラレータ)は1080p60の映像を処理できる優れものです。
aptpod Advent Calendar 2019の12日目は、このSoCを使って1920x1080 30fpsのH.264エンコードをしてみた話をハードウェアエンジニアの塩出がお届けします。
AM5728について
AM5728はTI社が提供しているSitara™ プロセッサシリーズの1つです。Sitara™ プロセッサは産業用途向けに開発されたチップで、AM5728はマルチメディア処理用のハードウェアアクセラレータが乗ったシリーズになります。
AM5728の搭載機能を把握するには、以下のブロック図を見るのが一番早いと思います。 特徴として以下のようにかなり機能モリモリなSoCです。
- Cortex-A15
- Cortex-M4が2つ
- DSP
- 産業通信用にRPUも使える
- GPU搭載
- ペリフェラルも充実
- 映像出力にも強い
- IVAで1080p60の映像処理性能
- ビデオ信号を受けるVIP(Video Input Port)が複数あり,複数の映像入力に対応
- VPE(Video Processing Engine)と呼ばれる色空間変換 などなど
今回使用したボード
機能評価を行うにあたり、TI社が提供しているAM572x 評価モジュールとこれに接続できるカメラモジュールを使用しました。合わせて8万円程度でした。このパッケージにlinuxイメージが入ったSDカードが同封されており、開封後すぐに機能評価を始められます。
カメラモジュールはリビジョンによって使用されているイメージセンサが異なり、リビジョンA1はMT9T11
、リビジョンC1はOV10635CSP
となっています。今回手元に届いたのはMT9T11
でした。
このカメラモジュールでは解像度によって30fpsまでの出力が可能です。データシートはこちらから参照ください。
どうやってIVAを使うのか?
TIのwikiページに説明があります。ここではかいつまんで説明したいと思います。
前提
SMPモード動作しているA15コアでlinuxが動いています。また、2つあるM4の片方はIPUMM
と呼ばれるTI社が提供しているマルチメディア処理用のファームウェアが動いています。このファームウェアはTI社製のRTOSをベースとしています。linuxとRTOS間の通信はremotoproc
とRPMsg
を使用しています。
IVAにはTI社製のコーデックが乗っているようですが、ソースコードは公開されていないので詳細はわかりません。
IVAとM4間の通信はCodec EngineというTI社が提供しているRTOSパッケージを介して行なっています。Codec Engine
はXDAIS準拠のアルゴリズムを実行するためのAPI群で、おそらくIVAに乗っているコーデックがXDAIS準拠のアルゴリズムになっていると思われます。そのコーデックアルゴリズムをCodec Engine
を通じて実行しているものと考えられます。XDAISに関しては詳しく調査しておりませんので詳細は触れません。
下の図は先ほどのwikiページから抜粋した、マルチメディア処理のソフトウェアスタック説明図です。青色がTI社が提供しているソフトウェアですが、色分けする意味がほとんどないくらいほぼ青色です。マルチメディア処理のフレームワークには,Gstreamerが採用されています。
流れ
- まずGstreamerのプラグインである、
GST-Ducati Plugin
で上流から画像データを受け取る - この画像を処理するためのエンコード指示が発行される
- この時に、inputする画像のメモリとエンコード後のデータ格納先メモリも一緒に指示する
- この指示はlibdceを使用してRPMsg経由で,M4へ指示を送る
- M4はlinuxから送られてきたエンコード指示を受け取り、
Codec Engine
に登録されているIVAのエンコードアルゴリズムを呼ぶ - IVAがエンコードを開始する
- エンコードが終了したらlinuxの処理へ戻り、あらかじめ指示したエンコード後のデータ格納先を参照する
- Gstreamerなので、エンコードされたデータを下流のエレメントにpushする
- 1へ戻る
Gstreamerのパイプラインの例
Gstreamerのプラグインを使えばIVAを使えるということがわかりましたので,1920x1080 30fpsのH.264エンコードをとりあえず動かしてみます。
GST_DEBUG=ducati*:LOG gst-launch-1.0 -e \ v4l2src device=/dev/video1 num-buffers=300 io-mode=4 ! \ 'video/x-raw,format=(string)YUY2,width=(int)1280,height=(int)720,framerate=(fraction)30/1' ! \ queue ! vpe num-input-buffers=8 ! 'video/x-raw,format=(string)NV12,width=(int)1920,height=(int)1080' ! queue! \ ducatih264enc level=level-51 profile=high intra-interval=10 inter-interval=5 ! queue ! h264parse ! filesink location=test.h264
エンコードされたデータはffplay
で再生できますが、am5728には入っていなかったのでffplayが使えるPCにコピーして再生します。
$ ffplay test.h264
実行結果は以下のような感じで、とりあえずのエンコードはできました。
VPEで1280x720 -> 1920x1080に変換しているので引き伸ばされている感があります。
コマンドの説明
v4l2src
まずはカメラモジュールからデータを取得するために、v4l2srcを使っています。/dev/video1
はこのカメラモジュールのことを直接意味してる訳ではなく、VIPと呼ばれるパラレルなビデオ信号を受け取るためのペリフェラルのディスクリプタです。このVIPは結構優れもので、色空間変換やスケーリングなども行えます。
video/x-rawで指定しているフォーマット、サイズがカメラモジュール側で対応していなければ、それに合うようにカメラモジュールからの画像を変換した状態で受け取ってくれます。(もちろん限界がありますが)
重要なのは io-mode
です。ここでは4を指定しています。この4はdmabufを意味しています。これにより、VIPで受け取った画像データをユーザ空間のメモリにコピーすることなく下流にpushするようにしてくれます。
vpe
vpe
はM2Mデバイスで、メモリにある画像データを色空間変換、スケーリングしてくれるペリフェラルです。ここでは、YUY2からNV12へのフォーマット変換とサイズの変換を行なっています。
なお、フォーマット変換をしているのは、IVAのエンコーダがNV12のみに対応しているからです。
ducatih264enc
これがH.264エンコードするプラグインです。profileやlevel、IDRフレームの間隔などを指示しています。1920x1080のフルHD画質をエンコードするために、profileはhigh、levelは51を指定しています。
ここら辺はH.264のフォーマットの話なので、詳しいことは割愛します。
下流はfilesink
でH.264データをそのまま保存するという形です。
本当に1920x1080 60fpsを処理できるのか?
上記のコードを実行すると以下のようなログが得られ、エンコード時間がわかります。 60fpsだとすると16.6msec以内にエンコードが終わっていないといけないことになりますが、1920x1080のエンコード時間はだいたい14~15msec程度で,1フレーム以内に処理が終わっており、1920x1080 60fpsの処理を達成できると思われます。
0:00:00.646824120 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.661454910 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14541811ns (14 ms): 13 bytes 0:00:00.661699724 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb4f0ba10 0:00:00.669780062 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.684446964 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14629488ns (14 ms): 13 bytes 0:00:00.684697797 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb5b1bef8 0:00:00.702904178 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.718422318 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 15482353ns (15 ms): 13 bytes 0:00:00.718666319 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb4f0b830 0:00:00.723180013 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.738440976 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 15225990ns (15 ms): 13 bytes 0:00:00.738673591 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb4f0b8d0 0:00:00.746872188 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.761427988 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14521315ns (14 ms): 13 bytes 0:00:00.761634738 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb4f0b970 0:00:00.783937083 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.798444083 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14469098ns (14 ms): 41589 bytes 0:00:00.799538671 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb4f0b8d0 0:00:00.807385094 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.821465092 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14044862ns (14 ms): 21440 bytes 0:00:00.822195631 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb5b1be58 0:00:00.830828876 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.845438681 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14575971ns (14 ms): 13287 bytes 0:00:00.845915134 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb4f0ba10 0:00:00.864899390 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.879473246 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14534166ns (14 ms): 27112 bytes 0:00:00.880307242 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb5b1bef8 0:00:00.888348052 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.902456842 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14073979ns (14 ms): 27080 bytes 0:00:00.903251797 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb4f0b830 0:00:00.921819950 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00.936442281 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame:<ducatih264enc0> VIDENC2_process took 14588171ns (14 ms): 14010 bytes 0:00:00.936930121 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame:<ducatih264enc0> free buffer: 0xb5b1bef8
終わりに
TI社のAM5728というSoCを使って1920x1080 30fpsの映像を H.264エンコードをしてみました。使用したカメラモジュールが30fpsしか出ないものでしたが、エンコード時間から60fpsでも問題なくエンコードできそうなことがわかりました。
今後はこのSoCを使って取得したデータを弊社フレームワークのintdash
で扱えるようにしたり、並行して製作しているFPGAを搭載したカメラモジュール側と結合したりしていきます。
今回はエンコードしてみたというゆるい内容になってしまいましたが、メモリの動きやTI製のRTOSの使い方、H.264エンコードにメタデータを含める方法、M4とA15の通信など書けそうなことは多々あるので、時間を見つけて書いていきたいと思います。
読んでいただきありがとうございました。aptpod Advent Calendar 2019の12日目担当の塩出でした。