尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Qt5.7.1项目里,不用QTextToSpeech,怎么用Windows自带语音合成(SAPI.SpVoice)搞定TTS?

Qt5.7.1项目里,不用QTextToSpeech,怎么用Windows自带语音合成(SAPI.SpVoice)搞定TTS? Qt5.7.1项目中调用Windows原生语音合成(SAPI.SpVoice)的实战指南在Qt5.8之前官方并未提供QTextToSpeech类来处理文本转语音(TTS)功能。对于仍在使用Qt5.7.1等旧版本的项目直接调用Windows平台的SAPI(语音应用程序接口)成为实现TTS功能的可靠选择。本文将深入探讨如何通过COM接口与SAPI.SpVoice交互在保持Qt项目架构的同时实现高质量的语音合成。1. 环境准备与基础概念在开始编码前需要确保开发环境满足以下条件Windows SDK确保已安装包含SAPI组件的Windows SDK版本Qt配置项目中已启用ActiveQt模块用于COM交互语音包检查通过控制面板→语音识别→文本到语音转换验证系统已安装所需语音引擎SAPI的核心接口ISpVoice通过COM技术暴露语音合成功能。在Qt中我们可以通过QAxObject封装COM调用其底层原理是// COM初始化流程伪代码 CoInitialize(NULL); // 初始化COM库 ISpVoice* pVoice nullptr; CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)pVoice); pVoice-Speak(LHello world, SPF_DEFAULT, NULL); pVoice-Release(); CoUninitialize();关键注册表项位于HKEY_CLASSES_ROOT\SAPI.SpVoiceCLSID为96749377-3391-11D2-9EE3-00C04F797396。这个GUID将在后续代码中用于创建语音对象实例。2. 核心实现步骤2.1 COM对象初始化封装创建TextToSpeech类管理语音合成生命周期class TextToSpeech : public QObject { Q_OBJECT public: explicit TextToSpeech(QObject *parent nullptr); ~TextToSpeech(); bool initialize(); bool speak(const QString text); void setRate(int rate); // -10到10 void setVolume(int volume); // 0-100 void setVoice(const QString voiceToken); private: QAxObject *m_voice nullptr; bool m_initialized false; };初始化实现需要特别注意COM线程模型bool TextToSpeech::initialize() { if(m_initialized) return true; m_voice new QAxObject(SAPI.SpVoice, this); if(m_voice-isNull()) { qWarning() Failed to create SAPI.SpVoice object; return false; } // 连接事件信号 QObject::connect(m_voice, SIGNAL(signal(QString, int, void*)), this, SLOT(handleVoiceEvent(QString, int, void*))); m_initialized true; return true; }2.2 语音参数配置实战语音引擎支持多种实时调整参数以下表格展示了关键参数范围及效果参数类型有效范围推荐值效果说明Rate-10~100负值减速正值加速Volume0~10080百分比音量控制Voice-系统依赖通过Token字符串指定实现参数设置的代码示例void TextToSpeech::setRate(int rate) { if(!m_initialized) return; m_voice-dynamicCall(SetRate(int), qBound(-10, rate, 10)); } void TextToSpeech::setVolume(int volume) { if(!m_initialized) return; m_voice-dynamicCall(SetVolume(int), qBound(0, volume, 100)); }2.3 多语音引擎支持现代Windows系统通常包含多个语音引擎可通过注册表枚举QStringList TextToSpeech::availableVoices() const { QStringList voices; // 32位系统注册表路径 const QString basePath HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens; QSettings registry(basePath, QSettings::NativeFormat); foreach (const QString child, registry.childGroups()) { registry.beginGroup(child); voices registry.value().toString(); registry.endGroup(); } return voices; }设置特定语音的典型实现bool TextToSpeech::setVoice(const QString voiceToken) { if(!m_initialized) return false; QAxObject token(SAPI.SpObjectToken); if(token.isNull()) return false; token.dynamicCall(SetId(QString), voiceToken); return m_voice-setProperty(Voice, token.asVariant()); }3. 高级功能实现3.1 异步处理与事件回调SAPI支持事件通知机制需要建立Qt信号槽与COM事件的连接// 在初始化后添加事件连接 m_voice-queryInterface(ISpVoice::IID, (void**)m_spVoice); if(m_spVoice) { m_spVoice-SetNotifyCallbackFunction(EventCallback, 0, 0); m_spVoice-SetInterest(SPFEI_ALL_EVENTS, SPFEI_ALL_EVENTS); } // 回调函数处理 void TextToSpeech::handleVoiceEvent(QString name, int argc, void* argv) { if(name Word) { // 处理单词边界事件 QString word QString::fromWCharArray((wchar_t*)argv); emit wordSpoken(word); } }3.2 语音输出到内存除了默认的音频输出设备SAPI支持将语音流输出到内存QByteArray TextToSpeech::speakToBuffer(const QString text) { if(!m_initialized) return QByteArray(); QAxObject stream(SAPI.SpMemoryStream); if(stream.isNull()) return QByteArray(); m_voice-dynamicCall(SetOutput(IUnknown*), stream.asVariant()); m_voice-dynamicCall(Speak(QString, int), text, SPF_PURGEBEFORESPEAK); // 获取音频数据 QVariant var stream.property(Format); return stream.dynamicCall(GetData()).toByteArray(); }4. Qt项目集成最佳实践4.1 线程安全方案由于COM对象通常有线程亲和性要求推荐采用以下架构classDiagram class TTSWorker { QAxObject* voice initialize() speak(text) setParameters() } class TTSController { QThread workerThread TTSWorker* worker startSpeaking(text) } TTSController -- TTSWorker: 跨线程调用具体实现时使用Qt的信号槽跨线程通信// Worker对象 class TTSWorker : public QObject { Q_OBJECT public slots: void speak(const QString text) { if(!m_voice) return; m_voice-dynamicCall(Speak(QString, int), text, SPF_ASYNC); } }; // 控制器 class TTSController : public QObject { Q_OBJECT public: TTSController(QObject *parent nullptr) { m_worker.moveToThread(m_thread); connect(this, TTSController::requestSpeak, m_worker, TTSWorker::speak); m_thread.start(); } signals: void requestSpeak(const QString text); private: QThread m_thread; TTSWorker m_worker; };4.2 错误处理与恢复完善的错误处理机制应包括COM调用返回值检查HRESULT错误码解析对象状态验证bool TextToSpeech::safeCall(const QString method, const QVariantList args) { QVariant result m_voice-dynamicCall(method, args); if(result.isNull()) { IErrorInfo *errorInfo nullptr; if(SUCCEEDED(GetErrorInfo(0, errorInfo)) errorInfo) { BSTR desc; errorInfo-GetDescription(desc); qWarning() COM Error: QString::fromWCharArray(desc); errorInfo-Release(); } return false; } return true; }在实际项目中我发现微软语音引擎对中文标点的处理有时会出现异常停顿。通过预处理文本将连续标点替换为单个标点空格可以显著改善自然度QString TextToSpeech::preprocessText(const QString input) { QString output; QChar lastChar; foreach (const QChar c, input) { if(c.isPunct() lastChar.isPunct()) { if(!output.endsWith( )) output ; } output c; lastChar c; } return output; }
返回列表