SoundWise 转写前先用 ffmpeg 统一音频格式 / 把批量文件压成同一套参数

文章导读
素材来自手机、录音笔和录屏时,容器和采样率往往各不相同,直接上传会出现有的能传、有的被拒的情况。把格式统一这件事放在转写之前做,能把“文件本身参数不一致”这个变量先消掉,再去看平台侧的报错;判断是否需要这么做,可以先跑一遍 ffprobe 看参数分散到什么程度,如果同一批素材里采样率、声道、编码都有两三种以上,统一一次通常比逐个重传更省事。
📋 目录
  1. 一 用 ffprobe 逐个读出容器、编码、采样率、声道
  2. 二 写一条单文件 ffmpeg 转码模板命令
  3. 三 用 shell 循环把模板套到整个目录
  4. 四 抽检处理后的时长、音量与首尾完整性
  5. 五 按统一命名顺序上传并记录失败文件
A A

素材来自手机、录音笔和录屏时,容器和采样率往往各不相同,直接上传会出现有的能传、有的被拒的情况。把格式统一这件事放在转写之前做,能把“文件本身参数不一致”这个变量先消掉,再去看平台侧的报错;判断是否需要这么做,可以先跑一遍 ffprobe 看参数分散到什么程度,如果同一批素材里采样率、声道、编码都有两三种以上,统一一次通常比逐个重传更省事。

先统一、再上传。用 ffprobe 把每个素材的容器、编码、采样率、声道列成清单,写一条 ffmpeg 模板命令固定输出参数,再用 shell 循环套到整个目录;处理完抽检时长、音量和首尾是否完整,最后按统一命名顺序上传并记录失败文件。这只解决格式差异带来的问题,如果报错指向时长上限、权限或文件损坏,仍需回到平台侧确认。

用 ffprobe 逐个读出容器、编码、采样率、声道

先建立一份素材参数清单,目的不是看某个文件好不好,而是判断这批素材能不能用一条模板命令覆盖。ffprobe 随 ffmpeg 一起安装,下面的骨架对单个文件读取音频流的编码、采样率和声道,同时把容器格式和时长一起打出来:

ffprobe -v error -select_streams a:0 \
  -show_entries stream=codec_name,sample_rate,channels,bit_rate:format=format_name,duration \
  -of csv=p=0 "input.m4a"

输出中用逗号分隔的字段依次对应编码、采样率、声道、码率、容器、时长。要重点看三类:sample_rate 是否只有 8000、16000、44100、48000 这几种常见值;channels 是 1 还是 2;codec_name 是 aac、mp3、pcm_s16le 还是 opus。如果某个文件 select_streams a:0 读不到任何一行,说明它可能没有音频流,或本身就是损坏文件,这类文件不适合继续往批量流程里塞。

