やりたいこと
ちょっとしたサイネージのように延々と動画を流していたい。
あるいは、監視カメラの映像をRTSPで取得して、それを小型モニタに映しておきたい。
みたいなこと。
結果として、こんな感じなことができる。
けど、「普通のモニタとして使いたい」なら次に試した(2)のやり方の方が良い。

用意したもの
NanoPi NEO
3.2 インチ LCD:ILI9341 SPI接続 320×240
配線
SPI:4本 その他制御用:2本 電源:2本 LEDバックライト:1本
とりあえず、この9本配線されてればOK(削ろうと思えば削れるけど…)
そして、DC と RESET がクロスしてるのは謎…
いや、RESET を一番若い番号にしておきたかった、という思いがあっただけで、今にして思えばどっちでもいいじゃん…という感じなので、これは敢えてクロスさせる必要はない。
が、まっすぐに繋げたなら、Python 側もそれぞれに指示しているピン番号を入れ替えるのを忘れないように。

NanoPi NEO の準備
まずは SPI の有効化。
$ sudo vi /boot/armbianEnv.txt
として設定ファイルを書き換える。
次の2行を末尾に付け加えて、保存し、再起動。
overlays=spi-spidev
param_spidev_spi_bus=0
※実は、I2Cと同時に使おうと、 overlays=spi-spidev i2c0 としたら SPI が動かない…
何やってもダメなので、とりあえず、SPI 一本に絞って使うことにした。
…これに気づくのに結構かかったのよね…たぶん2,3時間は無駄にしたw
なにせ /dev/spidev0.0 がちゃんと出てくるので NanoPi NEO の GPIO ピンか LCD がおかしいのでは?と。
MOSI からちゃんと信号出てるか、MISO と直結してループバックテストやってみたりしたw
結論、I2Cが有効だと何やっても MOSI から信号が出ない。/dev/spidev0.0 がいても。
で、
$ sudo reboot
として再起動後、SPIがちゃんと有効化されて起動してきたかを確認する。
$ ls -al /dev/spidev*
crw------- 1 root root 153, 0 6月 20 00:04 /dev/spidev0.0
$ lsmod | grep spi
spidev 20480 0
こんな感じで表示されればOK
続いて、Python の仮想環境をホームディレクトリ配下に作る。(ホームじゃなくてもお好きなところで良いかと)
$ cd ~
$ mkdir lcd_con
$ python3 -m venv ./lcd_con
$ cd lcd_con
$ source ./bin/activate
続いてライブラリの導入。
あ、ついでに pip も更新しておく。
(lcd_con)$ sudo apt update
(lcd_con)$ ./bin/pip install --upgrade pip
(lcd_con)$ ./bin/pip install spidev OPi.GPIO
また、NanoPi NEO の GPIO をどうやって使えるようにするかは別記事にてまとめてある。

準備完了。あとはコードを書いていくだけ。
ffmpeg で映像を流し込む
非力な NanoPi NEO に ffmpeg でエンコさせるなんて…サディスティック…
けどやってみた。
import OPi.GPIO as GPIO
import spidev, time, subprocess
DC = 18
RST = 16
GPIO.setmode(GPIO.CUSTOM)
GPIO.setup(DC, GPIO.OUT)
GPIO.setup(RST, GPIO.OUT)
spi = spidev.SpiDev()
spi.open(0, 0)
spi.mode = 0
spi.max_speed_hz = int(32 * 1000 * 1000)
def cmd(c):
GPIO.output(DC, 0)
spi.writebytes([c])
def cmd_with_data(c, data_list):
GPIO.output(DC, 0)
spi.writebytes([c])
GPIO.output(DC, 1)
spi.writebytes(data_list)
# LCD初期化
def init_lcd():
GPIO.output(RST, 0)
time.sleep(0.05)
GPIO.output(RST, 1)
time.sleep(0.05)
cmd(0x11)
time.sleep(0.12)
# 画面転送方向を横向き(320x240)に変更
cmd_with_data(0x36, [0x28])
time.sleep(0.01)
cmd_with_data(0x2A, [0x00, 0x00, 0x01, 0x3F])
cmd_with_data(0x2B, [0x00, 0x00, 0x00, 0xEF])
time.sleep(0.01)
cmd(0x29)
time.sleep(0.02)
init_lcd()
# --- 配信・画面設定 ---
RTSP_URL = "rtsp://[ID]:[PW]@[Camera IP]/"
WIDTH = 320
HEIGHT = 240
FRAME_RATE = 10
FRAME_SIZE = WIDTH * HEIGHT * 3
# ffmpegのコマンド作成
ffmpeg_cmd = [
'ffmpeg',
'-fflags', 'nobuffer',
'-rtsp_transport', 'tcp',
'-r', f'{FRAME_RATE}',
'-i', RTSP_URL,
'-vf', f'scale={WIDTH}:{HEIGHT}:flags=neighbor',
'-f', 'rawvideo',
'-pix_fmt', 'rgb24',
'-an', '-sn',
'-loglevel', 'quiet',
'-'
]
proc = subprocess.Popen(ffmpeg_cmd, stdout=subprocess.PIPE, bufsize=FRAME_SIZE)
try:
while True:
t_start = time.time()
frame_data = proc.stdout.read(FRAME_SIZE)
if len(frame_data) < FRAME_SIZE:
break
# LCDへ書き込み
cmd(0x2C)
GPIO.output(DC, 1)
spi.writebytes2(frame_data)
# ウェイト
t_elasped = time.time() - t_start
t_sleep = 1.0 / FRAME_RATE - t_elasped
if t_sleep > 0:
time.sleep(t_sleep)
except KeyboardInterrupt:
print("\n終了")
finally:
if proc.stdout:
proc.stdout.close()
proc.terminate()
proc.wait()
spi.close()
GPIO.cleanup()
まぁ 30fps とか 20fps なんてのは到底無理よね。
せいぜい 5~10fps くらいかねぇ…
とはいえ、目的は達した。
3000円の NanoPi NEO に、2000円の LCD、それだけで超ポータブルな RTSP ビューアができた。
映像ソースはローカルの MP4 な動画でもいいし、ネットワークに繋がっていたらそのストレージにある物でもいいし、あとはモバイルバッテリーでどれだけ動くか、かな。
この記事にコメントしてみる