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

MNN sherpa-mnn 实战:基于 C API 与 ALSA 实现 Linux 麦克风实时语音识别

MNN sherpa-mnn 实战基于 C API 与 ALSA 实现 Linux 麦克风实时语音识别【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN本篇指南围绕 MNN 仓库中 sherpa-mnn 的麦克风实时 ASR 示例 展开讲解如何用 C API 从 Linux 麦克风ALSA 设备读取音频、完成端到端点检测的流式语音识别。读完本文你将掌握 sherpa-mnn 在线识别器 C API 的完整调用生命周期、命令行参数体系、ALSA 录音设备的选取方法以及端点检测三条规则的调参思路可以直接在树莓派或 Linux 主机上跑通边说边出字的离线语音识别 Demo。1. 示例定位在 MNN 中的位置与角色该示例位于apps/frameworks/sherpa-mnn/c-api-examples/asr-microphone-example/目录目录内容非常精简文件作用README.md示例说明文档c-api-alsa.cc主程序麦克风实时识别仅支持 Linuxalsa.cc / alsa.hALSA 录音封装单文件内指向sherpa-mnn/csrc/alsa.cc的内容文件CMakeLists.txt构建脚本链接 C API 库与 ALSA 库README 明确了两个关键前提也是理解整个示例的钥匙这是一个实时流式识别示例音频从麦克风持续读取边录边解码文本增量式产出而非整段录音结束后一次性识别C API 完全可以从 C 文件调用虽然接口是纯 C 风格extern C、不透明指针、手动释放但c-api-alsa.cc本身就是一个.cc文件混合使用 C 特性std::vector、std::transform、lambda因此 C 工程可以直接链接 sherpa-mnn 的 C API 库无需写 C 胶水层。底层 C API 的声明集中在 c-api.h实现位于 c-api.cc。2. 程序入口与命令行参数c-api-alsa.cc的main要求至少 6 个参数4 个选项 设备名 程序名否则打印用法并退出。用法格式如下摘自源码中的kUsage字符串./bin/c-api-alsa \ --tokens/path/to/tokens.txt \ --encoder/path/to/encoder.onnx \ --decoder/path/to/decoder.onnx \ --joiner/path/to/decoder.onnx \ device_name其中device_name用于在多麦克风系统中指定使用哪一个录音设备。参数解析借助第三方cargs.h完成源码注释特别说明这是示例自身为了解析命令行引入的你自己的项目不必使用。完整参数表如下短选项长选项含义对应配置字段-h--help打印用法无-t--tokens词表文件路径config.model_config.tokens-e--encoder流式 transducer 编码器模型config.model_config.transducer.encoder-d--decoder解码器模型config.model_config.transducer.decoder-j--joinerjoiner 模型config.model_config.transducer.joiner-n--num-threads推理线程数整型config.model_config.num_threads-p--provider计算后端cpu默认、cuda、coremlconfig.model_config.provider-m--decoding-method解码方式greedy_search默认、modified_beam_searchconfig.decoding_method-f--hotwords-file热词文件每行一个词/短语词内 bpe/中文字符用空格分隔如▁HE LL O ▁WORLD、你 好 世 界config.hotwords_file-s--hotwords-score每个热词 token 的加分仅在decoding_method为modified_beam_search时生效config.hotwords_score几个值得注意的实现细节模型三件套encoder/decoder/joiner都挂在transducer子结构下说明该示例面向流式 transducer 模型c-api.h中同时还定义了paraformer与zipformer2_ctc两套子配置只是本示例未启用参数解析采用 C 风格的状态机cag_option_prepare准备上下文随后while (cag_option_fetch(context))循环内switch各identifier逐一赋值未识别的选项直接忽略注释说明 config 已有合法默认值--provider虽然写着支持cuda、coreml但 sherpa-mnn 走的是 MNN 引擎实际可用的后端以 MNN 编译时启用的后端为准示例默认使用cpu。3. 识别器配置采样率、特征维度与端点检测main中先memset整个SherpaMnnOnlineRecognizerConfig为零再逐项赋值这段代码完整展示了在线识别器的核心配置SherpaMnnOnlineRecognizerConfig config; memset(config, 0, sizeof(config)); config.model_config.debug 0; config.model_config.num_threads 1; config.model_config.provider cpu; config.decoding_method greedy_search; config.max_active_paths 4; config.feat_config.sample_rate 16000; // 期望采样率 config.feat_config.feature_dim 80; // 特征维度80 维 log-mel config.enable_endpoint 1; // 开启端点检测 config.rule1_min_trailing_silence 2.4; // 规则1未解码出任何内容时的尾部静音秒数 config.rule2_min_trailing_silence 1.2; // 规则2已解码出内容后的尾部静音秒数 config.rule3_min_utterance_length 300; // 规则3单句最大时长秒结合 c-api.h 中的结构体注释SherpaMnnFeatureConfig注释明确期望 16 kHz、16-bit、单声道sample_rate必须与模型期望一致官方模型为 16000feature_dim对官方模型为 80decoding_method取值greedy_search/modified_beam_searchmax_active_paths示例取 4仅在 beam search 下生效三条端点规则的精确语义来自头文件注释与 endpoint.hrule1_min_trailing_silence2.4s即使还没解码出任何内容只要尾部静音超过该值就判端点防一直不说话时流永不结束rule2_min_trailing_silence1.2s解码出非静音 token 之后尾部静音超过该值即判端点识别到用户说完一句话rule3_min_utterance_length示例取 300即 5 分钟单句超过该时长强制判端点防止超长语音拖垮状态。endpoint.h中EndpointConfig的默认值与之对应rule1{false, 2.4, 0}、rule2{true, 1.2, 0}、rule3{false, 0, 20}其中must_contain_nonsilence区分了规则 1/3可不依赖非静音与规则 2必须已解码出非静音。从 c-api.cc 的实现看provider为空时会兜底为cpuSHERPA_ONNX_OR(config-model_config.provider, cpu)这解释了示例中为何先硬编码cpu再允许命令行覆盖。4. ALSA 录音封装采样率协商、重采样与 XRUN 处理c-api-alsa.cc通过sherpa_mnn::Alsa类读取麦克风该类的实现位于 sherpa-mnn/csrc/alsa.cc、声明在 alsa.h注意整个类被SHERPA_ONNX_ENABLE_ALSA宏保护这就是 README 强调仅 Linux、不支持 macOS/Windows的原因——macOS 用的是 PortAudio/CoreAudio 路线Windows 用 WASAPI。从源码结构看Alsa构造函数做了四件事打开设备snd_pcm_open(capture_handle_, device_name, SND_PCM_STREAM_CAPTURE, 0)失败时打印错误并给出arecord -l的排查提示硬件参数协商依次设置SND_PCM_ACCESS_RW_INTERLEAVED访问模式、SND_PCM_FORMAT_S16_LE16 位小端格式、单声道若单声道不可用则回退到双声道并在解码时只取一路再用snd_pcm_hw_params_set_rate_near尽量接近期望的 16000 Hz采样率不匹配时自动重采样若实际采样率 ≠ 16000则构造一个LinearResample低通截止频率取较小采样率的 0.495 倍滤波器宽度 6Read()返回重采样后的数据16-bit → float 归一化ToFloat将int16样本除以 32768.0 映射到[-1, 1]正好满足SherpaMnnOnlineStreamAcceptWaveform对样本须归一化到 [-1, 1]的契约。Read()是阻塞读取另一个细节是XRUNoverrun容错snd_pcm_readi返回-EPIPE时先重新snd_pcm_prepare并返回空向量如果连续超过 5 次 overrun会直接报错退出并提示RTF 很可能大于 1即推理比实时还慢流式识别无法跟上。这个提示对嵌入式部署非常有价值它把麦克风没声/卡顿直接归因到算力不足并引导你计算 RTF 来定位。5. 选麦克风arecord -l 与 plughw 命名当系统里挂有多个麦克风时device_name参数决定使用哪一个。README 与源码给出的方法是先用arecord -l列出所有捕获设备。若输出类似**** List of CAPTURE Hardware Devices **** card 3: UACDemoV10 [UACDemoV1.0], device 0: USB Audio [USB Audio] Subdevices: 1/1 Subdevice #0: subdevice #0想选择 card 3、device 0则把设备名写为plughw:3,0。plughw:前缀意味着 ALSA 的 plug 插件层会负责必要的格式/采样率转换与Alsa内部的set_rate_near 软件重采样互为补充。6. 主循环流式识别的完整调用链创建识别器之后程序进入核心主循环。这段代码是理解 sherpa-mnn 在线 C API 生命周期最重要的部分逐段拆解如下const SherpaMnnOnlineRecognizer *recognizer SherpaMnnCreateOnlineRecognizer(config); // ① 创建识别器全局可复用 const SherpaMnnOnlineStream *stream SherpaMnnCreateOnlineStream(recognizer); // ② 创建一条流每路音频一条 const SherpaMnnDisplay *display SherpaMnnCreateDisplay(50); // 每行最多 50 个词的显示对象 const char *device_name argv[context.index]; // 解析完选项后的剩余参数即设备名 sherpa_mnn::Alsa alsa(device_name); if (alsa.GetExpectedSampleRate() ! 16000) { /* 采样率不匹配直接退出 */ } int32_t chunk 0.1 * alsa.GetActualSampleRate(); // 每 100ms 读一批 while (!stop) { // stop 由 SIGINT 信号置位 const std::vectorfloat samples alsa.Read(chunk); // ③ 阻塞读取 100ms 音频 SherpaMnnOnlineStreamAcceptWaveform(stream, 16000, samples.data(), samples.size()); // ④ 喂入波形 while (SherpaMnnIsOnlineStreamReady(recognizer, stream)) { SherpaMnnDecodeOnlineStream(recognizer, stream); // ⑤ 特征帧够多时反复解码 } const SherpaMnnOnlineRecognizerResult *r SherpaMnnGetOnlineStreamResult(recognizer, stream); // ⑥ 取当前结果 std::string text r-text; SherpaMnnDestroyOnlineRecognizerResult(r); // 必须手动释放 if (!text.empty() last_text ! text) { // 文本变化才重绘 // 转小写后打印实现增量刷新效果 SherpaMnnPrint(display, segment_index, text.c_str()); fflush(stderr); } if (SherpaMnnOnlineStreamIsEndpoint(recognizer, stream)) { // ⑦ 句尾判定 if (!text.empty()) segment_index; SherpaMnnOnlineStreamReset(recognizer, stream); // 清状态准备下一句 } } // ⑧ 退出前按先创建后销毁的逆序释放三个对象 SherpaMnnDestroyDisplay(display); SherpaMnnDestroyOnlineStream(stream); SherpaMnnDestroyOnlineRecognizer(recognizer);调用链要点AcceptWaveform只负责累积特征注释明确调用后还需调用SherpaMnnDecodeOnlineStream才会真正跑网络与解码且传入的采样率若与配置不符内部会自动重采样IsOnlineStreamReadyDecodeOnlineStream的 while 组合一批音频可能积累出多组可解码的特征帧所以要循环解码直到不再 ready。这也是 C API 头文件里给出的标准用法范式结果对象的生命周期是手动管理SherpaMnnGetOnlineStreamResult返回的指针必须用SherpaMnnDestroyOnlineRecognizerResult释放否则内存泄漏。SherpaMnnOnlineRecognizerResult除text外还带tokens/tokens_arr、timestamps可能为 NULL访问前必须判空、json等字段本示例只用了text端点检测驱动一句话一个 segment命中端点后先递增segment_index空文本不计数再SherpaMnnOnlineStreamReset清空网络状态与解码状态下一句从零开始。这正是第 3 节三条端点规则在实际流程中的落点优雅退出signal(SIGINT, Handler)注册 CtrlC 处理函数仅把stop置 true 让主循环自然走完资源释放而不是直接exit保证Destroy*都被调用。此外C API 还提供SherpaMnnCreateOnlineStreamWithHotwords按流级热词建流与SherpaMnnDecodeMultipleOnlineStreams并行解码多条流前者配合-f/--hotwords-file可实现同一个人一条流、多个热词列表的场景后者则服务于一对多如多个听者共享一个识别器的并发需求。7. 构建与依赖CMake 视角CMakeLists.txt 只有几行但把依赖关系说得很清楚add_executable(c-api-alsa c-api-alsa.cc alsa.cc) target_link_libraries(c-api-alsa sherpa-onnx-c-api cargs) if(DEFINED ENV{SHERPA_MNN_ALSA_LIB_DIR}) target_link_libraries(c-api-alsa -L$ENV{SHERPA_MNN_ALSA_LIB_DIR} -lasound) else() target_link_libraries(c-api-alsa asound) endif()链接目标sherpa-onnx-c-api即 sherpa-mnn 编译出的 C API 静态/共享库历史沿用了 sherpa-onnx 的库名cargs由 cmake/cargs.cmake 提供仅用于本示例的命令行解析ALSA 库asound支持通过环境变量SHERPA_MNN_ALSA_LIB_DIR指定额外搜索路径方便交叉编译或系统未把 ALSA 开发库装进标准目录的场景构建入口在 sherpa-mnn 顶层 CMakeLists.txt 一级的 sherpa-mnn 子工程内c-api-examples通过 c-api-examples/CMakeLists.txt 加入。实际启用该目标需开启 ALSA 开关对应源码中的SHERPA_ONNX_ENABLE_ALSA宏并安装系统的 ALSA 开发包仓库 toolchains 目录下还自带了 aarch64/riscv64 等交叉编译 toolchain 文件说明该示例的主要落地场景正是 Linux ARM 开发板。运行前还需要准备流式 transducer 模型四件套encoder.onnx、decoder.onnx、joiner.onnx、tokens.txt。8. 小结从示例到你的产品代码这个 259 行的c-api-alsa.cc是 MNN 仓库内一份教科书级的流式 ASR 接入样板它把 sherpa-mnn 在线识别的关键工程点全部演示到位了C API 生命周期CreateOnlineRecognizer → CreateOnlineStream → AcceptWaveform / IsReady / Decode / GetResult → IsEndpoint → Reset → 逆序 DestroyC 工程可直接照抄这一骨架配置语义16 kHz/80 维特征、greedy_search与modified_beam_search的取舍、三条端点规则各自守护静音超时、句尾判定、超长保护三类问题音频采集细节plughw:card,dev设备命名、arecord -l排查、16-bit 转 float 归一化、采样率不匹配时的软件重采样、XRUN 超限即判定 RTF1 并退出增量 UI 模式仅在text变化时通过SherpaMnnDisplay重绘配合segment_index实现一句话一段的展示。若需要非 Linux 平台的麦克风输入可参考同仓库的 Python 示例 python-api-examples 中的speech-recognition-from-microphone.pyPortAudio 路线与speech-recognition-from-microphone-with-endpoint-detection-alsa.pyALSA VAD 路线做对照同目录 c-api-examples 下还有文件解码decode-file-c-api.c、热词版流式 zipformerstreaming-zipformer-buffered-tokens-hotwords-c-api.c等 C API 示例可作为本示例的自然扩展。【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

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

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