目录
前言
上篇就不总结了个人感觉很水,本篇过一遍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目录下的两个类。第一个类是功能类,主要用于将语音文本转为语音,进行应用数据的实时播报。第二个类是测试类。