diff --git a/build/depends.py b/build/depends.py index aa7b8c61bf7..fea5a58c5c8 100644 --- a/build/depends.py +++ b/build/depends.py @@ -892,6 +892,8 @@ def sources(self, build): "playermanager.cpp", "samplerbank.cpp", "sounddevice.cpp", + "sounddevicenetwork.cpp", + "engine/sidechain/enginenetworkstream.cpp", "soundmanager.cpp", "soundmanagerconfig.cpp", "soundmanagerutil.cpp", diff --git a/src/dlgprefsound.cpp b/src/dlgprefsound.cpp index bd0cbc7bc80..a623df18ca8 100644 --- a/src/dlgprefsound.cpp +++ b/src/dlgprefsound.cpp @@ -231,7 +231,9 @@ void DlgPrefSound::slotApply() { */ void DlgPrefSound::initializePaths() { foreach (AudioOutput out, m_pSoundManager->registeredOutputs()) { - addPath(out); + if (!out.isHidden()) { + addPath(out); + } } foreach (AudioInput in, m_pSoundManager->registeredInputs()) { addPath(in); diff --git a/src/engine/enginemaster.cpp b/src/engine/enginemaster.cpp index daaf8b714da..2bc27f644da 100644 --- a/src/engine/enginemaster.cpp +++ b/src/engine/enginemaster.cpp @@ -54,6 +54,7 @@ EngineMaster::EngineMaster(ConfigObject* _config, bool bRampingGain) : m_pEngineEffectsManager(pEffectsManager ? pEffectsManager->getEngineEffectsManager() : NULL), m_bRampingGain(bRampingGain), + m_ppSidechain(&m_pMaster), m_masterGainOld(0.0), m_headphoneMasterGainOld(0.0), m_headphoneGainOld(1.0), @@ -153,7 +154,7 @@ EngineMaster::EngineMaster(ConfigObject* _config, } // Starts a thread for recording and shoutcast - m_pSideChain = bEnableSidechain ? new EngineSideChain(_config) : NULL; + m_pEngineSideChain = bEnableSidechain ? new EngineSideChain(_config) : NULL; // X-Fader Setup m_pXFaderMode = new ControlPushButton( @@ -179,6 +180,7 @@ EngineMaster::EngineMaster(ConfigObject* _config, m_pMasterTalkoverMix = new ControlObject(ConfigKey(group, "talkover_mix"), true, false, true); // persist = true m_pHeadphoneEnabled = new ControlObject(ConfigKey(group, "headEnabled")); + m_pHeadphoneEnabled = new ControlObject(ConfigKey(group, "sidechainEnabled")); // Note: the EQ Rack is set in EffectsManager::setupDefaults(); @@ -195,7 +197,7 @@ EngineMaster::~EngineMaster() { delete m_pHeadGain; delete m_pTalkoverDucking; delete m_pVumeter; - delete m_pSideChain; + delete m_pEngineSideChain; delete m_pMasterDelay; delete m_pHeadDelay; @@ -245,6 +247,10 @@ const CSAMPLE* EngineMaster::getHeadphoneBuffer() const { return m_pHead; } +const CSAMPLE* EngineMaster::getSidechainBuffer() const { + return *m_ppSidechain; +} + void EngineMaster::processChannels(int iBufferSize) { m_activeBusChannels[EngineChannel::LEFT].clear(); m_activeBusChannels[EngineChannel::CENTER].clear(); @@ -513,22 +519,22 @@ void EngineMaster::process(const int iBufferSize) { // Submit master samples to the side chain to do shoutcasting, recording, // etc. (cpu intensive non-realtime tasks) - CSAMPLE* pSidechain = m_pMaster; - if (m_pSideChain != NULL) { + m_ppSidechain = &m_pMaster; + if (m_pEngineSideChain != NULL) { if (m_pMasterTalkoverMix->toBool()) { - // Add Talkover to Sidechain output, re-use the talkover buffer + // Add Master and Talkover to Sidechain output, re-use the talkover buffer SampleUtil::addWithGain(m_pTalkover, m_pMaster, 1.0, iBufferSize); - pSidechain = m_pTalkover; + m_ppSidechain = &m_pTalkover; } - m_pSideChain->writeSamples(pSidechain, iBufferSize); + m_pEngineSideChain->writeSamples(*m_ppSidechain, iBufferSize); } // Update VU meter (it does not return anything). Needs to be here so that // master balance and talkover is reflected in the VU meter. if (m_pVumeter != NULL) { - m_pVumeter->process(pSidechain, iBufferSize); + m_pVumeter->process(*m_ppSidechain, iBufferSize); } // Add master to headphone with appropriate gain @@ -675,6 +681,9 @@ const CSAMPLE* EngineMaster::buffer(AudioOutput output) const { case AudioOutput::DECK: return getDeckBuffer(output.getIndex()); break; + case AudioOutput::SIDECHAIN: + return getSidechainBuffer(); + break; default: return NULL; } @@ -695,6 +704,9 @@ void EngineMaster::onOutputConnected(AudioOutput output) { case AudioOutput::DECK: // We don't track enabled decks. break; + case AudioOutput::SIDECHAIN: + // We don't track enabled sidechain. + break; default: break; } @@ -707,7 +719,7 @@ void EngineMaster::onOutputDisconnected(AudioOutput output) { // and recording/broadcasting as well break; case AudioOutput::HEADPHONES: - m_pHeadphoneEnabled->set(1.0); + m_pHeadphoneEnabled->set(0.0); break; case AudioOutput::BUS: m_bBusOutputConnected[output.getIndex()] = false; @@ -715,6 +727,9 @@ void EngineMaster::onOutputDisconnected(AudioOutput output) { case AudioOutput::DECK: // We don't track enabled decks. break; + case AudioOutput::SIDECHAIN: + // We don't track enabled sidechain. + break; default: break; } diff --git a/src/engine/enginemaster.h b/src/engine/enginemaster.h index 212160032a9..c9631720609 100644 --- a/src/engine/enginemaster.h +++ b/src/engine/enginemaster.h @@ -129,9 +129,10 @@ class EngineMaster : public QObject, public AudioSource { const CSAMPLE* getOutputBusBuffer(unsigned int i) const; const CSAMPLE* getDeckBuffer(unsigned int i) const; const CSAMPLE* getChannelBuffer(QString name) const; + const CSAMPLE* getSidechainBuffer() const; EngineSideChain* getSideChain() const { - return m_pSideChain; + return m_pEngineSideChain; } struct ChannelInfo { @@ -291,6 +292,8 @@ class EngineMaster : public QObject, public AudioSource { CSAMPLE* m_pHead; CSAMPLE* m_pTalkover; + CSAMPLE** m_ppSidechain; // points to master or to talkover buffer + EngineWorkerScheduler* m_pWorkerScheduler; EngineSync* m_pMasterSync; @@ -308,7 +311,7 @@ class EngineMaster : public QObject, public AudioSource { EngineDelay* m_pHeadDelay; EngineVuMeter* m_pVumeter; - EngineSideChain* m_pSideChain; + EngineSideChain* m_pEngineSideChain; ControlPotmeter* m_pCrossfader; ControlPotmeter* m_pHeadMix; diff --git a/src/engine/sidechain/enginenetworkstream.cpp b/src/engine/sidechain/enginenetworkstream.cpp new file mode 100644 index 00000000000..384c1f94a7f --- /dev/null +++ b/src/engine/sidechain/enginenetworkstream.cpp @@ -0,0 +1,142 @@ +#ifdef __WINDOWS__ +#include +#include +#else +#include +#include +#endif + + +#include "engine/sidechain/enginenetworkstream.h" + +#include "sampleutil.h" + +unsigned int kBufferFrames = 32768; // 743 ms @ 44100 Hz + +EngineNetworkStream::EngineNetworkStream(double sampleRate, + int numOutputChannels, + int numInputChannels) + : m_pOutputFifo(NULL), + m_pInputFifo(NULL), + m_numOutputChannels(numOutputChannels), + m_numInputChannels(numInputChannels), + m_sampleRate(sampleRate), + m_streamStartTimeMs(-1), + m_streamFramesWritten(0), + m_streamFramesRead(0) { + if (numOutputChannels) { + m_pOutputFifo = new FIFO(numOutputChannels * kBufferFrames); + } + if (numInputChannels) { + m_pInputFifo = new FIFO(numInputChannels * kBufferFrames); + } +} + +EngineNetworkStream::~EngineNetworkStream() { + if (m_streamStartTimeMs >= 0) { + stopStream(); + } + delete m_pOutputFifo; + delete m_pInputFifo; +} + +void EngineNetworkStream::startStream() { + m_streamStartTimeMs = getNetworkTimeMs(); + m_streamFramesWritten = 0; +} + +void EngineNetworkStream::stopStream() { + m_streamStartTimeMs = -1; +} + +int EngineNetworkStream::getWriteExpected() { + return static_cast(getStreamTimeFrames() - m_streamFramesWritten); +} + +int EngineNetworkStream::getReadExpected() { + return static_cast(getStreamTimeFrames() - m_streamFramesRead); +} + +void EngineNetworkStream::write(const CSAMPLE* buffer, int frames) { + int writeAvailable = m_pOutputFifo->writeAvailable(); + int writeRequired = frames * m_numOutputChannels; + if (writeAvailable < writeRequired) { + // Flush outdated frames to free space for writing + int readRequired = writeRequired - writeAvailable; + qDebug() << "EngineNetworkStream::write flushed" << readRequired + << "samples"; + m_pOutputFifo->flushReadData(readRequired); + writeAvailable = m_pOutputFifo->writeAvailable(); + } + int copyCount = math_min(writeAvailable, writeRequired); + if (copyCount > 0) { + (void)m_pOutputFifo->write(buffer, copyCount); + } + m_streamFramesWritten += frames; +} + +void EngineNetworkStream::writeSilence(int frames) { + int writeAvailable = m_pOutputFifo->writeAvailable(); + int writeRequired = frames * m_numOutputChannels; + if (writeAvailable < writeRequired) { + // Flush outdated frames to free space for writing + int readRequired = writeRequired - writeAvailable; + qDebug() << "EngineNetworkStream::writeSilence flushed" << readRequired + << "samples"; + m_pOutputFifo->flushReadData(readRequired); + writeAvailable = m_pOutputFifo->writeAvailable(); + } + int clearCount = math_min(writeAvailable, writeRequired); + if (clearCount > 0) { + CSAMPLE* dataPtr1; + ring_buffer_size_t size1; + CSAMPLE* dataPtr2; + ring_buffer_size_t size2; + (void)m_pOutputFifo->aquireWriteRegions(clearCount, + &dataPtr1, &size1, &dataPtr2, &size2); + SampleUtil::clear(dataPtr1,size1); + if (size2 > 0) { + SampleUtil::clear(dataPtr2,size2); + } + m_pOutputFifo->releaseWriteRegions(clearCount); + } + m_streamFramesWritten += frames; +} + +void EngineNetworkStream::read(CSAMPLE* buffer, int frames) { + int readAvailable = m_pOutputFifo->readAvailable(); + int readRequired = frames * m_numInputChannels; + int copyCount = math_min(readAvailable, readRequired); + if (copyCount > 0) { + (void)m_pOutputFifo->read(buffer, copyCount); + buffer += copyCount; + } + if (readAvailable < readRequired) { + // Fill missing Samples with silence + int silenceCount = readRequired - readAvailable; + qDebug() << "EngineNetworkStream::write flushed" << readRequired + << "samples"; + SampleUtil::clear(buffer, silenceCount); + } +} + +qint64 EngineNetworkStream::getStreamTimeMs() { + return getNetworkTimeMs() - m_streamStartTimeMs; +} + +qint64 EngineNetworkStream::getStreamTimeFrames() { + return static_cast(getStreamTimeMs()) * m_sampleRate / 1000; +} + +// static +qint64 EngineNetworkStream::getNetworkTimeMs() { + // This matches the GPL2 implementation found in + // https://github.com/codders/libshout/blob/a17fb84671d3732317b0353d7281cc47e2df6cf6/src/timing/timing.c +#ifdef __WINDOWS__ + return timeGetTime(); +#else + struct timeval mtv; + gettimeofday(&mtv, NULL); + return (qint64)(mtv.tv_sec) * 1000 + (qint64)(mtv.tv_usec) / 1000; +#endif +} diff --git a/src/engine/sidechain/enginenetworkstream.h b/src/engine/sidechain/enginenetworkstream.h new file mode 100644 index 00000000000..2e1a61f9f12 --- /dev/null +++ b/src/engine/sidechain/enginenetworkstream.h @@ -0,0 +1,40 @@ +#ifndef ENGINENETWORKSTREAM_H_ +#define ENGINENETWORKSTREAM_H_ + +#include "util/types.h" +#include "util/fifo.h" + +class EngineNetworkStream { + public: + EngineNetworkStream(double sampleRate, + int numOutputChannels, + int numInputChannels); + virtual ~EngineNetworkStream(); + + void startStream(); + void stopStream(); + + int getWriteExpected(); + int getReadExpected(); + + void write(const CSAMPLE* buffer, int frames); + void read(CSAMPLE* buffer, int frames); + void writeSilence(int frames); + + qint64 getStreamTimeMs(); + qint64 getStreamTimeFrames(); + + static qint64 getNetworkTimeMs(); + + private: + FIFO* m_pOutputFifo; + FIFO* m_pInputFifo; + int m_numOutputChannels; + int m_numInputChannels; + double m_sampleRate; + qint64 m_streamStartTimeMs; + qint64 m_streamFramesWritten; + qint64 m_streamFramesRead; +}; + +#endif /* ENGINENETWORKSTREAM_H_ */ diff --git a/src/mixxx.cpp b/src/mixxx.cpp index 833be4c75d8..d91f29d7c1f 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -186,7 +186,8 @@ void MixxxMainWindow::initalize(QApplication* pApp, const CmdlineArgs& args) { m_pEffectsManager = new EffectsManager(this, m_pConfig); // Starting the master (mixing of the channels and effects): - m_pEngine = new EngineMaster(m_pConfig, "[Master]", m_pEffectsManager, true, true); + m_pEngine = new EngineMaster(m_pConfig, "[Master]", m_pEffectsManager, + true, true); // Create effect backends. We do this after creating EngineMaster to allow // effect backends to refer to controls that are produced by the engine. diff --git a/src/playermanager.cpp b/src/playermanager.cpp index c875a3ee415..cdcfb66d434 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -74,6 +74,8 @@ PlayerManager::PlayerManager(ConfigObject* pConfig, m_pSoundManager->registerOutput(AudioOutput(AudioOutput::BUS, 0, 0, o), m_pEngine); } + m_pSoundManager->registerOutput(AudioOutput(AudioOutput::SIDECHAIN), + m_pEngine); } PlayerManager::~PlayerManager() { diff --git a/src/sounddevice.cpp b/src/sounddevice.cpp index 49cee8c843c..36bd9a1411a 100644 --- a/src/sounddevice.cpp +++ b/src/sounddevice.cpp @@ -48,10 +48,6 @@ int SoundDevice::getNumOutputChannels() const { return m_iNumOutputChannels; } -void SoundDevice::setHostAPI(QString api) { - m_hostAPI = api; -} - void SoundDevice::setSampleRate(double sampleRate) { if (sampleRate <= 0.0) { // this is the default value used elsewhere in this file @@ -72,7 +68,7 @@ void SoundDevice::setFramesPerBuffer(unsigned int framesPerBuffer) { } SoundDeviceError SoundDevice::addOutput(const AudioOutputBuffer &out) { - //Check if the output channels are already used + // Check if the output channels are already used foreach (AudioOutputBuffer myOut, m_audioOutputs) { if (out.channelsClash(myOut)) { return SOUNDDEVICE_ERROR_DUPLICATE_OUTPUT_CHANNEL; diff --git a/src/sounddevice.h b/src/sounddevice.h index bbf2e8e6919..c5ada2bcf07 100644 --- a/src/sounddevice.h +++ b/src/sounddevice.h @@ -37,6 +37,8 @@ enum SoundDeviceError { SOUNDDEVICE_ERROR_EXCESSIVE_INPUT_CHANNEL, }; +const QString kNetworkDeviceInternalName = "Network stream"; + class SoundDevice { public: SoundDevice(ConfigObject *config, SoundManager* sm); @@ -51,7 +53,6 @@ class SoundDevice { inline const QString& getHostAPI() const { return m_hostAPI; } - void setHostAPI(QString api); void setSampleRate(double sampleRate); void setFramesPerBuffer(unsigned int framesPerBuffer); virtual Result open(bool isClkRefDevice, int syncBuffers) = 0; diff --git a/src/sounddevicenetwork.cpp b/src/sounddevicenetwork.cpp new file mode 100644 index 00000000000..19130eb36c7 --- /dev/null +++ b/src/sounddevicenetwork.cpp @@ -0,0 +1,305 @@ + +#include + +#include "sounddevicenetwork.h" + +#include "soundmanager.h" +#include "sounddevice.h" +#include "soundmanagerutil.h" +#include "controlobject.h" +#include "visualplayposition.h" +#include "util/timer.h" +#include "util/trace.h" +#include "vinylcontrol/defs_vinylcontrol.h" +#include "sampleutil.h" +#include "controlobjectslave.h" +#include "util/performancetimer.h" +#include "util/denormalsarezero.h" +#include "engine/sidechain/enginenetworkstream.h" + +// Buffer for drift correction 1 full, 1 for r/w, 1 empty +static const int kDriftReserve = 1; +// Buffer for drift correction 1 full, 1 for r/w, 1 empty +static const int kFifoSize = 2 * kDriftReserve + 1; + +// static +volatile int SoundDeviceNetwork::m_underflowHappend = 0; + +SoundDeviceNetwork::SoundDeviceNetwork(ConfigObject *config, + SoundManager *sm, + unsigned int devIndex) + : SoundDevice(config, sm), + m_pNetworkStream(NULL), + m_outputFifo(NULL), + m_inputFifo(NULL), + m_outputDrift(false), + m_inputDrift(false), + m_bSetThreadPriority(false), + m_underflowUpdateCount(0), + m_nsInAudioCb(0), + m_framesSinceAudioLatencyUsageUpdate(0) { + (void)devIndex; + + // Setting parent class members: + m_hostAPI = "Network stream"; + m_dSampleRate = 44100.0; + m_strInternalName = kNetworkDeviceInternalName; + m_strDisplayName = QObject::tr("Network stream"); + m_iNumInputChannels = 0; + m_iNumOutputChannels = 2; + + m_pMasterAudioLatencyOverloadCount = new ControlObjectSlave("[Master]", + "audio_latency_overload_count"); + m_pMasterAudioLatencyUsage = new ControlObjectSlave("[Master]", + "audio_latency_usage"); + m_pMasterAudioLatencyOverload = new ControlObjectSlave("[Master]", + "audio_latency_overload"); +} + +SoundDeviceNetwork::~SoundDeviceNetwork() { + delete m_pMasterAudioLatencyOverloadCount; + delete m_pMasterAudioLatencyUsage; + delete m_pMasterAudioLatencyOverload; +} + +Result SoundDeviceNetwork::open(bool isClkRefDevice, int syncBuffers) { + Q_UNUSED(syncBuffers); + qDebug() << "SoundDeviceNetwork::open()" << getInternalName(); + + // Sample rate + if (m_dSampleRate <= 0) { + m_dSampleRate = 44100.0; + } + + // 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. + + if (m_iNumOutputChannels) { + m_outputFifo = new FIFO( + m_iNumOutputChannels * m_framesPerBuffer * 2); + } + if (m_iNumInputChannels) { + m_inputFifo = new FIFO( + m_iNumInputChannels * m_framesPerBuffer * 2); + } + } + + m_pNetworkStream = new EngineNetworkStream(m_dSampleRate, + m_iNumOutputChannels, m_iNumInputChannels); + + m_pNetworkStream->startStream(); + + return OK; +} + +Result SoundDeviceNetwork::close() { + //qDebug() << "SoundDeviceNetwork::close()" << getInternalName(); + if (m_pNetworkStream) { + m_pNetworkStream->stopStream(); + delete m_pNetworkStream; + m_pNetworkStream = NULL; + } + if (m_outputFifo) { + delete m_outputFifo; + m_outputFifo = NULL; + } + if (m_inputFifo) { + delete m_inputFifo; + m_inputFifo = NULL; + } + if (m_pNetworkStream) { + delete m_pNetworkStream; + } + m_bSetThreadPriority = false; + return OK; +} + +QString SoundDeviceNetwork::getError() const { + return m_lastError; +} + +void SoundDeviceNetwork::readProcess() { + if (!m_inputFifo || !m_pNetworkStream) return; + + int inChunkSize = m_framesPerBuffer * m_iNumInputChannels; + int readAvailable = m_pNetworkStream->getReadExpected() + * m_iNumInputChannels; + int writeAvailable = m_inputFifo->writeAvailable(); + int copyCount = qMin(writeAvailable, readAvailable); + if (copyCount > 0) { + CSAMPLE* dataPtr1; + ring_buffer_size_t size1; + CSAMPLE* dataPtr2; + ring_buffer_size_t size2; + (void)m_inputFifo->aquireWriteRegions(copyCount, + &dataPtr1, &size1, &dataPtr2, &size2); + // Fetch fresh samples and write to the the input buffer + m_pNetworkStream->read(dataPtr1, + size1 / m_iNumInputChannels); + CSAMPLE* lastFrame = &dataPtr1[size1 - m_iNumInputChannels]; + if (size2 > 0) { + m_pNetworkStream->read(dataPtr2, + size2 / m_iNumInputChannels); + lastFrame = &dataPtr2[size2 - m_iNumInputChannels]; + } + m_inputFifo->releaseWriteRegions(copyCount); + + if (readAvailable > writeAvailable + inChunkSize / 2) { + // we are not able to consume all frames + if (m_inputDrift) { + // Skip one frame + //qDebug() << "SoundDevicePortAudio::readProcess() skip one frame" + // << (float)writeAvailable / inChunkSize << (float)readAvailable / inChunkSize; + m_pNetworkStream->read(dataPtr1, 1); + } else { + m_inputDrift = true; + } + } else if (readAvailable < inChunkSize / 2) { + // We should read at least inChunkSize + if (m_inputDrift) { + // duplicate one frame + //qDebug() << "SoundDevicePortAudio::readProcess() duplicate one frame" + // << (float)writeAvailable / inChunkSize << (float)readAvailable / inChunkSize; + (void) m_inputFifo->aquireWriteRegions( + m_iNumInputChannels, &dataPtr1, &size1, + &dataPtr2, &size2); + if (size1) { + SampleUtil::copy(dataPtr1, lastFrame, size1); + m_inputFifo->releaseWriteRegions(size1); + } + } else { + m_inputDrift = true; + } + } else { + m_inputDrift = false; + } + } + + readAvailable = m_inputFifo->readAvailable(); + int readCount = inChunkSize; + if (inChunkSize > readAvailable) { + readCount = readAvailable; + m_underflowHappend = 1; + //qDebug() << "readProcess()" << (float)readAvailable / inChunkSize << "underflow"; + } + if (readCount) { + CSAMPLE* dataPtr1; + ring_buffer_size_t size1; + CSAMPLE* dataPtr2; + ring_buffer_size_t size2; + // We use size1 and size2, so we can ignore the return value + (void) m_inputFifo->aquireReadRegions(readCount, &dataPtr1, &size1, + &dataPtr2, &size2); + // Fetch fresh samples and write to the the output buffer + composeInputBuffer(dataPtr1, + size1 / m_iNumInputChannels, 0, + m_iNumInputChannels); + if (size2 > 0) { + composeInputBuffer(dataPtr2, + size2 / m_iNumInputChannels, + size1 / m_iNumInputChannels, + m_iNumInputChannels); + } + m_inputFifo->releaseReadRegions(readCount); + } + if (readCount < inChunkSize) { + // Fill remaining buffers with zeros + clearInputBuffer(inChunkSize - readCount, readCount); + } + + m_pSoundManager->pushInputBuffers(m_audioInputs, m_framesPerBuffer); +} + +void SoundDeviceNetwork::writeProcess() { + if (!m_outputFifo || !m_pNetworkStream) return; + + int outChunkSize = m_framesPerBuffer * m_iNumOutputChannels; + int writeAvailable = m_outputFifo->writeAvailable(); + int writeCount = outChunkSize; + if (outChunkSize > writeAvailable) { + writeCount = writeAvailable; + m_underflowHappend = 1; + //qDebug() << "writeProcess():" << (float) writeAvailable / outChunkSize << "Overflow"; + } + //qDebug() << "writeProcess():" << (float) writeAvailable / outChunkSize; + if (writeCount) { + CSAMPLE* dataPtr1; + ring_buffer_size_t size1; + CSAMPLE* dataPtr2; + ring_buffer_size_t size2; + // We use size1 and size2, so we can ignore the return value + (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)); + if (size2 > 0) { + composeOutputBuffer(dataPtr2, + size2 / m_iNumOutputChannels, + size1 / m_iNumOutputChannels, + static_cast(m_iNumOutputChannels)); + } + m_outputFifo->releaseWriteRegions(writeCount); + } + writeAvailable = m_pNetworkStream->getWriteExpected() + * m_iNumOutputChannels; + int readAvailable = m_outputFifo->readAvailable(); + int copyCount = qMin(readAvailable, writeAvailable); + //qDebug() << "SoundDevicePortAudio::writeProcess()" << toRead << writeAvailable; + if (copyCount > 0) { + CSAMPLE* dataPtr1; + ring_buffer_size_t size1; + CSAMPLE* dataPtr2; + ring_buffer_size_t size2; + m_outputFifo->aquireReadRegions(copyCount, + &dataPtr1, &size1, &dataPtr2, &size2); + if (writeAvailable >= outChunkSize * 2) { + // Underflow + //qDebug() << "SoundDeviceNetwork::writeProcess() Buffer empty"; + // catch up by filling buffer until we are synced + m_pNetworkStream->writeSilence(writeAvailable - copyCount); + m_underflowHappend = 1; + } else if (writeAvailable > readAvailable + outChunkSize / 2) { + // try to keep PAs buffer filled up to 0.5 chunks + if (m_outputDrift) { + // duplicate one frame + //qDebug() << "SoundDeviceNetwork::writeProcess() duplicate one frame" + // << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize; + m_pNetworkStream->write(dataPtr1, 1); + } else { + m_outputDrift = true; + } + } else if (writeAvailable < outChunkSize / 2) { + // We are not able to store all new frames + if (m_outputDrift) { + //qDebug() << "SoundDeviceNetwork::writeProcess() skip one frame" + // << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize; + ++copyCount; + } else { + m_outputDrift = true; + } + } else { + m_outputDrift = false; + } + + m_pNetworkStream->write(dataPtr1, + size1 / m_iNumOutputChannels); + if (size2 > 0) { + m_pNetworkStream->write(dataPtr2, + size2 / m_iNumOutputChannels); + } + m_outputFifo->releaseReadRegions(copyCount); + } +} diff --git a/src/sounddevicenetwork.h b/src/sounddevicenetwork.h new file mode 100644 index 00000000000..ff723d97362 --- /dev/null +++ b/src/sounddevicenetwork.h @@ -0,0 +1,52 @@ +#ifndef SOUNDDEVICENETWORK_H +#define SOUNDDEVICENETWORK_H + +#include + +#include "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 ControlObjectSlave; +class EngineNetworkStream; + +class SoundDeviceNetwork : public SoundDevice { + public: + SoundDeviceNetwork(ConfigObject *config, + SoundManager *sm, + unsigned int devIndex); + virtual ~SoundDeviceNetwork(); + + virtual Result open(bool isClkRefDevice, int syncBuffers); + virtual Result close(); + virtual void readProcess(); + virtual void writeProcess(); + virtual QString getError() const; + + virtual unsigned int getDefaultSampleRate() const { + return 44100; + } + + private: + EngineNetworkStream* m_pNetworkStream; + FIFO* m_outputFifo; + FIFO* m_inputFifo; + bool m_outputDrift; + bool m_inputDrift; + + // A string describing the last PortAudio error to occur. + QString m_lastError; + // Whether we have set the thread priority to realtime or not. + bool m_bSetThreadPriority; + ControlObjectSlave* m_pMasterAudioLatencyOverloadCount; + ControlObjectSlave* m_pMasterAudioLatencyUsage; + ControlObjectSlave* m_pMasterAudioLatencyOverload; + int m_underflowUpdateCount; + static volatile int m_underflowHappend; + qint64 m_nsInAudioCb; + int m_framesSinceAudioLatencyUsageUpdate; +}; + +#endif // SOUNDDEVICENETWORK_H diff --git a/src/sounddeviceportaudio.cpp b/src/sounddeviceportaudio.cpp index 7e6c3e0375c..b327c13fd74 100644 --- a/src/sounddeviceportaudio.cpp +++ b/src/sounddeviceportaudio.cpp @@ -39,14 +39,18 @@ #include "util/performancetimer.h" #include "util/denormalsarezero.h" -static const int kDriftReserve = 1; // Buffer for drift correction 1 full, 1 for r/w, 1 empty -static const int kFifoSize = 2 * kDriftReserve + 1; // Buffer for drift correction 1 full, 1 for r/w, 1 empty +// Buffer for drift correction 1 full, 1 for r/w, 1 empty +static const int kDriftReserve = 1; +// Buffer for drift correction 1 full, 1 for r/w, 1 empty +static const int kFifoSize = 2 * kDriftReserve + 1; // static volatile int SoundDevicePortAudio::m_underflowHappend = 0; -SoundDevicePortAudio::SoundDevicePortAudio(ConfigObject *config, SoundManager *sm, - const PaDeviceInfo *deviceInfo, unsigned int devIndex) +SoundDevicePortAudio::SoundDevicePortAudio(ConfigObject *config, + SoundManager *sm, + const PaDeviceInfo *deviceInfo, + unsigned int devIndex) : SoundDevice(config, sm), m_pStream(NULL), m_devId(devIndex), @@ -63,14 +67,18 @@ SoundDevicePortAudio::SoundDevicePortAudio(ConfigObject *config, So // Setting parent class members: m_hostAPI = Pa_GetHostApiInfo(deviceInfo->hostApi)->name; m_dSampleRate = deviceInfo->defaultSampleRate; - m_strInternalName = QString("%1, %2").arg(QString::number(m_devId), deviceInfo->name); + m_strInternalName = QString("%1, %2").arg(QString::number(m_devId), + deviceInfo->name); m_strDisplayName = QString::fromLocal8Bit(deviceInfo->name); m_iNumInputChannels = m_deviceInfo->maxInputChannels; m_iNumOutputChannels = m_deviceInfo->maxOutputChannels; - m_pMasterAudioLatencyOverloadCount = new ControlObjectSlave("[Master]", "audio_latency_overload_count"); - m_pMasterAudioLatencyUsage = new ControlObjectSlave("[Master]", "audio_latency_usage"); - m_pMasterAudioLatencyOverload = new ControlObjectSlave("[Master]", "audio_latency_overload"); + m_pMasterAudioLatencyOverloadCount = new ControlObjectSlave("[Master]", + "audio_latency_overload_count"); + m_pMasterAudioLatencyUsage = new ControlObjectSlave("[Master]", + "audio_latency_usage"); + m_pMasterAudioLatencyOverload = new ControlObjectSlave("[Master]", + "audio_latency_overload"); m_inputParams.device = 0; m_inputParams.channelCount = 0; @@ -96,8 +104,9 @@ Result SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers) { PaError err; if (m_audioOutputs.empty() && m_audioInputs.empty()) { - m_lastError = QString::fromAscii("No inputs or outputs in SDPA::open() " - "(THIS IS A BUG, this should be filtered by SM::setupDevices)"); + m_lastError = QString::fromAscii( + "No inputs or outputs in SDPA::open() " + "(THIS IS A BUG, this should be filtered by SM::setupDevices)"); return ERR; } @@ -168,10 +177,12 @@ Result SoundDevicePortAudio::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"; + qDebug() << "Requested sample rate: " << m_dSampleRate << "Hz, latency:" + << bufferMSec << "ms"; - qDebug() << "Output channels:" << m_outputParams.channelCount << "| Input channels:" - << m_inputParams.channelCount; + qDebug() << "Output channels:" << m_outputParams.channelCount + << "| Input channels:" + << m_inputParams.channelCount; // PortAudio's JACK backend also only properly supports // paFramesPerBufferUnspecified in non-blocking mode because the latency @@ -196,29 +207,38 @@ Result SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers) { m_syncBuffers = syncBuffers; - //Create the callback function pointer. + // Create the callback function pointer. PaStreamCallback* callback = NULL; if (isClkRefDevice) { callback = paV19CallbackClkRef; - } else if (m_syncBuffers == 2) { + } else if (m_syncBuffers == 2) { // "Default (long delay)" callback = paV19CallbackDrift; // to avoid overflows when one callback overtakes the other or // when there is a clock drift compared to the clock reference device // we need an additional artificial delay if (m_outputParams.channelCount) { // On chunk for reading one for writing and on for drift correction - m_outputFifo = new FIFO(m_outputParams.channelCount * m_framesPerBuffer * kFifoSize); - // Clear first 1.5 chunks on for the required artificial delaly to a allow jitter - // and a half, because we can't predict which callback fires first. - m_outputFifo->releaseWriteRegions(m_outputParams.channelCount * m_framesPerBuffer * kFifoSize / 2); + m_outputFifo = new FIFO( + m_outputParams.channelCount * m_framesPerBuffer + * kFifoSize); + // Clear first 1.5 chunks on for the required artificial delaly to + // a allow jitter and a half, because we can't predict which + // callback fires first. + m_outputFifo->releaseWriteRegions( + m_outputParams.channelCount * m_framesPerBuffer * kFifoSize + / 2); } if (m_inputParams.channelCount) { - m_inputFifo = new FIFO(m_inputParams.channelCount * m_framesPerBuffer * kFifoSize); + m_inputFifo = new FIFO( + m_inputParams.channelCount * m_framesPerBuffer * kFifoSize); // Clear first two 1.5 chunks (see above) - m_inputFifo->releaseWriteRegions(m_inputParams.channelCount * m_framesPerBuffer * kFifoSize / 2); + m_inputFifo->releaseWriteRegions( + m_inputParams.channelCount * m_framesPerBuffer * kFifoSize + / 2); } - } else if (m_syncBuffers == 1) { - // this can be used on a second device when it id driven by the Clock reference device clock + } else if (m_syncBuffers == 1) { // "Disabled (short delay)" + // this can be used on a second device when it is driven by the Clock + // reference device clock callback = paV19Callback; if (m_outputParams.channelCount) { m_outputFifo = new FIFO( @@ -228,12 +248,14 @@ Result SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers) { m_inputFifo = new FIFO( m_inputParams.channelCount * m_framesPerBuffer); } - } else if (m_syncBuffers == 0) { + } else if (m_syncBuffers == 0) { // "Experimental (no delay)" if (m_outputParams.channelCount) { - m_outputFifo = new FIFO(m_outputParams.channelCount * m_framesPerBuffer * 2); + m_outputFifo = new FIFO( + m_outputParams.channelCount * m_framesPerBuffer * 2); } if (m_inputParams.channelCount) { - m_inputFifo = new FIFO(m_inputParams.channelCount * m_framesPerBuffer * 2); + m_inputFifo = new FIFO( + m_inputParams.channelCount * m_framesPerBuffer * 2); } } @@ -266,7 +288,8 @@ Result SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers) { else qDebug() << "Dynamically loaded PortAudio library"; - EnableAlsaRT enableRealtime = (EnableAlsaRT) portaudio.resolve("PaAlsa_EnableRealtimeScheduling"); + EnableAlsaRT enableRealtime = (EnableAlsaRT) portaudio.resolve( + "PaAlsa_EnableRealtimeScheduling"); if (enableRealtime) { enableRealtime(pStream, 1); } @@ -280,7 +303,8 @@ Result SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers) { m_lastError = QString::fromUtf8(Pa_GetErrorText(err)); err = Pa_CloseStream(pStream); if (err != paNoError) { - qWarning() << "PortAudio: Close stream error:" << Pa_GetErrorText(err) << getInternalName(); + qWarning() << "PortAudio: Close stream error:" + << Pa_GetErrorText(err) << getInternalName(); } return ERR; } else { @@ -291,7 +315,8 @@ Result SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers) { const PaStreamInfo* streamDetails = Pa_GetStreamInfo(pStream); m_dSampleRate = streamDetails->sampleRate; double currentLatencyMSec = streamDetails->outputLatency * 1000; - qDebug() << " Actual sample rate: " << m_dSampleRate << "Hz, latency:" << currentLatencyMSec << "ms"; + qDebug() << " Actual sample rate: " << m_dSampleRate << "Hz, latency:" + << currentLatencyMSec << "ms"; if (isClkRefDevice) { // Update the samplerate and latency ControlObjects, which allow the @@ -338,14 +363,16 @@ Result SoundDevicePortAudio::close() { //state. Don't use it! if (err != paNoError) { - qWarning() << "PortAudio: Stop stream error:" << Pa_GetErrorText(err) << getInternalName(); + qWarning() << "PortAudio: Stop stream error:" + << Pa_GetErrorText(err) << getInternalName(); return ERR; } // Close stream err = Pa_CloseStream(pStream); if (err != paNoError) { - qWarning() << "PortAudio: Close stream error:" << Pa_GetErrorText(err) << getInternalName(); + qWarning() << "PortAudio: Close stream error:" + << Pa_GetErrorText(err) << getInternalName(); return ERR; } @@ -372,7 +399,7 @@ void SoundDevicePortAudio::readProcess() { PaStream* pStream = m_pStream; if (pStream && m_inputParams.channelCount && m_inputFifo) { int inChunkSize = m_framesPerBuffer * m_inputParams.channelCount; - if (m_syncBuffers == 0) { + if (m_syncBuffers == 0) { // "Experimental (no delay)" // Polling mode signed int readAvailable = Pa_GetStreamReadAvailable(pStream) * m_inputParams.channelCount; int writeAvailable = m_inputFifo->writeAvailable(); @@ -385,14 +412,16 @@ void SoundDevicePortAudio::readProcess() { (void)m_inputFifo->aquireWriteRegions(copyCount, &dataPtr1, &size1, &dataPtr2, &size2); // Fetch fresh samples and write to the the input buffer - PaError err = Pa_ReadStream(pStream, dataPtr1, size1 / m_inputParams.channelCount); + PaError err = Pa_ReadStream(pStream, dataPtr1, + size1 / m_inputParams.channelCount); CSAMPLE* lastFrame = &dataPtr1[size1 - m_inputParams.channelCount]; if (err == paInputOverflowed) { //qDebug() << "SoundDevicePortAudio::readProcess() Pa_ReadStream paInputOverflowed" << getInternalName(); m_underflowHappend = 1; } if (size2 > 0) { - PaError err = Pa_ReadStream(pStream, dataPtr2, size2 / m_inputParams.channelCount); + PaError err = Pa_ReadStream(pStream, dataPtr2, + size2 / m_inputParams.channelCount); lastFrame = &dataPtr2[size2 - m_inputParams.channelCount]; if (err == paInputOverflowed) { //qDebug() << "SoundDevicePortAudio::readProcess() Pa_ReadStream paInputOverflowed" << getInternalName(); @@ -423,8 +452,9 @@ void SoundDevicePortAudio::readProcess() { // duplicate one frame //qDebug() << "SoundDevicePortAudio::readProcess() duplicate one frame" // << (float)writeAvailable / inChunkSize << (float)readAvailable / inChunkSize; - (void)m_inputFifo->aquireWriteRegions(m_inputParams.channelCount, - &dataPtr1, &size1, &dataPtr2, &size2); + (void) m_inputFifo->aquireWriteRegions( + m_inputParams.channelCount, &dataPtr1, &size1, + &dataPtr2, &size2); if (size1) { SampleUtil::copy(dataPtr1, lastFrame, size1); m_inputFifo->releaseWriteRegions(size1); @@ -451,14 +481,16 @@ void SoundDevicePortAudio::readProcess() { CSAMPLE* dataPtr2; ring_buffer_size_t size2; // We use size1 and size2, so we can ignore the return value - (void)m_inputFifo->aquireReadRegions(readCount, &dataPtr1, &size1, &dataPtr2, &size2); + (void) m_inputFifo->aquireReadRegions(readCount, &dataPtr1, &size1, + &dataPtr2, &size2); // Fetch fresh samples and write to the the output buffer composeInputBuffer(dataPtr1, size1 / m_inputParams.channelCount, 0, m_inputParams.channelCount); if (size2 > 0) { composeInputBuffer(dataPtr2, - size2 / m_inputParams.channelCount, size1 / m_inputParams.channelCount, + size2 / m_inputParams.channelCount, + size1 / m_inputParams.channelCount, m_inputParams.channelCount); } m_inputFifo->releaseReadRegions(readCount); @@ -490,20 +522,24 @@ void SoundDevicePortAudio::writeProcess() { CSAMPLE* dataPtr2; ring_buffer_size_t size2; // We use size1 and size2, so we can ignore the return value - (void)m_outputFifo->aquireWriteRegions(writeCount, &dataPtr1, &size1, &dataPtr2, &size2); + (void) m_outputFifo->aquireWriteRegions(writeCount, &dataPtr1, + &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)); if (size2 > 0) { - composeOutputBuffer(dataPtr2, size2 / m_outputParams.channelCount, size1 / m_outputParams.channelCount, + composeOutputBuffer(dataPtr2, + size2 / m_outputParams.channelCount, + size1 / m_outputParams.channelCount, static_cast(m_outputParams.channelCount)); } m_outputFifo->releaseWriteRegions(writeCount); } - if (m_syncBuffers == 0) { + if (m_syncBuffers == 0) { // "Experimental (no delay)" // Polling - signed int writeAvailable = Pa_GetStreamWriteAvailable(pStream) * m_outputParams.channelCount; + signed int writeAvailable = Pa_GetStreamWriteAvailable(pStream) + * m_outputParams.channelCount; int readAvailable = m_outputFifo->readAvailable(); int copyCount = qMin(readAvailable, writeAvailable); //qDebug() << "SoundDevicePortAudio::writeProcess()" << toRead << writeAvailable; @@ -518,7 +554,8 @@ void SoundDevicePortAudio::writeProcess() { // Underflow //qDebug() << "SoundDevicePortAudio::writeProcess() Buffer empty"; // fill buffer duplicate one sample - for (int i = 0; i < writeAvailable - copyCount; i += m_outputParams.channelCount) { + for (int i = 0; i < writeAvailable - copyCount; i += + m_outputParams.channelCount) { Pa_WriteStream(pStream, dataPtr1, 1); } m_underflowHappend = 1; @@ -549,13 +586,15 @@ void SoundDevicePortAudio::writeProcess() { } else { m_outputDrift = false; } - PaError err = Pa_WriteStream(pStream, dataPtr1, size1 / m_outputParams.channelCount); + PaError err = Pa_WriteStream(pStream, dataPtr1, + size1 / m_outputParams.channelCount); if (err == paOutputUnderflowed) { //qDebug() << "SoundDevicePortAudio::writeProcess() Pa_ReadStream paOutputUnderflowed" << getInternalName(); m_underflowHappend = 1; } if (size2 > 0) { - PaError err = Pa_WriteStream(pStream, dataPtr2, size2 / m_outputParams.channelCount); + PaError err = Pa_WriteStream(pStream, dataPtr2, + size2 / m_outputParams.channelCount); if (err == paOutputUnderflowed) { //qDebug() << "SoundDevicePortAudio::writeProcess() Pa_WriteStream paOutputUnderflowed" << getInternalName(); m_underflowHappend = 1; @@ -567,12 +606,13 @@ void SoundDevicePortAudio::writeProcess() { } } -int SoundDevicePortAudio::callbackProcessDrift(const unsigned int framesPerBuffer, - CSAMPLE *out, const CSAMPLE *in, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags) { +int SoundDevicePortAudio::callbackProcessDrift( + const unsigned int framesPerBuffer, CSAMPLE *out, const CSAMPLE *in, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags) { Q_UNUSED(timeInfo); - Trace trace("SoundDevicePortAudio::callbackProcessDrift %1", getInternalName()); + Trace trace("SoundDevicePortAudio::callbackProcessDrift %1", + getInternalName()); if (statusFlags & (paOutputUnderflow | paInputOverflow)) { m_underflowHappend = 1; @@ -613,7 +653,9 @@ int SoundDevicePortAudio::callbackProcessDrift(const unsigned int framesPerBuffe // Do not compensate the first delay, because it is likely a jitter // corrected in the next cycle // Duplicate one frame - m_inputFifo->write(&in[inChunkSize - m_inputParams.channelCount], m_inputParams.channelCount); + m_inputFifo->write( + &in[inChunkSize - m_inputParams.channelCount], + m_inputParams.channelCount); //qDebug() << "callbackProcessDrift write:" << (float)readAvailable / inChunkSize << "Skip"; } else { m_inputDrift = true; @@ -667,9 +709,11 @@ int SoundDevicePortAudio::callbackProcessDrift(const unsigned int framesPerBuffe } else if (readAvailable >= outChunkSize) { if (m_outputDrift) { // Risk of underflow, duplicate one frame - m_outputFifo->read(out, outChunkSize - m_outputParams.channelCount); - SampleUtil::copy(&out[outChunkSize - m_outputParams.channelCount], - &out[outChunkSize - (2 * m_outputParams.channelCount)], + m_outputFifo->read(out, + outChunkSize - m_outputParams.channelCount); + SampleUtil::copy( + &out[outChunkSize - m_outputParams.channelCount], + &out[outChunkSize - (2 * m_outputParams.channelCount)], m_outputParams.channelCount); //qDebug() << "callbackProcessDrift read:" << (float)readAvailable / outChunkSize << "Save"; } else { @@ -696,9 +740,9 @@ int SoundDevicePortAudio::callbackProcessDrift(const unsigned int framesPerBuffe } int SoundDevicePortAudio::callbackProcess(const unsigned int framesPerBuffer, - CSAMPLE *out, const CSAMPLE *in, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags) { + CSAMPLE *out, const CSAMPLE *in, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags) { Q_UNUSED(timeInfo); Trace trace("SoundDevicePortAudio::callbackProcess %1", getInternalName()); @@ -748,14 +792,15 @@ int SoundDevicePortAudio::callbackProcess(const unsigned int framesPerBuffer, return paContinue; } -int SoundDevicePortAudio::callbackProcessClkRef(const unsigned int framesPerBuffer, - CSAMPLE *out, const CSAMPLE *in, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags) { +int SoundDevicePortAudio::callbackProcessClkRef( + const unsigned int framesPerBuffer, CSAMPLE *out, const CSAMPLE *in, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags) { PerformanceTimer timer; timer.start(); - Trace trace("SoundDevicePortAudio::callbackProcessClkRef %1", getInternalName()); + Trace trace("SoundDevicePortAudio::callbackProcessClkRef %1", + getInternalName()); //qDebug() << "SoundDevicePortAudio::callbackProcess:" << getInternalName(); // Turn on TimeCritical priority for the callback thread. If we are running @@ -795,7 +840,8 @@ int SoundDevicePortAudio::callbackProcessClkRef(const unsigned int framesPerBuff m_pMasterAudioLatencyOverload->set(1.0); m_pMasterAudioLatencyOverloadCount->set( m_pMasterAudioLatencyOverloadCount->get() + 1); - m_underflowUpdateCount = CPU_OVERLOAD_DURATION * m_dSampleRate / framesPerBuffer / 1000; + m_underflowUpdateCount = CPU_OVERLOAD_DURATION * m_dSampleRate + / framesPerBuffer / 1000; m_underflowHappend = 0; // reseting her is not thread save, // but that is OK, because we count only // 1 underflow each 500 ms @@ -807,12 +853,15 @@ int SoundDevicePortAudio::callbackProcessClkRef(const unsigned int framesPerBuff } m_framesSinceAudioLatencyUsageUpdate += framesPerBuffer; - if (m_framesSinceAudioLatencyUsageUpdate > (m_dSampleRate / CPU_USAGE_UPDATE_RATE)) { - double secInAudioCb = (double)m_nsInAudioCb / 1000000000.0; - m_pMasterAudioLatencyUsage->set(secInAudioCb / (m_framesSinceAudioLatencyUsageUpdate / m_dSampleRate)); + if (m_framesSinceAudioLatencyUsageUpdate + > (m_dSampleRate / CPU_USAGE_UPDATE_RATE)) { + double secInAudioCb = (double) m_nsInAudioCb / 1000000000.0; + m_pMasterAudioLatencyUsage->set(secInAudioCb / + (m_framesSinceAudioLatencyUsageUpdate / m_dSampleRate)); m_nsInAudioCb = 0; m_framesSinceAudioLatencyUsageUpdate = 0; - //qDebug() << m_pMasterAudioLatencyUsage << m_pMasterAudioLatencyUsage->get(); + //qDebug() << m_pMasterAudioLatencyUsage + // << m_pMasterAudioLatencyUsage->get(); } //Note: Input is processed first so that any ControlObject changes made in @@ -821,7 +870,8 @@ int SoundDevicePortAudio::callbackProcessClkRef(const unsigned int framesPerBuff // Send audio from the soundcard's input off to the SoundManager... if (in) { - ScopedTimer t("SoundDevicePortAudio::callbackProcess input %1", getInternalName()); + ScopedTimer t("SoundDevicePortAudio::callbackProcess input %1", + getInternalName()); composeInputBuffer(in, framesPerBuffer, 0, m_inputParams.channelCount); m_pSoundManager->pushInputBuffers(m_audioInputs, m_framesPerBuffer); @@ -830,15 +880,19 @@ int SoundDevicePortAudio::callbackProcessClkRef(const unsigned int framesPerBuff m_pSoundManager->readProcess(); { - ScopedTimer t("SoundDevicePortAudio::callbackProcess prepare %1", getInternalName()); + ScopedTimer t("SoundDevicePortAudio::callbackProcess prepare %1", + getInternalName()); m_pSoundManager->onDeviceOutputCallback(framesPerBuffer); } if (out) { - ScopedTimer t("SoundDevicePortAudio::callbackProcess output %1", getInternalName()); + ScopedTimer t("SoundDevicePortAudio::callbackProcess output %1", + getInternalName()); if (m_outputParams.channelCount <= 0) { - qWarning() << "SoundDevicePortAudio::callbackProcess m_outputParams channel count is zero or less:" << m_outputParams.channelCount; + qWarning() + << "SoundDevicePortAudio::callbackProcess m_outputParams channel count is zero or less:" + << m_outputParams.channelCount; // Bail out. return paContinue; } @@ -858,24 +912,27 @@ int paV19Callback(const void *inputBuffer, void *outputBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *soundDevice) { - return ((SoundDevicePortAudio*)soundDevice)->callbackProcess((unsigned int)framesPerBuffer, - (CSAMPLE*)outputBuffer, (const CSAMPLE*)inputBuffer, timeInfo, statusFlags); + return ((SoundDevicePortAudio*) soundDevice)->callbackProcess( + (unsigned int) framesPerBuffer, (CSAMPLE*) outputBuffer, + (const CSAMPLE*) inputBuffer, timeInfo, statusFlags); } int paV19CallbackDrift(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *soundDevice) { - return ((SoundDevicePortAudio*)soundDevice)->callbackProcessDrift((unsigned int)framesPerBuffer, - (CSAMPLE*)outputBuffer, (const CSAMPLE*)inputBuffer, timeInfo, statusFlags); + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *soundDevice) { + return ((SoundDevicePortAudio*) soundDevice)->callbackProcessDrift( + (unsigned int) framesPerBuffer, (CSAMPLE*) outputBuffer, + (const CSAMPLE*) inputBuffer, timeInfo, statusFlags); } int paV19CallbackClkRef(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *soundDevice) { - return ((SoundDevicePortAudio*)soundDevice)->callbackProcessClkRef((unsigned int)framesPerBuffer, - (CSAMPLE*)outputBuffer, (const CSAMPLE*)inputBuffer, timeInfo, statusFlags); + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *soundDevice) { + return ((SoundDevicePortAudio*) soundDevice)->callbackProcessClkRef( + (unsigned int) framesPerBuffer, (CSAMPLE*) outputBuffer, + (const CSAMPLE*) inputBuffer, timeInfo, statusFlags); } diff --git a/src/soundmanager.cpp b/src/soundmanager.cpp index 0ec9d40eacb..87e96c25bf1 100644 --- a/src/soundmanager.cpp +++ b/src/soundmanager.cpp @@ -25,6 +25,7 @@ #include "soundmanager.h" #include "sounddevice.h" #include "sounddeviceportaudio.h" +#include "sounddevicenetwork.h" #include "engine/enginemaster.h" #include "engine/enginebuffer.h" #include "soundmanagerutil.h" @@ -219,6 +220,14 @@ void SoundManager::queryDevices() { //qDebug() << "SoundManager::queryDevices()"; clearDeviceList(); + queryDevicesPortaudio(); + queryDevicesMixxx(); + + // now tell the prefs that we updated the device list -- bkgood + emit(devicesUpdated()); +} + +void SoundManager::queryDevicesPortaudio() { #ifdef __PORTAUDIO__ PaError err = paNoError; if (!m_paInitialized) { @@ -243,8 +252,9 @@ void SoundManager::queryDevices() { const PaDeviceInfo* deviceInfo; for (int i = 0; i < iNumDevices; i++) { deviceInfo = Pa_GetDeviceInfo(i); - if (!deviceInfo) + if (!deviceInfo) { continue; + } /* deviceInfo fields for quick reference: int structVersion const char * name @@ -257,7 +267,8 @@ void SoundManager::queryDevices() { PaTime defaultHighOutputLatency double defaultSampleRate */ - SoundDevicePortAudio *currentDevice = new SoundDevicePortAudio(m_pConfig, this, deviceInfo, i); + SoundDevicePortAudio* currentDevice = new SoundDevicePortAudio( + m_pConfig, this, deviceInfo, i); m_devices.push_back(currentDevice); if (!strcmp(Pa_GetHostApiInfo(deviceInfo->hostApi)->name, MIXXX_PORTAUDIO_JACK_STRING)) { @@ -265,8 +276,12 @@ void SoundManager::queryDevices() { } } #endif - // now tell the prefs that we updated the device list -- bkgood - emit(devicesUpdated()); +} + +void SoundManager::queryDevicesMixxx() { + SoundDeviceNetwork* currentDevice = new SoundDeviceNetwork( + m_pConfig, this, 0); + m_devices.push_back(currentDevice); } Result SoundManager::setupDevices() { @@ -322,7 +337,8 @@ Result SoundManager::setupDevices() { device->clearInputs(); device->clearOutputs(); m_pErrorDevice = device; - foreach (AudioInput in, m_config.getInputs().values(device->getInternalName())) { + foreach (AudioInput in, + m_config.getInputs().values(device->getInternalName())) { isInput = true; // TODO(bkgood) look into allocating this with the frames per // buffer value from SMConfig @@ -343,7 +359,16 @@ Result SoundManager::setupDevices() { it.value()->onInputConfigured(in); } } - foreach (AudioOutput out, m_config.getOutputs().values(device->getInternalName())) { + QList outputs = + m_config.getOutputs().values(device->getInternalName()); + + // Statically connect the Network Device to the Sidechain + if (device->getInternalName() == kNetworkDeviceInternalName) { + AudioOutput out(AudioPath::SIDECHAIN, 0, 2, 0); + outputs.append(out); + } + + foreach (AudioOutput out, outputs) { isOutput = true; // following keeps us from asking for a channel buffer EngineMaster // doesn't have -- bkgood @@ -372,6 +397,7 @@ Result SoundManager::setupDevices() { it.value()->onOutputConnected(out); } } + if (isInput || isOutput) { device->setSampleRate(m_config.getSampleRate()); device->setFramesPerBuffer(m_config.getFramesPerBuffer()); @@ -422,7 +448,8 @@ Result SoundManager::setupDevices() { qDebug() << inputDevicesOpened << "input sound devices opened"; m_pControlObjectSoundStatusCO->set( - outputDevicesOpened > 0 ? SOUNDMANAGER_CONNECTED : SOUNDMANAGER_DISCONNECTED); + outputDevicesOpened > 0 ? + SOUNDMANAGER_CONNECTED : SOUNDMANAGER_DISCONNECTED); // returns OK if we were able to open all the devices the user wanted if (devicesAttempted == devicesOpened) { diff --git a/src/soundmanager.h b/src/soundmanager.h index 42dbc1d8859..cfde8bfc231 100644 --- a/src/soundmanager.h +++ b/src/soundmanager.h @@ -66,8 +66,10 @@ class SoundManager : public QObject { // Closes all the devices and empties the list of devices we have. void clearDeviceList(); - // Creates a list of sound devices that PortAudio sees. + // Creates a list of sound devices void queryDevices(); + void queryDevicesPortaudio(); + void queryDevicesMixxx(); // Opens all the devices chosen by the user in the preferences dialog, and // establishes the proper connections between them and the mixing engine. diff --git a/src/soundmanagerutil.cpp b/src/soundmanagerutil.cpp index 7457d420a7c..d830a6a1137 100644 --- a/src/soundmanagerutil.cpp +++ b/src/soundmanagerutil.cpp @@ -166,6 +166,8 @@ QString AudioPath::getStringFromType(AudioPathType type) { return QString::fromAscii("Microphone"); case AUXILIARY: return QString::fromAscii("Auxiliary"); + case SIDECHAIN: + return QString::fromAscii("Sidechain"); } return QString::fromAscii("Unknown path type %1").arg(type); } @@ -207,6 +209,8 @@ QString AudioPath::getTrStringFromType(AudioPathType type, unsigned char index) case AUXILIARY: return QString("%1 %2").arg(QObject::tr("Auxiliary"), QString::number(index + 1)); + case SIDECHAIN: + return QObject::tr("Sidechain"); } return QObject::tr("Unknown path type %1").arg(type); } @@ -231,6 +235,8 @@ AudioPathType AudioPath::getTypeFromString(QString string) { return AudioPath::MICROPHONE; } else if (string == AudioPath::getStringFromType(AudioPath::AUXILIARY).toLower()) { return AudioPath::AUXILIARY; + } else if (string == AudioPath::getStringFromType(AudioPath::SIDECHAIN).toLower()) { + return AudioPath::SIDECHAIN; } else { return AudioPath::INVALID; } @@ -346,9 +352,15 @@ QList AudioOutput::getSupportedTypes() { types.append(HEADPHONES); types.append(BUS); types.append(DECK); + types.append(SIDECHAIN); return types; } +bool AudioOutput::isHidden() { + return m_type == SIDECHAIN; +} + + /** * Implements setting the type of an AudioOutput, using * AudioOutput::getSupportedTypes. diff --git a/src/soundmanagerutil.h b/src/soundmanagerutil.h index e52b2a48165..233ac888a55 100644 --- a/src/soundmanagerutil.h +++ b/src/soundmanagerutil.h @@ -62,6 +62,7 @@ class AudioPath { VINYLCONTROL, MICROPHONE, AUXILIARY, + SIDECHAIN, INVALID, // if this isn't last bad things will happen -bkgood }; AudioPath(unsigned char channelBase, unsigned char channels); @@ -108,6 +109,7 @@ class AudioOutput : public AudioPath { QDomElement toXML(QDomElement *element) const; static AudioOutput fromXML(const QDomElement &xml); static QList getSupportedTypes(); + bool isHidden(); protected: void setType(AudioPathType type); }; diff --git a/src/util/fifo.h b/src/util/fifo.h index bb07f0491b3..43a6b6468c4 100644 --- a/src/util/fifo.h +++ b/src/util/fifo.h @@ -67,6 +67,11 @@ class FIFO { int releaseReadRegions(int count) { return PaUtil_AdvanceRingBufferReadIndex(&m_ringBuffer, count); } + int flushReadData(int count) { + int flush = math_min(readAvailable(), count); + return PaUtil_AdvanceRingBufferReadIndex(&m_ringBuffer, flush); + } + private: DataType* m_data; PaUtilRingBuffer m_ringBuffer;