1. 程式人生 > >車載BlueTooth通話機制原理及開發

車載BlueTooth通話機制原理及開發

[摘要]: 本文主要論述基於android 6.0的藍芽上層(Java層)通話機制;總結了藍芽通話框架,並且給出了接聽電話的詳細的流程圖;最後說明了apk的實現以及總結了藍芽/android 相關的知識點。

1, 藍芽框架

主要程式碼路徑:

路徑1: frameworks\base\core\java\android\bluetooth\  

藍芽相關介面,藍芽各種功能的發起點。

路徑2:packages\apps\Bluetooth\src\com\android\bluetooth\  

獨立的Bluetooth.apk,裡面包含藍芽相關的各種服務,是java層和C/C++層的橋樑。

路徑3: packages\apps\Bluetooth\jni\

  呼叫底層C/C++實現各種藍芽功能,並且反饋給java層。

在路徑2裡面還有各種相互獨立的java程式碼包,每一個包都包含一個協議,實現一個具體的功能:

btservice: 統一管理,控制其他服務。

a2dp: 和藍芽耳機,音訊有關,比如聽歌等。

avrcp: 音訊/視訊通過連線的藍芽控制,比如放歌時控制暫停等。

gatt:低功耗BLE有關,比如藍芽按鍵。

hdp: 藍芽醫療有關

hfp和hfpclient : 藍芽通話有關,比如藍芽通話的相關操作

hid: 藍芽鍵盤鍵盤/滑鼠

map: 同步藍芽簡訊相關

opp: 藍芽傳輸,比如傳輸檔案等

pan: 個人區域網

pbap: 同步電話本,比如聯絡人/通話記錄等

sap : 藍芽通話,主要和SIM卡相關

sdp: 藍芽服務發現/獲取相關

這12個包分別實現了12中藍芽功能,大多數以服務的形式存在,執行在Bluetooth.apk中。不僅如此,還具有以下特點:

1,每一個服務相互獨立,互相毫無任何影響, 繼承自 ProfileService,由

AdapterService服務統一管理。

2,每一個服務在路徑1中都存在對應的客戶端類,通過Binder進行跨程序通訊。

3,每一個服務在路徑3中都存在對應的C/C++類,通過JNI機制互相呼叫。

4,每一個服務的啟動,對應的Binder以及JNI機制的呼叫原理,方法,流程幾乎都是一樣的。

下面以藍芽通話功能為例來解析相關介面以及程式碼實現框架圖。

2 藍芽通話框架

2.1 相關類的說明

藍芽通話上層程式碼主要分為3個部分:

1,藍芽api相關程式碼, 路徑4: frameworks\base\core\java\android\bluetooth\

主要有2個類

BluetoothHeadsetClient.java主要負責藍芽通話的相關動作,比如接聽等等

BluetoothHeadsetClientCall.java主要負責藍芽通話的狀態,比如是來電還是去電等等。

2,藍芽服務端的相關程式碼,路徑5:

packages\apps\Bluetooth\src\com\android\bluetooth\hfpclient\

有3個類

HeadsetClientHalConstants.java類裡面只是定義了一些int/boolean 型別的值。

HeadsetClientService.java從名字就知道它是一個服務,它的設計很有意思,裡面還有一個BluetoothHeadsetClientBinder內部類,該內部類主要負責和

BluetoothHeadsetClient進行跨程序通訊。另外,HeadsetClientService也是

BluetoothHeadsetClientBinder和HeadsetClientStateMachine之間的橋樑。

HeadsetClientStateMachine是一個狀態機,即管理連線的狀態也是通話時java和C/C++之間的橋樑,通過JNI機制和com_android_bluetooth_hfpclient 裡面的方法互相呼叫。

3,JNI相關程式碼,路徑6: packages\apps\Bluetooth\jni\

有1個檔案: 

com_android_bluetooth_hfpclient  藍芽通話動作,撥號/接聽/結束通話/拒接 實際的執行者。

DialerBTHfpService服務是開機之後啟動的。

2.2類圖

圖一 類圖

BluetoothHeadsetClient是一個api,由第三方apk直接呼叫,可以進行撥號/接聽/拒接/結束通話操作,對應的方法依次為dial()/acceptCall()/rejectCall()/terminateCall().

HeadsetClientService是一個服務, BluetoothHeadsetClientBinder是它的內部類, BluetoothHeadsetClientBinder是BluetoothHeadsetClient在HeadsetClientService中的代理,通過aidl進行方法的呼叫。

