QTextToSpeech的使用——Qt

2024-03-15 14:04
文章标签 使用 qt qtexttospeech

本文主要是介绍QTextToSpeech的使用——Qt,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

之前随便看了几眼QTextToSpeech的帮助就封装使用了,达到了效果就没再管了,最近需要在上面加功能(变换语速),就写了个小Demo后,发现不对劲了。

出现的问题

场景

写了个队列添加到语音播放子线程中,在run循环查询tts引擎状态,来依次播放。代码如下:

void AudioThread::run()
{while (m_iRunning){if(m_audioQueue.size() != 0&&m_pTextToSpeech->state() == QTextToSpeech::Ready){SingleAudio aud = m_audioQueue.dequeue();double rate = 0.0; //-1.0 ~ 1.0if(aud.iType == 1){rate = 0.4;}m_pTextToSpeech->setRate(rate);m_pTextToSpeech->say(aud.strContent);}msleep(200);}
}

 单看代码没有毛病,只是多条文本一起投入,就会出现tts的状态一直为Ready,一直执行say,没合成一条语音,都执行完了,导致没有播放一条语音。

分析及措施

出现这问题,怀疑是我用错了,所以我又仔细看了下Qt帮助文档,看自己的使用是否有问题,看完后,确实有疏漏。

void QTextToSpeech::say(const QString &text)
Start synthesizing the text. This function will start the asynchronous reading of the text. The current state is available using the state property. Once the synthesis is done, a stateChanged() signal with the Ready state is emitted.

大致说这个行为是异步的,属性state记录当前状态,当完成后会发出stateChanged信号(Ready)。

 状态有以上四种,如果tts引擎还在合成,比如文本过于长,所需时长大于等待时长,状态还是没有改变即Ready状态,这样确实会出现这种问题。

这种问题是可以避免的, 具体如下:

方法一 

通过信号stateChanged来控制播放,确实会避免这种问题。代码如下:

void AudioController::addAudioQueue(const QQueue<SingleAudio> &audioQueue)
{m_audioQueue.append(audioQueue);if(m_pTextToSpeech->state() == QTextToSpeech::Ready){playAudio();}
}void AudioController::onStateChanged(QTextToSpeech::State state)
{if(state == QTextToSpeech::Ready){playAudio();}
}void AudioController::playAudio()
{if(m_audioQueue.size() == 0)return;SingleAudio aud = m_audioQueue.dequeue();double rate = 0.0; //-1.0 ~ 1.0if(aud.iType == 1){rate = 0.4;}m_pTextToSpeech->setRate(rate);m_pTextToSpeech->say(aud.strContent);
}

 以上在主线程中执行的,没有任何问题。

后面我试图将QTextToSpeech对象移入子线程,想让它在子线程中执行所有操作,失败了:移入后,感觉整个停住了,状态也不会变化,感觉它只能在主线程中使用,后面的测试也给我这样的感觉。

void init()
{m_pTextToSpeech = new QTextToSpeech;m_pTextToSpeech->moveToThread(&m_ttsThread);connect(m_pTextToSpeech,&QTextToSpeech::stateChanged,this,&AudioController::onStateChanged);m_ttsThread.start();}void AudioController::playAudio()
{if(m_audioQueue.size() == 0)return;SingleAudio aud = m_audioQueue.dequeue();double rate = 0.0; //-1.0 ~ 1.0if(aud.iType == 1){rate = 0.4;}QMetaObject::invokeMethod(m_pTextToSpeech,"setRate",Qt::AutoConnection,Q_ARG(double,rate));QMetaObject::invokeMethod(m_pTextToSpeech,"say",Qt::AutoConnection,Q_ARG(QString,aud.strContent));}

 方法二

由于之前语音模块的代码是在子线程执行(QThread的run中执行),这种结构是变不了的,信号控制的方式又无法嵌入,所以只能在原基础上更改。

为保证状态更改过(Ready -> Speaking ->Ready),所以添加了个标识符进行标记,代码如下:

void AudioThread::run()
{bool isStateChanged =true;while (m_iRunning){if(m_pTextToSpeech->state() == QTextToSpeech::Ready){if(m_audioQueue.size() != 0&&isStateChanged){SingleAudio aud = m_audioQueue.dequeue();double rate = 0.0; //-1.0 ~ 1.0if(aud.iType == 1){rate = 0.4;}m_pTextToSpeech->setRate(rate);m_pTextToSpeech->say(aud.strContent);isStateChanged = false;}}else{isStateChanged = true;}msleep(200);}}

此代码在安卓平台下是正常的,然而在Windows下是不能正常运行的:QTextToSpeech状态是不变的,类似上面在子线程中运行卡住,但是如果在主线程的其他地方先say一下,然后子线程中就正常了。我看了一点点源码,不同平台调用的是不同的引擎,Windows封装的代码中也没看到线程之类的东西,异步的实现是回调,在子线程中会影响回调或者阻碍语音的合成,这个其中的道理我也搞不清,只能根据代码运行后的效果进行猜测:跟线程有关系。

因为猜测和线程有关,所以就换了调用QTextToSpeech方法的方式(如下),更换为此种方式调用后,Windows平台和安卓平台都可以正常运行了。

                QMetaObject::invokeMethod(m_pTextToSpeech,"setRate",Qt::QueuedConnection,Q_ARG(double,rate));QMetaObject::invokeMethod(m_pTextToSpeech,"say",Qt::QueuedConnection,Q_ARG(QString,aud.strContent));

 

使用

完整的使用的代码如下:

#include "AudioThread.h"
#include <QTimer>
#include <QDebug>AudioThread::AudioThread(QObject *parent):QThread{parent},m_pTextToSpeech(new QTextToSpeech(this)),m_iRunning(true),m_bPause(false)
{//0.5秒后再初始化tts(tts引擎启动时异步的)QTimer::singleShot(500,this,[=](){m_pTextToSpeech->setRate(-0.1);const QVector<QLocale>& locales = m_pTextToSpeech->availableLocales();for(int i = 0; i < locales.count(); i++){if(locales.at(i).language() == QLocale::Chinese){m_pTextToSpeech->setLocale(locales.at(i));break;}}});}AudioThread::~AudioThread()
{
}void AudioThread::addAudioQueue(const QQueue<SingleAudio> &audioQueue)
{m_audioQueue.append(audioQueue);
}void AudioThread::addSingleAudio(const SingleAudio& audio)
{m_audioQueue.enqueue(audio);
}void AudioThread::stop()
{m_iRunning = false;m_audioQueue.clear();quit();wait();
}void AudioThread::run()
{while (m_iRunning){static bool isStateChanged =true;if(m_pTextToSpeech->state() == QTextToSpeech::Ready){if(m_audioQueue.size() != 0&&isStateChanged){SingleAudio aud = m_audioQueue.dequeue();double rate = 0.0; //-1.0 ~ 1.0if(aud.iType == 1){rate = 0.4;}QMetaObject::invokeMethod(m_pTextToSpeech,"setRate",Qt::QueuedConnection,Q_ARG(double,rate));QMetaObject::invokeMethod(m_pTextToSpeech,"say",Qt::QueuedConnection,Q_ARG(QString,aud.strContent));isStateChanged = false;}}else{isStateChanged = true;}msleep(200);}
}

结束语

很多时候发现只有帮助文档是不够的,源码才是真理。

这篇关于QTextToSpeech的使用——Qt的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/812220

相关文章

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

prometheus如何使用pushgateway监控网路丢包

《prometheus如何使用pushgateway监控网路丢包》:本文主要介绍prometheus如何使用pushgateway监控网路丢包问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录监控网路丢包脚本数据图表总结监控网路丢包脚本[root@gtcq-gt-monitor-prome

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

Ubuntu如何分配​​未使用的空间

《Ubuntu如何分配​​未使用的空间》Ubuntu磁盘空间不足,实际未分配空间8.2G因LVM卷组名称格式差异(双破折号误写)导致无法扩展,确认正确卷组名后,使用lvextend和resize2fs... 目录1:原因2:操作3:报错5:解决问题:确认卷组名称​6:再次操作7:验证扩展是否成功8:问题已解