LingYin 一个基于模型完成输入语音识别的工具,语音识别后展示在屏幕的滚动区域中,可根据设置为透明背景,字体颜色之类的。|作者个人站:https://softwarescience.top/projects/lingyin
当前访问频次受限,请登录后继续访问
🎙️ 项目方案书:“聆音” (LingYin)
作者个人站:https://softwarescience.top/ · 本项目主页:https://softwarescience.top/projects/lingyin(版本、截图与下载都在那儿)
一个基于 Rust 的低资源、高性能中文实时语音字幕工具
一、项目概述
1.1 项目名称
聆音 (LingYin) ——“聆听声音,转化为文字”。
英文代号:lingyin
1.2 项目定位
一款纯本地运行、低资源消耗的桌面实时语音识别工具。用户开启麦克风后,系统持续将语音转换为文字,并显示在半透明悬浮字幕窗口中,同时将全部识别结果保存到日志文件。
1.3 核心价值
| 维度 | 说明 |
|---|---|
| 隐私安全 | 所有语音识别在本地完成,无需联网,数据不上传 |
| 低资源占用 | 纯 CPU 推理(可选 GPU),INT8 量化模型仅 ~230MB |
| 实时流畅 | 端到端延迟 < 500ms,支持持续识别与滚动展示 |
| 跨平台 | 支持 Windows / macOS / Linux |
| 开箱即用 | 模型首次启动自动下载,用户无需手动配置 |
1.4 目标用户
- 需要会议/讲座实时字幕的参会者
- 听力障碍人士的辅助工具
- 视频创作者的字幕辅助生成
- 需要语音转文字记录的任何场景
二、需求分析
2.1 功能需求
| 编号 | 需求 | 优先级 | 说明 |
|---|---|---|---|
| FR-01 | 麦克风音频实时捕获 | P0 | 从系统默认麦克风捕获音频流 |
| FR-02 | 实时语音识别 | P0 | 将音频流实时转换为中文文字 |
| FR-03 | 悬浮字幕窗口 | P0 | 半透明、置顶显示、滚动展示识别文本 |
| FR-04 | 识别结果持久化 | P0 | 将所有识别文本按时间写入日志文件 |
| FR-05 | 模型动态下载 | P0 | 首次启动从 ModelScope 自动下载模型 |
| FR-06 | 启动/停止控制 | P1 | 用户可控制开始/停止识别 |
| FR-07 | 窗口可拖拽/调整 | P1 | 悬浮窗可拖动位置、调整大小 |
| FR-08 | 字体/颜色自定义 | P2 | 用户可调整字幕字体大小和颜色 |
| FR-09 | 系统托盘支持 | P2 | 后台运行,系统托盘图标控制 |
2.2 非功能需求
| 编号 | 需求 | 指标 |
|---|---|---|
| NFR-01 | 识别延迟 | 端到端延迟 < 500ms |
| NFR-02 | CPU 占用 | 平均 < 30%(现代 CPU) |
| NFR-03 | 内存占用 | < 500MB(含模型) |
| NFR-04 | 模型大小 | < 250MB(INT8 量化) |
| NFR-05 | 启动时间 | < 5 秒(模型已下载) |
| NFR-06 | 跨平台 | Windows 10+ / macOS 11+ / Linux |
2.3 约束条件
- 部署环境不确定:用户环境是“黑盒”,可能有显卡也可能没有,需默认 CPU 模式
- 无需 Python 环境:最终交付物为单一可执行文件,不依赖外部运行时
- 模型来源:从 ModelScope 下载,需处理网络不稳定情况
三、功能设计
3.1 用户界面设计
主界面:半透明悬浮字幕窗
┌─────────────────────────────────────────────┐
│ ═══════════════════════════════════════════ │ ← 半透明黑色背景 (alpha 0.7)
│ 今天我们要讨论的是关于项目进度的汇报 │ ← 白色文字,滚动显示
│ 首先,第一部分是关于上个季度的总结 │
│ 从数据上来看,我们的用户增长达到了预期 │
│ 但是在留存率方面还有提升空间 │
│ █ │ ← 光标/指示器
│ [⏹] [⚙] [─] [✕] │ ← 控制按钮(停止/设置/最小化/关闭)
└─────────────────────────────────────────────┘
交互方式:
- 窗口始终置顶,浮于所有应用之上
- 鼠标拖拽标题区域可移动窗口
- 鼠标悬停时显示控制按钮,移出后自动隐藏(保持清爽)
- 支持调整窗口大小和字体大小
系统托盘(可选 P2)
- 右键托盘图标可:开始/停止、显示/隐藏窗口、退出应用
3.2 核心流程
┌─────────────┐
│ 启动应用 │
└──────┬──────┘
▼
┌─────────────┐
│ 检查模型缓存 │ ──不存在──▶ ┌─────────────┐
└──────┬──────┘ │ 从ModelScope │
│ 存在 │ 下载模型 │
▼ └──────┬──────┘
┌─────────────┐ │
│ 加载模型 │ ◀────────────────────┘
└──────┬──────┘
▼
┌─────────────┐
│ 打开麦克风 │
└──────┬──────┘
▼
┌─────────────┐ ┌─────────────┐
│ 音频流捕获 │────▶│ VAD 语音检测 │ ──有语音──▶ ┌─────────────┐
└─────────────┘ └─────────────┘ │ ASR 推理 │
└──────┬──────┘
▼
┌─────────────┐
│ 输出识别文本 │
└──────┬──────┘
▼
┌───────────────┴───────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 更新字幕窗口 │ │ 写入日志文件 │
└─────────────┘ └─────────────┘
3.3 数据模型
识别结果记录
struct TranscriptEntry {
timestamp: DateTime<Local>, // 识别时间
text: String, // 识别文本
is_final: bool, // 是否为最终结果(vs 中间结果)
}
日志文件格式
[2026-09-05 14:23:15] 今天我们要讨论的是关于项目进度的汇报
[2026-09-05 14:23:18] 首先,第一部分是关于上个季度的总结
[2026-09-05 14:23:22] 从数据上来看,我们的用户增长达到了预期
3.4 配置管理
struct AppConfig {
// 模型配置
model_cache_dir: PathBuf, // 模型缓存目录
model_repo_id: String, // ModelScope 模型 ID
// 音频配置
sample_rate: u32, // 采样率 (16000 Hz)
chunk_duration_ms: u32, // 音频分块时长 (1000ms)
// 字幕配置
window_opacity: f32, // 窗口透明度 (0.7)
font_size: u32, // 字体大小 (24)
text_color: String, // 文字颜色 (#FFFFFF)
max_lines: usize, // 最大显示行数 (100)
// 日志配置
log_dir: PathBuf, // 日志目录
log_file_prefix: String, // 日志文件名前缀
}
四、技术方案
4.1 技术栈总览
| 层级 | 技术选型 | 说明 |
|---|---|---|
| 编程语言 | Rust 2024 Edition | 高性能、内存安全、单文件部署 |
| UI 框架 | GPUI | GPU 加速、原生性能、支持透明窗口 |
| 语音识别引擎 | sensevoice-rs |
Rust 原生 SenseVoiceSmall 绑定,支持流式识别 |
| 推理后端 | ONNX Runtime | 跨平台 CPU 推理引擎,支持 INT8 量化 |
| 模型格式 | SenseVoiceSmall INT8 ONNX | ~230MB,纯 CPU 高效推理 |
| 模型下载 | model-hub |
轻量异步,支持 ModelScope 断点续传 |
| 音频捕获 | cpal |
跨平台底层音频 I/O |
| 音频处理 | hound |
WAV 格式读写 |
| 序列化 | serde + serde_json |
配置管理 |
| 日志 | tracing + tracing-appender |
结构化日志 |
| 异步运行时 | tokio |
异步任务调度 |
4.2 核心技术选型详解
4.2.1 语音识别:SenseVoiceSmall + ONNX
选型理由:
- 中文 SOTA 水平:在中文普通话测试集上字符错误率(CER)仅 8.01%,远超 Whisper 系列
- 极致轻量:INT8 量化后模型仅 ~230 MB
- CPU 友好:纯 CPU 推理速度可达 20 倍实时
- 多语言支持:支持中文、英文、日语、韩语、粤语等
- Rust 生态完善:
sensevoice-rs提供 Rust 原生绑定,支持流式音频处理
模型来源:
- 原始模型:ModelScope
iic/SenseVoiceSmall - ONNX 导出(INT8 量化):可从 Hugging Face 或 ModelScope 获取
4.2.2 UI 框架:GPUI
选型理由:
- GPU 加速渲染:60 FPS 流畅体验,滚动字幕无卡顿
- 透明窗口支持:原生支持每像素透明渲染,实现悬浮字幕效果
- 虚拟滚动:支持海量文本的高效滚动显示
- 跨平台:Windows / macOS / Linux 统一 API
- Rust 原生:无需绑定其他语言,与项目技术栈一致
4.2.3 模型下载:model-hub
选型理由:
- 多平台支持:原生支持 Hugging Face 和 ModelScope
- 断点续传:网络不稳定时可恢复下载
- 轻量异步:基于 Tokio,不阻塞 UI
- Rust 原生:无需调用外部工具
4.2.4 音频捕获:cpal
选型理由:
- 跨平台:Windows (WASAPI)、macOS (CoreAudio)、Linux (ALSA/PulseAudio)
- 低延迟:支持实时优先级线程调度
- 活跃维护:Rust 音频生态的事实标准
4.3 架构设计
整体架构图
┌─────────────────────────────────────────────────────────────────┐
│ 聆音 (LingYin) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ UI 层 │ │ 业务逻辑层 │ │ 基础设施层 │ │
│ │ (GPUI) │◀──▶│ (调度器) │◀──▶│ (模型/音频) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 字幕渲染 │ │ 文本聚合 │ │ sensevoice-rs│ │
│ │ 滚动管理 │ │ 日志写入 │ │ (ONNX) │ │
│ │ 窗口控制 │ │ 状态管理 │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 配置管理 │ │ cpal │ │
│ │ (serde) │ │ (音频捕获) │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ model-hub │ │ 麦克风硬件 │ │
│ │ (模型下载) │ │ │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
模块划分
| 模块 | 职责 | 关键 Crate |
|---|---|---|
lingyin-app |
应用入口、生命周期管理 | tokio, anyhow |
lingyin-ui |
字幕窗口渲染、交互 | gpui, gpui-component |
lingyin-asr |
语音识别引擎封装 | sensevoice-rs, onnxruntime |
lingyin-audio |
麦克风音频捕获 | cpal, hound |
lingyin-model |
模型下载与缓存管理 | model-hub |
lingyin-storage |
日志写入、配置持久化 | tracing, serde |
lingyin-core |
核心数据模型、错误定义 | 标准库 |
4.4 关键流程时序
启动流程
User ──▶ main() ──▶ 初始化 Tokio Runtime
│
▼
加载/创建配置
│
▼
检查模型缓存 ──不存在──▶ model-hub 下载模型
│ │
▼ ▼
初始化 sensevoice-rs 等待下载完成
│ │
└──────────┬────────────┘
▼
创建 GPUI 窗口
│
▼
启动音频捕获线程
│
▼
应用进入事件循环
识别流程
Audio Callback ──▶ 收到 PCM 数据 (16kHz, mono, f32)
│
▼
VAD 检测 ──无语音──▶ 丢弃
│有语音
▼
送入 sensevoice-rs 流式接口
│
▼
返回识别文本 (异步)
│
▼
┌─────────┴─────────┐
▼ ▼
通过 Channel 发送 写入日志文件
到 UI 线程
│
▼
GPUI 更新 TextView
│
▼
自动滚动到最新行
4.5 项目结构
lingyin/
├── Cargo.toml # 工作区配置
├── README.md
├── .gitignore
├── assets/
│ ├── icon.ico # Windows 图标
│ ├── icon.icns # macOS 图标
│ └── icon.png # Linux 图标
├── crates/
│ ├── lingyin-core/ # 核心数据模型
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── config.rs
│ │ ├── transcript.rs
│ │ └── error.rs
│ │
│ ├── lingyin-model/ # 模型下载与管理
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ └── downloader.rs
│ │
│ ├── lingyin-audio/ # 音频捕获
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ └── capture.rs
│ │
│ ├── lingyin-asr/ # 语音识别引擎
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ └── engine.rs
│ │
│ ├── lingyin-storage/ # 日志与配置持久化
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ └── logger.rs
│ │
│ ├── lingyin-ui/ # GPUI 界面
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── window.rs
│ │ ├── subtitle_view.rs
│ │ └── controls.rs
│ │
│ └── lingyin-app/ # 应用入口
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs
│ └── app.rs
│
└── scripts/
├── build.sh
└── package.sh
4.6 关键依赖版本
[workspace.dependencies]
# UI
gpui = { git = "https://github.com/zed-industries/zed", package = "gpui" }
gpui-component = "0.1" # 或使用 gpui 内置组件
# ASR
sensevoice-rs = "0.1.4" # 或使用 sherpa-onnx Rust API
onnxruntime = { version = "0.1", features = ["tensorrt"] } # optional
# 音频
cpal = "0.15"
hound = "3.5"
# 模型下载
model-hub = "0.1"
# 异步
tokio = { version = "1.0", features = ["full"] }
# 序列化
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 日志
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = "0.3"
# 错误处理
anyhow = "1.0"
thiserror = "1.0"
# 路径操作
dirs = "5.0"
五、开发计划
5.1 里程碑
| 阶段 | 周期 | 目标 | 交付物 |
|---|---|---|---|
| M1: 基础验证 | 第 1 周 | 验证核心技术可行性 | 模型下载 POC、音频捕获 POC、ASR 推理 POC |
| M2: 核心功能 | 第 2-3 周 | 实现端到端核心流程 | 可工作的命令行原型(音频→文字→文件) |
| M3: UI 集成 | 第 3-4 周 | 集成 GPUI 字幕窗口 | 带悬浮字幕窗口的桌面应用 |
| M4: 产品化 | 第 5 周 | 优化、打包、测试 | 可发布的跨平台安装包 |
5.2 详细任务分解
M1: 基础验证(第 1 周)
| 任务 | 说明 | 产出 |
|---|---|---|
| T1.1 | 使用 model-hub 从 ModelScope 下载 SenseVoiceSmall ONNX 模型 |
下载脚本 POC |
| T1.2 | 使用 cpal 捕获麦克风音频并保存为 WAV |
音频捕获 POC |
| T1.3 | 使用 sensevoice-rs 对本地 WAV 文件进行离线识别 |
离线 ASR POC |
| T1.4 | 验证 INT8 量化模型在目标 CPU 上的性能 | 性能基准报告 |
M2: 核心功能(第 2-3 周)
| 任务 | 说明 | 产出 |
|---|---|---|
| T2.1 | 实现流式音频捕获 + VAD 语音活动检测 | 实时音频流模块 |
| T2.2 | 实现 sensevoice-rs 流式推理接口封装 |
ASR 引擎模块 |
| T2.3 | 实现识别结果的 Channel 通信机制 | 跨线程通信 |
| T2.4 | 实现日志文件写入(按时间戳) | 日志模块 |
| T2.5 | 端到端命令行原型集成与测试 | 可工作原型 |
M3: UI 集成(第 3-4 周)
| 任务 | 说明 | 产出 |
|---|---|---|
| T3.1 | 搭建 GPUI 应用骨架 | 基本窗口 |
| T3.2 | 实现半透明置顶窗口 | 悬浮窗效果 |
| T3.3 | 实现 TextView 滚动字幕显示 |
字幕滚动 |
| T3.4 | 实现控制按钮(开始/停止) | 基本交互 |
| T3.5 | 集成 ASR 引擎与 UI | 完整应用 |
M4: 产品化(第 5 周)
| 任务 | 说明 | 产出 |
|---|---|---|
| T4.1 | 错误处理与异常恢复 | 健壮性提升 |
| T4.2 | 跨平台打包(Windows/macOS/Linux) | 安装包 |
| T4.3 | 用户文档与使用指南 | 文档 |
| T4.4 | 集成测试与性能调优 | 测试报告 |
5.3 开发进度(2026-09-13 更新)
已完成
| 里程碑 | 状态 | 说明 |
|---|---|---|
| M1 基础验证 | ✅ 完成 | 模型下载(ModelScope 直连 + 断点续传)、麦克风捕获、WAV 离线识别、RTF 基准 |
| M2 核心功能 | ✅ 完成 | 流式音频捕获、VAD 模拟流式识别、Channel 通信、日志落盘、命令行原型 |
| M3 UI 集成 | ✅ 完成 | gpui-pre 0.3.x 悬浮字幕窗:半透明 + 置顶 + 滚动字幕 + 悬停控制栏 + 拖拽/缩放 |
| M4 产品化 | 🔄 进行中 | ✅ Windows 便携压缩包(scripts/package.ps1);✅ 用户文档(本节);⏳ macOS/Linux 打包、性能调优 |
技术选型与本方案书的差异(
sensevoice-rs→sherpa-onnx、model-hub→ 直连 HTTPS 下载) 见根目录Cargo.toml头部注释与.omo/ulw-research/20260905-180626/。
实测数据(本机 Ryzen 9 9900X,纯 CPU)
| 场景 | 结果 |
|---|---|
| 9.4 秒中文语音离线识别 | 热启动 96 ms,RTF 0.010(约 100× 实时) |
| 9.4 秒 WAV 走完整管线(VAD + 分段识别 + 日志) | 0.4 秒,21× 实时 |
--realtime 回放延迟 |
每句在静音判定(0.5 秒)后立即出最终结果,端到端 0.5–0.7 秒 |
| 麦克风(48 kHz 立体声)实时捕获 | 4 秒录音 → 4.0 秒 16 kHz 单声道音频,无丢块 |
| 悬浮窗启动耗时 | 窗口先出现,模型在后台线程加载(约 1 秒),界面全程可交互 |
| 关闭窗口 | 进程立即退出(exit 0),当前正在说的句子被强制成句并落盘 |
| 缩放拖拽(右下角 / 右边 / 下边) | 拖动 200 逻辑像素 → 窗口尺寸同步变化 200 像素 |
| 便携包分发 | 压缩包 12.5 MB(exe 32 MB);解压后 lingyin.exe replay x.wav 零配置识别成功 |
| 便携包首次运行 | 自动下载 3 个模型文件到 %LOCALAPPDATA%\lingyin\models,字节数与 downloader.rs 常量完全一致 |
| 窗口缩放拖拽 | 拖 100px → 尺寸 +100px;缩 -580px → 尺寸 -580px(严格 1:1,三个方向均验证) |
| 长句折行(900px 与 320px 两种宽度) | 自动折行,文字最右端距内边距边界仍有 36–374px 余量,无横向溢出 |
| 视口/页脚边界 | 逐像素校验:页脚带内无任何文字像素,文字全部落在 3 行视口内 |
使用方法
cargo run -p lingyin-app -- gui # 悬浮字幕窗(麦克风,默认)
cargo run -p lingyin-app -- gui --wav speech.wav --realtime # 悬浮字幕窗(WAV 回放演示)
cargo run -p lingyin-app -- devices # 列出输入设备
cargo run -p lingyin-app -- models # 下载 / 校验模型(约 230 MiB)
cargo run -p lingyin-app -- run --seconds 30 # 麦克风实时识别,Ctrl+C 退出
cargo run -p lingyin-app -- replay speech.wav --realtime # WAV 回放,按真实时间轴演示
cargo run -p lingyin-app -- replay speech.wav # WAV 回放自测(尽可能快)
识别结果按 [YYYY-MM-DD HH:MM:SS] 文本 追加写入 <日志目录>/lingyin-YYYYMMDD.log,
并在每次会话首尾写入标记行(含输入来源与统计),因此日志可独立回答
「哪段文字来自哪次会话、哪个输入源」:
[2026-09-13 22:27:54] ===== 识别会话开始 · 来源:WAV 回放 C:/…/many.wav =====
[2026-09-13 22:27:55] 第一句话。
[2026-09-13 22:27:57] 第二句话。
[2026-09-13 22:28:04] ===== 识别会话结束 · 最终结果 2 条 / 中间结果 6 条 / 音频 10.0 秒 =====
标记行以 ===== 包裹,用 grep -v ===== 即可筛出纯识别文本。
两种界面共用同一条管线(crates/lingyin-app/src/pipeline.rs):
GUI 用悬浮字幕窗渲染(lingyin-ui),终端模式原地刷新中间结果、最终结果另起一行。
悬浮字幕窗交互:
| 操作 | 位置 | 说明 |
|---|---|---|
| 移动窗口 | 任意字幕区域 | 整块字幕区都是拖拽区,不必去找顶部细条 |
| 缩放窗口 | 右 / 下 / 右下边缘 8px 抓取条 | 位移与尺寸严格 1:1(见实现结论 4、6) |
| 开始 / 停止 | 控制栏 ▶ / ■ | 停止会冲刷最后一句到日志 |
| 字号、不透明度 | 控制栏 A− A+ ◐ ◑ |
即时生效,范围受夹取保护 |
| 最小化 / 退出 | 控制栏 ─ / ✕ |
退出前会结束识别会话并落盘 |
| 显示控制栏 | 鼠标移入窗口 | 移出后自动隐藏;状态栏始终占位,避免高度跳动 |
字幕显示规则:
| 规则 | 行为 |
|---|---|
| 保留条数 | 面板最多持有最近 10 条(max_lines),更早的丢弃、不做回看 |
| 可见行数 | 默认 3 行(visible_lines),窗口拉高可见更多(最多 10 条) |
| 滚动方式 | 视口底部对齐,新内容使整体上移一行(6 帧动画),始终显示最新几行 |
| 长句折行 | 超出宽度的长句自动折行,不会横向溢出(窄到 320px 仍正常折行) |
| 中间结果 | 原地更新同一行,不额外占行;最终结果才定稿成一行 |
| 历史留存 | 面板不留历史;完整记录(含来源与会话边界)写入日志 |
实现结论(与初版方案的关键差异)
- VAD 必须按窗口对齐喂入:sherpa-onnx 的 Silero VAD 需按
window_size(16 kHz 下固定 512 样本)对齐送入音频;按 1 秒整块喂入会把相邻句子并成一段 并丢失段首音频。lingyin-asr::StreamingRecognizer内部自行按 512 样本窗口切分。 - 「流式」由两层拼出:SenseVoice 为 CTC 架构,不支持增量解码,因此
- 中间结果(
is_final = false):说话过程中每 800 ms 把当前语音段整体重识别一次; - 最终结果(
is_final = true):VAD 判定成句后对该段完整音频识别,此时才写入日志。
- 中间结果(
- 模型不再打包方案书中的依赖:
sensevoice-rs(实为 Candle)、model-hub(未发布)均已替换,详见Cargo.toml。 - 置顶窗口在 Windows 上没有原生边框,需自绘缩放手柄:
WindowKind::PopUp的窗口样式为WS_EX_TOPMOST | WS_EX_TOOLWINDOW,dwstyle为 0, 因而不含WS_THICKFRAME——除顶边(gpui-pre 自行处理)外,系统不提供缩放热区。lingyin-ui::resize因此自绘右 / 下 / 右下三个拖拽柄,拖动时调用Window::resize。 只做这三个方向:Window::resize不改变窗口原点,在左/上边放手柄会得到反向的拖拽手感。 - 控制栏显隐取自平台悬停状态:
Window::is_window_hovered()由平台在鼠标进出窗口时 更新并触发重绘,比自行维护 hover 事件更可靠(自带拖拽命中测试,不会因布局变化而错位)。 - 缩放抓取条必须占据布局空间,且基准点取「按下」而非首个拖动事件:
- 字幕区的
WindowControlArea::Drag会把按下事件交回DefWindowProc,由系统执行窗口 移动;抓取条若绝对定位覆盖其上,按下只会移动窗口(实测on_drag_move完全不触发)。 因此抓取条改为独立布局列/行(resize::right_gutter等)。 - 以首个
on_drag_move为基准会丢掉「按下点 → 首个事件」之间的位移(实测按下 x=897、 首个事件 x=913,拖 100px 只长出 84px)。改为记录按下时的尺寸与鼠标位置,用 「窗口原点固定 ⇒ 鼠标窗口内坐标即到左/上边距离」直接推导目标尺寸,位移与尺寸严格 1:1。
- 字幕区的
- 颜色必须用
rgba()而不是rgb():rgb()取0xRRGGBB并把 alpha 强制为 1.0, 误写rgb(0x000000a0)会得到不透明蓝色(末字节被当作 B 分量)——控制栏曾因此变成蓝条。 - 窗口高度预算必须包含底部抓取条:抓取条占 8px 布局高度,漏算会使视口比设计值矮
8px,导致字幕文字溢到状态栏上(实测)。
style::window_height_for统一计算, 并有「任意字号下预算都能容纳可见行数」的测试守住。
打包(Windows 便携包)
# Windows Terminal / PowerShell(推荐)
powershell -ExecutionPolicy Bypass -File scripts\package.ps1
powershell -ExecutionPolicy Bypass -File scripts\package.ps1 -NoBuild # 仅打包,复用现有产物
# Git Bash / MSYS(转发到同一个 PowerShell 实现)
scripts/package.sh
scripts/package.sh --no-build
产物:dist/lingyin-<版本>-windows-x64.zip,解压后目录:
lingyin-0.1.1-windows-x64/
├── lingyin.exe 发布版可执行文件(32 MB)
├── start.cmd 双击启动悬浮字幕窗
├── README.md 方案书与使用说明
└── LICENSE
打包逻辑实现在 scripts/package.ps1(原生 Windows 路径与工具链);
scripts/package.sh 是跨平台入口,在 Git Bash / MSYS 下转发给它,在非 Windows 平台上明确报错。
分发说明:
- 无需安装运行时:
sherpa-onnx以静态库链接(staticfeature),CRT 通过.cargo/config.toml的target-feature=+crt-static静态链接, 产物只导入 Windows 自带系统 DLL(实测导入表中无VCRUNTIME140.dll/MSVCP140.dll/onnxruntime.dll),目标机不需要 VC++ 可再发行组件。 - 不含模型:模型约 230 MB,首次运行由
lingyin.exe自动从 ModelScope 下载到用户缓存目录 (%LOCALAPPDATA%\lingyin\models);也可提前用lingyin.exe models单独下载。 - 免安装:解压到任意目录即可运行,日志写在
%USERPROFILE%\Documents\lingyin\logs。 start.cmd刻意只用 ASCII:cmd.exe 按 OEM 代码页(简中系统为 GBK)解析批处理文件, 内含中文会破坏批处理语法;脚本内先chcp 65001切换控制台到 UTF-8, 使lingyin.exe自身的中文日志正常显示。- 当前仅实现 Windows 包;macOS
.app/ Linux AppImage 待补。
版本历史
| 版本 | 变更 |
|---|---|
| 0.1.1 | 修正窗口缩放(此前完全无效、且不跟手)、长句横向溢出、控制栏配色错误(rgb 误用导致蓝条)、字幕文字溢到状态栏;改为持续滚动 + 面板保留最近 10 条 / 默认可见 3 行;日志新增会话首尾标记(含输入来源与统计) |
| 0.1.0 | 首个可运行版本:M2 流式管线 + M3 悬浮字幕窗 + M4 Windows 便携包 |
当前代码结构
crates/
├── lingyin-core/ config.rs / transcript.rs / error.rs / audio.rs(采样率与换算常量)
├── lingyin-model/ downloader.rs(ModelScope 直连 + 断点续传)
├── lingyin-audio/ capture.rs(cpal 流式捕获)/ resample.rs / chunker.rs
│ source.rs(ChunkSource 抽象)/ wav_source.rs(回放源)
├── lingyin-asr/ engine.rs(离线识别)/ stream.rs(VAD + 模拟流式)
├── lingyin-storage/ logger.rs(按天滚动日志)
├── lingyin-ui/ window.rs(透明置顶窗)/ subtitle_view.rs(滚动字幕)
│ controls.rs(控制栏)/ resize.rs(缩放手柄)/ style.rs(样式)
│ backend.rs(与识别后端的契约,UI 不依赖音频/识别 crate)
└── lingyin-app/ app.rs(CLI)/ pipeline.rs(音频 → 识别 → 日志)
gui.rs(把管线接到字幕窗的 Backend 实现)
scripts/
├── build.sh 发布构建
├── package.ps1 Windows 便携包打包(实现)
├── package.sh 打包入口(Git Bash / MSYS 转发到 package.ps1)
└── start.cmd 发布包内的启动脚本(ASCII-only)
.cargo/config.toml Windows 目标静态链接 CRT(保证免安装分发)
六、风险与应对
| 风险 | 影响 | 概率 | 应对措施 |
|---|---|---|---|
| ASR 引擎流式能力 | 中 | — | 已定案:采用 sherpa-onnx + VAD 分段(模拟流式),M2 已验证;sensevoice-rs(Candle)不采用 |
| ModelScope 下载不稳定/被墙 | 中 | 中 | 已实现断点续传与重试;备选:Hugging Face 镜像 |
| GPUI 透明窗口跨平台差异 | 中 | 中 | 采用 gpui-pre 0.3.4(Zed main 快照,已修复 CJK 截断崩溃);透明 + 置顶 + 中文渲染已在 Windows 实测通过 |
| 无边框置顶窗口缺少原生缩放热区 | 低 | — | 已解决:lingyin-ui::resize 自绘右/下/右下拖拽柄(见 5.3 节结论 4) |
| CPU 推理性能不足 | 高 | 低 | SenseVoiceSmall INT8 已针对 CPU 优化;实测 RTF 0.010(约 100× 实时) |
| VAD 分段参数不当导致并句/丢字 | 中 | 中 | StreamingRecognizer 按 512 样本窗口对齐喂入(见 5.3 节结论 1),阈值集中在 VadConfig |
| 音频设备兼容性问题 | 中 | 低 | cpal 成熟跨平台;提供设备选择功能 |
七、总结
“聆音” 是一个技术成熟、方案可行的实时语音字幕工具。其核心优势在于:
- 纯 Rust 实现:单文件部署,无需 Python 环境
- 低资源消耗:INT8 量化模型仅 ~230MB,纯 CPU 流畅运行
- 中文识别 SOTA:SenseVoiceSmall 在中文场景表现优异
- 优雅的悬浮字幕:GPUI 驱动的 GPU 加速渲染,半透明置顶显示
该方案已充分考虑“黑盒”部署环境的约束,所有技术选型均有成熟的 Rust 生态支持,可交付给 Agent 团队按里程碑推进开发。
版本与发版
每次代码改动落地后先判断:这次要不要升版本? 要升就一路做完——绝不停在「代码改了、版本号没动、 标签没打」;判断不用升的,在交付说明里写明理由。
- 版本号唯一来源:根
Cargo.toml的[workspace.package].version(打包目录名dist/lingyin-<版本>-windows-x64/由它派生)。 - 标签与版本同号,前缀跟随本仓库既有习惯:
v前缀(如v0.1.1);一个仓库里不得混用两种写法。 - 要升的完整序列(缺一步都算没做完):
- 在版本号唯一来源处改号;仓库里还有别处重复写了版本(前端
package.json、tauri.conf.json、 README 里的版本引用)的,同一提交里一起改; - 重新构建并验证:
cargo test --workspace→cargo build --release→ 按 README「打包」跑scripts/package.ps1,并用lingyin.exe replay <wav>听一遍识别结果; - 提交、
git push当前分支; - 打附注标签(
git tag -a <标签> -m "聆音 LingYin <版本号>")并git push origin <标签>; - 对外发布的版本要在本仓库留下可查的记录:在 README 的版本历史 / CHANGELOG(没有就新建一节) 写清这一版改了什么、为什么改;并确认分发物(安装包 / 便携包 / 镜像等)的文件名与版本号一致。
- 在版本号唯一来源处改号;仓库里还有别处重复写了版本(前端
- 判断口径:用户可见的行为 / 界面 / 协议 / 只读数据格式变了,或修了用户能踩到的缺陷 → 升 (补丁位修缺陷,小版本位加能力);纯内部重构、注释、测试、脚本、文档 → 不升。
- 交付说明里写清两件事:这次要不要升版本(要升就写升到哪个号),以及版本号 / 标签 / 推送 三件事是否都做完;不升就写「不升 + 理由」,不许留白。
关于
这个项目由 CodeLife Leon 独立开发与维护。更多项目、各项目发版信息、赞赏与联系方式都在个人站:
- 个人站:https://softwarescience.top/
- 本项目主页:https://softwarescience.top/projects/lingyin
- 下载与发版:https://softwarescience.top/releases/lingyin
- 赞赏支持:https://softwarescience.top/sponsor(自愿,不影响任何版本的发布)
问题反馈与需求建议:优先在仓库提 Issue,也可以邮件 virgil_lgp@163.com。
项目介绍
LingYin 一个基于模型完成输入语音识别的工具,语音识别后展示在屏幕的滚动区域中,可根据设置为透明背景,字体颜色之类的。|作者个人站:https://softwarescience.top/projects/lingyin
https://softwarescience.top/projects/lingyin定制我的领域