How to run CamillaDSP with Multiple DACs
I use two 4-channel DACs working together with CamillaDSP to provide 6 active speaker channels. Here’s what I do to get it working in Ubuntu on a Raspberry PI.
Use Two of the Same DAC
The issue with USB audio is that it is (generally) asynchronous. Data is sent via USB in a block then queued up to stream the audio device. Depending on processor speeds and block size and queueing methods the latency through this varies. Two of the same DAC will have the same streaming software and hardware so they are likely to have the same latency.
I started with a 2-channel DAC (SMSL DO-100) and a 4–channel DAC (Motu M4) but the stereo image seemed very muddy. I switched to a PreSonus Studio 26c 4-channel DAC instead of the SMSL to do one device per speaker and realized there was a clear echo — the latency difference was that large between the Motu and the PreSonus.
Investigation
I bought another PreSonus 26c to investigate what was going on. Here’s what I found.
Using the Motu with the PreSonus. When driven by a simple sine wave the phase difference between the two would add/subtract 360 every second or so — so the latency difference was wildly changing and large. When looking at a burstier output the bursts were roughly synchronized but the latency difference was visible.
Using two PreSonus Studio 26c DACs. On a sine wave stream… The starting phase difference was random somewhere between -180 and 180. That difference would pretty much not change during the entire signal duration (10 minutes) so it looked like a function of when the first data bytes of the stream hit the device. A very simple analysis (which does not currently exist) would make that error near-zero.
For numerical results, I created a sound file that was repeating: 10 seconds of a low-volume wave (to fill buffers) and then a second of high-volume square waves. Playing the file on the aggregate device (two PreSonus DACs) produced this capture on a scope:
I expanded the time scale and measured the delay: .005 mS.
Retrying the test gave varying results
Even in the worst case, though, the delay was about 256uS (1/4mS).
So, my decision is simple -> use two PreSonus 26c DACs. Currently the left DAC runs the left speaker and the right DAC the right speaker. I tried other configurations, and the signal was subjectively too muddy, while the crossover had to assume one driver at random phase/delay.
Configuration
̶I̶ ̶w̶a̶s̶ ̶n̶e̶v̶e̶r̶ ̶a̶b̶l̶e̶ ̶t̶o̶ ̶g̶e̶t̶ ̶C̶a̶m̶i̶l̶l̶a̶D̶s̶p̶ ̶t̶o̶ ̶t̶a̶l̶k̶ ̶t̶o̶ ̶a̶ ̶c̶o̶n̶s̶t̶r̶u̶c̶t̶e̶d̶ ̶a̶l̶s̶a̶ ̶d̶e̶v̶i̶c̶e̶.̶ ̶I̶ ̶d̶o̶n̶’̶t̶ ̶k̶n̶o̶w̶ ̶w̶h̶y̶.̶ ̶ Postscript (Jan 2024) — so I finally found out how to use a constructed device with CamillaDsp. The device must be created in the system-level /etc/asound.conf instead of the user-level ~/.asoundrc. Then CamillaDsp has access and works fine.
Use the constructed Alsa device if possible. The alsa loopback does work and I’ve left the text on how to use it, but I would avoid it because it adds significant latency.
Create the Merged alsa Device
In etc create a file named asound.conf . This will be autoscanned at start to create alsa devices.
Content to build a 32/96 merged 8 channel device:
-> File: /etc/asound.conf
# create a virtual eight-channel device with two 4-channel devices:
# This is in fact two interleaved quad streams in
# different memory locations, so JACK will complain that it
# cannot get mmap-based access. see below.
pcm.both {
type route;
slave.pcm {
type multi;
slaves.a.pcm "hw:S26c_1,0";
slaves.b.pcm "hw:S26c,0";
slaves.a.channels 4;
slaves.b.channels 4;
bindings.0.slave a;
bindings.0.channel 0;
bindings.1.slave a;
bindings.1.channel 1;
bindings.2.slave a;
bindings.2.channel 2;
bindings.3.slave a;
bindings.3.channel 3;
bindings.4.slave b;
bindings.4.channel 0;
bindings.5.slave b;
bindings.5.channel 1;
bindings.6.slave b;
bindings.6.channel 2;
bindings.7.slave b;
bindings.7.channel 3;
hint { description "The Combo Hw" }
}
ttable.0.0 1;
ttable.1.1 1;
ttable.2.2 1;
ttable.3.3 1;
ttable.4.4 1;
ttable.5.5 1;
ttable.6.6 1;
ttable.7.7 1;
}
ctl.both {
type hw;
card S26c;
}
# The "plug" plugin converts channels, rate and format on request.
# In our case it converts the 32 format to whatever the application request.
pcm.convert {
type plug
slave {
pcm both
format S32_LE
channels 8
rate 96000
}
hint { description "The Combo Converted" }
}
Here the both device is the merged device with a control channel from one of the DACs. The convert device is a convenience that will auto-convert stuff to 32/96 for the merged device.
Save the above file then reboot or use
sudo alsa force-reload
Using the Merged Device
To tell CamillaDsp to use the merged both device on output just write the name alone:
Using the Alsa Loopback
Get the Alsa Loopback Device
To use the alsa loopback as output and continuously stream that to the constructed alsa device.
(from RPi4 + CamillaDSP Tutorial | Audio Science Review (ASR) Forum)
On the raspberry pi the loopback device has to be installed.
sudo apt install linux-modules-extra-raspi
sudo nano /etc/modules-load.d/snd-aloop.conf
enter this line in snd-aloop.conf and save
snd-aloop
Tell CamillaDsp to use the loopback device on output:
then start a permanent stream device from loopback to the both device
alsaloop -fS32_LE -c8 -r96000 -d --sync=simple -l 512 -T 1 -Chw:Loopback,1 -Pboth
with the -d option the above line will run a daemon in the background that streams from Loopback,1 (the Loopback output channel when 0 is input) to both — the merged device. If this fails to start run it without the -d to get errors on-screen.