もしもの時には音も重要

小型のマイクを取り付けて、音も録音出来るようにしたいと思います。
SPH0645LM4H搭載 I2S MEMSマイクモジュール
このモジュールはモノラルですが、小さいためバイクに収めるのも楽そうです。ドラレコの音声としては事故発生時の記録として衝撃音などが録音されていれば十分と考えていますのでこういった安価で小さなマイクモジュールで十分かなと思います。
ラズパイに配線しましょう。下記に配線とドライバインストール方法が詳しく記載されています。
ドライバのインストールのためカーネルのビルドが必要なようです。インストールスクリプトが用意されているのでそれに従ってインストールを実施します。
インストールが完了したら、後日アップデートしたときに標準カーネルへと戻ってしまわないように、
$ sudo apt-mark hold raspberrypi-kernel $ sudo apt-mark hold firmware*
を実行して、勝手にカーネルがバージョンUPされないようにしておきます。
録画と録音を同時に行う
ffmpegで動画を撮影しつつ、録音する設定にしていきましょう。
このサイト、
I2S Mems microphone & FFMPEG RTP Settings [Solved] – Raspberry Pi Forums
が参考になりました。
最終的に、次のようなコマンドで録画と録音ができました。
ffmpeg \ -use_wallclock_as_timestamps 1 \ -rtbufsize 30M \ -itsoffset -2.01 \ -f alsa -ac 2 -ar 8000 -acodec pcm_s32le -thread_queue_size 256 -i dmic_sv \ -f v4l2 -input_format h264 -video_size 1920x1080 -framerate 30 -thread_queue_size 256 \ -i /dev/video0 -c:v copy \ -acodec aac -ar 8000 -b:a 128k -vol 512 \ -f segment -strftime 1 -segment_time 60 \ -reset_timestamps 1 \ /media/usb0/front_%Y-%m-%d_%H-%M-%S.avi
今までの録画設定にオーディオ関係のオプションを追加しています。
-ac 2
チャンネルは2に設定します。
ボリュームを上げるため、また音声デバイス指定をdmic_svという文字列で行うために、
Raspberry Pi Wiring & Test | Adafruit I2S MEMS Microphone Breakout | Adafruit Learning System
を実施しています。これを実施するとモノラルでもチャンネル設定が2になります。
-vol <n>
ボリュームを設定します。nは256単位で設定します。元のボリュームが少し小さめだったので200%を指定しています。
-vol 256 = 100% -vol 512 = 200% -vol 768 = 300% -vol 1024 = 400% -vol 2048 = 800%
-f <alsa>
音声入力を
-ar <rate>
音声のサンプリングレートを指定します。音質を表します。
-acodec pcm_s32le
入力音声のコーディックを指定します。このマイクはS32LEですので、これを指定します。指定しないとエラーになってしまいます。
-b:a:音声のビットレート(bitrate:audio)
音声のビットレートを指定するコマンドです。音量を何段階で表すかを表します。一般的では-b:a 192k (192kbps)と指定します。
-i dmic_sv
音声デバイスを指定します。普通は、hw:0とか、hw:1という風に割り当てられている番号で指定するのですが、ディスプレイを繋いでいると、HDMIディスプレイは音声出力を持っているためhw:0が割り振られ、マイクモジュールがhw:1になります。ディスプレイを繋いでいないと、マイクモジュールにhw:0が割り振られます。このように、接続デバイス数によって番号が変化するので不便ですので、先の手順で.asoundrcファイル内で付けた名前である、dmic_svを使用して接続します。
なお、ユーザ設定の場合は~/.asoundrcファイルに、システム全体に反映させる場合は/etc/asound.confに設定します。
後方カメラに音声を追加することにしました。現在のスクリプトはこんな感じです。
#!/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',
'-use_wallclock_as_timestamps', '1',
'-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',
'-use_wallclock_as_timestamps', '1',
'-rtbufsize', '30M',
'-itsoffset', '-1.90',
'-f', 'alsa', '-ac', '2', '-ar', '8000', '-acodec', 'pcm_s32le', '-thread_queue_size', '256', '-i', 'dmic_sv',
'-f', 'v4l2', '-input_format', 'h264', '-video_size', rear_cam_size, '-framerate', '30',
'-i', video_rear, '-c:v', 'copy',
'-acodec', 'aac', '-ar', '8000', '-b:a', '128k', '-vol', '512',
'-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()
今日はここまでです。
