语音转文本完整方案

test 语音转文本完整方案

一、方案概述

将 test 录课视频(MKV 格式)批量转录为带标点的中文文本。

  • 输入U:\test\ 下 119 个 MKV 文件(共 93GB,约 83 小时音频)
  • 输出F:\test_transcripts\ 下 119 个同名 .txt 文件(共 115 万字)
  • 总耗时:约 80 分钟(含音频提取 + ASR + 标点恢复)
  • 环境:Windows 10 + WSL Ubuntu 24.04 + RTX 4070 Ti SUPER 16GB

二、技术选型

模型对比(实测数据)

模型中文精度GPU 实时率VRAM 占用特点
SenseVoice-Small⭐⭐⭐⭐51.8x~2GB最快,情绪/事件检测
Paraformer-Large⭐⭐⭐⭐⭐33.5x~3GB更准,内置标点,支持说话人分离
Fun-ASR-Nano⭐⭐⭐⭐⭐17x~4GBLLM 级精度(需 transformers 兼容修复)

最终选择:SenseVoice-Small + ct-punc

理由

  1. 速度最快(51.8x 实时),83 小时音频纯 ASR 只需 ~12 分钟
  2. 中文识别准确率在 AISHELL-1/2 上超越 Whisper
  3. 标点恢复用 ct-punc 模型后处理(几乎不耗时)
  4. 说话人分离可选(加 cam++ 模型,但大部分单人课程不需要)

三、环境搭建

3.1 前置条件

Windows 10/11 + WSL2 Ubuntu 24.04
NVIDIA GPU(CUDA 12.x)
ffmpeg(Windows 或 WSL 内安装均可)

3.2 WSL 内 Python 环境

# 创建虚拟环境
python3 -m venv ~/funasr_env
source ~/funasr_env/bin/activate

# 安装 PyTorch(根据 CUDA 版本选择)
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124

# 安装 FunASR
pip install funasr -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

# 安装 ffmpeg(WSL 内)
sudo apt-get install -y ffmpeg

3.3 验证安装

source ~/funasr_env/bin/activate
python3 -c "
import torch
print(f'PyTorch {torch.__version__}, CUDA: {torch.cuda.is_available()}')
import funasr
print(f'FunASR {funasr.__version__}')
"

四、完整流程

步骤 1:挂载 Windows 盘

# WSL 内挂载网络盘/本地盘
sudo mkdir -p /mnt/u /mnt/f
sudo mount -t drvfs 'U:' /mnt/u   # test 源文件
sudo mount -t drvfs 'F:' /mnt/f   # 输出目录

步骤 2:批量提取音频(ffmpeg 并行)

#!/bin/bash
# extract_all.sh — 从 MKV 批量提取 16kHz 单声道 WAV

OUTDIR="/mnt/f/test_audio"
mkdir -p "$OUTDIR"

# 生成文件列表
find /mnt/u/test -name '*.mkv' -type f | sort > /tmp/mkv_list.txt
TOTAL=$(wc -l < /tmp/mkv_list.txt)
echo "共 $TOTAL 个文件待提取"

# 单文件提取(自动跳过已存在的)
extract_one() {
    local src="$1"
    local rel="${src#/mnt/u/test/}"
    local dst="$OUTDIR/${rel%.mkv}.wav"
    mkdir -p "$(dirname "$dst")"
    [ -f "$dst" ] && echo "SKIP: $rel" && return 0
    ffmpeg -y -loglevel error -i "$src" -vn -acodec pcm_s16le -ar 16000 -ac 1 "$dst"
    [ $? -eq 0 ] && echo "OK: $rel → $(du -h "$dst" | cut -f1)" || echo "FAIL: $rel"
}
export -f extract_one
export OUTDIR

# 8 并发提取(网络盘建议 4-8,本地盘可更高)
cat /tmp/mkv_list.txt | xargs -P 8 -I {} bash -c 'extract_one "$@"' _ {}

说明

  • 输出格式:16kHz、单声道、16bit PCM WAV(ASR 标准格式)
  • 8 并发时 119 个文件约 18 分钟完成
  • 已存在的文件自动跳过(可断点续传)

步骤 3:批量 ASR 转录

#!/usr/bin/env python3
# batch_asr.py — SenseVoice-Small 批量转录(单进程安全版)

import time, os, sys, glob, subprocess, json

os.environ["MODELSCOPE_CACHE"] = "/home/tmzn/modelscope_cache"

AUDIO_DIR = "/mnt/f/test_audio"
OUTPUT_DIR = "/mnt/f/test_transcripts"
os.makedirs(OUTPUT_DIR, exist_ok=True)

wav_files = sorted(glob.glob(os.path.join(AUDIO_DIR, "**/*.wav"), recursive=True))
print(f"共 {len(wav_files)} 个文件待转录")

# 模型只加载一次
from funasr import AutoModel
from funasr.utils.postprocess_utils import rich_transcription_postprocess

model = AutoModel(
    model="iic/SenseVoiceSmall",
    vad_model="fsmn-vad",
    vad_kwargs={"max_single_segment_time": 30000},
    device="cuda:0",
    disable_update=True,
)
print("✅ 模型加载完成")

for i, wav_path in enumerate(wav_files):
    rel = os.path.relpath(wav_path, AUDIO_DIR)
    out_name = os.path.splitext(rel)[0]
    out_txt = os.path.join(OUTPUT_DIR, out_name + ".txt")
    os.makedirs(os.path.dirname(out_txt), exist_ok=True)

    if os.path.exists(out_txt):
        print(f"[{i+1}/{len(wav_files)}] SKIP: {rel}")
        continue

    r = subprocess.run(["ffprobe", "-v", "quiet", "-show_entries",
        "format=duration", "-of", "csv=p=0", wav_path],
        capture_output=True, text=True)
    try:
        audio_sec = float(r.stdout.strip())
    except:
        audio_sec = 0

    t0 = time.time()
    res = model.generate(
        input=wav_path, cache={}, language="zh",
        use_itn=True, batch_size_s=300,
        merge_vad=True, merge_length_s=15,
    )
    raw_text = res[0]["text"]
    rich_text = rich_transcription_postprocess(raw_text)
    elapsed = time.time() - t0

    with open(out_txt, "w", encoding="utf-8") as f:
        f.write(rich_text)

    speed = audio_sec / elapsed if elapsed > 0 else 0
    print(f"[{i+1}/{len(wav_files)}] ✅ {rel} → {elapsed:.1f}s ({speed:.0f}x) {len(rich_text)}字")

关键参数说明

参数说明
modeliic/SenseVoiceSmall最快的中文 ASR 模型
vad_modelfsmn-vad语音活动检测,自动切分静音段
max_single_segment_time30000VAD 最大切片 30 秒
language"zh"强制中文(比 auto 更准)
use_itnTrue逆文本归一化(数字、日期等)
batch_size_s300每批处理 300 秒音频
merge_vadTrue合并相邻 VAD 片段
merge_length_s15合并后最小 15 秒

五、并行策略(重要教训)

5.1 并行数量与 VRAM 的关系

GPU: RTX 4070 Ti SUPER 16GB
Windows 显示层占用: ~1-2GB(重启后)/ ~9.4GB(长时间使用后)
实际可用: 14-15GB(刚重启)/ ~7GB(长时间使用后)
并行数VRAM 占用稳定性建议
1 路~2GB✅ 绝对稳定保守选择
2 路~4-6GB✅ 稳定推荐
3 路~6-12GB⚠️ 大文件会爆不推荐
4 路+~8-16GB❌ 很容易崩禁止

5.2 为什么 3 路会崩

每个 Worker:
  模型本身: ~2GB(固定)
  VAD 中间张量: ~1-4GB(随音频长度波动)
  ─────────────────
  单 Worker 峰值: ~3-6GB

3 路并行峰值: 3 × 6GB = 18GB > 16GB → CUDA OOM

大文件尤其危险:170 分钟的音频,VAD 产生的中间张量远大于 10 分钟的短音频。

5.3 推荐方案:2 路并行 + 大文件单独处理

