Matlab变声器代码实现:音高、语速与共振峰处理全解析
简介一个基于MATLAB的变声器程序代码包面向计算机、电子信息工程、数学等专业学生适合课程设计、期末大作业或毕业设计中的音频信号处理实践。代码兼容MATLAB 2014、2019a、2021a采用参数化编程参数调整便捷注释清晰便于理解变声原理并自行修改音调、速度等效果。压缩包共含4个文件包括2个M脚本与2个MP3示例音频脚本提供算法实现与运行入口音频用于输入样音测试整体仅5.82MB。目前已有209人学习或下载。资源内附可直接运行的案例数据结合原始音频与处理脚本可快速复现变声效果通过阅读注释还能掌握核心信号处理流程为后续扩展或二次开发提供基础。1. 变声器matlab代码能干什么先把音高、语速和音色分开从网上下载的变声器matlab代码.zip多半是课程设计或毕设几个 .m 文件读 wav、改采样率、sound 播放。跑通很容易可把音高拉到两倍再播得到的不是“另一个人”而是语速飞快的花栗鼠。问题不在代码在把变声理解成了单一操作。变声要处理三个彼此独立的量基频决定音高时长决定语速声道形状决定音色。男变女、机器音本质是把三者拆开各自搬到目标位置。改音高用重采样或相位声码器改语速用时间拉伸改音色要搬移共振峰实现路径和参数完全不同。网上绝大多数 matlab 教程讲到 FFT 就停了变声器恰恰是把 FFT、LPC、滤波器设计串起来的一个完整闭环。这套内容适合两类人被“变声器源码”吸引、想从“能跑”升级到“能调”的 matlab 新手以及做语音信号处理、把它当信号处理综合练习的从业者。下面按重采样、相位声码器、共振峰、实时与验证的顺序走。2. 重采样改音高变声器matlab代码里最基础的 30 行2.1 读入音频并先看基频变声前要有一把“尺子”拿到任何 wav 第一步不是急着改而是先摸清现状。常见变声器代码里直接用audioread读整个文件但很多录音是双声道、带直流偏置的直接处理会产生低频噗噗声。我一般会先压成单声道再做一次基频统计后面的 pitch_ratio 才有个基准值。[x, fs] audioread(voice.wav); if size(x, 2) 1 x mean(x, 2); % 双声道压成单声道变声器按单声道处理更稳 end x x(:); % 强制转列向量 [f0, t0] pitch(x, fs); % 基频分析需要 Audio Toolbox f0 f0(f0 0); % 丢掉静音帧否则中位数会被大量 0 拉低 disp([原始基频中位数: , num2str(median(f0))]);pitch输出的单位是 Hz它背后是自相关加窗分段估计静音段返回 0所以先过滤再去算中位数。用median而不是mean是因为语音里有大量清音段s、f 这些音没有稳定基频均值会被极端值带偏。这段输出就是你的“尺子”成年男声基频中位数通常在 100~150 Hz女声在 180~260 Hz儿童更高。后面改完再跑一遍这段对比中位数变化就知道音高到底动了多少。2.2 重采样因子换算pitch_ratio 与时长变化的关系改音高最直接的做法是重采样。resample(x, p, q)的含义是输出采样数是输入的 p/q 倍注意这个比例是反直觉的想让音高变高得减少采样数。目标音高是原来的 pitch_ratio 倍输出采样数就要变成原来的 1/pitch_ratio于是 p/q 1/pitch_ratio。pitch_ratio 1.4; % 目标音高是原来的 1.4 倍 out resample(x, 1000, round(1000 * pitch_ratio)); % 关键播放采样率仍是原来的 fs时长变为原来的 1/pitch_ratio sound(out, fs); audiowrite(pitch_up.wav, out / max(abs(out)), fs);用 1000 做中间整数是为了把小数的 pitch_ratio 转成整数参数时保持精度round(1000 * pitch_ratio)的误差在千分之一以内。注意resample内部带抗混叠滤波器这比用interp1做线性插值干净得多后者会在频谱上产生镜频听感像“含着一口水说话”。播放和写文件时采样率一律沿用原始 fs这是整个变调逻辑成立的前提。pitch_ratio输出/输入采样数听感方向时长变化0.71000/700低沉、人声变“厚”拉长到 1.43 倍1.01000/1000原声不变1.41000/1400偏高、语速明显变快缩短到 0.71 倍2.01000/2000经典花栗鼠只剩一半2.3 变速副作用单靠重采样做不出“变调不变速”上面的表暴露了重采样的硬伤音高和时长是绑定的。变声最常被要求的两种效果——变调但语速不变、语速变但音调不变——单靠resample都做不到。有人试图用resample改完后再用interp1把时长拉回去结果两轮插值叠加频谱混叠和相位错位一起出现出来的声音像坏掉的收音机。提示任何只靠“重采样 补时长”的组合本质上都没有解耦音高和语速。解耦要靠短时傅里叶变换域上的相位处理也就是第三章的相位声码器。另一个实际问题是性能。resample的 q 参数很大时内部滤波会显著变慢对一首三分钟的歌全量处理很吃亏。调试阶段建议用audioread(voice.wav, [1 2*fs])只读前两秒参数调好再处理整段。3. 相位声码器变声器matlab代码不丢语速的核心3.1 STFT 帧、hop 与相位传播相位声码器的基础是把语音切成重叠的短帧每帧做 FFT然后调整合成时的帧移。先定义三个量帧长 N、分析帧移 H、合成帧移 Hout。时间拉伸系数 alpha 就是 Hout/H。相邻分析帧之间同一个频点 k 的相位理论上应该增加2*pi*k*H/N但由于窗函数截断和语音非平稳性实际增量会有偏差。相位声码器要做的是把“分析相位增量”和“目标相位增量”之间的差包络到主值区间后补回去再按 Hout 累加。写成伪代码就是合成相位 上一帧合成相位 Hout 对应的自然增量 包络后的相位残差。这一步看起来只是中学三角函数的计算实际上它决定了合成语音有没有“金属味”。不做相位修正、直接对帧做 overlap-add谐波之间会互相抵消声音发虚、发颤。帧参数的选择直接决定质量。N1024、H256 意味着 75% 重叠频率分辨率约 43 Hz44.1 kHz对 80~300 Hz 的语音基频来说够用。N 再大会把瞬态抹平N 太小则低频谐波站不稳。3.2 可直接跑的时间拉伸函数这段函数是很多变声器源码里pvoc的简化版不依赖istft老版本 matlab 也能跑。新版 Signal Processing Toolbox 里已经有现成的istft但在理解原理这件事上自己写循环更划算function y pv_stretch(x, alpha) % 相位声码器时间拉伸alpha 1 变慢alpha 1 变快 N 1024; H 256; % 帧长与分析帧移 win hann(N, periodic); x [zeros(N,1); x(:); zeros(N,1)]; % 首尾补零避免边缘帧能量塌陷 nf floor((length(x) - N) / H); specA zeros(N, nf); for m 1:nf % 分析阶段逐帧 FFT seg x((m-1)*H1 : (m-1)*HN) .* win; specA(:,m) fft(seg); end Hout round(alpha * H); % 合成帧移 ylen nf * Hout N; y zeros(ylen, 1); wsum zeros(ylen, 1); half N/2 1; phi angle(specA(1:half, 1)); % 初始合成相位 omega 2*pi*(0:half-1) * H / N; % 相邻分析帧的自然相位增量 pos 1; for m 1:nf % 合成阶段相位累积 OLA Cur specA(1:half, m); if m 1 dphi angle(Cur) - angle(specA(1:half, m-1)) - omega; dphi mod(dphi pi, 2*pi) - pi; % 卷绕到 (-pi, pi] phi phi omega * (Hout/H) dphi; end newspec abs(Cur) .* exp(1i*phi); fullspec [newspec; conj(newspec(end-1:-1:2))]; % 补共轭对称 frame ifft(fullspec); idx pos : posN-1; y(idx) y(idx) real(frame) .* win; wsum(idx) wsum(idx) win.^2; % 归一化累积 pos pos Hout; end y y ./ max(wsum, 1e-10); % 抑制除以零 y y(N1 : end-N); % 去掉补零 end参数上最值得琢磨的是dphi那两行。mod(dphi pi, 2*pi) - pi把相位差限制在 ±π 之间这一步跳过了叠加时帧间相位就会互相打架。abs(Cur)保留原幅度谱幅度谱在相邻帧之间变化小这也是相位声码器能在音质上胜过直接 OLA 的根本原因。alpha 超过 2 后单帧相位信息不足以支撑大拉伸语音会明显变“糊”这是方法本身的边界。3.3 时间拉伸与重采样组合任意音高且不改变时长相位声码器只负责“变时长不动音高”要“变调不动语速”组合这两章的工具即可先用真空带声码器拉伸 alpha 倍再用resample把采样数压回原来的量级。拉伸把时长拉长重采样把时长压回去的同时把音高抬上来alpha 1.4; % 目标音高比例也是时间拉伸因子 slow pv_stretch(x, alpha); % 语速变慢音高不变 y resample(slow, 1000, round(1000 * alpha)); % 压缩回原时长音高升高 1.4 倍 sound(y, fs);这里resample的因子是 1000/(1000×alpha)也就是 1/alpha和第二章里的方向相反因为pv_stretch已经先把时长变长了。两步合起来输出的时长约等于输入基频变为 alpha 倍。这套“先拉伸再压缩”的两步法是课程设计和开源变声器代码里最常见的音高偏移实现比直接在频域搬移谱线稳定得多。目标效果第一步第二步变调不变速pv_stretch(x, r)resample(slow, 1000, round(1000*r))变速不变调pv_stretch(x, r)直接输出都变、带原始时长约束resample(x, 1000, round(1000/r))pv_stretch(·, r)第二行值得单独记相位声码器本身就能做到变速不变调这是它相对重采样最核心的卖点。第三行顺序反过来的做法适合想先改音色再校正时长的场景但相位声码器对非语音信号比如打击乐效果很差瞬态会被相位累积拖成混响尾巴做音乐素材时要谨慎。4. 共振峰搬移让变声器matlab代码从“音高变了”到“人变了”4.1 只改音高为什么像花栗鼠把基频中位数从 120 Hz 抬到 200 Hz听起来却不像女声而是像动画片里的角色。原因是共振峰没动。频谱图上谐波是一根根竖线间距等于基频共振峰是包络线上的鼓包由声道形状决定。男人和女人的声道长度差大约 15%~20%这个长度差直接映射到共振峰位置F1 大概在 500 Hz 附近F2 在 1500 Hz 附近女人的普遍比男人的高 15%~20%。重采样改音高时竖线整体平移但包络的鼓包还留在原地。结果就是谐波和共振峰的“相对关系”被破坏了大脑收到的是“一个喉咙没变的生物在发高音”——这正是花栗鼠效果的来源。所以专业变声器里音高和共振峰永远是分开调的两个旋钮pitch_ratio 管“多高”共振峰因子 gamma 管“像谁”。4.2 用 LPC 抽出声道形状lpc 与 freqz 看共振峰线性预测编码LPC是拿声道建模最便宜的手段。它的假设是当前语音采样可以由过去 p 个采样的线性组合预测出来预测误差就是声门激励。预测误差信号叫残差滤波器系数 a 的幅度响应就是声道包络。lpc一行就能把声源和声道解耦这也是为什么多数变声器源码里都有 LPC 相关函数的根本原因。xd resample(x, 16000, fs); % 先统一降到 16kLPC 估计更稳 xd xd - mean(xd); % 去直流防止低频能量污染包络 p 18; % LPC 阶数16k 采样常用 16~20 [a, g] lpc(xd, p); % a 是声道逆滤波器系数g 是增益 [H, F] freqz(g, a, 512, 16000); figure; plot(F, 20*log10(abs(H))); % 谱包络鼓包就是共振峰为什么要先降到 16 kHz44.1 kHz 采样下语音能量集中在 8 kHz 以下高频段几乎全是噪声和齿音LPC 会把阶数浪费在拟合噪声上。降到 16 kHz 后p 取 18 就能覆盖前四五个共振峰。阶数太低小于 12F1 和 F2 会糊成一个鼓包太高大于 30会出现一堆假峰搬移时把噪声也放大了。经验公式是 p ≈ fs/1000 416k 采样取 20 以内都合理。4.3 频率轴缩放搬移共振峰interp1 与最小相位重建搬移共振峰的本质是改变声道滤波器的频率响应而不是改变残差的基频。常规做法是把包络的频轴整体做缩放gamma 大于 1包络上的鼓包整体往高频移声音变“女生”小于 1 则整体下压。频轴缩放后直接反变换会得到非因果滤波器所以要先做最小相位化把幅度谱转成对应的因果冲激响应这一步是“能出声”和“能听”的分界线。gamma 1.2; % 1 共振峰上移偏女声1 下移 K 2048; Hw freqz(g, a, K, 16000); % 取复数包络后面要保留相位信息 f_axis linspace(0, 8000, K).; % 包络频轴整体缩放原频率 f 的鼓包被搬到 f*gamma 处 f_warp min(f_axis / gamma, 8000); mag_warp interp1(f_axis, abs(Hw), f_warp, linear, abs(Hw(end))); % 最小相位重建对对数谱做 ifft再把非因果部分的系数翻倍 c real(ifft(log(mag_warp eps))); c(2:end) 2 * c(2:end); % 最小相位化 h_new real(ifft(exp(fft(c)))); h_new h_new(1:256); % 截断成 256 点 FIR 滤波器 % 残差激励保留基频信息用新声道滤波器重建 resid filter(a, 1, xd); y_f fftfilt(h_new, resid); y_f y_f / max(abs(y_f)); audiowrite(formant_shift.wav, y_f, 16000);interp1的最后一个参数abs(Hw(end))是频轴超出 8 kHz 时的兜底值不写的话会填充 NaN整段输出变静音。最小相位化那句c(2:end) 2*c(2:end)是倒谱域的常规操作把倒谱系数翻倍等价于把零点和极点归到单位圆内。截断到 256 点是为了控制 FIR 长度太长低频会共振发闷太短小于 64包络细节丢失。4.4 三参数组合男变女、女变男的实际取值与翻车点目标pitch_ratiogamma翻车点男→女1.6~1.81.15~1.3只调音高不调 gamma出来是“小孩声”女→男0.6~0.750.85~0.95gamma 不跟着降像“女声硬压低”变童声1.51.4~1.5gamma 过 1.5 后齿音变刺组合顺序也影响结果。常见做法是先做第二章的重采样把音高抬上去此时共振峰也被连带搬动了再用 LPC 估一次包络、把 gamma 拉回目标位置。这相当于“先把整体抬起来再把喉咙的形状单独修回去”比反过来先搬共振峰再调音高更不容易引入金属感。gamma 超过 1.4 后高频增益会明显抬升齿音和气流声被放大听起来像加了激励器的电子音——这是共振峰搬移的听感边界。5. 把变声器matlab代码接到麦克风和文件实时框架、参数与验证5.1 从 wav 到麦克风audioDeviceReader 的最小实时框架离线处理只能做文件到文件想对着麦克风实时变声需要换一套 I/O 接口。Audio Toolbox 里audioDeviceReader负责从声卡拉音频块audioDeviceWriter负责推给播放设备。下面这段是能跑的最小循环adr audioDeviceReader(SampleRate, fs, SamplesPerFrame, 1024); adw audioDeviceWriter(SampleRate, fs); disp(开始变声CtrlC 退出); while true in adr(); % 取一帧 1024 点 out realtime_shift(in, fs, 1.2); % 按帧做变调 adw(out); drawnow; end这里的坑在于第三章的pv_stretch是整个文件级的处理不能直接塞进 1024 点的帧里。实时场景需要维护一个长度约 3 到 4 帧的重叠缓冲每次只处理中间部分再滑窗这等于把相位声码器改成流式 OLA 版本。只做重采样的话延迟很低但音高和时长还是绑定的。实测 1024 帧长在 44.1 kHz 下设备缓冲约 23 ms处理耗时通常在 5 ms 以内声音基本感觉不到延迟。5.2 三个必调参数与听感对照表参数默认值常用范围调大后的听感pitch_ratio1.20.5~2.0越高越尖超过 1.8 容易破音、出现明显共振峰失真gamma1.10.8~1.4越高越像女性/卡通超过 1.4 齿音发刺N / H1024/256N 取 512~2048H 取 N/4N 太大谐波干净但瞬态糊太小基频抖动调试顺序我一般是固定的先动 pitch_ratio 找到“性别感”再动 gamma 找到“人感”最后动帧长解决“糊涂”或“发虚”。每次只改一个参数用 5.3 的频谱图确认而不是靠耳朵一遍遍试听——人耳对音色的记忆只有几秒来回切换时很容易误判。5.3 用频谱图和基频轨迹验证变声效果调参之后别急着验收先把验证脚本跑一遍。基频中位数能确认 pitch_ratio 是否真的生效频谱图能同时看出谐波间距和共振峰包络的位置变化[f0x, ~] pitch(x, fs); [f0y, ~] pitch(y, fs); fprintf(原始基频中位: %.1f Hz\n, median(f0x(f0x0))); fprintf(变声后基频中位: %.1f Hz\n, median(f0y(f0y0))); figure(Position, [100 100 900 600]); subplot(2,1,1); spectrogram(x, hann(512), 384, 1024, fs, yaxis); title(原始语音); subplot(2,1,2); spectrogram(y, hann(512), 384, 1024, fs, yaxis); title(变声后);读图时看两处横向的亮线是谐波相邻亮线间距应接近原始基频乘上 pitch_ratio纵向的暗色鼓包包络是共振峰鼓包位置应朝 gamma 方向整体平移。如果基频中位数达标但包络鼓包没动就是只做了第二章没做第四章的典型症状。最后检查max(abs(y))保持在 0.9 以下超过 1.0 的削波会额外引入高频刺音。整套验证脚本固定下来后每次调参都能量化对比远比一边改 pitch_ratio 一边反复试听来得快。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →