用樹莓派 + Tube-Pi 當藍芽接收器播放音樂
自從發現自己多了一塊樹莓派的擴接板 Tube Pi 後,我就開使用它做一些無聊的事,
如 Audio Visualizer - 替音頻加上可視化特效 及 利用樹莓派 + Tube-Pi 播放 Kiss Radio 廣播。
最近我就在想,樹莓派 + Tube Pi 能不能當成藍芽接收器,讓它接收手機的音樂,再由 Tube Pi 輸出?最好是 headless 的方式。
結論是:可以的。
不過我在網路上找了很多資料,試了很多種方式,不知什麼原因,都無法成功。今天不死心,又胡搞瞎搞了一番,結果居然可以了。
雖然有點碰運氣,不過大致上的步驟我會把他記錄如下。
因為實在是修改了太多次,沒辦法完整記錄,有些地方可能無關緊要,不過我還是把他寫出來。
¶ 修改 /boot/config.txt
# 刪除下面這行,直接刪除或是前面加個 # 註解掉
# dtparam=audio=on
# 新增下面這行
dtoverlay=hifiberry-dac
重新開機。
¶ 安裝套件
sudo apt update
sudo apt install bluez pulseaudio-module-bluetooth python-gobject python-gobject-2 bluez-tools udev
sudo apt install libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev
¶ 更新 bluez 程式
原生的 bluez 的版本是 5.43,這一版似乎問題很多,所以我們要自行把他更新成最新版。
git clone git://git.kernel.org/pub/scm/bluetooth/bluez.git
cd bluez
git checkout 5.50
./configure --prefix=/usr --mandir=/usr/share/man --sysconfdir=/etc --localstatedir=/var --enable-experimental
make -j4
sudo make install
讓使用者 pi
擁有使用 bluetoothctl
的權限。
sudo adduser pi bluetooth
修改 Dbus 設定檔。
sudo cp /etc/dbus-1/system.d/bluetooth.conf /etc/dbus-1/system.d/bluetooth.conf.bak
sudo nano /etc/dbus-1/system.d/bluetooth.conf
# 在 <policy user="root"> 底下加入
<allow send_interface="org.bluez.ThermometerWatcher1"/>
<allow send_interface="org.bluez.HeartRateWatcher1"/>
<allow send_interface="org.bluez.CyclingSpeedWatcher1"/>
# 在 <!-- allow users of bluetooth group to communicate --> 底下加入
<policy group="bluetooth">
<allow send_destination="org.bluez"/>
</policy>
¶ 安裝 bluealsa
$ sudo apt install bluealsa
重新開機。
沒問題的話,應該可以看到 bluetoothctl
的版本是預期的 5.50。
pi@raspberrypi:~ $ bluetoothctl -v
bluetoothctl: 5.50
pi@raspberrypi:~ $
¶ 修改設定檔
把 pi
加入 pulseaudio
群組內。
$ sudo usermod -a -G lp pi
新增 /etc/bluetooth/audio.conf
檔案,並新增:
$ sudo nano /etc/bluetooth/audio.conf
# 加入
[General]
Enable = Source,Sink,Media,Socket
編輯 /etc/bluetooth/main.conf
,加入:
# 在 [General] 下加入
Enable = Source,Sink,Media
Class = 0x00041C
DiscoverableTimeout = 0
PairableTimeout = 0
0x00041C 代表藍芽可以支援 A2DP 協定。
修改 /etc/pulse/daemon.conf
:
resample-method = trivial
關於 resample-method
還有其他種選擇。
resample-method= The resampling algorithm to use.
Use one of src-sinc-best-quality, src-sinc-medium-quality, src-sinc-fastest, src-zero-order-hold, src-linear, trivial, speex-float-N, speex-fixed-N, ffmpeg. See the documentation of libsamplerate for an explanation for the different src- methods. The method trivial is the most basic algorithm implemented. If you’re tight on CPU consider using this. On the other hand it has the worst quality of them all. The Speex resamplers take an integer quality setting in the range 0…9 (bad…good). They exist in two flavours: fixed and float. The former uses fixed point numbers, the latter relies on floating point numbers. On most desktop CPUs the float point resmampler is a lot faster, and it also offers slightly better quality. See the output of dump-resample-methods for a complete list of all available resamplers. Defaults to speex-float-3. The --resample-method command line option takes precedence. Note that some modules overwrite or allow overwriting of the resampler to use.
執行 pulseaudio -D
。在這裡,我把他設成開機啟動。
新增 /etc/init.d/pulseaudio.sh
:
$ sudo nano /etc/init.d/pulseaudio.sh
# 加入以下內容
#! /bin/sh
#
# pulseaudio initscript
#
### BEGIN INIT INFO
# Provides: pulseaudio.sh
# Required-Start: $local_fs $remote_fs
# Required-Stop: $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts pulseaudio at startup
# Description: starts pulseaudio at startup
### END INIT INFO
# This is needed for bluetooth audio
pulseaudio -D
# 存檔跳出
$ sudo chmod 755 /etc/init.d/pulseaudio.sh
$ sudo update-rc.d pulseaudio.sh defaults
編輯 /etc/udev/rules.d/99-input.rules
檔案:
SUBSYSTEM="input", GROUP="input", MODE="0660"
KERNEL=="input[0-9]*", RUN+="/usr/lib/udev/bluetooth"
新增 /usr/lib/udev/bluetooth
檔案並將檔案權限改為 777 :
$ sudo mkdir /usr/lib/udev && cd /usr/lib/udev
$ sudo nano bluetooth && sudo chmod bluetooth
# 加入以下內容
#!/bin/bash
# This script is called by udev when you link a bluetooth device with your computer
# It's called to add or remove the device from pulseaudio
#
#
# Output to this file
LOGFILE="/var/log/bluetooth_dev"
# Name of the local sink in this computer
# You can get it by calling : pactl list short sinks
# AUDIOSINK="alsa_output.platform-bcm2835_AUD0.0.analog-stereo"
AUDIOSINK="alsa_output.0.analog-stereo.monitor"
# User used to execute pulseaudio, an active session must be open to avoid errors
USER="pi"
# Audio Output for raspberry-pi
# 0=auto, 1=headphones, 2=hdmi.
AUDIO_OUTPUT=1
# If on, this computer is not discovearable when an audio device is connected
# 0=off, 1=on
ENABLE_BT_DISCOVER=1
echo "For output see $LOGFILE"
## This function add the pulseaudio loopback interface from source to sink
## The source is set by the bluetooth mac address using XX_XX_XX_XX_XX_XX format.
## param: XX_XX_XX_XX_XX_XX
## return 0 on success
add_from_mac(){
if [ -z "$1" ] # zero params
then
echo "Mac not found" >> $LOGFILE
else
mac=$1 # Mac is parameter-1
# Setting source name
bluez_dev=bluez_source.$mac
echo "bluez source: $mac" >> $LOGFILE
# This script is called early, we just wait to be sure that pulseaudio discovered the device
sleep 1
# Very that the source is present
CONFIRM=`sudo -u pi pactl list short | grep $bluez_dev`
if [ ! -z "$CONFIRM" ]
then
echo "Adding the loopback interface: $bluez_dev" >> $LOGFILE
echo "sudo -u $USER pactl load-module module-loopback source=$bluez_dev sink=$AUDIOSINK rate=44100 adjust_time=0" >> $LOGFILE
# This command route audio from bluetooth source to the local sink..
# it's the main goal of this script
sudo -u $USER pactl load-module module-loopback source=$bluez_dev sink=$AUDIOSINK rate=44100 adjust_time=0 >> $LOGFILE
return $?
else
echo "Unable to find a bluetooth device compatible with pulsaudio using the following device: $bluez_dev" >> $LOGFILE
return -1
fi
fi
}
## This function set volume to maximum and choose the right output
## return 0 on success
volume_max(){
# Set the audio OUTPUT on raspberry pi
# amixer cset numid=3 <n>
# where n is 0=auto, 1=headphones, 2=hdmi.
amixer cset numid=3 $AUDIO_OUTPUT >> $LOGFILE
# Set volume level to 100 percent
amixer set Master 100% >> $LOGFILE
pacmd set-sink-volume 0 65537 >> $LOGFILE
return $?
}
## This function will detect the bluetooth mac address from input device and configure it.
## Lots of devices are seen as input devices. But Mac OS X is not detected as input
## return 0 on success
detect_mac_from_input(){
ERRORCODE=-1
echo "Detecting mac from input devices" >> $LOGFILE
for dev in $(find /sys/devices/virtual/input/ -name input*)
do
if [ -f "$dev/name" ]
then
mac=$(cat "$dev/name" | sed 's/:/_/g')
add_from_mac $mac
# Endfor if the command is successfull
ERRORCODE=$?
if [ $ERRORCODE -eq 0]; then
return 0
fi
fi
done
# Error
return $ERRORCODE
}
## This function will detect the bt mac address from dev-path and configure it.
## Devpath is set by udev on device link
## return 0 on success
detect_mac_from_devpath(){
ERRORCODE=-1
if [ ! -z "$DEVPATH" ]; then
echo "Detecting mac from DEVPATH" >> $LOGFILE
for dev in $(find /sys$DEVPATH -name address)
do
mac=$(cat "$dev" | sed 's/:/_/g')
add_from_mac $mac
# Endfor if the command is successfull
ERRORCODE=$?
if [ $ERRORCODE -eq 0]; then
return 0
fi
done
return $ERRORCODE;
else
echo "DEVPATH not set, wrong bluetooth device? " >> $LOGFILE
return -2
fi
return $ERRORCODE
}
## Detecting if an action is set
if [ -z "$ACTION" ]; then
echo "The script must be called from udev." >> $LOGFILE
exit -1;
fi
## Getting the action
ACTION=$(expr "$ACTION" : "\([a-zA-Z]\+\).*")
# Switch case
case "$ACTION" in
"add")
# Turn off bluetooth discovery before connecting existing BT device to audio
if [ $ENABLE_BT_DISCOVER -eq 1]; then
echo "Stet computer as hidden" >> $LOGFILE
hciconfig hci0 noscan
fi
# Turn volume to max
volume_max
# Detect BT Mac Address from input devices
detect_mac_from_input
OK=$?
# Detect BT Mac address from device path on a bluetooth event
if [ $OK != 0 ]; then
if [ "$SUBSYSTEM" == "bluetooth" ]; then
detect_mac_from_devpath
OK=$?
fi
fi
# Check if the add was successfull, otherwise display all available sources
if [ $OK != 0 ]; then
echo "Your bluetooth device is not detected !" >> $LOGFILE
echo "Available sources are:" >> $LOGFILE
sudo -u $USER pactl list short sources >> $LOGFILE
else
echo "Device successfully added " >> $LOGFILE
fi
;;
"remove")
# Turn on bluetooth discovery if device disconnects
if [ $ENABLE_BT_DISCOVER -eq 1]; then
echo "Set computer as visible" >> $LOGFILE
sudo hciconfig hci0 piscan
fi
echo "Removed" >> $LOGFILE
;;
#
*)
echo "Unsuported action $action" >> $LOGFILE
;;
esac
echo "--" >> $LOGFILE
裡面有個變數 AUDIOSINK
必須要改成自己用的,可以用 pactl list short sinks
指令查看。
pi@raspberrypi:/usr/lib/udev $ pactl list short sinks
0 alsa_output.platform-soc_sound.analog-stereo module-alsa-card.c s16le 2ch 44100Hz RUNNING
pi@raspberrypi:/usr/lib/udev $
像我的就是 pactl list short sinks
。
¶ 使用 bluetoothctl 控制藍芽設備
接著我們要使用 bluetoothctl
來跟設備連線,依序要輸入的指令為:
- power on
- kbd agent on
- kbd default-agent
- kbd discoverable on
- kbd pairable on
- kbd trust xx:xx:xx:xx:xx:xx
接著我打開我 iPhone 的藍芽開關,會看到一個設備名稱為 raspberrypi
的設備,點選它進行連線,然後手機跟 rpi 都會出現驗證碼,
選確認就可以了。
pi@raspberrypi:/usr/lib/udev $ bluetoothctl
Agent registered
[bluetooth]# power on
Changing power on succeeded
[bluetooth]# agent on
Agent is already registered
[bluetooth]# default-agent
Default agent request successful
[bluetooth]# discoverable on
Changing discoverable on succeeded
[CHG] Controller B8:27:EB:49:EA:A3 Discoverable: yes
[bluetooth]# pairable on
Changing pairable on succeeded
[CHG] Device xx:xx:xx:xx:xx:xx Connected: yes
Request confirmation
[agent] Confirm passkey 931286 (yes/no): y
Request confirmation
[agent] Confirm passkey 090994 (yes/no): yes
[CHG] Device xx:xx:xx:xx:xx:xx Connected: no
[CHG] Device xx:xx:xx:xx:xx:xx Connected: yes
Request confirmation
[agent] Confirm passkey 018954 (yes/no): yes
[CHG] Controller B8:27:EB:49:EA:A3 Discoverable: no
[iPhone]# trust xx:xx:xx:xx:xx:xx
Changing xx:xx:xx:xx:xx:xx trust succeeded
[iPhone]# exit
當然,可以在 bluetoothctl
內用 show
指令看一下連接點的資訊。
[iPhone]# show
Controller B8:27:EB:49:EA:A3 (public)
Name: raspberrypi
Alias: raspberrypi
Class: 0x004c0000
Powered: yes
Discoverable: no
Pairable: yes
UUID: Headset AG (00001112-0000-1000-8000-00805f9b34fb)
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: Audio Source (0000110a-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: Handsfree Audio Gateway (0000111f-0000-1000-8000-00805f9b34fb)
Modalias: usb:v1D6Bp0246d0532
Discovering: no
[iPhone]# exit
總算,東搞西搞總算可以連線樹莓派 + Tube Pi 發聲成功了。
我最近都把喜歡的歌放到 Telegram 的自建頻道內,這樣即使關閉螢幕或跳到其他視窗還是可以繼續播歌,也可以在鎖定畫面中控制歌曲哦。
至於藍芽連線的音質… 就見仁見智了,有機會的話,我想試試看走 I2S 連接 Tube Pi 輸出。
因為我發現,rpi 好像不能當成 USB 喇叭,也就是我用 PC 透過 USB 接到 rpi,讓 rpi 經過 Tube-Pi 輸出音樂。
What I mean is whether the rpi can be configured to act as a “USB SPEAKER”. In other words, that I can connect a Windows10 computer to the rpi through a USB cable (male-male) and that the W10 sees the rpi as a usb speaker […]
No, that is not possible, except perhaps with the zero, because USB relationships are not symmetrical; they are master/slave and the pi ports (except on the zero) are, like most of those on normal computers, master only (I realize that is a duplicate, but you can click through or read both Q&As if you like), whereas a USB speaker is a slave device. You cannot effectively attach a slave to a slave or a master to a master.
In fact: Beware that it is possible to cause permanent physical damage to one or both computers by connecting them using a male-male cable in the manner you describe. There are special adapter cables that can be used, but they tend to be expensive and have spotty support on the pi, so probably not a route worth exploring.