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_hs
、enable_hfp_hf
和 enable_shared_profiles
,它们主要来源于 module-bluez5-discover 模块的模块参数。
HSP 是蓝牙协议栈中最早的耳机配置文件,主要用于支持蓝牙耳机和手机之间的基本音频通信,它支持单声道音频传输(通常用于语音通话),和基本的远程控制功能(如接听/挂断电话),主要用于简单的蓝牙耳机,功能较为基础。HFP 是 HSP 的升级版,提供了更丰富的功能,主要用于支持免提设备(如车载蓝牙系统)与手机之间的通信,它支持单声道音频传输(语音通话),更复杂的远程控制功能(如音量控制、重拨、拒接电话等),电话状态信息(如电池电量、信号强度等),及多方通话和语音识别,主要用于车载蓝牙系统、高端蓝牙耳机等。
enable_hfp_hf
用于配置是否开启 HFP,默认为 ture
,enable_hsp_hs
用于配置是否开启 HSP,默认为 false
,enable_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_AG
和 PA_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_HS :HSP_AG_PROFILE (/Profile/HSPAGProfile)
- PA_BLUETOOTH_PROFILE_HSP_AG :HSP_HS_PROFILE (/Profile/HSPHSProfile)
- PA_BLUETOOTH_PROFILE_HFP_HF :HSP_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