机器人地面站-[QGroundControl源码解析]-[8]-[Audio]

目录

前言

一.AudioOutput

二.AudioOutputTest

总结:


前言

上篇就不总结了个人感觉很水,本篇过一遍Audio下的代码,只有两个类,具体干什么的看注释感觉是文本转语音的这个功能话不多说直接看代码。

一.AudioOutput

这是要一个语音播报类,用于将字符串文本转为语音进行播报,在项目中应该是用于播报数据,报错,警告这些内容的。

/****************************************************************************
 *
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#pragma once

#include <QObject>
#include <QTimer>
#include <QThread>
#include <QStringList>
#include <QTextToSpeech>

#include "QGCToolbox.h"

class QGCApplication;

/// Text to Speech Interface
/// 文本到语音接口
class AudioOutput : public QGCTool
{
    Q_OBJECT
public:
    AudioOutput(QGCApplication* app, QGCToolbox* toolbox);

    static bool     getMillisecondString    (const QString& string, QString& match, int& number);
    static QString  fixTextMessageForAudio  (const QString& string);

public slots:
    /// Convert string to speech output and say it
    /// 将字符串转换为语音输出并说出它
    void            say                     (const QString& text);

private slots:
    void            _stateChanged           (QTextToSpeech::State state);

protected:
    QTextToSpeech*  _tts;
    QStringList     _texts;
};

/****************************************************************************
 *
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

#include <QApplication>
#include <QDebug>
#include <QRegularExpression>

#include "AudioOutput.h"
#include "QGCApplication.h"
#include "QGC.h"
#include "SettingsManager.h"

AudioOutput::AudioOutput(QGCApplication* app, QGCToolbox* toolbox)
    : QGCTool   (app, toolbox)
    , _tts      (nullptr)
{
    if (qgcApp()->runningUnitTests()) {
        //基于云的单元测试没有语音功能。 如果你试着加大音量 语音引擎会弹出一个qWarning来阻止QT_FATAL_WARNINGS的使用
        // Cloud based unit tests don't have speech capabilty. If you try to crank up
        // speech engine it will pop a qWarning which prevents usage of QT_FATAL_WARNINGS
        return;
    }

    _tts = new QTextToSpeech(this);

    //-- Force TTS engine to English as all incoming messages from the autopilot 
    //   are in English and not localized.
    // 强制TTS引擎为英文,因为所有传入的信息都来自自动驾驶仪
#ifdef Q_OS_LINUX
    _tts->setLocale(QLocale("en_US"));
#endif
    connect(_tts, &QTextToSpeech::stateChanged, this, &AudioOutput::_stateChanged);
}

void AudioOutput::say(const QString& inText)
{
    if (!_tts) {
        qDebug() << "say" << inText;
        return;
    }

    bool muted = qgcApp()->toolbox()->settingsManager()->appSettings()->audioMuted()->rawValue().toBool();
    muted |= qgcApp()->runningUnitTests();
    if (!muted && !qgcApp()->runningUnitTests()) {
        QString text = fixTextMessageForAudio(inText);
        //如果当前状态是正在说话的状态
        if(_tts->state() == QTextToSpeech::Speaking) {
            //如果语言列表没有当前的text
            if(!_texts.contains(text)) {
                //-- Some arbitrary limit
                //语音列表的大小最大为20
                if(_texts.size() > 20) {
                    _texts.removeFirst();
                }
                //添加当前的语音文本
                _texts.append(text);
            }
        } else {
            //如果已经有了机会播报
            _tts->say(text);
        }
    }
}

void AudioOutput::_stateChanged(QTextToSpeech::State state)
{
    //tts信号 表示状态的改变
    //如果状态变为准备状态
    if(state == QTextToSpeech::Ready) {
        if(_texts.size()) {
            //拿到语音文本列表的第一个,播报完就删除了
            QString text = _texts.first();
            _texts.removeFirst();
            _tts->say(text);
        }
    }
}


//获取时间字符串
bool AudioOutput::getMillisecondString(const QString& string, QString& match, int& number) {
    //正则表达式
    static QRegularExpression re("([0-9]+ms)");
    QRegularExpressionMatchIterator i = re.globalMatch(string);
    while (i.hasNext()) {
        QRegularExpressionMatch qmatch = i.next();
        if (qmatch.hasMatch()) {
            match = qmatch.captured(0);
            number = qmatch.captured(0).replace("ms", "").toInt();
            return true;
        }
    }
    return false;
}


//对语音文本进行加工
QString AudioOutput::fixTextMessageForAudio(const QString& string) {
    QString match;
    QString newNumber;
    QString result = string;

    //-- Look for codified terms
    //将err改为error 方便语音播报
    if(result.contains("ERR ", Qt::CaseInsensitive)) {
        result.replace("ERR ", "error ", Qt::CaseInsensitive);
    }
    if(result.contains("ERR:", Qt::CaseInsensitive)) {
        result.replace("ERR:", "error.", Qt::CaseInsensitive);
    }
    if(result.contains("POSCTL", Qt::CaseInsensitive)) {
        result.replace("POSCTL", "Position Control", Qt::CaseInsensitive);
    }
    if(result.contains("ALTCTL", Qt::CaseInsensitive)) {
        result.replace("ALTCTL", "Altitude Control", Qt::CaseInsensitive);
    }
    if(result.contains("AUTO_RTL", Qt::CaseInsensitive)) {
        result.replace("AUTO_RTL", "auto Return To Launch", Qt::CaseInsensitive);
    } else if(result.contains("RTL", Qt::CaseInsensitive)) {
        result.replace("RTL", "Return To Launch", Qt::CaseInsensitive);
    }
    if(result.contains("ACCEL ", Qt::CaseInsensitive)) {
        result.replace("ACCEL ", "accelerometer ", Qt::CaseInsensitive);
    }
    if(result.contains("RC_MAP_MODE_SW", Qt::CaseInsensitive)) {
        result.replace("RC_MAP_MODE_SW", "RC mode switch", Qt::CaseInsensitive);
    }
    if(result.contains("REJ.", Qt::CaseInsensitive)) {
        result.replace("REJ.", "Rejected", Qt::CaseInsensitive);
    }
    if(result.contains("WP", Qt::CaseInsensitive)) {
        result.replace("WP", "way point", Qt::CaseInsensitive);
    }
    if(result.contains("CMD", Qt::CaseInsensitive)) {
        result.replace("CMD", "command", Qt::CaseInsensitive);
    }
    if(result.contains("COMPID", Qt::CaseInsensitive)) {
        result.replace("COMPID", "component eye dee", Qt::CaseInsensitive);
    }
    if(result.contains(" params ", Qt::CaseInsensitive)) {
        result.replace(" params ", " parameters ", Qt::CaseInsensitive);
    }
    if(result.contains(" id ", Qt::CaseInsensitive)) {
        result.replace(" id ", " eye dee ", Qt::CaseInsensitive);
    }
    if(result.contains(" ADSB ", Qt::CaseInsensitive)) {
        result.replace(" ADSB ", " Hey Dee Ess Bee ", Qt::CaseInsensitive);
    }
    if(result.contains(" EKF ", Qt::CaseInsensitive)) {
        result.replace(" EKF ", " Eee Kay Eff ", Qt::CaseInsensitive);
    }
    if(result.contains("PREARM", Qt::CaseInsensitive)) {
        result.replace("PREARM", "pre arm", Qt::CaseInsensitive);
    }
    if(result.contains("PITOT", Qt::CaseInsensitive)) {
        result.replace("PITOT", "pee toe", Qt::CaseInsensitive);
    }

    // Convert negative numbers 转换为负数
    QRegularExpression re(QStringLiteral("(-)[0-9]*\\.?[0-9]"));
    QRegularExpressionMatch reMatch = re.match(result);
    while (reMatch.hasMatch()) {
        if (!reMatch.captured(1).isNull()) {
            // There is a negative prefix 有一个negative的前缀
            result.replace(reMatch.capturedStart(1), reMatch.capturedEnd(1) - reMatch.capturedStart(1), tr(" negative "));
        }
        reMatch = re.match(result);
    }

    // Convert real number with decimal point 将实数转换为小数点
    re.setPattern(QStringLiteral("([0-9]+)(\\.)([0-9]+)"));
    reMatch = re.match(result);
    while (reMatch.hasMatch()) {
        if (!reMatch.captured(2).isNull()) {
            // There is a decimal point // point
            result.replace(reMatch.capturedStart(2), reMatch.capturedEnd(2) - reMatch.capturedStart(2), tr(" point "));
        }
        reMatch = re.match(result);
    }

    // Convert meter postfix after real number
    re.setPattern(QStringLiteral("[0-9]*\\.?[0-9]\\s?(m)([^A-Za-z]|$)"));
    reMatch = re.match(result);
    while (reMatch.hasMatch()) {
        if (!reMatch.captured(1).isNull()) {
            // There is a meter postfix
            result.replace(reMatch.capturedStart(1), reMatch.capturedEnd(1) - reMatch.capturedStart(1), tr(" meters"));
        }
        reMatch = re.match(result);
    }

    int number;
    if(getMillisecondString(string, match, number) && number > 1000) {
        if(number < 60000) {
            int seconds = number / 1000;
            newNumber = QString("%1 second%2").arg(seconds).arg(seconds > 1 ? "s" : "");
        } else {
            int minutes = number / 60000;
            int seconds = (number - (minutes * 60000)) / 1000;
            if (!seconds) {
                newNumber = QString("%1 minute%2").arg(minutes).arg(minutes > 1 ? "s" : "");
            } else {
                newNumber = QString("%1 minute%2 and %3 second%4").arg(minutes).arg(minutes > 1 ? "s" : "").arg(seconds).arg(seconds > 1 ? "s" : "");
            }
        }
        result.replace(match, newNumber);
    }
    return result;
}

二.AudioOutputTest

这个类主要是用于上一个类的测试,例如:

    QString result = AudioOutput::fixTextMessageForAudio(QStringLiteral("-10.5m, -10.5m. -10.5 m"));
    QCOMPARE(result, QStringLiteral(" negative 10 point 5 meters,  negative 10 point 5 meters.  negative 10 point 5  meters"));

总结:

本篇主要讲解了Audio目录下的两个类。第一个类是功能类,主要用于将语音文本转为语音,进行应用数据的实时播报。第二个类是测试类。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值