Commit Graph

40 Commits

Author SHA1 Message Date
4836a82cad feat: 添加系统托盘图标,实时指示语音录制状态
托盘图标根据状态变色:绿色(就绪)、黄色(等待确认)、红色(录音中)、
橙色(识别中)。支持双击托盘显示/隐藏主窗口,右键托盘菜单显示窗口和退出。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 15:20:09 +08:00
277ac01f46 fix: 1s 后自动复位 CapsLock 灯,LED 作为录音状态指示器
按下 CapsLock → 灯亮 (PreRecording) → 1s 后灯灭 → 正式录音 (Recording)
→ 松开 → 识别 → 注入。Recording/Cooldown 状态屏蔽所有 Activated 信号防抖。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 15:03:09 +08:00
88b0d57722 fix: 长按松开后增加 CapsLock 复位 2026-06-11 14:52:17 +08:00
58a732e161 refactor: 用明确状态机重写 CapsLock 语音输入,彻底解决抖动
旧方案依赖多个布尔标志(capsResetDone_/cooldownActive_/longPressDetected_)
分散在各个处理函数中,复杂交互下容易产生竞态。

新方案使用明确四态状态机:
  Idle → PreRecording(按下) → Recording(1s后) → Cooldown(松开后)

核心防抖:
- Recording 状态下屏蔽所有 Activated 信号
- Cooldown 状态下屏蔽所有 Activated 信号
- PreRecording 状态下忽略重复 Activated

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:46:08 +08:00
13d4aae725 fix: 长按 1s 录音 + 3s 复位 CapsLock 灯 + 防抖保护
- 分离两个定时器:longPressTimer(1s) 启动录音,capsResetTimer(3s) 复位灯
- 1s 时只确认录音状态,不操作 CapsLock 灯
- 3s 时才模拟 CapsLock 按键复位灯,避免手指抖动导致状态闪烁
- 松开时自动取消 3s 定时器,避免松开后再复位

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:38:30 +08:00
d4db8756b5 fix: 松开 CapsLock 后 1s 冷却期再检测下次按下
新增 cooldownTimer_ 和 cooldownActive_ 标志,
松开后启动 1s 冷却定时器,期间忽略所有 Activated 信号,
防止快速连续操作导致的重复触发。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:32:07 +08:00
74212ff564 fix: CapsLock 复位后忽略重复 Activated 信号
长按阈值触发 CapsLock 复位时设置 capsResetDone_ 标志,
松开前忽略任何重复的 Activated 信号,防止状态混乱。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:25:41 +08:00
1bb0d3895b fix: 长按阈值调回 1s 2026-06-11 14:22:21 +08:00
072a571a89 fix: 长按 3s 立即复位 CapsLock,不等松开
CapsLock 语音输入流程调整为:
按下 CapsLock → 预录音 → 长按 3s → 立即复位 CapsLock → 正式录音 →
松开 → 停止录音 → 识别 → 注入文本

短按(< 3s)保持原有行为:模拟 CapsLock 切换大小写

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:19:52 +08:00
cf44a23c57 fix: 修复 CTC 重复字符识别和 CapsLock 复位时机
- CTC 解码新增空白帧检测:空白后出现相同 token 且置信度>0.5 时
  保留为重复字符(如"好好好"),无空白时仍按 CTC 规则去重