把结果整理成表格最方便对比,一行一个文件:

  • 列:文件名、容器、编码、采样率、声道、时长、备注
  • 备注列写“无音频流”“时长异常短”“疑似截断”等人工判断
  • 可以在上面的命令外面套一层 for f in ./raw/*; do ... done,把文件名也打出来,例如 echo "== $f"; ffprobe ...

清单出来之后,采样率和声道种类越少,后面的模板命令越简单;如果出现 8 kHz 和 48 kHz 混在一起,统一到同一档位基本是必要的。

写一条单文件 ffmpeg 转码模板命令

模板命令的目标是“进来什么都能出同一套参数”。输入用 -i 放在输出路径之前,输出路径放在最后,顺序写反 ffmpeg 会直接报错。下面是一条可复用的骨架,输出成单声道 16 kHz 的 WAV:

ffmpeg -hide_banner -nostdin -i "input.m4a" \
  -vn -map 0:a:0 \
  -c:a pcm_s16le -ar 16000 -ac 1 \
  -map_metadata -1 \
  -y "output.wav"

参数含义:-vn 丢掉视频流,录屏素材里这一项很关键;-map 0:a:0 只取第一条音轨,避免多音轨文件输出成带有意外轨道的文件;-ar 指定采样率,-ac 指定声道数,这两个是统一参数的核心;-map_metadata -1 清掉原文件里的元信息,减少不同来源带来的额外差异;-y 覆盖同名输出,方便重复执行。具体输出编码和采样率要按转写平台实际接受的范围来定,可以先拿一个文件试转再确认。

执行后不要直接进批量,先用同样的 ffprobe 命令对输出文件再跑一遍,对比采样率、声道、编码是否和模板一致。如果输出仍然带着原采样率,通常是把 -ar 写在了 -i 前面,或者被后续的 -c:a copy 覆盖了。

SoundWise 转写前先用 ffmpeg 统一音频格式 / 把批量文件压成同一套参数

用 shell 循环把模板套到整个目录

单文件确认没问题后,用 for 循环套到整个目录。循环里先判断输出是否已存在,避免重复转码浪费时间:

in_dir="./raw"; out_dir="./norm"
mkdir -p "$out_dir"
for f in "$in_dir"/*; do
  [ -f "$f" ] || continue
  base=$(basename "$f"); stem="${base%.*}"
  out="$out_dir/${stem}.wav"
  if [ -s "$out" ]; then echo "skip: $out"; continue; fi
  ffmpeg -hide_banner -nostdin -loglevel error -i "$f" \
    -vn -map 0:a:0 -c:a pcm_s16le -ar 16000 -ac 1 \
    -map_metadata -1 -y "$out" || echo "fail: $f"
done

命名规则建议保留原主文件名、只替换扩展名,这样本地文件和原始素材能直接对上号;如果不同目录下有重名文件,可以在前面加目录前缀或批次号。跳过判断用 [ -s "$out" ] 检查文件存在且非空,比只看是否存在更稳妥,因为中途失败的 ffmpeg 也可能留下一个 0 字节文件。-loglevel error 让循环输出干净一些,但失败时必须靠 || echo 把文件名打出来,否则会悄悄漏掉。

抽检处理后的时长、音量与首尾完整性

批量跑完不能只看“没有报错”,要抽检几个文件确认没有被截断或转成静音。时长对比用 ffprobe 分别读原始文件和输出文件的 duration,再相减,差值明显偏大就说明有问题:

ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "a.m4a"
ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "norm/a.wav"

音量查看用 volumedetect 滤镜,读 mean_volume 和 max_volume:

ffmpeg -hide_banner -nostdin -i "norm/a.wav" -af volumedetect -f null - 2>&1 | grep -E "mean_volume|max_volume"

如果 max_volume 显示为一个很低的负值,或者报出整段无音频,这个文件多半是静音输出。首尾完整性可以截取开头和结尾各几秒单独听一下,或者用 silencedetect 看是否整段被识别为静音。不合格文件的处理步骤是:回到原始文件,核对 ffprobe 清单里这一行的参数,用它单独重跑模板命令;如果原文件本身就短、就是静音,或在 ffprobe 阶段已经显示无音频流,那就把它列入失败清单,不要在批量目录里反复重试。

按统一命名顺序上传并记录失败文件

上传前先固定命名规则,例如“批次号_原文件名”,并统一扩展名,这样目录里的顺序和材料顺序一致。上传顺序按文件名排序执行即可,例如 ls norm/*.wav | sort | while read f; do ...,把统一的文件名作为唯一标识贯穿整个流程,避免出现“上传成功了但不知道对应哪个原始素材”的情况。

失败清单需要记录的字段建议包括:本地原始路径、统一后的文件名、文件大小、转码后时长、上传返回的提示信息、失败时间,以及是否已重试。有了这几列,重传时可以直接按清单筛选,不需要再把整个目录重新过一遍;把这份清单和处理前的 ffprobe 参数表放在一起,后续再遇到同类素材,也能快速判断是格式问题还是文件本身的问题。