diff --git a/src/preferences/dialog/dlgprefsound.cpp b/src/preferences/dialog/dlgprefsound.cpp index 779db978d4c2..527541ef753d 100644 --- a/src/preferences/dialog/dlgprefsound.cpp +++ b/src/preferences/dialog/dlgprefsound.cpp @@ -74,6 +74,13 @@ DlgPrefSound::DlgPrefSound(QWidget* pParent, SoundManager* pSoundManager, connect(deviceSyncComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(syncBuffersChanged(int))); + engineClockComboBox->clear(); + engineClockComboBox->addItem(tr("Soundcard Clock")); + engineClockComboBox->addItem(tr("Network Clock")); + engineClockComboBox->setCurrentIndex(0); + connect(engineClockComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(engineClockChanged(int))); + keylockComboBox->clear(); for (int i = 0; i < EngineBuffer::KEYLOCK_ENGINE_COUNT; ++i) { keylockComboBox->addItem( @@ -126,6 +133,8 @@ DlgPrefSound::DlgPrefSound(QWidget* pParent, SoundManager* pSoundManager, this, SLOT(settingChanged())); connect(deviceSyncComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(settingChanged())); + connect(engineClockComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(settingChanged())); connect(keylockComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(settingChanged())); @@ -398,6 +407,12 @@ void DlgPrefSound::loadSettings(const SoundManagerConfig &config) { deviceSyncComboBox->setCurrentIndex(0); } + if (m_config.getForceNetworkClock()) { + engineClockComboBox->setCurrentIndex(1); + } else { + engineClockComboBox->setCurrentIndex(0); + } + // Default keylock is Rubberband. int keylock_engine = m_pConfig->getValue( ConfigKey("[Master]", "keylock_engine"), 1); @@ -486,6 +501,16 @@ void DlgPrefSound::syncBuffersChanged(int index) { } } +void DlgPrefSound::engineClockChanged(int index) { + if (index == 0) { + // "Soundcard Clock" + m_config.setForceNetworkClock(false); + } else { + // "Network Clock" + m_config.setForceNetworkClock(true); + } +} + // Slot called whenever the selected sample rate is changed. Populates the // audio buffer input box with SMConfig::kMaxLatency values, starting at 1ms, // representing a number of frames per buffer, which will always be a power diff --git a/src/preferences/dialog/dlgprefsound.h b/src/preferences/dialog/dlgprefsound.h index ef81d68de708..c4cf218f66c4 100644 --- a/src/preferences/dialog/dlgprefsound.h +++ b/src/preferences/dialog/dlgprefsound.h @@ -80,6 +80,7 @@ class DlgPrefSound : public DlgPreferencePage, public Ui::DlgPrefSoundDlg { void audioBufferChanged(int index); void updateAudioBufferSizes(int sampleRateIndex); void syncBuffersChanged(int index); + void engineClockChanged(int index); void refreshDevices(); void settingChanged(); void deviceSettingChanged(); diff --git a/src/preferences/dialog/dlgprefsounddlg.ui b/src/preferences/dialog/dlgprefsounddlg.ui index 1f066c204755..376d9f5fe5f1 100644 --- a/src/preferences/dialog/dlgprefsounddlg.ui +++ b/src/preferences/dialog/dlgprefsounddlg.ui @@ -66,6 +66,20 @@ + + + Engine Clock + + + + + + + Use soundcard clock for live audience setups and lowest latency.<br>Use network clock for broadcasting without a live audience. + + + + Keylock/Pitch-Bending Engine @@ -76,12 +90,9 @@ - - - - + Master Mix @@ -89,33 +100,36 @@ + + + - + Master Output Mode - + - + Microphone Monitor Mode - + Microphone Latency Compensation - + ms @@ -131,14 +145,14 @@ - + Master Delay - + ms @@ -154,14 +168,14 @@ - + Headphone Delay - + ms @@ -177,14 +191,14 @@ - + Booth Delay - + ms @@ -200,7 +214,7 @@ - + warning goes here diff --git a/src/soundio/sounddevice.cpp b/src/soundio/sounddevice.cpp index 509cb4795b8b..d3d98f791212 100644 --- a/src/soundio/sounddevice.cpp +++ b/src/soundio/sounddevice.cpp @@ -112,9 +112,9 @@ bool SoundDevice::operator==(const QString &other) const { } void SoundDevice::composeOutputBuffer(CSAMPLE* outputBuffer, - const unsigned int framesToCompose, - const unsigned int framesReadOffset, - const unsigned int iFrameSize) { + const SINT framesToCompose, + const SINT framesReadOffset, + const int iFrameSize) { //qDebug() << "SoundDevice::composeOutputBuffer()" // << device->getInternalName() // << framesToCompose << iFrameSize; @@ -149,20 +149,20 @@ void SoundDevice::composeOutputBuffer(CSAMPLE* outputBuffer, if (iChannelCount == 1) { // All AudioOutputs are stereo as of Mixxx 1.12.0. If we have a mono // output then we need to downsample. - for (unsigned int iFrameNo = 0; iFrameNo < framesToCompose; ++iFrameNo) { + for (SINT iFrameNo = 0; iFrameNo < framesToCompose; ++iFrameNo) { // iFrameBase is the "base sample" in a frame (ie. the first // sample in a frame) - const unsigned int iFrameBase = iFrameNo * iFrameSize; + const SINT iFrameBase = iFrameNo * iFrameSize; outputBuffer[iFrameBase + iChannelBase] = SampleUtil::clampSample( (pAudioOutputBuffer[iFrameNo * 2] + pAudioOutputBuffer[iFrameNo * 2 + 1]) / 2.0f); } } else { - for (unsigned int iFrameNo = 0; iFrameNo < framesToCompose; ++iFrameNo) { + for (SINT iFrameNo = 0; iFrameNo < framesToCompose; ++iFrameNo) { // iFrameBase is the "base sample" in a frame (ie. the first // sample in a frame) - const unsigned int iFrameBase = iFrameNo * iFrameSize; - const unsigned int iLocalFrameBase = iFrameNo * iChannelCount; + const SINT iFrameBase = iFrameNo * iFrameSize; + const SINT iLocalFrameBase = iFrameNo * iChannelCount; // this will make sure a sample from each channel is copied for (int iChannel = 0; iChannel < iChannelCount; ++iChannel) { @@ -182,9 +182,9 @@ void SoundDevice::composeOutputBuffer(CSAMPLE* outputBuffer, } void SoundDevice::composeInputBuffer(const CSAMPLE* inputBuffer, - const unsigned int framesToPush, - const unsigned int framesWriteOffset, - const unsigned int iFrameSize) { + const SINT framesToPush, + const SINT framesWriteOffset, + const int iFrameSize) { //qDebug() << "SoundManager::pushBuffer" // << framesToPush << framesWriteOffset << iFrameSize; // This function is called a *lot* and is a big source of CPU usage. @@ -199,7 +199,7 @@ void SoundDevice::composeInputBuffer(const CSAMPLE* inputBuffer, const AudioInputBuffer& in = m_audioInputs.at(0); CSAMPLE* pInputBuffer = in.getBuffer(); // Always Stereo pInputBuffer = &pInputBuffer[framesWriteOffset * 2]; - for (unsigned int iFrameNo = 0; iFrameNo < framesToPush; ++iFrameNo) { + for (SINT iFrameNo = 0; iFrameNo < framesToPush; ++iFrameNo) { pInputBuffer[iFrameNo * 2] = inputBuffer[iFrameNo]; pInputBuffer[iFrameNo * 2 + 1] = @@ -225,11 +225,11 @@ void SoundDevice::composeInputBuffer(const CSAMPLE* inputBuffer, CSAMPLE* pInputBuffer = in.getBuffer(); pInputBuffer = &pInputBuffer[framesWriteOffset * 2]; - for (unsigned int iFrameNo = 0; iFrameNo < framesToPush; ++iFrameNo) { + for (SINT iFrameNo = 0; iFrameNo < framesToPush; ++iFrameNo) { // iFrameBase is the "base sample" in a frame (ie. the first // sample in a frame) - unsigned int iFrameBase = iFrameNo * iFrameSize; - unsigned int iLocalFrameBase = iFrameNo * 2; + SINT iFrameBase = iFrameNo * iFrameSize; + SINT iLocalFrameBase = iFrameNo * 2; if (iChannelCount == 1) { pInputBuffer[iLocalFrameBase] = @@ -247,9 +247,8 @@ void SoundDevice::composeInputBuffer(const CSAMPLE* inputBuffer, } } -void SoundDevice::clearInputBuffer(const unsigned int framesToPush, - const unsigned int framesWriteOffset) { - +void SoundDevice::clearInputBuffer(const SINT framesToPush, + const SINT framesWriteOffset) { for (QList::const_iterator i = m_audioInputs.begin(), e = m_audioInputs.end(); i != e; ++i) { const AudioInputBuffer& in = *i; diff --git a/src/soundio/sounddevice.h b/src/soundio/sounddevice.h index 4084d2ffbb61..7c3c71933273 100644 --- a/src/soundio/sounddevice.h +++ b/src/soundio/sounddevice.h @@ -73,17 +73,17 @@ class SoundDevice { protected: void composeOutputBuffer(CSAMPLE* outputBuffer, - const unsigned int iFramesPerBuffer, - const unsigned int readOffset, - const unsigned int iFrameSize); + const SINT iFramesPerBuffer, + const SINT readOffset, + const int iFrameSize); void composeInputBuffer(const CSAMPLE* inputBuffer, - const unsigned int framesToPush, - const unsigned int framesWriteOffset, - const unsigned int iFrameSize); + const SINT framesToPush, + const SINT framesWriteOffset, + const int iFrameSize); - void clearInputBuffer(const unsigned int framesToPush, - const unsigned int framesWriteOffset); + void clearInputBuffer(const SINT framesToPush, + const SINT framesWriteOffset); UserSettingsPointer m_pConfig; // Pointer to the SoundManager object which we'll request audio from. @@ -100,7 +100,7 @@ class SoundDevice { double m_dSampleRate; // The name of the audio API used by this device. QString m_hostAPI; - unsigned int m_framesPerBuffer; + SINT m_framesPerBuffer; QList m_audioOutputs; QList m_audioInputs; }; diff --git a/src/soundio/sounddevicenetwork.cpp b/src/soundio/sounddevicenetwork.cpp index 1018dd9c0b7d..7da7338558a8 100644 --- a/src/soundio/sounddevicenetwork.cpp +++ b/src/soundio/sounddevicenetwork.cpp @@ -2,24 +2,30 @@ #include +#include "waveform/visualplayposition.h" +#include "util/timer.h" +#include "util/trace.h" +#include "control/controlproxy.h" +#include "control/controlobject.h" +#include "util/denormalsarezero.h" #include "engine/sidechain/enginenetworkstream.h" +#include "float.h" #include "soundio/sounddevice.h" #include "soundio/soundmanager.h" #include "soundio/soundmanagerutil.h" #include "util/sample.h" -// static -volatile int SoundDeviceNetwork::m_underflowHappened = 0; - SoundDeviceNetwork::SoundDeviceNetwork(UserSettingsPointer config, SoundManager *sm, QSharedPointer pNetworkStream) : SoundDevice(config, sm), m_pNetworkStream(pNetworkStream), - m_outputFifo(NULL), - m_inputFifo(NULL), m_outputDrift(false), - m_inputDrift(false) { + m_inputDrift(false), + m_framesSinceAudioLatencyUsageUpdate(0), + m_denormals(false), + m_targetTime(0), + m_lastCallbackEntrytoDacSecs(0) { // Setting parent class members: m_hostAPI = "Network stream"; m_dSampleRate = 44100.0; @@ -27,6 +33,9 @@ SoundDeviceNetwork::SoundDeviceNetwork(UserSettingsPointer config, m_strDisplayName = QObject::tr("Network stream"); m_iNumInputChannels = pNetworkStream->getNumInputChannels(); m_iNumOutputChannels = pNetworkStream->getNumOutputChannels(); + + m_pMasterAudioLatencyUsage = std::make_unique("[Master]", + "audio_latency_usage"); } SoundDeviceNetwork::~SoundDeviceNetwork() { @@ -43,31 +52,45 @@ SoundDeviceError SoundDeviceNetwork::open(bool isClkRefDevice, int syncBuffers) // Get latency in milleseconds qDebug() << "framesPerBuffer:" << m_framesPerBuffer; - double bufferMSec = m_framesPerBuffer / m_dSampleRate * 1000; - qDebug() << "Requested sample rate: " << m_dSampleRate << "Hz, latency:" - << bufferMSec << "ms"; - // Create the callback function pointer. - if (isClkRefDevice) { - // Network device as clock Reference is not yet supported - DEBUG_ASSERT(false); - } else { - // Feet the network device buffer directly from the - // clock reference device callback - // This is what should work best. + m_audioBufferTime = mixxx::Duration::fromSeconds( + m_framesPerBuffer / m_dSampleRate); + qDebug() << "Requested sample rate: " << m_dSampleRate << "Hz, latency:" + << m_audioBufferTime; - if (m_iNumOutputChannels) { - m_outputFifo = new FIFO( - m_iNumOutputChannels * m_framesPerBuffer * 2); - } - if (m_iNumInputChannels) { - m_inputFifo = new FIFO( - m_iNumInputChannels * m_framesPerBuffer * 2); - } + // Feed the network device buffer directly from the + // clock reference device callback + // This is what should work best. + if (m_iNumOutputChannels) { + m_outputFifo = std::make_unique >( + m_iNumOutputChannels * m_framesPerBuffer * 2); + } + if (m_iNumInputChannels) { + m_inputFifo = std::make_unique >( + m_iNumInputChannels * m_framesPerBuffer * 2); } m_pNetworkStream->startStream(m_dSampleRate); + // Create the callback Thread if requested + if (isClkRefDevice) { + // Update the samplerate and latency ControlObjects, which allow the + // waveform view to properly correct for the latency. + ControlObject::set(ConfigKey("[Master]", "latency"), + m_audioBufferTime.toDoubleMillis()); + ControlObject::set(ConfigKey("[Master]", "samplerate"), m_dSampleRate); + ControlObject::set(ConfigKey("[Master]", "audio_buffer_size"), + m_audioBufferTime.toDoubleMillis()); + + // Network stream was just started above so we have to wait until + // we can pass one chunk. + // The first callback runs early to do the one time setups + m_targetTime = m_audioBufferTime.toIntegerMicros(); + + m_pThread = std::make_unique(this); + m_pThread->start(QThread::TimeCriticalPriority); + } + return SOUNDDEVICE_ERROR_OK; } @@ -78,14 +101,15 @@ bool SoundDeviceNetwork::isOpen() const { SoundDeviceError SoundDeviceNetwork::close() { //qDebug() << "SoundDeviceNetwork::close()" << getInternalName(); m_pNetworkStream->stopStream(); - if (m_outputFifo) { - delete m_outputFifo; - m_outputFifo = NULL; - } - if (m_inputFifo) { - delete m_inputFifo; - m_inputFifo = NULL; + if (m_pThread) { + m_pThread->stop(); + m_pThread->wait(); + m_pThread.reset(); } + + m_outputFifo.reset(); + m_inputFifo.reset(); + return SOUNDDEVICE_ERROR_OK; } @@ -154,7 +178,7 @@ void SoundDeviceNetwork::readProcess() { int readCount = inChunkSize; if (inChunkSize > readAvailable) { readCount = readAvailable; - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(21); //qDebug() << "readProcess()" << (float)readAvailable / inChunkSize << "underflow"; } if (readCount) { @@ -193,11 +217,11 @@ void SoundDeviceNetwork::writeProcess() { int writeCount = outChunkSize; if (outChunkSize > writeAvailable) { writeCount = writeAvailable; - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(23); //qDebug() << "writeProcess():" << (float) writeAvailable / outChunkSize << "Overflow"; } //qDebug() << "writeProcess():" << (float) writeAvailable / outChunkSize; - if (writeCount) { + if (writeCount > 0) { CSAMPLE* dataPtr1; ring_buffer_size_t size1; CSAMPLE* dataPtr2; @@ -206,13 +230,12 @@ void SoundDeviceNetwork::writeProcess() { (void)m_outputFifo->aquireWriteRegions(writeCount, &dataPtr1, &size1, &dataPtr2, &size2); // Fetch fresh samples and write to the the output buffer - composeOutputBuffer(dataPtr1, size1 / m_iNumOutputChannels, 0, - static_cast(m_iNumOutputChannels)); + composeOutputBuffer(dataPtr1, size1 / m_iNumOutputChannels, 0, m_iNumOutputChannels); if (size2 > 0) { composeOutputBuffer(dataPtr2, size2 / m_iNumOutputChannels, size1 / m_iNumOutputChannels, - static_cast(m_iNumOutputChannels)); + m_iNumOutputChannels); } m_outputFifo->releaseWriteRegions(writeCount); } @@ -220,7 +243,7 @@ void SoundDeviceNetwork::writeProcess() { * m_iNumOutputChannels; int readAvailable = m_outputFifo->readAvailable(); int copyCount = qMin(readAvailable, writeAvailable); - //qDebug() << "SoundDevicePortAudio::writeProcess()" << toRead << writeAvailable; + //qDebug() << "SoundDevicePortAudio::writeProcess()" << readAvailable << writeAvailable; if (copyCount > 0) { CSAMPLE* dataPtr1; ring_buffer_size_t size1; @@ -228,14 +251,14 @@ void SoundDeviceNetwork::writeProcess() { ring_buffer_size_t size2; m_outputFifo->aquireReadRegions(copyCount, &dataPtr1, &size1, &dataPtr2, &size2); - if (writeAvailable >= outChunkSize * 2) { + if (writeAvailable - copyCount > outChunkSize) { // Underflow //qDebug() << "SoundDeviceNetwork::writeProcess() Buffer empty"; - // catch up by filling buffer until we are synced - m_pNetworkStream->writeSilence(writeAvailable - copyCount); - m_underflowHappened = 1; - } else if (writeAvailable > readAvailable + outChunkSize / 2) { - // try to keep PAs buffer filled up to 0.5 chunks + // catch up by filling buffer until we are a half buffer behind + m_pNetworkStream->writeSilence(writeAvailable - copyCount - outChunkSize / 2); + m_pSoundManager->underflowHappened(24); + } else if (writeAvailable - copyCount > outChunkSize / 2) { + // try to keep network buffer filled up to 0.5 chunks if (m_outputDrift) { // duplicate one frame //qDebug() << "SoundDeviceNetwork::writeProcess() duplicate one frame" @@ -244,12 +267,15 @@ void SoundDeviceNetwork::writeProcess() { } else { m_outputDrift = true; } - } else if (writeAvailable < outChunkSize / 2) { - // We are not able to store all new frames + } else if (writeAvailable < outChunkSize / 2 || + readAvailable > outChunkSize * 1.5 + ) { + // We are not able to store at least the half of the new frames + // or we have a risk of an m_outputFifo overflow if (m_outputDrift) { //qDebug() << "SoundDeviceNetwork::writeProcess() skip one frame" // << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize; - ++copyCount; + copyCount = qMin(readAvailable, copyCount + m_iNumOutputChannels); } else { m_outputDrift = true; } @@ -267,3 +293,101 @@ void SoundDeviceNetwork::writeProcess() { m_pNetworkStream->writingDone(copyCount); } } + +void SoundDeviceNetwork::callbackProcessClkRef() { + // This must be the very first call, to measure an exact value + updateCallbackEntryToDacTime(); + + Trace trace("SoundDeviceNetwork::callbackProcessClkRef %1", + getInternalName()); + + + if (!m_denormals) { + m_denormals = true; + // This disables the denormals calculations, to avoid a + // performance penalty of ~20 + // https://bugs.launchpad.net/mixxx/+bug/1404401 +#ifdef __SSE__ + if (!_MM_GET_DENORMALS_ZERO_MODE()) { + qDebug() << "SSE: Enabling denormals to zero mode"; + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); + } else { + qDebug() << "SSE: Denormals to zero mode already enabled"; + } + + if (!_MM_GET_FLUSH_ZERO_MODE()) { + qDebug() << "SSE: Enabling flush to zero mode"; + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + } else { + qDebug() << "SSE: Flush to zero mode already enabled"; + } + // verify if flush to zero or denormals to zero works + // test passes if one of the two flag is set. + volatile double doubleMin = DBL_MIN; // the smallest normalized double + VERIFY_OR_DEBUG_ASSERT(doubleMin / 2 == 0.0) { + qWarning() << "SSE: Denormals to zero mode is not working. EQs and effects may suffer high CPU load"; + } else { + qDebug() << "SSE: Denormals to zero mode is working"; + } +#else + qWarning() << "No SSE: No denormals to zero mode available. EQs and effects may suffer high CPU load"; +#endif + } + + m_pSoundManager->readProcess(); + + { + ScopedTimer t("SoundDevicePortAudio::callbackProcess prepare %1", + getInternalName()); + m_pSoundManager->onDeviceOutputCallback(m_framesPerBuffer); + } + + m_pSoundManager->writeProcess(); + + m_pSoundManager->processUnderflowHappened(); + + updateAudioLatencyUsage(); +} + +void SoundDeviceNetwork::updateCallbackEntryToDacTime() { + m_clkRefTimer.start(); + qint64 currentTime = m_pNetworkStream->getStreamTimeUs(); + m_targetTime += m_audioBufferTime.toIntegerMicros(); + double callbackEntrytoDacSecs = (m_targetTime - currentTime) / 1000000.0; + callbackEntrytoDacSecs = math_max(callbackEntrytoDacSecs, 0.0001); + VisualPlayPosition::setCallbackEntryToDacSecs(callbackEntrytoDacSecs, m_clkRefTimer); + //qDebug() << callbackEntrytoDacSecs << timeSinceLastCbSecs; +} + +void SoundDeviceNetwork::updateAudioLatencyUsage() { + m_framesSinceAudioLatencyUsageUpdate += m_framesPerBuffer; + if (m_framesSinceAudioLatencyUsageUpdate + > (m_dSampleRate / CPU_USAGE_UPDATE_RATE)) { + double secInAudioCb = m_timeInAudioCallback.toDoubleSeconds(); + m_pMasterAudioLatencyUsage->set(secInAudioCb / + (m_framesSinceAudioLatencyUsageUpdate / m_dSampleRate)); + m_timeInAudioCallback.reset(); + m_framesSinceAudioLatencyUsageUpdate = 0; + //qDebug() << m_pMasterAudioLatencyUsage->get(); + } + + qint64 currentTime = m_pNetworkStream->getStreamTimeUs(); + unsigned long sleepUs = 0; + if (currentTime > m_targetTime) { + m_pSoundManager->underflowHappened(22); + //qDebug() << "underflow" << currentTime << m_targetTime; + m_targetTime = currentTime; + } else { + sleepUs = m_targetTime - currentTime; + } + + //qDebug() << "sleep" << sleepUs; + + // measure time in Audio callback at the very last + m_timeInAudioCallback += m_clkRefTimer.elapsed(); + + // now go to sleep until the next callback + if (sleepUs > 0) { + m_pThread->usleep_(sleepUs); + } +} diff --git a/src/soundio/sounddevicenetwork.h b/src/soundio/sounddevicenetwork.h index 2bc315ed2d8f..7c53bb1c4f05 100644 --- a/src/soundio/sounddevicenetwork.h +++ b/src/soundio/sounddevicenetwork.h @@ -2,14 +2,20 @@ #define SOUNDDEVICENETWORK_H #include +#include +#include "util/performancetimer.h" +#include "util/memory.h" #include "soundio/sounddevice.h" #define CPU_USAGE_UPDATE_RATE 30 // in 1/s, fits to display frame rate #define CPU_OVERLOAD_DURATION 500 // in ms class SoundManager; +class ControlProxy; class EngineNetworkStream; +class SoundDeviceNetworkThread; + class SoundDeviceNetwork : public SoundDevice { public: @@ -29,13 +35,53 @@ class SoundDeviceNetwork : public SoundDevice { return 44100; } + void callbackProcessClkRef(); + private: + void updateCallbackEntryToDacTime(); + void updateAudioLatencyUsage(); + QSharedPointer m_pNetworkStream; - FIFO* m_outputFifo; - FIFO* m_inputFifo; + std::unique_ptr > m_outputFifo; + std::unique_ptr > m_inputFifo; bool m_outputDrift; bool m_inputDrift; - static volatile int m_underflowHappened; + + std::unique_ptr m_pMasterAudioLatencyUsage; + mixxx::Duration m_timeInAudioCallback; + mixxx::Duration m_audioBufferTime; + int m_framesSinceAudioLatencyUsageUpdate; + std::unique_ptr m_pThread; + bool m_denormals; + qint64 m_targetTime; + PerformanceTimer m_clkRefTimer; + double m_lastCallbackEntrytoDacSecs; +}; + +class SoundDeviceNetworkThread : public QThread { + Q_OBJECT + public: + SoundDeviceNetworkThread(SoundDeviceNetwork* pParent) + : m_pParent(pParent), + m_stop(false) { + } + + void stop() { + m_stop = true; + } + + void usleep_(unsigned long t) { + usleep(t); + } + + private: + void run() { + while(!m_stop) { + m_pParent->callbackProcessClkRef(); + } + } + SoundDeviceNetwork* m_pParent; + bool m_stop; }; #endif // SOUNDDEVICENETWORK_H diff --git a/src/soundio/sounddeviceportaudio.cpp b/src/soundio/sounddeviceportaudio.cpp index 266688bea987..6b8b91619435 100644 --- a/src/soundio/sounddeviceportaudio.cpp +++ b/src/soundio/sounddeviceportaudio.cpp @@ -40,8 +40,6 @@ #include "vinylcontrol/defs_vinylcontrol.h" #include "waveform/visualplayposition.h" -// static -volatile int SoundDevicePortAudio::m_underflowHappened = 0; namespace { @@ -61,7 +59,7 @@ int paV19Callback(const void *inputBuffer, void *outputBuffer, PaStreamCallbackFlags statusFlags, void *soundDevice) { return ((SoundDevicePortAudio*) soundDevice)->callbackProcess( - (unsigned int) framesPerBuffer, (CSAMPLE*) outputBuffer, + (SINT) framesPerBuffer, (CSAMPLE*) outputBuffer, (const CSAMPLE*) inputBuffer, timeInfo, statusFlags); } @@ -71,7 +69,7 @@ int paV19CallbackDrift(const void *inputBuffer, void *outputBuffer, PaStreamCallbackFlags statusFlags, void *soundDevice) { return ((SoundDevicePortAudio*) soundDevice)->callbackProcessDrift( - (unsigned int) framesPerBuffer, (CSAMPLE*) outputBuffer, + (SINT) framesPerBuffer, (CSAMPLE*) outputBuffer, (const CSAMPLE*) inputBuffer, timeInfo, statusFlags); } @@ -81,7 +79,7 @@ int paV19CallbackClkRef(const void *inputBuffer, void *outputBuffer, PaStreamCallbackFlags statusFlags, void *soundDevice) { return ((SoundDevicePortAudio*) soundDevice)->callbackProcessClkRef( - (unsigned int) framesPerBuffer, (CSAMPLE*) outputBuffer, + (SINT) framesPerBuffer, (CSAMPLE*) outputBuffer, (const CSAMPLE*) inputBuffer, timeInfo, statusFlags); } @@ -102,7 +100,6 @@ SoundDevicePortAudio::SoundDevicePortAudio(UserSettingsPointer config, m_outputDrift(false), m_inputDrift(false), m_bSetThreadPriority(false), - m_underflowUpdateCount(0), m_framesSinceAudioLatencyUsageUpdate(0), m_syncBuffers(2), m_invalidTimeInfoCount(0), @@ -116,12 +113,8 @@ SoundDevicePortAudio::SoundDevicePortAudio(UserSettingsPointer config, m_iNumInputChannels = m_deviceInfo->maxInputChannels; m_iNumOutputChannels = m_deviceInfo->maxOutputChannels; - m_pMasterAudioLatencyOverloadCount = new ControlProxy("[Master]", - "audio_latency_overload_count"); m_pMasterAudioLatencyUsage = new ControlProxy("[Master]", "audio_latency_usage"); - m_pMasterAudioLatencyOverload = new ControlProxy("[Master]", - "audio_latency_overload"); m_inputParams.device = 0; m_inputParams.channelCount = 0; @@ -137,9 +130,7 @@ SoundDevicePortAudio::SoundDevicePortAudio(UserSettingsPointer config, } SoundDevicePortAudio::~SoundDevicePortAudio() { - delete m_pMasterAudioLatencyOverloadCount; delete m_pMasterAudioLatencyUsage; - delete m_pMasterAudioLatencyOverload; } SoundDeviceError SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers) { @@ -369,11 +360,6 @@ SoundDeviceError SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers ControlObject::set(ConfigKey("[Master]", "latency"), currentLatencyMSec); ControlObject::set(ConfigKey("[Master]", "samplerate"), m_dSampleRate); ControlObject::set(ConfigKey("[Master]", "audio_buffer_size"), bufferMSec); - - if (m_pMasterAudioLatencyOverloadCount) { - m_pMasterAudioLatencyOverloadCount->set(0); - } - m_invalidTimeInfoCount = 0; } m_pStream = pStream; @@ -451,10 +437,29 @@ void SoundDevicePortAudio::readProcess() { if (pStream && m_inputParams.channelCount && m_inputFifo) { int inChunkSize = m_framesPerBuffer * m_inputParams.channelCount; if (m_syncBuffers == 0) { // "Experimental (no delay)" + + if (m_inputFifo->readAvailable() == 0) { + // Initial call or underflow at last call + // Init half of the buffer with silence + CSAMPLE* dataPtr1; + ring_buffer_size_t size1; + CSAMPLE* dataPtr2; + ring_buffer_size_t size2; + (void)m_inputFifo->aquireWriteRegions(inChunkSize, + &dataPtr1, &size1, &dataPtr2, &size2); + // Fetch fresh samples and write to the the input buffer + SampleUtil::clear(dataPtr1, size1); + if (size2 > 0) { + SampleUtil::clear(dataPtr2, size2); + } + m_inputFifo->releaseWriteRegions(inChunkSize); + } + // Polling mode signed int readAvailable = Pa_GetStreamReadAvailable(pStream) * m_inputParams.channelCount; int writeAvailable = m_inputFifo->writeAvailable(); int copyCount = qMin(writeAvailable, readAvailable); + //qDebug() << "readProcess()" << (float)writeAvailable / inChunkSize << (float)readAvailable / inChunkSize; if (copyCount > 0) { CSAMPLE* dataPtr1; ring_buffer_size_t size1; @@ -468,7 +473,7 @@ void SoundDevicePortAudio::readProcess() { CSAMPLE* lastFrame = &dataPtr1[size1 - m_inputParams.channelCount]; if (err == paInputOverflowed) { //qDebug() << "SoundDevicePortAudio::readProcess() Pa_ReadStream paInputOverflowed" << getInternalName(); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(12); } if (size2 > 0) { PaError err = Pa_ReadStream(pStream, dataPtr2, @@ -476,13 +481,13 @@ void SoundDevicePortAudio::readProcess() { lastFrame = &dataPtr2[size2 - m_inputParams.channelCount]; if (err == paInputOverflowed) { //qDebug() << "SoundDevicePortAudio::readProcess() Pa_ReadStream paInputOverflowed" << getInternalName(); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(13); } } m_inputFifo->releaseWriteRegions(copyCount); if (readAvailable > writeAvailable + inChunkSize / 2) { - // we are not able to consume all frames + // we are not able to consume enough frames if (m_inputDrift) { // Skip one frame //qDebug() << "SoundDevicePortAudio::readProcess() skip one frame" @@ -492,13 +497,15 @@ void SoundDevicePortAudio::readProcess() { //qDebug() // << "SoundDevicePortAudio::readProcess() Pa_ReadStream paInputOverflowed" // << getInternalName(); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(14); } } else { m_inputDrift = true; } - } else if (readAvailable < inChunkSize / 2) { - // We should read at least inChunkSize + } else if (readAvailable < inChunkSize / 2 || + m_inputFifo->readAvailable() < inChunkSize * 1.5) { + // We should read at least a half inChunkSize + // and our m_iputFifo should now hold a half chunk extra if (m_inputDrift) { // duplicate one frame //qDebug() << "SoundDevicePortAudio::readProcess() duplicate one frame" @@ -523,9 +530,10 @@ void SoundDevicePortAudio::readProcess() { int readCount = inChunkSize; if (inChunkSize > readAvailable) { readCount = readAvailable; - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(15); //qDebug() << "readProcess()" << (float)readAvailable / inChunkSize << "underflow"; } + //qDebug() << "readProcess()" << (float)readAvailable / inChunkSize; if (readCount) { CSAMPLE* dataPtr1; ring_buffer_size_t size1; @@ -564,10 +572,10 @@ void SoundDevicePortAudio::writeProcess() { int writeCount = outChunkSize; if (outChunkSize > writeAvailable) { writeCount = writeAvailable; - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(16); //qDebug() << "writeProcess():" << (float) writeAvailable / outChunkSize << "Overflow"; } - if (writeCount) { + if (writeCount > 0) { CSAMPLE* dataPtr1; ring_buffer_size_t size1; CSAMPLE* dataPtr2; @@ -577,12 +585,12 @@ void SoundDevicePortAudio::writeProcess() { &size1, &dataPtr2, &size2); // Fetch fresh samples and write to the the output buffer composeOutputBuffer(dataPtr1, size1 / m_outputParams.channelCount, 0, - static_cast(m_outputParams.channelCount)); + m_outputParams.channelCount); if (size2 > 0) { composeOutputBuffer(dataPtr2, size2 / m_outputParams.channelCount, size1 / m_outputParams.channelCount, - static_cast(m_outputParams.channelCount)); + m_outputParams.channelCount); } m_outputFifo->releaseWriteRegions(writeCount); } @@ -593,7 +601,7 @@ void SoundDevicePortAudio::writeProcess() { * m_outputParams.channelCount; int readAvailable = m_outputFifo->readAvailable(); int copyCount = qMin(readAvailable, writeAvailable); - //qDebug() << "SoundDevicePortAudio::writeProcess()" << toRead << writeAvailable; + //qDebug() << "SoundDevicePortAudio::writeProcess()" << (float)readAvailable / outChunkSize << (float)writeAvailable / outChunkSize; if (copyCount > 0) { CSAMPLE* dataPtr1; ring_buffer_size_t size1; @@ -601,15 +609,15 @@ void SoundDevicePortAudio::writeProcess() { ring_buffer_size_t size2; m_outputFifo->aquireReadRegions(copyCount, &dataPtr1, &size1, &dataPtr2, &size2); - if (writeAvailable == outChunkSize * 2) { - // Underflow - //qDebug() << "SoundDevicePortAudio::writeProcess() Buffer empty"; - // fill buffer duplicate one sample - for (int i = 0; i < writeAvailable - copyCount; i += - m_outputParams.channelCount) { + if (writeAvailable >= outChunkSize * 2) { + // Underflow (2 is max for native ALSA devices) + //qDebug() << "SoundDevicePortAudio::writeProcess() fill buffer" << (float)(writeAvailable - copyCount) / outChunkSize; + // fill buffer with duplicate of first sample + for (int i = 0; i < writeAvailable - copyCount; + i += m_outputParams.channelCount) { Pa_WriteStream(pStream, dataPtr1, 1); } - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(17); } else if (writeAvailable > readAvailable + outChunkSize / 2) { // try to keep PAs buffer filled up to 0.5 chunks if (m_outputDrift) { @@ -619,18 +627,21 @@ void SoundDevicePortAudio::writeProcess() { PaError err = Pa_WriteStream(pStream, dataPtr1, 1); if (err == paOutputUnderflowed) { //qDebug() << "SoundDevicePortAudio::writeProcess() Pa_ReadStream paOutputUnderflowed"; - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(18); } } else { //qDebug() << "SoundDevicePortAudio::writeProcess() OK" << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize; m_outputDrift = true; } - } else if (writeAvailable < outChunkSize / 2) { - // We are not able to store all new frames + } else if (writeAvailable < outChunkSize / 2 || + readAvailable - copyCount > outChunkSize * 0.5 + ) { + // We are not able to store at least the half of the new frames + // or we have a risk of an m_outputFifo overflow if (m_outputDrift) { //qDebug() << "SoundDevicePortAudio::writeProcess() skip one frame" // << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize; - ++copyCount; + copyCount = qMin(readAvailable, copyCount + m_iNumOutputChannels); } else { m_outputDrift = true; } @@ -641,14 +652,14 @@ void SoundDevicePortAudio::writeProcess() { size1 / m_outputParams.channelCount); if (err == paOutputUnderflowed) { //qDebug() << "SoundDevicePortAudio::writeProcess() Pa_ReadStream paOutputUnderflowed" << getInternalName(); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(19); } if (size2 > 0) { PaError err = Pa_WriteStream(pStream, dataPtr2, size2 / m_outputParams.channelCount); if (err == paOutputUnderflowed) { //qDebug() << "SoundDevicePortAudio::writeProcess() Pa_WriteStream paOutputUnderflowed" << getInternalName(); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(20); } } m_outputFifo->releaseReadRegions(copyCount); @@ -658,7 +669,7 @@ void SoundDevicePortAudio::writeProcess() { } int SoundDevicePortAudio::callbackProcessDrift( - const unsigned int framesPerBuffer, CSAMPLE *out, const CSAMPLE *in, + const SINT framesPerBuffer, CSAMPLE *out, const CSAMPLE *in, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) { Q_UNUSED(timeInfo); @@ -666,7 +677,7 @@ int SoundDevicePortAudio::callbackProcessDrift( getInternalName()); if (statusFlags & (paOutputUnderflow | paInputOverflow)) { - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(7); } // Since we are on the non Clock reference device and may have an independent @@ -730,11 +741,11 @@ int SoundDevicePortAudio::callbackProcessDrift( } else if (writeAvailable) { // Fifo Overflow m_inputFifo->write(in, writeAvailable); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(8); //qDebug() << "callbackProcessDrift write:" << (float) readAvailable / inChunkSize << "Overflow"; } else { // Buffer full - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(9); //qDebug() << "callbackProcessDrift write:" << (float) readAvailable / inChunkSize << "Buffer full"; } } @@ -754,7 +765,7 @@ int SoundDevicePortAudio::callbackProcessDrift( //qDebug() << "callbackProcessDrift read:" << (float)readAvailable / outChunkSize << "Jitter Skip"; } } else if (readAvailable == outChunkSize * (kDriftReserve + 1)) { - m_outputFifo->read(out,outChunkSize); + m_outputFifo->read(out, outChunkSize); m_outputDrift = false; //qDebug() << "callbackProcessDrift read:" << (float)readAvailable / outChunkSize << "Normal"; } else if (readAvailable >= outChunkSize) { @@ -778,19 +789,19 @@ int SoundDevicePortAudio::callbackProcessDrift( // underflow SampleUtil::clear(&out[readAvailable], outChunkSize - readAvailable); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(10); //qDebug() << "callbackProcessDrift read:" << (float)readAvailable / outChunkSize << "Underflow"; } else { // underflow SampleUtil::clear(out, outChunkSize); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(11); //qDebug() << "callbackProcess read:" << (float)readAvailable / outChunkSize << "Buffer empty"; } } return paContinue; } -int SoundDevicePortAudio::callbackProcess(const unsigned int framesPerBuffer, +int SoundDevicePortAudio::callbackProcess(const SINT framesPerBuffer, CSAMPLE *out, const CSAMPLE *in, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) { @@ -798,9 +809,8 @@ int SoundDevicePortAudio::callbackProcess(const unsigned int framesPerBuffer, Trace trace("SoundDevicePortAudio::callbackProcess %1", getInternalName()); if (statusFlags & (paOutputUnderflow | paInputOverflow)) { - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(1); //qDebug() << "callbackProcess read:" << "Underflow"; - } if (m_inputParams.channelCount) { @@ -811,11 +821,11 @@ int SoundDevicePortAudio::callbackProcess(const unsigned int framesPerBuffer, } else if (writeAvailable) { // Fifo Overflow m_inputFifo->write(in, writeAvailable); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(2); //qDebug() << "callbackProcess write:" << "Overflow"; } else { // Buffer full - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(3); //qDebug() << "callbackProcess write:" << "Buffer full"; } } @@ -831,12 +841,12 @@ int SoundDevicePortAudio::callbackProcess(const unsigned int framesPerBuffer, // underflow SampleUtil::clear(&out[readAvailable], outChunkSize - readAvailable); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(4); //qDebug() << "callbackProcess read:" << "Underflow"; } else { // underflow SampleUtil::clear(out, outChunkSize); - m_underflowHappened = 1; + m_pSoundManager->underflowHappened(5); //qDebug() << "callbackProcess read:" << "Buffer empty"; } } @@ -844,7 +854,7 @@ int SoundDevicePortAudio::callbackProcess(const unsigned int framesPerBuffer, } int SoundDevicePortAudio::callbackProcessClkRef( - const unsigned int framesPerBuffer, CSAMPLE *out, const CSAMPLE *in, + const SINT framesPerBuffer, CSAMPLE *out, const CSAMPLE *in, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags) { // This must be the very first call, else timeInfo becomes invalid @@ -906,25 +916,10 @@ int SoundDevicePortAudio::callbackProcessClkRef( #endif if (statusFlags & (paOutputUnderflow | paInputOverflow)) { - m_underflowHappened = true; + m_pSoundManager->underflowHappened(6); } - if (m_underflowUpdateCount == 0) { - if (m_underflowHappened) { - m_pMasterAudioLatencyOverload->set(1.0); - m_pMasterAudioLatencyOverloadCount->set( - m_pMasterAudioLatencyOverloadCount->get() + 1); - m_underflowUpdateCount = CPU_OVERLOAD_DURATION * m_dSampleRate - / framesPerBuffer / 1000; - m_underflowHappened = 0; // resetting here is not thread safe, - // but that is OK, because we count only - // 1 underflow each 500 ms - } else { - m_pMasterAudioLatencyOverload->set(0.0); - } - } else { - --m_underflowUpdateCount; - } + m_pSoundManager->processUnderflowHappened(); //Note: Input is processed first so that any ControlObject changes made in // response to input are processed as soon as possible (that is, when @@ -934,8 +929,7 @@ int SoundDevicePortAudio::callbackProcessClkRef( if (in) { ScopedTimer t("SoundDevicePortAudio::callbackProcess input %1", getInternalName()); - composeInputBuffer(in, framesPerBuffer, 0, - m_inputParams.channelCount); + composeInputBuffer(in, framesPerBuffer, 0, m_inputParams.channelCount); m_pSoundManager->pushInputBuffers(m_audioInputs, m_framesPerBuffer); } @@ -959,8 +953,7 @@ int SoundDevicePortAudio::callbackProcessClkRef( return paContinue; } - composeOutputBuffer(out, framesPerBuffer, 0, static_cast( - m_outputParams.channelCount)); + composeOutputBuffer(out, framesPerBuffer, 0, m_outputParams.channelCount); } m_pSoundManager->writeProcess(); @@ -1040,7 +1033,7 @@ void SoundDevicePortAudio::updateCallbackEntryToDacTime( } void SoundDevicePortAudio::updateAudioLatencyUsage( - const unsigned int framesPerBuffer) { + const SINT framesPerBuffer) { m_framesSinceAudioLatencyUsageUpdate += framesPerBuffer; if (m_framesSinceAudioLatencyUsageUpdate > (m_dSampleRate / CPU_USAGE_UPDATE_RATE)) { diff --git a/src/soundio/sounddeviceportaudio.h b/src/soundio/sounddeviceportaudio.h index de753d846b7a..70190b2d1094 100644 --- a/src/soundio/sounddeviceportaudio.h +++ b/src/soundio/sounddeviceportaudio.h @@ -26,9 +26,7 @@ #include "soundio/sounddevice.h" #include "util/duration.h" - #define CPU_USAGE_UPDATE_RATE 30 // in 1/s, fits to display frame rate -#define CPU_OVERLOAD_DURATION 500 // in ms class SoundManager; class ControlProxy; @@ -54,17 +52,17 @@ class SoundDevicePortAudio : public SoundDevice { // This callback function gets called everytime the sound device runs out of // samples (ie. when it needs more sound to play) - int callbackProcess(const unsigned int framesPerBuffer, + int callbackProcess(const SINT framesPerBuffer, CSAMPLE *output, const CSAMPLE* in, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags); // Same as above but with drift correction - int callbackProcessDrift(const unsigned int framesPerBuffer, + int callbackProcessDrift(const SINT framesPerBuffer, CSAMPLE *output, const CSAMPLE* in, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags); // The same as above but drives the MixxEngine - int callbackProcessClkRef(const unsigned int framesPerBuffer, + int callbackProcessClkRef(const SINT framesPerBuffer, CSAMPLE *output, const CSAMPLE* in, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags); @@ -76,7 +74,7 @@ class SoundDevicePortAudio : public SoundDevice { private: void updateCallbackEntryToDacTime(const PaStreamCallbackTimeInfo* timeInfo); - void updateAudioLatencyUsage(const unsigned int framesPerBuffer); + void updateAudioLatencyUsage(const SINT framesPerBuffer); // PortAudio stream for this device. PaStream* volatile m_pStream; @@ -98,11 +96,7 @@ class SoundDevicePortAudio : public SoundDevice { QString m_lastError; // Whether we have set the thread priority to realtime or not. bool m_bSetThreadPriority; - ControlProxy* m_pMasterAudioLatencyOverloadCount; ControlProxy* m_pMasterAudioLatencyUsage; - ControlProxy* m_pMasterAudioLatencyOverload; - int m_underflowUpdateCount; - static volatile int m_underflowHappened; mixxx::Duration m_timeInAudioCallback; int m_framesSinceAudioLatencyUsageUpdate; int m_syncBuffers; diff --git a/src/soundio/soundmanager.cpp b/src/soundio/soundmanager.cpp index 0e9545eb1012..1bf64ae2a58c 100644 --- a/src/soundio/soundmanager.cpp +++ b/src/soundio/soundmanager.cpp @@ -25,6 +25,7 @@ #endif // ifdef __PORTAUDIO__ #include "control/controlobject.h" +#include "control/controlproxy.h" #include "engine/enginebuffer.h" #include "engine/enginemaster.h" #include "engine/sidechain/enginenetworkstream.h" @@ -47,6 +48,8 @@ typedef PaError (*SetJackClientName)(const char *name); namespace { +#define CPU_OVERLOAD_DURATION 500 // in ms + struct DeviceMode { SoundDevice* device; bool isInput; @@ -66,12 +69,22 @@ SoundManager::SoundManager(UserSettingsPointer pConfig, m_paInitialized(false), m_jackSampleRate(-1), #endif - m_pErrorDevice(NULL) { + m_pErrorDevice(NULL), + m_underflowHappened(0) { // TODO(xxx) some of these ControlObject are not needed by soundmanager, or are unused here. // It is possible to take them out? - m_pControlObjectSoundStatusCO = new ControlObject(ConfigKey("[SoundManager]", "status")); + m_pControlObjectSoundStatusCO = new ControlObject( + ConfigKey("[SoundManager]", "status")); m_pControlObjectSoundStatusCO->set(SOUNDMANAGER_DISCONNECTED); - m_pControlObjectVinylControlGainCO = new ControlObject(ConfigKey(VINYL_PREF_KEY, "gain")); + + m_pControlObjectVinylControlGainCO = new ControlObject( + ConfigKey(VINYL_PREF_KEY, "gain")); + + m_pMasterAudioLatencyOverloadCount = new ControlProxy("[Master]", + "audio_latency_overload_count"); + + m_pMasterAudioLatencyOverload = new ControlProxy("[Master]", + "audio_latency_overload"); //Hack because PortAudio samplerate enumeration is slow as hell on Linux (ALSA dmix sucks, so we can't blame PortAudio) m_samplerates.push_back(44100); @@ -106,6 +119,8 @@ SoundManager::~SoundManager() { delete m_pControlObjectSoundStatusCO; delete m_pControlObjectVinylControlGainCO; + delete m_pMasterAudioLatencyOverloadCount; + delete m_pMasterAudioLatencyOverload; } QList SoundManager::getDeviceList( @@ -350,6 +365,8 @@ SoundDeviceError SoundManager::setupDevices() { // compute the new one then atomically hand off below. SoundDevice* pNewMasterClockRef = NULL; + m_pMasterAudioLatencyOverloadCount->set(0); + // load with all configured devices. // all found devices are removed below QSet devicesNotFound = m_config.getDevices(); @@ -393,6 +410,9 @@ SoundDeviceError SoundManager::setupDevices() { if (device->getInternalName() == kNetworkDeviceInternalName) { AudioOutput out(AudioPath::RECORD_BROADCAST, 0, 2, 0); outputs.append(out); + if (m_config.getForceNetworkClock()) { + pNewMasterClockRef = device; + } } foreach (AudioOutput out, outputs) { @@ -411,12 +431,15 @@ SoundDeviceError SoundManager::setupDevices() { AudioOutputBuffer aob(out, pBuffer); err = device->addOutput(aob); if (err != SOUNDDEVICE_ERROR_OK) goto closeAndError; - if (out.getType() == AudioOutput::MASTER) { - pNewMasterClockRef = device; - } else if ((out.getType() == AudioOutput::DECK || - out.getType() == AudioOutput::BUS) - && !pNewMasterClockRef) { - pNewMasterClockRef = device; + + if (!m_config.getForceNetworkClock()) { + if (out.getType() == AudioOutput::MASTER) { + pNewMasterClockRef = device; + } else if ((out.getType() == AudioOutput::DECK || + out.getType() == AudioOutput::BUS) + && !pNewMasterClockRef) { + pNewMasterClockRef = device; + } } // Check if any AudioSource is registered for this AudioOutput and @@ -438,10 +461,10 @@ SoundDeviceError SoundManager::setupDevices() { for (const auto& mode: toOpen) { SoundDevice* device = mode.device; m_pErrorDevice = device; + // If we have not yet set a clock source then we use the first // output device - if (device->getInternalName() != kNetworkDeviceInternalName && - pNewMasterClockRef == NULL && + if (pNewMasterClockRef == NULL && (!haveOutput || mode.isOutput)) { pNewMasterClockRef = device; qWarning() << "Output sound device clock reference not set! Using" @@ -573,14 +596,14 @@ void SoundManager::checkConfig() { // latency checks itself for validity on SMConfig::setLatency() } -void SoundManager::onDeviceOutputCallback(const unsigned int iFramesPerBuffer) { +void SoundManager::onDeviceOutputCallback(const SINT iFramesPerBuffer) { // Produce a block of samples for output. EngineMaster expects stereo // samples so multiply iFramesPerBuffer by 2. - m_pMaster->process(iFramesPerBuffer*2); + m_pMaster->process(iFramesPerBuffer * 2); } void SoundManager::pushInputBuffers(const QList& inputs, - const unsigned int iFramesPerBuffer) { + const SINT iFramesPerBuffer) { for (QList::ConstIterator i = inputs.begin(), e = inputs.end(); i != e; ++i) { const AudioInputBuffer& in = *i; @@ -675,3 +698,23 @@ void SoundManager::setConfiguredDeckCount(int count) { int SoundManager::getConfiguredDeckCount() const { return m_config.getDeckCount(); } + +void SoundManager::processUnderflowHappened() { + if (m_underflowUpdateCount == 0) { + if (m_underflowHappened) { + m_pMasterAudioLatencyOverload->set(1.0); + m_pMasterAudioLatencyOverloadCount->set( + m_pMasterAudioLatencyOverloadCount->get() + 1); + m_underflowUpdateCount = CPU_OVERLOAD_DURATION * m_config.getSampleRate() + / m_config.getFramesPerBuffer() / 1000; + + m_underflowHappened = 0; // reseting her is not thread save, + // but that is OK, because we count only + // 1 underflow each 500 ms + } else { + m_pMasterAudioLatencyOverload->set(0.0); + } + } else { + --m_underflowUpdateCount; + } +} diff --git a/src/soundio/soundmanager.h b/src/soundio/soundmanager.h index a04276162887..01a5d1796bd2 100644 --- a/src/soundio/soundmanager.h +++ b/src/soundio/soundmanager.h @@ -30,6 +30,7 @@ #include "soundio/sounddevice.h" #include "soundio/sounddeviceerror.h" #include "util/types.h" +#include "util/cmdlineargs.h" class EngineMaster; class AudioOutput; @@ -37,6 +38,7 @@ class AudioInput; class AudioSource; class AudioDestination; class ControlObject; +class ControlProxy; class SoundDeviceNotFound; #define MIXXX_PORTAUDIO_JACK_STRING "JACK Audio Connection Kit" @@ -91,12 +93,12 @@ class SoundManager : public QObject { SoundDeviceError setConfig(SoundManagerConfig config); void checkConfig(); - void onDeviceOutputCallback(const unsigned int iFramesPerBuffer); + void onDeviceOutputCallback(const SINT iFramesPerBuffer); // Used by SoundDevices to "push" any audio from their inputs that they have // into the mixing engine. void pushInputBuffers(const QList& inputs, - const unsigned int iFramesPerBuffer); + const SINT iFramesPerBuffer); void writeProcess(); @@ -111,6 +113,17 @@ class SoundManager : public QObject { return m_pNetworkStream; } + void underflowHappened(int code) { + m_underflowHappened = 1; + // Disable the engine warnings by default, because printing a warning is a + // locking function that will make the problem worse + if (CmdlineArgs::Instance().getDeveloper()) { + qWarning() << "underflowHappened code:" << code; + } + } + + void processUnderflowHappened(); + signals: void devicesUpdated(); // emitted when pointers to SoundDevices go stale void devicesSetup(); // emitted when the sound devices have been set up @@ -148,6 +161,11 @@ class SoundManager : public QObject { QSharedPointer m_pNetworkStream; + QAtomicInt m_underflowHappened; + int m_underflowUpdateCount; + ControlProxy* m_pMasterAudioLatencyOverloadCount; + ControlProxy* m_pMasterAudioLatencyOverload; + std::unique_ptr m_soundDeviceNotFound; }; diff --git a/src/soundio/soundmanagerconfig.cpp b/src/soundio/soundmanagerconfig.cpp index 232a790e91ef..fc02df90925b 100644 --- a/src/soundio/soundmanagerconfig.cpp +++ b/src/soundio/soundmanagerconfig.cpp @@ -39,6 +39,7 @@ SoundManagerConfig::SoundManagerConfig() m_deckCount(kDefaultDeckCount), m_audioBufferSizeIndex(kDefaultAudioBufferSizeIndex), m_syncBuffers(2), + m_forceNetworkClock(false), m_iNumMicInputs(0), m_bExternalRecordBroadcastConnected(false) { m_configFile = QFileInfo(QDir(CmdlineArgs::Instance().getSettingsPath()).filePath(SOUNDMANAGERCONFIG_FILENAME)); @@ -72,8 +73,10 @@ bool SoundManagerConfig::readFromDisk() { // audioBufferSizeIndex is refereed as "latency" in the config file setAudioBufferSizeIndex(rootElement.attribute("latency", "0").toUInt()); setSyncBuffers(rootElement.attribute("sync_buffers", "2").toUInt()); + setForceNetworkClock(rootElement.attribute("force_network_clock", + "0").toUInt() != 0); setDeckCount(rootElement.attribute("deck_count", - QString(kDefaultDeckCount)).toUInt()); + QString(kDefaultDeckCount)).toUInt()); clearOutputs(); clearInputs(); QDomNodeList devElements(rootElement.elementsByTagName("SoundDevice")); @@ -128,6 +131,7 @@ bool SoundManagerConfig::writeToDisk() const { // audioBufferSizeIndex is refereed as "latency" in the config file docElement.setAttribute("latency", m_audioBufferSizeIndex); docElement.setAttribute("sync_buffers", m_syncBuffers); + docElement.setAttribute("force_network_clock", m_forceNetworkClock); docElement.setAttribute("deck_count", m_deckCount); doc.appendChild(docElement); @@ -198,6 +202,14 @@ void SoundManagerConfig::setSyncBuffers(unsigned int syncBuffers) { m_syncBuffers = qMin(syncBuffers, (unsigned int)2); } +bool SoundManagerConfig::getForceNetworkClock() const { + return m_forceNetworkClock; +} + +void SoundManagerConfig::setForceNetworkClock(bool force) { + m_forceNetworkClock = force; +} + /** * Checks that the sample rate in the object is valid according to the list of * sample rates given by SoundManager. @@ -401,6 +413,7 @@ void SoundManagerConfig::loadDefaults(SoundManager *soundManager, unsigned int f } m_syncBuffers = kDefaultSyncBuffers; + m_forceNetworkClock = false; } QSet SoundManagerConfig::getDevices() const { diff --git a/src/soundio/soundmanagerconfig.h b/src/soundio/soundmanagerconfig.h index b84526bc5dca..d8f6357cf6c5 100644 --- a/src/soundio/soundmanagerconfig.h +++ b/src/soundio/soundmanagerconfig.h @@ -70,6 +70,8 @@ class SoundManagerConfig { void setAudioBufferSizeIndex(unsigned int latency); unsigned int getSyncBuffers() const; void setSyncBuffers(unsigned int sampleRate); + bool getForceNetworkClock() const; + void setForceNetworkClock(bool force); void addOutput(const QString &device, const AudioOutput &out); void addInput(const QString &device, const AudioInput &in); QMultiHash getOutputs() const; @@ -92,6 +94,7 @@ class SoundManagerConfig { // values vary with sample rate) -- bkgood unsigned int m_audioBufferSizeIndex; unsigned int m_syncBuffers; + bool m_forceNetworkClock; QMultiHash m_outputs; QMultiHash m_inputs; int m_iNumMicInputs; diff --git a/src/util/duration.h b/src/util/duration.h index 7c817d414b3d..01391ce61876 100644 --- a/src/util/duration.h +++ b/src/util/duration.h @@ -128,7 +128,8 @@ class DurationDebug : public DurationBase { class Duration : public DurationBase { public: // Returns a Duration object representing a duration of 'seconds'. - static Duration fromSeconds(qint64 seconds) { + template + static Duration fromSeconds(T seconds) { return Duration(seconds * kNanosPerSecond); } @@ -151,6 +152,10 @@ class Duration : public DurationBase { : DurationBase(0) { } + void reset() { + m_durationNanos = 0; + } + const Duration operator+(const Duration& other) const { Duration result = *this; result += other; diff --git a/src/waveform/visualplayposition.cpp b/src/waveform/visualplayposition.cpp index 8e47ca8221bb..1fa7794dbb29 100644 --- a/src/waveform/visualplayposition.cpp +++ b/src/waveform/visualplayposition.cpp @@ -54,8 +54,7 @@ double VisualPlayPosition::getAtNextVSync(VSyncThread* vsyncThread) { // add the offset for the position of the sample that will be transfered to the DAC // When the next display frame is displayed playPos += data.m_positionStep * offset * data.m_rate / m_dAudioBufferSize / 1000; - //qDebug() << "delta Pos" << playPos - m_playPosOld << offset; - //m_playPosOld = playPos; + //qDebug() << "playPos" << playPos << offset; return playPos; } return -1;