2023-02-08 18:12:00 +01:00
/* Spa V4l2 dbus */
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
/* SPDX-License-Identifier: MIT */
2018-01-11 10:23:37 +01:00
# include <errno.h>
# include <stddef.h>
# include <unistd.h>
# include <stdio.h>
# include <sys/types.h>
# include <sys/stat.h>
2018-11-27 17:08:36 +01:00
# include <sys/socket.h>
2018-01-11 10:23:37 +01:00
# include <fcntl.h>
2023-03-05 01:03:13 +02:00
# include <limits.h>
2018-01-11 10:23:37 +01:00
2018-11-27 17:08:36 +01:00
# include <bluetooth/bluetooth.h>
2018-02-08 10:02:17 +01:00
# include <dbus/dbus.h>
2023-01-18 17:41:16 +01:00
# include <spa/debug/mem.h>
# include <spa/debug/log.h>
2018-01-11 10:23:37 +01:00
# include <spa/support/log.h>
# include <spa/support/loop.h>
# include <spa/support/dbus.h>
2018-08-13 17:17:23 +02:00
# include <spa/support/plugin.h>
2021-09-01 00:33:43 +03:00
# include <spa/support/plugin-loader.h>
2019-09-20 13:04:14 +02:00
# include <spa/monitor/device.h>
2019-05-20 10:14:00 +02:00
# include <spa/monitor/utils.h>
2019-09-20 13:04:14 +02:00
# include <spa/utils/hook.h>
2018-08-23 17:47:57 +02:00
# include <spa/utils/type.h>
2019-06-03 16:48:01 +02:00
# include <spa/utils/keys.h>
2019-06-21 13:31:34 +02:00
# include <spa/utils/names.h>
2020-12-06 09:32:12 +01:00
# include <spa/utils/result.h>
2021-05-18 11:36:13 +10:00
# include <spa/utils/string.h>
2021-01-30 18:15:58 +02:00
# include <spa/utils/json.h>
2023-12-30 01:08:31 +01:00
# include <spa-private/dbus-helpers.h>
2018-11-21 15:51:44 +01:00
2022-05-27 15:00:17 +02:00
# include "config.h"
2021-09-01 00:33:43 +03:00
# include "codec-loader.h"
2021-10-09 19:11:51 +03:00
# include "player.h"
2023-03-25 16:58:27 +02:00
# include "iso-io.h"
2023-10-21 13:36:10 +03:00
# include "bap-codec-caps.h"
2018-01-11 10:23:37 +01:00
# include "defs.h"
2023-12-23 21:07:45 +02:00
SPA_LOG_TOPIC_DEFINE_STATIC ( log_topic , " spa.bluez5 " ) ;
2021-10-01 19:03:49 +03:00
# undef SPA_LOG_TOPIC_DEFAULT
# define SPA_LOG_TOPIC_DEFAULT &log_topic
2018-01-11 10:23:37 +01:00
2021-08-29 18:22:41 +03:00
enum backend_selection {
BACKEND_NONE = - 2 ,
BACKEND_ANY = - 1 ,
BACKEND_HSPHFPD = 0 ,
BACKEND_OFONO = 1 ,
BACKEND_NATIVE = 2 ,
BACKEND_NUM ,
} ;
2021-02-10 01:14:58 +02:00
/*
* Rate limit for BlueZ SetConfiguration calls .
*
* Too rapid calls to BlueZ API may cause A2DP profile to disappear , as the
* internal BlueZ / connection state gets confused . Use some reasonable minimum
* interval .
*
* AVDTP v1 .3 Sec . 6.13 mentions 3 seconds as a reasonable timeout in one case
* ( ACP connection reset timeout , if no INT response ) . The case here is
* different , but we assume a similar value is fine here .
*/
# define BLUEZ_ACTION_RATE_MSEC 3000
2022-01-04 22:08:25 +02:00
# define CODEC_SWITCH_RETRIES 1
2023-03-18 14:48:16 +02:00
/* How many times to retry acquire on errors, and how long delay to require before we can
* try again .
*/
# define TRANSPORT_ERROR_MAX_RETRY 3
# define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC)
2021-02-10 01:14:58 +02:00
2021-03-11 11:00:11 +03:00
struct spa_bt_monitor {
struct spa_handle handle ;
struct spa_device device ;
struct spa_log * log ;
struct spa_loop * main_loop ;
2023-03-25 16:58:27 +02:00
struct spa_loop * data_loop ;
2021-03-11 11:00:11 +03:00
struct spa_system * main_system ;
2023-03-25 16:58:27 +02:00
struct spa_system * data_system ;
2021-09-01 00:33:43 +03:00
struct spa_plugin_loader * plugin_loader ;
2021-03-11 11:00:11 +03:00
struct spa_dbus * dbus ;
struct spa_dbus_connection * dbus_connection ;
DBusConnection * conn ;
struct spa_hook_list hooks ;
uint32_t id ;
2022-06-15 17:24:41 +02:00
const struct media_codec * const * media_codecs ;
2021-09-01 00:33:43 +03:00
2021-03-11 11:00:11 +03:00
/*
* Lists of BlueZ objects , kept up - to - date by following DBus events
* initiated by BlueZ . Object lifetime is also determined by that .
*/
struct spa_list adapter_list ;
struct spa_list device_list ;
struct spa_list remote_endpoint_list ;
struct spa_list transport_list ;
unsigned int filters_added : 1 ;
unsigned int objects_listed : 1 ;
2022-10-08 01:11:09 +02:00
DBusPendingCall * get_managed_objects_call ;
2021-03-11 11:00:11 +03:00
2021-08-29 18:22:41 +03:00
struct spa_bt_backend * backend ;
struct spa_bt_backend * backends [ BACKEND_NUM ] ;
enum backend_selection backend_selection ;
2021-03-11 11:00:11 +03:00
struct spa_dict enabled_codecs ;
2023-03-16 20:40:17 +02:00
enum spa_bt_profile enabled_profiles ;
2021-03-16 10:56:27 +08:00
unsigned int connection_info_supported : 1 ;
2021-10-09 19:11:51 +03:00
unsigned int dummy_avrcp_player : 1 ;
2021-01-24 15:15:27 +02:00
2024-04-22 09:35:49 +03:00
struct spa_list bcast_source_config_list ;
2021-01-24 15:15:27 +02:00
struct spa_bt_quirks * quirks ;
2022-06-04 19:01:51 +03:00
# define MAX_SETTINGS 128
struct spa_dict_item global_setting_items [ MAX_SETTINGS ] ;
struct spa_dict global_settings ;
2021-05-03 15:40:13 +08:00
/* A reference audio info for A2DP codec configuration. */
2022-06-15 17:24:41 +02:00
struct media_codec_audio_info default_audio_info ;
2021-03-11 11:00:11 +03:00
} ;
2021-01-24 20:38:13 +02:00
/* Stream endpoints owned by BlueZ for each device */
struct spa_bt_remote_endpoint {
struct spa_list link ;
struct spa_list device_link ;
struct spa_bt_monitor * monitor ;
char * path ;
char * uuid ;
unsigned int codec ;
struct spa_bt_device * device ;
uint8_t * capabilities ;
int capabilities_len ;
bool delay_reporting ;
2022-08-30 15:45:03 +02:00
bool acceptor ;
2021-01-24 20:38:13 +02:00
} ;
2024-04-22 09:35:49 +03:00
# define METADATA_MAX_LEN 255
# define CC_MAX_LEN 255
2024-05-13 18:01:12 +03:00
/*
* This structure stores metadata as defined
* in Assigned Numbers chapter 6.12 .6 Metadata
* LTV structures . Length contains the size of
* type and value .
*/
2024-04-22 09:35:49 +03:00
struct spa_bt_metadata {
struct spa_list link ;
int length ;
int type ;
2024-05-13 18:01:12 +03:00
uint8_t value [ METADATA_MAX_LEN - 1 ] ;
2024-04-22 09:35:49 +03:00
} ;
struct spa_bt_bis {
struct spa_list link ;
char qos_preset [ 255 ] ;
int channel_allocation ;
struct spa_list metadata_list ;
} ;
# define BROADCAST_CODE_LEN 16
struct spa_bt_big {
struct spa_list link ;
int broadcast_code [ BROADCAST_CODE_LEN ] ;
int presentation_delay ;
struct spa_list bis_list ;
int big_id ;
} ;
2021-01-25 19:57:45 +02:00
/*
* Codec switching tries various codec / remote endpoint combinations
* in order , until an acceptable one is found . This triggers BlueZ
* to initiate DBus calls that result to the creation of a transport
* with the desired capabilities .
* The codec switch struct tracks candidates still to be tried .
*/
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch {
2021-01-25 19:57:45 +02:00
struct spa_bt_device * device ;
struct spa_list device_link ;
2021-02-10 01:14:58 +02:00
/*
* Codec switch may be waiting for either DBus reply from BlueZ
* or a timeout ( but not both ) .
*/
struct spa_source timer ;
2021-01-25 19:57:45 +02:00
DBusPendingCall * pending ;
uint32_t profile ;
/*
* Called asynchronously , so endpoint paths instead of pointers ( which may be
* invalidated in the meantime ) .
*/
2022-06-15 17:24:41 +02:00
const struct media_codec * * codecs ;
2021-01-25 19:57:45 +02:00
char * * paths ;
2022-06-15 17:24:41 +02:00
const struct media_codec * * codec_iter ; /**< outer iterator over codecs */
2021-01-25 19:57:45 +02:00
char * * path_iter ; /**< inner iterator over endpoint paths */
2022-01-04 22:08:25 +02:00
uint16_t retries ;
2021-01-25 19:57:45 +02:00
size_t num_paths ;
} ;
2021-04-17 18:53:28 +08:00
# define DEFAULT_RECONNECT_PROFILES SPA_BT_PROFILE_NULL
2021-06-19 23:49:58 +03:00
# define DEFAULT_HW_VOLUME_PROFILES (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | SPA_BT_PROFILE_HEADSET_HEAD_UNIT | \
SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_A2DP_SINK )
2021-04-17 18:53:28 +08:00
2021-03-14 14:06:50 +08:00
# define BT_DEVICE_DISCONNECTED 0
# define BT_DEVICE_CONNECTED 1
# define BT_DEVICE_INIT -1
2021-01-04 15:54:27 +02:00
/*
* SCO socket connect may fail with ECONNABORTED if it is done too soon after
* previous close . To avoid this in cases where nodes are toggled between
* stopped / started rapidly , postpone release until the transport has remained
2023-03-26 16:15:33 +03:00
* unused for a time .
*
2023-08-12 20:02:40 +03:00
* Avoiding unnecessary release + reacquire also makes sense for ISO .
2021-01-04 15:54:27 +02:00
*/
2023-03-26 16:15:33 +03:00
# define TRANSPORT_RELEASE_TIMEOUT_MSEC 1000
2021-01-04 15:54:27 +02:00
2021-04-15 11:39:49 +08:00
# define TRANSPORT_VOLUME_TIMEOUT_MSEC 200
2023-08-12 20:02:40 +03:00
# define SPA_BT_TRANSPORT_IS_A2DP(transport) ((transport)->profile & (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_A2DP_SINK))
2021-04-15 11:39:49 +08:00
static int spa_bt_transport_stop_volume_timer ( struct spa_bt_transport * transport ) ;
static int spa_bt_transport_start_volume_timer ( struct spa_bt_transport * transport ) ;
2021-01-04 15:54:27 +02:00
static int spa_bt_transport_stop_release_timer ( struct spa_bt_transport * transport ) ;
static int spa_bt_transport_start_release_timer ( struct spa_bt_transport * transport ) ;
2023-05-25 19:12:12 +03:00
static void spa_bt_transport_commit_release_timer ( struct spa_bt_transport * transport ) ;
2021-01-04 15:54:27 +02:00
2021-06-21 14:47:13 +08:00
static int device_start_timer ( struct spa_bt_device * device ) ;
static int device_stop_timer ( struct spa_bt_device * device ) ;
2023-07-03 04:20:21 +02:00
static void media_codec_switch_free ( struct spa_bt_media_codec_switch * sw ) ;
2021-03-11 11:00:11 +03:00
// Working with BlueZ Battery Provider.
// Developed using https://github.com/dgreid/adhd/commit/655b58f as an example of DBus calls.
// Name of battery, formatted as /org/freedesktop/pipewire/battery/org/bluez/hciX/dev_XX_XX_XX_XX_XX_XX
2021-03-18 09:49:50 +08:00
static char * battery_get_name ( const char * device_path )
2021-03-11 11:00:11 +03:00
{
2023-07-03 04:23:03 +02:00
return spa_aprintf ( PIPEWIRE_BATTERY_PROVIDER " %s " , device_path ) ;
2021-03-11 11:00:11 +03:00
}
// Unregister virtual battery of device
2023-07-11 19:24:46 +02:00
static void battery_remove ( struct spa_bt_device * device )
{
2021-03-18 09:49:50 +08:00
DBusMessageIter i , entry ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2021-03-18 09:49:50 +08:00
const char * interface ;
2023-07-11 17:01:00 +02:00
cancel_and_unref ( & device - > battery_pending_call ) ;
2021-04-05 14:58:50 +03:00
2022-01-19 19:42:03 +02:00
if ( ! device - > adapter | | ! device - > adapter - > has_battery_provider | | ! device - > has_battery )
2021-03-18 09:49:50 +08:00
return ;
2021-03-11 11:00:11 +03:00
2021-10-01 19:03:49 +03:00
spa_log_debug ( device - > monitor - > log , " Removing virtual battery: %s " , device - > battery_path ) ;
2021-03-11 11:00:11 +03:00
2021-03-18 09:49:50 +08:00
m = dbus_message_new_signal ( PIPEWIRE_BATTERY_PROVIDER ,
2021-03-11 11:00:11 +03:00
DBUS_INTERFACE_OBJECT_MANAGER ,
DBUS_SIGNAL_INTERFACES_REMOVED ) ;
dbus_message_iter_init_append ( m , & i ) ;
dbus_message_iter_append_basic ( & i , DBUS_TYPE_OBJECT_PATH ,
2021-03-18 09:49:50 +08:00
& device - > battery_path ) ;
2021-03-11 11:00:11 +03:00
dbus_message_iter_open_container ( & i , DBUS_TYPE_ARRAY ,
DBUS_TYPE_STRING_AS_STRING , & entry ) ;
2021-03-18 09:49:50 +08:00
interface = BLUEZ_INTERFACE_BATTERY_PROVIDER ;
2021-03-11 11:00:11 +03:00
dbus_message_iter_append_basic ( & entry , DBUS_TYPE_STRING ,
& interface ) ;
dbus_message_iter_close_container ( & i , & entry ) ;
if ( ! dbus_connection_send ( device - > monitor - > conn , m , NULL ) ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( device - > monitor - > log , " sending " DBUS_SIGNAL_INTERFACES_REMOVED " failed " ) ;
2021-03-11 11:00:11 +03:00
}
device - > has_battery = false ;
}
// Create properties for Battery Provider request
static void battery_write_properties ( DBusMessageIter * iter , struct spa_bt_device * device )
{
DBusMessageIter dict , entry , variant ;
dbus_message_iter_open_container ( iter , DBUS_TYPE_ARRAY , " {sv} " , & dict ) ;
dbus_message_iter_open_container ( & dict , DBUS_TYPE_DICT_ENTRY , NULL ,
& entry ) ;
const char * prop_percentage = " Percentage " ;
dbus_message_iter_append_basic ( & entry , DBUS_TYPE_STRING , & prop_percentage ) ;
dbus_message_iter_open_container ( & entry , DBUS_TYPE_VARIANT ,
DBUS_TYPE_BYTE_AS_STRING , & variant ) ;
dbus_message_iter_append_basic ( & variant , DBUS_TYPE_BYTE , & device - > battery ) ;
dbus_message_iter_close_container ( & entry , & variant ) ;
dbus_message_iter_close_container ( & dict , & entry ) ;
dbus_message_iter_open_container ( & dict , DBUS_TYPE_DICT_ENTRY , NULL , & entry ) ;
const char * prop_device = " Device " ;
dbus_message_iter_append_basic ( & entry , DBUS_TYPE_STRING , & prop_device ) ;
dbus_message_iter_open_container ( & entry , DBUS_TYPE_VARIANT ,
DBUS_TYPE_OBJECT_PATH_AS_STRING ,
& variant ) ;
dbus_message_iter_append_basic ( & variant , DBUS_TYPE_OBJECT_PATH , & device - > path ) ;
dbus_message_iter_close_container ( & entry , & variant ) ;
dbus_message_iter_close_container ( & dict , & entry ) ;
dbus_message_iter_close_container ( iter , & dict ) ;
}
// Send current percentage to BlueZ
static void battery_update ( struct spa_bt_device * device )
{
2021-10-01 19:03:49 +03:00
spa_log_debug ( device - > monitor - > log , " updating battery: %s " , device - > battery_path ) ;
2021-03-11 11:00:11 +03:00
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) msg = NULL ;
2021-03-11 11:00:11 +03:00
DBusMessageIter iter ;
2021-03-18 09:49:50 +08:00
msg = dbus_message_new_signal ( device - > battery_path ,
2021-03-11 11:00:11 +03:00
DBUS_INTERFACE_PROPERTIES ,
DBUS_SIGNAL_PROPERTIES_CHANGED ) ;
dbus_message_iter_init_append ( msg , & iter ) ;
const char * interface = BLUEZ_INTERFACE_BATTERY_PROVIDER ;
dbus_message_iter_append_basic ( & iter , DBUS_TYPE_STRING ,
& interface ) ;
battery_write_properties ( & iter , device ) ;
if ( ! dbus_connection_send ( device - > monitor - > conn , msg , NULL ) )
2021-10-01 19:03:49 +03:00
spa_log_error ( device - > monitor - > log , " Error updating battery " ) ;
2021-03-11 11:00:11 +03:00
}
2021-04-28 20:29:44 +02:00
// Create new virtual battery with value stored in current device object
2023-07-11 19:24:46 +02:00
static void battery_create ( struct spa_bt_device * device )
{
spa_autoptr ( DBusMessage ) msg = NULL ;
2021-03-11 11:00:11 +03:00
DBusMessageIter iter , entry , dict ;
msg = dbus_message_new_signal ( PIPEWIRE_BATTERY_PROVIDER ,
DBUS_INTERFACE_OBJECT_MANAGER ,
DBUS_SIGNAL_INTERFACES_ADDED ) ;
dbus_message_iter_init_append ( msg , & iter ) ;
dbus_message_iter_append_basic ( & iter , DBUS_TYPE_OBJECT_PATH ,
2021-03-18 09:49:50 +08:00
& device - > battery_path ) ;
2021-03-11 11:00:11 +03:00
dbus_message_iter_open_container ( & iter , DBUS_TYPE_ARRAY , " {sa{sv}} " , & dict ) ;
dbus_message_iter_open_container ( & dict , DBUS_TYPE_DICT_ENTRY , NULL , & entry ) ;
const char * interface = BLUEZ_INTERFACE_BATTERY_PROVIDER ;
dbus_message_iter_append_basic ( & entry , DBUS_TYPE_STRING ,
& interface ) ;
battery_write_properties ( & entry , device ) ;
dbus_message_iter_close_container ( & dict , & entry ) ;
dbus_message_iter_close_container ( & iter , & dict ) ;
if ( ! dbus_connection_send ( device - > monitor - > conn , msg , NULL ) ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( device - > monitor - > log , " Failed to create virtual battery for %s " , device - > address ) ;
2021-03-11 11:00:11 +03:00
return ;
}
2021-10-01 19:03:49 +03:00
spa_log_debug ( device - > monitor - > log , " Created virtual battery for %s " , device - > address ) ;
2021-03-11 11:00:11 +03:00
device - > has_battery = true ;
}
static void on_battery_provider_registered ( DBusPendingCall * pending_call ,
void * data )
{
struct spa_bt_device * device = data ;
2023-07-11 17:10:37 +02:00
spa_assert ( device - > battery_pending_call = = pending_call ) ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) reply = steal_reply_and_unref ( & device - > battery_pending_call ) ;
2021-03-11 18:09:51 +03:00
2021-03-11 11:00:11 +03:00
if ( dbus_message_get_type ( reply ) = = DBUS_MESSAGE_TYPE_ERROR ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( device - > monitor - > log , " Failed to register battery provider. Error: %s " , dbus_message_get_error_name ( reply ) ) ;
spa_log_error ( device - > monitor - > log , " BlueZ Battery Provider is not available, won't retry to register it. Make sure you are running BlueZ 5.56+ with experimental features to use Battery Provider. " ) ;
2021-03-11 11:00:11 +03:00
device - > adapter - > battery_provider_unavailable = true ;
return ;
}
2021-10-01 19:03:49 +03:00
spa_log_debug ( device - > monitor - > log , " Registered Battery Provider " ) ;
2021-03-11 11:00:11 +03:00
device - > adapter - > has_battery_provider = true ;
if ( ! device - > has_battery )
battery_create ( device ) ;
}
// Register Battery Provider for adapter and then create virtual battery for device
static void register_battery_provider ( struct spa_bt_device * device )
{
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) method_call = NULL ;
2021-03-11 11:00:11 +03:00
DBusMessageIter message_iter ;
2021-04-05 14:58:50 +03:00
if ( device - > battery_pending_call ) {
2021-10-01 19:03:49 +03:00
spa_log_debug ( device - > monitor - > log , " Already registering battery provider " ) ;
2021-04-05 14:58:50 +03:00
return ;
}
2021-03-11 11:00:11 +03:00
method_call = dbus_message_new_method_call (
BLUEZ_SERVICE , device - > adapter_path ,
BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER ,
" RegisterBatteryProvider " ) ;
if ( ! method_call ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( device - > monitor - > log , " Failed to register battery provider " ) ;
2021-03-11 11:00:11 +03:00
return ;
}
dbus_message_iter_init_append ( method_call , & message_iter ) ;
const char * object_path = PIPEWIRE_BATTERY_PROVIDER ;
dbus_message_iter_append_basic ( & message_iter , DBUS_TYPE_OBJECT_PATH ,
& object_path ) ;
2023-07-11 19:57:23 +02:00
device - > battery_pending_call = send_with_reply ( device - > monitor - > conn , method_call ,
on_battery_provider_registered , device ) ;
2021-03-11 18:09:51 +03:00
if ( ! device - > battery_pending_call ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( device - > monitor - > log , " Failed to register battery provider " ) ;
2021-03-11 11:00:11 +03:00
return ;
}
}
2021-01-04 15:54:27 +02:00
2022-06-15 17:24:41 +02:00
static int media_codec_to_endpoint ( const struct media_codec * codec ,
2022-06-16 17:15:14 +02:00
enum spa_bt_media_direction direction ,
2020-12-29 10:21:03 +08:00
char * * object_path )
{
2022-06-16 17:15:14 +02:00
const char * endpoint ;
if ( direction = = SPA_BT_MEDIA_SOURCE )
endpoint = codec - > bap ? BAP_SOURCE_ENDPOINT : A2DP_SOURCE_ENDPOINT ;
2023-12-30 01:28:16 +01:00
else if ( direction = = SPA_BT_MEDIA_SINK )
2022-06-16 17:15:14 +02:00
endpoint = codec - > bap ? BAP_SINK_ENDPOINT : A2DP_SINK_ENDPOINT ;
2023-12-30 01:28:16 +01:00
else if ( direction = = SPA_BT_MEDIA_SOURCE_BROADCAST )
2023-07-23 22:16:17 +03:00
endpoint = BAP_BROADCAST_SOURCE_ENDPOINT ;
2023-12-30 01:28:16 +01:00
else if ( direction = = SPA_BT_MEDIA_SINK_BROADCAST )
2023-07-23 22:16:17 +03:00
endpoint = BAP_BROADCAST_SINK_ENDPOINT ;
2022-06-16 17:15:14 +02:00
2021-08-15 20:12:51 +03:00
* object_path = spa_aprintf ( " %s/%s " , endpoint ,
codec - > endpoint_name ? codec - > endpoint_name : codec - > name ) ;
2020-12-29 10:21:03 +08:00
if ( * object_path = = NULL )
return - errno ;
return 0 ;
}
2022-10-23 14:05:05 +03:00
static const struct media_codec * media_endpoint_to_codec ( struct spa_bt_monitor * monitor , const char * endpoint , bool * sink , const struct media_codec * preferred )
2020-10-19 13:27:11 +02:00
{
2021-08-15 20:12:51 +03:00
const char * ep_name ;
2022-06-15 17:24:41 +02:00
const struct media_codec * const * const media_codecs = monitor - > media_codecs ;
2022-10-23 14:05:05 +03:00
const struct media_codec * found = NULL ;
2020-10-19 13:27:11 +02:00
int i ;
2022-05-21 13:18:38 +03:00
if ( spa_strstartswith ( endpoint , A2DP_SINK_ENDPOINT " / " ) ) {
2021-08-15 20:12:51 +03:00
ep_name = endpoint + strlen ( A2DP_SINK_ENDPOINT " / " ) ;
2022-05-21 13:18:38 +03:00
* sink = true ;
} else if ( spa_strstartswith ( endpoint , A2DP_SOURCE_ENDPOINT " / " ) ) {
2021-08-15 20:12:51 +03:00
ep_name = endpoint + strlen ( A2DP_SOURCE_ENDPOINT " / " ) ;
2022-05-21 13:18:38 +03:00
* sink = false ;
2022-06-16 17:15:14 +02:00
} else if ( spa_strstartswith ( endpoint , BAP_SOURCE_ENDPOINT " / " ) ) {
ep_name = endpoint + strlen ( BAP_SOURCE_ENDPOINT " / " ) ;
* sink = false ;
} else if ( spa_strstartswith ( endpoint , BAP_SINK_ENDPOINT " / " ) ) {
ep_name = endpoint + strlen ( BAP_SINK_ENDPOINT " / " ) ;
* sink = true ;
2023-07-23 22:16:17 +03:00
} else if ( spa_strstartswith ( endpoint , BAP_BROADCAST_SOURCE_ENDPOINT " / " ) ) {
ep_name = endpoint + strlen ( BAP_BROADCAST_SOURCE_ENDPOINT " / " ) ;
* sink = false ;
} else if ( spa_strstartswith ( endpoint , BAP_BROADCAST_SINK_ENDPOINT " / " ) ) {
ep_name = endpoint + strlen ( BAP_BROADCAST_SINK_ENDPOINT " / " ) ;
* sink = true ;
2022-05-21 13:18:38 +03:00
} else {
2022-06-16 17:15:14 +02:00
* sink = true ;
2020-10-19 13:27:11 +02:00
return NULL ;
2022-05-21 13:18:38 +03:00
}
2020-10-19 13:27:11 +02:00
2022-06-15 17:24:41 +02:00
for ( i = 0 ; media_codecs [ i ] ; i + + ) {
const struct media_codec * codec = media_codecs [ i ] ;
2021-08-15 20:12:51 +03:00
const char * codec_ep_name =
codec - > endpoint_name ? codec - > endpoint_name : codec - > name ;
2022-10-23 14:05:05 +03:00
2024-01-22 22:36:43 +02:00
if ( ! preferred & & ! codec - > fill_caps )
continue ;
2022-10-23 14:05:05 +03:00
if ( ! spa_streq ( ep_name , codec_ep_name ) )
continue ;
if ( ( * sink & & ! codec - > decode ) | | ( ! * sink & & ! codec - > encode ) )
continue ;
/* Same endpoint may be shared with multiple codec objects,
* which may e . g . correspond to different encoder settings .
* Look up which one we selected .
*/
if ( ( preferred & & codec = = preferred ) | | found = = NULL )
found = codec ;
2020-10-19 13:27:11 +02:00
}
2022-10-23 14:05:05 +03:00
return found ;
2020-10-19 13:27:11 +02:00
}
2022-06-15 17:24:41 +02:00
static int media_endpoint_to_profile ( const char * endpoint )
2021-04-15 11:39:49 +08:00
{
2021-08-02 14:05:45 +10:00
if ( spa_strstartswith ( endpoint , A2DP_SINK_ENDPOINT " / " ) )
2021-04-15 11:39:49 +08:00
return SPA_BT_PROFILE_A2DP_SOURCE ;
2021-08-02 14:05:45 +10:00
else if ( spa_strstartswith ( endpoint , A2DP_SOURCE_ENDPOINT " / " ) )
2021-04-15 11:39:49 +08:00
return SPA_BT_PROFILE_A2DP_SINK ;
2022-06-16 17:15:14 +02:00
else if ( spa_strstartswith ( endpoint , BAP_SINK_ENDPOINT " / " ) )
return SPA_BT_PROFILE_BAP_SOURCE ;
else if ( spa_strstartswith ( endpoint , BAP_SOURCE_ENDPOINT " / " ) )
return SPA_BT_PROFILE_BAP_SINK ;
2023-07-23 22:16:17 +03:00
else if ( spa_strstartswith ( endpoint , BAP_BROADCAST_SINK_ENDPOINT " / " ) )
return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ;
else if ( spa_strstartswith ( endpoint , BAP_BROADCAST_SOURCE_ENDPOINT " / " ) )
return SPA_BT_PROFILE_BAP_BROADCAST_SINK ;
2021-04-15 11:39:49 +08:00
else
return SPA_BT_PROFILE_NULL ;
}
2022-06-15 17:24:41 +02:00
static bool is_media_codec_enabled ( struct spa_bt_monitor * monitor , const struct media_codec * codec )
2021-01-30 18:15:58 +02:00
{
return spa_dict_lookup ( & monitor - > enabled_codecs , codec - > name ) ! = NULL ;
}
2022-10-23 14:05:05 +03:00
static bool codec_has_direction ( const struct media_codec * codec , enum spa_bt_media_direction direction )
{
switch ( direction ) {
case SPA_BT_MEDIA_SOURCE :
2023-07-23 22:16:17 +03:00
case SPA_BT_MEDIA_SOURCE_BROADCAST :
2022-10-23 14:05:05 +03:00
return codec - > encode ;
case SPA_BT_MEDIA_SINK :
2023-08-17 18:52:48 +03:00
case SPA_BT_MEDIA_SINK_BROADCAST :
2022-10-23 14:05:05 +03:00
return codec - > decode ;
default :
spa_assert_not_reached ( ) ;
}
}
2023-03-16 20:40:17 +02:00
static enum spa_bt_profile get_codec_profile ( const struct media_codec * codec ,
enum spa_bt_media_direction direction )
{
switch ( direction ) {
case SPA_BT_MEDIA_SOURCE :
return codec - > bap ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE ;
case SPA_BT_MEDIA_SINK :
return codec - > bap ? SPA_BT_PROFILE_BAP_SINK : SPA_BT_PROFILE_A2DP_SINK ;
2023-08-02 11:19:06 +03:00
case SPA_BT_MEDIA_SOURCE_BROADCAST :
return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ;
2023-08-17 18:52:48 +03:00
case SPA_BT_MEDIA_SINK_BROADCAST :
return SPA_BT_PROFILE_BAP_BROADCAST_SINK ;
2023-03-16 20:40:17 +02:00
default :
spa_assert_not_reached ( ) ;
}
}
2023-09-02 15:06:27 +03:00
static enum spa_bt_profile swap_profile ( enum spa_bt_profile profile )
{
switch ( profile ) {
case SPA_BT_PROFILE_A2DP_SOURCE :
return SPA_BT_PROFILE_A2DP_SINK ;
case SPA_BT_PROFILE_A2DP_SINK :
return SPA_BT_PROFILE_A2DP_SOURCE ;
case SPA_BT_PROFILE_BAP_SOURCE :
return SPA_BT_PROFILE_BAP_SINK ;
case SPA_BT_PROFILE_BAP_SINK :
return SPA_BT_PROFILE_BAP_SOURCE ;
case SPA_BT_PROFILE_BAP_BROADCAST_SOURCE :
return SPA_BT_PROFILE_BAP_BROADCAST_SINK ;
case SPA_BT_PROFILE_BAP_BROADCAST_SINK :
return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ;
default :
return SPA_BT_PROFILE_NULL ;
}
}
2022-10-23 14:05:05 +03:00
static bool endpoint_should_be_registered ( struct spa_bt_monitor * monitor ,
const struct media_codec * codec ,
enum spa_bt_media_direction direction )
{
/* Codecs with fill_caps == NULL share endpoint with another codec,
* and don ' t have their own endpoint
*/
return is_media_codec_enabled ( monitor , codec ) & &
codec_has_direction ( codec , direction ) & &
2023-03-16 20:40:17 +02:00
codec - > fill_caps & &
( get_codec_profile ( codec , direction ) & monitor - > enabled_profiles ) ;
2022-10-23 14:05:05 +03:00
}
2018-01-11 10:23:37 +01:00
static DBusHandlerResult endpoint_select_configuration ( DBusConnection * conn , DBusMessage * m , void * userdata )
{
struct spa_bt_monitor * monitor = userdata ;
2018-11-21 15:51:44 +01:00
const char * path ;
2020-10-19 12:12:21 +02:00
uint8_t * cap , config [ A2DP_MAX_CAPS_SIZE ] ;
2018-11-21 15:51:44 +01:00
uint8_t * pconf = ( uint8_t * ) config ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = NULL ;
2023-07-11 20:44:23 +02:00
spa_auto ( DBusError ) err = DBUS_ERROR_INIT ;
2022-07-18 11:26:59 +02:00
int size , res ;
2022-06-15 17:24:41 +02:00
const struct media_codec * codec ;
2022-05-21 13:18:38 +03:00
bool sink ;
2018-01-11 10:23:37 +01:00
2018-11-21 15:51:44 +01:00
path = dbus_message_get_path ( m ) ;
2018-01-11 10:23:37 +01:00
if ( ! dbus_message_get_args ( m , & err , DBUS_TYPE_ARRAY ,
DBUS_TYPE_BYTE , & cap , & size , DBUS_TYPE_INVALID ) ) {
spa_log_error ( monitor - > log , " Endpoint SelectConfiguration(): %s " , err . message ) ;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
2020-12-05 20:56:56 +01:00
spa_log_info ( monitor - > log , " %p: %s select conf %d " , monitor , path , size ) ;
2023-01-18 17:41:16 +01:00
spa_debug_log_mem ( monitor - > log , SPA_LOG_LEVEL_DEBUG , 2 , cap , ( size_t ) size ) ;
2018-01-11 10:23:37 +01:00
2022-10-23 14:05:05 +03:00
/* For codecs sharing the same endpoint, BlueZ-initiated connections
* always pick the default one . The session manager will
* switch the codec to a saved value after connection , so this generally
* does not matter .
*/
codec = media_endpoint_to_codec ( monitor , path , & sink , NULL ) ;
spa_log_debug ( monitor - > log , " %p: %s codec:%s " , monitor , path , codec ? codec - > name : " <null> " ) ;
2021-01-25 22:06:34 +02:00
if ( codec ! = NULL )
2021-03-14 17:53:31 +08:00
/* FIXME: We can't determine which device the SelectConfiguration()
* call is associated with , therefore device settings are not passed .
* This causes inconsistency with SelectConfiguration ( ) triggered
* by codec switching .
*/
2022-06-15 17:24:41 +02:00
res = codec - > select_config ( codec , sink ? MEDIA_CODEC_FLAG_SINK : 0 , cap , size , & monitor - > default_audio_info ,
2022-06-04 19:01:51 +03:00
& monitor - > global_settings , config ) ;
2020-10-19 13:27:11 +02:00
else
2018-11-21 15:51:44 +01:00
res = - ENOTSUP ;
2020-12-06 09:32:12 +01:00
if ( res < 0 | | res ! = size ) {
spa_log_error ( monitor - > log , " can't select config: %d (%s) " ,
res , spa_strerror ( res ) ) ;
2018-01-11 10:23:37 +01:00
if ( ( r = dbus_message_new_error ( m , " org.bluez.Error.InvalidArguments " ,
" Unable to select configuration " ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
goto exit_send ;
}
2023-01-18 17:41:16 +01:00
spa_debug_log_mem ( monitor - > log , SPA_LOG_LEVEL_DEBUG , 2 , pconf , ( size_t ) size ) ;
2018-01-11 10:23:37 +01:00
if ( ( r = dbus_message_new_method_return ( m ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
if ( ! dbus_message_append_args ( r , DBUS_TYPE_ARRAY ,
DBUS_TYPE_BYTE , & pconf , size , DBUS_TYPE_INVALID ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
2022-10-08 01:43:27 +02:00
exit_send :
2018-01-11 10:23:37 +01:00
if ( ! dbus_connection_send ( conn , r , NULL ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
return DBUS_HANDLER_RESULT_HANDLED ;
}
2022-06-20 15:39:01 +02:00
static void append_basic_variant_dict_entry ( DBusMessageIter * dict , const char * key , int variant_type_int , const char * variant_type_str , void * variant ) ;
static void append_basic_array_variant_dict_entry ( DBusMessageIter * dict , const char * key , const char * variant_type_str , const char * array_type_str , int array_type_int , void * data , int data_size ) ;
2022-08-30 15:45:03 +02:00
static struct spa_bt_remote_endpoint * remote_endpoint_find ( struct spa_bt_monitor * monitor , const char * path ) ;
2022-06-20 15:39:01 +02:00
2023-09-23 14:24:52 +03:00
static bool check_iter_signature ( DBusMessageIter * it , const char * sig )
{
char * v ;
bool res ;
v = dbus_message_iter_get_signature ( it ) ;
res = spa_streq ( v , sig ) ;
dbus_free ( v ) ;
return res ;
}
static void parse_codec_qos ( struct spa_bt_monitor * monitor , DBusMessageIter * iter , struct bap_codec_qos_full * qos )
{
DBusMessageIter dict_iter = * iter ;
memset ( qos , 0 , sizeof ( * qos ) ) ;
qos - > cig = 0xff ;
qos - > cis = 0xff ;
qos - > big = 0xff ;
qos - > bis = 0xff ;
if ( ! check_iter_signature ( & dict_iter , " {sv} " ) ) {
spa_log_warn ( monitor - > log , " Invalid BAP QoS in DBus " ) ;
return ;
}
while ( dbus_message_iter_get_arg_type ( & dict_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
int type ;
dbus_message_iter_recurse ( & dict_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 1 ] ) ;
if ( type = = DBUS_TYPE_BYTE ) {
uint8_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " qos: %s=%d " , key , ( int ) value ) ;
if ( spa_streq ( key , " PHY " ) )
qos - > qos . phy = value ;
else if ( spa_streq ( key , " Retransmissions " ) )
qos - > qos . retransmission = value ;
else if ( spa_streq ( key , " CIG " ) )
qos - > cig = value ;
else if ( spa_streq ( key , " CIS " ) )
qos - > cis = value ;
else if ( spa_streq ( key , " BIG " ) )
qos - > big = value ;
else if ( spa_streq ( key , " BIS " ) )
qos - > bis = value ;
else if ( spa_streq ( key , " TargetLatency " ) )
qos - > qos . target_latency = value ;
else if ( spa_streq ( key , " Framing " ) )
qos - > qos . framing = value ;
}
else if ( type = = DBUS_TYPE_UINT16 ) {
dbus_uint16_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " qos: %s=%d " , key , ( int ) value ) ;
if ( spa_streq ( key , " SDU " ) )
qos - > qos . sdu = value ;
else if ( spa_streq ( key , " Latency " ) | | spa_streq ( key , " MaximumLatency " ) )
qos - > qos . latency = value ;
}
else if ( type = = DBUS_TYPE_UINT32 ) {
dbus_uint32_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " qos: %s=%d " , key , ( int ) value ) ;
if ( spa_streq ( key , " Interval " ) )
qos - > qos . interval = value ;
else if ( spa_streq ( key , " PresentationDelay " ) )
qos - > qos . delay = value ;
}
dbus_message_iter_next ( & dict_iter ) ;
}
}
static void parse_endpoint_qos ( struct spa_bt_monitor * monitor , DBusMessageIter * iter ,
struct bap_endpoint_qos * qos )
{
DBusMessageIter dict_iter = * iter ;
if ( ! check_iter_signature ( & dict_iter , " {sv} " ) ) {
spa_log_warn ( monitor - > log , " Invalid BAP Endpoint QoS in DBus " ) ;
return ;
}
while ( dbus_message_iter_get_arg_type ( & dict_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
int type ;
dbus_message_iter_recurse ( & dict_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 1 ] ) ;
if ( type = = DBUS_TYPE_BYTE ) {
uint8_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " ep qos: %s=%d " , key , ( int ) value ) ;
if ( spa_streq ( key , " Framing " ) )
qos - > framing = value ;
else if ( spa_streq ( key , " PHY " ) )
qos - > phy = value ;
else if ( spa_streq ( key , " Retransmissions " ) )
qos - > retransmission = value ;
} else if ( type = = DBUS_TYPE_UINT16 ) {
dbus_uint16_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " ep qos: %s=%d " , key , ( int ) value ) ;
if ( spa_streq ( key , " Latency " ) | | spa_streq ( key , " MaximumLatency " ) )
qos - > latency = value ;
else if ( spa_streq ( key , " Context " ) )
qos - > context = value ;
else if ( spa_streq ( key , " SupportedContext " ) )
qos - > supported_context = value ;
} else if ( type = = DBUS_TYPE_UINT32 ) {
dbus_uint32_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " ep qos: %s=%d " , key , ( int ) value ) ;
if ( spa_streq ( key , " MinimumDelay " ) )
qos - > delay_min = value ;
else if ( spa_streq ( key , " MaximumDelay " ) )
qos - > delay_max = value ;
else if ( spa_streq ( key , " PreferredMinimumDelay " ) )
qos - > preferred_delay_min = value ;
else if ( spa_streq ( key , " PreferredMaximumDelay " ) )
qos - > preferred_delay_max = value ;
}
dbus_message_iter_next ( & dict_iter ) ;
}
}
2023-10-03 20:10:01 +03:00
static int parse_endpoint_props ( struct spa_bt_monitor * monitor , DBusMessageIter * iter ,
uint8_t caps [ A2DP_MAX_CAPS_SIZE ] , int * caps_size , const char * * endpoint_path ,
struct bap_endpoint_qos * qos )
{
DBusMessageIter dict_iter = * iter ;
const char * key = NULL ;
int type = 0 ;
memset ( caps , 0 , A2DP_MAX_CAPS_SIZE ) ;
* endpoint_path = NULL ;
memset ( qos , 0 , sizeof ( * qos ) ) ;
if ( ! check_iter_signature ( & dict_iter , " {sv} " ) ) {
spa_log_warn ( monitor - > log , " Invalid BAP Endpoint QoS in DBus " ) ;
return - EINVAL ;
}
while ( dbus_message_iter_get_arg_type ( & dict_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 3 ] ;
dbus_message_iter_recurse ( & dict_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 1 ] ) ;
if ( spa_streq ( key , " Capabilities " ) ) {
uint8_t * buf ;
if ( type ! = DBUS_TYPE_ARRAY )
goto bad_property ;
dbus_message_iter_recurse ( & it [ 1 ] , & it [ 2 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 2 ] ) ;
if ( type ! = DBUS_TYPE_BYTE )
goto bad_property ;
dbus_message_iter_get_fixed_array ( & it [ 2 ] , & buf , caps_size ) ;
if ( * caps_size > A2DP_MAX_CAPS_SIZE ) {
spa_log_error ( monitor - > log , " %s size:%d too large " , key , ( int ) * caps_size ) ;
return - EINVAL ;
}
memcpy ( caps , buf , * caps_size ) ;
spa_log_info ( monitor - > log , " %p: %s size:%d " , monitor , key , * caps_size ) ;
spa_debug_log_mem ( monitor - > log , SPA_LOG_LEVEL_DEBUG , ' ' , caps , ( size_t ) * caps_size ) ;
} else if ( spa_streq ( key , " Endpoint " ) ) {
if ( type ! = DBUS_TYPE_OBJECT_PATH )
goto bad_property ;
dbus_message_iter_get_basic ( & it [ 1 ] , endpoint_path ) ;
spa_log_info ( monitor - > log , " %p: %s %s " , monitor , key , * endpoint_path ) ;
} else if ( spa_streq ( key , " QoS " ) ) {
if ( ! check_iter_signature ( & it [ 1 ] , " a{sv} " ) )
goto bad_property ;
dbus_message_iter_recurse ( & it [ 1 ] , & it [ 2 ] ) ;
parse_endpoint_qos ( monitor , & it [ 2 ] , qos ) ;
2024-01-07 14:49:41 +02:00
} else if ( spa_streq ( key , " Locations " ) | | spa_streq ( key , " Location " ) ) {
2023-10-03 20:10:01 +03:00
dbus_uint32_t value ;
if ( type ! = DBUS_TYPE_UINT32 )
goto bad_property ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " ep qos: %s=%d " , key , ( int ) value ) ;
qos - > locations = value ;
2024-01-07 14:49:41 +02:00
} else if ( spa_streq ( key , " ChannelAllocation " ) ) {
dbus_uint32_t value ;
if ( type ! = DBUS_TYPE_UINT32 )
goto bad_property ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " ep qos: %s=%d " , key , ( int ) value ) ;
qos - > channel_allocation = value ;
2023-10-03 20:10:01 +03:00
}
dbus_message_iter_next ( & dict_iter ) ;
}
return 0 ;
bad_property :
spa_log_error ( monitor - > log , " Property %s of wrong type %c " , key , ( char ) type ) ;
return - EINVAL ;
}
2022-06-20 15:39:01 +02:00
static DBusHandlerResult endpoint_select_properties ( DBusConnection * conn , DBusMessage * m , void * userdata )
{
struct spa_bt_monitor * monitor = userdata ;
const char * path ;
DBusMessageIter args , props , iter ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = NULL ;
2022-10-09 20:49:43 +03:00
int res ;
2022-06-20 15:39:01 +02:00
const struct media_codec * codec ;
2023-09-23 14:24:52 +03:00
struct spa_bt_remote_endpoint * ep ;
2023-11-27 22:00:13 +02:00
bool sink , duplex ;
2022-10-09 20:49:43 +03:00
const char * err_msg = " Unknown error " ;
2023-04-22 19:32:59 +03:00
struct spa_dict settings ;
2023-11-27 22:00:13 +02:00
struct spa_dict_item setting_items [ SPA_N_ELEMENTS ( monitor - > global_setting_items ) + 5 ] ;
2023-04-22 19:32:59 +03:00
int i ;
2022-10-09 20:49:43 +03:00
2022-10-23 14:12:41 +03:00
const char * endpoint_path = NULL ;
2022-10-09 20:49:43 +03:00
uint8_t caps [ A2DP_MAX_CAPS_SIZE ] ;
uint8_t config [ A2DP_MAX_CAPS_SIZE ] ;
2023-04-22 19:32:59 +03:00
char locations [ 64 ] = { 0 } ;
2024-01-07 14:49:41 +02:00
char channel_allocation [ 64 ] = { 0 } ;
2022-10-09 20:49:43 +03:00
int caps_size = 0 ;
2023-01-16 22:54:27 +02:00
int conf_size ;
2022-10-09 20:49:43 +03:00
DBusMessageIter dict ;
struct bap_endpoint_qos endpoint_qos ;
spa_zero ( endpoint_qos ) ;
2022-06-20 15:39:01 +02:00
if ( ! dbus_message_iter_init ( m , & args ) | | ! spa_streq ( dbus_message_get_signature ( m ) , " a{sv} " ) ) {
spa_log_error ( monitor - > log , " Invalid signature for method SelectProperties() " ) ;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
dbus_message_iter_recurse ( & args , & props ) ;
if ( dbus_message_iter_get_arg_type ( & props ) ! = DBUS_TYPE_DICT_ENTRY )
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
path = dbus_message_get_path ( m ) ;
2022-10-23 14:05:05 +03:00
/* TODO: for codecs with shared endpoint, this currently always picks the default
* one . However , currently we don ' t have BAP codecs with shared endpoint , so
* this does not matter , but in case they are needed later we should pick the
* right one here .
*/
codec = media_endpoint_to_codec ( monitor , path , & sink , NULL ) ;
spa_log_debug ( monitor - > log , " %p: %s codec:%s " , monitor , path , codec ? codec - > name : " <null> " ) ;
2023-09-23 14:24:52 +03:00
if ( ! codec | | ! codec - > bap | | ! codec - > get_qos ) {
2022-10-09 20:49:43 +03:00
spa_log_error ( monitor - > log , " Unsupported codec " ) ;
err_msg = " Unsupported codec " ;
goto error ;
2022-08-30 15:45:03 +02:00
}
2023-09-23 14:24:52 +03:00
/* Parse endpoint properties */
2023-10-03 20:10:01 +03:00
if ( parse_endpoint_props ( monitor , & props , caps , & caps_size , & endpoint_path , & endpoint_qos ) < 0 )
goto error_invalid ;
2023-09-23 14:24:52 +03:00
if ( endpoint_qos . locations )
spa_scnprintf ( locations , sizeof ( locations ) , " % " PRIu32 , endpoint_qos . locations ) ;
2024-01-07 14:49:41 +02:00
if ( endpoint_qos . channel_allocation )
spa_scnprintf ( channel_allocation , sizeof ( channel_allocation ) , " % " PRIu32 , endpoint_qos . channel_allocation ) ;
2023-09-23 14:24:52 +03:00
ep = remote_endpoint_find ( monitor , endpoint_path ) ;
2023-11-27 22:00:13 +02:00
if ( ! ep | | ! ep - > device ) {
2023-09-23 14:24:52 +03:00
spa_log_warn ( monitor - > log , " Unable to find remote endpoint for %s " , endpoint_path ) ;
goto error_invalid ;
2022-06-20 15:39:01 +02:00
}
2023-11-27 22:00:13 +02:00
duplex = SPA_FLAG_IS_SET ( ep - > device - > profiles , SPA_BT_PROFILE_BAP_DUPLEX ) ;
2023-09-23 14:24:52 +03:00
/* Call of SelectProperties means that local device acts as an initiator
2024-05-20 10:45:04 -03:00
* and therefore remote endpoint is an acceptor
2023-09-23 14:24:52 +03:00
*/
ep - > acceptor = true ;
2023-04-22 19:32:59 +03:00
for ( i = 0 ; i < ( int ) monitor - > global_settings . n_items ; + + i )
setting_items [ i ] = monitor - > global_settings . items [ i ] ;
2023-11-23 19:20:49 +02:00
setting_items [ i + + ] = SPA_DICT_ITEM_INIT ( " bluez5.bap.locations " , locations ) ;
2024-01-07 14:49:41 +02:00
setting_items [ i + + ] = SPA_DICT_ITEM_INIT ( " bluez5.bap.channel-allocation " , channel_allocation ) ;
2023-11-27 22:00:13 +02:00
setting_items [ i + + ] = SPA_DICT_ITEM_INIT ( " bluez5.bap.sink " , sink ? " true " : " false " ) ;
setting_items [ i + + ] = SPA_DICT_ITEM_INIT ( " bluez5.bap.duplex " , duplex ? " true " : " false " ) ;
2023-11-23 19:20:49 +02:00
setting_items [ i + + ] = SPA_DICT_ITEM_INIT ( " bluez5.bap.debug " , " true " ) ;
settings = SPA_DICT_INIT ( setting_items , i ) ;
2024-01-07 14:49:41 +02:00
spa_assert ( ( size_t ) i < = SPA_N_ELEMENTS ( setting_items ) ) ;
2023-04-22 19:32:59 +03:00
conf_size = codec - > select_config ( codec , 0 , caps , caps_size , & monitor - > default_audio_info , & settings , config ) ;
2023-01-16 22:54:27 +02:00
if ( conf_size < 0 ) {
2022-10-09 20:49:43 +03:00
spa_log_error ( monitor - > log , " can't select config: %d (%s) " ,
2023-01-16 22:54:27 +02:00
conf_size , spa_strerror ( conf_size ) ) ;
2022-10-09 20:49:43 +03:00
goto error_invalid ;
}
2023-01-16 22:54:27 +02:00
spa_log_info ( monitor - > log , " %p: selected conf %d " , monitor , conf_size ) ;
2023-01-18 17:41:16 +01:00
spa_debug_log_mem ( monitor - > log , SPA_LOG_LEVEL_DEBUG , ' ' , ( uint8_t * ) config , ( size_t ) conf_size ) ;
2022-10-09 20:49:43 +03:00
if ( ( r = dbus_message_new_method_return ( m ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
dbus_message_iter_init_append ( r , & iter ) ;
dbus_message_iter_open_container ( & iter , 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 ,
& dict ) ;
2023-01-16 22:54:27 +02:00
append_basic_array_variant_dict_entry ( & dict , " Capabilities " , " ay " , " y " , DBUS_TYPE_BYTE , & config , conf_size ) ;
2022-10-09 20:49:43 +03:00
2023-09-23 14:24:52 +03:00
{
2022-10-09 20:49:43 +03:00
struct bap_codec_qos qos ;
2023-09-23 14:24:52 +03:00
DBusMessageIter entry , variant , qos_dict ;
const char * entry_key = " QoS " ;
2024-02-21 18:21:32 +02:00
uint8_t cig = 0xff ;
2022-10-09 20:49:43 +03:00
spa_zero ( qos ) ;
2023-01-16 22:54:27 +02:00
res = codec - > get_qos ( codec , config , conf_size , & endpoint_qos , & qos ) ;
2022-10-09 20:49:43 +03:00
if ( res < 0 ) {
spa_log_error ( monitor - > log , " can't select QOS config: %d (%s) " ,
res , spa_strerror ( res ) ) ;
goto error_invalid ;
}
2024-02-21 18:21:32 +02:00
if ( ep - > device - > settings ) {
const char * str = spa_dict_lookup ( ep - > device - > settings , " bluez5.bap.cig " ) ;
uint32_t value ;
if ( spa_atou32 ( str , & value , 0 ) )
cig = value ;
}
2023-11-14 22:08:23 +02:00
spa_log_debug ( monitor - > log , " select qos: interval:%d framing:%d phy:%d sdu:%d "
2024-02-21 18:21:32 +02:00
" rtn:%d latency:%d delay:%d target_latency:%d cig:%u " ,
2023-11-14 22:08:23 +02:00
qos . interval , qos . framing , qos . phy , qos . sdu , qos . retransmission ,
2024-02-21 18:21:32 +02:00
qos . latency , ( int ) qos . delay , qos . target_latency , cig ) ;
2023-11-14 22:08:23 +02:00
2023-09-23 14:24:52 +03:00
dbus_message_iter_open_container ( & dict , DBUS_TYPE_DICT_ENTRY , NULL , & entry ) ;
dbus_message_iter_append_basic ( & entry , DBUS_TYPE_STRING , & entry_key ) ;
dbus_message_iter_open_container ( & entry , DBUS_TYPE_VARIANT , " a{sv} " , & variant ) ;
dbus_message_iter_open_container ( & variant , 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 ,
& qos_dict ) ;
append_basic_variant_dict_entry ( & qos_dict , " Interval " , DBUS_TYPE_UINT32 , " u " , & qos . interval ) ;
append_basic_variant_dict_entry ( & qos_dict , " Framing " , DBUS_TYPE_BYTE , " y " , & qos . framing ) ;
append_basic_variant_dict_entry ( & qos_dict , " PHY " , DBUS_TYPE_BYTE , " y " , & qos . phy ) ;
append_basic_variant_dict_entry ( & qos_dict , " SDU " , DBUS_TYPE_UINT16 , " q " , & qos . sdu ) ;
append_basic_variant_dict_entry ( & qos_dict , " Retransmissions " , DBUS_TYPE_BYTE , " y " , & qos . retransmission ) ;
append_basic_variant_dict_entry ( & qos_dict , " Latency " , DBUS_TYPE_UINT16 , " q " , & qos . latency ) ;
append_basic_variant_dict_entry ( & qos_dict , " PresentationDelay " , DBUS_TYPE_UINT32 , " u " , & qos . delay ) ;
append_basic_variant_dict_entry ( & qos_dict , " TargetLatency " , DBUS_TYPE_BYTE , " y " , & qos . target_latency ) ;
2024-02-21 18:21:32 +02:00
if ( cig < 0xf0 )
append_basic_variant_dict_entry ( & qos_dict , " CIG " , DBUS_TYPE_BYTE , " y " , & cig ) ;
2023-09-23 14:24:52 +03:00
dbus_message_iter_close_container ( & variant , & qos_dict ) ;
dbus_message_iter_close_container ( & entry , & variant ) ;
dbus_message_iter_close_container ( & dict , & entry ) ;
2022-10-09 20:49:43 +03:00
}
dbus_message_iter_close_container ( & iter , & dict ) ;
2023-07-11 19:24:46 +02:00
if ( ! dbus_connection_send ( conn , r , NULL ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
2022-06-20 15:39:01 +02:00
return DBUS_HANDLER_RESULT_HANDLED ;
2022-10-09 20:49:43 +03:00
error_invalid :
err_msg = " Invalid property " ;
goto error ;
error :
2023-07-11 19:24:46 +02:00
if ( ! reply_with_error ( conn , m , " org.bluez.Error.InvalidArguments " , err_msg ) )
2022-10-09 20:49:43 +03:00
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
return DBUS_HANDLER_RESULT_HANDLED ;
2022-06-20 15:39:01 +02:00
}
2018-01-11 10:23:37 +01:00
static struct spa_bt_adapter * adapter_find ( struct spa_bt_monitor * monitor , const char * path )
{
struct spa_bt_adapter * d ;
spa_list_for_each ( d , & monitor - > adapter_list , link )
2021-05-18 11:36:13 +10:00
if ( spa_streq ( d - > path , path ) )
2018-01-11 10:23:37 +01:00
return d ;
return NULL ;
}
2021-06-13 19:18:07 +03:00
static int parse_modalias ( const char * modalias , uint16_t * source , uint16_t * vendor ,
uint16_t * product , uint16_t * version )
{
char * pos ;
unsigned int src , i , j , k ;
2022-10-08 01:46:22 +02:00
if ( spa_strstartswith ( modalias , " bluetooth: " ) )
2021-06-13 19:18:07 +03:00
src = SOURCE_ID_BLUETOOTH ;
2022-10-08 01:46:22 +02:00
else if ( spa_strstartswith ( modalias , " usb: " ) )
2021-06-13 19:18:07 +03:00
src = SOURCE_ID_USB ;
else
return - EINVAL ;
pos = strchr ( modalias , ' : ' ) ;
if ( pos = = NULL )
return - EINVAL ;
if ( sscanf ( pos + 1 , " v%04Xp%04Xd%04X " , & i , & j , & k ) ! = 3 )
return - EINVAL ;
/* Ignore BlueZ placeholder value */
if ( src = = SOURCE_ID_USB & & i = = 0x1d6b & & j = = 0x0246 )
return - ENXIO ;
* source = src ;
* vendor = i ;
* product = j ;
* version = k ;
return 0 ;
}
2018-01-11 10:23:37 +01:00
static int adapter_update_props ( struct spa_bt_adapter * adapter ,
DBusMessageIter * props_iter ,
DBusMessageIter * invalidated_iter )
{
struct spa_bt_monitor * monitor = adapter - > monitor ;
while ( dbus_message_iter_get_arg_type ( props_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
int type ;
dbus_message_iter_recurse ( props_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 1 ] ) ;
if ( type = = DBUS_TYPE_STRING | | type = = DBUS_TYPE_OBJECT_PATH ) {
const char * value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " adapter %p: %s=%s " , adapter , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Alias " ) ) {
2018-01-11 10:23:37 +01:00
free ( adapter - > alias ) ;
adapter - > alias = strdup ( value ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Name " ) ) {
2018-01-11 10:23:37 +01:00
free ( adapter - > name ) ;
adapter - > name = strdup ( value ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Address " ) ) {
2018-01-11 10:23:37 +01:00
free ( adapter - > address ) ;
adapter - > address = strdup ( value ) ;
}
2021-06-13 19:18:07 +03:00
else if ( spa_streq ( key , " Modalias " ) ) {
int ret ;
ret = parse_modalias ( value , & adapter - > source_id , & adapter - > vendor_id ,
& adapter - > product_id , & adapter - > version_id ) ;
if ( ret < 0 )
spa_log_debug ( monitor - > log , " adapter %p: %s=%s ignored: %s " ,
adapter , key , value , spa_strerror ( ret ) ) ;
}
2018-01-11 10:23:37 +01:00
}
else if ( type = = DBUS_TYPE_UINT32 ) {
uint32_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " adapter %p: %s=%d " , adapter , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Class " ) )
2018-01-11 10:23:37 +01:00
adapter - > bluetooth_class = value ;
}
else if ( type = = DBUS_TYPE_BOOLEAN ) {
int value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " adapter %p: %s=%d " , adapter , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Powered " ) ) {
2018-01-11 10:23:37 +01:00
adapter - > powered = value ;
}
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " UUIDs " ) ) {
2018-01-11 10:23:37 +01:00
DBusMessageIter iter ;
2020-11-09 16:48:44 +01:00
if ( ! check_iter_signature ( & it [ 1 ] , " as " ) )
2018-01-11 10:23:37 +01:00
goto next ;
dbus_message_iter_recurse ( & it [ 1 ] , & iter ) ;
while ( dbus_message_iter_get_arg_type ( & iter ) ! = DBUS_TYPE_INVALID ) {
const char * uuid ;
2019-05-16 13:18:45 +02:00
enum spa_bt_profile profile ;
2018-01-11 10:23:37 +01:00
dbus_message_iter_get_basic ( & iter , & uuid ) ;
2019-05-16 13:18:45 +02:00
profile = spa_bt_profile_from_uuid ( uuid ) ;
2018-01-11 10:23:37 +01:00
2019-05-16 13:18:45 +02:00
if ( profile & & ( adapter - > profiles & profile ) = = 0 ) {
spa_log_debug ( monitor - > log , " adapter %p: add UUID=%s " , adapter , uuid ) ;
adapter - > profiles | = profile ;
2022-07-07 11:23:17 +02:00
} else if ( strcasecmp ( uuid , SPA_BT_UUID_PACS ) = = 0 & &
( adapter - > profiles & SPA_BT_PROFILE_BAP_SINK ) = = 0 ) {
spa_log_debug ( monitor - > log , " adapter %p: add UUID=%s " , adapter , SPA_BT_UUID_BAP_SINK ) ;
adapter - > profiles | = SPA_BT_PROFILE_BAP_SINK ;
spa_log_debug ( monitor - > log , " adapter %p: add UUID=%s " , adapter , SPA_BT_UUID_BAP_SOURCE ) ;
adapter - > profiles | = SPA_BT_PROFILE_BAP_SOURCE ;
2023-08-02 13:26:02 +03:00
spa_log_debug ( monitor - > log , " adapter %p: add UUID=%s " , adapter , SPA_BT_UUID_BAP_BROADCAST_SOURCE ) ;
adapter - > profiles | = SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ;
spa_log_debug ( monitor - > log , " adapter %p: add UUID=%s " , adapter , SPA_BT_UUID_BAP_BROADCAST_SINK ) ;
adapter - > profiles | = SPA_BT_PROFILE_BAP_BROADCAST_SINK ;
2019-05-16 13:18:45 +02:00
}
2018-01-11 10:23:37 +01:00
dbus_message_iter_next ( & iter ) ;
}
}
else
spa_log_debug ( monitor - > log , " adapter %p: unhandled key %s " , adapter , key ) ;
2022-10-08 01:43:27 +02:00
next :
2018-01-11 10:23:37 +01:00
dbus_message_iter_next ( props_iter ) ;
}
return 0 ;
}
2023-01-22 14:45:32 +02:00
static int adapter_media_update_props ( struct spa_bt_adapter * adapter ,
DBusMessageIter * props_iter ,
DBusMessageIter * invalidated_iter )
{
/* Handle org.bluez.Media1 interface properties of .Adapter1 objects */
struct spa_bt_monitor * monitor = adapter - > monitor ;
while ( dbus_message_iter_get_arg_type ( props_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
dbus_message_iter_recurse ( props_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
if ( spa_streq ( key , " SupportedUUIDs " ) ) {
DBusMessageIter iter ;
if ( ! check_iter_signature ( & it [ 1 ] , " as " ) )
goto next ;
dbus_message_iter_recurse ( & it [ 1 ] , & iter ) ;
while ( dbus_message_iter_get_arg_type ( & iter ) ! = DBUS_TYPE_INVALID ) {
const char * uuid ;
dbus_message_iter_get_basic ( & iter , & uuid ) ;
2023-08-02 13:26:02 +03:00
if ( spa_streq ( uuid , SPA_BT_UUID_BAP_SINK ) ) {
2023-01-22 14:45:32 +02:00
adapter - > le_audio_supported = true ;
spa_log_info ( monitor - > log , " Adapter %s: LE Audio supported " ,
adapter - > path ) ;
}
2023-08-02 13:26:02 +03:00
2023-08-17 18:52:48 +03:00
if ( spa_streq ( uuid , SPA_BT_UUID_BAP_BROADCAST_SOURCE ) | |
spa_streq ( uuid , SPA_BT_UUID_BAP_BROADCAST_SINK ) ) {
2023-08-02 13:26:02 +03:00
adapter - > le_audio_bcast_supported = true ;
spa_log_info ( monitor - > log , " Adapter %s: LE Broadcast Audio supported " ,
adapter - > path ) ;
}
2023-01-22 14:45:32 +02:00
dbus_message_iter_next ( & iter ) ;
}
}
else
spa_log_debug ( monitor - > log , " media: unhandled key %s " , key ) ;
next :
dbus_message_iter_next ( props_iter ) ;
}
return 0 ;
}
2022-01-20 23:55:06 +02:00
static void adapter_update_devices ( struct spa_bt_adapter * adapter )
{
struct spa_bt_monitor * monitor = adapter - > monitor ;
struct spa_bt_device * device ;
/*
* Update devices when new adapter appears .
* Devices may appear on DBus before or after the adapter does .
*/
spa_list_for_each ( device , & monitor - > device_list , link ) {
if ( device - > adapter = = NULL & & spa_streq ( device - > adapter_path , adapter - > path ) )
device - > adapter = adapter ;
}
}
2021-10-09 19:11:51 +03:00
static void adapter_register_player ( struct spa_bt_adapter * adapter )
{
if ( adapter - > player_registered | | ! adapter - > monitor - > dummy_avrcp_player )
return ;
if ( spa_bt_player_register ( adapter - > dummy_player , adapter - > path ) = = 0 )
adapter - > player_registered = true ;
}
2021-06-13 19:18:07 +03:00
static int adapter_init_bus_type ( struct spa_bt_monitor * monitor , struct spa_bt_adapter * d )
{
char path [ 1024 ] , buf [ 1024 ] ;
const char * str ;
ssize_t res = - EINVAL ;
d - > bus_type = BUS_TYPE_OTHER ;
str = strrchr ( d - > path , ' / ' ) ; /* hciXX */
if ( str = = NULL )
return - ENOENT ;
snprintf ( path , sizeof ( path ) , " /sys/class/bluetooth/%s/device/subsystem " , str ) ;
if ( ( res = readlink ( path , buf , sizeof ( buf ) - 1 ) ) < 0 )
return - errno ;
buf [ res ] = ' \0 ' ;
str = strrchr ( buf , ' / ' ) ;
if ( str & & spa_streq ( str , " /usb " ) )
d - > bus_type = BUS_TYPE_USB ;
return 0 ;
}
static int adapter_init_modalias ( struct spa_bt_monitor * monitor , struct spa_bt_adapter * d )
{
char path [ 1024 ] ;
int vendor_id , product_id ;
const char * str ;
/* Lookup vendor/product id for the device, if present */
str = strrchr ( d - > path , ' / ' ) ; /* hciXX */
if ( str = = NULL )
2023-07-11 20:16:50 +02:00
return - EINVAL ;
2021-06-13 19:18:07 +03:00
snprintf ( path , sizeof ( path ) , " /sys/class/bluetooth/%s/device/modalias " , str ) ;
2023-07-11 20:16:50 +02:00
spa_autoptr ( FILE ) f = fopen ( path , " rbe " ) ;
if ( f = = NULL )
return - errno ;
2021-06-13 19:18:07 +03:00
if ( fscanf ( f , " usb:v%04Xp%04X " , & vendor_id , & product_id ) ! = 2 )
2023-07-11 20:16:50 +02:00
return - EINVAL ;
2021-06-13 19:18:07 +03:00
d - > source_id = SOURCE_ID_USB ;
d - > vendor_id = vendor_id ;
d - > product_id = product_id ;
spa_log_debug ( monitor - > log , " adapter %p: usb vendor:%04x product:%04x " ,
d , vendor_id , product_id ) ;
return 0 ;
}
2018-01-11 10:23:37 +01:00
static struct spa_bt_adapter * adapter_create ( struct spa_bt_monitor * monitor , const char * path )
{
struct spa_bt_adapter * d ;
d = calloc ( 1 , sizeof ( struct spa_bt_adapter ) ) ;
if ( d = = NULL )
return NULL ;
2021-10-09 19:11:51 +03:00
d - > dummy_player = spa_bt_player_new ( monitor - > conn , monitor - > log ) ;
if ( d - > dummy_player = = NULL ) {
free ( d ) ;
return NULL ;
}
2018-01-11 10:23:37 +01:00
d - > monitor = monitor ;
d - > path = strdup ( path ) ;
spa_list_prepend ( & monitor - > adapter_list , & d - > link ) ;
2021-06-13 19:18:07 +03:00
adapter_init_bus_type ( monitor , d ) ;
adapter_init_modalias ( monitor , d ) ;
2018-01-11 10:23:37 +01:00
return d ;
}
2022-01-19 19:05:37 +02:00
static void device_free ( struct spa_bt_device * device ) ;
2020-06-05 15:48:57 +02:00
static void adapter_free ( struct spa_bt_adapter * adapter )
{
struct spa_bt_monitor * monitor = adapter - > monitor ;
2022-01-19 19:05:37 +02:00
struct spa_bt_device * d , * td ;
2020-06-05 15:48:57 +02:00
spa_log_debug ( monitor - > log , " %p " , adapter ) ;
2022-01-19 19:05:37 +02:00
/* Devices should be destroyed before their assigned adapter */
spa_list_for_each_safe ( d , td , & monitor - > device_list , link )
if ( d - > adapter = = adapter )
device_free ( d ) ;
2021-10-09 19:11:51 +03:00
spa_bt_player_destroy ( adapter - > dummy_player ) ;
2020-06-05 15:48:57 +02:00
spa_list_remove ( & adapter - > link ) ;
free ( adapter - > alias ) ;
free ( adapter - > name ) ;
free ( adapter - > address ) ;
free ( adapter - > path ) ;
free ( adapter ) ;
}
2024-04-22 09:35:49 +03:00
static void metadata_entry_free ( struct spa_bt_metadata * metadata_entry )
{
spa_list_remove ( & metadata_entry - > link ) ;
free ( metadata_entry ) ;
}
static void bis_entry_free ( struct spa_bt_bis * bis_entry )
{
struct spa_bt_metadata * m ;
spa_list_consume ( m , & bis_entry - > metadata_list , link )
metadata_entry_free ( m ) ;
spa_list_remove ( & bis_entry - > link ) ;
free ( bis_entry ) ;
}
static void big_entry_free ( struct spa_bt_big * big_entry )
{
struct spa_bt_bis * b ;
2024-05-10 16:25:50 +03:00
2024-04-22 09:35:49 +03:00
spa_list_consume ( b , & big_entry - > bis_list , link )
bis_entry_free ( b ) ;
spa_list_remove ( & big_entry - > link ) ;
free ( big_entry ) ;
}
2022-05-22 15:10:19 +03:00
static uint32_t adapter_connectable_profiles ( struct spa_bt_adapter * adapter )
{
2023-06-18 12:25:39 +03:00
struct spa_bt_monitor * monitor = adapter - > monitor ;
2022-05-22 15:10:19 +03:00
const uint32_t profiles = adapter - > profiles ;
uint32_t mask = 0 ;
if ( profiles & SPA_BT_PROFILE_A2DP_SINK )
mask | = SPA_BT_PROFILE_A2DP_SOURCE ;
if ( profiles & SPA_BT_PROFILE_A2DP_SOURCE )
mask | = SPA_BT_PROFILE_A2DP_SINK ;
2022-06-16 17:15:14 +02:00
if ( profiles & SPA_BT_PROFILE_BAP_SINK )
mask | = SPA_BT_PROFILE_BAP_SOURCE ;
if ( profiles & SPA_BT_PROFILE_BAP_SOURCE )
mask | = SPA_BT_PROFILE_BAP_SINK ;
2023-07-23 22:16:17 +03:00
if ( profiles & SPA_BT_PROFILE_BAP_BROADCAST_SINK )
mask | = SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ;
if ( profiles & SPA_BT_PROFILE_BAP_BROADCAST_SOURCE )
mask | = SPA_BT_PROFILE_BAP_BROADCAST_SINK ;
2022-05-22 15:10:19 +03:00
if ( profiles & SPA_BT_PROFILE_HSP_AG )
mask | = SPA_BT_PROFILE_HSP_HS ;
if ( profiles & SPA_BT_PROFILE_HSP_HS )
mask | = SPA_BT_PROFILE_HSP_AG ;
if ( profiles & SPA_BT_PROFILE_HFP_AG )
mask | = SPA_BT_PROFILE_HFP_HF ;
if ( profiles & SPA_BT_PROFILE_HFP_HF )
mask | = SPA_BT_PROFILE_HFP_AG ;
2023-06-18 12:25:39 +03:00
if ( monitor - > backend_selection = = BACKEND_NONE )
mask & = ~ SPA_BT_PROFILE_HEADSET_AUDIO ;
2022-05-22 15:10:19 +03:00
return mask ;
}
2020-07-17 15:18:10 +02:00
struct spa_bt_device * spa_bt_device_find ( struct spa_bt_monitor * monitor , const char * path )
2018-01-11 10:23:37 +01:00
{
struct spa_bt_device * d ;
spa_list_for_each ( d , & monitor - > device_list , link )
2021-05-18 11:36:13 +10:00
if ( spa_streq ( d - > path , path ) )
2018-01-11 10:23:37 +01:00
return d ;
return NULL ;
}
2020-07-21 11:00:57 +02:00
struct spa_bt_device * spa_bt_device_find_by_address ( struct spa_bt_monitor * monitor , const char * remote_address , const char * local_address )
{
struct spa_bt_device * d ;
spa_list_for_each ( d , & monitor - > device_list , link )
2021-05-18 11:36:13 +10:00
if ( spa_streq ( d - > address , remote_address ) & & spa_streq ( d - > adapter - > address , local_address ) )
2020-07-21 11:00:57 +02:00
return d ;
return NULL ;
}
2023-03-18 14:48:16 +02:00
static uint64_t get_time_now ( struct spa_bt_monitor * monitor )
2021-02-10 01:14:58 +02:00
{
struct timespec ts ;
2023-03-18 14:48:16 +02:00
spa_system_clock_gettime ( monitor - > main_system , CLOCK_MONOTONIC , & ts ) ;
return SPA_TIMESPEC_TO_NSEC ( & ts ) ;
}
void spa_bt_device_update_last_bluez_action_time ( struct spa_bt_device * device )
{
device - > last_bluez_action_time = get_time_now ( device - > monitor ) ;
2021-02-10 01:14:58 +02:00
}
2018-01-11 10:23:37 +01:00
static struct spa_bt_device * device_create ( struct spa_bt_monitor * monitor , const char * path )
{
struct spa_bt_device * d ;
d = calloc ( 1 , sizeof ( struct spa_bt_device ) ) ;
if ( d = = NULL )
return NULL ;
2019-05-30 12:45:06 +02:00
d - > id = monitor - > id + + ;
2018-01-11 10:23:37 +01:00
d - > monitor = monitor ;
d - > path = strdup ( path ) ;
2021-03-18 09:49:50 +08:00
d - > battery_path = battery_get_name ( d - > path ) ;
2021-04-17 18:53:28 +08:00
d - > reconnect_profiles = DEFAULT_RECONNECT_PROFILES ;
d - > hw_volume_profiles = DEFAULT_HW_VOLUME_PROFILES ;
2021-01-24 20:38:13 +02:00
spa_list_init ( & d - > remote_endpoint_list ) ;
2018-11-27 17:08:36 +01:00
spa_list_init ( & d - > transport_list ) ;
2021-01-25 19:57:45 +02:00
spa_list_init ( & d - > codec_switch_list ) ;
2023-03-05 01:03:13 +02:00
spa_list_init ( & d - > set_membership_list ) ;
2021-01-25 19:57:45 +02:00
spa_hook_list_init ( & d - > listener_list ) ;
2018-01-11 10:23:37 +01:00
spa_list_prepend ( & monitor - > device_list , & d - > link ) ;
bluez5: backend-ofono: don't do codec probe connections + add wait
Codec probe connections can trigger bad behavior from oFono if done when
device is busy (e.g. at connect), and they might be done at the same
time as A2DP transport is acquired which cannot work.
Also, oFono will not reply to DBus Acquire, if device does not complete
codec negotiation correctly. This is most likely to happen just after
device connect, when it is busy with other stuff (eg A2DP).
Remove codec probe connections altogether: instead, we guess mSBC if
mSBC is enabled and otherwise CVSD. If the guess turns out to be wrong,
which is unlikely (almost all devices have mSBC), we recreate the
transport with correct codec (from main loop, must not be done in
*_acquire because that can destroy nodes + unload the spa libs while
we're being called from there).
To avoid oFono DBus hangs at startup, add delay before marking the
profile connected, enforcing a time difference to A2DP operations.
2022-03-03 23:32:33 +02:00
spa_bt_device_update_last_bluez_action_time ( d ) ;
2021-02-10 01:14:58 +02:00
2018-01-11 10:23:37 +01:00
return d ;
}
2021-06-21 09:58:43 +08:00
static void device_clear_sub ( struct spa_bt_device * device )
{
battery_remove ( device ) ;
spa_bt_device_release_transports ( device ) ;
2024-01-29 00:17:05 +02:00
device - > preferred_codec = NULL ;
2021-06-21 09:58:43 +08:00
}
2020-06-05 15:48:57 +02:00
static void device_free ( struct spa_bt_device * device )
2018-11-27 17:08:36 +01:00
{
2021-02-02 11:48:47 +01:00
struct spa_bt_remote_endpoint * ep , * tep ;
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * sw ;
2021-02-02 11:48:47 +01:00
struct spa_bt_transport * t , * tt ;
2018-11-27 17:08:36 +01:00
struct spa_bt_monitor * monitor = device - > monitor ;
2023-03-05 01:03:13 +02:00
struct spa_bt_set_membership * s ;
2018-11-27 17:08:36 +01:00
spa_log_debug ( monitor - > log , " %p " , device ) ;
2021-03-11 18:09:51 +03:00
2021-04-10 17:59:13 +03:00
spa_bt_device_emit_destroy ( device ) ;
2021-06-21 09:58:43 +08:00
device_clear_sub ( device ) ;
2020-06-05 15:48:57 +02:00
device_stop_timer ( device ) ;
2021-03-14 14:06:50 +08:00
if ( device - > added ) {
spa_device_emit_object_info ( & monitor - > hooks , device - > id , NULL ) ;
}
2018-11-27 17:08:36 +01:00
2021-02-02 11:48:47 +01:00
spa_list_for_each_safe ( ep , tep , & device - > remote_endpoint_list , device_link ) {
2021-01-24 20:38:13 +02:00
if ( ep - > device = = device ) {
spa_list_remove ( & ep - > device_link ) ;
ep - > device = NULL ;
}
}
2021-02-02 11:48:47 +01:00
spa_list_for_each_safe ( t , tt , & device - > transport_list , device_link ) {
2018-11-27 17:08:36 +01:00
if ( t - > device = = device ) {
spa_list_remove ( & t - > device_link ) ;
t - > device = NULL ;
}
}
2021-01-25 19:57:45 +02:00
spa_list_consume ( sw , & device - > codec_switch_list , device_link )
2022-06-15 17:24:41 +02:00
media_codec_switch_free ( sw ) ;
2021-01-25 19:57:45 +02:00
2023-03-05 01:03:13 +02:00
spa_list_consume ( s , & device - > set_membership_list , link ) {
spa_list_remove ( & s - > link ) ;
spa_list_remove ( & s - > others ) ;
free ( s - > path ) ;
free ( s ) ;
}
2018-11-27 17:08:36 +01:00
spa_list_remove ( & device - > link ) ;
free ( device - > path ) ;
2020-06-05 15:48:57 +02:00
free ( device - > alias ) ;
free ( device - > address ) ;
free ( device - > adapter_path ) ;
2021-03-18 09:49:50 +08:00
free ( device - > battery_path ) ;
2020-06-05 15:48:57 +02:00
free ( device - > name ) ;
free ( device - > icon ) ;
2018-11-27 17:08:36 +01:00
free ( device ) ;
}
2023-03-05 01:03:13 +02:00
static struct spa_bt_set_membership * device_set_find ( struct spa_bt_monitor * monitor , const char * path )
{
struct spa_bt_device * d ;
spa_list_for_each ( d , & monitor - > device_list , link ) {
struct spa_bt_set_membership * s ;
spa_list_for_each ( s , & d - > set_membership_list , link ) {
if ( spa_streq ( s - > path , path ) )
return s ;
}
}
return NULL ;
}
static int device_add_device_set ( struct spa_bt_device * device , const char * path , uint8_t rank )
{
struct spa_bt_monitor * monitor = device - > monitor ;
struct spa_bt_set_membership * s , * set ;
spa_list_for_each ( s , & device - > set_membership_list , link ) {
if ( spa_streq ( s - > path , path ) ) {
if ( rank )
s - > rank = rank ;
return 0 ;
}
}
s = calloc ( 1 , sizeof ( struct spa_bt_set_membership ) ) ;
if ( s = = NULL )
return - ENOMEM ;
s - > path = strdup ( path ) ;
if ( ! s - > path ) {
free ( s ) ;
return - ENOMEM ;
}
s - > device = device ;
s - > rank = rank ;
spa_list_init ( & s - > others ) ;
/* Join with other set members, if any */
set = device_set_find ( monitor , path ) ;
if ( set )
spa_list_append ( & set - > others , & s - > others ) ;
spa_list_append ( & device - > set_membership_list , & s - > link ) ;
spa_log_debug ( monitor - > log , " device %p: add %s to device set %s " , device ,
device - > path , path ) ;
return 1 ;
}
static bool device_remove_device_set ( struct spa_bt_device * device , const char * path )
{
struct spa_bt_monitor * monitor = device - > monitor ;
struct spa_bt_set_membership * s ;
spa_list_for_each ( s , & device - > set_membership_list , link ) {
if ( spa_streq ( s - > path , path ) ) {
spa_log_debug ( monitor - > log ,
" device %p: remove %s from device set %s " , device ,
device - > path , path ) ;
spa_list_remove ( & s - > link ) ;
spa_list_remove ( & s - > others ) ;
free ( s - > path ) ;
free ( s ) ;
return true ;
}
}
return false ;
}
2021-06-13 19:18:07 +03:00
int spa_bt_format_vendor_product_id ( uint16_t source_id , uint16_t vendor_id , uint16_t product_id ,
char * vendor_str , int vendor_str_size , char * product_str , int product_str_size )
{
2023-09-16 17:47:35 +02:00
const char * source_str ;
2021-06-13 19:18:07 +03:00
switch ( source_id ) {
case SOURCE_ID_USB :
source_str = " usb " ;
break ;
case SOURCE_ID_BLUETOOTH :
source_str = " bluetooth " ;
break ;
default :
return - EINVAL ;
}
spa_scnprintf ( vendor_str , vendor_str_size , " %s:%04x " , source_str , ( unsigned int ) vendor_id ) ;
spa_scnprintf ( product_str , product_str_size , " %04x " , ( unsigned int ) product_id ) ;
return 0 ;
}
2021-06-21 09:58:43 +08:00
static void emit_device_info ( struct spa_bt_monitor * monitor ,
struct spa_bt_device * device , bool with_connection )
2021-03-16 10:56:27 +08:00
{
struct spa_device_object_info info ;
2021-06-13 19:18:07 +03:00
char dev [ 32 ] , name [ 128 ] , class [ 16 ] , vendor_id [ 64 ] , product_id [ 64 ] , product_id_tot [ 67 ] ;
struct spa_dict_item items [ 23 ] ;
2021-03-16 10:56:27 +08:00
uint32_t n_items = 0 ;
2021-06-21 09:58:43 +08:00
info = SPA_DEVICE_OBJECT_INFO_INIT ( ) ;
info . type = SPA_TYPE_INTERFACE_Device ;
info . factory_name = SPA_NAME_API_BLUEZ5_DEVICE ;
info . change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS |
SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS ;
info . flags = 0 ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_API , " bluez5 " ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_BUS , " bluetooth " ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_MEDIA_CLASS , " Audio/Device " ) ;
snprintf ( name , sizeof ( name ) , " bluez_card.%s " , device - > address ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_NAME , name ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_DESCRIPTION , device - > alias ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_ALIAS , device - > name ) ;
2021-06-13 19:18:07 +03:00
if ( spa_bt_format_vendor_product_id (
device - > source_id , device - > vendor_id , device - > product_id ,
vendor_id , sizeof ( vendor_id ) , product_id , sizeof ( product_id ) ) = = 0 ) {
snprintf ( product_id_tot , sizeof ( product_id_tot ) , " 0x%s " , product_id ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_VENDOR_ID , vendor_id ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_PRODUCT_ID , product_id_tot ) ;
}
2021-06-21 09:58:43 +08:00
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_FORM_FACTOR ,
spa_bt_form_factor_name (
spa_bt_form_factor_from_class ( device - > bluetooth_class ) ) ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_DEVICE_STRING , device - > address ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_API_BLUEZ5_ICON , device - > icon ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_API_BLUEZ5_PATH , device - > path ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_API_BLUEZ5_ADDRESS , device - > address ) ;
snprintf ( dev , sizeof ( dev ) , " pointer:%p " , device ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_API_BLUEZ5_DEVICE , dev ) ;
snprintf ( class , sizeof ( class ) , " 0x%06x " , device - > bluetooth_class ) ;
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_API_BLUEZ5_CLASS , class ) ;
2021-03-16 10:56:27 +08:00
2021-06-21 09:58:43 +08:00
if ( with_connection ) {
items [ n_items + + ] = SPA_DICT_ITEM_INIT ( SPA_KEY_API_BLUEZ5_CONNECTION ,
device - > connected ? " connected " : " disconnected " ) ;
}
2021-03-16 10:56:27 +08:00
2021-06-21 09:58:43 +08:00
info . props = & SPA_DICT_INIT ( items , n_items ) ;
spa_device_emit_object_info ( & monitor - > hooks , device - > id , & info ) ;
}
static int device_connected_old ( struct spa_bt_monitor * monitor ,
struct spa_bt_device * device , int connected )
{
if ( connected = = BT_DEVICE_INIT )
2021-03-16 10:56:27 +08:00
return 0 ;
2021-06-21 09:58:43 +08:00
device - > connected = connected ;
2021-03-16 10:56:27 +08:00
if ( device - > connected ) {
2021-06-21 09:58:43 +08:00
emit_device_info ( monitor , device , false ) ;
device - > added = true ;
2021-03-16 10:56:27 +08:00
} else {
2021-06-21 09:58:43 +08:00
if ( ! device - > added )
return 0 ;
device_clear_sub ( device ) ;
2021-03-16 10:56:27 +08:00
spa_device_emit_object_info ( & monitor - > hooks , device - > id , NULL ) ;
2021-06-21 09:58:43 +08:00
device - > added = false ;
2021-03-16 10:56:27 +08:00
}
return 0 ;
}
2021-03-26 12:53:04 +08:00
enum {
BT_DEVICE_RECONNECT_INIT = 0 ,
BT_DEVICE_RECONNECT_PROFILE ,
BT_DEVICE_RECONNECT_STOP
} ;
2021-06-21 09:58:43 +08:00
static int device_connected ( struct spa_bt_monitor * monitor ,
struct spa_bt_device * device , int status )
2018-11-27 17:08:36 +01:00
{
2021-06-21 09:58:43 +08:00
bool connected , init = ( status = = BT_DEVICE_INIT ) ;
2021-03-16 10:56:27 +08:00
2021-06-21 09:58:43 +08:00
connected = init ? 0 : status ;
2018-11-27 17:08:36 +01:00
2021-06-21 14:47:13 +08:00
if ( ! init ) {
device - > reconnect_state =
connected ? BT_DEVICE_RECONNECT_STOP
: BT_DEVICE_RECONNECT_PROFILE ;
}
2021-03-14 14:06:50 +08:00
2021-06-21 09:58:43 +08:00
if ( ( device - > connected_profiles ! = 0 ) ^ connected ) {
2021-03-14 14:06:50 +08:00
spa_log_error ( monitor - > log ,
2021-06-21 09:58:43 +08:00
" device %p: unexpected call, connected_profiles:%08x connected:%d " ,
device , device - > connected_profiles , device - > connected ) ;
2021-03-14 14:06:50 +08:00
return - EINVAL ;
}
2021-06-21 09:58:43 +08:00
if ( ! monitor - > connection_info_supported )
return device_connected_old ( monitor , device , status ) ;
2018-11-27 17:08:36 +01:00
2021-06-21 09:58:43 +08:00
if ( init ) {
device - > connected = connected ;
} else {
if ( ! device - > added | | ! ( connected ^ device - > connected ) )
return 0 ;
2019-05-30 12:45:06 +02:00
2021-06-21 09:58:43 +08:00
device - > connected = connected ;
spa_bt_device_emit_connected ( device , device - > connected ) ;
2019-05-30 12:45:06 +02:00
2021-06-21 09:58:43 +08:00
if ( ! device - > connected )
device_clear_sub ( device ) ;
}
emit_device_info ( monitor , device , true ) ;
device - > added = true ;
2018-11-27 17:08:36 +01:00
return 0 ;
}
2021-06-20 23:00:37 +08:00
/*
* Add profile to device based on bluez actions
* ( update property UUIDs , trigger profile handlers ) ,
* in case UUIDs is empty on signal InterfaceAdded for
* org . bluez . Device1 . And emit device info if there is
* at least 1 profile on device . This should be called
* before any device setting accessing .
*/
int spa_bt_device_add_profile ( struct spa_bt_device * device , enum spa_bt_profile profile )
{
struct spa_bt_monitor * monitor = device - > monitor ;
if ( profile & & ( device - > profiles & profile ) = = 0 ) {
spa_log_info ( monitor - > log , " device %p: add new profile %08x " , device , profile ) ;
device - > profiles | = profile ;
}
2021-06-21 14:47:13 +08:00
if ( ! device - > added & & device - > profiles ) {
device_connected ( monitor , device , BT_DEVICE_INIT ) ;
if ( device - > reconnect_state = = BT_DEVICE_RECONNECT_INIT )
device_start_timer ( device ) ;
}
2021-06-20 23:00:37 +08:00
return 0 ;
}
2021-03-26 12:53:04 +08:00
static int device_try_connect_profile ( struct spa_bt_device * device ,
2022-10-08 01:40:03 +02:00
const char * profile_uuid )
2021-03-26 12:53:04 +08:00
{
struct spa_bt_monitor * monitor = device - > monitor ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2021-03-26 12:53:04 +08:00
spa_log_info ( monitor - > log , " device %p %s: profile %s not connected; try ConnectProfile() " ,
device , device - > path , profile_uuid ) ;
/* Call org.bluez.Device1.ConnectProfile() on device, ignoring result */
m = dbus_message_new_method_call ( BLUEZ_SERVICE ,
2022-10-08 01:40:03 +02:00
device - > path ,
BLUEZ_DEVICE_INTERFACE ,
" ConnectProfile " ) ;
2021-03-26 12:53:04 +08:00
if ( m = = NULL )
return - ENOMEM ;
dbus_message_append_args ( m , DBUS_TYPE_STRING , & profile_uuid , DBUS_TYPE_INVALID ) ;
2023-07-11 19:24:46 +02:00
if ( ! dbus_connection_send ( monitor - > conn , m , NULL ) )
2021-03-26 12:53:04 +08:00
return - EIO ;
return 0 ;
}
static int reconnect_device_profiles ( struct spa_bt_device * device )
{
2021-08-29 15:40:14 +03:00
struct spa_bt_monitor * monitor = device - > monitor ;
struct spa_bt_device * d ;
2021-03-26 12:53:04 +08:00
uint32_t reconnect = device - > profiles
& device - > reconnect_profiles
& ( device - > connected_profiles ^ device - > profiles ) ;
2021-08-29 15:40:14 +03:00
/* Don't try to connect to same device via multiple adapters */
spa_list_for_each ( d , & monitor - > device_list , link ) {
if ( d ! = device & & spa_streq ( d - > address , device - > address ) ) {
if ( d - > paired & & d - > trusted & & ! d - > blocked & &
d - > reconnect_state = = BT_DEVICE_RECONNECT_STOP )
reconnect & = ~ d - > reconnect_profiles ;
if ( d - > connected_profiles )
reconnect = 0 ;
}
}
2022-05-22 15:10:19 +03:00
/* Connect only profiles the adapter has a counterpart for */
if ( device - > adapter )
reconnect & = adapter_connectable_profiles ( device - > adapter ) ;
2021-03-26 12:53:04 +08:00
if ( ! ( device - > connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT ) ) {
if ( reconnect & SPA_BT_PROFILE_HFP_HF ) {
SPA_FLAG_CLEAR ( reconnect , SPA_BT_PROFILE_HSP_HS ) ;
} else if ( reconnect & SPA_BT_PROFILE_HSP_HS ) {
SPA_FLAG_CLEAR ( reconnect , SPA_BT_PROFILE_HFP_HF ) ;
}
} else
SPA_FLAG_CLEAR ( reconnect , SPA_BT_PROFILE_HEADSET_HEAD_UNIT ) ;
if ( ! ( device - > connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY ) ) {
if ( reconnect & SPA_BT_PROFILE_HFP_AG )
SPA_FLAG_CLEAR ( reconnect , SPA_BT_PROFILE_HSP_AG ) ;
else if ( reconnect & SPA_BT_PROFILE_HSP_AG )
SPA_FLAG_CLEAR ( reconnect , SPA_BT_PROFILE_HFP_AG ) ;
} else
SPA_FLAG_CLEAR ( reconnect , SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY ) ;
if ( reconnect & SPA_BT_PROFILE_HFP_HF )
device_try_connect_profile ( device , SPA_BT_UUID_HFP_HF ) ;
if ( reconnect & SPA_BT_PROFILE_HSP_HS )
device_try_connect_profile ( device , SPA_BT_UUID_HSP_HS ) ;
if ( reconnect & SPA_BT_PROFILE_HFP_AG )
device_try_connect_profile ( device , SPA_BT_UUID_HFP_AG ) ;
if ( reconnect & SPA_BT_PROFILE_HSP_AG )
device_try_connect_profile ( device , SPA_BT_UUID_HSP_AG ) ;
if ( reconnect & SPA_BT_PROFILE_A2DP_SINK )
device_try_connect_profile ( device , SPA_BT_UUID_A2DP_SINK ) ;
if ( reconnect & SPA_BT_PROFILE_A2DP_SOURCE )
device_try_connect_profile ( device , SPA_BT_UUID_A2DP_SOURCE ) ;
2022-06-16 17:15:14 +02:00
if ( reconnect & SPA_BT_PROFILE_BAP_SINK )
device_try_connect_profile ( device , SPA_BT_UUID_BAP_SINK ) ;
if ( reconnect & SPA_BT_PROFILE_BAP_SOURCE )
device_try_connect_profile ( device , SPA_BT_UUID_BAP_SOURCE ) ;
2023-07-23 22:16:17 +03:00
if ( reconnect & SPA_BT_PROFILE_BAP_BROADCAST_SINK )
2023-08-17 18:52:48 +03:00
device_try_connect_profile ( device , SPA_BT_UUID_BAP_BROADCAST_SINK ) ;
2023-07-23 22:16:17 +03:00
if ( reconnect & SPA_BT_PROFILE_BAP_BROADCAST_SOURCE )
device_try_connect_profile ( device , SPA_BT_UUID_BAP_BROADCAST_SOURCE ) ;
2021-03-26 12:53:04 +08:00
return reconnect ;
}
# define DEVICE_RECONNECT_TIMEOUT_SEC 2
2022-01-06 21:08:07 +02:00
# define DEVICE_PROFILE_TIMEOUT_SEC 6
2018-11-27 17:08:36 +01:00
static void device_timer_event ( struct spa_source * source )
{
struct spa_bt_device * device = source - > data ;
struct spa_bt_monitor * monitor = device - > monitor ;
uint64_t exp ;
2019-11-19 13:40:46 +01:00
if ( spa_system_timerfd_read ( monitor - > main_system , source - > fd , & exp ) < 0 )
2022-10-08 01:40:03 +02:00
spa_log_warn ( monitor - > log , " error reading timerfd: %s " , strerror ( errno ) ) ;
2018-11-27 17:08:36 +01:00
2019-05-30 13:01:15 +02:00
spa_log_debug ( monitor - > log , " device %p: timeout %08x %08x " ,
device , device - > profiles , device - > connected_profiles ) ;
2021-03-26 12:53:04 +08:00
device_stop_timer ( device ) ;
if ( BT_DEVICE_RECONNECT_STOP ! = device - > reconnect_state ) {
device - > reconnect_state = BT_DEVICE_RECONNECT_STOP ;
if ( device - > paired
& & device - > trusted
& & ! device - > blocked
& & device - > reconnect_profiles ! = 0
& & reconnect_device_profiles ( device ) )
{
device_start_timer ( device ) ;
return ;
}
}
if ( device - > connected_profiles )
device_connected ( device - > monitor , device , BT_DEVICE_CONNECTED ) ;
2018-11-27 17:08:36 +01:00
}
static int device_start_timer ( struct spa_bt_device * device )
{
struct spa_bt_monitor * monitor = device - > monitor ;
struct itimerspec ts ;
2019-05-30 13:01:15 +02:00
spa_log_debug ( monitor - > log , " device %p: start timer " , device ) ;
2018-11-27 17:08:36 +01:00
if ( device - > timer . data = = NULL ) {
device - > timer . data = device ;
device - > timer . func = device_timer_event ;
2019-11-19 13:40:46 +01:00
device - > timer . fd = spa_system_timerfd_create ( monitor - > main_system ,
CLOCK_MONOTONIC , SPA_FD_CLOEXEC | SPA_FD_NONBLOCK ) ;
2018-11-27 17:08:36 +01:00
device - > timer . mask = SPA_IO_IN ;
device - > timer . rmask = 0 ;
spa_loop_add_source ( monitor - > main_loop , & device - > timer ) ;
}
2021-03-26 12:53:04 +08:00
ts . it_value . tv_sec = device - > reconnect_state = = BT_DEVICE_RECONNECT_STOP
? DEVICE_PROFILE_TIMEOUT_SEC
: DEVICE_RECONNECT_TIMEOUT_SEC ;
2018-11-27 17:08:36 +01:00
ts . it_value . tv_nsec = 0 ;
ts . it_interval . tv_sec = 0 ;
ts . it_interval . tv_nsec = 0 ;
2019-11-19 13:40:46 +01:00
spa_system_timerfd_settime ( monitor - > main_system , device - > timer . fd , 0 , & ts , NULL ) ;
2018-11-27 17:08:36 +01:00
return 0 ;
}
static int device_stop_timer ( struct spa_bt_device * device )
{
struct spa_bt_monitor * monitor = device - > monitor ;
struct itimerspec ts ;
if ( device - > timer . data = = NULL )
return 0 ;
2019-05-30 13:01:15 +02:00
spa_log_debug ( monitor - > log , " device %p: stop timer " , device ) ;
2018-11-27 17:08:36 +01:00
spa_loop_remove_source ( monitor - > main_loop , & device - > timer ) ;
2022-10-08 01:40:03 +02:00
ts . it_value . tv_sec = 0 ;
ts . it_value . tv_nsec = 0 ;
ts . it_interval . tv_sec = 0 ;
ts . it_interval . tv_nsec = 0 ;
spa_system_timerfd_settime ( monitor - > main_system , device - > timer . fd , 0 , & ts , NULL ) ;
2019-11-19 13:40:46 +01:00
spa_system_close ( monitor - > main_system , device - > timer . fd ) ;
2018-11-27 17:08:36 +01:00
device - > timer . data = NULL ;
return 0 ;
}
2020-07-17 15:18:10 +02:00
int spa_bt_device_check_profiles ( struct spa_bt_device * device , bool force )
2018-11-27 17:08:36 +01:00
{
struct spa_bt_monitor * monitor = device - > monitor ;
2023-04-01 20:48:44 +03:00
struct spa_bt_set_membership * s , * set ;
2018-11-27 17:08:36 +01:00
uint32_t connected_profiles = device - > connected_profiles ;
2022-05-22 15:10:19 +03:00
uint32_t connectable_profiles =
device - > adapter ? adapter_connectable_profiles ( device - > adapter ) : 0 ;
uint32_t direction_masks [ 3 ] = {
2022-06-16 17:15:14 +02:00
SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT ,
SPA_BT_PROFILE_MEDIA_SOURCE ,
2022-05-22 15:10:19 +03:00
SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY ,
2022-01-06 21:07:01 +02:00
} ;
bool direction_connected = false ;
2023-04-01 20:48:44 +03:00
bool set_connected = true ;
2022-01-06 21:07:01 +02:00
bool all_connected ;
size_t i ;
2018-11-27 17:08:36 +01:00
if ( connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT )
connected_profiles | = SPA_BT_PROFILE_HEADSET_HEAD_UNIT ;
if ( connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY )
connected_profiles | = SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY ;
2022-01-06 21:07:01 +02:00
for ( i = 0 ; i < SPA_N_ELEMENTS ( direction_masks ) ; + + i ) {
2022-05-22 15:10:19 +03:00
uint32_t mask = direction_masks [ i ] & device - > profiles & connectable_profiles ;
2022-01-06 21:07:01 +02:00
if ( mask & & ( connected_profiles & mask ) = = mask )
direction_connected = true ;
}
2023-06-18 12:25:39 +03:00
all_connected = ( ( device - > profiles & connected_profiles & connectable_profiles )
= = ( device - > profiles & connectable_profiles ) ) ;
2022-01-06 21:07:01 +02:00
2023-04-01 20:48:44 +03:00
spa_list_for_each ( set , & device - > set_membership_list , link )
spa_bt_for_each_set_member ( s , set )
if ( ( s - > device - > connected_profiles & s - > device - > profiles ) ! = s - > device - > profiles )
set_connected = false ;
spa_log_debug ( monitor - > log , " device %p: profiles %08x %08x connectable:%08x added:%d all:%d dir:%d set:%d " ,
2022-05-22 15:10:19 +03:00
device , device - > profiles , connected_profiles , connectable_profiles ,
2023-04-01 20:48:44 +03:00
device - > added , all_connected , direction_connected , set_connected ) ;
2018-11-27 17:08:36 +01:00
2021-02-24 00:20:32 +02:00
if ( connected_profiles = = 0 & & spa_list_is_empty ( & device - > codec_switch_list ) ) {
2021-03-14 14:06:50 +08:00
device_stop_timer ( device ) ;
device_connected ( monitor , device , BT_DEVICE_DISCONNECTED ) ;
2023-06-18 12:25:39 +03:00
} else if ( force | | ( ( direction_connected | | all_connected ) & & set_connected & & connected_profiles ) ) {
2018-11-27 17:08:36 +01:00
device_stop_timer ( device ) ;
2021-03-14 14:06:50 +08:00
device_connected ( monitor , device , BT_DEVICE_CONNECTED ) ;
2018-11-27 17:08:36 +01:00
} else {
2021-04-28 20:29:44 +02:00
/* The initial reconnect event has not been triggered,
* the connecting is triggered by bluez . */
2021-03-26 12:53:04 +08:00
if ( device - > reconnect_state = = BT_DEVICE_RECONNECT_INIT )
device - > reconnect_state = BT_DEVICE_RECONNECT_PROFILE ;
2018-11-27 17:08:36 +01:00
device_start_timer ( device ) ;
}
return 0 ;
}
2018-01-11 10:23:37 +01:00
static void device_set_connected ( struct spa_bt_device * device , int connected )
{
2021-02-10 22:34:32 +02:00
struct spa_bt_monitor * monitor = device - > monitor ;
2018-11-27 17:08:36 +01:00
if ( device - > connected & & ! connected )
device - > connected_profiles = 0 ;
if ( connected )
2020-07-17 15:18:10 +02:00
spa_bt_device_check_profiles ( device , false ) ;
2021-02-10 22:34:32 +02:00
else {
2022-04-25 20:11:44 +03:00
/* Stop codec switch on disconnect */
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * sw ;
2022-04-25 20:11:44 +03:00
spa_list_consume ( sw , & device - > codec_switch_list , device_link )
2022-06-15 17:24:41 +02:00
media_codec_switch_free ( sw ) ;
2022-04-25 20:11:44 +03:00
2021-03-26 12:53:04 +08:00
if ( device - > reconnect_state ! = BT_DEVICE_RECONNECT_INIT )
device_stop_timer ( device ) ;
device_connected ( monitor , device , BT_DEVICE_DISCONNECTED ) ;
2021-02-10 22:34:32 +02:00
}
2018-11-27 17:08:36 +01:00
}
2023-03-05 01:03:13 +02:00
static void device_update_set_status ( struct spa_bt_device * device , bool force , const char * path ) ;
2020-07-17 15:18:10 +02:00
int spa_bt_device_connect_profile ( struct spa_bt_device * device , enum spa_bt_profile profile )
2018-11-27 17:08:36 +01:00
{
2021-01-29 19:41:26 +02:00
uint32_t prev_connected = device - > connected_profiles ;
2018-11-27 17:08:36 +01:00
device - > connected_profiles | = profile ;
2023-03-05 01:03:13 +02:00
if ( ( prev_connected ^ device - > connected_profiles ) & SPA_BT_PROFILE_BAP_DUPLEX )
device_update_set_status ( device , true , NULL ) ;
2020-07-17 15:18:10 +02:00
spa_bt_device_check_profiles ( device , false ) ;
2021-01-29 19:41:26 +02:00
if ( device - > connected_profiles ! = prev_connected )
spa_bt_device_emit_profiles_changed ( device , device - > profiles , prev_connected ) ;
2018-11-27 17:08:36 +01:00
return 0 ;
2018-01-11 10:23:37 +01:00
}
2021-06-19 23:49:58 +03:00
static void device_update_hw_volume_profiles ( struct spa_bt_device * device )
{
struct spa_bt_monitor * monitor = device - > monitor ;
uint32_t bt_features = 0 ;
if ( ! monitor - > quirks )
return ;
if ( spa_bt_quirks_get_features ( monitor - > quirks , device - > adapter , device , & bt_features ) ! = 0 )
return ;
if ( ! ( bt_features & SPA_BT_FEATURE_HW_VOLUME ) )
device - > hw_volume_profiles = 0 ;
2021-10-01 19:03:49 +03:00
spa_log_debug ( monitor - > log , " hw-volume-profiles:%08x " , ( int ) device - > hw_volume_profiles ) ;
2021-06-19 23:49:58 +03:00
}
2018-11-27 17:08:36 +01:00
2023-03-05 01:03:13 +02:00
static bool device_set_update_leader ( struct spa_bt_set_membership * set )
{
2023-10-24 19:25:59 +03:00
struct spa_bt_set_membership * s , * leader = NULL ;
2023-03-05 01:03:13 +02:00
2023-10-24 19:25:59 +03:00
/* Make minimum rank device the leader, so that device set nodes always
* appear under a specific device .
*/
2023-03-05 01:03:13 +02:00
spa_bt_for_each_set_member ( s , set ) {
2023-04-11 19:39:25 +03:00
if ( ! ( s - > device - > connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX ) )
continue ;
2023-10-24 19:25:59 +03:00
if ( leader = = NULL | | s - > rank < leader - > rank | |
( s - > rank = = leader - > rank & & s - > leader ) )
2023-03-05 01:03:13 +02:00
leader = s ;
}
2023-10-24 19:25:59 +03:00
if ( leader = = NULL | | ( leader & & leader - > leader ) )
2023-03-05 01:03:13 +02:00
return false ;
2023-10-24 19:25:59 +03:00
spa_bt_for_each_set_member ( s , set )
s - > leader = false ;
2023-03-05 01:03:13 +02:00
2023-10-24 19:25:59 +03:00
leader - > leader = true ;
2023-03-05 01:03:13 +02:00
2023-10-24 19:25:59 +03:00
spa_log_debug ( leader - > device - > monitor - > log ,
" device set %p %s: leader is %s " ,
set , leader - > path , leader - > device - > path ) ;
2023-03-05 01:03:13 +02:00
return true ;
}
static void device_update_set_status ( struct spa_bt_device * device , bool force , const char * path )
{
struct spa_bt_set_membership * s , * set ;
spa_list_for_each ( set , & device - > set_membership_list , link ) {
if ( path & & ! spa_streq ( set - > path , path ) )
continue ;
if ( device_set_update_leader ( set ) | | force ) {
spa_bt_for_each_set_member ( s , set )
if ( ! s - > leader )
spa_bt_device_emit_device_set_changed ( s - > device ) ;
spa_bt_for_each_set_member ( s , set )
if ( s - > leader )
spa_bt_device_emit_device_set_changed ( s - > device ) ;
}
}
}
static int device_set_update_props ( struct spa_bt_monitor * monitor ,
const char * path , DBusMessageIter * props_iter , DBusMessageIter * invalidated_iter )
{
struct spa_bt_device * old [ 256 ] ;
struct spa_bt_device * new [ 256 ] ;
struct spa_bt_set_membership * set ;
size_t num_old = 0 , num_new = 0 ;
size_t i ;
if ( ! props_iter )
goto done ;
/* Find current devices */
while ( dbus_message_iter_get_arg_type ( props_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
dbus_message_iter_recurse ( props_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
if ( spa_streq ( key , " Devices " ) ) {
DBusMessageIter iter ;
int i = 0 ;
if ( ! check_iter_signature ( & it [ 1 ] , " ao " ) )
goto next ;
dbus_message_iter_recurse ( & it [ 1 ] , & iter ) ;
while ( dbus_message_iter_get_arg_type ( & iter ) ! = DBUS_TYPE_INVALID ) {
struct spa_bt_device * d ;
const char * dev_path ;
dbus_message_iter_get_basic ( & iter , & dev_path ) ;
spa_log_debug ( monitor - > log , " device set %s: Devices[%d]=%s " ,
path , i + + , dev_path ) ;
if ( num_new > = SPA_N_ELEMENTS ( new ) )
break ;
d = spa_bt_device_find ( monitor , dev_path ) ;
if ( d )
new [ num_new + + ] = d ;
dbus_message_iter_next ( & iter ) ;
}
}
else
spa_log_debug ( monitor - > log , " device set %s: unhandled key %s " ,
path , key ) ;
next :
dbus_message_iter_next ( props_iter ) ;
}
done :
/* Find devices to remove */
set = device_set_find ( monitor , path ) ;
if ( set ) {
struct spa_bt_set_membership * s ;
spa_bt_for_each_set_member ( s , set ) {
for ( i = 0 ; i < num_new ; + + i )
if ( s - > device = = new [ i ] )
break ;
if ( i = = num_new ) {
if ( num_old > = SPA_N_ELEMENTS ( old ) )
break ;
old [ num_old + + ] = s - > device ;
}
}
}
/* Remove old devices */
for ( i = 0 ; i < num_old ; + + i )
device_remove_device_set ( old [ i ] , path ) ;
/* Add new devices */
for ( i = 0 ; i < num_new ; + + i )
device_add_device_set ( new [ i ] , path , 0 ) ;
/* Emit signals & update set leader */
for ( i = 0 ; i < num_old ; + + i )
spa_bt_device_emit_device_set_changed ( old [ i ] ) ;
if ( num_new > 0 )
device_update_set_status ( new [ 0 ] , true , path ) ;
return 0 ;
}
static int device_update_device_sets_prop ( struct spa_bt_device * device ,
DBusMessageIter * iter )
{
struct spa_bt_monitor * monitor = device - > monitor ;
DBusMessageIter it [ 5 ] ;
bool changed = false ;
if ( ! check_iter_signature ( iter , " a{oa{sv}} " ) )
return - EINVAL ;
dbus_message_iter_recurse ( iter , & it [ 0 ] ) ;
while ( dbus_message_iter_get_arg_type ( & it [ 0 ] ) ! = DBUS_TYPE_INVALID ) {
uint8_t rank = 0 ;
const char * set_path ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
dbus_message_iter_get_basic ( & it [ 1 ] , & set_path ) ;
dbus_message_iter_next ( & it [ 1 ] ) ;
dbus_message_iter_recurse ( & it [ 1 ] , & it [ 2 ] ) ;
while ( dbus_message_iter_get_arg_type ( & it [ 2 ] ) ! = DBUS_TYPE_INVALID ) {
const char * key ;
int type ;
dbus_message_iter_recurse ( & it [ 2 ] , & it [ 3 ] ) ;
dbus_message_iter_get_basic ( & it [ 3 ] , & key ) ;
dbus_message_iter_next ( & it [ 3 ] ) ;
dbus_message_iter_recurse ( & it [ 3 ] , & it [ 4 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 4 ] ) ;
if ( spa_streq ( key , " Rank " ) & & type = = DBUS_TYPE_BYTE )
dbus_message_iter_get_basic ( & it [ 4 ] , & rank ) ;
dbus_message_iter_next ( & it [ 2 ] ) ;
}
spa_log_debug ( monitor - > log , " device %p: path %s device set %s rank %d " ,
device , device - > path , set_path , ( int ) rank ) ;
/* Only add. Removals are handled in device set updates. */
if ( device_add_device_set ( device , set_path , rank ) = = 1 )
changed = true ;
dbus_message_iter_next ( & it [ 0 ] ) ;
}
/* Emit change signals */
device_update_set_status ( device , changed , NULL ) ;
return 0 ;
}
2018-01-11 10:23:37 +01:00
static int device_update_props ( struct spa_bt_device * device ,
DBusMessageIter * props_iter ,
DBusMessageIter * invalidated_iter )
{
struct spa_bt_monitor * monitor = device - > monitor ;
while ( dbus_message_iter_get_arg_type ( props_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
int type ;
dbus_message_iter_recurse ( props_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 1 ] ) ;
if ( type = = DBUS_TYPE_STRING | | type = = DBUS_TYPE_OBJECT_PATH ) {
const char * value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " device %p: %s=%s " , device , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Alias " ) ) {
2018-01-11 10:23:37 +01:00
free ( device - > alias ) ;
device - > alias = strdup ( value ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Name " ) ) {
2018-01-11 10:23:37 +01:00
free ( device - > name ) ;
device - > name = strdup ( value ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Address " ) ) {
2018-01-11 10:23:37 +01:00
free ( device - > address ) ;
device - > address = strdup ( value ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Adapter " ) ) {
2018-01-11 10:23:37 +01:00
free ( device - > adapter_path ) ;
device - > adapter_path = strdup ( value ) ;
2018-11-27 17:08:36 +01:00
device - > adapter = adapter_find ( monitor , value ) ;
if ( device - > adapter = = NULL ) {
2022-01-20 23:55:06 +02:00
spa_log_info ( monitor - > log , " unknown adapter %s " , value ) ;
2018-11-27 17:08:36 +01:00
}
2018-01-11 10:23:37 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Icon " ) ) {
2018-01-11 10:23:37 +01:00
free ( device - > icon ) ;
device - > icon = strdup ( value ) ;
}
2021-06-13 19:18:07 +03:00
else if ( spa_streq ( key , " Modalias " ) ) {
int ret ;
ret = parse_modalias ( value , & device - > source_id , & device - > vendor_id ,
& device - > product_id , & device - > version_id ) ;
if ( ret < 0 )
spa_log_debug ( monitor - > log , " device %p: %s=%s ignored: %s " ,
device , key , value , spa_strerror ( ret ) ) ;
}
2018-01-11 10:23:37 +01:00
}
else if ( type = = DBUS_TYPE_UINT32 ) {
uint32_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
2019-05-16 13:18:45 +02:00
spa_log_debug ( monitor - > log , " device %p: %s=%08x " , device , key , value ) ;
2018-01-11 10:23:37 +01:00
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Class " ) )
2018-01-11 10:23:37 +01:00
device - > bluetooth_class = value ;
}
else if ( type = = DBUS_TYPE_UINT16 ) {
uint16_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " device %p: %s=%d " , device , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Appearance " ) )
2018-01-11 10:23:37 +01:00
device - > appearance = value ;
}
else if ( type = = DBUS_TYPE_INT16 ) {
int16_t value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " device %p: %s=%d " , device , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " RSSI " ) )
2018-01-11 10:23:37 +01:00
device - > RSSI = value ;
}
else if ( type = = DBUS_TYPE_BOOLEAN ) {
int value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " device %p: %s=%d " , device , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Paired " ) ) {
2018-01-11 10:23:37 +01:00
device - > paired = value ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Trusted " ) ) {
2018-01-11 10:23:37 +01:00
device - > trusted = value ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Connected " ) ) {
2018-01-11 10:23:37 +01:00
device_set_connected ( device , value ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Blocked " ) ) {
2018-01-11 10:23:37 +01:00
device - > blocked = value ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " ServicesResolved " ) ) {
2019-05-16 13:18:45 +02:00
if ( value )
2020-07-22 14:48:34 +02:00
spa_bt_device_check_profiles ( device , false ) ;
2019-05-16 13:18:45 +02:00
}
2018-01-11 10:23:37 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " UUIDs " ) ) {
2018-01-11 10:23:37 +01:00
DBusMessageIter iter ;
2021-01-29 19:41:26 +02:00
uint32_t prev_profiles = device - > profiles ;
2018-01-11 10:23:37 +01:00
2020-11-09 16:48:44 +01:00
if ( ! check_iter_signature ( & it [ 1 ] , " as " ) )
2018-01-11 10:23:37 +01:00
goto next ;
dbus_message_iter_recurse ( & it [ 1 ] , & iter ) ;
while ( dbus_message_iter_get_arg_type ( & iter ) ! = DBUS_TYPE_INVALID ) {
const char * uuid ;
2019-05-16 13:18:45 +02:00
enum spa_bt_profile profile ;
2018-01-11 10:23:37 +01:00
dbus_message_iter_get_basic ( & iter , & uuid ) ;
2019-05-16 13:18:45 +02:00
profile = spa_bt_profile_from_uuid ( uuid ) ;
2022-02-23 14:33:32 -05:00
2023-06-18 12:25:39 +03:00
if ( profile & & ( device - > profiles & profile ) = = 0 ) {
spa_log_debug ( monitor - > log , " device %p: add UUID=%s " , device , uuid ) ;
device - > profiles | = profile ;
2019-05-16 13:18:45 +02:00
}
2023-06-18 12:25:39 +03:00
2018-01-11 10:23:37 +01:00
dbus_message_iter_next ( & iter ) ;
}
2021-01-29 19:41:26 +02:00
if ( device - > profiles ! = prev_profiles )
spa_bt_device_emit_profiles_changed (
device , prev_profiles , device - > connected_profiles ) ;
2018-01-11 10:23:37 +01:00
}
2023-03-05 01:03:13 +02:00
else if ( spa_streq ( key , " Sets " ) ) {
device_update_device_sets_prop ( device , & it [ 1 ] ) ;
}
2018-01-11 10:23:37 +01:00
else
spa_log_debug ( monitor - > log , " device %p: unhandled key %s type %d " , device , key , type ) ;
2022-10-08 01:43:27 +02:00
next :
2018-01-11 10:23:37 +01:00
dbus_message_iter_next ( props_iter ) ;
}
return 0 ;
}
2022-01-17 19:10:14 +02:00
static bool device_props_ready ( struct spa_bt_device * device )
{
/*
* In some cases , BlueZ device props may be missing part of
* the information required when the interface first appears .
*/
return device - > adapter & & device - > address ;
}
2023-09-02 15:06:27 +03:00
bool spa_bt_device_supports_media_codec ( struct spa_bt_device * device , const struct media_codec * codec , enum spa_bt_profile profile )
2021-01-25 22:42:53 +02:00
{
2021-08-13 19:31:00 +03:00
struct spa_bt_monitor * monitor = device - > monitor ;
2021-01-25 22:42:53 +02:00
struct spa_bt_remote_endpoint * ep ;
2023-09-02 15:06:27 +03:00
enum spa_bt_profile codec_target_profile ;
2023-01-29 17:38:21 +02:00
struct spa_bt_transport * t ;
2021-09-29 18:31:45 +03:00
const struct { enum spa_bluetooth_audio_codec codec ; uint32_t mask ; } quirks [ ] = {
{ SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ , SPA_BT_FEATURE_SBC_XQ } ,
{ SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM , SPA_BT_FEATURE_FASTSTREAM } ,
{ SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX , SPA_BT_FEATURE_FASTSTREAM } ,
2021-11-07 15:57:28 +02:00
{ SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX , SPA_BT_FEATURE_A2DP_DUPLEX } ,
{ SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX , SPA_BT_FEATURE_A2DP_DUPLEX } ,
2021-09-29 18:31:45 +03:00
} ;
size_t i ;
2021-01-25 22:42:53 +02:00
2022-06-15 17:24:41 +02:00
if ( ! is_media_codec_enabled ( device - > monitor , codec ) )
2021-01-25 22:42:53 +02:00
return false ;
2023-01-21 20:57:27 +02:00
if ( ! device - > adapter - > a2dp_application_registered & & ! codec - > bap ) {
2021-01-25 22:42:53 +02:00
/* Codec switching not supported: only plain SBC allowed */
2023-02-05 17:33:45 +02:00
return ( codec - > codec_id = = A2DP_CODEC_SBC & & spa_streq ( codec - > name , " sbc " ) & &
device - > adapter - > legacy_endpoints_registered ) ;
2021-01-25 22:42:53 +02:00
}
2023-01-21 20:57:27 +02:00
if ( ! device - > adapter - > bap_application_registered & & codec - > bap )
return false ;
2021-01-25 22:42:53 +02:00
2021-09-29 18:31:45 +03:00
/* Check codec quirks */
for ( i = 0 ; i < SPA_N_ELEMENTS ( quirks ) ; + + i ) {
uint32_t bt_features ;
if ( codec - > id ! = quirks [ i ] . codec )
continue ;
if ( monitor - > quirks = = NULL )
break ;
if ( spa_bt_quirks_get_features ( monitor - > quirks , device - > adapter , device , & bt_features ) < 0 )
break ;
if ( ! ( bt_features & quirks [ i ] . mask ) )
2021-08-13 19:31:00 +03:00
return false ;
}
2023-09-02 15:06:27 +03:00
for ( i = 0 , codec_target_profile = 0 ; i < ( size_t ) SPA_BT_MEDIA_DIRECTION_LAST ; + + i )
if ( codec_has_direction ( codec , i ) )
codec_target_profile | = swap_profile ( get_codec_profile ( codec , i ) ) ;
2021-01-25 22:42:53 +02:00
spa_list_for_each ( ep , & device - > remote_endpoint_list , device_link ) {
2023-09-02 15:06:27 +03:00
enum spa_bt_profile ep_profile = spa_bt_profile_from_uuid ( ep - > uuid ) ;
2022-06-16 17:15:14 +02:00
2023-09-02 15:06:27 +03:00
if ( ! ( ep_profile & codec_target_profile & profile ) )
2022-06-12 19:09:29 +03:00
continue ;
2022-06-15 17:24:41 +02:00
if ( media_codec_check_caps ( codec , ep - > codec , ep - > capabilities , ep - > capabilities_len ,
2022-06-04 19:01:51 +03:00
& ep - > monitor - > default_audio_info , & monitor - > global_settings ) )
2021-01-25 22:42:53 +02:00
return true ;
}
2023-01-29 17:38:21 +02:00
/* Codecs on configured transports are always supported.
*
* Remote BAP endpoints correspond to capabilities of the remote
* BAP Server , not to remote BAP Client , and need not be the same .
* BAP Clients may not have any remote endpoints . In this case we
* can only know that the currently configured codec is supported .
*/
spa_list_for_each ( t , & device - > transport_list , device_link ) {
2023-09-02 15:06:27 +03:00
if ( ! ( t - > profile & codec_target_profile & profile ) )
2023-01-29 17:38:21 +02:00
continue ;
if ( codec = = t - > media_codec )
return true ;
}
2021-01-25 22:42:53 +02:00
return false ;
}
2023-09-02 15:06:27 +03:00
const struct media_codec * * spa_bt_device_get_supported_media_codecs ( struct spa_bt_device * device , size_t * count )
2021-01-25 22:42:53 +02:00
{
2021-09-01 00:33:43 +03:00
struct spa_bt_monitor * monitor = device - > monitor ;
2022-06-15 17:24:41 +02:00
const struct media_codec * const * const media_codecs = monitor - > media_codecs ;
2023-07-11 20:16:50 +02:00
spa_autofree const struct media_codec * * supported_codecs = NULL ;
2021-01-25 22:42:53 +02:00
size_t i , j , size ;
* count = 0 ;
size = 8 ;
2022-06-15 17:24:41 +02:00
supported_codecs = malloc ( size * sizeof ( const struct media_codec * ) ) ;
2021-01-25 22:42:53 +02:00
if ( supported_codecs = = NULL )
return NULL ;
j = 0 ;
2022-06-15 17:24:41 +02:00
for ( i = 0 ; media_codecs [ i ] ! = NULL ; + + i ) {
2023-09-02 15:06:27 +03:00
if ( spa_bt_device_supports_media_codec ( device , media_codecs [ i ] , device - > connected_profiles ) ) {
2022-06-15 17:24:41 +02:00
supported_codecs [ j ] = media_codecs [ i ] ;
2021-01-25 22:42:53 +02:00
+ + j ;
}
if ( j > = size ) {
2022-06-15 17:24:41 +02:00
const struct media_codec * * p ;
2021-01-25 22:42:53 +02:00
size = size * 2 ;
2023-07-14 18:19:26 +02:00
# ifdef HAVE_REALLOCARRAY
2022-06-15 17:24:41 +02:00
p = reallocarray ( supported_codecs , size , sizeof ( const struct media_codec * ) ) ;
2022-05-27 15:00:17 +02:00
# else
2022-06-15 17:24:41 +02:00
p = realloc ( supported_codecs , size * sizeof ( const struct media_codec * ) ) ;
2022-05-27 15:00:17 +02:00
# endif
2023-07-11 20:16:50 +02:00
if ( p = = NULL )
2021-01-25 22:42:53 +02:00
return NULL ;
2023-07-11 20:16:50 +02:00
2021-01-25 22:42:53 +02:00
supported_codecs = p ;
}
}
supported_codecs [ j ] = NULL ;
* count = j ;
2023-07-11 20:16:50 +02:00
return spa_steal_ptr ( supported_codecs ) ;
2021-01-25 22:42:53 +02:00
}
2021-01-24 20:38:13 +02:00
static struct spa_bt_remote_endpoint * device_remote_endpoint_find ( struct spa_bt_device * device , const char * path )
{
struct spa_bt_remote_endpoint * ep ;
spa_list_for_each ( ep , & device - > remote_endpoint_list , device_link )
2021-05-18 11:36:13 +10:00
if ( spa_streq ( ep - > path , path ) )
2021-01-24 20:38:13 +02:00
return ep ;
return NULL ;
}
static struct spa_bt_remote_endpoint * remote_endpoint_find ( struct spa_bt_monitor * monitor , const char * path )
{
struct spa_bt_remote_endpoint * ep ;
spa_list_for_each ( ep , & monitor - > remote_endpoint_list , link )
2021-05-18 11:36:13 +10:00
if ( spa_streq ( ep - > path , path ) )
2021-01-24 20:38:13 +02:00
return ep ;
return NULL ;
}
2023-09-10 15:45:01 +03:00
static struct spa_bt_device * create_bcast_device ( struct spa_bt_monitor * monitor , const char * object_path )
2023-12-30 01:28:16 +01:00
{
2023-08-02 10:35:20 +03:00
struct spa_bt_device * d ;
2023-09-10 15:45:01 +03:00
struct spa_bt_adapter * adapter ;
adapter = adapter_find ( monitor , object_path ) ;
if ( adapter = = NULL ) {
spa_log_warn ( monitor - > log , " unknown adapter %s " , object_path ) ;
return NULL ;
}
2023-08-02 10:35:20 +03:00
d = device_create ( monitor , object_path ) ;
if ( d = = NULL ) {
spa_log_warn ( monitor - > log , " can't create Bluetooth device %s: %m " ,
object_path ) ;
return NULL ;
}
2023-09-10 15:45:01 +03:00
d - > adapter = adapter ;
d - > adapter_path = strdup ( adapter - > path ) ;
d - > alias = strdup ( adapter - > alias ) ;
d - > name = strdup ( adapter - > name ) ;
2023-08-09 13:10:36 +03:00
d - > address = strdup ( " 00:00:00:00:00:00 " ) ;
2023-09-10 15:45:01 +03:00
d - > reconnect_state = BT_DEVICE_RECONNECT_STOP ;
2023-08-02 10:35:20 +03:00
device_update_hw_volume_profiles ( d ) ;
spa_bt_device_add_profile ( d , SPA_BT_PROFILE_NULL ) ;
return d ;
}
2021-01-24 20:38:13 +02:00
static int remote_endpoint_update_props ( struct spa_bt_remote_endpoint * remote_endpoint ,
DBusMessageIter * props_iter ,
DBusMessageIter * invalidated_iter )
{
struct spa_bt_monitor * monitor = remote_endpoint - > monitor ;
while ( dbus_message_iter_get_arg_type ( props_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
int type ;
dbus_message_iter_recurse ( props_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 1 ] ) ;
if ( type = = DBUS_TYPE_STRING | | type = = DBUS_TYPE_OBJECT_PATH ) {
const char * value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " remote_endpoint %p: %s=%s " , remote_endpoint , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " UUID " ) ) {
2021-01-24 20:38:13 +02:00
free ( remote_endpoint - > uuid ) ;
remote_endpoint - > uuid = strdup ( value ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Device " ) ) {
2021-01-24 20:38:13 +02:00
struct spa_bt_device * device ;
2023-09-10 15:45:01 +03:00
2021-01-24 20:38:13 +02:00
device = spa_bt_device_find ( monitor , value ) ;
2023-08-02 10:35:20 +03:00
if ( device = = NULL ) {
/*
* If a broadcast sink endpoint is detected ( over DBus ) a new device
2023-12-30 01:28:16 +01:00
* will be created . This device will be our simulated remote device .
2023-08-02 10:35:20 +03:00
* This is done because BlueZ sets the adapter as the device
* that is connected to for a broadcast sink endpoint / transport .
*/
2023-09-10 15:45:01 +03:00
if ( spa_streq ( remote_endpoint - > uuid , SPA_BT_UUID_BAP_BROADCAST_SINK ) ) {
2023-08-02 10:35:20 +03:00
device = create_bcast_device ( monitor , value ) ;
2023-09-10 15:45:01 +03:00
if ( device = = NULL )
2023-08-09 13:10:36 +03:00
goto next ;
2023-09-10 15:45:01 +03:00
2023-08-02 10:35:20 +03:00
remote_endpoint - > acceptor = true ;
device_set_connected ( device , 1 ) ;
} else {
goto next ;
}
}
2021-01-24 20:38:13 +02:00
spa_log_debug ( monitor - > log , " remote_endpoint %p: device -> %p " , remote_endpoint , device ) ;
if ( remote_endpoint - > device ! = device ) {
if ( remote_endpoint - > device ! = NULL )
spa_list_remove ( & remote_endpoint - > device_link ) ;
remote_endpoint - > device = device ;
if ( device ! = NULL )
spa_list_append ( & device - > remote_endpoint_list , & remote_endpoint - > device_link ) ;
}
}
}
else if ( type = = DBUS_TYPE_BOOLEAN ) {
int value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " remote_endpoint %p: %s=%d " , remote_endpoint , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " DelayReporting " ) ) {
2021-01-24 20:38:13 +02:00
remote_endpoint - > delay_reporting = value ;
}
}
else if ( type = = DBUS_TYPE_BYTE ) {
2021-02-02 23:42:34 +08:00
uint8_t value ;
2021-01-24 20:38:13 +02:00
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " remote_endpoint %p: %s=%02x " , remote_endpoint , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " Codec " ) ) {
2021-01-24 20:38:13 +02:00
remote_endpoint - > codec = value ;
}
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Capabilities " ) ) {
2021-01-24 20:38:13 +02:00
DBusMessageIter iter ;
uint8_t * value ;
2022-07-18 11:26:59 +02:00
int len ;
2021-01-24 20:38:13 +02:00
if ( ! check_iter_signature ( & it [ 1 ] , " ay " ) )
goto next ;
dbus_message_iter_recurse ( & it [ 1 ] , & iter ) ;
dbus_message_iter_get_fixed_array ( & iter , & value , & len ) ;
spa_log_debug ( monitor - > log , " remote_endpoint %p: %s=%d " , remote_endpoint , key , len ) ;
2023-01-18 17:41:16 +01:00
spa_debug_log_mem ( monitor - > log , SPA_LOG_LEVEL_DEBUG , 2 , value , ( size_t ) len ) ;
2021-01-24 20:38:13 +02:00
free ( remote_endpoint - > capabilities ) ;
remote_endpoint - > capabilities_len = 0 ;
remote_endpoint - > capabilities = malloc ( len ) ;
if ( remote_endpoint - > capabilities ) {
memcpy ( remote_endpoint - > capabilities , value , len ) ;
remote_endpoint - > capabilities_len = len ;
}
}
else
spa_log_debug ( monitor - > log , " remote_endpoint %p: unhandled key %s " , remote_endpoint , key ) ;
2022-10-08 01:43:27 +02:00
next :
2021-01-24 20:38:13 +02:00
dbus_message_iter_next ( props_iter ) ;
}
2024-01-21 20:16:13 +02:00
/* BAP profile UUIDs do not appear in device UUID list.
* Instead , we detect these capabilities based on available
* endpoints ( i . e . PACs ) .
*/
if ( remote_endpoint - > uuid & & remote_endpoint - > device ) {
enum spa_bt_profile profile ;
profile = spa_bt_profile_from_uuid ( remote_endpoint - > uuid ) ;
if ( profile & SPA_BT_PROFILE_BAP_AUDIO )
spa_bt_device_add_profile ( remote_endpoint - > device , profile ) ;
}
2021-01-24 20:38:13 +02:00
return 0 ;
}
static struct spa_bt_remote_endpoint * remote_endpoint_create ( struct spa_bt_monitor * monitor , const char * path )
{
struct spa_bt_remote_endpoint * ep ;
ep = calloc ( 1 , sizeof ( struct spa_bt_remote_endpoint ) ) ;
if ( ep = = NULL )
return NULL ;
ep - > monitor = monitor ;
ep - > path = strdup ( path ) ;
spa_list_prepend ( & monitor - > remote_endpoint_list , & ep - > link ) ;
return ep ;
}
static void remote_endpoint_free ( struct spa_bt_remote_endpoint * remote_endpoint )
{
struct spa_bt_monitor * monitor = remote_endpoint - > monitor ;
spa_log_debug ( monitor - > log , " remote endpoint %p: free %s " ,
remote_endpoint , remote_endpoint - > path ) ;
if ( remote_endpoint - > device )
spa_list_remove ( & remote_endpoint - > device_link ) ;
spa_list_remove ( & remote_endpoint - > link ) ;
free ( remote_endpoint - > path ) ;
free ( remote_endpoint - > uuid ) ;
free ( remote_endpoint - > capabilities ) ;
free ( remote_endpoint ) ;
}
2020-07-21 11:00:57 +02:00
struct spa_bt_transport * spa_bt_transport_find ( struct spa_bt_monitor * monitor , const char * path )
2018-01-11 10:23:37 +01:00
{
struct spa_bt_transport * t ;
spa_list_for_each ( t , & monitor - > transport_list , link )
2021-05-18 11:36:13 +10:00
if ( spa_streq ( t - > path , path ) )
2018-01-11 10:23:37 +01:00
return t ;
return NULL ;
}
2020-08-17 09:13:07 +02:00
struct spa_bt_transport * spa_bt_transport_find_full ( struct spa_bt_monitor * monitor ,
2022-10-08 01:40:03 +02:00
bool ( * callback ) ( struct spa_bt_transport * t , const void * data ) ,
const void * data )
2020-08-17 09:13:07 +02:00
{
struct spa_bt_transport * t ;
spa_list_for_each ( t , & monitor - > transport_list , link )
if ( callback ( t , data ) = = true )
return t ;
return NULL ;
}
2020-07-17 15:18:10 +02:00
struct spa_bt_transport * spa_bt_transport_create ( struct spa_bt_monitor * monitor , char * path , size_t extra )
2018-01-11 10:23:37 +01:00
{
struct spa_bt_transport * t ;
2018-11-27 17:08:36 +01:00
t = calloc ( 1 , sizeof ( struct spa_bt_transport ) + extra ) ;
2018-01-11 10:23:37 +01:00
if ( t = = NULL )
return NULL ;
2020-12-22 20:36:04 +02:00
t - > acquire_refcount = 0 ;
2018-01-11 10:23:37 +01:00
t - > monitor = monitor ;
2018-11-27 17:08:36 +01:00
t - > path = path ;
2018-01-11 10:23:37 +01:00
t - > fd = - 1 ;
2020-12-23 15:10:04 +02:00
t - > sco_io = NULL ;
2023-02-19 17:25:14 +02:00
t - > delay_us = SPA_BT_UNKNOWN_DELAY ;
t - > latency_us = SPA_BT_UNKNOWN_DELAY ;
2023-03-15 18:55:54 +02:00
t - > bap_cig = 0xff ;
t - > bap_cis = 0xff ;
2023-07-23 22:16:17 +03:00
t - > bap_big = 0xff ;
t - > bap_bis = 0xff ;
2021-05-06 13:41:44 +10:00
t - > user_data = SPA_PTROFF ( t , sizeof ( struct spa_bt_transport ) , void ) ;
2019-05-14 18:10:18 +02:00
spa_hook_list_init ( & t - > listener_list ) ;
2022-08-04 16:45:56 +02:00
spa_list_init ( & t - > bap_transport_linked ) ;
2018-01-11 10:23:37 +01:00
2018-11-27 17:08:36 +01:00
spa_list_append ( & monitor - > transport_list , & t - > link ) ;
2018-01-11 10:23:37 +01:00
return t ;
}
2021-03-18 22:07:47 +02:00
2021-04-17 18:53:28 +08:00
bool spa_bt_transport_volume_enabled ( struct spa_bt_transport * transport )
{
return transport - > device ! = NULL
& & ( transport - > device - > hw_volume_profiles & transport - > profile ) ;
}
2021-04-15 11:39:49 +08:00
static void transport_sync_volume ( struct spa_bt_transport * transport )
{
2021-04-17 18:53:28 +08:00
if ( ! spa_bt_transport_volume_enabled ( transport ) )
return ;
2021-04-15 11:39:49 +08:00
for ( int i = 0 ; i < SPA_BT_VOLUME_ID_TERM ; + + i )
spa_bt_transport_set_volume ( transport , i , transport - > volumes [ i ] . volume ) ;
spa_bt_transport_emit_volume_changed ( transport ) ;
}
2021-03-18 22:07:47 +02:00
void spa_bt_transport_set_state ( struct spa_bt_transport * transport , enum spa_bt_transport_state state )
2019-05-16 13:18:45 +02:00
{
struct spa_bt_monitor * monitor = transport - > monitor ;
enum spa_bt_transport_state old = transport - > state ;
if ( old ! = state ) {
transport - > state = state ;
spa_log_debug ( monitor - > log , " transport %p: %s state changed %d -> %d " ,
transport , transport - > path , old , state ) ;
spa_bt_transport_emit_state_changed ( transport , old , state ) ;
2021-04-15 11:39:49 +08:00
if ( state > = SPA_BT_TRANSPORT_STATE_PENDING & & old < SPA_BT_TRANSPORT_STATE_PENDING )
transport_sync_volume ( transport ) ;
2023-03-18 14:48:16 +02:00
2023-05-25 19:12:12 +03:00
if ( state < SPA_BT_TRANSPORT_STATE_ACTIVE ) {
/* If transport becomes inactive, do any pending releases
* immediately , since the fd is not usable any more .
*/
spa_bt_transport_commit_release_timer ( transport ) ;
}
2023-03-18 14:48:16 +02:00
if ( state = = SPA_BT_TRANSPORT_STATE_ERROR ) {
uint64_t now = get_time_now ( monitor ) ;
if ( now > transport - > last_error_time + TRANSPORT_ERROR_TIMEOUT ) {
spa_log_error ( monitor - > log , " Failure in Bluetooth audio transport %s " ,
transport - > path ) ;
}
transport - > last_error_time = now ;
+ + transport - > error_count ;
}
2019-05-16 13:18:45 +02:00
}
}
2018-01-11 10:23:37 +01:00
2020-07-17 15:18:10 +02:00
void spa_bt_transport_free ( struct spa_bt_transport * transport )
2018-01-11 10:23:37 +01:00
{
2019-05-16 13:18:45 +02:00
struct spa_bt_monitor * monitor = transport - > monitor ;
2021-01-29 19:41:26 +02:00
struct spa_bt_device * device = transport - > device ;
uint32_t prev_connected = 0 ;
2019-05-16 13:18:45 +02:00
spa_log_debug ( monitor - > log , " transport %p: free %s " , transport , transport - > path ) ;
2021-03-18 22:07:47 +02:00
spa_bt_transport_set_state ( transport , SPA_BT_TRANSPORT_STATE_IDLE ) ;
2019-05-14 18:10:18 +02:00
2022-01-31 00:08:26 +02:00
spa_bt_transport_keepalive ( transport , false ) ;
2019-05-14 18:10:18 +02:00
spa_bt_transport_emit_destroy ( transport ) ;
2021-04-15 11:39:49 +08:00
spa_bt_transport_stop_volume_timer ( transport ) ;
2021-01-04 15:54:27 +02:00
spa_bt_transport_stop_release_timer ( transport ) ;
2020-12-23 15:10:04 +02:00
if ( transport - > sco_io ) {
spa_bt_sco_io_destroy ( transport - > sco_io ) ;
transport - > sco_io = NULL ;
}
2023-03-25 16:58:27 +02:00
if ( transport - > iso_io )
spa_bt_iso_io_destroy ( transport - > iso_io ) ;
2019-05-14 18:10:18 +02:00
spa_bt_transport_destroy ( transport ) ;
2023-07-11 17:01:00 +02:00
cancel_and_unref ( & transport - > acquire_call ) ;
cancel_and_unref ( & transport - > volume_call ) ;
2023-04-30 18:39:31 +03:00
2020-12-22 20:36:04 +02:00
if ( transport - > fd > = 0 ) {
2021-10-09 19:11:51 +03:00
spa_bt_player_set_state ( transport - > device - > adapter - > dummy_player , SPA_BT_PLAYER_STOPPED ) ;
2020-12-22 20:36:04 +02:00
shutdown ( transport - > fd , SHUT_RDWR ) ;
close ( transport - > fd ) ;
transport - > fd = - 1 ;
}
2018-01-11 10:23:37 +01:00
spa_list_remove ( & transport - > link ) ;
2018-11-27 17:08:36 +01:00
if ( transport - > device ) {
2021-01-29 19:41:26 +02:00
prev_connected = transport - > device - > connected_profiles ;
2018-11-27 17:08:36 +01:00
transport - > device - > connected_profiles & = ~ transport - > profile ;
spa_list_remove ( & transport - > device_link ) ;
}
2021-01-29 19:41:26 +02:00
2023-03-05 01:03:13 +02:00
if ( device & & device - > connected_profiles ! = prev_connected ) {
if ( ( prev_connected ^ device - > connected_profiles ) & SPA_BT_PROFILE_BAP_DUPLEX )
device_update_set_status ( device , true , NULL ) ;
2021-01-29 19:41:26 +02:00
spa_bt_device_emit_profiles_changed ( device , device - > profiles , prev_connected ) ;
2023-03-05 01:03:13 +02:00
}
2021-03-20 16:02:47 +08:00
2022-08-04 16:45:56 +02:00
spa_list_remove ( & transport - > bap_transport_linked ) ;
2023-07-13 21:27:53 +02:00
free ( transport - > configuration ) ;
2022-05-26 12:44:07 +03:00
free ( transport - > endpoint_path ) ;
2021-03-20 16:02:47 +08:00
free ( transport - > path ) ;
free ( transport ) ;
2018-01-11 10:23:37 +01:00
}
2022-01-31 00:08:26 +02:00
int spa_bt_transport_keepalive ( struct spa_bt_transport * t , bool keepalive )
{
if ( keepalive ) {
t - > keepalive = true ;
return 0 ;
}
t - > keepalive = false ;
if ( t - > acquire_refcount = = 0 & & t - > acquired ) {
t - > acquire_refcount = 1 ;
return spa_bt_transport_release ( t ) ;
}
return 0 ;
}
2020-12-22 20:36:04 +02:00
int spa_bt_transport_acquire ( struct spa_bt_transport * transport , bool optional )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
int res ;
if ( transport - > acquire_refcount > 0 ) {
spa_log_debug ( monitor - > log , " transport %p: incref %s " , transport , transport - > path ) ;
transport - > acquire_refcount + = 1 ;
2023-03-14 22:06:23 +02:00
spa_bt_transport_emit_state_changed ( transport , transport - > state , transport - > state ) ;
2020-12-22 20:36:04 +02:00
return 0 ;
}
spa_assert ( transport - > acquire_refcount = = 0 ) ;
2023-03-18 14:48:16 +02:00
/* If we are getting into error state too often, stop trying */
if ( get_time_now ( monitor ) > transport - > last_error_time + TRANSPORT_ERROR_TIMEOUT )
transport - > error_count = 0 ;
if ( transport - > error_count > = TRANSPORT_ERROR_MAX_RETRY )
return - EIO ;
2022-01-31 00:08:26 +02:00
if ( ! transport - > acquired )
res = spa_bt_transport_impl ( transport , acquire , 0 , optional ) ;
else
res = 0 ;
2020-12-22 20:36:04 +02:00
2022-01-31 00:08:26 +02:00
if ( res > = 0 ) {
2020-12-22 20:36:04 +02:00
transport - > acquire_refcount = 1 ;
2022-01-31 00:08:26 +02:00
transport - > acquired = true ;
}
2020-12-22 20:36:04 +02:00
return res ;
}
2023-05-25 19:12:12 +03:00
static void spa_bt_transport_do_release ( struct spa_bt_transport * transport )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
spa_assert ( transport - > acquire_refcount > = 1 ) ;
spa_assert ( transport - > acquired ) ;
if ( transport - > acquire_refcount = = 1 ) {
if ( ! transport - > keepalive ) {
spa_bt_transport_impl ( transport , release , 0 ) ;
transport - > acquired = false ;
} else {
spa_log_debug ( monitor - > log , " transport %p: keepalive %s on release " ,
transport , transport - > path ) ;
}
} else {
spa_log_debug ( monitor - > log , " transport %p: delayed decref %s " , transport , transport - > path ) ;
}
transport - > acquire_refcount - = 1 ;
}
2020-12-22 20:36:04 +02:00
int spa_bt_transport_release ( struct spa_bt_transport * transport )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
if ( transport - > acquire_refcount > 1 ) {
spa_log_debug ( monitor - > log , " transport %p: decref %s " , transport , transport - > path ) ;
transport - > acquire_refcount - = 1 ;
2023-03-14 22:06:23 +02:00
spa_bt_transport_emit_state_changed ( transport , transport - > state , transport - > state ) ;
2020-12-22 20:36:04 +02:00
return 0 ;
}
else if ( transport - > acquire_refcount = = 0 ) {
spa_log_info ( monitor - > log , " transport %s already released " , transport - > path ) ;
return 0 ;
}
spa_assert ( transport - > acquire_refcount = = 1 ) ;
2022-01-31 00:08:26 +02:00
spa_assert ( transport - > acquired ) ;
2020-12-22 20:36:04 +02:00
2023-05-25 19:12:12 +03:00
/* Postpone active transport releases, since we might need it again soon.
* If not active , release now since it has to be reacquired before using again .
*/
2023-08-12 20:02:40 +03:00
if ( transport - > state = = SPA_BT_TRANSPORT_STATE_ACTIVE & &
! SPA_BT_TRANSPORT_IS_A2DP ( transport ) ) {
2023-05-25 19:12:12 +03:00
return spa_bt_transport_start_release_timer ( transport ) ;
} else {
spa_bt_transport_do_release ( transport ) ;
return 0 ;
}
2020-12-22 20:36:04 +02:00
}
2021-03-08 23:39:01 +02:00
static int spa_bt_transport_release_now ( struct spa_bt_transport * transport )
{
int res ;
2022-01-31 00:08:26 +02:00
if ( ! transport - > acquired )
2021-03-08 23:39:01 +02:00
return 0 ;
spa_bt_transport_stop_release_timer ( transport ) ;
res = spa_bt_transport_impl ( transport , release , 0 ) ;
2022-01-31 00:08:26 +02:00
if ( res > = 0 ) {
2021-03-08 23:39:01 +02:00
transport - > acquire_refcount = 0 ;
2022-01-31 00:08:26 +02:00
transport - > acquired = false ;
}
2021-03-08 23:39:01 +02:00
return res ;
}
int spa_bt_device_release_transports ( struct spa_bt_device * device )
{
struct spa_bt_transport * t ;
spa_list_for_each ( t , & device - > transport_list , device_link )
spa_bt_transport_release_now ( t ) ;
2021-03-09 12:25:20 +01:00
return 0 ;
2021-03-08 23:39:01 +02:00
}
2021-04-15 11:39:49 +08:00
static int start_timeout_timer ( struct spa_bt_monitor * monitor ,
struct spa_source * timer , spa_source_func_t timer_event ,
time_t timeout_msec , void * data )
{
struct itimerspec ts ;
if ( timer - > data = = NULL ) {
timer - > data = data ;
timer - > func = timer_event ;
timer - > fd = spa_system_timerfd_create (
monitor - > main_system , CLOCK_MONOTONIC , SPA_FD_CLOEXEC | SPA_FD_NONBLOCK ) ;
timer - > mask = SPA_IO_IN ;
timer - > rmask = 0 ;
spa_loop_add_source ( monitor - > main_loop , timer ) ;
}
ts . it_value . tv_sec = timeout_msec / SPA_MSEC_PER_SEC ;
ts . it_value . tv_nsec = ( timeout_msec % SPA_MSEC_PER_SEC ) * SPA_NSEC_PER_MSEC ;
ts . it_interval . tv_sec = 0 ;
ts . it_interval . tv_nsec = 0 ;
spa_system_timerfd_settime ( monitor - > main_system , timer - > fd , 0 , & ts , NULL ) ;
return 0 ;
}
static int stop_timeout_timer ( struct spa_bt_monitor * monitor , struct spa_source * timer )
{
struct itimerspec ts ;
if ( timer - > data = = NULL )
return 0 ;
spa_loop_remove_source ( monitor - > main_loop , timer ) ;
ts . it_value . tv_sec = 0 ;
ts . it_value . tv_nsec = 0 ;
ts . it_interval . tv_sec = 0 ;
ts . it_interval . tv_nsec = 0 ;
spa_system_timerfd_settime ( monitor - > main_system , timer - > fd , 0 , & ts , NULL ) ;
spa_system_close ( monitor - > main_system , timer - > fd ) ;
timer - > data = NULL ;
return 0 ;
}
2021-01-04 15:54:27 +02:00
static void spa_bt_transport_release_timer_event ( struct spa_source * source )
{
struct spa_bt_transport * transport = source - > data ;
spa_bt_transport_stop_release_timer ( transport ) ;
2023-05-25 19:12:12 +03:00
spa_bt_transport_do_release ( transport ) ;
2021-01-04 15:54:27 +02:00
}
static int spa_bt_transport_start_release_timer ( struct spa_bt_transport * transport )
2021-04-15 11:39:49 +08:00
{
return start_timeout_timer ( transport - > monitor ,
& transport - > release_timer ,
spa_bt_transport_release_timer_event ,
2023-03-26 16:15:33 +03:00
TRANSPORT_RELEASE_TIMEOUT_MSEC , transport ) ;
2021-04-15 11:39:49 +08:00
}
static int spa_bt_transport_stop_release_timer ( struct spa_bt_transport * transport )
{
return stop_timeout_timer ( transport - > monitor , & transport - > release_timer ) ;
}
2023-05-25 19:12:12 +03:00
static void spa_bt_transport_commit_release_timer ( struct spa_bt_transport * transport )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
/* Do release now if it is pending */
if ( transport - > release_timer . data ) {
spa_log_debug ( monitor - > log , " transport %p: commit pending release " , transport ) ;
spa_bt_transport_release_timer_event ( & transport - > release_timer ) ;
}
}
2021-04-15 11:39:49 +08:00
static void spa_bt_transport_volume_changed ( struct spa_bt_transport * transport )
2021-01-04 15:54:27 +02:00
{
struct spa_bt_monitor * monitor = transport - > monitor ;
2021-04-15 11:39:49 +08:00
struct spa_bt_transport_volume * t_volume ;
2021-04-21 09:18:13 +08:00
int volume_id ;
2021-01-04 15:54:27 +02:00
2021-04-15 11:39:49 +08:00
if ( transport - > profile & SPA_BT_PROFILE_A2DP_SINK )
2021-04-21 09:18:13 +08:00
volume_id = SPA_BT_VOLUME_ID_TX ;
2021-04-15 11:39:49 +08:00
else if ( transport - > profile & SPA_BT_PROFILE_A2DP_SOURCE )
2021-04-21 09:18:13 +08:00
volume_id = SPA_BT_VOLUME_ID_RX ;
2021-04-15 11:39:49 +08:00
else
return ;
2021-04-21 09:18:13 +08:00
t_volume = & transport - > volumes [ volume_id ] ;
2021-04-15 11:39:49 +08:00
if ( t_volume - > hw_volume ! = t_volume - > new_hw_volume ) {
t_volume - > hw_volume = t_volume - > new_hw_volume ;
t_volume - > volume = spa_bt_volume_hw_to_linear ( t_volume - > hw_volume ,
t_volume - > hw_volume_max ) ;
spa_log_debug ( monitor - > log , " transport %p: volume changed %d(%f) " ,
transport , t_volume - > new_hw_volume , t_volume - > volume ) ;
2021-04-21 09:18:13 +08:00
if ( spa_bt_transport_volume_enabled ( transport ) ) {
transport - > device - > a2dp_volume_active [ volume_id ] = true ;
2021-04-17 18:53:28 +08:00
spa_bt_transport_emit_volume_changed ( transport ) ;
2021-04-21 09:18:13 +08:00
}
2021-01-04 15:54:27 +02:00
}
}
2021-04-15 11:39:49 +08:00
static void spa_bt_transport_volume_timer_event ( struct spa_source * source )
2021-01-04 15:54:27 +02:00
{
2021-04-15 11:39:49 +08:00
struct spa_bt_transport * transport = source - > data ;
2021-01-04 15:54:27 +02:00
struct spa_bt_monitor * monitor = transport - > monitor ;
2021-04-15 11:39:49 +08:00
uint64_t exp ;
2021-01-04 15:54:27 +02:00
2021-04-15 11:39:49 +08:00
if ( spa_system_timerfd_read ( monitor - > main_system , source - > fd , & exp ) < 0 )
2022-10-08 01:40:03 +02:00
spa_log_warn ( monitor - > log , " error reading timerfd: %s " , strerror ( errno ) ) ;
2021-01-04 15:54:27 +02:00
2021-04-15 11:39:49 +08:00
spa_bt_transport_volume_changed ( transport ) ;
}
static int spa_bt_transport_start_volume_timer ( struct spa_bt_transport * transport )
{
return start_timeout_timer ( transport - > monitor ,
& transport - > volume_timer ,
spa_bt_transport_volume_timer_event ,
TRANSPORT_VOLUME_TIMEOUT_MSEC , transport ) ;
}
static int spa_bt_transport_stop_volume_timer ( struct spa_bt_transport * transport )
{
return stop_timeout_timer ( transport - > monitor , & transport - > volume_timer ) ;
2021-01-04 15:54:27 +02:00
}
2021-04-15 11:39:49 +08:00
2021-03-20 12:18:13 +02:00
int spa_bt_transport_ensure_sco_io ( struct spa_bt_transport * t , struct spa_loop * data_loop )
2020-12-23 15:10:04 +02:00
{
if ( t - > sco_io = = NULL ) {
2024-05-12 12:41:36 +03:00
t - > sco_io = spa_bt_sco_io_create ( t , data_loop , t - > monitor - > log ) ;
2021-03-20 12:18:13 +02:00
if ( t - > sco_io = = NULL )
return - ENOMEM ;
2020-12-23 15:10:04 +02:00
}
2021-03-20 12:18:13 +02:00
return 0 ;
2020-12-23 15:10:04 +02:00
}
2021-02-13 22:59:04 +02:00
int64_t spa_bt_transport_get_delay_nsec ( struct spa_bt_transport * t )
{
2023-02-19 17:25:14 +02:00
if ( t - > delay_us ! = SPA_BT_UNKNOWN_DELAY ) {
/* end-to-end delay = (presentation) delay + transport latency
*
* For BAP , see Core v5 .3 Vol 6 / G Sec 3.2 .2 Fig . 3.2 &
* BAP v1 .0 Sec 7.1 .1 .
*/
int64_t delay = t - > delay_us ;
if ( t - > latency_us ! = SPA_BT_UNKNOWN_DELAY )
delay + = t - > latency_us ;
return delay * SPA_NSEC_PER_USEC ;
}
2021-02-13 22:59:04 +02:00
/* Fallback values when device does not provide information */
2022-06-15 17:24:41 +02:00
if ( t - > media_codec = = NULL )
2021-08-25 21:17:33 +03:00
return 20 * SPA_NSEC_PER_MSEC ;
2021-02-13 22:59:04 +02:00
2022-06-15 17:24:41 +02:00
switch ( t - > media_codec - > id ) {
2021-08-15 20:15:58 +03:00
case SPA_BLUETOOTH_AUDIO_CODEC_SBC :
case SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ :
case SPA_BLUETOOTH_AUDIO_CODEC_MPEG :
case SPA_BLUETOOTH_AUDIO_CODEC_AAC :
case SPA_BLUETOOTH_AUDIO_CODEC_APTX :
case SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD :
case SPA_BLUETOOTH_AUDIO_CODEC_LDAC :
2021-08-25 21:17:33 +03:00
return 125 * SPA_NSEC_PER_MSEC ;
2024-01-21 17:09:27 +00:00
case SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD :
2021-08-15 20:15:58 +03:00
case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL :
2021-08-18 17:19:05 +03:00
case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX :
2021-08-20 01:18:50 +03:00
case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM :
case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX :
2022-07-20 12:42:41 +02:00
case SPA_BLUETOOTH_AUDIO_CODEC_LC3 :
2021-08-15 20:15:58 +03:00
return 40 * SPA_NSEC_PER_MSEC ;
2021-02-13 22:59:04 +02:00
default :
break ;
} ;
2021-08-25 21:17:33 +03:00
return 125 * SPA_NSEC_PER_MSEC ;
2021-02-13 22:59:04 +02:00
}
2018-01-11 10:23:37 +01:00
static int transport_update_props ( struct spa_bt_transport * transport ,
DBusMessageIter * props_iter ,
DBusMessageIter * invalidated_iter )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
while ( dbus_message_iter_get_arg_type ( props_iter ) ! = DBUS_TYPE_INVALID ) {
DBusMessageIter it [ 2 ] ;
const char * key ;
int type ;
dbus_message_iter_recurse ( props_iter , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & key ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
type = dbus_message_iter_get_arg_type ( & it [ 1 ] ) ;
if ( type = = DBUS_TYPE_STRING | | type = = DBUS_TYPE_OBJECT_PATH ) {
const char * value ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " transport %p: %s=%s " , transport , key , value ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( key , " UUID " ) ) {
2023-09-02 15:06:27 +03:00
transport - > profile = swap_profile ( spa_bt_profile_from_uuid ( value ) ) ;
if ( transport - > profile = = SPA_BT_PROFILE_NULL )
2018-11-27 17:08:36 +01:00
spa_log_warn ( monitor - > log , " unknown profile %s " , value ) ;
2018-01-11 10:23:37 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " State " ) ) {
2023-03-18 14:09:10 +02:00
enum spa_bt_transport_state state = spa_bt_transport_state_from_string ( value ) ;
2023-04-11 20:59:25 +03:00
/* Transition to active emitted only from acquire callback. */
if ( state ! = SPA_BT_TRANSPORT_STATE_ACTIVE )
2023-03-18 14:09:10 +02:00
spa_bt_transport_set_state ( transport , state ) ;
2018-01-11 10:23:37 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Device " ) ) {
2022-01-19 19:41:08 +02:00
struct spa_bt_device * device = spa_bt_device_find ( monitor , value ) ;
if ( transport - > device ! = device ) {
if ( transport - > device ! = NULL )
spa_list_remove ( & transport - > device_link ) ;
transport - > device = device ;
if ( device ! = NULL )
spa_list_append ( & device - > transport_list , & transport - > device_link ) ;
else
spa_log_warn ( monitor - > log , " could not find device %s " , value ) ;
}
2018-01-11 10:23:37 +01:00
}
2022-08-30 15:45:03 +02:00
else if ( spa_streq ( key , " Endpoint " ) ) {
struct spa_bt_remote_endpoint * ep = remote_endpoint_find ( monitor , value ) ;
if ( ! ep ) {
spa_log_warn ( monitor - > log , " Unable to find remote endpoint for %s " , value ) ;
goto next ;
}
// If the remote endpoint is an acceptor this transport is an initiator
transport - > bap_initiator = ep - > acceptor ;
}
2018-01-11 10:23:37 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Codec " ) ) {
2021-02-02 23:42:34 +08:00
uint8_t value ;
2018-01-11 10:23:37 +01:00
if ( type ! = DBUS_TYPE_BYTE )
goto next ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " transport %p: %s=%02x " , transport , key , value ) ;
transport - > codec = value ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Configuration " ) ) {
2018-01-11 10:23:37 +01:00
DBusMessageIter iter ;
2020-12-06 09:32:12 +01:00
uint8_t * value ;
2022-07-18 11:26:59 +02:00
int len ;
2018-01-11 10:23:37 +01:00
2020-11-09 16:48:44 +01:00
if ( ! check_iter_signature ( & it [ 1 ] , " ay " ) )
2018-01-11 10:23:37 +01:00
goto next ;
dbus_message_iter_recurse ( & it [ 1 ] , & iter ) ;
dbus_message_iter_get_fixed_array ( & iter , & value , & len ) ;
spa_log_debug ( monitor - > log , " transport %p: %s=%d " , transport , key , len ) ;
2023-01-18 17:41:16 +01:00
spa_debug_log_mem ( monitor - > log , SPA_LOG_LEVEL_DEBUG , 2 , value , ( size_t ) len ) ;
2018-01-11 10:23:37 +01:00
free ( transport - > configuration ) ;
transport - > configuration_len = 0 ;
transport - > configuration = malloc ( len ) ;
if ( transport - > configuration ) {
memcpy ( transport - > configuration , value , len ) ;
transport - > configuration_len = len ;
}
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Volume " ) ) {
2021-04-15 11:39:49 +08:00
uint16_t value ;
struct spa_bt_transport_volume * t_volume ;
if ( type ! = DBUS_TYPE_UINT16 )
goto next ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
spa_log_debug ( monitor - > log , " transport %p: %s=%d " , transport , key , value ) ;
if ( transport - > profile & SPA_BT_PROFILE_A2DP_SINK )
t_volume = & transport - > volumes [ SPA_BT_VOLUME_ID_TX ] ;
else if ( transport - > profile & SPA_BT_PROFILE_A2DP_SOURCE )
t_volume = & transport - > volumes [ SPA_BT_VOLUME_ID_RX ] ;
else
goto next ;
t_volume - > active = true ;
t_volume - > new_hw_volume = value ;
if ( transport - > profile & SPA_BT_PROFILE_A2DP_SINK )
spa_bt_transport_start_volume_timer ( transport ) ;
else
spa_bt_transport_volume_changed ( transport ) ;
2018-01-11 10:23:37 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( key , " Delay " ) ) {
2023-09-23 14:24:52 +03:00
uint16_t value ;
2023-02-19 17:25:14 +02:00
2023-09-23 14:24:52 +03:00
if ( type ! = DBUS_TYPE_UINT16 )
goto next ;
dbus_message_iter_get_basic ( & it [ 1 ] , & value ) ;
2023-02-19 17:25:14 +02:00
2023-09-23 14:24:52 +03:00
spa_log_debug ( monitor - > log , " transport %p: %s=%d " , transport , key , ( int ) value ) ;
2023-02-19 17:25:14 +02:00
2023-09-23 14:24:52 +03:00
transport - > delay_us = value * 100 ;
2020-12-23 19:13:54 +01:00
2021-05-28 19:02:06 +08:00
spa_bt_transport_emit_delay_changed ( transport ) ;
2020-12-23 19:13:54 +01:00
}
2023-09-23 14:24:52 +03:00
else if ( spa_streq ( key , " QoS " ) ) {
struct bap_codec_qos_full qos ;
DBusMessageIter value ;
2022-07-20 12:42:41 +02:00
2023-09-23 14:24:52 +03:00
if ( ! check_iter_signature ( & it [ 1 ] , " a{sv} " ) )
2022-07-20 12:42:41 +02:00
goto next ;
2023-09-23 14:24:52 +03:00
dbus_message_iter_recurse ( & it [ 1 ] , & value ) ;
parse_codec_qos ( monitor , & value , & qos ) ;
transport - > bap_cig = qos . cig ;
transport - > bap_cis = qos . cis ;
transport - > bap_big = qos . big ;
transport - > bap_bis = qos . bis ;
transport - > delay_us = qos . qos . delay ;
transport - > latency_us = ( unsigned int ) qos . qos . latency * 1000 ;
2022-07-20 12:42:41 +02:00
spa_bt_transport_emit_delay_changed ( transport ) ;
}
2022-08-04 16:45:56 +02:00
else if ( spa_streq ( key , " Links " ) ) {
DBusMessageIter iter ;
if ( ! check_iter_signature ( & it [ 1 ] , " ao " ) )
goto next ;
dbus_message_iter_recurse ( & it [ 1 ] , & iter ) ;
while ( dbus_message_iter_get_arg_type ( & iter ) ! = DBUS_TYPE_INVALID ) {
const char * transport_path ;
struct spa_bt_transport * t ;
dbus_message_iter_get_basic ( & iter , & transport_path ) ;
spa_log_debug ( monitor - > log , " transport %p: Linked with=%s " , transport , transport_path ) ;
t = spa_bt_transport_find ( monitor , transport_path ) ;
if ( ! t ) {
spa_log_warn ( monitor - > log , " Unable to find linked transport " ) ;
dbus_message_iter_next ( & iter ) ;
continue ;
}
if ( spa_list_is_empty ( & t - > bap_transport_linked ) )
spa_list_append ( & transport - > bap_transport_linked , & t - > bap_transport_linked ) ;
else if ( spa_list_is_empty ( & transport - > bap_transport_linked ) )
spa_list_append ( & t - > bap_transport_linked , & transport - > bap_transport_linked ) ;
dbus_message_iter_next ( & iter ) ;
}
}
2023-03-15 18:55:54 +02:00
2022-10-08 01:43:27 +02:00
next :
2018-01-11 10:23:37 +01:00
dbus_message_iter_next ( props_iter ) ;
}
return 0 ;
}
2023-04-30 18:39:31 +03:00
static void transport_set_property_volume_reply ( DBusPendingCall * pending , void * user_data )
2021-04-15 11:39:49 +08:00
{
2023-04-30 18:39:31 +03:00
struct spa_bt_transport * transport = user_data ;
2021-04-15 11:39:49 +08:00
struct spa_bt_monitor * monitor = transport - > monitor ;
2023-07-11 20:44:23 +02:00
spa_auto ( DBusError ) err = DBUS_ERROR_INIT ;
2023-04-30 18:39:31 +03:00
spa_assert ( transport - > volume_call = = pending ) ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = steal_reply_and_unref ( & transport - > volume_call ) ;
2023-04-30 18:39:31 +03:00
if ( dbus_set_error_from_message ( & err , r ) ) {
spa_log_info ( monitor - > log , " transport %p: set volume failed for transport %s: %s " ,
transport , transport - > path , err . message ) ;
} else {
spa_log_debug ( monitor - > log , " transport %p: set volume complete " ,
transport ) ;
}
}
static void transport_set_property_volume ( struct spa_bt_transport * transport , uint16_t value )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2021-04-15 11:39:49 +08:00
DBusMessageIter it [ 2 ] ;
const char * interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE ;
const char * name = " Volume " ;
int res = 0 ;
2023-04-30 18:39:31 +03:00
2023-07-11 17:01:00 +02:00
cancel_and_unref ( & transport - > volume_call ) ;
2021-04-15 11:39:49 +08:00
m = dbus_message_new_method_call ( BLUEZ_SERVICE ,
transport - > path ,
DBUS_INTERFACE_PROPERTIES ,
" Set " ) ;
2023-04-30 18:39:31 +03:00
if ( m = = NULL ) {
res = - ENOMEM ;
goto fail ;
}
2021-04-15 11:39:49 +08:00
dbus_message_iter_init_append ( m , & it [ 0 ] ) ;
dbus_message_iter_append_basic ( & it [ 0 ] , DBUS_TYPE_STRING , & interface ) ;
dbus_message_iter_append_basic ( & it [ 0 ] , DBUS_TYPE_STRING , & name ) ;
dbus_message_iter_open_container ( & it [ 0 ] , DBUS_TYPE_VARIANT ,
DBUS_TYPE_UINT16_AS_STRING , & it [ 1 ] ) ;
dbus_message_iter_append_basic ( & it [ 1 ] , DBUS_TYPE_UINT16 , & value ) ;
dbus_message_iter_close_container ( & it [ 0 ] , & it [ 1 ] ) ;
2023-07-11 19:57:23 +02:00
transport - > volume_call = send_with_reply ( monitor - > conn , m , transport_set_property_volume_reply , transport ) ;
if ( ! transport - > volume_call ) {
2021-04-15 11:39:49 +08:00
res = - EIO ;
2023-04-30 18:39:31 +03:00
goto fail ;
}
2021-04-15 11:39:49 +08:00
2023-04-30 18:39:31 +03:00
spa_log_debug ( monitor - > log , " transport %p: setting volume to %d " , transport , value ) ;
return ;
2021-04-15 11:39:49 +08:00
2023-04-30 18:39:31 +03:00
fail :
spa_log_debug ( monitor - > log , " transport %p: failed to set volume %d: %s " ,
transport , value , spa_strerror ( res ) ) ;
2021-04-15 11:39:49 +08:00
}
static int transport_set_volume ( void * data , int id , float volume )
{
struct spa_bt_transport * transport = data ;
struct spa_bt_transport_volume * t_volume = & transport - > volumes [ id ] ;
uint16_t value ;
2021-04-17 18:53:28 +08:00
if ( ! t_volume - > active | | ! spa_bt_transport_volume_enabled ( transport ) )
2021-04-15 11:39:49 +08:00
return - ENOTSUP ;
value = spa_bt_volume_linear_to_hw ( volume , 127 ) ;
t_volume - > volume = volume ;
/* AVRCP volume would not applied on remote sink device
* if transport is not acquired ( idle ) . */
if ( transport - > fd < 0 & & ( transport - > profile & SPA_BT_PROFILE_A2DP_SINK ) ) {
t_volume - > hw_volume = SPA_BT_VOLUME_INVALID ;
return 0 ;
} else if ( t_volume - > hw_volume ! = value ) {
t_volume - > hw_volume = value ;
spa_bt_transport_stop_volume_timer ( transport ) ;
transport_set_property_volume ( transport , value ) ;
}
return 0 ;
}
2023-03-25 16:58:27 +02:00
static int transport_create_iso_io ( struct spa_bt_transport * transport )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
struct spa_bt_transport * t ;
2023-08-17 18:52:48 +03:00
if ( ! ( transport - > profile & ( SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE |
2023-08-22 14:53:51 +03:00
SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ) ) )
2023-03-25 16:58:27 +02:00
return 0 ;
2023-08-17 18:52:48 +03:00
if ( ( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SINK ) | |
( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ) ) {
2023-07-23 22:16:17 +03:00
if ( transport - > bap_big = = 0xff | | transport - > bap_bis = = 0xff )
return - EINVAL ;
} else {
if ( transport - > bap_cig = = 0xff | | transport - > bap_cis = = 0xff )
return - EINVAL ;
}
2023-03-25 16:58:27 +02:00
if ( transport - > iso_io ) {
spa_log_debug ( monitor - > log , " transport %p: remove ISO IO " , transport ) ;
spa_bt_iso_io_destroy ( transport - > iso_io ) ;
transport - > iso_io = NULL ;
}
/* Transports in same connected iso group share the same i/o */
spa_list_for_each ( t , & monitor - > transport_list , link ) {
2023-08-17 18:52:48 +03:00
if ( ! ( t - > profile & ( SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE |
SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ) ) )
2023-03-25 16:58:27 +02:00
continue ;
2023-08-17 18:52:48 +03:00
2023-11-12 18:22:15 +02:00
if ( t - > device - > adapter ! = transport - > device - > adapter )
continue ;
2023-08-17 18:52:48 +03:00
if ( ( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SINK ) | |
( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ) ) {
2023-07-23 22:16:17 +03:00
if ( t - > bap_big ! = transport - > bap_big )
continue ;
} else {
if ( t - > bap_cig ! = transport - > bap_cig )
continue ;
}
2023-03-25 16:58:27 +02:00
if ( t - > iso_io ) {
spa_log_debug ( monitor - > log , " transport %p: attach ISO IO to %p " ,
transport , t ) ;
2023-04-09 21:28:40 +03:00
transport - > iso_io = spa_bt_iso_io_attach ( t - > iso_io , transport ) ;
2023-04-11 20:59:25 +03:00
if ( transport - > iso_io = = NULL )
return - errno ;
2023-03-25 16:58:27 +02:00
return 0 ;
}
}
spa_log_debug ( monitor - > log , " transport %p: new ISO IO " , transport ) ;
2023-04-09 21:28:40 +03:00
transport - > iso_io = spa_bt_iso_io_create ( transport , monitor - > log , monitor - > data_loop , monitor - > data_system ) ;
2023-03-25 16:58:27 +02:00
if ( transport - > iso_io = = NULL )
return - errno ;
return 0 ;
}
2023-04-11 20:59:25 +03:00
static bool transport_in_same_cig ( struct spa_bt_transport * transport , struct spa_bt_transport * other )
{
return ( other - > profile & ( SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE ) ) & &
other - > bap_cig = = transport - > bap_cig & &
other - > bap_initiator ;
}
2023-03-14 22:06:23 +02:00
static void transport_acquire_reply ( DBusPendingCall * pending , void * user_data )
2018-01-11 10:23:37 +01:00
{
2023-03-14 22:06:23 +02:00
struct spa_bt_transport * transport = user_data ;
2018-01-11 10:23:37 +01:00
struct spa_bt_monitor * monitor = transport - > monitor ;
2023-03-14 22:06:23 +02:00
struct spa_bt_device * device = transport - > device ;
2018-01-11 10:23:37 +01:00
int ret = 0 ;
2023-07-11 20:44:23 +02:00
spa_auto ( DBusError ) err = DBUS_ERROR_INIT ;
2023-04-11 20:59:25 +03:00
struct spa_bt_transport * t , * t_linked ;
2022-08-04 16:45:56 +02:00
2023-03-14 22:06:23 +02:00
spa_assert ( transport - > acquire_call = = pending ) ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = steal_reply_and_unref ( & transport - > acquire_call ) ;
2018-01-11 10:23:37 +01:00
2023-03-14 22:06:23 +02:00
spa_bt_device_update_last_bluez_action_time ( device ) ;
2018-01-11 10:23:37 +01:00
if ( dbus_message_get_type ( r ) = = DBUS_MESSAGE_TYPE_ERROR ) {
2023-03-14 22:06:23 +02:00
spa_log_error ( monitor - > log , " Acquire %s returned error: %s " ,
transport - > path ,
dbus_message_get_error_name ( r ) ) ;
2018-01-11 10:23:37 +01:00
ret = - EIO ;
goto finish ;
}
2023-03-25 16:58:27 +02:00
if ( transport - > fd > = 0 ) {
spa_log_error ( monitor - > log , " transport %p: invalid duplicate acquire " , transport ) ;
ret = - EINVAL ;
goto finish ;
}
2018-01-11 10:23:37 +01:00
if ( ! dbus_message_get_args ( r , & err ,
DBUS_TYPE_UNIX_FD , & transport - > fd ,
DBUS_TYPE_UINT16 , & transport - > read_mtu ,
DBUS_TYPE_UINT16 , & transport - > write_mtu ,
DBUS_TYPE_INVALID ) ) {
2023-03-14 22:06:23 +02:00
spa_log_error ( monitor - > log , " Failed to parse Acquire %s reply: %s " ,
transport - > path , err . message ) ;
2018-01-11 10:23:37 +01:00
ret = - EIO ;
goto finish ;
}
2023-03-14 22:06:23 +02:00
spa_log_debug ( monitor - > log , " transport %p: Acquired %s, fd %d MTU %d:%d " , transport ,
2019-05-14 18:10:18 +02:00
transport - > path , transport - > fd , transport - > read_mtu , transport - > write_mtu ) ;
2018-01-11 10:23:37 +01:00
2021-10-09 19:11:51 +03:00
spa_bt_player_set_state ( transport - > device - > adapter - > dummy_player , SPA_BT_PLAYER_PLAYING ) ;
2021-04-15 11:39:49 +08:00
transport_sync_volume ( transport ) ;
2018-01-11 10:23:37 +01:00
finish :
2023-03-14 22:06:23 +02:00
if ( ret < 0 )
spa_bt_transport_set_state ( transport , SPA_BT_TRANSPORT_STATE_ERROR ) ;
2023-03-25 16:58:27 +02:00
else {
if ( transport_create_iso_io ( transport ) < 0 )
spa_log_error ( monitor - > log , " transport %p: transport_create_iso_io failed " ,
transport ) ;
2023-08-17 18:52:48 +03:00
/* For broadcast the initiator moves the transport state to SPA_BT_TRANSPORT_STATE_ACTIVE */
2024-05-20 10:45:04 -03:00
/* TODO: handling multiple BIGs support */
2023-08-17 18:52:48 +03:00
if ( ( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SINK ) | |
( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ) ) {
2023-04-11 20:59:25 +03:00
spa_bt_transport_set_state ( transport , SPA_BT_TRANSPORT_STATE_ACTIVE ) ;
2023-07-23 22:16:17 +03:00
} else {
if ( ! transport - > bap_initiator )
spa_bt_transport_set_state ( transport , SPA_BT_TRANSPORT_STATE_ACTIVE ) ;
}
2023-03-25 16:58:27 +02:00
}
2023-03-14 22:06:23 +02:00
/* For LE Audio, multiple transport from the same device may share the same
* stream ( CIS ) and group ( CIG ) but for different direction , e . g . a speaker and
* a microphone . In this case they are linked , and we need to set the values
* for all of them here .
*/
spa_list_for_each ( t_linked , & transport - > bap_transport_linked , bap_transport_linked ) {
if ( ret < 0 ) {
spa_bt_transport_set_state ( t_linked , SPA_BT_TRANSPORT_STATE_ERROR ) ;
continue ;
}
t_linked - > fd = transport - > fd ;
t_linked - > read_mtu = transport - > read_mtu ;
t_linked - > write_mtu = transport - > write_mtu ;
spa_log_debug ( monitor - > log , " transport %p: linked Acquired %s, fd %d MTU %d:%d " , t_linked ,
t_linked - > path , t_linked - > fd , t_linked - > read_mtu , t_linked - > write_mtu ) ;
2023-03-18 14:09:10 +02:00
2023-03-25 16:58:27 +02:00
if ( transport_create_iso_io ( t_linked ) < 0 )
spa_log_error ( monitor - > log , " transport %p: transport_create_iso_io failed " ,
t_linked ) ;
2023-07-23 22:16:17 +03:00
/* For broadcast there initiator moves the transport state to SPA_BT_TRANSPORT_STATE_ACTIVE */
2023-08-17 18:52:48 +03:00
if ( ( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SINK ) | |
( transport - > profile = = SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ) ) {
2023-11-07 14:05:40 +02:00
spa_bt_transport_set_state ( t_linked , SPA_BT_TRANSPORT_STATE_ACTIVE ) ;
2023-07-23 22:16:17 +03:00
} else {
if ( ! transport - > bap_initiator )
2023-11-07 14:05:40 +02:00
spa_bt_transport_set_state ( t_linked , SPA_BT_TRANSPORT_STATE_ACTIVE ) ;
2023-07-23 22:16:17 +03:00
}
2023-04-11 20:59:25 +03:00
}
/*
* Transports in same CIG emit state change events at the same time ,
* after all pending acquires complete .
*/
if ( transport - > bap_initiator ) {
spa_list_for_each ( t , & monitor - > transport_list , link ) {
if ( ! transport_in_same_cig ( transport , t ) )
continue ;
if ( t - > acquire_call )
return ;
}
spa_list_for_each ( t , & monitor - > transport_list , link ) {
if ( ! transport_in_same_cig ( transport , t ) )
continue ;
if ( t - > fd > = 0 )
spa_bt_transport_set_state ( t , SPA_BT_TRANSPORT_STATE_ACTIVE ) ;
}
2023-03-14 22:06:23 +02:00
}
2018-01-11 10:23:37 +01:00
}
2023-03-15 23:34:12 +02:00
static int do_transport_acquire ( struct spa_bt_transport * transport )
2018-01-11 10:23:37 +01:00
{
struct spa_bt_monitor * monitor = transport - > monitor ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2023-03-14 22:06:23 +02:00
struct spa_bt_transport * t_linked ;
spa_list_for_each ( t_linked , & transport - > bap_transport_linked , bap_transport_linked ) {
/* If a linked transport has been acquired, it will do all the work */
if ( t_linked - > acquire_call | | t_linked - > acquired ) {
spa_log_debug ( monitor - > log , " Acquiring %s: use linked transport %s " ,
transport - > path , t_linked - > path ) ;
spa_bt_transport_emit_state_changed ( transport , transport - > state , transport - > state ) ;
return 0 ;
}
}
if ( transport - > acquire_call )
return - EBUSY ;
spa_log_info ( monitor - > log , " Acquiring transport %s " , transport - > path ) ;
m = dbus_message_new_method_call ( BLUEZ_SERVICE ,
transport - > path ,
BLUEZ_MEDIA_TRANSPORT_INTERFACE ,
" Acquire " ) ;
if ( m = = NULL )
return - ENOMEM ;
2023-07-11 19:57:23 +02:00
transport - > acquire_call = send_with_reply ( monitor - > conn , m , transport_acquire_reply , transport ) ;
if ( ! transport - > acquire_call )
2023-03-14 22:06:23 +02:00
return - EIO ;
return 0 ;
}
2023-03-15 23:34:12 +02:00
static bool another_cig_transport_active ( struct spa_bt_transport * transport )
{
struct spa_bt_monitor * monitor = transport - > monitor ;
struct spa_bt_transport * t ;
spa_list_for_each ( t , & monitor - > transport_list , link ) {
if ( ! transport_in_same_cig ( transport , t ) | | t = = transport )
continue ;
if ( t - > acquired )
return true ;
}
return false ;
}
static int transport_acquire ( void * data , bool optional )
{
struct spa_bt_transport * transport = data ;
struct spa_bt_monitor * monitor = transport - > monitor ;
/*
* XXX : When as BAP Central , all CIS in a CIG must be acquired at the same time .
* XXX : This is because of kernel ISO socket limitations , which does not handle
* XXX : currently starting streams in the group one by one .
*/
if ( transport - > bap_initiator & & ! another_cig_transport_active ( transport ) ) {
struct spa_bt_transport * t ;
spa_list_for_each ( t , & monitor - > transport_list , link ) {
if ( ! transport_in_same_cig ( transport , t ) | | t = = transport )
continue ;
spa_log_debug ( monitor - > log , " Acquire CIG %d: transport %s " ,
transport - > bap_cig , t - > path ) ;
do_transport_acquire ( t ) ;
}
spa_log_debug ( monitor - > log , " Acquire CIG %d: transport %s " ,
transport - > bap_cig , transport - > path ) ;
}
if ( transport - > bap_initiator & &
( transport - > fd > = 0 | | transport - > acquire_call ) ) {
/* Already acquired/acquiring */
spa_log_debug ( monitor - > log , " Acquiring %s: was in acquired CIG " , transport - > path ) ;
spa_bt_transport_emit_state_changed ( transport , transport - > state , transport - > state ) ;
return 0 ;
}
return do_transport_acquire ( data ) ;
}
static int do_transport_release ( struct spa_bt_transport * transport )
2023-03-14 22:06:23 +02:00
{
struct spa_bt_monitor * monitor = transport - > monitor ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL , r = NULL ;
2022-08-04 16:45:56 +02:00
struct spa_bt_transport * t_linked ;
2023-03-25 22:05:17 +02:00
bool is_idle = ( transport - > state = = SPA_BT_TRANSPORT_STATE_IDLE ) ;
2022-08-04 16:45:56 +02:00
bool linked = false ;
2018-01-11 10:23:37 +01:00
2021-10-01 19:03:49 +03:00
spa_log_debug ( monitor - > log , " transport %p: Release %s " ,
2019-05-14 18:10:18 +02:00
transport , transport - > path ) ;
2018-01-11 10:23:37 +01:00
2021-10-09 19:11:51 +03:00
spa_bt_player_set_state ( transport - > device - > adapter - > dummy_player , SPA_BT_PLAYER_STOPPED ) ;
2023-03-25 16:58:27 +02:00
spa_bt_transport_set_state ( transport , SPA_BT_TRANSPORT_STATE_IDLE ) ;
2023-07-11 17:01:00 +02:00
cancel_and_unref ( & transport - > acquire_call ) ;
2023-03-14 22:06:23 +02:00
2023-03-25 16:58:27 +02:00
if ( transport - > iso_io ) {
spa_log_debug ( monitor - > log , " transport %p: remove ISO IO " , transport ) ;
spa_bt_iso_io_destroy ( transport - > iso_io ) ;
transport - > iso_io = NULL ;
}
2022-08-04 16:45:56 +02:00
/* For LE Audio, multiple transport stream (CIS) can be linked together (CIG).
2024-05-20 10:45:04 -03:00
* If they are part of the same device they reuse the same fd , and call to
2022-08-04 16:45:56 +02:00
* release should be done for the last one only .
*/
spa_list_for_each ( t_linked , & transport - > bap_transport_linked , bap_transport_linked ) {
2023-03-14 22:06:23 +02:00
if ( t_linked - > acquire_call | | t_linked - > acquired ) {
2022-08-04 16:45:56 +02:00
linked = true ;
break ;
}
}
if ( linked ) {
spa_log_info ( monitor - > log , " Linked transport %s released " , transport - > path ) ;
transport - > fd = - 1 ;
return 0 ;
}
2023-03-14 22:06:23 +02:00
if ( transport - > fd > = 0 ) {
close ( transport - > fd ) ;
transport - > fd = - 1 ;
}
spa_log_info ( monitor - > log , " Releasing transport %s " , transport - > path ) ;
2018-01-11 10:23:37 +01:00
m = dbus_message_new_method_call ( BLUEZ_SERVICE ,
transport - > path ,
BLUEZ_MEDIA_TRANSPORT_INTERFACE ,
" Release " ) ;
if ( m = = NULL )
return - ENOMEM ;
2023-07-11 20:44:23 +02:00
spa_auto ( DBusError ) err = DBUS_ERROR_INIT ;
2023-03-25 22:05:17 +02:00
r = dbus_connection_send_with_reply_and_block ( monitor - > conn , m , - 1 , & err ) ;
if ( r = = NULL ) {
if ( is_idle ) {
/* XXX: The fd always needs to be closed. However, Release()
* XXX : apparently doesn ' t need to be called on idle transports
* XXX : and fails . We call it just to be sure ( e . g . in case
* XXX : there ' s a race with updating the property ) , but tone down the error .
*/
spa_log_debug ( monitor - > log , " Failed to release idle transport %s: %s " ,
transport - > path , err . message ) ;
} else {
spa_log_error ( monitor - > log , " Failed to release transport %s: %s " ,
transport - > path , err . message ) ;
}
} else {
spa_log_info ( monitor - > log , " Transport %s released " , transport - > path ) ;
}
2018-01-11 10:23:37 +01:00
return 0 ;
}
2023-03-15 23:34:12 +02:00
static int transport_release ( void * data )
{
struct spa_bt_transport * transport = data ;
struct spa_bt_monitor * monitor = transport - > monitor ;
struct spa_bt_transport * t ;
/*
* XXX : When as BAP Central , release CIS in a CIG when the last transport
* XXX : goes away .
*/
if ( transport - > bap_initiator ) {
/* Check if another transport is alive */
if ( another_cig_transport_active ( transport ) ) {
spa_log_debug ( monitor - > log , " Releasing %s: wait for CIG %d " ,
transport - > path , transport - > bap_cig ) ;
return 0 ;
}
/* Release remaining transports in CIG */
spa_list_for_each ( t , & monitor - > transport_list , link ) {
if ( ! transport_in_same_cig ( transport , t ) | | t = = transport )
continue ;
spa_log_debug ( monitor - > log , " Release CIG %d: transport %s " ,
transport - > bap_cig , t - > path ) ;
if ( t - > fd > = 0 )
do_transport_release ( t ) ;
}
spa_log_debug ( monitor - > log , " Release CIG %d: transport %s " ,
transport - > bap_cig , transport - > path ) ;
}
return do_transport_release ( data ) ;
}
2019-05-14 18:10:18 +02:00
static const struct spa_bt_transport_implementation transport_impl = {
SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION ,
. acquire = transport_acquire ,
. release = transport_release ,
2021-04-15 11:39:49 +08:00
. set_volume = transport_set_volume ,
2019-05-14 18:10:18 +02:00
} ;
2022-06-15 17:24:41 +02:00
static void media_codec_switch_reply ( DBusPendingCall * pending , void * userdata ) ;
2021-01-25 19:57:45 +02:00
2022-06-15 17:24:41 +02:00
static int media_codec_switch_cmp ( const void * a , const void * b ) ;
2021-01-25 19:57:45 +02:00
2022-06-15 17:24:41 +02:00
static struct spa_bt_media_codec_switch * media_codec_switch_cmp_sw ; /* global for qsort */
2021-01-25 19:57:45 +02:00
2022-06-15 17:24:41 +02:00
static int media_codec_switch_start_timer ( struct spa_bt_media_codec_switch * sw , uint64_t timeout ) ;
2021-02-10 01:14:58 +02:00
2022-06-15 17:24:41 +02:00
static int media_codec_switch_stop_timer ( struct spa_bt_media_codec_switch * sw ) ;
2021-02-10 01:14:58 +02:00
2022-06-15 17:24:41 +02:00
static void media_codec_switch_free ( struct spa_bt_media_codec_switch * sw )
2021-01-25 19:57:45 +02:00
{
char * * p ;
2022-06-15 17:24:41 +02:00
media_codec_switch_stop_timer ( sw ) ;
2021-02-10 01:14:58 +02:00
2023-07-11 17:01:00 +02:00
cancel_and_unref ( & sw - > pending ) ;
2021-01-25 19:57:45 +02:00
if ( sw - > device ! = NULL )
spa_list_remove ( & sw - > device_link ) ;
if ( sw - > paths ! = NULL )
for ( p = sw - > paths ; * p ! = NULL ; + + p )
free ( * p ) ;
free ( sw - > paths ) ;
free ( sw - > codecs ) ;
free ( sw ) ;
}
2022-06-15 17:24:41 +02:00
static void media_codec_switch_next ( struct spa_bt_media_codec_switch * sw )
2021-01-25 19:57:45 +02:00
{
spa_assert ( * sw - > codec_iter ! = NULL & & * sw - > path_iter ! = NULL ) ;
+ + sw - > path_iter ;
if ( * sw - > path_iter = = NULL ) {
+ + sw - > codec_iter ;
sw - > path_iter = sw - > paths ;
}
2022-01-04 22:08:25 +02:00
sw - > retries = CODEC_SWITCH_RETRIES ;
2021-01-25 19:57:45 +02:00
}
2022-06-15 17:24:41 +02:00
static bool media_codec_switch_process_current ( struct spa_bt_media_codec_switch * sw )
2021-01-25 19:57:45 +02:00
{
struct spa_bt_remote_endpoint * ep ;
2022-05-26 12:44:07 +03:00
struct spa_bt_transport * t ;
2022-06-15 17:24:41 +02:00
const struct media_codec * codec ;
2021-01-25 19:57:45 +02:00
uint8_t config [ A2DP_MAX_CAPS_SIZE ] ;
2022-06-16 17:15:14 +02:00
enum spa_bt_media_direction direction ;
2023-07-11 20:16:50 +02:00
spa_autofree char * local_endpoint = NULL ;
2021-01-25 19:57:45 +02:00
int res , config_size ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2021-01-25 19:57:45 +02:00
DBusMessageIter iter , d ;
int i ;
2022-05-21 13:18:38 +03:00
bool sink ;
2021-01-25 19:57:45 +02:00
/* Try setting configuration for current codec on current endpoint in list */
codec = * sw - > codec_iter ;
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: consider codec %s for remote endpoint %s " ,
2021-01-25 19:57:45 +02:00
sw , ( * sw - > codec_iter ) - > name , * sw - > path_iter ) ;
ep = device_remote_endpoint_find ( sw - > device , * sw - > path_iter ) ;
if ( ep = = NULL | | ep - > capabilities = = NULL | | ep - > uuid = = NULL ) {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: endpoint %s not valid, try next " ,
2021-02-02 11:00:16 +01:00
sw , * sw - > path_iter ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
/* Setup and check compatible configuration */
if ( ep - > codec ! = codec - > codec_id ) {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: different codec, try next " , sw ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
if ( ! ( sw - > profile & spa_bt_profile_from_uuid ( ep - > uuid ) ) ) {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: wrong uuid (%s) for profile, try next " ,
2021-01-25 19:57:45 +02:00
sw , ep - > uuid ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
2022-06-16 17:15:14 +02:00
if ( ( sw - > profile & SPA_BT_PROFILE_A2DP_SINK ) | | ( sw - > profile & SPA_BT_PROFILE_BAP_SINK ) ) {
direction = SPA_BT_MEDIA_SOURCE ;
2022-05-21 13:18:38 +03:00
sink = false ;
2022-06-16 17:15:14 +02:00
} else if ( ( sw - > profile & SPA_BT_PROFILE_A2DP_SOURCE ) | | ( sw - > profile & SPA_BT_PROFILE_BAP_SOURCE ) ) {
direction = SPA_BT_MEDIA_SINK ;
2022-05-21 13:18:38 +03:00
sink = true ;
2021-01-25 19:57:45 +02:00
} else {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: bad profile (%d), try next " ,
2021-01-25 19:57:45 +02:00
sw , sw - > profile ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
2022-06-16 17:15:14 +02:00
if ( media_codec_to_endpoint ( codec , direction , & local_endpoint ) < 0 ) {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: no endpoint for codec %s, try next " ,
2021-01-25 19:57:45 +02:00
sw , codec - > name ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
2022-05-26 12:44:07 +03:00
/* Each endpoint can be used by only one device at a time (on each adapter) */
spa_list_for_each ( t , & sw - > device - > monitor - > transport_list , link ) {
if ( t - > device = = sw - > device )
continue ;
if ( t - > device - > adapter ! = sw - > device - > adapter )
continue ;
if ( spa_streq ( t - > endpoint_path , local_endpoint ) ) {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: endpoint %s in use, try next " ,
2022-05-26 12:44:07 +03:00
sw , local_endpoint ) ;
2023-07-11 20:16:50 +02:00
return false ;
2022-05-26 12:44:07 +03:00
}
}
2022-06-15 17:24:41 +02:00
res = codec - > select_config ( codec , sink ? MEDIA_CODEC_FLAG_SINK : 0 , ep - > capabilities , ep - > capabilities_len ,
2021-05-03 15:40:13 +08:00
& sw - > device - > monitor - > default_audio_info ,
2022-06-04 19:01:51 +03:00
& sw - > device - > monitor - > global_settings , config ) ;
2021-01-25 19:57:45 +02:00
if ( res < 0 ) {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: incompatible capabilities (%d), try next " ,
2021-01-25 19:57:45 +02:00
sw , res ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
config_size = res ;
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: configuration %d " , sw , config_size ) ;
2021-01-25 19:57:45 +02:00
for ( i = 0 ; i < config_size ; i + + )
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: %d: %02x " , sw , i , config [ i ] ) ;
2021-01-25 19:57:45 +02:00
2022-10-23 14:05:05 +03:00
/* Codecs may share the same endpoint, so indicate which one we are using */
sw - > device - > preferred_codec = codec ;
2021-01-25 19:57:45 +02:00
/* org.bluez.MediaEndpoint1.SetConfiguration on remote endpoint */
m = dbus_message_new_method_call ( BLUEZ_SERVICE , ep - > path , BLUEZ_MEDIA_ENDPOINT_INTERFACE , " SetConfiguration " ) ;
if ( m = = NULL ) {
2022-06-15 17:24:41 +02:00
spa_log_debug ( sw - > device - > monitor - > log , " media codec switch %p: dbus allocation failure, try next " , sw ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
bluez5: backend-ofono: don't do codec probe connections + add wait
Codec probe connections can trigger bad behavior from oFono if done when
device is busy (e.g. at connect), and they might be done at the same
time as A2DP transport is acquired which cannot work.
Also, oFono will not reply to DBus Acquire, if device does not complete
codec negotiation correctly. This is most likely to happen just after
device connect, when it is busy with other stuff (eg A2DP).
Remove codec probe connections altogether: instead, we guess mSBC if
mSBC is enabled and otherwise CVSD. If the guess turns out to be wrong,
which is unlikely (almost all devices have mSBC), we recreate the
transport with correct codec (from main loop, must not be done in
*_acquire because that can destroy nodes + unload the spa libs while
we're being called from there).
To avoid oFono DBus hangs at startup, add delay before marking the
profile connected, enforcing a time difference to A2DP operations.
2022-03-03 23:32:33 +02:00
spa_bt_device_update_last_bluez_action_time ( sw - > device ) ;
2021-02-10 01:14:58 +02:00
2022-06-15 17:24:41 +02:00
spa_log_info ( sw - > device - > monitor - > log , " media codec switch %p: trying codec %s for endpoint %s, local endpoint %s " ,
2021-01-25 19:57:45 +02:00
sw , codec - > name , ep - > path , local_endpoint ) ;
dbus_message_iter_init_append ( m , & iter ) ;
dbus_message_iter_append_basic ( & iter , DBUS_TYPE_OBJECT_PATH , & local_endpoint ) ;
dbus_message_iter_open_container ( & iter , DBUS_TYPE_ARRAY , " {sv} " , & d ) ;
2022-07-20 17:45:15 +02:00
append_basic_array_variant_dict_entry ( & d , " Capabilities " , " ay " , " y " , DBUS_TYPE_BYTE , config , config_size ) ;
2021-01-25 19:57:45 +02:00
dbus_message_iter_close_container ( & iter , & d ) ;
spa_assert ( sw - > pending = = NULL ) ;
2023-07-11 19:57:23 +02:00
sw - > pending = send_with_reply ( sw - > device - > monitor - > conn , m , media_codec_switch_reply , sw ) ;
if ( ! sw - > pending ) {
2022-06-15 17:24:41 +02:00
spa_log_error ( sw - > device - > monitor - > log , " media codec switch %p: dbus call failure, try next " , sw ) ;
2023-07-11 20:16:50 +02:00
return false ;
2021-01-25 19:57:45 +02:00
}
return true ;
}
2022-06-15 17:24:41 +02:00
static void media_codec_switch_process ( struct spa_bt_media_codec_switch * sw )
2021-01-25 19:57:45 +02:00
{
while ( * sw - > codec_iter ! = NULL & & * sw - > path_iter ! = NULL ) {
2021-02-10 01:14:58 +02:00
uint64_t now , threshold ;
/* Rate limit BlueZ calls */
2023-03-18 14:48:16 +02:00
now = get_time_now ( sw - > device - > monitor ) ;
2021-02-10 01:14:58 +02:00
threshold = sw - > device - > last_bluez_action_time + BLUEZ_ACTION_RATE_MSEC * SPA_NSEC_PER_MSEC ;
if ( now < threshold ) {
/* Wait for timeout */
2022-06-15 17:24:41 +02:00
media_codec_switch_start_timer ( sw , threshold - now ) ;
2021-02-10 01:14:58 +02:00
return ;
}
2021-01-25 19:57:45 +02:00
if ( sw - > path_iter = = sw - > paths & & ( * sw - > codec_iter ) - > caps_preference_cmp ) {
/* Sort endpoints according to codec preference, when at a new codec. */
2022-06-15 17:24:41 +02:00
media_codec_switch_cmp_sw = sw ;
qsort ( sw - > paths , sw - > num_paths , sizeof ( char * ) , media_codec_switch_cmp ) ;
2021-01-25 19:57:45 +02:00
}
2022-06-15 17:24:41 +02:00
if ( media_codec_switch_process_current ( sw ) ) {
2021-01-25 19:57:45 +02:00
/* Wait for dbus reply */
return ;
}
2022-06-15 17:24:41 +02:00
media_codec_switch_next ( sw ) ;
2021-01-25 19:57:45 +02:00
} ;
/* Didn't find any suitable endpoint. Report failure. */
2022-06-15 17:24:41 +02:00
spa_log_info ( sw - > device - > monitor - > log , " media codec switch %p: failed to get an endpoint " , sw ) ;
2021-01-25 19:57:45 +02:00
spa_bt_device_emit_codec_switched ( sw - > device , - ENODEV ) ;
2021-01-29 17:35:39 +02:00
spa_bt_device_check_profiles ( sw - > device , false ) ;
2022-06-15 17:24:41 +02:00
media_codec_switch_free ( sw ) ;
2021-01-25 19:57:45 +02:00
}
2022-06-15 17:24:41 +02:00
static bool media_codec_switch_goto_active ( struct spa_bt_media_codec_switch * sw )
2021-01-25 19:57:45 +02:00
{
struct spa_bt_device * device = sw - > device ;
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * active_sw ;
2021-01-25 19:57:45 +02:00
2022-06-15 17:24:41 +02:00
active_sw = spa_list_first ( & device - > codec_switch_list , struct spa_bt_media_codec_switch , device_link ) ;
2021-02-24 00:20:32 +02:00
if ( active_sw ! = sw ) {
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * t ;
2021-02-24 00:20:32 +02:00
/* This codec switch has been canceled. Switch to the newest one. */
2021-02-10 01:14:58 +02:00
spa_log_debug ( sw - > device - > monitor - > log ,
2022-06-15 17:24:41 +02:00
" media codec switch %p: canceled, go to new switch " , sw ) ;
2021-01-25 19:57:45 +02:00
2021-02-24 00:20:32 +02:00
spa_list_for_each_safe ( sw , t , & device - > codec_switch_list , device_link ) {
if ( sw ! = active_sw )
2022-06-15 17:24:41 +02:00
media_codec_switch_free ( sw ) ;
2021-02-24 00:20:32 +02:00
}
2022-06-15 17:24:41 +02:00
media_codec_switch_process ( active_sw ) ;
2021-02-10 01:14:58 +02:00
return false ;
}
return true ;
}
2022-06-15 17:24:41 +02:00
static void media_codec_switch_timer_event ( struct spa_source * source )
2021-02-10 01:14:58 +02:00
{
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * sw = source - > data ;
2021-02-10 01:14:58 +02:00
struct spa_bt_device * device = sw - > device ;
struct spa_bt_monitor * monitor = device - > monitor ;
uint64_t exp ;
if ( spa_system_timerfd_read ( monitor - > main_system , source - > fd , & exp ) < 0 )
2022-10-08 01:40:03 +02:00
spa_log_warn ( monitor - > log , " error reading timerfd: %s " , strerror ( errno ) ) ;
2021-02-10 01:14:58 +02:00
2022-06-15 17:24:41 +02:00
spa_log_debug ( monitor - > log , " media codec switch %p: rate limit timer event " , sw ) ;
2021-02-10 01:14:58 +02:00
2022-06-15 17:24:41 +02:00
media_codec_switch_stop_timer ( sw ) ;
2021-02-10 01:14:58 +02:00
2022-06-15 17:24:41 +02:00
if ( ! media_codec_switch_goto_active ( sw ) )
2021-02-10 01:14:58 +02:00
return ;
2022-06-15 17:24:41 +02:00
media_codec_switch_process ( sw ) ;
2021-02-10 01:14:58 +02:00
}
2022-06-15 17:24:41 +02:00
static void media_codec_switch_reply ( DBusPendingCall * pending , void * user_data )
2021-02-10 01:14:58 +02:00
{
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * sw = user_data ;
2021-02-10 01:14:58 +02:00
struct spa_bt_device * device = sw - > device ;
spa_assert ( sw - > pending = = pending ) ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = steal_reply_and_unref ( & sw - > pending ) ;
2021-02-10 01:14:58 +02:00
bluez5: backend-ofono: don't do codec probe connections + add wait
Codec probe connections can trigger bad behavior from oFono if done when
device is busy (e.g. at connect), and they might be done at the same
time as A2DP transport is acquired which cannot work.
Also, oFono will not reply to DBus Acquire, if device does not complete
codec negotiation correctly. This is most likely to happen just after
device connect, when it is busy with other stuff (eg A2DP).
Remove codec probe connections altogether: instead, we guess mSBC if
mSBC is enabled and otherwise CVSD. If the guess turns out to be wrong,
which is unlikely (almost all devices have mSBC), we recreate the
transport with correct codec (from main loop, must not be done in
*_acquire because that can destroy nodes + unload the spa libs while
we're being called from there).
To avoid oFono DBus hangs at startup, add delay before marking the
profile connected, enforcing a time difference to A2DP operations.
2022-03-03 23:32:33 +02:00
spa_bt_device_update_last_bluez_action_time ( device ) ;
2022-01-04 22:08:25 +02:00
2023-07-11 19:24:46 +02:00
if ( ! media_codec_switch_goto_active ( sw ) )
2021-01-25 19:57:45 +02:00
return ;
if ( r = = NULL ) {
spa_log_error ( sw - > device - > monitor - > log ,
2022-06-15 17:24:41 +02:00
" media codec switch %p: empty reply from dbus, trying next " ,
2021-01-25 19:57:45 +02:00
sw ) ;
2021-01-29 17:35:39 +02:00
goto next ;
2021-01-25 19:57:45 +02:00
}
if ( dbus_message_get_type ( r ) = = DBUS_MESSAGE_TYPE_ERROR ) {
spa_log_debug ( sw - > device - > monitor - > log ,
2022-06-15 17:24:41 +02:00
" media codec switch %p: failed (%s), trying next " ,
2021-01-25 19:57:45 +02:00
sw , dbus_message_get_error_name ( r ) ) ;
goto next ;
}
/* Success */
2022-06-15 17:24:41 +02:00
spa_log_info ( sw - > device - > monitor - > log , " media codec switch %p: success " , sw ) ;
2021-01-25 19:57:45 +02:00
spa_bt_device_emit_codec_switched ( sw - > device , 0 ) ;
2021-01-29 17:35:39 +02:00
spa_bt_device_check_profiles ( sw - > device , false ) ;
2022-06-15 17:24:41 +02:00
media_codec_switch_free ( sw ) ;
2021-01-25 19:57:45 +02:00
return ;
next :
2022-01-04 22:08:25 +02:00
if ( sw - > retries > 0 )
- - sw - > retries ;
else
2022-06-15 17:24:41 +02:00
media_codec_switch_next ( sw ) ;
2022-01-04 22:08:25 +02:00
2022-06-15 17:24:41 +02:00
media_codec_switch_process ( sw ) ;
2021-01-25 19:57:45 +02:00
return ;
}
2022-06-15 17:24:41 +02:00
static int media_codec_switch_start_timer ( struct spa_bt_media_codec_switch * sw , uint64_t timeout )
2021-02-10 01:14:58 +02:00
{
struct spa_bt_monitor * monitor = sw - > device - > monitor ;
struct itimerspec ts ;
spa_assert ( sw - > timer . data = = NULL ) ;
2022-06-15 17:24:41 +02:00
spa_log_debug ( monitor - > log , " media codec switch %p: starting rate limit timer " , sw ) ;
2021-02-10 01:14:58 +02:00
if ( sw - > timer . data = = NULL ) {
sw - > timer . data = sw ;
2022-06-15 17:24:41 +02:00
sw - > timer . func = media_codec_switch_timer_event ;
2021-02-10 01:14:58 +02:00
sw - > timer . fd = spa_system_timerfd_create ( monitor - > main_system ,
CLOCK_MONOTONIC , SPA_FD_CLOEXEC | SPA_FD_NONBLOCK ) ;
sw - > timer . mask = SPA_IO_IN ;
sw - > timer . rmask = 0 ;
spa_loop_add_source ( monitor - > main_loop , & sw - > timer ) ;
}
ts . it_value . tv_sec = timeout / SPA_NSEC_PER_SEC ;
ts . it_value . tv_nsec = timeout % SPA_NSEC_PER_SEC ;
ts . it_interval . tv_sec = 0 ;
ts . it_interval . tv_nsec = 0 ;
spa_system_timerfd_settime ( monitor - > main_system , sw - > timer . fd , 0 , & ts , NULL ) ;
return 0 ;
}
2022-06-15 17:24:41 +02:00
static int media_codec_switch_stop_timer ( struct spa_bt_media_codec_switch * sw )
2021-02-10 01:14:58 +02:00
{
struct spa_bt_monitor * monitor = sw - > device - > monitor ;
struct itimerspec ts ;
if ( sw - > timer . data = = NULL )
return 0 ;
2022-06-15 17:24:41 +02:00
spa_log_debug ( monitor - > log , " media codec switch %p: stopping rate limit timer " , sw ) ;
2021-02-10 01:14:58 +02:00
spa_loop_remove_source ( monitor - > main_loop , & sw - > timer ) ;
ts . it_value . tv_sec = 0 ;
ts . it_value . tv_nsec = 0 ;
ts . it_interval . tv_sec = 0 ;
ts . it_interval . tv_nsec = 0 ;
spa_system_timerfd_settime ( monitor - > main_system , sw - > timer . fd , 0 , & ts , NULL ) ;
spa_system_close ( monitor - > main_system , sw - > timer . fd ) ;
sw - > timer . data = NULL ;
return 0 ;
}
2022-06-15 17:24:41 +02:00
static int media_codec_switch_cmp ( const void * a , const void * b )
2021-01-25 19:57:45 +02:00
{
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * sw = media_codec_switch_cmp_sw ;
const struct media_codec * codec = * sw - > codec_iter ;
2021-02-03 22:47:29 +02:00
const char * path1 = * ( char * * ) a , * path2 = * ( char * * ) b ;
2021-01-25 19:57:45 +02:00
struct spa_bt_remote_endpoint * ep1 , * ep2 ;
2022-05-21 13:18:38 +03:00
uint32_t flags ;
2021-01-25 19:57:45 +02:00
ep1 = device_remote_endpoint_find ( sw - > device , path1 ) ;
ep2 = device_remote_endpoint_find ( sw - > device , path2 ) ;
if ( ep1 ! = NULL & & ( ep1 - > uuid = = NULL | | ep1 - > codec ! = codec - > codec_id | | ep1 - > capabilities = = NULL ) )
ep1 = NULL ;
if ( ep2 ! = NULL & & ( ep2 - > uuid = = NULL | | ep2 - > codec ! = codec - > codec_id | | ep2 - > capabilities = = NULL ) )
ep2 = NULL ;
2022-05-21 13:18:38 +03:00
if ( ep1 & & ep2 & & ! spa_streq ( ep1 - > uuid , ep2 - > uuid ) ) {
ep1 = NULL ;
ep2 = NULL ;
}
2021-01-25 19:57:45 +02:00
if ( ep1 = = NULL & & ep2 = = NULL )
return 0 ;
else if ( ep1 = = NULL )
return 1 ;
else if ( ep2 = = NULL )
return - 1 ;
2022-06-16 17:15:14 +02:00
if ( codec - > bap )
flags = spa_streq ( ep1 - > uuid , SPA_BT_UUID_BAP_SOURCE ) ? MEDIA_CODEC_FLAG_SINK : 0 ;
else
flags = spa_streq ( ep1 - > uuid , SPA_BT_UUID_A2DP_SOURCE ) ? MEDIA_CODEC_FLAG_SINK : 0 ;
2022-05-21 13:18:38 +03:00
return codec - > caps_preference_cmp ( codec , flags , ep1 - > capabilities , ep1 - > capabilities_len ,
2022-06-04 19:01:51 +03:00
ep2 - > capabilities , ep2 - > capabilities_len , & sw - > device - > monitor - > default_audio_info ,
& sw - > device - > monitor - > global_settings ) ;
2021-01-25 19:57:45 +02:00
}
/* Ensure there's a transport for at least one of the listed codecs */
2022-06-15 17:24:41 +02:00
int spa_bt_device_ensure_media_codec ( struct spa_bt_device * device , const struct media_codec * const * codecs )
2021-01-25 19:57:45 +02:00
{
2022-06-15 17:24:41 +02:00
struct spa_bt_media_codec_switch * sw ;
2021-01-25 19:57:45 +02:00
struct spa_bt_remote_endpoint * ep ;
struct spa_bt_transport * t ;
2022-06-15 17:24:41 +02:00
const struct media_codec * preferred_codec = NULL ;
2021-01-25 22:06:34 +02:00
size_t i , j , num_codecs , num_eps ;
2021-01-25 19:57:45 +02:00
2023-01-21 20:57:27 +02:00
if ( ! device - > adapter - > a2dp_application_registered & &
! device - > adapter - > bap_application_registered ) {
2021-01-25 19:57:45 +02:00
/* Codec switching not supported */
return - ENOTSUP ;
}
2021-01-25 22:06:34 +02:00
for ( i = 0 ; codecs [ i ] ! = NULL ; + + i ) {
2023-09-02 15:06:27 +03:00
if ( spa_bt_device_supports_media_codec ( device , codecs [ i ] , device - > connected_profiles ) ) {
2021-01-25 22:06:34 +02:00
preferred_codec = codecs [ i ] ;
break ;
}
}
2021-01-25 19:57:45 +02:00
/* Check if we already have an enabled transport for the most preferred codec.
* However , if there already was a codec switch running , these transports may
2021-04-28 20:29:44 +02:00
* disappear soon . In that case , we have to do the full thing .
2021-01-25 19:57:45 +02:00
*/
2021-02-24 00:20:32 +02:00
if ( spa_list_is_empty ( & device - > codec_switch_list ) & & preferred_codec ! = NULL ) {
2021-01-25 19:57:45 +02:00
spa_list_for_each ( t , & device - > transport_list , device_link ) {
2022-06-15 17:24:41 +02:00
if ( t - > media_codec ! = preferred_codec )
2021-01-25 19:57:45 +02:00
continue ;
if ( ( device - > connected_profiles & t - > profile ) ! = t - > profile )
continue ;
spa_bt_device_emit_codec_switched ( device , 0 ) ;
return 0 ;
}
}
/* Setup and start iteration */
2022-06-15 17:24:41 +02:00
sw = calloc ( 1 , sizeof ( struct spa_bt_media_codec_switch ) ) ;
2021-01-25 19:57:45 +02:00
if ( sw = = NULL )
return - ENOMEM ;
num_eps = 0 ;
spa_list_for_each ( ep , & device - > remote_endpoint_list , device_link )
+ + num_eps ;
num_codecs = 0 ;
while ( codecs [ num_codecs ] ! = NULL )
+ + num_codecs ;
2022-06-15 17:24:41 +02:00
sw - > codecs = calloc ( num_codecs + 1 , sizeof ( const struct media_codec * ) ) ;
2021-01-25 19:57:45 +02:00
sw - > paths = calloc ( num_eps + 1 , sizeof ( char * ) ) ;
sw - > num_paths = num_eps ;
if ( sw - > codecs = = NULL | | sw - > paths = = NULL ) {
2022-06-15 17:24:41 +02:00
media_codec_switch_free ( sw ) ;
2021-01-25 19:57:45 +02:00
return - ENOMEM ;
}
2021-01-25 22:06:34 +02:00
for ( i = 0 , j = 0 ; i < num_codecs ; + + i ) {
2022-06-15 17:24:41 +02:00
if ( is_media_codec_enabled ( device - > monitor , codecs [ i ] ) ) {
2021-01-25 22:06:34 +02:00
sw - > codecs [ j ] = codecs [ i ] ;
+ + j ;
}
}
sw - > codecs [ j ] = NULL ;
2021-01-25 19:57:45 +02:00
i = 0 ;
spa_list_for_each ( ep , & device - > remote_endpoint_list , device_link ) {
sw - > paths [ i ] = strdup ( ep - > path ) ;
if ( sw - > paths [ i ] = = NULL ) {
2022-06-15 17:24:41 +02:00
media_codec_switch_free ( sw ) ;
2021-01-25 19:57:45 +02:00
return - ENOMEM ;
}
+ + i ;
}
2021-01-25 22:06:34 +02:00
sw - > paths [ i ] = NULL ;
2021-01-25 19:57:45 +02:00
sw - > codec_iter = sw - > codecs ;
sw - > path_iter = sw - > paths ;
2022-01-04 22:08:25 +02:00
sw - > retries = CODEC_SWITCH_RETRIES ;
2021-01-25 19:57:45 +02:00
sw - > profile = device - > connected_profiles ;
sw - > device = device ;
2021-02-24 00:20:32 +02:00
if ( ! spa_list_is_empty ( & device - > codec_switch_list ) ) {
2021-01-25 19:57:45 +02:00
/*
2021-02-10 01:14:58 +02:00
* There ' s a codec switch already running , either waiting for timeout or
* BlueZ reply .
*
* BlueZ does not appear to allow calling dbus_pending_call_cancel on an
* active request , so we have to wait for the reply to arrive first , and
* only then start processing this request . The timeout we would also have
* to wait to pass in any case , so we don ' t cancel it either .
2021-01-25 19:57:45 +02:00
*/
spa_log_debug ( sw - > device - > monitor - > log ,
2022-06-15 17:24:41 +02:00
" media codec switch %p: already in progress, canceling previous " ,
2021-02-10 01:14:58 +02:00
sw ) ;
2021-02-24 00:20:32 +02:00
spa_list_prepend ( & device - > codec_switch_list , & sw - > device_link ) ;
2021-01-25 19:57:45 +02:00
} else {
2021-02-24 00:20:32 +02:00
spa_list_prepend ( & device - > codec_switch_list , & sw - > device_link ) ;
2022-06-15 17:24:41 +02:00
media_codec_switch_process ( sw ) ;
2021-01-25 19:57:45 +02:00
}
return 0 ;
}
2021-03-18 23:15:03 +02:00
int spa_bt_device_ensure_hfp_codec ( struct spa_bt_device * device , unsigned int codec )
{
struct spa_bt_monitor * monitor = device - > monitor ;
2021-08-29 18:22:41 +03:00
return spa_bt_backend_ensure_codec ( monitor - > backend , device , codec ) ;
2021-03-18 23:15:03 +02:00
}
int spa_bt_device_supports_hfp_codec ( struct spa_bt_device * device , unsigned int codec )
{
struct spa_bt_monitor * monitor = device - > monitor ;
2021-08-29 18:22:41 +03:00
return spa_bt_backend_supports_codec ( monitor - > backend , device , codec ) ;
2021-03-18 23:15:03 +02:00
}
2018-01-11 10:23:37 +01:00
static DBusHandlerResult endpoint_set_configuration ( DBusConnection * conn ,
const char * path , DBusMessage * m , void * userdata )
{
struct spa_bt_monitor * monitor = userdata ;
2020-10-19 13:27:11 +02:00
const char * transport_path , * endpoint ;
2018-01-11 10:23:37 +01:00
DBusMessageIter it [ 2 ] ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = NULL ;
2018-01-11 10:23:37 +01:00
struct spa_bt_transport * transport ;
2022-06-15 17:24:41 +02:00
const struct media_codec * codec ;
2021-04-15 11:39:49 +08:00
int profile ;
2022-05-21 13:18:38 +03:00
bool sink ;
2018-01-11 10:23:37 +01:00
if ( ! dbus_message_has_signature ( m , " oa{sv} " ) ) {
spa_log_warn ( monitor - > log , " invalid SetConfiguration() signature " ) ;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
2020-10-19 13:27:11 +02:00
endpoint = dbus_message_get_path ( m ) ;
2022-06-15 17:24:41 +02:00
profile = media_endpoint_to_profile ( endpoint ) ;
2022-10-23 14:05:05 +03:00
codec = media_endpoint_to_codec ( monitor , endpoint , & sink , NULL ) ;
2020-10-19 13:27:11 +02:00
if ( codec = = NULL ) {
spa_log_warn ( monitor - > log , " unknown SetConfiguration() codec " ) ;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
2018-01-11 10:23:37 +01:00
dbus_message_iter_init ( m , & it [ 0 ] ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & transport_path ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
2020-07-21 11:00:57 +02:00
transport = spa_bt_transport_find ( monitor , transport_path ) ;
2018-01-11 10:23:37 +01:00
2022-01-19 19:41:08 +02:00
if ( transport = = NULL ) {
2021-07-06 17:55:16 +02:00
char * tpath = strdup ( transport_path ) ;
2021-05-12 15:33:33 +10:00
2021-07-06 17:55:16 +02:00
transport = spa_bt_transport_create ( monitor , tpath , 0 ) ;
2021-05-12 15:33:33 +10:00
if ( transport = = NULL ) {
2021-07-06 17:55:16 +02:00
free ( tpath ) ;
2018-01-11 10:23:37 +01:00
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
2021-05-12 15:33:33 +10:00
}
2018-01-11 10:23:37 +01:00
2019-05-14 18:10:18 +02:00
spa_bt_transport_set_implementation ( transport , & transport_impl , transport ) ;
2021-04-15 11:39:49 +08:00
if ( profile & SPA_BT_PROFILE_A2DP_SOURCE ) {
transport - > volumes [ SPA_BT_VOLUME_ID_RX ] . volume = DEFAULT_AG_VOLUME ;
transport - > volumes [ SPA_BT_VOLUME_ID_TX ] . volume = DEFAULT_AG_VOLUME ;
} else {
transport - > volumes [ SPA_BT_VOLUME_ID_RX ] . volume = DEFAULT_RX_VOLUME ;
transport - > volumes [ SPA_BT_VOLUME_ID_TX ] . volume = DEFAULT_TX_VOLUME ;
}
}
for ( int i = 0 ; i < SPA_BT_VOLUME_ID_TERM ; + + i ) {
transport - > volumes [ i ] . hw_volume = SPA_BT_VOLUME_INVALID ;
transport - > volumes [ i ] . hw_volume_max = SPA_BT_VOLUME_A2DP_MAX ;
}
2022-05-26 12:44:07 +03:00
free ( transport - > endpoint_path ) ;
transport - > endpoint_path = strdup ( endpoint ) ;
2021-06-20 23:00:37 +08:00
transport - > profile = profile ;
2022-06-15 17:24:41 +02:00
transport - > media_codec = codec ;
2020-12-06 09:32:12 +01:00
transport_update_props ( transport , & it [ 1 ] , NULL ) ;
2018-01-11 10:23:37 +01:00
2022-01-19 19:05:37 +02:00
if ( transport - > device = = NULL | | transport - > device - > adapter = = NULL ) {
2021-01-08 22:26:46 +02:00
spa_log_warn ( monitor - > log , " no device found for transport " ) ;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
2022-10-23 14:05:05 +03:00
/* If multiple codecs share the endpoint, pick the one we wanted */
transport - > media_codec = codec = media_endpoint_to_codec ( monitor , endpoint , & sink ,
transport - > device - > preferred_codec ) ;
spa_assert ( codec ! = NULL ) ;
spa_log_debug ( monitor - > log , " %p: %s codec:%s " , monitor , path , codec ? codec - > name : " <null> " ) ;
bluez5: backend-ofono: don't do codec probe connections + add wait
Codec probe connections can trigger bad behavior from oFono if done when
device is busy (e.g. at connect), and they might be done at the same
time as A2DP transport is acquired which cannot work.
Also, oFono will not reply to DBus Acquire, if device does not complete
codec negotiation correctly. This is most likely to happen just after
device connect, when it is busy with other stuff (eg A2DP).
Remove codec probe connections altogether: instead, we guess mSBC if
mSBC is enabled and otherwise CVSD. If the guess turns out to be wrong,
which is unlikely (almost all devices have mSBC), we recreate the
transport with correct codec (from main loop, must not be done in
*_acquire because that can destroy nodes + unload the spa libs while
we're being called from there).
To avoid oFono DBus hangs at startup, add delay before marking the
profile connected, enforcing a time difference to A2DP operations.
2022-03-03 23:32:33 +02:00
spa_bt_device_update_last_bluez_action_time ( transport - > device ) ;
2021-02-10 01:14:58 +02:00
2021-04-21 09:18:13 +08:00
if ( profile & SPA_BT_PROFILE_A2DP_SOURCE ) {
/* PW is the rendering device so it's responsible for reporting hardware volume. */
transport - > volumes [ SPA_BT_VOLUME_ID_RX ] . active = true ;
} else if ( profile & SPA_BT_PROFILE_A2DP_SINK ) {
transport - > volumes [ SPA_BT_VOLUME_ID_TX ] . active
| = transport - > device - > a2dp_volume_active [ SPA_BT_VOLUME_ID_TX ] ;
}
2021-01-07 18:10:22 +01:00
if ( codec - > validate_config ) {
struct spa_audio_info info ;
2022-06-15 17:24:41 +02:00
if ( codec - > validate_config ( codec , sink ? MEDIA_CODEC_FLAG_SINK : 0 ,
2021-01-07 18:10:22 +01:00
transport - > configuration , transport - > configuration_len ,
& info ) < 0 ) {
spa_log_error ( monitor - > log , " invalid transport configuration " ) ;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
transport - > n_channels = info . info . raw . channels ;
memcpy ( transport - > channels , info . info . raw . position ,
transport - > n_channels * sizeof ( uint32_t ) ) ;
} else {
transport - > n_channels = 2 ;
transport - > channels [ 0 ] = SPA_AUDIO_CHANNEL_FL ;
transport - > channels [ 1 ] = SPA_AUDIO_CHANNEL_FR ;
}
spa_log_info ( monitor - > log , " %p: %s validate conf channels:%d " ,
monitor , path , transport - > n_channels ) ;
2022-01-19 19:41:08 +02:00
spa_bt_device_add_profile ( transport - > device , transport - > profile ) ;
2020-07-17 15:18:10 +02:00
spa_bt_device_connect_profile ( transport - > device , transport - > profile ) ;
2018-01-11 10:23:37 +01:00
2021-04-15 11:39:49 +08:00
/* Sync initial volumes */
transport_sync_volume ( transport ) ;
2018-01-11 10:23:37 +01:00
if ( ( r = dbus_message_new_method_return ( m ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
if ( ! dbus_connection_send ( conn , r , NULL ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
return DBUS_HANDLER_RESULT_HANDLED ;
}
static DBusHandlerResult endpoint_clear_configuration ( DBusConnection * conn , DBusMessage * m , void * userdata )
{
2018-04-24 17:02:19 +02:00
struct spa_bt_monitor * monitor = userdata ;
2023-07-11 20:44:23 +02:00
spa_auto ( DBusError ) err = DBUS_ERROR_INIT ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = NULL ;
2018-04-24 17:02:19 +02:00
const char * transport_path ;
struct spa_bt_transport * transport ;
if ( ! dbus_message_get_args ( m , & err ,
DBUS_TYPE_OBJECT_PATH , & transport_path ,
DBUS_TYPE_INVALID ) ) {
spa_log_warn ( monitor - > log , " Bad ClearConfiguration method call: %s " ,
err . message ) ;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
2020-07-21 11:00:57 +02:00
transport = spa_bt_transport_find ( monitor , transport_path ) ;
2018-11-27 17:08:36 +01:00
if ( transport ! = NULL ) {
struct spa_bt_device * device = transport - > device ;
2019-05-14 18:10:18 +02:00
spa_log_debug ( monitor - > log , " transport %p: free %s " ,
transport , transport - > path ) ;
2020-07-17 15:18:10 +02:00
spa_bt_transport_free ( transport ) ;
2018-11-27 17:08:36 +01:00
if ( device ! = NULL )
2020-07-17 15:18:10 +02:00
spa_bt_device_check_profiles ( device , false ) ;
2018-11-27 17:08:36 +01:00
}
2018-01-11 10:23:37 +01:00
if ( ( r = dbus_message_new_method_return ( m ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
if ( ! dbus_connection_send ( conn , r , NULL ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
return DBUS_HANDLER_RESULT_HANDLED ;
}
static DBusHandlerResult endpoint_release ( DBusConnection * conn , DBusMessage * m , void * userdata )
{
2023-07-11 19:24:46 +02:00
if ( ! reply_with_error ( conn , m , BLUEZ_MEDIA_ENDPOINT_INTERFACE " .Error.NotImplemented " , " Method not implemented " ) )
2018-01-11 10:23:37 +01:00
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
return DBUS_HANDLER_RESULT_HANDLED ;
}
static DBusHandlerResult endpoint_handler ( DBusConnection * c , DBusMessage * m , void * userdata )
{
struct spa_bt_monitor * monitor = userdata ;
const char * path , * interface , * member ;
DBusHandlerResult res ;
path = dbus_message_get_path ( m ) ;
interface = dbus_message_get_interface ( m ) ;
member = dbus_message_get_member ( m ) ;
spa_log_debug ( monitor - > log , " dbus: path=%s, interface=%s, member=%s " , path , interface , member ) ;
if ( dbus_message_is_method_call ( m , " org.freedesktop.DBus.Introspectable " , " Introspect " ) ) {
const char * xml = ENDPOINT_INTROSPECT_XML ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = NULL ;
2018-01-11 10:23:37 +01:00
if ( ( r = dbus_message_new_method_return ( m ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
if ( ! dbus_message_append_args ( r , DBUS_TYPE_STRING , & xml , DBUS_TYPE_INVALID ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
if ( ! dbus_connection_send ( monitor - > conn , r , NULL ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
res = DBUS_HANDLER_RESULT_HANDLED ;
}
else if ( dbus_message_is_method_call ( m , BLUEZ_MEDIA_ENDPOINT_INTERFACE , " SetConfiguration " ) )
res = endpoint_set_configuration ( c , path , m , userdata ) ;
else if ( dbus_message_is_method_call ( m , BLUEZ_MEDIA_ENDPOINT_INTERFACE , " SelectConfiguration " ) )
res = endpoint_select_configuration ( c , m , userdata ) ;
2022-06-20 15:39:01 +02:00
else if ( dbus_message_is_method_call ( m , BLUEZ_MEDIA_ENDPOINT_INTERFACE , " SelectProperties " ) )
res = endpoint_select_properties ( c , m , userdata ) ;
2018-01-11 10:23:37 +01:00
else if ( dbus_message_is_method_call ( m , BLUEZ_MEDIA_ENDPOINT_INTERFACE , " ClearConfiguration " ) )
res = endpoint_clear_configuration ( c , m , userdata ) ;
else if ( dbus_message_is_method_call ( m , BLUEZ_MEDIA_ENDPOINT_INTERFACE , " Release " ) )
res = endpoint_release ( c , m , userdata ) ;
else
res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
return res ;
}
2023-01-21 20:57:27 +02:00
static void bluez_register_endpoint_legacy_reply ( DBusPendingCall * pending , void * user_data )
2018-01-11 10:23:37 +01:00
{
2023-02-05 17:33:45 +02:00
struct spa_bt_adapter * adapter = user_data ;
struct spa_bt_monitor * monitor = adapter - > monitor ;
2018-01-11 10:23:37 +01:00
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = steal_reply_and_unref ( & pending ) ;
2018-01-11 10:23:37 +01:00
if ( r = = NULL )
return ;
if ( dbus_message_is_error ( r , DBUS_ERROR_UNKNOWN_METHOD ) ) {
spa_log_warn ( monitor - > log , " BlueZ D-Bus ObjectManager not available " ) ;
2023-07-11 19:24:46 +02:00
return ;
2018-01-11 10:23:37 +01:00
}
if ( dbus_message_get_type ( r ) = = DBUS_MESSAGE_TYPE_ERROR ) {
spa_log_error ( monitor - > log , " RegisterEndpoint() failed: %s " ,
dbus_message_get_error_name ( r ) ) ;
2023-07-11 19:24:46 +02:00
return ;
2018-01-11 10:23:37 +01:00
}
2023-02-05 17:33:45 +02:00
adapter - > legacy_endpoints_registered = true ;
2018-01-11 10:23:37 +01:00
}
2022-07-20 17:45:15 +02:00
static void append_basic_variant_dict_entry ( DBusMessageIter * dict , const char * key , int variant_type_int , const char * variant_type_str , void * variant ) {
2020-11-01 21:56:44 +01:00
DBusMessageIter dict_entry_it , variant_it ;
dbus_message_iter_open_container ( dict , DBUS_TYPE_DICT_ENTRY , NULL , & dict_entry_it ) ;
2022-07-23 16:33:44 +08:00
dbus_message_iter_append_basic ( & dict_entry_it , DBUS_TYPE_STRING , & key ) ;
2020-11-01 21:56:44 +01:00
dbus_message_iter_open_container ( & dict_entry_it , DBUS_TYPE_VARIANT , variant_type_str , & variant_it ) ;
dbus_message_iter_append_basic ( & variant_it , variant_type_int , variant ) ;
dbus_message_iter_close_container ( & dict_entry_it , & variant_it ) ;
dbus_message_iter_close_container ( dict , & dict_entry_it ) ;
}
2022-07-20 17:45:15 +02:00
static void append_basic_array_variant_dict_entry ( DBusMessageIter * dict , const char * key , const char * variant_type_str , const char * array_type_str , int array_type_int , void * data , int data_size ) {
2020-11-01 21:56:44 +01:00
DBusMessageIter dict_entry_it , variant_it , array_it ;
dbus_message_iter_open_container ( dict , DBUS_TYPE_DICT_ENTRY , NULL , & dict_entry_it ) ;
2022-07-23 16:33:44 +08:00
dbus_message_iter_append_basic ( & dict_entry_it , DBUS_TYPE_STRING , & key ) ;
2020-11-01 21:56:44 +01:00
dbus_message_iter_open_container ( & dict_entry_it , DBUS_TYPE_VARIANT , variant_type_str , & variant_it ) ;
dbus_message_iter_open_container ( & variant_it , DBUS_TYPE_ARRAY , array_type_str , & array_it ) ;
dbus_message_iter_append_fixed_array ( & array_it , array_type_int , & data , data_size ) ;
dbus_message_iter_close_container ( & variant_it , & array_it ) ;
dbus_message_iter_close_container ( & dict_entry_it , & variant_it ) ;
dbus_message_iter_close_container ( dict , & dict_entry_it ) ;
}
2023-02-05 17:33:45 +02:00
static int bluez_register_endpoint_legacy ( struct spa_bt_adapter * adapter ,
enum spa_bt_media_direction direction ,
2022-10-08 01:40:03 +02:00
const char * uuid , const struct media_codec * codec )
{
2023-02-05 17:33:45 +02:00
struct spa_bt_monitor * monitor = adapter - > monitor ;
const char * path = adapter - > path ;
2023-07-11 20:16:50 +02:00
spa_autofree char * object_path = NULL ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2020-11-01 21:56:44 +01:00
DBusMessageIter object_it , dict_it ;
2020-10-19 13:27:11 +02:00
uint8_t caps [ A2DP_MAX_CAPS_SIZE ] ;
2020-12-29 10:21:03 +08:00
int ret , caps_size ;
2020-12-04 11:34:38 +01:00
uint16_t codec_id = codec - > codec_id ;
2022-06-16 17:15:14 +02:00
bool sink = ( direction = = SPA_BT_MEDIA_SINK ) ;
2018-01-11 10:23:37 +01:00
2022-10-23 14:05:05 +03:00
spa_assert ( codec - > fill_caps ) ;
2022-06-16 17:15:14 +02:00
ret = media_codec_to_endpoint ( codec , direction , & object_path ) ;
2020-12-29 10:21:03 +08:00
if ( ret < 0 )
2023-07-11 20:16:50 +02:00
return ret ;
2020-12-29 10:21:03 +08:00
2022-06-15 17:24:41 +02:00
ret = caps_size = codec - > fill_caps ( codec , sink ? MEDIA_CODEC_FLAG_SINK : 0 , caps ) ;
2022-07-06 13:38:06 +03:00
if ( ret < 0 )
2023-07-11 20:16:50 +02:00
return ret ;
2018-01-11 10:23:37 +01:00
m = dbus_message_new_method_call ( BLUEZ_SERVICE ,
2020-11-01 21:56:44 +01:00
path ,
BLUEZ_MEDIA_INTERFACE ,
" RegisterEndpoint " ) ;
2023-07-11 20:16:50 +02:00
if ( m = = NULL )
return - EIO ;
2018-01-11 10:23:37 +01:00
2020-11-01 21:56:44 +01:00
dbus_message_iter_init_append ( m , & object_it ) ;
dbus_message_iter_append_basic ( & object_it , DBUS_TYPE_OBJECT_PATH , & object_path ) ;
2018-01-11 10:23:37 +01:00
2020-11-01 21:56:44 +01:00
dbus_message_iter_open_container ( & object_it , DBUS_TYPE_ARRAY , " {sv} " , & dict_it ) ;
2018-01-11 10:23:37 +01:00
2022-07-20 17:45:15 +02:00
append_basic_variant_dict_entry ( & dict_it , " UUID " , DBUS_TYPE_STRING , " s " , & uuid ) ;
append_basic_variant_dict_entry ( & dict_it , " Codec " , DBUS_TYPE_BYTE , " y " , & codec_id ) ;
append_basic_array_variant_dict_entry ( & dict_it , " Capabilities " , " ay " , " y " , DBUS_TYPE_BYTE , caps , caps_size ) ;
2020-11-01 21:56:44 +01:00
dbus_message_iter_close_container ( & object_it , & dict_it ) ;
2018-01-11 10:23:37 +01:00
2023-07-11 20:16:50 +02:00
if ( ! send_with_reply ( monitor - > conn , m , bluez_register_endpoint_legacy_reply , adapter ) )
return - EIO ;
2020-12-29 10:21:03 +08:00
2020-11-01 21:56:44 +01:00
return 0 ;
}
2023-01-21 20:57:27 +02:00
static int adapter_register_endpoints_legacy ( struct spa_bt_adapter * a )
2018-01-11 10:23:37 +01:00
{
struct spa_bt_monitor * monitor = a - > monitor ;
2022-06-15 17:24:41 +02:00
const struct media_codec * const * const media_codecs = monitor - > media_codecs ;
2020-10-19 13:27:11 +02:00
int i ;
2020-11-01 21:56:44 +01:00
int err = 0 ;
2023-02-05 17:33:45 +02:00
bool registered = false ;
2020-11-01 21:56:44 +01:00
2023-01-21 20:57:27 +02:00
if ( a - > legacy_endpoints_registered )
2020-11-01 21:56:44 +01:00
return err ;
/* The legacy bluez5 api doesn't support codec switching
* It doesn ' t make sense to register codecs other than SBC
* as bluez5 will probably use SBC anyway and we have no control over it
* let ' s incentivize users to upgrade their bluez5 daemon
2022-06-15 17:24:41 +02:00
* if they want proper media codec support
2020-11-01 21:56:44 +01:00
* */
2022-10-08 01:40:03 +02:00
spa_log_warn ( monitor - > log ,
" Using legacy bluez5 API for A2DP - only SBC will be supported. "
2023-01-21 20:57:27 +02:00
" Please upgrade bluez5. " ) ;
2020-10-19 13:27:11 +02:00
2022-06-15 17:24:41 +02:00
for ( i = 0 ; media_codecs [ i ] ; i + + ) {
const struct media_codec * codec = media_codecs [ i ] ;
2020-11-01 21:56:44 +01:00
2022-10-23 14:05:05 +03:00
if ( codec - > id ! = SPA_BLUETOOTH_AUDIO_CODEC_SBC )
2021-01-30 18:15:58 +02:00
continue ;
2022-10-23 14:05:05 +03:00
if ( endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SOURCE ) ) {
2023-02-05 17:33:45 +02:00
if ( ( err = bluez_register_endpoint_legacy ( a , SPA_BT_MEDIA_SOURCE ,
2022-10-23 14:05:05 +03:00
SPA_BT_UUID_A2DP_SOURCE ,
codec ) ) )
goto out ;
}
2020-11-01 21:56:44 +01:00
2022-10-23 14:05:05 +03:00
if ( endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SINK ) ) {
2023-02-05 17:33:45 +02:00
if ( ( err = bluez_register_endpoint_legacy ( a , SPA_BT_MEDIA_SINK ,
2022-10-23 14:05:05 +03:00
SPA_BT_UUID_A2DP_SINK ,
codec ) ) )
goto out ;
}
2020-11-01 21:56:44 +01:00
2023-02-05 17:33:45 +02:00
registered = true ;
2020-11-01 21:56:44 +01:00
break ;
2020-10-19 13:27:11 +02:00
}
2020-11-01 21:56:44 +01:00
2023-02-05 17:33:45 +02:00
if ( ! registered ) {
2020-11-01 21:56:44 +01:00
/* Should never happen as SBC support is always enabled */
2021-07-14 15:41:58 +10:00
spa_log_error ( monitor - > log , " Broken PipeWire build - unable to locate SBC codec " ) ;
2020-11-01 21:56:44 +01:00
err = - ENOSYS ;
}
2022-10-08 01:43:27 +02:00
out :
2020-11-01 21:56:44 +01:00
if ( err ) {
spa_log_error ( monitor - > log , " Failed to register bluez5 endpoints " ) ;
}
return err ;
}
2022-06-15 17:24:41 +02:00
static void append_media_object ( DBusMessageIter * iter , const char * endpoint ,
2020-11-05 09:46:11 +01:00
const char * uuid , uint8_t codec_id , uint8_t * caps , size_t caps_size )
{
2020-11-01 21:56:44 +01:00
const char * interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE ;
DBusMessageIter object , array , entry , dict ;
dbus_message_iter_open_container ( iter , DBUS_TYPE_DICT_ENTRY , NULL , & object ) ;
dbus_message_iter_append_basic ( & object , DBUS_TYPE_OBJECT_PATH , & endpoint ) ;
dbus_message_iter_open_container ( & object , DBUS_TYPE_ARRAY , " {sa{sv}} " , & array ) ;
dbus_message_iter_open_container ( & array , DBUS_TYPE_DICT_ENTRY , NULL , & entry ) ;
dbus_message_iter_append_basic ( & entry , DBUS_TYPE_STRING , & interface_name ) ;
dbus_message_iter_open_container ( & entry , DBUS_TYPE_ARRAY , " {sv} " , & dict ) ;
2022-07-20 17:45:15 +02:00
append_basic_variant_dict_entry ( & dict , " UUID " , DBUS_TYPE_STRING , " s " , & uuid ) ;
append_basic_variant_dict_entry ( & dict , " Codec " , DBUS_TYPE_BYTE , " y " , & codec_id ) ;
append_basic_array_variant_dict_entry ( & dict , " Capabilities " , " ay " , " y " , DBUS_TYPE_BYTE , caps , caps_size ) ;
2023-10-21 13:36:10 +03:00
2021-02-05 04:59:35 +02:00
if ( spa_bt_profile_from_uuid ( uuid ) & SPA_BT_PROFILE_A2DP_SOURCE ) {
2023-10-21 13:36:10 +03:00
dbus_bool_t delay_reporting = TRUE ;
2022-07-20 17:45:15 +02:00
append_basic_variant_dict_entry ( & dict , " DelayReporting " , DBUS_TYPE_BOOLEAN , " b " , & delay_reporting ) ;
2021-02-05 04:59:35 +02:00
}
2023-10-21 13:36:10 +03:00
if ( spa_bt_profile_from_uuid ( uuid ) & ( SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE ) ) {
dbus_uint32_t locations ;
dbus_uint16_t supported_context , context ;
locations = BAP_CHANNEL_ALL ;
if ( spa_bt_profile_from_uuid ( uuid ) & SPA_BT_PROFILE_BAP_SINK ) {
supported_context = context = BAP_CONTEXT_ALL ;
} else {
supported_context = context = ( BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL |
BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME ) ;
}
append_basic_variant_dict_entry ( & dict , " Locations " , DBUS_TYPE_UINT32 , " u " , & locations ) ;
append_basic_variant_dict_entry ( & dict , " Context " , DBUS_TYPE_UINT16 , " q " , & context ) ;
append_basic_variant_dict_entry ( & dict , " SupportedContext " , DBUS_TYPE_UINT16 , " q " , & supported_context ) ;
}
2020-11-01 21:56:44 +01:00
dbus_message_iter_close_container ( & entry , & dict ) ;
dbus_message_iter_close_container ( & array , & entry ) ;
dbus_message_iter_close_container ( & object , & array ) ;
dbus_message_iter_close_container ( iter , & object ) ;
2018-01-11 10:23:37 +01:00
}
2023-01-21 20:57:27 +02:00
static DBusHandlerResult object_manager_handler ( DBusConnection * c , DBusMessage * m , void * user_data , bool is_bap )
2020-11-01 21:56:44 +01:00
{
struct spa_bt_monitor * monitor = user_data ;
2022-06-15 17:24:41 +02:00
const struct media_codec * const * const media_codecs = monitor - > media_codecs ;
2020-11-01 21:56:44 +01:00
const char * path , * interface , * member ;
DBusMessageIter iter , array ;
DBusHandlerResult res ;
int i ;
path = dbus_message_get_path ( m ) ;
interface = dbus_message_get_interface ( m ) ;
member = dbus_message_get_member ( m ) ;
spa_log_debug ( monitor - > log , " dbus: path=%s, interface=%s, member=%s " , path , interface , member ) ;
if ( dbus_message_is_method_call ( m , " org.freedesktop.DBus.Introspectable " , " Introspect " ) ) {
const char * xml = OBJECT_MANAGER_INTROSPECT_XML ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = NULL ;
2020-11-01 21:56:44 +01:00
if ( ( r = dbus_message_new_method_return ( m ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
if ( ! dbus_message_append_args ( r , DBUS_TYPE_STRING , & xml , DBUS_TYPE_INVALID ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
if ( ! dbus_connection_send ( monitor - > conn , r , NULL ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
res = DBUS_HANDLER_RESULT_HANDLED ;
}
else if ( dbus_message_is_method_call ( m , " org.freedesktop.DBus.ObjectManager " , " GetManagedObjects " ) ) {
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = NULL ;
2023-08-02 13:26:02 +03:00
struct spa_bt_adapter * a ;
bool register_bcast = false ;
2023-07-11 19:24:46 +02:00
2020-11-01 21:56:44 +01:00
if ( ( r = dbus_message_new_method_return ( m ) ) = = NULL )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
dbus_message_iter_init_append ( r , & iter ) ;
dbus_message_iter_open_container ( & iter , DBUS_TYPE_ARRAY , " {oa{sa{sv}}} " , & array ) ;
2023-08-02 13:26:02 +03:00
/*
* Verify if an adapter exists that supports bap broadcast .
* If this adapter exists will register the broadcast endpoint .
*/
spa_list_for_each ( a , & monitor - > adapter_list , link ) {
if ( a - > le_audio_bcast_supported ) {
register_bcast = true ;
break ;
}
}
2022-06-15 17:24:41 +02:00
for ( i = 0 ; media_codecs [ i ] ; i + + ) {
const struct media_codec * codec = media_codecs [ i ] ;
2020-11-01 21:56:44 +01:00
uint8_t caps [ A2DP_MAX_CAPS_SIZE ] ;
2020-12-29 10:21:03 +08:00
int caps_size , ret ;
2020-12-04 11:34:38 +01:00
uint16_t codec_id = codec - > codec_id ;
2020-11-01 21:56:44 +01:00
2023-01-21 20:57:27 +02:00
if ( codec - > bap ! = is_bap )
2021-01-30 18:15:58 +02:00
continue ;
2023-01-21 20:57:27 +02:00
if ( ! is_media_codec_enabled ( monitor , codec ) )
2022-07-01 17:20:41 +02:00
continue ;
2022-10-23 14:05:05 +03:00
if ( endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SINK ) ) {
2022-06-15 17:24:41 +02:00
caps_size = codec - > fill_caps ( codec , MEDIA_CODEC_FLAG_SINK , caps ) ;
2022-05-21 13:18:38 +03:00
if ( caps_size < 0 )
continue ;
2023-07-11 20:16:50 +02:00
spa_autofree char * endpoint = NULL ;
2022-06-16 17:15:14 +02:00
ret = media_codec_to_endpoint ( codec , SPA_BT_MEDIA_SINK , & endpoint ) ;
2020-12-29 10:21:03 +08:00
if ( ret = = 0 ) {
2022-06-15 17:24:41 +02:00
spa_log_info ( monitor - > log , " register media sink codec %s: %s " , media_codecs [ i ] - > name , endpoint ) ;
2022-06-16 17:15:14 +02:00
append_media_object ( & array , endpoint ,
codec - > bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK ,
2020-12-29 10:21:03 +08:00
codec_id , caps , caps_size ) ;
}
2020-12-03 11:09:13 +01:00
}
2022-10-23 14:05:05 +03:00
if ( endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SOURCE ) ) {
2022-05-21 13:18:38 +03:00
caps_size = codec - > fill_caps ( codec , 0 , caps ) ;
if ( caps_size < 0 )
continue ;
2023-07-11 20:16:50 +02:00
spa_autofree char * endpoint = NULL ;
2022-06-16 17:15:14 +02:00
ret = media_codec_to_endpoint ( codec , SPA_BT_MEDIA_SOURCE , & endpoint ) ;
2020-12-29 10:21:03 +08:00
if ( ret = = 0 ) {
2022-06-15 17:24:41 +02:00
spa_log_info ( monitor - > log , " register media source codec %s: %s " , media_codecs [ i ] - > name , endpoint ) ;
2022-06-16 17:15:14 +02:00
append_media_object ( & array , endpoint ,
codec - > bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE ,
2020-12-29 10:21:03 +08:00
codec_id , caps , caps_size ) ;
}
2020-12-03 11:09:13 +01:00
}
2023-07-23 22:16:17 +03:00
2023-08-22 14:53:51 +03:00
if ( codec - > bap & & register_bcast ) {
2023-07-23 22:16:17 +03:00
if ( endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SOURCE_BROADCAST ) ) {
caps_size = codec - > fill_caps ( codec , 0 , caps ) ;
if ( caps_size < 0 )
continue ;
2023-08-02 10:35:20 +03:00
spa_autofree char * endpoint = NULL ;
2023-07-23 22:16:17 +03:00
ret = media_codec_to_endpoint ( codec , SPA_BT_MEDIA_SOURCE_BROADCAST , & endpoint ) ;
if ( ret = = 0 ) {
spa_log_info ( monitor - > log , " register media source codec %s: %s " , media_codecs [ i ] - > name , endpoint ) ;
append_media_object ( & array , endpoint ,
SPA_BT_UUID_BAP_BROADCAST_SOURCE ,
codec_id , caps , caps_size ) ;
}
}
2023-12-30 01:28:16 +01:00
2023-08-17 18:52:48 +03:00
if ( endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SINK_BROADCAST ) ) {
caps_size = codec - > fill_caps ( codec , MEDIA_CODEC_FLAG_SINK , caps ) ;
if ( caps_size < 0 )
continue ;
spa_autofree char * endpoint = NULL ;
ret = media_codec_to_endpoint ( codec , SPA_BT_MEDIA_SINK_BROADCAST , & endpoint ) ;
if ( ret = = 0 ) {
spa_log_info ( monitor - > log , " register broadcast media sink codec %s: %s " , media_codecs [ i ] - > name , endpoint ) ;
append_media_object ( & array , endpoint ,
SPA_BT_UUID_BAP_BROADCAST_SINK ,
codec_id , caps , caps_size ) ;
}
}
2023-07-23 22:16:17 +03:00
}
2020-11-01 21:56:44 +01:00
}
dbus_message_iter_close_container ( & iter , & array ) ;
if ( ! dbus_connection_send ( monitor - > conn , r , NULL ) )
return DBUS_HANDLER_RESULT_NEED_MEMORY ;
res = DBUS_HANDLER_RESULT_HANDLED ;
}
else
res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
return res ;
}
2023-01-21 20:57:27 +02:00
static DBusHandlerResult object_manager_handler_a2dp ( DBusConnection * c , DBusMessage * m , void * user_data )
{
return object_manager_handler ( c , m , user_data , false ) ;
}
static DBusHandlerResult object_manager_handler_bap ( DBusConnection * c , DBusMessage * m , void * user_data )
{
return object_manager_handler ( c , m , user_data , true ) ;
}
static void bluez_register_application_a2dp_reply ( DBusPendingCall * pending , void * user_data )
2020-11-01 21:56:44 +01:00
{
struct spa_bt_adapter * adapter = user_data ;
struct spa_bt_monitor * monitor = adapter - > monitor ;
bool fallback = true ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = steal_reply_and_unref ( & pending ) ;
2020-11-01 21:56:44 +01:00
if ( r = = NULL )
return ;
if ( dbus_message_is_error ( r , BLUEZ_ERROR_NOT_SUPPORTED ) ) {
spa_log_warn ( monitor - > log , " Registering media applications for adapter %s is disabled in bluez5 " , adapter - > path ) ;
goto finish ;
}
if ( dbus_message_get_type ( r ) = = DBUS_MESSAGE_TYPE_ERROR ) {
spa_log_error ( monitor - > log , " RegisterApplication() failed: %s " ,
dbus_message_get_error_name ( r ) ) ;
goto finish ;
}
fallback = false ;
2023-01-21 20:57:27 +02:00
adapter - > a2dp_application_registered = true ;
2020-11-01 21:56:44 +01:00
2020-12-29 10:21:03 +08:00
finish :
2020-11-01 21:56:44 +01:00
if ( fallback )
2023-01-21 20:57:27 +02:00
adapter_register_endpoints_legacy ( adapter ) ;
}
static void bluez_register_application_bap_reply ( DBusPendingCall * pending , void * user_data )
{
struct spa_bt_adapter * adapter = user_data ;
struct spa_bt_monitor * monitor = adapter - > monitor ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = steal_reply_and_unref ( & pending ) ;
2023-01-21 20:57:27 +02:00
if ( r = = NULL )
return ;
if ( dbus_message_get_type ( r ) = = DBUS_MESSAGE_TYPE_ERROR ) {
spa_log_error ( monitor - > log , " RegisterApplication() failed: %s " ,
dbus_message_get_error_name ( r ) ) ;
2023-07-11 19:24:46 +02:00
return ;
2023-01-21 20:57:27 +02:00
}
adapter - > bap_application_registered = true ;
2020-11-01 21:56:44 +01:00
}
2022-10-21 15:22:17 +02:00
static int register_media_endpoint ( struct spa_bt_monitor * monitor ,
const struct media_codec * codec ,
enum spa_bt_media_direction direction )
{
static const DBusObjectPathVTable vtable_endpoint = {
. message_function = endpoint_handler ,
} ;
if ( ! endpoint_should_be_registered ( monitor , codec , direction ) )
return 0 ;
2023-07-11 20:16:50 +02:00
spa_autofree char * object_path = NULL ;
2022-10-21 15:22:17 +02:00
int ret = media_codec_to_endpoint ( codec , direction , & object_path ) ;
if ( ret < 0 )
return ret ;
2023-01-21 20:57:27 +02:00
spa_log_info ( monitor - > log , " Registering DBus media endpoint: %s " , object_path ) ;
2022-10-21 15:22:17 +02:00
if ( ! dbus_connection_register_object_path ( monitor - > conn ,
object_path ,
& vtable_endpoint , monitor ) )
2023-07-11 20:16:50 +02:00
return - EIO ;
2022-10-21 15:22:17 +02:00
2023-07-11 20:16:50 +02:00
return 0 ;
2022-10-21 15:22:17 +02:00
}
2020-12-29 10:21:03 +08:00
static int register_media_application ( struct spa_bt_monitor * monitor )
{
2022-06-15 17:24:41 +02:00
const struct media_codec * const * const media_codecs = monitor - > media_codecs ;
2023-01-21 20:57:27 +02:00
const DBusObjectPathVTable vtable_object_manager_a2dp = {
. message_function = object_manager_handler_a2dp ,
2020-11-01 21:56:44 +01:00
} ;
2023-01-21 20:57:27 +02:00
const DBusObjectPathVTable vtable_object_manager_bap = {
. message_function = object_manager_handler_bap ,
} ;
spa_log_info ( monitor - > log , " Registering DBus media object manager: %s " ,
A2DP_OBJECT_MANAGER_PATH ) ;
2020-12-29 10:21:03 +08:00
2023-01-21 20:57:27 +02:00
if ( ! dbus_connection_register_object_path ( monitor - > conn ,
A2DP_OBJECT_MANAGER_PATH ,
& vtable_object_manager_a2dp , monitor ) )
return - EIO ;
spa_log_info ( monitor - > log , " Registering DBus media object manager: %s " ,
BAP_OBJECT_MANAGER_PATH ) ;
2020-12-29 10:21:03 +08:00
if ( ! dbus_connection_register_object_path ( monitor - > conn ,
2023-01-21 20:57:27 +02:00
BAP_OBJECT_MANAGER_PATH ,
& vtable_object_manager_bap , monitor ) )
2020-12-29 10:21:03 +08:00
return - EIO ;
2022-06-15 17:24:41 +02:00
for ( int i = 0 ; media_codecs [ i ] ; i + + ) {
const struct media_codec * codec = media_codecs [ i ] ;
2021-01-30 18:15:58 +02:00
2022-10-21 15:22:17 +02:00
register_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SOURCE ) ;
register_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SINK ) ;
2023-08-22 14:53:51 +03:00
if ( codec - > bap ) {
2023-07-23 22:16:17 +03:00
register_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SOURCE_BROADCAST ) ;
2023-08-17 18:52:48 +03:00
register_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SINK_BROADCAST ) ;
2023-07-23 22:16:17 +03:00
}
2020-12-29 10:21:03 +08:00
}
return 0 ;
}
2022-10-21 15:22:17 +02:00
static void unregister_media_endpoint ( struct spa_bt_monitor * monitor ,
const struct media_codec * codec ,
enum spa_bt_media_direction direction )
{
if ( ! endpoint_should_be_registered ( monitor , codec , direction ) )
return ;
2023-07-11 20:16:50 +02:00
spa_autofree char * object_path = NULL ;
2022-10-21 15:22:17 +02:00
int ret = media_codec_to_endpoint ( codec , direction , & object_path ) ;
if ( ret < 0 )
return ;
spa_log_info ( monitor - > log , " unregistering endpoint: %s " , object_path ) ;
if ( ! dbus_connection_unregister_object_path ( monitor - > conn , object_path ) )
spa_log_warn ( monitor - > log , " failed to unregister %s \n " , object_path ) ;
}
2020-12-29 10:21:03 +08:00
static void unregister_media_application ( struct spa_bt_monitor * monitor )
{
2022-06-15 17:24:41 +02:00
const struct media_codec * const * const media_codecs = monitor - > media_codecs ;
2020-12-29 10:21:03 +08:00
2022-06-15 17:24:41 +02:00
for ( int i = 0 ; media_codecs [ i ] ; i + + ) {
const struct media_codec * codec = media_codecs [ i ] ;
2020-12-29 10:21:03 +08:00
2022-10-21 15:22:17 +02:00
unregister_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SOURCE ) ;
unregister_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SINK ) ;
2023-08-22 14:53:51 +03:00
if ( codec - > bap ) {
2023-08-09 13:10:36 +03:00
unregister_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SOURCE_BROADCAST ) ;
2023-08-17 18:52:48 +03:00
unregister_media_endpoint ( monitor , codec , SPA_BT_MEDIA_SINK_BROADCAST ) ;
2023-08-09 13:10:36 +03:00
}
2020-12-29 10:21:03 +08:00
}
2021-04-10 18:10:37 +03:00
2023-01-21 20:57:27 +02:00
dbus_connection_unregister_object_path ( monitor - > conn , BAP_OBJECT_MANAGER_PATH ) ;
dbus_connection_unregister_object_path ( monitor - > conn , A2DP_OBJECT_MANAGER_PATH ) ;
2020-12-29 10:21:03 +08:00
}
2023-05-17 23:40:23 +03:00
static bool have_codec_endpoints ( struct spa_bt_monitor * monitor , bool bap )
{
const struct media_codec * const * const media_codecs = monitor - > media_codecs ;
int i ;
for ( i = 0 ; media_codecs [ i ] ; i + + ) {
const struct media_codec * codec = media_codecs [ i ] ;
if ( codec - > bap ! = bap )
continue ;
if ( endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SINK ) | |
2023-08-02 11:19:06 +03:00
endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SOURCE ) | |
2023-12-30 01:28:16 +01:00
endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SOURCE_BROADCAST ) | |
2023-08-17 18:52:48 +03:00
endpoint_should_be_registered ( monitor , codec , SPA_BT_MEDIA_SINK_BROADCAST ) )
2023-05-17 23:40:23 +03:00
return true ;
}
return false ;
}
2023-01-21 20:57:27 +02:00
static int adapter_register_application ( struct spa_bt_adapter * a , bool bap )
{
const char * object_manager_path = bap ? BAP_OBJECT_MANAGER_PATH : A2DP_OBJECT_MANAGER_PATH ;
2020-12-29 10:21:03 +08:00
struct spa_bt_monitor * monitor = a - > monitor ;
2023-05-17 23:40:23 +03:00
const char * ep_type_name = ( bap ? " LE Audio " : " A2DP " ) ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2020-11-01 21:56:44 +01:00
DBusMessageIter i , d ;
2023-01-21 20:57:27 +02:00
if ( bap & & a - > bap_application_registered )
return 0 ;
if ( ! bap & & a - > a2dp_application_registered )
2020-11-01 21:56:44 +01:00
return 0 ;
2023-08-02 13:26:02 +03:00
if ( ( bap & & ! a - > le_audio_supported ) & & ( bap & & ! a - > le_audio_bcast_supported ) ) {
2023-01-22 14:45:32 +02:00
spa_log_info ( monitor - > log , " Adapter %s indicates LE Audio unsupported: not registering application " ,
a - > path ) ;
return - ENOTSUP ;
}
2023-05-17 23:40:23 +03:00
if ( ! have_codec_endpoints ( monitor , bap ) ) {
spa_log_warn ( monitor - > log , " No available %s codecs to register on adapter %s " ,
ep_type_name , a - > path ) ;
return - ENOENT ;
}
2023-01-21 20:57:27 +02:00
spa_log_debug ( monitor - > log , " Registering bluez5 %s media application on adapter %s " ,
2023-05-17 23:40:23 +03:00
ep_type_name , a - > path ) ;
2020-11-01 21:56:44 +01:00
m = dbus_message_new_method_call ( BLUEZ_SERVICE ,
a - > path ,
BLUEZ_MEDIA_INTERFACE ,
" RegisterApplication " ) ;
2020-12-29 10:21:03 +08:00
if ( m = = NULL )
2020-11-01 21:56:44 +01:00
return - EIO ;
dbus_message_iter_init_append ( m , & i ) ;
dbus_message_iter_append_basic ( & i , DBUS_TYPE_OBJECT_PATH , & object_manager_path ) ;
dbus_message_iter_open_container ( & i , DBUS_TYPE_ARRAY , " {sv} " , & d ) ;
dbus_message_iter_close_container ( & i , & d ) ;
2023-07-11 19:57:23 +02:00
if ( ! send_with_reply ( monitor - > conn , m , bap ? bluez_register_application_bap_reply : bluez_register_application_a2dp_reply , a ) )
return - EIO ;
2020-11-01 21:56:44 +01:00
return 0 ;
}
2018-11-27 17:08:36 +01:00
2021-08-29 18:22:41 +03:00
static int switch_backend ( struct spa_bt_monitor * monitor , struct spa_bt_backend * backend )
{
int res ;
size_t i ;
spa_return_val_if_fail ( backend ! = NULL , - EINVAL ) ;
if ( ! backend - > available )
return - ENODEV ;
for ( i = 0 ; i < SPA_N_ELEMENTS ( monitor - > backends ) ; + + i ) {
struct spa_bt_backend * b = monitor - > backends [ i ] ;
if ( backend ! = b & & b & & b - > available & & b - > exclusive )
spa_log_warn ( monitor - > log ,
" %s running, but not configured as HFP/HSP backend: "
" it may interfere with HFP/HSP functionality. " ,
b - > name ) ;
}
if ( monitor - > backend = = backend )
return 0 ;
spa_log_info ( monitor - > log , " Switching to HFP/HSP backend %s " , backend - > name ) ;
spa_bt_backend_unregister_profiles ( monitor - > backend ) ;
if ( ( res = spa_bt_backend_register_profiles ( backend ) ) < 0 ) {
monitor - > backend = NULL ;
return res ;
}
monitor - > backend = backend ;
return 0 ;
}
2021-10-09 15:23:04 +03:00
static void reselect_backend ( struct spa_bt_monitor * monitor , bool silent )
2021-08-29 18:22:41 +03:00
{
struct spa_bt_backend * backend ;
size_t i ;
2021-10-01 19:03:49 +03:00
spa_log_debug ( monitor - > log , " re-selecting HFP/HSP backend " ) ;
2021-08-29 18:22:41 +03:00
if ( monitor - > backend_selection = = BACKEND_NONE ) {
spa_bt_backend_unregister_profiles ( monitor - > backend ) ;
monitor - > backend = NULL ;
return ;
} else if ( monitor - > backend_selection = = BACKEND_ANY ) {
for ( i = 0 ; i < SPA_N_ELEMENTS ( monitor - > backends ) ; + + i ) {
backend = monitor - > backends [ i ] ;
if ( backend & & switch_backend ( monitor , backend ) = = 0 )
return ;
}
} else {
backend = monitor - > backends [ monitor - > backend_selection ] ;
if ( backend & & switch_backend ( monitor , backend ) = = 0 )
return ;
}
2021-10-09 15:23:04 +03:00
spa_bt_backend_unregister_profiles ( monitor - > backend ) ;
monitor - > backend = NULL ;
if ( ! silent )
spa_log_error ( monitor - > log , " Failed to start HFP/HSP backend %s " ,
backend ? backend - > name : " none " ) ;
2021-08-29 18:22:41 +03:00
}
2024-04-22 09:35:49 +03:00
static void configure_bis ( struct spa_bt_monitor * monitor ,
const struct media_codec * codec ,
DBusConnection * conn ,
const char * object_path ,
const char * interface_name ,
struct spa_bt_big * big ,
struct spa_bt_bis * bis ,
const char * local_endpoint )
{
DBusMessageIter iter , entry , variant , qos_dict ;
2024-05-10 16:25:50 +03:00
spa_autoptr ( DBusMessage ) msg = NULL ;
2024-04-22 09:35:49 +03:00
DBusMessageIter dict ;
int bis_id = 0xFF ;
uint8_t caps [ CC_MAX_LEN ] ;
uint8_t metadata [ METADATA_MAX_LEN ] ;
uint8_t caps_size , metadata_size = 0 ;
struct bap_codec_qos qos ;
int presentation_delay ;
struct spa_bt_metadata * metadata_entry ;
2024-05-10 16:25:50 +03:00
struct spa_dict settings ;
struct spa_dict_item setting_items [ 2 ] ;
char channel_allocation [ 64 ] = { 0 } ;
2024-04-22 09:35:49 +03:00
int mse = 0 ;
int options = 0 ;
int skip = 0 ;
int sync_cte_type = 0 ;
int sync_factor = 1 ;
int sync_timeout = 2000 ;
int timeout = 2000 ;
2024-05-10 16:25:50 +03:00
2024-04-22 09:35:49 +03:00
/* Configure each BIS from a BIG */
spa_list_for_each ( metadata_entry , & bis - > metadata_list , link ) {
2024-05-10 16:25:50 +03:00
if ( ( metadata_size + metadata_entry - > length + 1 ) > METADATA_MAX_LEN ) {
spa_log_warn ( monitor - > log , " Metadata configured for the BIS exceeds the maximum metadata size " ) ;
return ;
}
2024-04-22 09:35:49 +03:00
metadata [ metadata_size ] = ( uint8_t ) metadata_entry - > length ;
metadata_size + + ;
metadata [ metadata_size ] = ( uint8_t ) metadata_entry - > type ;
metadata_size + + ;
memcpy ( & metadata [ metadata_size ] , metadata_entry - > value , metadata_entry - > length - 1 ) ;
metadata_size + = metadata_entry - > length - 1 ;
}
2024-05-10 16:25:50 +03:00
spa_log_debug ( monitor - > log , " bis->channel_allocation %d " , bis - > channel_allocation ) ;
if ( bis - > channel_allocation )
spa_scnprintf ( channel_allocation , sizeof ( channel_allocation ) , " % " PRIu32 , bis - > channel_allocation ) ;
setting_items [ 0 ] = SPA_DICT_ITEM_INIT ( " channel_allocation " , channel_allocation ) ;
setting_items [ 1 ] = SPA_DICT_ITEM_INIT ( " preset " , bis - > qos_preset ) ;
settings = SPA_DICT_INIT ( setting_items , 2 ) ;
codec - > get_bis_config ( codec , caps , & caps_size , & settings , & qos ) ;
2024-04-22 09:35:49 +03:00
msg = dbus_message_new_method_call ( BLUEZ_SERVICE ,
object_path ,
interface_name ,
" SetConfiguration " ) ;
dbus_message_iter_init_append ( msg , & iter ) ;
dbus_message_iter_append_basic ( & iter , DBUS_TYPE_OBJECT_PATH , & local_endpoint ) ;
dbus_message_iter_open_container ( & iter , DBUS_TYPE_ARRAY , " {sv} " , & dict ) ;
append_basic_array_variant_dict_entry ( & dict , " Capabilities " , " ay " , " y " , DBUS_TYPE_BYTE , caps , caps_size ) ;
append_basic_array_variant_dict_entry ( & dict , " Metadata " , " ay " , " y " , DBUS_TYPE_BYTE , metadata , metadata_size ) ;
dbus_message_iter_open_container ( & dict , DBUS_TYPE_DICT_ENTRY , NULL , & entry ) ;
2024-05-13 18:01:12 +03:00
dbus_message_iter_append_basic ( & entry , DBUS_TYPE_STRING , & ( const char * ) { " QoS " } ) ;
2024-04-22 09:35:49 +03:00
dbus_message_iter_open_container ( & entry , DBUS_TYPE_VARIANT , " a{sv} " , & variant ) ;
dbus_message_iter_open_container ( & variant , 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 ,
& qos_dict ) ;
append_basic_variant_dict_entry ( & qos_dict , " BIG " , DBUS_TYPE_BYTE , " y " , & big - > big_id ) ;
append_basic_variant_dict_entry ( & qos_dict , " BIS " , DBUS_TYPE_BYTE , " y " , & bis_id ) ;
append_basic_variant_dict_entry ( & qos_dict , " SyncFactor " , DBUS_TYPE_BYTE , " y " , & sync_factor ) ;
append_basic_variant_dict_entry ( & qos_dict , " Options " , DBUS_TYPE_BYTE , " y " , & options ) ;
append_basic_variant_dict_entry ( & qos_dict , " Skip " , DBUS_TYPE_UINT16 , " q " , & skip ) ;
append_basic_variant_dict_entry ( & qos_dict , " SyncTimeout " , DBUS_TYPE_UINT16 , " q " , & sync_timeout ) ;
append_basic_variant_dict_entry ( & qos_dict , " SyncCteType " , DBUS_TYPE_BYTE , " y " , & sync_cte_type ) ;
append_basic_variant_dict_entry ( & qos_dict , " MSE " , DBUS_TYPE_BYTE , " y " , & mse ) ;
append_basic_variant_dict_entry ( & qos_dict , " Timeout " , DBUS_TYPE_UINT16 , " q " , & timeout ) ;
append_basic_array_variant_dict_entry ( & qos_dict , " BCode " , " ay " , " y " , DBUS_TYPE_BYTE , big - > broadcast_code , BROADCAST_CODE_LEN ) ;
append_basic_variant_dict_entry ( & qos_dict , " Interval " , DBUS_TYPE_UINT32 , " u " , & qos . interval ) ;
append_basic_variant_dict_entry ( & qos_dict , " Framing " , DBUS_TYPE_BYTE , " y " , & qos . framing ) ;
append_basic_variant_dict_entry ( & qos_dict , " PHY " , DBUS_TYPE_BYTE , " y " , & qos . phy ) ;
append_basic_variant_dict_entry ( & qos_dict , " SDU " , DBUS_TYPE_UINT16 , " q " , & qos . sdu ) ;
append_basic_variant_dict_entry ( & qos_dict , " Retransmissions " , DBUS_TYPE_BYTE , " y " , & qos . retransmission ) ;
append_basic_variant_dict_entry ( & qos_dict , " Latency " , DBUS_TYPE_UINT16 , " q " , & qos . latency ) ;
append_basic_variant_dict_entry ( & qos_dict , " PresentationDelay " , DBUS_TYPE_UINT32 , " u " , & presentation_delay ) ;
dbus_message_iter_close_container ( & variant , & qos_dict ) ;
dbus_message_iter_close_container ( & entry , & variant ) ;
dbus_message_iter_close_container ( & dict , & entry ) ;
dbus_message_iter_close_container ( & iter , & dict ) ;
dbus_message_set_no_reply ( msg , TRUE ) ;
if ( ! dbus_connection_send ( conn , msg , NULL ) ) {
spa_log_error ( monitor - > log , " sending SetConfiguration failed " ) ;
}
}
static void configure_bcast_source ( struct spa_bt_monitor * monitor ,
const struct media_codec * codec ,
DBusConnection * conn ,
const char * object_path ,
const char * interface_name ,
const char * local_endpoint )
{
struct spa_bt_big * big ;
struct spa_bt_bis * bis ;
/* Configure each BIS from a BIG */
spa_list_for_each ( big , & monitor - > bcast_source_config_list , link ) {
spa_list_for_each ( bis , & big - > bis_list , link ) {
configure_bis ( monitor , codec , conn , object_path , interface_name ,
big , bis , local_endpoint ) ;
}
}
}
2018-01-11 10:23:37 +01:00
static void interface_added ( struct spa_bt_monitor * monitor ,
DBusConnection * conn ,
const char * object_path ,
const char * interface_name ,
DBusMessageIter * props_iter )
{
2018-11-27 17:08:36 +01:00
spa_log_debug ( monitor - > log , " Found object %s, interface %s " , object_path , interface_name ) ;
2023-01-22 14:45:32 +02:00
if ( spa_streq ( interface_name , BLUEZ_ADAPTER_INTERFACE ) | |
spa_streq ( interface_name , BLUEZ_MEDIA_INTERFACE ) ) {
2018-01-11 10:23:37 +01:00
struct spa_bt_adapter * a ;
a = adapter_find ( monitor , object_path ) ;
if ( a = = NULL ) {
a = adapter_create ( monitor , object_path ) ;
if ( a = = NULL ) {
2019-06-07 17:50:46 +02:00
spa_log_warn ( monitor - > log , " can't create adapter: %m " ) ;
2018-01-11 10:23:37 +01:00
return ;
}
}
2023-01-22 14:45:32 +02:00
if ( spa_streq ( interface_name , BLUEZ_ADAPTER_INTERFACE ) ) {
adapter_update_props ( a , props_iter , NULL ) ;
a - > has_adapter1_interface = true ;
} else {
adapter_media_update_props ( a , props_iter , NULL ) ;
a - > has_media1_interface = true ;
}
if ( a - > has_adapter1_interface & & a - > has_media1_interface ) {
adapter_register_application ( a , false ) ;
adapter_register_application ( a , true ) ;
adapter_register_player ( a ) ;
adapter_update_devices ( a ) ;
}
2018-01-11 10:23:37 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( interface_name , BLUEZ_PROFILE_MANAGER_INTERFACE ) ) {
2021-08-29 18:22:41 +03:00
if ( monitor - > backends [ BACKEND_NATIVE ] )
monitor - > backends [ BACKEND_NATIVE ] - > available = true ;
2021-10-09 15:23:04 +03:00
reselect_backend ( monitor , false ) ;
2018-11-27 17:08:36 +01:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( interface_name , BLUEZ_DEVICE_INTERFACE ) ) {
2018-01-11 10:23:37 +01:00
struct spa_bt_device * d ;
2021-05-04 15:39:34 +02:00
d = spa_bt_device_find ( monitor , object_path ) ;
2018-01-11 10:23:37 +01:00
if ( d = = NULL ) {
2021-05-04 15:39:34 +02:00
d = device_create ( monitor , object_path ) ;
if ( d = = NULL ) {
spa_log_warn ( monitor - > log , " can't create Bluetooth device %s: %m " ,
object_path ) ;
return ;
}
2018-01-11 10:23:37 +01:00
}
2021-03-14 14:06:50 +08:00
2018-01-11 10:23:37 +01:00
device_update_props ( d , props_iter , NULL ) ;
2021-06-21 14:47:13 +08:00
d - > reconnect_state = BT_DEVICE_RECONNECT_INIT ;
2021-06-19 23:49:58 +03:00
2022-01-17 19:10:14 +02:00
if ( ! device_props_ready ( d ) )
return ;
2021-06-19 23:49:58 +03:00
device_update_hw_volume_profiles ( d ) ;
2021-03-14 14:06:50 +08:00
/* Trigger bluez device creation before bluez profile negotiation started so that
* profile connection handlers can receive per - device settings during profile negotiation . */
2021-06-20 23:00:37 +08:00
spa_bt_device_add_profile ( d , SPA_BT_PROFILE_NULL ) ;
2018-01-11 10:23:37 +01:00
}
2023-03-05 01:03:13 +02:00
else if ( spa_streq ( interface_name , BLUEZ_DEVICE_SET_INTERFACE ) ) {
device_set_update_props ( monitor , object_path , props_iter , NULL ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( interface_name , BLUEZ_MEDIA_ENDPOINT_INTERFACE ) ) {
2021-01-24 20:38:13 +02:00
struct spa_bt_remote_endpoint * ep ;
2021-03-26 18:06:27 +02:00
struct spa_bt_device * d ;
2021-01-24 20:38:13 +02:00
ep = remote_endpoint_find ( monitor , object_path ) ;
if ( ep = = NULL ) {
ep = remote_endpoint_create ( monitor , object_path ) ;
if ( ep = = NULL ) {
spa_log_warn ( monitor - > log , " can't create Bluetooth remote endpoint %s: %m " ,
object_path ) ;
return ;
}
}
remote_endpoint_update_props ( ep , props_iter , NULL ) ;
2021-03-26 18:06:27 +02:00
d = ep - > device ;
if ( d )
spa_bt_device_emit_profiles_changed ( d , d - > profiles , d - > connected_profiles ) ;
2024-04-22 09:35:49 +03:00
if ( spa_streq ( ep - > uuid , SPA_BT_UUID_BAP_BROADCAST_SINK ) ) {
int ret , i ;
2024-05-10 16:25:50 +03:00
bool codec_found = false ;
2024-04-22 09:35:49 +03:00
spa_autofree char * local_endpoint = NULL ;
/* get local endpoint */
for ( i = 0 ; monitor - > media_codecs ; i + + ) {
if ( ! monitor - > media_codecs [ i ] - > bap )
continue ;
if ( ! is_media_codec_enabled ( monitor , monitor - > media_codecs [ i ] ) )
continue ;
if ( monitor - > media_codecs [ i ] - > codec_id = = ep - > codec ) {
ret = media_codec_to_endpoint ( monitor - > media_codecs [ i ] , SPA_BT_MEDIA_SOURCE_BROADCAST , & local_endpoint ) ;
2024-05-10 16:25:50 +03:00
if ( ret = = 0 ) {
codec_found = true ;
2024-04-22 09:35:49 +03:00
break ;
2024-05-10 16:25:50 +03:00
}
2024-04-22 09:35:49 +03:00
}
}
2024-05-10 16:25:50 +03:00
if ( ! codec_found ) {
spa_log_warn ( monitor - > log , " endpoint codec not found " ) ;
return ;
}
2024-04-22 09:35:49 +03:00
if ( local_endpoint ! = NULL )
configure_bcast_source ( monitor , monitor - > media_codecs [ i ] , conn , object_path , interface_name , local_endpoint ) ;
}
2021-01-24 20:38:13 +02:00
}
2018-01-11 10:23:37 +01:00
}
2020-12-29 10:21:03 +08:00
static void interfaces_added ( struct spa_bt_monitor * monitor , DBusMessageIter * arg_iter )
{
DBusMessageIter it [ 3 ] ;
const char * object_path ;
dbus_message_iter_get_basic ( arg_iter , & object_path ) ;
dbus_message_iter_next ( arg_iter ) ;
dbus_message_iter_recurse ( arg_iter , & it [ 0 ] ) ;
while ( dbus_message_iter_get_arg_type ( & it [ 0 ] ) ! = DBUS_TYPE_INVALID ) {
const char * interface_name ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
dbus_message_iter_get_basic ( & it [ 1 ] , & interface_name ) ;
dbus_message_iter_next ( & it [ 1 ] ) ;
dbus_message_iter_recurse ( & it [ 1 ] , & it [ 2 ] ) ;
interface_added ( monitor , monitor - > conn ,
object_path , interface_name ,
& it [ 2 ] ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
}
}
static void interfaces_removed ( struct spa_bt_monitor * monitor , DBusMessageIter * arg_iter )
{
const char * object_path ;
DBusMessageIter it ;
dbus_message_iter_get_basic ( arg_iter , & object_path ) ;
dbus_message_iter_next ( arg_iter ) ;
dbus_message_iter_recurse ( arg_iter , & it ) ;
while ( dbus_message_iter_get_arg_type ( & it ) ! = DBUS_TYPE_INVALID ) {
const char * interface_name ;
dbus_message_iter_get_basic ( & it , & interface_name ) ;
2020-12-30 05:25:51 +08:00
spa_log_debug ( monitor - > log , " Found object %s, interface %s " , object_path , interface_name ) ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( interface_name , BLUEZ_DEVICE_INTERFACE ) ) {
2020-12-29 10:21:03 +08:00
struct spa_bt_device * d ;
2020-12-30 05:25:51 +08:00
d = spa_bt_device_find ( monitor , object_path ) ;
if ( d ! = NULL )
2020-12-29 10:21:03 +08:00
device_free ( d ) ;
2023-03-05 01:03:13 +02:00
} else if ( spa_streq ( interface_name , BLUEZ_DEVICE_SET_INTERFACE ) ) {
device_set_update_props ( monitor , object_path , NULL , NULL ) ;
2023-01-22 14:45:32 +02:00
} else if ( spa_streq ( interface_name , BLUEZ_ADAPTER_INTERFACE ) | |
spa_streq ( interface_name , BLUEZ_MEDIA_INTERFACE ) ) {
2020-12-29 10:21:03 +08:00
struct spa_bt_adapter * a ;
2020-12-30 05:25:51 +08:00
a = adapter_find ( monitor , object_path ) ;
if ( a ! = NULL )
2020-12-29 10:21:03 +08:00
adapter_free ( a ) ;
2021-05-18 11:36:13 +10:00
} else if ( spa_streq ( interface_name , BLUEZ_MEDIA_ENDPOINT_INTERFACE ) ) {
2021-01-24 20:38:13 +02:00
struct spa_bt_remote_endpoint * ep ;
ep = remote_endpoint_find ( monitor , object_path ) ;
2021-03-26 18:06:27 +02:00
if ( ep ! = NULL ) {
struct spa_bt_device * d = ep - > device ;
2021-01-24 20:38:13 +02:00
remote_endpoint_free ( ep ) ;
2021-03-26 18:06:27 +02:00
if ( d )
spa_bt_device_emit_profiles_changed ( d , d - > profiles , d - > connected_profiles ) ;
}
2020-12-29 10:21:03 +08:00
}
dbus_message_iter_next ( & it ) ;
}
}
2018-01-11 10:23:37 +01:00
static void get_managed_objects_reply ( DBusPendingCall * pending , void * user_data )
{
struct spa_bt_monitor * monitor = user_data ;
DBusMessageIter it [ 6 ] ;
2023-07-11 17:10:37 +02:00
spa_assert ( monitor - > get_managed_objects_call = = pending ) ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) r = steal_reply_and_unref ( & monitor - > get_managed_objects_call ) ;
2018-01-11 10:23:37 +01:00
if ( r = = NULL )
return ;
if ( dbus_message_is_error ( r , DBUS_ERROR_UNKNOWN_METHOD ) ) {
spa_log_warn ( monitor - > log , " BlueZ D-Bus ObjectManager not available " ) ;
2023-07-11 19:24:46 +02:00
return ;
2018-01-11 10:23:37 +01:00
}
2024-03-25 09:29:07 +02:00
if ( dbus_message_is_error ( r , DBUS_ERROR_NAME_HAS_NO_OWNER ) ) {
spa_log_warn ( monitor - > log , " BlueZ system service is not available " ) ;
return ;
}
2018-01-11 10:23:37 +01:00
if ( dbus_message_get_type ( r ) = = DBUS_MESSAGE_TYPE_ERROR ) {
spa_log_error ( monitor - > log , " GetManagedObjects() failed: %s " ,
dbus_message_get_error_name ( r ) ) ;
2023-07-11 19:24:46 +02:00
return ;
2018-01-11 10:23:37 +01:00
}
if ( ! dbus_message_iter_init ( r , & it [ 0 ] ) | |
2021-05-18 11:43:49 +10:00
! spa_streq ( dbus_message_get_signature ( r ) , " a{oa{sa{sv}}} " ) ) {
2018-01-11 10:23:37 +01:00
spa_log_error ( monitor - > log , " Invalid reply signature for GetManagedObjects() " ) ;
2023-07-11 19:24:46 +02:00
return ;
2018-01-11 10:23:37 +01:00
}
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
while ( dbus_message_iter_get_arg_type ( & it [ 1 ] ) ! = DBUS_TYPE_INVALID ) {
dbus_message_iter_recurse ( & it [ 1 ] , & it [ 2 ] ) ;
2020-12-29 10:21:03 +08:00
interfaces_added ( monitor , & it [ 2 ] ) ;
2018-01-11 10:23:37 +01:00
dbus_message_iter_next ( & it [ 1 ] ) ;
}
2021-10-09 15:23:04 +03:00
reselect_backend ( monitor , false ) ;
2021-09-24 22:50:34 +03:00
2020-12-30 05:25:51 +08:00
monitor - > objects_listed = true ;
2018-01-11 10:23:37 +01:00
}
static void get_managed_objects ( struct spa_bt_monitor * monitor )
{
2022-10-08 01:17:19 +02:00
if ( monitor - > objects_listed | | monitor - > get_managed_objects_call )
2022-10-08 00:57:49 +02:00
return ;
2023-07-11 19:24:46 +02:00
spa_autoptr ( DBusMessage ) m = NULL ;
2018-01-11 10:23:37 +01:00
m = dbus_message_new_method_call ( BLUEZ_SERVICE ,
" / " ,
" org.freedesktop.DBus.ObjectManager " ,
" GetManagedObjects " ) ;
2022-10-08 01:23:13 +02:00
dbus_message_set_auto_start ( m , false ) ;
2023-07-11 19:57:23 +02:00
monitor - > get_managed_objects_call = send_with_reply ( monitor - > conn , m , get_managed_objects_reply , monitor ) ;
2018-01-11 10:23:37 +01:00
}
2019-05-16 13:18:45 +02:00
static DBusHandlerResult filter_cb ( DBusConnection * bus , DBusMessage * m , void * user_data )
{
struct spa_bt_monitor * monitor = user_data ;
if ( dbus_message_is_signal ( m , " org.freedesktop.DBus " , " NameOwnerChanged " ) ) {
2020-12-29 10:21:03 +08:00
const char * name , * old_owner , * new_owner ;
2023-07-11 20:44:23 +02:00
spa_auto ( DBusError ) err = DBUS_ERROR_INIT ;
2020-12-29 10:21:03 +08:00
2019-05-16 13:18:45 +02:00
spa_log_debug ( monitor - > log , " Name owner changed %s " , dbus_message_get_path ( m ) ) ;
2020-12-29 10:21:03 +08:00
if ( ! dbus_message_get_args ( m , & err ,
DBUS_TYPE_STRING , & name ,
DBUS_TYPE_STRING , & old_owner ,
DBUS_TYPE_STRING , & new_owner ,
DBUS_TYPE_INVALID ) ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( monitor - > log , " Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s " , err . message ) ;
2023-07-11 20:44:23 +02:00
goto finish ;
2020-12-29 10:21:03 +08:00
}
2021-05-18 11:36:13 +10:00
if ( spa_streq ( name , BLUEZ_SERVICE ) ) {
2021-03-18 10:48:02 +08:00
bool has_old_owner = old_owner & & * old_owner ;
bool has_new_owner = new_owner & & * new_owner ;
if ( has_old_owner ) {
spa_log_debug ( monitor - > log , " Bluetooth daemon disappeared " ) ;
2021-10-09 15:23:04 +03:00
if ( monitor - > backends [ BACKEND_NATIVE ] )
monitor - > backends [ BACKEND_NATIVE ] - > available = false ;
reselect_backend ( monitor , true ) ;
2021-03-18 10:48:02 +08:00
}
if ( has_old_owner | | has_new_owner ) {
2020-12-29 10:21:03 +08:00
struct spa_bt_adapter * a ;
struct spa_bt_device * d ;
2021-01-24 20:38:13 +02:00
struct spa_bt_remote_endpoint * ep ;
2020-12-29 10:21:03 +08:00
struct spa_bt_transport * t ;
monitor - > objects_listed = false ;
spa_list_consume ( t , & monitor - > transport_list , link )
spa_bt_transport_free ( t ) ;
2021-01-24 20:38:13 +02:00
spa_list_consume ( ep , & monitor - > remote_endpoint_list , link )
remote_endpoint_free ( ep ) ;
2020-12-29 10:21:03 +08:00
spa_list_consume ( d , & monitor - > device_list , link )
device_free ( d ) ;
spa_list_consume ( a , & monitor - > adapter_list , link )
adapter_free ( a ) ;
}
2021-03-18 10:48:02 +08:00
if ( has_new_owner ) {
2020-12-29 10:21:03 +08:00
spa_log_debug ( monitor - > log , " Bluetooth daemon appeared " ) ;
get_managed_objects ( monitor ) ;
}
2021-08-29 18:22:41 +03:00
} else if ( spa_streq ( name , OFONO_SERVICE ) ) {
if ( monitor - > backends [ BACKEND_OFONO ] )
monitor - > backends [ BACKEND_OFONO ] - > available = ( new_owner & & * new_owner ) ;
2021-10-09 15:23:04 +03:00
reselect_backend ( monitor , false ) ;
2021-08-29 18:22:41 +03:00
} else if ( spa_streq ( name , HSPHFPD_SERVICE ) ) {
if ( monitor - > backends [ BACKEND_HSPHFPD ] )
monitor - > backends [ BACKEND_HSPHFPD ] - > available = ( new_owner & & * new_owner ) ;
2021-10-09 15:23:04 +03:00
reselect_backend ( monitor , false ) ;
2020-12-29 10:21:03 +08:00
}
2019-05-16 13:18:45 +02:00
} else if ( dbus_message_is_signal ( m , " org.freedesktop.DBus.ObjectManager " , " InterfacesAdded " ) ) {
2020-12-29 10:21:03 +08:00
DBusMessageIter it ;
2019-05-16 13:18:45 +02:00
spa_log_debug ( monitor - > log , " interfaces added %s " , dbus_message_get_path ( m ) ) ;
2020-12-29 10:21:03 +08:00
if ( ! monitor - > objects_listed )
goto finish ;
2021-05-18 11:43:49 +10:00
if ( ! dbus_message_iter_init ( m , & it ) | | ! spa_streq ( dbus_message_get_signature ( m ) , " oa{sa{sv}} " ) ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( monitor - > log , " Invalid signature found in InterfacesAdded " ) ;
2020-12-29 10:21:03 +08:00
goto finish ;
}
interfaces_added ( monitor , & it ) ;
2019-05-16 13:18:45 +02:00
} else if ( dbus_message_is_signal ( m , " org.freedesktop.DBus.ObjectManager " , " InterfacesRemoved " ) ) {
2020-12-29 10:21:03 +08:00
DBusMessageIter it ;
2019-05-16 13:18:45 +02:00
spa_log_debug ( monitor - > log , " interfaces removed %s " , dbus_message_get_path ( m ) ) ;
2020-12-29 10:21:03 +08:00
if ( ! monitor - > objects_listed )
goto finish ;
2021-05-18 11:43:49 +10:00
if ( ! dbus_message_iter_init ( m , & it ) | | ! spa_streq ( dbus_message_get_signature ( m ) , " oas " ) ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( monitor - > log , " Invalid signature found in InterfacesRemoved " ) ;
2020-12-29 10:21:03 +08:00
goto finish ;
}
interfaces_removed ( monitor , & it ) ;
2019-05-16 13:18:45 +02:00
} else if ( dbus_message_is_signal ( m , " org.freedesktop.DBus.Properties " , " PropertiesChanged " ) ) {
DBusMessageIter it [ 2 ] ;
const char * iface , * path ;
2020-12-30 05:25:51 +08:00
if ( ! monitor - > objects_listed )
goto finish ;
2019-05-16 13:18:45 +02:00
if ( ! dbus_message_iter_init ( m , & it [ 0 ] ) | |
2021-05-18 11:43:49 +10:00
! spa_streq ( dbus_message_get_signature ( m ) , " sa{sv}as " ) ) {
2019-05-16 13:18:45 +02:00
spa_log_error ( monitor - > log , " Invalid signature found in PropertiesChanged " ) ;
2020-12-29 10:21:03 +08:00
goto finish ;
2019-05-16 13:18:45 +02:00
}
path = dbus_message_get_path ( m ) ;
dbus_message_iter_get_basic ( & it [ 0 ] , & iface ) ;
dbus_message_iter_next ( & it [ 0 ] ) ;
dbus_message_iter_recurse ( & it [ 0 ] , & it [ 1 ] ) ;
2023-01-22 14:45:32 +02:00
if ( spa_streq ( iface , BLUEZ_ADAPTER_INTERFACE ) | |
spa_streq ( iface , BLUEZ_MEDIA_INTERFACE ) ) {
2019-05-16 13:18:45 +02:00
struct spa_bt_adapter * a ;
a = adapter_find ( monitor , path ) ;
if ( a = = NULL ) {
spa_log_warn ( monitor - > log ,
" Properties changed in unknown adapter %s " , path ) ;
2020-12-29 10:21:03 +08:00
goto finish ;
2019-05-16 13:18:45 +02:00
}
spa_log_debug ( monitor - > log , " Properties changed in adapter %s " , path ) ;
2023-01-22 14:45:32 +02:00
if ( spa_streq ( iface , BLUEZ_ADAPTER_INTERFACE ) )
adapter_update_props ( a , & it [ 1 ] , NULL ) ;
else
adapter_media_update_props ( a , & it [ 1 ] , NULL ) ;
2019-05-16 13:18:45 +02:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( iface , BLUEZ_DEVICE_INTERFACE ) ) {
2019-05-16 13:18:45 +02:00
struct spa_bt_device * d ;
2020-07-17 15:18:10 +02:00
d = spa_bt_device_find ( monitor , path ) ;
2019-05-16 13:18:45 +02:00
if ( d = = NULL ) {
2020-12-05 20:56:56 +01:00
spa_log_debug ( monitor - > log ,
2019-05-16 13:18:45 +02:00
" Properties changed in unknown device %s " , path ) ;
2020-12-29 10:21:03 +08:00
goto finish ;
2019-05-16 13:18:45 +02:00
}
spa_log_debug ( monitor - > log , " Properties changed in device %s " , path ) ;
device_update_props ( d , & it [ 1 ] , NULL ) ;
2022-01-17 19:10:14 +02:00
if ( ! device_props_ready ( d ) )
goto finish ;
device_update_hw_volume_profiles ( d ) ;
2021-06-20 23:00:37 +08:00
spa_bt_device_add_profile ( d , SPA_BT_PROFILE_NULL ) ;
2019-05-16 13:18:45 +02:00
}
2023-03-05 01:03:13 +02:00
else if ( spa_streq ( iface , BLUEZ_DEVICE_SET_INTERFACE ) ) {
device_set_update_props ( monitor , path , & it [ 1 ] , NULL ) ;
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( iface , BLUEZ_MEDIA_ENDPOINT_INTERFACE ) ) {
2021-01-24 20:38:13 +02:00
struct spa_bt_remote_endpoint * ep ;
2021-03-26 18:06:27 +02:00
struct spa_bt_device * d ;
2021-01-24 20:38:13 +02:00
ep = remote_endpoint_find ( monitor , path ) ;
if ( ep = = NULL ) {
spa_log_debug ( monitor - > log ,
" Properties changed in unknown remote endpoint %s " , path ) ;
goto finish ;
}
spa_log_debug ( monitor - > log , " Properties changed in remote endpoint %s " , path ) ;
remote_endpoint_update_props ( ep , & it [ 1 ] , NULL ) ;
2021-03-26 18:06:27 +02:00
d = ep - > device ;
if ( d )
spa_bt_device_emit_profiles_changed ( d , d - > profiles , d - > connected_profiles ) ;
2021-01-24 20:38:13 +02:00
}
2021-05-18 11:36:13 +10:00
else if ( spa_streq ( iface , BLUEZ_MEDIA_TRANSPORT_INTERFACE ) ) {
2019-05-16 13:18:45 +02:00
struct spa_bt_transport * transport ;
2020-07-21 11:00:57 +02:00
transport = spa_bt_transport_find ( monitor , path ) ;
2019-05-16 13:18:45 +02:00
if ( transport = = NULL ) {
spa_log_warn ( monitor - > log ,
2024-01-13 13:18:51 +02:00
" Properties changed in unknown transport '%s'. "
" Multiple sound server instances (PipeWire/Pulseaudio/bluez-alsa) are "
" probably trying to use Bluetooth audio at the same time, which can "
" cause problems. The system configuration likely should be fixed "
" to have only one sound server that manages Bluetooth audio. " ,
path ) ;
2020-12-29 10:21:03 +08:00
goto finish ;
2019-05-16 13:18:45 +02:00
}
spa_log_debug ( monitor - > log , " Properties changed in transport %s " , path ) ;
transport_update_props ( transport , & it [ 1 ] , NULL ) ;
}
2022-07-22 11:01:18 +02:00
}
2019-05-16 13:18:45 +02:00
2020-12-29 10:21:03 +08:00
finish :
2019-05-16 13:18:45 +02:00
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED ;
}
static void add_filters ( struct spa_bt_monitor * this )
{
2019-09-20 13:04:14 +02:00
if ( this - > filters_added )
return ;
2019-05-16 13:18:45 +02:00
if ( ! dbus_connection_add_filter ( this - > conn , filter_cb , this , NULL ) ) {
spa_log_error ( this - > log , " failed to add filter function " ) ;
2023-07-11 20:44:23 +02:00
return ;
2019-05-16 13:18:45 +02:00
}
2023-07-11 20:44:23 +02:00
spa_auto ( DBusError ) err = DBUS_ERROR_INIT ;
2019-05-16 13:18:45 +02:00
dbus_bus_add_match ( this - > conn ,
" type='signal',sender='org.freedesktop.DBus', "
" interface='org.freedesktop.DBus',member='NameOwnerChanged', "
" arg0=' " BLUEZ_SERVICE " ' " , & err ) ;
2021-02-17 18:41:24 +01:00
# ifdef HAVE_BLUEZ_5_BACKEND_OFONO
dbus_bus_add_match ( this - > conn ,
" type='signal',sender='org.freedesktop.DBus', "
" interface='org.freedesktop.DBus',member='NameOwnerChanged', "
" arg0=' " OFONO_SERVICE " ' " , & err ) ;
# endif
# ifdef HAVE_BLUEZ_5_BACKEND_HSPHFPD
dbus_bus_add_match ( this - > conn ,
" type='signal',sender='org.freedesktop.DBus', "
" interface='org.freedesktop.DBus',member='NameOwnerChanged', "
" arg0=' " HSPHFPD_SERVICE " ' " , & err ) ;
# endif
2019-05-16 13:18:45 +02:00
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded' " , & err ) ;
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved' " , & err ) ;
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', "
" arg0=' " BLUEZ_ADAPTER_INTERFACE " ' " , & err ) ;
2023-01-22 14:45:32 +02:00
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', "
" arg0=' " BLUEZ_MEDIA_INTERFACE " ' " , & err ) ;
2019-05-16 13:18:45 +02:00
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', "
" arg0=' " BLUEZ_DEVICE_INTERFACE " ' " , & err ) ;
2023-03-05 01:03:13 +02:00
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', "
" arg0=' " BLUEZ_DEVICE_SET_INTERFACE " ' " , & err ) ;
2021-01-24 20:38:13 +02:00
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', "
" arg0=' " BLUEZ_MEDIA_ENDPOINT_INTERFACE " ' " , & err ) ;
2019-05-16 13:18:45 +02:00
dbus_bus_add_match ( this - > conn ,
" type='signal',sender=' " BLUEZ_SERVICE " ', "
" interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', "
" arg0=' " BLUEZ_MEDIA_TRANSPORT_INTERFACE " ' " , & err ) ;
2019-09-20 13:04:14 +02:00
this - > filters_added = true ;
2019-05-16 13:18:45 +02:00
}
2018-01-11 10:23:37 +01:00
static int
2019-09-20 13:04:14 +02:00
impl_device_add_listener ( void * object , struct spa_hook * listener ,
const struct spa_device_events * events , void * data )
2018-01-11 10:23:37 +01:00
{
2019-05-20 16:11:23 +02:00
struct spa_bt_monitor * this = object ;
2021-11-17 11:56:37 +01:00
struct spa_hook_list save ;
2018-01-11 10:23:37 +01:00
2019-05-20 16:11:23 +02:00
spa_return_val_if_fail ( this ! = NULL , - EINVAL ) ;
2019-09-20 13:04:14 +02:00
spa_return_val_if_fail ( events ! = NULL , - EINVAL ) ;
2018-01-11 10:23:37 +01:00
2021-11-17 11:56:37 +01:00
spa_hook_list_isolate ( & this - > hooks , & save , listener , events , data ) ;
2018-01-11 10:23:37 +01:00
2019-09-20 13:04:14 +02:00
add_filters ( this ) ;
2022-10-08 01:23:13 +02:00
get_managed_objects ( this ) ;
2019-09-20 13:04:14 +02:00
2022-10-08 18:16:25 +02:00
struct spa_bt_device * device ;
spa_list_for_each ( device , & this - > device_list , link ) {
if ( device - > added )
emit_device_info ( this , device , this - > connection_info_supported ) ;
}
2021-11-17 11:56:37 +01:00
spa_hook_list_join ( & this - > hooks , & save ) ;
2018-01-11 10:23:37 +01:00
return 0 ;
}
2019-09-20 13:04:14 +02:00
static const struct spa_device_methods impl_device = {
SPA_VERSION_DEVICE_METHODS ,
. add_listener = impl_device_add_listener ,
2018-01-11 10:23:37 +01:00
} ;
2019-12-19 13:15:10 +01:00
static int impl_get_interface ( struct spa_handle * handle , const char * type , void * * interface )
2018-01-11 10:23:37 +01:00
{
struct spa_bt_monitor * this ;
spa_return_val_if_fail ( handle ! = NULL , - EINVAL ) ;
spa_return_val_if_fail ( interface ! = NULL , - EINVAL ) ;
this = ( struct spa_bt_monitor * ) handle ;
2021-05-18 11:36:13 +10:00
if ( spa_streq ( type , SPA_TYPE_INTERFACE_Device ) )
2019-09-20 13:04:14 +02:00
* interface = & this - > device ;
2019-12-19 13:15:10 +01:00
else
2018-01-11 10:23:37 +01:00
return - ENOENT ;
2019-12-19 13:15:10 +01:00
2018-01-11 10:23:37 +01:00
return 0 ;
}
static int impl_clear ( struct spa_handle * handle )
{
2020-06-05 15:48:57 +02:00
struct spa_bt_monitor * monitor ;
struct spa_bt_adapter * a ;
struct spa_bt_device * d ;
2021-01-24 20:38:13 +02:00
struct spa_bt_remote_endpoint * ep ;
2020-06-05 15:48:57 +02:00
struct spa_bt_transport * t ;
2024-04-22 09:35:49 +03:00
struct spa_bt_big * b ;
2022-06-04 19:01:51 +03:00
const struct spa_dict_item * it ;
2021-08-29 18:22:41 +03:00
size_t i ;
2020-06-05 15:48:57 +02:00
monitor = ( struct spa_bt_monitor * ) handle ;
2021-04-10 18:10:37 +03:00
/*
* We don ' t call BlueZ API unregister methods here , since BlueZ generally does the
* unregistration when the DBus connection is closed below . We ' ll unregister DBus
* object managers and filter callbacks though .
*/
2020-12-29 10:21:03 +08:00
unregister_media_application ( monitor ) ;
2021-04-10 18:10:37 +03:00
if ( monitor - > filters_added ) {
dbus_connection_remove_filter ( monitor - > conn , filter_cb , monitor ) ;
monitor - > filters_added = false ;
}
2023-07-11 17:01:00 +02:00
cancel_and_unref ( & monitor - > get_managed_objects_call ) ;
2022-10-08 01:11:09 +02:00
2020-06-05 15:48:57 +02:00
spa_list_consume ( t , & monitor - > transport_list , link )
2020-07-17 15:18:10 +02:00
spa_bt_transport_free ( t ) ;
2021-01-24 20:38:13 +02:00
spa_list_consume ( ep , & monitor - > remote_endpoint_list , link )
remote_endpoint_free ( ep ) ;
2020-06-05 15:48:57 +02:00
spa_list_consume ( d , & monitor - > device_list , link )
device_free ( d ) ;
spa_list_consume ( a , & monitor - > adapter_list , link )
adapter_free ( a ) ;
2024-04-22 09:35:49 +03:00
spa_list_consume ( b , & monitor - > bcast_source_config_list , link )
big_entry_free ( b ) ;
2020-06-05 15:48:57 +02:00
2021-08-29 18:22:41 +03:00
for ( i = 0 ; i < SPA_N_ELEMENTS ( monitor - > backends ) ; + + i ) {
spa_bt_backend_free ( monitor - > backends [ i ] ) ;
monitor - > backends [ i ] = NULL ;
2020-07-22 17:40:32 +02:00
}
2022-06-04 19:01:51 +03:00
spa_dict_for_each ( it , & monitor - > global_settings ) {
free ( ( void * ) it - > key ) ;
free ( ( void * ) it - > value ) ;
}
2021-01-30 19:31:52 +01:00
free ( ( void * ) monitor - > enabled_codecs . items ) ;
2021-01-30 18:15:58 +02:00
spa_zero ( monitor - > enabled_codecs ) ;
2021-05-17 18:19:44 +03:00
dbus_connection_unref ( monitor - > conn ) ;
2021-04-10 18:10:37 +03:00
spa_dbus_connection_destroy ( monitor - > dbus_connection ) ;
monitor - > dbus_connection = NULL ;
monitor - > conn = NULL ;
monitor - > objects_listed = false ;
monitor - > connection_info_supported = false ;
2021-08-29 18:22:41 +03:00
monitor - > backend = NULL ;
monitor - > backend_selection = BACKEND_NATIVE ;
2021-04-10 18:10:37 +03:00
2021-01-24 15:15:27 +02:00
spa_bt_quirks_destroy ( monitor - > quirks ) ;
2022-06-15 17:24:41 +02:00
free_media_codecs ( monitor - > media_codecs ) ;
2021-09-01 00:33:43 +03:00
2018-01-11 10:23:37 +01:00
return 0 ;
}
2018-04-09 10:06:17 +02:00
static size_t
impl_get_size ( const struct spa_handle_factory * factory ,
const struct spa_dict * params )
{
return sizeof ( struct spa_bt_monitor ) ;
}
2021-03-26 12:53:04 +08:00
int spa_bt_profiles_from_json_array ( const char * str )
{
struct spa_json it , it_array ;
char role_name [ 256 ] ;
enum spa_bt_profile profiles = SPA_BT_PROFILE_NULL ;
spa_json_init ( & it , str , strlen ( str ) ) ;
if ( spa_json_enter_array ( & it , & it_array ) < = 0 )
return - EINVAL ;
while ( spa_json_get_string ( & it_array , role_name , sizeof ( role_name ) ) > 0 ) {
2021-05-18 11:36:13 +10:00
if ( spa_streq ( role_name , " hsp_hs " ) ) {
2021-03-26 12:53:04 +08:00
profiles | = SPA_BT_PROFILE_HSP_HS ;
2021-05-18 11:36:13 +10:00
} else if ( spa_streq ( role_name , " hsp_ag " ) ) {
2021-03-26 12:53:04 +08:00
profiles | = SPA_BT_PROFILE_HSP_AG ;
2021-05-18 11:36:13 +10:00
} else if ( spa_streq ( role_name , " hfp_hf " ) ) {
2021-03-26 12:53:04 +08:00
profiles | = SPA_BT_PROFILE_HFP_HF ;
2021-05-18 11:36:13 +10:00
} else if ( spa_streq ( role_name , " hfp_ag " ) ) {
2021-03-26 12:53:04 +08:00
profiles | = SPA_BT_PROFILE_HFP_AG ;
2021-05-18 11:36:13 +10:00
} else if ( spa_streq ( role_name , " a2dp_sink " ) ) {
2021-03-26 12:53:04 +08:00
profiles | = SPA_BT_PROFILE_A2DP_SINK ;
2021-05-18 11:36:13 +10:00
} else if ( spa_streq ( role_name , " a2dp_source " ) ) {
2021-03-26 12:53:04 +08:00
profiles | = SPA_BT_PROFILE_A2DP_SOURCE ;
2022-06-16 17:15:14 +02:00
} else if ( spa_streq ( role_name , " bap_sink " ) ) {
profiles | = SPA_BT_PROFILE_BAP_SINK ;
} else if ( spa_streq ( role_name , " bap_source " ) ) {
profiles | = SPA_BT_PROFILE_BAP_SOURCE ;
2023-08-02 11:19:06 +03:00
} else if ( spa_streq ( role_name , " bap_bcast_source " ) ) {
profiles | = SPA_BT_PROFILE_BAP_BROADCAST_SOURCE ;
2023-08-17 18:52:48 +03:00
} else if ( spa_streq ( role_name , " bap_bcast_sink " ) ) {
profiles | = SPA_BT_PROFILE_BAP_BROADCAST_SINK ;
2021-03-26 12:53:04 +08:00
}
}
return profiles ;
}
2023-03-16 20:40:17 +02:00
static int parse_roles ( struct spa_bt_monitor * monitor , const struct spa_dict * info )
{
const char * str ;
int res = 0 ;
int profiles = SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE ;
/* HSP/HFP backends parse this property separately */
if ( info & & ( str = spa_dict_lookup ( info , " bluez5.roles " ) ) ) {
res = spa_bt_profiles_from_json_array ( str ) ;
if ( res < 0 ) {
spa_log_warn ( monitor - > log , " malformed bluez5.roles setting ignored " ) ;
goto done ;
}
profiles & = res ;
}
res = 0 ;
done :
monitor - > enabled_profiles = profiles ;
return res ;
}
2024-04-22 09:35:49 +03:00
static void parse_broadcast_source_config ( struct spa_bt_monitor * monitor , const struct spa_dict * info )
{
2024-05-10 16:25:50 +03:00
const char * str ;
2024-04-22 09:35:49 +03:00
char key [ 256 ] ;
char bis_key [ 256 ] ;
char qos_key [ 256 ] ;
int cursor ;
int big_id = 0 ;
struct spa_json it [ 4 ] , it_array [ 4 ] ;
2024-05-10 16:25:50 +03:00
struct spa_list big_list = SPA_LIST_INIT ( & big_list ) ;
2024-05-10 20:15:18 +03:00
struct spa_error_location loc ;
struct spa_bt_big * big ;
2024-04-22 09:35:49 +03:00
/* Search for bluez5.bcast_source.config */
2024-05-10 20:15:18 +03:00
if ( ! ( info & & ( str = spa_dict_lookup ( info , " bluez5.bcast_source.config " ) ) ) )
return ;
spa_json_init ( & it [ 0 ] , str , strlen ( str ) ) ;
/* Verify is an array of BIGS */
if ( spa_json_enter_array ( & it [ 0 ] , & it_array [ 0 ] ) < = 0 )
goto parse_failed ;
/* Iterate on all BIG objects */
while ( spa_json_enter_object ( & it_array [ 0 ] , & it [ 1 ] ) > 0 ) {
struct spa_bt_big * big_entry = calloc ( 1 , sizeof ( struct spa_bt_big ) ) ;
if ( ! big_entry )
goto errno_failed ;
big_entry - > big_id = big_id + + ;
spa_list_init ( & big_entry - > bis_list ) ;
spa_list_append ( & big_list , & big_entry - > link ) ;
/* Iterate on all BIG values */
while ( spa_json_get_string ( & it [ 1 ] , key , sizeof ( key ) ) > 0 ) {
if ( spa_streq ( key , " broadcast_code " ) ) {
if ( spa_json_enter_array ( & it [ 1 ] , & it_array [ 1 ] ) < = 0 )
goto parse_failed ;
for ( cursor = 0 ; cursor < BROADCAST_CODE_LEN ; cursor + + ) {
if ( spa_json_get_int ( & it_array [ 1 ] , & big_entry - > broadcast_code [ cursor ] ) < = 0 )
2024-04-22 09:35:49 +03:00
goto parse_failed ;
2024-05-10 20:15:18 +03:00
spa_log_debug ( monitor - > log , " big_entry->broadcast_code[%d] %d " , cursor , big_entry - > broadcast_code [ cursor ] ) ;
}
} else if ( spa_streq ( key , " bis " ) ) {
if ( spa_json_enter_array ( & it [ 1 ] , & it_array [ 1 ] ) < = 0 )
goto parse_failed ;
while ( spa_json_enter_object ( & it_array [ 1 ] , & it [ 2 ] ) > 0 ) {
/* Iterate on all BIS values */
struct spa_bt_bis * bis_entry = calloc ( 1 , sizeof ( struct spa_bt_bis ) ) ;
if ( ! bis_entry )
goto errno_failed ;
spa_list_init ( & bis_entry - > metadata_list ) ;
spa_list_append ( & big_entry - > bis_list , & bis_entry - > link ) ;
while ( spa_json_get_string ( & it [ 2 ] , bis_key , sizeof ( bis_key ) ) > 0 ) {
if ( spa_streq ( bis_key , " qos_preset " ) ) {
if ( spa_json_get_string ( & it [ 2 ] , bis_entry - > qos_preset , sizeof ( bis_entry - > qos_preset ) ) < = 0 )
goto parse_failed ;
spa_log_debug ( monitor - > log , " bis_entry->qos_preset %s " , bis_entry - > qos_preset ) ;
} else if ( spa_streq ( bis_key , " audio_channel_allocation " ) ) {
if ( spa_json_get_int ( & it [ 2 ] , & bis_entry - > channel_allocation ) < = 0 )
goto parse_failed ;
spa_log_debug ( monitor - > log , " bis_entry->channel_allocation %d " , bis_entry - > channel_allocation ) ;
} else if ( spa_streq ( bis_key , " metadata " ) ) {
if ( spa_json_enter_array ( & it [ 2 ] , & it_array [ 2 ] ) < = 0 )
goto parse_failed ;
while ( spa_json_enter_object ( & it_array [ 2 ] , & it [ 3 ] ) > 0 ) {
struct spa_bt_metadata * metadata_entry = calloc ( 1 , sizeof ( struct spa_bt_metadata ) ) ;
if ( ! metadata_entry )
goto errno_failed ;
spa_list_append ( & bis_entry - > metadata_list , & metadata_entry - > link ) ;
while ( spa_json_get_string ( & it [ 3 ] , qos_key , sizeof ( qos_key ) ) > 0 ) {
if ( spa_streq ( qos_key , " type " ) ) {
if ( spa_json_get_int ( & it [ 3 ] , & metadata_entry - > type ) < = 0 )
2024-04-22 09:35:49 +03:00
goto parse_failed ;
2024-05-10 20:15:18 +03:00
spa_log_debug ( monitor - > log , " metadata_entry->type %d " , metadata_entry - > type ) ;
} else if ( spa_streq ( qos_key , " value " ) ) {
if ( spa_json_enter_array ( & it [ 3 ] , & it_array [ 3 ] ) < = 0 )
goto parse_failed ;
2024-05-13 18:01:12 +03:00
for ( cursor = 0 ; cursor < METADATA_MAX_LEN - 1 ; cursor + + ) {
2024-05-10 20:15:18 +03:00
int temp_val = 0 ;
if ( spa_json_get_int ( & it_array [ 3 ] , & temp_val ) < = 0 )
break ;
metadata_entry - > value [ cursor ] = ( uint8_t ) temp_val ;
spa_log_debug ( monitor - > log , " metadata_entry->value[%d] %d " , cursor , metadata_entry - > value [ cursor ] ) ;
2024-04-22 09:35:49 +03:00
}
2024-05-10 20:15:18 +03:00
/* length is size of value plus 1 octet for type */
metadata_entry - > length = cursor + 1 ;
spa_log_debug ( monitor - > log , " metadata_entry->length %d " , metadata_entry - > length ) ;
spa_log_debug ( monitor - > log , " metadata_entry->value_size %d " , cursor ) ;
2024-04-22 09:35:49 +03:00
}
}
}
}
}
}
}
}
}
2024-05-10 16:25:50 +03:00
spa_list_insert_list ( & monitor - > bcast_source_config_list , & big_list ) ;
2024-04-22 09:35:49 +03:00
return ;
2024-05-10 20:15:18 +03:00
errno_failed :
spa_log_warn ( monitor - > log , " failed in bluez5.bcast_source.config: %m " ) ;
goto cleanup ;
2024-05-10 16:25:50 +03:00
2024-05-10 20:15:18 +03:00
parse_failed :
2024-05-10 16:25:50 +03:00
str = spa_dict_lookup ( info , " bluez5.bcast_source.config " ) ;
if ( spa_json_get_error ( & it [ 0 ] , str , & loc ) ) {
spa_debug_log_error_location ( monitor - > log , SPA_LOG_LEVEL_WARN ,
& loc , " malformed bluez5.bcast_source.config: %s " , loc . reason ) ;
2024-05-10 20:15:18 +03:00
} else {
spa_log_warn ( monitor - > log , " malformed bluez5.bcast_source.config " ) ;
2024-05-10 16:25:50 +03:00
}
2024-05-10 20:15:18 +03:00
goto cleanup ;
2024-05-10 16:25:50 +03:00
2024-05-10 20:15:18 +03:00
cleanup :
spa_list_consume ( big , & big_list , link )
big_entry_free ( big ) ;
2024-04-22 09:35:49 +03:00
}
2021-01-30 18:15:58 +02:00
static int parse_codec_array ( struct spa_bt_monitor * this , const struct spa_dict * info )
{
2022-06-15 17:24:41 +02:00
const struct media_codec * const * const media_codecs = this - > media_codecs ;
2021-01-30 18:15:58 +02:00
const char * str ;
struct spa_dict_item * codecs ;
struct spa_json it , it_array ;
char codec_name [ 256 ] ;
size_t num_codecs ;
int i ;
/* Parse bluez5.codecs property to a dict of enabled codecs */
num_codecs = 0 ;
2022-06-15 17:24:41 +02:00
while ( media_codecs [ num_codecs ] )
2021-01-30 18:15:58 +02:00
+ + num_codecs ;
codecs = calloc ( num_codecs , sizeof ( struct spa_dict_item ) ) ;
if ( codecs = = NULL )
return - ENOMEM ;
2021-02-16 17:57:21 +01:00
if ( info = = NULL | | ( str = spa_dict_lookup ( info , " bluez5.codecs " ) ) = = NULL )
2021-01-30 18:15:58 +02:00
goto fallback ;
spa_json_init ( & it , str , strlen ( str ) ) ;
if ( spa_json_enter_array ( & it , & it_array ) < = 0 ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( this - > log , " property bluez5.codecs '%s' is not an array " , str ) ;
2021-01-30 18:15:58 +02:00
goto fallback ;
}
this - > enabled_codecs = SPA_DICT_INIT ( codecs , 0 ) ;
while ( spa_json_get_string ( & it_array , codec_name , sizeof ( codec_name ) ) > 0 ) {
int i ;
2022-06-15 17:24:41 +02:00
for ( i = 0 ; media_codecs [ i ] ; + + i ) {
const struct media_codec * codec = media_codecs [ i ] ;
2021-01-30 18:15:58 +02:00
2021-05-18 11:43:49 +10:00
if ( ! spa_streq ( codec - > name , codec_name ) )
2021-01-30 18:15:58 +02:00
continue ;
if ( spa_dict_lookup_item ( & this - > enabled_codecs , codec - > name ) ! = NULL )
continue ;
2021-10-01 19:03:49 +03:00
spa_log_debug ( this - > log , " enabling codec %s " , codec - > name ) ;
2021-01-30 18:15:58 +02:00
spa_assert ( this - > enabled_codecs . n_items < num_codecs ) ;
codecs [ this - > enabled_codecs . n_items ] . key = codec - > name ;
codecs [ this - > enabled_codecs . n_items ] . value = " true " ;
+ + this - > enabled_codecs . n_items ;
break ;
}
}
spa_dict_qsort ( & this - > enabled_codecs ) ;
2022-06-15 17:24:41 +02:00
for ( i = 0 ; media_codecs [ i ] ; + + i ) {
const struct media_codec * codec = media_codecs [ i ] ;
if ( ! is_media_codec_enabled ( this , codec ) )
2021-10-01 19:03:49 +03:00
spa_log_debug ( this - > log , " disabling codec %s " , codec - > name ) ;
2021-01-30 18:15:58 +02:00
}
return 0 ;
fallback :
2022-06-15 17:24:41 +02:00
for ( i = 0 ; media_codecs [ i ] ; + + i ) {
const struct media_codec * codec = media_codecs [ i ] ;
2021-10-01 19:03:49 +03:00
spa_log_debug ( this - > log , " enabling codec %s " , codec - > name ) ;
2021-01-30 18:15:58 +02:00
codecs [ i ] . key = codec - > name ;
codecs [ i ] . value = " true " ;
}
this - > enabled_codecs = SPA_DICT_INIT ( codecs , i ) ;
spa_dict_qsort ( & this - > enabled_codecs ) ;
return 0 ;
}
2022-06-04 19:01:51 +03:00
static void get_global_settings ( struct spa_bt_monitor * this , const struct spa_dict * dict )
{
uint32_t n_items = 0 ;
uint32_t i ;
if ( dict = = NULL ) {
this - > global_settings = SPA_DICT_INIT ( this - > global_setting_items , 0 ) ;
return ;
}
for ( i = 0 ; i < dict - > n_items & & n_items < SPA_N_ELEMENTS ( this - > global_setting_items ) ; i + + ) {
const struct spa_dict_item * it = & dict - > items [ i ] ;
if ( spa_strstartswith ( it - > key , " bluez5. " ) & & it - > value ! = NULL )
this - > global_setting_items [ n_items + + ] =
SPA_DICT_ITEM_INIT ( strdup ( it - > key ) , strdup ( it - > value ) ) ;
}
this - > global_settings = SPA_DICT_INIT ( this - > global_setting_items , n_items ) ;
}
2018-01-11 10:23:37 +01:00
static int
impl_init ( const struct spa_handle_factory * factory ,
struct spa_handle * handle ,
const struct spa_dict * info ,
const struct spa_support * support ,
uint32_t n_support )
{
struct spa_bt_monitor * this ;
2021-01-30 18:15:58 +02:00
int res ;
2018-01-11 10:23:37 +01:00
spa_return_val_if_fail ( factory ! = NULL , - EINVAL ) ;
spa_return_val_if_fail ( handle ! = NULL , - EINVAL ) ;
handle - > get_interface = impl_get_interface ;
handle - > clear = impl_clear ;
this = ( struct spa_bt_monitor * ) handle ;
2019-12-19 13:15:10 +01:00
this - > log = spa_support_find ( support , n_support , SPA_TYPE_INTERFACE_Log ) ;
this - > dbus = spa_support_find ( support , n_support , SPA_TYPE_INTERFACE_DBus ) ;
this - > main_loop = spa_support_find ( support , n_support , SPA_TYPE_INTERFACE_Loop ) ;
2023-03-25 16:58:27 +02:00
this - > data_loop = spa_support_find ( support , n_support , SPA_TYPE_INTERFACE_DataLoop ) ;
2019-12-19 13:15:10 +01:00
this - > main_system = spa_support_find ( support , n_support , SPA_TYPE_INTERFACE_System ) ;
2023-03-25 16:58:27 +02:00
this - > data_system = spa_support_find ( support , n_support , SPA_TYPE_INTERFACE_DataSystem ) ;
2021-09-01 00:33:43 +03:00
this - > plugin_loader = spa_support_find ( support , n_support , SPA_TYPE_INTERFACE_PluginLoader ) ;
2019-12-19 13:15:10 +01:00
2021-10-01 19:03:49 +03:00
spa_log_topic_init ( this - > log , & log_topic ) ;
2018-01-11 10:23:37 +01:00
if ( this - > dbus = = NULL ) {
spa_log_error ( this - > log , " a dbus is needed " ) ;
return - EINVAL ;
}
2021-09-01 00:33:43 +03:00
if ( this - > plugin_loader = = NULL ) {
spa_log_error ( this - > log , " a plugin loader is needed " ) ;
return - EINVAL ;
}
2022-06-15 17:24:41 +02:00
this - > media_codecs = NULL ;
2021-09-01 00:33:43 +03:00
this - > quirks = NULL ;
2021-09-04 17:13:08 +03:00
this - > conn = NULL ;
2021-09-01 00:33:43 +03:00
this - > dbus_connection = NULL ;
2022-06-15 17:24:41 +02:00
this - > media_codecs = load_media_codecs ( this - > plugin_loader , this - > log ) ;
if ( this - > media_codecs = = NULL ) {
spa_log_error ( this - > log , " failed to load required media codec plugins " ) ;
2021-09-01 00:33:43 +03:00
res = - EIO ;
goto fail ;
}
2021-01-24 15:15:27 +02:00
this - > quirks = spa_bt_quirks_create ( info , this - > log ) ;
if ( this - > quirks = = NULL ) {
2021-10-01 19:03:49 +03:00
spa_log_error ( this - > log , " failed to parse quirk table " ) ;
2021-09-01 00:33:43 +03:00
res = - EINVAL ;
goto fail ;
2021-01-24 15:15:27 +02:00
}
2020-04-04 19:59:27 +02:00
this - > dbus_connection = spa_dbus_get_connection ( this - > dbus , SPA_DBUS_TYPE_SYSTEM ) ;
2018-01-11 10:23:37 +01:00
if ( this - > dbus_connection = = NULL ) {
2018-02-08 10:02:17 +01:00
spa_log_error ( this - > log , " no dbus connection " ) ;
2021-09-01 00:33:43 +03:00
res = - EIO ;
goto fail ;
2018-01-11 10:23:37 +01:00
}
this - > conn = spa_dbus_connection_get ( this - > dbus_connection ) ;
2021-05-17 18:19:44 +03:00
if ( this - > conn = = NULL ) {
spa_log_error ( this - > log , " failed to get dbus connection " ) ;
2021-09-01 00:33:43 +03:00
res = - EIO ;
goto fail ;
2021-05-17 18:19:44 +03:00
}
/* XXX: We should handle spa_dbus reconnecting, but we don't, so ref
* XXX : the handle so that we can keep it if spa_dbus unrefs it .
*/
dbus_connection_ref ( this - > conn ) ;
2018-01-11 10:23:37 +01:00
2019-09-20 13:04:14 +02:00
spa_hook_list_init ( & this - > hooks ) ;
this - > device . iface = SPA_INTERFACE_INIT (
SPA_TYPE_INTERFACE_Device ,
SPA_VERSION_DEVICE ,
& impl_device , this ) ;
2018-01-11 10:23:37 +01:00
spa_list_init ( & this - > adapter_list ) ;
spa_list_init ( & this - > device_list ) ;
2021-01-24 20:38:13 +02:00
spa_list_init ( & this - > remote_endpoint_list ) ;
2018-01-11 10:23:37 +01:00
spa_list_init ( & this - > transport_list ) ;
2024-04-22 09:35:49 +03:00
spa_list_init ( & this - > bcast_source_config_list ) ;
2018-01-11 10:23:37 +01:00
2021-01-30 18:15:58 +02:00
if ( ( res = parse_codec_array ( this , info ) ) < 0 )
2021-09-01 00:33:43 +03:00
goto fail ;
2021-01-30 18:15:58 +02:00
2023-03-16 20:40:17 +02:00
parse_roles ( this , info ) ;
2024-04-22 09:35:49 +03:00
parse_broadcast_source_config ( this , info ) ;
2023-03-16 20:40:17 +02:00
2021-05-03 15:40:13 +08:00
this - > default_audio_info . rate = A2DP_CODEC_DEFAULT_RATE ;
this - > default_audio_info . channels = A2DP_CODEC_DEFAULT_CHANNELS ;
2021-08-29 18:22:41 +03:00
this - > backend_selection = BACKEND_NATIVE ;
2022-06-04 19:01:51 +03:00
get_global_settings ( this , info ) ;
2021-01-08 13:00:44 +01:00
if ( info ) {
const char * str ;
2021-05-03 15:40:13 +08:00
uint32_t tmp ;
2021-01-08 13:00:44 +01:00
2021-03-16 10:56:27 +08:00
if ( ( str = spa_dict_lookup ( info , " api.bluez5.connection-info " ) ) ! = NULL & &
2021-05-18 15:18:14 +10:00
spa_atob ( str ) )
2021-03-16 10:56:27 +08:00
this - > connection_info_supported = true ;
2021-05-03 15:40:13 +08:00
if ( ( str = spa_dict_lookup ( info , " bluez5.default.rate " ) ) ! = NULL & &
( tmp = atoi ( str ) ) > 0 )
this - > default_audio_info . rate = tmp ;
if ( ( str = spa_dict_lookup ( info , " bluez5.default.channels " ) ) ! = NULL & &
( ( tmp = atoi ( str ) ) > 0 ) )
this - > default_audio_info . channels = tmp ;
2021-08-29 18:22:41 +03:00
if ( ( str = spa_dict_lookup ( info , " bluez5.hfphsp-backend " ) ) ! = NULL ) {
if ( spa_streq ( str , " none " ) )
this - > backend_selection = BACKEND_NONE ;
else if ( spa_streq ( str , " any " ) )
this - > backend_selection = BACKEND_ANY ;
else if ( spa_streq ( str , " ofono " ) )
this - > backend_selection = BACKEND_OFONO ;
else if ( spa_streq ( str , " hsphfpd " ) )
this - > backend_selection = BACKEND_HSPHFPD ;
else if ( spa_streq ( str , " native " ) )
this - > backend_selection = BACKEND_NATIVE ;
}
2021-10-09 19:11:51 +03:00
if ( ( str = spa_dict_lookup ( info , " bluez5.dummy-avrcp-player " ) ) ! = NULL )
this - > dummy_avrcp_player = spa_atob ( str ) ;
else
2022-06-07 18:13:06 +03:00
this - > dummy_avrcp_player = false ;
2021-01-08 13:00:44 +01:00
}
2021-03-06 14:46:57 +02:00
register_media_application ( this ) ;
2021-09-24 22:50:34 +03:00
/* Create backends. They're started after we get a reply from Bluez. */
2021-08-29 18:22:41 +03:00
this - > backends [ BACKEND_NATIVE ] = backend_native_new ( this , this - > conn , info , this - > quirks , support , n_support ) ;
this - > backends [ BACKEND_OFONO ] = backend_ofono_new ( this , this - > conn , info , this - > quirks , support , n_support ) ;
this - > backends [ BACKEND_HSPHFPD ] = backend_hsphfpd_new ( this , this - > conn , info , this - > quirks , support , n_support ) ;
2020-07-17 15:18:10 +02:00
2018-01-11 10:23:37 +01:00
return 0 ;
2021-09-01 00:33:43 +03:00
fail :
2022-06-15 17:24:41 +02:00
if ( this - > media_codecs )
free_media_codecs ( this - > media_codecs ) ;
2021-09-01 00:33:43 +03:00
if ( this - > quirks )
spa_bt_quirks_destroy ( this - > quirks ) ;
2021-09-04 17:13:08 +03:00
if ( this - > conn )
dbus_connection_unref ( this - > conn ) ;
2021-09-01 00:33:43 +03:00
if ( this - > dbus_connection )
spa_dbus_connection_destroy ( this - > dbus_connection ) ;
2022-06-15 17:24:41 +02:00
this - > media_codecs = NULL ;
2021-09-01 00:33:43 +03:00
this - > quirks = NULL ;
2021-09-04 17:13:08 +03:00
this - > conn = NULL ;
2021-09-01 00:33:43 +03:00
this - > dbus_connection = NULL ;
return res ;
2018-01-11 10:23:37 +01:00
}
static const struct spa_interface_info impl_interfaces [ ] = {
2019-09-20 13:04:14 +02:00
{ SPA_TYPE_INTERFACE_Device , } ,
2018-01-11 10:23:37 +01:00
} ;
static int
impl_enum_interface_info ( const struct spa_handle_factory * factory ,
const struct spa_interface_info * * info ,
uint32_t * index )
{
spa_return_val_if_fail ( factory ! = NULL , - EINVAL ) ;
spa_return_val_if_fail ( info ! = NULL , - EINVAL ) ;
spa_return_val_if_fail ( index ! = NULL , - EINVAL ) ;
if ( * index > = SPA_N_ELEMENTS ( impl_interfaces ) )
return 0 ;
* info = & impl_interfaces [ ( * index ) + + ] ;
return 1 ;
}
2019-09-20 13:04:14 +02:00
const struct spa_handle_factory spa_bluez5_dbus_factory = {
2019-05-20 16:11:23 +02:00
SPA_VERSION_HANDLE_FACTORY ,
2019-09-20 13:04:14 +02:00
SPA_NAME_API_BLUEZ5_ENUM_DBUS ,
2018-01-11 10:23:37 +01:00
NULL ,
2018-04-09 10:06:17 +02:00
impl_get_size ,
2018-01-11 10:23:37 +01:00
impl_init ,
impl_enum_interface_info ,
} ;
2021-03-11 11:00:11 +03:00
2021-04-28 20:29:44 +02:00
// Report battery percentage to BlueZ using experimental (BlueZ 5.56) Battery Provider API. No-op if no changes occurred.
2021-03-11 11:00:11 +03:00
int spa_bt_device_report_battery_level ( struct spa_bt_device * device , uint8_t percentage )
{
2021-04-05 14:58:50 +03:00
if ( percentage = = SPA_BT_NO_BATTERY ) {
battery_remove ( device ) ;
return 0 ;
}
2021-03-11 11:00:11 +03:00
// BlueZ likely is running without battery provider support, don't try to report battery
if ( device - > adapter - > battery_provider_unavailable ) return 0 ;
// If everything is initialized and battery level has not changed we don't need to send anything to BlueZ
if ( device - > adapter - > has_battery_provider & & device - > has_battery & & device - > battery = = percentage ) return 1 ;
device - > battery = percentage ;
if ( ! device - > adapter - > has_battery_provider ) {
// No provider: register it, create battery when registered
register_battery_provider ( device ) ;
} else if ( ! device - > has_battery ) {
// Have provider but no battery: create battery with correct percentage
battery_create ( device ) ;
} else {
// Just update existing battery percentage
battery_update ( device ) ;
}
return 1 ;
}