mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: add bluetoothAudioCodec property to device
Also make the "codecless" profiles to automatically switch to the codec profiles.
This commit is contained in:
parent
6b0cf799c4
commit
a552655edc
6 changed files with 292 additions and 133 deletions
|
|
@ -439,6 +439,7 @@ static int codec_increase_bitpool(void *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct a2dp_codec a2dp_codec_aac = {
|
const struct a2dp_codec a2dp_codec_aac = {
|
||||||
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_AAC,
|
||||||
.codec_id = A2DP_CODEC_MPEG24,
|
.codec_id = A2DP_CODEC_MPEG24,
|
||||||
.name = "aac",
|
.name = "aac",
|
||||||
.description = "AAC",
|
.description = "AAC",
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,7 @@ static int codec_decode(void *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct a2dp_codec a2dp_codec_aptx = {
|
const struct a2dp_codec a2dp_codec_aptx = {
|
||||||
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_APTX,
|
||||||
.codec_id = A2DP_CODEC_VENDOR,
|
.codec_id = A2DP_CODEC_VENDOR,
|
||||||
.vendor = { .vendor_id = APTX_VENDOR_ID,
|
.vendor = { .vendor_id = APTX_VENDOR_ID,
|
||||||
.codec_id = APTX_CODEC_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 = {
|
const struct a2dp_codec a2dp_codec_aptx_hd = {
|
||||||
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
|
||||||
.codec_id = A2DP_CODEC_VENDOR,
|
.codec_id = A2DP_CODEC_VENDOR,
|
||||||
.vendor = { .vendor_id = APTX_HD_VENDOR_ID,
|
.vendor = { .vendor_id = APTX_HD_VENDOR_ID,
|
||||||
.codec_id = APTX_HD_CODEC_ID },
|
.codec_id = APTX_HD_CODEC_ID },
|
||||||
|
|
|
||||||
|
|
@ -624,6 +624,7 @@ static int codec_encode(void *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct a2dp_codec a2dp_codec_ldac = {
|
const struct a2dp_codec a2dp_codec_ldac = {
|
||||||
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC,
|
||||||
.codec_id = A2DP_CODEC_VENDOR,
|
.codec_id = A2DP_CODEC_VENDOR,
|
||||||
.vendor = { .vendor_id = LDAC_VENDOR_ID,
|
.vendor = { .vendor_id = LDAC_VENDOR_ID,
|
||||||
.codec_id = LDAC_CODEC_ID },
|
.codec_id = LDAC_CODEC_ID },
|
||||||
|
|
|
||||||
|
|
@ -624,6 +624,7 @@ static int codec_decode(void *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct a2dp_codec a2dp_codec_sbc = {
|
const struct a2dp_codec a2dp_codec_sbc = {
|
||||||
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_SBC,
|
||||||
.codec_id = A2DP_CODEC_SBC,
|
.codec_id = A2DP_CODEC_SBC,
|
||||||
.name = "sbc",
|
.name = "sbc",
|
||||||
.description = "SBC",
|
.description = "SBC",
|
||||||
|
|
@ -646,6 +647,7 @@ const struct a2dp_codec a2dp_codec_sbc = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct a2dp_codec a2dp_codec_sbc_xq = {
|
const struct a2dp_codec a2dp_codec_sbc_xq = {
|
||||||
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ,
|
||||||
.codec_id = A2DP_CODEC_SBC,
|
.codec_id = A2DP_CODEC_SBC,
|
||||||
.name = "sbc_xq",
|
.name = "sbc_xq",
|
||||||
.description = "SBC-XQ",
|
.description = "SBC-XQ",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include <spa/param/audio/format.h>
|
#include <spa/param/audio/format.h>
|
||||||
|
#include <spa/param/bluetooth/audio.h>
|
||||||
#include <spa/pod/pod.h>
|
#include <spa/pod/pod.h>
|
||||||
#include <spa/pod/builder.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_handle;
|
||||||
|
|
||||||
struct a2dp_codec {
|
struct a2dp_codec {
|
||||||
|
enum spa_bluetooth_audio_codec id;
|
||||||
uint8_t codec_id;
|
uint8_t codec_id;
|
||||||
a2dp_vendor_codec_t vendor;
|
a2dp_vendor_codec_t vendor;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@
|
||||||
#include <spa/pod/parser.h>
|
#include <spa/pod/parser.h>
|
||||||
#include <spa/param/param.h>
|
#include <spa/param/param.h>
|
||||||
#include <spa/param/audio/raw.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 <spa/debug/pod.h>
|
||||||
|
|
||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
|
|
@ -64,15 +66,13 @@ enum {
|
||||||
DEVICE_PROFILE_HSP_HFP = 3,
|
DEVICE_PROFILE_HSP_HFP = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char default_device[] = "";
|
|
||||||
|
|
||||||
struct props {
|
struct props {
|
||||||
char device[64];
|
enum spa_bluetooth_audio_codec codec;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void reset_props(struct props *props)
|
static void reset_props(struct props *props)
|
||||||
{
|
{
|
||||||
strncpy(props->device, default_device, 64);
|
props->codec = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct node {
|
struct node {
|
||||||
|
|
@ -107,7 +107,9 @@ struct impl {
|
||||||
#define IDX_Profile 1
|
#define IDX_Profile 1
|
||||||
#define IDX_EnumRoute 2
|
#define IDX_EnumRoute 2
|
||||||
#define IDX_Route 3
|
#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;
|
struct spa_hook_list hooks;
|
||||||
|
|
||||||
|
|
@ -117,8 +119,6 @@ struct impl {
|
||||||
struct spa_hook bt_dev_listener;
|
struct spa_hook bt_dev_listener;
|
||||||
|
|
||||||
uint32_t profile;
|
uint32_t profile;
|
||||||
const struct a2dp_codec *selected_a2dp_codec; /**< Codec wanted. NULL means any. */
|
|
||||||
int selected_hfp_codec;
|
|
||||||
unsigned int switching_codec:1;
|
unsigned int switching_codec:1;
|
||||||
uint32_t prev_bt_connected_profiles;
|
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;
|
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)
|
static const char *get_hfp_codec_description(unsigned int codec)
|
||||||
{
|
{
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
|
|
@ -226,31 +260,22 @@ 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_device *device = this->bt_dev;
|
||||||
struct spa_bt_transport *t;
|
struct spa_bt_transport *t;
|
||||||
const struct a2dp_codec **codecs;
|
const struct a2dp_codec *a2dp_codec;
|
||||||
size_t i, num_codecs;
|
unsigned int hfp_codec;
|
||||||
|
|
||||||
codecs = &a2dp_codec;
|
a2dp_codec = get_a2dp_codec(codec);
|
||||||
num_codecs = 1;
|
hfp_codec = get_hfp_codec(codec);
|
||||||
|
|
||||||
if (a2dp_codec == NULL && (profile == SPA_BT_PROFILE_A2DP_SOURCE || profile == SPA_BT_PROFILE_A2DP_SINK)) {
|
spa_list_for_each(t, &device->transport_list, device_link) {
|
||||||
codecs = a2dp_codecs;
|
if ((t->profile & device->connected_profiles) &&
|
||||||
num_codecs = 0;
|
(t->profile & profile) == t->profile &&
|
||||||
while (codecs[num_codecs] != NULL)
|
(a2dp_codec == NULL || t->a2dp_codec == a2dp_codec) &&
|
||||||
++num_codecs;
|
(hfp_codec == 0 || t->codec == hfp_codec))
|
||||||
}
|
return t;
|
||||||
|
|
||||||
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]) &&
|
|
||||||
(hfp_codec == 0 || t->codec == hfp_codec))
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -334,10 +359,11 @@ static int emit_nodes(struct impl *this)
|
||||||
break;
|
break;
|
||||||
case DEVICE_PROFILE_AG:
|
case DEVICE_PROFILE_AG:
|
||||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) {
|
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)
|
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) {
|
if (t) {
|
||||||
|
this->props.codec = get_hfp_codec_id(t->codec);
|
||||||
emit_dynamic_node(&this->dyn_sco_source, this, t,
|
emit_dynamic_node(&this->dyn_sco_source, this, t,
|
||||||
0, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
|
0, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
|
||||||
emit_dynamic_node(&this->dyn_sco_sink, this, t,
|
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) {
|
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);
|
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0);
|
||||||
if (t)
|
if (t) {
|
||||||
|
this->props.codec = t->a2dp_codec->id;
|
||||||
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
||||||
2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
|
2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DEVICE_PROFILE_A2DP:
|
case DEVICE_PROFILE_A2DP:
|
||||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
|
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);
|
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0);
|
||||||
if (t)
|
if (t) {
|
||||||
|
this->props.codec = t->a2dp_codec->id;
|
||||||
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
emit_dynamic_node(&this->dyn_a2dp_source, this, t,
|
||||||
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
|
DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) {
|
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);
|
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->props.codec);
|
||||||
if (t)
|
if (t) {
|
||||||
|
this->props.codec = t->a2dp_codec->id;
|
||||||
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK);
|
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DEVICE_PROFILE_HSP_HFP:
|
case DEVICE_PROFILE_HSP_HFP:
|
||||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) {
|
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)
|
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) {
|
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_SOURCE, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
|
||||||
emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_SCO_SINK);
|
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 &&
|
if (this->profile == profile &&
|
||||||
(this->profile != DEVICE_PROFILE_A2DP || a2dp_codec == this->selected_a2dp_codec) &&
|
(this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) &&
|
||||||
(this->profile != DEVICE_PROFILE_HSP_HFP || hfp_codec == this->selected_hfp_codec))
|
(this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
emit_remove_nodes(this);
|
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->profile = profile;
|
||||||
this->prev_bt_connected_profiles = this->bt_dev->connected_profiles;
|
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).
|
* 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)) {
|
if (profile == DEVICE_PROFILE_A2DP && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) {
|
||||||
int ret;
|
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) {
|
if (a2dp_codec == NULL) {
|
||||||
codecs = a2dp_codecs;
|
codecs = a2dp_codecs;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -448,7 +483,6 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co
|
||||||
}
|
}
|
||||||
|
|
||||||
this->switching_codec = true;
|
this->switching_codec = true;
|
||||||
this->selected_a2dp_codec = a2dp_codec;
|
|
||||||
|
|
||||||
ret = spa_bt_device_ensure_a2dp_codec(this->bt_dev, codecs);
|
ret = spa_bt_device_ensure_a2dp_codec(this->bt_dev, codecs);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
|
@ -457,13 +491,12 @@ static int set_profile(struct impl *this, uint32_t profile, const struct a2dp_co
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
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;
|
int ret;
|
||||||
|
|
||||||
this->switching_codec = true;
|
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 < 0) {
|
||||||
if (ret != -ENOTSUP)
|
if (ret != -ENOTSUP)
|
||||||
spa_log_error(this->log, NAME": failed to switch codec (%d), setting basic profile", ret);
|
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->switching_codec = false;
|
||||||
this->selected_a2dp_codec = NULL;
|
this->props.codec = 0;
|
||||||
this->selected_hfp_codec = 0;
|
|
||||||
emit_nodes(this);
|
emit_nodes(this);
|
||||||
|
|
||||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||||
this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL;
|
this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||||
this->params[IDX_Route].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_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);
|
emit_info(this, false);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -497,10 +531,10 @@ static void codec_switched(void *userdata, int status)
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
/* Failed to switch: return to a fallback profile */
|
/* Failed to switch: return to a fallback profile */
|
||||||
spa_log_error(this->log, NAME": failed to switch codec (%d), setting fallback profile", status);
|
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) {
|
if (this->profile == DEVICE_PROFILE_A2DP && this->props.codec != 0) {
|
||||||
this->selected_a2dp_codec = NULL;
|
this->props.codec = 0;
|
||||||
} else if (this->profile == DEVICE_PROFILE_HSP_HFP && this->selected_hfp_codec != 0) {
|
} else if (this->profile == DEVICE_PROFILE_HSP_HFP && this->props.codec != 0) {
|
||||||
this->selected_hfp_codec = 0;
|
this->props.codec = 0;
|
||||||
} else {
|
} else {
|
||||||
this->profile = DEVICE_PROFILE_OFF;
|
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_Profile].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||||
this->params[IDX_Route].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_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);
|
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_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||||
this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; /* Profile changes may affect routes */
|
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_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);
|
emit_info(this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,7 +624,7 @@ static void device_connected(void *userdata, bool connected) {
|
||||||
|
|
||||||
spa_log_debug(this->log, "connected: %d", 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);
|
set_initial_profile(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -659,72 +697,52 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index)
|
||||||
return mask;
|
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: The codecs should probably become a separate param, and not have
|
||||||
* XXX: separate profiles for each one.
|
* XXX: separate profiles for each one.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
*codec = NULL;
|
*codec = 0;
|
||||||
*hfp_codec = 0;
|
|
||||||
*next = index + 1;
|
*next = index + 1;
|
||||||
|
|
||||||
if (index < a2dp_codec_mask) {
|
if (index <= 3) {
|
||||||
if (index >= 3)
|
return index;
|
||||||
*next = a2dp_codec_mask;
|
} else if (index != SPA_ID_INVALID) {
|
||||||
if (index <= 3)
|
const struct spa_type_info *info;
|
||||||
return index;
|
|
||||||
} else if (index & a2dp_codec_mask) {
|
*codec = index - 3;
|
||||||
uint32_t i = index & ~a2dp_codec_mask;
|
*next = SPA_ID_INVALID;
|
||||||
if (i + 1 >= this->supported_codec_count)
|
|
||||||
*next = hfp_codec_mask;
|
for (info = spa_type_bluetooth_audio_codec; info->type; ++info)
|
||||||
if (i < this->supported_codec_count) {
|
if (info->type > *codec)
|
||||||
*codec = this->supported_codecs[i];
|
*next = SPA_MIN(info->type + 3, *next);
|
||||||
return DEVICE_PROFILE_A2DP;
|
|
||||||
}
|
return get_hfp_codec(*codec) ? DEVICE_PROFILE_HSP_HFP : 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*next = SPA_ID_INVALID;
|
*next = SPA_ID_INVALID;
|
||||||
return 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)
|
if (profile == DEVICE_PROFILE_OFF || profile == DEVICE_PROFILE_AG)
|
||||||
return profile;
|
return profile;
|
||||||
|
|
||||||
if (profile == DEVICE_PROFILE_A2DP) {
|
if (profile == DEVICE_PROFILE_A2DP) {
|
||||||
if (codec == NULL)
|
if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE))
|
||||||
return profile;
|
return profile;
|
||||||
|
|
||||||
for (i = 0; i < this->supported_codec_count; ++i) {
|
return codec + 3;
|
||||||
if (this->supported_codecs[i] == codec)
|
|
||||||
return a2dp_codec_mask | i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile == DEVICE_PROFILE_HSP_HFP) {
|
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 profile;
|
||||||
|
|
||||||
return hfp_codec_mask | ((hfp_codec == HFP_AUDIO_CODEC_MSBC) ? 1 : 0);
|
return codec + 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SPA_ID_INVALID;
|
return SPA_ID_INVALID;
|
||||||
|
|
@ -747,17 +765,11 @@ static void set_initial_profile(struct impl *this)
|
||||||
if (!(this->bt_dev->connected_profiles & i))
|
if (!(this->bt_dev->connected_profiles & i))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
t = find_transport(this, i, NULL, 0);
|
t = find_transport(this, i, 0);
|
||||||
if (t) {
|
if (t) {
|
||||||
this->profile = (i == SPA_BT_PROFILE_A2DP_SOURCE) ?
|
this->profile = (i == SPA_BT_PROFILE_A2DP_SOURCE) ?
|
||||||
DEVICE_PROFILE_AG : DEVICE_PROFILE_A2DP;
|
DEVICE_PROFILE_AG : DEVICE_PROFILE_A2DP;
|
||||||
this->selected_hfp_codec = 0;
|
this->props.codec = t->a2dp_codec->id;
|
||||||
|
|
||||||
/* 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;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -766,27 +778,21 @@ static void set_initial_profile(struct impl *this)
|
||||||
if (!(this->bt_dev->connected_profiles & i))
|
if (!(this->bt_dev->connected_profiles & i))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
t = find_transport(this, i, NULL, 0);
|
t = find_transport(this, i, 0);
|
||||||
if (t) {
|
if (t) {
|
||||||
this->profile = (i & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) ?
|
this->profile = (i & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) ?
|
||||||
DEVICE_PROFILE_AG : DEVICE_PROFILE_HSP_HFP;
|
DEVICE_PROFILE_AG : DEVICE_PROFILE_HSP_HFP;
|
||||||
this->selected_a2dp_codec = NULL;
|
this->props.codec = get_hfp_codec_id(t->codec);
|
||||||
|
|
||||||
/* 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;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->profile = DEVICE_PROFILE_OFF;
|
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,
|
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_bt_device *device = this->bt_dev;
|
||||||
struct spa_pod_frame f[2];
|
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);
|
name = spa_bt_profile_name(profile);
|
||||||
n_sink++;
|
n_sink++;
|
||||||
if (codec != NULL) {
|
if (codec) {
|
||||||
name_and_codec = spa_aprintf("%s-%s", name, codec->name);
|
uint32_t i;
|
||||||
desc_and_codec = spa_aprintf(desc, ", codec ", codec->description);
|
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;
|
name = name_and_codec;
|
||||||
desc = desc_and_codec;
|
desc = desc_and_codec;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -851,9 +866,10 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *
|
||||||
name = spa_bt_profile_name(profile);
|
name = spa_bt_profile_name(profile);
|
||||||
n_source++;
|
n_source++;
|
||||||
n_sink++;
|
n_sink++;
|
||||||
if (hfp_codec != 0) {
|
if (codec) {
|
||||||
bool codec_ok = !(profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY);
|
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;
|
codec_ok = false;
|
||||||
if (!codec_ok) {
|
if (!codec_ok) {
|
||||||
errno = -EINVAL;
|
errno = -EINVAL;
|
||||||
|
|
@ -915,10 +931,9 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
|
||||||
enum spa_direction direction;
|
enum spa_direction direction;
|
||||||
const char *name_prefix, *description, *port_type;
|
const char *name_prefix, *description, *port_type;
|
||||||
enum spa_bt_form_factor ff;
|
enum spa_bt_form_factor ff;
|
||||||
const struct a2dp_codec *codec;
|
enum spa_bluetooth_audio_codec codec;
|
||||||
char name[128];
|
char name[128];
|
||||||
uint32_t i, j, mask, next;
|
uint32_t i, j, mask, next;
|
||||||
int hfp_codec;
|
|
||||||
|
|
||||||
ff = spa_bt_form_factor_from_class(device->bluetooth_class);
|
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_pop(b, &f[1]);
|
||||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0);
|
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0);
|
||||||
spa_pod_builder_push_array(b, &f[1]);
|
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 };
|
struct spa_pod_builder b2 = { 0 };
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
struct spa_pod *param;
|
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 */
|
/* Check the profile actually exists */
|
||||||
spa_pod_builder_init(&b2, buffer, sizeof(buffer));
|
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)
|
if (param == NULL)
|
||||||
continue;
|
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]);
|
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,
|
static int impl_enum_params(void *object, int seq,
|
||||||
uint32_t id, uint32_t start, uint32_t num,
|
uint32_t id, uint32_t start, uint32_t num,
|
||||||
const struct spa_pod *filter)
|
const struct spa_pod *filter)
|
||||||
|
|
@ -1084,7 +1175,7 @@ static int impl_enum_params(void *object, int seq,
|
||||||
struct impl *this = object;
|
struct impl *this = object;
|
||||||
struct spa_pod *param;
|
struct spa_pod *param;
|
||||||
struct spa_pod_builder b = { 0 };
|
struct spa_pod_builder b = { 0 };
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[2048];
|
||||||
struct spa_result_device_params result;
|
struct spa_result_device_params result;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
|
|
||||||
|
|
@ -1102,17 +1193,16 @@ static int impl_enum_params(void *object, int seq,
|
||||||
case SPA_PARAM_EnumProfile:
|
case SPA_PARAM_EnumProfile:
|
||||||
{
|
{
|
||||||
uint32_t profile;
|
uint32_t profile;
|
||||||
const struct a2dp_codec *codec;
|
enum spa_bluetooth_audio_codec codec;
|
||||||
int hfp_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) {
|
switch (profile) {
|
||||||
case DEVICE_PROFILE_OFF:
|
case DEVICE_PROFILE_OFF:
|
||||||
case DEVICE_PROFILE_AG:
|
case DEVICE_PROFILE_AG:
|
||||||
case DEVICE_PROFILE_A2DP:
|
case DEVICE_PROFILE_A2DP:
|
||||||
case DEVICE_PROFILE_HSP_HFP:
|
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)
|
if (param == NULL)
|
||||||
goto next;
|
goto next;
|
||||||
break;
|
break;
|
||||||
|
|
@ -1127,8 +1217,8 @@ static int impl_enum_params(void *object, int seq,
|
||||||
|
|
||||||
switch (result.index) {
|
switch (result.index) {
|
||||||
case 0:
|
case 0:
|
||||||
index = get_index_from_profile(this, 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->selected_a2dp_codec, this->selected_hfp_codec);
|
param = build_profile(this, &b, id, index, this->profile, this->props.codec);
|
||||||
if (param == NULL)
|
if (param == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
break;
|
break;
|
||||||
|
|
@ -1165,6 +1255,28 @@ static int impl_enum_params(void *object, int seq,
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
@ -1350,8 +1462,7 @@ static int impl_set_param(void *object,
|
||||||
{
|
{
|
||||||
uint32_t id, next;
|
uint32_t id, next;
|
||||||
uint32_t profile;
|
uint32_t profile;
|
||||||
const struct a2dp_codec *codec;
|
enum spa_bluetooth_audio_codec codec;
|
||||||
int hfp_codec;
|
|
||||||
|
|
||||||
if ((res = spa_pod_parse_object(param,
|
if ((res = spa_pod_parse_object(param,
|
||||||
SPA_TYPE_OBJECT_ParamProfile, NULL,
|
SPA_TYPE_OBJECT_ParamProfile, NULL,
|
||||||
|
|
@ -1361,13 +1472,12 @@ static int impl_set_param(void *object,
|
||||||
return res;
|
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)
|
if (profile == SPA_ID_INVALID)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
spa_log_debug(this->log, NAME": setting profile %d codec:%s hfp-codec:%d", profile,
|
spa_log_debug(this->log, NAME": setting profile %d codec:%d", profile, codec);
|
||||||
codec ? codec->name : "<null>", hfp_codec);
|
set_profile(this, profile, codec);
|
||||||
set_profile(this, profile, codec, hfp_codec);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SPA_PARAM_Route:
|
case SPA_PARAM_Route:
|
||||||
|
|
@ -1399,6 +1509,45 @@ static int impl_set_param(void *object,
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
return -ENOENT;
|
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_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_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_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.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);
|
spa_bt_device_add_listener(this->bt_dev, &this->bt_dev_listener, &bt_dev_events, this);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue