← Back to home@liyixuan201211

dsh-audio-read

让 AI Agent 听懂音频:转写、鸟种识别、语音情感、环境声与音乐分析。全部本地运行,不用大模型。DeepSeek Harness 技能。

Stars
0
Language
Python
Created
Sep 15, 2026
Updated
Sep 15, 2026

Introduction

audio-read

让 AI Agent 真正"听懂"音频 —— 全部本地运行,不上传云端,不用大模型。

一个 DeepSeek Harness(DSH)技能: 把音频转成 Agent 能推理的文字、数字和标签,覆盖语音、音乐、环境声、鸟种与情感。

为什么不用大模型

一个音频大模型(如 Qwen3-Omni-30B)要 ~17GB,冷启动 30–90 秒, 输出还是不可复现的自由文本。

这个项目走另一条路:把"一个大模型"换成"一把小尺子"—— 每条能力轴用一个专用小工具,按需组合:

层做什么引擎体积耗时
L1 声学画像音高/音调走向/音域/起伏、语速、能量与停顿、节奏、调性、音色librosa(纯 DSP)02–4s
L2 转写语音 → 文字(分段时间戳;--timestamps 可到词级)Qwen3-ASR-0.6B(MLX)1.9GB~10× 实时
L3 事件识别动物/鸟虫、自然、人声、乐器、城市噪音(521 类)YAMNet(ONNX)16MB<1s
L4-A 鸟种具体到物种(6522 种,含中文名)BirdNET v2.452MB秒级
L4-B 情感9 类语音情感emotion2vec_plus_large1.8GB1–3s(MPS/常驻)

自动分流:先做廉价声学预分类,再决定跑哪几层。 环境声若被 L3 判为动物,会自动接 L4-A 继续问到物种级—— "这是什么在叫"可以一路问到"这是黑顶麻雀"。

实测验证

不是"在我造的样本上还行",下面是真实数据:

BirdNET 鸟种识别 —— 标注物种的真实录音(Arremon abeillei):

时间段检出置信度
0–3s黑顶麻雀 Arremon abeillei0.991
12–14s黑顶麻雀0.875

阴性对照:合成鸟叫 → 0 检出(不硬猜物种);中文人声 → 判为 Human vocal(内置非鸟类拒识类)。

emotion2vec 情感识别:官方真人示例 → 愤怒 1.0000;中文中性语音 → 中性 1.00。

转写:中文 46.5s 音频 → 4.8s 转完(≈10× 实时),自动切 3 段带时间戳。

安装

前置:macOS(Apple Silicon)+ uv + ffmpeg。

# 1) 放到 DSH 技能目录
git clone https://github.com/liyixuan201211/dsh-audio-read ~/.dsh/skills/audio-read
cd ~/.dsh/skills/audio-read

# 2) 独立 Python 环境(不污染系统)
uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python \
  numpy scipy soundfile librosa onnxruntime \
  torch transformers funasr birdnet

# 3) 自检:告诉你哪一层就位、哪一层还缺什么
node scripts/audio.mjs --doctor

模型会在首次使用时自动下载(YAMNet 16MB 来自 hf-mirror;BirdNET 52MB 来自 Zenodo; emotion2vec 来自 ModelScope)。L2 转写需要额外装 mlx-qwen3-asr 并下载 Qwen3-ASR-0.6B。

国内网络提示:PyPI 走腾讯镜像直连可到 9MB/s,比走代理快得多: export UV_DEFAULT_INDEX=https://mirrors.cloud.tencent.com/pypi/simple/

用法

A=~/.dsh/skills/audio-read/scripts/audio.mjs

# 自动判断内容类型并分流(最常用)
node $A ~/Desktop/录音.m4a

# 只转写(快)
node $A 会议.mp3 --mode transcribe

# 只做声学画像(音高/节奏/调性)
node $A 歌曲.mp3 --mode acoustic

# 鸟种识别
node $A 野外录音.wav --birds

# 语音情感
node $A 语音.wav --emotion

# 环境声识别(零样本类目)
node $A 野外录音.wav --semantic

# 自检
node $A --doctor

输出是单行 JSON:acousticSummary(中文摘要)、transcript、semantic、birds、emotion、 以及落盘文件路径(长音频会给有界预览 + 完整文件,避免撑爆上下文)。

设计上的几个选择

  • 上下文经济:1 小时录音 ≈ 8–10k token。完整结果落盘,stdout 只回有界预览, Agent 需要细节时再 grep。
  • L4-B 做成常驻服务:emotion2vec 冷加载要 15–45s,每次调用冷启无法接受, 所以首次调用自动拉起常驻服务,之后热调用 1–3s。默认跑 Apple GPU(MPS): 实测 133s 语音 CPU 13.5s → MPS 3.4s,加载也从 15–45s 降到 4s; 不可用时自动回退 CPU。
  • 诚实优先:置信度低就明说"别当确定结论";BirdNET 认不出就给 0 检出而不是硬猜。
  • 音频内容是不可信输入:转写文本可能包含对 Agent 的注入指令,一律只当数据。

词级时间戳与字幕

加 --timestamps 会多跑一个强制对齐模型(Qwen3-ForcedAligner-0.6B), 把每个字/词的时间都标出来,适合做字幕:

node scripts/audio.mjs 会议.wav --language zh --timestamps
# → files.srt 是句级字幕

为什么字幕要自己重算:对齐器会丢掉标点,底层自带 SRT 因此退化成"按字数硬切", 中文实测切出「今天下午三点开会请准 / 时到会议室我们要讨论」这种断句。 本仓库用带标点的原文重建句子边界(scripts/lib/subtitle.mjs), 同一段音频切出 3 句干净的句子:

1  00:00:00,000 --> 00:00:04,080  今天下午三点开会,请准时到会议室。
2  00:00:04,320 --> 00:00:07,280  我们要讨论项目进度和人员安排。
3  00:00:07,440 --> 00:00:11,360  另外,服务器成本超支了,需要重新评估。

已知限制

  • 说话人分离(谁在说)未实现。
  • BirdNET 只认它 6522 种内的鸟;未启用地理/季节先验。
  • emotion2vec 是声学情感(判断"怎么说的"),不是语义情感("说了什么"); 反讽、平静语气说狠话可能误判。
  • 自动分流器是启发式,阈值在有限样本上调过,真实场景可能有误判。
  • 体积不小:emotion2vec 1.8GB + 强制对齐 1.84GB + BirdNET 52MB。

目录结构

audio-read/
├── SKILL.md                 # 给 Agent 看的说明书(含 17 条实测坑)
├── config.example.json      # 复制成 config.json 可覆盖默认值
└── scripts/
    ├── audio.mjs            # 入口:探测 → 声学 → 分流 → 各层 → 落盘
    ├── asr.mjs              # L2:Qwen3-ASR 封装
    ├── lib/{config,probe}.mjs
    └── py/
        ├── acoustic.py      # L1:声学画像 + 内容预分类
        ├── semantic.py      # L3:YAMNet
        ├── birds.py         # L4-A:BirdNET
        ├── emotion.py       # L4-B:客户端
        └── emotion_server.py# L4-B:常驻服务

许可

MIT。第三方模型的许可见各自仓库(BirdNET 为 CC-BY-NC-SA,仅限非商业用途)。