
简介语音信号的短时分析是语音识别、端点检测、基音估计等任务的基础环节核心思想是对非平稳语音信号分帧加窗后在短时帧内提取特征。该资源基于MATLAB 2022A搭建围绕短时能量、短时过零率和短时自相关三类经典特征展开仿真配有中文注释源码适合正在学习数字语音处理课程的学生、准备课程设计或毕业设计的本科生以及需要快速验证算法的工程师。压缩包共10个文件包含8个.m源程序覆盖主程序与分帧、特征计算等子函数、1个.wav测试语音样本和1个.avi仿真操作录像整体仅590KB轻量易用。操作录像完整演示了MATLAB左侧当前文件夹路径的设置与运行流程能帮助初学者规避常见的路径报错问题。目前已有617人学习下载代码模块化程度高注释清晰便于对照教材公式逐段理解短时分析实现细节也可直接修改参数用于其他语音样本。1. 从波形到特征语音短时分析在Matlab里到底做了什么把一段语音丢进Matlab直接用plot(wavread)看到的是成千上万个采样点构成的复杂波形既看不出声调也看不出清浊音。语音信号本身是非平稳的但因为发音器官有一定惯性在10~30毫秒的短区间内可以近似看成平稳信号。所谓短时分析就是把语音切成一段段的“短时帧”再对每一帧计算能量、过零率、自相关这类特征。这个资源围绕的就是这件事用Matlab实现短时能量、短时过零率、短时自相关和平均幅度差函数并给出可直接运行的main.m、分帧函数enframe.m、采样语音C3_2_y.wav以及一段操作录像。适合正在做语音信号课程设计的人也适合想用手写代码替代工具箱函数来理解特征含义的工程师。看完这套代码你不仅能跑通仿真还能把特征参数改成自己需要的帧长和窗型。2. 分帧加窗enframe.m 与 FrameTimeC.m 里的时间对齐2.1 语音信号为什么必须分帧如果不分帧直接对整个 2 秒语音计算一个能量值得到的只是“这段录音整体响不响”完全反映不出每个音节的位置。语音特征的时序变化才是识别和标注的关键。分帧就是把一个长信号按固定步长切成多个有重叠的短段让每一段独立计算特征。常见的分帧参数是帧长 25ms、帧移 10ms。以 8kHz 采样率为例25ms 对应 200 个采样点帧移对应 80 个采样点。相邻帧重叠 120 个点这样从前一帧到后一帧的特征变化是平滑过渡的不会因为窗口边界产生剧烈跳变。代码里要处理的核心问题不是“怎么切”而是“切完之后每帧的起始位置怎么算、帧数怎么算”。2.2 enframe.m 分帧函数的实现与计算逻辑资源里的enframe.m是整套分析的地基。常见实现如下function frame enframe(x, win, inc) % 分帧函数输入信号x窗函数或帧长win帧移inc % 输出frame为nf行,nw列的矩阵每行是一帧 if length(win) 1 nw win; % win是帧长 w ones(1, nw); % 默认矩形窗 else nw length(win); % win是窗函数向量 w win(:); end nx length(x); % 信号总长度 nf fix((nx - nw) / inc) 1; % 帧数 frame zeros(nf, nw); indf (0:nf-1) * inc; % 每帧起始索引 for i 1:nf start indf(i) 1; frame(i, :) x(start:startnw-1) .* w; end这里nf的计算必须用fix向下取整不能四舍五入。比如信号长度 1000帧长 200帧移 80(1000 - 200) / 80 1 11余下的末尾不足一帧的数据会被丢弃。如果改用round当余数恰好超过半帧时最后一帧会越界读取导致index exceeds array bounds错误。这种错误在仿真录像里很常见因为操作者改参数后忘了一起改帧数。2.3 FrameTimeC.m 把帧序号映射回时间坐标算出特征后画图时横轴不能写帧序号要写真实时间。FrameTimeC.m的作用就是做这个映射function t FrameTimeC(nf, inc, fs, nw) % 计算每帧中心点对应的时间 t ((0:nf-1) * inc nw/2) / fs;帧序号从 0 开始第 i 帧的起始采样点是i * inc加上半帧长nw/2就是该帧中心再除以采样率fs得到秒。比如 fs8000、inc80、nw200第 0 帧中心是(0 100)/8000 0.0125s第 1 帧中心是(80100)/8000 0.0225s。这个时间轴要和原始波形叠加显示时注意偏移量是半帧长而不是帧移。2.4 帧长、帧移、窗型的参数选择不同窗函数对特征值的影响很大。矩形窗虽然主瓣窄但旁瓣高会导致能量泄漏汉明窗旁瓣衰减快是语音短时分析最常用的窗。参数参考如下场景帧长帧移窗函数说明端点检测10~20ms5~10ms矩形窗过零率对噪声敏感短帧更细基音周期估计20~30ms10ms汉明窗帧内至少包含两个基音周期频谱分析20~50ms10~20ms汉明窗/汉宁窗频率分辨率和时间分辨率折中enframe.m里如果win传入一个向量比如hamming(200)那么返回的每一帧都已经乘过窗。如果传入标量 200则相当于矩形窗分帧。我通常会先传标量分帧把帧存下来后面要比较不同窗型时再在特征函数里乘窗这样避免重新分帧。一个常见的误用是把帧移设为和帧长相等。这样相邻帧不重叠过零率和能量的曲线会呈锯齿状看起来像抖动噪声。重叠 50% 以上才能得到平滑的包络不要为省计算量去掉重叠。3. 短时能量与短时过零率STEn.m、STMn.m 与 STZcr.m 的端点检测打法3.1 短时能量定义与 STEn.m 实现短时能量的本质是“这一帧信号的平方和”用于判断语音是否开始或结束。静音段和噪声段的能量通常远低于语音段。STEn.m的典型实现是function E STEn(frame, w) % frame: 分帧后的矩阵每行一帧 % w: 窗函数向量默认全1 if nargin 2, w ones(1, size(frame,2)); end w w(:); E sum((frame .* repmat(w, size(frame,1), 1)).^2, 2);代码里repmat把窗函数复制成和frame同样行数的矩阵然后逐行乘窗再平方求和。如果分帧时已经加过窗这里就不要二次乘窗否则能量会被压低到原来的平方量级。我习惯把加窗全部放在enframe.m里做特征函数只处理“已经加好窗的帧”这样STEn、STZcr、STAc的输入格式统一。3.2 STMn.m 短时幅度与 STEn.m 的差异资源里还有STMn.m短时幅度。它计算的是绝对值平均function M STMn(frame) M mean(abs(frame), 2);能量对大的采样值做了平方扩大了大振幅样本的权重因此语音段的能量峰值比幅度更尖锐。幅度则更接近人耳对响度的感觉曲线更平坦。端点检测一般用能量因为它在语音段和静音段的落差更大而幅度适合做可视化不容易让大峰值掩盖后续的弱音段。3.3 STZcr.m 过零率的实现与噪声敏感度过零率统计的是信号在每个符号变化的次数。清音段的频谱集中在高频波形快速振荡过零率高浊音段低频能量强波形平滑过零率低。实现如下function zcr STZcr(frame) % 每帧过零率符号变化次数的一半 s sign(frame); diff_s diff(s, 1, 2); % 相邻采样点符号差 zcr sum(abs(diff_s), 2) / 2; % 每个变化算0.5次sign会把正数变成 1负数变成 -1。如果信号恰好为 0sign(0) 0差分会多算一次变化所以实际使用前我会先把信号减去均值消除直流偏置。噪声的存在会让静音段的过零率居高不下因此单靠过零率做端点检测很容易误判。常见的做法是先用能量找到语音段的粗范围再在这个范围内用过零率细分清音和浊音。3.4 双门限端点检测的参数参考把能量和过零率结合是这套代码最直接的实战用法。先设定两个能量阈值较高阈值T_high确定必然存在语音的区间较低阈值T_low用于向外扩展搜索边界。过零率阈值T_zcr用于在扩展时判断是否碰到清音。参数推荐区间取值依据帧长10~20ms太短能量抖动大帧移5~10ms保证边界定位精度T_high最大能量的 0.1~0.3取首段语音能量峰值的比例T_low最大能量的 0.01~0.05低于此值视为静音T_zcr全序列过零率均值的 2~3 倍高于此值判定为清音如果你跑通后看到能量包络有多个尖峰先别急着调阈值检查一下分帧是否重叠。帧移太大时每个音节的应急起音会被切成独立尖峰看起来像多个语音段。把帧移到 5ms 左右包络会连续得多阈值判断自然稳定。4. 短时自相关与 AMDFSTAc.m 与 STAmdf.m 的基音周期估计4.1 自相关函数的周期性与 STAc.m 实现浊音信号具有准周期性周期性信号的自相关函数在基音周期的整数倍处出现峰值。因此在自相关序列中寻找第二个明显峰值跳过零滞后处的最大峰的位置就能估算基音周期。STAc.m的实现逻辑是function R STAc(frame, maxLag) % 短时自相关返回滞后0到maxLag的自相关值 N length(frame); R zeros(1, maxLag 1); for k 0:maxLag R(k 1) sum(frame(1:N-k) .* frame(1k:N)); end循环里frame(1:N-k)和frame(1k:N)长度相同是同一帧信号错位 k 个点后相乘求和。自相关值随滞后 k 增大而采样点减少所以大滞后处的峰值幅度会自然衰减。这个衰减会造成基音周期越长峰值被压得越低导致误判为更短的周期。4.2 归一化与滞后范围的选择消除衰减影响有两个手段。一是除以重叠长度做归一化R(k 1) sum(frame(1:N-k) .* frame(1k:N)) / (N - k);二是只搜索基音周期可能对应的滞后范围。人声基频通常在 80Hz 到 400Hz 之间采样率 fs 下滞后范围取fs/400到fs/80。比如 fs8000就是搜索滞后 20 到 100 个采样点。在这段范围内找最大值能避开零滞后的大峰。注意STAc.m默认返回全部滞后你自己搜索峰值时要限定区间fs 8000; minLag round(fs / 400); maxLag round(fs / 80); [~, idx] max(R(minLag:maxLag1)); pitchPeriod idx minLag - 1;如果你直接在整个R里找最大值找到的基本都是 0 滞后的那个点基音周期永远是 0。4.3 STAmdf.m 平均幅度差函数的实现自相关在滞后处做乘法计算量大而且幅度大的采样点对结果影响过大。平均幅度差函数 AMDF 改用差值的绝对值平均计算量更小且对幅度不敏感。实现如下function D STAmdf(frame, maxLag) N length(frame); D zeros(1, maxLag 1); for k 0:maxLag D(k 1) sum(abs(frame(1:N-k) - frame(1k:N))) / (N - k); endAMDF 是找极小值而不是极大值。当滞后等于基音周期时两段波形对齐程度最高差值绝对值之和最小。由于 AMDF 用的是减法信号幅度平稳时不受直流偏置影响但如果信号本身带有较强噪声极小值会变得平坦不容易定位。实际使用时我通常把STAmdf的结果反转即-D再和STAc的结果放在同一坐标系里对比这样两者都是找峰。4.4 两种方法在清浊音上的表现差异与参数调整方法浊音表现清音表现主要问题短时自相关 STAc基音周期处有明显峰无规律峰值随机乘法量大幅度敏感AMDF STAmdf基音周期处有明显谷谷值浅且不稳定噪声大时谷值模糊清音本身没有基音周期两种方法都会给出一个偶然的检测值。因此实际仿真中先用第 3 章的过零率判断当前帧是清音还是浊音只在浊音帧上做基音估计。如果你跑main.m时发现基音轨迹在某些帧突然跳变到很低的频率大概率是那一帧被误判为浊音平流层的清音。可以把过零率阈值调高一点或者在基音检测前对自相关值做一次平滑比如对相邻三帧的峰值位置取中值过滤掉孤立跳变。帧长的选择对自相关也有关键影响。帧长必须至少包含两个基音周期否则自相关峰不明显。比如女声基频 300Hz周期约 3.3ms8kHz 采样下约 27 个点25ms 帧长包含约 7 个周期足够。男声基频 100Hz 时周期 10ms25ms 帧长包含 2.5 个周期勉强可用。如果你处理男性低音把帧长放宽到 30ms自相关峰会更清晰。5. 从 main.m 跑通到录像对照路径、画图与批量化改进5.1 main.m 的执行顺序与代码结构main.m是整个仿真的入口文件不多但调用关系很清晰。按顺序做四件事读 wav、分帧、逐个计算特征、画图。典型流程可以用下面这段序列概括[x, fs] audioread(C3_2_y.wav); % 读取语音 x x - mean(x); % 去直流 enframeLen round(fs * 0.025); % 25ms帧长 inc round(fs * 0.010); % 10ms帧移 frame enframe(x, hamming(enframeLen), inc); E STEn(frame); % 短时能量 zcr STZcr(frame); % 短时过零率 R STAc(frame(1, :), round(fs/80)); % 第一帧自相关 t FrameTimeC(size(frame,1), inc, fs, enframeLen);audioread是 Matlab 2012 之后推荐的读取函数老代码里的wavread在 2022A 中还能用但已不推荐。C3_2_y.wav是资源自带的采样语音如果换成自己的 wav注意采样率要和帧长度参数匹配。你可以在main.m开头加一行disp(fs)先确认采样率再继续避免帧长和实际采样率不匹配导致特征曲线异常。5.2 文件清单与运行顺序资源压缩包里的文件各自承担一个职责运行时必须全部放在同一目录。下面这张表是运行前核对用的文件名作用main.m主脚本读 wav 并调用各特征函数enframe.m分帧及加窗FrameTimeC.m帧序号转时间坐标STEn.m短时能量STMn.m短时幅度STZcr.m短时过零率STAc.m短时自相关STAmdf.m平均幅度差函数C3_2_y.wav测试语音仿真操作录像0020.avi操作演示按录像步骤核对目录路径注意 Matlab 左侧“当前文件夹”必须指向上述文件所在目录。如果你在编辑器里打开main.m直接点运行而左侧路径还停在C:\Users\...audioread会找不到C3_2_y.wav。这是录像里强调最多的注意事项也是最常见的报错来源。5.3 三个直接能用的改进点第一把特征曲线和原始语音画在同一个时间轴。FrameTimeC返回的时间向量是从 0 开始的但原始语音的横轴是采样点除以fs。用t_audio (0:length(x)-1)/fs生成对应时间然后在plot(t_audio, x)上叠加特征曲线能直观看到能量突变点是不是正好落在语音起止位置。第二把单帧自相关改成逐帧循环并保存基音轨迹numFrames size(frame, 1); pitch zeros(numFrames, 1); for i 1:numFrames R STAc(frame(i, :), maxLag); [~, idx] max(R(minLag:maxLag1)); pitch(i) fs / (idx minLag - 1); % 转换成频率 end画pitch时只显示浊音段清音段直接设为 NaN避免画出随机毛刺。第三批量处理多个 wav。用dir(*.wav)列出文件循环处理把能量、过零率、基音轨迹保存到结构体里最后统一写 CSV。这样课程设计交实验报告时不用手动替换文件名一次一次跑。5.4 验证仿真是否正确的几个判据跑完main.m第一张能量图应该在每个音节开始处出现一个明显的上升沿。过零率图的静音段应当接近一个稳定的小值如果静音段过零率比浊音段还高说明原始信号里叠加了直流或低频噪声回去检查x x - mean(x)是否执行。自相关图在浊音段应当等间隔出现几个逐渐衰减的峰间隔对应的滞后值乘以采样率再取倒数得到的频率应落在 80~300Hz 之间。最后用录像对照一遍窗口布局如果曲线形状和录像里差异很大优先检查帧长参数是否被改动过再用whos frame查看矩阵尺寸是否符合预期。本文还有配套的精品资源点击获取