かずの不定期便ブログ

備忘録代わりに書きます

Arduino向けILI9486 LCDパネルを使ってラズパイで動画再生(ソフト作成編その2)

前回の
Arduino向けILI9486 LCDパネルを使ってラズパイで動画再生(ソフト作成編その1)」
からの続きになります。今回が最終記事です。
spend-carefree.hatenablog.com

目次

動画の表示の仕方

前回、静止画像(bmp)ファイルを表示させましたが、静止画像を連続で表示させて動画に見せる事にします。
一般的な動画コーデックをデコードしながらという事はしません。

動画の準備

まずは、PC(windows)で動画を連番画像へ変換します。連番画像の形式はBMPとします。
連番画像への変換はffmpegを使いました。
また320x480pixへの縮小及び90度回転を行っています。
回転はLCD側で出来るので必須ではありません(Memory Access Controlで制御できます)。
コマンドプロンプトを起動して、動画ファイルの置いてあるdirへcdして、そこにbmpフォルダを作って以下のコマンドでbmpフォルダ配下に連番数字4桁の連番画像(bmp形式)を作成しました。
原画像のアスペクト比が16:9であったため、270x480サイズへ変換しています。動画を再生する際は横置きにした状態で上下に黒帯を付ける予定です。

ffmpeg -i 動画file -vf "transpose=1, scale=270:480" -vcodec bmp bmp\image_%04d.bmp

フレームレートが出ない

前回作成した静止画表示プログラムを改造して、bmpロード→表示をframe数分、繰り返すようにすれば完成になります。
しかし、1枚の表示毎にfopenしてbmpのロードを行うとfopenに時間がかかりすぎるのか、フレームレートが全く出ませんでした。
1枚のロードで44ms~50ms程度もかかっています。表示自体は13ms~14ms程度でした。

動画表示速度の改善 その1

BMPファイル読み込みのボトルネックを解消すべく以下の項目を実行しました。

  1. 連番静止画ファイルの1ファイル化
  2. 4枚分の画像をメモリへ一気にロード

一つ目の「連番静止画ファイルの1ファイル化」ですが、fopenに時間がかかるので、すべてのbmpファイルをまとめて一つのfileにすることとしました。これでfopen自体は一回で済みます。
また少しでもファイルサイズが小さくなるように、ファイルをまとめる際にbmpファイルのヘッダーは削除しました。

2つ目の「4枚分の画像をメモリへ一気にロード」は、ちまちまreadするより、大きなサイズを読み込んだ方が早いので4枚分を一気にロードしています。(のちの実験で2枚の方が早かったです)。

これで4枚分の画像ロードで57~66ms程度(14~17ms/枚)時間がかかり、一枚の画像をRGB888→RGB565変換。LCDへデータを送信作業で21.3ms程度かかりました。つまり一枚当たり17+21.3=38.3msかかるのでフレームレートは27fpsになります。
かなりSDカードのリードにばらつきがあるようです。
目標の30fpsへはもう少し工夫が必要です。

一度に読み込む枚数の違いによる性能確認

一度に読み込む枚数を変えて実行すると以下の様な結果となりました。全フレーム数は2693枚あるので各フレームでのばらつきは平均化されていると思います。

一度に読み込む枚数 fps
1枚 29.12fps
2枚 29.84fps
4枚 27.35fps
8枚 26.83fps

理由は分かりませんが2枚が最も性能がいいです。
ソースコードを以下に示します。
https://github.com/kazuokada/ili9486_disp_movie_st
ソースコードの#define LOAD_PIC_NUMで一度に読み込む枚数を変更できるようにしています。
また、ili9486_ctrl.cpp,fileio.cpp の#define PRINT_RUNTIMEを有効化すると標準出力に実行時間を出すようにしています。

動画表示速度の改善 その2

プログラムの構造は、

  1. SDカードのアクセス
  2. RGB888→RGB565変換
  3. LCDへのデータ書き込み

と3blockに分かれています。
1はcpuパワーはほとんど使ってないと思います。2,3はCPUは全力で動いていると思います。
ラズパイは4cpuあるので、処理を並列化出来れば、早くなりそうです。
1の処理と2,3の処理を別CPUで行うイメージで処理のチャートを書いてみました。

SDカードアクセスとそのほかの処理をパイプライン化

上側のタイミングチャートが、シングルスレッドで書いたプログラムですが、
SDカードアクセスとそのほかの処理を並列化する事で下側のタイミングチャートになり、SDカードアクセスが2,3の処理で隠蔽されて1枚当たりの処理時間が34ms→22msへと改善されます。

マルチスレッド化

先ほどのプログラムを並列処理可能なようにマルチスレッド化します。
SDカードからの画像の読み出しバッファを2面化して"SDカードからリードするバッファ"と"LCD書き込み時のリードバッファ"が競合しないようにします。

SDカードからのリードバッファ 0面 1面 0面 1面 0面 1面
LCDへ書き込む際のリードバッファ - 0面 1面 0面 1面 0面

multi thread化にあたり関数Load_SD_4pic()を一つの引数になる様に関数thread_func_4pic()でラッピングします。Load_SD_4pic()に必要な引数は構造体Load_SD_dataにまとめます。本構造体がthread_func_4pic()の引数になります。
(関数名に4が付いているのは作成当初4枚読み込みだったためです。現在は引数で枚数を指定するようになっています)
処理速度は46.19fpsへと改善されました。一枚当たりの処理時間が21.7msです。
ソースコードは以下になります。
https://github.com/kazuokada/ili9486_disp_movie_mt

まとめ

以下の表へシングルスレッドで一度に読み込む枚数を変えた場合とマルチスレッドにした場合の性能をまとめます。

スレッド 一度に読み込む枚数 fps
Single thread 1枚 29.12fps
Single thread 2枚 29.84fps
Single thread 4枚 27.35fps
Single thread 8枚 26.83fps
multi thread 2枚 46.19fps

公開していい動画ではないので載せられないのが残念です。