From c4aab97504ec1ce79853c4a86332f778f1b612fd Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sat, 3 Dec 2016 02:34:10 +0100 Subject: [PATCH 01/20] Initial implementation of multithreaded analysis --- build/depends.py | 7 +- src/analyzer/analyzerbeats.cpp | 7 +- src/analyzer/analyzerbeats.h | 3 +- src/analyzer/analyzermanager.cpp | 206 ++++++++++++++++++++ src/analyzer/analyzermanager.h | 62 ++++++ src/analyzer/analyzerqueue.cpp | 10 +- src/analyzer/analyzerwaveform.cpp | 8 +- src/analyzer/analyzerwaveform.h | 4 +- src/analyzer/analyzerworker.cpp | 307 ++++++++++++++++++++++++++++++ src/analyzer/analyzerworker.h | 104 ++++++++++ src/library/analysisfeature.cpp | 53 ++---- src/library/analysisfeature.h | 4 - src/mixer/basetrackplayer.cpp | 1 - src/mixer/playermanager.cpp | 39 ++-- src/mixer/playermanager.h | 2 - src/mixxx.cpp | 2 +- 16 files changed, 739 insertions(+), 80 deletions(-) create mode 100644 src/analyzer/analyzermanager.cpp create mode 100644 src/analyzer/analyzermanager.h create mode 100644 src/analyzer/analyzerworker.cpp create mode 100644 src/analyzer/analyzerworker.h diff --git a/build/depends.py b/build/depends.py index a0539700921..7b892c3dc86 100644 --- a/build/depends.py +++ b/build/depends.py @@ -240,10 +240,10 @@ def enabled_modules(build): def enabled_imageformats(build): qt5 = Qt.qt5_enabled(build) qt_imageformats = [ - 'qgif', 'qico', 'qjpeg', 'qmng', 'qtga', 'qtiff', 'qsvg' + 'qgif', 'qico', 'qjpeg', 'qtga', 'qtiff', 'qsvg' ] if qt5: - qt_imageformats.extend(['qdds', 'qicns', 'qjp2', 'qwbmp', 'qwebp']) + qt_imageformats.extend(['qdds', 'qicns', 'qwbmp', 'qwebp']) return qt_imageformats def satisfy(self): @@ -737,7 +737,8 @@ def sources(self, build): "engine/cachingreaderchunk.cpp", "engine/cachingreaderworker.cpp", - "analyzer/analyzerqueue.cpp", + "analyzer/analyzermanager.cpp", + "analyzer/analyzerworker.cpp", "analyzer/analyzerwaveform.cpp", "analyzer/analyzergain.cpp", "analyzer/analyzerebur128.cpp", diff --git a/src/analyzer/analyzerbeats.cpp b/src/analyzer/analyzerbeats.cpp index 39e6bc0710d..6804ccccaa5 100644 --- a/src/analyzer/analyzerbeats.cpp +++ b/src/analyzer/analyzerbeats.cpp @@ -17,7 +17,7 @@ #include "track/beatutils.h" #include "track/track.h" -AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig) +AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool batch) : m_pConfig(pConfig), m_pVamp(NULL), m_bPreferencesReanalyzeOldBpm(false), @@ -27,7 +27,8 @@ AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig) m_iSampleRate(0), m_iTotalSamples(0), m_iMinBpm(0), - m_iMaxBpm(9999) { + m_iMaxBpm(9999), + m_batch(batch) { } AnalyzerBeats::~AnalyzerBeats() { @@ -38,7 +39,7 @@ bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSample return false; } - bool bPreferencesBeatDetectionEnabled = static_cast( + bool bPreferencesBeatDetectionEnabled = m_batch || static_cast( m_pConfig->getValueString( ConfigKey(BPM_CONFIG_KEY, BPM_DETECTION_ENABLED)).toInt()); if (!bPreferencesBeatDetectionEnabled) { diff --git a/src/analyzer/analyzerbeats.h b/src/analyzer/analyzerbeats.h index 0de0d4e4167..dff4c94bb75 100644 --- a/src/analyzer/analyzerbeats.h +++ b/src/analyzer/analyzerbeats.h @@ -16,7 +16,7 @@ class AnalyzerBeats: public Analyzer { public: - AnalyzerBeats(UserSettingsPointer pConfig); + AnalyzerBeats(UserSettingsPointer pConfig, bool batch); virtual ~AnalyzerBeats(); bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override; @@ -40,6 +40,7 @@ class AnalyzerBeats: public Analyzer { int m_iSampleRate, m_iTotalSamples; int m_iMinBpm, m_iMaxBpm; + bool m_batch; }; #endif /* ANALYZER_ANALYZERBEATS_H */ diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp new file mode 100644 index 00000000000..bb1e47b8d82 --- /dev/null +++ b/src/analyzer/analyzermanager.cpp @@ -0,0 +1,206 @@ +#include "analyzer/analyzermanager.h" + +#include +#include + +#include +#include +#include + +#include "library/trackcollection.h" +#include "mixer/playerinfo.h" +#include "track/track.h" +#include "util/compatibility.h" +#include "util/event.h" +#include "util/timer.h" +#include "util/trace.h" + +AnalyzerManager* AnalyzerManager::m_pAnalyzerManager = NULL; + +//static +AnalyzerManager& AnalyzerManager::getInstance(UserSettingsPointer pConfig) { + if (!m_pAnalyzerManager) { + // There exists only one UserSettingsPointer in the app, so it doens't matter + // if we only assign it once. + m_pAnalyzerManager = new AnalyzerManager(pConfig); + } + int maxThreads = m_pAnalyzerManager->m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); + if (maxThreads < 0 || maxThreads > 2 * QThread::idealThreadCount()) { + //Assume the value is incorrect, so fix it. + maxThreads = QThread::idealThreadCount(); + m_pAnalyzerManager->m_pConfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), maxThreads); + } + if (maxThreads == 0) { + maxThreads = QThread::idealThreadCount(); + } + m_pAnalyzerManager->m_MaxThreads = maxThreads; + return *m_pAnalyzerManager; +} + +AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) : + m_pConfig(pConfig), + m_batchTrackQueue(), + m_prioTrackQueue(), + m_backgroundWorkers(), + m_foregroundWorkers(), + m_pausedWorkers() { +} + +AnalyzerManager::~AnalyzerManager() { + stop(true); +} + +bool AnalyzerManager::isActive(bool includeForeground) { + //I don't count foregroundWorkers because + int total = (includeForeground ? m_foregroundWorkers.size() : 0) + + m_backgroundWorkers.size() + m_pausedWorkers.size(); + return total > 0; +} + +void AnalyzerManager::stop(bool shutdown) { + QListIterator it(m_backgroundWorkers); + while (it.hasNext()) { + AnalyzerWorker* worker = it.next(); + worker->endProcess(); + } + QListIterator it3(m_pausedWorkers); + while (it3.hasNext()) { + AnalyzerWorker* worker = it3.next(); + worker->endProcess(); + } + if (shutdown) { + QListIterator it2(m_foregroundWorkers); + while (it2.hasNext()) { + AnalyzerWorker* worker = it2.next(); + worker->endProcess(); + } + //TODO: ensure that they are all forcibly stopped. + } +} +// Analyze it with a foreground worker. (foreground as in interactive, i.e. not a batch worker). +void AnalyzerManager::analyseTrackNow(TrackPointer tio) { + if (m_batchTrackQueue.contains(tio)) { + m_batchTrackQueue.removeAll(tio); + } + if (!m_prioTrackQueue.contains(tio)) { + m_prioTrackQueue.append(tio); + if (m_foregroundWorkers.size() < m_MaxThreads) { + createNewWorker(false); + if (m_foregroundWorkers.size() + m_backgroundWorkers.size() > m_MaxThreads) { + AnalyzerWorker * backwork = m_backgroundWorkers.first(); + backwork->pause(); + //Ideally i would have done this on the slotPaused slot, but then i cannot + //ensure i won't call pause twice for the same worker. + m_pausedWorkers.append(backwork); + m_backgroundWorkers.removeAll(backwork); + } + } + } +} +// This is called from the GUI for batch analysis. +void AnalyzerManager::queueAnalyseTrack(TrackPointer tio) { + if (!m_batchTrackQueue.contains(tio)) { + m_batchTrackQueue.append(tio); + if (m_pausedWorkers.size() + m_backgroundWorkers.size() < m_MaxThreads) { + createNewWorker(true); + } + } +} + +// This slot is called from the decks and samplers when the track is loaded. +void AnalyzerManager::slotAnalyseTrack(TrackPointer tio) { + analyseTrackNow(tio); +} + + +//slot +void AnalyzerManager::slotUpdateProgress(struct AnalyzerWorker::progress_info* progressInfo) { + if (progressInfo->current_track) { + progressInfo->current_track->setAnalyzerProgress( + progressInfo->track_progress); + if (progressInfo->track_progress == 1000) { + emit(trackDone(progressInfo->current_track)); + } + progressInfo->current_track.clear(); + } + emit(trackProgress(progressInfo->track_progress / 10)); + if (progressInfo->track_progress == 1000) { + emit(trackFinished(m_backgroundWorkers.size() + m_batchTrackQueue.size() - 1)); + } + progressInfo->sema.release(); +} + +void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { + //The while loop is done in the event that the track which was added to the queue is no + //longer available. + + //TODO: The old scan checked in isLoadedTrackWaiting for pTrack->getAnalyzerProgress() + // and either tried to load a previuos scan, or discarded the track if it had already been + // analyzed. I don't fully understand the scenario and I am not doing that right now. + TrackPointer track = TrackPointer(); + if (worker->isBatch()) { + while (!track && !m_batchTrackQueue.isEmpty()) { + track = m_batchTrackQueue.dequeue(); + } + } + else { + while (!track && !m_prioTrackQueue.isEmpty()) { + track = m_prioTrackQueue.dequeue(); + } + } + if (track) { + worker->nextTrack(track); + } + else { + worker->endProcess(); + if (!m_pausedWorkers.isEmpty()) { + AnalyzerWorker* otherworker = m_pausedWorkers.first(); + otherworker->resume(); + m_pausedWorkers.removeOne(otherworker); + } + } + //Check if all queues are empty. + if (!isActive(false)) { + emit(queueEmpty()); + } +} +void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) { + m_backgroundWorkers.removeAll(worker); + m_foregroundWorkers.removeAll(worker); + m_pausedWorkers.removeAll(worker); +} +void AnalyzerManager::slotPaused(AnalyzerWorker* worker) { + //No useful code to execute right now. +} +void AnalyzerManager::slotErrorString(QString errMsg) { + //TODO: This is currently unused. + qWarning() << "Testing with :" << errMsg; +} + +AnalyzerWorker* AnalyzerManager::createNewWorker(bool batchJob) { + QThread* thread = new QThread(); + AnalyzerWorker* worker = new AnalyzerWorker(m_pConfig, batchJob); + worker->moveToThread(thread); + //Auto startup and auto cleanup of worker and thread. + connect(thread, SIGNAL(started()), worker, SLOT(slotProcess())); + connect(worker, SIGNAL(finished()), thread, SLOT(quit())); + connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + //Connect with manager. + connect(worker, SIGNAL(updateProgress(struct AnalyzerWorker::progress_info*)), this, SLOT(slotUpdateProgress(struct AnalyzerWorker::progress_info*))); + connect(worker, SIGNAL(waitingForNextTrack(AnalyzerWorker*)), this, SLOT(slotNextTrack(AnalyzerWorker*))); + connect(worker, SIGNAL(paused(AnalyzerWorker*)), this, SLOT(slotPaused(AnalyzerWorker*))); + connect(worker, SIGNAL(workerFinished(AnalyzerWorker*)), this, SLOT(slotWorkerFinished(AnalyzerWorker*))); + connect(worker, SIGNAL(error(QString)), this, SLOT(slotErrorString(QString))); + thread->start(); + if (batchJob) { + m_backgroundWorkers.append(worker); + } + else { + m_foregroundWorkers.append(worker); + } + return worker; +} + + + diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h new file mode 100644 index 00000000000..c30e98c19c3 --- /dev/null +++ b/src/analyzer/analyzermanager.h @@ -0,0 +1,62 @@ +#ifndef ANALYZER_ANALYZERMANAGER_H +#define ANALYZER_ANALYZERMANAGER_H + +#include +#include + +#include + +#include "analyzer/analyzerworker.h" +#include "preferences/usersettings.h" +#include "track/track.h" + +class TrackCollection; + +class AnalyzerManager : public QObject { + Q_OBJECT + +protected: + AnalyzerManager(UserSettingsPointer pConfig); + +public: + static AnalyzerManager& getInstance(UserSettingsPointer pConfig); + virtual ~AnalyzerManager(); + + void stop(bool shutdown); + //This method might need to be protected an called only via slot. + void analyseTrackNow(TrackPointer tio); + void queueAnalyseTrack(TrackPointer tio); + bool isActive(bool includeForeground); + + +public slots: + // This slot is called from the decks and samplers when the track is loaded. + void slotAnalyseTrack(TrackPointer tio); + void slotUpdateProgress(struct AnalyzerWorker::progress_info*); + void slotNextTrack(AnalyzerWorker*); + void slotPaused(AnalyzerWorker*); + void slotWorkerFinished(AnalyzerWorker*); + void slotErrorString(QString); + +signals: + void trackProgress(int progress); + void trackDone(TrackPointer track); + void trackFinished(int size); + void queueEmpty(); +private: + + AnalyzerWorker* createNewWorker(bool batchJob); + + static AnalyzerManager* m_pAnalyzerManager; + UserSettingsPointer m_pConfig; + int m_MaxThreads; + // The processing queue and associated mutex + QQueue m_batchTrackQueue; + QQueue m_prioTrackQueue; + + QList m_backgroundWorkers; + QList m_foregroundWorkers; + QList m_pausedWorkers; +}; + +#endif /* ANALYZER_ANALYZERMANAGER_H */ diff --git a/src/analyzer/analyzerqueue.cpp b/src/analyzer/analyzerqueue.cpp index dda1826bc2e..e2ac0fe5caf 100644 --- a/src/analyzer/analyzerqueue.cpp +++ b/src/analyzer/analyzerqueue.cpp @@ -439,12 +439,12 @@ AnalyzerQueue* AnalyzerQueue::createDefaultAnalyzerQueue( UserSettingsPointer pConfig, TrackCollection* pTrackCollection) { AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection); - ret->addAnalyzer(new AnalyzerWaveform(pConfig)); + ret->addAnalyzer(new AnalyzerWaveform(pConfig, false)); ret->addAnalyzer(new AnalyzerGain(pConfig)); ret->addAnalyzer(new AnalyzerEbur128(pConfig)); #ifdef __VAMP__ VampAnalyzer::initializePluginPaths(); - ret->addAnalyzer(new AnalyzerBeats(pConfig)); + ret->addAnalyzer(new AnalyzerBeats(pConfig, false)); ret->addAnalyzer(new AnalyzerKey(pConfig)); #endif @@ -457,14 +457,12 @@ AnalyzerQueue* AnalyzerQueue::createAnalysisFeatureAnalyzerQueue( UserSettingsPointer pConfig, TrackCollection* pTrackCollection) { AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection); - if (pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) { - ret->addAnalyzer(new AnalyzerWaveform(pConfig)); - } + ret->addAnalyzer(new AnalyzerWaveform(pConfig, true)); ret->addAnalyzer(new AnalyzerGain(pConfig)); ret->addAnalyzer(new AnalyzerEbur128(pConfig)); #ifdef __VAMP__ VampAnalyzer::initializePluginPaths(); - ret->addAnalyzer(new AnalyzerBeats(pConfig)); + ret->addAnalyzer(new AnalyzerBeats(pConfig, true)); ret->addAnalyzer(new AnalyzerKey(pConfig)); #endif diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index ac125c2e8f4..e07420971b9 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -10,8 +10,10 @@ #include "track/track.h" #include "waveform/waveformfactory.h" -AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig) : +AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig, bool batch) : m_skipProcessing(false), + m_batch(batch), + m_pConfig(pConfig), m_waveformData(nullptr), m_waveformSummaryData(nullptr), m_stride(0, 0), @@ -104,6 +106,10 @@ bool AnalyzerWaveform::initialize(TrackPointer tio, int sampleRate, int totalSam } bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { + if (m_batch && m_pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) { + return true; + } + ConstWaveformPointer pTrackWaveform = tio->getWaveform(); ConstWaveformPointer pTrackWaveformSummary = tio->getWaveformSummary(); ConstWaveformPointer pLoadedTrackWaveform; diff --git a/src/analyzer/analyzerwaveform.h b/src/analyzer/analyzerwaveform.h index 912436c3297..e54ac34242e 100644 --- a/src/analyzer/analyzerwaveform.h +++ b/src/analyzer/analyzerwaveform.h @@ -140,7 +140,7 @@ struct WaveformStride { class AnalyzerWaveform : public Analyzer { public: - AnalyzerWaveform(UserSettingsPointer pConfig); + AnalyzerWaveform(UserSettingsPointer pConfig, bool batch); virtual ~AnalyzerWaveform(); bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override; @@ -158,6 +158,8 @@ class AnalyzerWaveform : public Analyzer { void storeIfGreater(float* pDest, float source); bool m_skipProcessing; + bool m_batch; + UserSettingsPointer m_pConfig; WaveformPointer m_waveform; WaveformPointer m_waveformSummary; diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp new file mode 100644 index 00000000000..824c32079a3 --- /dev/null +++ b/src/analyzer/analyzerworker.cpp @@ -0,0 +1,307 @@ +#include "analyzer/analyzerworker.h" + +#include + +#include +#include + +#ifdef __VAMP__ +#include "analyzer/analyzerbeats.h" +#include "analyzer/analyzerkey.h" +#include "analyzer/vamp/vampanalyzer.h" +#endif +#include "analyzer/analyzergain.h" +#include "analyzer/analyzerebur128.h" +#include "analyzer/analyzerwaveform.h" + +#include "sources/soundsourceproxy.h" +#include "util/compatibility.h" +#include "util/event.h" +#include "util/timer.h" +#include "util/trace.h" + +namespace { + // Analysis is done in blocks. + // We need to use a smaller block size, because on Linux the AnalyzerWorker + // can starve the CPU of its resources, resulting in xruns. A block size + // of 4096 frames per block seems to do fine. + const SINT kAnalysisChannels = mixxx::AudioSource::kChannelCountStereo; + const SINT kAnalysisFramesPerBlock = 4096; + const SINT kAnalysisSamplesPerBlock = + kAnalysisFramesPerBlock * kAnalysisChannels; +} // anonymous namespace + +// Measured in 0.1%, +// 0 for no progress during finalize +// 1 to display the text "finalizing" +// 100 for 10% step after finalize +#define FINALIZE_PROMILLE 1 + +// --- CONSTRUCTOR --- +AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, bool batchJob) : + m_pConfig(pConfig), + m_analyzelist(), + m_batchJob(batchJob), + m_sampleBuffer(kAnalysisSamplesPerBlock), + m_exit(false), + m_pauseRequested(false), + m_qm(), + m_qwait() { +} + +// --- DESTRUCTOR --- +AnalyzerWorker::~AnalyzerWorker() { + qDebug << "Ending analyzer"; + // free resources + m_progressInfo.sema.release(); + + QListIterator it(m_analyzelist); + while (it.hasNext()) { + Analyzer* an = it.next(); + //qDebug() << "AnalyzerWorker: deleting " << typeid(an).name(); + delete an; + } +} + +void AnalyzerWorker::nextTrack(TrackPointer newTrack) { + m_qm.lock(); +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + m_currentTrack.clear(); +#else + m_currentTrack.reset(); +#endif + m_currentTrack = newTrack; + m_qwait.wakeAll(); + m_qm.unlock(); +} +void AnalyzerWorker::pause() { + m_pauseRequested = true; +} +void AnalyzerWorker::resume() { + m_qm.lock(); + m_qwait.wakeAll(); + m_qm.unlock(); +} + +void AnalyzerWorker::endProcess() { + m_exit = true; + m_qm.lock(); + m_qwait.wakeAll(); + m_qm.unlock(); +} + +// This is called from the AnalyzerWorker thread +bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAudioSource) { + + QTime progressUpdateInhibitTimer; + progressUpdateInhibitTimer.start(); // Inhibit Updates for 60 milliseconds + + SINT frameIndex = pAudioSource->getMinFrameIndex(); + bool dieflag = false; + bool cancelled = false; + + qDebug() << "Analyzing" << tio->getTitle() << tio->getLocation(); + do { + ScopedTimer t("AnalyzerWorker::doAnalysis block"); + + DEBUG_ASSERT(frameIndex < pAudioSource->getMaxFrameIndex()); + const SINT framesRemaining = + pAudioSource->getMaxFrameIndex() - frameIndex; + const SINT framesToRead = + math_min(kAnalysisFramesPerBlock, framesRemaining); + DEBUG_ASSERT(0 < framesToRead); + + const SINT framesRead = + pAudioSource->readSampleFramesStereo( + kAnalysisFramesPerBlock, + &m_sampleBuffer); + DEBUG_ASSERT(framesRead <= framesToRead); + frameIndex += framesRead; + DEBUG_ASSERT(pAudioSource->isValidFrameIndex(frameIndex)); + + // To compare apples to apples, let's only look at blocks that are + // the full block size. + if (kAnalysisFramesPerBlock == framesRead) { + // Complete analysis block of audio samples has been read. + QListIterator it(m_analyzelist); + while (it.hasNext()) { + Analyzer* an = it.next(); + //qDebug() << typeid(*an).name() << ".process()"; + an->process(m_sampleBuffer.data(), m_sampleBuffer.size()); + //qDebug() << "Done " << typeid(*an).name() << ".process()"; + } + } else { + // Partial analysis block of audio samples has been read. + // This should only happen at the end of an audio stream, + // otherwise a decoding error must have occurred. + if (frameIndex < pAudioSource->getMaxFrameIndex()) { + // EOF not reached -> Maybe a corrupt file? + qWarning() << "Failed to read sample data from file:" + << tio->getLocation() + << "@" << frameIndex; + if (0 >= framesRead) { + // If no frames have been read then abort the analysis. + // Otherwise we might get stuck in this loop forever. + dieflag = true; // abort + cancelled = false; // completed, no retry + } + } + } + + // emit progress updates + // During the doAnalysis function it goes only to 100% - FINALIZE_PERCENT + // because the finalize functions will take also some time + //fp div here prevents insane signed overflow + DEBUG_ASSERT(pAudioSource->isValidFrameIndex(frameIndex)); + const double frameProgress = + double(frameIndex) / double(pAudioSource->getMaxFrameIndex()); + int progressPromille = frameProgress * (1000 - FINALIZE_PROMILLE); + + if (m_progressInfo.track_progress != progressPromille && + progressUpdateInhibitTimer.elapsed() > 60) { + // Inhibit Updates for 60 milliseconds + emitUpdateProgress(progressPromille); + progressUpdateInhibitTimer.start(); + } + + // Since this is a background analysis queue, we should co-operatively + // yield every now and then to try and reduce CPU contention. The + // analyzer queue is CPU intensive so we want to get out of the way of + // the audio callback thread. + //QThread::yieldCurrentThread(); + + // When a priority analysis comes in, we pause a working thread until a new prioritized + // worker finishes. Once it finishes, this worker will get resumed. + if (m_pauseRequested.fetchAndStoreAcquire(false)) { + m_qm.lock(); + emit(paused(this)); + m_qwait.wait(&m_qm); + m_qm.unlock(); + } + + if (m_exit) { + dieflag = true; + cancelled = true; + } + + // Ignore blocks in which we decided to bail for stats purposes. + if (dieflag || cancelled) { + t.cancel(); + } + } while (!dieflag && (frameIndex < pAudioSource->getMaxFrameIndex())); + + return !cancelled; //don't return !dieflag or we might reanalyze over and over +} + +void AnalyzerWorker::slotProcess() { + unsigned static id = 0; // the id of this thread, for debugging purposes + QThread::currentThread()->setObjectName(QString("AnalyzerWorker %1").arg(++id)); + + //The instantiation of the initializers needs to be done on the worker thread. + //cannot be done on constructor. + createAnalyzers(); + m_progressInfo.sema.release(); + + while (!m_exit) { + //We emit waitingForNextTrack to inform that we're done and we need a new track. + m_qm.lock(); + emit(waitingForNextTrack(this)); + m_qwait.wait(&m_qm); + m_qm.unlock(); + // We recheck m_exit, since it's also the way that the manager indicates that there are no + // more tracks to process. + if (m_exit) { + break; + } + Event::start(QString("AnalyzerWorker %1 process").arg(id)); + Trace trace("AnalyzerWorker analyzing track"); + + // Get the audio + SoundSourceProxy soundSourceProxy(m_currentTrack); + mixxx::AudioSourceConfig audioSrcCfg; + audioSrcCfg.setChannelCount(kAnalysisChannels); + mixxx::AudioSourcePointer pAudioSource(soundSourceProxy.openAudioSource(audioSrcCfg)); + if (!pAudioSource) { + qWarning() << "Failed to open file for analyzing:" << m_currentTrack->getLocation(); + //TODO: maybe emit error("Failed to bblablalba"); + continue; + } + + QListIterator it(m_analyzelist); + bool processTrack = false; + while (it.hasNext()) { + // Make sure not to short-circuit initialize(...) + if (it.next()->initialize(m_currentTrack, pAudioSource->getSamplingRate(), pAudioSource->getFrameCount() * kAnalysisChannels)) { + processTrack = true; + } + } + + if (processTrack) { + emitUpdateProgress(0); + bool completed = doAnalysis(m_currentTrack, pAudioSource); + // We can end doAnalysis because of two causes: The analysis has finished + // or the analysis has been interrupted. + if (!completed) { + // This track was cancelled + QListIterator itf(m_analyzelist); + while (itf.hasNext()) { + itf.next()->cleanup(m_currentTrack); + } + emitUpdateProgress(0); + } else { + // 100% - FINALIZE_PERCENT finished + emitUpdateProgress(1000 - FINALIZE_PROMILLE); + // This takes around 3 sec on a Atom Netbook + QListIterator itf(m_analyzelist); + while (itf.hasNext()) { + itf.next()->finalize(m_currentTrack); + } + emitUpdateProgress(1000); // 100% + } + } else { + emitUpdateProgress(1000); // 100% + qDebug() << "Skipping track analysis because no analyzer initialized."; + } + Event::end(QString("AnalyzerWorker %1 process").arg(id)); + } + emit(workerFinished(this)); + emit(finished()); +} + +// This is called from the AnalyzerWorker thread +void AnalyzerWorker::emitUpdateProgress(int progress) { + if (!m_exit) { + // TODO: Since we are using a timer of 60 milliseconds in doAnalysis, we probably don't + // need the semaphore. On the other hand, the semaphore would be useful if it was the UI + // the one that requested us about updates. Then, the semaphore would prevent updates + // until the UI has rendered the last update. As it is, it only prevents sending another + // update if the analyzermanager slot hasn't read the update. (not the UI slot). + // --------- + // First tryAcqire will have always success because sema is initialized with on + // The following tries will success if the previous signal was processed in the GUI Thread + // This prevent the AnalysisQueue from filling up the GUI Thread event Queue + // 100 % is emitted in any case + if (progress < 1000 - FINALIZE_PROMILLE && progress > 0) { + // Signals during processing are not required in any case + if (!m_progressInfo.sema.tryAcquire()) { + return; + } + } else { + m_progressInfo.sema.acquire(); + } + m_progressInfo.current_track = m_currentTrack; + m_progressInfo.track_progress = progress; + emit(updateProgress(&m_progressInfo)); + } +} + +void AnalyzerWorker::createAnalyzers() { + m_analyzelist.append(new AnalyzerWaveform(m_pConfig, m_batchJob)); + m_analyzelist.append(new AnalyzerGain(m_pConfig)); + m_analyzelist.append(new AnalyzerEbur128(m_pConfig)); +#ifdef __VAMP__ + VampAnalyzer::initializePluginPaths(); + m_analyzelist.append(new AnalyzerBeats(m_pConfig, m_batchJob)); + m_analyzelist.append(new AnalyzerKey(m_pConfig)); +#endif +} diff --git a/src/analyzer/analyzerworker.h b/src/analyzer/analyzerworker.h new file mode 100644 index 00000000000..443584370d6 --- /dev/null +++ b/src/analyzer/analyzerworker.h @@ -0,0 +1,104 @@ +#ifndef ANALYZER_ANALYZERWORKER_H +#define ANALYZER_ANALYZERWORKER_H + +#include +#include +#include +#include + +#include + +#include "sources/audiosource.h" +#include "track/track.h" +#include "util/samplebuffer.h" +#include "preferences/usersettings.h" + + +class Analyzer; +class QThread; + + +/* +start_the_job{ +Worker* worker1 = new Worker(this); +worker1.addAnalyzer(new xxx); +connect(worker, SIGNAL(updateProgress(struct progress_info*)), this, SLOT(slotUpdateProgress(struct progress_info*))); +connect(worker, SIGNAL(waitingForNextTrack(AnalyzerWorker*)), this, SLOT(slotNextTrack(AnalyzerWorker*))); +connect(worker, SIGNAL(paused(AnalyzerWorker*)), this, SLOT(slotPaused(AnalyzerWorker*))); +connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString))); +worker1->start(); +} +*/ +class AnalyzerWorker : public QObject { + Q_OBJECT + +public: + struct progress_info { + TrackPointer current_track; + int track_progress; // in 0.1 % + QSemaphore sema; + }; + //Constructor. Qthread is passed only to connect several signals. It is not stored. + //pConfig is stored and batchJob indicates if this worker is a analysisfeature job (batch) + //or player job. + // Call Qthread->start() when you are ready for the worker to start. + AnalyzerWorker(UserSettingsPointer pConfig, bool batchJob); + virtual ~AnalyzerWorker(); + + //Called by the manager as a response to the waitingForNextTrack signal. and ONLY then. + void nextTrack(TrackPointer newTrack); + //called to pause this worker (waits on a qwaitcondition) + void pause(); + //resumes from a previous call to pause + void resume(); + //Tells this worker to end. It will delete itself and the Qthread. + // An updateProgress signal with progress 0 will be emited and also the finished signal + void endProcess(); + // Is this a batch worker? + bool isBatch(); + +public slots: + //Called automatically by the owning thread to start the process + void slotProcess(); + +signals: + //Signal that informs about the progress of the analysis. + void updateProgress(struct AnalyzerWorker::progress_info*); + //Signal emited to the manager in order to receive a new track. The manager should call nextTrack(); + void waitingForNextTrack(AnalyzerWorker* worker); + //Signal emited when the worker has effectively paused + void paused(AnalyzerWorker* worker); + //Signal emited when the worker ends and deletes itself. + void workerFinished(AnalyzerWorker* worker); + //Signal emited when this worker ends + void finished(); + //Currently this signal is unused. It might be useful in the future. + void error(QString err); + +private: + //Analyze one track. + bool doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAudioSource); + //helper function to emit the updateProgress signal + void emitUpdateProgress(int progress); + //helper function to create the analyzers. + void createAnalyzers(); + + UserSettingsPointer m_pConfig; + QList m_analyzelist; + bool m_batchJob; + SampleBuffer m_sampleBuffer; + TrackPointer m_currentTrack; + + bool m_exit; + QAtomicInt m_pauseRequested; + QMutex m_qm; + QWaitCondition m_qwait; + struct progress_info m_progressInfo; + +}; + +inline bool AnalyzerWorker::isBatch() { + return m_batchJob; +} + +#endif /* ANALYZER_ANALYZERWORKER_H */ diff --git a/src/library/analysisfeature.cpp b/src/library/analysisfeature.cpp index ab925a9a2b7..771c8683908 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysisfeature.cpp @@ -10,7 +10,7 @@ #include "library/dlganalysis.h" #include "widget/wlibrary.h" #include "controllers/keyboard/keyboardeventfilter.h" -#include "analyzer/analyzerqueue.h" +#include "analyzer/analyzermanager.h" #include "sources/soundsourceproxy.h" #include "util/dnd.h" #include "util/debug.h" @@ -23,8 +23,6 @@ AnalysisFeature::AnalysisFeature(QObject* parent, LibraryFeature(parent), m_pConfig(pConfig), m_pTrackCollection(pTrackCollection), - m_pAnalyzerQueue(NULL), - m_iOldBpmEnabled(0), m_analysisTitleName(tr("Analyze")), m_pAnalysisView(NULL) { setTitleDefault(); @@ -83,7 +81,7 @@ void AnalysisFeature::bindWidget(WLibrary* libraryWidget, m_pAnalysisView->installEventFilter(keyboard); // Let the DlgAnalysis know whether or not analysis is active. - bool bAnalysisActive = m_pAnalyzerQueue != NULL; + bool bAnalysisActive = AnalyzerManager::getInstance(m_pConfig).isActive(false); emit(analysisActive(bAnalysisActive)); libraryWidget->registerView(m_sAnalysisViewName, m_pAnalysisView); @@ -109,32 +107,25 @@ void AnalysisFeature::activate() { } void AnalysisFeature::analyzeTracks(QList trackIds) { - if (m_pAnalyzerQueue == NULL) { - // Save the old BPM detection prefs setting (on or off) - m_iOldBpmEnabled = m_pConfig->getValueString(ConfigKey("[BPM]","BPMDetectionEnabled")).toInt(); - // Force BPM detection to be on. - m_pConfig->set(ConfigKey("[BPM]","BPMDetectionEnabled"), ConfigValue(1)); - // Note: this sucks... we should refactor the prefs/analyzer to fix this hacky bit ^^^^. - - m_pAnalyzerQueue = AnalyzerQueue::createAnalysisFeatureAnalyzerQueue(m_pConfig, m_pTrackCollection); - - connect(m_pAnalyzerQueue, SIGNAL(trackProgress(int)), - m_pAnalysisView, SLOT(trackAnalysisProgress(int))); - connect(m_pAnalyzerQueue, SIGNAL(trackFinished(int)), - this, SLOT(slotProgressUpdate(int))); - connect(m_pAnalyzerQueue, SIGNAL(trackFinished(int)), - m_pAnalysisView, SLOT(trackAnalysisFinished(int))); - - connect(m_pAnalyzerQueue, SIGNAL(queueEmpty()), - this, SLOT(cleanupAnalyzer())); - emit(analysisActive(true)); - } + AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); + + connect(&analyzerManager, SIGNAL(trackProgress(int)), + m_pAnalysisView, SLOT(trackAnalysisProgress(int))); + connect(&analyzerManager, SIGNAL(trackFinished(int)), + this, SLOT(slotProgressUpdate(int))); + connect(&analyzerManager, SIGNAL(trackFinished(int)), + m_pAnalysisView, SLOT(trackAnalysisFinished(int))); + + connect(&analyzerManager, SIGNAL(queueEmpty()), + this, SLOT(cleanupAnalyzer())); + + emit(analysisActive(true)); for (const auto& trackId: trackIds) { TrackPointer pTrack = m_pTrackCollection->getTrackDAO().getTrack(trackId); if (pTrack) { //qDebug() << this << "Queueing track for analysis" << pTrack->getLocation(); - m_pAnalyzerQueue->queueAnalyseTrack(pTrack); + analyzerManager.queueAnalyseTrack(pTrack); } } if (trackIds.size() > 0) { @@ -153,21 +144,13 @@ void AnalysisFeature::slotProgressUpdate(int num_left) { void AnalysisFeature::stopAnalysis() { //qDebug() << this << "stopAnalysis()"; - if (m_pAnalyzerQueue != NULL) { - m_pAnalyzerQueue->stop(); - } + AnalyzerManager::getInstance(m_pConfig).stop(false); } void AnalysisFeature::cleanupAnalyzer() { setTitleDefault(); emit(analysisActive(false)); - if (m_pAnalyzerQueue != NULL) { - m_pAnalyzerQueue->stop(); - m_pAnalyzerQueue->deleteLater(); - m_pAnalyzerQueue = NULL; - // Restore old BPM detection setting for preferences... - m_pConfig->set(ConfigKey("[BPM]","BPMDetectionEnabled"), ConfigValue(m_iOldBpmEnabled)); - } + } bool AnalysisFeature::dropAccept(QList urls, QObject* pSource) { diff --git a/src/library/analysisfeature.h b/src/library/analysisfeature.h index bbdd853fc45..32437a386d5 100644 --- a/src/library/analysisfeature.h +++ b/src/library/analysisfeature.h @@ -17,7 +17,6 @@ #include "treeitemmodel.h" #include "library/dlganalysis.h" -class AnalyzerQueue; class TrackCollection; class AnalysisFeature : public LibraryFeature { @@ -64,9 +63,6 @@ class AnalysisFeature : public LibraryFeature { UserSettingsPointer m_pConfig; TrackCollection* m_pTrackCollection; - AnalyzerQueue* m_pAnalyzerQueue; - // Used to temporarily enable BPM detection in the prefs before we analyse - int m_iOldBpmEnabled; // The title returned by title() QVariant m_Title; TreeItemModel m_childModel; diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index d478fb3fb66..48e00df3edd 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -12,7 +12,6 @@ #include "engine/enginemaster.h" #include "track/beatgrid.h" #include "waveform/renderers/waveformwidgetrenderer.h" -#include "analyzer/analyzerqueue.h" #include "util/platform.h" #include "util/sandbox.h" #include "effects/effectsmanager.h" diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index 07fcf1d2ac3..74354a74550 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -4,7 +4,7 @@ #include -#include "analyzer/analyzerqueue.h" +#include "analyzer/analyzermanager.h" #include "control/controlobject.h" #include "control/controlobject.h" #include "effects/effectsmanager.h" @@ -35,7 +35,6 @@ PlayerManager::PlayerManager(UserSettingsPointer pConfig, m_pEngine(pEngine), // NOTE(XXX) LegacySkinParser relies on these controls being Controls // and not ControlProxies. - m_pAnalyzerQueue(NULL), m_pCONumDecks(new ControlObject( ConfigKey("[Master]", "num_decks"), true, true)), m_pCONumSamplers(new ControlObject( @@ -109,9 +108,6 @@ PlayerManager::~PlayerManager() { delete m_pCONumPreviewDecks; delete m_pCONumMicrophones; delete m_pCONumAuxiliaries; - if (m_pAnalyzerQueue) { - delete m_pAnalyzerQueue; - } } void PlayerManager::bindToLibrary(Library* pLibrary) { @@ -123,28 +119,27 @@ void PlayerManager::bindToLibrary(Library* pLibrary) { connect(this, SIGNAL(loadLocationToPlayer(QString, QString)), pLibrary, SLOT(slotLoadLocationToPlayer(QString, QString))); - m_pAnalyzerQueue = AnalyzerQueue::createDefaultAnalyzerQueue(m_pConfig, - pLibrary->getTrackCollection()); + AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); // Connect the player to the analyzer queue so that loaded tracks are // analysed. foreach(Deck* pDeck, m_decks) { connect(pDeck, SIGNAL(newTrackLoaded(TrackPointer)), - m_pAnalyzerQueue, SLOT(slotAnalyseTrack(TrackPointer))); + &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); } // Connect the player to the analyzer queue so that loaded tracks are // analysed. foreach(Sampler* pSampler, m_samplers) { connect(pSampler, SIGNAL(newTrackLoaded(TrackPointer)), - m_pAnalyzerQueue, SLOT(slotAnalyseTrack(TrackPointer))); + &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); } // Connect the player to the analyzer queue so that loaded tracks are // analysed. foreach(PreviewDeck* pPreviewDeck, m_preview_decks) { connect(pPreviewDeck, SIGNAL(newTrackLoaded(TrackPointer)), - m_pAnalyzerQueue, SLOT(slotAnalyseTrack(TrackPointer))); + &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); } } @@ -344,10 +339,10 @@ void PlayerManager::addDeckInner() { connect(pDeck, SIGNAL(noVinylControlInputConfigured()), this, SIGNAL(noVinylControlInputConfigured())); - if (m_pAnalyzerQueue) { - connect(pDeck, SIGNAL(newTrackLoaded(TrackPointer)), - m_pAnalyzerQueue, SLOT(slotAnalyseTrack(TrackPointer))); - } + AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); + connect(pDeck, SIGNAL(newTrackLoaded(TrackPointer)), + &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_players[group] = pDeck; m_decks.append(pDeck); @@ -398,10 +393,10 @@ void PlayerManager::addSamplerInner() { Sampler* pSampler = new Sampler(this, m_pConfig, m_pEngine, m_pEffectsManager, orientation, group); - if (m_pAnalyzerQueue) { - connect(pSampler, SIGNAL(newTrackLoaded(TrackPointer)), - m_pAnalyzerQueue, SLOT(slotAnalyseTrack(TrackPointer))); - } + + AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); + connect(pSampler, SIGNAL(newTrackLoaded(TrackPointer)), + &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); m_players[group] = pSampler; m_samplers.append(pSampler); @@ -426,10 +421,10 @@ void PlayerManager::addPreviewDeckInner() { PreviewDeck* pPreviewDeck = new PreviewDeck(this, m_pConfig, m_pEngine, m_pEffectsManager, orientation, group); - if (m_pAnalyzerQueue) { - connect(pPreviewDeck, SIGNAL(newTrackLoaded(TrackPointer)), - m_pAnalyzerQueue, SLOT(slotAnalyseTrack(TrackPointer))); - } + + AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); + connect(pPreviewDeck, SIGNAL(newTrackLoaded(TrackPointer)), + &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); m_players[group] = pPreviewDeck; m_preview_decks.append(pPreviewDeck); diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index ca5c3ae703a..f4903eb86e1 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -12,7 +12,6 @@ #include "preferences/usersettings.h" #include "track/track.h" -class AnalyzerQueue; class Auxiliary; class BaseTrackPlayer; class ControlObject; @@ -228,7 +227,6 @@ class PlayerManager : public QObject, public PlayerManagerInterface { SoundManager* m_pSoundManager; EffectsManager* m_pEffectsManager; EngineMaster* m_pEngine; - AnalyzerQueue* m_pAnalyzerQueue; ControlObject* m_pCONumDecks; ControlObject* m_pCONumSamplers; ControlObject* m_pCONumPreviewDecks; diff --git a/src/mixxx.cpp b/src/mixxx.cpp index d7511d7d478..6e13ae5a35d 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -24,7 +24,7 @@ #include #include -#include "analyzer/analyzerqueue.h" +#include "analyzer/analyzermanager.h" #include "dialog/dlgabout.h" #include "preferences/dialog/dlgpreferences.h" #include "preferences/dialog/dlgprefeq.h" From 4a2a142e10f860ca73dc0456823c908e8a70b5f1 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sat, 3 Dec 2016 16:24:12 +0100 Subject: [PATCH 02/20] Fixed end detection and updated signals to show multiple thread progress --- src/analyzer/analyzermanager.cpp | 45 +++++++++++++++++++++----------- src/analyzer/analyzermanager.h | 8 +++--- src/analyzer/analyzerqueue.cpp | 1 + src/analyzer/analyzerqueue.h | 1 + src/analyzer/analyzerworker.cpp | 15 ++++++----- src/analyzer/analyzerworker.h | 18 +++---------- src/library/analysisfeature.cpp | 6 ++--- src/library/dlganalysis.cpp | 24 ++++++++++++++--- src/library/dlganalysis.h | 4 ++- 9 files changed, 76 insertions(+), 46 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index bb1e47b8d82..78a4f14ef6a 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -39,6 +39,7 @@ AnalyzerManager& AnalyzerManager::getInstance(UserSettingsPointer pConfig) { AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) : m_pConfig(pConfig), + m_nextWorkerId(0), m_batchTrackQueue(), m_prioTrackQueue(), m_backgroundWorkers(), @@ -51,7 +52,6 @@ AnalyzerManager::~AnalyzerManager() { } bool AnalyzerManager::isActive(bool includeForeground) { - //I don't count foregroundWorkers because int total = (includeForeground ? m_foregroundWorkers.size() : 0) + m_backgroundWorkers.size() + m_pausedWorkers.size(); return total > 0; @@ -62,17 +62,20 @@ void AnalyzerManager::stop(bool shutdown) { while (it.hasNext()) { AnalyzerWorker* worker = it.next(); worker->endProcess(); + m_endingWorkers.append(worker); } QListIterator it3(m_pausedWorkers); while (it3.hasNext()) { AnalyzerWorker* worker = it3.next(); worker->endProcess(); + m_endingWorkers.append(worker); } if (shutdown) { QListIterator it2(m_foregroundWorkers); while (it2.hasNext()) { AnalyzerWorker* worker = it2.next(); worker->endProcess(); + m_endingWorkers.append(worker); } //TODO: ensure that they are all forcibly stopped. } @@ -82,6 +85,8 @@ void AnalyzerManager::analyseTrackNow(TrackPointer tio) { if (m_batchTrackQueue.contains(tio)) { m_batchTrackQueue.removeAll(tio); } + //TODO: There's one scenario that we still miss: load on a deck a track that is currently + //being analyzed by the background worker. We cannot reuse the background worker, but we should discard its work. if (!m_prioTrackQueue.contains(tio)) { m_prioTrackQueue.append(tio); if (m_foregroundWorkers.size() < m_MaxThreads) { @@ -114,19 +119,23 @@ void AnalyzerManager::slotAnalyseTrack(TrackPointer tio) { //slot -void AnalyzerManager::slotUpdateProgress(struct AnalyzerWorker::progress_info* progressInfo) { - if (progressInfo->current_track) { - progressInfo->current_track->setAnalyzerProgress( - progressInfo->track_progress); - if (progressInfo->track_progress == 1000) { - emit(trackDone(progressInfo->current_track)); - } - progressInfo->current_track.clear(); - } - emit(trackProgress(progressInfo->track_progress / 10)); +void AnalyzerManager::slotUpdateProgress(int workerIdx, struct AnalyzerWorker::progress_info* progressInfo) { + //Updates to wave overview and player status text comes from a signal emited from the track by calling setAnalyzerProgress. + progressInfo->current_track->setAnalyzerProgress(progressInfo->track_progress); + //These update the Analysis feature and analysis view. + emit(trackProgress(workerIdx, progressInfo->track_progress / 10)); if (progressInfo->track_progress == 1000) { + //Right now no one is listening to trackDone, but it's here just in case. + emit(trackDone(progressInfo->current_track)); + //Report that a track analysis has finished, and how many are still remaining. emit(trackFinished(m_backgroundWorkers.size() + m_batchTrackQueue.size() - 1)); } + //TODO: Which is the consequence of not calling clear? +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + progressInfo->current_track.clear(); +#else + progressInfo->current_track.reset(); +#endif progressInfo->sema.release(); } @@ -153,18 +162,24 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { } else { worker->endProcess(); + //Removing from active lists, so that "isActive" can return the correct value. + m_backgroundWorkers.removeAll(worker); + m_foregroundWorkers.removeAll(worker); + m_endingWorkers.append(worker); + if (!m_pausedWorkers.isEmpty()) { AnalyzerWorker* otherworker = m_pausedWorkers.first(); otherworker->resume(); m_pausedWorkers.removeOne(otherworker); } } - //Check if all queues are empty. + //Check if background workers are empty. if (!isActive(false)) { emit(queueEmpty()); } } void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) { + m_endingWorkers.removeAll(worker); m_backgroundWorkers.removeAll(worker); m_foregroundWorkers.removeAll(worker); m_pausedWorkers.removeAll(worker); @@ -179,7 +194,7 @@ void AnalyzerManager::slotErrorString(QString errMsg) { AnalyzerWorker* AnalyzerManager::createNewWorker(bool batchJob) { QThread* thread = new QThread(); - AnalyzerWorker* worker = new AnalyzerWorker(m_pConfig, batchJob); + AnalyzerWorker* worker = new AnalyzerWorker(m_pConfig, ++m_nextWorkerId, batchJob); worker->moveToThread(thread); //Auto startup and auto cleanup of worker and thread. connect(thread, SIGNAL(started()), worker, SLOT(slotProcess())); @@ -187,12 +202,12 @@ AnalyzerWorker* AnalyzerManager::createNewWorker(bool batchJob) { connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); //Connect with manager. - connect(worker, SIGNAL(updateProgress(struct AnalyzerWorker::progress_info*)), this, SLOT(slotUpdateProgress(struct AnalyzerWorker::progress_info*))); + connect(worker, SIGNAL(updateProgress(int, struct AnalyzerWorker::progress_info*)), this, SLOT(slotUpdateProgress(int, struct AnalyzerWorker::progress_info*))); connect(worker, SIGNAL(waitingForNextTrack(AnalyzerWorker*)), this, SLOT(slotNextTrack(AnalyzerWorker*))); connect(worker, SIGNAL(paused(AnalyzerWorker*)), this, SLOT(slotPaused(AnalyzerWorker*))); connect(worker, SIGNAL(workerFinished(AnalyzerWorker*)), this, SLOT(slotWorkerFinished(AnalyzerWorker*))); connect(worker, SIGNAL(error(QString)), this, SLOT(slotErrorString(QString))); - thread->start(); + thread->start(QThread::LowPriority); if (batchJob) { m_backgroundWorkers.append(worker); } diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h index c30e98c19c3..9d483a271ba 100644 --- a/src/analyzer/analyzermanager.h +++ b/src/analyzer/analyzermanager.h @@ -32,23 +32,23 @@ class AnalyzerManager : public QObject { public slots: // This slot is called from the decks and samplers when the track is loaded. void slotAnalyseTrack(TrackPointer tio); - void slotUpdateProgress(struct AnalyzerWorker::progress_info*); + void slotUpdateProgress(int, struct AnalyzerWorker::progress_info*); void slotNextTrack(AnalyzerWorker*); void slotPaused(AnalyzerWorker*); void slotWorkerFinished(AnalyzerWorker*); void slotErrorString(QString); signals: - void trackProgress(int progress); + void trackProgress(int worker, int progress); void trackDone(TrackPointer track); void trackFinished(int size); void queueEmpty(); private: AnalyzerWorker* createNewWorker(bool batchJob); - static AnalyzerManager* m_pAnalyzerManager; UserSettingsPointer m_pConfig; + int m_nextWorkerId; int m_MaxThreads; // The processing queue and associated mutex QQueue m_batchTrackQueue; @@ -57,6 +57,8 @@ public slots: QList m_backgroundWorkers; QList m_foregroundWorkers; QList m_pausedWorkers; + //This list is used mostly so that isActive() can return the correct value + QList m_endingWorkers; }; #endif /* ANALYZER_ANALYZERMANAGER_H */ diff --git a/src/analyzer/analyzerqueue.cpp b/src/analyzer/analyzerqueue.cpp index e2ac0fe5caf..a5f5a937781 100644 --- a/src/analyzer/analyzerqueue.cpp +++ b/src/analyzer/analyzerqueue.cpp @@ -39,6 +39,7 @@ namespace { kAnalysisFramesPerBlock * kAnalysisChannels; } // anonymous namespace +//DEPRECATED. Now we use AnalyzerManager and AnalyzerWorkers. AnalyzerQueue::AnalyzerQueue(TrackCollection* pTrackCollection) : m_aq(), m_exit(false), diff --git a/src/analyzer/analyzerqueue.h b/src/analyzer/analyzerqueue.h index 642eca0d9c7..803620045c9 100644 --- a/src/analyzer/analyzerqueue.h +++ b/src/analyzer/analyzerqueue.h @@ -17,6 +17,7 @@ class TrackCollection; +//DEPRECATED. Now we use AnalyzerManager and AnalyzerWorkers. class AnalyzerQueue : public QThread { Q_OBJECT diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index 824c32079a3..300411e8424 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -35,13 +35,15 @@ namespace { // 0 for no progress during finalize // 1 to display the text "finalizing" // 100 for 10% step after finalize +// NOTE: If this is changed, change woverview.cpp slotAnalyzerProgress(). #define FINALIZE_PROMILLE 1 // --- CONSTRUCTOR --- -AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, bool batchJob) : +AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool batchJob) : m_pConfig(pConfig), m_analyzelist(), m_batchJob(batchJob), + m_workerIdx(workerIdx), m_sampleBuffer(kAnalysisSamplesPerBlock), m_exit(false), m_pauseRequested(false), @@ -51,7 +53,7 @@ AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, bool batchJob) : // --- DESTRUCTOR --- AnalyzerWorker::~AnalyzerWorker() { - qDebug << "Ending analyzer"; + qDebug() << "Ending analyzer"; // free resources m_progressInfo.sema.release(); @@ -194,8 +196,7 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud } void AnalyzerWorker::slotProcess() { - unsigned static id = 0; // the id of this thread, for debugging purposes - QThread::currentThread()->setObjectName(QString("AnalyzerWorker %1").arg(++id)); + QThread::currentThread()->setObjectName(QString("AnalyzerWorker %1").arg(m_workerIdx)); //The instantiation of the initializers needs to be done on the worker thread. //cannot be done on constructor. @@ -213,7 +214,7 @@ void AnalyzerWorker::slotProcess() { if (m_exit) { break; } - Event::start(QString("AnalyzerWorker %1 process").arg(id)); + Event::start(QString("AnalyzerWorker %1 process").arg(m_workerIdx)); Trace trace("AnalyzerWorker analyzing track"); // Get the audio @@ -262,7 +263,7 @@ void AnalyzerWorker::slotProcess() { emitUpdateProgress(1000); // 100% qDebug() << "Skipping track analysis because no analyzer initialized."; } - Event::end(QString("AnalyzerWorker %1 process").arg(id)); + Event::end(QString("AnalyzerWorker %1 process").arg(m_workerIdx)); } emit(workerFinished(this)); emit(finished()); @@ -291,7 +292,7 @@ void AnalyzerWorker::emitUpdateProgress(int progress) { } m_progressInfo.current_track = m_currentTrack; m_progressInfo.track_progress = progress; - emit(updateProgress(&m_progressInfo)); + emit(updateProgress(m_workerIdx, &m_progressInfo)); } } diff --git a/src/analyzer/analyzerworker.h b/src/analyzer/analyzerworker.h index 443584370d6..dcf5ab6d3fd 100644 --- a/src/analyzer/analyzerworker.h +++ b/src/analyzer/analyzerworker.h @@ -17,23 +17,12 @@ class Analyzer; class QThread; - -/* -start_the_job{ -Worker* worker1 = new Worker(this); -worker1.addAnalyzer(new xxx); -connect(worker, SIGNAL(updateProgress(struct progress_info*)), this, SLOT(slotUpdateProgress(struct progress_info*))); -connect(worker, SIGNAL(waitingForNextTrack(AnalyzerWorker*)), this, SLOT(slotNextTrack(AnalyzerWorker*))); -connect(worker, SIGNAL(paused(AnalyzerWorker*)), this, SLOT(slotPaused(AnalyzerWorker*))); -connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString))); -worker1->start(); -} -*/ class AnalyzerWorker : public QObject { Q_OBJECT public: struct progress_info { + int worker; TrackPointer current_track; int track_progress; // in 0.1 % QSemaphore sema; @@ -42,7 +31,7 @@ class AnalyzerWorker : public QObject { //pConfig is stored and batchJob indicates if this worker is a analysisfeature job (batch) //or player job. // Call Qthread->start() when you are ready for the worker to start. - AnalyzerWorker(UserSettingsPointer pConfig, bool batchJob); + AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool batchJob); virtual ~AnalyzerWorker(); //Called by the manager as a response to the waitingForNextTrack signal. and ONLY then. @@ -63,7 +52,7 @@ public slots: signals: //Signal that informs about the progress of the analysis. - void updateProgress(struct AnalyzerWorker::progress_info*); + void updateProgress(int workerIdx, struct AnalyzerWorker::progress_info*); //Signal emited to the manager in order to receive a new track. The manager should call nextTrack(); void waitingForNextTrack(AnalyzerWorker* worker); //Signal emited when the worker has effectively paused @@ -86,6 +75,7 @@ public slots: UserSettingsPointer m_pConfig; QList m_analyzelist; bool m_batchJob; + int m_workerIdx; SampleBuffer m_sampleBuffer; TrackPointer m_currentTrack; diff --git a/src/library/analysisfeature.cpp b/src/library/analysisfeature.cpp index 771c8683908..85ddd98976c 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysisfeature.cpp @@ -109,8 +109,8 @@ void AnalysisFeature::activate() { void AnalysisFeature::analyzeTracks(QList trackIds) { AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); - connect(&analyzerManager, SIGNAL(trackProgress(int)), - m_pAnalysisView, SLOT(trackAnalysisProgress(int))); + connect(&analyzerManager, SIGNAL(trackProgress(int, int)), + m_pAnalysisView, SLOT(trackAnalysisProgress(int, int))); connect(&analyzerManager, SIGNAL(trackFinished(int)), this, SLOT(slotProgressUpdate(int))); connect(&analyzerManager, SIGNAL(trackFinished(int)), @@ -137,7 +137,7 @@ void AnalysisFeature::analyzeTracks(QList trackIds) { void AnalysisFeature::slotProgressUpdate(int num_left) { int num_tracks = m_pAnalysisView->getNumTracks(); if (num_left > 0) { - int currentTrack = num_tracks - num_left + 1; + int currentTrack = num_tracks - num_left; setTitleProgress(currentTrack, num_tracks); } } diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index 6d8ec9f3801..ffca8d862a0 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -14,6 +14,7 @@ DlgAnalysis::DlgAnalysis(QWidget* parent, m_pConfig(pConfig), m_pTrackCollection(pTrackCollection), m_bAnalysisActive(false), + m_percentages(), m_tracksInQueue(0), m_currentTrack(0) { setupUi(this); @@ -141,6 +142,7 @@ void DlgAnalysis::analysisActive(bool bActive) { pushButtonAnalyze->setText(tr("Analyze")); labelProgress->setText(""); labelProgress->setEnabled(false); + m_percentages.clear(); } } @@ -148,18 +150,34 @@ void DlgAnalysis::analysisActive(bool bActive) { void DlgAnalysis::trackAnalysisFinished(int size) { qDebug() << "Analysis finished" << size << "tracks left"; if (size > 0) { - m_currentTrack = m_tracksInQueue - size + 1; + m_currentTrack = m_tracksInQueue - size; } } // slot -void DlgAnalysis::trackAnalysisProgress(int progress) { +void DlgAnalysis::trackAnalysisProgress(int worker, int progress) { if (m_bAnalysisActive) { + m_percentages[worker] = progress; + //This is a bit cumbersome, yes, I just avoided to change the tranlating text. + QString perc; + bool add = false; + foreach(int percentage, m_percentages) { + if (add) { + perc+= "% "; + } + perc += QString::number(percentage / 10); + add = true; + } QString text = tr("Analyzing %1/%2 %3%").arg( QString::number(m_currentTrack), QString::number(m_tracksInQueue), - QString::number(progress)); + perc); labelProgress->setText(text); + //This isn't strictly necessary, but it is useful to remove foreground (player) worker analysis + //which would accumulate otherwise. Another option is to not send this signal for foreground workers. + if (progress == 1000) { + m_percentages.remove(worker); + } } } diff --git a/src/library/dlganalysis.h b/src/library/dlganalysis.h index 84520b38cf6..35795c5857f 100644 --- a/src/library/dlganalysis.h +++ b/src/library/dlganalysis.h @@ -38,7 +38,7 @@ class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual Libra void selectAll(); void analyze(); void trackAnalysisFinished(int size); - void trackAnalysisProgress(int progress); + void trackAnalysisProgress(int worker, int progress); void trackAnalysisStarted(int size); void showRecentSongs(); void showAllSongs(); @@ -60,6 +60,8 @@ class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual Libra QButtonGroup m_songsButtonGroup; WAnalysisLibraryTableView* m_pAnalysisLibraryTableView; AnalysisLibraryTableModel* m_pAnalysisLibraryTableModel; + //Since we iterate it, it is better to provide a consistent order + QMap m_percentages; int m_tracksInQueue; int m_currentTrack; }; From c06ab244c7387076ff50a8fc3bd7d9f314e9e540 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sat, 3 Dec 2016 16:30:50 +0100 Subject: [PATCH 03/20] bugfix: one division too much --- src/library/dlganalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index ffca8d862a0..758c1c4762e 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -165,7 +165,7 @@ void DlgAnalysis::trackAnalysisProgress(int worker, int progress) { if (add) { perc+= "% "; } - perc += QString::number(percentage / 10); + perc += QString::number(percentage); add = true; } QString text = tr("Analyzing %1/%2 %3%").arg( From 3c5c6803c19d53ee67f769510a4f7e337032eee9 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sat, 3 Dec 2016 19:20:37 +0100 Subject: [PATCH 04/20] Inform the UI to update when stopping the manager --- src/analyzer/analyzermanager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 78a4f14ef6a..3bbeac6d2a3 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -58,6 +58,7 @@ bool AnalyzerManager::isActive(bool includeForeground) { } void AnalyzerManager::stop(bool shutdown) { + m_batchTrackQueue.clear(); QListIterator it(m_backgroundWorkers); while (it.hasNext()) { AnalyzerWorker* worker = it.next(); @@ -71,6 +72,7 @@ void AnalyzerManager::stop(bool shutdown) { m_endingWorkers.append(worker); } if (shutdown) { + m_prioTrackQueue.clear(); QListIterator it2(m_foregroundWorkers); while (it2.hasNext()) { AnalyzerWorker* worker = it2.next(); @@ -183,6 +185,9 @@ void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) { m_backgroundWorkers.removeAll(worker); m_foregroundWorkers.removeAll(worker); m_pausedWorkers.removeAll(worker); + if (!isActive(false)) { + emit(queueEmpty()); + } } void AnalyzerManager::slotPaused(AnalyzerWorker* worker) { //No useful code to execute right now. From 90882d85e1ac674125c83e89f80b5ada384544a1 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sat, 3 Dec 2016 19:38:40 +0100 Subject: [PATCH 05/20] ups. forgot to change this when fixing the division --- src/library/dlganalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index 758c1c4762e..a81e172e839 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -175,7 +175,7 @@ void DlgAnalysis::trackAnalysisProgress(int worker, int progress) { labelProgress->setText(text); //This isn't strictly necessary, but it is useful to remove foreground (player) worker analysis //which would accumulate otherwise. Another option is to not send this signal for foreground workers. - if (progress == 1000) { + if (progress == 100) { m_percentages.remove(worker); } } From 0d283541d74a48d90d4c663f94a888aa345134ff Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 4 Dec 2016 17:32:49 +0100 Subject: [PATCH 06/20] Fix abatch waveform analysis --- src/analyzer/analyzerwaveform.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index e07420971b9..ae34b8dae3d 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -106,7 +106,7 @@ bool AnalyzerWaveform::initialize(TrackPointer tio, int sampleRate, int totalSam } bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { - if (m_batch && m_pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) { + if (m_batch && !m_pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) { return true; } @@ -319,6 +319,10 @@ void AnalyzerWaveform::finalize(TrackPointer tio) { #ifdef TEST_HEAT_MAP test_heatMap->save("heatMap.png"); #endif + //Ensure that the analyses get saved. This is also called from TrackDAO.updateTrack(), but it can + //happen that we batch analyze only the waveforms (i.e. if the setting was disabled in the previous scan) + //and then it is not called. The other analyzers have signals which control the update of their data. + m_pAnalysisDao->saveTrackAnalyses(*tio); qDebug() << "Waveform generation for track" << tio->getId() << "done" << m_timer.elapsed().debugSecondsWithUnit(); From 8b450dc971e8a1f7a216567507c44e9677f3e0c6 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sat, 10 Dec 2016 14:27:29 +0100 Subject: [PATCH 07/20] documentation of methods --- src/analyzer/analyzermanager.h | 39 +++++++++++++++++++++++++++++++-- src/analyzer/analyzerqueue.cpp | 10 ++++++++- src/analyzer/analyzerworker.cpp | 17 ++++++-------- src/analyzer/analyzerworker.h | 29 +++++++++++++++--------- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h index 9d483a271ba..1b5ec184ace 100644 --- a/src/analyzer/analyzermanager.h +++ b/src/analyzer/analyzermanager.h @@ -12,6 +12,10 @@ class TrackCollection; +/* AnalyzerManager class +* Manages the task of analying multiple tracks. Setups a maximum amount of workers +* and provides the tracks to analyze. It also sends the signals to other parts of Mixxx. +*/ class AnalyzerManager : public QObject { Q_OBJECT @@ -19,43 +23,74 @@ class AnalyzerManager : public QObject { AnalyzerManager(UserSettingsPointer pConfig); public: + //Get the only instance of the manager: + // TODO: maybe move it to an instance in MixxxMainWindow. static AnalyzerManager& getInstance(UserSettingsPointer pConfig); virtual ~AnalyzerManager(); + //Tell the background analysis to stop. If shutdown is true. stop also the foreground analysis. void stop(bool shutdown); //This method might need to be protected an called only via slot. void analyseTrackNow(TrackPointer tio); + //Add a track to be analyzed by the background analyzer. void queueAnalyseTrack(TrackPointer tio); + //Check if there is any background worker active, paused or track in queue + //If includeForeground is true, it also checks the foreground workers. bool isActive(bool includeForeground); public slots: // This slot is called from the decks and samplers when the track is loaded. void slotAnalyseTrack(TrackPointer tio); + // This slot is called from the workers to indicate progress. void slotUpdateProgress(int, struct AnalyzerWorker::progress_info*); + // This slot is called from the workers when they need a new track to process. void slotNextTrack(AnalyzerWorker*); + // This slot is called from the workers to inform that they reached the paused status. void slotPaused(AnalyzerWorker*); + // This slot is called from the workers to inform that they are ending themselves. void slotWorkerFinished(AnalyzerWorker*); + // This slot is intended to receive textual messages. It us unused right now. void slotErrorString(QString); signals: + //This signal is emited to inform other UI elements about the analysis progress. + //Note: the foreground analyzers don't listen to this, but instead they listen to + //a signal emited by the track object itself. void trackProgress(int worker, int progress); + //This signal is emited to indicate that such track is done. Not really used right now. void trackDone(TrackPointer track); + //This sitnal is emited to indicate that a track has finished. The size indicates how many + //tracks remain to be scanned. It is currently used by the AnalysisFeature and DlgAnalisys + //to show the track progression. void trackFinished(int size); + //Indicates that the background analysis job has finished (I.e. all background tracks have been + // analyzed). It is used for the UI to refresh the text and buttons. void queueEmpty(); private: - + //Method that creates a worker, assigns it to a new thread and the correct list, and starts + //the thread with low priority. AnalyzerWorker* createNewWorker(bool batchJob); + static AnalyzerManager* m_pAnalyzerManager; UserSettingsPointer m_pConfig; + // Autoincremented ID to use as an identifier for each worker. int m_nextWorkerId; + // Max number of threads to be active analyzing at a time including both, background and foreground analysis int m_MaxThreads; - // The processing queue and associated mutex + // TODO: We do a "contains" over these queues before adding a new track to them. + // The more tracks that we add to the queue, the slower this check is. + // No UI response is shown until all tracks are queued. + // The processing queue for batch analysis QQueue m_batchTrackQueue; + // The processing queue for the player analysis. QQueue m_prioTrackQueue; + //List of background workers (excluding the paused ones). QList m_backgroundWorkers; + //List of foreground workers (foreground workers are not paused) QList m_foregroundWorkers; + //List of background workers that are currently paused QList m_pausedWorkers; //This list is used mostly so that isActive() can return the correct value QList m_endingWorkers; diff --git a/src/analyzer/analyzerqueue.cpp b/src/analyzer/analyzerqueue.cpp index a5f5a937781..a27fe317345 100644 --- a/src/analyzer/analyzerqueue.cpp +++ b/src/analyzer/analyzerqueue.cpp @@ -295,7 +295,11 @@ void AnalyzerQueue::run() { if (m_aq.size() == 0) return; +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + m_progressInfo.current_track.clear(); +#else m_progressInfo.current_track.reset(); +#endif m_progressInfo.track_progress = 0; m_progressInfo.queue_size = 0; m_progressInfo.sema.release(); // Initialize with one @@ -410,7 +414,11 @@ void AnalyzerQueue::slotUpdateProgress() { if (m_progressInfo.current_track) { m_progressInfo.current_track->setAnalyzerProgress( m_progressInfo.track_progress); - m_progressInfo.current_track.reset(); +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + m_progressInfo.current_track.clear(); +#else + m_progressInfo.current_track.reset(); +#endif } emit(trackProgress(m_progressInfo.track_progress / 10)); if (m_progressInfo.track_progress == 1000) { diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index 300411e8424..c434cfd9fc3 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -53,14 +53,13 @@ AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool // --- DESTRUCTOR --- AnalyzerWorker::~AnalyzerWorker() { - qDebug() << "Ending analyzer"; + qDebug() << "Ending AnalyzerWorker"; // free resources m_progressInfo.sema.release(); QListIterator it(m_analyzelist); while (it.hasNext()) { Analyzer* an = it.next(); - //qDebug() << "AnalyzerWorker: deleting " << typeid(an).name(); delete an; } } @@ -166,13 +165,11 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud progressUpdateInhibitTimer.start(); } - // Since this is a background analysis queue, we should co-operatively - // yield every now and then to try and reduce CPU contention. The - // analyzer queue is CPU intensive so we want to get out of the way of - // the audio callback thread. - //QThread::yieldCurrentThread(); + // This has proven to not be necessary, and if used, it should be done with care so as to + // not make the analysis slower than what it should. Also note that the user has the option + // to reduce the number of threads that run the analysis. - // When a priority analysis comes in, we pause a working thread until a new prioritized + // When a priority analysis comes in, we pause this working thread until one prioritized // worker finishes. Once it finishes, this worker will get resumed. if (m_pauseRequested.fetchAndStoreAcquire(false)) { m_qm.lock(); @@ -195,6 +192,7 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud return !cancelled; //don't return !dieflag or we might reanalyze over and over } +//Called automatically by the owning thread to start the process (Configured to do so by AnalyzerManager) void AnalyzerWorker::slotProcess() { QThread::currentThread()->setObjectName(QString("AnalyzerWorker %1").arg(m_workerIdx)); @@ -252,7 +250,6 @@ void AnalyzerWorker::slotProcess() { } else { // 100% - FINALIZE_PERCENT finished emitUpdateProgress(1000 - FINALIZE_PROMILLE); - // This takes around 3 sec on a Atom Netbook QListIterator itf(m_analyzelist); while (itf.hasNext()) { itf.next()->finalize(m_currentTrack); @@ -278,7 +275,7 @@ void AnalyzerWorker::emitUpdateProgress(int progress) { // until the UI has rendered the last update. As it is, it only prevents sending another // update if the analyzermanager slot hasn't read the update. (not the UI slot). // --------- - // First tryAcqire will have always success because sema is initialized with on + // First tryAcquire will have always success because sema is initialized with on // The following tries will success if the previous signal was processed in the GUI Thread // This prevent the AnalysisQueue from filling up the GUI Thread event Queue // 100 % is emitted in any case diff --git a/src/analyzer/analyzerworker.h b/src/analyzer/analyzerworker.h index dcf5ab6d3fd..8e3c01bf973 100644 --- a/src/analyzer/analyzerworker.h +++ b/src/analyzer/analyzerworker.h @@ -1,7 +1,6 @@ #ifndef ANALYZER_ANALYZERWORKER_H #define ANALYZER_ANALYZERWORKER_H -#include #include #include #include @@ -17,37 +16,47 @@ class Analyzer; class QThread; +/* Worker class. +* It represents a job that runs on a thread, analyzing tracks until no more tracks need to be analyzed. +*/ class AnalyzerWorker : public QObject { Q_OBJECT public: + //Information of the analysis used with the updateProgress signal. struct progress_info { + //Worker identifier. This is used to differentiate between updateProgress signals, and it is + //what lets the DlgAnalysis class to differentiate and show the different percentages. int worker; + //Track being analyzed. This is currently used in the AnalyzerManager. TrackPointer current_track; - int track_progress; // in 0.1 % + //track progress in steps of 0.1 % + int track_progress; + //Semaphore to avoid exccesive signaling. QSemaphore sema; }; - //Constructor. Qthread is passed only to connect several signals. It is not stored. - //pConfig is stored and batchJob indicates if this worker is a analysisfeature job (batch) - //or player job. + + // Constructor. If it is a batch job, the analyzers might be configured differently. // Call Qthread->start() when you are ready for the worker to start. AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool batchJob); virtual ~AnalyzerWorker(); //Called by the manager as a response to the waitingForNextTrack signal. and ONLY then. void nextTrack(TrackPointer newTrack); - //called to pause this worker (waits on a qwaitcondition) + //called to pause this worker (The call is not blocking. The worker will wait on a qwaitcondition) void pause(); - //resumes from a previous call to pause + //resumes from a previous call to pause (The call is not blocking) void resume(); - //Tells this worker to end. It will delete itself and the Qthread. - // An updateProgress signal with progress 0 will be emited and also the finished signal + //Tells this worker to end. (the call is not blocking. Sets a variable for the worker to end) + // An updateProgress signal with progress 0 will be emited and also the finished signal. + // The AnalyzerManager connects it so that it will delete itself and the Qthread. void endProcess(); // Is this a batch worker? bool isBatch(); public slots: - //Called automatically by the owning thread to start the process + //starts the analysis job. + //Called automatically by the owning thread to start the process (Configured to do so by AnalyzerManager) void slotProcess(); signals: From b581f88139ff67ce4850a19f6d81eee165ec9717 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 11 Dec 2016 02:15:54 +0100 Subject: [PATCH 08/20] Preferences option to set the max number of threads for analysis --- src/analyzer/analyzermanager.cpp | 106 ++++++++++++++++---- src/analyzer/analyzermanager.h | 2 + src/analyzer/analyzerwaveform.cpp | 2 +- src/mixxx.cpp | 1 - src/preferences/dialog/dlgpreflibrary.cpp | 68 ++++++++++++- src/preferences/dialog/dlgpreflibrary.h | 5 + src/preferences/dialog/dlgpreflibrarydlg.ui | 66 ++++++------ 7 files changed, 202 insertions(+), 48 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 3bbeac6d2a3..f4171b52947 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -25,13 +25,13 @@ AnalyzerManager& AnalyzerManager::getInstance(UserSettingsPointer pConfig) { m_pAnalyzerManager = new AnalyzerManager(pConfig); } int maxThreads = m_pAnalyzerManager->m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); - if (maxThreads < 0 || maxThreads > 2 * QThread::idealThreadCount()) { - //Assume the value is incorrect, so fix it. - maxThreads = QThread::idealThreadCount(); - m_pAnalyzerManager->m_pConfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), maxThreads); + int ideal = QThread::idealThreadCount(); + if (QThread::idealThreadCount() < 1) { + ideal = 1; } - if (maxThreads == 0) { - maxThreads = QThread::idealThreadCount(); + if (maxThreads <= 0 || maxThreads > 32) { + //Assume the value is incorrect, so fix it. + maxThreads = ideal; } m_pAnalyzerManager->m_MaxThreads = maxThreads; return *m_pAnalyzerManager; @@ -142,21 +142,31 @@ void AnalyzerManager::slotUpdateProgress(int workerIdx, struct AnalyzerWorker::p } void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { - //The while loop is done in the event that the track which was added to the queue is no - //longer available. - //TODO: The old scan checked in isLoadedTrackWaiting for pTrack->getAnalyzerProgress() // and either tried to load a previuos scan, or discarded the track if it had already been // analyzed. I don't fully understand the scenario and I am not doing that right now. + + //This is used when the maxThreads change. Extra workers are paused until active workers end. + //Then, those are terminated and the paused workers are resumed. TrackPointer track = TrackPointer(); - if (worker->isBatch()) { - while (!track && !m_batchTrackQueue.isEmpty()) { - track = m_batchTrackQueue.dequeue(); - } + AnalyzerWorker* forepaused=nullptr; + foreach(AnalyzerWorker* worker, m_pausedWorkers) { + if (!worker->isBatch()) { forepaused=worker; break; } } - else { - while (!track && !m_prioTrackQueue.isEmpty()) { - track = m_prioTrackQueue.dequeue(); + if (!forepaused) { + if (worker->isBatch()) { + if (m_backgroundWorkers.size() + m_pausedWorkers.size() <= m_MaxThreads) { + //The while loop is done in the event that the track which was added to the queue is no + //longer available. + while (!track && !m_batchTrackQueue.isEmpty()) { + track = m_batchTrackQueue.dequeue(); + } + } + } + else { + while (!track && !m_prioTrackQueue.isEmpty()) { + track = m_prioTrackQueue.dequeue(); + } } } if (track) { @@ -169,10 +179,21 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { m_foregroundWorkers.removeAll(worker); m_endingWorkers.append(worker); - if (!m_pausedWorkers.isEmpty()) { + if (forepaused) { + forepaused->resume(); + m_pausedWorkers.removeOne(forepaused); + m_foregroundWorkers.append(forepaused); + } + else if (!m_pausedWorkers.isEmpty()) { AnalyzerWorker* otherworker = m_pausedWorkers.first(); otherworker->resume(); m_pausedWorkers.removeOne(otherworker); + if (otherworker->isBatch()) { + m_backgroundWorkers.append(otherworker); + } + else { + m_foregroundWorkers.append(otherworker); + } } } //Check if background workers are empty. @@ -197,6 +218,57 @@ void AnalyzerManager::slotErrorString(QString errMsg) { qWarning() << "Testing with :" << errMsg; } + +void AnalyzerManager::slotMaxThreadsChanged(int threads) { + // If it is running, adapt the job count. + if (threads < m_MaxThreads) { + //Pause workers + while (!m_backgroundWorkers.isEmpty() + && m_foregroundWorkers.size() + m_backgroundWorkers.size() > threads) { + AnalyzerWorker * backwork = m_backgroundWorkers.first(); + backwork->pause(); + //Ideally i would have done this on the slotPaused slot, but then i cannot + //ensure i won't call pause twice for the same worker. + m_pausedWorkers.append(backwork); + m_backgroundWorkers.removeAll(backwork); + } + while (m_foregroundWorkers.size() > threads) { + AnalyzerWorker * backwork = m_foregroundWorkers.first(); + backwork->pause(); + //Ideally i would have done this on the slotPaused slot, but then i cannot + //ensure i won't call pause twice for the same worker. + m_pausedWorkers.append(backwork); + m_foregroundWorkers.removeAll(backwork); + } + } + else { + //resume workers + int pendingworkers=threads-m_MaxThreads; + foreach(AnalyzerWorker* worker, m_pausedWorkers) { + if (!worker->isBatch() && pendingworkers > 0) { + worker->resume(); + m_pausedWorkers.removeOne(worker); + m_foregroundWorkers.append(worker); + --pendingworkers; + } + } + foreach(AnalyzerWorker* worker, m_pausedWorkers) { + if (worker->isBatch() && pendingworkers > 0) { + worker->resume(); + m_pausedWorkers.removeOne(worker); + m_backgroundWorkers.append(worker); + --pendingworkers; + } + } + //Create new workers, if tracks in queue. + pendingworkers = math_min(pendingworkers,m_batchTrackQueue.size()); + for ( ;pendingworkers > 0; --pendingworkers) { + createNewWorker(true); + } + } + m_MaxThreads=threads; +} + AnalyzerWorker* AnalyzerManager::createNewWorker(bool batchJob) { QThread* thread = new QThread(); AnalyzerWorker* worker = new AnalyzerWorker(m_pConfig, ++m_nextWorkerId, batchJob); diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h index 1b5ec184ace..4ecdfd20b9e 100644 --- a/src/analyzer/analyzermanager.h +++ b/src/analyzer/analyzermanager.h @@ -52,6 +52,8 @@ public slots: void slotWorkerFinished(AnalyzerWorker*); // This slot is intended to receive textual messages. It us unused right now. void slotErrorString(QString); + // This slot is called from the preferences dialog to update the max value. It will stop extra threads if running. + void slotMaxThreadsChanged(int threads); signals: //This signal is emited to inform other UI elements about the analysis progress. diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index ae34b8dae3d..1887cbce122 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -322,7 +322,7 @@ void AnalyzerWaveform::finalize(TrackPointer tio) { //Ensure that the analyses get saved. This is also called from TrackDAO.updateTrack(), but it can //happen that we batch analyze only the waveforms (i.e. if the setting was disabled in the previous scan) //and then it is not called. The other analyzers have signals which control the update of their data. - m_pAnalysisDao->saveTrackAnalyses(*tio); + m_pAnalysisDao->saveTrackAnalyses(tio.data()); qDebug() << "Waveform generation for track" << tio->getId() << "done" << m_timer.elapsed().debugSecondsWithUnit(); diff --git a/src/mixxx.cpp b/src/mixxx.cpp index 6e13ae5a35d..f08e41b414a 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -24,7 +24,6 @@ #include #include -#include "analyzer/analyzermanager.h" #include "dialog/dlgabout.h" #include "preferences/dialog/dlgpreferences.h" #include "preferences/dialog/dlgprefeq.h" diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index b6da7e7ab4b..e75f53c357c 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -26,6 +26,7 @@ #include "preferences/dialog/dlgpreflibrary.h" #include "sources/soundsourceproxy.h" +#include "analyzer/analyzermanager.h" #define MIXXX_ADDONS_URL "http://www.mixxx.org/wiki/doku.php/add-ons" @@ -36,7 +37,8 @@ DlgPrefLibrary::DlgPrefLibrary(QWidget * parent, m_pconfig(config), m_pLibrary(pLibrary), m_baddedDirectory(false), - m_iOriginalTrackTableRowHeight(Library::kDefaultRowHeightPx) { + m_iOriginalTrackTableRowHeight(Library::kDefaultRowHeightPx), + m_iOriginalMaxThreads(1) { setupUi(this); slotUpdate(); checkbox_ID3_sync->setVisible(false); @@ -77,6 +79,13 @@ DlgPrefLibrary::DlgPrefLibrary(QWidget * parent, connect(this, SIGNAL(setTrackTableRowHeight(int)), m_pLibrary, SLOT(slotSetTrackTableRowHeight(int))); + connect(cmbMaxThreads, SIGNAL(currentIndexChanged(int)), + this, SLOT(slotMaxThreadsChanged(int))); + + AnalyzerManager& manager = AnalyzerManager::getInstance(config); + connect(cmbMaxThreads, SIGNAL(setMaxThreads(int)), + &manager, SLOT(slotMaxThreadsChanged(int))); + // TODO(XXX) this string should be extracted from the soundsources QString builtInFormatsStr = "Ogg Vorbis, FLAC, WAVe, AIFF"; #if defined(__MAD__) || defined(__APPLE__) @@ -142,6 +151,25 @@ void DlgPrefLibrary::initializeDirList() { } } +void DlgPrefLibrary::initializeThreadsCombo() { + disconnect(cmbMaxThreads, SIGNAL(currentIndexChanged(int)), + this, SLOT(slotMaxThreadsChanged(int))); + + // save which index was selected + const int selected = cmbMaxThreads->currentIndex(); + // clear and fill model + cmbMaxThreads->clear(); + int cpuMax = QThread::idealThreadCount(); + if (cpuMax < 1) { cpuMax = 8; } + for (int i=1; i <= cpuMax; i++) { + QString displayname = QString::number(i); + cmbMaxThreads->addItem(displayname); + } + cmbMaxThreads->setCurrentIndex(selected); + connect(cmbMaxThreads, SIGNAL(currentIndexChanged(int)), + this, SLOT(slotMaxThreadsChanged(int))); +} + void DlgPrefLibrary::slotExtraPlugins() { QDesktopServices::openUrl(QUrl(MIXXX_ADDONS_URL)); } @@ -158,11 +186,17 @@ void DlgPrefLibrary::slotResetToDefaults() { radioButton_dbclick_top->setChecked(false); radioButton_dbclick_deck->setChecked(true); spinBoxRowHeight->setValue(Library::kDefaultRowHeightPx); + int cpuMax = QThread::idealThreadCount(); + if (cpuMax < 1) { cpuMax = 8; } + //setCurrentIndex is zero based. threads is one based. + cmbMaxThreads->setCurrentIndex(cpuMax-1); + setLibraryFont(QApplication::font()); } void DlgPrefLibrary::slotUpdate() { initializeDirList(); + initializeThreadsCombo(); checkBox_library_scan->setChecked((bool)m_pconfig->getValueString( ConfigKey("[Library]","RescanOnStartup")).toInt()); checkbox_ID3_sync->setChecked((bool)m_pconfig->getValueString( @@ -195,12 +229,28 @@ void DlgPrefLibrary::slotUpdate() { m_iOriginalTrackTableRowHeight = m_pLibrary->getTrackTableRowHeight(); spinBoxRowHeight->setValue(m_iOriginalTrackTableRowHeight); setLibraryFont(m_originalTrackTableFont); + + m_iOriginalMaxThreads = m_pconfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); + int ideal = QThread::idealThreadCount(); + if (QThread::idealThreadCount() < 1) { + ideal = 1; + } + if (m_iOriginalMaxThreads <= 0 || m_iOriginalMaxThreads > 32) { + //Assume the value is incorrect, so fix it. + m_iOriginalMaxThreads = ideal; + m_pconfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), + m_iOriginalMaxThreads); + } + //setCurrentIndex is zero based. threads is one based. + cmbMaxThreads->setCurrentIndex(m_iOriginalMaxThreads-1); + } void DlgPrefLibrary::slotCancel() { // Undo any changes in the library font or row height. emit(setTrackTableRowHeight(m_iOriginalTrackTableRowHeight)); emit(setTrackTableFont(m_originalTrackTableFont)); + emit(setMaxThreads(m_iOriginalMaxThreads)); } void DlgPrefLibrary::slotAddDir() { @@ -333,6 +383,14 @@ void DlgPrefLibrary::slotApply() { ConfigValue(rowHeight)); } + //setCurrentIndex is zero based. threads is one based. + int threads = cmbMaxThreads->currentIndex()+1; + if (m_iOriginalMaxThreads != threads) { + m_pconfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), + threads); + } + + // TODO(rryan): Don't save here. m_pconfig->save(); } @@ -341,6 +399,14 @@ void DlgPrefLibrary::slotRowHeightValueChanged(int height) { emit(setTrackTableRowHeight(height)); } +void DlgPrefLibrary::slotMaxThreadsChanged(int cmbindex) { + //I'm not sure if it's the best to emit it when changing the value, instead of only on onApply + //setCurrentIndex is zero based. threads is one based. + if (cmbindex >=0) { + emit(setMaxThreads(cmbindex+1)); + } +} + void DlgPrefLibrary::setLibraryFont(const QFont& font) { libraryFont->setText(QString("%1 %2 %3pt").arg( font.family(), font.styleName(), QString::number(font.pointSizeF()))); diff --git a/src/preferences/dialog/dlgpreflibrary.h b/src/preferences/dialog/dlgpreflibrary.h index 339a203cf05..128f58ef7d5 100644 --- a/src/preferences/dialog/dlgpreflibrary.h +++ b/src/preferences/dialog/dlgpreflibrary.h @@ -67,13 +67,16 @@ class DlgPrefLibrary : public DlgPreferencePage, public Ui::DlgPrefLibraryDlg { void requestRelocateDir(QString currentDir, QString newDir); void setTrackTableFont(const QFont& font); void setTrackTableRowHeight(int rowHeight); + void setMaxThreads(int threads); private slots: void slotRowHeightValueChanged(int); + void slotMaxThreadsChanged(int); void slotSelectFont(); private: void initializeDirList(); + void initializeThreadsCombo(); void setLibraryFont(const QFont& font); QStandardItemModel m_dirListModel; @@ -82,6 +85,8 @@ class DlgPrefLibrary : public DlgPreferencePage, public Ui::DlgPrefLibraryDlg { bool m_baddedDirectory; QFont m_originalTrackTableFont; int m_iOriginalTrackTableRowHeight; + int m_iOriginalMaxThreads; + }; #endif diff --git a/src/preferences/dialog/dlgpreflibrarydlg.ui b/src/preferences/dialog/dlgpreflibrarydlg.ui index 508bdcb03f7..696b19a4d67 100644 --- a/src/preferences/dialog/dlgpreflibrarydlg.ui +++ b/src/preferences/dialog/dlgpreflibrarydlg.ui @@ -7,7 +7,7 @@ 0 0 593 - 791 + 821 @@ -166,6 +166,16 @@ Miscellaneous + + + + false + + + Synchronize ID3 tags on track modifications + + + @@ -186,20 +196,26 @@ - - - - false + + + + px - - Synchronize ID3 tags on track modifications + + 5 + + + 100 + + + 20 - - - - Use relative paths for playlist export if possible + + + + true @@ -220,29 +236,23 @@ - - - - px - - - 5 - - - 100 - - - 20 + + + + Use relative paths for playlist export if possible - - - - true + + + + Max number of simultaneous track analysis + + + From 711a3f936b1bd0d6c25f7a01a17d4fba9c2d26b7 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 11 Dec 2016 16:45:54 +0100 Subject: [PATCH 09/20] build fixes and changes from PR https://github.com/mixxxdj/mixxx/pull/1069 --- src/analyzer/analyzermanager.cpp | 55 +++++++++++----------- src/analyzer/analyzermanager.h | 12 ++--- src/analyzer/analyzerwaveform.cpp | 2 +- src/library/analysisfeature.cpp | 20 ++++---- src/library/analysisfeature.h | 7 ++- src/library/library.cpp | 6 ++- src/library/library.h | 9 +++- src/mixer/playermanager.cpp | 19 ++++---- src/mixer/playermanager.h | 3 ++ src/mixxx.cpp | 9 +++- src/mixxx.h | 3 ++ src/preferences/dialog/dlgpreflibrary.cpp | 4 +- src/preferences/dialog/dlgprefwaveform.cpp | 1 + 13 files changed, 86 insertions(+), 64 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index f4171b52947..5aa63a8e380 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -15,28 +15,6 @@ #include "util/timer.h" #include "util/trace.h" -AnalyzerManager* AnalyzerManager::m_pAnalyzerManager = NULL; - -//static -AnalyzerManager& AnalyzerManager::getInstance(UserSettingsPointer pConfig) { - if (!m_pAnalyzerManager) { - // There exists only one UserSettingsPointer in the app, so it doens't matter - // if we only assign it once. - m_pAnalyzerManager = new AnalyzerManager(pConfig); - } - int maxThreads = m_pAnalyzerManager->m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); - int ideal = QThread::idealThreadCount(); - if (QThread::idealThreadCount() < 1) { - ideal = 1; - } - if (maxThreads <= 0 || maxThreads > 32) { - //Assume the value is incorrect, so fix it. - maxThreads = ideal; - } - m_pAnalyzerManager->m_MaxThreads = maxThreads; - return *m_pAnalyzerManager; -} - AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) : m_pConfig(pConfig), m_nextWorkerId(0), @@ -45,17 +23,40 @@ AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) : m_backgroundWorkers(), m_foregroundWorkers(), m_pausedWorkers() { + + int maxThreads = m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); + int ideal = QThread::idealThreadCount(); + if (QThread::idealThreadCount() < 1) { + if (maxThreads > 0 && maxThreads <= 32) { + qDebug() << "Cannot detect idealThreadCount. maxThreads is: " << maxThreads; + ideal = maxThreads; + } + else { + qWarning() << "Cannot detect idealThreadCount. Using the sane value of 1"; + ideal = 1; + } + } + if (maxThreads <= 0 || maxThreads > ideal) { + qWarning() << "maxThreads value is incorrect. Changing it to " << ideal; + //Assume the value is incorrect, so fix it. + maxThreads = ideal; + } + m_MaxThreads = maxThreads; } AnalyzerManager::~AnalyzerManager() { stop(true); } -bool AnalyzerManager::isActive(bool includeForeground) { - int total = (includeForeground ? m_foregroundWorkers.size() : 0) + +bool AnalyzerManager::isActive() { + int total = m_foregroundWorkers.size() + m_backgroundWorkers.size() + m_pausedWorkers.size(); return total > 0; } +bool AnalyzerManager::isBackgroundWorkerActive() { + int total = m_backgroundWorkers.size() + m_pausedWorkers.size(); + return total > 0; +} void AnalyzerManager::stop(bool shutdown) { m_batchTrackQueue.clear(); @@ -197,7 +198,7 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { } } //Check if background workers are empty. - if (!isActive(false)) { + if (!isBackgroundWorkerActive()) { emit(queueEmpty()); } } @@ -206,7 +207,7 @@ void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) { m_backgroundWorkers.removeAll(worker); m_foregroundWorkers.removeAll(worker); m_pausedWorkers.removeAll(worker); - if (!isActive(false)) { + if (!isBackgroundWorkerActive()) { emit(queueEmpty()); } } @@ -220,7 +221,7 @@ void AnalyzerManager::slotErrorString(QString errMsg) { void AnalyzerManager::slotMaxThreadsChanged(int threads) { - // If it is running, adapt the job count. + // If it is Active, adapt the amount of workers. If it is not active, it will just update the variable. if (threads < m_MaxThreads) { //Pause workers while (!m_backgroundWorkers.isEmpty() diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h index 4ecdfd20b9e..869a8efe301 100644 --- a/src/analyzer/analyzermanager.h +++ b/src/analyzer/analyzermanager.h @@ -20,12 +20,10 @@ class AnalyzerManager : public QObject { Q_OBJECT protected: - AnalyzerManager(UserSettingsPointer pConfig); public: - //Get the only instance of the manager: - // TODO: maybe move it to an instance in MixxxMainWindow. - static AnalyzerManager& getInstance(UserSettingsPointer pConfig); + //There should exist only one AnalyzerManager in order to control the amount of threads executing. + AnalyzerManager(UserSettingsPointer pConfig); virtual ~AnalyzerManager(); //Tell the background analysis to stop. If shutdown is true. stop also the foreground analysis. @@ -34,9 +32,10 @@ class AnalyzerManager : public QObject { void analyseTrackNow(TrackPointer tio); //Add a track to be analyzed by the background analyzer. void queueAnalyseTrack(TrackPointer tio); + //Check if there is any background or foreground worker active, paused or track in queue + bool isActive(); //Check if there is any background worker active, paused or track in queue - //If includeForeground is true, it also checks the foreground workers. - bool isActive(bool includeForeground); + bool isBackgroundWorkerActive(); public slots: @@ -74,7 +73,6 @@ public slots: //the thread with low priority. AnalyzerWorker* createNewWorker(bool batchJob); - static AnalyzerManager* m_pAnalyzerManager; UserSettingsPointer m_pConfig; // Autoincremented ID to use as an identifier for each worker. int m_nextWorkerId; diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index 1887cbce122..ae34b8dae3d 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -322,7 +322,7 @@ void AnalyzerWaveform::finalize(TrackPointer tio) { //Ensure that the analyses get saved. This is also called from TrackDAO.updateTrack(), but it can //happen that we batch analyze only the waveforms (i.e. if the setting was disabled in the previous scan) //and then it is not called. The other analyzers have signals which control the update of their data. - m_pAnalysisDao->saveTrackAnalyses(tio.data()); + m_pAnalysisDao->saveTrackAnalyses(*tio); qDebug() << "Waveform generation for track" << tio->getId() << "done" << m_timer.elapsed().debugSecondsWithUnit(); diff --git a/src/library/analysisfeature.cpp b/src/library/analysisfeature.cpp index 85ddd98976c..8f1e787d996 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysisfeature.cpp @@ -15,14 +15,17 @@ #include "util/dnd.h" #include "util/debug.h" + const QString AnalysisFeature::m_sAnalysisViewName = QString("Analysis"); AnalysisFeature::AnalysisFeature(QObject* parent, UserSettingsPointer pConfig, - TrackCollection* pTrackCollection) : + TrackCollection* pTrackCollection, + AnalyzerManager* pAnalyzerManager) : LibraryFeature(parent), m_pConfig(pConfig), m_pTrackCollection(pTrackCollection), + m_pAnalyzerManager(pAnalyzerManager), m_analysisTitleName(tr("Analyze")), m_pAnalysisView(NULL) { setTitleDefault(); @@ -81,7 +84,7 @@ void AnalysisFeature::bindWidget(WLibrary* libraryWidget, m_pAnalysisView->installEventFilter(keyboard); // Let the DlgAnalysis know whether or not analysis is active. - bool bAnalysisActive = AnalyzerManager::getInstance(m_pConfig).isActive(false); + bool bAnalysisActive = m_pAnalyzerManager->isBackgroundWorkerActive(); emit(analysisActive(bAnalysisActive)); libraryWidget->registerView(m_sAnalysisViewName, m_pAnalysisView); @@ -107,16 +110,15 @@ void AnalysisFeature::activate() { } void AnalysisFeature::analyzeTracks(QList trackIds) { - AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); - connect(&analyzerManager, SIGNAL(trackProgress(int, int)), + connect(m_pAnalyzerManager, SIGNAL(trackProgress(int, int)), m_pAnalysisView, SLOT(trackAnalysisProgress(int, int))); - connect(&analyzerManager, SIGNAL(trackFinished(int)), + connect(m_pAnalyzerManager, SIGNAL(trackFinished(int)), this, SLOT(slotProgressUpdate(int))); - connect(&analyzerManager, SIGNAL(trackFinished(int)), + connect(m_pAnalyzerManager, SIGNAL(trackFinished(int)), m_pAnalysisView, SLOT(trackAnalysisFinished(int))); - connect(&analyzerManager, SIGNAL(queueEmpty()), + connect(m_pAnalyzerManager, SIGNAL(queueEmpty()), this, SLOT(cleanupAnalyzer())); emit(analysisActive(true)); @@ -125,7 +127,7 @@ void AnalysisFeature::analyzeTracks(QList trackIds) { TrackPointer pTrack = m_pTrackCollection->getTrackDAO().getTrack(trackId); if (pTrack) { //qDebug() << this << "Queueing track for analysis" << pTrack->getLocation(); - analyzerManager.queueAnalyseTrack(pTrack); + m_pAnalyzerManager->queueAnalyseTrack(pTrack); } } if (trackIds.size() > 0) { @@ -144,7 +146,7 @@ void AnalysisFeature::slotProgressUpdate(int num_left) { void AnalysisFeature::stopAnalysis() { //qDebug() << this << "stopAnalysis()"; - AnalyzerManager::getInstance(m_pConfig).stop(false); + m_pAnalyzerManager->stop(false); } void AnalysisFeature::cleanupAnalyzer() { diff --git a/src/library/analysisfeature.h b/src/library/analysisfeature.h index 32437a386d5..acfc1bbb89c 100644 --- a/src/library/analysisfeature.h +++ b/src/library/analysisfeature.h @@ -15,16 +15,18 @@ #include "library/libraryfeature.h" #include "preferences/usersettings.h" #include "treeitemmodel.h" -#include "library/dlganalysis.h" +class AnalyzerManager; class TrackCollection; +class DlgAnalysis; class AnalysisFeature : public LibraryFeature { Q_OBJECT public: AnalysisFeature(QObject* parent, UserSettingsPointer pConfig, - TrackCollection* pTrackCollection); + TrackCollection* pTrackCollection, + AnalyzerManager* pAnalyzerManager); virtual ~AnalysisFeature(); QVariant title(); @@ -63,6 +65,7 @@ class AnalysisFeature : public LibraryFeature { UserSettingsPointer m_pConfig; TrackCollection* m_pTrackCollection; + AnalyzerManager* m_pAnalyzerManager; // The title returned by title() QVariant m_Title; TreeItemModel m_childModel; diff --git a/src/library/library.cpp b/src/library/library.cpp index ecd04714633..bad3ee3866d 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -44,12 +44,14 @@ const int Library::kDefaultRowHeightPx = 20; Library::Library(QObject* parent, UserSettingsPointer pConfig, PlayerManagerInterface* pPlayerManager, - RecordingManager* pRecordingManager) : + RecordingManager* pRecordingManager, + AnalyzerManager* pAnalyzerManager) : m_pConfig(pConfig), m_pSidebarModel(new SidebarModel(parent)), m_pTrackCollection(new TrackCollection(pConfig)), m_pLibraryControl(new LibraryControl(this)), m_pRecordingManager(pRecordingManager), + m_pAnalyzerManager(m_pAnalyzerManager), m_scanner(m_pTrackCollection, pConfig) { qRegisterMetaType("Library::RemovalType"); @@ -85,7 +87,7 @@ Library::Library(QObject* parent, UserSettingsPointer pConfig, addFeature(browseFeature); addFeature(new RecordingFeature(this, pConfig, m_pTrackCollection, m_pRecordingManager)); addFeature(new SetlogFeature(this, pConfig, m_pTrackCollection)); - m_pAnalysisFeature = new AnalysisFeature(this, pConfig, m_pTrackCollection); + m_pAnalysisFeature = new AnalysisFeature(this, pConfig, m_pTrackCollection, pAnalyzerManager); connect(m_pPlaylistFeature, SIGNAL(analyzeTracks(QList)), m_pAnalysisFeature, SLOT(analyzeTracks(QList))); connect(m_pCrateFeature, SIGNAL(analyzeTracks(QList)), diff --git a/src/library/library.h b/src/library/library.h index 7a0b8b7a518..dd720ff15e2 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -34,6 +34,7 @@ class CrateFeature; class LibraryControl; class KeyboardEventFilter; class PlayerManagerInterface; +class AnalyzerManager; class Library : public QObject { Q_OBJECT @@ -41,7 +42,8 @@ class Library : public QObject { Library(QObject* parent, UserSettingsPointer pConfig, PlayerManagerInterface* pPlayerManager, - RecordingManager* pRecordingManager); + RecordingManager* pRecordingManager, + AnalyzerManager* pAnalyzerManager); virtual ~Library(); void bindWidget(WLibrary* libraryWidget, @@ -58,6 +60,10 @@ class Library : public QObject { TrackCollection* getTrackCollection() { return m_pTrackCollection; } + + AnalyzerManager* getAnalyzerManager() { + return m_pAnalyzerManager; + } inline int getTrackTableRowHeight() const { return m_iTrackTableRowHeight; @@ -131,6 +137,7 @@ class Library : public QObject { AnalysisFeature* m_pAnalysisFeature; LibraryControl* m_pLibraryControl; RecordingManager* m_pRecordingManager; + AnalyzerManager* m_pAnalyzerManager; LibraryScanner m_scanner; QFont m_trackTableFont; int m_iTrackTableRowHeight; diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index 74354a74550..8be251c2477 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -26,11 +26,13 @@ PlayerManager::PlayerManager(UserSettingsPointer pConfig, SoundManager* pSoundManager, + AnalyzerManager* pAnalyzerManager, EffectsManager* pEffectsManager, EngineMaster* pEngine) : m_mutex(QMutex::Recursive), m_pConfig(pConfig), m_pSoundManager(pSoundManager), + m_pAnalyzerManager(pAnalyzerManager), m_pEffectsManager(pEffectsManager), m_pEngine(pEngine), // NOTE(XXX) LegacySkinParser relies on these controls being Controls @@ -119,27 +121,25 @@ void PlayerManager::bindToLibrary(Library* pLibrary) { connect(this, SIGNAL(loadLocationToPlayer(QString, QString)), pLibrary, SLOT(slotLoadLocationToPlayer(QString, QString))); - AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); - // Connect the player to the analyzer queue so that loaded tracks are // analysed. foreach(Deck* pDeck, m_decks) { connect(pDeck, SIGNAL(newTrackLoaded(TrackPointer)), - &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); } // Connect the player to the analyzer queue so that loaded tracks are // analysed. foreach(Sampler* pSampler, m_samplers) { connect(pSampler, SIGNAL(newTrackLoaded(TrackPointer)), - &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); } // Connect the player to the analyzer queue so that loaded tracks are // analysed. foreach(PreviewDeck* pPreviewDeck, m_preview_decks) { connect(pPreviewDeck, SIGNAL(newTrackLoaded(TrackPointer)), - &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); } } @@ -339,9 +339,8 @@ void PlayerManager::addDeckInner() { connect(pDeck, SIGNAL(noVinylControlInputConfigured()), this, SIGNAL(noVinylControlInputConfigured())); - AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); connect(pDeck, SIGNAL(newTrackLoaded(TrackPointer)), - &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); m_players[group] = pDeck; @@ -394,9 +393,8 @@ void PlayerManager::addSamplerInner() { Sampler* pSampler = new Sampler(this, m_pConfig, m_pEngine, m_pEffectsManager, orientation, group); - AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); connect(pSampler, SIGNAL(newTrackLoaded(TrackPointer)), - &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); m_players[group] = pSampler; m_samplers.append(pSampler); @@ -422,9 +420,8 @@ void PlayerManager::addPreviewDeckInner() { m_pEffectsManager, orientation, group); - AnalyzerManager& analyzerManager = AnalyzerManager::getInstance(m_pConfig); connect(pPreviewDeck, SIGNAL(newTrackLoaded(TrackPointer)), - &analyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); m_players[group] = pPreviewDeck; m_preview_decks.append(pPreviewDeck); diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index f4903eb86e1..c1940a575c1 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -23,6 +23,7 @@ class Microphone; class PreviewDeck; class Sampler; class SoundManager; +class AnalyzerManager; class TrackCollection; // For mocking PlayerManager. @@ -56,6 +57,7 @@ class PlayerManager : public QObject, public PlayerManagerInterface { public: PlayerManager(UserSettingsPointer pConfig, SoundManager* pSoundManager, + AnalyzerManager* pAnalyzerManager, EffectsManager* pEffectsManager, EngineMaster* pEngine); virtual ~PlayerManager(); @@ -225,6 +227,7 @@ class PlayerManager : public QObject, public PlayerManagerInterface { UserSettingsPointer m_pConfig; SoundManager* m_pSoundManager; + AnalyzerManager* m_pAnalyzerManager; EffectsManager* m_pEffectsManager; EngineMaster* m_pEngine; ControlObject* m_pCONumDecks; diff --git a/src/mixxx.cpp b/src/mixxx.cpp index f08e41b414a..fec19f25d59 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -36,6 +36,7 @@ #include "library/library_preferences.h" #include "controllers/controllermanager.h" #include "controllers/keyboard/keyboardeventfilter.h" +#include "analyzer/analyzermanager.h" #include "mixer/playermanager.h" #include "recording/recordingmanager.h" #include "broadcast/broadcastmanager.h" @@ -91,6 +92,7 @@ MixxxMainWindow::MixxxMainWindow(QApplication* pApp, const CmdlineArgs& args) m_pBroadcastManager(nullptr), #endif m_pControllerManager(nullptr), + m_pAnalyzerManager(nullptr), m_pGuiTick(nullptr), #ifdef __VINYLCONTROL__ m_pVCManager(nullptr), @@ -209,6 +211,8 @@ void MixxxMainWindow::initialize(QApplication* pApp, const CmdlineArgs& args) { m_pBroadcastManager = new BroadcastManager(pConfig, m_pSoundManager); #endif + m_pAnalyzerManager = new AnalyzerManager(pConfig); + launchProgress(11); // Needs to be created before CueControl (decks) and WTrackTableView. @@ -221,7 +225,7 @@ void MixxxMainWindow::initialize(QApplication* pApp, const CmdlineArgs& args) { #endif // Create the player manager. (long) - m_pPlayerManager = new PlayerManager(pConfig, m_pSoundManager, + m_pPlayerManager = new PlayerManager(pConfig, m_pSoundManager, m_pAnalyzerManager, m_pEffectsManager, m_pEngine); connect(m_pPlayerManager, SIGNAL(noMicrophoneInputConfigured()), this, SLOT(slotNoMicrophoneInputConfigured())); @@ -264,7 +268,8 @@ void MixxxMainWindow::initialize(QApplication* pApp, const CmdlineArgs& args) { // (long) m_pLibrary = new Library(this, pConfig, m_pPlayerManager, - m_pRecordingManager); + m_pRecordingManager, + m_pAnalyzerManager); m_pPlayerManager->bindToLibrary(m_pLibrary); launchProgress(35); diff --git a/src/mixxx.h b/src/mixxx.h index a1793d56b54..6782405b2ca 100644 --- a/src/mixxx.h +++ b/src/mixxx.h @@ -42,6 +42,7 @@ class PlayerManager; class RecordingManager; class SettingsManager; class BroadcastManager; +class AnalyzerManager; class SkinLoader; class SoundManager; class VinylControlManager; @@ -145,6 +146,8 @@ class MixxxMainWindow : public QMainWindow { #endif ControllerManager* m_pControllerManager; + AnalyzerManager* m_pAnalyzerManager; + GuiTick* m_pGuiTick; VinylControlManager* m_pVCManager; diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index e75f53c357c..d6e489398c3 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -82,9 +82,9 @@ DlgPrefLibrary::DlgPrefLibrary(QWidget * parent, connect(cmbMaxThreads, SIGNAL(currentIndexChanged(int)), this, SLOT(slotMaxThreadsChanged(int))); - AnalyzerManager& manager = AnalyzerManager::getInstance(config); + AnalyzerManager* analyzerManager = m_pLibrary->getAnalyzerManager(); connect(cmbMaxThreads, SIGNAL(setMaxThreads(int)), - &manager, SLOT(slotMaxThreadsChanged(int))); + analyzerManager, SLOT(slotMaxThreadsChanged(int))); // TODO(XXX) this string should be extracted from the soundsources QString builtInFormatsStr = "Ogg Vorbis, FLAC, WAVe, AIFF"; diff --git a/src/preferences/dialog/dlgprefwaveform.cpp b/src/preferences/dialog/dlgprefwaveform.cpp index bf85e0f7f1d..e9fe7a73bdf 100644 --- a/src/preferences/dialog/dlgprefwaveform.cpp +++ b/src/preferences/dialog/dlgprefwaveform.cpp @@ -4,6 +4,7 @@ #include "preferences/waveformsettings.h" #include "waveform/waveformwidgetfactory.h" #include "waveform/renderers/waveformwidgetrenderer.h" +#include "library/trackcollection.h" DlgPrefWaveform::DlgPrefWaveform(QWidget* pParent, MixxxMainWindow* pMixxx, UserSettingsPointer pConfig, Library* pLibrary) From 569493e1caf2d5944f51aecbf4c732d6611decba Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 11 Dec 2016 18:42:56 +0100 Subject: [PATCH 10/20] second round of fixes --- src/analyzer/analyzermanager.cpp | 6 +----- src/analyzer/analyzerqueue.cpp | 10 +--------- src/analyzer/analyzerworker.cpp | 5 ----- src/test/analyserwaveformtest.cpp | 2 +- 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 5aa63a8e380..600b9436444 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -133,12 +133,8 @@ void AnalyzerManager::slotUpdateProgress(int workerIdx, struct AnalyzerWorker::p //Report that a track analysis has finished, and how many are still remaining. emit(trackFinished(m_backgroundWorkers.size() + m_batchTrackQueue.size() - 1)); } - //TODO: Which is the consequence of not calling clear? -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - progressInfo->current_track.clear(); -#else + //TODO: Which is the consequence of not calling reset? progressInfo->current_track.reset(); -#endif progressInfo->sema.release(); } diff --git a/src/analyzer/analyzerqueue.cpp b/src/analyzer/analyzerqueue.cpp index a27fe317345..a5f5a937781 100644 --- a/src/analyzer/analyzerqueue.cpp +++ b/src/analyzer/analyzerqueue.cpp @@ -295,11 +295,7 @@ void AnalyzerQueue::run() { if (m_aq.size() == 0) return; -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - m_progressInfo.current_track.clear(); -#else m_progressInfo.current_track.reset(); -#endif m_progressInfo.track_progress = 0; m_progressInfo.queue_size = 0; m_progressInfo.sema.release(); // Initialize with one @@ -414,11 +410,7 @@ void AnalyzerQueue::slotUpdateProgress() { if (m_progressInfo.current_track) { m_progressInfo.current_track->setAnalyzerProgress( m_progressInfo.track_progress); -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - m_progressInfo.current_track.clear(); -#else - m_progressInfo.current_track.reset(); -#endif + m_progressInfo.current_track.reset(); } emit(trackProgress(m_progressInfo.track_progress / 10)); if (m_progressInfo.track_progress == 1000) { diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index c434cfd9fc3..27545558a19 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -66,11 +66,6 @@ AnalyzerWorker::~AnalyzerWorker() { void AnalyzerWorker::nextTrack(TrackPointer newTrack) { m_qm.lock(); -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - m_currentTrack.clear(); -#else - m_currentTrack.reset(); -#endif m_currentTrack = newTrack; m_qwait.wakeAll(); m_qm.unlock(); diff --git a/src/test/analyserwaveformtest.cpp b/src/test/analyserwaveformtest.cpp index 5b19da1cafb..bbb99432d7e 100644 --- a/src/test/analyserwaveformtest.cpp +++ b/src/test/analyserwaveformtest.cpp @@ -16,7 +16,7 @@ namespace { class AnalyzerWaveformTest: public MixxxTest { protected: virtual void SetUp() { - aw = new AnalyzerWaveform(config()); + aw = new AnalyzerWaveform(config(), false); tio = Track::newTemporary(); tio->setSampleRate(44100); From b478ecc8ce48c23e54304bc699a7228efc0cc84b Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Mon, 12 Dec 2016 00:15:10 +0100 Subject: [PATCH 11/20] renaming of background/foreground/batch to default/priority/force, added QMutexLocker, made m_exit QAtomicInt --- src/analyzer/analyzerbeats.cpp | 8 +- src/analyzer/analyzerbeats.h | 4 +- src/analyzer/analyzermanager.cpp | 129 +++++++++++++++--------------- src/analyzer/analyzermanager.h | 39 +++++---- src/analyzer/analyzerwaveform.cpp | 8 +- src/analyzer/analyzerwaveform.h | 4 +- src/analyzer/analyzerworker.cpp | 29 +++---- src/analyzer/analyzerworker.h | 16 ++-- src/library/analysisfeature.cpp | 2 +- src/test/analyserwaveformtest.cpp | 2 +- 10 files changed, 122 insertions(+), 119 deletions(-) diff --git a/src/analyzer/analyzerbeats.cpp b/src/analyzer/analyzerbeats.cpp index 6804ccccaa5..abbb8a7de61 100644 --- a/src/analyzer/analyzerbeats.cpp +++ b/src/analyzer/analyzerbeats.cpp @@ -17,7 +17,7 @@ #include "track/beatutils.h" #include "track/track.h" -AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool batch) +AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool forceBeatDetection) : m_pConfig(pConfig), m_pVamp(NULL), m_bPreferencesReanalyzeOldBpm(false), @@ -28,7 +28,7 @@ AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool batch) m_iTotalSamples(0), m_iMinBpm(0), m_iMaxBpm(9999), - m_batch(batch) { + m_forceBeatDetection(forceBeatDetection) { } AnalyzerBeats::~AnalyzerBeats() { @@ -39,10 +39,10 @@ bool AnalyzerBeats::initialize(TrackPointer tio, int sampleRate, int totalSample return false; } - bool bPreferencesBeatDetectionEnabled = m_batch || static_cast( + bool beatDetectionEnabled = m_forceBeatDetection || static_cast( m_pConfig->getValueString( ConfigKey(BPM_CONFIG_KEY, BPM_DETECTION_ENABLED)).toInt()); - if (!bPreferencesBeatDetectionEnabled) { + if (!beatDetectionEnabled) { qDebug() << "Beat calculation is deactivated"; return false; } diff --git a/src/analyzer/analyzerbeats.h b/src/analyzer/analyzerbeats.h index dff4c94bb75..f3d876bd0e0 100644 --- a/src/analyzer/analyzerbeats.h +++ b/src/analyzer/analyzerbeats.h @@ -16,7 +16,7 @@ class AnalyzerBeats: public Analyzer { public: - AnalyzerBeats(UserSettingsPointer pConfig, bool batch); + AnalyzerBeats(UserSettingsPointer pConfig, bool forceBeatDetection); virtual ~AnalyzerBeats(); bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override; @@ -40,7 +40,7 @@ class AnalyzerBeats: public Analyzer { int m_iSampleRate, m_iTotalSamples; int m_iMinBpm, m_iMaxBpm; - bool m_batch; + bool m_forceBeatDetection; }; #endif /* ANALYZER_ANALYZERBEATS_H */ diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 600b9436444..c41c3ed40b1 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -18,15 +18,15 @@ AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) : m_pConfig(pConfig), m_nextWorkerId(0), - m_batchTrackQueue(), + m_defaultTrackQueue(), m_prioTrackQueue(), - m_backgroundWorkers(), - m_foregroundWorkers(), + m_defaultWorkers(), + m_priorityWorkers(), m_pausedWorkers() { int maxThreads = m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); int ideal = QThread::idealThreadCount(); - if (QThread::idealThreadCount() < 1) { + if (ideal < 1) { if (maxThreads > 0 && maxThreads <= 32) { qDebug() << "Cannot detect idealThreadCount. maxThreads is: " << maxThreads; ideal = maxThreads; @@ -49,18 +49,18 @@ AnalyzerManager::~AnalyzerManager() { } bool AnalyzerManager::isActive() { - int total = m_foregroundWorkers.size() + - m_backgroundWorkers.size() + m_pausedWorkers.size(); + int total = m_priorityWorkers.size() + + m_defaultWorkers.size() + m_pausedWorkers.size(); return total > 0; } -bool AnalyzerManager::isBackgroundWorkerActive() { - int total = m_backgroundWorkers.size() + m_pausedWorkers.size(); +bool AnalyzerManager::isDefaultQueueActive() { + int total = m_defaultWorkers.size() + m_pausedWorkers.size(); return total > 0; } void AnalyzerManager::stop(bool shutdown) { - m_batchTrackQueue.clear(); - QListIterator it(m_backgroundWorkers); + m_defaultTrackQueue.clear(); + QListIterator it(m_defaultWorkers); while (it.hasNext()) { AnalyzerWorker* worker = it.next(); worker->endProcess(); @@ -74,7 +74,7 @@ void AnalyzerManager::stop(bool shutdown) { } if (shutdown) { m_prioTrackQueue.clear(); - QListIterator it2(m_foregroundWorkers); + QListIterator it2(m_priorityWorkers); while (it2.hasNext()) { AnalyzerWorker* worker = it2.next(); worker->endProcess(); @@ -83,34 +83,34 @@ void AnalyzerManager::stop(bool shutdown) { //TODO: ensure that they are all forcibly stopped. } } -// Analyze it with a foreground worker. (foreground as in interactive, i.e. not a batch worker). +//Add a track to be analyzed with a priority worker. (Like those required by loading a track into a player). void AnalyzerManager::analyseTrackNow(TrackPointer tio) { - if (m_batchTrackQueue.contains(tio)) { - m_batchTrackQueue.removeAll(tio); + if (m_defaultTrackQueue.contains(tio)) { + m_defaultTrackQueue.removeAll(tio); } //TODO: There's one scenario that we still miss: load on a deck a track that is currently //being analyzed by the background worker. We cannot reuse the background worker, but we should discard its work. if (!m_prioTrackQueue.contains(tio)) { m_prioTrackQueue.append(tio); - if (m_foregroundWorkers.size() < m_MaxThreads) { - createNewWorker(false); - if (m_foregroundWorkers.size() + m_backgroundWorkers.size() > m_MaxThreads) { - AnalyzerWorker * backwork = m_backgroundWorkers.first(); + if (m_priorityWorkers.size() < m_MaxThreads) { + createNewWorker(WorkerType::priorityWorker); + if (m_priorityWorkers.size() + m_defaultWorkers.size() > m_MaxThreads) { + AnalyzerWorker * backwork = m_defaultWorkers.first(); backwork->pause(); //Ideally i would have done this on the slotPaused slot, but then i cannot //ensure i won't call pause twice for the same worker. m_pausedWorkers.append(backwork); - m_backgroundWorkers.removeAll(backwork); + m_defaultWorkers.removeAll(backwork); } } } } -// This is called from the GUI for batch analysis. +// This is called from the GUI for the analysis feature of the library. void AnalyzerManager::queueAnalyseTrack(TrackPointer tio) { - if (!m_batchTrackQueue.contains(tio)) { - m_batchTrackQueue.append(tio); - if (m_pausedWorkers.size() + m_backgroundWorkers.size() < m_MaxThreads) { - createNewWorker(true); + if (!m_defaultTrackQueue.contains(tio)) { + m_defaultTrackQueue.append(tio); + if (m_pausedWorkers.size() + m_defaultWorkers.size() < m_MaxThreads) { + createNewWorker(WorkerType::defaultWorker); } } } @@ -131,7 +131,7 @@ void AnalyzerManager::slotUpdateProgress(int workerIdx, struct AnalyzerWorker::p //Right now no one is listening to trackDone, but it's here just in case. emit(trackDone(progressInfo->current_track)); //Report that a track analysis has finished, and how many are still remaining. - emit(trackFinished(m_backgroundWorkers.size() + m_batchTrackQueue.size() - 1)); + emit(trackFinished(m_defaultWorkers.size() + m_defaultTrackQueue.size() - 1)); } //TODO: Which is the consequence of not calling reset? progressInfo->current_track.reset(); @@ -145,24 +145,24 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { //This is used when the maxThreads change. Extra workers are paused until active workers end. //Then, those are terminated and the paused workers are resumed. - TrackPointer track = TrackPointer(); + TrackPointer track; AnalyzerWorker* forepaused=nullptr; foreach(AnalyzerWorker* worker, m_pausedWorkers) { - if (!worker->isBatch()) { forepaused=worker; break; } + if (worker->isPriorized()) { forepaused=worker; break; } } if (!forepaused) { - if (worker->isBatch()) { - if (m_backgroundWorkers.size() + m_pausedWorkers.size() <= m_MaxThreads) { - //The while loop is done in the event that the track which was added to the queue is no - //longer available. - while (!track && !m_batchTrackQueue.isEmpty()) { - track = m_batchTrackQueue.dequeue(); - } + if (worker->isPriorized()) { + while (!track && !m_prioTrackQueue.isEmpty()) { + track = m_prioTrackQueue.dequeue(); } } else { - while (!track && !m_prioTrackQueue.isEmpty()) { - track = m_prioTrackQueue.dequeue(); + if (m_defaultWorkers.size() + m_pausedWorkers.size() <= m_MaxThreads) { + //The while loop is done in the event that the track which was added to the queue is no + //longer available. + while (!track && !m_defaultTrackQueue.isEmpty()) { + track = m_defaultTrackQueue.dequeue(); + } } } } @@ -172,38 +172,38 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { else { worker->endProcess(); //Removing from active lists, so that "isActive" can return the correct value. - m_backgroundWorkers.removeAll(worker); - m_foregroundWorkers.removeAll(worker); + m_defaultWorkers.removeAll(worker); + m_priorityWorkers.removeAll(worker); m_endingWorkers.append(worker); if (forepaused) { forepaused->resume(); m_pausedWorkers.removeOne(forepaused); - m_foregroundWorkers.append(forepaused); + m_priorityWorkers.append(forepaused); } else if (!m_pausedWorkers.isEmpty()) { AnalyzerWorker* otherworker = m_pausedWorkers.first(); otherworker->resume(); m_pausedWorkers.removeOne(otherworker); - if (otherworker->isBatch()) { - m_backgroundWorkers.append(otherworker); + if (otherworker->isPriorized()) { + m_priorityWorkers.append(otherworker); } else { - m_foregroundWorkers.append(otherworker); + m_defaultWorkers.append(otherworker); } } } //Check if background workers are empty. - if (!isBackgroundWorkerActive()) { + if (!isDefaultQueueActive()) { emit(queueEmpty()); } } void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) { m_endingWorkers.removeAll(worker); - m_backgroundWorkers.removeAll(worker); - m_foregroundWorkers.removeAll(worker); + m_defaultWorkers.removeAll(worker); + m_priorityWorkers.removeAll(worker); m_pausedWorkers.removeAll(worker); - if (!isBackgroundWorkerActive()) { + if (!isDefaultQueueActive()) { emit(queueEmpty()); } } @@ -220,55 +220,56 @@ void AnalyzerManager::slotMaxThreadsChanged(int threads) { // If it is Active, adapt the amount of workers. If it is not active, it will just update the variable. if (threads < m_MaxThreads) { //Pause workers - while (!m_backgroundWorkers.isEmpty() - && m_foregroundWorkers.size() + m_backgroundWorkers.size() > threads) { - AnalyzerWorker * backwork = m_backgroundWorkers.first(); + while (!m_defaultWorkers.isEmpty() + && m_priorityWorkers.size() + m_defaultWorkers.size() > threads) { + AnalyzerWorker * backwork = m_defaultWorkers.first(); backwork->pause(); //Ideally i would have done this on the slotPaused slot, but then i cannot //ensure i won't call pause twice for the same worker. m_pausedWorkers.append(backwork); - m_backgroundWorkers.removeAll(backwork); + m_defaultWorkers.removeAll(backwork); } - while (m_foregroundWorkers.size() > threads) { - AnalyzerWorker * backwork = m_foregroundWorkers.first(); + while (m_priorityWorkers.size() > threads) { + AnalyzerWorker * backwork = m_priorityWorkers.first(); backwork->pause(); //Ideally i would have done this on the slotPaused slot, but then i cannot //ensure i won't call pause twice for the same worker. m_pausedWorkers.append(backwork); - m_foregroundWorkers.removeAll(backwork); + m_priorityWorkers.removeAll(backwork); } } else { //resume workers int pendingworkers=threads-m_MaxThreads; foreach(AnalyzerWorker* worker, m_pausedWorkers) { - if (!worker->isBatch() && pendingworkers > 0) { + if (worker->isPriorized() && pendingworkers > 0) { worker->resume(); m_pausedWorkers.removeOne(worker); - m_foregroundWorkers.append(worker); + m_priorityWorkers.append(worker); --pendingworkers; } } foreach(AnalyzerWorker* worker, m_pausedWorkers) { - if (worker->isBatch() && pendingworkers > 0) { + if (!worker->isPriorized() && pendingworkers > 0) { worker->resume(); m_pausedWorkers.removeOne(worker); - m_backgroundWorkers.append(worker); + m_defaultWorkers.append(worker); --pendingworkers; } } //Create new workers, if tracks in queue. - pendingworkers = math_min(pendingworkers,m_batchTrackQueue.size()); + pendingworkers = math_min(pendingworkers,m_defaultTrackQueue.size()); for ( ;pendingworkers > 0; --pendingworkers) { - createNewWorker(true); + createNewWorker(WorkerType::defaultWorker); } } m_MaxThreads=threads; } -AnalyzerWorker* AnalyzerManager::createNewWorker(bool batchJob) { +AnalyzerWorker* AnalyzerManager::createNewWorker(WorkerType wtype) { + bool priorized = (wtype == WorkerType::priorityWorker); QThread* thread = new QThread(); - AnalyzerWorker* worker = new AnalyzerWorker(m_pConfig, ++m_nextWorkerId, batchJob); + AnalyzerWorker* worker = new AnalyzerWorker(m_pConfig, ++m_nextWorkerId, priorized); worker->moveToThread(thread); //Auto startup and auto cleanup of worker and thread. connect(thread, SIGNAL(started()), worker, SLOT(slotProcess())); @@ -282,11 +283,11 @@ AnalyzerWorker* AnalyzerManager::createNewWorker(bool batchJob) { connect(worker, SIGNAL(workerFinished(AnalyzerWorker*)), this, SLOT(slotWorkerFinished(AnalyzerWorker*))); connect(worker, SIGNAL(error(QString)), this, SLOT(slotErrorString(QString))); thread->start(QThread::LowPriority); - if (batchJob) { - m_backgroundWorkers.append(worker); + if (priorized) { + m_priorityWorkers.append(worker); } else { - m_foregroundWorkers.append(worker); + m_defaultWorkers.append(worker); } return worker; } diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h index 869a8efe301..3a7a6bfd1d4 100644 --- a/src/analyzer/analyzermanager.h +++ b/src/analyzer/analyzermanager.h @@ -20,22 +20,27 @@ class AnalyzerManager : public QObject { Q_OBJECT protected: +enum class WorkerType { + defaultWorker, + priorityWorker +}; public: //There should exist only one AnalyzerManager in order to control the amount of threads executing. AnalyzerManager(UserSettingsPointer pConfig); virtual ~AnalyzerManager(); - //Tell the background analysis to stop. If shutdown is true. stop also the foreground analysis. + //Tell the analyzers of the default queue to stop. If shutdown is true. stop also the priority analyzers. void stop(bool shutdown); - //This method might need to be protected an called only via slot. + //Add a track to be analyzed with a priority worker. (Like those required by loading a track into a player). + //This method might need to be protected an called only via the slotAnalyzeTrack slot. void analyseTrackNow(TrackPointer tio); - //Add a track to be analyzed by the background analyzer. + //Add a track to be analyzed by the default queue. void queueAnalyseTrack(TrackPointer tio); - //Check if there is any background or foreground worker active, paused or track in queue + //Check if there is any default worker or priority worker active, paused or a track in queue bool isActive(); - //Check if there is any background worker active, paused or track in queue - bool isBackgroundWorkerActive(); + //Check if there is any default worker active, paused or track in queue + bool isDefaultQueueActive(); public slots: @@ -65,32 +70,32 @@ public slots: //tracks remain to be scanned. It is currently used by the AnalysisFeature and DlgAnalisys //to show the track progression. void trackFinished(int size); - //Indicates that the background analysis job has finished (I.e. all background tracks have been + //Indicates that the default analysis job has finished (I.e. all tracks queued on the default queue have been // analyzed). It is used for the UI to refresh the text and buttons. void queueEmpty(); private: //Method that creates a worker, assigns it to a new thread and the correct list, and starts //the thread with low priority. - AnalyzerWorker* createNewWorker(bool batchJob); + AnalyzerWorker* createNewWorker(WorkerType wtype); UserSettingsPointer m_pConfig; // Autoincremented ID to use as an identifier for each worker. int m_nextWorkerId; - // Max number of threads to be active analyzing at a time including both, background and foreground analysis + // Max number of threads to be active analyzing at a time including both, default and priority analysis int m_MaxThreads; // TODO: We do a "contains" over these queues before adding a new track to them. // The more tracks that we add to the queue, the slower this check is. // No UI response is shown until all tracks are queued. - // The processing queue for batch analysis - QQueue m_batchTrackQueue; - // The processing queue for the player analysis. + // The processing queue for the analysis feature of the library. + QQueue m_defaultTrackQueue; + // The processing queue for the analysis of tracks loaded into players. QQueue m_prioTrackQueue; - //List of background workers (excluding the paused ones). - QList m_backgroundWorkers; - //List of foreground workers (foreground workers are not paused) - QList m_foregroundWorkers; - //List of background workers that are currently paused + //List of default workers (excluding the paused ones). + QList m_defaultWorkers; + //List of priority workers (excluding the paused ones). + QList m_priorityWorkers; + //List of workers that are currently paused (priority workers are only paused if it was required from reducing the maxThreads) QList m_pausedWorkers; //This list is used mostly so that isActive() can return the correct value QList m_endingWorkers; diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index ae34b8dae3d..2ccd0c4d1eb 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -10,9 +10,9 @@ #include "track/track.h" #include "waveform/waveformfactory.h" -AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig, bool batch) : +AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig, bool forceAnalysis) : m_skipProcessing(false), - m_batch(batch), + m_forceAnalysis(forceAnalysis), m_pConfig(pConfig), m_waveformData(nullptr), m_waveformSummaryData(nullptr), @@ -106,7 +106,7 @@ bool AnalyzerWaveform::initialize(TrackPointer tio, int sampleRate, int totalSam } bool AnalyzerWaveform::isDisabledOrLoadStoredSuccess(TrackPointer tio) const { - if (m_batch && !m_pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) { + if (!m_forceAnalysis && !m_pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"))) { return true; } @@ -320,7 +320,7 @@ void AnalyzerWaveform::finalize(TrackPointer tio) { test_heatMap->save("heatMap.png"); #endif //Ensure that the analyses get saved. This is also called from TrackDAO.updateTrack(), but it can - //happen that we batch analyze only the waveforms (i.e. if the setting was disabled in the previous scan) + //happen that we analyze only the waveforms (i.e. if the config setting was disabled in a previous scan) //and then it is not called. The other analyzers have signals which control the update of their data. m_pAnalysisDao->saveTrackAnalyses(*tio); diff --git a/src/analyzer/analyzerwaveform.h b/src/analyzer/analyzerwaveform.h index e54ac34242e..75cf0faafc7 100644 --- a/src/analyzer/analyzerwaveform.h +++ b/src/analyzer/analyzerwaveform.h @@ -140,7 +140,7 @@ struct WaveformStride { class AnalyzerWaveform : public Analyzer { public: - AnalyzerWaveform(UserSettingsPointer pConfig, bool batch); + AnalyzerWaveform(UserSettingsPointer pConfig, bool forceAnalysis); virtual ~AnalyzerWaveform(); bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override; @@ -158,7 +158,7 @@ class AnalyzerWaveform : public Analyzer { void storeIfGreater(float* pDest, float source); bool m_skipProcessing; - bool m_batch; + bool m_forceAnalysis; UserSettingsPointer m_pConfig; WaveformPointer m_waveform; diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index 27545558a19..7b0ff5a1dfe 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -39,10 +39,10 @@ namespace { #define FINALIZE_PROMILLE 1 // --- CONSTRUCTOR --- -AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool batchJob) : +AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool priorized) : m_pConfig(pConfig), m_analyzelist(), - m_batchJob(batchJob), + m_priorizedJob(priorized), m_workerIdx(workerIdx), m_sampleBuffer(kAnalysisSamplesPerBlock), m_exit(false), @@ -65,25 +65,22 @@ AnalyzerWorker::~AnalyzerWorker() { } void AnalyzerWorker::nextTrack(TrackPointer newTrack) { - m_qm.lock(); + QMutexLocker locker(&m_qm); m_currentTrack = newTrack; m_qwait.wakeAll(); - m_qm.unlock(); } void AnalyzerWorker::pause() { m_pauseRequested = true; } void AnalyzerWorker::resume() { - m_qm.lock(); + QMutexLocker locker(&m_qm); m_qwait.wakeAll(); - m_qm.unlock(); } void AnalyzerWorker::endProcess() { + QMutexLocker locker(&m_qm); m_exit = true; - m_qm.lock(); m_qwait.wakeAll(); - m_qm.unlock(); } // This is called from the AnalyzerWorker thread @@ -167,10 +164,9 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud // When a priority analysis comes in, we pause this working thread until one prioritized // worker finishes. Once it finishes, this worker will get resumed. if (m_pauseRequested.fetchAndStoreAcquire(false)) { - m_qm.lock(); + QMutexLocker locker(&m_qm); emit(paused(this)); m_qwait.wait(&m_qm); - m_qm.unlock(); } if (m_exit) { @@ -198,10 +194,11 @@ void AnalyzerWorker::slotProcess() { while (!m_exit) { //We emit waitingForNextTrack to inform that we're done and we need a new track. - m_qm.lock(); - emit(waitingForNextTrack(this)); - m_qwait.wait(&m_qm); - m_qm.unlock(); + { + QMutexLocker locker(&m_qm); + emit(waitingForNextTrack(this)); + m_qwait.wait(&m_qm); + } // We recheck m_exit, since it's also the way that the manager indicates that there are no // more tracks to process. if (m_exit) { @@ -289,12 +286,12 @@ void AnalyzerWorker::emitUpdateProgress(int progress) { } void AnalyzerWorker::createAnalyzers() { - m_analyzelist.append(new AnalyzerWaveform(m_pConfig, m_batchJob)); + m_analyzelist.append(new AnalyzerWaveform(m_pConfig, m_priorizedJob)); m_analyzelist.append(new AnalyzerGain(m_pConfig)); m_analyzelist.append(new AnalyzerEbur128(m_pConfig)); #ifdef __VAMP__ VampAnalyzer::initializePluginPaths(); - m_analyzelist.append(new AnalyzerBeats(m_pConfig, m_batchJob)); + m_analyzelist.append(new AnalyzerBeats(m_pConfig, !m_priorizedJob)); m_analyzelist.append(new AnalyzerKey(m_pConfig)); #endif } diff --git a/src/analyzer/analyzerworker.h b/src/analyzer/analyzerworker.h index 8e3c01bf973..6e1abf24834 100644 --- a/src/analyzer/analyzerworker.h +++ b/src/analyzer/analyzerworker.h @@ -36,9 +36,9 @@ class AnalyzerWorker : public QObject { QSemaphore sema; }; - // Constructor. If it is a batch job, the analyzers might be configured differently. + // Constructor. If it is a priorized job, the analyzers are configured differently. // Call Qthread->start() when you are ready for the worker to start. - AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool batchJob); + AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool priorized); virtual ~AnalyzerWorker(); //Called by the manager as a response to the waitingForNextTrack signal. and ONLY then. @@ -51,8 +51,8 @@ class AnalyzerWorker : public QObject { // An updateProgress signal with progress 0 will be emited and also the finished signal. // The AnalyzerManager connects it so that it will delete itself and the Qthread. void endProcess(); - // Is this a batch worker? - bool isBatch(); + // Is this a priorized worker? + bool isPriorized(); public slots: //starts the analysis job. @@ -83,12 +83,12 @@ public slots: UserSettingsPointer m_pConfig; QList m_analyzelist; - bool m_batchJob; + bool m_priorizedJob; int m_workerIdx; SampleBuffer m_sampleBuffer; TrackPointer m_currentTrack; - bool m_exit; + QAtomicInt m_exit; QAtomicInt m_pauseRequested; QMutex m_qm; QWaitCondition m_qwait; @@ -96,8 +96,8 @@ public slots: }; -inline bool AnalyzerWorker::isBatch() { - return m_batchJob; +inline bool AnalyzerWorker::isPriorized() { + return m_priorizedJob; } #endif /* ANALYZER_ANALYZERWORKER_H */ diff --git a/src/library/analysisfeature.cpp b/src/library/analysisfeature.cpp index 8f1e787d996..fa6d80c7371 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysisfeature.cpp @@ -84,7 +84,7 @@ void AnalysisFeature::bindWidget(WLibrary* libraryWidget, m_pAnalysisView->installEventFilter(keyboard); // Let the DlgAnalysis know whether or not analysis is active. - bool bAnalysisActive = m_pAnalyzerManager->isBackgroundWorkerActive(); + bool bAnalysisActive = m_pAnalyzerManager->isDefaultQueueActive(); emit(analysisActive(bAnalysisActive)); libraryWidget->registerView(m_sAnalysisViewName, m_pAnalysisView); diff --git a/src/test/analyserwaveformtest.cpp b/src/test/analyserwaveformtest.cpp index bbb99432d7e..413e5b1e861 100644 --- a/src/test/analyserwaveformtest.cpp +++ b/src/test/analyserwaveformtest.cpp @@ -16,7 +16,7 @@ namespace { class AnalyzerWaveformTest: public MixxxTest { protected: virtual void SetUp() { - aw = new AnalyzerWaveform(config(), false); + aw = new AnalyzerWaveform(config(), true); tio = Track::newTemporary(); tio->setSampleRate(44100); From 4bf11521e55d59ec5fff8c77c4a5fff57a8ffc61 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Fri, 16 Dec 2016 00:35:03 +0100 Subject: [PATCH 12/20] bugfixes related to changing maxthreads --- src/analyzer/analyzermanager.cpp | 8 ++++---- src/preferences/dialog/dlgpreflibrary.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index c41c3ed40b1..29fdd2dbd4f 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -32,12 +32,12 @@ AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) : ideal = maxThreads; } else { - qWarning() << "Cannot detect idealThreadCount. Using the sane value of 1"; + qWarning() << "Cannot detect idealThreadCount and maxThreads is incorrect: " << maxTrheads" <<. Using the sane value of 1"; ideal = 1; } } if (maxThreads <= 0 || maxThreads > ideal) { - qWarning() << "maxThreads value is incorrect. Changing it to " << ideal; + qWarning() << "maxThreads value is incorrect: " << maxTrheads << ". Changing it to " << ideal; //Assume the value is incorrect, so fix it. maxThreads = ideal; } @@ -147,8 +147,8 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { //Then, those are terminated and the paused workers are resumed. TrackPointer track; AnalyzerWorker* forepaused=nullptr; - foreach(AnalyzerWorker* worker, m_pausedWorkers) { - if (worker->isPriorized()) { forepaused=worker; break; } + foreach(AnalyzerWorker* theworker, m_pausedWorkers) { + if (theworker->isPriorized()) { forepaused=theworker; break; } } if (!forepaused) { if (worker->isPriorized()) { diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index d6e489398c3..e3ed4d7d82b 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -83,7 +83,7 @@ DlgPrefLibrary::DlgPrefLibrary(QWidget * parent, this, SLOT(slotMaxThreadsChanged(int))); AnalyzerManager* analyzerManager = m_pLibrary->getAnalyzerManager(); - connect(cmbMaxThreads, SIGNAL(setMaxThreads(int)), + connect(this, SIGNAL(setMaxThreads(int)), analyzerManager, SLOT(slotMaxThreadsChanged(int))); // TODO(XXX) this string should be extracted from the soundsources From 1613fb6113a602cb81a78a24a7890611dcf34b95 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Fri, 16 Dec 2016 16:55:23 +0100 Subject: [PATCH 13/20] build fix, oups! --- src/analyzer/analyzermanager.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 29fdd2dbd4f..e9d2bbe561f 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -24,20 +24,20 @@ AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig) : m_priorityWorkers(), m_pausedWorkers() { - int maxThreads = m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); int ideal = QThread::idealThreadCount(); + int maxThreads = m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads"), ideal); if (ideal < 1) { if (maxThreads > 0 && maxThreads <= 32) { qDebug() << "Cannot detect idealThreadCount. maxThreads is: " << maxThreads; ideal = maxThreads; } else { - qWarning() << "Cannot detect idealThreadCount and maxThreads is incorrect: " << maxTrheads" <<. Using the sane value of 1"; + qWarning() << "Cannot detect idealThreadCount and maxThreads is incorrect: " << maxThreads <<". Using the sane value of 1"; ideal = 1; } } if (maxThreads <= 0 || maxThreads > ideal) { - qWarning() << "maxThreads value is incorrect: " << maxTrheads << ". Changing it to " << ideal; + qWarning() << "maxThreads value is incorrect: " << maxThreads << ". Changing it to " << ideal; //Assume the value is incorrect, so fix it. maxThreads = ideal; } @@ -209,6 +209,7 @@ void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) { } void AnalyzerManager::slotPaused(AnalyzerWorker* worker) { //No useful code to execute right now. + Q_UNUSED(worker); } void AnalyzerManager::slotErrorString(QString errMsg) { //TODO: This is currently unused. From cfb55423562f6c3d5d84cc71fba1f5033fd91b44 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 29 Jan 2017 11:46:02 +0100 Subject: [PATCH 14/20] leave one idle thread whenever there's a priority thread running. This speeds up generation when Mixxx is in use --- src/analyzer/analyzermanager.cpp | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index e9d2bbe561f..2afed9e00ae 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -92,9 +92,20 @@ void AnalyzerManager::analyseTrackNow(TrackPointer tio) { //being analyzed by the background worker. We cannot reuse the background worker, but we should discard its work. if (!m_prioTrackQueue.contains(tio)) { m_prioTrackQueue.append(tio); - if (m_priorityWorkers.size() < m_MaxThreads) { + if (m_priorityWorkers.isEmpty() && m_defaultWorkers.size() > 0) { + //In order to keep the application responsive, and ensure that a priority worker is + //not slowed down by default workers (because they have the same OS thread priority), + //we stop one additional default worker + AnalyzerWorker * backwork = m_defaultWorkers.first(); + backwork->pause(); + //Ideally i would have done this on the slotPaused slot, but then i cannot + //ensure i won't call pause twice for the same worker. + m_pausedWorkers.append(backwork); + m_defaultWorkers.removeAll(backwork); + } + if (m_priorityWorkers.size() < m_MaxThreads-1) { createNewWorker(WorkerType::priorityWorker); - if (m_priorityWorkers.size() + m_defaultWorkers.size() > m_MaxThreads) { + if (m_priorityWorkers.size() + m_defaultWorkers.size() > m_MaxThreads-1) { AnalyzerWorker * backwork = m_defaultWorkers.first(); backwork->pause(); //Ideally i would have done this on the slotPaused slot, but then i cannot @@ -107,9 +118,11 @@ void AnalyzerManager::analyseTrackNow(TrackPointer tio) { } // This is called from the GUI for the analysis feature of the library. void AnalyzerManager::queueAnalyseTrack(TrackPointer tio) { + //See notes on analyseTrackNow of why we reduce the number of threads in this case. + int maxDefThreads = (m_priorityWorkers.isEmpty()) ? m_MaxThreads : m_MaxThreads-1; if (!m_defaultTrackQueue.contains(tio)) { m_defaultTrackQueue.append(tio); - if (m_pausedWorkers.size() + m_defaultWorkers.size() < m_MaxThreads) { + if (m_pausedWorkers.size() + m_defaultWorkers.size() < maxDefThreads) { createNewWorker(WorkerType::defaultWorker); } } @@ -185,12 +198,7 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { AnalyzerWorker* otherworker = m_pausedWorkers.first(); otherworker->resume(); m_pausedWorkers.removeOne(otherworker); - if (otherworker->isPriorized()) { - m_priorityWorkers.append(otherworker); - } - else { - m_defaultWorkers.append(otherworker); - } + m_defaultWorkers.append(otherworker); } } //Check if background workers are empty. @@ -218,11 +226,13 @@ void AnalyzerManager::slotErrorString(QString errMsg) { void AnalyzerManager::slotMaxThreadsChanged(int threads) { + //See notes on analyseTrackNow of why we reduce the number of threads in this case. + int maxDefThreads = (m_priorityWorkers.isEmpty()) ? threads : threads-1; // If it is Active, adapt the amount of workers. If it is not active, it will just update the variable. if (threads < m_MaxThreads) { //Pause workers while (!m_defaultWorkers.isEmpty() - && m_priorityWorkers.size() + m_defaultWorkers.size() > threads) { + && m_priorityWorkers.size() + m_defaultWorkers.size() > maxDefThreads) { AnalyzerWorker * backwork = m_defaultWorkers.first(); backwork->pause(); //Ideally i would have done this on the slotPaused slot, but then i cannot @@ -250,6 +260,9 @@ void AnalyzerManager::slotMaxThreadsChanged(int threads) { --pendingworkers; } } + if (!m_priorityWorkers.isEmpty() && pendingworkers > 0) { + pendingworkers--; + } foreach(AnalyzerWorker* worker, m_pausedWorkers) { if (!worker->isPriorized() && pendingworkers > 0) { worker->resume(); From bb09bb735d51ccaed0a95513d017d9e6414e29bc Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 29 Jan 2017 23:44:33 +0100 Subject: [PATCH 15/20] corrected VAMP_PATH multi-initialization. Corrected trackfinished called multiple times --- src/analyzer/analyzermanager.cpp | 4 ---- src/analyzer/analyzerwaveform.cpp | 3 +++ src/analyzer/analyzerwaveform.h | 2 +- src/analyzer/analyzerworker.cpp | 6 ++---- src/library/analysisfeature.cpp | 23 +++++++++++------------ src/library/dlganalysis.cpp | 2 +- src/mixxx.cpp | 7 +++++++ src/preferences/dialog/dlgprefbeats.cpp | 2 -- src/preferences/dialog/dlgprefkey.cpp | 2 -- 9 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 2afed9e00ae..df624b9c55e 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -201,10 +201,6 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { m_defaultWorkers.append(otherworker); } } - //Check if background workers are empty. - if (!isDefaultQueueActive()) { - emit(queueEmpty()); - } } void AnalyzerManager::slotWorkerFinished(AnalyzerWorker* worker) { m_endingWorkers.removeAll(worker); diff --git a/src/analyzer/analyzerwaveform.cpp b/src/analyzer/analyzerwaveform.cpp index 2ccd0c4d1eb..604fe69d460 100644 --- a/src/analyzer/analyzerwaveform.cpp +++ b/src/analyzer/analyzerwaveform.cpp @@ -10,6 +10,8 @@ #include "track/track.h" #include "waveform/waveformfactory.h" +QMutex AnalyzerWaveform::s_mutex; + AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig, bool forceAnalysis) : m_skipProcessing(false), m_forceAnalysis(forceAnalysis), @@ -25,6 +27,7 @@ AnalyzerWaveform::AnalyzerWaveform(UserSettingsPointer pConfig, bool forceAnalys m_filter[1] = 0; m_filter[2] = 0; + QMutexLocker lock(&s_mutex); static int i = 0; m_database = QSqlDatabase::addDatabase("QSQLITE", "WAVEFORM_ANALYSIS" + QString::number(i++)); if (!m_database.isOpen()) { diff --git a/src/analyzer/analyzerwaveform.h b/src/analyzer/analyzerwaveform.h index 75cf0faafc7..3b7835c7f4e 100644 --- a/src/analyzer/analyzerwaveform.h +++ b/src/analyzer/analyzerwaveform.h @@ -177,7 +177,7 @@ class AnalyzerWaveform : public Analyzer { PerformanceTimer m_timer; QSqlDatabase m_database; std::unique_ptr m_pAnalysisDao; - + static QMutex s_mutex; #ifdef TEST_HEAT_MAP QImage* test_heatMap; #endif diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index 7b0ff5a1dfe..bfac2758eed 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -8,7 +8,6 @@ #ifdef __VAMP__ #include "analyzer/analyzerbeats.h" #include "analyzer/analyzerkey.h" -#include "analyzer/vamp/vampanalyzer.h" #endif #include "analyzer/analyzergain.h" #include "analyzer/analyzerebur128.h" @@ -36,7 +35,7 @@ namespace { // 1 to display the text "finalizing" // 100 for 10% step after finalize // NOTE: If this is changed, change woverview.cpp slotAnalyzerProgress(). -#define FINALIZE_PROMILLE 1 +#define FINALIZE_PROMILLE 1.0 // --- CONSTRUCTOR --- AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, int workerIdx, bool priorized) : @@ -148,7 +147,7 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud DEBUG_ASSERT(pAudioSource->isValidFrameIndex(frameIndex)); const double frameProgress = double(frameIndex) / double(pAudioSource->getMaxFrameIndex()); - int progressPromille = frameProgress * (1000 - FINALIZE_PROMILLE); + int progressPromille = frameProgress * (1000.0 - FINALIZE_PROMILLE); if (m_progressInfo.track_progress != progressPromille && progressUpdateInhibitTimer.elapsed() > 60) { @@ -290,7 +289,6 @@ void AnalyzerWorker::createAnalyzers() { m_analyzelist.append(new AnalyzerGain(m_pConfig)); m_analyzelist.append(new AnalyzerEbur128(m_pConfig)); #ifdef __VAMP__ - VampAnalyzer::initializePluginPaths(); m_analyzelist.append(new AnalyzerBeats(m_pConfig, !m_priorizedJob)); m_analyzelist.append(new AnalyzerKey(m_pConfig)); #endif diff --git a/src/library/analysisfeature.cpp b/src/library/analysisfeature.cpp index fa6d80c7371..0ada919b525 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysisfeature.cpp @@ -81,10 +81,21 @@ void AnalysisFeature::bindWidget(WLibrary* libraryWidget, connect(this, SIGNAL(trackAnalysisStarted(int)), m_pAnalysisView, SLOT(trackAnalysisStarted(int))); + connect(m_pAnalyzerManager, SIGNAL(trackProgress(int, int)), + m_pAnalysisView, SLOT(trackAnalysisProgress(int, int))); + connect(m_pAnalyzerManager, SIGNAL(trackFinished(int)), + this, SLOT(slotProgressUpdate(int))); + connect(m_pAnalyzerManager, SIGNAL(trackFinished(int)), + m_pAnalysisView, SLOT(trackAnalysisFinished(int))); + + connect(m_pAnalyzerManager, SIGNAL(queueEmpty()), + this, SLOT(cleanupAnalyzer())); + m_pAnalysisView->installEventFilter(keyboard); // Let the DlgAnalysis know whether or not analysis is active. bool bAnalysisActive = m_pAnalyzerManager->isDefaultQueueActive(); + qDebug() << "AnalysisFeature::bindWidget " << bAnalysisActive; emit(analysisActive(bAnalysisActive)); libraryWidget->registerView(m_sAnalysisViewName, m_pAnalysisView); @@ -111,16 +122,6 @@ void AnalysisFeature::activate() { void AnalysisFeature::analyzeTracks(QList trackIds) { - connect(m_pAnalyzerManager, SIGNAL(trackProgress(int, int)), - m_pAnalysisView, SLOT(trackAnalysisProgress(int, int))); - connect(m_pAnalyzerManager, SIGNAL(trackFinished(int)), - this, SLOT(slotProgressUpdate(int))); - connect(m_pAnalyzerManager, SIGNAL(trackFinished(int)), - m_pAnalysisView, SLOT(trackAnalysisFinished(int))); - - connect(m_pAnalyzerManager, SIGNAL(queueEmpty()), - this, SLOT(cleanupAnalyzer())); - emit(analysisActive(true)); for (const auto& trackId: trackIds) { @@ -145,14 +146,12 @@ void AnalysisFeature::slotProgressUpdate(int num_left) { } void AnalysisFeature::stopAnalysis() { - //qDebug() << this << "stopAnalysis()"; m_pAnalyzerManager->stop(false); } void AnalysisFeature::cleanupAnalyzer() { setTitleDefault(); emit(analysisActive(false)); - } bool AnalysisFeature::dropAccept(QList urls, QObject* pSource) { diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index e33e94d4ff9..8c50771fe5f 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -162,7 +162,7 @@ void DlgAnalysis::trackAnalysisFinished(int size) { void DlgAnalysis::trackAnalysisProgress(int worker, int progress) { if (m_bAnalysisActive) { m_percentages[worker] = progress; - //This is a bit cumbersome, yes, I just avoided to change the tranlating text. + //This is a bit cumbersome, yes, I just avoided to change the translating text. QString perc; bool add = false; foreach(int percentage, m_percentages) { diff --git a/src/mixxx.cpp b/src/mixxx.cpp index 60662db04e8..85bf3a0fb3b 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -72,6 +72,9 @@ #ifdef __MODPLUG__ #include "preferences/dialog/dlgprefmodplug.h" #endif +#ifdef __VAMP__ +#include "analyzer/vamp/vampanalyzer.h" +#endif // static const int MixxxMainWindow::kMicrophoneCount = 4; @@ -163,6 +166,10 @@ void MixxxMainWindow::initialize(QApplication* pApp, const CmdlineArgs& args) { QString resourcePath = pConfig->getResourcePath(); FontUtils::initializeFonts(resourcePath); // takes a long time +#ifdef __VAMP__ + //This call sets the environment variable so that the plugins can be found. + VampAnalyzer::initializePluginPaths(); +#endif launchProgress(2); diff --git a/src/preferences/dialog/dlgprefbeats.cpp b/src/preferences/dialog/dlgprefbeats.cpp index 39bde518e67..001581c2b08 100644 --- a/src/preferences/dialog/dlgprefbeats.cpp +++ b/src/preferences/dialog/dlgprefbeats.cpp @@ -5,7 +5,6 @@ #include -#include "analyzer/vamp/vampanalyzer.h" #include "control/controlobject.h" #include "preferences/dialog/dlgprefbeats.h" #include "track/beat_preferences.h" @@ -218,7 +217,6 @@ void DlgPrefBeats::slotApply() { } void DlgPrefBeats::populate() { - VampAnalyzer::initializePluginPaths(); m_listIdentifier.clear(); m_listName.clear(); m_listLibrary.clear(); diff --git a/src/preferences/dialog/dlgprefkey.cpp b/src/preferences/dialog/dlgprefkey.cpp index a95e608540d..f32a861db82 100644 --- a/src/preferences/dialog/dlgprefkey.cpp +++ b/src/preferences/dialog/dlgprefkey.cpp @@ -20,7 +20,6 @@ #include #include -#include "analyzer/vamp/vampanalyzer.h" #include "analyzer/vamp/vamppluginloader.h" #include "control/controlproxy.h" #include "track/key_preferences.h" @@ -279,7 +278,6 @@ void DlgPrefKey::slotUpdate() { } void DlgPrefKey::populate() { - VampAnalyzer::initializePluginPaths(); m_listIdentifier.clear(); m_listName.clear(); m_listLibrary.clear(); From 8a0e69588a5d2682d4f4a6fa2a64c98302838ff4 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 12 Feb 2017 14:39:48 +0100 Subject: [PATCH 16/20] restart the paused default worker once all the priority workers finish --- src/analyzer/analyzermanager.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index df624b9c55e..e9dd562e0bc 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -199,6 +199,13 @@ void AnalyzerManager::slotNextTrack(AnalyzerWorker* worker) { otherworker->resume(); m_pausedWorkers.removeOne(otherworker); m_defaultWorkers.append(otherworker); + if (m_priorityWorkers.isEmpty() && !m_pausedWorkers.isEmpty()) { + //Once the priority workers have ended, restart the extra paused default worker. + otherworker = m_pausedWorkers.first(); + otherworker->resume(); + m_pausedWorkers.removeOne(otherworker); + m_defaultWorkers.append(otherworker); + } } } } From 7b09641fa7261e002977e422ccd4e69a82217f37 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Sun, 19 Nov 2017 16:35:18 +0100 Subject: [PATCH 17/20] Several fixes and retouches. I believe this fixes the rare crashes that I had --- src/analyzer/analyzermanager.cpp | 4 +- src/analyzer/analyzermanager.h | 2 +- src/analyzer/analyzerqueue.cpp | 46 ------ src/analyzer/analyzerworker.cpp | 160 ++++++++++---------- src/analyzer/analyzerworker.h | 2 +- src/engine/sidechain/enginebroadcast.cpp | 2 +- src/library/library.cpp | 2 +- src/mixer/playermanager.cpp | 4 +- src/preferences/dialog/dlgpreflibrary.cpp | 17 +-- src/preferences/dialog/dlgpreflibrary.h | 1 - src/preferences/dialog/dlgpreflibrarydlg.ui | 60 ++++---- 11 files changed, 118 insertions(+), 182 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 8c20ff10cc9..29cc2312341 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -120,7 +120,7 @@ void AnalyzerManager::analyseTrackNow(TrackPointer tio) { } // This is called from the GUI for the analysis feature of the library. void AnalyzerManager::queueAnalyseTrack(TrackPointer tio) { - //See notes on analyseTrackNow of why we reduce the number of threads in this case. + //See notes on analyseTrackNow of why we reduce the number of threads if there are priority workers. int maxDefThreads = (m_priorityWorkers.isEmpty()) ? m_MaxThreads : m_MaxThreads-1; if (!m_defaultTrackQueue.contains(tio)) { m_defaultTrackQueue.append(tio); @@ -231,7 +231,7 @@ void AnalyzerManager::slotErrorString(QString errMsg) { void AnalyzerManager::slotMaxThreadsChanged(int threads) { - //See notes on analyseTrackNow of why we reduce the number of threads in this case. + //See notes on analyseTrackNow of why we reduce the number of threads if there are priority workers. int maxDefThreads = (m_priorityWorkers.isEmpty()) ? threads : threads-1; // If it is Active, adapt the amount of workers. If it is not active, it will just update the variable. if (threads < m_MaxThreads) { diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h index b9331bbb724..1f215c282d5 100644 --- a/src/analyzer/analyzermanager.h +++ b/src/analyzer/analyzermanager.h @@ -13,7 +13,7 @@ class TrackCollection; /* AnalyzerManager class -* Manages the task of analying multiple tracks. Setups a maximum amount of workers +* Manages the task of analysing multiple tracks. Setups a maximum amount of workers * and provides the tracks to analyze. It also sends the signals to other parts of Mixxx. */ class AnalyzerManager : public QObject { diff --git a/src/analyzer/analyzerqueue.cpp b/src/analyzer/analyzerqueue.cpp index 8bedf3d9aa8..af51abd659b 100644 --- a/src/analyzer/analyzerqueue.cpp +++ b/src/analyzer/analyzerqueue.cpp @@ -450,51 +450,6 @@ void AnalyzerQueue::slotAnalyseTrack(TrackPointer pTrack) { } // This is called from the GUI and from the AnalyzerQueue thread -<<<<<<< HEAD -void AnalyzerQueue::queueAnalyseTrack(TrackPointer tio) { - m_qm.lock(); - if (!m_tioq.contains(tio)) { - m_tioq.enqueue(tio); - m_qwait.wakeAll(); - } - m_qm.unlock(); -} - -// static -AnalyzerQueue* AnalyzerQueue::createDefaultAnalyzerQueue( - UserSettingsPointer pConfig, TrackCollection* pTrackCollection) { - AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection); - - ret->addAnalyzer(new AnalyzerWaveform(pConfig, false)); - ret->addAnalyzer(new AnalyzerGain(pConfig)); - ret->addAnalyzer(new AnalyzerEbur128(pConfig)); -#ifdef __VAMP__ - VampAnalyzer::initializePluginPaths(); - ret->addAnalyzer(new AnalyzerBeats(pConfig, false)); - ret->addAnalyzer(new AnalyzerKey(pConfig)); -#endif - - ret->start(QThread::LowPriority); - return ret; -} - -// static -AnalyzerQueue* AnalyzerQueue::createAnalysisFeatureAnalyzerQueue( - UserSettingsPointer pConfig, TrackCollection* pTrackCollection) { - AnalyzerQueue* ret = new AnalyzerQueue(pTrackCollection); - - ret->addAnalyzer(new AnalyzerWaveform(pConfig, true)); - ret->addAnalyzer(new AnalyzerGain(pConfig)); - ret->addAnalyzer(new AnalyzerEbur128(pConfig)); -#ifdef __VAMP__ - VampAnalyzer::initializePluginPaths(); - ret->addAnalyzer(new AnalyzerBeats(pConfig, true)); - ret->addAnalyzer(new AnalyzerKey(pConfig)); -#endif - - ret->start(QThread::LowPriority); - return ret; -======= void AnalyzerQueue::queueAnalyseTrack(TrackPointer pTrack) { if (pTrack) { QMutexLocker locked(&m_qm); @@ -503,5 +458,4 @@ void AnalyzerQueue::queueAnalyseTrack(TrackPointer pTrack) { m_qwait.wakeAll(); } } ->>>>>>> origin/master } diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index 7fb732930ab..eb5078dcae1 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -22,19 +22,6 @@ #include "util/trace.h" #include "util/logger.h" -namespace { - - mixxx::Logger kLogger("AnalyzerWorker"); - // Analysis is done in blocks. - // We need to use a smaller block size, because on Linux the AnalyzerWorker - // can starve the CPU of its resources, resulting in xruns. A block size - // of 4096 frames per block seems to do fine. - const SINT kAnalysisChannels = mixxx::AudioSource::kChannelCountStereo; - const SINT kAnalysisFramesPerBlock = 4096; - const SINT kAnalysisSamplesPerBlock = - kAnalysisFramesPerBlock * kAnalysisChannels; -} // anonymous namespace - // Measured in 0.1%, // 0 for no progress during finalize // 1 to display the text "finalizing" @@ -42,11 +29,37 @@ namespace { // NOTE: If this is changed, change woverview.cpp slotAnalyzerProgress(). #define FINALIZE_PROMILLE 1.0 +namespace { + +mixxx::Logger kLogger("AnalyzerWorker"); +// Analysis is done in blocks. +// We need to use a smaller block size, because on Linux the AnalyzerWorker +// can starve the CPU of its resources, resulting in xruns. A block size +// of 4096 frames per block seems to do fine. +const SINT kAnalysisChannels = mixxx::AudioSource::kChannelCountStereo; +const SINT kAnalysisFramesPerBlock = 4096; +const SINT kAnalysisSamplesPerBlock = + kAnalysisFramesPerBlock * kAnalysisChannels; + +inline +AnalyzerWaveform::Mode getAnalyzerWorkerMode( + const UserSettingsPointer& pConfig) { + if (pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"), true)) { + return AnalyzerWaveform::Mode::Default; + } else { + return AnalyzerWaveform::Mode::WithoutWaveform; + } +} + +} // anonymous namespace + + + // --- CONSTRUCTOR --- AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, mixxx::DbConnectionPoolPtr pDbConnectionPool, int workerIdx, bool priorized) : m_pConfig(pConfig), - m_analyzelist(), + m_pAnalyzers(), m_priorizedJob(priorized), m_workerIdx(workerIdx), m_sampleBuffer(kAnalysisSamplesPerBlock), @@ -55,17 +68,17 @@ AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, m_qm(), m_qwait(), m_pDbConnectionPool(std::move(pDbConnectionPool)) { + + m_pAnalysisDao = std::make_unique(m_pConfig); + createAnalyzers(); + } // --- DESTRUCTOR --- AnalyzerWorker::~AnalyzerWorker() { - qDebug() << "Ending AnalyzerWorker"; + kLogger.debug() << "Ending AnalyzerWorker"; // free resources m_progressInfo.sema.release(); - - for (auto& an: m_analyzelist) { - delete an.get(); - } } void AnalyzerWorker::nextTrack(TrackPointer newTrack) { @@ -88,7 +101,7 @@ void AnalyzerWorker::endProcess() { } // This is called from the AnalyzerWorker thread -bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAudioSource) { +bool AnalyzerWorker::doAnalysis(TrackPointer pTrack, mixxx::AudioSourcePointer pAudioSource) { QTime progressUpdateInhibitTimer; progressUpdateInhibitTimer.start(); // Inhibit Updates for 60 milliseconds @@ -97,7 +110,7 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud bool dieflag = false; bool cancelled = false; - qDebug() << "Analyzing" << tio->getTitle() << tio->getLocation(); + kLogger.debug() << "Analyzing" << pTrack->getTitle() << pTrack->getLocation(); do { ScopedTimer t("AnalyzerWorker::doAnalysis block"); @@ -110,7 +123,7 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud const SINT framesRead = pAudioSource->readSampleFramesStereo( - kAnalysisFramesPerBlock, + framesToRead, &m_sampleBuffer); DEBUG_ASSERT(framesRead <= framesToRead); frameIndex += framesRead; @@ -120,10 +133,10 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud // the full block size. if (kAnalysisFramesPerBlock == framesRead) { // Complete analysis block of audio samples has been read. - for (auto& an: m_analyzelist) { - //qDebug() << typeid(*an).name() << ".process()"; - an->process(m_sampleBuffer.data(), m_sampleBuffer.size()); - //qDebug() << "Done " << typeid(*an).name() << ".process()"; + for (auto const& pAnalyzer: m_pAnalyzers) { + //kLogger.debug() << typeid(*an).name() << ".process()"; + pAnalyzer->process(m_sampleBuffer.data(), m_sampleBuffer.size()); + //kLogger.debug() << "Done " << typeid(*an).name() << ".process()"; } } else { // Partial analysis block of audio samples has been read. @@ -131,8 +144,8 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud // otherwise a decoding error must have occurred. if (frameIndex < pAudioSource->getMaxFrameIndex()) { // EOF not reached -> Maybe a corrupt file? - qWarning() << "Failed to read sample data from file:" - << tio->getLocation() + kLogger.warning() << "Failed to read sample data from file:" + << pTrack->getLocation() << "@" << frameIndex; if (0 >= framesRead) { // If no frames have been read then abort the analysis. @@ -159,10 +172,6 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud progressUpdateInhibitTimer.start(); } - // This has proven to not be necessary, and if used, it should be done with care so as to - // not make the analysis slower than what it should. Also note that the user has the option - // to reduce the number of threads that run the analysis. - // When a priority analysis comes in, we pause this working thread until one prioritized // worker finishes. Once it finishes, this worker will get resumed. if (m_pauseRequested.fetchAndStoreAcquire(false)) { @@ -189,9 +198,28 @@ bool AnalyzerWorker::doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAud void AnalyzerWorker::slotProcess() { QThread::currentThread()->setObjectName(QString("AnalyzerWorker %1").arg(m_workerIdx)); - //The instantiation of the initializers needs to be done on the worker thread. - //cannot be done on constructor. - createAnalyzers(); + + // The thread-local database connection for waveform analysis must not + // be closed before returning from this function. Therefore the + // DbConnectionPooler is defined at this outer function scope, + // independent of whether a database connection will be opened + // or not. + mixxx::DbConnectionPooler dbConnectionPooler; + // m_pAnalysisDao remains null if no analyzer needs database access. + // Currently only waveform analyses makes use of it. + if (m_pAnalysisDao) { + dbConnectionPooler = mixxx::DbConnectionPooler(m_pDbConnectionPool); // move assignment + if (!dbConnectionPooler.isPooling()) { + kLogger.warning() + << "Failed to obtain database connection for analyzer queue thread"; + return; + } + // Obtain and use the newly created database connection within this thread + QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); + DEBUG_ASSERT(dbConnection.isOpen()); + m_pAnalysisDao->initialize(dbConnection); + } + m_progressInfo.sema.release(); while (!m_exit) { @@ -210,20 +238,19 @@ void AnalyzerWorker::slotProcess() { Trace trace("AnalyzerWorker analyzing track"); // Get the audio - SoundSourceProxy soundSourceProxy(m_currentTrack); mixxx::AudioSourceConfig audioSrcCfg; audioSrcCfg.setChannelCount(kAnalysisChannels); - mixxx::AudioSourcePointer pAudioSource(soundSourceProxy.openAudioSource(audioSrcCfg)); + auto pAudioSource = SoundSourceProxy(m_currentTrack).openAudioSource(audioSrcCfg); if (!pAudioSource) { - qWarning() << "Failed to open file for analyzing:" << m_currentTrack->getLocation(); + kLogger.warning() << "Failed to open file for analyzing:" << m_currentTrack->getLocation(); //TODO: maybe emit error("Failed to bblablalba"); continue; } bool processTrack = false; - for (auto& an: m_analyzelist) { + for (auto const& pAnalyzer: m_pAnalyzers) { // Make sure not to short-circuit initialize(...) - if (an->initialize(m_currentTrack, pAudioSource->getSamplingRate(), pAudioSource->getFrameCount() * kAnalysisChannels)) { + if (pAnalyzer->initialize(m_currentTrack, pAudioSource->getSamplingRate(), pAudioSource->getFrameCount() * kAnalysisChannels)) { processTrack = true; } } @@ -235,21 +262,21 @@ void AnalyzerWorker::slotProcess() { // or the analysis has been interrupted. if (!completed) { // This track was cancelled - for (auto& an: m_analyzelist) { - an->cleanup(m_currentTrack); + for (auto const& pAnalyzer: m_pAnalyzers) { + pAnalyzer->cleanup(m_currentTrack); } emitUpdateProgress(0); } else { // 100% - FINALIZE_PERCENT finished emitUpdateProgress(1000 - FINALIZE_PROMILLE); - for (auto& an: m_analyzelist) { - an->finalize(m_currentTrack); + for (auto const& pAnalyzer: m_pAnalyzers) { + pAnalyzer->finalize(m_currentTrack); } emitUpdateProgress(1000); // 100% } } else { emitUpdateProgress(1000); // 100% - qDebug() << "Skipping track analysis because no analyzer initialized."; + kLogger.debug() << "Skipping track analysis because no analyzer initialized."; } Event::end(QString("AnalyzerWorker %1 process").arg(m_workerIdx)); } @@ -290,17 +317,6 @@ void AnalyzerWorker::emitUpdateProgress(int progress) { emit(updateProgress(m_workerIdx, &m_progressInfo)); } } -namespace { - inline - AnalyzerWaveform::Mode getAnalyzerWorkerMode( - const UserSettingsPointer& pConfig) { - if (pConfig->getValue(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"), true)) { - return AnalyzerWaveform::Mode::Default; - } else { - return AnalyzerWaveform::Mode::WithoutWaveform; - } - } -} // anonymous namespace void AnalyzerWorker::createAnalyzers() { AnalyzerWaveform::Mode mode; @@ -310,33 +326,11 @@ void AnalyzerWorker::createAnalyzers() { mode = getAnalyzerWorkerMode(m_pConfig); } - m_pAnalysisDao = std::make_unique(m_pConfig); - // The thread-local database connection for waveform analysis must not - // be closed before returning from this function. Therefore the - // DbConnectionPooler is defined at this outer function scope, - // independent of whether a database connection will be opened - // or not. - mixxx::DbConnectionPooler dbConnectionPooler; - // m_pAnalysisDao remains null if no analyzer needs database access. - // Currently only waveform analyses makes use of it. - if (m_pAnalysisDao) { - dbConnectionPooler = mixxx::DbConnectionPooler(m_pDbConnectionPool); // move assignment - if (!dbConnectionPooler.isPooling()) { - kLogger.warning() - << "Failed to obtain database connection for analyzer queue thread"; - return; - } - // Obtain and use the newly created database connection within this thread - QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); - DEBUG_ASSERT(dbConnection.isOpen()); - m_pAnalysisDao->initialize(dbConnection); - } - - m_analyzelist.push_back(std::make_unique(m_pAnalysisDao.get(), mode)); - m_analyzelist.push_back(std::make_unique(m_pConfig)); - m_analyzelist.push_back(std::make_unique(m_pConfig)); + m_pAnalyzers.push_back(std::make_unique(m_pAnalysisDao.get(), mode)); + m_pAnalyzers.push_back(std::make_unique(m_pConfig)); + m_pAnalyzers.push_back(std::make_unique(m_pConfig)); #ifdef __VAMP__ - m_analyzelist.push_back(std::make_unique(m_pConfig, !m_priorizedJob)); - m_analyzelist.push_back(std::make_unique(m_pConfig)); + m_pAnalyzers.push_back(std::make_unique(m_pConfig, !m_priorizedJob)); + m_pAnalyzers.push_back(std::make_unique(m_pConfig)); #endif } diff --git a/src/analyzer/analyzerworker.h b/src/analyzer/analyzerworker.h index 3db576c2a36..c9ea4a82f45 100644 --- a/src/analyzer/analyzerworker.h +++ b/src/analyzer/analyzerworker.h @@ -90,7 +90,7 @@ public slots: std::unique_ptr m_pAnalysisDao; typedef std::unique_ptr AnalyzerPtr; - std::vector m_analyzelist; + std::vector m_pAnalyzers; bool m_priorizedJob; int m_workerIdx; SampleBuffer m_sampleBuffer; diff --git a/src/engine/sidechain/enginebroadcast.cpp b/src/engine/sidechain/enginebroadcast.cpp index b01a2b57e3d..5ba5628f1ed 100644 --- a/src/engine/sidechain/enginebroadcast.cpp +++ b/src/engine/sidechain/enginebroadcast.cpp @@ -311,7 +311,7 @@ void EngineBroadcast::updateFromPreferences() { } if (shout_set_format(m_pShout, format) != SHOUTERR_SUCCESS) { - errorDialog("Error setting streaming format!", shout_get_error(m_pShout)); + errorDialog(tr("Error setting streaming format!"), shout_get_error(m_pShout)); return; } diff --git a/src/library/library.cpp b/src/library/library.cpp index 6f746e42742..5e3d2bfa2e0 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -79,7 +79,7 @@ Library::Library( QSqlDatabase dbConnection = mixxx::DbConnectionPooled(m_pDbConnectionPool); - m_pAnalyzerManager = new AnalyzerManager(pConfig,pDbConnectionPool); + m_pAnalyzerManager = new AnalyzerManager(pConfig,m_pDbConnectionPool); // TODO(XXX): Add a checkbox in the library preferences for checking // and repairing the database on the next restart of the application. diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index cd3dda8fb6d..f678386935d 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -32,9 +32,9 @@ PlayerManager::PlayerManager(UserSettingsPointer pConfig, m_pSoundManager(pSoundManager), m_pEffectsManager(pEffectsManager), m_pEngine(pEngine), + m_pAnalyzerManager(nullptr), // NOTE(XXX) LegacySkinParser relies on these controls being Controls // and not ControlProxies. - m_pAnalyzerManager(nullptr), m_pCONumDecks(new ControlObject( ConfigKey("[Master]", "num_decks"), true, true)), m_pCONumSamplers(new ControlObject( @@ -348,7 +348,7 @@ void PlayerManager::addDeckInner() { if (m_pAnalyzerManager) { connect(pDeck, SIGNAL(newTrackLoaded(TrackPointer)), - m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); + m_pAnalyzerManager, SLOT(slotAnalyseTrack(TrackPointer))); } m_players[group] = pDeck; diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index 8d9cdbfb203..5cde9a4e31d 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -79,9 +79,6 @@ DlgPrefLibrary::DlgPrefLibrary(QWidget * parent, connect(this, SIGNAL(setTrackTableRowHeight(int)), m_pLibrary, SLOT(slotSetTrackTableRowHeight(int))); - connect(cmbMaxThreads, SIGNAL(currentIndexChanged(int)), - this, SLOT(slotMaxThreadsChanged(int))); - AnalyzerManager* analyzerManager = m_pLibrary->getAnalyzerManager(); connect(this, SIGNAL(setMaxThreads(int)), analyzerManager, SLOT(slotMaxThreadsChanged(int))); @@ -187,7 +184,7 @@ void DlgPrefLibrary::slotResetToDefaults() { radioButton_dbclick_deck->setChecked(true); spinBoxRowHeight->setValue(Library::kDefaultRowHeightPx); int cpuMax = QThread::idealThreadCount(); - if (cpuMax < 1) { cpuMax = 8; } + if (cpuMax < 1) { cpuMax = 1; } //setCurrentIndex is zero based. threads is one based. cmbMaxThreads->setCurrentIndex(cpuMax-1); @@ -250,7 +247,6 @@ void DlgPrefLibrary::slotCancel() { // Undo any changes in the library font or row height. emit(setTrackTableRowHeight(m_iOriginalTrackTableRowHeight)); emit(setTrackTableFont(m_originalTrackTableFont)); - emit(setMaxThreads(m_iOriginalMaxThreads)); } void DlgPrefLibrary::slotAddDir() { @@ -385,9 +381,10 @@ void DlgPrefLibrary::slotApply() { //setCurrentIndex is zero based. threads is one based. int threads = cmbMaxThreads->currentIndex()+1; - if (m_iOriginalMaxThreads != threads) { + if (m_iOriginalMaxThreads != threads && threads > 0) { m_pconfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), threads); + emit(setMaxThreads(threads)); } @@ -399,14 +396,6 @@ void DlgPrefLibrary::slotRowHeightValueChanged(int height) { emit(setTrackTableRowHeight(height)); } -void DlgPrefLibrary::slotMaxThreadsChanged(int cmbindex) { - //I'm not sure if it's the best to emit it when changing the value, instead of only on onApply - //setCurrentIndex is zero based. threads is one based. - if (cmbindex >=0) { - emit(setMaxThreads(cmbindex+1)); - } -} - void DlgPrefLibrary::setLibraryFont(const QFont& font) { libraryFont->setText(QString("%1 %2 %3pt").arg( font.family(), font.styleName(), QString::number(font.pointSizeF()))); diff --git a/src/preferences/dialog/dlgpreflibrary.h b/src/preferences/dialog/dlgpreflibrary.h index 128f58ef7d5..29df7bddd26 100644 --- a/src/preferences/dialog/dlgpreflibrary.h +++ b/src/preferences/dialog/dlgpreflibrary.h @@ -71,7 +71,6 @@ class DlgPrefLibrary : public DlgPreferencePage, public Ui::DlgPrefLibraryDlg { private slots: void slotRowHeightValueChanged(int); - void slotMaxThreadsChanged(int); void slotSelectFont(); private: diff --git a/src/preferences/dialog/dlgpreflibrarydlg.ui b/src/preferences/dialog/dlgpreflibrarydlg.ui index 696b19a4d67..5783f09a9e8 100644 --- a/src/preferences/dialog/dlgpreflibrarydlg.ui +++ b/src/preferences/dialog/dlgpreflibrarydlg.ui @@ -166,16 +166,6 @@ Miscellaneous - - - - false - - - Synchronize ID3 tags on track modifications - - - @@ -196,26 +186,20 @@ - - - - px - - - 5 - - - 100 + + + + false - - 20 + + Synchronize ID3 tags on track modifications - - - - true + + + + Use relative paths for playlist export if possible @@ -236,10 +220,26 @@ - - - - Use relative paths for playlist export if possible + + + + px + + + 5 + + + 100 + + + 20 + + + + + + + true From a1c8aa156fddb426ae9e082827c7a078c82482b1 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Fri, 24 Nov 2017 00:20:45 +0100 Subject: [PATCH 18/20] linux compilation fixes plus removed calculation of threads from the preferences dialog --- src/analyzer/analyzermanager.cpp | 22 +++++++++------------- src/analyzer/analyzermanager.h | 2 +- src/analyzer/analyzerworker.cpp | 2 +- src/preferences/dialog/dlgpreflibrary.cpp | 15 --------------- 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/analyzer/analyzermanager.cpp b/src/analyzer/analyzermanager.cpp index 29cc2312341..6f954a30bbe 100644 --- a/src/analyzer/analyzermanager.cpp +++ b/src/analyzer/analyzermanager.cpp @@ -17,6 +17,7 @@ AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig, mixxx::DbConnectionPoolPtr pDbConnectionPool) : + m_pDbConnectionPool(std::move(pDbConnectionPool)), m_pConfig(pConfig), m_nextWorkerId(0), m_defaultTrackQueue(), @@ -24,24 +25,25 @@ AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig, m_defaultWorkers(), m_priorityWorkers(), m_pausedWorkers(), - m_pDbConnectionPool(std::move(pDbConnectionPool)) { + m_endingWorkers() { int ideal = QThread::idealThreadCount(); int maxThreads = m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads"), ideal); if (ideal < 1) { if (maxThreads > 0 && maxThreads <= 32) { qDebug() << "Cannot detect idealThreadCount. maxThreads is: " << maxThreads; - ideal = maxThreads; } else { qWarning() << "Cannot detect idealThreadCount and maxThreads is incorrect: " << maxThreads <<". Using the sane value of 1"; - ideal = 1; + maxThreads = ideal; + m_pConfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), maxThreads); } } - if (maxThreads <= 0 || maxThreads > ideal) { + else if (maxThreads <= 0 || maxThreads > ideal) { qWarning() << "maxThreads value is incorrect: " << maxThreads << ". Changing it to " << ideal; //Assume the value is incorrect, so fix it. maxThreads = ideal; + m_pConfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), maxThreads); } m_MaxThreads = maxThreads; } @@ -62,23 +64,17 @@ bool AnalyzerManager::isDefaultQueueActive() { void AnalyzerManager::stop(bool shutdown) { m_defaultTrackQueue.clear(); - QListIterator it(m_defaultWorkers); - while (it.hasNext()) { - AnalyzerWorker* worker = it.next(); + foreach(AnalyzerWorker* worker, m_defaultWorkers) { worker->endProcess(); m_endingWorkers.append(worker); } - QListIterator it3(m_pausedWorkers); - while (it3.hasNext()) { - AnalyzerWorker* worker = it3.next(); + foreach(AnalyzerWorker* worker, m_pausedWorkers) { worker->endProcess(); m_endingWorkers.append(worker); } if (shutdown) { m_prioTrackQueue.clear(); - QListIterator it2(m_priorityWorkers); - while (it2.hasNext()) { - AnalyzerWorker* worker = it2.next(); + foreach(AnalyzerWorker* worker, m_priorityWorkers) { worker->endProcess(); m_endingWorkers.append(worker); } diff --git a/src/analyzer/analyzermanager.h b/src/analyzer/analyzermanager.h index 1f215c282d5..f829a938d0b 100644 --- a/src/analyzer/analyzermanager.h +++ b/src/analyzer/analyzermanager.h @@ -27,7 +27,7 @@ enum class WorkerType { public: //There should exist only one AnalyzerManager in order to control the amount of threads executing. - AnalyzerManager::AnalyzerManager(UserSettingsPointer pConfig, + AnalyzerManager(UserSettingsPointer pConfig, mixxx::DbConnectionPoolPtr pDbConnectionPool); virtual ~AnalyzerManager(); diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index eb5078dcae1..fd7526b53dc 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -70,7 +70,7 @@ AnalyzerWorker::AnalyzerWorker(UserSettingsPointer pConfig, m_pDbConnectionPool(std::move(pDbConnectionPool)) { m_pAnalysisDao = std::make_unique(m_pConfig); - createAnalyzers(); + createAnalyzers(); } diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index 5cde9a4e31d..4cd7da8e852 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -149,9 +149,6 @@ void DlgPrefLibrary::initializeDirList() { } void DlgPrefLibrary::initializeThreadsCombo() { - disconnect(cmbMaxThreads, SIGNAL(currentIndexChanged(int)), - this, SLOT(slotMaxThreadsChanged(int))); - // save which index was selected const int selected = cmbMaxThreads->currentIndex(); // clear and fill model @@ -163,8 +160,6 @@ void DlgPrefLibrary::initializeThreadsCombo() { cmbMaxThreads->addItem(displayname); } cmbMaxThreads->setCurrentIndex(selected); - connect(cmbMaxThreads, SIGNAL(currentIndexChanged(int)), - this, SLOT(slotMaxThreadsChanged(int))); } void DlgPrefLibrary::slotExtraPlugins() { @@ -228,16 +223,6 @@ void DlgPrefLibrary::slotUpdate() { setLibraryFont(m_originalTrackTableFont); m_iOriginalMaxThreads = m_pconfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); - int ideal = QThread::idealThreadCount(); - if (QThread::idealThreadCount() < 1) { - ideal = 1; - } - if (m_iOriginalMaxThreads <= 0 || m_iOriginalMaxThreads > 32) { - //Assume the value is incorrect, so fix it. - m_iOriginalMaxThreads = ideal; - m_pconfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), - m_iOriginalMaxThreads); - } //setCurrentIndex is zero based. threads is one based. cmbMaxThreads->setCurrentIndex(m_iOriginalMaxThreads-1); From 6da72efa33e76e655667b69375f8dbbacc73aa5d Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Wed, 6 Dec 2017 20:45:38 +0100 Subject: [PATCH 19/20] changes for sourceaudioapi v2 compatibility --- src/analyzer/analyzerworker.cpp | 82 +++++++++++++++++---------------- src/analyzer/analyzerworker.h | 2 +- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/analyzer/analyzerworker.cpp b/src/analyzer/analyzerworker.cpp index fd7526b53dc..62891666fed 100644 --- a/src/analyzer/analyzerworker.cpp +++ b/src/analyzer/analyzerworker.cpp @@ -13,7 +13,9 @@ #include "analyzer/analyzerebur128.h" #include "analyzer/analyzerwaveform.h" #include "library/dao/analysisdao.h" +#include "engine/engine.h" #include "sources/soundsourceproxy.h" +#include "sources/audiosourcestereoproxy.h" #include "util/compatibility.h" #include "util/db/dbconnectionpooler.h" #include "util/db/dbconnectionpooled.h" @@ -36,7 +38,7 @@ mixxx::Logger kLogger("AnalyzerWorker"); // We need to use a smaller block size, because on Linux the AnalyzerWorker // can starve the CPU of its resources, resulting in xruns. A block size // of 4096 frames per block seems to do fine. -const SINT kAnalysisChannels = mixxx::AudioSource::kChannelCountStereo; +const mixxx::AudioSignal::ChannelCount kAnalysisChannels(mixxx::kEngineChannelCount); const SINT kAnalysisFramesPerBlock = 4096; const SINT kAnalysisSamplesPerBlock = kAnalysisFramesPerBlock * kAnalysisChannels; @@ -106,53 +108,50 @@ bool AnalyzerWorker::doAnalysis(TrackPointer pTrack, mixxx::AudioSourcePointer p QTime progressUpdateInhibitTimer; progressUpdateInhibitTimer.start(); // Inhibit Updates for 60 milliseconds - SINT frameIndex = pAudioSource->getMinFrameIndex(); + mixxx::AudioSourceStereoProxy audioSourceProxy( + pAudioSource, + kAnalysisFramesPerBlock); + DEBUG_ASSERT(audioSourceProxy.channelCount() == kAnalysisChannels); + + mixxx::IndexRange remainingFrames = pAudioSource->frameIndexRange(); bool dieflag = false; bool cancelled = false; kLogger.debug() << "Analyzing" << pTrack->getTitle() << pTrack->getLocation(); - do { + while (!dieflag && !remainingFrames.empty()) { ScopedTimer t("AnalyzerWorker::doAnalysis block"); - DEBUG_ASSERT(frameIndex < pAudioSource->getMaxFrameIndex()); - const SINT framesRemaining = - pAudioSource->getMaxFrameIndex() - frameIndex; - const SINT framesToRead = - math_min(kAnalysisFramesPerBlock, framesRemaining); - DEBUG_ASSERT(0 < framesToRead); - - const SINT framesRead = - pAudioSource->readSampleFramesStereo( - framesToRead, - &m_sampleBuffer); - DEBUG_ASSERT(framesRead <= framesToRead); - frameIndex += framesRead; - DEBUG_ASSERT(pAudioSource->isValidFrameIndex(frameIndex)); - + const auto inputFrameIndexRange = + remainingFrames.splitAndShrinkFront( + math_min(kAnalysisFramesPerBlock, remainingFrames.length())); + DEBUG_ASSERT(!inputFrameIndexRange.empty()); + const auto readableSampleFrames = + audioSourceProxy.readSampleFrames( + mixxx::WritableSampleFrames( + inputFrameIndexRange, + mixxx::SampleBuffer::WritableSlice(m_sampleBuffer))); // To compare apples to apples, let's only look at blocks that are // the full block size. - if (kAnalysisFramesPerBlock == framesRead) { + if (readableSampleFrames.frameLength() == kAnalysisFramesPerBlock) { // Complete analysis block of audio samples has been read. for (auto const& pAnalyzer: m_pAnalyzers) { - //kLogger.debug() << typeid(*an).name() << ".process()"; - pAnalyzer->process(m_sampleBuffer.data(), m_sampleBuffer.size()); - //kLogger.debug() << "Done " << typeid(*an).name() << ".process()"; + pAnalyzer->process( + readableSampleFrames.readableData(), + readableSampleFrames.readableLength()); } } else { // Partial analysis block of audio samples has been read. // This should only happen at the end of an audio stream, // otherwise a decoding error must have occurred. - if (frameIndex < pAudioSource->getMaxFrameIndex()) { + if (!remainingFrames.empty()) { // EOF not reached -> Maybe a corrupt file? - kLogger.warning() << "Failed to read sample data from file:" + kLogger.warning() + << "Aborting analysis after failed to read sample data from " << pTrack->getLocation() - << "@" << frameIndex; - if (0 >= framesRead) { - // If no frames have been read then abort the analysis. - // Otherwise we might get stuck in this loop forever. - dieflag = true; // abort - cancelled = false; // completed, no retry - } + << ": expected frames =" << inputFrameIndexRange + << ", actual frames =" << readableSampleFrames.frameIndexRange(); + dieflag = true; // abort + cancelled = false; // completed, no retry } } @@ -160,9 +159,9 @@ bool AnalyzerWorker::doAnalysis(TrackPointer pTrack, mixxx::AudioSourcePointer p // During the doAnalysis function it goes only to 100% - FINALIZE_PERCENT // because the finalize functions will take also some time //fp div here prevents insane signed overflow - DEBUG_ASSERT(pAudioSource->isValidFrameIndex(frameIndex)); const double frameProgress = - double(frameIndex) / double(pAudioSource->getMaxFrameIndex()); + double(pAudioSource->frameLength() - remainingFrames.length()) / + double(pAudioSource->frameLength()); int progressPromille = frameProgress * (1000.0 - FINALIZE_PROMILLE); if (m_progressInfo.track_progress != progressPromille && @@ -189,7 +188,7 @@ bool AnalyzerWorker::doAnalysis(TrackPointer pTrack, mixxx::AudioSourcePointer p if (dieflag || cancelled) { t.cancel(); } - } while (!dieflag && (frameIndex < pAudioSource->getMaxFrameIndex())); + } return !cancelled; //don't return !dieflag or we might reanalyze over and over } @@ -238,11 +237,14 @@ void AnalyzerWorker::slotProcess() { Trace trace("AnalyzerWorker analyzing track"); // Get the audio - mixxx::AudioSourceConfig audioSrcCfg; - audioSrcCfg.setChannelCount(kAnalysisChannels); - auto pAudioSource = SoundSourceProxy(m_currentTrack).openAudioSource(audioSrcCfg); + mixxx::AudioSource::OpenParams openParams; + openParams.setChannelCount(kAnalysisChannels); + auto pAudioSource = SoundSourceProxy(m_currentTrack).openAudioSource(openParams); if (!pAudioSource) { - kLogger.warning() << "Failed to open file for analyzing:" << m_currentTrack->getLocation(); + kLogger.warning() + << "Failed to open file for analyzing: " + << m_currentTrack->getLocation() + << " " << *pAudioSource; //TODO: maybe emit error("Failed to bblablalba"); continue; } @@ -250,7 +252,9 @@ void AnalyzerWorker::slotProcess() { bool processTrack = false; for (auto const& pAnalyzer: m_pAnalyzers) { // Make sure not to short-circuit initialize(...) - if (pAnalyzer->initialize(m_currentTrack, pAudioSource->getSamplingRate(), pAudioSource->getFrameCount() * kAnalysisChannels)) { + if (pAnalyzer->initialize(m_currentTrack, + pAudioSource->sampleRate(), + pAudioSource->frameLength() * kAnalysisChannels)) { processTrack = true; } } diff --git a/src/analyzer/analyzerworker.h b/src/analyzer/analyzerworker.h index c9ea4a82f45..2d134ee19d9 100644 --- a/src/analyzer/analyzerworker.h +++ b/src/analyzer/analyzerworker.h @@ -93,7 +93,7 @@ public slots: std::vector m_pAnalyzers; bool m_priorizedJob; int m_workerIdx; - SampleBuffer m_sampleBuffer; + mixxx::SampleBuffer m_sampleBuffer; TrackPointer m_currentTrack; QAtomicInt m_exit; From a467944c953b0efb643d5ef7b67f5418d61c0711 Mon Sep 17 00:00:00 2001 From: JosepMaJAZ Date: Thu, 21 Dec 2017 20:31:13 +0100 Subject: [PATCH 20/20] build fix --- src/preferences/dialog/dlgpreflibrary.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index 6a1eef865df..bc4b0d53425 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -210,7 +210,7 @@ void DlgPrefLibrary::slotUpdate() { spinBoxRowHeight->setValue(m_iOriginalTrackTableRowHeight); setLibraryFont(m_originalTrackTableFont); - m_iOriginalMaxThreads = m_pconfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); + m_iOriginalMaxThreads = m_pConfig->getValue(ConfigKey("[Library]", "MaxAnalysisThreads")); //setCurrentIndex is zero based. threads is one based. cmbMaxThreads->setCurrentIndex(m_iOriginalMaxThreads-1); @@ -355,7 +355,7 @@ void DlgPrefLibrary::slotApply() { //setCurrentIndex is zero based. threads is one based. int threads = cmbMaxThreads->currentIndex()+1; if (m_iOriginalMaxThreads != threads && threads > 0) { - m_pconfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), + m_pConfig->setValue(ConfigKey("[Library]", "MaxAnalysisThreads"), threads); emit(setMaxThreads(threads)); }