安卓中藍芽擴音HFP的一些分析
最近看了一些大牛的部落格,無不提到了技術分享的重要性,的確,分享自己的知識不僅僅是與他人討論並相互提高的過程,更是對自己知識的梳理。希望自己能以此為契機,形成周期性部落格分享的習慣。
文章標題為HFP的一些分析,HFP(Hand Free Profile)主要定義了安卓中與接打電話相關的一些功能的實現。本文主要介紹這其中對於HFP非常重要的Service Level Connection的建立。首先貼一個HFP實現的主要功能的圖。
從此圖也可以看到基本上都是與電話相關的功能。
這是SPEC中Service Level Connection建立的互動過程。其中HF為Hand Free,AG為Audio Gateway,這裡可以認為是藍芽耳機(HF)和手機(AG)。 從圖中可以看到SLC的建立是以RFCOMM連線建立為基礎,RFCOMM是串列埠模擬協議,這裡不做討論。然後開始進行AT命令的傳送與回覆。 接下來就到了喜聞樂見的程式碼階段分析階段了 為此我做了一張圖
圖中可以看到SLC建立的起點是從connect開始,就是當耳機和手機已經處於paired狀態。這時候你點選該裝置,安卓中應該都是此時開始連線。 這張圖可能有的程式碼有一些跳躍,如果不明白的可以參照一下下面接聽電話的程式碼流程圖,因為兩個流程有一些相同的地方。
android中的藍芽連線操作是在settings裡面完成的,所以最開始的onclick是在package/apps/Settings/Bluetooth中的BluetoothDevicePreference類中實現的onclicked方法中對connect進行呼叫,BluetoothDevicePreference這個類的具體含義這裡不做描述,這裡的onclick很明顯指的是Bluetooth裝置對應的那一行的按鍵click事件處理(其實我也不太明白這個類的含義,仍待繼續研究)。接下來就像程式碼中的流程一樣,圖中有幾處需要注意的是: 1.首先是在CachedBluetoothDevice這個類裡的connectInt中的profile,這是java最基本的多型,使用父引用指向子物件。因為對HFP的實現是HeadsetProfile這個類,所以這裡我們看該類中的connect函式。 2.在BluetoothHeadset中的connect通過安卓獨特的IPC機制AIDL呼叫到Headsetservice中connect然後通過sendmessage進入HeadsetStatemachion狀態機,對connectHfpNative進行了呼叫。 3.帶有Native的JAVA層函式通過JNI呼叫跳到了CPP檔案中,CPP檔案是對JAVA層到C層的一箇中轉,可以實現JAVA層對下面native層的介面呼叫。 4.sBluetoothHfpInterface是通過btInf->get_profile_interface得到的,btInf由getBluetoothInterface得到,getBluetoothInterface返回了com_android_bluetooth_btservice_AdapterService.cpp中的 sBluetoothInterface變數,這個變數由HAL_MODULE_INFO_SYM->methods->open(module, id, &abstraction)中的abstraction->get_bluetooth_interface得到,具體操作可以追蹤一下程式碼。 5。兜兜轉轉終於看到了connect的函式的真容,發現它是呼叫了btif_queue_connect(UUID_SERVCLASS_AG_HANDSFREE, bd_addr, connect_int)得到,前面的引數是一個巨集後面的是connect_int介面,此處先不看connect_int,有點經驗的應該都會知道後面會回調回來。 6.btif_queue_connect函式的核心是btif_transfer_context(queue_int_handle_evt, BTIF_QUEUE_CONNECT_EVT,(char *)&node, sizeof(connect_node_t), NULL);這裡又傳入了queue_int_handle_evt這一個函式介面以供後面回撥,而上一點中的connect_int放在node的connect_cb中繼續往下傳。 7.在btif_queue_connect中的核心函式是btif_sendmsg(p_msg),其中p_msg攜帶了上一層中queue_int_handle_evt函式作為p_msg->p_cb,node作為p_msg->params,並呼叫了bluedroid中一個很重要的GKI_send_msg,這個函式是用來與Bluetooth幾個重要的task進行通訊的介面,通過這個函式的GKI_send_event與GKI_wait組成一組訊號函式(具體的是pthread_cond系列的兩個函式,有興趣自行百度),可以進入task裡面進行處理,Task由GKI_send_msg的引數決定,此處進入btif_task。 8.進入btif_task之後根據GKI_send_msg的引數找到btif_context_switched,終於發現了函式回撥,此時回調了p_msg的p_cb,也就是前面提到的queue_int_handle_evt。 9.然後就是對profile佇列的add和connect next操作,在queue_int_connect_next中最終是呼叫了p_head->connect_cb,但是在對HFP的處理中此處仍然呼叫了前面提到的connect_int,這個是在queue_int_add中的list_append的時候進行了處理,將connect_int賦給了head(此處我也沒搞清楚裡面的判斷關係)。
10.在connect_int中有bta_AGopen然後像前面一樣,呼叫到了GKI_send_msg,但是這次訊息傳送到了btu_task,在btu_task中對訊息進行了處理,並呼叫了bta_sys_event函式,在這個函式中通過ID對bta_sys_cb中註冊的函式進行呼叫,在bta_AGopen所進行的賦值BTA_AG_API_OPEN_EVT左移兩位之後為BTA_ID_AG,因此此處的呼叫為bta_ag_hdl_event。 11.bta_ag_hdl_event中根據p_msg->event進行判斷,最後呼叫到bta_ag_action中的bta_ag_start_open函式,在這個函式中呼叫了bta_ag_do_disc。然後我們回到那張圖,發現AG在建立的時候其實就是等待HF傳送第一個AT命令過來,所以SLC的建立分析至此就告以段落了。在往下層走需要日後慢慢繼續研究。 (中間插播一下,沒想到寫一篇負責任一點的部落格竟然如此之累,心中再一次對部落格大牛們無限敬仰,能做到拿捏有度,抓住重點發表)
接下來是接聽電話的流程分析 首相上一張SPEC裡面的互動圖
這本來是一張圖,但是因為我截圖的時候弄成了3張。 這張圖比上一張圖的程式碼流程步驟更加詳細一點,是因為我是先作的這一張圖,在前面的圖裡我就省略了一些過程,但是在BLOG裡面因為上面已經介紹了上一個SLC的建立,所以這個過程的分析會依賴於上一個一些。 如圖中 1.在接聽電話的時候,起點明顯是現有tele發起的,呼叫圖中BluetoothPhoneService的UpdateHeadsetCallState方法,其中使用了前面一樣的方法呼叫phoneStateChangeNative開始通過JNI往下層呼叫。 2.此處可以省略一大段前面介紹過的流程,圖中也比較清晰,直接呼叫到bta_ag_result中的bta_ag_hfp_result. 3.bta_ag_hfp_result這個函式中的核心是bta_ag_send_call_inds這個函式,從名字也是可看出來是傳送indicators的函式。具體應該傳送什麼ind,HFP spec上也有詳細介紹。這個函式最終將會通過RFCOMM這個模擬串列埠將資料write到底層呼叫下面的協議提供的藉口。 4.最後是一個關於audio connection的介紹,audio connection在這個情境中指的就是與SCO鏈路,而關於SCO鏈路的操作規則需要參照core SPEC。這裡提一下的原因是接電話中兩種情況inband和no-inband有所不同,就是對audio connection的連線需求,在SPEC上也有兩張不同的流程圖,無非是一個需要在接電話之前建立(如圖),另一個卻是在電話接通之後建立。在程式碼上面的體現或者說對於SCO的不同就是bta_ag_sco_open這個函式的呼叫時機不同,而SCO鏈路的建立並沒有這麼簡單,這也是後面需要深入探究的地方。