bluez5: add bluetoothAudioCodec property to device

Also make the "codecless" profiles to automatically switch to the codec
profiles.
This commit is contained in:
Pauli Virtanen 2021-03-21 02:38:26 +02:00 committed by Wim Taymans
parent 6b0cf799c4
commit a552655edc
6 changed files with 292 additions and 133 deletions

View file

@ -439,6 +439,7 @@ static int codec_increase_bitpool(void *data)
}
const struct a2dp_codec a2dp_codec_aac = {
.id = SPA_BLUETOOTH_AUDIO_CODEC_AAC,
.codec_id = A2DP_CODEC_MPEG24,
.name = "aac",
.description = "AAC",

View file

@ -340,6 +340,7 @@ static int codec_decode(void *data,
}
const struct a2dp_codec a2dp_codec_aptx = {
.id = SPA_BLUETOOTH_AUDIO_CODEC_APTX,
.codec_id = A2DP_CODEC_VENDOR,
.vendor = { .vendor_id = APTX_VENDOR_ID,
.codec_id = APTX_CODEC_ID },
@ -363,6 +364,7 @@ const struct a2dp_codec a2dp_codec_aptx = {
const struct a2dp_codec a2dp_codec_aptx_hd = {
.id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
.codec_id = A2DP_CODEC_VENDOR,
.vendor = { .vendor_id = APTX_HD_VENDOR_ID,
.codec_id = APTX_HD_CODEC_ID },

View file

@ -624,6 +624,7 @@ static int codec_encode(void *data,
}
const struct a2dp_codec a2dp_codec_ldac = {
.id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC,
.codec_id = A2DP_CODEC_VENDOR,
.vendor = { .vendor_id = LDAC_VENDOR_ID,
.codec_id = LDAC_CODEC_ID },

View file

@ -624,6 +624,7 @@ static int codec_decode(void *data,
}
const struct a2dp_codec a2dp_codec_sbc = {
.id = SPA_BLUETOOTH_AUDIO_CODEC_SBC,
.codec_id = A2DP_CODEC_SBC,
.name = "sbc",
.description = "SBC",
@ -646,6 +647,7 @@ const struct a2dp_codec a2dp_codec_sbc = {
};
const struct a2dp_codec a2dp_codec_sbc_xq = {
.id = SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ,
.codec_id = A2DP_CODEC_SBC,
.name = "sbc_xq",
.description = "SBC-XQ",

View file

@ -29,6 +29,7 @@
#include <stddef.h>
#include <spa/param/audio/format.h>
#include <spa/param/bluetooth/audio.h>
#include <spa/pod/pod.h>
#include <spa/pod/builder.h>
@ -325,6 +326,7 @@ static inline int a2dp_sbc_get_frequency(a2dp_sbc_t *config)
struct a2dp_codec_handle;
struct a2dp_codec {
enum spa_bluetooth_audio_codec id;
uint8_t codec_id;
a2dp_vendor_codec_t vendor;

View file

@ -44,6 +44,8 @@
#include <spa/pod/parser.h>
#include <spa/param/param.h>
#include <spa/param/audio/raw.h>
#include <spa/param/bluetooth/audio.h>
#include <spa/param/bluetooth/type-info.h>
#include <spa/debug/pod.h>
#include "defs.h"
@ -64,15 +66,13 @@ enum {
DEVICE_PROFILE_HSP_HFP = 3,
};
static const char default_device[] = "";
struct props {
char device[64];
enum spa_bluetooth_audio_codec codec;
};
static void reset_props(struct props *props)
{
strncpy(props->device, default_device, 64);
props->codec = 0;
}
struct node {
@ -107,7 +107,9 @@ struct impl {
#define IDX_Profile 1
#define IDX_EnumRoute 2
#define IDX_Route 3
struct spa_param_info params[4];
#define IDX_PropInfo 4
#define IDX_Props 5
struct spa_param_info params[6];
struct spa_hook_list hooks;
@ -117,8 +119,6 @@ struct impl {
struct spa_hook bt_dev_listener;
uint32_t profile;
const struct a2dp_codec *selected_a2dp_codec; /**< Codec wanted. NULL means any. */
int selected_hfp_codec;
unsigned int switching_codec:1;
uint32_t prev_bt_connected_profiles;
@ -146,6 +146,40 @@ static void init_node(struct impl *this, struct node *node, uint32_t id)
node->volumes[i] = 1.0;
}
static const struct a2dp_codec *get_a2dp_codec(enum spa_bluetooth_audio_codec id)
{
const struct a2dp_codec **c;
for (c = a2dp_codecs; *c; ++c)
if ((*c)->id == id)
return *c;
return NULL;
}
static unsigned int get_hfp_codec(enum spa_bluetooth_audio_codec id)
{
switch (id) {
case SPA_BLUETOOTH_AUDIO_CODEC_CVSD:
return HFP_AUDIO_CODEC_CVSD;
case SPA_BLUETOOTH_AUDIO_CODEC_MSBC:
return HFP_AUDIO_CODEC_MSBC;
default:
return 0;
}
}
static enum spa_bluetooth_audio_codec get_hfp_codec_id(unsigned int codec)
{
switch (codec) {
case HFP_AUDIO_CODEC_MSBC:
return SPA_BLUETOOTH_AUDIO_CODEC_MSBC;
case HFP_AUDIO_CODEC_CVSD:
return SPA_BLUETOOTH_AUDIO_CODEC_CVSD;
}
return SPA_ID_INVALID;
}
static const char *get_hfp_codec_description(unsigned int codec)
{
switch (codec) {
@ -226,32 +260,23 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t,
}
}
static struct spa_bt_transport *find_transport(struct impl *this, int profile, const struct a2dp_codec *a2dp_codec, unsigned int hfp_codec)
static struct spa_bt_transport *find_transport(struct impl *this, int profile, enum spa_bluetooth_audio_codec codec)
{
struct spa_bt_device *device = this->bt_dev;
struct spa_bt_transport *t;
const struct a2dp_codec **codecs;
size_t i, num_codecs;
const struct a2dp_codec *a2dp_codec;
unsigned int hfp_codec;
codecs = &a2dp_codec;
num_codecs = 1;
a2dp_codec = get_a2dp_codec(codec);
hfp_codec = get_hfp_codec(codec);
if (a2dp_codec == NULL && (profile == SPA_BT_PROFILE_A2DP_SOURCE || profile == SPA_BT_PROFILE_A2DP_SINK)) {
codecs = a2dp_codecs;
num_codecs = 0;
while (codecs[num_codecs] != NULL)
++num_codecs;
}
for (i = 0; i < num_codecs; ++i) {
spa_list_for_each(t, &device->transport_list, device_link) {
if ((t->profile & device->connected_profiles) &&
(t->profile & profile) == t->profile &&
(codecs[i] == NULL || t->a2dp_codec == codecs[i]) &&
(a2dp_codec == NULL || t->a2dp_codec == a2dp_codec) &&
(hfp_codec == 0 || t->codec == hfp_codec))
return t;
}
}
return NULL;
}
@ -334,10 +359,11 @@ static int emit_nodes(struct impl *this)
break;
case DEVICE_PROFILE_AG:
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) {
t = find_transport(this, SPA_BT_PROFILE_HFP_AG, NULL, 0);
t = find_transport(this, SPA_BT_PROFILE_HFP_AG, 0);
if (!t)
t = find_transport(this, SPA_BT_PROFILE_HSP_AG, NULL, 0);
t = find_transport(this, SPA_BT_PROFILE_HSP_AG, 0);
if (t) {
this->props.codec = get_hfp_codec_id(t->codec);
emit_dynamic_node(&this->dyn_sco_source, this, t,
0, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
emit_dynamic_node(&this->dyn_sco_sink, this, t,
@ -345,32 +371,39 @@ static int emit_nodes(struct impl *this)
}
}
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, this->selected_a2dp_codec, 0);
if (t)
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0);
if (t) {
this->props.codec = t->a2dp_codec->id;
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
}
}
break;
case DEVICE_PROFILE_A2DP:
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, this->selected_a2dp_codec, 0);
if (t)
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0);
if (t) {
this->props.codec = t->a2dp_codec->id;
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
}
}
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) {
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->selected_a2dp_codec, 0);
if (t)
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->props.codec);
if (t) {
this->props.codec = t->a2dp_codec->id;
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK);
}
}
break;
case DEVICE_PROFILE_HSP_HFP:
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) {
t = find_transport(this, SPA_BT_PROFILE_HFP_HF, NULL, this->selected_hfp_codec);
t = find_transport(this, SPA_BT_PROFILE_HFP_HF, this->props.codec);
if (!t)
t = find_transport(this, SPA_BT_PROFILE_HSP_HS, NULL, 0);
t = find_transport(this, SPA_BT_PROFILE_HSP_HS, 0);
if (t) {
this->props.codec = get_hfp_codec_id(t->codec);
emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_SCO_SINK);
}
@ -416,11 +449,11 @@ static void emit_remove_nodes(struct impl *this)
}
}
static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_codec *a2dp_codec, int hfp_codec)
static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_audio_codec codec)
{
if (this->profile == profile &&
(this->profile != DEVICE_PROFILE_A2DP || a2dp_codec == this->selected_a2dp_codec) &&
(this->profile != DEVICE_PROFILE_HSP_HFP || hfp_codec == this->selected_hfp_codec))
(this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) &&
(this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec))
return 0;
emit_remove_nodes(this);
@ -429,6 +462,7 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co
this->profile = profile;
this->prev_bt_connected_profiles = this->bt_dev->connected_profiles;
this->props.codec = codec;
/*
* A2DP: ensure there's a transport with the selected codec (NULL means any).
@ -437,8 +471,9 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co
*/
if (profile == DEVICE_PROFILE_A2DP && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) {
int ret;
const struct a2dp_codec *codec_list[2], **codecs;
const struct a2dp_codec *codec_list[2], **codecs, *a2dp_codec;
a2dp_codec = get_a2dp_codec(codec);
if (a2dp_codec == NULL) {
codecs = a2dp_codecs;
} else {
@ -448,7 +483,6 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co
}
this->switching_codec = true;
this->selected_a2dp_codec = a2dp_codec;
ret = spa_bt_device_ensure_a2dp_codec(this->bt_dev, codecs);
if (ret < 0) {
@ -457,13 +491,12 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co
} else {
return 0;
}
} else if (profile == DEVICE_PROFILE_HSP_HFP && hfp_codec != 0 && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_HFP_AG)) {
} else if (profile == DEVICE_PROFILE_HSP_HFP && get_hfp_codec(codec) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_HFP_AG)) {
int ret;
this->switching_codec = true;
this->selected_hfp_codec = hfp_codec;
ret = spa_bt_device_ensure_hfp_codec(this->bt_dev, hfp_codec);
ret = spa_bt_device_ensure_hfp_codec(this->bt_dev, get_hfp_codec(codec));
if (ret < 0) {
if (ret != -ENOTSUP)
spa_log_error(this->log, NAME": failed to switch codec (%d), setting basic profile", ret);
@ -473,14 +506,15 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co
}
this->switching_codec = false;
this->selected_a2dp_codec = NULL;
this->selected_hfp_codec = 0;
this->props.codec = 0;
emit_nodes(this);
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_PropInfo].flags ^= SPA_PARAM_INFO_SERIAL;
emit_info(this, false);
return 0;
@ -497,10 +531,10 @@ static void codec_switched(void *userdata, int status)
if (status < 0) {
/* Failed to switch: return to a fallback profile */
spa_log_error(this->log, NAME": failed to switch codec (%d), setting fallback profile", status);
if (this->profile == DEVICE_PROFILE_A2DP && this->selected_a2dp_codec != NULL) {
this->selected_a2dp_codec = NULL;
} else if (this->profile == DEVICE_PROFILE_HSP_HFP && this->selected_hfp_codec != 0) {
this->selected_hfp_codec = 0;
if (this->profile == DEVICE_PROFILE_A2DP && this->props.codec != 0) {
this->props.codec = 0;
} else if (this->profile == DEVICE_PROFILE_HSP_HFP && this->props.codec != 0) {
this->props.codec = 0;
} else {
this->profile = DEVICE_PROFILE_OFF;
}
@ -515,6 +549,8 @@ static void codec_switched(void *userdata, int status)
this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_PropInfo].flags ^= SPA_PARAM_INFO_SERIAL;
emit_info(this, false);
}
@ -576,6 +612,8 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr
this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; /* Profile changes may affect routes */
this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL;
this->params[IDX_PropInfo].flags ^= SPA_PARAM_INFO_SERIAL;
emit_info(this, false);
}
@ -586,7 +624,7 @@ static void device_connected(void *userdata, bool connected) {
spa_log_debug(this->log, "connected: %d", connected);
if (connected ^ (this->profile != 0))
if (connected ^ (this->profile != DEVICE_PROFILE_OFF))
set_initial_profile(this);
}
@ -659,72 +697,52 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index)
return mask;
}
static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32_t *next, const struct a2dp_codec **codec, int *hfp_codec)
static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32_t *next, enum spa_bluetooth_audio_codec *codec)
{
uint32_t a2dp_codec_mask = 0x100;
uint32_t hfp_codec_mask = 0x200;
/*
* XXX: The codecs should probably become a separate param, and not have
* XXX: separate profiles for each one.
*/
*codec = NULL;
*hfp_codec = 0;
*codec = 0;
*next = index + 1;
if (index < a2dp_codec_mask) {
if (index >= 3)
*next = a2dp_codec_mask;
if (index <= 3)
if (index <= 3) {
return index;
} else if (index & a2dp_codec_mask) {
uint32_t i = index & ~a2dp_codec_mask;
if (i + 1 >= this->supported_codec_count)
*next = hfp_codec_mask;
if (i < this->supported_codec_count) {
*codec = this->supported_codecs[i];
return DEVICE_PROFILE_A2DP;
}
} else if (index & hfp_codec_mask) {
uint32_t i = index & ~hfp_codec_mask;
if (i == 0) {
*hfp_codec = HFP_AUDIO_CODEC_CVSD;
return DEVICE_PROFILE_HSP_HFP;
} else if (i == 1) {
*hfp_codec = HFP_AUDIO_CODEC_MSBC;
return DEVICE_PROFILE_HSP_HFP;
}
} else if (index != SPA_ID_INVALID) {
const struct spa_type_info *info;
*codec = index - 3;
*next = SPA_ID_INVALID;
for (info = spa_type_bluetooth_audio_codec; info->type; ++info)
if (info->type > *codec)
*next = SPA_MIN(info->type + 3, *next);
return get_hfp_codec(*codec) ? DEVICE_PROFILE_HSP_HFP : DEVICE_PROFILE_A2DP;
}
*next = SPA_ID_INVALID;
return SPA_ID_INVALID;
}
static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, const struct a2dp_codec *codec, int hfp_codec)
static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_audio_codec codec)
{
uint32_t a2dp_codec_mask = 0x100;
uint32_t hfp_codec_mask = 0x200;
uint32_t i;
if (profile == DEVICE_PROFILE_OFF || profile == DEVICE_PROFILE_AG)
return profile;
if (profile == DEVICE_PROFILE_A2DP) {
if (codec == NULL)
if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE))
return profile;
for (i = 0; i < this->supported_codec_count; ++i) {
if (this->supported_codecs[i] == codec)
return a2dp_codec_mask | i;
}
return codec + 3;
}
if (profile == DEVICE_PROFILE_HSP_HFP) {
if (hfp_codec == 0)
if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HFP_AG))
return profile;
return hfp_codec_mask | ((hfp_codec == HFP_AUDIO_CODEC_MSBC) ? 1 : 0);
return codec + 3;
}
return SPA_ID_INVALID;
@ -747,17 +765,11 @@ static void set_initial_profile(struct impl *this)
if (!(this->bt_dev->connected_profiles & i))
continue;
t = find_transport(this, i, NULL, 0);
t = find_transport(this, i, 0);
if (t) {
this->profile = (i == SPA_BT_PROFILE_A2DP_SOURCE) ?
DEVICE_PROFILE_AG : DEVICE_PROFILE_A2DP;
this->selected_hfp_codec = 0;
/* Source devices don't have codec selection */
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)
this->selected_a2dp_codec = NULL;
else
this->selected_a2dp_codec = t->a2dp_codec;
this->props.codec = t->a2dp_codec->id;
return;
}
}
@ -766,27 +778,21 @@ static void set_initial_profile(struct impl *this)
if (!(this->bt_dev->connected_profiles & i))
continue;
t = find_transport(this, i, NULL, 0);
t = find_transport(this, i, 0);
if (t) {
this->profile = (i & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) ?
DEVICE_PROFILE_AG : DEVICE_PROFILE_HSP_HFP;
this->selected_a2dp_codec = NULL;
/* Source devices don't have codec selection */
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HFP_AG)
this->selected_hfp_codec = 0;
else
this->selected_hfp_codec = t->codec;
this->props.codec = get_hfp_codec_id(t->codec);
return;
}
}
this->profile = DEVICE_PROFILE_OFF;
this->selected_a2dp_codec = NULL;
this->props.codec = 0;
}
static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *b,
uint32_t id, uint32_t index, uint32_t profile_index, const struct a2dp_codec *codec, int hfp_codec)
uint32_t id, uint32_t index, uint32_t profile_index, enum spa_bluetooth_audio_codec codec)
{
struct spa_bt_device *device = this->bt_dev;
struct spa_pod_frame f[2];
@ -827,9 +833,18 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *
}
name = spa_bt_profile_name(profile);
n_sink++;
if (codec != NULL) {
name_and_codec = spa_aprintf("%s-%s", name, codec->name);
desc_and_codec = spa_aprintf(desc, ", codec ", codec->description);
if (codec) {
uint32_t i;
const struct a2dp_codec *a2dp_codec = NULL;
for (i = 0; i < this->supported_codec_count; ++i)
if (codec == this->supported_codecs[i]->id)
a2dp_codec = this->supported_codecs[i];
if (a2dp_codec == NULL) {
errno = -EINVAL;
return NULL;
}
name_and_codec = spa_aprintf("%s-%s", name, a2dp_codec->name);
desc_and_codec = spa_aprintf(desc, ", codec ", a2dp_codec->description);
name = name_and_codec;
desc = desc_and_codec;
} else {
@ -851,9 +866,10 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *
name = spa_bt_profile_name(profile);
n_source++;
n_sink++;
if (hfp_codec != 0) {
if (codec) {
bool codec_ok = !(profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY);
if (spa_bt_device_supports_hfp_codec(this->bt_dev, hfp_codec) != 1)
unsigned int hfp_codec = get_hfp_codec(codec);
if (spa_bt_device_supports_hfp_codec(this->bt_dev, hfp_codec) == 0)
codec_ok = false;
if (!codec_ok) {
errno = -EINVAL;
@ -915,10 +931,9 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
enum spa_direction direction;
const char *name_prefix, *description, *port_type;
enum spa_bt_form_factor ff;
const struct a2dp_codec *codec;
enum spa_bluetooth_audio_codec codec;
char name[128];
uint32_t i, j, mask, next;
int hfp_codec;
ff = spa_bt_form_factor_from_class(device->bluetooth_class);
@ -1018,7 +1033,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
spa_pod_builder_pop(b, &f[1]);
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0);
spa_pod_builder_push_array(b, &f[1]);
for (i = 1; (j = get_profile_from_index(this, i, &next, &codec, &hfp_codec)) != SPA_ID_INVALID; i = next) {
for (i = 1; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) {
struct spa_pod_builder b2 = { 0 };
uint8_t buffer[1024];
struct spa_pod *param;
@ -1028,7 +1043,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
/* Check the profile actually exists */
spa_pod_builder_init(&b2, buffer, sizeof(buffer));
param = build_profile(this, &b2, 0, i, j, codec, hfp_codec);
param = build_profile(this, &b2, 0, i, j, codec);
if (param == NULL)
continue;
@ -1077,6 +1092,82 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
return spa_pod_builder_pop(b, &f[0]);
}
static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder *b, uint32_t id)
{
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
const struct a2dp_codec *codec;
size_t n, j;
#define FOR_EACH_A2DP_CODEC(j, codec) \
for (j = 0; (j < this->supported_codec_count) ? (codec = this->supported_codecs[j]) : NULL; ++j)
#define FOR_EACH_HFP_CODEC(j) \
for (j = HFP_AUDIO_CODEC_MSBC; j >= HFP_AUDIO_CODEC_CVSD; --j) \
if (spa_bt_device_supports_hfp_codec(this->bt_dev, j) == 1)
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id);
/*
* XXX: the ids in principle should use builder_id, not builder_int,
* XXX: but the type info for _type and _labels doesn't work quite right now.
*/
/* Transport codec */
spa_pod_builder_prop(b, SPA_PROP_INFO_id, 0);
spa_pod_builder_id(b, SPA_PROP_bluetoothAudioCodec);
spa_pod_builder_prop(b, SPA_PROP_INFO_name, 0);
spa_pod_builder_string(b, "Air codec");
spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0);
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
choice = (struct spa_pod_choice *)spa_pod_builder_frame(b, &f[1]);
n = 0;
if (this->profile == DEVICE_PROFILE_A2DP) {
FOR_EACH_A2DP_CODEC(j, codec) {
if (n == 0)
spa_pod_builder_int(b, codec->id);
spa_pod_builder_int(b, codec->id);
++n;
}
} else if (this->profile == DEVICE_PROFILE_HSP_HFP) {
FOR_EACH_HFP_CODEC(j) {
if (n == 0)
spa_pod_builder_int(b, get_hfp_codec_id(j));
spa_pod_builder_int(b, get_hfp_codec_id(j));
++n;
}
}
if (n == 0)
choice->body.type = SPA_CHOICE_None;
spa_pod_builder_pop(b, &f[1]);
spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0);
spa_pod_builder_push_struct(b, &f[1]);
if (this->profile == DEVICE_PROFILE_A2DP) {
FOR_EACH_A2DP_CODEC(j, codec) {
spa_pod_builder_int(b, codec->id);
spa_pod_builder_string(b, codec->description);
}
} else if (this->profile == DEVICE_PROFILE_HSP_HFP) {
FOR_EACH_HFP_CODEC(j) {
spa_pod_builder_int(b, get_hfp_codec_id(j));
spa_pod_builder_string(b, get_hfp_codec_description(j));
}
}
spa_pod_builder_pop(b, &f[1]);
return spa_pod_builder_pop(b, &f[0]);
#undef FOR_EACH_A2DP_CODEC
#undef FOR_EACH_HFP_CODEC
}
static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id)
{
struct props *p = &this->props;
return spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec));
}
static int impl_enum_params(void *object, int seq,
uint32_t id, uint32_t start, uint32_t num,
const struct spa_pod *filter)
@ -1084,7 +1175,7 @@ static int impl_enum_params(void *object, int seq,
struct impl *this = object;
struct spa_pod *param;
struct spa_pod_builder b = { 0 };
uint8_t buffer[1024];
uint8_t buffer[2048];
struct spa_result_device_params result;
uint32_t count = 0;
@ -1102,17 +1193,16 @@ static int impl_enum_params(void *object, int seq,
case SPA_PARAM_EnumProfile:
{
uint32_t profile;
const struct a2dp_codec *codec;
int hfp_codec;
enum spa_bluetooth_audio_codec codec;
profile = get_profile_from_index(this, result.index, &result.next, &codec, &hfp_codec);
profile = get_profile_from_index(this, result.index, &result.next, &codec);
switch (profile) {
case DEVICE_PROFILE_OFF:
case DEVICE_PROFILE_AG:
case DEVICE_PROFILE_A2DP:
case DEVICE_PROFILE_HSP_HFP:
param = build_profile(this, &b, id, result.index, profile, codec, hfp_codec);
param = build_profile(this, &b, id, result.index, profile, codec);
if (param == NULL)
goto next;
break;
@ -1127,8 +1217,8 @@ static int impl_enum_params(void *object, int seq,
switch (result.index) {
case 0:
index = get_index_from_profile(this, this->profile, this->selected_a2dp_codec, this->selected_hfp_codec);
param = build_profile(this, &b, id, index, this->profile, this->selected_a2dp_codec, this->selected_hfp_codec);
index = get_index_from_profile(this, this->profile, this->props.codec);
param = build_profile(this, &b, id, index, this->profile, this->props.codec);
if (param == NULL)
return 0;
break;
@ -1165,6 +1255,28 @@ static int impl_enum_params(void *object, int seq,
}
break;
}
case SPA_PARAM_PropInfo:
{
switch (result.index) {
case 0:
param = build_prop_info(this, &b, id);
break;
default:
return 0;
}
break;
}
case SPA_PARAM_Props:
{
switch (result.index) {
case 0:
param = build_props(this, &b, id);
break;
default:
return 0;
}
break;
}
default:
return -ENOENT;
}
@ -1350,8 +1462,7 @@ static int impl_set_param(void *object,
{
uint32_t id, next;
uint32_t profile;
const struct a2dp_codec *codec;
int hfp_codec;
enum spa_bluetooth_audio_codec codec;
if ((res = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_ParamProfile, NULL,
@ -1361,13 +1472,12 @@ static int impl_set_param(void *object,
return res;
}
profile = get_profile_from_index(this, id, &next, &codec, &hfp_codec);
profile = get_profile_from_index(this, id, &next, &codec);
if (profile == SPA_ID_INVALID)
return -EINVAL;
spa_log_debug(this->log, NAME": setting profile %d codec:%s hfp-codec:%d", profile,
codec ? codec->name : "<null>", hfp_codec);
set_profile(this, profile, codec, hfp_codec);
spa_log_debug(this->log, NAME": setting profile %d codec:%d", profile, codec);
set_profile(this, profile, codec);
break;
}
case SPA_PARAM_Route:
@ -1399,6 +1509,45 @@ static int impl_set_param(void *object,
}
break;
}
case SPA_PARAM_Props:
{
const uint32_t codec_id = SPA_ID_INVALID;
if (param == NULL)
return 0;
if ((res = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id))) < 0) {
spa_log_warn(this->log, "can't parse props");
spa_debug_pod(0, NULL, param);
return res;
}
if (codec_id == SPA_ID_INVALID)
return 0;
if (this->profile == DEVICE_PROFILE_A2DP) {
size_t j;
for (j = 0; j < this->supported_codec_count; ++j) {
if (this->supported_codecs[j]->id == codec_id) {
set_profile(this, this->profile, codec_id);
return 0;
}
}
} else if (this->profile == DEVICE_PROFILE_HSP_HFP) {
if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_CVSD &&
spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_CVSD) == 1) {
set_profile(this, this->profile, codec_id);
return 0;
} else if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_MSBC &&
spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_MSBC) == 1) {
set_profile(this, this->profile, codec_id);
return 0;
}
}
return -EINVAL;
}
default:
return -ENOENT;
}
@ -1534,8 +1683,10 @@ impl_init(const struct spa_handle_factory *factory,
this->params[IDX_Profile] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE);
this->params[IDX_EnumRoute] = SPA_PARAM_INFO(SPA_PARAM_EnumRoute, SPA_PARAM_INFO_READ);
this->params[IDX_Route] = SPA_PARAM_INFO(SPA_PARAM_Route, SPA_PARAM_INFO_READWRITE);
this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
this->info.params = this->params;
this->info.n_params = 4;
this->info.n_params = 6;
spa_bt_device_add_listener(this->bt_dev, &this->bt_dev_listener, &bt_dev_events, this);