From 2740007da498faec658f82f6943bd462dc17684f Mon Sep 17 00:00:00 2001 From: Reed Wade Date: Sat, 10 Oct 2020 13:03:48 +0200 Subject: [PATCH] New package: bluez-alsa-bmix-1.0.0 --- srcpkgs/bluez-alsa-bmix/INSTALL | 10 + srcpkgs/bluez-alsa-bmix/REMOVE | 4 + srcpkgs/bluez-alsa-bmix/files/21-bmix.conf | 130 ++++++++ srcpkgs/bluez-alsa-bmix/files/bluez-bmix/run | 5 + srcpkgs/bluez-alsa-bmix/files/bmix.conf | 15 + srcpkgs/bluez-alsa-bmix/files/bmixd.bash | 330 +++++++++++++++++++ srcpkgs/bluez-alsa-bmix/template | 20 ++ 7 files changed, 514 insertions(+) create mode 100644 srcpkgs/bluez-alsa-bmix/INSTALL create mode 100644 srcpkgs/bluez-alsa-bmix/REMOVE create mode 100644 srcpkgs/bluez-alsa-bmix/files/21-bmix.conf create mode 100755 srcpkgs/bluez-alsa-bmix/files/bluez-bmix/run create mode 100644 srcpkgs/bluez-alsa-bmix/files/bmix.conf create mode 100644 srcpkgs/bluez-alsa-bmix/files/bmixd.bash create mode 100644 srcpkgs/bluez-alsa-bmix/template diff --git a/srcpkgs/bluez-alsa-bmix/INSTALL b/srcpkgs/bluez-alsa-bmix/INSTALL new file mode 100644 index 00000000000..2427aba1848 --- /dev/null +++ b/srcpkgs/bluez-alsa-bmix/INSTALL @@ -0,0 +1,10 @@ +# INSTALL +case "$ACTION" in +post) + if [ ! -d "/var/lib/bmix" ]; then + mkdir "/var/lib/bmix" + touch "/var/lib/bmix/bmix.conf" + chmod 660 "/var/lib/bmix/bmix.conf" + chown -R _bluez_bmix:audio "/var/lib/bmix" + fi +esac diff --git a/srcpkgs/bluez-alsa-bmix/REMOVE b/srcpkgs/bluez-alsa-bmix/REMOVE new file mode 100644 index 00000000000..532ccd92661 --- /dev/null +++ b/srcpkgs/bluez-alsa-bmix/REMOVE @@ -0,0 +1,4 @@ +# REMOVE +if [ -d "/var/lib/bmix" ]; then + rm -r "/var/lib/bmix" +fi diff --git a/srcpkgs/bluez-alsa-bmix/files/21-bmix.conf b/srcpkgs/bluez-alsa-bmix/files/21-bmix.conf new file mode 100644 index 00000000000..bf75f9b5155 --- /dev/null +++ b/srcpkgs/bluez-alsa-bmix/files/21-bmix.conf @@ -0,0 +1,130 @@ +pcm.bluealsa_raw { + @args [ DEV PROFILE DELAY SRV ] + @args.DEV { + type string + default { + @func refer + name defaults.bluealsa.device + } + } + @args.PROFILE { + type string + default { + @func refer + name defaults.bluealsa.profile + } + } + @args.DELAY { + type integer + default { + @func refer + name defaults.bluealsa.delay + } + } + @args.SRV { + type string + default { + @func refer + name defaults.bluealsa.service + } + } + type bluealsa + device $DEV + profile $PROFILE + delay $DELAY + service $SRV +} + +defaults.bmix { + loop 0 + channels 2 + rate 48000 + period 20000 +} + +pcm.bmix { + @args [ IPC_KEY LOOP CHANNELS RATE PERIOD ] + @args.IPC_KEY { + type integer + } + @args.LOOP { + type integer + default { + @func refer + name defaults.bmix.loop + } + } + @args.CHANNELS { + type integer + default { + @func refer + name defaults.bmix.channels + } + } + @args.RATE { + type integer + default { + @func refer + name defaults.bmix.rate + } + } + @args.PERIOD { + type integer + default { + @func refer + name defaults.bmix.period + } + } + type plug + slave { + pcm { + type dmix + ipc_key $IPC_KEY + ipc_perm 0660 + ipc_gid audio + slave { + channels $CHANNELS + pcm { + type hw + card "Loopback" + device 0 + subdevice $LOOP + format "s16_le" + rate $RATE + } + period_time $PERIOD + } + } + } + hint { + show { + @func refer + name defaults.namehint.basic + } + description "Bluetooth Audio - Mix Multiple Streams" + } +} + +ctl.bmix { + @args [ SRV BAT ] + @args.SRV { + type string + default { + @func refer + name defaults.bluealsa.service + } + } + @args.BAT { + type string + default { + @func refer + name defaults.bluealsa.battery + } + } + type bluealsa + service $SRV + battery $BAT +} + + + diff --git a/srcpkgs/bluez-alsa-bmix/files/bluez-bmix/run b/srcpkgs/bluez-alsa-bmix/files/bluez-bmix/run new file mode 100755 index 00000000000..42b12f908dd --- /dev/null +++ b/srcpkgs/bluez-alsa-bmix/files/bluez-bmix/run @@ -0,0 +1,5 @@ +#!/bin/sh +[ -r ./conf ] && . ./conf + +exec chpst -u _bluez_bmix:audio /bin/bmixd + diff --git a/srcpkgs/bluez-alsa-bmix/files/bmix.conf b/srcpkgs/bluez-alsa-bmix/files/bmix.conf new file mode 100644 index 00000000000..4aa3b79f35c --- /dev/null +++ b/srcpkgs/bluez-alsa-bmix/files/bmix.conf @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/srcpkgs/bluez-alsa-bmix/files/bmixd.bash b/srcpkgs/bluez-alsa-bmix/files/bmixd.bash new file mode 100644 index 00000000000..7cfc466d20e --- /dev/null +++ b/srcpkgs/bluez-alsa-bmix/files/bmixd.bash @@ -0,0 +1,330 @@ +#!/bin/bash + +# revision 0.2 + + +# select which profile types to use: "a2dp", "sco", or "all" +profiles="${BMIX_PROFILE:-all}" + +# d-bus service name for bluealsa +servicename="${BMIX_BLUEALSA_SRV:-org.bluealsa}" + +# experiment with higher latency if you experience underruns/overruns +latency="${BMIX_LATENCY:-50000}" + +# location of dynamic alsa configuration file +conffile="${BMIX_ALSA_CONF:-/var/lib/bmix/bmix.conf}" + +# dmix ipc will use 8 consecutive ipc key numbers starting with this value +ipc_key_start=${BMIX_IPC_KEY:-30000} + +# to reserve some Loopback substreams for other applications, set the lowest +# substream to use here: +lowest_loop_substream=${BMIX_LOOPBACK:-0} + +# name to use for default bluetooth pcm +default_pcm_name="${BMIX_DEFAULT_PCM_NAME:-bluetooth}" + +# extra arguments to apply to alsaloop +alsaloop_args="${BMIX_ALSALOOP_ARGS:---sync=none}" + + +ALSALOOP="alsaloop $alsaloop_args" + + +awk_parse_dbus_message=' + /member=PCMAdded/ { added = 1; next; } + /member=PCMRemoved/ { removed = 1; next; } + /member=NameOwnerChanged/ { printf("ServiceStopped\n"); fflush(); next; } + !/variant/ && /object path/ { + match($0, /dev_([[:xdigit:]_]+)\/(a2dp|hfp|hsp)(.*)/, arr) + if (RSTART > 0) { + brackets = 0 + addr = arr[1] + if (removed) { + if (arr[2] ~ /hfp|hsp/) + profile = "SCO" + else if (arr[2] ~ a2dp) + profile = "A2DP" + else + next + if (arr[3] ~ /source/) + printf("PCMRemoved %s %s %s\n", addr, profile, "source") + else if (arr[3] ~ /sink/) + printf("PCMRemoved %s %s %s\n", addr, profile, "sink") + else + next + fflush() + removed = 0 + next + } + profile = "" + mode = "" + format = "" + channels = "" + sampling = "" + next + } + } + /string "Transport"/ { t = 1; next; } + /string "Mode/ { m = 1; next; } + /string "Format"/ { f = 1; next; } + /string "Channels"/ { c = 1; next; } + /string "Sampling"/ { s = 1; next; } + t && /A2DP/ { profile = "A2DP"; t = 0; next; } + t && /HFP|HSP/ { profile = "SCO"; t = 0; next; } + m && /string/ { + sub(/^.*string /,"") + gsub("\"","") + mode = $0 + next + } + f && /uint16/ { format = $NF; f = 0; next; } + c && /byte/ { channels = $NF; c = 0; next; } + s && /uint32/ { sampling = $NF; s = 0; next; } + m && /)/ { m = 0; next; } + t && /)/ { t = 0; next; } + f && /)/ { f = 0; next; } + c && /)/ { c = 0; next; } + s && /)/ { s = 0; next; } + /\[/ { brackets++; next; } + addr && /]/ && (--brackets == 0) { + if (addr && profile && mode && format && channels && sampling) { + if (added) + printf("PCMAdded ") + printf("%s %s %s %s %s %s\n", addr, profile, mode, format, channels, sampling) + fflush() + added = 0 + } + } +' + +declare -A formats +formats[264]=U8 +formats[33296]=S16_LE +formats[33560]=S24_3LE +formats[33816]=S24_LE +formats[33824]=S32_LE +formats[8]=U8 # pre bluealsa v3.0.0 +formats[32784]=S16_LE # pre bluealsa v3.0.0 +formats[32792]=S24_LE # pre bluealsa v3.0.0 + +declare -A select_profile +select_profile[A2DP]=yes +select_profile[SCO]=yes + +profiles=${profiles^^?} +if [[ "$profiles" = SCO ]] ; then + select_profile[A2DP]=no +elif [[ "$profiles" = A2DP ]] ; then + select_profile[SCO]=no +fi + +declare -A pids +declare -A loops +declare -A name + +declare default_devid="" + +declare -i first_loop="$lowest_loop_substream" +declare -i num_loops=$(grep -c substream /proc/asound/Loopback/cable\#0) + +get_loop_by_devid() { + declare -n result=$1 + declare devid="$2" + declare i + result="" + for i in "${!loops[@]}" ; do + if [[ "$i" = "$devid" ]] ; then + result=${loops[$i]} + break + fi + done + if [[ -z "$result" ]] ; then + declare -i next_loop=$((first_loop + ${#loops[@]})) + if [[ $next_loop -lt $num_loops ]] ; then + loops["$devid"]=$next_loop + result=$next_loop + fi + fi +} + +launch_alsaloop() { + # arg1 devid + # arg2 loop + # arg3 addr + # arg4 profile + # arg5 format + # arg6 channels + # arg7 sample rate + declare devid="$1" loop=$2 addr="$3" profile="$4" format="$5" channels=$6 rate=$7 pid + + sleep 2 + $ALSALOOP -f "$format" -c "$channels" -r "$rate" -C hw:Loopback,1,$loop -P bluealsa_raw:SRV="$servicename",DEV="$addr",PROFILE="$profile",DELAY=0 -t "$latency" >/dev/null 2>&1 & + pid=$! + sleep 1 + if ! kill -0 $pid >/dev/null 2>&1; then + echo "failed to start alsaloop for $addr ($profile)" >&2 + return 1 + fi + pids["$devid"]=$pid +} + +create_alsa_config() { + # arg1 device id + # arg2 device alias + # arg3 profile + # arg4 channels + # arg5 rate + declare devid="$1" dev_alias="$2" profile="$3" + declare -i channels=$4 rate=$5 + declare -i ipc_key=$((ipc_key_start + loops[$devid])) + declare loop + + cat >> "$conffile" <<-EOF + pcm."${name[$devid]}".type empty + pcm."${name[$devid]}".slave.pcm "bmix:IPC_KEY=${ipc_key},LOOP=${loops[$devid]},CHANNELS=${channels},RATE=${rate},PERIOD=$(($latency / 2))" + pcm."${name[$devid]}".hint.show.@func refer + pcm."${name[$devid]}".hint.show.name defaults.namehint.basic + pcm."${name[$devid]}".hint.description "$dev_alias ($profile) Bluetooth Audio Playback" + EOF +} + +# get device alias from Bluez +get_alias() { + # arg1 name of variable to store result + # arg2 addr (underscored) + declare -n result="$1" + result=$(dbus-send --print-reply=literal --system --dest=org.bluez \ + /org/bluez/hci0/dev_$2 \ + org.freedesktop.DBus.Properties.Get string:"org.bluez.Device1" string:"Alias") + result=${result# variant } +} + +add_pcm() { + # arg1 addr (underscored) + # arg2 profile + # arg3 format + # arg4 channels + # arg5 sample rate + declare devid="$1,$2" profile="$2" format=${formats["$3"]} addr="${1//_/:}" + declare -i channels=$4 rate=$5 + declare loop dev_alias + + get_alias dev_alias "$1" + [[ -n "$dev_alias" ]] || return 1 + + get_loop_by_devid loop $devid + + if [[ -z "$loop" ]] ; then + echo "No free Loopback substreams" >&2 + return 1 + fi + launch_alsaloop "$devid" "$loop" "$addr" "$profile" "$format" "$channels" "$rate" \ + || return 1 + name["$devid"]="$dev_alias - $profile" + create_alsa_config "$devid" "$dev_alias" "$profile" "$channels" "$rate" +} + +remove_pcm() { + #arg1 devid + declare devid="$1" + sed -i '/^pcm."'"${name["$devid"]}"'/d' "$conffile" + kill "${pids["$devid"]}" 2>/dev/null + unset pids["$devid"] + unset name["$devid"] +} + +# make sure default name is alias for pcm that is currently connected +update_default() { + for devid in "${!name[@]}" ; do + [[ "$devid" = "$default_devid" ]] && return + done + declare -a arr=("${!name[@]}") + default_devid="${arr[0]}" + sed -i -e '/^pcm."'"$default_pcm_name"'/d' "$conffile" 2>/dev/null + if [[ -n "$default_devid" ]] ; then + cat >> "$conffile" <<-EOF + pcm."$default_pcm_name".type empty + pcm."$default_pcm_name".slave.pcm "${name[$default_devid]}" + pcm."$default_pcm_name".hint.show.@func refer + pcm."$default_pcm_name".hint.show.name defaults.namehint.basic + pcm."$default_pcm_name".hint.description "Default bluetooth playback" + EOF + fi +} + +# get list of connected devices +get_devices() { + dbus-send --print-reply --system --dest=org.bluealsa \ + /org/bluealsa org.bluealsa.Manager1.GetPCMs 2>/dev/null | gawk "$awk_parse_dbus_message" +} + +handle_device_added_event() { + if [[ "$4" = sink && "${select_profile["$3"]}" = yes ]] ; then + add_pcm "$2" "$3" "$5" "$6" "$7" + update_default + fi +} + +handle_device_removed_event() { + if [[ "$4" = sink ]] ; then + remove_pcm "$2,$3" + update_default + fi +} + +add_initial_devices() { + readarray -t devices < <(get_devices) + for device in "${devices[@]}" ; do + params=($device) + if [[ "${params[2]}" = sink && "${select_profile["${params[1]}"]}" = yes ]] ; then + add_pcm "${params[0]}" "${params[1]}" "${params[3]}" "${params[4]}" "${params[5]}" + fi + done + update_default +} + +# remove all pcms if bluealsa service terminates +handle_service_stopped_event() { + for pcm in "${!name[@]}" ; do + remove_pcm "$pcm" + done + update_default +} + +# delete all stale entries from alsa config file +echo "# DO NOT DELETE, DO NOT EDIT - automatically managed by bmixd" > "$conffile" + +# create a temporary named pipe to communicate with dbus monitor +PIPE=$(mktemp -u) +mkfifo $PIPE +# attach it to unused file descriptor FD +exec {FD}<>$PIPE +# unlink the named pipe +rm $PIPE + +# make sure the pipeline is shut down if this script interrupted +trap "kill %1; exec {FD}>&-; handle_service_stopped_event; exit 0" INT TERM + +# start dbus monitor in background +dbus-monitor --system "type='signal',sender='org.bluealsa',interface='org.bluealsa.Manager1'" "sender='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluealsa',arg2=''" 2>/dev/null | gawk "$awk_parse_dbus_message" >&$FD & + +# load initial set of connected devices +add_initial_devices + +# now listen for dbus signals +while read +do + case "$REPLY" in + PCMAdded*) + handle_device_added_event $REPLY + ;; + PCMRemoved*) + handle_device_removed_event $REPLY + ;; + ServiceStopped*) + handle_service_stopped_event + ;; + esac +done <&$FD diff --git a/srcpkgs/bluez-alsa-bmix/template b/srcpkgs/bluez-alsa-bmix/template new file mode 100644 index 00000000000..97f6c6231cc --- /dev/null +++ b/srcpkgs/bluez-alsa-bmix/template @@ -0,0 +1,20 @@ +# Template file for 'bluez-alsa-bmix' +pkgname=bluez-alsa-bmix +version=1.0.0 +revision=1 +build_style=meta +short_desc="Bluetooth Audio ALSA Backend - bmix daemon" +depends="bluez-alsa" +maintainer="Stacy Harper " +license="MIT" +homepage="https://github.com/Arkq/bluez-alsa/wiki/Using-bluealsa-with-dmix" +system_accounts="_bluez_bmix" +_bluez_bmix_groups="audio" +_bluez_bmix_homedir="/var/lib/bmix/" + +do_install() { + vinstall ${FILESDIR}/21-bmix.conf 644 /etc/alsa/conf.d + vinstall ${FILESDIR}/bmix.conf 644 /etc/dbus-1/system.d + vbin ${FILESDIR}/bmixd.bash bmixd + vsv bluez-bmix +}