这里只介绍如何在ubuntu下进行shell脚本的开发与检查
0.参考资料
1.如何安装shellcheck
bash
sudo apt-get install shellcheck
2.案例
我用kimi写了一个gst的数字变焦测试案例,shell脚本如下:
sh
#!/bin/bash
# 多相机变焦录制脚本 - QCS8550 平台 (支持SAT分辨率切换)
# 超过6.3x时sensor分辨率变为4624x2608
# 支持: 固定zoom录制 / 连续zoom变化录制 / 自动档位遍历 / RTSP实时观察
# ==================== 配置参数 ====================
CAMERA_ID=3
FOCUS_MODE=3
WIDTH=1920
HEIGHT=1080
FRAMERATE=30
OUTPUT_FILE="/data/test3.mp4"
# SAT切换阈值
SAT_THRESHOLD=6.9
# sensor-ov50h40分辨率 (Wide)
LOW_RES_W=4096
LOW_RES_H=3072
# sensor-imx682分辨率 (Tele/Periscope)
HIGH_RES_W=4624
HIGH_RES_H=2608
# 预定义zoom档位 (基于对应sensor分辨率计算)
# 1.0x~6.3x: 基于4096x3072
ZOOM_1_0X="<0,0,4096,3072>"
ZOOM_2_0X="<1024,768,2048,1536>"
ZOOM_4_0X="<1536,1152,1024,768>"
ZOOM_6_0X="<1706,1280,682,512>"
# 6.3x~24.0x: 基于4624x2608 (中心点2312,1304)
ZOOM_8_0X_HIGH="<2024,1139,578,326>"
ZOOM_12_0X_HIGH="<2119,1188,385,217>"
ZOOM_24_0X_HIGH="<2215,1235,193,109>"
# 默认zoom (1.0x)
DEFAULT_ZOOM="${ZOOM_1_0X}"
# ==================== 函数定义 ====================
show_help() {
echo "========================================"
echo " QMMF SAT变焦录制脚本"
echo "========================================"
echo "用法: $0 [模式] [选项]"
echo ""
echo "模式:"
echo " record 固定zoom录制 (默认)"
echo " sweep zoom从1.0x连续变化到24.0x录制"
echo " step 自动遍历zoom档位录制"
echo " rtsp RTSP实时观察"
echo ""
echo "选项:"
echo " -z, --zoom <倍数> 设置zoom倍数 (record模式, 默认1.0)"
echo " -s, --speed <秒> sweep模式每档停留时间 (默认3)"
echo " -d, --delay <秒> step模式每档停留时间 (默认5)"
echo " -o, --output <路径> 输出文件路径"
echo " -t, --time <秒> 录制总时长 (record模式)"
echo " -p, --port <端口> RTSP端口 (默认8554)"
echo " -h, --help 显示帮助"
echo ""
echo "示例:"
echo " $0 record -z 8.0 -t 60 # 固定8倍录制60秒"
echo " $0 sweep -s 2 -o /data/zoom.mp4 # 连续zoom变化录制"
echo " $0 step -d 5 # 遍历档位每档5秒"
echo " $0 rtsp -z 4.0 # RTSP实时观察4倍变焦"
echo ""
echo "注意: >6.3x时sensor自动切换到4624x2608高分辨率"
echo "========================================"
}
# 根据倍数获取zoom参数 (自动判断SAT切换)
get_zoom_by_factor() {
local factor="$1"
local is_high_res
is_high_res=$(echo "$factor > $SAT_THRESHOLD" | bc -l)
if [ "$is_high_res" -eq 1 ]; then
# 高倍率模式 (4624x2608)
case "${factor}" in
8.0|8) echo "${ZOOM_8_0X_HIGH}" ;;
12.0|12) echo "${ZOOM_12_0X_HIGH}" ;;
16.0|16) echo "<2167,1213,289,163>" ;;
20.0|20) echo "<2204,1240,231,130>" ;;
24.0|24) echo "${ZOOM_24_0X_HIGH}" ;;
*)
# 动态计算高分辨率zoom
local w h x y
w=$(echo "scale=0; $HIGH_RES_W / $factor" | bc)
h=$(echo "scale=0; $HIGH_RES_H / $factor" | bc)
x=$(echo "scale=0; ($HIGH_RES_W - $w) / 2" | bc)
y=$(echo "scale=0; ($HIGH_RES_H - $h) / 2" | bc)
echo "<${x},${y},${w},${h}>"
;;
esac
else
# 低倍率模式 (4096x3072)
case "${factor}" in
1.0|1) echo "${ZOOM_1_0X}" ;;
2.0|2) echo "${ZOOM_2_0X}" ;;
3.0|3) echo "<1024,768,1365,1024>" ;;
4.0|4) echo "${ZOOM_4_0X}" ;;
5.0|5) echo "<1638,1228,819,614>" ;;
6.0|6) echo "${ZOOM_6_0X}" ;;
*)
# 动态计算低分辨率zoom
local w h x y
w=$(echo "scale=0; $LOW_RES_W / $factor" | bc)
h=$(echo "scale=0; $LOW_RES_H / $factor" | bc)
x=$(echo "scale=0; ($LOW_RES_W - $w) / 2" | bc)
y=$(echo "scale=0; ($LOW_RES_H - $h) / 2" | bc)
echo "<${x},${y},${w},${h}>"
;;
esac
fi
}
# 判断当前分辨率模式
get_sensor_mode() {
local factor="$1"
local is_high
is_high=$(echo "$factor > $SAT_THRESHOLD" | bc -l)
if [ "$is_high" -eq 1 ]; then
echo "HIGH(4624x2608)"
else
echo "LOW(4096x3072)"
fi
}
# 构建GStreamer管道
build_pipeline() {
local zoom="$1"
local mode="$2"
local port="$3"
local src="qtiqmmfsrc focus-mode=${FOCUS_MODE} camera=${CAMERA_ID} zoom=${zoom} name=camsrc"
local caps="video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT},framerate=${FRAMERATE}/1"
local encode="qtic2venc ! h264parse"
case "$mode" in
rtsp)
echo "${src} ! ${caps} ! ${encode} ! rtspclientsink location=rtsp://0.0.0.0:${port}/live"
;;
*)
echo "${src} ! ${caps} ! ${encode} ! mp4mux ! queue ! filesink location=${OUTPUT_FILE}"
;;
esac
}
# 固定zoom录制
start_recording() {
local zoom_param="$1"
local duration="$2"
local output="$3"
echo "========================================"
echo " 固定Zoom录制"
echo "========================================"
echo "相机ID: ${CAMERA_ID} (逻辑相机/SAT)"
echo "输出分辨率: ${WIDTH}x${HEIGHT}@${FRAMERATE}fps"
echo "Zoom: ${zoom_param}"
echo "SAT阈值: ${SAT_THRESHOLD}x"
echo "输出文件: ${output}"
if [ -n "${duration}" ]; then
echo "录制时长: ${duration}秒"
else
echo "录制时长: 无限 (按Ctrl+C停止)"
fi
echo "========================================"
if [ ! -d "$(dirname "${output}")" ]; then
echo "错误: 输出目录不存在"
exit 1
fi
local pipeline
pipeline=$(build_pipeline "$zoom_param" "record")
echo "执行命令:"
echo "${pipeline}"
echo ""
if [ -n "${duration}" ]; then
timeout "${duration}" bash -c "${pipeline}"
else
eval "${pipeline}"
fi
local ret=$?
if [ $ret -eq 0 ] || [ $ret -eq 124 ]; then
echo ""
echo "录制完成: ${output}"
if [ -f "${output}" ]; then
ls -lh "${output}"
fi
else
echo ""
echo "录制失败"
exit 1
fi
}
# zoom连续变化录制 (sweep模式)
sweep_zoom() {
local speed="$1"
local output="$2"
echo "========================================"
echo " Zoom连续变化录制 (1.0x -> 24.0x)"
echo "========================================"
echo "变化范围: 1.0x ~ 24.0x"
echo "每档停留: ${speed}秒"
echo "输出文件: ${output}"
echo "========================================"
if [ ! -d "$(dirname "${output}")" ]; then
echo "错误: 输出目录不存在"
exit 1
fi
# 定义zoom变化序列 (从1.0x到24.0x)
local zoom_factors=(1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.3 6.5 7.0 7.5 8.0 9.0 10.0 11.0 12.0 14.0 16.0 18.0 20.0 22.0 24.0)
# 分段录制,每段一个zoom值,最后合并
local temp_dir
temp_dir="/data/zoom_sweep_$$"
mkdir -p "${temp_dir}"
local segment_idx=0
for factor in "${zoom_factors[@]}"; do
local zoom_param sensor_mode segment_file
zoom_param=$(get_zoom_by_factor "$factor")
sensor_mode=$(get_sensor_mode "$factor")
segment_file="${temp_dir}/seg_${segment_idx}.mp4"
echo ""
echo "[$(date '+%H:%M:%S')] Zoom: ${factor}x | Sensor: ${sensor_mode} | 录制${speed}秒"
local pipeline
pipeline="gst-launch-1.0 -e \
qtiqmmfsrc focus-mode=${FOCUS_MODE} camera=${CAMERA_ID} zoom=${zoom_param} name=camsrc \
! video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT},framerate=${FRAMERATE}/1 \
! qtic2venc \
! h264parse \
! mp4mux \
! queue \
! filesink location=${segment_file}"
timeout "${speed}" bash -c "${pipeline}" 2>/dev/null
if [ -f "${segment_file}" ] && [ -s "${segment_file}" ]; then
echo " 片段${segment_idx}完成: ${factor}x"
else
echo " 片段${segment_idx}失败或为空: ${factor}x"
fi
segment_idx=$((segment_idx + 1))
sleep 0.5
done
# 合并所有片段
echo ""
echo "正在合并${segment_idx}个片段..."
# 创建concat列表文件
local concat_list
concat_list="${temp_dir}/list.txt"
true > "${concat_list}"
for i in $(seq 0 $((segment_idx - 1))); do
local seg
seg="${temp_dir}/seg_${i}.mp4"
if [ -f "$seg" ] && [ -s "$seg" ]; then
echo "file '${seg}'" >> "${concat_list}"
fi
done
if [ -s "${concat_list}" ]; then
if ffmpeg -f concat -safe 0 -i "${concat_list}" -c copy "${output}" -y 2>/dev/null; then
if [ -f "${output}" ]; then
echo "合并完成: ${output}"
ls -lh "${output}"
fi
else
echo "合并失败,保留分段文件在: ${temp_dir}"
fi
else
echo "没有有效的片段文件"
fi
# 清理临时文件
rm -rf "${temp_dir}"
echo "Zoom连续变化录制完成"
}
# 自动档位遍历录制 (step模式)
step_zoom() {
local delay="$1"
local output="$2"
echo "========================================"
echo " Zoom档位遍历录制"
echo "========================================"
echo "遍历档位: 1.0x, 2.0x, 4.0x, 6.0x, 6.3x, 8.0x, 12.0x, 16.0x, 24.0x"
echo "每档停留: ${delay}秒"
echo "输出文件: ${output}"
echo "========================================"
if [ ! -d "$(dirname "${output}")" ]; then
echo "错误: 输出目录不存在"
exit 1
fi
local zoom_factors=(1.0 2.0 4.0 6.0 6.3 8.0 12.0 16.0 24.0)
local temp_dir
temp_dir="/data/zoom_step_$$"
mkdir -p "${temp_dir}"
local segment_idx=0
for factor in "${zoom_factors[@]}"; do
local zoom_param sensor_mode segment_file
zoom_param=$(get_zoom_by_factor "$factor")
sensor_mode=$(get_sensor_mode "$factor")
segment_file="${temp_dir}/seg_${segment_idx}.mp4"
echo ""
echo "[$(date '+%H:%M:%S')] 档位 ${factor}x | Sensor: ${sensor_mode} | 录制${delay}秒"
local pipeline
pipeline="gst-launch-1.0 -e \
qtiqmmfsrc focus-mode=${FOCUS_MODE} camera=${CAMERA_ID} zoom=${zoom_param} name=camsrc \
! video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT},framerate=${FRAMERATE}/1 \
! qtic2venc \
! h264parse \
! mp4mux \
! queue \
! filesink location=${segment_file}"
timeout "${delay}" bash -c "${pipeline}" 2>/dev/null
if [ -f "${segment_file}" ] && [ -s "${segment_file}" ]; then
echo " 片段完成"
fi
segment_idx=$((segment_idx + 1))
sleep 0.5
done
# 合并片段
echo ""
echo "正在合并${segment_idx}个片段..."
local concat_list
concat_list="${temp_dir}/list.txt"
true > "${concat_list}"
for i in $(seq 0 $((segment_idx - 1))); do
local seg
seg="${temp_dir}/seg_${i}.mp4"
if [ -f "$seg" ] && [ -s "$seg" ]; then
echo "file '${seg}'" >> "${concat_list}"
fi
done
if [ -s "${concat_list}" ]; then
if ffmpeg -f concat -safe 0 -i "${concat_list}" -c copy "${output}" -y 2>/dev/null; then
if [ -f "${output}" ]; then
echo "合并完成: ${output}"
ls -lh "${output}"
fi
else
echo "合并失败,保留分段文件在: ${temp_dir}"
fi
fi
rm -rf "${temp_dir}"
echo "Zoom档位遍历录制完成"
}
# RTSP实时观察
rtsp_preview() {
local zoom_param="$1"
local port="$2"
echo "========================================"
echo " RTSP实时观察"
echo "========================================"
echo "Zoom: ${zoom_param}"
echo "RTSP端口: ${port}"
echo "========================================"
local ip pipeline
ip=$(hostname -I | awk '{print $1}')
pipeline=$(build_pipeline "$zoom_param" "rtsp" "$port")
echo "RTSP地址: rtsp://${ip}:${port}/live"
echo "VLC播放: vlc rtsp://${ip}:${port}/live"
echo "ffplay: ffplay rtsp://${ip}:${port}/live"
echo ""
echo "按Ctrl+C停止"
echo "========================================"
eval "${pipeline}"
}
# ==================== 主程序 ====================
# 默认值
MODE="record"
ZOOM_PARAM="${DEFAULT_ZOOM}"
DURATION=""
SWEEP_SPEED=3
STEP_DELAY=5
RTSP_PORT=8554
# 解析模式参数
if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
MODE="$1"
shift
fi
while [[ $# -gt 0 ]]; do
case "$1" in
-z|--zoom)
ZOOM_PARAM=$(get_zoom_by_factor "$2")
shift 2
;;
-s|--speed)
SWEEP_SPEED="$2"
shift 2
;;
-d|--delay)
STEP_DELAY="$2"
shift 2
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-t|--time)
DURATION="$2"
shift 2
;;
-p|--port)
RTSP_PORT="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo "未知选项: $1"
show_help
exit 1
;;
esac
done
# 检查bc命令
if ! command -v bc &> /dev/null; then
echo "错误: 需要安装bc (基本计算器)"
echo "安装: apt-get install bc 或 opkg install bc"
exit 1
fi
# 检查ffmpeg (sweep/step模式需要)
if [ "$MODE" == "sweep" ] || [ "$MODE" == "step" ]; then
if ! command -v ffmpeg &> /dev/null; then
echo "警告: 未找到ffmpeg,合并功能将不可用"
echo "建议安装: apt-get install ffmpeg"
fi
fi
# 执行对应模式
case "$MODE" in
record)
start_recording "${ZOOM_PARAM}" "${DURATION}" "${OUTPUT_FILE}"
;;
sweep)
sweep_zoom "${SWEEP_SPEED}" "${OUTPUT_FILE}"
;;
step)
step_zoom "${STEP_DELAY}" "${OUTPUT_FILE}"
;;
rtsp)
rtsp_preview "${ZOOM_PARAM}" "${RTSP_PORT}"
;;
*)
echo "未知模式: $MODE"
show_help
exit 1
;;
esac
通过shellcheck可以轻松检查出脚本问题,将执行结果告诉kimi后,让kimi重新生成脚本,这样可以快速解决shell脚本中的语法错误,检查结果如下:
txt
In test_logicalcamera.sh line 78:
local is_high_res=$(echo "$factor > $SAT_THRESHOLD" | bc -l)
^---------^ SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 90:
local w=$(echo "scale=0; $HIGH_RES_W / $factor" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 91:
local h=$(echo "scale=0; $HIGH_RES_H / $factor" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 92:
local x=$(echo "scale=0; ($HIGH_RES_W - $w) / 2" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 93:
local y=$(echo "scale=0; ($HIGH_RES_H - $h) / 2" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 108:
local w=$(echo "scale=0; $LOW_RES_W / $factor" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 109:
local h=$(echo "scale=0; $LOW_RES_H / $factor" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 110:
local x=$(echo "scale=0; ($LOW_RES_W - $w) / 2" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 111:
local y=$(echo "scale=0; ($LOW_RES_H - $h) / 2" | bc)
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 121:
local is_high=$(echo "$factor > $SAT_THRESHOLD" | bc -l)
^-----^ SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 170:
if [ ! -d "$(dirname ${output})" ]; then
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
if [ ! -d "$(dirname "${output}")" ]; then
In test_logicalcamera.sh line 175:
local pipeline=$(build_pipeline "$zoom_param" "record")
^------^ SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 182:
timeout ${duration} bash -c "${pipeline}"
^---------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
timeout "${duration}" bash -c "${pipeline}"
In test_logicalcamera.sh line 187:
if [ $? -eq 0 ] || [ $? -eq 124 ]; then
^-- SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?.
In test_logicalcamera.sh line 190:
[ -f "${output}" ] && ls -lh ${output}
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
[ -f "${output}" ] && ls -lh "${output}"
In test_logicalcamera.sh line 212:
if [ ! -d "$(dirname ${output})" ]; then
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
if [ ! -d "$(dirname "${output}")" ]; then
In test_logicalcamera.sh line 226:
local zoom_param=$(get_zoom_by_factor "$factor")
^--------^ SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 227:
local sensor_mode=$(get_sensor_mode "$factor")
^---------^ SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 242:
timeout ${speed} bash -c "${pipeline}" 2>/dev/null
^------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
timeout "${speed}" bash -c "${pipeline}" 2>/dev/null
In test_logicalcamera.sh line 260:
> ${concat_list}
^--------------^ SC2188: This redirection doesn't have a command. Move to its command (or use 'true' as no-op).
In test_logicalcamera.sh line 269:
ffmpeg -f concat -safe 0 -i ${concat_list} -c copy ${output} -y 2>/dev/null
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
ffmpeg -f concat -safe 0 -i ${concat_list} -c copy "${output}" -y 2>/dev/null
In test_logicalcamera.sh line 270:
if [ $? -eq 0 ] && [ -f "${output}" ]; then
^-- SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?.
In test_logicalcamera.sh line 272:
ls -lh ${output}
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
ls -lh "${output}"
In test_logicalcamera.sh line 298:
if [ ! -d "$(dirname ${output})" ]; then
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
if [ ! -d "$(dirname "${output}")" ]; then
In test_logicalcamera.sh line 309:
local zoom_param=$(get_zoom_by_factor "$factor")
^--------^ SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 310:
local sensor_mode=$(get_sensor_mode "$factor")
^---------^ SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 325:
timeout ${delay} bash -c "${pipeline}" 2>/dev/null
^------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
timeout "${delay}" bash -c "${pipeline}" 2>/dev/null
In test_logicalcamera.sh line 340:
> ${concat_list}
^--------------^ SC2188: This redirection doesn't have a command. Move to its command (or use 'true' as no-op).
In test_logicalcamera.sh line 349:
ffmpeg -f concat -safe 0 -i ${concat_list} -c copy ${output} -y 2>/dev/null
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
ffmpeg -f concat -safe 0 -i ${concat_list} -c copy "${output}" -y 2>/dev/null
In test_logicalcamera.sh line 350:
if [ $? -eq 0 ] && [ -f "${output}" ]; then
^-- SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?.
In test_logicalcamera.sh line 352:
ls -lh ${output}
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
ls -lh "${output}"
In test_logicalcamera.sh line 374:
local ip=$(hostname -I | awk '{print $1}')
^-- SC2155: Declare and assign separately to avoid masking return values.
In test_logicalcamera.sh line 375:
local pipeline=$(build_pipeline "$zoom_param" "rtsp" "$port")
^------^ SC2155: Declare and assign separately to avoid masking return values.
kimi重新修改后输出结果如下:
sh
#!/bin/bash
# 多相机变焦录制脚本 - QCS8550 平台 (支持SAT分辨率切换)
# 超过6.3x时sensor分辨率变为4624x2608
# 支持: 固定zoom录制 / 连续zoom变化录制 / 自动档位遍历 / RTSP实时观察
# ==================== 配置参数 ====================
CAMERA_ID=3
FOCUS_MODE=3
WIDTH=1920
HEIGHT=1080
FRAMERATE=30
OUTPUT_FILE="/data/test3.mp4"
# SAT切换阈值
SAT_THRESHOLD=6.9
# sensor-ov50h40分辨率 (Wide)
LOW_RES_W=4096
LOW_RES_H=3072
# sensor-imx682分辨率 (Tele/Periscope)
HIGH_RES_W=4624
HIGH_RES_H=2608
# 预定义zoom档位 (基于对应sensor分辨率计算)
# 1.0x~6.3x: 基于4096x3072
ZOOM_1_0X="<0,0,4096,3072>"
ZOOM_2_0X="<1024,768,2048,1536>"
ZOOM_4_0X="<1536,1152,1024,768>"
ZOOM_6_0X="<1706,1280,682,512>"
# 6.3x~24.0x: 基于4624x2608 (中心点2312,1304)
ZOOM_8_0X_HIGH="<2024,1139,578,326>"
ZOOM_12_0X_HIGH="<2119,1188,385,217>"
ZOOM_24_0X_HIGH="<2215,1235,193,109>"
# 默认zoom (1.0x)
DEFAULT_ZOOM="${ZOOM_1_0X}"
# ==================== 函数定义 ====================
show_help() {
echo "========================================"
echo " QMMF SAT变焦录制脚本"
echo "========================================"
echo "用法: $0 [模式] [选项]"
echo ""
echo "模式:"
echo " record 固定zoom录制 (默认)"
echo " sweep zoom从1.0x连续变化到24.0x录制"
echo " step 自动遍历zoom档位录制"
echo " rtsp RTSP实时观察"
echo ""
echo "选项:"
echo " -z, --zoom <倍数> 设置zoom倍数 (record模式, 默认1.0)"
echo " -s, --speed <秒> sweep模式每档停留时间 (默认3)"
echo " -d, --delay <秒> step模式每档停留时间 (默认5)"
echo " -o, --output <路径> 输出文件路径"
echo " -t, --time <秒> 录制总时长 (record模式)"
echo " -p, --port <端口> RTSP端口 (默认8554)"
echo " -h, --help 显示帮助"
echo ""
echo "示例:"
echo " $0 record -z 8.0 -t 60 # 固定8倍录制60秒"
echo " $0 sweep -s 2 -o /data/zoom.mp4 # 连续zoom变化录制"
echo " $0 step -d 5 # 遍历档位每档5秒"
echo " $0 rtsp -z 4.0 # RTSP实时观察4倍变焦"
echo ""
echo "注意: >6.3x时sensor自动切换到4624x2608高分辨率"
echo "========================================"
}
# 根据倍数获取zoom参数 (自动判断SAT切换)
get_zoom_by_factor() {
local factor="$1"
local is_high_res
is_high_res=$(echo "$factor > $SAT_THRESHOLD" | bc -l)
if [ "$is_high_res" -eq 1 ]; then
# 高倍率模式 (4624x2608)
case "${factor}" in
8.0|8) echo "${ZOOM_8_0X_HIGH}" ;;
12.0|12) echo "${ZOOM_12_0X_HIGH}" ;;
16.0|16) echo "<2167,1213,289,163>" ;;
20.0|20) echo "<2204,1240,231,130>" ;;
24.0|24) echo "${ZOOM_24_0X_HIGH}" ;;
*)
# 动态计算高分辨率zoom
local w h x y
w=$(echo "scale=0; $HIGH_RES_W / $factor" | bc)
h=$(echo "scale=0; $HIGH_RES_H / $factor" | bc)
x=$(echo "scale=0; ($HIGH_RES_W - $w) / 2" | bc)
y=$(echo "scale=0; ($HIGH_RES_H - $h) / 2" | bc)
echo "<${x},${y},${w},${h}>"
;;
esac
else
# 低倍率模式 (4096x3072)
case "${factor}" in
1.0|1) echo "${ZOOM_1_0X}" ;;
2.0|2) echo "${ZOOM_2_0X}" ;;
3.0|3) echo "<1024,768,1365,1024>" ;;
4.0|4) echo "${ZOOM_4_0X}" ;;
5.0|5) echo "<1638,1228,819,614>" ;;
6.0|6) echo "${ZOOM_6_0X}" ;;
*)
# 动态计算低分辨率zoom
local w h x y
w=$(echo "scale=0; $LOW_RES_W / $factor" | bc)
h=$(echo "scale=0; $LOW_RES_H / $factor" | bc)
x=$(echo "scale=0; ($LOW_RES_W - $w) / 2" | bc)
y=$(echo "scale=0; ($LOW_RES_H - $h) / 2" | bc)
echo "<${x},${y},${w},${h}>"
;;
esac
fi
}
# 判断当前分辨率模式
get_sensor_mode() {
local factor="$1"
local is_high
is_high=$(echo "$factor > $SAT_THRESHOLD" | bc -l)
if [ "$is_high" -eq 1 ]; then
echo "HIGH(4624x2608)"
else
echo "LOW(4096x3072)"
fi
}
# 构建GStreamer管道
build_pipeline() {
local zoom="$1"
local mode="$2"
local port="$3"
local src="qtiqmmfsrc focus-mode=${FOCUS_MODE} camera=${CAMERA_ID} zoom=${zoom} name=camsrc"
local caps="video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT},framerate=${FRAMERATE}/1"
local encode="qtic2venc ! h264parse"
case "$mode" in
rtsp)
echo "${src} ! ${caps} ! ${encode} ! rtspclientsink location=rtsp://0.0.0.0:${port}/live"
;;
*)
echo "${src} ! ${caps} ! ${encode} ! mp4mux ! queue ! filesink location=${OUTPUT_FILE}"
;;
esac
}
# 固定zoom录制
start_recording() {
local zoom_param="$1"
local duration="$2"
local output="$3"
echo "========================================"
echo " 固定Zoom录制"
echo "========================================"
echo "相机ID: ${CAMERA_ID} (逻辑相机/SAT)"
echo "输出分辨率: ${WIDTH}x${HEIGHT}@${FRAMERATE}fps"
echo "Zoom: ${zoom_param}"
echo "SAT阈值: ${SAT_THRESHOLD}x"
echo "输出文件: ${output}"
if [ -n "${duration}" ]; then
echo "录制时长: ${duration}秒"
else
echo "录制时长: 无限 (按Ctrl+C停止)"
fi
echo "========================================"
if [ ! -d "$(dirname "${output}")" ]; then
echo "错误: 输出目录不存在"
exit 1
fi
local pipeline
pipeline=$(build_pipeline "$zoom_param" "record")
echo "执行命令:"
echo "${pipeline}"
echo ""
if [ -n "${duration}" ]; then
timeout "${duration}" bash -c "${pipeline}"
else
eval "${pipeline}"
fi
local ret=$?
if [ $ret -eq 0 ] || [ $ret -eq 124 ]; then
echo ""
echo "录制完成: ${output}"
if [ -f "${output}" ]; then
ls -lh "${output}"
fi
else
echo ""
echo "录制失败"
exit 1
fi
}
# zoom连续变化录制 (sweep模式)
sweep_zoom() {
local speed="$1"
local output="$2"
echo "========================================"
echo " Zoom连续变化录制 (1.0x -> 24.0x)"
echo "========================================"
echo "变化范围: 1.0x ~ 24.0x"
echo "每档停留: ${speed}秒"
echo "输出文件: ${output}"
echo "========================================"
if [ ! -d "$(dirname "${output}")" ]; then
echo "错误: 输出目录不存在"
exit 1
fi
# 定义zoom变化序列 (从1.0x到24.0x)
local zoom_factors=(1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.3 6.5 7.0 7.5 8.0 9.0 10.0 11.0 12.0 14.0 16.0 18.0 20.0 22.0 24.0)
# 分段录制,每段一个zoom值,最后合并
local temp_dir
temp_dir="/data/zoom_sweep_$$"
mkdir -p "${temp_dir}"
local segment_idx=0
for factor in "${zoom_factors[@]}"; do
local zoom_param sensor_mode segment_file
zoom_param=$(get_zoom_by_factor "$factor")
sensor_mode=$(get_sensor_mode "$factor")
segment_file="${temp_dir}/seg_${segment_idx}.mp4"
echo ""
echo "[$(date '+%H:%M:%S')] Zoom: ${factor}x | Sensor: ${sensor_mode} | 录制${speed}秒"
local pipeline
pipeline="gst-launch-1.0 -e \
qtiqmmfsrc focus-mode=${FOCUS_MODE} camera=${CAMERA_ID} zoom=${zoom_param} name=camsrc \
! video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT},framerate=${FRAMERATE}/1 \
! qtic2venc \
! h264parse \
! mp4mux \
! queue \
! filesink location=${segment_file}"
timeout "${speed}" bash -c "${pipeline}" 2>/dev/null
if [ -f "${segment_file}" ] && [ -s "${segment_file}" ]; then
echo " 片段${segment_idx}完成: ${factor}x"
else
echo " 片段${segment_idx}失败或为空: ${factor}x"
fi
segment_idx=$((segment_idx + 1))
sleep 0.5
done
# 合并所有片段
echo ""
echo "正在合并${segment_idx}个片段..."
# 创建concat列表文件
local concat_list
concat_list="${temp_dir}/list.txt"
true > "${concat_list}"
for i in $(seq 0 $((segment_idx - 1))); do
local seg
seg="${temp_dir}/seg_${i}.mp4"
if [ -f "$seg" ] && [ -s "$seg" ]; then
echo "file '${seg}'" >> "${concat_list}"
fi
done
if [ -s "${concat_list}" ]; then
if ffmpeg -f concat -safe 0 -i "${concat_list}" -c copy "${output}" -y 2>/dev/null; then
if [ -f "${output}" ]; then
echo "合并完成: ${output}"
ls -lh "${output}"
fi
else
echo "合并失败,保留分段文件在: ${temp_dir}"
fi
else
echo "没有有效的片段文件"
fi
# 清理临时文件
rm -rf "${temp_dir}"
echo "Zoom连续变化录制完成"
}
# 自动档位遍历录制 (step模式)
step_zoom() {
local delay="$1"
local output="$2"
echo "========================================"
echo " Zoom档位遍历录制"
echo "========================================"
echo "遍历档位: 1.0x, 2.0x, 4.0x, 6.0x, 6.3x, 8.0x, 12.0x, 16.0x, 24.0x"
echo "每档停留: ${delay}秒"
echo "输出文件: ${output}"
echo "========================================"
if [ ! -d "$(dirname "${output}")" ]; then
echo "错误: 输出目录不存在"
exit 1
fi
local zoom_factors=(1.0 2.0 4.0 6.0 6.3 8.0 12.0 16.0 24.0)
local temp_dir
temp_dir="/data/zoom_step_$$"
mkdir -p "${temp_dir}"
local segment_idx=0
for factor in "${zoom_factors[@]}"; do
local zoom_param sensor_mode segment_file
zoom_param=$(get_zoom_by_factor "$factor")
sensor_mode=$(get_sensor_mode "$factor")
segment_file="${temp_dir}/seg_${segment_idx}.mp4"
echo ""
echo "[$(date '+%H:%M:%S')] 档位 ${factor}x | Sensor: ${sensor_mode} | 录制${delay}秒"
local pipeline
pipeline="gst-launch-1.0 -e \
qtiqmmfsrc focus-mode=${FOCUS_MODE} camera=${CAMERA_ID} zoom=${zoom_param} name=camsrc \
! video/x-raw,format=NV12,width=${WIDTH},height=${HEIGHT},framerate=${FRAMERATE}/1 \
! qtic2venc \
! h264parse \
! mp4mux \
! queue \
! filesink location=${segment_file}"
timeout "${delay}" bash -c "${pipeline}" 2>/dev/null
if [ -f "${segment_file}" ] && [ -s "${segment_file}" ]; then
echo " 片段完成"
fi
segment_idx=$((segment_idx + 1))
sleep 0.5
done
# 合并片段
echo ""
echo "正在合并${segment_idx}个片段..."
local concat_list
concat_list="${temp_dir}/list.txt"
true > "${concat_list}"
for i in $(seq 0 $((segment_idx - 1))); do
local seg
seg="${temp_dir}/seg_${i}.mp4"
if [ -f "$seg" ] && [ -s "$seg" ]; then
echo "file '${seg}'" >> "${concat_list}"
fi
done
if [ -s "${concat_list}" ]; then
if ffmpeg -f concat -safe 0 -i "${concat_list}" -c copy "${output}" -y 2>/dev/null; then
if [ -f "${output}" ]; then
echo "合并完成: ${output}"
ls -lh "${output}"
fi
else
echo "合并失败,保留分段文件在: ${temp_dir}"
fi
fi
rm -rf "${temp_dir}"
echo "Zoom档位遍历录制完成"
}
# RTSP实时观察
rtsp_preview() {
local zoom_param="$1"
local port="$2"
echo "========================================"
echo " RTSP实时观察"
echo "========================================"
echo "Zoom: ${zoom_param}"
echo "RTSP端口: ${port}"
echo "========================================"
local ip pipeline
ip=$(hostname -I | awk '{print $1}')
pipeline=$(build_pipeline "$zoom_param" "rtsp" "$port")
echo "RTSP地址: rtsp://${ip}:${port}/live"
echo "VLC播放: vlc rtsp://${ip}:${port}/live"
echo "ffplay: ffplay rtsp://${ip}:${port}/live"
echo ""
echo "按Ctrl+C停止"
echo "========================================"
eval "${pipeline}"
}
# ==================== 主程序 ====================
# 默认值
MODE="record"
ZOOM_PARAM="${DEFAULT_ZOOM}"
DURATION=""
SWEEP_SPEED=3
STEP_DELAY=5
RTSP_PORT=8554
# 解析模式参数
if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
MODE="$1"
shift
fi
while [[ $# -gt 0 ]]; do
case "$1" in
-z|--zoom)
ZOOM_PARAM=$(get_zoom_by_factor "$2")
shift 2
;;
-s|--speed)
SWEEP_SPEED="$2"
shift 2
;;
-d|--delay)
STEP_DELAY="$2"
shift 2
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-t|--time)
DURATION="$2"
shift 2
;;
-p|--port)
RTSP_PORT="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo "未知选项: $1"
show_help
exit 1
;;
esac
done
# 检查bc命令
if ! command -v bc &> /dev/null; then
echo "错误: 需要安装bc (基本计算器)"
echo "安装: apt-get install bc 或 opkg install bc"
exit 1
fi
# 检查ffmpeg (sweep/step模式需要)
if [ "$MODE" == "sweep" ] || [ "$MODE" == "step" ]; then
if ! command -v ffmpeg &> /dev/null; then
echo "警告: 未找到ffmpeg,合并功能将不可用"
echo "建议安装: apt-get install ffmpeg"
fi
fi
# 执行对应模式
case "$MODE" in
record)
start_recording "${ZOOM_PARAM}" "${DURATION}" "${OUTPUT_FILE}"
;;
sweep)
sweep_zoom "${SWEEP_SPEED}" "${OUTPUT_FILE}"
;;
step)
step_zoom "${STEP_DELAY}" "${OUTPUT_FILE}"
;;
rtsp)
rtsp_preview "${ZOOM_PARAM}" "${RTSP_PORT}"
;;
*)
echo "未知模式: $MODE"
show_help
exit 1
;;
esac