summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorUwe Klotz <uklotz@mixxx.org>2018-04-18 16:39:07 +0200
committerUwe Klotz <uklotz@mixxx.org>2018-10-30 23:26:56 +0100
commitc236956884e3c7b9a47536f98eb7eb5a126a26e4 (patch)
tree85ea9323501bb2f5176c35679e2307e50eeed49c /src
parentdb7fe92a9a5c63e0d9b6f0fe6f1ad379f2b0b46a (diff)
Fix and enable multi-threaded analysis
Diffstat (limited to 'src')
-rw-r--r--src/analyzer/analyzerebur128.h4
-rw-r--r--src/analyzer/analyzergain.h4
-rw-r--r--src/analyzer/analyzerprogress.h22
-rw-r--r--src/analyzer/analyzerqueue.cpp468
-rw-r--r--src/analyzer/analyzerqueue.h91
-rw-r--r--src/analyzer/analyzerthread.cpp326
-rw-r--r--src/analyzer/analyzerthread.h143
-rw-r--r--src/analyzer/trackanalysisscheduler.cpp276
-rw-r--r--src/analyzer/trackanalysisscheduler.h174
-rw-r--r--src/control/controlvalue.h111
-rw-r--r--src/library/analysisfeature.cpp153
-rw-r--r--src/library/analysisfeature.h25
-rw-r--r--src/library/dlganalysis.cpp71
-rw-r--r--src/library/dlganalysis.h15
-rw-r--r--src/library/library.cpp12
-rw-r--r--src/library/library.h9
-rw-r--r--src/mixer/basetrackplayer.cpp1
-rw-r--r--src/mixer/playermanager.cpp102
-rw-r--r--src/mixer/playermanager.h18
-rw-r--r--src/mixxx.cpp1
-rw-r--r--src/preferences/replaygainsettings.cpp13
-rw-r--r--src/preferences/replaygainsettings.h1
-rw-r--r--src/skin/legacyskinparser.cpp6
-rw-r--r--src/track/track.cpp19
-rw-r--r--src/track/track.h4
-rw-r--r--src/util/workerthread.cpp164
-rw-r--r--src/util/workerthread.h131
-rw-r--r--src/util/workerthreadscheduler.cpp52
-rw-r--r--src/util/workerthreadscheduler.h32
-rw-r--r--src/widget/woverview.cpp61
-rw-r--r--src/widget/woverview.h18
-rw-r--r--src/widget/woverviewhsv.cpp9
-rw-r--r--src/widget/woverviewhsv.h6
-rw-r--r--src/widget/woverviewlmh.cpp9
-rw-r--r--src/widget/woverviewlmh.h6
-rw-r--r--src/widget/woverviewrgb.cpp9
-rw-r--r--src/widget/woverviewrgb.h6
37 files changed, 1713 insertions, 859 deletions
diff --git a/src/analyzer/analyzerebur128.h b/src/analyzer/analyzerebur128.h
index 60e996eb01..fa8edc0760 100644
--- a/src/analyzer/analyzerebur128.h
+++ b/src/analyzer/analyzerebur128.h
@@ -11,6 +11,10 @@ class AnalyzerEbur128 : public Analyzer {
AnalyzerEbur128(UserSettingsPointer pConfig);
virtual ~AnalyzerEbur128();
+ static bool isEnabled(const ReplayGainSettings& rgSettings) {
+ return rgSettings.isAnalyzerEnabled(2);
+ }
+
bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override;
bool isDisabledOrLoadStoredSuccess(TrackPointer tio) const override;
void process(const CSAMPLE* pIn, const int iLen) override;
diff --git a/src/analyzer/analyzergain.h b/src/analyzer/analyzergain.h
index 247b2bcc75..d2958adfef 100644
--- a/src/analyzer/analyzergain.h
+++ b/src/analyzer/analyzergain.h
@@ -18,6 +18,10 @@ class AnalyzerGain : public Analyzer {
AnalyzerGain(UserSettingsPointer pConfig);
virtual ~AnalyzerGain();
+ static bool isEnabled(const ReplayGainSettings& rgSettings) {
+ return rgSettings.isAnalyzerEnabled(1);
+ }
+
bool initialize(TrackPointer tio, int sampleRate, int totalSamples) override;
bool isDisabledOrLoadStoredSuccess(TrackPointer tio) const override;
void process(const CSAMPLE* pIn, const int iLen) override;
diff --git a/src/analyzer/analyzerprogress.h b/src/analyzer/analyzerprogress.h
new file mode 100644
index 0000000000..b9b5673245
--- /dev/null
+++ b/src/analyzer/analyzerprogress.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "util/math.h"
+
+
+typedef double AnalyzerProgress;
+
+constexpr AnalyzerProgress kAnalyzerProgressUnknown = -1.0;
+constexpr AnalyzerProgress kAnalyzerProgressNone = 0.0; // 0.0 %
+constexpr AnalyzerProgress kAnalyzerProgressHalf = 0.5; // 50.0 %
+constexpr AnalyzerProgress kAnalyzerProgressFinalizing = 0.95; // 95.0 %
+constexpr AnalyzerProgress kAnalyzerProgressDone = 1.0; // 100.0%
+
+Q_DECLARE_METATYPE(AnalyzerProgress);
+
+// Integer [0, 100]
+inline
+int analyzerProgressPercent(AnalyzerProgress analyzerProgress) {
+ DEBUG_ASSERT(analyzerProgress >= kAnalyzerProgressNone);
+ return int((100 * (math_min(analyzerProgress, kAnalyzerProgressDone) - kAnalyzerProgressNone)) /
+ (kAnalyzerProgressDone - kAnalyzerProgressNone));
+}
diff --git a/src/analyzer/analyzerqueue.cpp b/src/analyzer/analyzerqueue.cpp
deleted file mode 100644
index f574f79cd9..0000000000
--- a/src/analyzer/analyzerqueue.cpp
+++ /dev/null
@@ -1,468 +0,0 @@
-#include "analyzer/analyzerqueue.h"
-
-#ifdef __VAMP__
-#include "analyzer/analyzerbeats.h"
-#include "analyzer/analyzerkey.h"
-#endif
-#include "analyzer/analyzergain.h"
-#include "analyzer/analyzerebur128.h"
-#include "analyzer/analyzerwaveform.h"
-#include "library/dao/analysisdao.h"
-#include "engine/engine.h"
-#include "mixer/playerinfo.h"
-#include "sources/soundsourceproxy.h"
-#include "sources/audiosourcestereoproxy.h"
-#include "track/track.h"
-#include "util/compatibility.h"
-#include "util/db/dbconnectionpooler.h"
-#include "util/db/dbconnectionpooled.h"
-#include "util/event.h"
-#include "util/timer.h"
-#include "util/trace.h"
-#include "util/logger.h"
-
-// 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
-
-namespace {
-
-mixxx::Logger kLogger("AnalyzerQueue");
-
-// Analysis is done in blocks.
-// We need to use a smaller block size, because on Linux the AnalyzerQueue
-// can starve the CPU of its resources, resulting in xruns. A block size
-// of 4096 frames per block seems to do fine.
-const mixxx::AudioSignal::ChannelCount kAnalysisChannels(mixxx::kEngineChannelCount);
-const SINT kAnalysisFramesPerBlock = 4096;
-const SINT kAnalysisSamplesPerBlock =
- kAnalysisFramesPerBlock * kAnalysisChannels;
-
-QAtomicInt s_instanceCounter(0);
-
-} // anonymous namespace
-
-AnalyzerQueue::AnalyzerQueue(
- mixxx::DbConnectionPoolPtr pDbConnectionPool,
- const UserSettingsPointer& pConfig,
- Mode mode)
- : m_pDbConnectionPool(std::move(pDbConnectionPool)),
- m_exit(false),
- m_aiCheckPriorities(false),
- m_sampleBuffer(kAnalysisSamplesPerBlock),
- m_queue_size(0) {
-
- if (mode != Mode::WithoutWaveform) {
- m_pAnalysisDao = std::make_unique<AnalysisDao>(pConfig);
- m_pAnalyzers.push_back(std::make_unique<AnalyzerWaveform>(m_pAnalysisDao.get()));
- }
- m_pAnalyzers.push_back(std::make_unique<AnalyzerGain>(pConfig));
- m_pAnalyzers.push_back(std::make_unique<AnalyzerEbur128>(pConfig));
-#ifdef __VAMP__
- m_pAnalyzers.push_back(std::make_unique<AnalyzerBeats>(pConfig));
- m_pAnalyzers.push_back(std::make_unique<AnalyzerKey>(pConfig));
-#endif
-
- connect(this, SIGNAL(updateProgress()),
- this, SLOT(slotUpdateProgress()));
-
- start(QThread::LowPriority);
-}
-
-AnalyzerQueue::~AnalyzerQueue() {
- stop();
- m_progressInfo.sema.release();
- wait(); //Wait until thread has actually stopped before proceeding.
-}
-
-// This is called from the AnalyzerQueue thread
-bool AnalyzerQueue::isLoadedTrackWaiting(TrackPointer analysingTrack) {
- const PlayerInfo& info = PlayerInfo::instance();
- TrackPointer pTrack;
- bool trackWaiting = false;
- QList<TrackPointer> progress100List;
- QList<TrackPointer> progress0List;
-
- QMutexLocker locked(&m_qm);
- QMutableListIterator<TrackPointer> it(m_queuedTracks);
- while (it.hasNext()) {
- TrackPointer& pTrack = it.next();
- if (!pTrack) {
- it.remove();
- continue;
- }
- if (!trackWaiting) {
- trackWaiting = info.isTrackLoaded(pTrack);
- }
- // try to load waveforms for all new tracks first
- // and remove them from queue if already analysed
- // This avoids waiting for a running analysis for those tracks.
- int progress = pTrack->getAnalyzerProgress();
- if (progress < 0) {
- // Load stored analysis
- bool processTrack = false;
- for (auto const& pAnalyzer: m_pAnalyzers) {
- if (!pAnalyzer->isDisabledOrLoadStoredSuccess(pTrack)) {
- processTrack = true;
- }
- }
- if (!processTrack) {
- progress100List.append(pTrack);
- it.remove(); // since pTrack is a reference it is invalid now.
- } else {
- progress0List.append(pTrack);
- }
- } else if (progress == 1000) {
- it.remove();
- }
- }
-
- locked.unlock();
-
- // update progress after unlock to avoid a deadlock
- foreach (TrackPointer pTrack, progress100List) {
- emitUpdateProgress(pTrack, 1000);
- }
- foreach (TrackPointer pTrack, progress0List) {
- emitUpdateProgress(pTrack, 0);
- }
-
- if (info.isTrackLoaded(analysingTrack)) {
- return false;
- }
- return trackWaiting;
-}
-
-// This is called from the AnalyzerQueue thread
-// The returned track might be NULL, up to the caller to check.
-TrackPointer AnalyzerQueue::dequeueNextBlocking() {
- QMutexLocker locked(&m_qm);
- if (m_queuedTracks.isEmpty()) {
- Event::end("AnalyzerQueue process");
- m_qwait.wait(&m_qm);
- Event::start("AnalyzerQueue process");
-
- if (m_exit) {
- return TrackPointer();
- }
- }
-
- const PlayerInfo& info = PlayerInfo::instance();
- TrackPointer pLoadTrack;
- QMutableListIterator<TrackPointer> it(m_queuedTracks);
- while (it.hasNext()) {
- TrackPointer& pTrack = it.next();
- DEBUG_ASSERT(pTrack);
- // Prioritize tracks that are loaded.
- if (info.isTrackLoaded(pTrack)) {
- kLogger.debug() << "Prioritizing" << pTrack->getTitle() << pTrack->getLocation();
- pLoadTrack = pTrack;
- it.remove();
- break;
- }
- }
-
- if (!pLoadTrack && !m_queuedTracks.isEmpty()) {
- // no prioritized track found, use head track
- pLoadTrack = m_queuedTracks.dequeue();
- }
-
- return pLoadTrack;
-}
-
-// This is called from the AnalyzerQueue thread
-bool AnalyzerQueue::doAnalysis(
- TrackPointer pTrack,
- mixxx::AudioSourcePointer pAudioSource) {
-
- QTime progressUpdateInhibitTimer;
- progressUpdateInhibitTimer.start(); // Inhibit Updates for 60 milliseconds
-
- mixxx::AudioSourceStereoProxy audioSourceProxy(
- pAudioSource,
- kAnalysisFramesPerBlock);
- DEBUG_ASSERT(audioSourceProxy.channelCount() == kAnalysisChannels);
-
- mixxx::IndexRange remainingFrames = pAudioSource->frameIndexRange();
- bool dieflag = false;
- bool cancelled = false;
- while (!dieflag && !remainingFrames.empty()) {
- ScopedTimer t("AnalyzerQueue::doAnalysis block");
-
- 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 (readableSampleFrames.frameLength() == kAnalysisFramesPerBlock) {
- // Complete analysis block of audio samples has been read.
- for (auto const& pAnalyzer: m_pAnalyzers) {
- 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 (!remainingFrames.empty()) {
- // EOF not reached -> Maybe a corrupt file?
- kLogger.warning()
- << "Aborting analysis after failed to read sample data from"
- << pTrack->getLocation()
- << ": expected frames =" << inputFrameIndexRange
- << ", actual frames =" << readableSampleFrames.frameIndexRange();
- 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
- const double frameProgress =
- double(pAudioSource->frameLength() - remainingFrames.length()) /
- double(pAudioSource->frameLength());
- int progressPromille = frameProgress * (1000 - FINALIZE_PROMILLE);
-
- if (m_progressInfo.track_progress != progressPromille) {
- if (progressUpdateInhibitTimer.elapsed() > 60) {
- // Inhibit Updates for 60 milliseconds
- emitUpdateProgress(pTrack, 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();
- //QThread::usleep(10);
-
- // has something new entered the queue?
- if (m_aiCheckPriorities.fetchAndStoreAcquire(false)) {
- if (isLoadedTrackWaiting(pTrack)) {
- kLogger.debug() << "Interrupting analysis to give preference to a loaded track.";
- dieflag = true;
- cancelled = true;
- }
- }
-
- if (m_exit) {
- dieflag = true;
- cancelled = true;
- }
-
- // Ignore blocks in which we decided to bail for stats purposes.
- if (dieflag || cancelled) {
- t.cancel();
- }
- }
-
- return !cancelled; //don't return !dieflag or we might reanalyze over and over
-}
-
-void AnalyzerQueue::stop() {
- m_exit = true;
- QMutexLocker locked(&m_qm);
- m_qwait.wakeAll();
-}
-
-void AnalyzerQueue::run() {
- // If there are no analyzers, don't waste time running.
- if (m_pAnalyzers.empty()) {
- return;
- }
-
- const int instanceId = s_instanceCounter.fetchAndAddAcquire(1) + 1;
- QThread::currentThread()->setObjectName(QString("AnalyzerQueue %1").arg(instanceId));
-
- kLogger.debug() << "Entering thread";
-
- execThread();
-
- kLogger.debug() << "Exiting thread";
-}
-
-void AnalyzerQueue::execThread() {
- // 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.current_track.reset();
- m_progressInfo.track_progress = 0;
- m_progressInfo.queue_size = 0;
- m_progressInfo.sema.release(); // Initialize with one
-
- while (!m_exit) {
- TrackPointer nextTrack = dequeueNextBlocking();
-
- // It's important to check for m_exit here in case we decided to exit
- // while blocking for a new track.
- if (m_exit) {
- break;
- }
-
- // If the track is NULL, try to get the next one.
- // Could happen if the track was queued but then deleted.
- // Or if dequeueNextBlocking is unblocked by exit == true
- if (!nextTrack) {
- emptyCheck();
- continue;
- }
-
- kLogger.debug() << "Analyzing" << nextTrack->getTitle() << nextTrack->getLocation();
-
- Trace trace("AnalyzerQueue analyzing track");
-
- // Get the audio
- mixxx::AudioSource::OpenParams openParams;
- openParams.setChannelCount(kAnalysisChannels);
- auto pAudioSource = SoundSourceProxy(nextTrack).openAudioSource(openParams);
- if (!pAudioSource) {
- kLogger.warning()
- << "Failed to open file for analyzing:"
- << nextTrack->getLocation();
- emptyCheck();
- continue;
- }
-
- bool processTrack = false;
- for (auto const& pAnalyzer: m_pAnalyzers) {
- // Make sure not to short-circuit initialize(...)
- if (pAnalyzer->initialize(
- nextTrack,
- pAudioSource->sampleRate(),
- pAudioSource->frameLength() * kAnalysisChannels)) {
- processTrack = true;
- }
- }
-
- updateSize();
-
- if (processTrack) {
- emitUpdateProgress(nextTrack, 0);
- bool completed = doAnalysis(nextTrack, pAudioSource);
- if (!completed) {
- // This track was cancelled
- for (auto const& pAnalyzer: m_pAnalyzers) {
- pAnalyzer->cleanup(nextTrack);
- }
- queueAnalyseTrack(nextTrack);
- emitUpdateProgress(nextTrack, 0);
- } else {
- // 100% - FINALIZE_PERCENT finished
- emitUpdateProgress(nextTrack, 1000 - FINALIZE_PROMILLE);
- // This takes around 3 sec on a Atom Netbook
- for (auto const& pAnalyzer: m_pAnalyzers) {
- pAnalyzer->finalize(nextTrack);
- }
- emit(trackDone(nextTrack));
- emitUpdateProgress(nextTrack, 1000); // 100%
- }
- } else {
- emitUpdateProgress(nextTrack, 1000); // 100%
- kLogger.debug() << "Skipping track analysis because no analyzer initialized.";
- }
- emptyCheck();
- }
-
- if (m_pAnalysisDao) {
- // Invalidate reference to the thread-local database connection
- // that will be closed soon. Not necessary, just in case ;)
- m_pAnalysisDao->initialize(QSqlDatabase());
- }
-
- emit(queueEmpty()); // emit in case of exit;
-}
-
-void AnalyzerQueue::emptyCheck() {
- updateSize();
- if (m_queue_size == 0) {
- emit(queueEmpty()); // emit asynchrony for no deadlock
- }
-}
-
-void AnalyzerQueue::updateSize() {
- QMutexLocker locked(&m_qm);
- m_queue_size = m_queuedTracks.size();
-}
-
-// This is called from the AnalyzerQueue thread
-void AnalyzerQueue::emitUpdateProgress(TrackPointer track, int progress) {
- if (!m_exit) {
- // 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 = track;
- m_progressInfo.track_progress = progress;
- m_progressInfo.queue_size = m_queue_size;
- emit(updateProgress());
- }
-}
-
-//slot
-void AnalyzerQueue::slotUpdateProgress() {
- if (m_progressInfo.current_track) {
- m_progressInfo.current_track->setAnalyzerProgress(m_progressInfo.track_progress);
- m_progressInfo.current_track.reset();
- }
- emit(trackProgress(m_progressInfo.track_progress / 10));
- if (m_progressInfo.track_progress == 1000) {
- emit(trackFinished(m_progressInfo.queue_size));
- }
- m_progressInfo.sema.release();
-}
-
-void AnalyzerQueue::slotAnalyseTrack(TrackPointer pTrack) {
- // This slot is called from the decks and and samplers when the track was loaded.
- queueAnalyseTrack(pTrack);
- m_aiCheckPriorities = true;
-}
-
-// This is called from the GUI and from the AnalyzerQueue thread
-void AnalyzerQueue::queueAnalyseTrack(TrackPointer pTrack) {
- if (pTrack) {
- QMutexLocker locked(&m_qm);
- if (!m_queuedTracks.contains(pTrack)) {
- m_queuedTracks.enqueue(pTrack);
- m_qwait.wakeAll();
- }
- }
-}
diff --git a/src/analyzer/analyzerqueue.h b/src/analyzer/analyzerqueue.h
deleted file mode 100644
index 41bd403b85..0000000000
--- a/src/analyzer/analyzerqueue.h
+++ /dev/null
@@ -1,91 +0,0 @@
-#ifndef ANALYZER_ANALYZERQUEUE_H
-#define ANALYZER_ANALYZERQUEUE_H
-
-#include <QThread>
-#include <QQueue>
-#include <QWaitCondition>
-#include <QSemaphore>
-
-#include <vector>
-
-#include "preferences/usersettings.h"
-#include "sources/audiosource.h"
-#include "track/track.h"
-#include "util/db/dbconnectionpool.h"
-#include "util/samplebuffer.h"
-#include "util/memory.h"
-
-class Analyzer;
-class AnalysisDao;
-
-class AnalyzerQueue : public QThread {
- Q_OBJECT
-
- public:
- enum class Mode {
- Default,
- WithoutWaveform,
- };
-
- AnalyzerQueue(
- mixxx::DbConnectionPoolPtr pDbConnectionPool,
- const UserSettingsPointer& pConfig,
- Mode mode = Mode::Default);
- ~AnalyzerQueue() override;
-
- void stop();
- void queueAnalyseTrack(TrackPointer tio);
-
- public slots:
- void slotAnalyseTrack(TrackPointer tio);
- void slotUpdateProgress();
-
- signals:
- void trackProgress(int progress);
- void trackDone(TrackPointer track);
- void trackFinished(int size);
- // Signals from AnalyzerQueue Thread:
- void queueEmpty();
- void updateProgress();
-
- protected:
- void run();
-
- private:
- struct progress_info {
- TrackPointer current_track;
- int track_progress; // in 0.1 %
- int queue_size;
- QSemaphore sema;
- };
-
- mixxx::DbConnectionPoolPtr m_pDbConnectionPool;
-
- std::unique_ptr<AnalysisDao> m_pAnalysisDao;
-
- typedef std::unique_ptr<Analyzer> AnalyzerPtr;
- std::vector<AnalyzerPtr> m_pAnalyzers;
-
- void execThread();
-
- bool isLoadedTrackWaiting(TrackPointer analysingTrack);
- TrackPointer dequeueNextBlocking();
- bool doAnalysis(TrackPointer tio, mixxx::AudioSourcePointer pAudioSource);
- void emitUpdateProgress(TrackPointer tio, int progress);
- void emptyCheck();
- void updateSize();
-
- bool m_exit;
- QAtomicInt m_aiCheckPriorities;
-
- mixxx::SampleBuffer m_sampleBuffer;
-
- // The processing queue and associated mutex
- QQueue<TrackPointer> m_queuedTracks;
- QMutex m_qm;
- QWaitCondition m_qwait;
- struct progress_info m_progressInfo;
- int m_queue_size;
-};
-
-#endif /* ANALYZER_ANALYZERQUEUE_H */
diff --git a/src/analyzer/analyzerthread.cpp b/src/analyzer/analyzerthread.cpp
new file mode 100644
index 0000000000..4951fe6fdb
--- /dev/null
+++ b/src/analyzer/analyzerthread.cpp
@@ -0,0 +1,326 @@
+#include "analyzer/analyzerthread.h"
+
+#ifdef __VAMP__
+#include "analyzer/analyzerbeats.h"
+#include "analyzer/analyzerkey.h"
+#endif
+#include "analyzer/analyzergain.h"
+#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/db/dbconnectionpooler.h"
+#include "util/db/dbconnectionpooled.h"
+#include "util/logger.h"
+#include "util/timer.h"
+
+
+namespace {
+
+mixxx::Logger kLogger("AnalyzerThread");
+
+// Analysis is done in blocks.
+// We need to use a smaller block size, because on Linux the AnalyzerThread
+// can starve the CPU of its resources, resulting in xruns. A block size
+// of 4096 frames per block seems to do fine.
+constexpr mixxx::AudioSignal::ChannelCount kAnalysisChannels = mixxx::kEngineChannelCount;
+constexpr SINT kAnalysisFramesPerBlock = 4096;
+const SINT kAnalysisSamplesPerBlock =
+ kAnalysisFramesPerBlock * kAnalysisChannels;
+
+// Maximum frequency of progress updates while busy
+const mixxx::Duration kBusyProgressInhibitDuration = mixxx::Duration::fromMillis(60);
+
+void deleteAnalyzerThread(AnalyzerThread* plainPtr) {
+ if (plainPtr) {
+ plainPtr->deleteAfterFinished();
+ }
+}
+
+} // anonymous namespace
+
+//static
+AnalyzerThread::Pointer AnalyzerThread::nullPointer() {
+ return Pointer(nullptr, [](AnalyzerThread*){});
+}
+
+//static
+AnalyzerThread::Pointer AnalyzerThread::createInstance(
+ int id,
+ mixxx::DbConnectionPoolPtr dbConnectionPool,
+ UserSettingsPointer pConfig,
+ AnalyzerMode mode) {
+ return Pointer(new AnalyzerThread(
+ id,
+ dbConnectionPool,
+ pConfig,
+ mode),
+ deleteAnalyzerThread);
+}
+
+AnalyzerThread::AnalyzerThread(
+ int id,
+ mixxx::DbConnectionPoolPtr dbConnectionPool,
+ UserSettingsPointer pConfig,
+ AnalyzerMode mode)
+ : WorkerThread(QString("AnalyzerThread %1").arg(id)),
+ m_id(id),
+ m_dbConnectionPool(std::move(dbConnectionPool)),
+ m_pConfig(std::move(pConfig)),
+ m_mode(mode),
+ m_sampleBuffer(kAnalysisSamplesPerBlock),
+ m_emittedState(AnalyzerThreadState::Void) {
+ m_lastBusyProgressEmittedTimer.start();
+ m_nextTrack.setValue(TrackPointer());
+ // The types are registered multiple times although once would be sufficient
+ qRegisterMetaType<AnalyzerThreadState>();
+ // AnalyzerProgress is just an alias/typedef and must be registered explicitly
+ // by name!
+ qRegisterMetaType<AnalyzerProgress>("AnalyzerProgress");
+}
+
+void AnalyzerThread::doRun() {
+ std::unique_ptr<AnalysisDao> pAnalysisDao;
+ if (m_mode != AnalyzerMode::WithBeatsWithoutWaveform) {
+ pAnalysisDao = std::make_unique<AnalysisDao>(m_pConfig);
+ m_analyzers.push_back(std::make_unique<AnalyzerWaveform>(pAnalysisDao.get()));
+ }
+ if (AnalyzerGain::isEnabled(ReplayGainSettings(m_pConfig))) {
+ m_analyzers.push_back(std::make_unique<AnalyzerGain>(m_pConfig));
+ }
+ if (AnalyzerEbur128::isEnabled(ReplayGainSettings(m_pConfig))) {
+ m_analyzers.push_back(std::make_unique<AnalyzerEbur128>(m_pConfig));
+ }
+#ifdef __VAMP__
+ const bool enforceBpmDetection = m_m