- 长按松开后先模拟 CapsLock 恢复原始状态,再开始识别
  确保识别结果注入时 CapsLock 状态已正确

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:15:26 +08:00
79085b862b fix: 修复 chunkCompleted 信号文件路径为空的 bug
closeCurrentFile() 会清空 currentFilePath_,之前代码在 closeCurrentFile()
之后才读取 currentFilePath_ 赋值给 completedPath,导致 emit 的路径为空。
修复:在 closeCurrentFile() 之前先保存文件路径和时长。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:08:16 +08:00
f4b7149435 feat: 支持手动指定日志目录和调试音频目录
- 新增 app.log_dir 配置项,支持通过设置页面或命令行 (-l/--log-dir) 指定日志目录
- 调试音频目录 (audio.debug_dir) 已正确传入 StreamingAudioWriter
- 设置页面新增"通用设置"分组,包含日志目录选择
- 未设置时使用系统默认 AppDataLocation 路径

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 14:00:23 +08:00
801dbe1ec2 fix: 降低 VAD 能量阈值并添加 STT 推理诊断日志
- VAD 能量阈值从 0.015 降至 0.003,适配低增益麦克风
- transcribeChunk 添加 WAV 文件大小、样本数、RMS 诊断日志
- onChunkCompleted 增加空文件路径检查

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 13:50:37 +08:00
271fccb39b fix: 修复 CapsLock 长按松开后未恢复大小写状态的问题
- 长按松开后调用 simulateCapsLock 模拟一次 CapsLock 按键恢复原始状态
- 新增 simulateKeysym 方法,正确处理 X11 keysym → keycode 转换
- Windows 端添加 X11 keysym → VK 虚拟键码映射

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 13:45:18 +08:00
8c2e787a25 feat: 添加 Windows 交叉编译支持与 ONNX Runtime MinGW 兼容方案
- 新增 C API shim (ort_api_shim.h) 解决 MinGW 与 ONNX Runtime 的 SAL 注解/_stdcall 兼容性问题
- 新增轻量级 C++ 包装器 (ort_minimal) 替代 onnxruntime_cxx_api.h
- cmake/dependencies.cmake 支持 Windows/ Linux 平台自动识别依赖路径
- 修复音频采集 paNonInterleaved bug(指针被误解析为 float 导致 RMS=inf)
- 修复 Windows 热键和 UI 相关代码
- 添加 MinGW 交叉编译工具链配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 11:50:16 +08:00
01a39ddc8c feat: 实现基于 VAD 的流式 WAV 录制识别方案
- 新增 StreamingAudioWriter 组件:持续录制 WAV 文件,VAD 检测静音段自动切换
- 静音检测:检测到 ~1s 连续静音后关闭当前文件,触发 chunkCompleted 信号
- STTTestPage 重构:移除缓冲区推理模式,改为 WAV 文件流式识别
- 每个 WAV 文件完成后在后台线程读取并推理,不阻塞继续录制
- 设置页面新增「调试音频目录」配置项
- 音频存储路径:debug 模式使用配置目录,非 debug 模式使用系统临时目录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 15:33:03 +08:00
dc4ebab47c feat: 添加音频输入设备选择器与音频电平诊断
- audio_capture 启动时输出详细设备信息(名称、Host API、采样率)
- 录音停止时输出 RMS 电平和峰值,帮助诊断音频质量问题
- 设置页面新增音频输入设备下拉选择,支持从 PortAudio 设备列表中手动选择
- 语音输入服务使用配置的音频设备和采样率参数
- 检测 monitor/output 类型设备时发出警告,避免选错回环设备

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 15:16:56 +08:00
a2b216092f fix: 修复配置保存死锁,添加快捷键录制组件
- 修复 ConfigManager::set() 在持有锁时发射信号导致的死锁
- 添加 setBatch() 方法批量更新配置,只发射一次 configChanged
- 新增 HotkeyRecorder 组件:点击按钮后按键录制任意快捷键
- SettingsPage 保存配置改为批量写入,避免多次触发服务重启

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 14:49:55 +08:00
33ae22ce62 fix: 修复模型加载时机问题,在配置加载后才加载模型
Application 构造函数中调用 loadGlobalModel() 时,配置文件
尚未加载,导致模型路径始终为空。改为将 loadGlobalModel() 设为
公开方法,在 main() 中配置加载和命令行参数处理完成后显式调用。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 14:36:59 +08:00
6f55ec5780 feat: 添加状态栏显示模型名称和加载状态
主窗口底部状态栏实时显示模型加载状态:
- 已就绪:绿色显示"模型已就绪: <文件名>"
- 路径为空:红色显示"⚠️ 模型路径未设置"
- 加载失败:橙色显示"⚠️ 模型加载失败: <文件名>"

Application 新增 modelLoading 信号和 modelPath() 方法,
配置变化时自动刷新状态栏显示。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 14:19:12 +08:00
ef97b962c3 refactor: 全局共享 STT 模型,避免重复加载
将 SenseVoiceEngine 提升为 Application 级别的全局单例,应用启动时
异步加载一次模型,实时语音识别、文件转写和快捷键语音输入共享同一实例。

- Application 创建并管理全局 SenseVoiceEngine,启动时加载模型
- STTTestPage、FileTranscribePage、VoiceInputService 不再各自
  创建引擎,改为接收全局实例
- 移除各模块中冗余的 loadModel/loadModelAsync/unloadModel 调用
- 模型未加载时提供友好的等待提示,而非加载失败的错误弹窗

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 14:10:31 +08:00
85a0890478 feat: 完善跨平台支持(Windows + macOS + Linux)
CMakeLists.txt:
- Qt6::DBus 和 pthread 改为平台条件链接
- 新增 PLATFORM_WINDOWS/PLATFORM_MACOS/PLATFORM_LINUX 编译宏
- 平台特定的源文件通过条件块选择

Windows 实现(新增):
- win_hotkey.cpp/h: 使用 RegisterHotKey API + QAbstractNativeEventFilter
  捕获 WM_HOTKEY 消息,通过 GetAsyncKeyState 轮询检测按键松开
- win_text_injector.cpp/h: 使用 SendInput API 的 KEYEVENTF_UNICODE
  实现全 Unicode 字符注入

