ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux接入讯飞语音识别SDK:Ubuntu环境PCM音频处理与工程实践

Linux接入讯飞语音识别SDK:Ubuntu环境PCM音频处理与工程实践 2025年快过半了做IoT、机器人、语音交互的团队基本都会撞上同一个需求识别能力要跑在Linux设备或者服务器上而手里能用的往往就是科大讯飞那套C动态库SDK。我在Ubuntu上接入讯飞语音识别SDK时前前后后折腾了一周多踩过不少文档里没写的坑。这篇文章不打算写成一个“官方文档复述版”而是把我从创建应用开始到在Ubuntu上成功跑通“音频/PCM文件 - 讯飞SDK - 识别文字”整个链路的经验原原本本整理出来。适合看这篇内容的人大概有三类一是在服务器上做语音识别接口的Linux开发二是做智能硬件、嵌入式产品、需要在设备端接入讯飞识别能力的工程师三是刚接触讯飞SDK、想先在Ubuntu上验证一下语音识别效果的学生或爱好者。后面涉及的内容包括环境准备、API调用流程、音频采集、错误码排查和工程化改造建议尽量做到每一步都落在实测过的实际上。1. 跑通之前的准备工作控制台、SDK包和Ubuntu依赖1.1 讯飞开放平台上的两步一步都不能少先去讯飞开放平台注册账号在控制台里创建一个应用。创建应用本身很快真正容易出问题的是你必须在应用下开通“语音听写”服务然后才能下载到Linux平台的SDK。第一次做的人往往在哪个环节摔跤在应用列表里直接点下载SDK结果下载回来的是Windows安装包或者Linux SDK在编译时各种报错。我一开始也干过这事。正确走向是先确认账号已经完成实名认证然后进入控制台的“语音听写”或“实时语音转写”服务创建应用创建完之后在应用管理里找到“服务管理”或“开通服务”把实时语音听写服务打开再回到SDK下载页选择Linux平台。不同版本控制台界面细节略有差异但核心就这两步先开服务再下载顺序别反。这里有个隐藏知识点讯飞的Linux SDK不是通用的libmsc.so这个核心动态库在生成时就已经和你的AppID绑定了。你换个账号、换个应用直接复用同一个libmsc.so是跑不起来的会报“无权限”“授权失败”之类的错误。所以不要找别人复制一个现成的libmsc.so过来用必须用自己账号下载的包。平台会给你三个关键凭证AppID、APIKey、SecretKey。在SDK登录调用里AppID是必填项APIKey和SecretKey主要用于部分需要签名鉴权的接口语音听写的在线识别用AppID登录基本就能走通。这三个值建议放到环境变量或者配置文件里不要硬编码到源码中。1.2 拿到SDK包以后先做三件小事下载下来的SDK是一个zip压缩包解压出来会看到bin、include、lib、samples这几个目录通常还有一份PDF或Markdown格式的开发手册。bin放着编译好的示例程序理论上可以直接运行验证。include核心头文件。语音听写最常用的是qisr.h对应识别接口msp_cmn.h对应通用接口msp_errors.h里面有全套错误码定义。lib核心动态库libmsc.so有的版本还会附带其他依赖库。samples官方示例源码里面通常有sample_online_asr这样的目录能直接make出可执行程序。在Ubuntu上解压这个包时如果遇到中文文件名乱码不一定非要去改系统的语言编码。用unzip指令加上-O参数指定GBK编码重新解压即可unzip -O GBK sdk.zip -d iflytek_sdk这个细节很小但真有人因此在群里卡了半天。根源是Windows下打包时用了GBK编码文件名Linux默认用UTF-8解压中文目录名就成了乱码。解压好之后我习惯先看一眼核心库的依赖情况ldd libs/x64/libmsc.so这个命令会列出libmsc.so运行时需要的系统动态库。在干净的Ubuntu Server上很可能会提示缺libasound.so.2。这就是为什么在开始之前就要装好音频开发库讯飞SDK在Linux上用ALSA录音libasound是它的基础依赖。如果你跳过了这一步后面编译Demo时可能不报错运行起来才突然说缺库再回头补环境就绕了远路。1.3 用一条命令装齐编译依赖在Ubuntu 20.04/22.04这类系统上把下面几个包装好环境基本就齐了sudo apt update sudo apt install -y build-essential libasound2-dev unzipbuild-essential会把gcc、g、make一起装好libasound2-dev提供ALSA头文件和动态库unzip用来解压SDK包。后面如果还要自己用ffmpeg转音频格式额外装一个sudo apt install -y ffmpeg装完再跑一次ldd libmsc.so如果不再提示missing编译环境就算准备好。整个过程不复杂核心思路是把“音频相关的系统依赖”一次性补干净避免中途被“缺这个缺那个”打断。2. 核心调用链从MSPLogin到QISRGetResult2.1 五个API搞定一次识别先看整体关系讯飞Linux SDK对外暴露的API数量不少但语音听写场景日常用到的核心函数就五个函数作用所属头文件MSPLogin登录初始化SDK全局环境msp_cmn.hQISRSessionBegin开启一次识别会话qisr.hQISRAudioWrite写入PCM音频数据qisr.hQISRGetResult获取识别结果qisr.hQISRSessionEnd结束识别会话qisr.hMSPLogout登出释放SDK全局资源msp_cmn.h对照一下时序登录 - 开识别会话 - 写音频 - 取结果 - 关会话 - 登出。这和HTTP接口的“建立连接 - 请求 - 响应 - 关闭连接”很像只不过协议细节被封装在C接口里。特别提醒一下MSPLogin和MSPLogout的使用节奏。这两个函数在进程生命周期里一般只调用一次。频繁登录登出不但慢还容易把SDK内部全局状态弄乱。识别多次就多次执行QISRSessionBegin和QISRSessionEnd让多个识别会话复用一个登录环境。2.2 会话参数就是那个决定识别效果的字符串QISRSessionBegin的第二个参数是一长串参数文本很多人在这个字符串上吃过亏。最常用的完整参数写法如下const char* session_params sub iat, domain iat, language zh_cn, accent mandarin, sample_rate 16000, result_type plain, result_encoding utf8;逐项拆开说sub和domain固定用iat表示“语音听写”。language表示语言zh_cn是中文。accent表示口音或方言。mandarin是普通话想测粤语可以改成cantonese四川话是sichuan河南话是henan。sample_rate必须是16000或8000并且要和输入音频的实际采样率保持一致。填错会导致识别结果为空或者乱码。result_type用plain返回纯文本如果用json结果会带上其他辅助字段。result_encoding使用utf8否则取回的中文结果会乱码。这些参数用逗号分隔参数名和等号之间有没有空格一般不影响解析。但为了少踩坑我习惯保持“sub iat”这种和官方文档一致的风格。2.3 音频写入的状态机决定服务端何时返回结果语音识别是流式过程不是等整段音频发完才开始识别而是边传边出结果。QISRAudioWrite每次写入一小块音频数据需要通过audioStatus参数告诉SDK当前这块数据在整段音频里的位置。状态有三个MSP_AUDIO_SAMPLE_FIRST表示第一帧MSP_AUDIO_SAMPLE_CONTINUE表示中间帧MSP_AUDIO_SAMPLE_LAST表示最后一帧。实际使用时第一次写FIRST中间连续写CONTINUE数据全部写完还要额外调用一次“写空数据、状态为LAST”的通知让服务端把最后的结果flush出来。这个“最后补一个空LAST”的动作最容易被忽略。如果不写识别结果往往会少最后几个字或者程序需要等很长的超时时间才返回。原理并不难理解服务端需要明确知道“这一句话结束了”才会把尾部缓冲区的识别结果提交出来。2.4 一个可以直接编译的最小Demo下面这个是简化但完整的最小Demo从PCM文件读音频按帧写入SDK打印识别结果。它可以先验证SDK链路本身通不通所以暂不涉及麦克风。#include stdio.h #include stdlib.h #include string.h #include unistd.h #include qisr.h #include msp_cmn.h #include msp_errors.h #define FRAME_LEN 1280 // 40ms 16kHz/16bit/mono static int read_audio_and_write(const char *session_id, FILE *fp) { char frame[FRAME_LEN]; size_t n 0; int status MSP_AUDIO_SAMPLE_FIRST; int err 0; while ((n fread(frame, 1, FRAME_LEN, fp)) 0) { int ret QISRAudioWrite(session_id, frame, (unsigned int)n, status, err); if (ret ! 0) { printf(QISRAudioWrite failed, ret%d, err%d\n, ret, err); return ret; } status MSP_AUDIO_SAMPLE_CONTINUE; } // 数据写完补一个空的LAST帧让服务端返回最终结果 return QISRAudioWrite(session_id, NULL, 0, MSP_AUDIO_SAMPLE_LAST, err); } static void fetch_result(const char *session_id) { int rslt_status 0; int syn_status 0; char *result NULL; int guard 0; while (1) { result QISRGetResult(session_id, rslt_status, 80, syn_status); if (rslt_status MSP_REC_STATUS_COMPLETE) { break; } if (result ! NULL *result ! \0) { printf(result: %s\n, result); } if (guard 200) { printf(timeout, force exit\n); break; } usleep(30000); } } int main(int argc, char *argv[]) { if (argc 2) { printf(usage: %s test.pcm\n, argv[0]); return -1; } FILE *fp fopen(argv[1], rb); if (!fp) { perror(fopen); return -1; } int err MSPLogin(NULL, NULL, appid 你的APPID, work_dir .); if (err) { printf(MSPLogin failed: %d\n, err); fclose(fp); return -1; } const char *params sub iat, domain iat, language zh_cn, accent mandarin, sample_rate 16000, result_type plain, result_encoding utf8; const char *session_id QISRSessionBegin(NULL, params, err); if (err) { printf(QISRSessionBegin failed: %d\n, err); MSPLogout(); fclose(fp); return -1; } err read_audio_and_write(session_id, fp); if (err 0) { fetch_result(session_id); } QISRSessionEnd(session_id, stop); MSPLogout(); fclose(fp); return 0; }把这个Demo保存为asr_demo.c编译时把头文件和库路径指好命令大概是这样的gcc -o asr_demo asr_demo.c -I./include -L./libs/x64 -lmsc -lpthread -ldl -Wl,-rpath,./libs/x64运行前记得让动态库可以被找到export LD_LIBRARY_PATH./libs/x64:$LD_LIBRARY_PATH ./asr_demo test.pcm这里有一个重要前提test.pcm必须是裸PCM数据不是wav文件。wav文件头的几十个字节会被当成音频数据送入SDK轻则识别为空重则直接报格式错误。Demo里的fetch_result是简化版循环调用QISRGetResult直到状态变成COMPLETE。实际产品里每次返回的result可能是多个中间片段可以自己拼接也可以把中间结果实时展示做成流式字幕效果。3. Ubuntu下的音频采集三种方式按需选择3.1 先用arecord验证麦克风和PCM采集链路在Linux上采集麦克风音频最直接的验证方式是用ALSA自带的arecord命令arecord -D plughw:0,0 -f S16_LE -r 16000 -c 1 -t raw -d 5 test.pcm这条命令会从默认声卡录制5秒PCM裸数据。参数含义-f S16_LE表示16bit小端-r 16000表示16k采样率-c 1表示单声道-t raw表示输出裸PCM-d 5表示时长5秒。录制前建议先执行arecord -l查看设备编号。有些机器上有多个音频设备默认设备不一定是麦克风。录完可以用aplay验证播放虽然有回放但至少确认文件有内容且格式正确。实测中有一个常见问题在虚拟机或开发板上USB外置声卡插拔之后设备编号可能变化。程序不要写死plughw:0,0用“default”或者根据设备名称匹配会更稳妥。这个问题的本质是Linux音频设备节点并不稳定尤其是多声卡环境。3.2 用ALSA库采集在程序里直接拿麦克风数据如果要把识别能力集成到自己的服务里用arecord这种外部命令始终不够优雅。更专业的做法是在C/C代码里直接调用ALSA库采集核心代码大致如下#include alsa/asoundlib.h snd_pcm_t *pcm; int ret snd_pcm_open(pcm, default, SND_PCM_STREAM_CAPTURE, 0); if (ret 0) { printf(snd_pcm_open failed: %s\n, snd_strerror(ret)); return -1; } snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, // 声道数 16000, // 采样率 1, // 软重采样开关 500000); // 缓存时间单位微秒 char buf[1280]; while (running) { int frames snd_pcm_readi(pcm, buf, 640); if (frames -EPIPE) { snd_pcm_prepare(pcm); continue; } // 拿到buf后按帧调用QISRAudioWrite } snd_pcm_close(pcm);snd_pcm_readi返回一次读到的采样帧数这里每帧2字节640帧对应约40ms音频。之所以强调代码里直接采集是因为外部命令涉及进程调度和管道拷贝音频数据流的实时性不好控而且如果录音进程被意外杀掉声卡设备可能残留僵死状态影响下一次打开。3.3 手头没有麦克风先用PCM文件绕过硬件调SDK很多开发环境是WSL、虚拟机或者不带麦克风的服务器。在这种环境下验证讯飞SDK链路完全不需要声卡。准备一个16kHz、16bit、单声道的PCM文件直接用Demo读取即可。如果手上只有wav或mp3先用ffmpeg转一下ffmpeg -i input.wav -ar 16000 -ac 1 -f s16le output.pcm这里再次强调转出来的是裸PCM已经去掉了wav文件头。讯飞SDK接收的是裸PCM不是wav。这也是为什么新手问得最多的问题之一明明有录音文件喂给SDK却得到空结果因为wav开头那44个字节RIFF头被当成了音频数据解析。如果你在WSL里做验证我还多说一句WSL2对ALSA设备和USB声卡的透传支持比WSL1好但依然存在各种设备映射问题。建议先把PCM文件这条链路跑通等真正要做产品的时候再放到物理Ubuntu机器或者树莓派这类设备上调麦克风。4. 实测中的高频问题动态库、格式、乱码与错误码4.1 libmsc.so加载不上不是缺库就是路径没告诉系统如果编译通过运行时报error while loading shared libraries: libmsc.so: cannot open shared object file说明程序运行时的动态库搜索路径里找不到libmsc.so。Linux默认不会去当前目录找动态库必须通过LD_LIBRARY_PATH或者rpath指定。我在前面的编译命令里加了-Wl,-rpath,./libs/x64如果你的SDK库目录结构不同rpath也要相应调整。也可以一劳永逸地把libmsc.so放到系统目录sudo cp libs/x64/libmsc.so /usr/local/lib/ sudo ldconfig这样后续所有程序的链接期和运行期都能找到这个库省得每次开终端都export一次。另一个相关问题是依赖缺失。我在干净系统上遇到过缺libasound.so.2现象是程序能编译运行时报缺库。解决办法就是前面说的安装libasound2-dev。4.2 识别结果为空、乱码或总是少词先检查音频格式空结果和乱码这两个问题90%出在音频格式上。讯飞语音听写对音频的要求是PCM裸数据采样率16000或8000位深16bit单声道。任何一项不满足SDK不一定会报明确错误而是静默返回空结果。尤其注意sample_rate这个参数。它表示“你告诉SDK音频是多少采样率”并不会帮你去转换采样率。如果你的PCM文件明明是48000的参数里填16000识别结果必定不对。乱码问题则多半是result_encoding没设成utf8或者终端本身的字符集不是UTF-8。Ubuntu Server默认使用UTF-8一般问题不大但如果通过某些Windows SSH客户端连上去看输出要手动把终端编码切成UTF-8。还有一种“结果只出大半句”的现象。这个很可能是最后没发送空的MSP_AUDIO_SAMPLE_LAST帧。服务端收不到明确的结束标记会一直等vad_eos超时尾部数据可能被丢弃。4.3 错误码不用背但要会查讯飞SDK把所有错误码定义在include/msp_errors.h里。程序报错时先去这个头文件里grep对应的宏比网上乱搜可信得多。我整理几个日常比较常碰到的错误码仅供参考错误码常见含义我的排查建议10105无权限/未开通服务检查AppID对应应用是否开通语音听写账号是否实名认证10114无资源/找不到资源检查下载的SDK服务是否和当前业务匹配10116音频写入失败检查会话是否有效音频状态参数是否正确10121网络错误确认设备能访问讯飞服务域名防火墙是否拦截152引擎鉴权失败确认AppID和SDK绑定一致是否跨应用复用了libmsc.so这个表只是排查线索具体含义一定要以自己下载的SDK包里的msp_errors.h为准。不同版本SDK对部分错误码的注释会有差异我遇到过有人按网上搜来的含义反复排查最后回来看头文件才发现注释写得清清楚楚。4.4 网络问题在线识别跑不动的第一怀疑对象在线语音听写依赖讯飞云端服务SDK会通过HTTPS或WebSocket和讯飞服务端通信。如果你的设备在一个受限网络环境比如只开放了少数端口、有出网白名单第一步就是确认能访问讯飞的服务域名。排查网络问题我用得最多的方式就是用curl或nc去试探对应端口是否连通。这一步看似基础但不少人卡了一整天之后才发现是测试环境出网被限制。SDK本身不会返回“网络被限制”这种友好提示只会给你一个网络类错误码。还有一个容易被忽略的点首次识别会比较慢。因为SDK要完成DNS解析、TCP建连、TLS握手在宽松网络环境下首次识别耗时可能达到2到3秒之后会明显变快。测试时不要因为一次慢就断定“识别延迟高”要多次采样取平均。5. 从Demo到可落地工程化改造的几个关键点5.1 登录一次会话复用把Demo改成服务之前第一件事是确认MSPLogin放在进程启动阶段。很多新手把MSPLogin写在每次识别的入口函数里识别十次就登录十次不但慢还可能触发服务端的并发连接限制。正确做法是进程初始化时MSPLogin进程退出前MSPLogout。每次识别都单独走“QISRSessionBegin - 写音频 - 取结果 - QISRSessionEnd”这个会话周期。不同会话之间互不影响可以并发。但要注意每个会话的QISRAudioWrite和QISRGetResult最好在同一个线程内完成尽量避免跨线程操作带来的状态不确定。5.2 分帧写入的节奏建议按40ms到100ms切QISRAudioWrite不是一次性接收整段音频的接口它是为流式识别设计的。每次写入太大SDK内部缓冲压力会升高写入太小则放大了I/O次数和网络交互的开销。我的经验是每帧40ms到100ms比较合适。以16kHz、16bit、单声道为例40ms就是1280字节100ms就是3200字节。Demo里用了1280字节采集麦克风时按40ms或80ms切帧也都可以。如果你做实时对讲类产品写入节奏要和录音节奏严格对齐。ALSA层读多少SDK层就写多少不要让音频数据在内存里堆积。切帧节奏没控制好端到端时延会越来越大。这也是不建议先录完整段再一次性发送的原因。5.3 方言、标点和流式优化的参数可以按需调整上面给的会话参数是能工作的最小集合。真实产品里通常要根据场景加参数。最常见的加标点需求在参数里写ptt 1识别出来的文本就会带上标点符号可读性明显提升。方言识别就改accent参数。讯飞现在支持普通话、粤语、四川话、河南话等多种口音。英文识别则把language改成en_usdomain一般保持iat不变。还有一个控制响应速度的参数vad_eos它表示“静音多久判定一句话结束”。默认值可能是3000ms如果希望识别停顿来得更快可以调小到800或1000。但调太短会把句中正常停顿也当成断句导致一句话被切碎。这个参数要根据产品形态试没有通用最优值。5.4 资源释放和日志是上线前最容易忽略的两件事Session用完QISRSessionEnd一定要调用。这个接口会释放服务端和本地的会话资源。不调用的后果是会话持续堆积运行一段时间后SDK会出现“无法创建新会话”甚至内存持续增长。SDK在工作目录下会生成日志MSPLogin里的work_dir .就指定了工作目录。上线前建议把日志目录指向有自动清理策略的位置并按发布需求调低日志级别。有些SDK版本会把音频和会话信息打得很详细在磁盘空间紧张的嵌入式设备上一晚上产生几千个日志文件很常见。另外如果要把识别能力封装成HTTP服务或消息队列消费者建议在服务初始化阶段规划好线程数、会话并发上限。每个会话都在进行网络I/O和内存拷贝并发开得太狠未知的流量尖峰很容易把服务器拖垮。我的经验是先让SDK在自己的进程里稳定跑24小时再谈对外提供服务。
返回列表