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;