前言
"淡入淡出"功能,这是纯 PCM 操作,零新依赖,实现简单却很实用------裁剪后的音频经常需要淡入淡出避免爆音,是音频后处理的基础能力。
本文介绍实现的关键点。
效果图:


功能概述
- 淡入(Fade In):音频开头从静音逐渐增大到正常音量
- 淡出(Fade Out):音频结尾从正常音量逐渐减小到静音
- 曲线类型:支持线性、等功率(指数)、S 曲线(余弦)三种渐变形状
- 非破坏性:不修改当前 PCM 数据,处理结果另存为 WAV,并支持播放试听对比
核心算法
本质是对 interleaved PCM 采样数据做幅度包络处理:
- 淡入:前 N 帧的所有通道采样乘以一个从
0 → 1递增的系数 - 淡出:后 N 帧的所有通道采样乘以一个从
1 → 0递减的系数
曲线函数
三种曲线的数学表达:
cpp
// 根据曲线类型计算增益系数,t ∈ [0, 1]
static double fadeCurveValue(double t, const QString &curveType)
{
if (curveType == QStringLiteral("exponential")) {
// 等功率曲线(正弦弧):听感上比线性更平滑
return std::sin(M_PI * 0.5 * t);
}
if (curveType == QStringLiteral("sc")) {
// S 曲线(余弦弧):首尾变化慢、中间变化快,最自然
return 0.5 * (1.0 - std::cos(M_PI * t));
}
// 默认线性:系数与时间成正比
return t;
}
| 曲线 | 公式 | 听感 |
|---|---|---|
| 线性 | f(t) = t |
均匀变化,简单直接 |
| 等功率 | f(t) = sin(πt/2) |
更平滑,专业音频常用 |
| S 曲线 | f(t) = (1 - cos(πt)) / 2 |
首尾柔和,最自然 |
C++ 后端实现
接口声明
在 AudioToolProcessor 中新增方法,遵循项目统一的无状态工具函数风格:
cpp
// 纯 PCM 操作,不引入任何新依赖,输出格式与输入完全一致
bool applyFadeInOut(const QByteArray &inputPcm,
const QVariantMap &inputInfo,
int fadeInMs, // 淡入时长(ms),0 表示跳过
int fadeOutMs, // 淡出时长(ms),0 表示跳过
const QString &curveType,
QByteArray *outputPcm,
QVariantMap *outputInfo,
QVariantMap *fadeInfo,
QString *errorText) const;
关键实现逻辑
cpp
bool AudioToolProcessor::applyFadeInOut(...) const
{
// 1. 解析 PCM 格式(采样率、通道数、位深)
const int sampleRate = inputInfo.value("sampleRate").toInt();
const int channels = inputInfo.value("channels").toInt();
const SampleSpec sampleSpec = sampleSpecFromInfo(inputInfo);
// 2. 毫秒 → frame 数(以 frame 为单位保证多通道对齐)
const qint64 fadeInFrames = fadeInMs * sampleRate / 1000;
const qint64 fadeOutFrames = fadeOutMs * sampleRate / 1000;
// 3. 防重叠:淡入 + 淡出 > 总帧数时,淡入优先,缩短淡出
if (safeFadeInFrames + safeFadeOutFrames > frameCount)
effectiveFadeOutFrames = frameCount - safeFadeInFrames;
// 4. 淡入:前 N 帧,每帧所有通道 × 递增系数
for (qint64 f = 0; f < safeFadeInFrames; ++f) {
const double t = (double)f / (safeFadeInFrames - 1);
const double coeff = fadeCurveValue(t, curve);
for (int ch = 0; ch < channels; ++ch) {
// 读取采样 → 乘以系数 → 钳位 → 写回
double value = readSample(...);
writeSample(..., qBound(min, value * coeff, max));
}
}
// 5. 淡出:后 N 帧,每帧所有通道 × 递减系数(逻辑对称)
...
}
要点:
- 以 frame 为单位遍历(而非 sample),确保多通道对齐不被破坏
- 支持 8-bit unsigned / 16-bit signed / 32-bit signed 三种位深
- 淡入淡出区间重叠时淡入优先,避免短音频异常
门面层串联
MediaAnalyzer 负责 QML 状态管理,exportFadeWav 串联处理和保存:
cpp
bool MediaAnalyzer::exportFadeWav(int fadeInMs, int fadeOutMs,
const QString &curveType, const QString &filePath)
{
// 调用 AudioToolProcessor 执行淡入淡出 PCM 处理
const bool processOk = m_toolProcessor.applyFadeInOut(
m_pcmData, currentPcmInfo, fadeInMs, fadeOutMs, curveType,
&processedPcm, &pcmInfo, &fadeInfo, &errorText);
if (processOk) {
// 处理成功后封装保存为 WAV
ok = m_toolProcessor.savePcmAsWav(outputPath, processedPcm, pcmInfo, &errorText);
}
if (ok) {
setFadeInfo(fadeInfo);
// 关键:将处理后的 PCM 存入预览属性,供 QML 波形播放控件试听
setFadedPcm(processedPcm, pcmInfo);
}
}
为了让 QML 侧能播放处理后的音频,新增了 fadedPcmData / fadedPcmSize / fadedPcmInfo 三个属性,与已有的 denoisedPcmData 预览模式一致。
QML 前端
参数控制
曲线类型三选一按钮 + 淡入/淡出时长滑块(0~5000ms):
qml
Repeater {
model: [
{ key: "linear", label: "线性" },
{ key: "exponential", label: "等功率" },
{ key: "sc", label: "S 曲线" }
]
delegate: Rectangle {
color: page.curveType === modelData.key ? accentColor : "#071526"
Text { text: modelData.label }
MouseArea { onClicked: page.curveType = modelData.key }
}
}
波形对比 + 播放试听
使用两个 WaveformPlayer 组件并排展示,自带播放/暂停/进度条/音量控制:
qml
GridLayout {
columns: width >= 820 ? 2 : 1
// 左侧:原始 PCM 波形 + 播放原声
WaveformPlayer {
title: "处理前(原始 PCM)"
pcmData: mediaAnalyzer.pcmData
pcmSize: mediaAnalyzer.pcmSize
previewKey: "fade_before"
}
// 右侧:处理后 PCM 波形 + 播放淡入淡出效果
WaveformPlayer {
title: "处理后(淡入淡出)"
pcmData: mediaAnalyzer.fadedPcmData
pcmSize: mediaAnalyzer.fadedPcmSize
previewKey: "fade_after"
}
}
WaveformPlayer 内部调用 preparePcmPreviewWav 将 PCM 写入临时 WAV 文件,再由 MediaPlayer 播放,实现即点即听。
架构总结
QML (FadePage.qml)
│ 用户设置 fadeInMs / fadeOutMs / curveType
▼
MediaAnalyzer::exportFadeWav()
│ 调度 + 状态管理 + 保存 WAV
▼
AudioToolProcessor::applyFadeInOut()
│ 纯 PCM 采样点遍历 × 渐变系数
▼
输出:fadedPcmData (预览) + _fade.wav (导出)
| 层级 | 文件 | 职责 |
|---|---|---|
| 工具层 | AudioToolProcessor |
纯 PCM 数据处理,无状态 |
| 门面层 | MediaAnalyzer |
QML 状态管理、异步调度、文件 I/O |
| 界面层 | FadePage.qml |
参数输入、波形展示、播放试听 |
一句话总结
淡入淡出是最简单的音频 DSP 功能之一------遍历 PCM 采样乘以渐变系数即可。关键在于以 frame 为单位保证通道对齐、支持多种曲线形状、以及通过预览 PCM 属性实现处理后即时试听对比。