perf: 消除按键到录音 1s 延迟,按下立即录音

旧流程: 按下 → PreRecording → 1s 定时器 → Recording (延迟 ~1032ms)
新流程: 按下 → 立即 Recording (延迟 ~0ms)

状态机简化:
  - 移除 PreRecording 状态和 1s 长按确认逻辑
  - Idle → Activated → 直接 Recording
  - Deactivated → 识别 → 复位 CapsLock → Cooldown

统计仍然保留,现在应该显示 0ms 延迟。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
impressionyang 2026-06-11 16:45:55 +08:00
parent 2152dcb296
commit c64a8123be
2 changed files with 28 additions and 54 deletions

View File

@ -42,33 +42,9 @@ VoiceInputService::VoiceInputService(ConfigManager* configManager,
{ {
impl_->sttEngine = sttEngine; impl_->sttEngine = sttEngine;
// 1s 定时器:确认长按 → 开始正式录音CapsLock 灯保持 ON识别后复位 // 确认长按定时器 → 直接进入 Recording消除 1s 延迟
longPressTimer_ = new QTimer(this); longPressTimer_ = new QTimer(this);
longPressTimer_->setSingleShot(true); longPressTimer_->setSingleShot(true);
connect(longPressTimer_, &QTimer::timeout, this, [this]() {
if (state_ == PreRecording) {
state_ = Recording;
audioBuffer_.clear(); // 清除预录音期间的静音
emit statusChanged("正在录音...");
// 统计按键到录音延迟
if (latencyTracking_ && hotkeyLatencyTimer_.isValid()) {
qint64 latencyMs = hotkeyLatencyTimer_.elapsed();
totalKeyCount_++;
totalLatencyMs_ += latencyMs;
maxLatencyMs_ = std::max(maxLatencyMs_, (double)latencyMs);
minLatencyMs_ = std::min(minLatencyMs_, (double)latencyMs);
double avgMs = totalLatencyMs_ / totalKeyCount_;
LOG_INFO(kTag, QString("⏱ 按键→录音延迟: %1ms (平均: %2ms, 最小: %3ms, 最大: %4ms, 累计: %5次)")
.arg(latencyMs).arg(avgMs, 0, 'f', 0)
.arg(minLatencyMs_, 0, 'f', 0).arg(maxLatencyMs_, 0, 'f', 0)
.arg(totalKeyCount_));
latencyTracking_ = false;
}
LOG_DEBUG(kTag, "PreRecording → Recording (灯保持 ON开始录音)");
}
});
// 松开后冷却定时器 // 松开后冷却定时器
cooldownTimer_ = new QTimer(this); cooldownTimer_ = new QTimer(this);
@ -163,31 +139,35 @@ void VoiceInputService::onHotkeyActivated() {
return; return;
} }
// PreRecording 重复触发:忽略 // Idle → 直接进入 Recording消除 1s 延迟
if (state_ == PreRecording) { state_ = Recording;
LOG_DEBUG(kTag, "忽略重复 Activated (PreRecording 防抖)");
return;
}
// Idle → PreRecording灯亮预录音
state_ = PreRecording;
recording_ = true; recording_ = true;
audioBuffer_.clear(); audioBuffer_.clear();
// 启动延迟统计
hotkeyLatencyTimer_.start();
latencyTracking_ = true;
int deviceIndex = configManager_->get("audio.input_device").toInt(); int deviceIndex = configManager_->get("audio.input_device").toInt();
int sampleRate = configManager_->get("stt.sample_rate").toInt(); int sampleRate = configManager_->get("stt.sample_rate").toInt();
int bufferSizeMs = configManager_->get("audio.buffer_size_ms").toInt(); int bufferSizeMs = configManager_->get("audio.buffer_size_ms").toInt();
impl_->audioCapture->start(deviceIndex, sampleRate, bufferSizeMs); impl_->audioCapture->start(deviceIndex, sampleRate, bufferSizeMs);
// 启动 1s 定时器:灯灭 → 正式录音 // 延迟统计(现在应该接近 0
longPressTimer_->start(longPressThreshold_); hotkeyLatencyTimer_.start();
latencyTracking_ = true;
qint64 latencyMs = 0;
LOG_DEBUG(kTag, "Idle → PreRecording (灯亮)"); LOG_DEBUG(kTag, "Idle → Recording (立即开始录音)");
emit statusChanged("等待长按确认..."); emit statusChanged("正在录音...");
// 统计打印
totalKeyCount_++;
totalLatencyMs_ += latencyMs;
maxLatencyMs_ = std::max(maxLatencyMs_, (double)latencyMs);
minLatencyMs_ = std::min(minLatencyMs_, (double)latencyMs);
double avgMs = totalLatencyMs_ / totalKeyCount_;
LOG_INFO(kTag, QString("⏱ 按键→录音延迟: %1ms (平均: %2ms, 最小: %3ms, 最大: %4ms, 累计: %5次)")
.arg(latencyMs).arg(avgMs, 0, 'f', 0)
.arg(minLatencyMs_, 0, 'f', 0).arg(maxLatencyMs_, 0, 'f', 0)
.arg(totalKeyCount_));
latencyTracking_ = false;
} }
void VoiceInputService::onHotkeyDeactivated() { void VoiceInputService::onHotkeyDeactivated() {
@ -205,15 +185,11 @@ void VoiceInputService::onHotkeyDeactivated() {
impl_->audioCapture->stop(); impl_->audioCapture->stop();
} }
if (state_ == PreRecording) { if (state_ == Recording) {
// 短按 → 恢复 CapsLock 灯 // 松开 → 先恢复 CapsLock 灯,再开始识别
simulateCapsLock(); simulateCapsLock();
state_ = Idle; state_ = Idle;
LOG_DEBUG(kTag, "短按,恢复 CapsLock 灯"); LOG_DEBUG(kTag, "Recording → Idle (松开转写)");
} else if (state_ == Recording) {
// 长按后松开 → 灯保持 ON等待识别完成后复位
state_ = Idle;
LOG_DEBUG(kTag, "Recording → Idle (松开转写,灯保持 ON)");
stopRecordingAndTranscribe(); stopRecordingAndTranscribe();
} }

View File

@ -18,20 +18,18 @@ class ConfigManager;
/** /**
* @brief CapsLock * @brief CapsLock
* *
* CapsLock *
* (PreRecording) 1s (Recording) * CapsLock Recording
* CapsLock * CapsLock
* *
* *
* 绿 * 绿
*
* *
* *
* *
* *
* Idle * Idle
* PreRecording * Recording Activated
* Recording 1s Portal
* Cooldown * Cooldown
*/ */
class VoiceInputService : public QObject { class VoiceInputService : public QObject {