Remove multithreading support from voicefile creation.
Running TTS and encoders with multiple threads is causing problems on Windows since introduction of the feature (FS#12106, FS#11994). The current implementation also makes wrong assumptions (having multiple threads talk to the SAPI script doesn't make it run faster since it's still one thread responsible for creation). Completely remove multithreading support for that for now -- a different implementation is necessary. Change-Id: Icafa223644efc370a09186ce28ac83c22902e0c0
This commit is contained in:
parent
c2f0ba7ecd
commit
820dcfdfed
2 changed files with 99 additions and 189 deletions
|
@ -21,16 +21,16 @@
|
||||||
#include "systeminfo.h"
|
#include "systeminfo.h"
|
||||||
#include "wavtrim.h"
|
#include "wavtrim.h"
|
||||||
|
|
||||||
TalkGenerator::TalkGenerator(QObject* parent): QObject(parent), encFutureWatcher(this), ttsFutureWatcher(this)
|
TalkGenerator::TalkGenerator(QObject* parent): QObject(parent)
|
||||||
{
|
{
|
||||||
m_userAborted = false;
|
|
||||||
m_lang = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \brief Creates Talkfiles.
|
//! \brief Creates Talkfiles.
|
||||||
//!
|
//!
|
||||||
TalkGenerator::Status TalkGenerator::process(QList<TalkEntry>* list,int wavtrimth)
|
TalkGenerator::Status TalkGenerator::process(QList<TalkEntry>* list,int wavtrimth)
|
||||||
{
|
{
|
||||||
|
m_abort = false;
|
||||||
QString errStr;
|
QString errStr;
|
||||||
bool warnings = false;
|
bool warnings = false;
|
||||||
|
|
||||||
|
@ -103,122 +103,81 @@ TalkGenerator::Status TalkGenerator::process(QList<TalkEntry>* list,int wavtrimt
|
||||||
//!
|
//!
|
||||||
TalkGenerator::Status TalkGenerator::voiceList(QList<TalkEntry>* list,int wavtrimth)
|
TalkGenerator::Status TalkGenerator::voiceList(QList<TalkEntry>* list,int wavtrimth)
|
||||||
{
|
{
|
||||||
emit logProgress(0, list->size());
|
int progressMax = list->size();
|
||||||
|
int m_progress = 0;
|
||||||
|
emit logProgress(m_progress,progressMax);
|
||||||
|
|
||||||
|
QStringList errors;
|
||||||
QStringList duplicates;
|
QStringList duplicates;
|
||||||
|
|
||||||
m_ttsWarnings = false;
|
bool warnings = false;
|
||||||
for(int i=0; i < list->size(); i++)
|
for(int i=0; i < list->size(); i++)
|
||||||
{
|
{
|
||||||
(*list)[i].refs.tts = m_tts;
|
if(m_abort)
|
||||||
(*list)[i].refs.wavtrim = wavtrimth;
|
{
|
||||||
(*list)[i].refs.generator = this;
|
emit logItem(tr("Voicing aborted"), LOGERROR);
|
||||||
// enable voice corrections only if a language is set.
|
return eERROR;
|
||||||
if(!m_lang.isEmpty()) {
|
|
||||||
QString s = (*list)[i].toSpeak;
|
|
||||||
(*list)[i].toSpeak = correctString(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip duplicated wav entries
|
// skip duplicated wav entrys
|
||||||
if(!duplicates.contains(list->at(i).wavfilename))
|
if(!duplicates.contains(list->at(i).wavfilename))
|
||||||
duplicates.append(list->at(i).wavfilename);
|
duplicates.append(list->at(i).wavfilename);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << "[TalkGen] duplicate skipped";
|
qDebug() << "[TalkGenerator] duplicate skipped";
|
||||||
(*list)[i].voiced = true;
|
(*list)[i].voiced = true;
|
||||||
|
emit logProgress(++m_progress,progressMax);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* If the engine can't be parallelized, we use only 1 thread */
|
// skip already voiced entrys
|
||||||
// NOTE: setting the number of maximum threads to use to 1 doesn't seem to
|
if(list->at(i).voiced == true)
|
||||||
// work as expected -- it causes sporadically output files missing (see
|
|
||||||
// FS#11994). As a stop-gap solution use a separate implementation in that
|
|
||||||
// case for running the TTS.
|
|
||||||
if((m_tts->capabilities() & TTSBase::RunInParallel) != 0)
|
|
||||||
{
|
|
||||||
int maxThreadCount = QThreadPool::globalInstance()->maxThreadCount();
|
|
||||||
qDebug() << "[TalkGenerator] Maximum number of threads used:"
|
|
||||||
<< QThreadPool::globalInstance()->maxThreadCount();
|
|
||||||
|
|
||||||
connect(&ttsFutureWatcher, SIGNAL(progressValueChanged(int)),
|
|
||||||
this, SLOT(ttsProgress(int)));
|
|
||||||
ttsFutureWatcher.setFuture(QtConcurrent::map(*list, &TalkGenerator::ttsEntryPoint));
|
|
||||||
|
|
||||||
/* We use this loop as an equivalent to ttsFutureWatcher.waitForFinished()
|
|
||||||
* since the latter blocks all events */
|
|
||||||
while(ttsFutureWatcher.isRunning())
|
|
||||||
QCoreApplication::processEvents();
|
|
||||||
|
|
||||||
/* Restore global settings, if we changed them */
|
|
||||||
if ((m_tts->capabilities() & TTSBase::RunInParallel) == 0)
|
|
||||||
QThreadPool::globalInstance()->setMaxThreadCount(maxThreadCount);
|
|
||||||
|
|
||||||
if(ttsFutureWatcher.isCanceled())
|
|
||||||
return eERROR;
|
|
||||||
else if(m_ttsWarnings)
|
|
||||||
return eWARNING;
|
|
||||||
else
|
|
||||||
return eOK;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qDebug() << "[TalkGenerator] Using single thread TTS workaround";
|
|
||||||
int items = list->size();
|
|
||||||
for(int i = 0; i < items; i++) {
|
|
||||||
if(m_userAborted) {
|
|
||||||
emit logItem(tr("Voicing aborted"), LOGERROR);
|
|
||||||
return eERROR;
|
|
||||||
}
|
|
||||||
TalkEntry entry = list->at(i);
|
|
||||||
TalkGenerator::ttsEntryPoint(entry);
|
|
||||||
(*list)[i] = entry;
|
|
||||||
emit logProgress(i, items);
|
|
||||||
}
|
|
||||||
return m_ttsWarnings ? eWARNING : eOK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TalkGenerator::ttsEntryPoint(TalkEntry& entry)
|
|
||||||
{
|
|
||||||
if (!entry.voiced && !entry.toSpeak.isEmpty())
|
|
||||||
{
|
|
||||||
QString error;
|
|
||||||
qDebug() << "[TalkGen] voicing: " << entry.toSpeak << "to" << entry.wavfilename;
|
|
||||||
TTSStatus status = entry.refs.tts->voice(entry.toSpeak,entry.wavfilename, &error);
|
|
||||||
if (status == Warning || status == FatalError)
|
|
||||||
{
|
{
|
||||||
entry.refs.generator->ttsFailEntry(entry, status, error);
|
emit logProgress(++m_progress,progressMax);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
if (entry.refs.wavtrim != -1)
|
// skip entry whith empty text
|
||||||
|
if(list->at(i).toSpeak == "")
|
||||||
|
{
|
||||||
|
emit logProgress(++m_progress,progressMax);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// voice entry
|
||||||
|
QString error;
|
||||||
|
qDebug() << "[TalkGenerator] voicing: " << list->at(i).toSpeak << "to" << list->at(i).wavfilename;
|
||||||
|
TTSStatus status = m_tts->voice(list->at(i).toSpeak,list->at(i).wavfilename, &error);
|
||||||
|
if(status == Warning)
|
||||||
|
{
|
||||||
|
warnings = true;
|
||||||
|
emit logItem(tr("Voicing of %1 failed: %2").arg(list->at(i).toSpeak).arg(error),
|
||||||
|
LOGWARNING);
|
||||||
|
}
|
||||||
|
else if (status == FatalError)
|
||||||
|
{
|
||||||
|
emit logItem(tr("Voicing of %1 failed: %2").arg(list->at(i).toSpeak).arg(error),
|
||||||
|
LOGERROR);
|
||||||
|
return eERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
(*list)[i].voiced = true;
|
||||||
|
|
||||||
|
//wavetrim if needed
|
||||||
|
if(wavtrimth != -1)
|
||||||
{
|
{
|
||||||
char buffer[255];
|
char buffer[255];
|
||||||
wavtrim(entry.wavfilename.toLocal8Bit().data(), entry.refs.wavtrim, buffer, 255);
|
wavtrim(list->at(i).wavfilename.toLocal8Bit().data(),wavtrimth,buffer,255);
|
||||||
}
|
}
|
||||||
entry.voiced = true;
|
|
||||||
|
emit logProgress(++m_progress,progressMax);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
}
|
}
|
||||||
|
if(warnings)
|
||||||
|
return eWARNING;
|
||||||
|
else
|
||||||
|
return eOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TalkGenerator::ttsFailEntry(const TalkEntry& entry, TTSStatus status, QString error)
|
|
||||||
{
|
|
||||||
if(status == Warning)
|
|
||||||
{
|
|
||||||
m_ttsWarnings = true;
|
|
||||||
emit logItem(tr("Voicing of %1 failed: %2").arg(entry.toSpeak).arg(error),
|
|
||||||
LOGWARNING);
|
|
||||||
}
|
|
||||||
else if (status == FatalError)
|
|
||||||
{
|
|
||||||
emit logItem(tr("Voicing of %1 failed: %2").arg(entry.toSpeak).arg(error),
|
|
||||||
LOGERROR);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TalkGenerator::ttsProgress(int value)
|
|
||||||
{
|
|
||||||
emit logProgress(value,ttsFutureWatcher.progressMaximum());
|
|
||||||
}
|
|
||||||
|
|
||||||
//! \brief Encodes a List of strings
|
//! \brief Encodes a List of strings
|
||||||
//!
|
//!
|
||||||
|
@ -226,86 +185,58 @@ TalkGenerator::Status TalkGenerator::encodeList(QList<TalkEntry>* list)
|
||||||
{
|
{
|
||||||
QStringList duplicates;
|
QStringList duplicates;
|
||||||
|
|
||||||
int itemsCount = list->size();
|
int progressMax = list->size();
|
||||||
emit logProgress(0, itemsCount);
|
int m_progress = 0;
|
||||||
|
emit logProgress(m_progress,progressMax);
|
||||||
|
|
||||||
/* Do some preprocessing and remove entries that have not been voiced. */
|
for(int i=0; i < list->size(); i++)
|
||||||
for (int idx=0; idx < itemsCount; idx++)
|
|
||||||
{
|
{
|
||||||
if(list->at(idx).voiced == false)
|
if(m_abort)
|
||||||
{
|
{
|
||||||
qDebug() << "[TalkGen] unvoiced entry" << list->at(idx).toSpeak <<"detected";
|
emit logItem(tr("Encoding aborted"), LOGERROR);
|
||||||
list->removeAt(idx);
|
return eERROR;
|
||||||
itemsCount--;
|
}
|
||||||
idx--;
|
|
||||||
|
//skip non-voiced entrys
|
||||||
|
if(list->at(i).voiced == false)
|
||||||
|
{
|
||||||
|
qDebug() << "non voiced entry" << list->at(i).toSpeak <<"detected";
|
||||||
|
emit logProgress(++m_progress,progressMax);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(duplicates.contains(list->at(idx).talkfilename))
|
//skip duplicates
|
||||||
|
if(!duplicates.contains(list->at(i).talkfilename))
|
||||||
|
duplicates.append(list->at(i).talkfilename);
|
||||||
|
else
|
||||||
{
|
{
|
||||||
(*list)[idx].encoded = true; /* make sure we skip this entry */
|
qDebug() << "[TalkGenerator] duplicate skipped";
|
||||||
|
(*list)[i].encoded = true;
|
||||||
|
emit logProgress(++m_progress,progressMax);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
duplicates.append(list->at(idx).talkfilename);
|
|
||||||
(*list)[idx].refs.encoder = m_enc;
|
//encode entry
|
||||||
(*list)[idx].refs.generator = this; /* not really needed, unless we end up
|
qDebug() << "[TalkGenerator] encoding " << list->at(i).wavfilename
|
||||||
voicing and encoding with two different
|
<< "to" << list->at(i).talkfilename;
|
||||||
TalkGenerators.*/
|
if(!m_enc->encode(list->at(i).wavfilename,list->at(i).talkfilename))
|
||||||
|
{
|
||||||
|
emit logItem(tr("Encoding of %1 failed").arg(
|
||||||
|
QFileInfo(list->at(i).wavfilename).baseName()), LOGERROR);
|
||||||
|
return eERROR;
|
||||||
|
}
|
||||||
|
(*list)[i].encoded = true;
|
||||||
|
emit logProgress(++m_progress,progressMax);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
}
|
}
|
||||||
|
return eOK;
|
||||||
connect(&encFutureWatcher, SIGNAL(progressValueChanged(int)),
|
|
||||||
this, SLOT(encProgress(int)));
|
|
||||||
encFutureWatcher.setFuture(QtConcurrent::map(*list, &TalkGenerator::encEntryPoint));
|
|
||||||
|
|
||||||
/* We use this loop as an equivalent to encFutureWatcher.waitForFinished()
|
|
||||||
* since the latter blocks all events */
|
|
||||||
while (encFutureWatcher.isRunning())
|
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
|
||||||
|
|
||||||
if (encFutureWatcher.isCanceled())
|
|
||||||
return eERROR;
|
|
||||||
else
|
|
||||||
return eOK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TalkGenerator::encEntryPoint(TalkEntry& entry)
|
//! \brief slot, which is connected to the abort of the Logger.
|
||||||
{
|
//Sets a flag, so Creating Talkfiles ends at the next possible position
|
||||||
if(!entry.encoded)
|
|
||||||
{
|
|
||||||
bool res = entry.refs.encoder->encode(entry.wavfilename, entry.talkfilename);
|
|
||||||
entry.encoded = res;
|
|
||||||
if (!entry.encoded)
|
|
||||||
entry.refs.generator->encFailEntry(entry);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TalkGenerator::encProgress(int value)
|
|
||||||
{
|
|
||||||
emit logProgress(value, encFutureWatcher.progressMaximum());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TalkGenerator::encFailEntry(const TalkEntry& entry)
|
|
||||||
{
|
|
||||||
emit logItem(tr("Encoding of %1 failed").arg(
|
|
||||||
QFileInfo(entry.wavfilename).baseName()), LOGERROR);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
//! \brief slot, which is connected to the abort of the Logger. Sets a flag, so Creating Talkfiles ends at the next possible position
|
|
||||||
//!
|
//!
|
||||||
void TalkGenerator::abort()
|
void TalkGenerator::abort()
|
||||||
{
|
{
|
||||||
if (ttsFutureWatcher.isRunning())
|
m_abort = true;
|
||||||
{
|
|
||||||
ttsFutureWatcher.cancel();
|
|
||||||
emit logItem(tr("Voicing aborted"), LOGERROR);
|
|
||||||
}
|
|
||||||
if (encFutureWatcher.isRunning())
|
|
||||||
{
|
|
||||||
encFutureWatcher.cancel();
|
|
||||||
emit logItem(tr("Encoding aborted"), LOGERROR);
|
|
||||||
}
|
|
||||||
m_userAborted = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TalkGenerator::correctString(QString s)
|
QString TalkGenerator::correctString(QString s)
|
||||||
|
@ -325,9 +256,9 @@ QString TalkGenerator::correctString(QString s)
|
||||||
qDebug() << "[VoiceFileCreator] corrected string" << s << "to" << corrected;
|
qDebug() << "[VoiceFileCreator] corrected string" << s << "to" << corrected;
|
||||||
|
|
||||||
return corrected;
|
return corrected;
|
||||||
|
m_abort = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void TalkGenerator::setLang(QString name)
|
void TalkGenerator::setLang(QString name)
|
||||||
{
|
{
|
||||||
m_lang = name;
|
m_lang = name;
|
||||||
|
|
|
@ -48,30 +48,15 @@ public:
|
||||||
QString target;
|
QString target;
|
||||||
bool voiced;
|
bool voiced;
|
||||||
bool encoded;
|
bool encoded;
|
||||||
|
|
||||||
/* We need the following members because
|
|
||||||
* 1) the QtConcurrent entry points are all static methods (and we
|
|
||||||
* need to communicate with the TalkGenerator)
|
|
||||||
* 2) we are not guaranteed to go through the list in any
|
|
||||||
* particular order, so we can't use the progress slot
|
|
||||||
* for error checking */
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
EncoderBase* encoder;
|
|
||||||
TTSBase* tts;
|
|
||||||
TalkGenerator* generator;
|
|
||||||
int wavtrim;
|
|
||||||
} refs;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TalkGenerator(QObject* parent);
|
TalkGenerator(QObject* parent);
|
||||||
|
|
||||||
Status process(QList<TalkEntry>* list,int wavtrimth = -1);
|
Status process(QList<TalkEntry>* list,int wavtrimth = -1);
|
||||||
QString correctString(QString s);
|
QString correctString(QString s);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void abort();
|
void abort();
|
||||||
void encProgress(int value);
|
|
||||||
void ttsProgress(int value);
|
|
||||||
void setLang(QString name);
|
void setLang(QString name);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -80,22 +65,12 @@ signals:
|
||||||
void logProgress(int, int); //! set progress bar.
|
void logProgress(int, int); //! set progress bar.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QFutureWatcher<void> encFutureWatcher;
|
|
||||||
QFutureWatcher<void> ttsFutureWatcher;
|
|
||||||
void encFailEntry(const TalkEntry& entry);
|
|
||||||
void ttsFailEntry(const TalkEntry& entry, TTSStatus status, QString error);
|
|
||||||
|
|
||||||
Status voiceList(QList<TalkEntry>* list,int wavetrimth);
|
Status voiceList(QList<TalkEntry>* list,int wavetrimth);
|
||||||
Status encodeList(QList<TalkEntry>* list);
|
Status encodeList(QList<TalkEntry>* list);
|
||||||
|
|
||||||
static void encEntryPoint(TalkEntry& entry);
|
|
||||||
static void ttsEntryPoint(TalkEntry& entry);
|
|
||||||
|
|
||||||
TTSBase* m_tts;
|
TTSBase* m_tts;
|
||||||
EncoderBase* m_enc;
|
EncoderBase* m_enc;
|
||||||
|
|
||||||
bool m_ttsWarnings;
|
|
||||||
bool m_userAborted;
|
|
||||||
QString m_lang;
|
QString m_lang;
|
||||||
|
|
||||||
struct CorrectionItems
|
struct CorrectionItems
|
||||||
|
@ -105,6 +80,10 @@ private:
|
||||||
QString modifier;
|
QString modifier;
|
||||||
};
|
};
|
||||||
QList<struct CorrectionItems> m_corrections;
|
QList<struct CorrectionItems> m_corrections;
|
||||||
|
|
||||||
|
bool m_abort;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue