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),互不依赖:
| 引擎 | 模式 | 配置 | 默认 |
|---|---|---|---|
deepgram | streaming WS | DEEPGRAM_API_KEY | Python 默认 |
whisper | local / file or frames | model path 或注入的 runner | Swift 默认(ggml-tiny.en) |
parakeet | streaming WS(/v3/stream) | HOSTED_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++)暴露完全相同的引擎名deepgram、whisper、parakeet,具体见 sdks/device/README.md。
统一逻辑接口与 PCM 契约
无论选择哪种引擎,设备端上层代码只面对同一个逻辑接口:
start(pcm16le_mono_16khz chunks) -> transcript events stop()在各语言的具体实现中,这一逻辑接口体现为:
- Go:
StreamingTranscriber接口,包含AppendPCM(pcm []byte) error与Stop() error两个方法(sdks/device/go/omidevice/stt/stt.go); - TypeScript:
StreamingTranscriber接口,包含appendPcm(chunk: Uint8Array | ArrayBuffer): void与stop(): 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_000、PCM_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 开关 | Deepgram | Whisper | Parakeet |
|---|---|---|---|---|
| Python | always(bleak 依赖) | default extra | optionalwhisperextra | optionalparakeet |
| Swift | always(CoreBluetooth) | 编译OMI_STT_DEEPGRAM/ always-on client | 默认 SwiftWhisper | always-on client |
| React Native | peerreact-native-ble-plx | always-on JS WS | optional 注入WhisperRunner | always-on JS WS |
| TypeScript device | optionalBleTransport | optional | optional runner | optional |
| Go | build tagble | default net | build tagwhisper | default net |
| Rust | featureble | featurestt-deepgram | featurestt-whisper | featurestt-parakeet |
| Dart | optional BLE 包(后续) | always-on | optional runner | always-on |
| C++ | 注入BleBackend | OMI_STT_DEEPGRAM | OMI_STT_WHISPER | OMI_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;deepgram与parakeet默认即走网络通道。完整的跨语言能力对账表见 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/ws→ws、其余 →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:8080 | ws://parakeet.example:8080/v3/stream?sample_rate=16000 |
https://parakeet.example/gateway?region=eu | wss://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#debug | wss://parakeet.example/gateway/v3/stream?region=eu&sample_rate=8000 |
parakeet.example/gateway?redirect=https://other.example | wss://parakeet.example/gateway/v3/stream?redirect=https://other.example&sample_rate=16000 |
https://parakeet.example/gateway%2Fv1?region=eu%20zone | wss://parakeet.example/gateway%2Fv1/v3/stream?region=eu%20zone&sample_rate=16000 |
https://parakeet.example/gateway?sample_rate=8000&token=abc#frag | wss://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)为:
- 通过
ParakeetWSURL生成 WS 地址并建立连接; - 后台读循环解析每条 JSON 消息,遇到
type == "ready"时把ready标志置真(此前的 PCM 一律丢弃,Go 与 Dart 实现都在!ready时直接忽略AppendPCM调用); - 之后收到的消息通过
extractText提取文本——优先取text字段,其次取transcript字段(Go 的extractText见 stt.go,TypeScript 还额外兼容了segments数组拼接); - 调用方持续推送 PCM 字节(二进制帧);
- 结束时发送文本帧
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=16000、channels=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参数都是为这个窗口服务的。
引擎工厂:一键创建三种转写器
为了进一步降低集成成本,各语言还提供了按引擎名分发的工厂函数:
- TypeScript:
createTranscriber(engine, opts)(typescript/src/stt/index.ts),parakeet分支会回退读取环境变量HOSTED_PARAKEET_API_URL(opts.apiUrl || process.env.HOSTED_PARAKEET_API_URL); - Dart:
createTranscriber({engine, onTranscript, apiKey, apiUrl, whisperRunner, sampleRate})(dart/lib/stt/stt.dart),各引擎缺参时抛出明确的ArgumentError; - Swift:
SttFactory同样从ProcessInfo.processInfo.environment["HOSTED_PARAKEET_API_URL"]读取 Parakeet 地址(sdks/swift/Sources/omi-lib/STT/SttFactory.swift); - Python:
sdks/python/omi/transcribe.py中parakeet引擎使用HOSTED_PARAKEET_API_URL或engine_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 设备端时的操作清单如下:
- 确定 PCM 输入:保证送入引擎的音频是 16-bit LE mono @ 16 kHz,与 BLE 解码契约(sdks/device/PROTOCOL.md)一致;Rust 常量
PCM_SAMPLE_RATE_HZ、PCM_CHANNELS是参考基准。 - 选择引擎并准备凭证:
- 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)。
- Deepgram:设置
- 按语言启用特性开关: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。 - 正确处理停止时序:发送引擎专属收尾帧(Parakeet 用
finalize,Deepgram 用{"type":"CloseStream"})后,等待服务端关闭或 drain 超时窗口结束,再断开连接,避免尾部转写丢失。 - 测试验证:可参考 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),仅供参考