Multi client support
Permit more than one client to open each PCM. For playback clients, the streams from each client are mixed together before encoding. For capture clients each client receives a copy of the decoded stream.
Stream mixing is done in C code, there is no explicit SIMD code. However, compiling with -O3 does appear to allow gcc to optimize the mix loop very well on x86_64. I have not tested optimization with aarch64 (my old RPi machines are all 32-bit with no SIMD); but even without optimization I can run 3 playback clients simultaneously on a RPi zero W.
There was some interest in this feature three years ago, but there have been only a few enquiries more recently. I think this is pushing the scope boundary a little, as for most systems requiring multiple simultaneous audio applications there are also other requirements which mean that pipewire is probably a better fit. However I do have one genuine use case which is a "kiosk" system running only chromium which requires each chromium tab to independently open the PCM; and for this system BlueALSA with multi-client support is ideal.
Codecov Report
:x: Patch coverage is 63.55599% with 371 lines in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 68.76%. Comparing base (2c09e0b) to head (5670ba3).
Additional details and impacted files
@@ Coverage Diff @@
## master #762 +/- ##
==========================================
- Coverage 69.28% 68.76% -0.52%
==========================================
Files 105 108 +3
Lines 17167 18154 +987
Branches 2739 2907 +168
==========================================
+ Hits 11894 12484 +590
- Misses 5156 5547 +391
- Partials 117 123 +6
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
I've tried to cherry-pick the Ensure encoder buffers are flushed when draining commit to master (the code with some cleanups is here: https://github.com/arkq/bluez-alsa/tree/drain), but when testing I've spotted that in case of mpg123 and cmus (and maybe some others) the final effect is not satisfactory. The problem is that these players, when stoping, trigger drop -> resume -> drain -> drop actions. And because of that my headset plays a click when silence is received. That happens with SBC and AAC codecs (I have not tested other codes yet, but I guess that the effect will be the same). Have you ever spotted some "clicks" when stopping playback (when using this drain flushing code)? Later today I will check with some other sinks. The problem does not occur with aplay, because it does not drop PCM before drain. I've also tested with mpv, but it does not use drain at all (at least when playing music), so mpv is not affected.
Have you ever spotted some "clicks" when stopping playback
Possibly - I think mpg123 did this - I will investigate today.
drop -> resume -> drain -> drop
hmm, that is very odd. I guess at ALSA API level that is
snd_pcm_drop();
snd_pcm_prepare();
snd_pcm_drain();
I will write a small test program to try that sequence, which should make it easier to debug the bluealsad code.
I've just tried with mpg123 v1.32.5 from the ubuntu repository and did not get any clicks at end of playback. I used your "drain" branch, and also this PR branch both with --multi-client and without. For me the sequence of client commands at end of playback was Drain -> Drain -> Drop -> Drop so perhaps the ubuntu version or build of mpg123 is different from yours? I will try to write a test program later.
I'm testing on Debian 12:
$ mpg123 --version
mpg123 1.31.2
I've checked with upstream master version of mpg123:
$ src/mpg123 --version
mpg123 1.33.0-dev
and the outcome is the same - click at the end.
Here is the calltrace from the termination:
| safe_exit
| | dump_close
| | | play_prebuffer
| | out123_drop
| | audio_cleanup
| | out123_del
| | | out123_close
| | | | out123_drain
| | | | | out123_pause
| | | | out123_stop
| | | out123_set_buffer
| | | | out123_close
| | | | | out123_drain
| | | | | out123_stop
| | mpg123_delete
| | | mpg123_close
| | mpg123_exit
| | stream_close
| | split_dir_file
| | term_exit
| | | term_have_fun
| | | term_restore
So, at least based on function names, it seems that mpg123 calls drop then it closes the output (which calls drain, then stop).
I think that a workaround would to to track whether we have some samples after the drop, and if there are no new samples, drain is not required. I'll try to add that to the IO code and will think about it whether it will not cause some undesired effects.
I've added the logic which checks whether inserting silence is required or not. It seems that it works OK, but some synthetic test for that would be nice.