Linux USB-Audio Gadget (RPi4 OTG)

I made a another test and I need to add precision.

When using windows as audio source. If I change the volume of the device on windows, the sound level doesn't change. But on the rasp, the value of the alsa mixer 'PCM' of the card UAC2_Gadget changes. So this script can retrieve the PCM volume value and set this value as volume of camillaDSP.

When using linux as audio source. If I change the volume of the device, the sound level does change. But on the rasp, no alsa mixer seems to be affected by this volume changes.


I tried to change the level of the alsa mixer of the usb gadget on my linux PC, and finaly the value of the alsa mixer 'PCM' of the card UAC2_Gadget changes !

So you're right, the sound manager on my linux doesn't change the alsamixer's volume and is probably software-based. Maybe this have something to do with the fact that my linux distribution use pulseaudio.
 
As always with audio service in linux, it's all about pain and suffering :cuss: (or it's just me ? )

Well, after a few headache, I found a way to solve my problem ! If pulseaudio doesn"t find an alsamixer which is named the right way, it use software volume control as fallback.

I found this page which seems to adress my problem : https://www.freedesktop.org/wiki/Software/PulseAudio/Backends/ALSA/Profiles/
Your hardware does not expose normal volume control names such as "Master", "PCM", "Headphone" etc but instead e g "Megaphone" and "Leslie speaker".

But personnally after reading this page, I was helpless and I had to do a lot of die & retry to figure out, how this is working.
This "how-to" works on my computer, a linux mint 21 which is Ubuntu 22.04 based.


  1. In the file /lib/udev/rules.d/90-pulseaudio.rules

    Inside the section :
    LABEL="pulseaudio_check_usb"
    ...
    GOTO="pulseaudio_end"

    Add udev rule :
    ATTRS{idVendor}=="1d6b", ATTRS{idProduct}=="0104", ENV{PULSE_PROFILE_SET}="uac2.conf"
    IdVendor & idProduct must be updated with value you specified for your usb gadget.

  2. Add the profile-set uac2.conf to /usr/share/pulseaudio/alsa-mixer/profile-sets/uac2.conf
    "UAC2Gadget96000" in this file, must correspond to the product name of your usb audio gadget


  3. Add the path uac2-output.conf to /usr/share/pulseaudio/alsa-mixer/paths/uac2-output.conf
    "Playback Volume" is the name of the alsamixer

Content of uac2.conf:
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.

[General]
auto-profiles = yes

[Mapping UAC2Gadget96000]
device-strings = hw:%f
channel-map = front-left,front-right
paths-output = uac2-output
priority = 15

Content of uac2-output.conf :
[General]
priority = 99

[Element Playback Volume]
switch = mute
volume = merge
override-map.1 = all
override-map.2 = all-left,all-right


I hope this will help others people :)
 
Last edited:
As always with audio service in linux, it's all about pain and suffering
That's the price of the complete flexibility. You cannot do anything like this on any other OS. udev knows everything about your soundcard - which USB port/PCI slot, device serial number, etc... unlike PA which already gets an alsa device, no other details. That's why udev is tasked with customizing PA. If someone wanted to use one volume control for a USB device plugged into slot A, and a different control for the same device when plugged into slot B (such a requirement may arise, or any other) - this method of configuration would allow it.

