视频转写全链路实录:从免费额度到说话人分离

2026-10-16

想把一个 7 分钟的访谈视频转成逐字稿,要求带时间戳、区分说话人。我用腾讯云语音识别全链路跑通了:1 分钟出结果,96 句转成 60 个话轮,3915 字。

但链路打通之前,我在 8 个坑里摔了一遍。这篇是完整实录。

为什么做这个

需求:视频转文字稿,两个硬要求——每句话带时间戳、区分说话人。市面上的免费工具要么没时间戳,要么不分说话人,要么把数据传到不知名的服务器。

选型时定了条长期策略:默认用临期/限免的 API,本地做备用。按有效期短的先用:

  1. 腾讯云录音文件识别(新用户 10 小时包)——自带说话人分离+逐句时间戳,有额度时默认走这条
  2. 硅基流动 SenseVoiceSmall(待接入)
  3. 腾讯云一句话识别分块(无说话人分离)
  4. Groq 免费层
  5. 本地 faster-whisper + ECAPA(备用链路)

核心逻辑:免费额度过期不候,先用要过期的。10 小时包 10 月 31 号到期,不用白不用。

怎么做的

路线 A:base64 直传(≤5MB 音频)

音频压成 16kHz 单声道、48k 码率的 mp3(7.8 分钟约 2.67MB),base64 编码后直接调 CreateRecTask:

{"EngineModelType": "16k_zh",
 "ChannelNum": 1,
 "ResTextFormat": 1,
 "SourceType": 1,           # base64 直传
 "Data": base64音频,
 "DataLen": len,
 "SpeakerDiarization": 1,   # 说话人分离
 "SpeakerNumber": 0,        # 16k 引擎不支持指定人数,0=自动识别
 "FilterPunc": 0}

零依赖、最顺手,是默认路线。

路线 B:COS 地址(>5MB 音频)

音频先传对象存储,拿预签名 URL 调 CreateRecTask:

{"EngineModelType": "16k_zh",
 "ChannelNum": 1,
 "ResTextFormat": 1,
 "SourceType": 0,           # COS 地址
 "Url": 预签名URL,
 "SpeakerDiarization": 1,
 "SpeakerNumber": 0,
 "FilterPunc": 0}

这是 >5MB 音频的逃生通道。

轮询结果

d = call("DescribeTaskStatus", {"TaskId": task_id})["Response"]["Data"]
# d["StatusStr"]: "success" / "failed"
# 成功后取 d["ResultUrl"] 下载结果文本

结果格式是 [开始,结束,说话人ID] 文本,每行一句。

话轮合并

按说话人 ID 合并连续同说话人的句子,输出标准格式:

[00:00:00] 说话人:同一人连续说的完整的一段话

每话轮只有一个开始时间戳,不按固定时长机械切段。说话人名字按内容推断,交付时必须说明是推断的。

实测数据

抖音访谈视频(465.9 秒),base64 路线:

  • 音频:2.67MB mp3
  • 任务提交到成功:约 1 分钟
  • 结果:96 句 → 60 话轮 → 3915 字
  • 花费:免费包约 0.13 小时

踩过的坑

坑 1:AppId ≠ 账号 UIN

现象:桶名后缀用了账号编号,create_bucket 报 AccessDenied,但 list_buckets 却是 200。

原因:腾讯云的 AppId 和账号 UIN 是两个不同的东西。桶名必须为 桶名-AppId。

解法:去控制台右上角头像→账号信息页查真实 AppId(本账号 AppId=1503285150,UIN=100053571078)。

教训:云厂商文档里"账号 ID"有好几种,动手前先确认用的是哪一个。

坑 2:新账号 COS 未在控制台开通

现象:create_bucket 报 AccessDenied。

原因:对象存储要在控制台手动点"开通",API 开不了。

解法:控制台手动开通,开通即领 50GB 标准存储免费 180 天。

坑 3:SourceType 没有 2

现象:CreateRecTask 报 InvalidParameter: SourceType: 2 not in list: [0,1]。

原因:想当然以为 COS 地址是 SourceType=2。

解法:COS 地址方式 = SourceType=0 + Url;base64 = SourceType=1。就两个值。

坑 4:私有桶裸 URL

现象:ASR 任务 failed,ErrorMsg: Failed to download audio file!。

原因:私有桶的裸 URL 外部访问不了。

解法:用 get_presigned_url 生成预签名 URL(有效期 1 小时)再提交。

坑 5:本机直连 COS 被墙

现象:SSL: WRONG_VERSION_NUMBER。

解法:必须走代理(保留 https_proxy 环境变量);CosConfig 的 Timeout 设 120 秒以上(代理传 2.8MB 约 98 秒,默认 30 秒必超时)。

坑 6:返回结构认错层级

现象:KeyError: 'TaskId'。

原因:CreateRecTask 和 DescribeTaskStatus 的返回都在 Response.Data 下,不是 Response 直属。

教训:调新 API 先打印完整返回再写解析代码,5 分钟能避的坑。

坑 7:16k 引擎指定说话人数

现象:传 SpeakerNumber=2 不生效。

原因:16k 引擎不支持指定人数。

解法:固定传 0(自动识别)。

坑 8:base64 单文件超限

现象:服务端拒收。

解法:单文件 ≤5MB;超了就压 48k mp3 或改走路线 B。

总结

  1. 免费额度按"有效期短先用":过期不候的东西,不用就是浪费
  2. 云厂商的"账号 ID"有好几种,动手前先确认用的是哪一个(AppId≠UIN)
  3. 调新 API 先打印完整返回再写解析,不要靠猜结构
  4. 转写质量由引擎层保证,与传输方式无关——选路线只看文件大小和依赖成本
  5. base64 直传是默认路线,COS 地址是逃生通道,两条都验证过才算链路打通

以上,全文完。

最后认识一下,我是 Liyu,一名 AI 实践爱好者,会不定期记录我的学习过程,期待你的关注和分享。

← 返回文章列表