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

结束语

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

  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值