bluez5: add delay adjustment property + fallback value for a2dp-sink

Not all devices report their A2DP delay. In those cases, use a fallback
value of 150ms by default.

Make the delay adjustable with a SPA_Prop, and expose it as a part of
the route. Implement the corresponding parts in media-session.
This commit is contained in:
Pauli Virtanen 2021-02-13 22:59:04 +02:00 committed by Wim Taymans
parent df1dbee687
commit d75a79babc
6 changed files with 116 additions and 8 deletions

View file

@ -84,6 +84,7 @@ enum spa_prop {
SPA_PROP_monitorMute, /**< mute (Bool) */
SPA_PROP_monitorVolumes, /**< a volume array, one volume per
* channel (Array of Float) */
SPA_PROP_latencyOffsetNsec, /**< delay adjustment */
SPA_PROP_START_Video = 0x20000, /**< video related properties */
SPA_PROP_brightness,

View file

@ -112,6 +112,7 @@ static const struct spa_type_info spa_type_props[] = {
{ SPA_PROP_channelMap, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelMap", spa_type_prop_channel_map },
{ SPA_PROP_monitorMute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "monitorMute", NULL },
{ SPA_PROP_monitorVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "monitorVolumes", spa_type_prop_monitor_volume },
{ SPA_PROP_latencyOffsetNsec, SPA_TYPE_Long, SPA_TYPE_INFO_PROPS_BASE "latencyOffsetNsec", NULL },
{ SPA_PROP_brightness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL },
{ SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL },

View file

@ -58,6 +58,7 @@ struct codec;
struct props {
uint32_t min_latency;
uint32_t max_latency;
int64_t latency_offset;
};
#define FILL_FRAMES 2
@ -154,6 +155,7 @@ static void reset_props(struct props *props)
{
props->min_latency = default_min_latency;
props->max_latency = default_max_latency;
props->latency_offset = 0;
}
static int impl_node_enum_params(void *object, int seq,
@ -197,6 +199,13 @@ static int impl_node_enum_params(void *object, int seq,
SPA_PROP_INFO_name, SPA_POD_String("The maximum latency"),
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX));
break;
case 2:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec),
SPA_PROP_INFO_name, SPA_POD_String("Latency offset (ns)"),
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0, INT64_MIN, INT64_MAX));
break;
default:
return 0;
}
@ -211,7 +220,8 @@ static int impl_node_enum_params(void *object, int seq,
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_minLatency, SPA_POD_Int(p->min_latency),
SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency));
SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency),
SPA_PROP_latencyOffsetNsec, SPA_POD_Long(p->latency_offset));
break;
default:
return 0;
@ -298,6 +308,8 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
return 0;
}
static void emit_node_props_changed(struct impl *this);
static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
const struct spa_pod *param)
{
@ -312,12 +324,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
if (param == NULL) {
reset_props(p);
emit_node_props_changed(this);
return 0;
}
spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency),
SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency));
if (spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency),
SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency),
SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&p->latency_offset)) > 0)
emit_node_props_changed(this);
break;
}
default:
@ -624,14 +639,20 @@ static void a2dp_on_timeout(struct spa_source *source)
this->next_time = now_time + duration * SPA_NSEC_PER_SEC / rate;
if (SPA_LIKELY(this->clock)) {
int64_t delay_nsec;
this->clock->nsec = now_time;
this->clock->position += duration;
this->clock->duration = duration;
this->clock->rate_diff = 1.0f;
this->clock->next_nsec = this->next_time;
// The bluetooth AVDTP delay value is measured in units of 100us
this->clock->delay = (100 * this->transport->delay * (int64_t) this->clock->rate.denom) / SPA_USEC_PER_SEC;
delay_nsec = spa_bt_transport_get_delay_nsec(this->transport);
/* Negative delay doesn't work properly, so disallow it */
delay_nsec += SPA_CLAMP(this->props.latency_offset, -delay_nsec, INT64_MAX / 2);
this->clock->delay = (delay_nsec * this->clock->rate.denom) / SPA_NSEC_PER_SEC;
}
spa_log_debug(this->log, NAME" %p: timeout %"PRIu64" %"PRIu64"", this,
@ -679,7 +700,8 @@ static int do_start(struct impl *this)
if (this->codec_data == NULL)
return -EIO;
spa_log_info(this->log, NAME " %p: using A2DP codec %s", this, this->codec->description);
spa_log_info(this->log, NAME " %p: using A2DP codec %s, delay:%"PRIi64" ms", this, this->codec->description,
(int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC));
this->seqnum = 0;
@ -847,6 +869,13 @@ static void emit_port_info(struct impl *this, struct port *port, bool full)
}
}
static void emit_node_props_changed(struct impl *this)
{
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
this->params[1].flags ^= SPA_PARAM_INFO_SERIAL;
emit_node_info(this, false);
}
static int
impl_node_add_listener(void *object,
struct spa_hook *listener,

View file

@ -1084,6 +1084,7 @@ struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor,
t->path = path;
t->fd = -1;
t->sco_io = NULL;
t->delay = SPA_BT_UNKNOWN_DELAY;
t->user_data = SPA_MEMBER(t, sizeof(struct spa_bt_transport), void);
spa_hook_list_init(&t->listener_list);
@ -1260,6 +1261,40 @@ void spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop
}
}
int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t)
{
if (t->delay != SPA_BT_UNKNOWN_DELAY)
return (int64_t)t->delay * 100 * SPA_NSEC_PER_USEC;
/* Fallback values when device does not provide information */
if (t->a2dp_codec == NULL)
return 30 * SPA_NSEC_PER_MSEC;
switch (t->a2dp_codec->codec_id) {
case A2DP_CODEC_SBC:
return 200 * SPA_NSEC_PER_MSEC;
case A2DP_CODEC_MPEG24:
return 200 * SPA_NSEC_PER_MSEC;
case A2DP_CODEC_VENDOR:
{
uint32_t vendor_id = t->a2dp_codec->vendor.vendor_id;
uint16_t codec_id = t->a2dp_codec->vendor.codec_id;
if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID)
return 150 * SPA_NSEC_PER_MSEC;
if (vendor_id == APTX_HD_VENDOR_ID && codec_id == APTX_HD_CODEC_ID)
return 150 * SPA_NSEC_PER_MSEC;
if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID)
return 175 * SPA_NSEC_PER_MSEC;
break;
}
default:
break;
};
return 150 * SPA_NSEC_PER_MSEC;
}
static int transport_update_props(struct spa_bt_transport *transport,
DBusMessageIter *props_iter,
DBusMessageIter *invalidated_iter)

View file

@ -72,6 +72,7 @@ struct node {
unsigned int active:1;
unsigned int mute:1;
uint32_t n_channels;
int64_t latency_offset;
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
float volumes[SPA_AUDIO_MAX_CHANNELS];
};
@ -795,6 +796,11 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id,
node->n_channels, node->channels);
if (this->profile == 1 && dev == DEVICE_ID_SINK) {
spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0);
spa_pod_builder_long(b, node->latency_offset);
}
spa_pod_builder_pop(b, &f[1]);
}
@ -969,6 +975,33 @@ static int node_set_mute(struct impl *this, struct node *node, bool mute)
return 0;
}
static int node_set_latency_offset(struct impl *this, struct node *node, int64_t latency_offset)
{
struct spa_event *event;
uint8_t buffer[4096];
struct spa_pod_builder b = { 0 };
struct spa_pod_frame f[1];
spa_log_info(this->log, "node %p latency offset %"PRIi64" nsec", node, latency_offset);
node->latency_offset = latency_offset;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
spa_pod_builder_push_object(&b, &f[0],
SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig);
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0);
spa_pod_builder_int(&b, node->id);
spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0);
spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props,
SPA_PROP_latencyOffsetNsec, SPA_POD_Long(latency_offset));
event = spa_pod_builder_pop(&b, &f[0]);
spa_device_emit_event(&this->hooks, event);
return 0;
}
static int apply_device_props(struct impl *this, struct node *node, struct spa_pod *props)
{
float volume = 0;
@ -979,6 +1012,7 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p
float volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
uint32_t n_volumes = 0, n_channels = 0;
int64_t latency_offset = 0;
if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props))
return -EINVAL;
@ -1009,6 +1043,11 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p
changed++;
}
break;
case SPA_PROP_latencyOffsetNsec:
if (spa_pod_get_long(&prop->value, &latency_offset) == 0) {
node_set_latency_offset(this, node, latency_offset);
changed++;
}
}
}
if (n_volumes > 0)

View file

@ -143,6 +143,8 @@ extern "C" {
#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink"
#define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource"
#define SPA_BT_UNKNOWN_DELAY 0
/* HFP uses SBC encoding with precisely defined parameters. Hence, the size
* of the input (number of PCM samples) and output is known up front. */
#define MSBC_DECODED_SIZE 240
@ -481,6 +483,7 @@ struct spa_bt_transport *spa_bt_transport_find(struct spa_bt_monitor *monitor, c
struct spa_bt_transport *spa_bt_transport_find_full(struct spa_bt_monitor *monitor,
bool (*callback) (struct spa_bt_transport *t, const void *data),
const void *data);
int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t);
int spa_bt_transport_acquire(struct spa_bt_transport *t, bool optional);
int spa_bt_transport_release(struct spa_bt_transport *t);