PA can be asked to assign a profile to a soundcard directly, without involving udev ( e.g. https://unix.stackexchange.com/a/462671 ), but that may not work correctly when hot-plugging the device.

IF the USB gadget used standard naming for mixer controls (like Master Playback/Capture Volume) https://www.kernel.org/doc/html/v4.12/sound/designs/control-names.html , PA would recognize the volume control out of the box, no udev rules would be required. The gadget uses deprecated Playback/Capture Volume names in the uac2 function https://github.com/torvalds/linux/b...rivers/usb/gadget/function/f_uac2.c#L118-L119 . I may later send a patch which will allow setting custom names for these controls via configfs, after the currently proposed UAC2 patches get through and the source code settles down.
 
RaspberryPI OS Bookworm has been released, with some changes of the WiFi setup which by now has been shown to be a bit quirky to set up in headless mode. Apparently the WiFi parameters declarations have been migrated from the wpa_supplicant to the NetworkManager configuration. And the method as described in my former HowTo for installing iwd instead of wpa_supplicant does not work any longer. Therefore I updated the WiFi section of the HowTo to work along the changes introduced with Bookworm.
 

Attachments

  • Rpi_USB_Audio_Gadet_HowTo_231015.pdf
    123.6 KB · Views: 178
  • Like
Reactions: 1 user
I am trying to get an asynchronous notification of alsa events occurred on the USB gadget device of my Raspberry Pi 4.
To this end I use the snd_async_add_ctl_handler and the snd_ctl_subscribe_events interfaces of the alsa C library.
Unfortunately, the callback function is never triggered.
I suspect that the gadget driver does not send the SIGIO signal, which the alsa lib requires to get notified of incoming events.

Is there a way to instruct the gadget driver to generate a signal when an event occurs?
(I apologize if this subject has already been covered).


NOTE: The synchronous event reading with the snd_ctl_read interface works quite well (see my litlle sample rate switcher for CamillaDSP at https://github.com/marcoevang/camilladsp-setrate)
 
Last edited:
It calls the notification at any change of samplerate, i.e. at start and end of capture/playback, when altsetting is being switched. This rust code listens for the notifications https://github.com/pavhofman/gaudio...eb81c34f17658e32d44f6ee2/src/bin.rs#L135-L150
Thanks a lot for your help.
I am not familiar with the rust language. However, it seems that the code at line 139:
let event = ctl.read()?.unwrap()
performs a synchronous read, that is what I already do in C language with the snd_ctl_read interface.
What I would like to obtain is an asynchronous read performed by a callback function, without the need to wait for an event to happen.
The Alsa C library provides an interface to set a callback function. As far as I understand, when the driver generates a SIGIO signal, the callback function is triggered. Unfortunately, this never happens.
 
Last edited:
In any case the sync -> async should be doable in your code using another thread.
Yes, this could be a good viable option.
However, I solved the issue. And the solution was trivial, but rather puzzling. The alsa control must be opened in READONLY or in NONBLOCK mode (the gadget driver does its job nicely... :) ). If it is opened in ASYNC mode, as I was doing previously, the callback function is not triggered.
The alsa C library is a complex beast but, unfortunately, its documentation is very poor.
Many thanks to @phofman, who is always very supportive.
 
Thansk @phofman - Sorry to mess up standard terminology. My intented use is an ADC with USB interface to capture vinyl. For this i used the term USB audio sourche. But now understand correct wording should be USB host.

Now then. if my host are capable of asynchronous transfer will the USB gadget with before mentioned implementation and use take advanses of this?

Edit: There isn't any mentioning of either or in the USB gadget configuration script by @Daihedz
 
Last edited:
The other direction ADC -> CDSP -> USB gadget -> USB host should be supported too, but not tested thoroughly. I never saw any discussion about it, AFAIK there is nothing online, we discussed the CDSP support for this direction with Henrik over email.

Input endpoint (i.e. data going from device to host) is typically asynchronous. That means every relevant frame (as specified by bInterval) the device sends all new samples it has available, no explicit async feedback messages are needed.

Implementation in device MCUs is quite simple - the MCU packs into the packet whatever samples have arrived into some FIFO from the I2S input dataline.

Unfortunately adapting this to linux alsa is more difficult becase every alsa device runs on its own and the client (writing application in this case) is timed by the device. To give the client control over speed at which the gadget device consumes samples, the gadget offers alsa control "Playback Pitch 1000000" (similarly to "Capture Pitch 1000000" ctl for the other direction). This control adjusts number of samples copied from the device alsa buffer to the packet going to the USB host.

So you should configure CDSP for the other direction (just flipping the devices) and (in the final setup) run gadget_ctl configured for the other direction. Host starts capture -> gadget_ctl fires CDSP which will bridge data between the ADC alsa card and the gadget alsa card, adjusting "Playback Pitch 1000000" continually to fit the fixed ADC clock.
 
Last edited:
  • Like
Reactions: 1 user