-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JACK buffer size fix. #11121
JACK buffer size fix. #11121
Conversation
`m_deviceInfo->hostApi` is an index in the list of supported APIs on this platform. So on Linux 0 is ALSA, 2 is JACK. This is also mentioned in the comment on the `hostApi` field. We should be checking the device type ID instead, as is done elsewhere in the code. This fixes the buffer size when using JACK clients. With JACK the buffer size should be coming from the server (and PortAudio chops it up into 1024 sample blocks if it's larger). Before this change it would use the now grayed out latency value set for the previous audio backend before switching to JACK (defaulting to 1024 samples).
In any operation that works with audio buffers passed from PortAudio. This is needed because PortAudio may pass us buffers that are smaller or larger than the size we request. And when using JACK the requested size is 0, which will cause PortAudio to pick a buffer size matching the JACK server.
The network backend already does this.
to clarify that thisis not the actually used value. The actual value is passied via every audi callback.
This clarifies that the value is a subject of change in case of JACK
This allows following the server buffer size that is not always const.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple preliminary comments
src/soundio/sounddevicenetwork.cpp
Outdated
m_audioBufferTime = mixxx::Duration::fromSeconds( | ||
const auto requestedBufferTime = mixxx::Duration::fromSeconds( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate the use of const auto
here 👍
m_targetTime += static_cast<qint64>(framesPerBuffer / m_dSampleRate * 1000000); | ||
double callbackEntrytoDacSecs = (m_targetTime - currentTime) / 1000000.0; | ||
callbackEntrytoDacSecs = math_max(callbackEntrytoDacSecs, 0.0001); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't we have an abstraction for sample and frame related time calculations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not aware of something for this special case.
@@ -45,45 +42,47 @@ void VisualPlayPosition::set(double playPos, double rate, double positionStep, | |||
data.m_positionStep = positionStep; | |||
data.m_slipPosition = slipPosition; | |||
data.m_tempoTrackSeconds = tempoTrackSeconds; | |||
data.m_audioBufferMicroS = audioBufferMicroS; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does the waveform need to know about the buffer size??
src/soundio/sounddeviceportaudio.cpp
Outdated
// 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 | ||
// which offers the best responds time without additional jitter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue here is extra latency, not extra jitter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The jitter is introduced if Portaudio composes new buffers from the native JACK buffer. In this case we have two callbacks in a row and than the pause. I will improve the comment.
// For every sound hardware. Clock drift can not happen, but the buffers | ||
// are processed: In 1 - Out 1 - In 2 - Out 2 | ||
// This causes even with the "Experimental (no delay)" setting | ||
// one extra buffer delay for the non clock reference device |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no way to avoid this extra latency when using PortAudio with multiple JACK clients? If so, I think that's a good justification to not use PortAudio for JACK. (That's beyond the scope of this bugfix, of course.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no way. And yes that is only one reason why Mixxx should become a native JACK client.
Done. |
My general worry with this PR is that we're disconnecting the buffer size from the actual buffer, this dramatically increases the chance for programmer error resulting in out-of-bounds accesses. |
Isn't it the other way around? This PR (and #11092 leading up to it) causes the actual buffer size to be used for computations working on the audio buffer, instead of the configured buffer size (which may or may not match the actual buffer size). |
I guess it depends on what buffer we're talking about. I'm not familiar enough with the surrounding code. It just worries me in general to see the size disconnected from the buffer (or rather vice versa). |
That is the essence of this PR, using the buffer size that is provided along with the pointer to the buffer. In the current main branch this is not the case, we rely on Portaudio that we receive the requested buffer size, which is luckily reliable the case. Also, we don't use iBufferSize to access the buffets allocated with MAX_BUFFER_LEN in this PR. We use them to pass sample across the two streams in case of two soundcards. mixxx/src/soundio/sounddeviceportaudio.cpp Line 589 in 44fa755
The FIFOs have there own save read and write size values. In the unlikely case the FIFO buffer is to small, the buffer from Portaudio is padded with zeros. |
I'm sorry, I can't comment on this, I don't understand how your explanation relates to the code you linked. |
That is the code where the FIFO of MAX_BUFFER_LEN used. You can see that it does not relate to iBufferSize. |
Ready |
@robbert-vdh can you confirm that this is working for you and do a brief review of my code? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daschuer I haven't tested it too extensively, but the JACK seems to still function the same as #11092. And while the waveforms from multiple tracks still aren't entirely in sync, they at least seem to move around less than they did before.
I'm not too familiar with these parts of Mixxx but it looks good to me. I grepped for m_iBufferSize
to find more remaining uses and I stumbled upon src/analyzer/analyzergain.h
. That class has a m_iBufferSize
field that's initialized to 0 and then used in a comparison that always returns true if the audio buffer being analyzed isn't empty. That's probably a remnant of some older behavior and can also be inlined.
src/soundio/soundmanager.h
Outdated
@@ -126,6 +125,9 @@ class SoundManager : public QObject { | |||
void closeDevices(bool sleepAfterClosing); | |||
|
|||
void setJACKName() const; | |||
bool jackApiUsed() const { | |||
return m_jackSampleRate.isValid(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? m_jackSampleRate
seems to be set in SoundManager::queryDevicesPortaudio()
when the JACK API is available. So it sounds like it will also be set when Mixxx uses ALSA, WasAPI, CoreAudio, etc. while a JACK server is available. I have not yet checked if this is actually the case here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ups, good catch. Fixed.
@robbert-vdh Is there anything left to do or can you formally approve this PR? |
It's been a hot minute since I looked at it but it looked good to me! I tested this before and it seemed to work fine. I've been using the original waveform change from #11092 in my own branch since rebasing this PR seemed a bit more involved so I have not tested it super extensively beyond that. Did you take a look at the removing that unused |
Yes, the fix has already been merged via eb0339f |
ControlObject::set(ConfigKey("[Master]", "samplerate"), m_dSampleRate); | ||
ControlObject::set(ConfigKey("[Master]", "audio_buffer_size"), | ||
m_audioBufferTime.toDoubleMillis()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's highly unlikely that this CO is used somewhere in mappings or skins - and therefore can be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to mention that it also needs to be removed from the Mixxx Controls section in the manual, but that CO does not seem to be documented anyway. 🤷
m_clkRefTimer.start(); | ||
qint64 currentTime = m_pNetworkStream->getInputStreamTimeUs(); | ||
m_targetTime += m_audioBufferTime.toIntegerMicros(); | ||
// This deadline for the next buffer in microseconds since the Unix epoch | ||
m_targetTime += static_cast<qint64>(framesPerBuffer / m_dSampleRate * 1000000); | ||
double callbackEntrytoDacSecs = (m_targetTime - currentTime) / 1000000.0; | ||
callbackEntrytoDacSecs = math_max(callbackEntrytoDacSecs, 0.0001); | ||
VisualPlayPosition::setCallbackEntryToDacSecs(callbackEntrytoDacSecs, m_clkRefTimer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that for rate syncing with the frequency of the network clock framesPerBuffer
is enough. But to show the visual waveform, not only rate, but also latency is needed.
I don't understand this calculation. Shouldn't the waveform be in sync with the sound that the DJ hears (headphones or booth speakers) even if he's broadcasting a network stream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case the latency of the main DAC is lost. It would be an extra feature to compensate this.
This calculation is only to compensate the jitter that happens due to the different cycles of the audio and video thread.
At least this is unrelated to this PR. Do we want to file a bug for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please create a bug for this. It would be great, if you could add some pointers to this report, how a possible fix could look like.
I agree, that this is not related to this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it is: #11219
@Swiftb0y I consider this a 2.3.4 fix. Is it now good to merge? |
I'm sorry, I can't approve this PR
|
I read through the code and it looks clean to me. If nobody has objections, I plan to merge this PR therefore. |
Thank you @robbert-vdh and @daschuer ! |
This PR disposes all stored buffer size values and uses consequently the value passed as callback parameter.
This is required because the JACK buffer size is externally controlled and can change at any time.
Current main uses the crazed out buffer size form hardware preferences.
Because if that the timing compensation with two soundcards or the network clock fails and is now grayed out in the hardware preferences.
Note: Multi soundcard setup is working, but because if the joint stream in JACK and the split-up in Portaudio. It adds an extra buffer latency for the non main device. It is also known that the clock synchronisation in native JACK is broken and the situation is not clear in case in pipewire.
So if you want a minimum latency setup, use ALSA directly.
This PR is on top of #11092
Many thanks to @robbert-vdh for finding the original issues and for the initial PR