Quantcast
Channel: Squeezebox : Community : Forums
Viewing all articles
Browse latest Browse all 6235

Alpine Linux Bluetooth and Squeezelite Experiments

$
0
0
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
 

Viewing all articles
Browse latest Browse all 6235

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>