蓝牙协议 - HFP协议

970 阅读4分钟

1、定义

蓝牙电话控制协议(Hands-Free Profile,HFP):HFP定义了蓝牙设备之间进行电话通信的基本流程和命令。 让蓝牙设备可以控制电话,如接听、挂断、拒接、语音拨号等。HFP中蓝牙两端的数据交互是通过定义好的AT指令来通讯。

HFP协议定义了音频网关(AG)和免提组件(HF)两个角色:

  • 音频网关(Audio Gateway) :该设备为音频输入/输出的网关 。典型作为网关的设备为手机。
  • 免提组件(Hand-Free) :该设备作为音频网关的远程音频输入/输出机制,并可提供若干遥控功能。典型作为免提组件的设备为车机、蓝牙耳机。

image.png

2、HFP连接流程

  1. L2CAP 连接:首先,客户端设备和 HFP 设备需要通过 L2CAP(Logical Link Control and Adaptation Protocol)协议建立连接。L2CAP 是蓝牙协议栈中的一个底层协议,用于提供透明数据通信服务。在 HFP 连接过程中,L2CAP 协议主要用于数据的传输和控制。

  2. SDP 查找服务:在 L2CAP 连接建立之后,客户端设备需要通过 SDP(Service Discovery Protocol)协议查找 HFP 设备的服务信息。SDP 协议是蓝牙协议栈中的一个服务发现协议,用于在蓝牙设备之间发现可用的服务。通过 SDP 查找服务,客户端设备可以确定要连接的 HFP 设备是否支持 HFP 协议,并获取 HFP 设备的服务信息。

  3. RFCOMM 连接:在确认 HFP 设备支持 HFP 协议后,客户端设备需要通过 RFCOMM(Radio Frequency Communication)协议建立连接。RFCOMM 协议是蓝牙协议栈中的一个串口模拟协议,用于在蓝牙设备之间建立虚拟串口连接。在 HFP 连接过程中,RFCOMM 协议主要用于 HFP 协议栈与蓝牙协议栈之间的数据交换。

  4. HFP 协议连接:在 RFCOMM 连接建立之后,客户端设备和 HFP 设备之间就可以开始进行 HFP 协议连接了。HFP 协议栈是蓝牙协议栈中实现 HFP 协议的核心组成部分,它负责处理与 HFP 相关的音频编解码、通话控制、音量控制等功能,保证了蓝牙设备之间的语音通信质量和流畅度。通过 HFP 协议栈,客户端设备和 HFP 设备之间可以进行通话管理和音频传输等功能。

image.png

3、获取HFP协议对象

public class BluetoothHeadsetUtil {

    private static final String TAG = "BluetoothHeadsetClientUtil";

    private static BluetoothHeadsetClient mBluetoothHeadsetClient;

    private static final int BLUETOOTH_PROFILE_HEADSET_CLIENT = 16;

    public static void getProfileProxy(Context mContext) {
        BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BLUETOOTH_PROFILE_HEADSET_CLIENT);
    }

    private static final BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            Log.i(TAG, "onServiceConnected: " + profile);
            if (profile == BLUETOOTH_PROFILE_HEADSET_CLIENT) {
                mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
            }
        }

        @Override
        public void onServiceDisconnected(int profile) {
            Log.i(TAG, "onServiceDisconnected: " + profile);
            mBluetoothHeadsetClient = null;
        }
    };

}

4、HFP 相关API

/**
 * 判断设备是否已连接
 *
 * @param device 蓝牙设备
 * @return bool true: 已连接
 */
public static boolean isDeviceConnected(BluetoothDevice device) {
    if (mBluetoothHeadsetClient != null) {
        return mBluetoothHeadsetClient.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
    }
    return false;
}

/**
 * 获取已连接设备
 *
 * @return BluetoothDevice
 */
public static BluetoothDevice getConnectedDevice() {
    if (mBluetoothHeadsetClient != null) {
        List<BluetoothDevice> deviceList = mBluetoothHeadsetClient.getConnectedDevices();
        if (!deviceList.isEmpty()) {
            return deviceList.get(0);
        }
    }
    return null;
}

/**
 * 拨打
 *
 * @param mContext 蓝牙设备
 * @param tel      号码
 */
public static void dial(Context mContext, String tel) {
    if (TextUtils.isEmpty(tel)) {
        Toast.makeText(mContext, "号码不可为空", Toast.LENGTH_SHORT).show();
        return;
    }
    if (mBluetoothHeadsetClient == null) {
        Log.e(TAG, "bluetoothHeadsetClient is null");
        return;
    }
    List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
    if (devices.isEmpty()) {
        Toast.makeText(mContext, "未连接蓝牙设备", Toast.LENGTH_SHORT).show();
        return;
    }
    BluetoothDevice device = devices.get(0);
    mBluetoothHeadsetClient.dial(device, tel);
}

/**
 * 接听
 *
 * @param mContext 蓝牙设备
 * @param type     BluetoothHeadsetClient.CALL_ACCEPT_NONE:正常接听(单路通话)
 *                 BluetoothHeadsetClient.CALL_ACCEPT_HOLD:应答且挂起(多路通话)
 *                 BluetoothHeadsetClient.CALL_ACCEPT_TERMINATE:接听且挂断(多路通话)
 */
public static void accept(Context mContext, int type) {
    if (mBluetoothHeadsetClient == null) {
        Log.e(TAG, "bluetoothHeadsetClient is null");
        return;
    }
    List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
    if (devices.isEmpty()) {
        Toast.makeText(mContext, "未连接蓝牙设备", Toast.LENGTH_SHORT).show();
        return;
    }
    BluetoothDevice device = devices.get(0);
    mBluetoothHeadsetClient.acceptCall(device, type);
}

/**
 * 挂断
 */
public static void terminate() {
    if (mBluetoothHeadsetClient == null) {
        Log.e(TAG, "bluetoothHeadsetClient is null");
        return;
    }
    List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
    if (devices.isEmpty()) {
        Log.e(TAG, "Connected BluetoothDevice is null");
        return;
    }
    if (mBluetoothHeadsetClientCall == null) {
        Log.e(TAG, "mBluetoothHeadsetClientCall is null");
        return;
    }
    mBluetoothHeadsetClient.terminateCall(devices.get(0), mBluetoothHeadsetClientCall);
}

/**
 * 拒接
 */
public static void rejectCall() {
    if (mBluetoothHeadsetClient == null) {
        Log.e(TAG, "bluetoothHeadsetClient is null");
        return;
    }
    List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
    if (devices.isEmpty()) {
        Log.e(TAG, "Connected BluetoothDevice is null");
        return;
    }
    mBluetoothHeadsetClient.rejectCall(devices.get(0));
}

/**
 * HFP切换音频
 *
 * @param audioRoute: 0 音频切换手机
 *                    2 音频切换车机
 */
public static void setAudioMode(int audioRoute) {
    if (mBluetoothHeadsetClient == null) {
        Log.e(TAG, "bluetoothHeadsetClient is null");
        return;
    }
    for (BluetoothDevice device : mBluetoothHeadsetClient.getConnectedDevices()) {
        List<BluetoothHeadsetClientCall> currentCalls = mBluetoothHeadsetClient.getCurrentCalls(device);
        if (currentCalls != null && !currentCalls.isEmpty()) {
            if (audioRoute == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) { //2
                mBluetoothHeadsetClient.connectAudio(device);
            } else if ((audioRoute == BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED)) { //0
                mBluetoothHeadsetClient.disconnectAudio(device);
            }
        }
    }
}

/**
 * 三方通话 - 保持通话
 *
 * @param mDevice 当前连接设备
 */
public static void onHold(BluetoothDevice mDevice) {
    if (mBluetoothHeadsetClient != null) {
        mBluetoothHeadsetClient.holdCall(mDevice);
    }
}

public static void unHoldCall(Call call) {
    if (call == null || call.getState() != Call.STATE_HOLDING) {
        return;
    }
    if (mBluetoothHeadsetClient == null) {
        return;
    }
    List<BluetoothDevice> connectedDevices = mBluetoothHeadsetClient.getConnectedDevices();
    if (connectedDevices != null && !connectedDevices.isEmpty()) {
        BluetoothDevice mDevice = connectedDevices.get(0);
        mBluetoothHeadsetClient.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
    }
}