两小时会议录音丢给转写接口,新手通常在三个地方翻车:同步接口 60 秒超时进不去、整段载入内存爆掉、95% 成功加 5% 失败等于 100% 重来。三个问题的共同解是分片——把失败半径从"整段"缩到"单片"。
怎么切:固定窗口加重叠
两种切法各有硬伤:固定窗口(每 60~120 秒一刀)实现零成本但词被拦腰切断;VAD 静音检测词界完整但依赖参数、安静录音切不开。工程折中是固定窗口 + 窗口间 1~2 秒重叠,重叠区转写后按时间戳去重,实现简单且词切断概率大降:
CHUNK_SEC, OVERLAP_SEC = 90, 2
def slice_audio(duration):
starts = list(range(0, duration, CHUNK_SEC - OVERLAP_SEC))
return [(s, min(s + CHUNK_SEC, duration)) for s in starts if s < duration]
90 秒窗口加 2 秒重叠意味着每 88 秒一个切点——数字可以按接口上限调,重叠必须保留。
轮询:指数退避加总超时
异步转写提交后轮询状态,两个要点缺一不可:
def poll_task(task_id, client):
wait = 2.0
while True:
state = client.task_status(task_id) # PENDING/RUNNING/DONE/FAILED
if state in ("DONE", "FAILED"):
return state
time.sleep(wait)
wait = min(wait * 1.6, 30) # 指数退避,封顶 30s
指数退避让短任务快速收口、长任务不空转;总超时必须有——单片超过 10 分钟视为挂死走重试,没有总超时的轮询就是死循环预备队。
断点续传:一个 JSON 文件就够
进程随时可能被打断(重启、发布),每片状态落盘,重启后跳过已完成片:
def transcribe_all(chunks, store):
results = {}
for i, (start, end) in enumerate(chunks):
if store.done(i):
results[i] = store.load(i)
continue
task = client.submit(chunk_file(i))
if poll_task(task.id, client) == "DONE":
store.save(i, client.fetch(task.id))
results[i] = store.load(i)
else:
raise RetryableChunk(i) # 只重试这一片
return merge(results) # 按时间戳合并+重叠区去重
checkpoint 用 JSON 文件即可,别上数据库。核心收益一句话:任何一片失败,代价只是那一片。
验收清单与选型参数
四步验收:切点处的词不被切断(重叠去重生效);人为杀进程重启后从断点继续;单片失败不影响其他片的结果合并;全片合并后时间轴连续无空洞。选接口看三个参数:单次时长上限(决定窗口硬顶)、异步任务支持(决定轮询还是同步等待)、并发配额(分片是并发友好型负载,QPS 决定总耗时)。分片缩半径、退避收状态、checkpoint 保断点——三者都是几十行代码,但没有它们,转写服务上生产就是定时炸弹。

评论0