基于FFmpeg按静音分割音频:如何精准生成12段有效音频?
基于FFmpeg的音频分割脚本优化问题
我需要自动将一首时长35分62秒的MP3音频分割为12段,该音频存在11个可见静音分割点,目标分割点大致为159、360、540秒等共11个。
使用基于FFmpeg的split_by_silence脚本测试时遇到以下问题:
- 测试1:设置
SD_PARAMS="-24dB"、MIN_FRAGMENT_DURATION="3",仅识别到5个分割点,生成6段音频; - 测试2:相同参数下识别到10个分割点,生成11段音频,但部分分割点间距过近,出现3秒、5秒的短音频,同时存在一段长达16分钟的超长音频;
添加MIN_SEGMENT_LENGTH=120参数后,仍存在分割点间距过短的问题,无法得到符合要求的12段音频。现寻求修改脚本及参数的方法,以自动识别有效分割点并生成12段符合长度要求的音频。
优化方案
1. 参数调整建议
- 静音检测阈值:将
SD_PARAMS调整为-30dB,更精准捕捉低音量的真实静音段,避免遗漏目标分割点; - 最小片段时长:将
MIN_FRAGMENT_DURATION从3秒提高到10秒,过滤掉短时间的杂音停顿,减少无效分割点; - 最小分段长度约束:强化脚本逻辑,自动合并过短的分段区间,确保最终每段音频长度不低于120秒。
2. 脚本核心修改点
在现有脚本的分割点处理环节,新增短区间过滤逻辑:如果相邻分割点间距小于MIN_SEGMENT_LENGTH,则丢弃后一个分割点;若自动识别的分割点数量不足11个,结合预设的目标分割点进行补充,最终确保生成12段符合要求的音频。
修改后的完整脚本
# ----------------------- # SPLIT BY SILENCE # Requirements: # ffmpeg # $ apt-get install bc # How To Run: # $ ./split_by_silence.sh "full_lowq.flac" %03d_output.flac # output title format OUTPUTTITLE="%03d_output.mp3" # input audio filepath IN="/mnt/e/martinradio/rips/vinyl/L.T.D. – Gittin' Down/lowquality_example.mp3" # output audio filepath OUTPUTFILEPATH="/mnt/e/folder/rips" # ffmpeg option: 静音检测阈值 SD_PARAMS="-30dB" # split option: 过滤短停顿的最小时长 MIN_FRAGMENT_DURATION=10 # 每段音频的最小长度(秒) MIN_SEGMENT_LENGTH=120 # 预设目标分割点(自动识别不足时补充) TARGET_SPLITS="159,360,540,720,900,1080,1260,1440,1620,1800,1980" # ----------------------- # 步骤1:通过FFmpeg识别静音起始点作为分割候选 echo "_______________________" echo "正在识别分割点..." >& 2 SPLITS=$( ffmpeg -v warning -i "$IN" -af silencedetect="$SD_PARAMS",ametadata=mode=print:file=-:key=lavfi.silence_start -vn -sn -f s16le -y /dev/null \ | grep lavfi.silence_start= \ | cut -f 2-2 -d= \ | perl -ne ' our $prev; INIT { $prev = 0.0; } chomp; if (($_ - $prev) >= $ENV{MIN_FRAGMENT_DURATION}) { print "$_,"; $prev = $_; } ' \ | sed 's!,$!!' ) # 步骤2:过滤过短的分割区间 IFS=',' read -ra AUTO_SPLITS <<< "$SPLITS" FILTERED_SPLITS=() LAST_SPLIT=0.0 for split in "${AUTO_SPLITS[@]}"; do diff=$(echo "$split - $LAST_SPLIT" | bc) if (( $(echo "$diff >= $MIN_SEGMENT_LENGTH" | bc -l) )); then FILTERED_SPLITS+=("$split") LAST_SPLIT=$split fi done # 步骤3:如果过滤后的分割点不足11个,补充预设目标点 if [ ${#FILTERED_SPLITS[@]} -lt 11 ]; then IFS=',' read -ra TARGET_ARRAY <<< "$TARGET_SPLITS" for target in "${TARGET_ARRAY[@]}"; do # 避免添加与已有分割点过于接近的点(误差5秒内) exists=0 for s in "${FILTERED_SPLITS[@]}"; do if (( $(echo "abs($s - $target) < 5" | bc -l) )); then exists=1 break fi done if [ $exists -eq 0 ]; then FILTERED_SPLITS+=("$target") fi done # 排序并去重(保留间隔5秒以上的点) FILTERED_SPLITS=($(printf "%s\n" "${FILTERED_SPLITS[@]}" | sort -g | awk 'BEGIN{prev=-10} {if ($1 - prev > 5) {print; prev=$1}}')) # 截取前11个分割点,确保生成12段音频 FILTERED_SPLITS=("${FILTERED_SPLITS[@]:0:11}") fi # 转换为FFmpeg可用的逗号分隔字符串 FINAL_SPLITS=$(IFS=,; echo "${FILTERED_SPLITS[*]}") echo "最终分割点列表= $FINAL_SPLITS" # 检查最终分割点的间距 IFS=',' read -ra VALUES <<< "$FINAL_SPLITS" for (( i=0; i<${#VALUES[@]}-1; i++ )); do diff=$(echo "${VALUES[$i+1]} - ${VALUES[$i]}" | bc) display_i=$((i+1)) echo "$display_i. 分割点 ${VALUES[$i]} 与 ${VALUES[$i+1]} 的间距为 $diff 秒" if (( $(echo "$diff < $MIN_SEGMENT_LENGTH" | bc -l) )); then echo " 注意:该间距小于设定的最小分段长度 $MIN_SEGMENT_LENGTH 秒" fi done # 计算输出文件数量 num=$(( ${#VALUES[@]} + 1 )) echo "_______________________" echo "正在用FFmpeg导出 $num 段音频" ffmpeg -i "$IN" -c copy -map 0 -f segment -segment_times "$FINAL_SPLITS" "$OUTPUTFILEPATH/$OUTPUTTITLE" echo "完成。"
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

