(IoT共通)ネットワークカメラをRTSPで受信し、動体検知の前後20秒分の動画を残す

# ■概略
### ◯RTSPサーバとRTSPクライアント(ffmpeg)
最近のネットワークカメラはRTSPサーバになれる
※今回はPanasonic WV-S1536Lを使用
Linux(MA-X300を使用)でffmpegを使えばRTSPクライアントになれる
これでカメラ画像をMP4で記録することができる

### ◯ネットワークカメラの動体検知
最近のネットワークカメラには動体検知機能がある
　※画像内で動きが発生したら通知してくれる
1)通知をLinuxにRESTで通知する
2)MP4を通知の前後10秒の合計20秒の動画にして保存する
これで動体検知した画像だけを残すことができる

# ■カメラ側
### ◯概略とカメラ設定
1)ネットワークカメラをRTSPサーバにして、MA-X300で常時受信する  
　受信したmp4は/ramdiskに10秒ごとに別ファイルで保存する
　/ramdiskの容量は少ないので10分経過で削除する
2)RESTサーバで動体検知通知を受信する
　10秒後に最新3のmp4を1つのmp4にして/mp4に保存する  

### ◯認証方法
rtspをID/PW認証する場合は以下のようにURLにID/PWを記載する  
```
rtsp://{ネットワークカメラのIP}:554/Src/MediaInput/stream_1
　↓
rtsp://{ID}:{パスワード}@{ネットワークカメラのIP}:554/Src/MediaInput/stream_1
```
しかしMA-X300のUbuntu22.04(ARM64)のffmpegではID/PWの指定をするとエラーになる  
そのためネットワークカメラはID/PW認証を止め、IP認証にする  

ネットワークカメラのWebI/Fにログインし、
```
ユーザ管理-ユーザ認証をoff
ユーザ管理-ホスト認証をonにして必要なIP(ここではMA-X300のIP)を追加
```

### ◯カメラ設定
歯車マークの[設定]-[詳細設定]-[カメラの詳細設定]-[映像/音声]-[映像]  
以下変更点と重要点のみ
<table border=1 cellpadding="0" cellspacing="0">          
<tr><td>初期表示ストリーム</td><td>ストリーム(1)</td></tr>
<tr><td>JPEG画像更新速度(動画時)</td><td>5fps</td></tr>
<tr><td>ストリーム配信</td><td>On</td></tr>
<tr><td>JPEG(1) 解像度</td><td>1280x720</td></tr>
<tr><td>ストリーム配信</td><td>◎ON</td></tr>
<tr><td>圧縮方式</td><td>H.264<BR>※H.265だとWindows Media Playerで再生<BR>　できない</td></tr>
<tr><td>解像度</td><td>1280x720<BR>※FD。FullHD(1920x1024)・15fpsにするかも</td></tr>
<tr><td>配信モード</td><td>ベストエフォート配信</td></tr>
<tr><td>フレームレート</td><td>5fps<BR>※6.1MB/分 ramdiskに保存できるように<BR>　容量は少なくする</td></tr>
<tr><td>1クライアントあたりのビットレート</td><td>1024kbps</td></tr>
</table>

　
### ◯カメラ動体検知設定
歯車マークの[設定]-[詳細設定]-[カメラの詳細設定]-[アラーム]-[動作検知エリア]  
　[全領域]をクリックする  
歯車マークの[設定]-[詳細設定]-[カメラの詳細設定]-[アラーム]-[通知]-[HTTPアラーム通知]  
通知先1 : http://{ネットワークカメラのIP}
　アラーム(左のチェックボックス) : x  
　通知データ  : /notice 
　※http://{ネットワークカメラのIP}/api/v1/notice にRESTサーバを用意する

　
# ■MA-X300側
### ◯処理の流れ
#### 1) カメラからRTSP受信常駐プログラム
プログラム名 : restserver_local/batch/systemd_rtspRecording1.py  
ffmpegコマンドでeth1のLAN側にあるカメラにRTSP接続し  
10秒毎のmp4ファイルを/ramdiskに  
{カメラ名}_yyyymmdd_HHMMSS.mp4の形式で作成しつづけている  
MA-X300が複数台のカメラを受信する場合複数の常駐プログラムが必要  

##### ◯ffmpeg他インストール
```
# apt install -y ffmpeg
# apt install -y python3-venv
# cd /var/www
# python3 -m venv python3-venv
# source /var/www/python3-venv/bin/activate
(python3-venv) root@netcam:~# pip3 install requests
```

##### ◯ffmpeg常駐スクリプト
MA-X300上のffmpegで以下のオプションで受信ができる  
-c:v copy を指定しないと再度エンコードを行おうとしてCPU稼働率が激上がりする  

pythonの設定はRESTサーバもcronもsystemdも全てconfg.pyで設定する  

/var/www/ismscam/batch/config.py  
```
device_name  = 'emboma-matsuyama'
camera1_url  = 'rtsp://{ネットワークカメラのIP}:554/Src/MediaInput/stream_1'
camera1_name = 'camera1'
```

/var/www/ismscam/batch/systemd_rtspRecording1.py
```
import config
…
cmd = 'ffmpeg -rtsp_transport tcp -i "{config.camera1_url}" \
    -f segment -segment_time "10" -segment_format_options movflags=+faststart \
    -c:v copy \
    -reset_timestamps "1" -strftime "1" /ramdisk/{config.camera1_name}_%Y%m%d_%H%M%S.mp4'
…
```
 
##### ○ffmepg起動スクリプト
/etc/systemd/system
```
[Unit]
Description=RTSP Recording 1
After=network.target

[Service]
Type=simple
PIDFile=/var/run/systemd_rtspRecording1.pid
RemainAfterExit=no
Restart=on-failure
RestartSec=10
ExecStart=/var/www/ismscam/batch/systemd_rtspRecording1.py

[Install]
WantedBy=multi-user.target
```
```
# systemctl enable rtsprecording1
# systemctl start rtsprecording1
```

/ramdiskに10秒ごとにmp4が書き出されれば成功  

#### 2) 10分経った/ramdiskの古いmp4を削除するcronプログラム
プログラム名 : restserver_local/batch/cron_rmOldMP4_1.bash  
cronで1分毎に起動し、10分経過した特定のカメラが受信したmp4を削除している  
複数台のカメラを受信する場合は複数の削除プログラムが必要   
削除は他に、以下がある
・/mp4のn日経ったmp4を削除する
・DISKが90%超で最古のMP4を1つ強制削除する

##### ◯/ramdiskの10分以上経ったMP4を削除するcron
カメラ毎に下記cronをrootで登録し、/ramdiskの10分以上経ったmp4は自動的に削除されるように設定する  
config.pyのcamera_nameを設定する  

```
# crontab -e
*/1 * * * * /var/www/ismscam/batch/cron_rmOldMP4_1.bash > /dev/null 2>&1
---
find /ramdisk -mmin +10 -name "camera1*.mp4" | xargs rm -f
---
```

#### 3) カメラのHTTP通知をWebサーバで受信しMP4を合成する  
カメラで動体検知をするとWebサーバにHTTP通知する  
10秒毎に分割されているMP4を必要な本数結合し/mp4に保存する  

##### ◯bottle.pyでRESTサーバを作成する
/api/v1/notice部分
```
### REST API
# mp4ファイルを結合し/mp4に保存する
@route('/api/v1/notice', method='GET')
@route('/api/v1/notice/<camera>', method='GET')
def notice(camera=''):
    forms = getUTF8forms(request.forms)
    result = {}

    result['camera'] = camera
    if camera == '':
        result['message'] = 'No camera name!'
        return template('notice', dict(forms=forms, result=result))

    nowtime = datetime.now().strftime('%Y%m%d%H%M%S')
    # ローカル出力ファイル
    # mp4結合ファイル:{device_name}_{camera}_yyyymmddHHMMSS.mp4
    localout_filename = '{0}_{1}_{2}.mp4'.format(config.device_name, camera, nowtime)
    localout_filepath = '/mp4/{0}'.format(localout_filename)
    # ローカル入力ファイル 呼び出しから10秒後の最新mp4を3つ取得する
    # カメラからのmp4:{camera}_yyyymmddHHMMSS.mp4
    time.sleep(10)
    files = glob.glob("/ramdisk/{0}*mp4".format(camera))
    files.sort()
    files = files[-3:]
    # 結合対象を書いたファイル
    target_file = "{0}_targets".format(localout_filepath)
    fileobj = open(target_file, "w", encoding = "utf_8")
    for i in range(3):
        fileobj.write("file {0}\n".format(files[i]))
    fileobj.close()
    # 3つのmp4を結合し結合対象を書いたファイルを削除
    cmd = 'ffmpeg -f concat -safe 0 -i {0} -c copy {1}'
    cmd = cmd.format(target_file, localout_filepath)
    ret = subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    os.remove(target_file)
    # 画面表示用にファイル名とファイルサイズを取得
    result['mp4file_name'] = localout_filename
    if os.path.exists(localout_filepath):
        result['mp4file_size'] = os.path.getsize(localout_filepath)

    return template('notice', dict(forms=forms, result=result))
```
##### ◯/mp4の7日以上経ったMP4を削除するcron
カメラ毎に下記cronをrootで登録し、/mp4の7日以上経ったmp4は自動的に削除されるように設定する  
config.pyのcamera_nameを設定する  
```
---
# crontab -e
*/1 * * * * /var/www/ismscam/batch/cron_rmOldStorageMP4_1.bash
---
find /mp4 -mtime 7 -name "camera1*.mp4" | xargs rm -f
---
```

##### ◯DISK90%超で最古のMP4を強制的に削除するcron(毎分)
```
# crontab -e
*/1 * * * * /var/www/ismscam/batch/cron_rmOldestMP4.bash > /dev/null 2>&1
---
#!/bin/bash
# DISK利用率90%を超えたら最も古いMP4を1つ削除する
use=`df -h|grep root|awk '{print $5}'|sed "s/%//"`
if [ $use -gt 90 ]; then
    mp4file=`ls -ltr /mp4| tail -n +2 | head -1|awk '{print $9}'`
    /bin/rm '/mp4/'$mp4file
fi
---
```

#### 4) その他
##### ◯ Scoreboard is Fullエラーを抑える
MX-X300でWebアプリを動かすと、半日程度稼働させると、apache2のerror_logに下記のエラーが頻発し、
Webアクセスができなくなる
```
AH03490: scoreboard is full, not at MaxRequestWorkers.Increase ServerLimit  .
```

下記のファイルの以下をコメントアウトし、apache2を再起動する
/etc/apache2/mods-available/mpm_event.conf
/etc/apache2/mods-available/mpm_prefork.conf
/etc/apache2/mods-available/mpm_worker.conf
```
#   MaxConnectionsPerChild   0
```