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.0
                if(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.0
    if(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.0
    if(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.0
                if(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.0
                if(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);
    }
}

结束语

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多线程(代码案例: 单例模式, 阻塞队列, 生产者消费者模型,定时器)

设计模式是什么 类似于棋谱一样的东西 计算机圈子里的大佬为了能让小菜鸡的代码不要写的太差 针对一些典型的场景, 给出了一些典型的解决方案 这样小菜鸡们可以根据这些方案(ACM里面叫板子, 象棋五子棋里叫棋谱, 咱这里叫 设计模式), 略加修改, 这样代码再差也差不到哪里去 … …

官方安装配置要求服务器最低2核4G

官方安装配置要求服务器至少2核、4G。 如果服务器低于这个要求&#xff0c;就没有必要安装&#xff0c;因为用户体验超级差。 对于服务器CPU来说&#xff0c;建议2到4核就完全足够了&#xff0c;太多就浪费了&#xff0c;但是内存越大越好&#xff0c;最好是4G以上。 如果服务器…

数据库 | Mysql - [binlog]

INDEX 1 什么是 binlog2 作用3 数据恢复4 主从复制 1 什么是 binlog Mysql server 的日志文件 自动开启 2 作用 数据恢复主从复制 3 数据恢复 实际场景 01.00&#xff1a;数据全量备份08.00&#xff1a;数据丢失&#xff08;比如被人误删&#xff09;09.00&#xff1a;故…

贪心问题题目集一(代码 注解)

目录 介绍&#xff1a; 题目一&#xff1a; 题目二&#xff1a; 题目三&#xff1a; 题目四&#xff1a; 题目五&#xff1a; 题目六&#xff1a; 题目七&#xff1a; 题目八&#xff1a; 介绍&#xff1a; 贪心算法是一种特殊的算法思想&#xff0c;它在每一步选择中都采取…

二叉树的初步学习和顺序结构实现

当我们学完顺序表、链表、栈和队列的时候&#xff0c;我们就要开始学习树了。树对于以后的学习有非常大的帮助&#xff0c;尤其是排序。好了&#xff0c;开始我们的学习吧。 1.树的概念及结构 1.1树的结构 树结构是一种非线性结构。它是由n&#xff08;n>0&#xff09;个…

ISIS接口MD5 算法认证实验简述

默认情况下&#xff0c;ISIS接口认证通过在ISIS协议数据单元&#xff08;PDU&#xff09;中添加认证字段&#xff0c;例如&#xff1a;MD5 算法&#xff0c;用于验证发送方的身份。 ISIS接口认证防止未经授权的设备加入到网络中&#xff0c;并确保邻居之间的通信是可信的。它可…

【教学类-34-10】20240313 春天拼图(Midjounery生成线描图,4*4格拼图块)(AI对话大师)

作品展示&#xff1a; 背景需求&#xff1a; 利用华文彩云空心字&#xff08;粗胖字体。凑满9个拼图&#xff09;制作了3*3的拼图块 【教学类-34-09】20240310华文彩云学号拼图&#xff08;3*3格子浅灰底图 深灰拼图块&#xff09;&#xff08;AI对话大师&#xff09;-CSDN博…

唯一约束

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 唯一约束 唯一约束的特点是在某一个列上的内容不允许出现重复。 例如&#xff0c;现在要收集用户的信息&#xff0c;假设包含编号&#xff08;mid&#xff09;、姓名&…

【vue在主页中点击主页面如何弹出一个指定某个页面的窗口】

【vue在主页中点击主页面跳转到某个页面的操作完整过程】 1.首先在主页面中加入一个卡槽用于展示弹出的窗口 代码如下&#xff1a; <el-dialog :visible.sync"dialogVisible1" :close-on-click-modal"false" :title"title" class"dial…

Docker出现容器名称重复如何解决

假如你的重复容器名称是mysql5 删除已存在的容器&#xff1a;如果你不再需要那个已经存在的名为“mysql5”的容器&#xff0c;你可以删除它。使用下面的命令&#xff1a; docker rm -f mysql5这条命令会强制删除正在运行的容器。一旦容器被删除&#xff0c;你就可以重新使用这个…

Git全套教程一套精通git.跟学黑马笔记

Git全套教程一套精通git.跟学黑马笔记 文章目录 Git全套教程一套精通git.跟学黑马笔记1.版本管理工具概念2. 版本管理工具介绍2.1版本管理发展简史(维基百科)2.1.1 SVN(SubVersion)2.1.2 Git 3. Git 发展简史4. Git 的安装4.1 git 的下载4.2 安装4.3 基本配置4.4 为常用指令配置…

ElasticSearch之Nested对象

写在前面 本文看下es的nested嵌套对象相关内容。 1&#xff1a;es用了啥范式&#xff1f; 在关系型数据库中定义了6大数据库范式,即1&#xff0c;2&#xff0c;3&#xff0c;BC&#xff0c;4&#xff0c;5的NF&#xff08;normal form&#xff09;,分别如下&#xff1a; 1N…

快速去除或提取视频中的任何声音,你学会了吗

怎么提取视频中的音频&#xff1f;本文将向您介绍多个简单而有效的方法&#xff0c;帮助您轻松掌握如何提取视频中的音频。无论您是视频编辑新手还是经验丰富的用户&#xff0c;这些建议都将为您提供多样选择&#xff0c;满足各种需求。 方法一&#xff1a;使用在线转换工具提取…

一维差分(模板)

差分是前缀和的逆运算&#xff0c;对于一个数组a&#xff0c;其差分数组b的每一项都是a [ i ]和前一项a [ i − 1 ]的差。 **注意&#xff1a;**差分数组和原数组必须分开存放&#xff01;&#xff01;&#xff01;&#xff01; #include <iostream> using namespace s…

python爬虫-AES.CBS加密案例(mmz批量爬取)

下载mmz本页数据 批量下载请看主页&#xff01;&#xff01;&#xff01; 代码&#xff1a; import requests from Crypto.Cipher import AES import base64cookies {PHPSESSID: 48nu182kdlsmgfo2g7hl6eufsa,Hm_lvt_6cd598ca665714ffcd8aca3aafc5e0dc: 1710568549,SECKEY_A…

deepseek-coder模型量化

1 简介 DeepSeek-Coder在多种编程语言和各种基准测试中取得了开源代码模型中最先进的性能。 为尝试在开发板进行部署&#xff0c;首先利用llama.cpp对其进行量化。 2 llama.cpp安装 git clone之后进入文件夹make即可&#xff0c;再将依赖补全pip install -r requirements.tx…

可视化图表:南丁格尔玫瑰图,来自历史上最著名的护士。

Hi&#xff0c;我是贝格前端工场的老司机&#xff0c;本文分享可视化图表设计的南丁格尔玫瑰图设计&#xff0c;欢迎老铁持续关注我们。 一、南丁格尔与玫瑰图 南丁格尔&#xff08;Florence Nightingale&#xff0c;1820年-1910年&#xff09;是一位英国护士和统计学家&…

江大白 | 万字长文,深度全面解读PyTorch内部机制,推荐阅读!

本文来源公众号“江大白”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;万字长文&#xff0c;深度全面解读PyTorch内部机制&#xff0c;推荐阅读&#xff01; 以下文章来源于知乎&#xff1a;人工智能前沿讲习 作者&#xff1a…

2024.4.17周报

目录 摘要 Abstract 文献阅读&#xff1a;耦合时间和非时间序列模型模拟城市洪涝区洪水深度 现有问题 提出方法 创新点 XGBoost和LSTM耦合模型 XGBoost算法 ​编辑 LSTM&#xff08;长短期记忆网络&#xff09; 耦合模型 研究实验 数据集 评估指标 研究目的 洪水…