macOS 实现(占位):
- mac_hotkey.cpp/h: 预留 CGEventTap 接口,暂不实现
- mac_text_injector.cpp/h: 使用 CGEventCreateKeyboardEvent 实现

通用修复:
- 硬编码 /tmp 路径替换为 QDir::tempPath()(跨平台临时目录)
- voice_input_service.cpp 使用条件 include 选择平台实现
- 设置页面移除 /tmp 硬编码提示文本

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 11:28:57 +08:00
baec3482a7 feat: 集成语音输入服务到主窗口和设置页面
主窗口新增 VoiceInputService 生命周期管理,支持通过配置页面
的开关动态启停 CapsLock 语音输入功能,窗口关闭时自动清理。

设置页面新增两个复选框:
- 调试录音:保存每次识别的原始音频到临时文件夹
- 快捷语音:启用 CapsLock 长按语音输入

转写页面和 STT 测试页面同步配置中的调试音频开关到引擎。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 11:13:15 +08:00
cda68e5376 feat: 添加 CapsLock 语音输入核心模块
新增三个核心组件:
- CapsLockVoiceHotkey: 基于 freedesktop GlobalShortcuts D-Bus Portal
  的全局快捷键注册,支持 Wayland 环境
- WaylandTextInjector: 通过 dlopen 动态加载 XTest 实现文本注入,
  无需编译时依赖 libXtst-devel 头文件
- VoiceInputService: 状态机协调器,实现长按 1s 录音、松开转写、
  短按恢复 CapsLock 的完整交互流程

CMakeLists.txt 新增 Qt6::DBus 依赖和新源文件。配置管理器新增
capslock_voice_enabled 开关。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 11:12:57 +08:00
d87b3e1ff8 fix: 修复 SenseVoice argmax 偏移 bug + 添加调试音频保存
修复转写结果显示为 token ID 的问题:argmax() 返回的是扁平化数组的
绝对索引,需减去 offset 才能得到正确的 token ID。同时修正置信度
计算使用正确的绝对索引。

添加调试音频保存功能:开启后每次推理将原始 PCM 保存为 WAV 文件
到 /tmp/impress_audio_debug/,并增加 RMS 电平和 NaN 诊断日志。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 11:12:34 +08:00
85b67780b1 fix: 过滤 SenseVoice 特殊标签 <|zh|> <|speech|> <|NEUTRAL|>
CTC 解码输出的 token 包含 SenseVoice 元数据标签(语言、事件、情感),
导致识别结果以 <|zh|><|speech|><|NEUTRAL|> 开头。在 tokenizer.decode()
中跳过所有 <|...|> 格式的标签,只保留实际识别文本,并清理首尾空白
和合并多余空格。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 20:35:40 +08:00
32d3a8e986 fix: SenseVoice推理张量类型错误 int64→int32
ONNX Runtime 报错 "Unexpected input data type. Actual: tensor(int64),
expected: tensor(int32)"。SenseVoice 模型的 x_length、language、text_norm
三个输入需要 int32 类型而非 int64。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 20:17:46 +08:00
ecc79aaeb6 fix: 修复 PortAudio 无音频设备检测问题 + 初始化生命周期
1. 重新编译 PortAudio 启用 ALSA 支持(原先编译时未启用任何音频后端,
   导致设备数为 0,Pa_GetDefaultInputDevice() 返回 -1)
2. 修复 getDeviceList() 中 Pa_Terminate/Pa_Initialize 顺序错误
   (先 Terminate 再 Initialize 会导致状态异常)
3. 使用全局 gPaInitialized 标志管理 PortAudio 生命周期,
   避免多次 start/stop 后重复初始化或意外终止

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 20:08:56 +08:00
6cb73b43a8 fix: 修复应用卡死的两个关键 bug
1. SenseVoiceEngine 死锁:loadModelSync/loadModelAsync 中调用
   unloadModel() 获取 mutex 后立即调用 loadInWorker() 再次获取
   同一非递归 mutex,导致死锁。改为内联清理逻辑。
2. PortAudio 回调内存分配:实时音频线程中 std::vector 分配
   导致 Linux 系统卡顿。改为预分配固定大小缓冲区。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 19:54:05 +08:00
a7a5b141a9 fix: STT测试页面异步推理,防止模型推理阻塞音频采集
SenseVoice 完整推理耗时数秒,原实现中 processAudioChunk 在
音频回调中同步调用 infer(),导致 PortAudio 回调线程阻塞,
表现为"程序无响应"。

