refactor: 全局共享 STT 模型,避免重复加载
将 SenseVoiceEngine 提升为 Application 级别的全局单例,应用启动时 异步加载一次模型,实时语音识别、文件转写和快捷键语音输入共享同一实例。 - Application 创建并管理全局 SenseVoiceEngine,启动时加载模型 - STTTestPage、FileTranscribePage、VoiceInputService 不再各自 创建引擎,改为接收全局实例 - 移除各模块中冗余的 loadModel/loadModelAsync/unloadModel 调用 - 模型未加载时提供友好的等待提示,而非加载失败的错误弹窗 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
85a0890478
commit
ef97b962c3
@ -1,5 +1,6 @@
|
|||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "config_manager.h"
|
#include "config_manager.h"
|
||||||
|
#include "core/sense_voice_engine.h"
|
||||||
#include "utils/logger.h"
|
#include "utils/logger.h"
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
@ -14,6 +15,22 @@ Application::Application(int& argc, char** argv)
|
|||||||
|
|
||||||
configManager_ = std::make_unique<ConfigManager>(this);
|
configManager_ = std::make_unique<ConfigManager>(this);
|
||||||
configManager_->loadDefaults();
|
configManager_->loadDefaults();
|
||||||
|
|
||||||
|
// 创建全局 STT 引擎(共享实例)
|
||||||
|
sttEngine_ = new SenseVoiceEngine(this);
|
||||||
|
connect(sttEngine_, &SenseVoiceEngine::modelLoaded, this, [this](const QString& path) {
|
||||||
|
modelLoaded_ = true;
|
||||||
|
LOG_INFO(kTag, QString("全局模型已加载: %1").arg(path));
|
||||||
|
emit modelLoaded();
|
||||||
|
});
|
||||||
|
connect(sttEngine_, &SenseVoiceEngine::modelLoadError, this, [this](const QString&, const QString& err) {
|
||||||
|
modelLoaded_ = false;
|
||||||
|
LOG_ERROR(kTag, QString("全局模型加载失败: %1").arg(err));
|
||||||
|
emit modelLoadError(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 异步加载全局模型
|
||||||
|
loadGlobalModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application() {
|
Application::~Application() {
|
||||||
@ -25,4 +42,30 @@ ConfigManager* Application::configManager() const {
|
|||||||
return configManager_.get();
|
return configManager_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SenseVoiceEngine* Application::sttEngine() const {
|
||||||
|
return sttEngine_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::isModelLoaded() const {
|
||||||
|
return modelLoaded_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::loadGlobalModel() {
|
||||||
|
QString modelPath = configManager_->get("stt.model_path").toString();
|
||||||
|
if (modelPath.isEmpty()) {
|
||||||
|
LOG_WARNING(kTag, "模型路径为空,请在配置中设置后重启");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString tokensPath = configManager_->get("stt.tokens_path").toString();
|
||||||
|
QString device = configManager_->get("stt.device").toString();
|
||||||
|
int numThreads = configManager_->get("stt.num_threads").toInt();
|
||||||
|
|
||||||
|
bool debugSave = configManager_->get("stt.debug_save_audio").toBool();
|
||||||
|
sttEngine_->setDebugSaveAudio(debugSave);
|
||||||
|
|
||||||
|
LOG_INFO(kTag, QString("正在异步加载全局模型: %1").arg(modelPath));
|
||||||
|
sttEngine_->loadModelAsync(modelPath, tokensPath, device, numThreads);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace impress
|
} // namespace impress
|
||||||
|
|||||||
@ -6,9 +6,13 @@
|
|||||||
namespace impress {
|
namespace impress {
|
||||||
|
|
||||||
class ConfigManager;
|
class ConfigManager;
|
||||||
|
class SenseVoiceEngine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 应用入口封装
|
* @brief 应用入口封装
|
||||||
|
*
|
||||||
|
* 管理全局共享组件:配置管理器、STT 引擎。
|
||||||
|
* STT 引擎在启动时异步加载模型,所有页面和服务共享同一实例。
|
||||||
*/
|
*/
|
||||||
class Application : public QApplication {
|
class Application : public QApplication {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -19,8 +23,25 @@ public:
|
|||||||
/** @brief 获取全局配置管理器 */
|
/** @brief 获取全局配置管理器 */
|
||||||
ConfigManager* configManager() const;
|
ConfigManager* configManager() const;
|
||||||
|
|
||||||
|
/** @brief 获取全局 STT 引擎(共享实例) */
|
||||||
|
SenseVoiceEngine* sttEngine() const;
|
||||||
|
|
||||||
|
/** @brief 获取全局 STT 引擎加载状态 */
|
||||||
|
bool isModelLoaded() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/** @brief 模型加载完成 */
|
||||||
|
void modelLoaded();
|
||||||
|
|
||||||
|
/** @brief 模型加载失败 */
|
||||||
|
void modelLoadError(const QString& error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void loadGlobalModel();
|
||||||
|
|
||||||
std::unique_ptr<ConfigManager> configManager_;
|
std::unique_ptr<ConfigManager> configManager_;
|
||||||
|
SenseVoiceEngine* sttEngine_ = nullptr;
|
||||||
|
bool modelLoaded_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace impress
|
} // namespace impress
|
||||||
|
|||||||
@ -31,11 +31,14 @@ struct VoiceInputService::Impl {
|
|||||||
WaylandTextInjector* injector = nullptr;
|
WaylandTextInjector* injector = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
VoiceInputService::VoiceInputService(ConfigManager* configManager, QObject* parent)
|
VoiceInputService::VoiceInputService(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, configManager_(configManager)
|
, configManager_(configManager)
|
||||||
, impl_(std::make_unique<Impl>())
|
, impl_(std::make_unique<Impl>())
|
||||||
{
|
{
|
||||||
|
impl_->sttEngine = sttEngine;
|
||||||
longPressTimer_ = new QTimer(this);
|
longPressTimer_ = new QTimer(this);
|
||||||
longPressTimer_->setSingleShot(true);
|
longPressTimer_->setSingleShot(true);
|
||||||
connect(longPressTimer_, &QTimer::timeout, this, [this]() {
|
connect(longPressTimer_, &QTimer::timeout, this, [this]() {
|
||||||
@ -59,30 +62,7 @@ bool VoiceInputService::start() {
|
|||||||
connect(impl_->audioCapture, &AudioCapture::audioDataReady,
|
connect(impl_->audioCapture, &AudioCapture::audioDataReady,
|
||||||
this, &VoiceInputService::onAudioData);
|
this, &VoiceInputService::onAudioData);
|
||||||
|
|
||||||
// 2. 初始化 STT 引擎并加载模型
|
// 2. STT 引擎已作为参数传入(共享全局实例)
|
||||||
impl_->sttEngine = new SenseVoiceEngine(this);
|
|
||||||
|
|
||||||
// 从配置加载模型
|
|
||||||
QString modelPath = configManager_->get("stt.model_path").toString();
|
|
||||||
QString tokensPath = configManager_->get("stt.tokens_path").toString();
|
|
||||||
QString device = configManager_->get("stt.device").toString();
|
|
||||||
int numThreads = configManager_->get("stt.num_threads").toInt();
|
|
||||||
|
|
||||||
if (!modelPath.isEmpty()) {
|
|
||||||
LOG_INFO(kTag, QString("正在加载 STT 模型: %1").arg(modelPath));
|
|
||||||
bool modelLoaded = impl_->sttEngine->loadModelSync(modelPath, tokensPath, device, numThreads);
|
|
||||||
if (!modelLoaded) {
|
|
||||||
emit error(QString("STT 模型加载失败: %1").arg(modelPath));
|
|
||||||
LOG_ERROR(kTag, "STT 模型加载失败");
|
|
||||||
} else {
|
|
||||||
LOG_INFO(kTag, "STT 模型加载成功");
|
|
||||||
// 同步调试音频设置
|
|
||||||
bool debugSave = configManager_->get("stt.debug_save_audio").toBool();
|
|
||||||
impl_->sttEngine->setDebugSaveAudio(debugSave);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG_WARNING(kTag, "模型路径为空,请先在配置中设置模型路径");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 初始化全局快捷键
|
// 3. 初始化全局快捷键
|
||||||
impl_->hotkey = new CapsLockVoiceHotkey(this);
|
impl_->hotkey = new CapsLockVoiceHotkey(this);
|
||||||
@ -124,9 +104,6 @@ void VoiceInputService::stop() {
|
|||||||
if (impl_->audioCapture) {
|
if (impl_->audioCapture) {
|
||||||
impl_->audioCapture->stop();
|
impl_->audioCapture->stop();
|
||||||
}
|
}
|
||||||
if (impl_->sttEngine) {
|
|
||||||
impl_->sttEngine->unloadModel();
|
|
||||||
}
|
|
||||||
if (impl_->hotkey) {
|
if (impl_->hotkey) {
|
||||||
impl_->hotkey->stop();
|
impl_->hotkey->stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,9 @@ class ConfigManager;
|
|||||||
class VoiceInputService : public QObject {
|
class VoiceInputService : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit VoiceInputService(ConfigManager* configManager, QObject* parent = nullptr);
|
explicit VoiceInputService(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QObject* parent = nullptr);
|
||||||
~VoiceInputService() override;
|
~VoiceInputService() override;
|
||||||
|
|
||||||
/** @brief 启动服务(初始化所有组件) */
|
/** @brief 启动服务(初始化所有组件) */
|
||||||
|
|||||||
@ -56,8 +56,8 @@ int main(int argc, char* argv[])
|
|||||||
configManager->set("stt.model_path", modelPath);
|
configManager->set("stt.model_path", modelPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建并显示主窗口
|
// 创建并显示主窗口(传入全局引擎)
|
||||||
impress::MainWindow mainWindow(configManager);
|
impress::MainWindow mainWindow(configManager, app.sttEngine());
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
|
|||||||
@ -31,10 +31,12 @@ static const char* const kTag = "FileTranscribePage";
|
|||||||
|
|
||||||
namespace impress {
|
namespace impress {
|
||||||
|
|
||||||
FileTranscribePage::FileTranscribePage(ConfigManager* configManager, QWidget* parent)
|
FileTranscribePage::FileTranscribePage(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, configManager_(configManager)
|
, configManager_(configManager)
|
||||||
, sttEngine_(new SenseVoiceEngine(this))
|
, sttEngine_(sttEngine)
|
||||||
, audioDecoder_(new AudioDecoder(this))
|
, audioDecoder_(new AudioDecoder(this))
|
||||||
{
|
{
|
||||||
setupUI();
|
setupUI();
|
||||||
@ -148,29 +150,10 @@ void FileTranscribePage::onStartTranscribe() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString modelPath = configManager_->get("stt.model_path").toString();
|
// 检查全局模型是否已加载
|
||||||
if (modelPath.isEmpty()) {
|
if (!sttEngine_->isLoaded()) {
|
||||||
QMessageBox::warning(this, "提示", "请先在配置页面设置模型路径");
|
QMessageBox::warning(this, "提示",
|
||||||
return;
|
"模型尚未加载完成,请稍候再试");
|
||||||
}
|
|
||||||
|
|
||||||
// 在后台线程中加载模型(不阻塞 UI)
|
|
||||||
statusLabel_->setText("正在加载模型...");
|
|
||||||
startBtn_->setEnabled(false);
|
|
||||||
activeWorkers_ = 1; // 标记正在加载模型
|
|
||||||
|
|
||||||
(void)QtConcurrent::run([this, modelPath]() {
|
|
||||||
bool success = sttEngine_->loadModelSync(modelPath,
|
|
||||||
configManager_->get("stt.tokens_path").toString(),
|
|
||||||
configManager_->get("stt.device").toString(),
|
|
||||||
configManager_->get("stt.num_threads").toInt());
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, [this, success]() {
|
|
||||||
activeWorkers_--;
|
|
||||||
if (!success) {
|
|
||||||
QMessageBox::critical(this, "错误", "模型加载失败");
|
|
||||||
statusLabel_->setText("模型加载失败");
|
|
||||||
startBtn_->setEnabled(true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +169,6 @@ void FileTranscribePage::onStartTranscribe() {
|
|||||||
|
|
||||||
// 启动后台转写队列
|
// 启动后台转写队列
|
||||||
startBatchTranscription();
|
startBatchTranscription();
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileTranscribePage::onStopTranscribe() {
|
void FileTranscribePage::onStopTranscribe() {
|
||||||
@ -195,7 +176,6 @@ void FileTranscribePage::onStopTranscribe() {
|
|||||||
activeWorkers_ = 0;
|
activeWorkers_ = 0;
|
||||||
progressBar_->setVisible(false);
|
progressBar_->setVisible(false);
|
||||||
statusLabel_->setText("已停止");
|
statusLabel_->setText("已停止");
|
||||||
sttEngine_->unloadModel();
|
|
||||||
updateUIState();
|
updateUIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +276,6 @@ void FileTranscribePage::onAllComplete() {
|
|||||||
isTranscribing_ = false;
|
isTranscribing_ = false;
|
||||||
statusLabel_->setText("全部完成");
|
statusLabel_->setText("全部完成");
|
||||||
progressBar_->setVisible(false);
|
progressBar_->setVisible(false);
|
||||||
sttEngine_->unloadModel();
|
|
||||||
updateUIState();
|
updateUIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,9 @@ struct TranscribeTask {
|
|||||||
class FileTranscribePage : public QWidget {
|
class FileTranscribePage : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit FileTranscribePage(ConfigManager* configManager, QWidget* parent = nullptr);
|
explicit FileTranscribePage(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
~FileTranscribePage() override;
|
~FileTranscribePage() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include "file_transcribe_page.h"
|
#include "file_transcribe_page.h"
|
||||||
#include "settings_page.h"
|
#include "settings_page.h"
|
||||||
#include "core/voice_input_service.h"
|
#include "core/voice_input_service.h"
|
||||||
|
#include "core/sense_voice_engine.h"
|
||||||
#include "app/config_manager.h"
|
#include "app/config_manager.h"
|
||||||
#include "utils/logger.h"
|
#include "utils/logger.h"
|
||||||
|
|
||||||
@ -16,19 +17,21 @@ static const char* const kTag = "MainWindow";
|
|||||||
|
|
||||||
namespace impress {
|
namespace impress {
|
||||||
|
|
||||||
MainWindow::MainWindow(ConfigManager* configManager, QWidget* parent)
|
MainWindow::MainWindow(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QWidget* parent)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
, configManager_(configManager)
|
, configManager_(configManager)
|
||||||
{
|
{
|
||||||
setWindowTitle("Impress Voice Input");
|
setWindowTitle("Impress Voice Input");
|
||||||
resize(1000, 700);
|
resize(1000, 700);
|
||||||
|
|
||||||
setupUI();
|
setupUI(sttEngine);
|
||||||
setupMenuBar();
|
setupMenuBar();
|
||||||
loadStyleSheet();
|
loadStyleSheet();
|
||||||
|
|
||||||
// 初始化语音输入服务
|
// 初始化语音输入服务(共享全局引擎)
|
||||||
voiceInputService_ = new VoiceInputService(configManager_, this);
|
voiceInputService_ = new VoiceInputService(configManager_, sttEngine, this);
|
||||||
connect(voiceInputService_, &VoiceInputService::statusChanged,
|
connect(voiceInputService_, &VoiceInputService::statusChanged,
|
||||||
this, [this](const QString& status) {
|
this, [this](const QString& status) {
|
||||||
LOG_DEBUG(kTag, QString("语音输入状态: %1").arg(status));
|
LOG_DEBUG(kTag, QString("语音输入状态: %1").arg(status));
|
||||||
@ -54,11 +57,11 @@ MainWindow::MainWindow(ConfigManager* configManager, QWidget* parent)
|
|||||||
|
|
||||||
MainWindow::~MainWindow() = default;
|
MainWindow::~MainWindow() = default;
|
||||||
|
|
||||||
void MainWindow::setupUI() {
|
void MainWindow::setupUI(SenseVoiceEngine* sttEngine) {
|
||||||
tabWidget_ = new QTabWidget(this);
|
tabWidget_ = new QTabWidget(this);
|
||||||
|
|
||||||
sttPage_ = new STTTestPage(configManager_, tabWidget_);
|
sttPage_ = new STTTestPage(configManager_, sttEngine, tabWidget_);
|
||||||
transcribePage_ = new FileTranscribePage(configManager_, tabWidget_);
|
transcribePage_ = new FileTranscribePage(configManager_, sttEngine, tabWidget_);
|
||||||
settingsPage_ = new SettingsPage(configManager_, tabWidget_);
|
settingsPage_ = new SettingsPage(configManager_, tabWidget_);
|
||||||
|
|
||||||
tabWidget_->addTab(sttPage_, "实时语音识别");
|
tabWidget_->addTab(sttPage_, "实时语音识别");
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
namespace impress {
|
namespace impress {
|
||||||
|
|
||||||
class ConfigManager;
|
class ConfigManager;
|
||||||
|
class SenseVoiceEngine;
|
||||||
class STTTestPage;
|
class STTTestPage;
|
||||||
class FileTranscribePage;
|
class FileTranscribePage;
|
||||||
class SettingsPage;
|
class SettingsPage;
|
||||||
@ -15,19 +16,21 @@ class VoiceInputService;
|
|||||||
/**
|
/**
|
||||||
* @brief 主窗口
|
* @brief 主窗口
|
||||||
*
|
*
|
||||||
* 使用 Tab 页导航管理三个功能页面。
|
* 使用 Tab 页导航管理三个功能页面,共享全局 STT 引擎。
|
||||||
*/
|
*/
|
||||||
class MainWindow : public QMainWindow {
|
class MainWindow : public QMainWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(ConfigManager* configManager, QWidget* parent = nullptr);
|
explicit MainWindow(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
~MainWindow() override;
|
~MainWindow() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent* event) override;
|
void closeEvent(QCloseEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupUI();
|
void setupUI(SenseVoiceEngine* sttEngine);
|
||||||
void setupMenuBar();
|
void setupMenuBar();
|
||||||
void loadStyleSheet();
|
void loadStyleSheet();
|
||||||
void onVoiceInputConfigChanged();
|
void onVoiceInputConfigChanged();
|
||||||
|
|||||||
@ -25,10 +25,12 @@ static const char* const kTag = "STTTestPage";
|
|||||||
|
|
||||||
namespace impress {
|
namespace impress {
|
||||||
|
|
||||||
STTTestPage::STTTestPage(ConfigManager* configManager, QWidget* parent)
|
STTTestPage::STTTestPage(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, configManager_(configManager)
|
, configManager_(configManager)
|
||||||
, sttEngine_(new SenseVoiceEngine(this))
|
, sttEngine_(sttEngine)
|
||||||
, audioCapture_(new AudioCapture(this))
|
, audioCapture_(new AudioCapture(this))
|
||||||
, inferenceTimer_(new QTimer(this))
|
, inferenceTimer_(new QTimer(this))
|
||||||
{
|
{
|
||||||
@ -122,16 +124,14 @@ void STTTestPage::onToggleRecording() {
|
|||||||
if (isRecording_) {
|
if (isRecording_) {
|
||||||
audioCapture_->stop();
|
audioCapture_->stop();
|
||||||
inferenceTimer_->stop();
|
inferenceTimer_->stop();
|
||||||
sttEngine_->unloadModel();
|
|
||||||
isRecording_ = false;
|
isRecording_ = false;
|
||||||
isInferencing_ = false;
|
isInferencing_ = false;
|
||||||
audioBuffer_.clear();
|
audioBuffer_.clear();
|
||||||
} else {
|
} else {
|
||||||
// 读取配置
|
// 检查全局模型是否已加载
|
||||||
QString modelPath = configManager_->get("stt.model_path").toString();
|
if (!sttEngine_->isLoaded()) {
|
||||||
if (modelPath.isEmpty()) {
|
|
||||||
QMessageBox::warning(this, "提示",
|
QMessageBox::warning(this, "提示",
|
||||||
"请先在「配置」页面设置模型路径并保存");
|
"模型尚未加载完成,请稍候再试");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,38 +139,17 @@ void STTTestPage::onToggleRecording() {
|
|||||||
sttEngine_->setDebugSaveAudio(
|
sttEngine_->setDebugSaveAudio(
|
||||||
configManager_->get("stt.debug_save_audio").toBool());
|
configManager_->get("stt.debug_save_audio").toBool());
|
||||||
|
|
||||||
// 异步加载模型
|
|
||||||
if (!sttEngine_->isLoaded() ||
|
|
||||||
currentModelPath_ != modelPath) {
|
|
||||||
isLoadingModel_ = true;
|
|
||||||
statusLabel_->setText("正在加载模型,请稍候...");
|
|
||||||
updateUIState();
|
|
||||||
|
|
||||||
sttEngine_->loadModelAsync(modelPath,
|
|
||||||
configManager_->get("stt.tokens_path").toString(),
|
|
||||||
configManager_->get("stt.device").toString(),
|
|
||||||
configManager_->get("stt.num_threads").toInt());
|
|
||||||
|
|
||||||
currentModelPath_ = modelPath;
|
|
||||||
// 注意:startAudioCapture() 将在 onModelLoaded() 回调中调用
|
|
||||||
} else {
|
|
||||||
startAudioCapture();
|
startAudioCapture();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
updateUIState();
|
updateUIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void STTTestPage::onModelLoaded(const QString& modelPath) {
|
void STTTestPage::onModelLoaded(const QString& modelPath) {
|
||||||
LOG_INFO(kTag, QString("模型加载成功: %1").arg(modelPath));
|
LOG_INFO(kTag, QString("全局模型加载成功: %1").arg(modelPath));
|
||||||
isLoadingModel_ = false;
|
isLoadingModel_ = false;
|
||||||
statusLabel_->setText(QString("模型就绪: %1").arg(
|
statusLabel_->setText(QString("模型就绪: %1").arg(
|
||||||
QFileInfo(modelPath).fileName()));
|
QFileInfo(modelPath).fileName()));
|
||||||
updateUIState();
|
updateUIState();
|
||||||
|
|
||||||
// 如果用户仍在录音状态(已切换 UI),启动采集
|
|
||||||
if (!isRecording_) {
|
|
||||||
startAudioCapture();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void STTTestPage::onModelLoadError(const QString& modelPath, const QString& error) {
|
void STTTestPage::onModelLoadError(const QString& modelPath, const QString& error) {
|
||||||
@ -206,8 +185,7 @@ void STTTestPage::startAudioCapture() {
|
|||||||
// 启动周期性推理定时器
|
// 启动周期性推理定时器
|
||||||
startInferenceTimer();
|
startInferenceTimer();
|
||||||
|
|
||||||
statusLabel_->setText(QString("录音中 | 模型: %1").arg(
|
statusLabel_->setText("录音中 | 模型已加载");
|
||||||
QFileInfo(currentModelPath_).fileName()));
|
|
||||||
updateUIState();
|
updateUIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,9 @@ class AudioCapture;
|
|||||||
class STTTestPage : public QWidget {
|
class STTTestPage : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit STTTestPage(ConfigManager* configManager, QWidget* parent = nullptr);
|
explicit STTTestPage(ConfigManager* configManager,
|
||||||
|
SenseVoiceEngine* sttEngine,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
~STTTestPage() override;
|
~STTTestPage() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -64,7 +66,6 @@ private:
|
|||||||
bool isInferencing_ = false;
|
bool isInferencing_ = false;
|
||||||
int audioSampleRate_ = 16000;
|
int audioSampleRate_ = 16000;
|
||||||
std::vector<float> audioBuffer_;
|
std::vector<float> audioBuffer_;
|
||||||
QString currentModelPath_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace impress
|
} // namespace impress
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user