长音频转写流水线:重叠分片与断点续传实现

两小时会议录音丢给转写接口,新手通常在三个地方翻车:同步接口 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

评论0

请先
显示验证码
没有账号?注册  忘记密码?