Skip to content

Commit 1a9699c

Browse files
ZhaoChaoqunclaude
andcommitted
feat: switch HUD to QwenASR output once it starts producing text
In DualEngine mode, HUD initially shows Paraformer output for fast feedback (~100ms latency). Once QwenASR produces its first text (~2s), HUD automatically switches to QwenASR which provides better accuracy and richer partial results (including unfixedText). This gives users consistent preview-to-final output while maintaining the snappy initial response. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent df1705a commit 1a9699c

1 file changed

Lines changed: 50 additions & 4 deletions

File tree

Sources/DualEngineASR.swift

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ private let logger = Logger(subsystem: "com.typeless.app", category: "DualEngine
66
/// 双引擎 ASR:Streaming Paraformer 实时预览 + QwenASR 流式精转写
77
///
88
/// 录音过程中:
9-
/// - Streaming Paraformer 流式处理音频,实时输出到 HUD
9+
/// - 前期(~2s):Streaming Paraformer 流式处理音频,实时输出到 HUD(低延迟预览)
1010
/// - QwenASR 同步接收相同音频流,持续增量推理
11+
/// - QwenASR 产出首段文本后,自动切换 HUD 显示源为 QwenASR(含 unfixedText)
12+
/// - 切换后 Paraformer 回调被忽略,但仍继续接收音频
1113
///
1214
/// 松开 Fn 后:
1315
/// - QwenASR finalize 并输出最终结果(含标点)
1416
///
1517
/// 并发模型:
1618
/// - `qwenQueue`(serial):保护 QwenASR 引擎的所有调用(pushAudio / getResult / reset)
1719
/// - `paraformerEngine` 使用自己内部的 `recognitionQueue`,完全独立
20+
/// - `qwenHasOutput` 标志通过 os_unfair_lock 保护,确保跨队列安全读写
1821
class DualEngineASR: ASREngine {
1922

2023
// MARK: - Constants
@@ -32,9 +35,33 @@ class DualEngineASR: ASREngine {
3235

3336
private let qwenQueue = DispatchQueue(label: "com.typeless.dualengine.qwen", qos: .userInitiated)
3437

38+
// MARK: - HUD 切换状态
39+
40+
/// QwenASR 是否已产出文本。一旦为 true,HUD 由 QwenASR 驱动,Paraformer 回调被忽略。
41+
/// 该标志只会从 false → true 单向转变(每次录音期间),在 reset()/flush() 中重置。
42+
/// 被 Paraformer 的 recognitionQueue 和 qwenQueue 两个队列读写,
43+
/// 使用 os_unfair_lock 保护以确保线程安全。
44+
private var _qwenHasOutput = false
45+
private var _qwenLock = os_unfair_lock_s()
46+
3547
let needsPunctuation = false
3648
let needsITN = true
3749

50+
/// 线程安全读取 qwenHasOutput
51+
private var qwenHasOutput: Bool {
52+
get {
53+
os_unfair_lock_lock(&_qwenLock)
54+
let value = _qwenHasOutput
55+
os_unfair_lock_unlock(&_qwenLock)
56+
return value
57+
}
58+
set {
59+
os_unfair_lock_lock(&_qwenLock)
60+
_qwenHasOutput = newValue
61+
os_unfair_lock_unlock(&_qwenLock)
62+
}
63+
}
64+
3865
// MARK: - 初始化
3966

4067
/// - Parameters:
@@ -50,13 +77,28 @@ class DualEngineASR: ASREngine {
5077
// MARK: - ASREngine 协议
5178

5279
func processAudio(samples: [Float], onPartialResult: @escaping (String, String?) -> Void) {
53-
// 1. 转发给 Paraformer 做实时预览
54-
paraformerEngine.processAudio(samples: samples, onPartialResult: onPartialResult)
80+
// 1. 转发给 Paraformer 做实时预览(QwenASR 接管后跳过回调)
81+
paraformerEngine.processAudio(samples: samples) { [weak self] text, unfixed in
82+
guard let self = self, !self.qwenHasOutput else { return }
83+
onPartialResult(text, unfixed)
84+
}
5585

56-
// 2. 同步推送给 QwenASR 流式引擎
86+
// 2. 同步推送给 QwenASR 流式引擎,产出文本后接管 HUD
5787
qwenQueue.async { [weak self] in
5888
guard let self = self else { return }
5989
_ = self.qwenRecognizer.pushAudio(samples: samples, finalize: false)
90+
91+
let stableText = self.qwenRecognizer.getResult()
92+
let unfixedText = self.qwenRecognizer.getUnfixed()
93+
94+
let hasContent = !stableText.isEmpty || (unfixedText != nil && !unfixedText!.isEmpty)
95+
if hasContent {
96+
if !self.qwenHasOutput {
97+
self.qwenHasOutput = true
98+
logger.info("HUD 切换:QwenASR 已产出文本,接管 HUD 显示")
99+
}
100+
onPartialResult(stableText, unfixedText)
101+
}
60102
}
61103
}
62104

@@ -75,6 +117,9 @@ class DualEngineASR: ASREngine {
75117
let result = self.qwenRecognizer.getResult()
76118
self.qwenRecognizer.reset()
77119

120+
// 重置 HUD 切换标志,为下一次录音做准备
121+
self.qwenHasOutput = false
122+
78123
// 同步 reset Paraformer,防止下次录音 HUD 残留上次的累积文本
79124
self.paraformerEngine.reset()
80125

@@ -87,6 +132,7 @@ class DualEngineASR: ASREngine {
87132
}
88133

89134
func reset() {
135+
qwenHasOutput = false
90136
paraformerEngine.reset()
91137
qwenQueue.sync {
92138
qwenRecognizer.reset()

0 commit comments

Comments
 (0)