mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: Configure LC3 codec capabilities
Currently, the PipeWire daemon registers BlueZ LE Media Endpoints with audio capabilities covering all settings defined in the BAP spec. However, some scenarios might require the capabilities to be restricted to specific configurations. This adds a method to read LC3 codec specific capabilities from the Wireplumber config file, and provide those settings when registering Media Endpoint objects with BlueZ. If the values are not present in the config file, all settings will be used by default. Below is an example of how to set the LC3 capabilities in the config file, to support the 16_2 setting from the BAP spec: bluez5.bap-server-capabilities.rates = [16000] bluez5.bap-server-capabilities.durations = [10] bluez5.bap-server-capabilities.channels = [1, 2] bluez5.bap-server-capabilities.framelen_min = 40 bluez5.bap-server-capabilities.framelen_max = 40 bluez5.bap-server-capabilities.max_frames = 2
This commit is contained in:
parent
b5490954d0
commit
9a5b2d42f9
12 changed files with 204 additions and 22 deletions
|
|
@ -71,7 +71,7 @@ done:
|
|||
}
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
const a2dp_aac_t a2dp_aac = {
|
||||
.object_type =
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ static inline size_t codec_get_caps_size(const struct media_codec *codec)
|
|||
}
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
size_t actual_conf_size = codec_get_caps_size(codec);
|
||||
const a2dp_aptx_t a2dp_aptx = {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ struct duplex_impl {
|
|||
};
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
const a2dp_faststream_t a2dp_faststream = {
|
||||
.info = codec->vendor,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ struct impl {
|
|||
};
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
const a2dp_lc3plus_hr_t a2dp_lc3plus_hr = {
|
||||
.info = codec->vendor,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ struct impl {
|
|||
int frame_count;
|
||||
};
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
static const a2dp_ldac_t a2dp_ldac = {
|
||||
.info.vendor_id = LDAC_VENDOR_ID,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ struct impl {
|
|||
};
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
a2dp_opus_g_t conf = {
|
||||
.info = codec->vendor,
|
||||
|
|
|
|||
|
|
@ -556,7 +556,7 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc
|
|||
}
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
a2dp_opus_05_t a2dp_opus_05 = {
|
||||
.info = codec->vendor,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ struct impl {
|
|||
};
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
static const a2dp_sbc_t a2dp_sbc = {
|
||||
.frequency =
|
||||
|
|
|
|||
|
|
@ -25,15 +25,42 @@
|
|||
LC3_FREQ_44KHZ | \
|
||||
LC3_FREQ_48KHZ)
|
||||
|
||||
#define LC3_VAL_FREQ_8KHZ 8000
|
||||
#define LC3_VAL_FREQ_11KHZ 11025
|
||||
#define LC3_VAL_FREQ_16KHZ 16000
|
||||
#define LC3_VAL_FREQ_22KHZ 22050
|
||||
#define LC3_VAL_FREQ_24KHZ 24000
|
||||
#define LC3_VAL_FREQ_32KHZ 32000
|
||||
#define LC3_VAL_FREQ_44KHZ 44100
|
||||
#define LC3_VAL_FREQ_48KHZ 48000
|
||||
|
||||
#define LC3_TYPE_DUR 0x02
|
||||
#define LC3_DUR_7_5 (1 << 0)
|
||||
#define LC3_DUR_10 (1 << 1)
|
||||
#define LC3_DUR_ANY (LC3_DUR_7_5 | \
|
||||
LC3_DUR_10)
|
||||
|
||||
#define LC3_VAL_DUR_7_5 7.5
|
||||
#define LC3_VAL_DUR_10 10
|
||||
|
||||
#define LC3_TYPE_CHAN 0x03
|
||||
#define LC3_CHAN_1 (1 << 0)
|
||||
#define LC3_CHAN_2 (1 << 1)
|
||||
#define LC3_CHAN_3 (1 << 2)
|
||||
#define LC3_CHAN_4 (1 << 3)
|
||||
#define LC3_CHAN_5 (1 << 4)
|
||||
#define LC3_CHAN_6 (1 << 5)
|
||||
#define LC3_CHAN_7 (1 << 6)
|
||||
#define LC3_CHAN_8 (1 << 7)
|
||||
|
||||
#define LC3_VAL_CHAN_1 1
|
||||
#define LC3_VAL_CHAN_2 2
|
||||
#define LC3_VAL_CHAN_3 3
|
||||
#define LC3_VAL_CHAN_4 4
|
||||
#define LC3_VAL_CHAN_5 5
|
||||
#define LC3_VAL_CHAN_6 6
|
||||
#define LC3_VAL_CHAN_7 7
|
||||
#define LC3_VAL_CHAN_8 8
|
||||
|
||||
#define LC3_TYPE_FRAMELEN 0x04
|
||||
#define LC3_TYPE_BLKS 0x05
|
||||
|
|
|
|||
|
|
@ -238,20 +238,174 @@ static int write_ltv_uint32(uint8_t *dest, uint8_t type, uint32_t value)
|
|||
return write_ltv(dest, type, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static uint16_t parse_rates(const char *str)
|
||||
{
|
||||
char *s, *p, *save = NULL;
|
||||
uint16_t rate_mask = 0;
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
s = strdup(str);
|
||||
if (s == NULL)
|
||||
return 0;
|
||||
|
||||
for (p = s; (p = strtok_r(p, "[, ]", &save)) != NULL; p = NULL) {
|
||||
if (*p == '\0')
|
||||
continue;
|
||||
|
||||
switch (atoi(p)) {
|
||||
case LC3_VAL_FREQ_8KHZ:
|
||||
rate_mask |= LC3_FREQ_8KHZ;
|
||||
break;
|
||||
case LC3_VAL_FREQ_16KHZ:
|
||||
rate_mask |= LC3_FREQ_16KHZ;
|
||||
break;
|
||||
case LC3_VAL_FREQ_24KHZ:
|
||||
rate_mask |= LC3_FREQ_24KHZ;
|
||||
break;
|
||||
case LC3_VAL_FREQ_32KHZ:
|
||||
rate_mask |= LC3_FREQ_32KHZ;
|
||||
break;
|
||||
case LC3_VAL_FREQ_44KHZ:
|
||||
rate_mask |= LC3_FREQ_44KHZ;
|
||||
break;
|
||||
case LC3_VAL_FREQ_48KHZ:
|
||||
rate_mask |= LC3_FREQ_48KHZ;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(s);
|
||||
|
||||
return rate_mask;
|
||||
}
|
||||
|
||||
static uint8_t parse_durations(const char *str)
|
||||
{
|
||||
char *s, *p, *save = NULL;
|
||||
uint8_t duration_mask = 0;
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
s = strdup(str);
|
||||
if (s == NULL)
|
||||
return 0;
|
||||
|
||||
for (p = s; (p = strtok_r(p, "[, ]", &save)) != NULL; p = NULL) {
|
||||
double duration;
|
||||
|
||||
if (*p == '\0')
|
||||
continue;
|
||||
|
||||
duration = atof(p);
|
||||
|
||||
if (duration == LC3_VAL_DUR_7_5)
|
||||
duration_mask |= LC3_DUR_7_5;
|
||||
else if (duration == LC3_VAL_DUR_10)
|
||||
duration_mask |= LC3_DUR_10;
|
||||
}
|
||||
free(s);
|
||||
|
||||
return duration_mask;
|
||||
}
|
||||
|
||||
static uint8_t parse_channel_counts(const char *str)
|
||||
{
|
||||
char *s, *p, *save = NULL;
|
||||
uint8_t channel_counts = 0;
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
s = strdup(str);
|
||||
if (s == NULL)
|
||||
return 0;
|
||||
|
||||
for (p = s; (p = strtok_r(p, "[, ]", &save)) != NULL; p = NULL) {
|
||||
if (*p == '\0')
|
||||
continue;
|
||||
|
||||
switch (atoi(p)) {
|
||||
case LC3_VAL_CHAN_1:
|
||||
channel_counts |= LC3_CHAN_1;
|
||||
break;
|
||||
case LC3_VAL_CHAN_2:
|
||||
channel_counts |= LC3_CHAN_2;
|
||||
break;
|
||||
case LC3_VAL_CHAN_3:
|
||||
channel_counts |= LC3_CHAN_3;
|
||||
break;
|
||||
case LC3_VAL_CHAN_4:
|
||||
channel_counts |= LC3_CHAN_4;
|
||||
break;
|
||||
case LC3_VAL_CHAN_5:
|
||||
channel_counts |= LC3_CHAN_5;
|
||||
break;
|
||||
case LC3_VAL_CHAN_6:
|
||||
channel_counts |= LC3_CHAN_6;
|
||||
break;
|
||||
case LC3_VAL_CHAN_7:
|
||||
channel_counts |= LC3_CHAN_7;
|
||||
break;
|
||||
case LC3_VAL_CHAN_8:
|
||||
channel_counts |= LC3_CHAN_8;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(s);
|
||||
|
||||
return channel_counts;
|
||||
}
|
||||
|
||||
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||
{
|
||||
uint8_t *data = caps;
|
||||
uint16_t framelen[2] = {htobs(LC3_MIN_FRAME_BYTES), htobs(LC3_MAX_FRAME_BYTES)};
|
||||
const char *str;
|
||||
uint16_t framelen[2];
|
||||
uint16_t rate_mask = LC3_FREQ_48KHZ | LC3_FREQ_32KHZ | \
|
||||
LC3_FREQ_24KHZ | LC3_FREQ_16KHZ | LC3_FREQ_8KHZ;
|
||||
uint8_t duration_mask = LC3_DUR_ANY;
|
||||
uint8_t channel_counts = LC3_CHAN_1 | LC3_CHAN_2;
|
||||
uint16_t framelen_min = LC3_MIN_FRAME_BYTES;
|
||||
uint16_t framelen_max = LC3_MAX_FRAME_BYTES;
|
||||
uint8_t max_frames = 2;
|
||||
|
||||
data += write_ltv_uint16(data, LC3_TYPE_FREQ,
|
||||
htobs(LC3_FREQ_48KHZ | LC3_FREQ_32KHZ | \
|
||||
LC3_FREQ_24KHZ | LC3_FREQ_16KHZ | LC3_FREQ_8KHZ));
|
||||
data += write_ltv_uint8(data, LC3_TYPE_DUR, LC3_DUR_ANY);
|
||||
data += write_ltv_uint8(data, LC3_TYPE_CHAN, LC3_CHAN_1 | LC3_CHAN_2);
|
||||
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.rates")))
|
||||
rate_mask = parse_rates(str);
|
||||
|
||||
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.durations")))
|
||||
duration_mask = parse_durations(str);
|
||||
|
||||
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.channels")))
|
||||
channel_counts = parse_channel_counts(str);
|
||||
|
||||
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.framelen_min")))
|
||||
framelen_min = atoi(str);
|
||||
|
||||
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.framelen_max")))
|
||||
framelen_max = atoi(str);
|
||||
|
||||
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.max_frames")))
|
||||
max_frames = atoi(str);
|
||||
|
||||
framelen[0] = htobs(framelen_min);
|
||||
framelen[1] = htobs(framelen_max);
|
||||
|
||||
data += write_ltv_uint16(data, LC3_TYPE_FREQ, htobs(rate_mask));
|
||||
data += write_ltv_uint8(data, LC3_TYPE_DUR, duration_mask);
|
||||
data += write_ltv_uint8(data, LC3_TYPE_CHAN, channel_counts);
|
||||
data += write_ltv(data, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen));
|
||||
/* XXX: we support only one frame block -> max 2 frames per SDU */
|
||||
data += write_ltv_uint8(data, LC3_TYPE_BLKS, 2);
|
||||
if (max_frames > 2)
|
||||
max_frames = 2;
|
||||
|
||||
data += write_ltv_uint8(data, LC3_TYPE_BLKS, max_frames);
|
||||
|
||||
return data - caps;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4791,7 +4791,7 @@ static int bluez_register_endpoint_legacy(struct spa_bt_adapter *adapter,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = caps_size = codec->fill_caps(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, caps);
|
||||
ret = caps_size = codec->fill_caps(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, &monitor->global_settings, caps);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
@ -4988,7 +4988,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
|||
continue;
|
||||
|
||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) {
|
||||
caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, caps);
|
||||
caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps);
|
||||
if (caps_size < 0)
|
||||
continue;
|
||||
|
||||
|
|
@ -5003,7 +5003,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
|||
}
|
||||
|
||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) {
|
||||
caps_size = codec->fill_caps(codec, 0, caps);
|
||||
caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps);
|
||||
if (caps_size < 0)
|
||||
continue;
|
||||
|
||||
|
|
@ -5019,7 +5019,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
|||
|
||||
if (codec->bap && register_bcast) {
|
||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST)) {
|
||||
caps_size = codec->fill_caps(codec, 0, caps);
|
||||
caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps);
|
||||
if (caps_size < 0)
|
||||
continue;
|
||||
|
||||
|
|
@ -5034,7 +5034,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
|
|||
}
|
||||
|
||||
if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST)) {
|
||||
caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, caps);
|
||||
caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps);
|
||||
if (caps_size < 0)
|
||||
continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ struct media_codec {
|
|||
|
||||
/** If fill_caps is NULL, no endpoint is registered (for sharing with another codec). */
|
||||
int (*fill_caps) (const struct media_codec *codec, uint32_t flags,
|
||||
uint8_t caps[A2DP_MAX_CAPS_SIZE]);
|
||||
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]);
|
||||
|
||||
int (*select_config) (const struct media_codec *codec, uint32_t flags,
|
||||
const void *caps, size_t caps_size,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue