当前位置: 首页 > article >正文

Linux 系统蓝牙音频服务实现分析

Linux 系统蓝牙音频服务实现分析

    • 蓝牙音频设备连接管理

Linux 系统中,蓝牙音频服务实现为系统音频服务 PulseAudio 的可加载模块,它用来以 PulseAudio 标准的方式描述蓝牙音频设备,将其嵌入 PulseAudio 的音频处理流水线,并呈现给用户,支持用户切换音频设备,如蓝牙耳机。

蓝牙音频设备连接管理

音频蓝牙协议栈主要有 HSP/HFP 和 A2DP/AVRCP 两种,这两种协议栈在系统蓝牙服务 bluez 和音频服务 pulseaudio 中具有不同的处理流程。通常一个蓝牙耳麦对这两种协议栈都支持,并可以在这两种协议栈之间进行切换。

对于 A2DP/AVRCP 协议栈,在 pa_bluetooth_discovery_get() 函数的执行过程中,会初始化蓝牙 A2DP endpoint 配置,并向 D-BUS 注册 endpoint 处理程序 endpoint_handler()。蓝牙音频设备在物理上与蓝牙控制器/适配器建立连接之后,系统蓝牙服务 bluez 会收到通知,在回调中处理连接,并通过 D-BUS 调用 pulseaudio 注册的 endpoint 处理程序 endpoint_handler()

对于 HSP/HFP 协议栈,主要通过蓝牙后端,即蓝牙 headset 后端处理。 get_managed_objects_reply() 函数执行的最后,创建蓝牙后端对象。pulseaudio 支持两种蓝牙后端,分别为 native 和 ofono,native 后端直接与 BlueZ 交互;ofono 后端通过 ofono(一个开源的电话栈)实现蓝牙音频支持,主要用于支持电话功能(如 HFP),pulseaudio 通常使用 native 蓝牙后端。get_managed_objects_reply() 调用 pa_bluetooth_native_backend_new() 函数创建 native 蓝牙后端对象,这个函数定义 (位于 pulseaudio/src/modules/bluetooth/backend-native.c) 如下:

struct pa_bluetooth_backend {
  pa_core *core;
  pa_dbus_connection *connection;
  pa_bluetooth_discovery *discovery;
  pa_hook_slot *adapter_uuids_changed_slot;
  bool enable_shared_profiles;
  bool enable_hsp_hs;
  bool enable_hfp_hf;

  PA_LLIST_HEAD(pa_dbus_pending, pending);
};
 . . . . . .
static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
        DBusPendingCallNotifyFunction func, void *call_data) {

    pa_dbus_pending *p;
    DBusPendingCall *call;

    pa_assert(backend);
    pa_assert(m);

    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1));

    p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data);
    PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p);
    dbus_pending_call_set_notify(call, func, p, NULL);

    return p;
}
 . . . . . .
static void register_profile_reply(DBusPendingCall *pending, void *userdata) {
    DBusMessage *r;
    pa_dbus_pending *p;
    pa_bluetooth_backend *b;
    pa_bluetooth_profile_t profile;

    pa_assert(pending);
    pa_assert_se(p = userdata);
    pa_assert_se(b = p->context_data);
    pa_assert_se(profile = (pa_bluetooth_profile_t)p->call_data);
    pa_assert_se(r = dbus_pending_call_steal_reply(pending));

    if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
        pa_log_info("Couldn't register profile %s because it is disabled in BlueZ", pa_bluetooth_profile_to_string(profile));
        profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);
        goto finish;
    }

    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
        pa_log_error(BLUEZ_PROFILE_MANAGER_INTERFACE ".RegisterProfile() failed: %s: %s", dbus_message_get_error_name(r),
                     pa_dbus_get_error_message(r));
        profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);
        goto finish;
    }

    profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_REGISTERED);

finish:
    dbus_message_unref(r);

    PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
    pa_dbus_pending_free(p);
}

static void register_profile(pa_bluetooth_backend *b, const char *object, const char *uuid, pa_bluetooth_profile_t profile) {
    DBusMessage *m;
    DBusMessageIter i, d;
    dbus_bool_t autoconnect;
    dbus_uint16_t version, chan;

    pa_assert(profile_status_get(b->discovery, profile) == PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);

    pa_log_debug("Registering Profile %s %s", pa_bluetooth_profile_to_string(profile), uuid);

    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", BLUEZ_PROFILE_MANAGER_INTERFACE, "RegisterProfile"));

    dbus_message_iter_init_append(m, &i);
    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &object));
    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &uuid));
    dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY,
                                     DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
                                     DBUS_TYPE_STRING_AS_STRING
                                     DBUS_TYPE_VARIANT_AS_STRING
                                     DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
                                     &d);
    if (pa_bluetooth_uuid_is_hsp_hs(uuid)) {
        /* In the headset role, the connection will only be initiated from the remote side */
        autoconnect = 0;
        pa_dbus_append_basic_variant_dict_entry(&d, "AutoConnect", DBUS_TYPE_BOOLEAN, &autoconnect);
        chan = HSP_HS_DEFAULT_CHANNEL;
        pa_dbus_append_basic_variant_dict_entry(&d, "Channel", DBUS_TYPE_UINT16, &chan);
        /* HSP version 1.2 */
        version = 0x0102;
        pa_dbus_append_basic_variant_dict_entry(&d, "Version", DBUS_TYPE_UINT16, &version);
    }
    dbus_message_iter_close_container(&i, &d);

    profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_REGISTERING);
    send_and_add_to_pending(b, m, register_profile_reply, (void *)profile);
}
 . . . . . .
static void profile_init(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile) {
    static const DBusObjectPathVTable vtable_profile = {
        .message_function = profile_handler,
    };
    const char *object_name;
    const char *uuid;

    pa_assert(b);

    switch (profile) {
        case PA_BLUETOOTH_PROFILE_HSP_HS:
            object_name = HSP_AG_PROFILE;
            uuid = PA_BLUETOOTH_UUID_HSP_AG;
            break;
        case PA_BLUETOOTH_PROFILE_HSP_AG:
            object_name = HSP_HS_PROFILE;
            uuid = PA_BLUETOOTH_UUID_HSP_HS;
            break;
        case PA_BLUETOOTH_PROFILE_HFP_HF:
            object_name = HFP_AG_PROFILE;
            uuid = PA_BLUETOOTH_UUID_HFP_AG;
            break;
        default:
            pa_assert_not_reached();
            break;
    }

    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(b->connection), object_name, &vtable_profile, b));

    profile_status_set(b->discovery, profile, PA_BLUETOOTH_PROFILE_STATUS_ACTIVE);
    register_profile(b, object_name, uuid, profile);
}
 . . . . . .
static void native_backend_apply_profile_registration_change(pa_bluetooth_backend *native_backend, bool enable_shared_profiles) {
    if (enable_shared_profiles) {
        profile_init(native_backend, PA_BLUETOOTH_PROFILE_HSP_AG);
        if (native_backend->enable_hfp_hf)
            profile_init(native_backend, PA_BLUETOOTH_PROFILE_HFP_HF);
    } else {
        profile_done(native_backend, PA_BLUETOOTH_PROFILE_HSP_AG);
        if (native_backend->enable_hfp_hf)
            profile_done(native_backend, PA_BLUETOOTH_PROFILE_HFP_HF);
    }
}
 . . . . . .
 pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_shared_profiles) {
    pa_bluetooth_backend *backend;
    DBusError err;

    pa_log_debug("Bluetooth Headset Backend API support using the native backend");

    backend = pa_xnew0(pa_bluetooth_backend, 1);
    backend->core = c;

    dbus_error_init(&err);
    if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) {
        pa_log("Failed to get D-Bus connection: %s", err.message);
        dbus_error_free(&err);
        pa_xfree(backend);
        return NULL;
    }

    backend->discovery = y;
    backend->enable_shared_profiles = enable_shared_profiles;
    backend->enable_hfp_hf = pa_bluetooth_discovery_get_enable_native_hfp_hf(y);
    backend->enable_hsp_hs = pa_bluetooth_discovery_get_enable_native_hsp_hs(y);

    backend->adapter_uuids_changed_slot =
        pa_hook_connect(pa_bluetooth_discovery_hook(y, PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED), PA_HOOK_NORMAL,
                        (pa_hook_cb_t) adapter_uuids_changed_cb, backend);

    if (!backend->enable_hsp_hs && !backend->enable_hfp_hf)
        pa_log_warn("Both HSP HS and HFP HF bluetooth profiles disabled in native backend. Native backend will not register for headset connections.");

    if (backend->enable_hsp_hs)
        profile_init(backend, PA_BLUETOOTH_PROFILE_HSP_HS);

    if (backend->enable_shared_profiles)
        native_backend_apply_profile_registration_change(backend, true);

    return backend;
}

pulseaudio 中用 pa_bluetooth_backend 对象描述蓝牙后端对象,pa_bluetooth_native_backend_new() 函数创建 pa_bluetooth_backend 对象,注册蓝牙控制器/适配器 UUID 改变处理程序,并根据配置初始化不同的 profile。配置具体指 enable_hsp_hsenable_hfp_hfenable_shared_profiles,它们主要来源于 module-bluez5-discover 模块的模块参数。

HSP 是蓝牙协议栈中最早的耳机配置文件,主要用于支持蓝牙耳机和手机之间的基本音频通信,它支持单声道音频传输(通常用于语音通话),和基本的远程控制功能(如接听/挂断电话),主要用于简单的蓝牙耳机,功能较为基础。HFP 是 HSP 的升级版,提供了更丰富的功能,主要用于支持免提设备(如车载蓝牙系统)与手机之间的通信,它支持单声道音频传输(语音通话),更复杂的远程控制功能(如音量控制、重拨、拒接电话等),电话状态信息(如电池电量、信号强度等),及多方通话和语音识别,主要用于车载蓝牙系统、高端蓝牙耳机等。

enable_hfp_hf 用于配置是否开启 HFP,默认为 tureenable_hsp_hs 用于配置是否开启 HSP,默认为 falseenable_shared_profiles 根据 headset 蓝牙后端的配置得到,headset 蓝牙后端为 native 时为 ture,否则为 false,默认为 ture。默认初始化的 profile 如下:

(4480.011|   0.000) D: [pulseaudio] backend-native.c: Registering Profile headset_audio_gateway 00001108-0000-1000-8000-00805f9b34fb
(4480.011|   0.000) D: [pulseaudio] backend-native.c: Registering Profile handsfree_head_unit 0000111f-0000-1000-8000-00805f9b34fb

即初始化 profile PA_BLUETOOTH_PROFILE_HSP_AGPA_BLUETOOTH_PROFILE_HFP_HF

dbus_connection_register_object_path() 是 D-Bus 库的一个函数,用于向 D-Bus 连接注册一个对象路径。这个对象路径表示一个在 D-Bus 总线上可以被其它应用程序调用的方法、信号或者属性。通过注册对象路径,可以定义应用程序在 D-Bus 上的接口,使其可以被其它应用程序访问和调用。初始化 profile 由 profile_init() 函数处理,主要是为 profile 注册对应的对象路径,并通过 D-BUS 向蓝牙服务 BlueZ 注册 profile。不同 profile 的对象路径不同,它们的对应关系如下:

  • PA_BLUETOOTH_PROFILE_HSP_HSHSP_AG_PROFILE (/Profile/HSPAGProfile)
  • PA_BLUETOOTH_PROFILE_HSP_AGHSP_HS_PROFILE (/Profile/HSPHSProfile)
  • PA_BLUETOOTH_PROFILE_HFP_HFHSP_AG_PROFILE (/Profile/HFPAGProfile)

对象路径上的处理程序为 profile_handler() 函数。向蓝牙服务 BlueZ 注册 profile,将调用 BlueZ 注册的 org.bluez.ProfileManager1 接口的 RegisterProfile 方法。

对于 HFP/HSP 协议栈,蓝牙音频设备在物理上与蓝牙控制器/适配器建立连接之后,系统蓝牙服务 bluez 会收到通知,在回调中处理连接,并通过 D-BUS 调用 pulseaudio 注册的 profile 的 D-BUS 对象路径上的处理程序 profile_handler()

下面是蓝牙耳机连上蓝牙服务 BlueZ 时的一段日志:

2月 17 14:41:45 workstation bluetoothd[17672]: src/adapter.c:connected_callback() hci0 device 24:D0:DF:96:AD:6E connected eir_len 18
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avctp.c:avctp_confirm_cb() AVCTP: incoming connect from 24:D0:DF:96:AD:6E
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avctp.c:avctp_set_state() AVCTP Connecting
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avctp.c:avctp_connect_cb() AVCTP: connected to 24:D0:DF:96:AD:6E
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avctp.c:init_uinput() AVRCP: uinput initialized for AirPods Pro
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avrcp.c:controller_init() 0xaaab0f5d2f90 version 0x0105
2月 17 14:41:46 workstation bluetoothd[17672]: src/service.c:change_state() 0xaaab0f5bf950: device 24:D0:DF:96:AD:6E profile audio-avrcp-target state changed: disconnected -> connected (0)
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avrcp.c:target_init() 0xaaab0f5e00e0 version 0x0105
2月 17 14:41:46 workstation bluetoothd[17672]: src/service.c:change_state() 0xaaab0f5cafb0: device 24:D0:DF:96:AD:6E profile avrcp-controller state changed: disconnected -> connected (0)
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avctp.c:avctp_set_state() AVCTP Connected
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avrcp.c:handle_vendordep_pdu() AVRCP PDU 0x10, company 0x001958 len 0x0001
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avrcp.c:avrcp_handle_get_capabilities() id=3
2月 17 14:41:46 workstation bluetoothd[17672]: src/profile.c:ext_confirm() incoming connect from 24:D0:DF:96:AD:6E
2月 17 14:41:46 workstation bluetoothd[17672]: src/service.c:btd_service_ref() 0xaaab0f5db3e0: ref=3
2月 17 14:41:46 workstation bluetoothd[17672]: src/profile.c:ext_confirm() Hands-Free Voice gateway authorizing connection from 24:D0:DF:96:AD:6E
2月 17 14:41:46 workstation bluetoothd[17672]: src/profile.c:ext_auth() 24:D0:DF:96:AD:6E authorized to connect to Hands-Free Voice gateway
2月 17 14:41:46 workstation bluetoothd[17672]: src/profile.c:ext_connect() Hands-Free Voice gateway connected to 24:D0:DF:96:AD:6E
2月 17 14:41:46 workstation bluetoothd[17672]: src/service.c:change_state() 0xaaab0f5db3e0: device 24:D0:DF:96:AD:6E profile Hands-Free Voice gateway state changed: disconnected -> connecting (0)
2月 17 14:41:46 workstation bluetoothd[17672]: src/service.c:change_state() 0xaaab0f5db3e0: device 24:D0:DF:96:AD:6E profile Hands-Free Voice gateway state changed: connecting -> connected (0)
2月 17 14:41:46 workstation bluetoothd[17672]: src/device.c:device_profile_connected() Hands-Free Voice gateway Success (0)
2月 17 14:41:46 workstation bluetoothd[17672]: plugins/policy.c:service_cb() Added Hands-Free Voice gateway reconnect 1
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avrcp.c:handle_vendordep_pdu() AVRCP PDU 0x31, company 0x001958 len 0x0005
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:confirm_cb() AVDTP: incoming connect from 24:D0:DF:96:AD:6E
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/sink.c:sink_set_state() State changed /org/bluez/hci0/dev_24_D0_DF_96_AD_6E: SINK_STATE_DISCONNECTED -> SINK_STATE_CONNECTING
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_connect_cb() AVDTP: connected signaling channel to 24:D0:DF:96:AD:6E
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_connect_cb() AVDTP imtu=672, omtu=1023
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_register_remote_sep() seid 1 type 1 media 0 delay_reporting true
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:register_remote_sep() Found remote SEP: /org/bluez/hci0/dev_24_D0_DF_96_AD_6E/sep1
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_register_remote_sep() seid 2 type 1 media 0 delay_reporting true
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:register_remote_sep() Found remote SEP: /org/bluez/hci0/dev_24_D0_DF_96_AD_6E/sep2
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_register_remote_sep() seid 3 type 1 media 0 delay_reporting true
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:register_remote_sep() Found remote SEP: /org/bluez/hci0/dev_24_D0_DF_96_AD_6E/sep3
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:load_remote_sep() LastUsed: lseid 4 rseid 1
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_ref() 0xaaab0f5e7c20: ref=1
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:set_disconnect_timer() timeout 1
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:session_cb()
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_parse_cmd() Received DISCOVER_CMD
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:session_cb()
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_parse_cmd() Received  GET_ALL_CAPABILITIES_CMD
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:endpoint_getcap_ind() Source 0xaaab0f5b0030: Get_Capability_Ind
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:session_cb()
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_parse_cmd() Received  GET_ALL_CAPABILITIES_CMD
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:endpoint_getcap_ind() Source 0xaaab0f5ce470: Get_Capability_Ind
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:session_cb()
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_parse_cmd() Received  GET_ALL_CAPABILITIES_CMD
2月 17 14:41:46 workstation bluetoothd[17672]: profiles/audio/a2dp.c:endpoint_getcap_ind() Source 0xaaab0f5d11e0: Get_Capability_Ind
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/avdtp.c:session_cb()
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_parse_cmd() Received  GET_ALL_CAPABILITIES_CMD
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/a2dp.c:endpoint_getcap_ind() Source 0xaaab0f5d1480: Get_Capability_Ind
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/avdtp.c:session_cb()
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_parse_cmd() Received  GET_ALL_CAPABILITIES_CMD
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/a2dp.c:endpoint_getcap_ind() Source 0xaaab0f5c0720: Get_Capability_Ind
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/avdtp.c:session_cb()
2月 17 14:41:47 workstation bluetoothd[17672]: profiles/audio/avdtp.c:avdtp_pa

http://www.kler.cn/a/586738.html

相关文章:

  • 基于深度学习的蛀牙智能检测与语音提示系统【python源码+Pyqt5界面+数据集+训练代码】
  • C语言数据类型取值范围及格式化符号
  • CentOS 8 停止维护后通过 rpm 包手动安装 docker
  • 《P1540 [NOIP 2010 提高组] 机器翻译 题解》
  • Scala语言的数据库编程
  • 基于雪雁算法(Snow Geese Algorithm,SGA)的多个无人机协同路径规划(可以自定义无人机数量及起始点),MATLAB代码
  • MongoDB集合(表)自动创建机制
  • ffmpeg基础整理
  • 《AI浪潮中的璀璨新星:Meta Llama、Ollama与DeepSeek的深度剖析》:此文为AI自动生成
  • 利用matlab编制的转子动力学
  • springboot树形结构 支持模糊查询,返回匹配节点和父节点,其他节点不返回
  • Android开源库——RxJava和RxAndroid
  • Training-free neural architecture search: A review
  • docker构建镜像时总会有部分文件没有被更新,解决办法
  • Android Framework 之了解系统启动流程二
  • 011【fate/extra link】【概率论与数理统计】大数定律与中心极限定理 ,数理统计的基本概念,常用的统计三大分布,正态总体的抽样分布定理
  • Python 实现大文件的高并发下载
  • jenkins通过ssh连接远程服务器出错解决方案(Algorithm negotiation fail)
  • MySQL(第四周)
  • 基于“动手学强化学习”的知识点(六):第 19 章 目标导向的强化学习(gym版本 >= 0.26)