Skip to content

Commit 0838841

Browse files
committed
Split the CoreAudio sink's liveness test from its feed test
A lost device does not stop the render callback -- the HAL keeps pulling until the unit does. So clear() asking unit_alive_() took the ring_.drop() branch while the callback was inside read(), writing both positions from the producer side and leaving the next read a whole ring of stale audio to play. clear() now asks callback_running_(), which is liveness alone. configure() and write() keep unit_alive_(), which is the question they actually have.
1 parent 18b2986 commit 0838841

2 files changed

Lines changed: 16 additions & 4 deletions

File tree

src/coreaudio_sink.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,8 +638,9 @@ void CoreAudioSink::clear() {
638638

639639
// Do not snap current_multiplier_: the callback keeps running through a flush.
640640

641-
if (this->unit_alive_()) {
642-
// The consumer owns read_pos_, so it drains on its next read.
641+
if (this->callback_running_()) {
642+
// The consumer owns read_pos_, so it drains on its next read. Liveness, not unit_alive_():
643+
// a lost device keeps being pulled, and drop() from this side would race that read.
643644
this->ring_.request_clear();
644645
return;
645646
}
@@ -1009,8 +1010,12 @@ void CoreAudioSink::discard_ring_tail_() {
10091010
static_cast<uint32_t>(this->ring_.available() / this->bytes_per_frame_));
10101011
}
10111012

1013+
bool CoreAudioSink::callback_running_() const {
1014+
return this->unit_ != nullptr && this->running_;
1015+
}
1016+
10121017
bool CoreAudioSink::unit_alive_() const {
1013-
return this->unit_ != nullptr && this->running_ && !this->device_lost_.load();
1018+
return this->callback_running_() && !this->device_lost_.load();
10141019
}
10151020

10161021
size_t CoreAudioSink::ring_capacity_(double device_latency_s) const {

src/coreaudio_sink.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,21 @@ class CoreAudioSink final : public AudioSink {
9393
/// lost unit recovery is about to close will never report them. Only for those closes --
9494
/// stop(), configure() and clear() end the stream the gap belonged to. Caller holds mutex_.
9595
void discard_ring_tail_();
96-
/// True while the open unit is still being driven by CoreAudio. Caller holds mutex_.
96+
/// True while the render callback is still being driven. Caller holds mutex_.
97+
/// Liveness only: a lost device does not stop the callback, so this stays true until the
98+
/// unit does. Anything touching the ring's consumer side must ask this, not unit_alive_().
99+
bool callback_running_() const;
100+
/// True while the open unit is still worth feeding: running, on a device that has not died.
101+
/// Caller holds mutex_.
97102
bool unit_alive_() const;
98103
/// Ring size in bytes. Caller holds mutex_ and the format fields are set.
99104
size_t ring_capacity_(double device_latency_s) const;
100105
void update_target_multiplier_();
101106

102107
/// Starts listening for the open device's death, and for default-output moves when following
103108
/// the default. Caller holds mutex_; listeners are removed by close_unit_().
109+
/// Removal does not wait an in-flight listener out, unlike PortAudio's stream close, so a
110+
/// notification can still land on the atomics just after the sink is destroyed.
104111
void add_listeners_(AudioDeviceID device);
105112
void remove_listeners_();
106113

0 commit comments

Comments
 (0)