前言
语音转文字在手机和电脑上已经很常见了。放到嵌入式设备里,它也有很直接的用途:给设备增加语音输入,把说话内容显示在屏幕上,或者交给后面的程序继续处理。
博主想在 STM32 上做一个普通话转写终端。对着开发板上的麦克风说话,由板子自己处理音频、运行识别模型,再把中文显示出来。整个运行过程放在本地,不依赖网络识别服务。
这里使用 STM32N6570-DK,识别模型选择中文 Citrinet-512。Cortex-M55 负责音频处理、文字解码和界面,Neural-ART NPU 负责神经网络计算,模型权重保存在板载外部 Flash 中。
01、模型部署
中文语音识别模型有不少,移植到单片机上,除了看体积,还要看输入形式、算子和量化方式能不能被部署工具支持。
这个工程使用 sherpa-onnx 发布的中文 Citrinet-512 CTC 模型。电脑端先固定输入尺寸,再对 SE 模块做等价图改写,进行静态 INT8 量化,最后交给 ST Edge AI 生成 NPU 网络代码。
模型不直接接收录音波形,它接收的是音频特征。当前固定输入为 640 帧、每帧 80 维,对应最多约 6.4 秒音频;输出为 80 个时间步,每个时间步有 5207 个类别,其中包含一个用于 CTC 解码的 blank。
大伙可以先把它理解成:前面把声音整理成一张随时间变化的特征表,网络再根据这张表,判断各个时间位置更可能对应哪个文字或词元。我们采用的的 NPU 权重镜像约 40.11 MB,存放在外部 NOR Flash。计算任务按功能分配:神经网络计算映射到 NPU,麦克风管理、FFT、Mel 特征、CTC 解码和屏幕绘制由 M55 执行。
02、音频采集
硬件上直接使用开发板的 PDM 麦克风,PE2 提供时钟,PE8 接收数据。音频经过 MDF1 Filter0 滤波、抽取,再由 GPDMA1 Channel6 搬运到内存,目标格式为 16 kHz、单声道、16 位 PCM。PDM 麦克风输出的是高速单比特数据流。经过滤波和抽取后,程序才能得到后续处理使用的 PCM 采样值。
工程按 20 ms 一块处理音频,每块 320 个采样点。DMA 使用双半缓冲,半满和全满回调把新数据复制到队列,主循环再逐块取出。中断里保留必要的数据搬运,特征提取和推理放到主循环处理。
队列默认有 128 块,可以容纳约 2.56 秒音频。这样,主循环处理上一段数据时,采集端还能继续接收声音。不过队列只能缓冲短时间的积压,持续处理速度仍然要跟得上采集。
为了判断什么时候开始一句话,代码计算每块音频的 RMS,并结合背景噪声估计设置门限。这种方法按声音能量判断是否触发,风噪、敲击声等较大的声音也可能越过门限。
触发前保留最多 200 ms 音频,开始说话时把这部分一起放入语句缓冲,给句首留一点余量。之后每积累约 1 秒新音频,重新识别当前语句;连续安静约 600 ms,或者达到 6.4 秒上限,就生成这一段的最终结果。对应的触发逻辑如下,省略了前面的能量计算和音频追加:
quiet_blocks = voiced ? 0 : quiet_blocks + 1;if (sample_count >= ASR_MAX_SAMPLES || quiet_blocks >= 30)recognize(1);elseif (sample_count - last_inferred >= 16000)recognize(0);
这里 30 块对应 600 ms,16000 个采样点对应 1 秒。recognize(0) 刷新临时文本,recognize(1) 将结果写入历史区,并重置当前语句。
03、特征与解码
音频采集正确以后,还要让输入特征符合模型的约定。
音频前端使用 25 ms 窗口、10 ms 帧移,计算 80 维 log-Mel 特征。16 kHz 采样率下,一帧取 400 个采样点,相邻帧移动 160 点,因此窗口之间有重叠。
每帧经过均值去除、预加重、Povey 窗、512 点 FFT、Mel 滤波和取对数,再对当前语句的每个特征维度做归一化。音频不足固定输入长度时,在归一化后补零。
这些参数需要和模型配套。采样率相同、都叫 Mel 特征,也不代表两份实现可以直接替换;窗函数、边界处理和归一化方式都会改变模型实际收到的数值。
工程还缓存了已经计算的原始 Mel 特征。语句增长时,主要补算新增帧,并重算受右边界反射影响的尾帧,然后重新做整段归一化,减少重复 FFT。
固定窗口模型在每次更新时重新推理当前语句,没有保存网络的跨窗口状态。因此临时文字可能随着后续声音而修正,显示延迟也会叠加特征处理和推理所花的时间。
特征归一化后,程序根据生成网络提供的 scale 和 zero point 量化为 INT8,再按实际张量布局写入 NPU 输入缓冲。
网络输出以后,还需要 CTC 解码。工程采用贪心方式:每个时间步选择分数最高的类别,合并相邻重复类别,去掉 blank,最后根据词表拼接 UTF-8 文本。
举个解码规则示例:一串“你、你、blank、好、好”会得到“你好”。如果是“好、blank、好”,中间的 blank 会把两个“好”分开,保留成“好好”。
04、界面显示
界面使用开发板的 800×480 LCD,显示历史文本、当前临时文本、音量以及运行状态,底部提供暂停和清空按钮。串口同步输出 UTF-8 文字,方便观察部分结果和最终结果。
功能上,历史文本和临时文本分开显示,方便看出哪些内容已经定稿、哪些还可能继续变化。暂停按钮用于停止采集,再次点击后开始新一段录音;清空按钮用于删除历史和临时文本,同时重置语句缓冲。这样操作时,上一句话的内容就不会接到新一句话前面。
总结
在使用中STM32N6总体的音频采集、模型推理的是实时性还是比较流畅的,准确率也可以接受。这个项目也给博主一个启发:STM32N6 的 NPU 除了处理图像,也可以承担语音识别这样的任务。大伙在做嵌入式 AI 项目时,也可以把目光放到音频和其他信号处理场景,尝试更多应用。
181