前言
重采样是音频处理流水线中的基础能力------不同来源的音频素材采样率、位深和通道数各异,许多后续功能(降噪、拼接、格式转换等)都依赖统一的 PCM 格式才能正确工作。AudioTools 的重采样功能支持同时调整三个维度:
| 参数 | 可选值 | 说明 |
|---|---|---|
| 采样率 | 8000 / 11025 / 16000 / 22050 / 32000 / 44100 / 48000 / 96000 Hz | 控制每秒采样点数 |
| 位深 | 8 / 16 / 32 bit | 控制每个采样值的精度和动态范围 |
| 通道数 | 1(单声道)/ 2(立体声)/ 4(四声道) | 控制独立声道数量 |
重采样是一个破坏性操作 ------成功后当前 PCM 会被替换为新数据,后续所有功能(波形、分析、保存、导出)都基于重采样后的结果。这一点与大多数非破坏性功能(降噪、淡入淡出等)不同。

功能流程
text
用户解码 PCM → ResamplePage 选择输出参数(采样率/位深/通道数)
↓
点击「执行重采样」
↓
MediaAnalyzer::resampleCurrentPcm()
↓
┌── 同步调用 ──┐
│ SwrContext 创建:输入格式 → 输出格式
│ packed PCM → swr_convert → 输出 PCM
│ 刷出内部延迟残留样本
└──────────────┘
↓
setPcmData(newPcm) ← 覆盖当前 PCM
↓
setPcmData 自动清空所有依赖旧 PCM 的分析结果
↓
后续功能均基于新 PCM 工作
关键特点:
- 覆盖式操作 :重采样成功后
setPcmData()替换当前 PCM,所有依赖旧 PCM 的分析结果和预览缓存被自动清空。 - 同步执行:重采样处理速度快(纯 SwrContext 转换),不引入异步调度。
- 链式处理基础:裁剪、二次重采样、保存 WAV 等操作都以当前 PCM 为输入,重采样可以作为流水线的中间环节。
核心算法
位深与采样格式映射
SampleSpec 结构体统一描述 PCM 采样格式,避免各函数重复解析:
cpp
struct SampleSpec {
AVSampleFormat format = AV_SAMPLE_FMT_NONE;
int bitsPerSample = 0;
int bytesPerSample = 0;
QString name;
};
sampleSpecFromBits() 将常用位深映射为 FFmpeg 采样格式:
| 位深 | AVSampleFormat | 字节数 | 名称 |
|---|---|---|---|
| 8 bit | AV_SAMPLE_FMT_U8 |
1 | u8(unsigned) |
| 16 bit | AV_SAMPLE_FMT_S16 |
2 | s16(signed) |
| 32 bit | AV_SAMPLE_FMT_S32 |
4 | s32(signed) |
SwrContext 重采样管线
FFmpeg 的 SwrContext(libswresample)同时处理采样率转换、位深转换和通道数转换,三者可以在一次调用中完成。
通道布局:按通道数使用 FFmpeg 默认布局,不做自定义映射:
cpp
av_channel_layout_default(&inputLayout, inputChannels);
av_channel_layout_default(&outputLayout, outputChannels);
- 1 通道 → mono
- 2 通道 → stereo
- 4 通道 → 4.0
SwrContext 创建 :输入输出格式一次性指定,swr_init 内部自动选择最优重采样算法:
cpp
swr_alloc_set_opts2(&swrContext,
&outputLayout, outputSpec.format, outputSampleRate, // 输出
&inputLayout, inputSpec.format, inputSampleRate, // 输入
0, nullptr);
swr_init(swrContext);
输出缓冲计算
重采样后帧数会因采样率变化而缩放,且 SwrContext 内部存在延迟,必须预留足够空间:
cpp
// 输出样本数 = (输入样本数 + swr 内部延迟) × (输出采样率 / 输入采样率),向上取整
const int64_t outputSamples64 = av_rescale_rnd(
swr_get_delay(swrContext, inputSampleRate) + inputSamples64,
outputSampleRate, inputSampleRate, AV_ROUND_UP);
swr_get_delay 返回 SwrContext 内部缓冲的延迟样本数(以输入采样率为单位),确保输出缓冲区不会因延迟样本溢出。
残留样本刷出
swr_convert 主调用完成后,SwrContext 内部可能仍有少量延迟样本(尤其是采样率变化时),需要循环刷出:
cpp
// 主转换
const int convertedSamples = swr_convert(swrContext,
outputData, outputSamples, inputData, inputSamples64);
// 刷出残留数据
while (true) {
const int flushedSamples = swr_convert(swrContext,
flushData, 1024, nullptr, 0); // 输入为空,只刷输出
if (flushedSamples <= 0) break;
convertedPcm.append(flushed);
}
不刷出残留会导致音频尾部被截断,在高采样率转低采样率时尤其明显。
整数溢出保护
输入帧数超过 int 范围时会拒绝处理,避免 SwrContext 参数溢出:
cpp
if (inputSamples64 > std::numeric_limits<int>::max()) {
*errorText = "Input PCM data is too large to resample at once.";
return false;
}
主处理函数
AudioToolProcessor::resamplePcm
位于 src/audio/audiotoolprocessor.cpp,无状态、无 UI 依赖,只负责 PCM 格式转换。
cpp
bool AudioToolProcessor::resamplePcm(
const QByteArray &inputPcm,
const QVariantMap &inputInfo,
int outputSampleRate,
int outputBitsPerSample,
int outputChannels,
QByteArray *outputPcm,
QVariantMap *outputInfo,
QString *errorText) const
处理步骤:
- 输入校验:检查 PCM 非空、格式字段有效、PCM 大小与格式匹配。
- 格式映射 :
sampleSpecFromInfo(inputInfo)解析输入格式,sampleSpecFromBits(bits)构建输出格式。 - SwrContext 创建与初始化:指定输入/输出的通道布局、采样格式和采样率。
- 输出缓冲分配 :
av_rescale_rnd计算输出样本数并考虑内部延迟。 - 主转换 :
swr_convert一次性转换全部输入样本(packed interleaved 格式,只需一个平面指针)。 - 残留刷出 :循环调用
swr_convert(nullptr, 0)直到内部缓冲清空。 - 截断与输出 :
truncate(convertedSamples × channels × bytesPerSample)精确截取有效数据,组装输出信息。
输出信息包含新 PCM 的完整格式描述:
cpp
resultInfo.insert("format", outputSpec.name); // "u8" / "s16" / "s32"
resultInfo.insert("sampleRate", outputSampleRate);
resultInfo.insert("channels", outputChannels);
resultInfo.insert("channelLayout", channelLayoutName); // "mono" / "stereo" / "4.0"
resultInfo.insert("bitsPerSample", outputSpec.bitsPerSample);
resultInfo.insert("size", convertedPcm.size());
resultInfo.insert("sizeText", formatBytes(convertedPcm.size()));
导出封装
MediaAnalyzer::resampleCurrentPcm
QML 调用入口,直接同步调用 resamplePcm,成功后覆盖当前 PCM:
cpp
bool MediaAnalyzer::resampleCurrentPcm(int sampleRate, int bitsPerSample, int channels)
{
if (m_pcmData.isEmpty()) {
setStatus("请先解码 PCM");
return false;
}
setBusy(true);
const QVariantMap currentPcmInfo = m_mediaInfo.value("pcm").toMap();
const bool ok = m_toolProcessor.resamplePcm(
m_pcmData, currentPcmInfo,
sampleRate, bitsPerSample, channels,
&resampledPcm, &pcmInfo, &errorText);
if (ok) {
// 更新 mediaInfo 中的 pcm 字段
QVariantMap info = m_mediaInfo;
info.insert("pcm", pcmInfo);
setMediaInfo(info);
// 覆盖当前 PCM ------ 这一步会触发级联状态清理
setPcmData(resampledPcm);
setStatus("重采样完成:" + pcmSummary());
} else {
setStatus("重采样失败:" + errorText);
}
setBusy(false);
return ok;
}
注意 setPcmData() 不仅更新 PCM 数据,还会更新 m_mediaInfo["pcm"] 中的格式信息,确保后续功能读取到正确的采样率、位深和通道数。
QML 页面调用
ResamplePage 直接传递三个下拉框的当前值:
javascript
ActionButton {
text: "执行重采样"
enabled: !mediaAnalyzer.busy && mediaAnalyzer.pcmSize > 0
onClicked: mediaAnalyzer.resampleCurrentPcm(
parseInt(sampleRateBox.currentText),
parseInt(bitsBox.currentText),
parseInt(channelsBox.currentText))
}
下拉框的默认值通过 appRoot.currentPcmValue() 读取当前 PCM 的实际参数,用户不改参数时执行等同于"原样重采样"(不改变数据)。
状态清理
重采样的状态管理是项目中最严格的------因为它是破坏性 PCM 操作,成功后当前 PCM 被替换,所有依赖旧 PCM 的结果都变为过期数据。
setPcmData() 内部统一清空以下状态:
| 被清空的状态 | 说明 |
|---|---|
volumeInfo |
音量分析(Peak/RMS/dBFS) |
audioFeature* |
音频特征图所有数据 |
silenceInfo |
静音段检测结果 |
trimInfo |
裁剪结果 |
smoothTrimInfo |
平滑截取结果 |
splitInfo |
声道拆分结果 |
convertInfo |
格式转换结果 |
extractAudioInfo |
视频提取音频结果 |
concatInfo |
拼接结果 |
lufsInfo |
LUFS 响度分析结果 |
speedInfo |
变速结果 |
denoiseInfo + denoisedPcmData |
降噪结果和预览 |
fadeInfo + fadedPcmData |
淡入淡出结果和预览 |
vocalExtractInfo |
人声提取结果 |
phaseInvertInfo + PCM |
相位反转预览 |
monoMixInfo + PCM |
单声道混合预览 |
pitchInfo + PCM |
音调调整预览 |
normalizeInfo + PCM |
响度归一化预览 |
filterInfo + PCM |
高低通滤波预览 |
dcOffsetInfo + PCM |
DC 偏移校正预览 |
reverbInfo |
混响效果结果 |
这一设计保证了 UI 不会出现"旧结果 + 新 PCM"的不一致状态------用户在重采样后看到的任何信息都基于最新的 PCM 数据。
小结
重采样功能是音频处理流水线的格式桥梁:
- 算法层 :
AudioToolProcessor::resamplePcm利用 FFmpegSwrContext一次性完成采样率、位深和通道数三个维度的转换,并正确处理内部延迟和残留刷出。 - 状态层 :
MediaAnalyzer::resampleCurrentPcm同步调用后通过setPcmData()覆盖当前 PCM,触发级联状态清理,确保所有下游功能不会读取过期数据。 - UI 层 :
ResamplePage提供三组下拉框选择输出参数,默认值跟随当前 PCM 的实际参数,操作简洁。 - 破坏性语义:重采样是少数会覆盖当前 PCM 的功能之一(另一个是增益/归一化),页面顶部注释明确提醒用户"后续功能均基于重采样结果"。