CamillaDSP - Cross-platform IIR and FIR engine for crossovers, room correction etc.

Thanks to @seashell it's now easy to switch config at sample rate changes!
Take a look here: GitHub - scripple/alsa_cdsp: ALSA plugin for Camilla DSP
This is an Alsa plugin that acts as a playback device. It takes a template config file and updates the values for samplerate etc, and then launches an instance of CamillaDSP that it pipes the audio data to.


When for example samplerate is changed, it ends the stream to the CamillaDSP instance which then exits. Then it starts a new one with a new config.


It seems to work really well, even the picky PulseAudio is happy to use it via an alsa-sink.


You need to compile the plugin to install it, but the documentation in the repo is very good so this shouldn't be an issue. Note that in addition to gcc etc you also need to have "pkg-config" installed as this is needed to figure out where the plugin should be placed. Verify this with the command:
pkg-config --variable=libdir alsa
The output should be something like "/usr/lib64" or "/usr/lib/arm-linux-gnueabihf", not "command not found" or empty.
 
Last edited:
Bravo seashell - really a nice one!

And a slightly puzzled remark - is this really ok, or a typo, in the sample .asoundrc file:
# Used when the sample rate is an integer multiple of 48000.
# In this case {extrasamples} will be replaced with
# {samplerate/48000 * extra_samples_48000}.
extra_samples_48000 8916

or shouldn't it state, more commonly rather something like
...
extra_samples_48000 8192
 
Last edited:
Bravo seashell - really a nice one!

And a critical remark - is this really ok in the sample .asoundrc file:
# Used when the sample rate is an integer multiple of 48000.
# In this case {extrasamples} will be replaced with
# {samplerate/48000 * extra_samples_48000}.
extra_samples_48000 8916

or shouldn't it state, more commonly rather something like
...
extra_samples_48000 8192
It's just meant as an example! 8916 = 8192*48/44.1, so an impulse response of 8916 samples at 48kHz is the same length in time as 8192 at 44.1kHz.
The numbers should be replaced by the actual length of the impulse responses that are used. The assumption is that the impulse responses for 192kHz and 96kHz are 4 and 2 times the length of the one for 48 kHz, and the same goes for the group 176.4, 88,2, 44.1 kHz.
 
Last edited:
@Daihdez Henrik is correct in the explanation of the size. For overlap and add/save based convolution there is no particular win to having a filter size be a power of 2. The FFTs must be larger than the filter to accomplish any work in terms of linear convolution so they will always be effectively padded with zeros for the circular convolution. The shorter the filter compared to the FFT size the more accomplished per FFT/IFFT pair. There are formulae for choosing the best size FFT but I don't know that CamillaDSP uses them.

There's even a threshold where for a small enough filter FFT based convolution is actually slower than the direct implementation of the linear convolution.

@phofman I don't know what latency you think the pipe adds. Unless you're overloading camilladsp (more processing than your cpu can do) it will read faster from the pipe than it will play out the hardware. The latency comes from the alsa buffer and the filters in camilladsp plus whatever latency camilladsp's internal queue produces.

If you want to reduce latency you can specify a minimum buffer size, but if you do that don't be surprised if you hit an xrun condition when things are starting.
 
delays and buffers

I would like to understand better which buffers are involved. How would I get total buffers and latency?

First there is a PA-ALSA sink, so pacmd list sinks will show it.
Then there is ALSA loopback, it is in the hw or sw params?
It is captured by Camilla, what is her latency, is it caused just by chunksize?
And then there is the ALSA playback device, so again hw or sw params?

Thanks
 
@HenrikEnquist Can you update the building guide for windows on the GitHub wiki?

Current instruction for windows on wiki
Code:
RUSTFLAGS='-C target-cpu=native' cargo build --release  --no-default-features --features cpal-backend --features websocket

Correct instruction for windows (Powershell):
Code:
$env:RUSTFLAGS="-C target-cpu=native"
cargo build --release  --no-default-features --features cpal-backend --features websocket
 
I don't know what latency you think the pipe adds

It's not actually about absolute latency length but about having a (close to) constant latency every start of the playback so that it can be compensated e.g. for AV lip sync etc. I am aware that having multiple threads is basically the same situation as having multiple processes regarding the jitter of the playback start. Nevertheless having the DSP directly in the alsa-lib chain of callbacks gives probably more chances to minimize the playback start jitter than having a separate process which must be notified via some kernel signal and resumed by the scheduler, or even completely started first.

Just that all chains I know about which care about latency control tend to avoid pipes (jackd, PA, alsa-lib + LADSPA, gstreamer, ecasound, GNU Radio, etc.) so maybe a solution could be feasible here too. Not that this has any significance or priority :)
 
It's not actually about absolute latency length but about having a (close to) constant latency every start of the playback so that it can be compensated e.g. for AV lip sync etc.
Maybe Henrik will differ in this opinion, but for that application I don't believe camilla is the right choice. It's internally built as input/processing/output threads so there is probably some variable start up latency with every stream. And yes my plugin starting a new process will definitely have some variable start up latency in addition to whatever is internal to camilladsp.

If you intend to wrap camilladsp for this you would probably want to call directly into the processing library functions. I suppose you could do that with an io plugin and have it also write directly to a slave hardware device. Or as you say you can write a full kernel driver. I wouldn't recommend the ALSA LADSPA and external filter plugins though as they have no concept of filter transients. LADSPA is samples in = samples out, ext filter plugins are samples out <= samples in. Neither has the option to say they have more data to play during the drain process.
 
you would probably want to call directly into the processing library functions. I suppose you could do that with an io plugin and have it also write directly to a slave hardware device.

Yes, something like that, but I have absolutely no detailed idea and honestly do not have any intention to explore it now :)

Or as you say you can write a full kernel driver.

Actually a kernel driver is for the hardware, user space has been (rightly so) the preferred way since leaving the OSS audio stack.
 
Hey seashell... Sweet! really sweet..! :wave:

I just got your alsa-plugin kompiled, installed and working on my piCorePlayer, Rpi 3. Just like that...

But I have one question (so far...):
I did set options for CamillaDSP log output, like in your example. But where do they go..?
When CamillaDSP is launched by alsa, the stdout from CamillaDSP seems to get lost..?
 
I found it... It ends up in squeezelite's log.

For now I started the player, squeezelite, from command-line, (but that shouldn't mater):
sudo /usr/local/bin/squeezelite -n SqueezePi2 -o camilladsp -s 192.168.1.5 -d output=info -f /var/log/pcp_squeezelite.log &
This makes me motivated to send a feature request to Henrik:

  • Log output to file.
Path could be set as a command line parameter, or in the .yml config file.
I have been thinking about this for some time, but it wasn't very important.

Although it could be handy to have both logs mixed in the same file in some situations, it can also be a hassle...
 
@HenrikEnquist Can you update the building guide for windows on the GitHub wiki?
Thanks! I'll correct it.

Maybe Henrik will differ in this opinion, but for that application I don't believe camilla is the right choice.
...
If you intend to wrap camilladsp for this you would probably want to call directly into the processing library functions.....
Yes the way CamillaDSP works is not ideal for this use case, the input and output threads and the queues between should be removed and replaced by something simpler just wrapping the processing part. Not on my todo list :)
And beside the Rust Alsa binding doesn't support the plugin api (yet at least).

This makes me motivated to send a feature request to Henrik:

  • Log output to file.
Path could be set as a command line parameter, or in the .yml config file.
I have been thinking about this for some time, but it wasn't very important.
.
I'll think about it. It's some work since it requires switching logging library. The path should then definitely be given as a command line parameter, not in the config file.