目录
1. 三方工具
2. 视频存储的实现
2.1 分段存储 - 比如每15分钟
2.2 对齐到15分钟整边界
2.3 循环存储的实现 video_space_daemon.sh
3.封装
3.1 主执行程序,修订版
3.2 创建服务
3.3 service关联的执行脚本文件
4.额外的工作
附录A: ffmpeg视频存储,运行时错误处理
1.运行ffmpeg存储视频时
1.1 处理
附录B 服务创建加载以及运行时异常处理
1.service无法enable
1.1 处理
1. 三方工具
建议使用ffmpeg,这个工具多平台可用,命令行和API都有提供,非常便捷。下载的位置:
Download FFmpeg
我工作在debian环境,下载对应的源码后编译:
./configure
make
make install
2. 视频存储的实现
2.1 分段存储 - 比如每15分钟
下面的脚本里 MIN_PER_FILE控制的是每个视频文件的最大尺寸
#usage: video_recorder <video_file_path> <min_per_file> <rtsp_path>
RTSP_URL_WITH_PASSWORD='rtsp://admin:xxxx@192.168.0.6:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1'
MIN_PER_FILE=03
VIDEO_FILE_PATH='/tmp/video/' #include postfix
mkdir -p $VIDEO_FILE_PATH
ffmpeg -rtsp_transport tcp -i $RTSP_URL_WITH_PASSWORD -c:v libx265 -preset fast -crf 28 -t 00:$MIN_PER_FILE:00 $VIDEO_FILE_PATH$(date +"%Y%m%d%H%M%S").mp4
这个命令执行时因为环境的原因,可能会提示错误,我这边的一个纠错参见附录A,正确调用后:
Output #0, mp4, to '/tmp/video/20240807094508.mp4':
Metadata:
title : Media Presentation
encoder : Lavf61.1.100
Stream #0:0: Video: hevc (hev1 / 0x31766568), yuvj420p(pc, bt709, progressive), 1920x1080, q=2-31, 10 fps, 10240 tbn
Metadata:
encoder : Lavc61.3.100 libx265
Side data:
cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
frame= 1555 fps= 10 q=31.9 size= 12288KiB time=00:02:35.30 bitrate= 648.2kbits/s speed= 1x
估算,最终的视频文件尺寸,单通道.h265大概每15分钟45~50MBytes.
2.2 对齐到15分钟整边界
虽然命令行已经提供了分段存储,比如15分钟一个文件的功能,但是它没有对齐到15分钟整边界,可以看到这个逻辑。注意那个重启视频录制进程的工作使用systemctl的语法来实现的,这个在第3节封装部分会引入。
#!/bin/bash
# 获取传递的参数数量
num_args=$#
if [ $num_args -ne 1 ]; then
echo "usage: $0 <chIdxbase1>"
exit 1
else
CHIDX_BASE1=$(printf "%02d" "$1")
fi
# 变量
SERVICE_NAME="guide_video_storage_ch"$CHIDX_BASE1
# 获取当前时间的秒数
now=$(date +%s)
# 获取当前分钟数和秒数
minute=$(date +%M)
second=$(date +%S)
minute=$(echo $minute | sed 's/^0*//') # 移除前导0
second=$(echo $second | sed 's/^0*//') # 移除前导0
# 计算最近 15 分钟整边界
remainder=$((minute % 15))
if (( remainder == 0 && second == 0 )); then
target_time=$now
echo $target_time
else
if (( remainder == 0 )); then
# 当前时间在整边界时刻,但秒数不为0
target_time=$((now + (60 - second)))
echo $target_time
else
# 计算下一个整边界的时间
next_minute=$(( (minute + (15 - remainder)) % 60 ))
next_hour=$(date +%H)
if ((minute + (15 - remainder) >= 60)); then
next_hour=$(echo $next_hour | sed 's/^0*//') # 移除前导0
next_hour=$(( (next_hour + 1) % 24 ))
fi
# 计算目标时间
target_time=$(date -d "$next_hour:$next_minute:00" +%s)
echo $target_time
fi
fi
# 计算需要等待的秒数
seconds_to_wait=$((target_time - now))
# 输出等待时间
echo "等待 $seconds_to_wait 秒"
sleep $seconds_to_wait
#重启脚本
sudo systemctl restart $SERVICE_NAME
2.3 循环存储的实现 video_space_daemon.sh
为了防止出现磁盘快速磨损,使用了回差的概念。
#!/bin/bash
VIDEO_FILE_PATH='/tmp/video/' #include postfix
mkdir -p $VIDEO_FILE_PATH
CH_CNTS=5
DIRECTORY=$VIDEO_FILE_PATH
MAX_SIZE_MB=$((50*$CH_CNTS))
CTRL_SIZE_MB=$(awk "BEGIN {print $MAX_SIZE_MB * 0.8}")
TEMP_FILE="/tmp/file_sizes.txt"
# 计算目录中所有文件的总大小
total_size=$(du -sm "$DIRECTORY" | cut -f1)
# 如果总大小超过最大允许值
if [ "$total_size" -gt "$MAX_SIZE_MB" ]; then
# 列出文件大小和路径,按时间排序(最旧的文件在前)
find "$DIRECTORY" -type f -printf '%T+ %s %p\n' | sort | awk '{print $2, $3}' > "$TEMP_FILE"
# 删除最旧的文件,直到总大小低于 90MB
while [ "$total_size" -gt "$CTRL_SIZE_MB" ]; do
oldest_file=$(head -n 1 "$TEMP_FILE" | awk '{print $2}')
file_size=$(head -n 1 "$TEMP_FILE" | awk '{print $1}')
# 删除文件
rm "$oldest_file"
# 更新总大小
total_size=$((total_size - file_size / 1024 / 1024))
# 重新列出文件
tail -n +2 "$TEMP_FILE" > "$TEMP_FILE.tmp" && mv "$TEMP_FILE.tmp" "$TEMP_FILE"
done
# 删除临时文件
rm "$TEMP_FILE"
fi
3.封装
因为视频源可能会出问题,所以需要有看护程序,最终使用Service来处理进程的遇错自动重启。然后用一个看护程序,来对齐到15分钟整边界。这里有全部的代码:
3.1 主执行程序,修订版
#!/bin/bash
#usage: gpVideoRecorder <video_file_path> <min_per_file> <rtsp_path> <chIdxbase1>
# 函数:确保目录路径以斜杠结尾
ensure_trailing_slash() {
local dir=$1
if [[ "$dir" != */ ]]; then
echo "${dir}/"
else
echo "$dir"
fi
}
# 获取传递的参数数量
num_args=$#
if [ $num_args -eq 0 ]; then
VIDEO_FILE_PATH='/tmp/video/' #include postfix
MIN_PER_FILE=03
RTSP_URL_WITH_PASSWORD='rtsp://admin:xxxxx@192.168.0.6:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1'
CH_STR="ch"$(printf "%02d" "0")_
elif [ $num_args -ne 4 ]; then
echo "usage: $0 <video_file_path> <min_per_file> <rtsp_path> <chIdxbase1>"
else
VIDEO_FILE_PATH=$1
MIN_PER_FILE=$2
RTSP_URL_WITH_PASSWORD=$3
CH_STR="ch"$(printf "%02d" "$4")_
fi
# 确保目录存在
mkdir -p $VIDEO_FILE_PATH
# 确保目录路径以斜杠结尾
VIDEO_FILE_PATH=$(ensure_trailing_slash "$VIDEO_FILE_PATH")/
echo $VIDEO_FILE_PATH$CH_STR$(date +"%Y%m%d%H%M%S").mp4
ffmpeg -rtsp_transport tcp -i $RTSP_URL_WITH_PASSWORD -c:v libx265 -preset fast -crf 28 -t 00:$MIN_PER_FILE:00 $VIDEO_FILE_PATH$CH_STR$(date +"%Y%m%d%H%M%S").mp4
3.2 创建服务
#!/bin/bash
# 获取传递的参数数量
num_args=$#
if [ $num_args -ne 1 ]; then
echo "usage: $0 <chIdxbase1>"
exit 1
else
# 使用 printf 将数字格式化为两位数
CHIDX_BASE1=$(printf "%02d" "$1")
fi
# 变量
PROGRAM_DIR="/etc/program"
SERVICE_NAME="guide_video_storage_ch"$CHIDX_BASE1
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
#service_name lookslike guide_video_storage_ch01
# 1. 创建目录
if [ ! -d "$PROGRAM_DIR" ]; then
echo "创建目录 $PROGRAM_DIR"
sudo mkdir -p "$PROGRAM_DIR"
fi
# 2. 创建服务文件
echo "创建服务文件 $SERVICE_FILE"
cat <<EOL | sudo tee "$SERVICE_FILE"
[Unit]
Description=video streamer local storage service
[Service]
ExecStart=sudo -E /home/app/common/start_video_recording.sh $CHIDX_BASE1
Restart=always
RestartSec=5
User=root
[Install]
WantedBy=multi-user.target
EOL
# 3. 重新加载 systemd 配置
echo "重新加载 systemd 配置"
sudo systemctl daemon-reload
# 4. 启动服务
echo "启动服务 ${SERVICE_NAME}"
sudo systemctl start "${SERVICE_NAME}"
# 5. 设置服务开机启动
echo "设置服务开机启动"
sudo systemctl enable "${SERVICE_NAME}"
echo "服务 ${SERVICE_NAME} 创建并启动完成。"
3.3 service关联的执行脚本文件
因为service不支持同时启动多个命令,所以要有这个.sh,注意那个背景进程的消灭。理论上不这样做也可以。毕竟视频文件截断的动作是从video_split_daemon.sh发出的。
#这里会对齐至整边界
sudo /home/app/common/video_split_daemon.sh 5&
# 获取 p1 的进程 ID
SPLIT_PID=$!
#space_dameon只执行一次无需关心
sudo /home/app/common/video_space_daemon.sh &
#这里的文件记录长度可以略长(注意rtsp的用户名密码的规则:)
sudo /home/app/common/gpVideoRecorder /tmp/video 30 'rtsp://admin:a1234567@192.168.0.6:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1' 5
# 在脚本退出时终止后台进程 p1
trap 'kill $SPLIT_PID' EXIT
注意最终的那个调用需要是个阻塞命令。
3.4 整个环境运行时的截屏
上面刚经历过一次重启,时间是从xx:45开始的,对齐到45分整边界,视频文件列表:
切换前列表 | 切换后 |
能看到循环存储已经生效。 所有的可执行文件列表:
4.额外的工作
在实际使用时,至少还有如下工作要做:
- json配置文件解析和开机boot
- mqtt远程命令执行接口
- 状态信息redis缓存
这个环节不再赘述。
附录A: ffmpeg视频存储,运行时错误处理
1.运行ffmpeg存储视频时
Unrecognized option 'preset'.
Error splitting the argument list: Option not found
1.1 处理
- 检查是x265编码器可能没有装
- ./configure --enable-libx265 --enable-libx264 --disable-x86asm --enable-gpl --pkg-config="pkg-config --static"
- 提示:ERROR: x264 not found using pkg-config
- sudo apt install pkg-config
- sudo apt install libx264-dev
- sudo apt install libx265-dev
- ./configure --enable-libx265 --enable-libx264 --disable-x86asm --enable-gpl
- make
- make install
附录B 服务创建加载以及运行时异常处理
1.service无法enable
Failed to enable unit: Unit file /etc/systemd/system/guide_video_storage_ch05.service is masked.
1.1 处理
在更新.service前,一定记得要先disable。如果出现mask:
sudo systemctl unmask guide_video_storage_ch05
sudo systemctl stop guide_video_storage_ch05
sudo systemctl disable guide_video_storage_ch05