Connected(已連線狀態)是HeadsetClientStateMachine 的其中一種狀態,通話的相關操作都建立在已連線狀態之上,其它三種狀態為Disconnected, Connecting,

AudioOn狀態。HeadsetClientService 根據不同的方法給狀態機發送不同的訊息,最後通過HeadsetClientStateMachine根據JNI機制呼叫

com_android_bluetooth_hfpclient.cpp對應的方法,最後呼叫底層C/C++ 來真正的實現撥號/接聽/拒接/結束通話操作。

撥號/接聽/拒接/結束通話方法呼叫的流程完全是一模一樣的,下小節給出接聽方法具體呼叫的完整流程圖。

2.3流程圖

圖二 接聽電話流程圖

除了dial 方法最後呼叫從C/C++ dialNative之外,其它的3個方法最後都是呼叫handleCallActionNative,只是引數不同而已。

撥號/接聽/拒接/結束通話都是主動完成的,那麼如果有來電,對方接通電話或者對方結束通話電話,我們怎麼知道呢?這些都是com_android_bluetooth_hfpclient.cpp通過JNI機制呼叫通話狀態機的方法sendCallChangedIntent,將電話的狀態(包含在

BluetoothHeadsetClientCall.java中)通過廣播發送出來,第三方apk監聽狀態就可以進行相應的操作了。具體的來電流程圖如下:

 圖三 來電流程圖

3 藍芽通話apk說明

在自己的apk中,只需要做2件事情就可以完成藍芽通話的幾乎所有動作,

1,根據上一小節的論述,註冊BluetoothHeadsetClientCall相關廣播,監聽來電/接通/對方結束通話的狀態,獲取藍芽電話的相關資訊,比如號碼,裝置資訊等等。

比如,如果收到來電廣播,就可以根據BluetoothHeadsetClientCall獲取來電的號碼等資訊,然後顯示來電介面。

2,通過BluetoothAdapter 獲取並且初始化BluetoothHeadsetClient物件,然後就可以直接呼叫dial()/acceptCall()/rejectCall()/terminateCall() 方法進行撥號/接聽/拒接/結束通話的操作了。

4, 小節

利用api實現藍芽通話不是很難,但是如果弄清楚具體的藍芽通話機制以及細節甚至藍芽相關功能,這就得下功夫了,關於藍芽,還可以進一步研究的android知識點以及藍芽涉及的上層java要點如下:

1,基本藍芽功能:開啟/關閉/掃描/配對/連線

2,藍芽功能:通過藍芽傳輸檔案/藍芽鍵盤/藍芽醫療服務/藍芽同步聯絡人等等

3,android知識:跨程序通訊機制/JNI機制/反射機制/狀態機機制等等。

實際開發過程

公司用的是android8.1的原始碼,系統api有改動,改動的地方會稍微標明一下。我是在系統原始碼上開發的,所以有些類或者[email protected]了 在開發工具上會報錯,但是可以編譯通過。如果是純應用上層需要利用反射,有一部分功能需要移植程式碼。

車載藍芽主要是實現藍芽電話,藍芽音樂,同步通訊錄。這些功能都是用到藍芽的配置檔案協議。下面簡單介紹一下這幾個協議。

1.HFP(Hands-free Profile),讓藍芽裝置可以控制電話,如接聽、結束通話、拒接、語音撥號等,拒接、語音撥號要視藍芽耳機及電話是否支援。

2.A2DP(Advanced Audio Distribution Profile)是藍芽的音訊傳輸協議,典型應用為藍芽耳機。A2DP協議的音訊資料在ACL Link上傳輸。

3.AVRCP(Audio/Video Remote Control Profile)作用是支援CT控制TG,具體來說如果手機和一個藍芽音箱裝置連線上了,那麼音箱可以控制手機播放/暫停/切歌以及獲得手機上播放歌曲的資訊,如專輯,歌名,歌手,時長等資訊。

4.PBAP(Phone Book Access Profile)訪問下載通訊錄以及通話記錄。

以上是簡單介紹這些協議的用途,想具體瞭解可以分別取查資料,這方面資料很多。

一.開啟藍芽,查詢藍芽裝置,連線藍芽協議。

要使用藍芽必須先在檔案清單加許可權

<uses-permission android:name="android.permission.BLUETOOTH"/>

    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

檢測裝置藍芽是否開啟,沒有可以自動開啟,也可以請求,給使用者提示,讓使用者自己開啟。

查詢藍芽之前可以先檢視是否有以前繫結過的藍芽裝置

Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
    	if (devices != null && devices.size() > 0) {
    		for (Iterator<BluetoothDevice> it = devices.iterator(); it.hasNext();) {
    			BluetoothDevice device = it.next();
    			if (device != null) {
    			    addBluetoothDevice(device);	
    			}
			}
    	}
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (!mBluetoothAdapter.isEnabled()) {
   mBluetoothAdapter.enable();
}

查詢藍芽可以先查詢低功耗藍芽,一般查詢時間為8-10秒,然後再查詢經典藍芽。兩種查詢用的api不同,先說一下查詢低功耗藍芽裝置。

拿到BluetoothLeScanner:mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();

呼叫startScan,引數為ScanCallback物件,它裡面有各種回撥,根據自己的需求實現。

private final ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                BluetoothDevice device = result.getDevice();
                Log.i(TAG, "scan succeed device ==  " +device);
                addBluetoothDevice(device);
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
                for (ScanResult result: results){
                	Log.i(TAG, "scan succeed device ==  " +result.getDevice());
                    addBluetoothDevice(result.getDevice());
                }
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            Log.i(TAG,"scan fail");
        }
    };

檢視經典藍芽直接使用BluetoothAdapter的startDiscovery方法,註冊BluetoothDevice.ACTION_FOUND廣播接收搜尋到的藍芽裝置。使用intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)可以得到BluetoothDevice。

接著連線裝置,連線裝置之前需要繫結裝置,就是確認雙方裝置的存在通過相同祕鑰,如果需要訪問通訊錄,需要勾選許可權。

可以先判斷BluetoothDevice的狀態,如果狀態為BluetoothDevice.BOND_NONE,呼叫bluetoothDevice.createBond()進行繫結。

講連線之前說一下自己踩過的坑,也不能說是坑,自己能力不足所以花了不少時間。開始我以為連線藍芽需要socket通訊,查google文件說要   兩臺裝置一臺充當客戶端一臺充當服務端,然後用io流傳輸資料。我一直在糾結,我只能操作車載應用,使用者手機安裝不了我開發的應用,那這怎麼連線呢。後來看到系統setting有連線藍芽的效果就果斷去setting的Bluetooth模組檢視連線藍芽的程式碼,終於找到了。在frameworks\base\packages\SettingsLib\src\com\android\settingslib\bluetooth中專門有個類儲存管理藍芽裝置CachedBluetoothDevice.java,他連線遠端藍芽裝置就是

synchronized void connectInt(LocalBluetoothProfile profile) {
        if (!ensurePaired()) {
            return;
        }
        if (profile.connect(mDevice)) {
            if (Utils.D) {
                Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
            }
            return;
        }
        Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
    }

LocalBluetoothProfile是一個介面,各種協議的封裝者實現了該介面,例如HfpClientProfile它裡面封裝的是BluetoothHeadsetClient,就是HFP協議。主要看它的connect方法

   @Override
    public boolean connect(BluetoothDevice device) {
        if (mService == null) return false;
        List<BluetoothDevice> srcs = getConnectedDevices();
        if (srcs != null) {
            for (BluetoothDevice src : srcs) {
                if (src.equals(device)) {
                    // Connect to same device, Ignore it
                    Log.d(TAG,"Ignoring Connect");
                    return true;
                }
            }
        }
        return mService.connect(device);
    }

先獲取已連線的藍芽裝置,如果藍芽裝置已連線返回true,不在列表中則呼叫BluetoothHeadsetClient的connect方法。接著看下這個BluetoothHeadsetClient怎麼例項化。

mLocalAdapter.getProfileProxy(context, new HfpClientServiceListener(),
                BluetoothProfile.HEADSET_CLIENT);

通過BluetoothAdapter的getProfileProxy方法去請求這個協議物件。

private final class HfpClientServiceListener
            implements BluetoothProfile.ServiceListener {

        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            if (V) Log.d(TAG,"Bluetooth service connected");
            mService = (BluetoothHeadsetClient) proxy;
            // We just bound to the service, so refresh the UI for any connected HFP devices.
            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
            while (!deviceList.isEmpty()) {
                BluetoothDevice nextDevice = deviceList.remove(0);
                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
                // we may add a new device here, but generally this should not happen
                if (device == null) {
                    Log.w(TAG, "HfpClient profile found new device: " + nextDevice);
                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
                }
                device.onProfileStateChanged(
                    HfpClientProfile.this, BluetoothProfile.STATE_CONNECTED);
                device.refresh();
            }
            mIsProfileReady=true;
        }

        @Override
        public void onServiceDisconnected(int profile) {
            if (V) Log.d(TAG,"Bluetooth service disconnected");
            mIsProfileReady=false;
        }
    }

監聽返回狀態,賦值。其實不僅僅是BluetoothHeadsetClient,BluetoothA2dpSink等等請求方式一模一樣,引數不同。看到這個之後恍然大悟,車載藍芽實現的功能不需要進行socket通訊,只需要連線這些協議。

好了,整理一下連線過程,先拿到BluetoothAdapter,呼叫getProfileProxy方法請求協議,第三個引數為協議的種類,都定義在BluetoothProfile中,例如BluetoothProfile.AVRCP_CONTROLLER(藍芽控制協議),BluetoothProfile.A2DP_SINK(音訊協議)。實現BluetoothProfile.ServiceListener介面,監聽返回狀態。如果返回成功得到物件,就可以呼叫connect(BluetoothDevice device)連線。

mAdapter = BluetoothAdapter.getDefaultAdapter();

mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.AVRCP_CONTROLLER);
		mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.A2DP_SINK);

private final class ProxyServiceListener implements BluetoothProfile.ServiceListener {

		@Override
		public void onServiceConnected(int profile, BluetoothProfile proxy) {
			Log.d(TAG,"Bluetooth service connected profile == "+profile);
			if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
				isAvrcpProfileReady = true;
				mAvrcpCt = (BluetoothAvrcpController)proxy;
				Log.d(TAG, "AvrcpController Profile Proxy Connected");
			} 
			if (profile == BluetoothProfile.A2DP_SINK) {
				isA2dpProfileReady = true;
				mA2dpSink = (BluetoothA2dpSink)proxy;
				Log.d(TAG, "BluetoothA2dpSink Profile Proxy Connected");
			}
		}

		@Override
		public void onServiceDisconnected(int profile) {
			if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
				isAvrcpProfileReady = false;
				mAvrcpCt = null;
				Log.d(TAG, "AvrcpController Profile Proxy Disconnected");
			}
			if (profile == BluetoothProfile.A2DP_SINK) {
				isA2dpProfileReady = false;
				mA2dpSink = null;
				Log.d(TAG, "BluetoothA2dpSink Profile Proxy Disconnected");
			}
		}
	}

public boolean connect(BluetoothDevice device) {
		if (null != mA2dpSink) {
			return mA2dpSink.connect(device);
		}
		Log.i(TAG, "mA2dpSink == null");
		return false;
	}

相關推薦

車載BlueTooth通話機制原理開發

[摘要]: 本文主要論述基於android 6.0的藍芽上層(Java層)通話機制;總結了藍芽通話框架,並且給出了接聽電話的詳細的流程圖;最後說明了apk的實現以及總結了藍芽/android 相關的知識點。 1, 藍芽框架 主要程式碼路徑: 路徑1: framewo

【RPC入門】RPC概念、原理開發

RPC基礎概念 RPC(Remote Procedure Call)—遠端過程呼叫,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。 在一般的應用程式中,被呼叫的過程在相同的地址空間中執行,並把結果返回給發出呼叫的過程。在分散式環境中,客戶機和伺服器在不同的機

POW共識機制原理優缺點

PoW共識機制 POW工作量證明(英文全稱為Proof of Work)在比特幣之前就已經出現,中本聰在設計區塊鏈的共識機制的時候就是借鑑了POW工作量證明。常見的是利用HASH運算的複雜度進行CPU運算實現工作量確定。 定義 工作量證明(Proof-of-W

Java集合-05fail-fast(快速失敗)機制原理解決方法

fail-fast簡介 fail-fast(快速失敗),是Java集合的一種錯誤檢測機制。當在遍歷集合的過程中該集合在結構(改變集合大小)上發生變化時候, 有可能發生fail-fast,丟擲java.util.ConcurrentModificationException異常。 fail-fas

OAuth2.0授權原理開發流程詳解

OAuth2.0在認證和授權的過程中參與的幾個角色如下: Client - 第三方應用, 下面以小明使用qq登陸本站為例來講解OAuth2.0授權原理(小明為Resource Owner,本站36nu.com為Client,qq授權伺服器為Authorization Server,提供小明qq基本資訊

藍芽通話機制原理

[摘要]: 本文主要論述基於android 6.0的藍芽上層(Java層)通話機制;總結了藍芽通話框架,並且給出了接聽電話的詳細的流程圖;最後說明了apk的實現以及總結了藍芽/android 相關的知識點。 1, 藍芽框架 主要程式碼路徑: 路徑1: frameworks\

QEMU快照(SNAPSHOT)機制原理關鍵技術理解

snapshot.xml <domainsnapshot> <name>snapshot01</name> <description>Snapshot of OS install and updates by boh</descrip

理解Attention機制原理模型

寫在前面 目前採用編碼器-解碼器 (Encode-Decode) 結構的模型非常熱門,是因為它在許多領域較其他的傳統模型方法都取得了更好的結果。這種結構的模型通常將輸入序列編碼成一個固定長度的向量表示,對於長度較短的輸入序列而言,該模型能夠學習出對應合理的向量表示。然而,這

[Android系統原理開發要點詳解

第1章 Android系統概述 1 1.1 基礎知識 1 1.1.1 Android開發系統的由來 1 1.1.2 行動電話系統開發模式 2 1.1.3 未來行動電話的功能及Android的優勢 4 1.2 Android的開發工作 6 1.2.1 Android移植

java併發程式設計——四(synchronized\Lock\volatile) 鎖機制原理關聯

前言 其實標題使用互斥機制更合適,併發中主要兩個問題是:執行緒如何同步以及執行緒如何通訊。 同步主要是通過互斥機制保證的,而互斥機制我們最熟悉的就是鎖,當然也有無鎖的CAS實現。 多執行緒共享資源,比如一個物件的記憶體,怎樣保證多個執行緒不會同時訪問(讀取

OAuth的機制原理講解開發流程

國內私募機構九鼎控股打造APP,來就送 20元現金領取地址:http://jdb.jiudingcapital.com/phone.html內部邀請碼:C8E245J (不寫邀請碼,沒有現金送)國內私募機構九鼎控股打造,九鼎投資是在全國股份轉讓系統掛牌的公眾公司,股票程式碼為

OAuth1.0/2.0的機制原理講解開發流程

1、OAuth的簡述 OAuth(Open Authorization,開放授權)是為使用者資源的授權定義了一個安全、開放及簡單的標準,第三方無需知道使用者的賬號及密碼,就可獲取到使用者的授權資訊,並且這是安全的。(我喜歡簡單明瞭,這裡沒看懂,沒關係,接著往下面看) 2、O

Struts2漏洞利用原理OGNL機制

基本 conf 數據集 fig 然而 example 所有 def 字符串類型 Struts2漏洞利用原理及OGNL機制研究 概述 在MVC開發框架中,數據會在MVC各個模塊中進行流轉。而這種流轉,也就會面臨一些困境,就是由於數據在不同MVC層次中表現出不同的形式和狀態

微信公眾平臺開發教程(二) 基本原理消息接口

username 普通用戶 縮放 地理位置 cfb 位置 註意 獲得 基本 一、基本原理 在開始做之前,大家可能對這個很感興趣,但是又比較茫然。是不是很復雜?很難學啊? 其實恰恰相反,很簡單。為了打消大家的顧慮,先簡單介紹了微信公眾平臺的基本原理。 微信服務器就相當於一個轉

JVM 垃圾回收機制原理

add IE 安全性 mod 銷毀 初始 文件 1.2 com JVM Java 虛擬機 Java 虛擬機(Java virtual machine,JVM)是運行 Java 程序必不可少的機制。JVM實現了Java語言最重要的特征:即平臺無關性。原理:編譯後的 Java

inotify機制監控文件系統事件原理使用

direct esc his 存儲 cname smo gin related oca 1.基本描述 inotify提供了一種監控文件系統事件的機制,可以用來監控單個的文件以及目錄。當一個目錄被監控,inotify會返回該目錄以及該目錄下面文件的事件。 2.原理以及使用 2

Java核心機制:反射機制原理應用方法

一、java的核心機制 java有兩種核心機制:java虛擬機器(JavaVirtual Machine)與垃圾收集機制(Garbage collection): 1、Java虛擬機器:是執行所有Java程式的抽象計算機,是Java語言的執行環境,在其上面執行Java程式碼編譯後的位元組碼程式,

java垃圾回收機制原理優缺點

  優點:a.不需要考慮記憶體管理, b.可以有效的防止記憶體洩漏,有效的利用可使用的記憶體, c.由於有垃圾回收機制,Java中的物件不再有"作用域"的概念,只有物件的引用才有"作用域" 原

malloc動態記憶體分配機制原理__linux/proc/介紹

程序系統資源的使用原理          大部分程序通過glibc申請使用記憶體,但是glibc也是一個應用程式庫,它最終也是要呼叫作業系統的記憶體管理介面來使用記憶體。大部分情況下,glibc對使用者和作業系統是透

Java反射機制原理在Android下的簡單應用

package crazypebble.reflectiontest;import java.lang.reflect.Constructor;import java.lang.reflect.Method;publicclass LoadMethod { /** * 在執行時載入指定的類,並呼