diff options
author | Uwe Klotz <uklotz@mixxx.org> | 2018-04-18 16:39:07 +0200 |
---|---|---|
committer | Uwe Klotz <uklotz@mixxx.org> | 2018-10-30 23:26:56 +0100 |
commit | c236956884e3c7b9a47536f98eb7eb5a126a26e4 (patch) | |
tree | 85ea9323501bb2f5176c35679e2307e50eeed49c /src/library | |
parent | db7fe92a9a5c63e0d9b6f0fe6f1ad379f2b0b46a (diff) |
Fix and enable multi-threaded analysis
Diffstat (limited to 'src/library')
-rw-r--r-- | src/library/analysisfeature.cpp | 153 | ||||
-rw-r--r-- | src/library/analysisfeature.h | 25 | ||||
-rw-r--r-- | src/library/dlganalysis.cpp | 71 | ||||
-rw-r--r-- | src/library/dlganalysis.h | 15 | ||||
-rw-r--r-- | src/library/library.cpp | 12 | ||||
-rw-r--r-- | src/library/library.h | 9 |
6 files changed, 139 insertions, 146 deletions
diff --git a/src/library/analysisfeature.cpp b/src/library/analysisfeature.cpp index a56279a2ef..bf51122c73 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysisfeature.cpp @@ -13,23 +13,36 @@ #include "library/dlganalysis.h" #include "widget/wlibrary.h" #include "controllers/keyboard/keyboardeventfilter.h" -#include "analyzer/analyzerqueue.h" #include "sources/soundsourceproxy.h" #include "util/dnd.h" #include "util/debug.h" const QString AnalysisFeature::m_sAnalysisViewName = QString("Analysis"); -AnalysisFeature::AnalysisFeature(Library* parent, - UserSettingsPointer pConfig, - TrackCollection* pTrackCollection) : - LibraryFeature(parent), +namespace { + +// Utilize all available cores for batch analysis of tracks +const int kNumberOfAnalyzerThreads = math_max(1, QThread::idealThreadCount()); + +inline +AnalyzerMode getAnalyzerMode( + const UserSettingsPointer& pConfig) { + if (pConfig->getValue<bool>(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"), true)) { + return AnalyzerMode::WithBeats; + } else { + return AnalyzerMode::WithBeatsWithoutWaveform; + } +} + +} // anonymous namespace + +AnalysisFeature::AnalysisFeature( + Library* parent, + UserSettingsPointer pConfig) + : LibraryFeature(parent), + m_library(parent), m_pConfig(pConfig), - m_pLibrary(parent), - m_pDbConnectionPool(parent->dbConnectionPool()), - m_pTrackCollection(pTrackCollection), - m_pAnalyzerQueue(nullptr), - m_iOldBpmEnabled(0), + m_pTrackAnalysisScheduler(TrackAnalysisScheduler::nullPointer()), m_analysisTitleName(tr("Analyze")), m_pAnalysisView(nullptr) { setTitleDefault(); @@ -38,20 +51,18 @@ AnalysisFeature::AnalysisFeature(Library* parent, AnalysisFeature::~AnalysisFeature() { // TODO(XXX) delete these //delete m_pLibraryTableModel; - cleanupAnalyzer(); } - void AnalysisFeature::setTitleDefault() { m_Title = m_analysisTitleName; emit(featureIsLoading(this, false)); } -void AnalysisFeature::setTitleProgress(int trackNum, int totalNum) { +void AnalysisFeature::setTitleProgress(int currentTrackNumber, int totalTracksCount) { m_Title = QString("%1 (%2 / %3)") .arg(m_analysisTitleName) - .arg(QString::number(trackNum)) - .arg(QString::number(totalNum)); + .arg(QString::number(currentTrackNumber)) + .arg(QString::number(totalTracksCount)); emit(featureIsLoading(this, false)); } @@ -67,8 +78,7 @@ void AnalysisFeature::bindWidget(WLibrary* libraryWidget, KeyboardEventFilter* keyboard) { m_pAnalysisView = new DlgAnalysis(libraryWidget, m_pConfig, - m_pLibrary, - m_pTrackCollection); + m_library); connect(m_pAnalysisView, SIGNAL(loadTrack(TrackPointer)), this, SIGNAL(loadTrack(TrackPointer))); connect(m_pAnalysisView, SIGNAL(loadTrackToPlayer(TrackPointer, QString)), @@ -82,15 +92,12 @@ void AnalysisFeature::bindWidget(WLibrary* libraryWidget, this, SIGNAL(trackSelected(TrackPointer))); connect(this, SIGNAL(analysisActive(bool)), - m_pAnalysisView, SLOT(analysisActive(bool))); - connect(this, SIGNAL(trackAnalysisStarted(int)), - m_pAnalysisView, SLOT(trackAnalysisStarted(int))); + m_pAnalysisView, SLOT(slotAnalysisActive(bool))); m_pAnalysisView->installEventFilter(keyboard); // Let the DlgAnalysis know whether or not analysis is active. - bool bAnalysisActive = m_pAnalyzerQueue != NULL; - emit(analysisActive(bAnalysisActive)); + emit(analysisActive(static_cast<bool>(m_pTrackAnalysisScheduler))); libraryWidget->registerView(m_sAnalysisViewName, m_pAnalysisView); } @@ -114,88 +121,80 @@ void AnalysisFeature::activate() { emit(enableCoverArtDisplay(true)); } -namespace { - inline - AnalyzerQueue::Mode getAnalyzerQueueMode( - const UserSettingsPointer& pConfig) { - if (pConfig->getValue<bool>(ConfigKey("[Library]", "EnableWaveformGenerationWithAnalysis"), true)) { - return AnalyzerQueue::Mode::Default; - } else { - return AnalyzerQueue::Mode::WithoutWaveform; - } - } -} // anonymous namespace - void AnalysisFeature::analyzeTracks(QList<TrackId> 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 = new AnalyzerQueue( - m_pDbConnectionPool, + if (!m_pTrackAnalysisScheduler) { + m_pTrackAnalysisScheduler = TrackAnalysisScheduler::createInstance( + m_library, + kNumberOfAnalyzerThreads, m_pConfig, - getAnalyzerQueueMode(m_pConfig)); + getAnalyzerMode(m_pConfig)); - 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_pTrackAnalysisScheduler.get(), SIGNAL(progress(AnalyzerProgress, int, int)), + m_pAnalysisView, SLOT(onTrackAnalysisSchedulerProgress(AnalyzerProgress, int, int))); + connect(m_pTrackAnalysisScheduler.get(), SIGNAL(progress(AnalyzerProgress, int, int)), + this, SLOT(onTrackAnalysisSchedulerProgress(AnalyzerProgress, int, int))); + connect(m_pTrackAnalysisScheduler.get(), SIGNAL(finished()), + this, SLOT(stopAnalysis())); - connect(m_pAnalyzerQueue, 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); + if (trackId.isValid()) { + m_pTrackAnalysisScheduler->scheduleTrackById(trackId); } } - if (trackIds.size() > 0) { - setTitleProgress(0, trackIds.size()); + m_pTrackAnalysisScheduler->resume(); +} + +void AnalysisFeature::onTrackAnalysisSchedulerProgress( + AnalyzerProgress /*currentTrackProgress*/, + int currentTrackNumber, + int totalTracksCount) { + // Ignore any delayed progress updates after the analysis + // has already been stopped. + if (m_pTrackAnalysisScheduler) { + if (totalTracksCount > 0) { + setTitleProgress(currentTrackNumber, totalTracksCount); + } else { + setTitleDefault(); + } } - emit(trackAnalysisStarted(trackIds.size())); } -void AnalysisFeature::slotProgressUpdate(int num_left) { - int num_tracks = m_pAnalysisView->getNumTracks(); - if (num_left > 0) { - int currentTrack = num_tracks - num_left + 1; - setTitleProgress(currentTrack, num_tracks); +void AnalysisFeature::suspendAnalysis() { + //qDebug() << this << "suspendAnalysis"; + if (m_pTrackAnalysisScheduler) { + m_pTrackAnalysisScheduler->suspend(); } } -void AnalysisFeature::stopAnalysis() { - //qDebug() << this << "stopAnalysis()"; - if (m_pAnalyzerQueue != NULL) { - m_pAnalyzerQueue->stop(); +void AnalysisFeature::resumeAnalysis() { + //qDebug() << this << "resumeAnalysis"; + if (m_pTrackAnalysisScheduler) { + m_pTrackAnalysisScheduler->resume(); } } -void AnalysisFeature::cleanupAnalyzer() { +void AnalysisFeature::stopAnalysis() { + //qDebug() << this << "stopAnalysis()"; + if (m_pTrackAnalysisScheduler) { + // Free resources by abandoning the queue after the batch analysis + // has completed. Batch analysis are not started very frequently + // during a session and should be avoided while performing live. + // If the user decides to start a new batch analysis the setup costs + // for creating the queue with its worker threads are acceptable. + m_pTrackAnalysisScheduler.reset(); + } 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<QUrl> urls, QObject* pSource) { Q_UNUSED(pSource); QList<QFileInfo> files = DragAndDropHelper::supportedTracksFromUrls(urls, false, true); // Adds track, does not insert duplicates, handles unremoving logic. - QList<TrackId> trackIds = m_pTrackCollection->getTrackDAO().addMultipleTracks(files, true); + QList<TrackId> trackIds = m_library->trackCollection().getTrackDAO().addMultipleTracks(files, true); analyzeTracks(trackIds); return trackIds.size() > 0; } diff --git a/src/library/analysisfeature.h b/src/library/analysisfeature.h index 74d6480b8c..4af45da37e 100644 --- a/src/library/analysisfeature.h +++ b/src/library/analysisfeature.h @@ -15,19 +15,17 @@ #include "library/libraryfeature.h" #include "library/dlganalysis.h" #include "library/treeitemmodel.h" +#include "analyzer/trackanalysisscheduler.h" #include "preferences/usersettings.h" -#include "util/db/dbconnectionpool.h" class Library; class TrackCollection; -class AnalyzerQueue; class AnalysisFeature : public LibraryFeature { Q_OBJECT public: AnalysisFeature(Library* parent, - UserSettingsPointer pConfig, - TrackCollection* pTrackCollection); + UserSettingsPointer pConfig); virtual ~AnalysisFeature(); QVariant title(); @@ -43,16 +41,17 @@ class AnalysisFeature : public LibraryFeature { signals: void analysisActive(bool bActive); - void trackAnalysisStarted(int size); public slots: void activate(); void analyzeTracks(QList<TrackId> trackIds); + void suspendAnalysis(); + void resumeAnalysis(); + private slots: - void slotProgressUpdate(int num_left); + void onTrackAnalysisSchedulerProgress(AnalyzerProgress currentTrackProgress, int currentTrackNumber, int totalTracksCount); void stopAnalysis(); - void cleanupAnalyzer(); private: // Sets the title of this feature to the default name, given by @@ -62,15 +61,13 @@ class AnalysisFeature : public LibraryFeature { // Sets the title of this feature to the default name followed by (x / y) // where x is the current track being analyzed and y is the total number of // tracks in the job - void setTitleProgress(int trackNum, int totalNum); + void setTitleProgress(int currentTrackNumber, int totalTracksCount); + + Library* m_library; UserSettingsPointer m_pConfig; - Library* m_pLibrary; - mixxx::DbConnectionPoolPtr m_pDbConnectionPool; - TrackCollection* m_pTrackCollection; - AnalyzerQueue* m_pAnalyzerQueue; - // Used to temporarily enable BPM detection in the prefs before we analyse - int m_iOldBpmEnabled; + TrackAnalysisScheduler::Pointer m_pTrackAnalysisScheduler; + // The title returned by title() QVariant m_Title; TreeItemModel m_childModel; diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index 8279c0c286..7f85e59c84 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -3,27 +3,25 @@ #include "widget/wwidget.h" #include "widget/wskincolor.h" #include "widget/wanalysislibrarytableview.h" +#include "analyzer/analyzerprogress.h" #include "library/dao/trackschema.h" #include "library/trackcollection.h" #include "library/dlganalysis.h" #include "library/library.h" #include "util/assert.h" -DlgAnalysis::DlgAnalysis(QWidget* pParent, +DlgAnalysis::DlgAnalysis(QWidget* parent, UserSettingsPointer pConfig, - Library* pLibrary, - TrackCollection* pTrackCollection) - : QWidget(pParent), + Library* pLibrary) + : QWidget(parent), m_pConfig(pConfig), - m_pTrackCollection(pTrackCollection), - m_bAnalysisActive(false), - m_tracksInQueue(0), - m_currentTrack(0) { + m_pTrackCollection(&pLibrary->trackCollection()), + m_bAnalysisActive(false) { setupUi(this); m_songsButtonGroup.addButton(radioButtonRecentlyAdded); m_songsButtonGroup.addButton(radioButtonAllSongs); - m_pAnalysisLibraryTableView = new WAnalysisLibraryTableView(this, pConfig, pTrackCollection); + m_pAnalysisLibraryTableView = new WAnalysisLibraryTableView(this, pConfig, m_pTrackCollection); connect(m_pAnalysisLibraryTableView, SIGNAL(loadTrack(TrackPointer)), this, SIGNAL(loadTrack(TrackPointer))); connect(m_pAnalysisLibraryTableView, SIGNAL(loadTrackToPlayer(TrackPointer, QString)), @@ -40,7 +38,7 @@ DlgAnalysis::DlgAnalysis(QWidget* pParent, box->insertWidget(1, m_pAnalysisLibraryTableView); } - m_pAnalysisLibraryTableModel = new AnalysisLibraryTableModel(this, pTrackCollection); + m_pAnalysisLibraryTableModel = new AnalysisLibraryTableModel(this, m_pTrackCollection); m_pAnalysisLibraryTableView->loadTrackModel(m_pAnalysisLibraryTableModel); connect(radioButtonRecentlyAdded, SIGNAL(clicked()), @@ -52,8 +50,6 @@ DlgAnalysis::DlgAnalysis(QWidget* pParent, // started up. Accounts for 0.2% of skin creation time. Get rid of this! radioButtonRecentlyAdded->click(); - labelProgress->setText(""); - pushButtonAnalyze->setEnabled(false); connect(pushButtonAnalyze, SIGNAL(clicked()), this, SLOT(analyze())); @@ -71,9 +67,8 @@ DlgAnalysis::DlgAnalysis(QWidget* pParent, m_pAnalysisLibraryTableView, SLOT(setTrackTableRowHeight(int))); connect(pLibrary, SIGNAL(setSelectedClick(bool)), m_pAnalysisLibraryTableView, SLOT(setSelectedClick(bool))); -} -DlgAnalysis::~DlgAnalysis() { + slotAnalysisActive(m_bAnalysisActive); } void DlgAnalysis::onShow() { @@ -143,13 +138,12 @@ void DlgAnalysis::analyze() { trackIds.append(trackId); } } - m_currentTrack = 1; emit(analyzeTracks(trackIds)); } } -void DlgAnalysis::analysisActive(bool bActive) { - qDebug() << this << "analysisActive" << bActive; +void DlgAnalysis::slotAnalysisActive(bool bActive) { + //qDebug() << this << "slotAnalysisActive" << bActive; m_bAnalysisActive = bActive; if (bActive) { pushButtonAnalyze->setEnabled(true); @@ -162,33 +156,28 @@ void DlgAnalysis::analysisActive(bool bActive) { } } -// slot -void DlgAnalysis::trackAnalysisFinished(int size) { - qDebug() << "Analysis finished" << size << "tracks left"; - if (size > 0) { - m_currentTrack = m_tracksInQueue - size + 1; - } -} - -// slot -void DlgAnalysis::trackAnalysisProgress(int progress) { - if (m_bAnalysisActive) { - QString text = tr("Analyzing %1/%2 %3%").arg( - QString::number(m_currentTrack), - QString::number(m_tracksInQueue), - QString::number(progress)); - labelProgress->setText(text); +void DlgAnalysis::onTrackAnalysisSchedulerProgress( + AnalyzerProgress analyzerProgress, int finishedCount, int totalCount) { + //qDebug() << this << "onTrackAnalysisSchedulerProgress" << analyzerProgress << finishedCount << totalCount; + if (labelProgress->isEnabled()) { + QString progressText; + if (analyzerProgress >= kAnalyzerProgressNone) { + QString progressPercent = QString::number( + analyzerProgressPercent(analyzerProgress)); + progressText = tr("Analyzing %1% %2/%3").arg( + progressPercent, + QString::number(finishedCount), + QString::number(totalCount)); + } else { + // Omit to display any percentage + progressText = tr("Analyzing %1/%2").arg( + QString::number(finishedCount), + QString::number(totalCount)); + } + labelProgress->setText(progressText); } } -int DlgAnalysis::getNumTracks() { - return m_tracksInQueue; -} - -void DlgAnalysis::trackAnalysisStarted(int size) { - m_tracksInQueue = size; -} - void DlgAnalysis::showRecentSongs() { m_pAnalysisLibraryTableModel->showRecentSongs(); } diff --git a/src/library/dlganalysis.h b/src/library/dlganalysis.h index f1adea8140..d0241368a3 100644 --- a/src/library/dlganalysis.h +++ b/src/library/dlganalysis.h @@ -9,6 +9,7 @@ #include "library/libraryview.h" #include "library/trackcollection.h" #include "library/ui_dlganalysis.h" +#include "analyzer/analyzerprogress.h" class AnalysisLibraryTableModel; class WAnalysisLibraryTableView; @@ -19,9 +20,8 @@ class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual Libra public: DlgAnalysis(QWidget *parent, UserSettingsPointer pConfig, - Library* pLibrary, - TrackCollection* pTrackCollection); - ~DlgAnalysis() override; + Library* pLibrary); + ~DlgAnalysis() override = default; void onSearch(const QString& text) override; void onShow() override; @@ -35,20 +35,17 @@ class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual Libra inline const QString currentSearch() { return m_pAnalysisLibraryTableModel->currentSearch(); } - int getNumTracks(); public slots: void tableSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void selectAll(); void analyze(); - void trackAnalysisFinished(int size); - void trackAnalysisProgress(int progress); - void trackAnalysisStarted(int size); + void slotAnalysisActive(bool bActive); + void onTrackAnalysisSchedulerProgress(AnalyzerProgress analyzerProgress, int finishedCount, int totalCount); void showRecentSongs(); void showAllSongs(); void installEventFilter(QObject* pFilter); - void analysisActive(bool bActive); signals: void loadTrack(TrackPointer pTrack); @@ -65,8 +62,6 @@ class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual Libra QButtonGroup m_songsButtonGroup; WAnalysisLibraryTableView* m_pAnalysisLibraryTableView; AnalysisLibraryTableModel* m_pAnalysisLibraryTableModel; - int m_tracksInQueue; - int m_currentTrack; }; #endif //DLGTRIAGE_H diff --git a/src/library/library.cpp b/src/library/library.cpp index c47f813b3c..0892d5aed1 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -63,7 +63,7 @@ Library::Library( QObject* parent, UserSettingsPointer pConfig, mixxx::DbConnectionPoolPtr pDbConnectionPool, - PlayerManagerInterface* pPlayerManager, + PlayerManager* pPlayerManager, RecordingManager* pRecordingManager) : m_pConfig(pConfig), m_pDbConnectionPool(pDbConnectionPool), @@ -124,12 +124,20 @@ Library::Library( addFeature(browseFeature); addFeature(new RecordingFeature(this, pConfig, m_pTrackCollection, pRecordingManager)); addFeature(new SetlogFeature(this, pConfig, m_pTrackCollection)); - m_pAnalysisFeature = new AnalysisFeature(this, pConfig, m_pTrackCollection); + + m_pAnalysisFeature = new AnalysisFeature(this, pConfig); connect(m_pPlaylistFeature, SIGNAL(analyzeTracks(QList<TrackId>)), m_pAnalysisFeature, SLOT(analyzeTracks(QList<TrackId>))); connect(m_pCrateFeature, SIGNAL(analyzeTracks(QList<TrackId>)), m_pAnalysisFeature, SLOT(analyzeTracks(QList<TrackId>))); addFeature(m_pAnalysisFeature); + // Suspend a batch analysis while an ad-hoc analysis of + // loaded tracks is in progress and resume it afterwards. + connect(pPlayerManager, SIGNAL(trackAnalyzerProgress(TrackId, AnalyzerProgress)), + m_pAnalysisFeature, SLOT(suspendAnalysis())); + connect(pPlayerManager, SIGNAL(trackAnalyzerIdle()), + m_pAnalysisFeature, SLOT(resumeAnalysis())); + //iTunes and Rhythmbox should be last until we no longer have an obnoxious //messagebox popup when you select them. (This forces you to reach for your //mouse or keyboard if you're using MIDI control and you scroll through them...) diff --git a/src/library/library.h b/src/library/library.h index e8e2a01d61..09e1e7e9da 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -34,7 +34,7 @@ class PlaylistFeature; class CrateFeature; class LibraryControl; class KeyboardEventFilter; -class PlayerManagerInterface; +class PlayerManager; class Library: public QObject, public virtual /*implements*/ GlobalTrackCacheSaver { @@ -48,7 +48,7 @@ class Library: public QObject, Library(QObject* parent, UserSettingsPointer pConfig, mixxx::DbConnectionPoolPtr pDbConnectionPool, - PlayerManagerInterface* pPlayerManager, + PlayerManager* pPlayerManager, RecordingManager* pRecordingManager); ~Library() override; @@ -56,6 +56,11 @@ class Library: public QObject, return m_pDbConnectionPool; } + TrackCollection& trackCollection() { + DEBUG_ASSERT(m_pTrackCollection); + return *m_pTrackCollection; + } + void bindWidget(WLibrary* libraryWidget, KeyboardEventFilter* pKeyboard); void bindSidebarWidget(WLibrarySidebar* sidebarWidget); |