mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-03 09:01:54 -05:00
pulse-bridge: work more on timings
Fixes sync in firefox
This commit is contained in:
parent
ab4def7e5f
commit
4ad085ef96
1 changed files with 180 additions and 57 deletions
|
|
@ -72,6 +72,9 @@
|
|||
#define NATIVE_COOKIE_LENGTH 256
|
||||
#define MAX_TAG_SIZE (64*1024)
|
||||
|
||||
#define MIN_BUFFERS 8u
|
||||
#define MAX_BUFFERS 64u
|
||||
|
||||
enum error_code {
|
||||
ERR_OK = 0, /**< No error */
|
||||
ERR_ACCESS, /**< Access failure */
|
||||
|
|
@ -220,6 +223,17 @@ static inline uint32_t format_pa2id(enum sample_format format)
|
|||
return audio_formats[format].format;
|
||||
}
|
||||
|
||||
static inline enum sample_format format_id2pa(uint32_t id)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < SPA_N_ELEMENTS(audio_formats); i++) {
|
||||
if (id == audio_formats[i].format)
|
||||
return i;
|
||||
}
|
||||
return SAMPLE_INVALID;
|
||||
}
|
||||
|
||||
|
||||
struct sample_spec {
|
||||
enum sample_format format;
|
||||
uint32_t rate;
|
||||
|
|
@ -293,7 +307,11 @@ struct stream {
|
|||
struct spa_list blocks;
|
||||
int64_t read_index;
|
||||
int64_t write_index;
|
||||
uint64_t underrun_for;
|
||||
uint64_t playing_for;
|
||||
uint64_t ticks_base;
|
||||
struct timeval timestamp;
|
||||
int64_t delay;
|
||||
|
||||
struct sample_spec ss;
|
||||
struct channel_map map;
|
||||
|
|
@ -308,6 +326,7 @@ struct stream {
|
|||
unsigned int volume_set:1;
|
||||
unsigned int muted_set:1;
|
||||
unsigned int adjust_latency:1;
|
||||
unsigned int have_time:1;
|
||||
};
|
||||
|
||||
enum {
|
||||
|
|
@ -1098,11 +1117,10 @@ static void stream_flush(struct stream *stream)
|
|||
struct block *block;
|
||||
spa_list_consume(block, &stream->blocks, link)
|
||||
block_free(block);
|
||||
if (stream->direction == PW_DIRECTION_INPUT)
|
||||
stream->read_index = stream->write_index;
|
||||
else
|
||||
stream->write_index = stream->read_index;
|
||||
stream->write_index = stream->read_index = 0;
|
||||
stream->playing_for = 0;
|
||||
stream->underrun_for = 0;
|
||||
stream->have_time = false;
|
||||
}
|
||||
|
||||
static void stream_free(struct stream *stream)
|
||||
|
|
@ -1116,6 +1134,7 @@ static void stream_free(struct stream *stream)
|
|||
}
|
||||
free(stream);
|
||||
}
|
||||
|
||||
static inline uint32_t queued_size(const struct stream *s, uint64_t elapsed)
|
||||
{
|
||||
uint64_t queued;
|
||||
|
|
@ -1356,13 +1375,80 @@ static void stream_state_changed(void *data, enum pw_stream_state old,
|
|||
}
|
||||
}
|
||||
|
||||
static const struct spa_pod *get_buffers_param(struct stream *s,
|
||||
struct buffer_attr *attr, struct spa_pod_builder *b)
|
||||
{
|
||||
const struct spa_pod *param;
|
||||
uint32_t blocks, buffers, size, maxsize, stride;
|
||||
|
||||
blocks = 1;
|
||||
stride = s->frame_size;
|
||||
|
||||
maxsize = attr->tlength;
|
||||
size = attr->minreq;
|
||||
buffers = SPA_CLAMP(maxsize / size, MIN_BUFFERS, MAX_BUFFERS);
|
||||
|
||||
pw_log_info("stream %p: stride %d maxsize %d size %u buffers %d", s, stride, maxsize,
|
||||
size, buffers);
|
||||
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
||||
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, MIN_BUFFERS, MAX_BUFFERS),
|
||||
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks),
|
||||
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(
|
||||
size, size, maxsize),
|
||||
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride),
|
||||
SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
|
||||
return param;
|
||||
}
|
||||
|
||||
static int parse_param(const struct spa_pod *param, struct sample_spec *ss, struct channel_map *map)
|
||||
{
|
||||
struct spa_audio_info info = { 0 };
|
||||
// uint32_t i;
|
||||
|
||||
spa_format_parse(param, &info.media_type, &info.media_subtype);
|
||||
|
||||
if (info.media_type != SPA_MEDIA_TYPE_audio ||
|
||||
info.media_subtype != SPA_MEDIA_SUBTYPE_raw ||
|
||||
spa_format_audio_raw_parse(param, &info.info.raw) < 0 ||
|
||||
!SPA_AUDIO_FORMAT_IS_INTERLEAVED(info.info.raw.format)) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
ss->format = format_id2pa(info.info.raw.format);
|
||||
if (ss->format == SAMPLE_INVALID)
|
||||
return -ENOTSUP;
|
||||
|
||||
ss->rate = info.info.raw.rate;
|
||||
ss->channels = info.info.raw.channels;
|
||||
|
||||
map->channels = info.info.raw.channels;
|
||||
// for (i = 0; i < map->channels; i++)
|
||||
// map->map[i] = info.info.raw.position[i];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
||||
{
|
||||
struct stream *stream = data;
|
||||
const struct spa_pod *params[4];
|
||||
uint32_t n_params = 0;
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
int res;
|
||||
|
||||
if (id != SPA_PARAM_Format || param == NULL)
|
||||
return;
|
||||
|
||||
if ((res = parse_param(param, &stream->ss, &stream->map)) < 0) {
|
||||
pw_stream_set_error(stream->stream, res, "format not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
pw_log_info(NAME" %p: got rate:%u channels:%u", stream, stream->ss.rate, stream->ss.channels);
|
||||
|
||||
stream->frame_size = sample_spec_frame_size(&stream->ss);
|
||||
|
||||
if (stream->create_tag != SPA_ID_INVALID) {
|
||||
|
|
@ -1377,11 +1463,46 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *
|
|||
}
|
||||
if (stream->corked)
|
||||
pw_stream_set_active(stream->stream, false);
|
||||
|
||||
if (stream->direction == PW_DIRECTION_OUTPUT)
|
||||
reply_create_playback_stream(stream);
|
||||
else
|
||||
reply_create_record_stream(stream);
|
||||
}
|
||||
|
||||
params[n_params++] = get_buffers_param(stream, &stream->attr, &b);
|
||||
pw_stream_update_params(stream->stream, params, n_params);
|
||||
}
|
||||
|
||||
static void update_timing_info(struct stream *stream)
|
||||
{
|
||||
struct pw_time pwt;
|
||||
int64_t delay, pos;
|
||||
|
||||
pw_stream_get_time(stream->stream, &pwt);
|
||||
|
||||
stream->timestamp.tv_sec = pwt.now / SPA_NSEC_PER_SEC;
|
||||
stream->timestamp.tv_usec = (pwt.now % SPA_NSEC_PER_SEC) / SPA_NSEC_PER_USEC;
|
||||
|
||||
if (pwt.rate.denom > 0) {
|
||||
uint64_t ticks = pwt.ticks;
|
||||
if (!stream->have_time)
|
||||
stream->ticks_base = ticks;
|
||||
if (ticks > stream->ticks_base)
|
||||
pos = ((ticks - stream->ticks_base) * stream->ss.rate / pwt.rate.denom) * stream->frame_size;
|
||||
else
|
||||
pos = 0;
|
||||
delay = pwt.delay * SPA_USEC_PER_SEC / pwt.rate.denom;
|
||||
stream->have_time = true;
|
||||
} else {
|
||||
pos = delay = 0;
|
||||
stream->have_time = false;
|
||||
}
|
||||
if (stream->direction == PW_DIRECTION_OUTPUT)
|
||||
stream->read_index = pos;
|
||||
else
|
||||
stream->write_index = pos;
|
||||
stream->delay = delay;
|
||||
}
|
||||
|
||||
static void stream_process(void *data)
|
||||
|
|
@ -1395,6 +1516,8 @@ static void stream_process(void *data)
|
|||
|
||||
pw_log_trace(NAME" %p: process", stream);
|
||||
|
||||
update_timing_info(stream);
|
||||
|
||||
while (!spa_list_is_empty(&stream->blocks)) {
|
||||
buffer = pw_stream_dequeue_buffer(stream->stream);
|
||||
if (buffer == NULL)
|
||||
|
|
@ -1448,63 +1571,58 @@ static const struct pw_stream_events stream_events =
|
|||
#define DEFAULT_PROCESS_MSEC 20 /* 20ms */
|
||||
#define DEFAULT_FRAGSIZE_MSEC DEFAULT_TLENGTH_MSEC
|
||||
|
||||
static size_t usec_to_bytes_round_up(uint64_t usec, const struct sample_spec *ss)
|
||||
static uint32_t usec_to_bytes_round_up(uint64_t usec, const struct sample_spec *ss)
|
||||
{
|
||||
uint64_t u;
|
||||
u = (uint64_t) usec * (uint64_t) ss->rate;
|
||||
u = (u + 1000000UL - 1) / 1000000UL;
|
||||
u *= sample_spec_frame_size(ss);
|
||||
return (size_t) u;
|
||||
return (uint32_t) u;
|
||||
}
|
||||
|
||||
static void fix_playback_buffer_attr(struct stream *s, struct buffer_attr *attr)
|
||||
{
|
||||
size_t frame_size, max_prebuf;
|
||||
uint32_t frame_size, max_prebuf;
|
||||
|
||||
frame_size = sample_spec_frame_size(&s->ss);
|
||||
frame_size = s->frame_size;
|
||||
|
||||
if (attr->maxlength == (uint32_t) -1 || attr->maxlength > MAXLENGTH)
|
||||
attr->maxlength = MAXLENGTH;
|
||||
if (attr->maxlength <= 0)
|
||||
attr->maxlength = (uint32_t) frame_size;
|
||||
attr->maxlength -= attr->maxlength % frame_size;
|
||||
attr->maxlength = SPA_MAX(attr->maxlength, frame_size);
|
||||
|
||||
if (attr->tlength == (uint32_t) -1)
|
||||
attr->tlength = (uint32_t) usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*1000, &s->ss);
|
||||
|
||||
if (attr->tlength <= 0)
|
||||
attr->tlength = (uint32_t) frame_size;
|
||||
attr->tlength = usec_to_bytes_round_up(DEFAULT_TLENGTH_MSEC*1000, &s->ss);
|
||||
if (attr->tlength > attr->maxlength)
|
||||
attr->tlength = attr->maxlength;
|
||||
attr->tlength -= attr->tlength % frame_size;
|
||||
attr->tlength = SPA_MAX(attr->tlength, frame_size);
|
||||
|
||||
if (attr->minreq == (uint32_t) -1) {
|
||||
uint32_t process = (uint32_t) usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*1000, &s->ss);
|
||||
uint32_t process = usec_to_bytes_round_up(DEFAULT_PROCESS_MSEC*1000, &s->ss);
|
||||
/* With low-latency, tlength/4 gives a decent default in all of traditional,
|
||||
* adjust latency and early request modes. */
|
||||
uint32_t m = attr->tlength / 4;
|
||||
if (frame_size)
|
||||
m -= m % frame_size;
|
||||
m -= m % frame_size;
|
||||
attr->minreq = SPA_MIN(process, m);
|
||||
}
|
||||
if (attr->minreq <= 0)
|
||||
attr->minreq = (uint32_t) frame_size;
|
||||
|
||||
if (attr->tlength < attr->minreq+frame_size)
|
||||
attr->tlength = attr->minreq+(uint32_t) frame_size;
|
||||
|
||||
attr->tlength = attr->minreq + frame_size;
|
||||
|
||||
attr->minreq -= attr->minreq % frame_size;
|
||||
if (attr->minreq <= 0) {
|
||||
attr->minreq = (uint32_t) frame_size;
|
||||
attr->tlength += (uint32_t) frame_size*2;
|
||||
attr->minreq = frame_size;
|
||||
attr->tlength += frame_size*2;
|
||||
}
|
||||
|
||||
if (attr->tlength <= attr->minreq)
|
||||
attr->tlength = attr->minreq*2 + (uint32_t) frame_size;
|
||||
attr->tlength = attr->minreq*2 + frame_size;
|
||||
|
||||
max_prebuf = attr->tlength + (uint32_t)frame_size - attr->minreq;
|
||||
|
||||
if (attr->prebuf == (uint32_t) -1 ||
|
||||
attr->prebuf > max_prebuf)
|
||||
attr->prebuf = max_prebuf;
|
||||
max_prebuf = attr->tlength + frame_size - attr->minreq;
|
||||
if (attr->prebuf == (uint32_t) -1 || attr->prebuf > max_prebuf)
|
||||
attr->prebuf = max_prebuf;
|
||||
attr->prebuf -= attr->prebuf % frame_size;
|
||||
attr->prebuf = SPA_MAX(attr->prebuf, frame_size);
|
||||
}
|
||||
|
||||
static int do_create_playback_stream(struct client *client, uint32_t command, uint32_t tag, struct data *d)
|
||||
|
|
@ -1655,16 +1773,6 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui
|
|||
stream->channel = pw_map_insert_new(&client->streams, stream);
|
||||
spa_list_init(&stream->blocks);
|
||||
|
||||
stream->stream = pw_stream_new(client->core, name, props);
|
||||
props = NULL;
|
||||
if (stream->stream == NULL) {
|
||||
res = -errno;
|
||||
goto error;
|
||||
}
|
||||
pw_stream_add_listener(stream->stream,
|
||||
&stream->stream_listener,
|
||||
&stream_events, stream);
|
||||
|
||||
stream->direction = PW_DIRECTION_OUTPUT;
|
||||
stream->create_tag = tag;
|
||||
stream->ss = ss;
|
||||
|
|
@ -1674,9 +1782,24 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui
|
|||
stream->muted = muted;
|
||||
stream->muted_set = muted_set;
|
||||
|
||||
stream->frame_size = sample_spec_frame_size(&stream->ss);
|
||||
|
||||
fix_playback_buffer_attr(stream, &attr);
|
||||
stream->attr = attr;
|
||||
|
||||
pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u",
|
||||
stream->attr.minreq * 2 / stream->frame_size, ss.rate);
|
||||
|
||||
stream->stream = pw_stream_new(client->core, name, props);
|
||||
props = NULL;
|
||||
if (stream->stream == NULL) {
|
||||
res = -errno;
|
||||
goto error;
|
||||
}
|
||||
pw_stream_add_listener(stream->stream,
|
||||
&stream->stream_listener,
|
||||
&stream_events, stream);
|
||||
|
||||
info = SPA_AUDIO_INFO_RAW_INIT(
|
||||
.format = format_pa2id(ss.format),
|
||||
.channels = ss.channels,
|
||||
|
|
@ -1910,7 +2033,7 @@ static int do_get_playback_latency(struct client *client, uint32_t command, uint
|
|||
uint8_t buffer[1024];
|
||||
struct data reply;
|
||||
uint32_t channel;
|
||||
struct timeval tv, now;
|
||||
struct timeval tv;
|
||||
struct stream *stream;
|
||||
int res;
|
||||
|
||||
|
|
@ -1925,32 +2048,31 @@ static int do_get_playback_latency(struct client *client, uint32_t command, uint
|
|||
if (stream == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
pw_log_debug("read:%"PRIi64" write:%"PRIi64" queued:%"PRIi64,
|
||||
stream->read_index, stream->write_index,
|
||||
stream->write_index - stream->read_index);
|
||||
|
||||
spa_zero(reply);
|
||||
reply.data = buffer;
|
||||
reply.length = sizeof(buffer);
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
pw_log_info("read:%"PRIi64" write:%"PRIi64" queued:%"PRIi64" delay:%"PRIi64,
|
||||
stream->read_index, stream->write_index,
|
||||
stream->write_index - stream->read_index, stream->delay);
|
||||
|
||||
data_put(&reply,
|
||||
TAG_U32, COMMAND_REPLY,
|
||||
TAG_U32, tag,
|
||||
TAG_USEC, 0, /* sink latency + queued samples */
|
||||
TAG_USEC, 0, /* always 0 */
|
||||
TAG_BOOLEAN, true, /* playing state */
|
||||
TAG_USEC, stream->delay, /* sink latency + queued samples */
|
||||
TAG_USEC, 0, /* always 0 */
|
||||
TAG_BOOLEAN, stream->playing_for > 0 &&
|
||||
!stream->corked, /* playing state */
|
||||
TAG_TIMEVAL, &tv,
|
||||
TAG_TIMEVAL, &now,
|
||||
TAG_TIMEVAL, &stream->timestamp,
|
||||
TAG_S64, stream->write_index,
|
||||
TAG_S64, stream->read_index,
|
||||
TAG_INVALID);
|
||||
|
||||
if (client->version >= 13) {
|
||||
data_put(&reply,
|
||||
TAG_U64, 0, /* underrun_for */
|
||||
TAG_U64, 0, /* playing_for */
|
||||
TAG_U64, stream->underrun_for,
|
||||
TAG_U64, stream->playing_for,
|
||||
TAG_INVALID);
|
||||
}
|
||||
return send_data(client, &reply);
|
||||
|
|
@ -2021,6 +2143,7 @@ static int do_cork_stream(struct client *client, uint32_t command, uint32_t tag,
|
|||
|
||||
pw_stream_set_active(stream->stream, !cork);
|
||||
stream->corked = cork;
|
||||
stream->playing_for = 0;
|
||||
|
||||
return reply_simple_ack(client, tag);
|
||||
}
|
||||
|
|
@ -2323,11 +2446,11 @@ static int do_stat(struct client *client, uint32_t command, uint32_t tag, struct
|
|||
data_put(&reply,
|
||||
TAG_U32, COMMAND_REPLY,
|
||||
TAG_U32, tag,
|
||||
TAG_U32, 0,
|
||||
TAG_U32, 0,
|
||||
TAG_U32, 0,
|
||||
TAG_U32, 0,
|
||||
TAG_U32, 0,
|
||||
TAG_U32, 0, /* n_allocated */
|
||||
TAG_U32, 0, /* allocated size */
|
||||
TAG_U32, 0, /* n_accumulated */
|
||||
TAG_U32, 0, /* accumulated_size */
|
||||
TAG_U32, 0, /* sample cache size */
|
||||
TAG_INVALID);
|
||||
|
||||
return send_data(client, &reply);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue