Home of the Squeezebox™ & Transporter® network music players.
Results 1 to 8 of 8
  1. #1
    Senior Member
    Join Date
    Jul 2008
    Posts
    135

    Alpine Linux Bluetooth and Squeezelite Experiments

    I've been experimenting with bluetooth and squeezelite on Alpine Linux. I'm going to use this thread to document my misadventures. My initial goal was to take the Wyse 3030LT (N06D) pictured and have the onboard audio jack at the front, the Apple USB DAC at the rear, and bluetooth headphones all playing the same audio from squeezelite. Further, I wanted to be able to pair my phone and play audio from the phone source (eg. Amazon Prime Music) over the same outputs without interfering with squeezelite.

    I was able to accomplish that so I will document what I have so far in case it's of any interest. This may already be well documented here on the forum but I haven't really looked, so forgive me if this is all repeated info.

    I tried many different variations of the alsa /etc/asound.conf some worked, some didn't. The one I have below will likely change, I'm not really convinced yet of the right approach to take. I know pCP is supporting bluetooth now and I've read the main thread on it but I haven't installed it or looked at any of the scripts to see how they are handling it. It would be cool to get some feedback from Paul and team on that, or feedback in general from anyone! I had an asound.conf that didn't use the Loopback device at all but something was telling me I needed to feed everything through the loopback and have a separate alsaloop process for each device. As I said above, not convinced this is the way to go. I also had a sort of working solution with two local squeezebox processes in a sync group but that felt pretty ugly and I ditched it fairly quickly.

    The basic idea here is that the default playback device is the Loopback and stereo audio is duplicated across multiple Loopback channels. Each alsaloop instance gets the same audio from a different Loopback output and feeds a different hardware output. The bt-pair.sh script is my clunky solution to managing pairing of new devices though still very much a work in progress. The script uses a LMS response on the status of the local squeezelite player and greps for certain words to decide whether to kick off the pairing process or exit. So to initiate, you select your player in LMS and play a track I added to the library that has "bluetooth-player" in the track name. Then you power off the player from LMS, which fires the script and both criteria are met: the correct track title and power state, then you pair from the phone.

    I'm going to skip all the standard Alpine setup steps I take and just focus on the squeezelite/bluetooth/alsa stuff. I added the following packages:

    alsa-utils
    bluez-alsa
    squeezelite

    linux-lts-5.4.49-r0.apk
    - I had to rebuild the kernel to enable the snd-aloop module. This should be enabled in future stock Alpine kernels, see:
    https://gitlab.alpinelinux.org/alpin...-/issues/11699

    Configure things to run at boot:

    rc-update add squeezelite
    rc-update add alsa
    rc-update add bluealsa
    rc-update add local

    I'm using the following squeezelite options set in /etc/conf.d/squeezelite:
    Code:
    SL_OPTS="-n Desk -S /home/sodface/bt-pair.sh"
    More on the -S option later:
    Code:
    -S <Power Script>	Absolute path to script to launch on power commands from LMS
    Added the following line to the /etc/init.d/bluealsa startup script:
    Code:
    command_args="-S -p a2dp-source -p a2dp-sink"
    Using the following settings in /etc/bluetooth/main.conf
    Code:
    Name = Desk
    AlwaysPairable = true
    AutoEnable = true
    Added snd-aloop to /etc/modules:
    Code:
    af_packet
    ipv6
    snd-aloop
    Created a local startup script in /etc/local.d/ with the following commands:
    Code:
    bluetoothctl discoverable off
    bluealsa-aplay 00:00:00:00:00:00 &
    alsaloop -C hw:Loopback,1,0 -P hw:A,0 -t 500000 &
    alsaloop -C hw:Loopback,1,1 -P hw:PCH,0 -t 500000 &
    alsaloop -C hw:Loopback,1,2 -P "btheadset" -t 1100000 &
    Contents of /etc/asound.conf
    Code:
    pcm.!default plug:aloopx
    
    ctl.!default {
      type hw
      card PCH 
    }
    
    pcm.aloopx {
      type route;
      slave.pcm {
          type multi;
          slaves.a.pcm "aloop0";
          slaves.b.pcm "aloop1";
          slaves.c.pcm "aloop2";
          slaves.d.pcm "aloop3";
          slaves.a.channels 2;
          slaves.b.channels 2;
          slaves.c.channels 2;
          slaves.d.channels 2;
          bindings.0.slave a;
          bindings.0.channel 0;
          bindings.1.slave a;
          bindings.1.channel 1;
    
          bindings.2.slave b;
          bindings.2.channel 0;
          bindings.3.slave b;
          bindings.3.channel 1;
    
          bindings.4.slave c;  
          bindings.4.channel 0;
          bindings.5.slave c;  
          bindings.5.channel 1;
    
          bindings.6.slave d;
          bindings.6.channel 0;
          bindings.7.slave d;
          bindings.7.channel 1;
      }
    
      ttable.0.0 1;
      ttable.1.1 1;
    
      ttable.0.2 1;
      ttable.1.3 1;
    
      ttable.0.4 1;
      ttable.1.5 1;
    
      ttable.0.6 1;
      ttable.1.7 1;
    }
    
    pcm.aloop0 {
       type dmix
       ipc_key 1024
       slave {
           pcm "hw:Loopback,0,0"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop1 {
       type dmix
       ipc_key 2048
       slave {
           pcm "hw:Loopback,0,1"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop2 {                 
       type dmix                 
       ipc_key 4096              
       slave {                   
           pcm "hw:Loopback,0,2"          
           period_time 0         
           period_size 2048       
           buffer_size 65536   
           buffer_time 0       
           periods 128         
           rate 48000          
           channels 2          
        }                      
        bindings {             
           0 0
           1 1
        }                      
    }
    
    pcm.aloop3 {                 
       type dmix                 
       ipc_key 8092              
       slave {                   
           pcm "hw:Loopback,0,3" 
           period_time 0        
           period_size 2048     
           buffer_size 65536    
           buffer_time 0        
           periods 128          
           rate 48000           
           channels 2           
        }                       
        bindings {              
           0 0                  
           1 1                  
        }                       
    }
    
    # Bluetooth headset
    pcm.btheadset {
       type plug 
       slave.pcm {
           type bluealsa
           device "20:9B:A5:5B:B7:A9"
           profile "a2dp"
        }
        hint {
           show on
           description "VMODA Crossfade Bluetooth Headset"
        }
    }
    
    ctl.aloopx {
       type hw
       card "Loopback"
    }
    Contents of the bt-pair.sh script that is fired on squeezelite power on/off commands:

    Code:
    #!/bin/sh
    
    uptime=$(cat /proc/uptime | cut -d'.' -f1)
    
    if [ ${uptime} -le 60 ]
    then
      exit
    fi
    
    lmsip=$(netstat -tn | grep 3483 | tr -s ' ' | cut -d ' ' -f5 | cut -d ':' -f1)
    iface=$(arp -an | grep ${lmsip} | tr -s ' ' | cut -d' ' -f7)
    read mac < /sys/class/net/${iface}/address
    btplayer="bluetooth-player"
    
    post=$(cat <<END_HEREDOC
    POST /jsonrpc.js HTTP/1.1
    HOST: ${lmsip}:9000
    Content-Type: application/json; charset=utf-8
    Content-Length: 97
    
    {"id":1,"method":"slim.request","params":["${mac}",["status","-","1","tags:acgltys"]]}
    END_HEREDOC
    )
    
    if ! (echo -e "${post}"; sleep .2) | nc ${lmsip} 9000 | grep "${btplayer}" | grep -q '"power":0'
    then
      exit
    fi
    
    i=0
    
    bluetoothctl discoverable on
    prepair=$(bluetoothctl paired-devices | cut -d' ' -f2)
    
    while [ $i -le 30 ]
    do  
      newpair=$(bluetoothctl paired-devices | cut -d' ' -f2)
      newdev=$(printf "${prepair}\n${newpair}\n" | sort | uniq -u)
      if [ "${newdev}" ]
      then
        bluetoothctl trust ${newdev}
        bluetoothctl connect ${newdev}
        break
      else
        i=$(( $i +1 ))
       sleep 2
      fi
    done
    
    bluetoothctl discoverable off
    Attached Images Attached Images  
    Last edited by sodface; 2020-07-04 at 10:28.

  2. #2
    Senior Member
    Join Date
    Jul 2008
    Posts
    135
    Here's the next iteration of the bt-pair.sh script. Now using two different tracks, one for a player and one for a speaker. Also switched to continuing the script on power on status and exiting immediately on power off instead of the other way around. For speakers, I'm using an rssi filter setting to reduce the number of devices that show up in a scan. Weaker devices are filtered out so you have to have the speaker close to the base station (in my case the 3030LT pictured above) for initial pairing to be successful. Trying to keep it as "hands off" as possible. I know it's hacky.

    Code:
    #!/bin/sh
    
    if [ $1 -eq 0 ]
    then
      exit
    fi
    
    uptime=$(cat /proc/uptime | cut -d'.' -f1)
    
    if [ ${uptime} -le 60 ]
    then
      exit
    fi
    
    lmsip=$(netstat -tn | grep 3483 | tr -s ' ' | cut -d ' ' -f5 | cut -d ':' -f1)
    iface=$(arp -an | grep ${lmsip} | tr -s ' ' | cut -d' ' -f7)
    read mac < /sys/class/net/${iface}/address
    btplayer="bluetooth-player"
    btspeaker="bluetooth-speaker"
    rssi="-60"
    
    post=$(cat <<END_HEREDOC
    POST /jsonrpc.js HTTP/1.1
    HOST: ${lmsip}:9000
    Content-Type: application/json; charset=utf-8
    Content-Length: 97
    
    {"id":1,"method":"slim.request","params":["${mac}",["status","-","1","tags:acgltys"]]}
    END_HEREDOC
    )
    
    if (echo -e "${post}"; sleep .2) | nc ${lmsip} 9000 | grep -q "${btplayer}"
    then
      i=0
      bluetoothctl discoverable on
      prepair=$(bluetoothctl paired-devices | cut -d' ' -f2)
      while [ $i -le 30 ]
      do
        sleep 2
        newpair=$(bluetoothctl devices | cut -d' ' -f2)
        newdev=$(printf "${prepair}\n${newpair}\n" | sort | uniq -u)
        if [ "${newdev}" ]
        then
          bluetoothctl pair ${newdev}
          bluetoothctl trust ${newdev}
          bluetoothctl connect ${newdev}
          break
        else
          i=$(( $i +1 ))
        fi
      done
      bluetoothctl discoverable off
    elif (echo -e "${post}"; sleep .2) | nc ${lmsip} 9000 | grep -q "${btspeaker}"
    then
      i=0
      prescan=$(bluetoothctl devices)
      while [ $i -le 6 ]
      do
        (printf "scan.rssi ${rssi}\nscan.rssi\nscan on\n"; sleep 10) | bluetoothctl
        sleep .2
        newscan=$(bluetoothctl devices)
        newdev=$(printf "${prescan}\n${newscan}\n" | sort | uniq -u | cut -d' ' -f2)
        if [ "${newdev}" ]
        then
          bluetoothctl pair ${newdev}
          bluetoothctl trust ${newdev}
          bluetoothctl connect ${newdev}
          break
        fi
      i=$(( $i +1 ))
      done
    fi
    Last edited by sodface; 2020-07-07 at 20:17.

  3. #3
    Senior Member
    Join Date
    Jul 2008
    Posts
    135
    I noticed bluetoothd throwing some errors in syslog related to uinput, a module which wasn't loaded, so I added it to /etc/modules. This was fortunate as soon after I realized I could use inotifywait to watch for event devices created by uinput when a bluetooth device connects to the system.

    Code:
    apk add inotify-tools
    This is the first whack at a script to restart the alsaloop process when my bluetooth headset connects. On disconnect I'm not doing anything, just letting the alsaloop process error out and then restarting it via the script next time it connects.

    Code:
    #!/bin/sh
    
    while :
    do
      if inotifywait -qq -r -e create /dev/input/
      then
        pdevs=$(bluetoothctl paired-devices | cut -d' ' -f2)
        for pdev in ${pdevs}
        do
          if bluetoothctl info "${pdev}" | tr -d '\n' | grep -Eq 'Connected: yes.*Audio Sink'
          then
            if ! ps | grep "[D]EV=${pdev}"
            then
              for i in $(seq 0 7)
              do
                if ! ps | grep -q "[L]oopback,1,${i}"
                then
                  break
                fi
              done
              alsaloop -d -C hw:Loopback,1,${i} -P bluealsa:SRV=org.bluealsa,DEV=${pdev},PROFILE=a2dp -t 500000
            fi
          fi
        done
      fi
    done
    I think that's most of the hacky pieces in place?
    Last edited by sodface; 2020-07-07 at 20:09.

  4. #4
    Senior Member
    Join Date
    Jul 2008
    Posts
    135
    Here's my latest configs. Removed all references to real hardware from /etc/asound.conf and now have stereo audio duplicated across all 8 loopback channels. Still not really sure about all the settings here and my feeling is this could be greatly simplified somehow but for now this is tested and works:

    Code:
    # duplicate audio to all Loopback channels
    pcm.!default plug:aloopx
    
    pcm.aloopx {
      type route;
      slave.pcm {
          type multi;
          slaves.a.pcm "aloop0";
          slaves.b.pcm "aloop1";
          slaves.c.pcm "aloop2";
          slaves.d.pcm "aloop3";
          slaves.e.pcm "aloop4";
          slaves.f.pcm "aloop5";
          slaves.g.pcm "aloop6";
          slaves.h.pcm "aloop7";
          slaves.a.channels 2;
          slaves.b.channels 2;
          slaves.c.channels 2;
          slaves.d.channels 2;
          slaves.e.channels 2;
          slaves.f.channels 2;
          slaves.g.channels 2;
          slaves.h.channels 2;
          bindings.0.slave a;
          bindings.0.channel 0;
          bindings.1.slave a;
          bindings.1.channel 1;
    
          bindings.2.slave b;
          bindings.2.channel 0;
          bindings.3.slave b;
          bindings.3.channel 1;
    
          bindings.4.slave c;
          bindings.4.channel 0;
          bindings.5.slave c;
          bindings.5.channel 1;
    
          bindings.6.slave d;
          bindings.6.channel 0;
          bindings.7.slave d;
          bindings.7.channel 1;
    
          bindings.8.slave e;
          bindings.8.channel 0;
          bindings.9.slave e;
          bindings.9.channel 1;
    
          bindings.10.slave f;
          bindings.10.channel 0;
          bindings.11.slave f;
          bindings.11.channel 1;
    
          bindings.12.slave g;
          bindings.12.channel 0;
          bindings.13.slave g;
          bindings.13.channel 1;
    
          bindings.14.slave h;
          bindings.14.channel 0;
          bindings.15.slave h;
          bindings.15.channel 1;
      }
    
      ttable.0.0 1;
      ttable.1.1 1;
    
      ttable.0.2 1;
      ttable.1.3 1;
    
      ttable.0.4 1;
      ttable.1.5 1;
    
      ttable.0.6 1;
      ttable.1.7 1;
    
      ttable.0.8 1;
      ttable.1.9 1;
    
      ttable.0.10 1;
      ttable.1.11 1;
    
      ttable.0.12 1;
      ttable.1.13 1;
    
      ttable.0.14 1;
      ttable.1.15 1;
    }
    
    pcm.aloop0 {
       type dmix
       ipc_key 1024
       slave {
           pcm "hw:Loopback,0,0"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop1 {
       type dmix
       ipc_key 2048
       slave {
           pcm "hw:Loopback,0,1"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop2 {
       type dmix
       ipc_key 4096
       slave {
           pcm "hw:Loopback,0,2"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop3 {
       type dmix
       ipc_key 8092
       slave {
           pcm "hw:Loopback,0,3"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop4 {
       type dmix
       ipc_key 16184
       slave {
           pcm "hw:Loopback,0,4"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop5 {
       type dmix
       ipc_key 32368
       slave {
           pcm "hw:Loopback,0,5"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop6 {
       type dmix
       ipc_key 64736
       slave {
           pcm "hw:Loopback,0,6"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop7 {
       type dmix
       ipc_key 129472
       slave {
           pcm "hw:Loopback,0,7"
           period_time 0
           period_size 2048
           buffer_size 65536
           buffer_time 0
           periods 128
           rate 48000
           channels 2
        }
        bindings {
           0 0
           1 1
        }
    }
    These are the three commands I have under /etc/local.d/AudioConfig.start to run at boot:
    Code:
    bluetoothctl discoverable off
    bluealsa-aplay 00:00:00:00:00:00 &
    /home/sodface/alsaloop.sh true &
    Contents of the alsaloop.sh script which starts an instance for each sound card it finds except for the Loopback device then sits and waits for bluetooth audio sink device connections and starts an alsaloop instance for them as required:

    Code:
    #!/bin/sh
    
    if [ $1 ]
    then
      for card in $(cat /proc/asound/cards | cut -d' ' -f3 | tr -d '[' | grep -v Loopback | tr -s '\n')
      do
        for i in $(seq 0 7)
          do
            if ! ps | grep -q "[L]oopback,1,${i}"
            then
              break
            fi
          done
        alsaloop -d -C hw:Loopback,1,${i} -P "hw:${card},0" -t 250000
      done
    fi
    
    while :
    do
      if inotifywait -qq -r -e create /dev/input/
      then
        pdevs=$(bluetoothctl paired-devices | cut -d' ' -f2)
        for pdev in ${pdevs}
        do
          if bluetoothctl info "${pdev}" | tr -d '\n' | grep -Eq 'Connected: yes.*Audio Sink'
          then
            if ! ps | grep "[D]EV=${pdev}"
            then
              for i in $(seq 0 7)
              do
                if ! ps | grep -q "[L]oopback,1,${i}"
                then
                  break
                fi
              done
              alsaloop -d -S 1 -C hw:Loopback,1,${i} -P bluealsa:SRV=org.bluealsa,DEV=${pdev},PROFILE=a2dp -t 450000
            fi
          fi
        done
      fi
    done
    And the contents of the bt-pair.sh script which is fired on a squeezelite power command and decides what to do based on the title of the currently playing squeezelite track:

    Code:
    #!/bin/sh
    
    if [ $1 -eq 0 ]
    then
      exit
    fi
    
    uptime=$(cat /proc/uptime | cut -d'.' -f1)
    
    if [ ${uptime} -le 60 ]
    then
      exit
    fi
    
    lmsip=$(netstat -tn | grep 3483 | tr -s ' ' | cut -d ' ' -f5 | cut -d ':' -f1)
    iface=$(arp -an | grep ${lmsip} | tr -s ' ' | cut -d' ' -f7)
    read mac < /sys/class/net/${iface}/address
    btplayer="bluetooth-player"
    btspeaker="bluetooth-speaker"
    rssi="-60"
    
    post=$(cat <<END_HEREDOC
    POST /jsonrpc.js HTTP/1.1
    HOST: ${lmsip}:9000
    Content-Type: application/json; charset=utf-8
    Content-Length: 97
    
    {"id":1,"method":"slim.request","params":["${mac}",["status","-","1","tags:acgltys"]]}
    END_HEREDOC
    )
    
    if (echo -e "${post}"; sleep .2) | nc ${lmsip} 9000 | grep -q "${btplayer}"
    then
      i=0
      bluetoothctl discoverable on
      prepair=$(bluetoothctl paired-devices | cut -d' ' -f2)
      while [ $i -le 30 ]
      do
        sleep 2
        newpair=$(bluetoothctl paired-devices | cut -d' ' -f2)
        newdev=$(printf "${prepair}\n${newpair}\n" | sort | uniq -u)
        if [ "${newdev}" ]                                  
        then 
          #bluetoothctl pair ${newdev}
          bluetoothctl trust ${newdev}
          bluetoothctl connect ${newdev}
          break
        else 
          i=$(( $i +1 ))
        fi   
      done   
      bluetoothctl discoverable off
    elif (echo -e "${post}"; sleep .2) | nc ${lmsip} 9000 | grep -q "${btspeaker}"
    then     
      i=0    
      prescan=$(bluetoothctl devices)
      while [ $i -le 6 ]
      do     
        (printf "scan.rssi ${rssi}\nscan.rssi\nscan on\n"; sleep 10) | bluetoothctl
        sleep .2
        newscan=$(bluetoothctl devices)
        newdev=$(printf "${prescan}\n${newscan}\n" | sort | uniq -u | cut -d' ' -f2)
        if [ "${newdev}" ]
        then 
          bluetoothctl pair ${newdev}
          bluetoothctl trust ${newdev}
          bluetoothctl connect ${newdev}
          break
        fi   
      i=$(( $i +1 ))
      done   
    fi
    This is working ok for now. Probably won't update this thread unless I have any major changes but I'm going to continue to tinker with it. Only real "issue" now is the audio sync between the local sound card outputs and a connected BT speaker. I can tweak the alsaloop -t values manually and dial them in pretty close but it seems to need adjustments on every connect/disconnect. Not really an issue for me since I'm using a headset and not trying to listen to all the outputs at the same time.
    Last edited by sodface; 2020-07-09 at 04:10.

  5. #5
    Senior Member
    Join Date
    Jul 2008
    Posts
    135
    I did some updates:

    -- redid everything into a single script run at startup and on squeezelite power on, actions dependent on current track title
    -- got rid of the three separate json queries. Finally figured out how to do it once and assign to a variable, just need a space after the opening $(
    -- simplified the json query to just get current song title, previously I recycled a query from another thread which had a bunch of tags I didn't need for this
    -- had to add back in some buffer settings into /etc/asound.conf (I'd removed them completely) because bluealsa-aplay dies without them.
    -- I tried a few different alsa utils to see if I could programmatically determine the delay between the onboard audio and bluetooth speaker but failed. Have added a manual sync method where every time you toggle the power on squeezelite it increases the latency to the onboard audio. You just keep doing it until it sounds right.
    -- got rid of a useless use of cat

    I've tested everything again and it's all working well. I paired my headphones, my phone, and my wife's phone and was able to play prime music from my phone, youtube audio from my wife's phone, music from squeezelite all at the same time streamed to my bluetooth headset synchronized with the onboard audio. Not that you'd ever want to do that, but it worked.

    Next I need to see if I can determine and then send sync settings to LMS so the squeezelite player on this box is in sync with the rest of the players in the house, currently it is not, because of the various buffer settings.

    /etc/asound.conf
    Code:
    # duplicate audio to all Loopback channels
    pcm.!default plug:aloopx
    
    pcm.aloopx {
      type route;
      slave.pcm {
          type multi;
          slaves.a.pcm "aloop0";
          slaves.b.pcm "aloop1";
          slaves.c.pcm "aloop2";
          slaves.d.pcm "aloop3";
          slaves.e.pcm "aloop4";
          slaves.f.pcm "aloop5";
          slaves.g.pcm "aloop6";
          slaves.h.pcm "aloop7";
          slaves.a.channels 2;
          slaves.b.channels 2;
          slaves.c.channels 2;
          slaves.d.channels 2;
          slaves.e.channels 2;
          slaves.f.channels 2;
          slaves.g.channels 2;
          slaves.h.channels 2;
          bindings.0.slave a;
          bindings.0.channel 0;
          bindings.1.slave a;
          bindings.1.channel 1;
    
          bindings.2.slave b;
          bindings.2.channel 0;
          bindings.3.slave b;
          bindings.3.channel 1;
    
          bindings.4.slave c;
          bindings.4.channel 0;
          bindings.5.slave c;
          bindings.5.channel 1;
    
          bindings.6.slave d;
          bindings.6.channel 0;
          bindings.7.slave d;
          bindings.7.channel 1;
    
          bindings.8.slave e;
          bindings.8.channel 0;
          bindings.9.slave e;
          bindings.9.channel 1;
    
          bindings.10.slave f;
          bindings.10.channel 0;
          bindings.11.slave f;
          bindings.11.channel 1;
    
          bindings.12.slave g;
          bindings.12.channel 0;
          bindings.13.slave g;
          bindings.13.channel 1;
    
          bindings.14.slave h;
          bindings.14.channel 0;
          bindings.15.slave h;
          bindings.15.channel 1;
    
      }
    
      ttable.0.0 1;
      ttable.1.1 1;
    
      ttable.0.2 1;
      ttable.1.3 1;
    
      ttable.0.4 1;
      ttable.1.5 1;
    
      ttable.0.6 1;
      ttable.1.7 1;
    
      ttable.0.8 1;
      ttable.1.9 1;
    
      ttable.0.10 1;
      ttable.1.11 1;
    
      ttable.0.12 1;
      ttable.1.13 1;
    
      ttable.0.14 1;
      ttable.1.15 1;
    }
    
    pcm.aloop0 {
       type dmix
       ipc_key 1024
       slave {
           pcm "hw:Loopback,0,0"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop1 {
       type dmix
       ipc_key 2048
       slave {
           pcm "hw:Loopback,0,1"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop2 {
       type dmix
       ipc_key 4096
       slave {
           pcm "hw:Loopback,0,2"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop3 {
       type dmix
       ipc_key 8092
       slave {
           pcm "hw:Loopback,0,3"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop4 {
       type dmix
       ipc_key 16184
       slave {
           pcm "hw:Loopback,0,4"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop5 {
       type dmix
       ipc_key 32368
       slave {
           pcm "hw:Loopback,0,5"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop6 {
       type dmix
       ipc_key 64736
       slave {
           pcm "hw:Loopback,0,6"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    
    pcm.aloop7 {
       type dmix
       ipc_key 129472
       slave {
           pcm "hw:Loopback,0,7"
           rate 48000
           channels 2
           period_size 1024
           buffer_size 8192
        }
        bindings {
           0 0
           1 1
        }
    }
    /etc/local.d/AudioConfig.start
    Code:
    su - squeezelite -s /bin/sh -c '/home/sodface/AudioConfig.sh boot &'
    /home/sodface/AudioConfig.sh
    Code:
    #!/bin/sh
    
    lcard=15000
    lbt=15000
    lstep=1500
    
    __start_cards()
    {
      if [ $1 ]
      then
        lcard=$1
      fi
      for card in $(cat /proc/asound/cards | cut -d' ' -f3 | tr -d '[' | grep -v Loopback | tr -s '\n')
      do
        for i in $(seq 0 7)
          do
            if ! ps | grep -q "[L]oopback,1,${i}"
            then
              break
            fi
          done
        alsaloop -d -C hw:Loopback,1,${i} -P "hw:${card},0" -S5 -l ${lcard}
      done
    }
    
    __watch_bluetooth()
    {
      while :
      do
        if inotifywait -qq -r -e create /dev/input/
        then
          pdevs=$(bluetoothctl paired-devices | cut -d' ' -f2)
          for pdev in ${pdevs}
          do
            if bluetoothctl info "${pdev}" | tr -d '\n' | grep -Eq 'Connected: yes.*Audio Sink'
            then
              if ! ps | grep "[D]EV=${pdev}"
              then
                for i in $(seq 0 7)
                do
                  if ! ps | grep -q "[L]oopback,1,${i}"
                  then
                    break
                  fi
                done
                alsaloop -d -C hw:Loopback,1,${i} -P bluealsa:SRV=org.bluealsa,DEV=${pdev},PROFILE=a2dp -S5 -l ${lbt}
              fi
            fi
          done
        fi
      done
    }
    
    if [ "$1" = "boot" ]
    then
      bluetoothctl discoverable off
      bluealsa-aplay 00:00:00:00:00:00 &
      __start_cards
      __watch_bluetooth
    elif [ $1 -eq 0 ]
    then
      exit
    fi
    
    uptime=$(cut -d'.' -f1 /proc/uptime)
    
    if [ ${uptime} -le 60 ]
    then
      exit
    fi
    
    lmsip=$(netstat -tn | grep 3483 | tr -s ' ' | cut -d ' ' -f5 | cut -d ':' -f1)
    iface=$(arp -an | grep ${lmsip} | tr -s ' ' | cut -d' ' -f7)
    read mac < /sys/class/net/${iface}/address
    btplayer="bluetooth-player"
    btspeaker="bluetooth-speaker"
    btsync="bluetooth-sync"
    lstep="1500"
    rssi="-60"
    
    post=$(cat <<END_HEREDOC
    POST /jsonrpc.js HTTP/1.1
    HOST: ${lmsip}:9000
    Content-Type: application/json; charset=utf-8
    Content-Length: 77
    
    {"id":1,"method":"slim.request","params":["${mac}",["title","?"]]}
    END_HEREDOC
    )
    
    response=$( (echo -e "${post}"; sleep .2) | nc ${lmsip} 9000 )
    
    if printf "${response}" | grep -q "${btplayer}"
    then
      i=0
      bluetoothctl discoverable on
      prepair=$(bluetoothctl paired-devices | cut -d' ' -f2)
      while [ $i -le 30 ]
      do
        sleep 2
        newpair=$(bluetoothctl paired-devices | cut -d' ' -f2)
        newdev=$(printf "${prepair}\n${newpair}\n" | sort | uniq -u)
        if [ "${newdev}" ]
        then
          #bluetoothctl pair ${newdev}
          bluetoothctl trust ${newdev}
          bluetoothctl connect ${newdev}
          break
        else
          i=$(( $i +1 ))
        fi
      done
      bluetoothctl discoverable off
    elif printf "${response}" | grep -q "${btspeaker}"
    then
      i=0
      prescan=$(bluetoothctl devices)
      while [ $i -le 6 ]
      do
        (printf "scan.rssi ${rssi}\nscan.rssi\nscan on\n"; sleep 10) | bluetoothctl
        sleep .2
        newscan=$(bluetoothctl devices)
        newdev=$(printf "${prescan}\n${newscan}\n" | sort | uniq -u | cut -d' ' -f2)
        if [ "${newdev}" ]
        then
          bluetoothctl pair ${newdev}
          bluetoothctl trust ${newdev}
          bluetoothctl connect ${newdev}
          break
        fi
      i=$(( $i +1 ))
      done
    elif printf "${response}" | grep -q "${btsync}"
    then
      lcardtmp=$(pgrep -fa hw:Loopback | grep -m1 -v bluealsa | rev | cut -d' ' -f1 | rev)
      lcard=$(( ${lcardtmp} + ${lstep} ))
      pids=$(pgrep -fa hw:Loopback | grep -v bluealsa | cut -d' ' -f1 | tr '\n' ' ')
      kill ${pids}
      __start_cards ${lcard} 
    fi

  6. #6
    Senior Member
    Join Date
    Jul 2008
    Posts
    135
    Cleaned up the script a little more. Switched over to a case statement from the if/elif, moved some variables to the top and got rid of another useless cat.

    I'm still a little surprised at how well this is working. I only have one bluetooth device though (a VModa headset which is really nice BTW), I'd like to test with more devices.

    Code:
    #!/bin/sh
    
    lcard=15000
    lbt=15000
    lstep=1500
    btplayer="bluetooth-player"
    btspeaker="bluetooth-speaker"
    btsync="bluetooth-sync"
    rssi="-60"
    
    __start_cards()
    {
      if [ $1 ]
      then
        lcard=$1
      fi
      for card in $(cut -d' ' -f3 /proc/asound/cards | tr -d '[' | grep -v Loopback | tr -s '\n')
      do
        for i in $(seq 0 7)
          do
            if ! ps | grep -q "[L]oopback,1,${i}"
            then
              break
            fi
          done
        alsaloop -d -C hw:Loopback,1,${i} -P "hw:${card},0" -S5 -l ${lcard}
      done
    }
    
    __watch_bluetooth()
    {
      while :
      do
        if inotifywait -qq -r -e create /dev/input/
        then
          pdevs=$(bluetoothctl paired-devices | cut -d' ' -f2)
          for pdev in ${pdevs}
          do
            if bluetoothctl info "${pdev}" | tr -d '\n' | grep -Eq 'Connected: yes.*Audio Sink'
            then
              if ! ps | grep "[D]EV=${pdev}"
              then
                for i in $(seq 0 7)
                do
                  if ! ps | grep -q "[L]oopback,1,${i}"
                  then
                    break
                  fi
                done
                alsaloop -d -C hw:Loopback,1,${i} -P bluealsa:SRV=org.bluealsa,DEV=${pdev},PROFILE=a2dp -S5 -l ${lbt}
              fi
            fi
          done
        fi
      done
    }
    
    if [ "$1" = "boot" ]
    then
      bluetoothctl discoverable off
      bluealsa-aplay 00:00:00:00:00:00 &
      __start_cards
      __watch_bluetooth
    elif [ $1 -eq 0 ]
    then
      exit
    fi
    
    uptime=$(cut -d'.' -f1 /proc/uptime)
    
    if [ ${uptime} -le 60 ]
    then
      exit
    fi
    
    lmsip=$(netstat -tn | grep 3483 | tr -s ' ' | cut -d' ' -f5 | cut -d ':' -f1)
    iface=$(arp -an | grep ${lmsip} | tr -s ' ' | cut -d' ' -f7)
    read mac < /sys/class/net/${iface}/address
    
    post=$(cat <<END_HEREDOC
    POST /jsonrpc.js HTTP/1.1
    HOST: ${lmsip}:9000
    Content-Type: application/json; charset=utf-8
    Content-Length: 77
    
    {"id":1,"method":"slim.request","params":["${mac}",["title","?"]]}
    END_HEREDOC
    )
    
    response=$( (echo -e "${post}"; sleep .2) | nc ${lmsip} 9000 | grep -o '\w*bluetooth-\w*' )
    i=0
    
    case "${response}" in
      "${btplayer}" )
        bluetoothctl discoverable on
        prepair=$(bluetoothctl paired-devices | cut -d' ' -f2)
        while [ $i -le 30 ]
        do
          sleep 2
          newpair=$(bluetoothctl paired-devices | cut -d' ' -f2)
          newdev=$(printf "${prepair}\n${newpair}\n" | sort | uniq -u)
          if [ "${newdev}" ]
          then
            bluetoothctl trust ${newdev}
            bluetoothctl connect ${newdev}
            break
          fi  
          i=$(( $i +1 ))
        done
        bluetoothctl discoverable off ;;
      "${btspeaker}" )
        prescan=$(bluetoothctl devices)
        while [ $i -le 6 ]
        do
          (printf "scan.rssi ${rssi}\nscan on\n"; sleep 5) | bluetoothctl
          sleep .2
          newscan=$(bluetoothctl devices)
          newdev=$(printf "${prescan}\n${newscan}\n" | sort | uniq -u | cut -d' ' -f2)
          if [ "${newdev}" ]
          then
            bluetoothctl pair ${newdev}
            bluetoothctl trust ${newdev}
            bluetoothctl connect ${newdev}
            break
          fi
          i=$(( $i +1 ))
        done ;;
      "${btsync}" )
        lcardtmp=$(pgrep -fa hw:Loopback | grep -m1 -v bluealsa | rev | cut -d' ' -f1 | rev)
        lcard=$(( ${lcardtmp} + ${lstep} ))
        pids=$(pgrep -fa hw:Loopback | grep -v bluealsa | cut -d' ' -f1 | tr '\n' ' ')
        kill ${pids}
        __start_cards ${lcard} ;;
    esac
    Last edited by sodface; 2020-07-17 at 19:31.

  7. #7
    Senior Member
    Join Date
    Jul 2008
    Posts
    135
    A few more changes to the script:

    -- switched from json to regular cli on 9090, simpler to parse, at least for this
    -- changed back to using the -t latency in usec argument for alsaloop
    -- changed the bt sync logic to get the elapsed track time and use that as the basis for the latency adjustment, that way you can go up and down instead of just up, but no lower than the starting -t latency, so pause the track, set the time slider and power squeezelite on and off to set the new latency setting for the onboard cards

    -- oh, and I borrowed a little bluetooth speaker for testing and it paired right up, so that was good.

    Code:
    #!/bin/sh
    
    tcard=300000
    tbt=375000
    btplayer="bluetooth-player"
    btspeaker="bluetooth-speaker"
    btsync="bluetooth-sync"
    rssi="-60"
    
    __start_cards()
    {
      if [ $1 ]
      then
        tcard=$1
      fi
      for card in $(cut -d' ' -f3 /proc/asound/cards | tr -d '[' | grep -v Loopback | tr -s '\n')
      do
        for i in $(seq 0 7)
          do
            if ! ps | grep -q "[L]oopback,1,${i}"
            then
              break
            fi
          done
        alsaloop -d -C hw:Loopback,1,${i} -P "hw:${card},0" -S5 -t ${tcard}
      done
    }
    
    __watch_bluetooth()
    {
      while :
      do
        if inotifywait -qq -r -e create /dev/input/
        then
          pdevs=$(bluetoothctl paired-devices | cut -d' ' -f2)
          for pdev in ${pdevs}
          do
            if bluetoothctl info "${pdev}" | tr -d '\n' | grep -Eq 'Connected: yes.*Audio Sink'
            then
              if ! ps | grep "[D]EV=${pdev}"
              then
                for i in $(seq 0 7)
                do
                  if ! ps | grep -q "[L]oopback,1,${i}"
                  then
                    break
                  fi
                done
                alsaloop -d -C hw:Loopback,1,${i} -P bluealsa:SRV=org.bluealsa,DEV=${pdev},PROFILE=a2dp -S5 -t ${tbt}
              fi
            fi
          done
        fi
      done
    }
    
    if [ "$1" = "boot" ]
    then
      bluetoothctl discoverable off
      bluealsa-aplay 00:00:00:00:00:00 &
      __start_cards
      __watch_bluetooth
    elif [ $1 -eq 0 ]
    then
      exit
    fi
    
    uptime=$(cut -d'.' -f1 /proc/uptime)
    
    if [ ${uptime} -le 60 ]
    then
      exit
    fi
    
    lmsip=$(netstat -tn | grep 3483 | tr -s ' ' | cut -d' ' -f5 | cut -d ':' -f1)
    iface=$(arp -an | grep ${lmsip} | tr -s ' ' | cut -d' ' -f7)
    read mac < /sys/class/net/${iface}/address
    
    response=$( (echo -e "${mac} title ?"; sleep .2) | nc ${lmsip} 9090 | cut -d' ' -f3 )
    i=0
    
    case "${response}" in
      "${btplayer}" )
        bluetoothctl discoverable on
        prepair=$(bluetoothctl paired-devices | cut -d' ' -f2)
        while [ $i -le 30 ]
        do
          sleep 2
          newpair=$(bluetoothctl paired-devices | cut -d' ' -f2)
          newdev=$(printf "${prepair}\n${newpair}\n" | sort | uniq -u)
          if [ "${newdev}" ]
          then
            bluetoothctl trust ${newdev}
            bluetoothctl connect ${newdev}
            break
          fi  
          i=$(( $i +1 ))
        done
        bluetoothctl discoverable off ;;
      "${btspeaker}" )
        prescan=$(bluetoothctl devices)
        while [ $i -le 6 ]
        do
          (printf "scan.rssi ${rssi}\nscan on\n"; sleep 5) | bluetoothctl
          sleep .2
          newscan=$(bluetoothctl devices)
          newdev=$(printf "${prescan}\n${newscan}\n" | sort | uniq -u | cut -d' ' -f2)
          if [ "${newdev}" ]
          then
            bluetoothctl pair ${newdev}
            bluetoothctl trust ${newdev}
            bluetoothctl connect ${newdev}
            break
          fi
          i=$(( $i +1 ))
        done ;;
      "${btsync}" )
        response=$( (echo -e "${mac} time ?"; sleep .2) | nc ${lmsip} 9090 | cut -d' ' -f3 )
        response=${response%.*}
        tadd=$(( ${response} * 10000 ))
        tcard=$(( ${tcard} + ${tadd} ))
        pids=$(pgrep -fa hw:Loopback | grep -v bluealsa | cut -d' ' -f1 | tr '\n' ' ')
        kill ${pids}
        __start_cards ${tcard} ;;
      esac

  8. #8
    Senior Member
    Join Date
    Jul 2008
    Posts
    135
    This'll probably be the last post in this thread (promise!?) - I moved sodface.com to a different server last night and kind of integrated a fossil scm instance along with it. I'd been working on the fossil integration locally but decided to push it up to the domain also. So you can find the latest version of the files mentioned in this thread here:

    http://www.sodface.com/fossil

    Under the alps folder. I plan on redoing the setup steps there soon also.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •