Closed nyanpasu64 closed 3 years ago
pa_context_get_server_info
calling rt_pa_mainloop_api_quit(0)
has been in the codebase for a long time. The call to pa_context_get_sink_info_list
was added later on in c0d33839f522710e5a616a0b5e43d42d95096c7f, but the author neglected to move the call to rt_pa_mainloop_api_quit(0)
to the last function called (rt_pa_source_info_cb
with eol=1
). I'm preparing a PR to do that.
Version, Distribution, Desktop Environment:
Description of Problem:
RtAudio apps running under
pipewire-pulse
see zero output devices and fail to start. The same error occurs randomly on PulseAudio, more commonly when the pulseaudio daemon doesn't have enough CPU time to run quickly.Steps to Reproduce:
EDIT:
pulseaudio-open-crash
branch.playsaw
example (which I modified to repeatedly open and close PulseAudio streams).Original instructions:
1. Install pipewire\[-git\] and pipewire-pulse\[-git\] on a system, and uninstall pulseaudio if present. Start the pipewire daemon. 2. Clone BambooTracker git master from https://github.com/BambooTracker/BambooTracker, and build and run it. (On Arch, you can install aur/bambootracker-git.) - There are prebuilt binaries at https://github.com/BambooTracker/BambooTracker/releases/download/v0.4.6/BambooTracker-v0.4.6-linux.zip, but it's 124 megabytes and is some strange nix-os shell script thing. 3. In File -\> Configuration -\> Sound, set the API to PulseAudio and click Apply.Actual Results:
On pipewire-pulse, you'll see
No audio devices found!
sinceRtApiPulse::getDeviceCount( void )
returns 0.The same bug can appear with regular PulseAudio, but with a much lower probability. On PulseAudio, you'll instead see an endless stream of
RtAudio pulse: running realtime scheduling
, eventually terminated by "RtApi::openStream: output device parameter value is invalid." I only observed this error on slow CPUs, or when I'm stress-testing my fast CPU cores so the PulseAudio server is starved of CPU time.This error arises because RtAudio tries to open device 0 from a list of 0 devices. There are 0 devices because RtAudio calls
pa_context_get_sink_info_list
to fetch a list of output devices, but the callback gets called 0 times. (pa_context_get_source_info_list
's callback also gets called 0 times.)pa_context_set_state_callback(context, rt_pa_context_state_callback, NULL)
.rt_pa_context_state_callback
withPA_CONTEXT_CONNECTING
(ignored).pa_mainloop_run
.rt_pa_context_state_callback
withPA_CONTEXT_AUTHORIZING
(ignored) andPA_CONTEXT_SETTING_NAME
(ignored), followed byPA_CONTEXT_READY
.rt_pa_context_state_callback
callspa_context_get_server_info(context, rt_pa_server_callback, NULL)
, followed bypa_context_get_sink_info_list(context, rt_pa_sink_info_cb, NULL)
, before returning.pa_mainloop_run
callsrt_pa_server_callback
, which callsrt_pa_mainloop_api_quit(0)
, which callsrt_pa_mainloop_api->quit(rt_pa_mainloop_api, ret)
.pa_mainloop_api::quit
is symbolmainloop_quit
, which callspa_mainloop_quit
, which sets them->quit
flag and returns.pa_mainloop_run
returns immediately (becausedispatch_pollfds
sees them->quit
flag and breaks) and doesn't callrt_pa_sink_info_cb
.pa_mainloop_run
continues and callsrt_pa_sink_info_cb
a few times before returning... most of the time. Sometimes it doesn't, and you see "RtApi::openStream: output device parameter value is invalid." Why is that?Stack trace of
rt_pa_server_callback
(which callspa_mainloop_quit
) on PulseAudio:When running under real PulseAudio,
do_pstream_read_write
loops (becausep->srb
is non-null) and callsdo_read
(which indirectly callsrt_pa_sink_info_cb
) before returning, sodispatch_pollfds
has no chance to notice thatm->quit
is set.Running on pipewire-pulse, the
srb_callback
andsrbchannel_rwloop
stack frames are missing (I think because pipewire-pulse doesn't support srb yet), anddo_pstream_read_write
only callsdo_read
once (becausep->srb
is null). Oncedo_pstream_read_write
returns,dispatch_pollfds
noticesm->quit
is set and returns. What is srb?Is it a libpulse issue that
pa_srbchannel
connections respond to packets before quitting (allowing code written like RtAudio to work most of the time on srb-enabled PulseAudio connections but always fail on non-srb-enabled connections)? Do they need to be made consistent (most likely by making srb-enableddo_pstream_read_write
check form->quit
on every message and consistently terminate immediately, consistently breaking RtAudio apps)?I think RtAudio is wrong for calling
pa_mainloop_api::quit
. I believe this introduces a race condition where if the PulseAudio server is unable to send the packets beforedo_pstream_read_write
callsdo_read
and checks for packets, thendo_pstream_read_write
returns anddispatch_pollfds
exits the loop becausem->quit
is set. As a result,rt_pa_sink_info_cb
never runs and RtAudio fails to scan for devices, instead failing on "RtApi::openStream: output device parameter value is invalid."I didn't prove causality (eg. by editing libpulse's source code so
do_pstream_read_write
prints how many times it loops), but I have indirect evidence:rt_pa_sink_info_cb
doesn't get calledpa_context_get_server_info()
but before replying topa_context_get_sink_info_list()
)Sadly I'm not sure how to rewrite
rt_pa_context_state_callback
to fix this bug, so it terminates the event loop after all the callbacks are done running. I'd have to look into it later.Symptom: PulseAudio device does not support output
While testing RtAudio apps, I also saw an "PulseAudio device does not support output." error when starting the program. Unlike the main bug report, calling
RtApiPulse::getDeviceCount()
again and trying to restart the stream only produced more of the same error, unlike the main bug report where eachRtApiPulse::getDeviceCount()
would randomly misbehave or work.However, recreating the RtAudio object seems to fix it. I'm not sure if it has the same root cause.EDIT: I set a breakpoint for "PulseAudio device does not support output." and you can't reroll the dice by destroying and recreating RtAudio objects, only by setting
oParams.deviceId = dac.getDefaultOutputDevice()
anew each iteration (which can trigger the bug whether or not you destroy and recreate the RtAudio object). When setting a breakpoint at this error message being printed, I saw that thedevice
passed intoRtApiPulse::probeDeviceOpen
(equal tooParams->deviceId
) was 0 instead of 1.I think this is another symptom of the same bug, where
RtApi :: getDefaultOutputDevice( void )
callsRtApiPulse::getDeviceCount( void )
and it erroneously returns 0 or so, and sogetDefaultOutputDevice
returns 0 as well, which is an input-only device on my machine.