Files
tps-dashboard/docs/doco/doco_xfyun_integration_notes.md
T

182 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 讯飞 ASR 集成踩坑笔记
> demo 期间累积的所有讯飞接入坑点。建议入 TPS 知识库,作为未来接入新 ASR 供应商时的对照。
---
## 选型坑
### 坑 1:不要用"大模型版"
- 讯飞开放平台有两个录音转写产品:
- 「录音文件转写(**标准版**)」← **用这个**
- 「录音文件转写(**大模型版**)」← 不要用
- **大模型版的免费包阉割了 `language` 参数**,调用时会报 `language verify fail`
- 这个错误信息**严重误导**:看上去是语种没设对,实际是免费包没开通该参数权限
- 排查耗时:约 30 分钟
### 坑 2:消费端和开发端密钥不通
- 「讯飞听见 App」属于**消费端**,给最终用户做人工转写用
- 「讯飞开放平台」属于**开发端**,给开发者调 API 用
- **两套密钥互不相通**——开发端的 APP_ID 在消费端没用,反之亦然
- 如果只在讯飞听见 App 注册了账号,**没有** APP_ID 和 SecretKey,必须再去开放平台注册一次
### 坑 3:免费额度不会自动激活
- 开放平台首页显示"5 小时免费试用包",但**只有走 0 元购买流程**才会激活
- 看到额度但没下单 → 调用时会报余额不足
- 流程:商品页 → 0 元下单 → 支付(实际免费)→ 额度到账
---
## 音频规格坑
### 坑 4:必须是 16kHz / 单声道 / 16bit WAV
- 其他规格讯飞会报错(且错误信息可能误导成"language verify fail"或"audio decode error"
- MP3 在标准版**也支持**,但保险起见统一转 WAV
- 转码命令(ffmpeg):
```bash
ffmpeg -i input.mp3 -ac 1 -ar 16000 -sample_fmt s16 output_16k_mono.wav
```
- `-ac 1` = 单声道
- `-ar 16000` = 16kHz 采样率
- `-sample_fmt s16` = 16bit 位深
### 坑 5:时长字段不严格校验但建议传准
- API 要求传 `duration`(毫秒)
- 实测哪怕传 0 也能上传成功,但官方文档建议传准确值
- WAV 从文件头读:`n_frames / sample_rate * 1000`
- MP3 用 `mutagen.mp3.MP3` 读
---
## 接口坑
### 坑 6:签名算法是老式 HmacSHA1,不是常见的 HmacSHA256
```python
# signa = base64(HmacSHA1(MD5(appid + ts), secretKey))
md5_str = hashlib.md5((appid + ts).encode("utf-8")).hexdigest() # 32 位小写 hex
mac = hmac.new(secret_key.encode("utf-8"), md5_str.encode("utf-8"),
digestmod=hashlib.sha1)
signa = base64.b64encode(mac.digest()).decode("utf-8")
```
注意:
- MD5 输出是**32 位小写 hex 字符串**,不是 16 字节 raw bytes
- HMAC 的 key 是 `secretKey`,data 是 MD5 字符串本身(不是其字节)
- 最后做 base64 编码
### 坑 7:参数全部走 URL query,不是 form-data 或 JSON body
- 上传接口:`POST https://raasr.xfyun.cn/v2/api/upload?appId=...&signa=...&ts=...&...`
- `Content-Type: application/json`,但**请求体是音频原始字节流**(不是 JSON)
- 所有业务参数(`fileSize` `fileName` `duration` `language` `pd` `hotWord` …)都在 URL query 里
- 中文热词必须做 URL encode`quote(hot_word_str, safe='')`
### 坑 8:热词用 `|` 分隔,不是逗号
```python
hot_word_str = "|".join(HOT_WORDS) # 萨德|爱国者|标准-6|...
params["hotWord"] = hot_word_str
```
限制:
- 最多 200 个热词
- 每个热词 216 字
- 整个 hotWord 参数 URL encode 后不能超过某个长度(实测 35 个常规军事术语没问题)
---
## 异步查询坑
### 坑 9:轮询用 POST 不是 GET
- 查询结果接口:`POST https://raasr.xfyun.cn/v2/api/getResult?...`
- 即便没有 body 也是 POST
- 用 GET 会报方法不允许
### 坑 10status 状态码语义
| status | 含义 |
|---|---|
| 0 | 待处理 |
| 1 | 处理中 |
| 4 | **完成** |
| -1 | 失败 |
完成是 `4`,不是 `2` 也不是 `200`。
### 坑 11:轮询间隔与超时
- 26 分钟音频实测转写耗时约 5 分钟
- 推荐轮询间隔 30 秒(taskEstimateTime 返回的预估值仅供参考)
- 超时上限设 30 分钟比较保险
---
## 结果解析坑
### 坑 12:返回结果是双层嵌套 JSON 字符串
```python
# 外层
data = response.json()
order_result_str = data["content"]["orderResult"] # 这是 JSON 字符串
# 中间还有一层反斜杠转义
cleaned = re.sub(r"\\\\", r"\\", order_result_str)
outer = json.loads(cleaned)
# 句子在 outer["lattice"] 里
for item in outer["lattice"]:
inner_str = item["json_1best"] # 又是 JSON 字符串
inner = json.loads(inner_str)
# 句子时间在 inner["st"]["bg"] / inner["st"]["ed"]
# 句子内容在 inner["st"]["rt"][..]["ws"][..]["cw"][..]["w"]
```
每个 `cw` 是一个字 / 词;要拼接所有 `cw["w"]`(跳过 `wp == "g"` 的空白标记)。
### 坑 13:时间戳单位是毫秒(不是秒、不是百分秒)
- `bg` `ed` 都是毫秒(int
- 与 B 稿的 `[Nm Ns]` 对齐时记得换算
---
## 业务参数坑
### 坑 14`pd=mil` 对军事术语显著加分
- 不开 `pd`:默认通用领域,军事术语会被识别成同音常见词
- 开 `pd=mil`:军事领域专用模型介入,配合热词效果叠加
### 坑 15`eng_smoothproc` + `eng_colloqproc` 让 ASR 接近书面
- `eng_smoothproc=true`:去掉"嗯""那个""呃"
- `eng_colloqproc=true`:口语规整("咱们"→"我们"等)
- 两个都开后,输出已经非常接近书面文稿
- 但**不会清理"那么""呢""啊""这个"**——这些保留与否需要在融合层决定
---
## 凭证与配额(demo 阶段使用)
```
APP_ID: 84eff996
SECRET_KEY: d37fb0c77e7f4b4f4398e6a8834d1017
免费包: 5 小时(已激活),有效期至 2027 年 5 月
```
> ⚠️ **正式上线时替换为生产凭证**,并把 SECRET_KEY 从代码移到环境变量 / 密钥管理服务。
---
## 一句话总结
讯飞 ASR 标准版**能用、好用**,但接入文档稀少且错误信息误导性强;以上 15 个坑全部踩完后,单次调用的成功率接近 100%。后续接入新 ASR 供应商时,建议按本笔记的结构(选型 / 音频 / 接口 / 异步 / 解析 / 业务参数 / 凭证)做同样的整理。