资讯动态

MATLAB语音变声实战:基频、共振峰与相位声码器的独立调节

发布时间:2026/9/14 14:50:56 来源:尧图企业网站定制
简介这是一份基于MATLAB的语音变声仿真程序面向语音信号处理学习者和MATLAB开发者通过改变基频、频谱包络等声学参数实现男声与女声之间的互换。程序集成了信号采集、预加重、分帧加窗、FFT/MFCC参数提取、模型构建、参数变换及逆变换重建全流程涉及基频修改、谐波调整、频谱搬移等关键方法适合用于课程设计、毕业设计或语音处理入门实操。资源包共6个文件以.m主程序、.fig交互界面和.wav测试音频为主整体大小仅656KB轻量易部署。VoiceChange.m包含核心变声算法VoiceChange.fig提供可视化操作界面另有两个辅助脚本和三段示例音频可直接运行并对比处理前后效果。目前已有462人学习下载借助完整源码与测试样本可快速理解语音变声实现原理动手调整参数观察声音变化掌握基于频谱包络与基频映射的变声思路也可作为扩展开发的基础。1. 做MATLAB语音变声时最容易踩的坑是把VoiceChange当成“调高音”做MATLAB语音变声时最容易踩的坑是把VoiceChange当成“调音量、调音高”这种单参数操作。人耳判断一段声音是男是女、是老是小靠的是三个独立物理量基频F0决定音调高低共振峰位置决定音色语速决定语气。如果只是把整个信号频率平移听到的一定是语速跟着变的“花栗鼠”而不是自然的变声效果。MATLAB在这条链路里有天然优势audioread负责读入、resample负责重采样、lpc与filter可以把声道模型拆开几个基础函数就能搭出一条可解释、可复现的变声管线。这套内容适合要做课程设计、语音前端预处理或想把手动变声做成可调参数的工程师。目标是把基频、共振峰、时长三个维度独立调节并给出能直接运行的代码和参数边界。2. 用MATLAB把语音拆成基频和共振峰先看懂变声改的是哪个量2.1 定标与统一采样率audioread之后的三个预处理任何变声脚本都从读文件开始。先约定输入格式避免后面每个环节都出低级错误。[x, fs] audioread(input.wav); % 返回 double范围约 [-1, 1] if size(x, 2) 1 x mean(x, 2); % 双声道取平均合成单声道 end x x / max(abs(x)); % 归一化到 [-1, 1] x resample(x, 16000, fs); % 统一采样率到 16 kHz fs 16000;这段代码是每次实验的前置条件。audioread对16-bit WAV返回的本来就是[-1,1]的double不需要再除以32768归一化是为了防止后续lpc或者freqz计算中动态范围过大。双声道语音的左右声道能量不完全一致只取左声道会丢掉信息取平均对变声来说足够稳定。统一到16kHz的原因有两个语音基频范围在80到400Hz16kHz的奈奎斯特频率余量充足且后续LPC阶数、帧长、hop等参数都可以按固定采样率写死避免每个文件都要重新推算。这里特别强调降采样一定要用resample而不是x(1:2:end)这种抽取写法resample内置抗混叠滤波裸抽取会把高频噪声折叠进基频估计范围表现为F0曲线上出现大量假峰。提示audioread和resample来自基础MATLAB与Signal Processing Toolbox。如果目标机器缺工具箱后续所有代码里用到的hann窗可以用 0.5 - 0.5cos(2pi*(0:N-1)/(N-1)) 手工生成不必纠结matlab下载安装的版本差异。2.2 用自相关法跟踪基频把F0画成曲线在动手改变声之前必须先能测量基频。MATLAB有Audio Toolbox的pitch函数但为了不依赖额外的License工程上最通用的还是短时自相关。语音是准周期信号基频周期就是自相关函数第一个明显峰值对应的延迟。实现思路是分帧、加窗、去直流、计算自相关、在延迟范围里找峰值。function f0 track_f0(x, fs) frame_len round(0.03*fs); % 30 ms 帧长 hop round(0.01*fs); % 10 ms 帧移 n length(x); nf floor((n - frame_len) / hop) 1; f0 zeros(nf, 1); fmin 80; fmax 400; % 语音基频搜索范围 minlag round(fs/fmax); % 最短延迟对应 400 Hz maxlag round(fs/fmin); % 最长延迟对应 80 Hz for i 1:nf idx (i-1)*hop 1 : (i-1)*hop frame_len; seg x(idx) - mean(x(idx)); % 去直流 r xcorr(seg, maxlag, coeff); r r(maxlag1 : end); % 取非负延迟半边 r(1:minlag) 0; % 短延迟段置零避免零点伪峰 [~, lag] max(r); f0(i) fs / lag; end f0(f0 fmin | f0 fmax) 0; % 越界视为无声 end这段函数的逻辑核心是延迟与频率的倒数关系。xcorr的结果在正负延迟方向对称取非负延迟的半边把延迟小于minlag的位置清零是为了避免把自相关在零点附近的高值当成基频周期否则低音男声的F0会被错误估计成几百赫兹。帧长取30ms而非10ms是因为最低基频80Hz对应12.5ms周期帧内至少要包含两个完整周期才能形成稳定峰值。静音帧的F0会被置零因为静音段自相关峰值非常随机画曲线时表现为毛刺分析变声效果时只看有声段的中位数即可。2.3 用LPC分离激励和声道共振峰就是LPC包络的峰基频由声带振动频率决定共振峰则是声道形状的滤波效果。LPC线性预测编码把声道近似成全极点IIR滤波器对一帧语音x_framelpc函数返回系数a用filter(a,1,x_frame)得到的残差就是预测误差反映的是声带激励与共振峰形状无关。x filter([1 -0.95], 1, x); % 预加重抬高高频能量 p round(fs / 1000) 2; % LPC 阶数16k 取 18 a lpc(x_frame, p); % 每帧一组 a 系数a(1)1 e_frame filter(a, 1, x_frame); % 去白化得到残差激励 [hh, w] freqz(1, a, 512, fs); % 声道频率响应预加重的作用是补偿语音本身低频集中、高频衰减的特性让LPC更关注高频共振峰而不是只拟合低频能量。阶数与采样率的关系是每1kHz带宽约取1到2阶16kHz采样对应14到18阶男生声道长、共振峰间隔近阶数可取下限女生声道短、共振峰间隔大取上限更稳。freqz(1,a)画出的包络里前两个凸起就是第一和第二共振峰这是性别感的直接来源。结合2.2节看F0是频谱图上细密梳齿的间距共振峰是包络凸起的位置变声时动的是两套独立系统。梳理成一张表后续选参直接查采样率LPC阶数 p适用场景8 kHz8~10电话语音、低带宽编码16 kHz14~18语音变声通用实验44.1 kHz20~24带音乐或高音质素材p取太小包络太平滑三个共振峰会糊成一个包p取太大会把基频的谐波梳状结构也包进去搬移极点时把谐波当共振峰变声结果出现“机器人音”。表里的范围是工程经验值而不是硬性约束。到这里“拆解”这一步已经完成手里有了原始信号、F0曲线、残差序列和声道滤波器系数第3章开始动手改。3. 只动基音不动时长MATLAB里重采样与相位声码器的变声组合3.1 一行resample就能升调但代价是语速跟着变最简单的VoiceChange在MATLAB里其实是一条命令resample(x, 2, 3)。这个调用的输出采样点数是输入的三分之二时长压缩为原来的三分之二以相同采样率回放时信号里的所有频率都变成原来的1.5倍。基频从120Hz变到180Hz听感就是“声音变尖、语速变快”。x_fast resample(x, 2, 3); % 时间×2/3频率×3/2 sound(x_fast, fs);这里最容易出现的参数方向错误就是把resample的p/q当成频率缩放比。resample(p,q)的语义是输出采样数与输入采样数的比值p/q小于1表示信号变短频率整体升高所以p/q是时间缩放比音高比是它的倒数。这种方案适合需要“语速快、音调高”的卡通话角色但它的副作用是三个物理量被耦合在一起变。如果目标是男声变自然女声、语速不能变就必须在重采样之后把时长拉回去。3.2 相位声码器做时域拉伸把语速拉回原速把语速拉回去的经典做法是相位声码器Phase Vocoder。它的思路是保持每帧频谱的幅度不变只改变帧与帧之间的时间间隔并通过相位的累积传播来维持频点之间的连续性。下面这个实现可以直接保存成time_stretch_pv.m参数已固定并注释。function y time_stretch_pv(x, hop_a, hop_s, N) % 简化相位声码器用分析帧间相位差推导合成相位 win hann(N, periodic); nf floor((length(x) - N) / hop_a); y zeros((nf-1)*hop_s N, 1); norm_win zeros(size(y)); freq_res N/2 1; phase_acc zeros(freq_res, 1); % 合成侧累积相位 prev_pha zeros(freq_res, 1); for i 1:nf idx_a (i-1)*hop_a (1:N); sp fft(x(idx_a) .* win); mag abs(sp(1:freq_res)); pha angle(sp(1:freq_res)); delta mod(pha - prev_pha pi, 2*pi) - pi; phase_acc phase_acc delta * (hop_s / hop_a); sp2 mag .* exp(1j*phase_acc); sp2 [sp2; conj(sp2(end-1:-1:2))]; % 恢复共轭对称 frm real(ifft(sp2)) .* win; idx_s (i-1)*hop_s (1:N); y(idx_s) y(idx_s) frm; norm_win(idx_s) norm_win(idx_s) win.^2; prev_pha pha; end y y ./ (norm_win eps); end逻辑上要理解三个点。第一每帧fft后只需要保存一半频谱合成时用共轭对称恢复实信号数据量减半且能保证ifft结果是实数。第二delta是把相邻帧的相位差折叠到[-pi,pi]乘以hop_s/hop_a就是在合成时间轴上按比例推进相位这个比例本质上就是时间伸缩比。第三最后用win.^2的累积和做归一化等效于WOLA重叠相加能消掉非整数倍伸缩时出现的幅度起伏。这个简版不做瞬时频率估计和相位锁定声音会带一点金属感对变声demo足够如果追求更干净把N加长到2048会降低相位误差但瞬态轮廓会变糊。主调用组合起来是这样的ratio 1.5; % 希望基频乘 1.5 x_s resample(x, 2, 3); % 先把音高抬 1.5 倍 x_v time_stretch_pv(x_s, 256, 384, 1024); % 拉回时长x_s的时长是原来的2/3time_stretch_pv的伸缩比是hop_s/hop_a即384/2561.5两者相乘正好回到原长。基频则保持抬高了1.5倍。这组代码的价值在于把“变调”和“变速”两个维度解耦了。3.3 变调参数怎么设hop、帧长与频率分辨率的取舍参数不是随便给的下面的表是16kHz采样率下的经验值区间。参数推荐值影响备注hop_a25616ms时间分辨率越小瞬态越清晰但相位噪声越大hop_s384对应1.5倍时间伸缩比hop_s/hop_a要精确等于伸缩比N102464ms频率分辨率越大频率越细但时间模糊越重窗型hann(N,periodic)频谱泄漏不要用矩形窗N和hop必须联动N越大频率分辨率越高但相位声码器把时间轴拉伸时长帧内的多个事件会被平均掉辅音爆破感变弱。要让改变的目标音高精确对应最省事的办法是先把变调比写成有理数比如3/2、4/3再让hop_s等于hop_a乘以这个比值避免帧对齐出现累积余数。无理数伸缩不是不能做只是中小型工程不值得处理边界帧。相位声码器处理后的高频金属感可以在预处理时对信号做7kHz低通代价是s音变闷但基频升高的部分主要在低频段听感上收益大于损失。4. 共振峰也搬家用LPC声道模型做男声与女声互转4.1 为什么男变女只抬基音不够共振峰是性别感的另一半成年男性声道长约17cm女性约13cm声道长度直接决定共振峰位置。F0从120Hz乘2到240Hz听感上仍然是个男声因为共振峰还留在原来的位置。所以真正可用的变声方案要把基音和音色分开两条链路基音由残差的重采样控制音色由共振峰位置搬移控制。LPC把声道建模成全极点滤波器极点频率就是共振峰位置因此搬移共振峰等价于搬移极点的极角。poles roots(a); % a 是 LPC 系数 r abs(poles); th angle(poles); th th * 1.2; % 共振峰频率整体抬高 20% r min(r, 0.99); % 限制极点不越出单位圆 a_new poly(r .* exp(1j*th)); % 由新极点还原系数 a_new real(a_new) / a_new(1);LPC系数必须是实系数所以poly之后要取实部再按a(1)归一化。极角乘1.2意味着所有共振峰频率都乘1.2这正好模拟声道缩短的效应。r限制到0.99是为了防止搬移后某些极点因数值误差落在单位圆外导致合成信号发散成爆音。搬移因子过大时原本接近奈奎斯特频率的高频极点会被折返听感像劣质电话语音参数上限在4.3节给出。4.2 一个可跑的MATLAB变声脚本残差重采样加极点搬移加重叠相加4.2.1 LPC分析到残差把2.3和3.2的思路串成完整函数。函数同时接收音高比f0_ratio和共振峰比fm_ratio返回变声后的信号。function y voice_change(x, fs, f0_ratio, fm_ratio) frame_len round(0.025*fs); % 帧长 25 ms hop round(0.005*fs); % 帧移 5 ms p round(fs/1000) 2; % LPC 阶数 win hann(frame_len, periodic); x x(:) / max(abs(x)); x filter([1 -0.95], 1, x); % 预加重 x [x; zeros(frame_len, 1)]; % 尾部补零避免最后一段截断 nf floor((length(x) - frame_len) / hop); % 第一阶段逐帧 LPC 分析求残差并重叠相加 e zeros(size(x)); norm_e zeros(size(x)); a_all zeros(nf, p1); for i 1:nf idx (i-1)*hop (1:frame_len); xf x(idx) .* win; a lpc(xf, p); a_all(i, :) a; e(idx) e(idx) filter(a, 1, xf); norm_e(idx) norm_e(idx) win; end e e ./ (norm_e eps); % 基频搬移对整段残差重采样 e2 resample(e, round(100/f0_ratio), 100);第一阶段的核心是filter(a,1,xf)得到预测误差信号也就是声带激励。激励中包含周期脉冲和噪声但没有共振峰形状所以对它重采样只改变脉冲重复率共振峰结构不会跟着变。残差的重叠相加需要按窗函数做归一化否则帧与帧重叠区域会有音量调制表现为背景有规律的“嗡嗡”声。4.2.2 极点搬移与稳定化第二阶段把之前保存的a_all逐帧做极点搬移再用搬移后的滤波器对激励做合成滤波。% 第二阶段极点搬移 合成滤波 重叠相加 y zeros(size(e2)); norm_win zeros(size(y)); for i 1:nf idx (i-1)*hop (1:frame_len); if idx(end) length(e2) break; end a a_all(i, :); poles roots(a); th angle(poles) * fm_ratio; r min(abs(poles), 0.99); a_new real(poly(r .* exp(1j*th))); a_new a_new / a_new(1); y(idx) y(idx) filter(1, a_new, e2(idx)) .* win; norm_win(idx) norm_win(idx) win.^2; end y y ./ (norm_win eps); y filter(1, [1 -0.95], y); % 去预加重 y y / max(abs(y)); endfilter(1, a_new, e2(idx))是标准的全极点合成相当于把改过位置的共振峰结构重新作用到激励上。因为e2是重采样后的残差长度可能比原信号短循环里用break兜底丢掉末尾不足一帧的部分对整句听感影响很小。这里的预加重和去预加重成对出现不能只加一头否则音色会发闷或发刺。4.2.3 合成与去预加重合成滤波后的重叠相加同样要归一化。win.^2作为归一化权重的理由是相邻帧在重叠区域的功率叠加拿功率和做除法能保持输出能量平稳。去预加重filter(1,[1 -0.95],y)是第一步filter([1 -0.95],1,x)的逆运算做完整条链路上高低频能量比例才恢复自然。整套脚本跑完F0已经被f0_ratio改变共振峰被fm_ratio改变。若要求输出和原wav等长把这段结果再送入3.2节的time_stretch_pv即可这是两个模块的拼接不需要修改内部逻辑。4.3 性别互转与卡通音的推荐参数表目标音色f0_ratiofm_ratio听感特征主要翻车点男转女1.6~2.01.15~1.30音调高、声道短fm_ratio超过1.4易折返爆音女转男0.55~0.750.85~0.95音调低、声道长f0低于60Hz会低沉到听不清卡通1.2~1.41.3~1.6尖细但保留清晰齿音辅音s段容易失真机器人1.01.0音色不变、质感发硬需要额外替换激励不在本表范围fm_ratio的上限来自极点搬移的折返约束极角乘以1.4之后原本3kHz附近的高频共振峰被推到4.2kHz以上接近8kHz奈奎斯特边缘超出部分会折返到低频产生类似“缸里说话”的相位抵消。下限其实没有硬约束但fm_ratio过低会让所有共振峰挤到300Hz以内声音变成闷哼语义清晰度断崖式下降。f0的上下限与2.2节自相关搜索范围一致改参数的时候注意别超出track_f0能测量的区间否则脚本里的基频搬移没报错但听起来已经完全不是语音了。5. 验证变声效果用F0曲线和谱包络定位参数边界5.1 对比变声前后的基频中位数与包络峰值变声效果不能只靠耳朵。把变声前后的F0中位数和共振峰峰位读出来量化差异是否与设定参数一致。f0_orig track_f0(x, fs); f0_new track_f0(x_v, fs); med_orig median(f0_orig(f0_orig0)); med_new median(f0_new(f0_new0)); fprintf(F0 中位数: %.1f Hz - %.1f Hz\n, med_orig, med_new); [H_orig, f] freqz(1, a_all(20,:), 512, fs); % 取中间一帧看包络 [H_new, ~] freqz(1, a_new(20,:), 512, fs); [~, i1] max(abs(H_orig(20:end))); % 跳过直流段 [~, i2] max(abs(H_new(20:end))); fprintf(第一共振峰: %.0f Hz - %.0f Hz\n, f(i119), f(i219));F0中位数的比值应该与f0_ratio基本一致偏差大于15%时先检查是不是静音帧被track_f0当成了极低基频晚点在5.2节给出静音判断的简化处理。第一共振峰位置是频谱中对数幅度最高的那个峰它的移动方向必须和fm_ratio同向fm_ratio大于1时峰位右移小于1时左移。如果峰位不动检查a_all里是否存了未搬移的旧系数这是最常见的“改了参数没效果”的原因。除了F0和共振峰还可以用spectrogram看谐波间距变化只是肉眼看图没有中位数断言来得高效。5.2 让效果可回归的三个小技巧第一个技巧是首尾淡入淡出。分帧重叠相加在信号首尾会产生不连续听感上是“啪”的爆音。可以用tukeywin给输出加一个非常窄的锥形窗10ms就够y y .* tukeywin(length(y), 0.002); % 只处理首尾 0.1% 的过渡第二个技巧是处理相位声码器的金属感。当输出要求不是很高时把所有频段都做相位传播会放大相位误差一个廉价替代是只对2kHz以下频段做时间伸缩2kHz以上直接线性插值搬移再相加。这样既保留中低频的音高变化又降低高频相位污染。第三个技巧是把5.1的指标写成断言assert(abs(med_new / med_orig - f0_ratio) 0.15, F0 偏差过大); assert(abs(peak_new / peak_orig - fm_ratio) 0.2, 共振峰未对齐);这样每次改完参数跑一遍就知道变声效果有没有落在设定范围内。把这几个断言存成voice_change_test.m以后换任何语音文件都能用同一套标准回归而不是反复试听猜参数。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价