前後カメラの録画プログラムを考える
ドラレコの要件としては、
- 前後カメラの録画
- 録画は最高画質で行う(もしもの時の資料)
- コーディックはH264(データ量が少ないので長い時間記録を残せる)
- 録画データは1分毎に別ファイルにする。
- メモリが一杯になったら、古いファイルから消していく
- 本体とは別のSDカードに記録する。SDカードは差し替え可能であること。
となります。
1. 前後カメラの録画は、カメラ2台用意したのでOK。Raspberry pi Zero 2 W次第ですが、パフォーマンス的にもなんとかなりそうです。
2. 録画は最高画質で行うも、大丈夫かな?ここもRaspberry pi Zero 2 W次第な部分ですね。とりあえずプログラム中で解像度を切り替え出来るようにしときましょう。
3. コーディックはH264は、カメラのハードウェアでH264にしてくれるので、2台のカメラともH264でできそう
4. 録画データは1分毎に別ファイルは、ffmpegの機能で出来ます。
5番目の要件「SDカードのメモリが一杯になったら古いファイル消す」の部分はプログラムを書かないと実現できなさそうです。これが一番大変かも?
でははずは、6. 本体とは別のSDカードに記録するから対応していきましょう。
動画保存用のSDカードをどうするか?
まずは、
6. 本体とは別のSDカードに記録する。SDカードは差し替え可能であること。
を行うために、SDカードリーダーとSDカードを用意します。
ドラレコに使用するSDカードは、消耗品です。常に古いデータを消して新しいデータを書き込み続けるのですが、そもそもSDカードやUSBメモリもですが、これらは書き換え可能回数はきまっていて、書き換えているとそのうち書き込めなくなります。MLCチップを使ったものが書き換え可能回数が多いので、ドラレコにはMLCチップのmicroSDカードを選択します。
万全を期すなら、SDカードは1年ぐらいで交換してあげるのが良いらしいです。
簡単に交換できるように、ラズパイのOSが入っているSDカードとは別に、SDカードを用意することにします。
USBハブにカードリーダーを入れます。製品はコレにしました。
耐久性の高いMLCのSDカードとして、これを選択しました。
USBメモリという手もあるのですが、通常ドラレコはmicroSDを採用しておりSDカードの方が入手しやすいと思うので、SDカードリーダーとSDカードという構成としました。
動画で見るとわかりやすいですが物凄くピッタリ収まってます。
【ドラレコ】microSDリーダーを用意する – YouTube
USBメモリの自動マウント
SDカードが認識されているか確認
sudo blkid /dev/sda1 中略 Device Boot Start End Sectors Size Id Type /dev/sda1 32768 249526271 249493504 119G 7 HPFS/NTFS/exFAT
/dev/sda1 として認識されていますね。
sudo blkid /dev/sda1 /dev/sda1: UUID="9C33-6BBD" BLOCK_SIZE="512" TYPE="exfat"
あとでマウントするためにUUIDとTYPEの情報を使用します。
マウントするフォルダを作成します。
sudo mkdir /mnt/usb1
/etc/fstabを編集します。 UUID=”9C33-6BBD…の1行を追加して、リブートします。自動でマウントされていれば、成功です。
sudo vim /etc/fstab UUID="9C33-6BBD" /mnt/usb1 exfat defaults,noatime,nofail 0 0 #←この行を追加
再起動したらdfコマンドでマウントされたか確認します。
pi@raspberrypi:~ $ df Filesystem 1K-blocks Used Available Use% Mounted on /dev/root 14900440 9836872 4424900 69% / devtmpfs 185492 0 185492 0% /dev tmpfs 218772 0 218772 0% /dev/shm tmpfs 87512 992 86520 2% /run tmpfs 5120 4 5116 1% /run/lock /dev/mmcblk0p1 261108 52102 209006 20% /boot /dev/sda1 124730368 384 124729984 1% /mnt/usb1 tmpfs 43752 0 43752 0% /run/user/1000
ただし、この方法だと、新しいメモリカードを差し替えるたびに、この作業をしないといけません。
なぜなら、UUIDはメモリカード毎に異なるからです。これは不便すぎですね。不採用です。
/etc/fstabを編集して元に戻すことにしました。
UUID=”9C33-6BBD…の1行をコメントアウトしてリブートします。
作成したフォルダも消しておきます。
sudo rmdir /mnt/usb1
usbmountで自動マウント
SDカードを別のものにするたびにfstabを編集するのは不便なので、差し替えて再起動すれば自動マウントしてくれるように構成しましょう。
usbmountというパッケージを使うと手軽に自動マウントできるようになります。
インストール及び自動起動設定
sudo apt install usbmount -y sudo mkdir /etc/systemd/system/systemd-udevd.service.d
設定ファイルをつくます。
sudo vim /etc/systemd/system/systemd-udevd.service.d/00-my-custom-mountflags.conf
[Service] PrivateMounts=no
保存して再起動。dfでマウントされていることを確認します。
パーテーションを2つ持つSDカードだと、usb0,usb1としてそれぞれのパーティションがマウントされているのがわかります。
$ df Filesystem 1K-blocks Used Available Use% Mounted on 省略 /dev/sda1 261108 52108 209000 20% /media/usb0 /dev/sda2 14585536 3782972 10151892 28% /media/usb1 省略
電源をOFFし、SDカードを入れ替えてみます。今度は1パーテーションのみのSDカードに差し替えました。
設定ファイルを確認します。
sudo vim /etc/usbmount/usbmount.conf
FILESYSTEMS= の行を探して、exfatがなければ追記します。
FILESYSTEMS="exfat vfat ext2 ext3 ext4 hfsplu xfs"
FS_MOUNTOPTIONS= の行を探して、次のように設定します。これをしておかないと、いちいちrootにならないと書き込み出来ないためです。
#FS_MOUNTOPTIONS="" FS_MOUNTOPTIONS="-fstype=exfat,iocharset=utf8,codepage=932,uid=pi,gid=pi,umask=000,dmask=000,fmask=011"
保存して再起動します。再起動したらdfで確認します。
pi@raspberrypi:~ $ df Filesystem 1K-blocks Used Available Use% Mounted on 省略 /dev/sda1 124730368 384 124729984 1% /media/usb0
無事ラズパイでSDカードを自動マウント出来るようになりました。
書き込みできるか確認します
pi@raspberrypi:/media/usb0 $ cd /media/usb0 pi@raspberrypi:/media/usb0 $ touch hoge pi@raspberrypi:/media/usb0 $ ls hoge pi@raspberrypi:/media/usb0 $ rm hoge pi@raspberrypi:/media/usb0 $ ls pi@raspberrypi:/media/usb0 $
できました。
プログラム作る。
それでは、本題です。「2台のカメラでH264最高画質で録画し、1分毎に動画ファイルを作る」というプログラムを作っていきます。
ffmpegでファイル分割
次のように設定すると、60秒毎に1ファイルのファイルに分割してくれます。これを使えばファイル分割の要件もクリアですね。
まずは、最高画質 h264 mp4形式で1分ごとに保存します。
ffmpeg -rtbufsize 30M \ -f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 \ -i /dev/video0 -c:v copy \ -f segment -strftime 1 -segment_time 60 \ -segment_format_options movflags=+faststart -segment_format mp4 \ -reset_timestamps 1 \ /media/usb0/front_%Y-%m-%d_%H-%M-%S.mp4
ファイルの頭が途切れる。困った。aviにしてみようか。
最高画質 h264 avi形式で1分ごとに保存します。1280×720
ffmpeg -rtbufsize 300M \ -use_wallclock_as_timestamps 1 \ -f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 \ -i /dev/video2 -c:v h264_v4l2m2m \ -f segment -strftime 1 -segment_time 60 \ -reset_timestamps 1 \ -pix_fmt yuv420p \ /media/usb0/rear_%Y-%m-%d_%H-%M-%S.avi
よしよし、aviは良いみたいです。
h264も試してみる。うまく分割されるかな?
ffmpeg \ -f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 \ -i /dev/video0 -c:v copy \ -f segment -strftime 1 -segment_time 60 \ -reset_timestamps 1 \ /media/usb0/front_%Y-%m-%d_%H-%M-%S.h264
生の画像データをそのまま格納するだけなので、一番安定している。ただ一番再生が大変そう。
mp4はファイルの切替時に崩れる事が多い感じだが、aviは安定している。.h264は再生ソフトがないと再生できない。ファイルフォーマットはaviで決定ですね。
では、お手軽にpythonでスクリプトを組んで行きましょう。
ファイル名はdashcam.pyとします。dashcam=英語でドラレコの事だそうです。ダッシュボードに置くカメラだからかな?
#!/usr/bin/env python3 from usbVideoDevice import UsbVideoDevice # https://smartphone-zine.hatenablog.com/entry/2023/02/25/065957 import subprocess import psutil # フォルダ残量確認 import os # ファイル操作に使う import sys # プログラムを途中で終了させるのに使う from operator import itemgetter # イテラブルから任意の要素を抜き出す import signal # 非同期イベントにハンドラを設定する import threading import time # スリープ用 # データを保存するフォルダ EX) folder = '/media/usb0/' folder = '/media/usb0/' # 保存形式 extension = ".avi" # デスク使用率がこの%を超えたら古いファイルを削除 dsk_usage_ratio = 90.0 # 前方カメラのUSBポート番号と画像サイズ front_cam_port = 1 front_cam_size = '1920x1080' # 後方カメラのUSBポート番号 rear_cam_port = 4 rear_cam_size = '1280x720' video_front_p = None # 録画用のプロセスです video_rear_p = None # 録画用のプロセスです # カメラのVideoポート判定 usbVideoDevice = UsbVideoDevice() video_front = "/dev/video" + str(usbVideoDevice.getId(front_cam_port)) video_rear = "/dev/video" + str(usbVideoDevice.getId(rear_cam_port)) # Ctrl+C or KILL で止められた時の処理 def sig_handler(signum, frame) -> None: print('\n\n\n------------sig_handler()\n\n\n') global video_front_p global video_rear_p if not video_front_p is None: #video_front_p.terminate() video_front_p.kill() if not video_rear_p is None: #video_rear_p.terminate() video_rear_p.kill() sys.exit(1) # プログラムを途中で終了させる # 録画処理 def record(): global video_front_p global video_rear_p video_front_p = subprocess.Popen( ['ffmpeg', '-rtbufsize', '300M', '-f', 'v4l2', '-input_format', 'h264', '-video_size', front_cam_size, '-framerate', '30', '-i', video_front, '-c:v', 'copy', '-f', 'segment', '-strftime', '1', '-segment_time', '60', '-reset_timestamps', '1', folder + '%Y-%m-%d_%H-%M-%S_f' + extension]) video_rear_p = subprocess.Popen( ['ffmpeg', '-rtbufsize', '30M', '-f', 'v4l2', '-input_format', 'h264', '-video_size', rear_cam_size, '-framerate', '30', '-i', video_rear, '-c:v', 'copy', '-f', 'segment', '-strftime', '1', '-segment_time', '60', '-reset_timestamps', '1', folder + '%Y-%m-%d_%H-%M-%S_r' + extension]) def diskfree(): file_list = [] # ディスク使用率を取得 dsk = psutil.disk_usage(folder) print('\n------------usage:' + str(dsk.percent)) if dsk.percent <= dsk_usage_ratio: return # ファイルデータ取得 for file in os.listdir(folder): file_info = os.stat(folder + file) file_list.append([folder + file, file_info.st_mtime, int(file_info.st_size / 1024)]) # 古い順にソート file_list.sort(key=itemgetter(1)) # ファイルの削除 for file in file_list: if dsk.percent > dsk_usage_ratio: print('\n------------[DELETE]' + file[0]) os.remove(file[0]) dsk = psutil.disk_usage(folder) print('\n------------usage:' + str(dsk.percent)) else: break def main(): # スクリプトが止められたらプロセスを停止する signal.signal(signal.SIGTERM, sig_handler) # KILLされたとき signal.signal(signal.SIGINT, sig_handler) # Ctrl+Cされたとき # 録画開始、メインスレッド record() while True: # ディスクが使用率を超えたら古いファイルを削除する thread = threading.Thread(target=diskfree) thread.start() # 暫く待つ time.sleep(60) if __name__ == '__main__': main()
実行可能ファイルにする
chmod 755 dashcam.py
実行してみる
./dashcam.py
さてここでまた問題発生、前後とも1920×1080で同時に撮影すると処理が追いつかず片方のカメラがfpsが5くらいまで落ちてしまう。
仕方ありません。泣く泣く、後方のカメラを1280×720に変更します。これで安定して30fps出ています。流石にラズパイZero2Wの限界でしょうか?USBカメラ2台ともフルHD録画とsdカード保存はゼロには荷が重いようです。
ただ、ラズパイZero2WでH264で前後カメラで録画出来るのは快挙ですよね!
ラズパイ起動時にドラレコの録画を開始する。
スクリプトも完成しましたので、サービス登録して起動時にスクリプトを起動するように設定しましょう。dashcam.serviceという名前のユニットファイルを作成します。
vim dashcam.service
次の内容で保存します。
# dash cam unit file [Unit] Description=dashcam # 保存先USBメディアが使用可能になるのを待つ RequiresMountsFor=/media/usb0 [Service] # Type=simpleはコマンドを実行したタイミングで起動完了と判断します Type=simple User=pi Group=pi # 作業ディレクトリ指定 WorkingDirectory=/home/pi/ # サービスの起動コマンド ExecStart=/home/pi/dashcam.py # Pythonのprint 関数の内容をログに残す設定 Environment=PYTHONUNBUFFERED=1 # 停止完了までに待機する時間 TimeoutStopSec=5 # /bin/sleepを使うと起動時にWaitかけることができる ExecStartPre=/bin/sleep 5 # ログ出力関連 StandardOutput=syslog StandardError=syslog SyslogIdentifier=dashcam [Install] WantedBy = multi-user.target
SyslogIdentifier=dashcam の名前を使って使ってログを設定。
sudo vim /etc/rsyslog.d/dashcam.conf
if $programname == 'dashcam' then { action(type="omfile" file="/var/log/dashcam.log") }
syslog再起動
sudo systemctl restart rsyslog
ユニットファイルは、/etc/systemd/systemに配置します。ここは管理者が変更した設定ファイルが配置されるディレクトリです。
ユニットファイルを配置したらdaemon-reloadで反映し、dashcamサービスの起動・終了のテストを行い、dashcamサービスを自動起動するように設定しましょう。
sudo cp dashcam.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl start dashcam sudo systemctl stop dashcam sudo systemctl enable dashcam
ログを確認してみましょう。
tail -f /var/log/dashcam.log
エラーが出てきます。
ModuleNotFoundError: No module named ‘psutil’
あ、これは多分psutilがsudoで実行出来ないからですね。sudoで実行できるようにインストールします。
sudo pip3 install psutil
これで解決しました。
さて、これでサービスとしてdashcamを実行出来るようになりました。ドラレコ用スクリプトも完成ですね。
おっと、まだスクリプトにバグが有るようです。削除処理ですね。
Feb 27 08:41:51 raspberrypi dashcam[1009]: ------------usage:90.4 Feb 27 08:41:51 raspberrypi dashcam[1014]: frame=1159046 fps= 36 q=-1.0 size=N/A time=08:49:25.13 bitrate=N/A speed= 1x Feb 27 08:41:51 raspberrypi dashcam[1009]: Exception in thread Thread-529: Feb 27 08:41:51 raspberrypi dashcam[1009]: Traceback (most recent call last): Feb 27 08:41:52 raspberrypi dashcam[1009]: File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner Feb 27 08:41:52 raspberrypi dashcam[1009]: self.run() Feb 27 08:41:52 raspberrypi dashcam[1009]: File "/usr/lib/python3.9/threading.py", line 892, in run Feb 27 08:41:52 raspberrypi dashcam[1009]: self._target(*self._args, **self._kwargs) Feb 27 08:41:52 raspberrypi dashcam[1009]: File "/home/pi/dashcam.py", line 88, in diskfree Feb 27 08:41:52 raspberrypi dashcam[1009]: file_info = os.stat(folder + file) Feb 27 08:41:52 raspberrypi dashcam[1009]: FileNotFoundError: [Errno 2] No such file or directory: '/media/usb02023-02-25_16-17-45_r.avi'
フォルダとファイル名の間にスラッシュが抜けていました。(上のdashcam.pyはバグ修正済みです)
今日はここまでです。
長時間稼働試験
丸一日稼働させてみてffmpegが安定稼働するか見ていますが、たまに
Conversion failed!
というエラーでffmpegが終了してしまします。
Google検索すると、-max_muxing_queue_size 512すると良いとか書かれていたので試してみます。
しかしmax_muxing_queue_sizeを付けると負荷が高くなり、FPSが7くらいまで落ちてしまい安定しませんでした。もう少し様子を見て、ダメなら拡張子を.AVIから.H465に変更してみようかと思います。