From 8612cbfae399f8b97993bc0dc8a3cbd1010d4869 Mon Sep 17 00:00:00 2001 From: Misaki Date: Wed, 31 Dec 2025 22:53:39 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BC=98=E5=8C=96=E4=BA=86=E9=9F=B3?= =?UTF-8?q?=E9=A2=91=E8=BE=93=E5=87=BA=E8=BE=93=E5=87=BA=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E7=9A=84=E5=86=85=E5=AE=B9=202.=20=E6=96=B0=E5=A2=9E=E4=BA=86?= =?UTF-8?q?=E5=AF=B9=E6=A8=A1=E5=9E=8B=E5=8A=A8=E4=BD=9C=E7=BB=84=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=9A=84=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E5=90=8E=E7=BB=AD=E6=A8=A1=E5=9E=8B=E7=9A=84=E5=8A=A8=E4=BD=9C?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Live2D/Src/LAppLive2D/Inc/LAppModel.hpp | 27 ++++- .../Live2D/Src/LAppLive2D/Src/LAppDefine.cpp | 2 +- .../Src/LAppLive2D/Src/LAppLive2DManager.cpp | 13 ++- .../Live2D/Src/LAppLive2D/Src/LAppModel.cpp | 102 +++++++++++++++++- README.md | 2 +- src/AudioHandle/Inc/AudioInput.h | 9 +- src/AudioHandle/Inc/AudioOutput.h | 6 +- src/AudioHandle/Src/AudioInput.cpp | 84 ++++++++++----- src/AudioHandle/Src/AudioOutput.cpp | 14 ++- src/DAO/Inc/NetWorkDO.h | 10 +- src/Render/TextRender/Inc/TextRenderer.h | 10 +- src/Setting/Src/AudioPage.cpp | 3 +- 12 files changed, 221 insertions(+), 61 deletions(-) diff --git a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp index 597de01..6e4b121 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp @@ -14,7 +14,9 @@ #include #include "LAppWavFileHandler.hpp" - +#include +#include +#include /** * @brief ユーザーが実際に使用するモデルの実装クラス
* モデル生成、機能コンポーネント生成、更新処理とレンダリングの呼び出しを行う。 @@ -34,19 +36,35 @@ public: */ virtual ~LAppModel(); + struct MotionInfo { + std::string FileName; // 动作的文件名 + int SequenceId; // 组内的播放序列号 + }; + + /** + * 获取按组分类的动作映射表 + * Key: 组名 (如 "Idle"), Value: 动作信息列表 + */ + std::map> GetMotionMap(); + + /** + * 在控制台打印当前模型所有的动作组和序列信息 + */ + void DumpMotionMap(); + /** * @brief 获得Idle动画总数量 * @author Misaki * @return int */ - int getIdleMotionCount(); + [[nodiscard]] int getIdleMotionCount() const; /** * @brief 获得TapBody动画总数量 * @author Misaki * @return int */ - int getTapBodyMotionCount(); + [[nodiscard]] int getTapBodyMotionCount() const; /** * @brief 获取 Live2D 模型的 Canvas 宽度像素 (在 Live2D 坐标系下) @@ -198,6 +216,8 @@ private: */ [[nodiscard]] bool IsPointOnDrawable(Csm::csmFloat32 x, Csm::csmFloat32 y); + // 辅助函数: 从完整路径提取纯文件名 + std::string ExtractFileName(const std::string& fullPath); private: /** * @brief model3.jsonからモデルを生成する。
@@ -266,6 +286,5 @@ private: Live2D::Cubism::Framework::csmFloat32 alpha = 0.8f; // 滤波系数,范围在0到1之间,值越小,平滑效果越强 Live2D::Cubism::Framework::csmFloat32 filteredValue = 0.0f; // 滤波后的值 - Csm::Rendering::CubismOffscreenSurface_OpenGLES2 _renderBuffer; ///< フレームバッファ以外の描画先 }; diff --git a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppDefine.cpp b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppDefine.cpp index 110827b..4716952 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppDefine.cpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppDefine.cpp @@ -38,7 +38,7 @@ namespace LAppDefine { // const csmChar* PowerImageName = "close.png"; // モデル定義------------------------------------------ - // 外部定義ファイル(json)と合わせる + // 外部定義ファイル(json)と合わせる [要注意:部分模型可能缺失下面的某个字段或者全部缺失] const csmChar* MotionGroupIdle = "Idle"; // アイドリング const csmChar* MotionGroupTapBody = "TapBody"; // 体をタップしたとき diff --git a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp index e5c0b79..26fb30a 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp @@ -72,6 +72,9 @@ LAppLive2DManager::LAppLive2DManager() // Resources/Haru/ Haru.model3.json LoadModelFromPath("Resources/Live2DModels/KITU17/", "KITU17.model3.json"); // 默认加载的模型 //ChangeScene(_sceneIndex); + if (DebugLogEnable) { + _models[0]->DumpMotionMap(); + } } LAppLive2DManager::~LAppLive2DManager() @@ -217,11 +220,10 @@ void LAppLive2DManager::OnTap(csmFloat32 x, csmFloat32 y) void LAppLive2DManager::OnUpdate() const { - int width, height; //glfwGetWindowSize(LAppDelegate::GetInstance()->GetWindow(), &width, &height); - width = LAppDelegate::GetInstance()->GetWindow()->width(); - height = LAppDelegate::GetInstance()->GetWindow()->height(); + int width = LAppDelegate::GetInstance()->GetWindow()->width(); + int height = LAppDelegate::GetInstance()->GetWindow()->height(); csmUint32 modelCount = _models.GetSize(); for (csmUint32 i = 0; i < modelCount; ++i) @@ -263,7 +265,6 @@ void LAppLive2DManager::OnUpdate() const } } #include - void LAppLive2DManager::ModelSizeChange(const int Sacle = 15) { // 加载完后根据模型大小来重新设置当前窗口大小 @@ -346,6 +347,10 @@ void LAppLive2DManager::MountLoadedModel(LAppModel* model) // 加入新模型 _models.PushBack(model); + if (DebugLogEnable) { + _models[0]->DumpMotionMap(); // 打印模型动作列表 + } + // 加载完后根据模型大小来重新设置当前窗口大小 const int width = static_cast(_models[0]->GetModel()->GetCanvasWidthPixel() / 15.0); const int height = static_cast(_models[0]->GetModel()->GetCanvasHeightPixel() / 15.0); diff --git a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp index e72c16c..8dc4b5f 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppModel.cpp @@ -660,6 +660,102 @@ CubismMotionQueueEntryHandle LAppModel::StartRandomMotion(const csmChar* group, return StartMotion(group, no, priority, onFinishedMotionHandler); } +std::map> LAppModel::GetMotionMap() +{ + std::map> motionMap; + if (_modelSetting == nullptr) + { + return motionMap; + } + // 获取组总数 + const int groupCount = _modelSetting->GetMotionGroupCount(); + for (int i = 0; i < groupCount; i++) + { + // 获取组名 (从 const char* 转 std::string) + const char* groupNameChar = _modelSetting->GetMotionGroupName(i); + std::string groupName(groupNameChar); + // 获取该组动作数 + const int motionCount = _modelSetting->GetMotionCount(groupNameChar); + + std::vector motionList; + for (int j = 0; j < motionCount; j++) + { + MotionInfo info; + info.SequenceId = j; + // 获取路径并处理 + const char* filePath = _modelSetting->GetMotionFileName(groupNameChar, j); + if (filePath) + { + info.FileName = ExtractFileName(std::string(filePath)); + } + motionList.push_back(info); + } + motionMap[groupName] = motionList; + } + return motionMap; +} + +void LAppModel::DumpMotionMap() +{ + if (_modelSetting == nullptr) + { + LAppPal::PrintLogLn("[Live2D Debug] Cannot dump MotionMap: Model assets not loaded yet."); + return; + } + // 获取映射表 + const std::map> motionMap = GetMotionMap(); + + if (motionMap.empty()) + { + // 模型动作栏目为空,大概率是模型本身没带动作 + printf("[Live2D Debug] MotionMap is empty. Make sure the model is loaded correctly.\n"); + return; + } + + printf("\n================ [Live2D Motion Dump] =================\n"); + + // 遍历 Map (组) + for (auto & it : motionMap) + { + const std::string& groupName = it.first; + const std::vector& motions = it.second; + + printf("Group: [%s] (%zu motions)\n", groupName.c_str(), motions.size()); + + // 遍历 Vector (组内动作) + for (const auto& info : motions) + { + // 打印 序列号 和 处理后的文件名 + printf(" ├── ID: %d | Name: %s\n", + info.SequenceId, + info.FileName.c_str()); + } + printf(" └── (End of %s)\n", groupName.c_str()); + } + printf("=======================================================\n\n"); +} + +// 辅助函数:处理 csmString 路径截取 +std::string LAppModel::ExtractFileName(const std::string& fullPath) +{ + // 提取带后缀的文件名 (例如从 "motions/special_03.motion3.json" 变为 "special_03.motion3.json") + const size_t lastSlash = fullPath.find_last_of("/\\"); + std::string fileName = (lastSlash == std::string::npos) ? fullPath : fullPath.substr(lastSlash + 1); + // 循环去掉后缀,直到名字中不再包含 ".json" 或 ".motion3" + // Live2D 动作文件通常以 .motion3.json 结尾 + const std::string extensions[] = { ".json", ".motion3" }; + + for (const std::string& ext : extensions) + { + const size_t pos = fileName.find(ext); + if (pos != std::string::npos) + { + fileName = fileName.substr(0, pos); + } + } + return fileName; +} + /** 讲一下两种动画的不同 * MotionGroupIdle: @@ -678,13 +774,11 @@ CubismMotionQueueEntryHandle LAppModel::StartRandomMotion(const csmChar* group, * 下面的两个函数分别就是获取这两种动作的数量的,不同的动作对应着一个序号,播放的序号不可以超过下面函数返回的最大值 * 要注意的是部分模型可能没有动画 */ -int LAppModel::getIdleMotionCount() -{ +int LAppModel::getIdleMotionCount() const { return _modelSetting->GetMotionCount(MotionGroupIdle); } -int LAppModel::getTapBodyMotionCount() -{ +int LAppModel::getTapBodyMotionCount() const { return _modelSetting->GetMotionCount(MotionGroupTapBody); } diff --git a/README.md b/README.md index 55bf4e4..edeb7bc 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ make 关于授权
本项目采用多重授权结构: -1. 原创代码部分:MIT License +1. 原创代码部分:MIT License
src/*.* 2. 依赖库: diff --git a/src/AudioHandle/Inc/AudioInput.h b/src/AudioHandle/Inc/AudioInput.h index 752ca04..f8ec6a1 100644 --- a/src/AudioHandle/Inc/AudioInput.h +++ b/src/AudioHandle/Inc/AudioInput.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include /** * @brief 录音模块 @@ -21,15 +23,15 @@ class AudioInput : public QObject { Q_OBJECT - +Q_DISABLE_COPY(AudioInput) // 禁用拷贝 private: /** * @brief 构造函数 * @param parent */ explicit AudioInput(QObject *parent = nullptr); - static AudioInput* instance; - + static QScopedPointer instance; + static QMutex mutex; public: /** * @brief 获取实例 @@ -145,5 +147,6 @@ private: std::vector m_rmsValues; /// RMS值vector qreal m_silenceThreshold = 1200; /// 静音阈值 int m_silenceDuration = 1500; /// 静音持续时间 + qreal m_smoothRms = 0.0; /// 平滑RMS值(用于防止低频杂波突然打断静音检测) }; diff --git a/src/AudioHandle/Inc/AudioOutput.h b/src/AudioHandle/Inc/AudioOutput.h index 8e1ec11..1997bd3 100644 --- a/src/AudioHandle/Inc/AudioOutput.h +++ b/src/AudioHandle/Inc/AudioOutput.h @@ -9,6 +9,8 @@ #include // 音频输出组件, 用于原始数据播放 #include #include +#include +#include /** * @brief 音频播放模块 @@ -23,6 +25,7 @@ class AudioOutput : public QObject { Q_OBJECT +Q_DISABLE_COPY(AudioOutput) // 禁用拷贝 private: /** * 构造函数私有化 @@ -30,7 +33,8 @@ private: */ explicit AudioOutput(QObject *parent = nullptr); // 并不将本模块挂在对象树当中,因为本模块为单例类,内存自行管理 - static AudioOutput *instance; // 单例类 + static QScopedPointer instance; // 单例类 + static QMutex mutex; public: static AudioOutput *getInstance(); diff --git a/src/AudioHandle/Src/AudioInput.cpp b/src/AudioHandle/Src/AudioInput.cpp index 773951e..84f016b 100644 --- a/src/AudioHandle/Src/AudioInput.cpp +++ b/src/AudioHandle/Src/AudioInput.cpp @@ -7,14 +7,18 @@ #include #include // 用于处理字节序 -AudioInput *AudioInput::instance = nullptr; +QScopedPointer AudioInput::instance; +QMutex AudioInput::mutex; AudioInput *AudioInput::getInstance() { // 懒汉式 依旧单线程无需加锁 - if (instance == nullptr) { - instance = new AudioInput(); + if (instance.isNull()) { + QMutexLocker locker(&mutex); + if (instance.isNull()) { + instance.reset(new AudioInput); + } } - return instance; + return instance.data(); } AudioInput::AudioInput(QObject *parent) : QObject(parent) @@ -79,7 +83,7 @@ void AudioInput::startAudio() // 调大缓冲区以避免溢出 m_audioSource->setBufferSize(128000); - // start() 返回一个 QIODevice,我们可以从中读取数据 + // start() 返回一个 QIODevice,可以从中读取数据 m_ioDevice = m_audioSource->start(); if (m_ioDevice) { @@ -107,8 +111,8 @@ void AudioInput::stopAudio() if (!m_rawPCMData.isEmpty()) { wavData = generateWavHeader(m_rawPCMData.size()); wavData.append(m_rawPCMData); - - // 如果需要保存文件 +#ifdef QT_DEBUG + // 如果需要保存文件(Debug下启用) if (!m_outputFilePath.isEmpty()) { QFile file(m_outputFilePath); if (file.open(QIODevice::WriteOnly)) { @@ -117,10 +121,9 @@ void AudioInput::stopAudio() qDebug() << "Saved WAV to:" << m_outputFilePath; } } - +#endif m_rawPCMData.clear(); } - isAutoRecording = false; isAutoThreshold = false; @@ -138,18 +141,28 @@ void AudioInput::onReadyRead() QByteArray data = m_ioDevice->readAll(); if (data.isEmpty()) return; - // 1. 保存原始 PCM 数据 + // 保存原始 PCM 数据 m_rawPCMData.append(data); - // 2. 计算 RMS (仅用于分析,取最后一小段或者整体计算,这里计算当前块的RMS) - m_rmsValue = calculateRMS(data); + // 计算 RMS (仅用于分析,计算当前块的RMS) + const qreal currentRms = calculateRMS(data); + m_rmsValue = currentRms; + // 计算平滑RMS (用于防止低频杂波突然打断静音检测) + constexpr qreal alpha = 0.3; // 70% 历史权重, 30% 当前权重 + if (qFuzzyIsNull(m_smoothRms)) { + // 如果是第一帧数据,直接赋值,避免从0开始慢慢爬升 + m_smoothRms = currentRms; + } else { + // 新值 = (旧值 * (1 - alpha)) + (当前值 * alpha) + m_smoothRms = (m_smoothRms * (1.0 - alpha)) + (currentRms * alpha); + } - // 3. 自动停止逻辑 (VAD) + // 自动停止逻辑 (VAD) if (isAutoRecording) { // 输出 RMS 用于调试 - // qDebug() << "RMS:" << m_rmsValue; + qDebug() << "Raw:" << currentRms << " Smooth:" << m_smoothRms; - if (m_rmsValue < m_silenceThreshold) { + if (m_smoothRms < m_silenceThreshold) { // 静音状态 if (!m_silenceTimer->isActive()) { m_silenceTimer->start(m_silenceDuration); @@ -161,10 +174,10 @@ void AudioInput::onReadyRead() } } - // 4. 自动阈值计算逻辑 + // 自动阈值计算逻辑 if (isAutoThreshold) { - m_rmsValues.push_back(m_rmsValue); - emit rmsRealValue(m_rmsValue); + m_rmsValues.push_back(m_smoothRms); + emit rmsRealValue(m_smoothRms); } } @@ -172,7 +185,7 @@ qreal AudioInput::calculateRMS(const QByteArray& buffer) { if (buffer.isEmpty()) return 0; - // 假设是 Int16 格式 (16位深) + // 设定为 Int16 格式 (16位深) // 如果是 Stereo,数据排列是 L R L R... // 简单的 RMS 计算可以将所有通道数据视为一个长序列 @@ -242,7 +255,7 @@ void AudioInput::startAutoStopAudio(const qreal silenceThreshold, const int sile } // 启动阈值计算 -void AudioInput::startAutoThresholdClu(int Duration) +void AudioInput::startAutoThresholdClu(const int Duration) { isAutoThreshold = true; m_rmsValues.clear(); @@ -250,19 +263,36 @@ void AudioInput::startAutoThresholdClu(int Duration) m_thresholdTimer->start(Duration); } +/** + * 2025.12.30重构 Misaki + * 从均值阈值计算的基础上增加了N倍标准差 + * 即阈值 = 均值 + N * 标准差(N取3) + */ void AudioInput::thresholdTimeout() { isAutoThreshold = false; stopAudio(); // 内部会处理 stop - if (!m_rmsValues.empty()) { - const double sum = std::accumulate(m_rmsValues.begin(), m_rmsValues.end(), 0.0); - const double avg = sum / m_rmsValues.size(); - m_silenceThreshold = avg + 500.0; // 这里的 500 是经验值,可以根据需要调整 - emit thresholdCalculated(m_silenceThreshold); - } else { + if (m_rmsValues.empty()) { emit thresholdCalculated(0); + return; } + // 计算均值 + const double mean = std::accumulate(m_rmsValues.begin(), m_rmsValues.end(), 0.0) / m_rmsValues.size(); + // 计算标准差 + const double sq_sum = std::inner_product(m_rmsValues.begin(), m_rmsValues.end(), m_rmsValues.begin(), 0.0); + double variance = (sq_sum / m_rmsValues.size()) - (mean * mean); + // 防止浮点误差导致负数 + if (variance < 0) variance = 0; + const double stdDev = std::sqrt(variance); + // 阈值 = 均值 + 2 * 标准差 + const double bestThreshold = mean + 3 * stdDev; + m_silenceThreshold = std::max(bestThreshold, 150.0); + m_silenceThreshold = std::min(m_silenceThreshold, 30000.0); + qDebug() << "Auto Threshold Calc -> Mean:" << mean + << " StdDev:" << stdDev + << " Result:" << m_silenceThreshold; + emit thresholdCalculated(m_silenceThreshold); } QByteArray AudioInput::generateWavHeader(const quint32 dataSize) const { @@ -285,7 +315,7 @@ QByteArray AudioInput::generateWavHeader(const quint32 dataSize) const { header.numChannels = static_cast(m_format.channelCount()); header.sampleRate = static_cast(m_format.sampleRate()); - header.bitsPerSample = 16; // 我们强制使用了 Int16 + header.bitsPerSample = 16; // 强制使用了 Int16 header.byteRate = header.sampleRate * header.numChannels * (header.bitsPerSample / 8); header.blockAlign = header.numChannels * (header.bitsPerSample / 8); diff --git a/src/AudioHandle/Src/AudioOutput.cpp b/src/AudioHandle/Src/AudioOutput.cpp index 9075524..4bd7036 100644 --- a/src/AudioHandle/Src/AudioOutput.cpp +++ b/src/AudioHandle/Src/AudioOutput.cpp @@ -6,15 +6,20 @@ #include #include -AudioOutput *AudioOutput::instance = nullptr; +QScopedPointer AudioOutput::instance; // 使用QScopedPointer去管理单例,自动析构 +QMutex AudioOutput::mutex; AudioOutput *AudioOutput::getInstance() { // 懒汉式(单线程播放,无需考虑加锁) - if (instance == nullptr) { - instance = new AudioOutput(); + if (instance.isNull()) { // 若未访问 + QMutexLocker locker(&mutex); + if (instance.isNull()) { + // 使用reset初始化 + instance.reset(new AudioOutput()); + } } - return instance; + return instance.data(); // 返回单例实例 } AudioOutput::AudioOutput(QObject *parent) : QObject(parent), mediaPlayer(nullptr), audioOutput(nullptr), audioSink(nullptr), audioBuffer(nullptr) @@ -39,7 +44,6 @@ AudioOutput::AudioOutput(QObject *parent) : QObject(parent), mediaPlayer(nullptr format.setSampleFormat(QAudioFormat::Int16); // 采样格式 audioSink = new QAudioSink(QMediaDevices::defaultAudioOutput(), format, this); audioBuffer = new QBuffer(this); - } diff --git a/src/DAO/Inc/NetWorkDO.h b/src/DAO/Inc/NetWorkDO.h index 5db4704..4dfbcbb 100644 --- a/src/DAO/Inc/NetWorkDO.h +++ b/src/DAO/Inc/NetWorkDO.h @@ -58,8 +58,8 @@ struct ControlDataPacket { */ class NetworkDO final : public QObject { - Q_OBJECT - Q_DISABLE_COPY(NetworkDO) // 禁用拷贝 +Q_OBJECT +Q_DISABLE_COPY(NetworkDO) // 禁用拷贝 public: // 单例访问点 @@ -80,9 +80,9 @@ public: signals: // 业务接收信号 - void audioPacketReceived(const AudioDataPacket& packet); - void controlPacketReceived(const ControlDataPacket& packet); - void errorOccurred(const QString& errorMsg); + void audioPacketReceived(const AudioDataPacket& packet); // 音频数据准备完成信号 + void controlPacketReceived(const ControlDataPacket& packet); // 控制数据准备完成信号 + void errorOccurred(const QString& errorMsg); // 错误信号 public slots: // 接收底层 JSON 数据 diff --git a/src/Render/TextRender/Inc/TextRenderer.h b/src/Render/TextRender/Inc/TextRenderer.h index 3703cab..08c6e3e 100644 --- a/src/Render/TextRender/Inc/TextRenderer.h +++ b/src/Render/TextRender/Inc/TextRenderer.h @@ -59,11 +59,11 @@ public: void setGlobalFont(const QFont &newFont); /** - * 参数建议值: - 效果类型 gravity dampFactor holdDuration - 柔和下落 600.0f 0.85f 1.0f - 快速坠落 1200.0f 0.6f 0.3f - 弹性效果 900.0f 0.75f 0.8f + * 参数建议值:
+ 效果类型 gravity dampFactor holdDuration
+ 柔和下落 600.0f 0.85f 1.0f
+ 快速坠落 1200.0f 0.6f 0.3f
+ 弹性效果 900.0f 0.75f 0.8f
真实物理模拟 980.0f 0.82f 0.5f */ void setHoldDuration(const float seconds) { defaultHoldDuration = seconds; } diff --git a/src/Setting/Src/AudioPage.cpp b/src/Setting/Src/AudioPage.cpp index 4303f24..5328b25 100644 --- a/src/Setting/Src/AudioPage.cpp +++ b/src/Setting/Src/AudioPage.cpp @@ -83,7 +83,8 @@ AudioPage::AudioPage(QWidget* parent) ElaScrollPageArea* audioInputProgressBarArea = new ElaScrollPageArea(this); QHBoxLayout* audioInputProgressBarLayout = new QHBoxLayout(audioInputProgressBarArea); - ElaText* audioInputProgressBarText = new ElaText("录音阈值", this); + ElaText* audioInputProgressBarText = new ElaText("静音检测阈值", this); + audioInputProgressBarText->setToolTip("测试当前环境的静音阈值,用于对话中的静音检测"); audioInputProgressBarText->setTextPixelSize(15); audioInputProgressBarLayout->addWidget(audioInputProgressBarText); audioInputProgressBarLayout->addWidget(audioInputProgressBar, 1);