# 2 路并行版本(推荐)
NUM_WORKERS = 2
chunks = [[] for _ in range(NUM_WORKERS)]
for i, f in enumerate(todo):
    chunks[i % NUM_WORKERS].append(f)

或者更稳妥:1 路 + 跳过标点后处理,先跑完 ASR,再批量标点恢复

5.4 CUDA 崩溃后的补救

如果某些文件因 CUDA 错误失败,用 CPU 模式补完:

# 补完脚本只改一行
model = AutoModel(
    model="iic/SenseVoiceSmall",
    vad_model="fsmn-vad",
    device="cpu",  # ← 改成 CPU
    disable_update=True,
)

CPU 模式速度约 17x realtime(170 分钟音频约 10 分钟),但绝对稳定。


六、说话人分离(可选)

如果音频有多人对话,加上 cam++ 模型:

model = AutoModel(
    model="iic/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
    vad_model="fsmn-vad",
    punc_model="ct-punc",
    spk_model="cam++",          # ← 加这个
    device="cuda:0",
    disable_update=True,
)

res = model.generate(
    input=audio_file,
    batch_size_s=300,
    merge_vad=True,
    merge_length_s=15,
    # preset_spk_num=2,  # ⚠️ 已知会卡住,不要用
)

# 输出格式: sentence_info 中包含 spk 字段
for item in res:
    for sent in item.get("sentence_info", []):
        print(f"Speaker {sent['spk']}: {sent['text']}")

注意事项

  • cam++ 会多占 ~1GB VRAM
  • 说话人数量是自动聚类的,不准确时需要后处理合并
  • ⚠️ preset_spk_num 参数已知会导致卡死,不要使用
  • 单人课程不需要加 cam++

七、文件结构

F:\
├── test_audio\                    # 中间文件(提取的 WAV,可删除)

│
└── test_transcripts\              # 最终输出 ⭐

    ├── _raw\                        # 原始 ASR(无标点,可删除)
    └── asr_summary.json             # 统计摘要

八、性能基准(实测数据)

音频提取

并发数119 文件耗时说明
4 并发~12 分钟网络盘安全速度
8 并发~8 分钟网络盘极限

ASR 转录

方案83 小时音频耗时实时率
单进程 GPU~96 分钟52x
2 路并行 GPU~20 分钟~250x
3 路并行 GPU~12 分钟~415x(但大文件会崩)
CPU 单进程~50 小时17x

标点恢复(ct-punc 后处理)

纯文本处理,119 个文件 < 1 秒完成。


九、快速复用命令

# 一键环境检查
source ~/funasr_env/bin/activate && python3 -c "import torch,funasr; print('OK')"

# 一键提取音频(替换路径即可)
bash ~/extract_all.sh

# 一键批量转录
source ~/funasr_env/bin/activate && python3 -u ~/batch_asr.py 2>&1 | tee ~/batch_asr.log

# 一键标点恢复
source ~/funasr_env/bin/activate && python3 -u ~/batch_punc.py 2>&1 | tee ~/batch_punc.log

# 查看进度
tail -f ~/batch_asr.log

# 查看 GPU 状态
nvidia-smi

十、已知问题与解决方案

问题原因解决方案
CUDA OOM(大文件)VAD 中间张量 + 多进程 VRAM 累积降并行数到 2 或 1;大文件用 CPU
Windows 显示层占 9.4GB VRAM长时间使用后 DWM 内存泄漏重启机器释放 VRAM
preset_spk_num 参数卡死FunASR 已知 bug不使用该参数;后处理合并说话人
Fun-ASR-Nano 加载失败transformers 缺少 Qwen3 支持等 FunASR 更新;或手动升级 transformers
WSL 挂载网络盘超时网络盘响应慢mount -t drvfs 加 timeout
说话人分离不准(5→2 人)cam++ 自动聚类过度后处理基于时间邻近性合并

十一、依赖版本(已验证可用)

Python:         3.12.3
PyTorch:        2.6.0+cu124
FunASR:         1.3.9
CUDA:           12.4 (WSL)
GPU Driver:     595.71
OS:             Windows 10 + WSL2 Ubuntu 24.04
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