news 2026/9/17 2:29:02

Omi 设备端 STT 引擎统一契约:Deepgram / Whisper / Parakeet 三种转录后端的跨语言实现指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Omi 设备端 STT 引擎统一契约:Deepgram / Whisper / Parakeet 三种转录后端的跨语言实现指南

Omi 设备端 STT 引擎统一契约:Deepgram / Whisper / Parakeet 三种转录后端的跨语言实现指南

【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend

Omi 是"看得见你的屏幕、听得见你的对话"的 AI 可穿戴设备。本篇技术指南以 sdks/device/STT.md 为骨架,深入讲解设备端语音转录(STT)的统一契约:三种可选后端(Deepgram 流式 WebSocket、Whisper 本地批处理、Parakeet 流式 WebSocket)在所有设备 SDK 中如何以一致的接口暴露,各自的配置项、wire format、优雅停止语义,以及各语言下的特性开关(feature gate)与源码级实现细节。读完本文,你将掌握在 Python、Swift、React Native、TypeScript、Go、Rust、Dart、C++ 任一设备端集成 Omi 语音转录的完整方法。

三种转录引擎总览

所有设备 SDK 都暴露完全相同的三种转录后端,每种都是可选的 / 通过特性开关启用(optional / feature-gated),互不依赖:

引擎模式配置默认
deepgramstreaming WSDEEPGRAM_API_KEYPython 默认
whisperlocal / file or framesmodel path 或注入的 runnerSwift 默认(ggml-tiny.en
parakeetstreaming WS(/v3/streamHOSTED_PARAKEET_API_URL(http→ws)hosted Omi Parakeet

三种引擎的定位差异非常清晰:

  • Deepgram:托管式云端流式转录,需要 API Key,适合延迟敏感、需要标点与大小写润色的实时场景;
  • Whisper:完全本地运行(Swift 默认后端,内置ggml-tiny.en模型),不上传任何音频,适合隐私敏感或离线的场景;
  • Parakeet:Omi 自托管的流式转录服务(backend/parakeet 目录中即包含完整的 GPU 批处理引擎与流式处理器实现),通过/v3/stream端点接收 16 kHz PCM 流。

这一引擎命名在三层 SDK 中完全一致:完整设备 SDK(sdks/python、sdks/swift、sdks/react-native)与新引入的可移植协议包(sdks/device 下的 TypeScript / Go / Rust / Dart / C++)暴露完全相同的引擎名deepgramwhisperparakeet,具体见 sdks/device/README.md。

统一逻辑接口与 PCM 契约

无论选择哪种引擎,设备端上层代码只面对同一个逻辑接口:

start(pcm16le_mono_16khz chunks) -> transcript events stop()

在各语言的具体实现中,这一逻辑接口体现为:

  • GoStreamingTranscriber接口,包含AppendPCM(pcm []byte) errorStop() error两个方法(sdks/device/go/omidevice/stt/stt.go);
  • TypeScriptStreamingTranscriber接口,包含appendPcm(chunk: Uint8Array | ArrayBuffer): voidstop(): void(sdks/device/typescript/src/stt/index.ts);
  • Dart:抽象类StreamingTranscriber,包含appendPcm(Uint8List chunk)Future<void> stop()(sdks/device/dart/lib/stt/stt.dart);
  • C++omi::device::stt::Engine枚举(Deepgram/Whisper/Parakeet)加 URL 工具函数(sdks/device/cpp/include/omi/device/stt/stt.hpp)。

PCM 契约与 BLE 解码完全一致:16-bit LE mono @ 16 kHz。这是 Omi 设备协议层的统一约定,见 sdks/device/PROTOCOL.md:BLE 音频特征(19b10001-e8f2-537e-4f6c-d104768a1214)的 notify 载荷为[3 字节包头][codec 载荷...],各 SDK 先剥掉前 3 字节包头,再经 Opus/PCM 解码后得到 16 kHz 单声道 16-bit LE PCM,这正是 STT 引擎的输入格式。Rust SDK 中对应的常量也印证了这一点:PCM_SAMPLE_RATE_HZ: u32 = 16_000PCM_CHANNELS: u8 = 1(sdks/device/rust/src/lib.rs)。

因此,从设备到转写的完整数据链路是:BLE 音频 notify → 剥离 3 字节包头 → Opus 解码(160 或 320 采样/帧)→ 16 kHz PCM16 分片 → 注入start/appendPcm→ 收到 transcript 事件 →stop()收尾

各语言特性开关矩阵

由于 BLE 协议栈是操作系统相关的(CoreBluetooth、bluer/btleplug、WinRT、Web Bluetooth、noble 等),且三种 STT 引擎各有依赖,每种语言都以各自的机制对功能做显式开关:

语言BLE 开关DeepgramWhisperParakeet
Pythonalways(bleak 依赖)default extraoptionalwhisperextraoptionalparakeet
Swiftalways(CoreBluetooth)编译OMI_STT_DEEPGRAM/ always-on client默认 SwiftWhisperalways-on client
React Nativepeerreact-native-ble-plxalways-on JS WSoptional 注入WhisperRunneralways-on JS WS
TypeScript deviceoptionalBleTransportoptionaloptional runneroptional
Gobuild tagbledefault netbuild tagwhisperdefault net
Rustfeatureblefeaturestt-deepgramfeaturestt-whisperfeaturestt-parakeet
Dartoptional BLE 包(后续)always-onoptional runneralways-on
C++注入BleBackendOMI_STT_DEEPGRAMOMI_STT_WHISPEROMI_STT_PARAKEET

以 Rust 为例,sdks/device/rust/Cargo.toml 中每个 STT 引擎都是独立 feature,且各自只引入所需依赖:

[features] default = [] ble = ["dep:btleplug", "dep:tokio", "dep:uuid", "dep:futures-util"] stt-deepgram = ["dep:tokio-tungstenite", "dep:tokio", "dep:futures-util", "dep:http"] stt-parakeet = ["dep:tokio-tungstenite", "dep:tokio", "dep:futures-util"] stt-whisper = [] stt-all = ["stt-deepgram", "stt-parakeet", "stt-whisper"]

C++ 端则以编译宏控制:定义OMI_STT_DEEPGRAM并链接 WebSocket 栈即可启用 Deepgram,定义OMI_STT_PARAKEET启用 Parakeet,定义OMI_STT_WHISPER并注入本地 runner 启用 Whisper;BLE 则通过实现omi::device::BleBackend后用SetBleBackend()注入,见 sdks/device/cpp/include/omi/device/stt/stt.hpp 中的注释说明。

Go 使用 build tag:-tags ble启用 tinygo 蓝牙栈,-tags whisper启用本地 Whisper runner;deepgramparakeet默认即走网络通道。完整的跨语言能力对账表见 sdks/device/PARITY.md,其中明确标注了各语言在 BLE 扫描/连接/监听、音频 notify + 包头剥离、codec/电量读取以及三种 STT 引擎上的支持程度。

Parakeet wire format 深度解析

Parakeet 的线上协议是整份契约中最关键的对接细节,原文档给出的规范如下:

  • Base:HOSTED_PARAKEET_API_URL,例如https://parakeet.example
  • WS:把 http 替换为 ws,并追加/v3/stream?sample_rate=16000
  • 等待 JSON{"type":"ready"}
  • 发送原始 PCM 字节;结束时发送字符串finalize

源码实现把这四条规则落实得非常精确。Go 的ParakeetWSURL(apiURL, sampleRate)(sdks/device/go/omidevice/stt/stt.go)会依次处理:空白修剪、无 scheme 时自动补https://http/wsws、其余 →wss、去掉路径尾斜杠后拼/v3/stream、保留已有 query 参数并把sample_rate覆盖为调用方指定值、剥离 fragment。它对应的测试用例 sdks/device/go/omidevice/stt/stt_test.go 覆盖了 8 种边界情况,非常有参考价值:

输入输出
https://parakeet.example/wss://parakeet.example/v3/stream?sample_rate=16000
http://parakeet.example:8080ws://parakeet.example:8080/v3/stream?sample_rate=16000
https://parakeet.example/gateway?region=euwss://parakeet.example/gateway/v3/stream?region=eu&sample_rate=16000
https://parakeet.example/gateway/wss://parakeet.example/gateway/v3/stream?sample_rate=16000
https://parakeet.example/gateway?region=eu#debugwss://parakeet.example/gateway/v3/stream?region=eu&sample_rate=8000
parakeet.example/gateway?redirect=https://other.examplewss://parakeet.example/gateway/v3/stream?redirect=https://other.example&sample_rate=16000
https://parakeet.example/gateway%2Fv1?region=eu%20zonewss://parakeet.example/gateway%2Fv1/v3/stream?region=eu%20zone&sample_rate=16000
https://parakeet.example/gateway?sample_rate=8000&token=abc#fragwss://parakeet.example/gateway/v3/stream?sample_rate=16000&token=abc

TypeScript 版parakeetWsUrl(sdks/device/typescript/src/stt/index.ts)与 Dart 版parakeetWsUrl(sdks/device/dart/lib/stt/stt.dart)语义一致;Rust 版parakeet_ws_url也通过strip_prefix完成同样的 scheme 替换(sdks/device/rust/src/lib.rs),并有单元测试断言https://parakeet.example/wss://parakeet.example/v3/stream?sample_rate=16000

连接建立后的交互流程(以 Go 的NewParakeet为例,sdks/device/go/omidevice/stt/stt.go)为:

  1. 通过ParakeetWSURL生成 WS 地址并建立连接;
  2. 后台读循环解析每条 JSON 消息,遇到type == "ready"时把ready标志置真(此前的 PCM 一律丢弃,Go 与 Dart 实现都在!ready时直接忽略AppendPCM调用);
  3. 之后收到的消息通过extractText提取文本——优先取text字段,其次取transcript字段(Go 的extractText见 stt.go,TypeScript 还额外兼容了segments数组拼接);
  4. 调用方持续推送 PCM 字节(二进制帧);
  5. 结束时发送文本帧finalize并等待服务端把尾部结果吐完再关闭连接。

Deepgram 流式接口细节

Deepgram 的接入参数与 Parakeet 不同,但同样在各语言中保持高度一致。以 Go 的NewDeepgram为例(sdks/device/go/omidevice/stt/stt.go),其 WS 地址形如:

wss://api.deepgram.com/v1/listen?punctuate=true&model=nova&language=en-US&encoding=linear16&sample_rate=16000&channels=1

关键点:

  • 鉴权方式:API Key 通过 HTTP 头Authorization: Token <DEEPGRAM_API_KEY>传递,绝不进入 URL(TypeScript 实现甚至要求调用方传入一个"带认证能力的 WebSocket 工厂"createWebSocket,把凭证封装在工厂内部,见 typescript/src/stt/index.ts);
  • 音频参数encoding=linear16(PCM16)、sample_rate=16000channels=1,与 Omi 的 PCM 契约严格对应;
  • 结果解析:响应 JSON 结构为channel.alternatives[0].transcript,Go 的readDeepgram(stt.go)与 Dart / TypeScript 实现解析路径完全一致;
  • 收尾语义:发送 JSON 控制消息{"type":"CloseStream"}请求服务端冲刷并关闭,而不是finalize

DEEPGRAM_API_KEY环境变量在完整 SDK 侧的使用可参考 sdks/python/README.md(export DEEPGRAM_API_KEY=...后由os.getenv("DEEPGRAM_API_KEY")读取)以及 sdks/swift/README.md(deepgramAPIKey: "YOUR_DEEPGRAM_API_KEY")。

本地 Whisper:注入 runner 的批处理模式

Whisper 是三种引擎中唯一的本地引擎,不走网络。它的统一形态是:调用方注入一个"runner"函数(负责把一段 PCM 交给本地模型推理),SDK 负责按批切分与生命周期管理。Swift 的默认后端是 SwiftWhisper(ggml-tiny.en模型),其余语言则需要自行注入 runner。

以 Go 的whisperBatch(sdks/device/go/omidevice/stt/stt.go)为例,其批处理语义为:

  • 批大小固定为16000 * 2 * 5字节,即5 秒的 PCM16 音频(TypeScript 通过batchSeconds参数可调,默认 5 秒;Rust 的WhisperTranscriber::new同样内置16000 * 2 * 5);
  • AppendPCM先把音频累积进缓冲,不足 5 秒时不触发推理;
  • 缓冲满 5 秒即整批交给 runner;若推理失败,缓冲被保留,下次调用重试同一批样本,而不是静默丢弃
  • Stop()会把不足 5 秒的尾部也 flush 掉(deliverWhenStopped语义);
  • 推理成功后清空缓冲,空转写文本不会触发回调。

Rust 版(sdks/device/rust/src/lib.rs)对这一语义的测试覆盖得尤为完整:失败批次保留供显式重试(failed_batch_is_retained_for_explicit_append_retry)、空缓冲不调用 runner(empty_flush_does_not_call_runner)、尾部只 flush 一次且 flush 后仍可继续追加音频(final_tail_is_flushed_once_and_more_audio_can_be_appended)、超大块一次性整块转写并释放峰值内存(successful_oversized_append_releases_peak_allocation)等。这些测试既是契约的精确文档,也是集成 Whisper runner 时的行为基准。

优雅停止:finalize / CloseStream 与 drain 窗口

流式转录最容易踩的坑是提前关闭 socket 导致尾部结果丢失。Omi 契约对此做了明确区分(见 Go 源码注释 sdks/device/go/omidevice/stt/stt.go):

  • Parakeet的收尾帧是纯文本finalize
  • Deepgram的收尾帧是 JSON 控制消息{"type":"CloseStream"}
  • 把 Parakeet 的帧发给 Deepgram 会被忽略,socket 关闭后服务端最终结果永远不会被发出,所以二者绝不能混用。

对应的停止流程(GowsTranscriber.Stop,stt.go)是:先发送引擎专属的 finalize 帧,然后等待服务端主动关闭,或等待一个 bounded 的 drain 超时defaultDrainTimeout = 2s,Dart / TypeScript 默认 5s,均可配置),确保读循环把服务端在 finalize 之后吐出的尾部分词结果全部交付给onTranscript,之后才真正关闭底层连接。TypeScript 的drainTimeoutMs、Dart 的drainTimeout参数都是为这个窗口服务的。

引擎工厂:一键创建三种转写器

为了进一步降低集成成本,各语言还提供了按引擎名分发的工厂函数:

  • TypeScriptcreateTranscriber(engine, opts)(typescript/src/stt/index.ts),parakeet分支会回退读取环境变量HOSTED_PARAKEET_API_URLopts.apiUrl || process.env.HOSTED_PARAKEET_API_URL);
  • DartcreateTranscriber({engine, onTranscript, apiKey, apiUrl, whisperRunner, sampleRate})(dart/lib/stt/stt.dart),各引擎缺参时抛出明确的ArgumentError
  • SwiftSttFactory同样从ProcessInfo.processInfo.environment["HOSTED_PARAKEET_API_URL"]读取 Parakeet 地址(sdks/swift/Sources/omi-lib/STT/SttFactory.swift);
  • Pythonsdks/python/omi/transcribe.pyparakeet引擎使用HOSTED_PARAKEET_API_URLengine_kwargs传入的api_url;底层实现见 sdks/python/omi/stt/parakeet.py(api_url or os.getenv("HOSTED_PARAKEET_API_URL"),缺失时抛ValueError)。

