-
Notifications
You must be signed in to change notification settings - Fork 50
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
subscribe ALSA Ctl and trigger CAPTUREFORMATCHANGE for rate changes. #202
Comments
This should definitely be added yes. But the reason I haven't spent time on it yet is that in practice there aren't very many cases where it's actually useful. On MacOs and windows it's needed to support rate changes of the virtual soundcard drivers used there. But on Linux the Alsa loopback never changes rate while it's opened because of the broken notify functionality. |
No, USB input. The USB Gadget driver that creates the alsa device does support rate changes from kernel 5.18. |
Ah yes of course. I haven't had time to play with the new multi-rate gadget yet. |
Yeah @DeLub and I were early testers of the gadget code and I found this piece is missing from alsa. |
IMO there are several steps which would be required.
IMO the gadget requires the delayed start as in https://github.com/pavhofman/gaudio_ctl#debouncing , to avoid false starts. Its params would require adding to alsa device config (with some reasonable default). For reverse chain (ADC -> aloop/gadget) the functionality would have to be added to the Playback part of alsadevice + corresponding handling of StatusMessage::PlaybackFormatChange/PlaybackRateChange in bin.rs. |
My thought on this:
|
I just tested the loopback notifications. What does not work is breaking the stream on capture side when playback closes the stream. But notifications via alsa ctls do work, the principle is basically identical to the gadget. Ctl "PCM Slave Active" (true/false) reports whether the playback side is playing. Ctls "PCM Slave Rate", "PCM Slave Channels", and "PCM Slave Format" inform about current params of the playback stream, to be used on the capture side.
I.e. monitoring the PCM Slave Active ctl controls the operation, with specific parameters to be subsequently read from the other ctls. For comparison - the USB gadget defines only one ctl for each direction - Playback/Capture Rate which flips to 0 when the USB host stops the operation, no channels/format is reported (they are configured as fixed in the kernel module params). Yeah, I can imagine the alsadevice having a dedicated constantly running thread which monitors these ctls, sending respective StatusMessages to the top-level thread which can start/stop the slaved threads, reload config etc. The existing code can already override the samplerate, channels, format, therefore handling that would be quite simple. But reloading a different config with filter params specific for the given samplerate would be tricky. But a plain loopback with no samplerate-specific DSP, just for the gadget async feedback rate control, would work out of the box. In configurations with resampling to one fixed playback rate for any capture rate no config reload would be needed, if the rate reported by the capture thread changed only the capture_samplerate, not the overall samplerate. It would have to be clearly communicated in the documentation as many users would get confused. |
Those controls on the loopback are very interesting! I think a separate python script is the right place to at least start. If that turns out to work well, and when the most common simple cases are well handled, then I would consider including this functionality directly in camilladsp. I'm a little worried about the use case where an application plays at one sample rate into the loopback, and then switches to another rate. If the app tries to reopen the device immediately after closing, then camilladsp probably hasn't managed to close the capture side of the loopback yet. Here the non-functional breaking of the stream would really help. |
I was thinking of a separate module, something like DeviceMonitor. Capture and playback could have one optionally (Some()). IMO it could be just some refactoring of key parts of my https://github.com/pavhofman/gaudio_ctl. The module could send messages to the main thread through the same channel as the capture/playback threads (crossbeam_channel is multiple-producer capable), simplifying the main thread. Basically the "new status" messages could be sent by the existing capture/playback threads (e.g. the change rate detected by the existing runtime samplerate monitor), as well optionally by the optional device monitor associated with respective source/sink. The main thread may not care whether the message was sent by the actual source/sink thread or by the associated device monitor. I can take a look at the implementation, in the following weeks, if you are OK with this direction.
The serial stream of events on the CDSP side will always be in correct order - the stream stop message will always precede the new-rate message. Having the other side open is not a problem for the gadget, the USB side does not care about status of the gadget alsa device. For the loopback - the slaved side does need to be closed first to allow re-opening the master side with different hw_params. You are right this could be a problem if the master side is very fast. But I believe fixing the loopback should not be a major issue, just nobody has really looked into the problem thoroughly yet, IMO. |
I have started prototyping a little in python: https://github.com/HEnquist/camilladsp-controller/blob/main/pyalsa_class.py I had to switch to the official Alsa python binding The Alsa backend should get a DeviceMonitor yes, to allow it to stop and report properly on rate/format changes. The monitor should send its messages to the capture or playback thread. Then that thread can stop and send a message to the main thread (that is how it's done in the Wasapi and CoreAudio backends). It would simplify things a lot if it can be done without adding more threads. Maybe by opening the hctl in non-blocking mode and letting the playback and capture loops call |
Theoretically alsa should be able to wait for both pcm and ctl in a single poll(fds) call. That would minimize the latency of processing the start/stop notifications which IMO we need in this usecase. I asked about the combined wait at alsa-devel mailing list. |
It looks like polling multiple fds is the recommended method for this case https://mailman.alsa-project.org/pipermail/alsa-devel/2022-June/201479.html https://github.com/alsa-project/alsa-utils/blob/a566f8a0ed6d7ca5fd6ae2d224f3f28654a2f3be/alsaloop/pcmjob.c#L1727 Alsaloop is even polling all playback & capture pcm & ctl file descriptors in one poll, so all the processing runs in a single thread https://github.com/alsa-project/alsa-utils/blob/a566f8a0ed6d7ca5fd6ae2d224f3f28654a2f3be/alsaloop/alsaloop.c#L759 The alsa crate has full support for this method, and diwic even recommends using it (e.g. diwic/alsa-rs#33 (comment) ) The change would not be large, "just" initializing the pcm and hctl file descriptors using pcm.fill/hctl.fill, replacing pcm.wait with the poll, and analyzing the wait results with pcm.revents/hctl.revents. Maybe the non-cached ctl would be faster than the cached hctl for receiving events, it should be easy to compare once the code for existing hctl works, the APIs are very similar. @HEnquist I will take a look at this feature now, should you agree. It would make the CDSP framework easier to use than my gaudio_ctl + CDSP combo, also latencies of reaction to ctl change should be lower. |
Sorry I won't be able to take a look right now. We are moving to a new apartment. I should have more time soon! |
@pavhofman Sorry it took a while! I agree that this seems like a good approach. Highly appreciated if you want to work on it! |
A very early version of this can be found here: #341 |
The existing design already subscribes for CoreAudio and WASAPI format changes and triggers STOP and throws
CAPTUREFORMATCHANGE
.Take CoreAudio as example, it has two logics: Here it gets the rate event from the system and trigger
CAPTUREFORMATCHANGE
, regardless of user definedstop_on_rate_change
value. This detection is based on CoreAudio's rate listener. And here it check if the measured rate is off 4% and triggerCAPTUREFORMATCHANGE
ifstop_on_rate_change = true
On ALSA the latter test is implemented here, but no former equivalent.
The text was updated successfully, but these errors were encountered: