-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
audio: don't block on lock in ao_read_data #12643
Conversation
Is it possible for the thread to be starved of an opportunity to lock? Seems like the potential issues are more severe than the theoretical fix. |
Being totally starved and having a negative impact on realtime threads are two separate questions IMO.
In paused mode it also return
Also fine by me. If it comes up at some point we would have this PR on file. |
73432fe
to
754deef
Compare
My assumption is - and maybe I'm wrong - that if you lock some fairness algorithm/scheduling will eventually give you the lock while trylock has no such guarantee. |
I don't think The lock shouldn't really be contended for pull AOs. If it is conteded, because a control function is blocking somewhere, the AO data thread, which actually drives the playback loop in pull AOs, would be stuck to wait for this control function to give up the lock. |
Hmm yeah, you're right. |
754deef
to
e7b4ca5
Compare
ao_read_data() is used by pull AOs potentially from threads managed by external libraries. These threads can be sensitive to blocking. For example the pipewire ao is using a realtime thread for the callbacks.
e7b4ca5
to
f2301d5
Compare
Small update to fix |
Commit ae908a7 introduced audio crackling on macOS. Test file: The Core Audio output handling code, which doesn't have any return value being 0 handling: Lines 68 to 83 in ae908a7
|
That's unfortunate. It seems coreaudio doesn't even have a way to signal partial data. |
A function parameter that indicates whether to block sounds easiest. |
Looks like only
It is good to not block here, but we need to remember that other than |
How about filling the buffer with silence when the lock can't be acquired and 0 is returned? Unfortunately I only can test this on Linux. |
|
Right, I also have not look in depth. I believe for this |
So, like this: diff --git a/audio/out/buffer.c b/audio/out/buffer.c
index e23e4cf108..96636ff675 100644
--- a/audio/out/buffer.c
+++ b/audio/out/buffer.c
@@ -184,8 +184,13 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns)
struct buffer_state *p = ao->buffer_state;
assert(!ao->driver->write);
- if (pthread_mutex_trylock(&p->lock))
+ if (pthread_mutex_trylock(&p->lock)) {
+ // failed to acquire the lock, fill with silence per the contract
+ for (int n = 0; n < ao->num_planes; n++) {
+ af_fill_silence((char *)data[n], samples * ao->sstride, ao->format);
+ }
return 0;
+ }
int pos = read_buffer(ao, data, samples, &(bool){0});
Unfortunately, it still results in more or less the same effect. I mean, it still appears to be the same length to the audio driver, regardless of the actual contents. This doesn't help either: diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index d96b597f6e..1c6f72ffd4 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -78,7 +78,11 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
int64_t end = mp_time_ns();
end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
- ao_read_data(ao, planes, frames, end);
+ int samples = ao_read_data(ao, planes, frames, end);
+ if (samples == 0) {
+ *aflags |= kAudioUnitRenderAction_OutputIsSilence;
+ }
+
return noErr;
}
|
I do want to mention though that this hack fixed the issue for me. Been using mpv for two days and haven't noticed any crackling (except rarely during the first second of playback, but it sounds more like underrun which is expected). I don't understand why or how it works, but I'm posting it anyway in case someone might find it useful. Would have been much easier if Core Audio was open-source (lol) so that I could just read their render callback code and check whether their scheduling changes depending on the value of diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index d96b597f6e..63315a881a 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -78,7 +78,18 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
int64_t end = mp_time_ns();
end += p->hw_latency_ns + ca_get_latency(ts) + ca_frames_to_ns(ao, frames);
- ao_read_data(ao, planes, frames, end);
+ int samples = ao_read_data(ao, planes, frames, end);
+
+ if (samples < frames) {
+ for (int i = 0; i < buffer_list->mNumberBuffers; i++) {
+ // HACK: I don't think mDataByteSize is supposed to be overwritten.
+ buffer_list->mBuffers[i].mDataByteSize = samples * ao->sstride;
+ }
+ if (samples == 0) {
+ *aflags |= kAudioUnitRenderAction_OutputIsSilence;
+ }
+ }
+
return noErr;
}
|
I guess at first we should revert the problematic commit. Unfortunately I don't have access to my dev setup right now. The "hack" by @handlerug is similar to what pipewire is doing. The docs mention this:
If there would be no way for the callback to signal the actual amount of data read, it would be a really crappy API IMO. The callback would need to block forever until all the requested data is present or risk desync. |
Yeah, I was reading that paragraph as well. Now that it’s a new day and I’m a bit refreshed, I guess it makes sense that you can modify the size field, though “memory layout” usually makes me think of stride and stuff and not length. |
I've pushed a revert. What we need to figure out is whether all AOs have the ability to output less data than requested. |
ao_read_data() is used by pull AOs potentially from threads managed by external libraries. These threads can be sensitive to blocking. For example the pipewire ao is using a realtime thread for the callbacks.
Note:
I don't have a specific problem this fixes, but it looked like it could be one in theory.