因此,一个典型的设备端集成只需要两步:① 把 BLE 解出的 PCM16 分片喂给引擎;② 在onTranscript回调里消费转写文本。引擎的鉴权凭证、URL 规整、ready 等待、finalize/drain 收尾全部由 SDK 封装完成。

集成清单与注意事项

基于以上契约,把 STT 接入 Omi 设备端时的操作清单如下:

  1. 确定 PCM 输入:保证送入引擎的音频是 16-bit LE mono @ 16 kHz,与 BLE 解码契约(sdks/device/PROTOCOL.md)一致;Rust 常量PCM_SAMPLE_RATE_HZPCM_CHANNELS是参考基准。
  2. 选择引擎并准备凭证
    • Deepgram:设置DEEPGRAM_API_KEY
    • Parakeet:设置HOSTED_PARAKEET_API_URL(自托管或 Omi 托管地址),SDK 会自动完成 http→ws 与/v3/stream?sample_rate=16000拼接;
    • Whisper:注入本地 runner(Swift 直接用内置 SwiftWhisper /ggml-tiny.en)。
  3. 按语言启用特性开关:Rust 用--features stt-deepgram/stt-parakeet/stt-whisper/stt-all;Go 用-tags ble/-tags whisper;C++ 用OMI_STT_DEEPGRAM/OMI_STT_PARAKEET/OMI_STT_WHISPER编译宏;Python 用pip install omi[whisper]/omi[parakeet]等 extra。
  4. 正确处理停止时序:发送引擎专属收尾帧(Parakeet 用finalize,Deepgram 用{"type":"CloseStream"})后,等待服务端关闭或 drain 超时窗口结束,再断开连接,避免尾部转写丢失。
  5. 测试验证:可参考 Go 的TestParakeetWSURL(URL 规整 8 组边界用例)与 Rust 的 whisper 测试集(重试、空缓冲、尾部 flush、超大块等),用同样边界用例验证自己语言的集成。

