Skip to content

Commit

Permalink
Merge pull request #11366 from daschuer/gh11341
Browse files Browse the repository at this point in the history
Audio Buffer Jack: Auto/2048/4096
  • Loading branch information
JoergAtGithub authored Mar 18, 2023
2 parents 12ddc1b + e2b4be8 commit 9080c7e
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 70 deletions.
83 changes: 51 additions & 32 deletions src/preferences/dialog/dlgprefsound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ DlgPrefSound::DlgPrefSound(QWidget* pParent,
&DlgPrefSound::apiChanged);

sampleRateComboBox->clear();
foreach (unsigned int srate, m_pSoundManager->getSampleRates()) {
for (auto& srate : m_pSoundManager->getSampleRates()) {
if (srate > 0) {
// no ridiculous sample rate values. prohibiting zero means
// avoiding a potential div-by-0 error in ::updateLatencies
Expand All @@ -58,10 +58,6 @@ DlgPrefSound::DlgPrefSound(QWidget* pParent,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
&DlgPrefSound::sampleRateChanged);
connect(sampleRateComboBox,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
&DlgPrefSound::updateAudioBufferSizes);
connect(audioBufferComboBox,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
Expand Down Expand Up @@ -516,22 +512,24 @@ void DlgPrefSound::apiChanged(int index) {
m_config.setAPI(apiComboBox->itemData(index).toString());
refreshDevices();
// JACK sets its own buffer size and sample rate that Mixxx cannot change.
// PortAudio is able to chop/combine the buffer but that will mess up the
// timing in Mixxx. When we request 0 (paFramesPerBufferUnspecified)
// https://github.com/PortAudio/portaudio/blob/v19.7.0/src/common/pa_process.c#L54
// PortAudio passes buffers up to 1024 frames through.
// For bigger buffers the user has to manually match the value with Jack.
// TODO(Be): Get the buffer size from JACK and update audioBufferComboBox.
// PortAudio does not have a way to get the buffer size from JACK as of July 2017.
// PortAudio as off v19.7.0 does not have a way to get the buffer size from JACK.
if (m_config.getAPI() == MIXXX_PORTAUDIO_JACK_STRING) {
sampleRateComboBox->setEnabled(false);
latencyLabel->setEnabled(false);
audioBufferComboBox->setEnabled(false);
deviceSyncComboBox->setEnabled(false);
engineClockComboBox->setEnabled(false);

} else {
sampleRateComboBox->setEnabled(true);
latencyLabel->setEnabled(true);
audioBufferComboBox->setEnabled(true);
deviceSyncComboBox->setEnabled(true);
engineClockComboBox->setEnabled(true);
}
updateAudioBufferSizes(sampleRateComboBox->currentIndex());
}

/**
Expand Down Expand Up @@ -562,6 +560,7 @@ void DlgPrefSound::sampleRateChanged(int index) {
m_config.setSampleRate(
sampleRateComboBox->itemData(index).toUInt());
m_bLatencyChanged = true;
updateAudioBufferSizes(index);
checkLatencyCompensation();
}

Expand Down Expand Up @@ -605,30 +604,50 @@ void DlgPrefSound::engineClockChanged(int index) {
// of 2 (so the values displayed in ms won't be constant between sample rates,
// but they'll be close).
void DlgPrefSound::updateAudioBufferSizes(int sampleRateIndex) {
double sampleRate = sampleRateComboBox->itemData(sampleRateIndex).toDouble();
int oldSizeIndex = audioBufferComboBox->currentIndex();
unsigned int framesPerBuffer = 1; // start this at 0 and inf loop happens
// we don't want to display any sub-1ms buffer sizes (well maybe we do but I
// don't right now!), so we iterate over all the buffer sizes until we
// find the first that gives us a buffer size >= 1 ms -- bkgood
// no div-by-0 in the next line because we don't allow srates of 0 in our
// srate list when we construct it in the ctor -- bkgood
for (; framesPerBuffer / sampleRate * 1000 < 1.0; framesPerBuffer *= 2) {
}
QVariant oldSizeIndex = audioBufferComboBox->currentData();
audioBufferComboBox->clear();
for (unsigned int i = 0; i < SoundManagerConfig::kMaxAudioBufferSizeIndex; ++i) {
const auto latency = static_cast<float>(framesPerBuffer / sampleRate * 1000);
// i + 1 in the next line is a latency index as described in SSConfig
audioBufferComboBox->addItem(tr("%1 ms").arg(latency,0,'g',3), i + 1);
framesPerBuffer <<= 1; // *= 2
}
if (oldSizeIndex < audioBufferComboBox->count() && oldSizeIndex >= 0) {
audioBufferComboBox->setCurrentIndex(oldSizeIndex);
if (m_config.getAPI() == MIXXX_PORTAUDIO_JACK_STRING) {
// in case of jack we configure the frames/period
// we cannot calc the resulting buffer size in ms because the
// Sample rate is not known yet. We assume 48000 KHz here
// to calculate the buffer size index
audioBufferComboBox->addItem(tr("auto (<= 1024 frames/period)"),
static_cast<unsigned int>(SoundManagerConfig::
JackAudioBufferSizeIndex::SizeAuto));
audioBufferComboBox->addItem(tr("2048 frames/period"),
static_cast<unsigned int>(SoundManagerConfig::
JackAudioBufferSizeIndex::Size2048fpp));
audioBufferComboBox->addItem(tr("4096 frames/period"),
static_cast<unsigned int>(SoundManagerConfig::
JackAudioBufferSizeIndex::Size4096fpp));
} else {
// set it to the max, let the user dig if they need better latency. better
// than having a user get the pops on first use and thinking poorly of mixxx
// because of it -- bkgood
audioBufferComboBox->setCurrentIndex(audioBufferComboBox->count() - 1);
double sampleRate = sampleRateComboBox->itemData(sampleRateIndex).toDouble();
unsigned int framesPerBuffer = 1; // start this at 0 and inf loop happens
// we don't want to display any sub-1ms buffer sizes (well maybe we do but I
// don't right now!), so we iterate over all the buffer sizes until we
// find the first that gives us a buffer size >= 1 ms -- bkgood
// no div-by-0 in the next line because we don't allow srates of 0 in our
// srate list when we construct it in the ctor -- bkgood
for (; framesPerBuffer / sampleRate * 1000 < 1.0; framesPerBuffer *= 2) {
}
for (unsigned int i = 0; i < SoundManagerConfig::kMaxAudioBufferSizeIndex; ++i) {
const auto latency = static_cast<float>(framesPerBuffer / sampleRate * 1000);
// i + 1 in the next line is a latency index as described in SSConfig
audioBufferComboBox->addItem(tr("%1 ms").arg(latency, 0, 'g', 3), i + 1);
framesPerBuffer <<= 1; // *= 2
}
}
int selectionIndex = audioBufferComboBox->findData(oldSizeIndex);
if (selectionIndex > -1) {
audioBufferComboBox->setCurrentIndex(selectionIndex);
} else {
// use our default of 5 (23 ms @ 48 kHz)
selectionIndex = audioBufferComboBox->findData(
SoundManagerConfig::kDefaultAudioBufferSizeIndex);
VERIFY_OR_DEBUG_ASSERT(selectionIndex > -1) {
return;
}
audioBufferComboBox->setCurrentIndex(selectionIndex);
}
}

Expand Down
72 changes: 45 additions & 27 deletions src/soundio/sounddevicenetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ const int kNetworkLatencyFrames = 8192; // 185 ms @ 44100 Hz
const mixxx::Logger kLogger("SoundDeviceNetwork");
} // namespace

SoundDeviceNetwork::SoundDeviceNetwork(UserSettingsPointer config,
SoundManager *sm,
QSharedPointer<EngineNetworkStream> pNetworkStream)
SoundDeviceNetwork::SoundDeviceNetwork(
UserSettingsPointer config,
SoundManager* sm,
QSharedPointer<EngineNetworkStream> pNetworkStream)
: SoundDevice(config, sm),
m_pNetworkStream(pNetworkStream),
m_inputDrift(false),
Expand Down Expand Up @@ -64,12 +65,8 @@ SoundDeviceError SoundDeviceNetwork::open(bool isClkRefDevice, int syncBuffers)
}

const SINT framesPerBuffer = m_configFramesPerBuffer;
qDebug() << "framesPerBuffer:" << framesPerBuffer;

const auto requestedBufferTime = mixxx::Duration::fromSeconds(
framesPerBuffer / m_dSampleRate);
qDebug() << "Requested sample rate: " << m_dSampleRate << "Hz, latency:"
<< requestedBufferTime;

// Feed the network device buffer directly from the
// clock reference device callback
Expand All @@ -87,6 +84,9 @@ SoundDeviceError SoundDeviceNetwork::open(bool isClkRefDevice, int syncBuffers)

// Create the callback Thread if requested
if (isClkRefDevice) {
kLogger.debug() << "Clock Reference with:" << framesPerBuffer << "frames/buffer @"
<< m_dSampleRate << "Hz =" << requestedBufferTime.formatMillisWithUnit();

// Update the samplerate and latency ControlObjects, which allow the
// waveform view to properly correct for the latency.
ControlObject::set(ConfigKey("[Master]", "latency"),
Expand All @@ -100,8 +100,10 @@ SoundDeviceError SoundDeviceNetwork::open(bool isClkRefDevice, int syncBuffers)

m_pThread = std::make_unique<SoundDeviceNetworkThread>(this);
m_pThread->start(QThread::TimeCriticalPriority);
} else {
kLogger.debug() << "Maximum:" << framesPerBuffer << "frames/buffer @"
<< m_dSampleRate << "Hz =" << requestedBufferTime.formatMillisWithUnit();
}

return SOUNDDEVICE_ERROR_OK;
}

Expand Down Expand Up @@ -132,6 +134,7 @@ void SoundDeviceNetwork::readProcess(SINT framesPerBuffer) {
if (!m_inputFifo || !m_pNetworkStream || !m_iNumInputChannels) {
return;
}
DEBUG_ASSERT(m_configFramesPerBuffer >= framesPerBuffer);

int inChunkSize = framesPerBuffer * m_iNumInputChannels;
int readAvailable = m_pNetworkStream->getReadExpected()
Expand Down Expand Up @@ -223,9 +226,10 @@ void SoundDeviceNetwork::readProcess(SINT framesPerBuffer) {
}

void SoundDeviceNetwork::writeProcess(SINT framesPerBuffer) {
if (!m_outputFifo || !m_pNetworkStream) {
if (!m_outputFifo || !m_pNetworkStream || !m_iNumOutputChannels) {
return;
}
DEBUG_ASSERT(m_configFramesPerBuffer >= framesPerBuffer);

int outChunkSize = framesPerBuffer * m_iNumOutputChannels;
int writeAvailable = m_outputFifo->writeAvailable();
Expand Down Expand Up @@ -287,37 +291,51 @@ void SoundDeviceNetwork::workerWriteProcess(NetworkOutputStreamWorkerPtr pWorker
int outChunkSize, int readAvailable,
CSAMPLE* dataPtr1, ring_buffer_size_t size1,
CSAMPLE* dataPtr2, ring_buffer_size_t size2) {
int writeExpected = static_cast<int>(pWorker->getStreamTimeFrames() - pWorker->framesWritten());

int writeAvailable = writeExpected * m_iNumOutputChannels;
int copyCount = qMin(readAvailable, writeAvailable);
int writeExpectedFrames = static_cast<int>(
pWorker->getStreamTimeFrames() - pWorker->framesWritten());

int writeExpected = writeExpectedFrames * m_iNumOutputChannels;

if (writeExpected <= 0) {
// Overflow
// kLogger.debug() << "workerWriteProcess: buffer full"
// << "outChunkSize" << outChunkSize
// << "readAvailable" << readAvailable
// << "writeExpected" << writeExpected << pWorker->getStreamTimeFrames();
// catch up by skipping chunk
m_pSoundManager->underflowHappened(25);
}
int copyCount = qMin(readAvailable, writeExpected);

if (copyCount > 0) {
if (writeAvailable - copyCount > outChunkSize) {
if (writeExpected - copyCount > outChunkSize) {
// Underflow
//kLogger.debug() << "workerWriteProcess: buffer empty";
// kLogger.debug() << "workerWriteProcess: buffer empty";
// catch up by filling buffer until we are synced
workerWriteSilence(pWorker, writeAvailable - copyCount);
workerWriteSilence(pWorker, writeExpected - copyCount);
m_pSoundManager->underflowHappened(24);
} else if (writeAvailable - copyCount > outChunkSize / 2) {
} else if (writeExpected - copyCount > outChunkSize / 2) {
// try to keep PAs buffer filled up to 0.5 chunks
if (pWorker->outputDrift()) {
// duplicate one frame
//kLogger.debug() << "workerWriteProcess() duplicate one frame"
// << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize;
// kLogger.debug() << "workerWriteProcess() duplicate one frame"
// << (float)writeExpected / outChunkSize
// << (float)readAvailable / outChunkSize;
workerWrite(pWorker, dataPtr1, 1);
} else {
pWorker->setOutputDrift(true);
}
} 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
} else if (writeExpected < outChunkSize / 2) {
// We will overshoot by more than a half of the new frames
if (pWorker->outputDrift()) {
//kLogger.debug() << "SoundDeviceNetwork::workerWriteProcess() skip one frame"
// << (float)writeAvailable / outChunkSize << (float)readAvailable / outChunkSize;
copyCount = qMin(readAvailable, copyCount + m_iNumOutputChannels);
// kLogger.debug() << "SoundDeviceNetwork::workerWriteProcess() "
// "skip one frame"
// << (float)writeAvailable / outChunkSize
// << (float)readAvailable / outChunkSize;
if (size1 >= m_iNumOutputChannels) {
dataPtr1 += m_iNumOutputChannels;
size1 -= m_iNumOutputChannels;
}
} else {
pWorker->setOutputDrift(true);
}
Expand Down
11 changes: 7 additions & 4 deletions src/soundio/sounddeviceportaudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,17 @@ SoundDeviceError SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffers
}

SINT framesPerBuffer = m_configFramesPerBuffer;
if (m_deviceTypeId == paJACK) {
// PortAudio's JACK back end has its own buffering to split or merge the buffer
// received from JACK to the desired size.
// However, we use here paFramesPerBufferUnspecified to use the JACK buffer size
if (m_deviceTypeId == paJACK && framesPerBuffer <= 1024) {
// Up to a Jack buffer size of 1024 frames/period, PortAudio is able to
// follow the Jack buffer size dynamically.
// We make use of it by requesting paFramesPerBufferUnspecified
// which offers the best response time without additional jitter due to two
// successive callback without the expected pause.
framesPerBuffer = paFramesPerBufferUnspecified;
qDebug() << "Using JACK server's frames per period";
// in case of bigger buffers, the user need to select the same buffers
// size in Mixxx and Jack to avoid buffer underflow/overflow during broadcasting
// This fixes https://github.com/mixxxdj/mixxx/issues/11341
} else {
qDebug() << "framesPerBuffer:" << framesPerBuffer;
}
Expand Down
24 changes: 19 additions & 5 deletions src/soundio/soundmanagerconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,11 @@
#include "util/cmdlineargs.h"
#include "util/math.h"

// this (7) represents latency values from 1 ms to about 80 ms -- bkgood
const unsigned int SoundManagerConfig::kMaxAudioBufferSizeIndex = 7;

const QString SoundManagerConfig::kDefaultAPI = QStringLiteral("None");
const QString SoundManagerConfig::kEmptyComboBox = QStringLiteral("---");
// Sample Rate even the cheap sound Devices will support most likely
const unsigned int SoundManagerConfig::kFallbackSampleRate = 48000;
const unsigned int SoundManagerConfig::kDefaultDeckCount = 2;
// audioBufferSizeIndex=5 means about 21 ms of latency which is default in trunk r2453 -- bkgood
const int SoundManagerConfig::kDefaultAudioBufferSizeIndex = 5;

const int SoundManagerConfig::kDefaultSyncBuffers = 2;

Expand Down Expand Up @@ -393,6 +388,25 @@ unsigned int SoundManagerConfig::getAudioBufferSizeIndex() const {
// This reflects the configured value only. In case of JACK the
// setting of the JACK server is used.
unsigned int SoundManagerConfig::getFramesPerBuffer() const {
if (m_api == MIXXX_PORTAUDIO_JACK_STRING) {
// in case of jack we configure the frames/period
if (m_audioBufferSizeIndex ==
static_cast<unsigned int>(
JackAudioBufferSizeIndex::Size4096fpp)) {
return 4096;
} else if (m_audioBufferSizeIndex ==
static_cast<unsigned int>(
JackAudioBufferSizeIndex::Size2048fpp)) {
return 2048;
}
// default is auto <= 1024
// The Jack buffer size can change at any time, so we
// need buffers for the maximum of 1024 (limited by Portaudio).
return 1024;
}

// With the other APIs we calc the frames per buffer form the sample rate

// endless loop otherwise
unsigned int audioBufferSizeIndex = m_audioBufferSizeIndex;
VERIFY_OR_DEBUG_ASSERT(audioBufferSizeIndex > 0) {
Expand Down
30 changes: 28 additions & 2 deletions src/soundio/soundmanagerconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,38 @@ class SoundManagerConfig {
OTHER = (1 << 2),
ALL = (API | DEVICES | OTHER),
};
static const unsigned int kMaxAudioBufferSizeIndex;

// Size1xms presents the first buffer size of 2^X
// that results in a buffer time above 1 ms
// It is 1.45 ms @ 44.1 kHz
// The other values are representing the following 2^X sizes.
enum class AudioBufferSizeIndex {
Size1xms = 1,
Size2xms = 2,
Size5xms = 3,
Size10xms = 4,
Size20xms = 5,
Size40xms = 6,
Size80xms = 7,
};

// Represents the sample rate independent frame/period
// index values in case of Jack
enum class JackAudioBufferSizeIndex {
SizeAuto = 5,
Size2048fpp = 6,
Size4096fpp = 7,
};

static constexpr auto kMaxAudioBufferSizeIndex =
static_cast<unsigned int>(AudioBufferSizeIndex::Size80xms);
static constexpr auto kDefaultAudioBufferSizeIndex =
static_cast<unsigned int>(AudioBufferSizeIndex::Size20xms);

static const QString kDefaultAPI;
static const QString kEmptyComboBox;
static const unsigned int kFallbackSampleRate;
static const unsigned int kDefaultDeckCount;
static const int kDefaultAudioBufferSizeIndex;
static const int kDefaultSyncBuffers;

SoundManagerConfig();
Expand Down

0 comments on commit 9080c7e

Please sign in to comment.