// This is called from the AnalyserQueue thread bool AnalyserQueue::isLoadedTrackWaiting(TrackPointer tio) { QMutexLocker queueLocker(&m_qm); const PlayerInfo& info = PlayerInfo::instance(); TrackPointer pTrack; bool trackWaiting = false; QMutableListIterator<TrackPointer> it(m_tioq); 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->getAnalyserProgress(); if (progress < 0) { // Load stored analysis QListIterator<Analyser*> ita(m_aq); bool processTrack = false; while (ita.hasNext()) { if (!ita.next()->loadStored(pTrack)) { processTrack = true; } } if (!processTrack) { emitUpdateProgress(pTrack, 1000); it.remove(); } else { emitUpdateProgress(pTrack, 0); } } else if (progress == 1000) { it.remove(); } } if (info.isTrackLoaded(tio)) { return false; } return trackWaiting; }
// This is called from the AnalyserQueue thread bool AnalyserQueue::isLoadedTrackWaiting(TrackPointer analysingTrack) { const PlayerInfo& info = PlayerInfo::instance(); TrackPointer pTrack; bool trackWaiting = false; QList<TrackPointer> progress100List; QList<TrackPointer> progress0List; m_qm.lock(); QMutableListIterator<TrackPointer> it(m_tioq); 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->getAnalyserProgress(); if (progress < 0) { // Load stored analysis QListIterator<Analyser*> ita(m_aq); bool processTrack = false; while (ita.hasNext()) { if (!ita.next()->loadStored(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(); } } m_qm.unlock(); // update progress after unlock to avoid a deadlock foreach (TrackPointer pTrack, progress100List) { emitUpdateProgress(pTrack, 1000); }
foreach (TrackPointer pTrack, progress0List) { emitUpdateProgress(pTrack, 0); }
void AnalyserQueue::run() { unsigned static id = 0; //the id of this thread, for debugging purposes QThread::currentThread()->setObjectName(QString("AnalyserQueue %1").arg(++id)); // If there are no analyzers, don't waste time running. if (m_aq.size() == 0) return; m_progressInfo.current_track = TrackPointer(); m_progressInfo.track_progress = 0; m_progressInfo.queue_size = 0; m_progressInfo.sema.release(); // Initalise 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) return; // 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) { m_qm.lock(); m_queue_size = m_tioq.size(); m_qm.unlock(); if (m_queue_size == 0) { emit(queueEmpty()); // emit asynchrony for no deadlock } continue; } Trace trace("AnalyserQueue analyzing track"); // Get the audio SoundSourceProxy soundSource(nextTrack); soundSource.open(); //Open the file for reading int iNumSamples = soundSource.length(); int iSampleRate = soundSource.getSampleRate(); if (iNumSamples == 0 || iSampleRate == 0) { qDebug() << "Skipping invalid file:" << nextTrack->getLocation(); continue; } QListIterator<Analyser*> it(m_aq); bool processTrack = false; while (it.hasNext()) { // Make sure not to short-circuit initialise(...) if (it.next()->initialise(nextTrack, iSampleRate, iNumSamples)) { processTrack = true; } } m_qm.lock(); m_queue_size = m_tioq.size(); m_qm.unlock(); if (processTrack) { emitUpdateProgress(nextTrack, 0); bool completed = doAnalysis(nextTrack, &soundSource); if (!completed) { //This track was cancelled QListIterator<Analyser*> itf(m_aq); while (itf.hasNext()) { itf.next()->cleanup(nextTrack); } queueAnalyseTrack(nextTrack); emitUpdateProgress(nextTrack, 0); } else { // 100% - FINALIZE_PERCENT finished emitUpdateProgress(nextTrack, 1000 - FINALIZE_PERCENT); // This takes around 3 sec on a Atom Netbook QListIterator<Analyser*> itf(m_aq); while (itf.hasNext()) { itf.next()->finalise(nextTrack); } emit(trackDone(nextTrack)); emitUpdateProgress(nextTrack, 1000); // 100% } } else { emitUpdateProgress(nextTrack, 1000); // 100% qDebug() << "Skipping track analysis because no analyzer initialized."; } m_qm.lock(); m_queue_size = m_tioq.size(); m_qm.unlock(); if (m_queue_size == 0) { emit(queueEmpty()); // emit asynchrony for no deadlock } } emit(queueEmpty()); // emit in case of exit; }
// This is called from the AnalyserQueue thread bool AnalyserQueue::doAnalysis(TrackPointer tio, SoundSourceProxy* pSoundSource) { int totalSamples = pSoundSource->length(); //qDebug() << tio->getFilename() << " has " << totalSamples << " samples."; int processedSamples = 0; QTime progressUpdateInhibitTimer; progressUpdateInhibitTimer.start(); // Inhibit Updates for 60 milliseconds int read = 0; bool dieflag = false; bool cancelled = false; int progress; // progress in 0 ... 100 do { ScopedTimer t("AnalyserQueue::doAnalysis block"); read = pSoundSource->read(kAnalysisBlockSize, m_pSamplesPCM); // To compare apples to apples, let's only look at blocks that are the // full block size. if (read != kAnalysisBlockSize) { t.cancel(); } // Safety net in case something later barfs on 0 sample input if (read == 0) { t.cancel(); break; } // If we get more samples than length, ask the analysers to process // up to the number we promised, then stop reading - AD if (read + processedSamples > totalSamples) { qDebug() << "While processing track of length " << totalSamples << " actually got " << read + processedSamples << " samples, truncating analysis at expected length"; read = totalSamples - processedSamples; dieflag = true; } // Normalize the samples from [SHRT_MIN, SHRT_MAX] to [-1.0, 1.0]. // TODO(rryan): Change the SoundSource API to do this for us. for (int i = 0; i < read; ++i) { m_pSamples[i] = static_cast<CSAMPLE>(m_pSamplesPCM[i]) / SHRT_MAX; } QListIterator<Analyser*> it(m_aq); while (it.hasNext()) { Analyser* an = it.next(); //qDebug() << typeid(*an).name() << ".process()"; an->process(m_pSamples, read); //qDebug() << "Done " << typeid(*an).name() << ".process()"; } // emit progress updates // During the doAnalysis function it goes only to 100% - FINALIZE_PERCENT // because the finalise functions will take also some time processedSamples += read; //fp div here prevents insane signed overflow progress = (int)(((float)processedSamples)/totalSamples * (1000 - FINALIZE_PERCENT)); if (m_progressInfo.track_progress != progress) { if (progressUpdateInhibitTimer.elapsed() > 60) { // Inhibit Updates for 60 milliseconds emitUpdateProgress(tio, progress); 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 // analyser 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 (load_atomic(m_aiCheckPriorities)) { m_aiCheckPriorities = false; if (isLoadedTrackWaiting(tio)) { qDebug() << "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(); } } while(read == kAnalysisBlockSize && !dieflag); return !cancelled; //don't return !dieflag or we might reanalyze over and over }
// This is called from the AnalyserQueue thread bool AnalyserQueue::doAnalysis(TrackPointer tio, SoundSourceProxy* pSoundSource) { // TonalAnalyser requires a block size of 65536. Using a different value // breaks the tonal analyser. We need to use a smaller block size becuase on // Linux, the AnalyserQueue can starve the CPU of its resources, resulting // in xruns.. A block size of 8192 seems to do fine. const int ANALYSISBLOCKSIZE = 8192; int totalSamples = pSoundSource->length(); //qDebug() << tio->getFilename() << " has " << totalSamples << " samples."; int processedSamples = 0; SAMPLE* data16 = new SAMPLE[ANALYSISBLOCKSIZE]; CSAMPLE* samples = new CSAMPLE[ANALYSISBLOCKSIZE]; QTime progressUpdateInhibitTimer; progressUpdateInhibitTimer.start(); // Inhibit Updates for 60 milliseconds int read = 0; bool dieflag = false; bool cancelled = false; int progress; // progress in 0 ... 100 do { ScopedTimer t("AnalyserQueue::doAnalysis block"); read = pSoundSource->read(ANALYSISBLOCKSIZE, data16); // To compare apples to apples, let's only look at blocks that are the // full block size. if (read != ANALYSISBLOCKSIZE) { t.cancel(); } // Safety net in case something later barfs on 0 sample input if (read == 0) { t.cancel(); break; } // If we get more samples than length, ask the analysers to process // up to the number we promised, then stop reading - AD if (read + processedSamples > totalSamples) { qDebug() << "While processing track of length " << totalSamples << " actually got " << read + processedSamples << " samples, truncating analysis at expected length"; read = totalSamples - processedSamples; dieflag = true; } for (int i = 0; i < read; ++i) { samples[i] = ((float)data16[i])/32767.0f; } QListIterator<Analyser*> it(m_aq); while (it.hasNext()) { Analyser* an = it.next(); //qDebug() << typeid(*an).name() << ".process()"; an->process(samples, read); //qDebug() << "Done " << typeid(*an).name() << ".process()"; } // emit progress updates // During the doAnalysis function it goes only to 100% - FINALIZE_PERCENT // because the finalise functions will take also some time processedSamples += read; //fp div here prevents insane signed overflow progress = (int)(((float)processedSamples)/totalSamples * (1000 - FINALIZE_PERCENT)); if (m_progressInfo.track_progress != progress) { if (progressUpdateInhibitTimer.elapsed() > 60) { // Inhibit Updates for 60 milliseconds emitUpdateProgress(tio, progress); 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 // analyser 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) { m_aiCheckPriorities = false; if (isLoadedTrackWaiting(tio)) { qDebug() << "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(); } } while(read == ANALYSISBLOCKSIZE && !dieflag); delete[] data16; delete[] samples; return !cancelled; //don't return !dieflag or we might reanalyze over and over }