using a Raspberry Pi 4 as a USB DSP-DAC

Status
This old topic is closed. If you want to reopen this topic, contact a moderator using the "Report Post" button.
I have been using the Raspberry Pi platform for several years to implement DSP (via LADSPA plugins) and as streaming clients in a whole-house audio system based on Gstreamer. In this scenario, there is a "source" computer that is playing some audio stream, the stream is directed over my LAN to another Linux computer (e.g. the Pi), and then the audio is split up in to multiple channels, DSP is performed on each channel, and then it is rendered via a DAC.

I recently rediscovered the "USB Gadget" module that has been around for a few years. This uses a USB OTG host to act as one of a number of different kinds of peripherals. The way it works (as far as I understand it) is that you configure the USB OTG host to signal that it is a device of the kind of your choosing. When you connect the gadget device to another USB device, it appears as that kind of peripheral. Adafruit has a very nice tutorial about this here:
Overview | Turning your Raspberry PI Zero into a USB Gadget | Adafruit Learning System
The Adafruit tutorial is written for a Pi Zero. The Pi Zero is not really up to the task of doing DSP unless it is very basic stuff for one or two channels, however, it does show the necessary steps. Note that one of the possibilities is to create an "ethernet gadget".

It turns out that the new Raspberry Pi 4B also has an OTG capabilities via the USB-C port (typically used to provide power to the Pi). We can use this OTG host to convert the USB-C port into a USB gadget connection as long as we power the Pi via other means (e.g. via the GPIO pins).

As luck would have it, I have been using an 8-channel HDMI audio extractor HAT that can power the Pi via the GPIO pins and can provide 8 analog outputs at up to 24/192. When combined with the Pi 4 running as a USB gadget, the Pi can receive audio, perform DSP on it, and then output up to 8 independent channels. This would comprise a very nice USB DSP+DAC unit!

So, how do we set this up?

The way I plan to do it is to put the Pi into "ethernet gadget" mode. Then, when the Pi is connected to another computer via its USB-C connector, it will appear as an ethernet connection (see the Adafruit tutorial at the link above). It is then a matter of redirecting the Windows audio output over this USB ethernet connection, receiving it on the Pi, and so on.


UPDATE: SEPT 3 2019:

The script files and HOW-TO for setting up and streaming audio in this way are attached to POST #77 of this thread. This is version 1.0 for all of these documents.



last Edit: 3 SEPT 2019.
 
Last edited:
Sounds to me that you are better off emulating a USB UAC2 audio device rather than Ethernet, then the Rpi will show up as an audio card in Windows and you won't need any additional software or drivers on the source computer. And it might be simpler on the RPi side as well.
 
Maybe it was RPi 3 discussion I looked up some months ago but it made it sound like getting the RPi side of the spectrum to be a USB slave nicely makes the UAC2 device pathway a gigantic pain over other more straightforward, if hardware intensive, ways.

* YMMV and take everything I say with a few truckloads of salt. :)
 
Sounds to me that you are better off emulating a USB UAC2 audio device rather than Ethernet, then the Rpi will show up as an audio card in Windows and you won't need any additional software or drivers on the source computer. And it might be simpler on the RPi side as well.

On the surface that sounds simpler. I am, after all, trying to implement an audio sink. But let's assume I create a USB audio gadget. Then what? I have no idea how to receive the audio on the Pi in that mode. On the other hand, I have been RXing audio over ethernet for a long time and know that well.

If anyone knows more about the Pi side when it is an audio gadget, please post about it.
 
On the surface that sounds simpler. I am, after all, trying to implement an audio sink. But let's assume I create a USB audio gadget. Then what? I have no idea how to receive the audio on the Pi in that mode. On the other hand, I have been RXing audio over ethernet for a long time and know that well.

If anyone knows more about the Pi side when it is an audio gadget, please post about it.

If using ethernet, why not just use ethernet then? What does the USB connection bring that an ethernet cable does not?
 
I did some testing with the Pi 4 configured as a USB audio gadget. Thanks for the tip, phofman.

I started by following the Adafruit guide, adding text to config.txt and cmdline.txt. Indeed, a new recording device showed up in ALSA:
Code:
pi@Pi4gadget:~ $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: UAC2Gadget [UAC2_Gadget], device 0: UAC2 PCM [UAC2 PCM]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

The problem was in the parameters it presented via aplay --dump-hw-parameters:
format: S16_LE
sample rate: 64000
These are the defaults for the audio device. Who chose the very non-standard 64kHz as the default recording rate???

I found some source code for the g_audio gadget here:
drivers/usb/gadget/legacy/audio.c - linux-fpga-chameleon - Git at Google
The code suggested I could set parameters for the sample rate and bit depth. The channel count and map could also be set, but the default of stereo is just fine for my needs. I could not seem to set the sample rate and bit depth by providing them as parameters directly in the cmdline.txt call. After a lot of searching of the web, I found some other gadget users were only able to set parameters by moving the initialization out of config.txt and cmdline.txt and into /etc/modules plus a file in /etc/modprobe.d to set the parameter values. So, after backing out my changes to the files in /boot, I did the following:

I added two lines to /etc/modules:
Code:
dwc2
g_audio

I created the file /etc/modprobe.d/usb_g_audio.conf containing the following:
Code:
#load the USB audio gadget module with the following options
options g_audio c_srate=96000 c_ssize=4
NOTE: c_srate sets the sample rate in Hertz and c_ssize sets the number of bytes per sample (4 bytes = 32 bits)

After rebooting, when I run arecord -D hw:0,0 --dump_hw_params the result is:
Code:
pi@Pi4gadget:~ $ arecord -D hw:0,0 --dump-hw-params
Recording WAVE 'stdin' : Unsigned 8 bit, Rate 8000 Hz, Mono
HW Params of device "hw:0,0":
--------------------
ACCESS:  MMAP_INTERLEAVED RW_INTERLEAVED
FORMAT:  S32_LE
SUBFORMAT:  STD
SAMPLE_BITS: 32
FRAME_BITS: 64
CHANNELS: 2
RATE: 96000
PERIOD_TIME: [500 5334)
PERIOD_SIZE: [48 512]
PERIOD_BYTES: [384 4096]
PERIODS: [4 16]
BUFFER_TIME: [2000 85334)
BUFFER_SIZE: [192 8192]
BUFFER_BYTES: [1536 65536]
TICK_TIME: ALL
--------------------
arecord: set_params:1339: Sample format non available
Available formats:
- S32_LE
So, I have been able to set up a 32-bit 96kHz USB audio device. Nice! Now I need to test it from the PC end and see how that looks.

It seems that there is one shortcoming to this approach: you can only set up one sample rate and bit depth at a time. It's possible to reconfigure it on the Pi by unloading the g_audio module and loading it again with a different set of parameters using modprobe. It's doable but you would need to be able to log into the Pi in order to do that.

On the other hand, if I set up the USB gadget as g_ether (ethernet over USB) I get the same simple USB connectivity, but I can easily ssh into the Pi to run commands, etc. The sample rate could be changed much more easily without requiring changes to the kernel modules. The only downside is that an app is needed on the PC to send windows audio over the USB-ethernet link in addition to the one running one the Pi that does the DSP, etc.

Either way, you get some nice DSP functionality plus hot-swappable USB connectivity.
 
Last edited:
Well, after the initial success, failure. When I connected the Pi to the host Windows PC via a USB-C cable, the USB audio device was installed on the Windows machine but there was a problem and the driver could not be started. I tried to find some information about this error, but everything I could find was just generic tips to reinstall drivers, try rebooting, etc. Garbage.

If that could be overcome, I think we could have something useful in the USB Audio Gadget. Otherwise it's a bit of a dead end.
 
Last edited:
So, on to plan B: the USB Ethernet gadget.

I followed the Adafruit guide. I made some good progress and then started to hit some serious snags when I tried to set up a fixed IP address for the USB Ethernet gadget connection. I need to do this so that I can ssh into the Pi from another computer. If you do not set up a fixed IP address at boot time, the Pi will assign a random address in the 192.254.X.X range. This address will change after each boot, so you would not be able to know it in advance. The IP address is needed to ssh into the Pi (I can't use Bonjour) and unless, like I was, you were also logged into the machine with keyboard mouse and screen all connected this might create an impasse.

The other problem was that setting up a fixed IP address for the USB Ethernet gadget connection by following the Adafruit example caused my wired ethernet connection to also default to the 192.254.X.X range, which is useless. If I unplugged the wired ethernet and then plugged it in again, it would get assigned an IP on my LAN but then the Ethernet gadget connection would go down and could only be brought up again by a reboot.

It seems that my Pi is not yet configured to handle two different networks with very different address ranges, etc., one connected via the ethernet and my LAN, and the other via the USB gadget. I'm not sure how to do that, but it might not be necessary (keep reading to find out why):

In the end I did get it working when I booted up the Pi without the wired or wireless network connected and then I connected the USB gadget to the host. By this time the Windows RNDIS connection was configured with the correct IP address info. At this point I could successfully SSH into the Pi from the host.

So, after a little pain, this type of USB gadget connection indeed works. The current limitation, barring a fix, is that the Pi cannot have any other internet connection if you want the USB gadget to function. Since I want to use it as a headless USB dongle and only access it via SSH from a host, that is no problem since I would not need any internet connections. It is also possible to share the host's internet connection over the USB gadget, but I have not tried that yet. You might want to access the internet to, for example, download new software or update the system from time to time. But mostly you would not need that capability in the role of a DSP-DAC unit.

I will keep working on this after some sleep.
 
Very well, you have made great progress.

IMO the USB-audio path is more logical and therefore better. The gadget driver was developed by/for Samsung, very likely for Android. They probably care about optimal quality, not about consumer audio standards.

No surprise the windows driver got stuck, quite typical for that OS. You may be able to find some logs, why it happened, maybe. Perhaps 96k/32bits is not supported by the windows driver, who knows. What does lsusb -v say?

Switching samplerate is not implemented, the driver is more like a proof of concept. But adding such feature would be VERY useful. Maybe you could ask the author of the module (in the source code).
 
I followed the Adafruit guide. I made some good progress and then started to hit some serious snags when I tried to set up a fixed IP address for the USB Ethernet gadget connection. I need to do this so that I can ssh into the Pi from another computer. If you do not set up a fixed IP address at boot time, the Pi will assign a random address in the 192.254.X.X range. This address will change after each boot, so you would not be able to know it in advance. The IP address is needed to ssh into the Pi (I can't use Bonjour) and unless, like I was, you were also logged into the machine with keyboard mouse and screen all connected this might create an impasse.
arpscan ? or are you wanting discovery to be completely automated ?
or could you not set a reserved address rather than static ?
 
Very well, you have made great progress.

IMO the USB-audio path is more logical and therefore better. The gadget driver was developed by/for Samsung, very likely for Android. They probably care about optimal quality, not about consumer audio standards.

No surprise the windows driver got stuck, quite typical for that OS. You may be able to find some logs, why it happened, maybe. Perhaps 96k/32bits is not supported by the windows driver, who knows. What does lsusb -v say?

Switching samplerate is not implemented, the driver is more like a proof of concept. But adding such feature would be VERY useful. Maybe you could ask the author of the module (in the source code).

As part of my experimentation and troubleshooting of the audio gadget problems I came across a feature request for the support of multiple sample rates. I too thought that perhaps 32/96 might be a problem so I tried it again with 16/44.1 and the problem remained. Without much further info to go on, I gave up and switched to the ethernet gadget mode.

I agree that the audio gadget mode would be simpler and more in the vein of what I am trying to accomplish, even if there is only one format and rate supported. Any help on this would be appreciated. I do not really know how to get detailed info out of Windows about this problem. I might look into it again today and see if I can find any tips on the web.

Edit: The link below is a post about the same problem I am experiencing. It seems to Windows specific:
linux audio gadget (USB audio class 2) not completing enumeration on Win 10 Creator's Edition?
 
Last edited:
arpscan ? or are you wanting discovery to be completely automated ?
or could you not set a reserved address rather than static ?

The way the ethernet gadget is set up now, it is assigned a static IP address, etc. Each time I boot, and then run ifconfig or fire up the GUI desktop, the wired ethernet would have an IP in the 192.254.x.x range, even if the usb gadget had not yet been connected. My guess is that there cannot be two concurrent networks with very different IP ranges.

I did not figure out a way to have the Pi's networking sort this out.
 
I found this bug report that describes a problem with the Linux UAC2 driver:
UAC2 gadget not recognized on Windows 10 — Linux USB
The problem occurred under kernel 4.9.68. Maybe kernel 5.x.x has a fix.

The quote below describes the source of the driver error:

Apparently there is an issue with the Linux UAC 2.0 driver that was identified early last year (2018) but has not been fixed yet.

Basically, as quoted:

an isochronous OUT ep with asynchronous synchronization is required
(at least by Microsoft) to have a feedback IN ep, to be able to report
to the host the rate so no under- or overrun condition occurs.


The Linux UAC2 driver does not provide this and hence the Microsoft driver will not start. So until the Linux UAC2 developers make some changes your Linux UAC2 gadget will not speak to a Windows 10 machine. It doesn't appear to have been any work on this for over a year.

The gadget will reportedly work under other OSes, e.g. Mac or Linux.
 
My 2 cents Network Manager is switching your interfaces. You might want to avoid NM for your fixed ethernet (with fixed configuration in /etc/network/interfaces) and let NM handle only the dynamically added USB-ethernet. Or avoid NM altogether so that it does not get under your hands.
 
My 2 cents Network Manager is switching your interfaces. You might want to avoid NM for your fixed ethernet (with fixed configuration in /etc/network/interfaces) and let NM handle only the dynamically added USB-ethernet. Or avoid NM altogether so that it does not get under your hands.

The ethernet USB gadget connection must be assigned a fixed ip address at boot. How do you suggest doing that?

I tried adding lines for that into /etc/network/interfaces (following the Adafruit tutorial) and then later as an entry in /etc/dhpcp.conf following another page. Both produced the same behavior. In the end I deduced what was going on (to the best of my ability) and then booted up with all interfaces disconnected. When I plugged in the ethernet gadget to a host, it worked.

It might be just be a matter of configuration for dual networks on the Pi.
 
Ad usb-audio gadget. While the requirement for feedback in async mode in general is understandable, it is also understandable linux/osx drivers do not require it. For capture (data being transfer from the device to the host) the feedback has no use - data transfer is timed by the device side and all data coming in are processed, no reason for the device to signal the exact rate to the host, host is sending no audio data. Clearly linux/OSX are reasonable, MS as always is making troubles.

I do not think that endpoint is already implemented History for drivers/usb/gadget/function/f_uac2.c - torvalds/linux * GitHub
 
PROBLEM SOLVED!

Well, the problem with the ethernet gadget at least...

I discovered some useful info on this page:
networking - Can I prevent a default route being added when bringing up an interface? - Unix & Linux Stack Exchange

Adding "nogateway" as the last line in my static route declaration in /etc/dhcpc.conf file for the usb0 interface solved the problem. The Pi now can boot up with the wired ethernet connected and both it and the USB gadget are correctly configured and work perfectly.

So, the USB ethernet gadget connection option is looking more promising now. Since the USB audio gadget connection doesn't work under Windows I will put that aside for now.
 
Status
This old topic is closed. If you want to reopen this topic, contact a moderator using the "Report Post" button.