AM5728 上のIVAを使ってH.264エンコードしてみた

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)と呼ばれる色空間変換 などなど

image.png

AM5728商品ページより引用

今回使用したボード

機能評価を行うにあたり、TI社が提供しているAM572x 評価モジュールとこれに接続できるカメラモジュールを使用しました。合わせて8万円程度でした。このパッケージにlinuxイメージが入ったSDカードが同封されており、開封後すぐに機能評価を始められます。 カメラモジュールはリビジョンによって使用されているイメージセンサが異なり、リビジョンA1はMT9T11、リビジョンC1はOV10635CSPとなっています。今回手元に届いたのはMT9T11でした。 このカメラモジュールでは解像度によって30fpsまでの出力が可能です。データシートはこちらから参照ください。

AM572x 評価モジュール

TI社AM572x 評価モジュールの商品ページより

どうやってIVAを使うのか?

TIのwikiページに説明があります。ここではかいつまんで説明したいと思います。

前提

SMPモード動作しているA15コアでlinuxが動いています。また、2つあるM4の片方はIPUMMと呼ばれるTI社が提供しているマルチメディア処理用のファームウェアが動いています。このファームウェアはTI社製のRTOSをベースとしています。linuxとRTOS間の通信はremotoprocRPMsgを使用しています。

IVAにはTI社製のコーデックが乗っているようですが、ソースコードは公開されていないので詳細はわかりません。 IVAとM4間の通信はCodec EngineというTI社が提供しているRTOSパッケージを介して行なっています。Codec EngineはXDAIS準拠のアルゴリズムを実行するためのAPI群で、おそらくIVAに乗っているコーデックがXDAIS準拠のアルゴリズムになっていると思われます。そのコーデックアルゴリズムをCodec Engineを通じて実行しているものと考えられます。XDAISに関しては詳しく調査しておりませんので詳細は触れません。

下の図は先ほどのwikiページから抜粋した、マルチメディア処理のソフトウェアスタック説明図です。青色がTI社が提供しているソフトウェアですが、色分けする意味がほとんどないくらいほぼ青色です。マルチメディア処理のフレームワークには,Gstreamerが採用されています。

image.png

流れ

  1. まずGstreamerのプラグインである、GST-Ducati Pluginで上流から画像データを受け取る
  2. この画像を処理するためのエンコード指示が発行される
    • この時に、inputする画像のメモリとエンコード後のデータ格納先メモリも一緒に指示する
  3. この指示はlibdceを使用してRPMsg経由で,M4へ指示を送る
  4. M4はlinuxから送られてきたエンコード指示を受け取り、Codec Engineに登録されているIVAのエンコードアルゴリズムを呼ぶ
  5. IVAがエンコードを開始する
  6. エンコードが終了したらlinuxの処理へ戻り、あらかじめ指示したエンコード後のデータ格納先を参照する
    • Gstreamerなので、エンコードされたデータを下流のエレメントにpushする
  7. 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に変換しているので引き伸ばされている感があります。

f:id:aptpod_tech-writer:20191209151629p:plain

コマンドの説明
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日目担当の塩出でした。