Quantcast
Channel: Squeezebox : Community : Forums - 3rd Party Software
Viewing all articles
Browse latest Browse all 2050

My way of connecting squeezelite, brutefir and drc (and Kodi)

$
0
0
I wanted to use squeezelite with brutefir in cubox-i-4pro with also Kodi running on same machine.

So I made a program controlling squeezelite and brutefir using alsa loopback for communication, because it works better than a pipe. The program restarts squeezelite and brutefir automatically, if hang would happen and also with HUP signal, so you can switch brutefir filter files on easily. Squeezelite up/downsamples with sox to 96kHz/24bits, only one brutefir config file needed. Brutefir is only running when squeezelite is playing, that way Kodi can use same USB DAC (Kodi set to release DAC too). DAC is XMOS DIY-IN-HK, system runs weeks without stopping. In my system lms is separate NAS, but cubox-i-4pro has enough power to run it too. The Linux distribution that I am using is Squeeze On Arch (soa).

System block diagram:
Name:  drc.png
Views: 45
Size:  117.9 KB

The code of sqbrctl:
Code:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sched.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <arpa/inet.h>

int room_correction = 1;
int upsample = 1;

typedef struct {
  char *key;
  char *value;
  int *parameter;
  int setvalue;
} config_t;

#define CONFS 4

config_t confs[CONFS] = {
  { "roomcorrection", "off", &room_correction, 0 },
  { "roomcorrection", "on" , &room_correction, 1 },
  { "upsample",      "off", &upsample,        0 },
  { "upsample",      "on" , &upsample,        1 },
};

void read_sqbrctl_conf(void)
{
  FILE *f;
  char key[4096];
  char *value;
  int i;

  f = fopen("/etc/drc/sqbrctl.conf", "r");
  if (f == NULL) return;

  /* read key/value pairs */
  while (!feof(f)) {
    if (fgets(key, sizeof(key), f) != NULL) {
      value = strstr(key, ":");
      if (value == NULL) continue;
      value++;
      for (i = 0; i < CONFS; i++) {
        if (strncmp(key,confs[i].key,strlen(confs[i].key)) == 0) {
          if (strncmp(value,confs[i].value, strlen(confs[i].value)) == 0) {
            *confs[i].parameter = confs[i].setvalue;
          }
        }
      }
    }
  }
  fclose(f);
}

int set_rt_priority(int priority)
{
    struct sched_param schp;

    memset(&schp, 0, sizeof(schp));
    schp.sched_priority = priority;

    if (sched_setscheduler(0, SCHED_FIFO, &schp) != 0) {
        return errno;
    }
    return 0;
}

int need_reload;

void signal_callback_handler(int signum)
{
  need_reload = 1;
  return;
}

pid_t stop_brutefir(pid_t previous)
{
    if (previous != 0) {
        system("killall brutefir");
        waitpid(previous, NULL, 0);
    }
    return 0;
}
               
pid_t start_brutefir(pid_t previous)
{
    pid_t pid;
    static char *argv[]={"brutefir","/etc/drc/brutefir.conf",NULL};

    stop_brutefir(previous);

    pid=fork();
    if (pid == 0) {
        /* child process */
        execv("/usr/bin/brutefir",argv);
        exit(0); /* only if execv fails */
    } else if (pid > 0) {
        return pid;
    } else {
        return 0;
    }
}

typedef enum { SQLITE_LOOP_PLAY, SQLITE_DIRECT_PLAY } sqlite_mode_e;

pid_t stop_squeezelite(pid_t previous)
{
    if (previous != 0) {
        system("killall squeezelite");
        waitpid(previous, NULL, 0);
    }
    return 0;
}
                       
pid_t start_squeezelite(pid_t previous, sqlite_mode_e sqlite_mode)
{
  pid_t pid;
  static char *argv_direct_upsample[]=
        {
                "squeezelite",
                "-n", "soa-sql-kjh",
                "-p", "9",
                "-r", "96000-96000",
                "-a", "200:128:32",
                "-u", "vLs:18:3",
                "-C", "2",
                "-o", "front:CARD=D20,DEV=0",
                NULL
        };
  static char *argv_direct_noupsample[]=
        {
                "squeezelite",
                "-n", "soa-sql-kjh",
                "-p", "9",
                "-a", "200:128:32",
                "-C", "2",
                "-o", "front:CARD=D20,DEV=0",
                NULL
        };

  static char *argv_loop_upsample[]=
        {
                "squeezelite",
                "-n", "soa-sql-kjh",
                "-p", "9",
                "-r", "96000-96000",
                "-a", "200:128:32",
                "-u", "vLs:18:3",
                "-C", "2",
                "-o", "hw:Loopback,1,0",
                NULL
        };

  static char *argv_loop_noupsample[]=
        {
                "squeezelite",
                "-n", "soa-sql-kjh",
                "-p", "9",
                "-a", "200:128:32",
                "-C", "2",
                "-o", "hw:Loopback,1,0",
                NULL
        };

  stop_squeezelite(previous);

  pid=fork();
  if (pid == 0) {
        /* child process */
        if (sqlite_mode == SQLITE_DIRECT_PLAY) {
                if (upsample) {
                        execv("/usr/bin/squeezelite", argv_direct_upsample);
                } else {
                        execv("/usr/bin/squeezelite", argv_direct_noupsample);
                }
        } else {
                if (upsample) {
                        execv("/usr/bin/squeezelite", argv_loop_upsample);
                } else {
                        execv("/usr/bin/squeezelite", argv_loop_noupsample);
                }
        }
        exit(0); /* only if execv fails */
  } else if (pid > 0 ) {
        return pid;
  } else {
        return 0;
  }
}

#define BUFFER_SIZE (4096)

int sqlite_playing_loop(void)
{       
        int ret = 0;
        char buffer[BUFFER_SIZE];
        int fd = open("/proc/asound/Loopback/cable#1",O_RDONLY);
        if (fd < 0) return ret;
        if (read(fd, buffer, BUFFER_SIZE) > 0) {
                if (strstr(buffer, "Playback") != NULL) {
                        ret = 1;
                }
        }
        close(fd);
        return ret;
}

int sqlite_playing_direct(void)
{
        int ret = 0;
        char buffer[BUFFER_SIZE];
        int fd = open("/proc/asound/D20/pcm0p/sub0/hw_params",O_RDONLY);
        if (fd < 0) return ret;
        if (read(fd, buffer, BUFFER_SIZE) > 0) {
                if (strstr(buffer, "access") != NULL) {
                        ret = 1;
                }
        }
        close(fd);       
        return ret;
}

#define SLEEP_TIME (1000*1000)

int main(int argc, char *argv[])
{
  int run_mode = 1;
  pid_t squeezelite = 0;
  pid_t brutefir    = 0;
  int st;
  int sqlite_play;
  pid_t exited;
  int loops = 0;
         
  usleep(20*1000*1000);

  close(2);
  close(1);
  close(0);
  open("/dev/null", O_RDONLY);
  open("/dev/null", O_WRONLY);
  open("/dev/null", O_WRONLY);
       
  signal(SIGHUP, signal_callback_handler);

  set_rt_priority(10);

  need_reload = 1;

  for (;;) {

    if (need_reload) {
      /* run at start, when play stopped or signal HUP send to sqbrctl */
      need_reload = 0;

      run_mode = 1;
      brutefir = stop_brutefir(brutefir);
      read_sqbrctl_conf();
      squeezelite = start_squeezelite(squeezelite,SQLITE_LOOP_PLAY);
    }

    usleep(SLEEP_TIME);

    if ((run_mode == 2) && (room_correction == 0)) {
        sqlite_play = sqlite_playing_direct();
        if (sqlite_play == 0) need_reload = 1;
    }

    if ((run_mode == 1) || (room_correction)) {

          sqlite_play = sqlite_playing_loop();

            if ((run_mode == 1) && (sqlite_play != 0)) {
                run_mode = 2;
                if (room_correction) {
                        brutefir = start_brutefir(brutefir);
                } else {
                              squeezelite = start_squeezelite(squeezelite,SQLITE_DIRECT_PLAY);
                }
        }
    }

    if ((run_mode == 2) && (room_correction) && (sqlite_play == 0)) need_reload = 1;

    if (++loops > 10) {
        loops = 0;
            exited = waitpid(-1, &st, WNOHANG);
            if (WIFEXITED(st)) {
                if (exited == squeezelite) {
                              squeezelite = start_squeezelite(0,SQLITE_LOOP_PLAY);
                    }
                if (exited == brutefir) {
                              if (run_mode == 2) {
                                      brutefir = start_brutefir(0);
                              }
                    }
            }
    }
  }

  return 0;
}

Brutefir config file /etc/drc/brutefir.conf:
Code:

float_bits: 64; # internal floating point precision
sampling_rate: 96000; # sampling rate in Hz of audio interfaces
filter_length: 32768; # length of filters
overflow_warnings: false; # echo warnings to stderr if overflow occurs
show_progress: false; # echo filtering progress to stderr
max_dither_table_size: 0; # maximum size in bytes of precalculated dither
allow_poll_mode: false; # allow use of input poll mode
modules_path: "/usr/lib/"; # extra path where to find BruteFIR modules
monitor_rate: false; # monitor sample rate
powersave: false; # pause filtering when input is zero
lock_memory: true; # try to lock memory if realtime prio is set
sdf_length: 63; # subsample filter length
convolver_config: "/etc/drc/convolver_config"; # location of convolver config file

coeff "drc_l" {
filename: "/etc/drc/filter-l.pcm";
format: "FLOAT_LE"; # file format
attenuation: 5.0; # attenuation in dB
shared_mem: true;
};

coeff "drc_r" {
filename: "/etc/drc/filter-r.pcm";
format: "FLOAT_LE"; # file format
attenuation: 5.0; # attenuation in dB
shared_mem: true;
};

## INPUT DEFAULTS ##
# module and parameters to get audio

input "left_in", "right_in" {
device: "alsa" { device: "hw:Loopback,0,0"; link: false; };
sample: "S32_LE";
channels: 2;
};

## OUTPUT DEFAULTS ##

output "left_out", "right_out" {
device: "alsa" { device: "front:CARD=D20,DEV=0"; link: false; ignore_xrun: true; };
sample: "S32_LE";
channels: 2;
dither: true;
};

## FILTER DEFAULTS ##

filter "l_filter" {
from_inputs: "left_in"/5.0;
to_outputs: "left_out"/0.0;
coeff: "drc_l";
};

filter "r_filter" {
from_inputs: "right_in"/5.0;
to_outputs: "right_out"/0.0;
coeff: "drc_r";
};

sqbrctl config file /etc/drc/sqbrctl.conf:
Code:

roomcorrection:on
upsample:on

I also made independent Kodi plugin visualisation, getting information from lms. It takes screen from Kodi when squeezelite starts to play and goes away when squeezelite is stopped or paused. Display is simple, just showing stream source (NAS, Spotify, Tidal), disc cover image, song information, progress and Kodi artist fan images from internet as backgound (with fallback images from a directory). It is too big and messy (server ip hard coded to python code) to be in this post, but if anybody is interested I can send the zip file.

Bad photo from small cheap screen in my device:
Name:  jjcale_kodi_sqliteplugin_small.png
Views: 39
Size:  182.6 KB
Attached Images
  

Viewing all articles
Browse latest Browse all 2050

Trending Articles