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

Sure, but IMO the conversion from double to tv_nsec of the timespec struct is generally not correct. OTOH in this particular case where the double is most likely always below one (i.e. tv_sec = 0) the conversion produces correct results. IMO only for cases where the frames were very large (periods above 1second) and write time was fast (i.e. some initial fill of a very large slave buffer) could the variable excess exceed 1 sec, resulting in incorrect sleep time, maybe.
 
  • Like
Reactions: 1 user
I just tried the updated version in my Mint VM, and unfortunately it's still the same. About two XRUN errors per second. But the sound plays fine anyway.
Code:
CDSP Plugin ERROR: XRUN OCCURRED!
underrun!!! (at least 463.738 ms long)
CDSP Plugin ERROR: XRUN OCCURRED!
underrun!!! (at least 444.090 ms long)
CDSP Plugin ERROR: XRUN OCCURRED!
underrun!!! (at least 454.116 ms long)
CDSP Plugin ERROR: XRUN OCCURRED!
underrun!!! (at least 470.748 ms long)
CDSP Plugin ERROR: XRUN OCCURRED!
underrun!!! (at least 439.202 ms long)
CDSP Plugin ERROR: XRUN OCCURRED!
underrun!!! (at least 455.519 ms long)
 
Henrik can you just bump the xrun > 4 at line 363 to something like xrun > 40 and see what happens?
That was one of the first things I tried, and it made no difference. But I think I have found something. There is something weird going on with io_avail_min on Mint, that doesn't happen on Manjaro. I'll write again when I have had time to look a little closer.
 
First of all, thanks for writing CDSP. I use it almost exclusively.

I am getting something strange as well.

I am checking snd_pcm_avail() to see how much room there is in the Ring Buffer that is 8192 frames "long" to make sure there is room to writei() 256 frames more on each callback. Each time I call writei(), I see the available space in the Ring Buffer diminish by 256 frames until I have to sleep until there is room to write another 256 frames to the Ring Buffer. Even though I see the Ring Buffer is full, I get the "IO Thread out of data" message. It is as if CDSP isn't getting a data-ready signal. I don't know if I need to call some ping method or if ALSA is supposed to handle those data ready signals automatically.

There also seems to be a marked delta in behavior between 96kHz and 176.4kHz. Up to 96kHz, I just sleep when the Ring Buffer is full to avoid a broken pipe, but above 96kHz, I have to sleep more often (i.e before the Ring Buffer is full) to avoid the broken pipe errors. I still get the "out of data" messages, but no broken pipes. It is behaving like a blocking/synchronization/context switching issue.

I also noticed if I sleep for seconds ( e.g. 3.X ) in the callback, CDSP's log messages hang in lockstep (longer than the 1/4 period).

[7] > openALSA: Device(camilladsp), channels(2), requestedRate(176400)
[8] openALSA: PCM name: (camilladsp), PCM state: (PREPARED)
[9] openALSA: channels: 2 : (stereo)
[10] openALSA: requestedRate: 176400, get_rate: 176400 bps
[11] openALSA: period_size - frames: 2048
[12] openALSA: period_time: 11609
[13] openALSA: ring buffer_size: 8192
[14] < openALSA: Device(camilladsp), channels(2), requestedRate(176400)
 
Last edited:
This looks very suspicious:
Code:
CDSP Plugin DEBUG: Initializing SW
CDSP Plugin INFO: Changing SW avail min: 44100 -> 139809450722243

comes from here, lines 779-793 in cdsp:
C:
static int cdsp_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) {
  cdsp_t *pcm = io->private_data;
  debug("Initializing SW\n");

  snd_pcm_sw_params_get_boundary(params, &pcm->io_hw_boundary);

  snd_pcm_uframes_t avail_min;
  snd_pcm_sw_params_get_avail_min(params, &avail_min);
  if (avail_min != pcm->io_avail_min) {
    info("Changing SW avail min: %lu -> %lu\n", pcm->io_avail_min, avail_min);
    pcm->io_avail_min = avail_min;
  }

  return 0;
}
It seems like "snd_pcm_sw_params_get_avail_min()" returns a nonsense value.
This hack seems to work better:
C:
static int cdsp_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) {
  cdsp_t *pcm = io->private_data;
  debug("Initializing SW\n");

  snd_pcm_sw_params_get_boundary(params, &pcm->io_hw_boundary);

  snd_pcm_uframes_t avail_min;
  snd_pcm_sw_params_get_avail_min(params, &avail_min);
  if (avail_min > 10000) {
      info("Rubbish detected, avail_min: %lu\n", avail_min);
      return 0;
  }
  if (avail_min != pcm->io_avail_min) {
    info("Changing SW avail min: %lu -> %lu\n", pcm->io_avail_min, avail_min);
    pcm->io_avail_min = avail_min;
  }

  return 0;
}

It logs this on start:
Code:
CDSP Plugin INFO: Initializing hw_params: S24_3LE 44100 2
CDSP Plugin INFO: FIFO buffer size: 682 frames
CDSP Plugin INFO: Selected HW buffer: 8 periods x 16380 bytes <= 131070 bytes
CDSP Plugin DEBUG: Initializing SW
CDSP Plugin INFO: Rubbish detected, avail_min: 44100
CDSP Plugin DEBUG: Prepared
CDSP Plugin DEBUG: Initializing SW
CDSP Plugin INFO: Rubbish detected, avail_min: 139881831863323
CDSP Plugin DEBUG: Starting
 
FWIW, some players support 768kHz.

I needed to bump up MAX_P from 16K to 32K to test 8 channels @ 768kHz.

// In order to prevent audio tearing and minimize CPU utilization, we're
// going to setup period size constraint. The limit is derived from the
// maximum sampling rate, max channels, and maximum integer format size
// (32 bits) so the minium period "time" size will be about 10ms. The upper
// limit will not be constrained.

unsigned int min_p = max_rate / 100 * max_channels * 4 / 8;

if ((err = snd_pcm_ioplug_set_param_minmax(&pcm->io,
SND_PCM_IOPLUG_HW_PERIOD_BYTES, min_p, 1024 * 16)) < 0) goto _err;
 
It seems like "snd_pcm_sw_params_get_avail_min()" returns a nonsense value.
This hack seems to work better:
C:
static int cdsp_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) {
  cdsp_t *pcm = io->private_data;
  debug("Initializing SW\n");

  snd_pcm_sw_params_get_boundary(params, &pcm->io_hw_boundary);

  snd_pcm_uframes_t avail_min;
  snd_pcm_sw_params_get_avail_min(params, &avail_min);
  if (avail_min > 10000) {
      info("Rubbish detected, avail_min: %lu\n", avail_min);
      return 0;
  }
  if (avail_min != pcm->io_avail_min) {
    info("Changing SW avail min: %lu -> %lu\n", pcm->io_avail_min, avail_min);
    pcm->io_avail_min = avail_min;
  }

  return 0;
}
It's easier to check the return value being != 0. Although that's buggy too as the return value is positive and should be negative. Either way, while it may be better in a Mint VM on a test file at 48kHz aplay throws a lot of warnings and errors during drain at the end of the file.

The big bluez_alsa fix doesn't work in the VM either. It actually crashes aplay with an assert. The period size and buffer size calls also return garbage values which are the basis of that fix.

I'm just going to check the error value and if it's not zero and the version of alsa is in the bad range I'll put out an error saying if you're experiencing problems upgrade alsa.

emailtim I've pulled in some updates from bluez_alsa that change max period to 1024*1024.
 
  • Like
Reactions: 1 user
Manjaro works for you? I just tried Debian Sid with 1.2.6-1 and it still gives garbage for any attempt to read HW or SW params.
FWIW (signed/unsigned issues) with -Wall
libasound_module_pcm_cdsp.c: In function ‘io_thread’:
libasound_module_pcm_cdsp.c:343:26: warning: overflow in conversion from ‘snd_pcm_uframes_t’ {aka ‘long unsigned int’} to ‘snd_pcm_sframes_t’ {aka ‘volatile long int’} changes value from ‘io_hw_ptr = 18446744073709551615’ to ‘-1’ [-Woverflow]
343 | pcm->io_hw_ptr = io_hw_ptr = -1;
| ^~~~~~~~~
libasound_module_pcm_cdsp.c:377:28: warning: overflow in conversion from ‘snd_pcm_uframes_t’ {aka ‘long unsigned int’} to ‘snd_pcm_sframes_t’ {aka ‘volatile long int’} changes value from ‘io_hw_ptr = 18446744073709551615’ to ‘-1’ [-Woverflow]
377 | pcm->io_hw_ptr = io_hw_ptr = -1;
| ^~~~~~~~~
 
Well I've decided alsa is just broken so I've pushed an update that doesn't attempt to get avail_min and just sticks to period_size. Yes this might cause a processor hit on any program that does request a larger avail_min. Alas.

I came to this conclusion by looking at the official jack plugin for alsa 1.2.26 and using Debian Sid which is on 1.2.26-1 to test. The Jack plugin pulls avail_min in the prepare call back (which requires it to allocate and fill a whole new sw_param block) rather than in the sw_params call back. So I grabbed their code and it has the exact same behavior as doing it in the sw_params callback. The alsa team just seems dead set on making things (INTERNAL) and thus unavailable even to plugins. (hw_params in the hook anyone?) Oddly the return code often equals the period_size instead of 0 or a negative error code as documented. Maybe it's even the avail_min as I don't think aplay sets it. But I'm certainly not going to try and rely on that undocumented behavior.

Anyway, testing in Debian Sid after dropping any attempt to get avail_min does stop the xrun errors. It should work in the other versions of alsa used in various new distributions as well.

And yes emailtim I got the same warning with the Debian Sid compiler and already made the variable signed as it should be in this same check-in. (We were cross posting.) Surprised earlier compilers didn't catch that.