2013年9月3日 星期二

A2DP學習筆記

其實在PC桌面Linux已經支援藍牙的A2DP協定,包括將音樂由電腦播至藍牙耳機,也包括反方向由手機或平板將音樂傳送至電腦。
在Ubuntu 12.10的桌面環境裡,操作很容易,只要先和耳機或手機(平板)配對後,一連線成功,在電腦的設定-音效畫面就會出現設備。藍牙耳機會出現在輸出的頁籤,而手機或平板會出現在輸入頁籤。如Fig.1及Fig.2。
如果配對連線後,音效設定畫面看不到藍牙設備,請檢查/etc/bluetooth/audio.conf檔[General]區段下是否有Disable=Headset,若有請移除。

Fig.1
Fig.2
這時,打開電腦的軟體播放器,開始播放音樂,我們可以在音效設定裡(如Fig.1)選擇要將音樂由那個輸出設備播出;選到内建音效,就會由電腦的喇叭發出韾音,選到藍牙耳機,就會在藍牙耳機聽到音樂了。
而要由電腦喇叭播放手機或平板的音樂,也只要直接在手機或平板播放,在iOS裝置中會看到可以選擇那一個AirPlay裝置的小圖示。確定是放送到電腦,則在音效設定(如Fig.2)的輸入頁籤,可以看到這個手機的圖示,只要選到這個圖示,可以看到輸入等級的指示圖在左右動作。這表示手機的音樂已經傳到電腦的錄音源了,此時可以打開錄音軟體,開始錄音,然後播放錄音的結果就可以聽到由手機傳來的音樂。這好像還是有點不方便,我們要的應該是直接由電腦的喇叭發出韾音。Ivan_wl[1]的文章下半部份就是在討讑這個問題的解法。摘要其解決方式有二個重點:
  • 在/etc/bluetooth/audio.conf中[General]區段要加入Enable=Source
  • 使用Blueman套件或pulseaudio將錄音源轉至電腦喇叭
 Ivan_wl比較推薦使用Blueman,因為什縻設定都不用做,但是這様一來,你原來的藍牙管理工具要留著或移除,這有時是很討厭的決定。所以來看一下怎縻用pulseaudio:

簡單版:
$ pactl load-module module-loopback
20
用以上指令的意思就是載入loopback模組,它會將錄音源轉送至輸出。所以你就可以聽到由手機透過藍牙走A2DP協定過來的音樂了。你會注意到這個指令有返回一個數字,在這裡的例子是20。當你想要關掉loopback模組的時候,你會用到這個數字,用以下指令:
$ pactl unload-module 20
但是有時候,我們不會去記這個數字,所以可以用以下指令,它會自己去找到這個數字,然後就可以關掉loopback模組了。
$ pactl unload-module $(pactl list short modules | grep "loopback.*$BTSOURCE" | cut -f1)
複雜版:
$ pacmd
Welcome to PulseAudio! Use "help" for usage information.
>>> list-sources
...
  * index: 2
    name: <bluez_source.1C_E6_2B_xx_xx_xx>
    driver: <module-bluetooth-device.c="">
    flags: HARDWARE DECIBEL_VOLUME LATENCY 
    state: RUNNING
    suspend cause: 
    priority: 9030
    volume: 0: 100% 1: 100%
            0: 0.00 dB 1: 0.00 dB
            balance 0.00
...
使用pacmd給pulseaudio下個list-sources指令,找到你的手機或平板的name,在這個例子裡就是“bluez_source.1C_E6_2B_xx_xx_xx”,請注意不包含<>的部份。
>>> list-sinks
2 sink(s) available.
    index: 0
    name: <alsa_output.pci-0000_00_1b.0.analog-stereo>
    driver: <module-alsa-card.c="">
    flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY DYNAMIC_LATENCY
    state: SUSPENDED
    suspend cause: IDLE 
...
同時也下list-sinks指今找到要輸出的設備名稱。在這個例子裡是“alsa_output.pci-0000_00_1b.0.analog-stereo”,同様不包括<>部份。
把以上兩個資訊組合成以下指令:
$ pactl load-module module-loopback source=bluez_source.1C_E6_2B_xx_xx_xx sink=alsa_output.pci-0000_00_1b.0.analog-stereo
23
這様,你應該就會聽到韾音了。記得之後要用上文提到的方法在不用時,把loopback模組關掉。那縻這兩個方法有什縻差别?簡單版比較容易,它會自己找到輸出設備,像是Fig.1所示,我們可以手動選取某個輸出設備。但是這個選取動作是全機適用的。所以錄音源會轉到這裡,而如果你的電腦有媒體播放器在播出,其韾音也會轉到這裡。意思是你會在藍牙耳機(如果你在Fig.1選了藍牙耳機)聽到媒體播放器(例如:VLC)放的音樂,還有你的手機透過藍牙走A2DP過來的音樂。
所以如果我有藍牙耳機,也有把電腦音效卡接上不錯的音響。而我想把電腦的媒體播放器上的韾音播到藍牙耳機;另外把手機上的音樂播到電腦的喇叭。那就要用複雜版的作法了,這様才能指定轉送的輸出為特定設備。

Fig.3
我成功的試了這個作法,此時我左耳是藍牙耳機,聽著Qmmp播放的網路廣播電台;而右耳是電腦耳機,聽著由平版透過藍牙傳到電腦的音樂。此時看一下音效設定的程式集畫面(Fig.3)可以看到Qmmp和loopback模組都連到pulseaudio了。

而這一切是如何做到的呢?在2012年的研討會TIZEN[2]有提到:
Fig.4
Fig.5
簡單地説,就是兩個套件合作出來的:BlueZ, PulseAudio。BlueZ負責藍牙資料傳輸的部份,而PulseAudio負責音訊的整合(包括輸入,輸出,音量控制等),其module-bluetooth-device模組讓我們可以把藍牙設備抽象化為音訊輸入或輸出設備。而PulseAudio和Bluez之間的溝通是透過DBus。
以上文的例子來看,電腦和一支藍牙耳機,還有一台平板透過藍牙相連。電腦同時和兩台藍牙設備傳送資料,也可以説成和兩台藍牙設備之間同時進行串流,打開d-feet來看(Fig.6),可以注意到這兩個設備下都出現另一個物件(/org/bluez//hci0/dev_xx_xx.../fd0)。而這個物件下的org.bluez.MediaTransport.Acquire()可以傳回一個file descriptor(Fig.4),透過這個file descriptor,我們可以使用read來取得音訊串流用來丢給音效驅動程式以發出音樂;或者使用write函數將音訊串流丢給藍牙耳機。

Fig.6
那也就是説,Bluez和PulseAudio都幫我們做好了。我們就沒事做了。
不過,有時候也是需要,例如有些人就是不喜歡PulseAudio。例如james[3]就不想用, 所以他自己動手寫了一支程式叫做a2dp-alsa, 請見a2dp-alsa[4]。在他這兩篇文章裡, 完整的説明了他寫這支程式的前因後果, 當然也附上了完整的原始程式碼。拜讀後, 深深敬佩其功力之深。個人建議要兩篇文章一起看, 會更深入了解程式。
在此摘要測試其程式過程的一些重點:

1. 編譯時, 一開始不會成功, 後來將Makefile改了一下就可以了, 其實只是把編譯器參數的前後順序挪動一下而己, 可能是不同Linux有一些差異。我在Ubuntu 12.10下的修改:
a2dp-alsa: a2dp-alsa.c sbc/libsbc.a
    $(CC) $(shell pkg-config --cflags dbus-1) $(CFLAGS) -o $@ $< sbc/libsbc.a $(shell pkg-config --libs dbus-1) $(LDFLAGS)
2. 編成功後, 一直跑不起來。後來做了以下調整就成功了:
  • 停用PulseAudio
  • 換另一台全新安裝的電腦
3. 換一台全新安裝電腦, 其實只是因原來電腦可能有些設定問題造成沒有PulseAudio就無法出聲音, 為了簡單釐清問題, 才如此做。果然, 預設的安裝, 即使停用PulseAudio, 還是可以由ALSA驅動發出韾音的。真正有影響的設定, 其實James的第一篇文章就提到的很清楚了:
  • Enable=Source
  • Disable=Socket
這兩個設定是在/etc/bluetooth/audio.conf的[General]區段裡設的。

4. 同様地,James的a2dp-alsa程式和pulseaudio一様都是透過dbus和bluez溝通。所以bluez很重要,James只在bluez 4.101做了測試可行,我則在bluez 4.89試過不行。因此去查看bluez 4.89的dbus api,發現這個版本沒有org.bluez.Media.RegisterEndpoint()。真的運氣不錯,如果再查下去,你會發現其實bluez 4.90的dbus api就有這個函數了。後來,升級bluez至4.98,這個版本,測試是可用的。

James提到幾個重點,就像他所説的,bluez真是有太多不為人知的地方了:
  1. 前文中/etc/bluetooth/audio.conf設定檔中Disable=Socket的意思為何?原來bluez有一個叫個"audio socket API"的界面,而且原本有一個ALSA PCM plugin(pcm_bluetooth.c)可以用來讓電腦把音樂播到藍牙喇叭去的。就是使用asoundrc檔設定的那個方式,它和bluez溝通的方式就是走這個界面。所以使用這個plugin時必須要Enable=Socket。但是這個API已經很久之前就被建議不要再使用了。而且若要使用bluez Media DBus API的話,就必須Disable=Socket,否則不會動。
  2.  同様,Enable=Source的意思為何?如果沒有設,org.bluez.Media.RegisterEndpoint()叫用不會動。
  3. A2DP封包為RTP封包。
  4. Bluez原始程式包裡的/test目錄有一些有趣的程式(特别是python程式),例如有一支simple-agent的程式,值得研究。
參考來源:
[1] lvan_wl, Ubuntu藍牙全攻略, LinuxToy, 2010/3/28
[2] Luiz Von Dentz, BlueZ - Plugging the Unpluggable, TIZEN developer conference, 2012/5/7
[3] Jamesbond, Bluez must be one of the best kept secrets in Linux, From the desk of James, 2013/6/22
[4] Jamesbond, Bluez A2DP AudioSink for ALSA, From the desk of James, 2013/6/25
張貼留言