星期日, 4月 09, 2006

在 Mandriva 2006 上,讓 skype 使用 Bluetooth 耳機

先說一點廢話。以前一直是使用 Red Hat 的,連我的 desktop 上,到最後都只剩下 Red Hat Linux 9.0+,也就是 9.0 加上一堆我自己的修改。不過因為 Red Hat 在 9.0 之後停止發展 RHL,而我自己對於 Fedora 的 FC1/2 感覺不是很好,因此一直有想要轉到 Mandriva 上的想法。雖然之前已經裝過 Mandrake 10.0,不過因為一些原因,裝好之後都一直沒有使用。最近重新裝了 Mandriva 2006.0,大致上感覺還不錯,但是一些小地方讓人覺得Mandriva 公司的軟體包裝策略實在有點讓人摸不著頭緒。

廢話說完,回歸到主題。Mandriva 2006 上,預設使用 bluez 套件來提供 bluetooth 支援。不過目前來說,bluez 仍然不直接支援使用 SCO (Sychronous Connection-Oriented) 和 A2DP (Advanced Audio Distribution Profile) 的藍芽裝置,例如藍芽無線耳機麥克風或藍芽立體聲無線耳機等等。因此即使安裝了相關的藍芽支援套件,例如 bluez-utils 和 gnome-bluetooth 以後,仍然無法使用藍芽耳機麥克風。

不過事情總是會有轉機,Free Software/Open Source Software 的美妙之處,就是總是可以發現一些高手看到某些不方便,然後就發展一些解決方案。在 Linux 上無法使用藍芽耳機麥克風,感覺實在是有點糟,因此就有了一個叫作 Bluetooth-ALSA 的計劃,這個計劃的目的就是讓 Linux 也能使用藍芽無線耳機和無線耳機麥克風。引用一段計劃網頁上的說明:

This project provides a way to use a bluetooth headset with Linux. We do this currently by making an alsa kernel driver which uses bluez to reach the headset. It works well enough now to get voice-quality audio to and from most headsets.


不過這個計劃還是在發展初期,最 新的 release 是 btsco 0.41。雖然 btsco v0.41 中已經包含了使用 skype 所需要的基本功能,不過由於是發展中的軟體,我是比較偏向使用 CVS 中的 source code。

目前來說,Mandriva 2006 中,還沒有出現已經包裝好的 btsco RPM 可以用,因此安裝上比較麻煩些。現階段來說,btsco 的使用還不是非常適合一般使用者使用,因此我暫時也沒有打包成 RPM 的計劃。大致上的安裝步驟如下:


  1. 由 CVS 取得最新的 source code:


    $ cd /tmp
    $ cvs -d:pserver:anonymous@cvs.sf.net:/cvsroot/bluetooth-alsa co btsco
    $ cd btsco


  2. 在開始進行 building 程序之前,需先確認以下 RPM 已經安裝:

    automake1.7
    libbluez1-devel
    libalsa2-devel
    libao2-devel


  3. 修改 bootstrap 這個 shell script 的內容。由於 Mandriva 2006 預設使用 automake 1.8,因此這個修改是必要的,否則接下來的 building 程序一定會失敗。修改 bootstrap 的內容,將 aclocal 和 automake 改成 aclocal-1.7 和 automake-1.7:

    #! /bin/sh

    aclocal-1.7 -I /usr/share/aclocal && autoheader && automake-1.7 --add-missing --copy && autoconf


  4. 開 始 building 程序。依序使用以下 building 程序,compile btsco 的 utility (a2play, a2recv, avrecv, avsnd, btsco, btsco2) 和 kernel 模組 (snd-bt-sco.ko.gz)。由於 Mandriva 2006 的 bluez-utils RPM 中已經包含了 btsco 裡面的 ALSA plugin,因此不需要自己 compile ALSA plugin。

    $ ./bootstrap
    $ ./configure --enable-all
    $ make
    $ cd kernel
    $ make
    $ gzip *.ko


  5. 把 snd-bt-sco.ko.gz 以 root 權限安裝到系統中。雖然直接放到 /lib/modules 裡面就可以了,但是我不是很建議這麼做,因為可能會造成一些後續管理上的麻煩。我的作法是把 snd-bt-sco.ko.gz 和其他的 utility 都先放在 $HOME/btsco 裡面,然後用 root 權限在 /lib/modules 下某個適當的位置建立 symbolic link:

    $ mkdir ~/btsco
    $ mv a2play a2recv avrecv avsnd btsco btsco2 snd-bt-sco.ko.gz ~/btsco/
    $ pushd /lib/modules/`uname -r`/kernel/3rdparty
    $ sudo ln -s $HOME/btsco/snd-bt-sco.ko.gz .
    $ sudo depmod
    $ sudo modprobe snd_bt_sco
    $ popd


  6. 在 ~/bin 中建立適當的 symbolic link。基本上只要有 btsco 這個 SCO daemon 即可,但是其他的 utility 在其他場合可能會用到,所以一併建立 link:

    $ cd ~/bin
    $ ln -s ../btsco/{a2play,a2recv,avrecv,avsnd,btsco,btsco2} .


  7. 使用 root 權限設定 hci0 這個 hci device 的 voice 參數:

    $ sudo hciconfig hci0 voice 0x0060


  8. 確 定藍芽無線耳機麥克風可以跟電腦建立連線。需要注意的是,大部分的藍芽無線耳機麥克風在非 pairing 模式時,使用 hcitool 進行 scan 是找不到的,因此這邊可能必須要重新對耳機麥克風做一次 pairing。方法是先把藍芽無線耳機麥克風切換至 pairing 模式,然後執行以下指令建立連線完成 pairing:

    $ hcitool scan
    00:0A:94:93:74:B7 Jabra Blah 1234
    $ hcitool cc 00:0A:94:93:74:B7


  9. 如果上面有成功建立連線的話,那就可以啟動 btsco 進行測試。啟動 btsco 的方法很簡單,不過需要上面得到的那個 BD Addr:

    $ btsco 00:0A:94:93:74:B7


  10. 如果上面 btsco daemon 能成功執行的話,那就可以用下面的指令使用 mplayer 播放 mp3,如果能夠在耳機中聽到正確的音樂,那麼基本的設定就已經完成:

    $ mplayer -ao alsa:device=plughw=Headset FOO_BAR.mp3




基 本上到這邊,系統已經可以透過 btsco 使用藍芽無線耳機麥克風了。但是如果要讓 skype 可以透過 btsco 使用藍芽無線耳機麥克風的話,還需要一些手續。skype 目前的 Linux 版本除了是 1.2 以外,最大的問題在於目前 skype 只支援 OSS,並不直接支援 ALSA。雖然這在一般使用上並沒有太大的問題,而在 GNOME/KDE 上,可以透過 EsounD/aRTs 的 wrapper,或者是 Mandriva 自己的 soundwrapper 來和 GNOME/KDE 和平相處,但是也因此要讓 skype 能使用 btsco 變得有一點麻煩。好在已經有人提出暫時的變通辦法,Andreas Beck 在 Bluetooth-ALSA 計劃的 mailing list 上,提到他對 btsco 做了些 patch,也寫了些 script 和 wrapper,讓 skype 可以使用 btsco 來聯接無線耳機麥克風。他的 patch 後來沒多久就被收錄進 btsco 的 CVS 中,因此最新的 0.41 tarball 以及 CVS source 中,都已經包含了所需要的 patch,而他寫的 script 和 wrapper 則可以直 接在他的網站下載, 或者也可以在最新的 tarball 和 CVS source 的 btsco/contrib 目錄裡面找到。假設使用 CVS 中的 souce,那麼 compile 和設定他的 script/wrapper 的程序如下,相關細節請參考原作者寫的 README 檔案內容:


  1. 先 compile 他的 libskype_bt_hijacker.so wrapper:

    $ cd /tmp/btsco/contrib
    $ make


  2. 修改 btscorunner script 的 HEADSETADDR 變數值,改成上面 scan 時得到的 BD Addr 值。

  3. 修 改 skype_bt_hijacker script 的 HIJACKDSP 和 SECONDARYDEV 的值,改成系統中的 primary 和 secondary dsp device 的值。在我的機器上,Mandriva 2006 的系統使用原本的設定值 (/dev/dsp 和 /dev/dsp1) 即可。這個主要是要告訴 wrapper 該攔截 skype 對哪個裝置的 I/O 動作。此外還要修改 BINPATH 的值,假設接下來要把這些 script 和 wrapper 都放在 ~/btsco 裡面的話,就把 BINPATH 改成 ~/btsco 即可。

  4. 修 改 skype_bt_hijacker_onopen。這個 script 是當 libskype_bt_hijacker.so 攔截到 skype 開啟 dsp 裝置時,用來通知藍芽無線耳機麥克風用的。原作者是播放一段鈴聲提醒他,不過也可以改成 "killall -USR1 btsco" 送 SIGUSR1 給 btsco,則 btsco 會送預設的鈴聲指令給藍芽無線耳機麥克風。但是需要注意的是,實際測試結果,送 SIGUSR1 的方法在單獨測試時都正常運作,但是搭配 skype_bt_hijacker_onopen 時,很容易失敗。原因代查。如果想像原作者一樣播放一段鈴聲,則把原 script 中 aplay 播放的檔案位置改為你要的位置即可。

  5. 修改 skype_bt_hijacker_on* script 的權限:

    $ chmod 700 skype_bt_hijacker_on*


  6. 把這些 script 和 wrapper,以及一個 btsco 設定檔放到適當的位置。我的作法是把這些都一起放在 $HOME/btsco,然後在適當的位置建立 symbolic link。
    $ mv btscorunner libskype_bt_hijacker.so skype_bt_hijacker skype_bt_hijacker_on* skype_pickup.py btscorc ~/btsco/
    $ cd ~/bin
    $ ln -s ../btsco/{btscorunner,skype_bt_hijacker,skype_pickup.py} .
    $ ln -s ../btsco/skype_bt_hijacker_on* .
    $ cd ..
    $ ln -s btsco/btscorc .btscorc




安 裝到此為止。不過在使用之前,要先確定 dbus-python RPM 有裝起來,因為 skypc_pickup.py 使用 dbus 的功能來呼叫 skype API,控制 skype 的接聽與掛斷動作。而第一次使用時,skype 會警告有程式使用 API 控制 skype,問使用者是否允許該操作,必須要回答允許才行。

使用的方法很簡單,先執行 btscorunner 啟動 btsco,如果沒有錯誤的話,使用 skype_bt_hijacker 程式啟動 skype。理論上如果都正確的話,可以使用 headset 的 handfree profile 功能來控制接通和切斷,這就是透過 ~/.btscorc 裡面的設定達成的,當 btsco 偵測到 ~/.btscorc 內設定的 pattern 時,會執行 pattern 下一列指定的 action,而 skype-bt-hijacker 提供的 btscorc 就是設定當使用者按下 headset 上的按鈕時,執行 skype_pickup.py 來控制 skype。這代表 skype_pickup.py 和其他相關 script 都必須放在 $PATH 所及的地方,skype 的啟動指令也是,這也是為何我會在 ~/bin 裡面建立相關的 symbolic link 的緣故。

實測結果,在我的感覺是堪用,但是音質不是很好。音質不好的原因目前 還不確定。此外,這種啟動程序其實還不是很方便,現階段來說,可能還需要寫一些 helper script 來讓整個啟動過程更順暢才行。而且由於目前 btsco 是直接透過 ALSA 的 plugin 和 ALSA 溝通,因此如果在有使用 GNOME/KDE 等桌面整合環境下使用 btsco 時,如果 esd 等 audio server 正在使用 /dev/dsp 等 PCM 裝置的話,可能會發生錯誤。

9 則留言:

匿名 提到...

你好
我叫做阿欽,剛好最近有在接觸bluetooth的東西,想請教問你一些相關的問題

我最近使用A2DP藍芽耳機聽音樂,作業系統Ubuntu,在聽的過程中,假如藍芽中斷(不管是電腦中斷或是耳機中斷),正在播放的播放器就會當掉,我是用banshee的(播放器),因為不曉得問題 是在Bluetooth這邊還是在播放器的問題還是本身OS聲音切換的問題??

還有說藍芽斷線沒有告知播放器所以播放器會當掉,我除了l2ping這個可以得知藍芽已經斷線之外,是否有其他的方式可以得知連線藍芽已斷線了,進而可以再想辦法通知播放器
或是需要用dbus來作為a2dp與播放器之間的聯繫呢??謝謝

Shang-Feng Yang 提到...

我不確定我有辦法回答你的問題,不過:

1. Bluez 對 A2DP 的支援方式,隨著 Bluetooth-ALSA 計畫的發展,有改過架構,目前是使用新的,直接由 bluez 經由 ALSA plugin 來驅動 A2DP,不再需要透過 snd-bt-sco.ko 來做,但是 Ubuntu 中仍然支援舊的透過 snd-bt-sco.ko 的方式。由於你的敘述不是很清楚,不知道你是怎樣驅動 A2DP 的呢?從你的描述中我無法判斷你是用哪一種架構來達成 A2DP 的。
2. 由於你給的訊息實在太少,不是很清楚到底是怎樣的狀況,但是通常藍芽連線中斷後,播放程式應該不會真的當掉,只是會發現聲音裝置無法正確驅動,會產生很多錯誤,看起來像是當掉,有可能是播放程式在存取聲音裝置,但是裝置因為藍芽連線中斷而沒有回應,在等 timeout。也許你可以試試看在 terminal 下直接由 command line 啟動 banshee,看看中斷藍芽連線後,console 上有沒有出現什麼錯誤訊息。我猜應該會看到很多 ALSA 相關的錯誤訊息才對。
3. 至於怎樣通知播放程式藍芽連線中斷,這個部分我不確定是否有簡單的方式可以做得到,因為不論是使用哪一種方式使用 A2DP,播放程式並不知道他是透過藍芽來播放,對播放程式來說,他是在存取 ALSA 裝置,因此恐怕沒有簡單的方式可以做到。我想得到的幾種可能辦法是,用某種方式監測藍芽連線是否存在,如果藍芽連線中斷的話,設法通知播放程式做相對的處理。偵測連線方式的話,除了可以透過像你說的 l2ping 定期去偵測之外,也許還可以透過 Bluez 的 Bluetooth Audio Service 的 D-Bus 介面來做(http://wiki.bluez.org/wiki/Audio)。而怎樣通知播放程式做相對應處置的部分,如果播放程式有提供 D-Bus 介面的話,自然是透過 D-Bus 來做事比較優美的做法。否則最簡單但是最暴力的做法是直接用 kill 送 signal 給該程式。

匿名 提到...

感謝你的回答
1.如何驅動a2dp我是利用dbus(參考來源:http://wiki.bluez.org/wiki/HOWTO/AudioDevices#SupportedPlayers)
import dbus
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'), 'org.bluez.Manager')
bus_id = manager.ActivateService('audio')
audio = dbus.Interface(bus.get_object(bus_id, '/org/bluez/audio'), 'org.bluez.audio.Manager')

path = audio.CreateDevice('00:11:22:33:44:55')
#audio.ChangeDefaultDevice(path) #change the device to be used by default
sink = dbus.Interface (bus.get_object(bus_id, path), 'org.bluez.audio.Sink')
sink.Connect()
然後在家目錄下.asoundre檔
pcm.bluetooth {
type bluetooth
}
就可以連上a2dp藍芽耳機
不曉得是算那一種架構?
2.我有寫一個script用來擷取藍芽的mac與連結dbus與判斷是否斷線與切換聲音
#!/bin/bash

hcitool scan| zenity --width=400 --height=250 --list --title "search Bluetooth device" --text "choice your device" --column "MAC" > tempmac
cat tempmac|cut -c 1-19 > tempmac2
read macaddr < tempmac2
sed -e s/sedmac/$macaddr/ ~/.a2dp/a2dppy > a2dp.py
rm -rf tempmac
rm -rf tempmac2
cd ~/.a2dp
python a2dp.py
state=`gconftool --get /system/gstreamer/0.10/default/musicaudiosink`

if [ $state=="autoaudiosink" ];then
gconftool --type string --set /system/gstreamer/0.10/default/musicaudiosink "alsasink device=bluetooth"
fi

while [ 0 ]
do
l2ping -f 00:0D:3C:EB:00:F7
if [ $?=1 ];then
python pause.py
zenity --info --width=400 --height=250 --text "Bluetooth disconnection."
gconftool --type string --set /system/gstreamer/0.10/default/musicaudiosink "autoaudiosink"
break
fi
done
但在"l2ping -f 00:0D:3C:EB:00:F7"後面原本想說在python pause.py(pause.py也許是播放軟體的暫停的dbus程式),但會發現等到$?=1 的時候播放器已經當掉來不及執行,有看到/var/log/syslog發現藍芽的通訊是用socket的方式,那會不會可能說是當播放器(client)建立一個socket,當連線斷掉時,造成這個建立的socket當掉呢??因為當播放器當掉之後我下
#killall banshee-1之後再看/var/log/syslog看到
Oct 27 10:25:35 hill33 audio[5189]: Unix client disconnected (fd=9)的訊息
3.你提供給我參考的資料(http://wiki.bluez.org/wiki/Audio)
有一段是audio service -> alsa
audio service應該是a2dp
而alsa是banshee(播放器)中有一段
rerouting event
* headset has disconnected or stream socket preempted; close socket and use default device
* headset has connected; should acquire and use audio stream socket
直覺說應該是可以達到,但這邊沒有寫很清楚是要如何完成,
還有你說"播放程式並不知道他是透過藍芽來播放"所以說debug的方向應該是朝bluez來研究嗎? 目前不太清楚是否要往bluez的套件code去下手還是其實只是一些設定的問題? 謝謝

Shang-Feng Yang 提到...

從你的連線方式來看,你是使用 bluez 的新架構,透過 Audio Service 來做連線。基本上我目前無法回答你的問題,因為我沒有用 banshee,必須實際做一些測試才能有更進一步的了解。不過 banshee 可能會是一個例外的程式。我前面的回應說,『程式並不知道是透過藍芽來播放』,那是指透過 Audio Service 以及 bluez 的 ALSA plugin,對一般透過 ALSA 來存取 A2DP 的程式來說(ex. mplayer),A2DP 連線只是你在 ~/.asoundrc 設定的一個 ALSA 裝置。不過 banshee 似乎是使用 gstreamer framework,而 bluez 除了提供 ALSA plugin 之外,也有 gstreamer plugin (請參考 bluez-audio 套件的檔案列表),但我不清楚 gstreamer plugin 是直接去存取 Audio Service 還是仍是透過 ALSA plugin 來達成,如果是直接存取的話,那嚴格來說,程式是知道它是使用藍芽連線的,因此才有可能做到你說的 rerouting。不過 rerouting 到底怎樣達成,老實說因為我沒有在用 banshee,我不知道。我要實際做些測試才行,恐怕暫時我也沒有頭緒到底是怎麼回事。不好意思,目前幫不上忙...

匿名 提到...

你好,你太客氣了,不厭其煩地回答我的問題,我有查過一下3.22的版本通過bluetoothd-service-audio來支持,觀察/var/log/syslog的訊息藍芽就兩個程式在跑一個是hicd一個就是bluetoothd-service-audio了。
對了,能否给一個常用的連絡方式e-mail,好仔细請教你,我的e-mail是f8742651@ms33.hinet.net。謝謝

匿名 提到...

HI 你好,謝謝你之前給的資訊,目前藍芽耳機斷線的問題已經解決了,我之前是採用bluez3.26版的後來我看到ubuntu8.10架構下不會當機,去發現它採用的是bluez3.36版的,後來我把套件移除後灌3.36版的之後就不會發生問題了,3.36版的我看/var/log/syslog看好像它只有一個daemon負責[hcid],跟之前3.26有兩個daemon不太一樣的形式,謝謝。

Shang-Feng Yang 提到...

雖然沒能幫上忙,不過還是很高興你的問題解決了。

Bluez 對於 Audio Service 的部分一直都還是在發展中的階段,看到你說你用新版的 Bluez 解決了 A2DP 的問題,相當高興。希望 Bluez 早日能夠有完整且穩定的 HSP 和 HFP 的實作出現。

Danniel 提到...

您好!
我正在玩Android的bluetooth,想請教一下,感謝您的指點!
假設我另外做了一個btsco的工具,然後使用它來撥音樂, 這時再開啟原來Android內附的Music來播音樂(使用bluez中的A2DP profile), 有辦法同時在藍牙A2DP耳機裡聽到這兩個不同的音樂嗎?

Shang-Feng Yang 提到...

Android 我也才剛接觸,因此還不熟悉,但就我所知,之前看過的 Android 架構圖,Android 自己有自己的 wrapper 來處理藍芽的部分,且 Android 是透過 bluez 新的 Audio Service 架構來處理 SCO 和 A2DP 的部分,因此你想要用 btsco 的方式同時存取藍芽輸出裝置,恐怕會造成存取衝突。

我想比較好的方式,是透過類似 esd/pulseaudio 之類的 sound server 把你的多個 audio stream 混合以後,再由 Android 送到 bluez 的 A2DP service,這樣問題會比較簡單。只是我對 Android 還不熟悉,不確定 Android 本身有沒有已經有類似 esd 之類的 sound server 機制,或者有類似的 API 處理這樣的事情,所以可能沒辦法回答你的問題。