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);}
}

结束语

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/540460.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins cron定时构建触发器

from&#xff1a; https://www.jenkins.io/doc/book/pipeline/syntax/#cron-syntax 以下内容为根据Jenkins官方文档cron表达式部分翻译过来&#xff0c;使用机翻加个人理解补充内容&#xff0c;包括举例。 目录 介绍举例&#xff1a;设置方法方法一&#xff1a;方法二&#xf…

UG NX二次开发(C#)-单选对话框UF_UI_select_with_single_dialog的使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、UF_UI_select_with_single_dialog函数3、实现代码3.1 利用委托创建一个方法3.2 直接调用1、前言 对于单选对话框,采用C++/C写的时候比较容易,也在帮助文档中有示例,但是对于C#开发采…

C# StartsWith 字符串的实例方法

在 C# 中&#xff0c;StartsWith 是一个字符串的实例方法&#xff0c;用于检查字符串是否以指定的前缀开头。它返回一个布尔值&#xff0c;指示字符串是否以指定的前缀开始。 以下是 StartsWith 方法的详细解释和示例说明&#xff1a; public bool StartsWith(string value);…

EtherCAT运动控制器在ROS上的应用(下)

本篇文章我们主要介绍正运动技术动态链接库环境配置以及EtherCAT运动控制器在ROS下的单轴运动。 在正式学习之前&#xff0c;我们先了解一下正运动技术的EtherCAT运动控制器ZMC432和ZMC408CE。这两款产品分别是32轴&#xff0c;8轴EtherCAT运动控制器。 ZMC432功能简介 ZMC4…

List(CS61B学习记录)

问题引入 上图中&#xff0c;赋给b海象的weight会改变a海象的weight&#xff0c;但x的赋值又不会改变y的赋值 Bits 要解释上图的问题&#xff0c;我们应该从Java的底层入手 相同的二进制编码&#xff0c;却因为数据类型不同&#xff0c;输出不同的值 变量的声明 基本类型…

tcp/ip协议2实现的插图,数据结构8 (30 - 32章)

(201) 201 三十0 中断优先级补充 (202) 202 三十1 TCP的用户需求 函tcp_usrreq一 (203) 203 三十2 TCP的用户需求 函tcp_usrreq二 (204) 204 三十3 TCP的用户需求 函tcp_usrreq三 (205) 205 三十4 TCP的用户需求 函tcp_usrreq四 (206) 206 三十5 TCP的用户需求 函tcp_usrreq五 …

国创证券策略:股指预计维持震荡格局 关注汽车、通信设备等板块

国创证券指出&#xff0c;近期两市指数持续反弹创新高&#xff0c;但沪指现已率先出现滞涨状况&#xff0c;一起均已进入阻力重压区。不过当时技术形状上坚持较好&#xff0c;可持续做多&#xff0c;一旦跌破重要支撑如沪指的3030点&#xff0c;则需降仓防卫&#xff0c;防止指…

拦截器和过滤器(原理区别)

目录 一、拦截器 拦截器是什么 拦截器的使用 拦截器的实现 导入依赖 实现HandlerInterceptor接口 注册拦截器 拦截器的生命周期 拦截器的执行顺序 拦截器的生命周期 多个拦截器的执行流程 拦截器的实际使用 拦截器实现日志记录 实现接口幂等性校验 拦截器的性能…

Gitee配置SSH登录

一、背景 新入手的电脑&#xff0c;需要对Gitee上存放的项目进行更改上传&#xff0c;发现上传不了需要登录&#xff0c;便采用SSH密钥进行登录&#xff0c;防止远程管理工程中的信息泄露 二、前提 电脑已下载Git Bash工具&#xff0c;在项目下点击鼠标右键&#xff0c;进入…

案例分析篇15:软件开发方法考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

Hadoop大数据应用:Linux 部署 HDFS 分布式集群

目录 一、实验 1.环境 2.Linux 部署 HDFS 分布式集群 3.Linux 使用 HDFS 文件系统 二、问题 1.ssh-copy-id 报错 2. 如何禁用ssh key 检测 3.HDFS有哪些配置文件 4.hadoop查看版本报错 5.启动集群报错 6.hadoop 的启动和停止命令 7.上传文件报错 8.HDFS 使用命令 一…

基于java+springboot+vue实现的旅游管理系统(文末源码+Lw+ppt)23-402

摘 要 甘肃旅游管理系统采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的java进行编写&#xff0c;使用了SpringBoot框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对用户、…