mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
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:
parent
df1dbee687
commit
d75a79babc
6 changed files with 116 additions and 8 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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_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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue