mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	pulse: improve handling of buffer attributes
Repect minreq and fragsize for playback and capture. Try to configure tlength at 2 seconds, minreq at around 25ms. Should greatly improve compatibility with audacious and mpv. Count since_underrun correctly. Fixes #278
This commit is contained in:
		
							parent
							
								
									6eb4b552ad
								
							
						
					
					
						commit
						8f75056689
					
				
					 2 changed files with 74 additions and 84 deletions
				
			
		| 
						 | 
				
			
			@ -401,9 +401,6 @@ struct pa_mem {
 | 
			
		|||
	void *user_data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define MAX_BUFFERS     64u
 | 
			
		||||
#define MASK_BUFFERS    (MAX_BUFFERS-1)
 | 
			
		||||
 | 
			
		||||
struct pa_stream {
 | 
			
		||||
	struct spa_list link;
 | 
			
		||||
	int refcount;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,14 +33,17 @@
 | 
			
		|||
#include "core-format.h"
 | 
			
		||||
#include "internal.h"
 | 
			
		||||
 | 
			
		||||
#define MIN_QUEUED	1
 | 
			
		||||
 | 
			
		||||
#define MAX_SIZE	(4*1024*1024)
 | 
			
		||||
#define BLOCK_SIZE	(64*1024)
 | 
			
		||||
#define MIN_BUFFERS     8u
 | 
			
		||||
#define MAX_BUFFERS     64u
 | 
			
		||||
 | 
			
		||||
#define MAX_BUFFER_SAMPLES	(8*1024u)
 | 
			
		||||
#define MAX_SIZE		(4*1024*1024u)
 | 
			
		||||
 | 
			
		||||
static void dump_buffer_attr(pa_stream *s, pa_buffer_attr *attr)
 | 
			
		||||
{
 | 
			
		||||
	char b[1024];
 | 
			
		||||
	pw_log_debug("stream %p: sample: %s", s, pa_sample_spec_snprint(b, sizeof(b), &s->sample_spec));
 | 
			
		||||
	pw_log_debug("stream %p: stride: %zu", s, pa_frame_size(&s->sample_spec));
 | 
			
		||||
	pw_log_debug("stream %p: maxlength: %u", s, attr->maxlength);
 | 
			
		||||
	pw_log_debug("stream %p: tlength: %u", s, attr->tlength);
 | 
			
		||||
	pw_log_debug("stream %p: minreq: %u", s, attr->minreq);
 | 
			
		||||
| 
						 | 
				
			
			@ -48,15 +51,6 @@ static void dump_buffer_attr(pa_stream *s, pa_buffer_attr *attr)
 | 
			
		|||
	pw_log_debug("stream %p: fragsize: %u", s, attr->fragsize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void configure_buffers(pa_stream *s)
 | 
			
		||||
{
 | 
			
		||||
	s->buffer_attr.maxlength = MAX_SIZE;
 | 
			
		||||
	if (s->buffer_attr.prebuf == (uint32_t)-1)
 | 
			
		||||
		s->buffer_attr.prebuf = s->buffer_attr.minreq;
 | 
			
		||||
	s->buffer_attr.fragsize = s->buffer_attr.minreq;
 | 
			
		||||
	dump_buffer_attr(s, &s->buffer_attr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void configure_device(pa_stream *s)
 | 
			
		||||
{
 | 
			
		||||
	struct global *g;
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +125,6 @@ static void stream_state_changed(void *data, enum pw_stream_state old,
 | 
			
		|||
		break;
 | 
			
		||||
	case PW_STREAM_STATE_STREAMING:
 | 
			
		||||
		configure_device(s);
 | 
			
		||||
		configure_buffers(s);
 | 
			
		||||
		pa_stream_set_state(s, PA_STREAM_READY);
 | 
			
		||||
		if (s->suspended) {
 | 
			
		||||
			s->suspended = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -150,32 +143,19 @@ static const struct spa_pod *get_buffers_param(pa_stream *s, pa_buffer_attr *att
 | 
			
		|||
	blocks = 1;
 | 
			
		||||
	stride = pa_frame_size(&s->sample_spec);
 | 
			
		||||
 | 
			
		||||
	if (attr->tlength == (uint32_t)-1 || attr->tlength == 0)
 | 
			
		||||
		maxsize = 1024;
 | 
			
		||||
	else
 | 
			
		||||
		maxsize = (attr->tlength / stride);
 | 
			
		||||
 | 
			
		||||
	if (attr->minreq == (uint32_t)-1 || attr->minreq == 0)
 | 
			
		||||
		size = maxsize;
 | 
			
		||||
	else
 | 
			
		||||
		size = SPA_MIN(attr->minreq / stride, maxsize);
 | 
			
		||||
 | 
			
		||||
	if (attr->maxlength == (uint32_t)-1)
 | 
			
		||||
		buffers = 3;
 | 
			
		||||
	else
 | 
			
		||||
		buffers = SPA_CLAMP(attr->maxlength / (size * stride), 3u, MAX_BUFFERS);
 | 
			
		||||
	maxsize = attr->tlength;
 | 
			
		||||
	size = attr->minreq;
 | 
			
		||||
	buffers = SPA_CLAMP(maxsize / size, MIN_BUFFERS, MAX_BUFFERS);
 | 
			
		||||
 | 
			
		||||
	pw_log_debug("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, buffers, MAX_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 * stride,
 | 
			
		||||
							size * stride,
 | 
			
		||||
							maxsize * stride),
 | 
			
		||||
							size, size, maxsize),
 | 
			
		||||
			SPA_PARAM_BUFFERS_stride,  SPA_POD_Int(stride),
 | 
			
		||||
			SPA_PARAM_BUFFERS_align,   SPA_POD_Int(16));
 | 
			
		||||
	return param;
 | 
			
		||||
| 
						 | 
				
			
			@ -184,6 +164,7 @@ static const struct spa_pod *get_buffers_param(pa_stream *s, pa_buffer_attr *att
 | 
			
		|||
static void patch_buffer_attr(pa_stream *s, pa_buffer_attr *attr, pa_stream_flags_t *flags) {
 | 
			
		||||
	const char *e, *str;
 | 
			
		||||
	char buf[100];
 | 
			
		||||
	uint32_t stride;
 | 
			
		||||
 | 
			
		||||
	pa_assert(s);
 | 
			
		||||
	pa_assert(attr);
 | 
			
		||||
| 
						 | 
				
			
			@ -228,21 +209,30 @@ static void patch_buffer_attr(pa_stream *s, pa_buffer_attr *attr, pa_stream_flag
 | 
			
		|||
				*flags |= PA_STREAM_ADJUST_LATENCY;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	dump_buffer_attr(s, attr);
 | 
			
		||||
 | 
			
		||||
	if (attr->maxlength == (uint32_t) -1)
 | 
			
		||||
	stride  = pa_frame_size(&s->sample_spec);
 | 
			
		||||
	if (attr->maxlength == (uint32_t) -1 || attr->maxlength == 0)
 | 
			
		||||
		attr->maxlength = MAX_SIZE; /* 4MB is the maximum queue length PulseAudio <= 0.9.9 supported. */
 | 
			
		||||
 | 
			
		||||
	if (attr->tlength == (uint32_t) -1)
 | 
			
		||||
		attr->tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &s->sample_spec); /* 250ms of buffering */
 | 
			
		||||
	if (attr->tlength == (uint32_t) -1 || attr->tlength == 0)
 | 
			
		||||
		attr->tlength = (uint32_t) pa_usec_to_bytes(2*PA_USEC_PER_SEC, &s->sample_spec);
 | 
			
		||||
	attr->tlength = SPA_MIN(attr->tlength, attr->maxlength);
 | 
			
		||||
 | 
			
		||||
	if (attr->minreq == (uint32_t) -1)
 | 
			
		||||
		attr->minreq = attr->tlength; /* Ask for more data when there are only 200ms left in the playback buffer */
 | 
			
		||||
	if (attr->minreq == (uint32_t) -1 || attr->minreq == 0)
 | 
			
		||||
		attr->minreq = pa_usec_to_bytes(25*PA_USEC_PER_MSEC, &s->sample_spec);
 | 
			
		||||
	attr->minreq = SPA_MIN(attr->minreq, attr->tlength / MIN_BUFFERS);
 | 
			
		||||
	attr->minreq = SPA_MAX(attr->minreq, stride);
 | 
			
		||||
 | 
			
		||||
	if (attr->prebuf == (uint32_t) -1)
 | 
			
		||||
		attr->prebuf = attr->tlength; /* Start to play only when the playback is fully filled up once */
 | 
			
		||||
	if (attr->fragsize == (uint32_t) -1 || attr->fragsize == 0)
 | 
			
		||||
		attr->fragsize = pa_usec_to_bytes(25*PA_USEC_PER_MSEC, &s->sample_spec);
 | 
			
		||||
	attr->fragsize = SPA_MIN(attr->fragsize, attr->tlength / MIN_BUFFERS);
 | 
			
		||||
	attr->fragsize = SPA_MAX(attr->fragsize, stride);
 | 
			
		||||
 | 
			
		||||
	if (attr->fragsize == (uint32_t) -1)
 | 
			
		||||
		attr->fragsize = attr->tlength; /* Pass data to the app only when the buffer is filled up once */
 | 
			
		||||
	if (attr->prebuf == (uint32_t) -1 || attr->prebuf == 0)
 | 
			
		||||
		attr->prebuf = attr->tlength - attr->minreq;
 | 
			
		||||
	attr->prebuf = SPA_MIN(attr->prebuf, attr->tlength - attr->minreq);
 | 
			
		||||
	attr->prebuf = SPA_MAX(attr->prebuf, stride);
 | 
			
		||||
 | 
			
		||||
	dump_buffer_attr(s, attr);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -355,7 +345,6 @@ static void update_timing_info(pa_stream *s)
 | 
			
		|||
		ti->configured_source_usec = delay;
 | 
			
		||||
		ti->write_index = pos;
 | 
			
		||||
	}
 | 
			
		||||
	ti->since_underrun = 0;
 | 
			
		||||
	s->timing_info_valid = true;
 | 
			
		||||
	s->queued_bytes = pwt.queued;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -449,20 +438,25 @@ static void stream_process(void *data)
 | 
			
		|||
 | 
			
		||||
	if (s->direction == PA_STREAM_PLAYBACK) {
 | 
			
		||||
		pa_timing_info *i = &s->timing_info;
 | 
			
		||||
		uint64_t queued, writable;
 | 
			
		||||
		uint64_t queued, writable, required;
 | 
			
		||||
 | 
			
		||||
		queue_output(s);
 | 
			
		||||
 | 
			
		||||
		queued = i->write_index - SPA_MIN(i->read_index, i->write_index);
 | 
			
		||||
		writable = s->maxblock - SPA_MIN(queued, s->maxblock);
 | 
			
		||||
		required = SPA_MIN(s->maxblock, s->buffer_attr.minreq);
 | 
			
		||||
 | 
			
		||||
		if (s->write_callback && s->state == PA_STREAM_READY && writable > 0)
 | 
			
		||||
		if (s->write_callback && s->state == PA_STREAM_READY && writable >= required)
 | 
			
		||||
			s->write_callback(s, writable, s->write_userdata);
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		uint64_t required;
 | 
			
		||||
 | 
			
		||||
		pull_input(s);
 | 
			
		||||
 | 
			
		||||
		if (s->read_callback && s->ready_bytes > 0 && s->state == PA_STREAM_READY)
 | 
			
		||||
		required = SPA_MIN(s->maxblock, s->buffer_attr.fragsize);
 | 
			
		||||
 | 
			
		||||
		if (s->read_callback && s->ready_bytes > required && s->state == PA_STREAM_READY)
 | 
			
		||||
			s->read_callback(s, s->ready_bytes, s->read_userdata);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -554,23 +548,6 @@ static pa_stream* stream_new(pa_context *c, const char *name,
 | 
			
		|||
	s->direct_on_input = PA_INVALID_INDEX;
 | 
			
		||||
 | 
			
		||||
	s->stream_index = PA_INVALID_INDEX;
 | 
			
		||||
 | 
			
		||||
	s->buffer_attr.maxlength = (uint32_t) -1;
 | 
			
		||||
	if (ss)
 | 
			
		||||
		s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, ss); /* 250ms of buffering */
 | 
			
		||||
	else {
 | 
			
		||||
		/* FIXME: We assume a worst-case compressed format corresponding to
 | 
			
		||||
		* 48000 Hz, 2 ch, S16 PCM, but this can very well be incorrect */
 | 
			
		||||
		pa_sample_spec tmp_ss = {
 | 
			
		||||
			.format   = PA_SAMPLE_S16NE,
 | 
			
		||||
			.rate     = 48000,
 | 
			
		||||
			.channels = 2,
 | 
			
		||||
		};
 | 
			
		||||
		s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &tmp_ss); /* 250ms of buffering */
 | 
			
		||||
	}
 | 
			
		||||
	s->buffer_attr.minreq = (uint32_t) -1;
 | 
			
		||||
	s->buffer_attr.prebuf = (uint32_t) -1;
 | 
			
		||||
	s->buffer_attr.fragsize = (uint32_t) -1;
 | 
			
		||||
	s->maxblock = INT_MAX;
 | 
			
		||||
 | 
			
		||||
	s->device_index = PA_INVALID_INDEX;
 | 
			
		||||
| 
						 | 
				
			
			@ -802,10 +779,9 @@ static int create_stream(pa_stream_direction_t direction,
 | 
			
		|||
	int res;
 | 
			
		||||
	enum pw_stream_flags fl;
 | 
			
		||||
	const struct spa_pod *params[16];
 | 
			
		||||
	uint32_t i, n_params = 0;
 | 
			
		||||
	uint32_t i, n_params = 0, stride;
 | 
			
		||||
	uint8_t buffer[4096];
 | 
			
		||||
	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
 | 
			
		||||
	uint32_t sample_rate = 0, stride = 0, latency_num;
 | 
			
		||||
	const char *str;
 | 
			
		||||
	uint32_t devid, n_items;
 | 
			
		||||
	struct global *g;
 | 
			
		||||
| 
						 | 
				
			
			@ -886,16 +862,18 @@ static int create_stream(pa_stream_direction_t direction,
 | 
			
		|||
	monitor = (flags & PA_STREAM_PEAK_DETECT);
 | 
			
		||||
	no_remix = (flags & PA_STREAM_NO_REMIX_CHANNELS);
 | 
			
		||||
 | 
			
		||||
	if (attr)
 | 
			
		||||
		s->buffer_attr = *attr;
 | 
			
		||||
 | 
			
		||||
	if (pa_sample_spec_valid(&s->sample_spec)) {
 | 
			
		||||
		params[n_params++] = pa_format_build_param(&b, SPA_PARAM_EnumFormat,
 | 
			
		||||
				&s->sample_spec, &s->channel_map);
 | 
			
		||||
		sample_rate = s->sample_spec.rate;
 | 
			
		||||
		stride = pa_frame_size(&s->sample_spec);
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		pa_sample_spec ss;
 | 
			
		||||
		pa_channel_map chmap;
 | 
			
		||||
		int i;
 | 
			
		||||
		uint32_t sample_rate = 0;
 | 
			
		||||
 | 
			
		||||
		for (i = 0; i < s->n_formats; i++) {
 | 
			
		||||
			if ((res = pa_format_info_to_sample_spec(s->req_formats[i], &ss, NULL)) < 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -911,17 +889,18 @@ static int create_stream(pa_stream_direction_t direction,
 | 
			
		|||
					&ss, &chmap);
 | 
			
		||||
			if (ss.rate > sample_rate) {
 | 
			
		||||
				sample_rate = ss.rate;
 | 
			
		||||
				stride = pa_frame_size(&ss);
 | 
			
		||||
			}
 | 
			
		||||
				s->sample_spec = ss;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (sample_rate == 0) {
 | 
			
		||||
		sample_rate = 48000;
 | 
			
		||||
		stride = sizeof(int16_t) * 2;
 | 
			
		||||
			s->sample_spec.format = PA_SAMPLE_S16NE;
 | 
			
		||||
			s->sample_spec.rate = 48000;
 | 
			
		||||
			s->sample_spec.channels = 2;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (!pa_sample_spec_valid(&s->sample_spec))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (attr)
 | 
			
		||||
		s->buffer_attr = *attr;
 | 
			
		||||
	patch_buffer_attr(s, &s->buffer_attr, &flags);
 | 
			
		||||
 | 
			
		||||
	if (direction == PA_STREAM_RECORD)
 | 
			
		||||
| 
						 | 
				
			
			@ -972,8 +951,8 @@ static int create_stream(pa_stream_direction_t direction,
 | 
			
		|||
	else
 | 
			
		||||
		str = "Music";
 | 
			
		||||
 | 
			
		||||
	latency_num = s->buffer_attr.minreq / stride;
 | 
			
		||||
	sprintf(latency, "%u/%u", SPA_MAX(latency_num, 1u), sample_rate);
 | 
			
		||||
	stride = pa_frame_size(&s->sample_spec);
 | 
			
		||||
	sprintf(latency, "%u/%u", s->buffer_attr.minreq / stride, s->sample_spec.rate);
 | 
			
		||||
	n_items = 0;
 | 
			
		||||
	items[n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency);
 | 
			
		||||
	items[n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE, "Audio");
 | 
			
		||||
| 
						 | 
				
			
			@ -1180,6 +1159,7 @@ int pa_stream_write_ext_free(pa_stream *s,
 | 
			
		|||
		free_cb(free_cb_data);
 | 
			
		||||
 | 
			
		||||
	s->timing_info.write_index += nbytes;
 | 
			
		||||
	s->timing_info.since_underrun += nbytes;
 | 
			
		||||
	pw_log_trace("stream %p: written %zd bytes", s, nbytes);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -1253,7 +1233,7 @@ SPA_EXPORT
 | 
			
		|||
size_t pa_stream_writable_size(PA_CONST pa_stream *s)
 | 
			
		||||
{
 | 
			
		||||
	const pa_timing_info *i;
 | 
			
		||||
	uint64_t now, then, queued, writable, elapsed;
 | 
			
		||||
	uint64_t now, then, queued, writable, elapsed, required;
 | 
			
		||||
	struct timespec ts;
 | 
			
		||||
 | 
			
		||||
	spa_assert(s);
 | 
			
		||||
| 
						 | 
				
			
			@ -1279,13 +1259,21 @@ size_t pa_stream_writable_size(PA_CONST pa_stream *s)
 | 
			
		|||
	queued -= SPA_MIN(queued, elapsed);
 | 
			
		||||
 | 
			
		||||
	writable = s->maxblock - SPA_MIN(queued, s->maxblock);
 | 
			
		||||
	pw_log_trace("stream %p: %"PRIu64, s, writable);
 | 
			
		||||
	required = SPA_MIN(s->maxblock, s->buffer_attr.minreq);
 | 
			
		||||
 | 
			
		||||
	pw_log_debug("stream %p: %"PRIu64" minreq:%u maxblock:%zu", s,
 | 
			
		||||
			writable, s->buffer_attr.minreq, s->maxblock);
 | 
			
		||||
	if (writable < required)
 | 
			
		||||
		writable = 0;
 | 
			
		||||
 | 
			
		||||
	return writable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SPA_EXPORT
 | 
			
		||||
size_t pa_stream_readable_size(PA_CONST pa_stream *s)
 | 
			
		||||
{
 | 
			
		||||
	uint64_t readable, required;
 | 
			
		||||
 | 
			
		||||
	spa_assert(s);
 | 
			
		||||
	spa_assert(s->refcount >= 1);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1294,8 +1282,13 @@ size_t pa_stream_readable_size(PA_CONST pa_stream *s)
 | 
			
		|||
	PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction == PA_STREAM_RECORD,
 | 
			
		||||
			PA_ERR_BADSTATE, (size_t) -1);
 | 
			
		||||
 | 
			
		||||
	pw_log_trace("stream %p: %zd", s, s->ready_bytes);
 | 
			
		||||
	return s->ready_bytes;
 | 
			
		||||
	readable = s->ready_bytes;
 | 
			
		||||
	required = SPA_MIN(s->maxblock, s->buffer_attr.fragsize);
 | 
			
		||||
	pw_log_trace("stream %p: %zd %zd", s, readable, required);
 | 
			
		||||
	if (readable < required)
 | 
			
		||||
		readable = 0;
 | 
			
		||||
 | 
			
		||||
	return readable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct success_ack {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue