How to run CamillaDSP with Multiple DACs

Mark Zachmann
Home Wireless
Published in
5 min readJan 26, 2024

--

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.

Pair of PreSonus Studio 26c DACs

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:

Scope capture of two DACs playing a square wave at the same time

I expanded the time scale and measured the delay: .005 mS.

Magnified view to show the delay

Retrying the test gave varying results

Another attempt with much larger delay

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 pcm both device for output

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:

Setting for the loopback device in Ubuntu

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.

--

--

Mark Zachmann
Home Wireless

Entrepreneur, software architect, electrical engineer. Ex-academic.