OSDN Git Service

23fa83430a9bbbffa9f0334b3bbf11a10576ad4d
[lamexp/LameXP.git] / src / Thread_Initialization.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version, but always including the *additional*
9 // restrictions defined in the "License.txt" file.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 //
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
22
23 #include "Thread_Initialization.h"
24
25 #include "LockedFile.h"
26 #include "Tools.h"
27 #include "Tool_Abstract.h"
28
29 #include <QFileInfo>
30 #include <QCoreApplication>
31 #include <QProcess>
32 #include <QMap>
33 #include <QDir>
34 #include <QResource>
35 #include <QTextStream>
36 #include <QRunnable>
37 #include <QThreadPool>
38 #include <QMutex>
39 #include <QQueue>
40
41 /* helper macros */
42 #define PRINT_CPU_TYPE(X) case X: qDebug("Selected CPU is: " #X)
43
44 /* constants */
45 static const double g_allowedExtractDelay = 12.0;
46 static const size_t BUFF_SIZE = 512;
47 static const size_t EXPECTED_TOOL_COUNT = 28;
48
49 /* benchmark */
50 #undef ENABLE_BENCHMARK
51
52 /* number of CPU cores -> number of threads */
53 static unsigned int cores2threads(const unsigned int cores)
54 {
55         static const size_t LUT_LEN = 4;
56         
57         static const struct
58         {
59                 const unsigned int upperBound;
60                 const double coeffs[4];
61         }
62         LUT[LUT_LEN] =
63         {
64                 {  4, { -0.052695810565,  0.158087431694, 4.982841530055, -1.088233151184 } },
65                 {  8, {  0.042431693989, -0.983442622951, 9.548961748634, -7.176393442623 } },
66                 { 12, { -0.006277322404,  0.185573770492, 0.196830601093, 17.762622950820 } },
67                 { 32, {  0.000673497268, -0.064655737705, 3.199584699454,  5.751606557377 } }
68         };
69
70         size_t index = 0;
71         while((cores > LUT[index].upperBound) && (index < (LUT_LEN-1))) index++;
72
73         const double x = qBound(1.0, double(cores), double(LUT[LUT_LEN-1].upperBound));
74         const double y = (LUT[index].coeffs[0] * pow(x, 3.0)) + (LUT[index].coeffs[1] * pow(x, 2.0)) + (LUT[index].coeffs[2] * x) + LUT[index].coeffs[3];
75
76         return qRound(abs(y));
77 }
78
79 ////////////////////////////////////////////////////////////
80 // ExtractorTask class
81 ////////////////////////////////////////////////////////////
82
83 class ExtractorTask : public QRunnable
84 {
85 public:
86         ExtractorTask(QResource *const toolResource, const QDir &appDir, const QString &toolName, const QByteArray &toolHash, const unsigned int toolVersion, const QString &toolTag)
87         :
88                 m_appDir(appDir),
89                 m_toolName(toolName),
90                 m_toolHash(toolHash),
91                 m_toolVersion(toolVersion),
92                 m_toolTag(toolTag),
93                 m_toolResource(toolResource)
94         {
95                 /* Nothing to do */
96         }
97
98         ~ExtractorTask(void)
99         {
100                 delete m_toolResource;
101         }
102
103         static void clearFlags(void)
104         {
105                 QMutexLocker lock(&s_mutex);
106                 s_bExcept = false;
107                 s_bCustom = false;
108                 s_errMsg[0] = char(0);
109         }
110
111         static bool getExcept(void) { bool ret; QMutexLocker lock(&s_mutex); ret = s_bExcept; return ret; }
112         static bool getCustom(void) { bool ret; QMutexLocker lock(&s_mutex); ret = s_bCustom; return ret; }
113
114         static bool getErrMsg(char *buffer, const size_t buffSize)
115         {
116                 QMutexLocker lock(&s_mutex);
117                 if(s_errMsg[0])
118                 {
119                         strncpy_s(buffer, BUFF_SIZE, s_errMsg, _TRUNCATE);
120                         return true;
121                 }
122                 return false;
123         }
124
125 protected:
126         void run(void)
127         {
128                 try
129                 {
130                         if(!getExcept()) doExtract();
131                 }
132                 catch(const std::exception &e)
133                 {
134                         QMutexLocker lock(&s_mutex);
135                         if(!s_bExcept)
136                         {
137                                 s_bExcept = true;
138                                 strncpy_s(s_errMsg, BUFF_SIZE, e.what(), _TRUNCATE);
139                         }
140                         lock.unlock();
141                         qWarning("ExtractorTask exception error:\n%s\n\n", e.what());
142                 }
143                 catch(...)
144                 {
145                         QMutexLocker lock(&s_mutex);
146                         if(!s_bExcept)
147                         {
148                                 s_bExcept = true;
149                                 strncpy_s(s_errMsg, BUFF_SIZE, "Unknown exception error!", _TRUNCATE);
150                         }
151                         lock.unlock();
152                         qWarning("ExtractorTask encountered an unknown exception!");
153                 }
154         }
155
156         void doExtract(void)
157         {
158                 LockedFile *lockedFile = NULL;
159                 unsigned int version = m_toolVersion;
160
161                 QFileInfo toolFileInfo(m_toolName);
162                 const QString toolShortName = QString("%1.%2").arg(toolFileInfo.baseName().toLower(), toolFileInfo.suffix().toLower());
163
164                 QFileInfo customTool(QString("%1/tools/%2/%3").arg(m_appDir.canonicalPath(), QString::number(lamexp_version_build()), toolShortName));
165                 if(customTool.exists() && customTool.isFile())
166                 {
167                         qDebug("Setting up file: %s <- %s", toolShortName.toLatin1().constData(), m_appDir.relativeFilePath(customTool.canonicalFilePath()).toLatin1().constData());
168                         lockedFile = new LockedFile(customTool.canonicalFilePath()); version = UINT_MAX; s_bCustom = true;
169                 }
170                 else
171                 {
172                         qDebug("Extracting file: %s -> %s", m_toolName.toLatin1().constData(), toolShortName.toLatin1().constData());
173                         lockedFile = new LockedFile(m_toolResource, QString("%1/lxp_%2").arg(lamexp_temp_folder2(), toolShortName), m_toolHash);
174                 }
175
176                 if(lockedFile)
177                 {
178                         lamexp_register_tool(toolShortName, lockedFile, version, &m_toolTag);
179                 }
180         }
181
182 private:
183         QResource *const m_toolResource;
184         const QDir m_appDir;
185         const QString m_toolName;
186         const QByteArray m_toolHash;
187         const unsigned int m_toolVersion;
188         const QString m_toolTag;
189
190         static volatile bool s_bExcept;
191         static volatile bool s_bCustom;
192         static QMutex s_mutex;
193         static char s_errMsg[BUFF_SIZE];
194 };
195
196 QMutex ExtractorTask::s_mutex;
197 char ExtractorTask::s_errMsg[BUFF_SIZE] = {'\0'};
198 volatile bool ExtractorTask::s_bExcept = false;
199 volatile bool ExtractorTask::s_bCustom = false;
200
201 ////////////////////////////////////////////////////////////
202 // Constructor
203 ////////////////////////////////////////////////////////////
204
205 InitializationThread::InitializationThread(const lamexp_cpu_t *cpuFeatures)
206 {
207         m_bSuccess = false;
208         memset(&m_cpuFeatures, 0, sizeof(lamexp_cpu_t));
209         m_slowIndicator = false;
210         
211         if(cpuFeatures)
212         {
213                 memcpy(&m_cpuFeatures, cpuFeatures, sizeof(lamexp_cpu_t));
214         }
215 }
216
217 ////////////////////////////////////////////////////////////
218 // Thread Main
219 ////////////////////////////////////////////////////////////
220
221 #ifdef ENABLE_BENCHMARK
222 #define DO_INIT_FUNCT runBenchmark
223 #else //ENABLE_BENCHMARK
224 #define DO_INIT_FUNCT doInit
225 #endif //ENABLE_BENCHMARK
226
227 void InitializationThread::run(void)
228 {
229         try
230         {
231                 DO_INIT_FUNCT();
232         }
233         catch(const std::exception &error)
234         {
235                 fflush(stdout); fflush(stderr);
236                 fprintf(stderr, "\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
237                 lamexp_fatal_exit(L"Unhandeled C++ exception error, application will exit!");
238         }
239         catch(...)
240         {
241                 fflush(stdout); fflush(stderr);
242                 fprintf(stderr, "\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
243                 lamexp_fatal_exit(L"Unhandeled C++ exception error, application will exit!");
244         }
245 }
246
247 double InitializationThread::doInit(const size_t threadCount)
248 {
249         m_bSuccess = false;
250         delay();
251
252         //CPU type selection
253         unsigned int cpuSupport = 0;
254         if(m_cpuFeatures.sse && m_cpuFeatures.sse2 && m_cpuFeatures.intel)
255         {
256                 cpuSupport = m_cpuFeatures.x64 ? CPU_TYPE_X64_SSE : CPU_TYPE_X86_SSE;
257         }
258         else
259         {
260                 cpuSupport = m_cpuFeatures.x64 ? CPU_TYPE_X64_GEN : CPU_TYPE_X86_GEN;
261         }
262
263         //Hack to disable x64 on Wine, as x64 binaries won't run under Wine (tested with Wine 1.4 under Ubuntu 12.04 x64)
264         if(cpuSupport & CPU_TYPE_X64_ALL)
265         {
266                 if(lamexp_detect_wine())
267                 {
268                         qWarning("Running under Wine on a 64-Bit system. Going to disable all x64 support!\n");
269                         cpuSupport = (cpuSupport == CPU_TYPE_X64_SSE) ? CPU_TYPE_X86_SSE : CPU_TYPE_X86_GEN;
270                 }
271         }
272
273         //Print selected CPU type
274         switch(cpuSupport)
275         {
276                 PRINT_CPU_TYPE(CPU_TYPE_X86_GEN); break;
277                 PRINT_CPU_TYPE(CPU_TYPE_X86_SSE); break;
278                 PRINT_CPU_TYPE(CPU_TYPE_X64_GEN); break;
279                 PRINT_CPU_TYPE(CPU_TYPE_X64_SSE); break;
280                 default: THROW("CPU support undefined!");
281         }
282
283         //Allocate queues
284         QQueue<QString> queueToolName;
285         QQueue<QString> queueChecksum;
286         QQueue<QString> queueVersInfo;
287         QQueue<unsigned int> queueVersions;
288         QQueue<unsigned int> queueCpuTypes;
289
290         //Init properties
291         for(int i = 0; true; i++)
292         {
293                 if(!(g_lamexp_tools[i].pcName || g_lamexp_tools[i].pcHash  || g_lamexp_tools[i].uiVersion))
294                 {
295                         break;
296                 }
297                 else if(g_lamexp_tools[i].pcName && g_lamexp_tools[i].pcHash && g_lamexp_tools[i].uiVersion)
298                 {
299                         queueToolName.enqueue(QString::fromLatin1(g_lamexp_tools[i].pcName));
300                         queueChecksum.enqueue(QString::fromLatin1(g_lamexp_tools[i].pcHash));
301                         queueVersInfo.enqueue(QString::fromLatin1(g_lamexp_tools[i].pcVersTag));
302                         queueCpuTypes.enqueue(g_lamexp_tools[i].uiCpuType);
303                         queueVersions.enqueue(g_lamexp_tools[i].uiVersion);
304                 }
305                 else
306                 {
307                         qFatal("Inconsistent checksum data detected. Take care!");
308                 }
309         }
310
311         QDir appDir = QDir(QCoreApplication::applicationDirPath()).canonicalPath();
312
313         QThreadPool *pool = new QThreadPool();
314         pool->setMaxThreadCount((threadCount > 0) ? threadCount : qBound(2U, cores2threads(m_cpuFeatures.count), EXPECTED_TOOL_COUNT));
315         /* qWarning("Using %u threads for extraction.", pool->maxThreadCount()); */
316
317         LockedFile::selfTest();
318         ExtractorTask::clearFlags();
319
320         const long long timeExtractStart = lamexp_perfcounter_value();
321         
322         //Extract all files
323         while(!(queueToolName.isEmpty() || queueChecksum.isEmpty() || queueVersInfo.isEmpty() || queueCpuTypes.isEmpty() || queueVersions.isEmpty()))
324         {
325                 const QString toolName = queueToolName.dequeue();
326                 const QString checksum = queueChecksum.dequeue();
327                 const QString versInfo = queueVersInfo.dequeue();
328                 const unsigned int cpuType = queueCpuTypes.dequeue();
329                 const unsigned int version = queueVersions.dequeue();
330                         
331                 const QByteArray toolHash(checksum.toLatin1());
332                 if(toolHash.size() != 96)
333                 {
334                         qFatal("The checksum for \"%s\" has an invalid size!", QUTF8(toolName));
335                         return -1.0;
336                 }
337                         
338                 QResource *resource = new QResource(QString(":/tools/%1").arg(toolName));
339                 if(!(resource->isValid() && resource->data()))
340                 {
341                         LAMEXP_DELETE(resource);
342                         qFatal("The resource for \"%s\" could not be found!", QUTF8(toolName));
343                         return -1.0;
344                 }
345                         
346                 if(cpuType & cpuSupport)
347                 {
348                         pool->start(new ExtractorTask(resource, appDir, toolName, toolHash, version, versInfo));
349                         continue;
350                 }
351
352                 LAMEXP_DELETE(resource);
353         }
354
355         //Sanity Check
356         if(!(queueToolName.isEmpty() && queueChecksum.isEmpty() && queueVersInfo.isEmpty() && queueCpuTypes.isEmpty() && queueVersions.isEmpty()))
357         {
358                 qFatal("Checksum queues *not* empty fater verification completed. Take care!");
359         }
360
361         //Wait for extrator threads to finish
362         pool->waitForDone();
363         LAMEXP_DELETE(pool);
364
365         const long long timeExtractEnd = lamexp_perfcounter_value();
366
367         //Make sure all files were extracted correctly
368         if(ExtractorTask::getExcept())
369         {
370                 char errorMsg[BUFF_SIZE];
371                 if(ExtractorTask::getErrMsg(errorMsg, BUFF_SIZE))
372                 {
373                         qFatal("At least one of the required tools could not be initialized:\n%s", errorMsg);
374                         return -1.0;
375                 }
376                 qFatal("At least one of the required tools could not be initialized!");
377                 return -1.0;
378         }
379
380         qDebug("All extracted.\n");
381
382         //Using any custom tools?
383         if(ExtractorTask::getCustom())
384         {
385                 qWarning("Warning: Using custom tools, you might encounter unexpected problems!\n");
386         }
387
388         //Check delay
389         const double delayExtract = static_cast<double>(timeExtractEnd - timeExtractStart) / static_cast<double>(lamexp_perfcounter_frequ());
390         if(delayExtract > g_allowedExtractDelay)
391         {
392                 m_slowIndicator = true;
393                 qWarning("Extracting tools took %.3f seconds -> probably slow realtime virus scanner.", delayExtract);
394                 qWarning("Please report performance problems to your anti-virus developer !!!\n");
395         }
396         else
397         {
398                 qDebug("Extracting the tools took %.5f seconds (OK).\n", delayExtract);
399         }
400
401         //Register all translations
402         initTranslations();
403
404         //Look for AAC encoders
405         initNeroAac();
406         initFhgAac();
407         initQAac();
408
409         m_bSuccess = true;
410         delay();
411
412         return delayExtract;
413 }
414
415 void InitializationThread::runBenchmark(void)
416 {
417 #ifdef ENABLE_BENCHMARK
418         static const size_t nLoops = 5;
419         const size_t maxThreads = (5 * m_cpuFeatures.count);
420         QMap<size_t, double> results;
421
422         for(size_t c = 1; c <= maxThreads; c++)
423         {
424                 QList<double> delayLst;
425                 double delayAvg = 0.0;
426                 for(size_t i = 0; i < nLoops; i++)
427                 {
428                         delayLst << doInit(c);
429                         lamexp_clean_all_tools();
430                 }
431                 qSort(delayLst.begin(), delayLst.end());
432                 delayLst.takeLast();
433                 delayLst.takeFirst();
434                 for(QList<double>::ConstIterator iter = delayLst.constBegin(); iter != delayLst.constEnd(); iter++)
435                 {
436                         delayAvg += (*iter);
437                 }
438                 results.insert(c, (delayAvg / double(delayLst.count())));
439         }
440
441         qWarning("\n----------------------------------------------");
442         qWarning("Benchmark Results:");
443         qWarning("----------------------------------------------");
444
445         double bestTime = DBL_MAX; size_t bestVal = 0;
446         QList<size_t> keys = results.keys();
447         for(QList<size_t>::ConstIterator iter = keys.begin(); iter != keys.end(); iter++)
448         {
449                 const double time = results.value((*iter), DBL_MAX);
450                 qWarning("%02u -> %7.4f", (*iter), time);
451                 if(time < bestTime)
452                 {
453                         bestTime = time;
454                         bestVal = (*iter);
455                 }
456         }
457
458         qWarning("----------------------------------------------");
459         qWarning("BEST: %u of %u (factor: %7.4f)", bestVal, m_cpuFeatures.count, (double(bestVal) / double(m_cpuFeatures.count)));
460         qWarning("----------------------------------------------\n");
461         
462         qFatal("Benchmark complete. Thanks and bye bye!");
463 #else //ENABLE_BENCHMARK
464         THROW("Sorry, the benchmark is *not* available in this build!");
465 #endif //ENABLE_BENCHMARK
466 }
467
468 ////////////////////////////////////////////////////////////
469 // PUBLIC FUNCTIONS
470 ////////////////////////////////////////////////////////////
471
472 void InitializationThread::delay(void)
473 {
474         lamexp_sleep(333);
475 }
476
477 void InitializationThread::initTranslations(void)
478 {
479         //Search for language files
480         QStringList qmFiles = QDir(":/localization").entryList(QStringList() << "LameXP_??.qm", QDir::Files, QDir::Name);
481
482         //Make sure we found at least one translation
483         if(qmFiles.count() < 1)
484         {
485                 qFatal("Could not find any translation files!");
486                 return;
487         }
488
489         //Add all available translations
490         while(!qmFiles.isEmpty())
491         {
492                 QString langId, langName;
493                 unsigned int systemId = 0, country = 0;
494                 QString qmFile = qmFiles.takeFirst();
495                 
496                 QRegExp langIdExp("LameXP_(\\w\\w)\\.qm", Qt::CaseInsensitive);
497                 if(langIdExp.indexIn(qmFile) >= 0)
498                 {
499                         langId = langIdExp.cap(1).toLower();
500                         QResource langRes = QResource(QString(":/localization/%1.txt").arg(qmFile));
501                         if(langRes.isValid() && langRes.size() > 0)
502                         {
503                                 QByteArray data = QByteArray::fromRawData(reinterpret_cast<const char*>(langRes.data()), langRes.size());
504                                 QTextStream stream(&data, QIODevice::ReadOnly);
505                                 stream.setAutoDetectUnicode(false); stream.setCodec("UTF-8");
506                                 while(!stream.atEnd())
507                                 {
508                                         QStringList langInfo = stream.readLine().simplified().split(",", QString::SkipEmptyParts);
509                                         if(langInfo.count() == 3)
510                                         {
511                                                 systemId = langInfo.at(0).trimmed().toUInt();
512                                                 country  = langInfo.at(1).trimmed().toUInt();
513                                                 langName = langInfo.at(2).trimmed();
514                                                 break;
515                                         }
516                                 }
517                         }
518                 }
519
520                 if(!(langId.isEmpty() || langName.isEmpty() || systemId == 0))
521                 {
522                         if(lamexp_translation_register(langId, qmFile, langName, systemId, country))
523                         {
524                                 qDebug("Registering translation: %s = %s (%u) [%u]", QUTF8(qmFile), QUTF8(langName), systemId, country);
525                         }
526                         else
527                         {
528                                 qWarning("Failed to register: %s", qmFile.toLatin1().constData());
529                         }
530                 }
531         }
532
533         qDebug("All registered.\n");
534 }
535
536 void InitializationThread::initNeroAac(void)
537 {
538         const QString appPath = QDir(QCoreApplication::applicationDirPath()).canonicalPath();
539         
540         QFileInfo neroFileInfo[3];
541         neroFileInfo[0] = QFileInfo(QString("%1/neroAacEnc.exe").arg(appPath));
542         neroFileInfo[1] = QFileInfo(QString("%1/neroAacDec.exe").arg(appPath));
543         neroFileInfo[2] = QFileInfo(QString("%1/neroAacTag.exe").arg(appPath));
544         
545         bool neroFilesFound = true;
546         for(int i = 0; i < 3; i++)      { if(!neroFileInfo[i].exists()) neroFilesFound = false; }
547
548         if(!neroFilesFound)
549         {
550                 qDebug("Nero encoder binaries not found -> AAC encoding support will be disabled!\n");
551                 return;
552         }
553
554         for(int i = 0; i < 3; i++)
555         {
556                 if(!lamexp_is_executable(neroFileInfo[i].canonicalFilePath()))
557                 {
558                         qDebug("%s executbale is invalid -> AAC encoding support will be disabled!\n", QUTF8(neroFileInfo[i].fileName()));
559                         return;
560                 }
561         }
562
563         qDebug("Found Nero AAC encoder binary:\n%s\n", QUTF8(neroFileInfo[0].canonicalFilePath()));
564
565         //Lock the Nero binaries
566         LockedFile *neroBin[3];
567         for(int i = 0; i < 3; i++) neroBin[i] = NULL;
568
569         try
570         {
571                 for(int i = 0; i < 3; i++)
572                 {
573                         neroBin[i] = new LockedFile(neroFileInfo[i].canonicalFilePath());
574                 }
575         }
576         catch(...)
577         {
578                 for(int i = 0; i < 3; i++) LAMEXP_DELETE(neroBin[i]);
579                 qWarning("Failed to get excluive lock to Nero encoder binary -> AAC encoding support will be disabled!");
580                 return;
581         }
582
583         QProcess process;
584         lamexp_init_process(process, neroFileInfo[0].absolutePath());
585
586         process.start(neroFileInfo[0].canonicalFilePath(), QStringList() << "-help");
587
588         if(!process.waitForStarted())
589         {
590                 qWarning("Nero process failed to create!");
591                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
592                 process.kill();
593                 process.waitForFinished(-1);
594                 for(int i = 0; i < 3; i++) LAMEXP_DELETE(neroBin[i]);
595                 return;
596         }
597
598         unsigned int neroVersion = 0;
599
600         while(process.state() != QProcess::NotRunning)
601         {
602                 if(!process.waitForReadyRead())
603                 {
604                         if(process.state() == QProcess::Running)
605                         {
606                                 qWarning("Nero process time out -> killing!");
607                                 process.kill();
608                                 process.waitForFinished(-1);
609                                 for(int i = 0; i < 3; i++) LAMEXP_DELETE(neroBin[i]);
610                                 return;
611                         }
612                 }
613
614                 while(process.canReadLine())
615                 {
616                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
617                         QStringList tokens = line.split(" ", QString::SkipEmptyParts, Qt::CaseInsensitive);
618                         int index1 = tokens.indexOf("Package");
619                         int index2 = tokens.indexOf("version:");
620                         if(index1 >= 0 && index2 >= 0 && index1 + 1 == index2 && index2 < tokens.count() - 1)
621                         {
622                                 QStringList versionTokens = tokens.at(index2 + 1).split(".", QString::SkipEmptyParts, Qt::CaseInsensitive);
623                                 if(versionTokens.count() == 4)
624                                 {
625                                         neroVersion = 0;
626                                         neroVersion += qMin(9, qMax(0, versionTokens.at(3).toInt()));
627                                         neroVersion += qMin(9, qMax(0, versionTokens.at(2).toInt())) * 10;
628                                         neroVersion += qMin(9, qMax(0, versionTokens.at(1).toInt())) * 100;
629                                         neroVersion += qMin(9, qMax(0, versionTokens.at(0).toInt())) * 1000;
630                                 }
631                         }
632                 }
633         }
634
635         if(!(neroVersion > 0))
636         {
637                 qWarning("Nero AAC version could not be determined -> AAC encoding support will be disabled!");
638                 for(int i = 0; i < 3; i++) LAMEXP_DELETE(neroBin[i]);
639                 return;
640         }
641         
642         for(int i = 0; i < 3; i++)
643         {
644                 lamexp_register_tool(neroFileInfo[i].fileName(), neroBin[i], neroVersion);
645         }
646 }
647
648 void InitializationThread::initFhgAac(void)
649 {
650         const QString appPath = QDir(QCoreApplication::applicationDirPath()).canonicalPath();
651         
652         QFileInfo fhgFileInfo[5];
653         fhgFileInfo[0] = QFileInfo(QString("%1/fhgaacenc.exe").arg(appPath));
654         fhgFileInfo[1] = QFileInfo(QString("%1/enc_fhgaac.dll").arg(appPath));
655         fhgFileInfo[2] = QFileInfo(QString("%1/nsutil.dll").arg(appPath));
656         fhgFileInfo[3] = QFileInfo(QString("%1/libmp4v2.dll").arg(appPath));
657         fhgFileInfo[4] = QFileInfo(QString("%1/libsndfile-1.dll").arg(appPath));
658         
659         bool fhgFilesFound = true;
660         for(int i = 0; i < 5; i++)      { if(!fhgFileInfo[i].exists()) fhgFilesFound = false; }
661
662         if(!fhgFilesFound)
663         {
664                 qDebug("FhgAacEnc binaries not found -> FhgAacEnc support will be disabled!\n");
665                 return;
666         }
667
668         if(!lamexp_is_executable(fhgFileInfo[0].canonicalFilePath()))
669         {
670                 qDebug("FhgAacEnc executbale is invalid -> FhgAacEnc support will be disabled!\n");
671                 return;
672         }
673
674         qDebug("Found FhgAacEnc cli_exe:\n%s\n", QUTF8(fhgFileInfo[0].canonicalFilePath()));
675         qDebug("Found FhgAacEnc enc_dll:\n%s\n", QUTF8(fhgFileInfo[1].canonicalFilePath()));
676
677         //Lock the FhgAacEnc binaries
678         LockedFile *fhgBin[5];
679         for(int i = 0; i < 5; i++) fhgBin[i] = NULL;
680
681         try
682         {
683                 for(int i = 0; i < 5; i++)
684                 {
685                         fhgBin[i] = new LockedFile(fhgFileInfo[i].canonicalFilePath());
686                 }
687         }
688         catch(...)
689         {
690                 for(int i = 0; i < 5; i++) LAMEXP_DELETE(fhgBin[i]);
691                 qWarning("Failed to get excluive lock to FhgAacEnc binary -> FhgAacEnc support will be disabled!");
692                 return;
693         }
694
695         QProcess process;
696         lamexp_init_process(process, fhgFileInfo[0].absolutePath());
697
698         process.start(fhgFileInfo[0].canonicalFilePath(), QStringList() << "--version");
699
700         if(!process.waitForStarted())
701         {
702                 qWarning("FhgAacEnc process failed to create!");
703                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
704                 process.kill();
705                 process.waitForFinished(-1);
706                 for(int i = 0; i < 5; i++) LAMEXP_DELETE(fhgBin[i]);
707                 return;
708         }
709
710         QRegExp fhgAacEncSig("fhgaacenc version (\\d+) by tmkk", Qt::CaseInsensitive);
711         unsigned int fhgVersion = 0;
712
713         while(process.state() != QProcess::NotRunning)
714         {
715                 process.waitForReadyRead();
716                 if(!process.bytesAvailable() && process.state() == QProcess::Running)
717                 {
718                         qWarning("FhgAacEnc process time out -> killing!");
719                         process.kill();
720                         process.waitForFinished(-1);
721                         for(int i = 0; i < 5; i++) LAMEXP_DELETE(fhgBin[i]);
722                         return;
723                 }
724                 while(process.bytesAvailable() > 0)
725                 {
726                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
727                         if(fhgAacEncSig.lastIndexIn(line) >= 0)
728                         {
729                                 bool ok = false;
730                                 unsigned int temp = fhgAacEncSig.cap(1).toUInt(&ok);
731                                 if(ok) fhgVersion = temp;
732                         }
733                 }
734         }
735
736         if(!(fhgVersion > 0))
737         {
738                 qWarning("FhgAacEnc version couldn't be determined -> FhgAacEnc support will be disabled!");
739                 for(int i = 0; i < 5; i++) LAMEXP_DELETE(fhgBin[i]);
740                 return;
741         }
742         else if(fhgVersion < lamexp_toolver_fhgaacenc())
743         {
744                 qWarning("FhgAacEnc version is too much outdated (%s) -> FhgAacEnc support will be disabled!", lamexp_version2string("????-??-??", fhgVersion, "N/A").toLatin1().constData());
745                 qWarning("Minimum required FhgAacEnc version currently is: %s\n", lamexp_version2string("????-??-??", lamexp_toolver_fhgaacenc(), "N/A").toLatin1().constData());
746                 for(int i = 0; i < 5; i++) LAMEXP_DELETE(fhgBin[i]);
747                 return;
748         }
749         
750         for(int i = 0; i < 5; i++)
751         {
752                 lamexp_register_tool(fhgFileInfo[i].fileName(), fhgBin[i], fhgVersion);
753         }
754 }
755
756 void InitializationThread::initQAac(void)
757 {
758         const QString appPath = QDir(QCoreApplication::applicationDirPath()).canonicalPath();
759
760         QFileInfo qaacFileInfo[4];
761         qaacFileInfo[0] = QFileInfo(QString("%1/qaac.exe").arg(appPath));
762         qaacFileInfo[1] = QFileInfo(QString("%1/libsoxr.dll").arg(appPath));
763         qaacFileInfo[2] = QFileInfo(QString("%1/libsoxconvolver.dll").arg(appPath));
764         qaacFileInfo[3] = QFileInfo(QString("%1/libgcc_s_sjlj-1.dll").arg(appPath));
765         
766         bool qaacFilesFound = true;
767         for(int i = 0; i < 4; i++)      { if(!qaacFileInfo[i].exists()) qaacFilesFound = false; }
768
769         if(!qaacFilesFound)
770         {
771                 qDebug("QAAC binary or companion DLL's not found -> QAAC support will be disabled!\n");
772                 return;
773         }
774
775         if(!lamexp_is_executable(qaacFileInfo[0].canonicalFilePath()))
776         {
777                 qDebug("QAAC executbale is invalid -> QAAC support will be disabled!\n");
778                 return;
779         }
780
781         qDebug("Found QAAC encoder:\n%s\n", QUTF8(qaacFileInfo[0].canonicalFilePath()));
782
783         //Lock the required QAAC binaries
784         LockedFile *qaacBin[4];
785         for(int i = 0; i < 4; i++) qaacBin[i] = NULL;
786
787         try
788         {
789                 for(int i = 0; i < 4; i++)
790                 {
791                         qaacBin[i] = new LockedFile(qaacFileInfo[i].canonicalFilePath());
792                 }
793         }
794         catch(...)
795         {
796                 for(int i = 0; i < 4; i++) LAMEXP_DELETE(qaacBin[i]);
797                 qWarning("Failed to get excluive lock to QAAC binary -> QAAC support will be disabled!");
798                 return;
799         }
800
801         QProcess process;
802         lamexp_init_process(process, qaacFileInfo[0].absolutePath());
803
804         process.start(qaacFileInfo[0].canonicalFilePath(), QStringList() << "--check");
805
806         if(!process.waitForStarted())
807         {
808                 qWarning("QAAC process failed to create!");
809                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
810                 process.kill();
811                 process.waitForFinished(-1);
812                 for(int i = 0; i < 4; i++) LAMEXP_DELETE(qaacBin[i]);
813                 return;
814         }
815
816         QRegExp qaacEncSig("qaac (\\d)\\.(\\d)(\\d)", Qt::CaseInsensitive);
817         QRegExp coreEncSig("CoreAudioToolbox (\\d)\\.(\\d)\\.(\\d)\\.(\\d)", Qt::CaseInsensitive);
818         QRegExp soxrEncSig("libsoxr-\\d\\.\\d\\.\\d", Qt::CaseInsensitive);
819         QRegExp soxcEncSig("libsoxconvolver \\d\\.\\d\\.\\d", Qt::CaseInsensitive);
820
821         unsigned int qaacVersion = 0;
822         unsigned int coreVersion = 0;
823         bool soxrFound = false;
824         bool soxcFound = false;
825
826         while(process.state() != QProcess::NotRunning)
827         {
828                 process.waitForReadyRead();
829                 if(!process.bytesAvailable() && process.state() == QProcess::Running)
830                 {
831                         qWarning("QAAC process time out -> killing!");
832                         process.kill();
833                         process.waitForFinished(-1);
834                 for(int i = 0; i < 4; i++) LAMEXP_DELETE(qaacBin[i]);
835                         return;
836                 }
837                 while(process.bytesAvailable() > 0)
838                 {
839                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
840                         if(qaacEncSig.lastIndexIn(line) >= 0)
841                         {
842                                 unsigned int tmp[3] = {0, 0, 0};
843                                 bool ok[3] = {false, false, false};
844                                 tmp[0] = qaacEncSig.cap(1).toUInt(&ok[0]);
845                                 tmp[1] = qaacEncSig.cap(2).toUInt(&ok[1]);
846                                 tmp[2] = qaacEncSig.cap(3).toUInt(&ok[2]);
847                                 if(ok[0] && ok[1] && ok[2])
848                                 {
849                                         qaacVersion = (qBound(0U, tmp[0], 9U) * 100) + (qBound(0U, tmp[1], 9U) * 10) + qBound(0U, tmp[2], 9U);
850                                 }
851                         }
852                         if(coreEncSig.lastIndexIn(line) >= 0)
853                         {
854                                 unsigned int tmp[4] = {0, 0, 0, 0};
855                                 bool ok[4] = {false, false, false, false};
856                                 tmp[0] = coreEncSig.cap(1).toUInt(&ok[0]);
857                                 tmp[1] = coreEncSig.cap(2).toUInt(&ok[1]);
858                                 tmp[2] = coreEncSig.cap(3).toUInt(&ok[2]);
859                                 tmp[3] = coreEncSig.cap(4).toUInt(&ok[3]);
860                                 if(ok[0] && ok[1] && ok[2] && ok[3])
861                                 {
862                                         coreVersion = (qBound(0U, tmp[0], 9U) * 1000) + (qBound(0U, tmp[1], 9U) * 100) + (qBound(0U, tmp[2], 9U) * 10) + qBound(0U, tmp[3], 9U);
863                                 }
864                         }
865                         if(soxcEncSig.lastIndexIn(line) >= 0) { soxcFound = true; }
866                         if(soxrEncSig.lastIndexIn(line) >= 0) { soxrFound = true; }
867                 }
868         }
869
870         //qDebug("qaac %d, CoreAudioToolbox %d", qaacVersion, coreVersion);
871
872         if(!(qaacVersion > 0))
873         {
874                 qWarning("QAAC version couldn't be determined -> QAAC support will be disabled!");
875                 for(int i = 0; i < 4; i++) LAMEXP_DELETE(qaacBin[i]);
876                 return;
877         }
878         else if(qaacVersion < lamexp_toolver_qaacenc())
879         {
880                 qWarning("QAAC version is too much outdated (%s) -> QAAC support will be disabled!", lamexp_version2string("v?.??", qaacVersion, "N/A").toLatin1().constData());
881                 qWarning("Minimum required QAAC version currently is: %s.\n", lamexp_version2string("v?.??", lamexp_toolver_qaacenc(), "N/A").toLatin1().constData());
882                 for(int i = 0; i < 4; i++) LAMEXP_DELETE(qaacBin[i]);
883                 return;
884         }
885
886         if(!(coreVersion > 0))
887         {
888                 qWarning("CoreAudioToolbox version couldn't be determined -> QAAC support will be disabled!");
889                 for(int i = 0; i < 4; i++) LAMEXP_DELETE(qaacBin[i]);
890                 return;
891         }
892         else if(coreVersion < lamexp_toolver_coreaudio())
893         {
894                 qWarning("CoreAudioToolbox version is too much outdated (%s) -> QAAC support will be disabled!", lamexp_version2string("v?.?.?.?", coreVersion, "N/A").toLatin1().constData());
895                 qWarning("Minimum required CoreAudioToolbox version currently is: %s.\n", lamexp_version2string("v?.??", lamexp_toolver_coreaudio(), "N/A").toLatin1().constData());
896                 for(int i = 0; i < 4; i++) LAMEXP_DELETE(qaacBin[i]);
897                 return;
898         }
899
900         if(!(soxrFound && soxcFound))
901         {
902                 qWarning("libsoxr and/or libsoxconvolver not available -> QAAC support will be disabled!\n");
903                 return;
904         }
905
906         lamexp_register_tool(qaacFileInfo[0].fileName(), qaacBin[0], qaacVersion);
907         lamexp_register_tool(qaacFileInfo[1].fileName(), qaacBin[1], qaacVersion);
908         lamexp_register_tool(qaacFileInfo[2].fileName(), qaacBin[2], qaacVersion);
909         lamexp_register_tool(qaacFileInfo[3].fileName(), qaacBin[3], qaacVersion);
910 }
911
912 void InitializationThread::selfTest(void)
913 {
914         const unsigned int cpu[4] = {CPU_TYPE_X86_GEN, CPU_TYPE_X86_SSE, CPU_TYPE_X64_GEN, CPU_TYPE_X64_SSE};
915
916         LockedFile::selfTest();
917
918         for(size_t k = 0; k < 4; k++)
919         {
920                 qDebug("[TEST]");
921                 switch(cpu[k])
922                 {
923                         PRINT_CPU_TYPE(CPU_TYPE_X86_GEN); break;
924                         PRINT_CPU_TYPE(CPU_TYPE_X86_SSE); break;
925                         PRINT_CPU_TYPE(CPU_TYPE_X64_GEN); break;
926                         PRINT_CPU_TYPE(CPU_TYPE_X64_SSE); break;
927                         default: THROW("CPU support undefined!");
928                 }
929                 unsigned int n = 0;
930                 for(int i = 0; true; i++)
931                 {
932                         if(!(g_lamexp_tools[i].pcName || g_lamexp_tools[i].pcHash  || g_lamexp_tools[i].uiVersion))
933                         {
934                                 break;
935                         }
936                         else if(g_lamexp_tools[i].pcName && g_lamexp_tools[i].pcHash && g_lamexp_tools[i].uiVersion)
937                         {
938                                 const QString toolName = QString::fromLatin1(g_lamexp_tools[i].pcName);
939                                 const QByteArray expectedHash = QByteArray(g_lamexp_tools[i].pcHash);
940                                 if(g_lamexp_tools[i].uiCpuType & cpu[k])
941                                 {
942                                         qDebug("%02i -> %s", ++n, QUTF8(toolName));
943                                         QFile resource(QString(":/tools/%1").arg(toolName));
944                                         if(!resource.open(QIODevice::ReadOnly))
945                                         {
946                                                 qFatal("The resource for \"%s\" could not be opened!", QUTF8(toolName));
947                                                 break;
948                                         }
949                                         QByteArray hash = LockedFile::fileHash(resource);
950                                         if(hash.isNull() || _stricmp(hash.constData(), expectedHash.constData()))
951                                         {
952                                                 qFatal("Hash check for tool \"%s\" has failed!", QUTF8(toolName));
953                                                 break;
954                                         }
955                                         resource.close();
956                                 }
957                         }
958                         else
959                         {
960                                 qFatal("Inconsistent checksum data detected. Take care!");
961                         }
962                 }
963                 if(n != EXPECTED_TOOL_COUNT)
964                 {
965                         qFatal("Tool count mismatch for CPU type %u !!!", cpu[k]);
966                 }
967                 qDebug("Done.\n");
968         }
969 }
970
971 ////////////////////////////////////////////////////////////
972 // EVENTS
973 ////////////////////////////////////////////////////////////
974
975 /*NONE*/