拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于MATLAB的语音变声仿真:基频与共振峰变换原理与实现

简介MATLAB语音变声仿真程序面向信号处理初学者与语音算法研究人员围绕男女声互换需求完整演示了从音频读取、预加重、分帧加窗、特征提取、参数变换到语音重构造的常用流程。压缩包共6个文件覆盖2个m格式源码文件、1个fig格式界面文件和3个wav格式测试音频整体仅656KB结构轻量适合快速运行与二次修改。其中GUI界面文件用于交互操作主程序文件包含核心变声算法辅助脚本可用于预处理或结果分析wav样本便于直接验证效果。已有462人学习下载适合用来理解基频、频谱包络等声学参数在变声中的具体作用。通过运行源码可以直观对比男女声转换前后的差异掌握audioread、FFT、逆变换及audiowrite等关键函数的使用思路为进一步开发实时变声或个性化音色处理提供基础参考。1. 变声不是变调为什么先用MATLAB搭语音变声仿真很多人拿到变声程序的第一反应是把语音文件播放速度拉快结果男声变女声变成一串“花栗鼠”叫女声变男声像开了0.5倍速的醉汉。真正的语音变声要同时修改两个物理量声带振动决定的基频以及声道形状决定的频谱包络。只改前者声音像捏着嗓子说话只改后者音高不对照样穿帮。这套 VoiceChange 源码把两个变换做进了一个 MATLAB GUI点开 VoiceChange.fig 就能选 wav、调参数、听变声结果同时还能在 axes 里看到频谱变化。源码内的 nv2.wav、n6.wav、T.wav 三个样本覆盖了男声和女声典型区间适合做课程设计演示也适合想快速验证变声算法的工程师当脚手架。下面从信号参数开始拆。2. 基频与共振峰男女声互换的两个特征坐标系动手写代码前先建立坐标系。基频是时间轴上的值单位 Hz描述声带每秒开合次数共振峰是频率轴上的峰单位也是 Hz描述声道对声音的放大位置。男性的声道平均 17cm 长共振峰天然偏低女性的声道平均 13cm 长共振峰整体上移。所以男变女时基频要抬升共振峰也要抬升女变男则反过来。下表是常见典型值也是调参起点。指标男声典型范围女声典型范围基频 F085-180 Hz165-255 Hz第一共振峰 F1400-700 Hz700-1200 Hz第二共振峰 F2900-1500 Hz1400-2200 Hz2.1 基频估计自相关窗口里的周期线索基频估计最稳的起点是自相关。把一帧语音与它的延迟版本做内积周期处的相关值会明显高于周围。MATLAB 的 xcorr 一次能返回全延迟的相关序列剩下的问题是怎么从峰值里挑出真实基频。常见的坑是二次谐波比基波能量还高直接用 findpeaks 会把二次谐波当基频因此必须设置 MinPeakDistance把搜索范围限制在最低基频周期附近。function f0 get_f0(x, fs) frameLen round(0.03 * fs); % 帧长30ms覆盖两到三个周期 frameShift round(0.01 * fs); % 帧移10ms平滑过渡 frames buffer(x, frameLen, frameLen - frameShift, nodelay); f0 zeros(size(frames, 2), 1); for i 1:size(frames, 2) seg frames(:, i) - mean(frames(:, i)); [r, lags] xcorr(seg, coeff); mid ceil(length(lags) / 2); r r(mid1:end); % 只保留正延迟 [~, loc] findpeaks(r, MinPeakHeight, 0.3, ... MinPeakDistance, floor(fs/300)); if isempty(loc) f0(i) 0; else f0(i) fs / loc(1); end end end这段代码里buffer 把音频切成长 30ms、间隔 10ms 的帧矩阵xcorr 返回归一化自相关。MinPeakHeight 取 0.3用来过滤噪声帧MinPeakDistance 设为 fs/300对应最小可接受基频 300Hz。处理童声或女高音时把 300 改成 500否则二次谐波会抢先成为第一个峰。另外清音段没有周期性f0 会返回 0后续变调必须跳过这些帧否则静音和噪声会被强行赋予音高听感变成“水声”。实际项目里可以用短时能量加过零率先做清浊音判决这里为了保持脚本简单我一般只在变调循环里判断if f0(i) 50。2.2 频谱包络用LPC搬动共振峰基频确定后第二个要处理的是频谱包络。包络可以理解成把频谱上的锯齿状谐波磨平后留下的轮廓轮廓上的局部极大值对应共振峰。MATLAB 的 lpc 函数是估计包络的捷径全极点滤波器的频率响应就是包络估计阶数取 fs/10002 是经验值44100Hz 采样率对应 22 阶足够分辨四个共振峰。之所以不用 MFCC是因为 MFCC 是倒谱域表示作为特征合适但要从 MFCC 重构声道滤波器需要先做逆 DCT多绕一圈反而损失精度。function [a, env] lpc_env(frame, order) frame frame .* hamming(length(frame)); [a, g] lpc(frame, order); [H, ~] freqz(g, a, 512, whole); env 20*log10(abs(H)); endlpc 返回的 a 是 AR 模型分母系数g 是增益。freqz 用 512 点绘制整个 Nyquist 频段响应取 dB 就是包络。加窗不能省裸信号直接丢给 lpc 会在帧边缘产生截断毛刺。拿到系数后做共振峰搬移本质是对 LPC 多项式求根再把根的幅角乘一个缩放因子。function a_new shift_formants(a, shift_factor) r roots(a); pole_idx abs(r) 0.99; r_pole r(pole_idx); for k 1:length(r_pole) ang angle(r_pole(k)) * shift_factor; r_pole(k) abs(r_pole(k)) * exp(1j * ang); end r(pole_idx) r_pole; a_new poly(r); endshift_factor 大于 1 时共振峰上移对应声道变短小于 1 时下移对应声道变长。这里有个必须成对处理的细节实系数多项式的根是共轭复数对只移动单个根poly 生成的新系数会带虚部生成的滤波器就不再是实系数了。对 44100Hz 采样率共振峰搬移量一般控制在 ±20% 以内搬移过大时声道模型会过冲输出频谱出现不自然的窄峰听感像金属喇叭。3. VoiceChange.m 主链路从audioread到重合成现在把基频和包络组装成一条完整链路。VoiceChange.m 的核心不是某个高端数学函数而是把读取、分帧、变换、合成四个阶段按顺序组织起来。下面按阶段展开。3.1 分帧加窗与短时傅里叶变换先读文件并统一采样率。wav 可能是双声道先转单声道采样率不是 44100 就 resample否则后续频点计算全偏。然后选 1024 点汉宁窗帧移 256即 75% 重叠。重叠的目的是让重叠相加合成时满足 COLA 条件避免输出幅度调制。[x, fs] audioread(nv2.wav); if size(x, 2) 1, x mean(x, 2); end x resample(x, 44100, fs); fs 44100; win hann(1024, periodic); hop 256; X stft(x, fs, Window, win, OverlapLength, 768, FFTLength, 1024);stft 要求 MATLAB R2019a 以上老版本可以用[X, ~, ~] spectrogram(x, win, 768, 1024, fs);得到同样结构的复数谱矩阵。OverlapLength 等于窗长减 hop768。stft 返回的 X 第一维是频率点索引第 k 行对应 k/1024*fs Hz这个频率轴在后续插值里要用。3.2 频谱插值与基频缩放有了复数谱变换在频域做。最直观的参数变换是频率轴映射把第 k 个频点的能量搬到 k/alpha 处alpha 大于 1 时频谱向高频拉伸基频升高alpha 小于 1 时压缩基频降低。直接用 interp1 对幅度谱插值相位另做处理。下表给出三个参数的取值方向方便对照。参数男变女女变男说明alpha 基频倍率1.4-1.80.5-0.7直接乘到瞬时频率上shift_factor 共振峰倍率1.1-1.30.8-0.9作用于 LPC 根幅角时长伸缩比1.01.0变声不改变语速alpha 1.6; % 男变女 [numBins, numFrames] size(X); freq_axis (0:numBins-1) * fs / (2 * (numBins-1)); % 单边频率轴 freq_new freq_axis / alpha; Y zeros(numBins, numFrames); for i 1:numFrames mag abs(X(:, i)); ph angle(X(:, i)); mag_new interp1(freq_axis, mag, freq_new, linear, 0); ph_new ph .* alpha; % 粗相位缩放 Y(:, i) mag_new .* exp(1j * ph_new); endinterp1 最后一个参数 0 表示插值域外补零避免频谱拉伸后高频区残留镜像。粗相位缩放速度快缺点是相位不连续轻则声音发虚重则出现金属共振。这里的 alpha 不只作用在频率轴还隐含着相位累积速度。如果只把它用在幅度上而相位不跟进声音会像磁带加速又减速。3.3 相位累加与istft重合成更稳的写法是相位声码器先估计每帧的瞬时频率再累加相位。瞬时频率来自相邻帧相位差减去期望相位增量关键是把相位差折叠到 [-pi, pi]否则 2pi 的整数倍会被误判为巨大偏移。Nfft 1024; omega 2 * pi * (0:numBins-1). * hop / Nfft; phase_acc angle(X(:, 1)); Y zeros(numBins, numFrames); for i 2:numFrames dphi angle(X(:, i)) - angle(X(:, i-1)) - omega; dphi mod(dphi pi, 2*pi) - pi; % 相位差分取主值 inst_freq omega dphi; % 原帧瞬时频率 phase_acc phase_acc inst_freq * alpha; % 按变调因子累加 Y(:, i) abs(X(:, i)) .* exp(1j * phase_acc); end这段代码的时间轴推进速度被 alpha 缩放例如 alpha1.6 时相位增量放大听觉上基频升高同时帧与帧之间相位连续不会断裂。alpha 同时作用于频谱插值和瞬时频率因此输出时长不变、音高变化这就是变声和变速的本质区别。[y, t] istft(Y, fs, Window, win, OverlapLength, 768, FFTLength, 1024); y y / max(abs(y)) * 0.9; audiowrite(output.wav, y, fs);istft 内部做重叠相加前提是窗函数满足加权一致条件。最后除以峰值再乘 0.9是留出 10% 净空防止后续滤波或叠加时削波。容易被忽略的是静音段相位累加会赋予静音噪声一个确定音高导致句首句尾出现电流噪声。我在工程版本里会在变换前加一个能量门限把低于阈值的帧幅度压到接近零否则三个样本里 T.wav 这种带尾音的测试文件输出末尾会特别明显。4. GUI回调与辅助脚本把变声算法变成可交互工具命令行脚本跑通后把它装进 GUI 是让非专业同学也能用的关键。VoiceChange.fig 在 GUIDE 里打开能看到典型布局上方波形显示 axes下方频谱显示 axes左侧是文件选择按钮、参数滑杆和变声方向单选组。VoiceChange.m 里每个控件都有对应回调共享同一个 handles 结构体。4.1 VoiceChange.fig 的界面结构与回调绑定GUIDE 里控件回调通过属性面板的 Callback 绑定到 m 文件同名函数。例如打开文件按钮 Tag 是 btnLoad回调就是 btnLoad_Callback。回调四参数依次是控件句柄、事件数据、handles 结构体、备用参数。handles 是 GUIDE 的标准数据通道用来在回调之间传递音频数据。如果你用较新版本 MATLABApp Designer 是更现代的选择但 GUIDE 的老项目仍然大量存在VoiceChange.fig 这类文件也只能靠 GUIDE 打开。function btnLoad_Callback(hObject, eventdata, handles) [file, path] uigetfile(*.wav, 选择语音文件); if isequal(file, 0), return; end fullpath fullfile(path, file); [x, fs] audioread(fullpath); handles.x x; handles.fs fs; guidata(hObject, handles); plot(handles.axes1, (0:length(x)-1)/fs, x); xlabel(handles.axes1, Time (s)); enduigetfile 返回文件名和路径audioread 读音频。guidata 这一行不能少只有调用它handles 里新增的字段才会写回 GUI 主存储区。plot 时显式指定父坐标轴句柄避免手滑把波形画到频谱 axes 上。如果你在 GUIDE 里删过 axes 重建Tag 会变成 axes2 之类回调里要同步修改这个错误经常导致“画了半天图不显示”。4.2 Untitled2.m 可能承担的工作批量导出或预加重源码包里的 Untitled2.m 按文件名看是早期没整理的辅助脚本。这种脚本在变声项目里最常见的两种任务一是对样本做预加重二是批量跑对比实验。预加重用 y(n)x(n)-0.97*x(n-1) 把高频抬起来变换完成后再用逆滤波压回去。我习惯把辅助脚本写成批量处理把 nv2、n6、T 三个 wav 一次性转换并打印基频变化统计比在 GUI 里手点三遍直观得多。files {nv2.wav, n6.wav, T.wav}; results table(); for k 1:length(files) [x, fs] audioread(files{k}); y voice_transform(x, fs, 1.5); [~, name] fileparts(files{k}); audiowrite([name _female.wav], y, fs); results(k).file name; results(k).f0_in median(get_f0(x, fs)); results(k).f0_out median(get_f0(y, fs)); end disp(results);table 类型从 R2013b 起可用老版本可以用 cell 存结果。这段脚本的价值是把实验变成可复现过程换一组参数能马上看到三个样本的基频变化是否落在目标区间。比如 nv2 是男声f0_in 大约 120Hzf0_out 应该到 180-200Hz如果只有 150说明 alpha 没生效要回主程序查是不是把清音帧也做了缩放。4.3 完整的按钮回调从滑杆到播放处理按钮是 GUI 的终点。它从 handles 取音频、从滑杆读参数、执行变声并显示频谱。滑杆 Value 属性是 double在 GUIDE 属性面板把 Min 设为 0.5、Max 设为 2.0默认 1.5。function btnProcess_Callback(hObject, eventdata, handles) if ~isfield(handles, x) || isempty(handles.x) errordlg(请先选择音频文件, 输入错误); return; end x handles.x; fs handles.fs; alpha get(handles.sliderAlpha, Value); y voice_transform(x, fs, alpha); handles.y y; guidata(hObject, handles); sound(y, fs); spectrogram(y, 1024, 768, 1024, fs, yaxis); endisfield 判断用户是否已加载文件避免空指针。sound 播放是异步的播放完才会返回。spectrogram 直接画到当前 axes如果 GUI 里有多个 axes建议显式指定坐标轴句柄否则点击过其他坐标轴之后输出会跑到错误位置。这里没有把变声方向男变女/女变男映射到 alpha 上实际使用时可以加一个弹出菜单选择后用不同系数组合覆盖滑杆值。5. 参数归一化与听感验证别让音量、时长毁了你的变声最后一部分是参数调整的实操经验。变声系统里基频偏移量、共振峰搬移量和输出音量是三个互相牵连的量调一个不看另外两个结果一定奇怪。5.1 基频偏移量与共振峰系数怎么配男变女时基频乘 1.4-1.8共振峰乘 1.1-1.3女变男时基频乘 0.5-0.7共振峰乘 0.8-0.9。基频倍率高于共振峰倍率太多输出像卡通人物反过来则像低频沙哑的低音炮。建议第一次运行先用 alpha1.5、shift_factor1.2 这套默认组再按听感微调。5.2 用spectrogram实时验证共振峰迁移听感是主观的频谱图是客观的。把原音频和输出音频画在同一张图的上下两侧看暗色横带也就是共振峰位置是否整体移动。subplot(2,1,1); spectrogram(x, 1024, 768, 1024, fs, yaxis); title(Original); subplot(2,1,2); spectrogram(y, 1024, 768, 1024, fs, yaxis); title(Shifted);如果共振峰搬移后背景出现连续亮线说明 LPC 阶数太低噪声被建模成了共振峰。把阶数提高到 30 重新试。如果共振峰搬了但基频原封不动去检查相位累加模块里是不是把 alpha 写成了局部变量曾经见过有同学在主函数定义 alpha子函数里又在 shadow 一个新 alpha结果 GUI 里怎么拖滑杆都没反应。5.3 时长不变与处理伪影的取舍相位声码器在 alpha 偏离 1 过大时会有明显混响感alpha 大于 2 后基本变成机器人。如果只做基频变换不搬共振峰可以用重采样再压缩时长的替代方案先把整个音频用 resample 变调再用动态时间规整把帧数压缩回原时长。实际工程里我更倾向把 alpha 做成随基频变化的函数低音区偏移大高音区偏移小alpha_adapt alpha0 ./ (1 f0 / 500);这样男声较低基频段偏移量最大较高音高段逐步回归原音避免高频区过度拉伸产生金属声。再把 alpha_adapt 替换掉 3.3 节里的固定 alpha并绕过 unvoiced 帧输出能保留更多说话情绪这是我在处理 n6.wav 这类自带气声样本时总结出来的经验。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门