进一步可阅读:sdks/device/STT.md(本契约原文)、sdks/device/PARITY.md(跨语言能力矩阵)、sdks/device/PROTOCOL.md(BLE 协议与 PCM 契约)、sdks/python、sdks/swift、sdks/react-native(三种完整设备 SDK 的参考实现),以及 backend/parakeet(Omi 自托管 Parakeet 服务端源码)。

【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 2:27:11

STM32CubeMX定时器配置避坑指南:从报错到稳定运行

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 2:27:10

AI漫剧工业化流水线:一站式工作台如何实现量产与品控

1. 这不是“AI画画AI配音”的拼凑&#xff0c;而是一套真正能跑通的漫剧工业化流水线最近三个月&#xff0c;我陆陆续续测试了27个标榜“AI漫剧制作”的工具或平台&#xff0c;从开源项目到SaaS服务&#xff0c;从单机脚本到云端工作台&#xff0c;踩过的坑足够填满三本实操笔记…

作者头像 李华
网站建设 2026/9/17 2:23:46

uTools超级文本片段:跨应用实时模板,终结重复输入

每天打开电脑&#xff0c;总有一堆内容在不停重复&#xff1a;回复客户的第一句话、提交代码前的注释模板、报销单里的公司抬头、每周周报的开头格式。以前我桌面一直放着几个 txt 文件&#xff0c;里面存着各种常用话术&#xff0c;要用的时候打开复制粘贴&#xff1b;后来换成…

作者头像 李华
网站建设 2026/9/17 2:23:36

数据分析与科学计算:从NumPy到Pandas的完整实操指南

做数据分析这行有些年头了&#xff0c;从最早用 Excel 抠数据&#xff0c;到后来天天跟 Python、NumPy、Pandas 打交道&#xff0c;一个很深的感受是&#xff1a;真正难的不是某个函数怎么用、某张图怎么画&#xff0c;而是你拿到一堆杂乱数据时&#xff0c;脑子里的分析框架和…

作者头像 李华
网站建设 2026/9/17 2:21:02

Flutter Web与混合开发:架构选型、通信设计与工程实践

做了一段跨平台项目之后&#xff0c;我越来越觉得有个观点值得反复说&#xff1a;Flutter Web和“用Flutter统一所有端”压根不是一回事。很多团队立项时都规划得挺美好——一套Dart代码横跨Android、iOS、Web&#xff0c;结果真到了发布阶段&#xff0c;被首屏体积、SEO和浏览…

作者头像 李华