1. soxr vs swr 重采样器
FFmpeg 的 libswresample 默认使用内置的 SWR 引擎进行音频重采样。此外还支持通过编译选项 --enable-libsoxr 集成 SoX Resampler (soxr) 作为替代引擎。
| 特性 | 内置 SWR | soxr |
|---|---|---|
| 重采样质量 | 良好,适合大多数场景 | 更高,适合对音频质量敏感的场景 |
| 性能 | 较快 | 略慢(高质量滤波器更长) |
| 编译依赖 | 无,FFmpeg 自带 | 需要额外编译 libsoxr |
适用场景:对重采样质量有较高要求的音频处理流程,例如专业音频制作、科学信号处理、需要与其他高质量重采样库对齐结果的场景等。
2. 四端编译 soxr
从 SourceForge 下载 soxr 0.1.3 源码,使用 CMake 编译静态库。
macOS (Fat 库,支持 arm64 + x86_64)
bash
for ARCH in $ARCHS; do
echo "building soxr for $ARCH..."
BUILD_DIR="$SHELL_DIR/soxr-build-$ARCH"
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
cmake "$SOXR_SRC" -DCMAKE_OSX_ARCHITECTURES="$ARCH" \
-DCMAKE_INSTALL_PREFIX="$THIN_SOXR/$ARCH" \
-DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release \
|| exit 1
cmake --build . -j8 || exit 1
cmake --install . || exit 1
cd -
rm -rf "$BUILD_DIR"
done
# 合并 Fat 库
lipo -create "$THIN_SOXR/arm64/lib/libsoxr.a" "$THIN_SOXR/x86_64/lib/libsoxr.a" \
-output "$FAT_SOXR/lib/libsoxr.a"
iOS
bash
if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ]; then
SOXR_SDK="iphonesimulator"
else
SOXR_SDK="iphoneos"
fi
SOXR_SYSROOT=$(xcrun --sdk $SOXR_SDK --show-sdk-path)
cmake "$SOXR_SRC" \
-DCMAKE_OSX_ARCHITECTURES="$ARCH" \
-DCMAKE_OSX_SYSROOT="$SOXR_SYSROOT" \
-DCMAKE_OSX_DEPLOYMENT_TARGET="$DEPLOYMENT_TARGET" \
-DCMAKE_INSTALL_PREFIX="$THIN_SOXR/$ARCH" \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTS=OFF \
-DCMAKE_BUILD_TYPE=Release
Android
bash
cmake "$SOXR_SRC" \
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \
-DANDROID_ABI=${ANDROID_ABI} \
-DANDROID_PLATFORM=${ANDROID_PLATFORM} \
-DCMAKE_INSTALL_PREFIX=${SOXR_INSTALL_DIR} \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_OPENMP=OFF \
-DBUILD_TESTS=OFF \
-DBUILD_EXAMPLES=OFF
Windows
bash
cmake -G "Ninja" -S "${SOXR_SRC}" -B "${basepath}/soxr-build" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTS=OFF \
-DBUILD_SHARED_RUNTIME=OFF \
-DCMAKE_INSTALL_PREFIX=${basepath}/soxr_install
Windows 注意事项 :soxr 的 CMakeLists.txt 最低版本要求为 3.1,不支持
CMAKE_MSVC_RUNTIME_LIBRARY。MT/MD 的切换只能通过 soxr 自身提供的BUILD_SHARED_RUNTIME选项控制(OFF= MT,ON= MD)。
编译完成后,在 FFmpeg 的 configure 中添加:
bash
--enable-libsoxr --extra-cflags="-I${SOXR_INSTALL}/include" --extra-ldflags="-L${SOXR_INSTALL}/lib"
静态库打包 :如果 soxr 和 FFmpeg 都编译为静态库,需要将
libsoxr.a的目标文件合并到libswresample.a中,否则下游链接时会出现未定义符号错误。
3. 使用 soxr 引擎
在创建 SwrContext 后、swr_init 之前(或首次 swr_convert_frame 前),通过 AVOption 设置引擎及参数:
cpp
SwrContext* swr = swr_alloc();
av_opt_set_int(swr, "resampler", SWR_ENGINE_SOXR, 0); // 切换到 soxr 引擎
av_opt_set_double(swr, "precision", 33.0, 0); // 重采样精度(bit)
av_opt_set_double(swr, "cutoff", 0.84, 0); // 通带截止频率
以上参数配置下的重采样效果与 Python resampy 库的默认重采样结果一致。
可以通过运行时检测当前 FFmpeg 是否编译了 soxr 支持:
cpp
bool isSoxrSupported() {
return strstr(swresample_configuration(), "enable-libsoxr") != nullptr;
}
4. 注意事项:soxr flush 阶段的已知问题
使用 swr_convert_frame + SWR_ENGINE_SOXR 复用输出 AVFrame 时,如果 output buffer 容量不足,SwrContext 内部会累积未消耗的输入数据(in_buffer)。在最后 flush 阶段,FFmpeg 的 soxr 集成层(soxr_resample.c)会先向 soxr 发送输入终结信号,再尝试处理 in_buffer 残留数据,导致 soxr 在终结状态下收到输入而崩溃。内置 SWR 引擎不受此影响。
规避方法 :每次 swr_convert_frame 调用前,通过 swr_get_out_samples 获取预期输出量,确保 output buffer 容量足够,使 in_buffer 始终为空:
cpp
if (swr_is_initialized(swrCtx)) {
int expected = swr_get_out_samples(swrCtx, in_frame->nb_samples); // flush 时传 0
reserveFrame(expected); // 仅在 expected > capacity 时才重新分配
}
该问题属于 FFmpeg soxr_resample.c 的集成缺陷,后续计划尝试从 FFmpeg 源码层面修复。