修复方案:
- onAudioDataReady 仅缓存音频数据,不再同步调用推理
- QTimer 周期性触发,从缓冲区提取音频块
- 推理在 QtConcurrent 后台线程执行
- isInferencing_ 标志防止排队积压,推理期间跳过新音频块
- UI 实时显示缓冲区状态和推理进度

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 19:41:38 +08:00
9a6dfa3b88 feat: 集成 SenseVoice 完整推理管线
- 新增 SenseVoiceEngine: 专用 SenseVoice ONNX 模型推理引擎
- 新增 SenseVoiceFeatures: Fbank 特征提取 + LFR 拼接 + CMVN 归一化
  - 80-dim Mel 滤波器组 + 对数压缩
  - LFR (Low Frame Rate): window_size=7, window_shift=6 → 560-dim
  - CMVN: neg_mean / inv_stddev 从模型元数据自动提取
- 新增 SenseVoiceTokenizer: 加载 tokens.txt 词表,BPE 解码
- 新增 CTC 贪婪解码: 去除重复 token 和空白符
- 配置页面新增词表路径选择
- STT 测试页面和文件转写页面切换至 SenseVoiceEngine
- 更新 CMakeLists.txt 包含所有新增源文件
- 模型: /home/alvin/Documents/SenseVoice-Small/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/model.onnx
- 语言支持: 中文/英语/日语/韩语/粤语 自动检测

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 19:26:11 +08:00
49313f15f9 feat: 完善文件转写导出功能 (TXT/SRT/JSON)
- 修复导出功能:SRT/JSON 格式直接输出纯文本的 bug
- 新增 SRT 导出:按句子分段,均匀分配时间戳,支持字幕格式
- 新增 JSON 导出:结构化数据,包含文件信息、时长、采样率等元数据
- 新增 TXT 导出:包含文件信息和音频参数的格式化文本
- 记录音频文件信息 (时长/采样率/声道数) 用于导出

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 19:00:55 +08:00
84cc5bbc22 docs: 更新 README 项目状态 (VAD + 单元测试)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:58:15 +08:00
a3f1b1d9a6 feat: 添加 VAD 模块和单元测试框架
- 新增 VoiceActivityDetector 基于能量+过零率的语音活动检测
- 引入 Catch2 单元测试框架
- 添加 4 个测试模块: AudioProcessor/VAD/MelSpectrogram/WhisperTokenizer
- 从构建中移除废弃的 tokenizer/decoder 文件
- 39 个测试用例全部通过

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:57:46 +08:00
59c12ab931 feat: 扩展音频格式支持与推理管线优化
- 新增 MP3/FLAC 格式解码 (dr_mp3/dr_flac)
- 修复 Mel 频谱图使用 magnitude² 替代 magnitude 的问题
- 推理管线增加音频重采样 (非 16kHz 自动转换)
- 更新 README 项目状态

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:35:48 +08:00
760899e81c feat: 完善推理管线和后台任务管理
核心改进:
- STTEngine 接入 WhisperTokenizer 解码,输出可读文本而非 [T1234]
- 模型加载时自动查找同目录下的 tokenizer.vocab 词表
- language 参数生效,推理时记录语言配置
- 卸载模型时清理 tokenizer 状态

文件转写后台化:
- FileTranscribePage 使用 QtConcurrent 后台线程执行解码+推理
- 模型加载也在后台执行,不阻塞 UI
- processFileAsync() + onTaskComplete() 异步队列处理
- 支持中途停止 (onStopTranscribe)

构建:
- CMake 默认使用 RelWithDebInfo (Release 带调试信息)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:27:36 +08:00
e31d51f12d docs: 更新 README 项目状态
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:18:07 +08:00
bba124aee4 feat: 实现 Whisper ONNX 完整推理管线
新增组件:
- MelSpectrogram: Mel 频谱图提取 (Hann 窗 + FFT + Mel 滤波器组)
- WhisperTokenizer: BPE 分词器 (支持 token 编解码和特殊 token)

核心改进:
- STTEngine 动态检测 ONNX 模型输入/输出名称
- 支持两种模型格式: 直接输出 [1, vocab_size] 和自回归 [1, seq, vocab]
- argmax + softmax 解码 + 置信度计算
- infer() 接口改为 language 参数替代 isStreaming

UI 调整:
- STTTestPage 和 FileTranscribePage 适配新的 infer() 接口

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:17:10 +08:00
09074a71fe docs: 添加第三方依赖部署指南
详细说明各依赖库的下载来源、编译方法和验证步骤,
包含快速部署脚本。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 16:00:52 +08:00
02e100b318 feat: 初始化 Impress Voice Input 项目
基于 ONNX 的实时语音转文本输入法,C++ 跨平台实现。

核心组件:
- Qt 6 跨平台 GUI(实时识别 / 文件转写 / 配置页面)
- ONNX Runtime 推理引擎(异步模型加载)
- PortAudio 音频采集
- dr_libs 音频文件解码
- JSON 配置管理(线程安全,自动持久化)
- 日志系统(控制台 + 文件输出)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 15:53:05 +08:00