Skip to content

Commit 362f122

Browse files
committed
[outputs] Allow more responsive streaming with configurable output buffer
This allows the user to configure the outputs buffering time. It's a modified version of PR #1954 by @msolo: Make the initial start buffer configurable to allow "instant" streaming on networks/hardware that support it. The default value for start_buffer_ms is 2000ms, preserving existing behavior. Values near 250ms work in some setups providing a much more responsive streaming experience.
1 parent 1087e5b commit 362f122

File tree

12 files changed

+71
-33
lines changed

12 files changed

+71
-33
lines changed

src/conffile.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ static cfg_opt_t sec_general[] =
7676
CFG_STR("user_agent", PACKAGE_NAME "/" PACKAGE_VERSION, CFGF_NONE),
7777
CFG_BOOL("ssl_verifypeer", cfg_true, CFGF_NONE),
7878
CFG_BOOL("timer_test", cfg_false, CFGF_NONE),
79+
CFG_INT("start_buffer_ms", 2000, CFGF_NONE),
7980
CFG_END()
8081
};
8182

src/misc.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,21 @@ timespec_add(struct timespec time1, struct timespec time2)
16201620
return result;
16211621
}
16221622

1623+
struct timespec
1624+
timespec_sub(struct timespec time1, struct timespec time2)
1625+
{
1626+
struct timespec result;
1627+
1628+
result.tv_sec = time1.tv_sec - time2.tv_sec;
1629+
result.tv_nsec = time1.tv_nsec - time2.tv_nsec;
1630+
if (result.tv_nsec < 0)
1631+
{
1632+
result.tv_sec--;
1633+
result.tv_nsec += 1000000000L;
1634+
}
1635+
return result;
1636+
}
1637+
16231638
int
16241639
timespec_cmp(struct timespec time1, struct timespec time2)
16251640
{

src/misc.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec
265265
struct timespec
266266
timespec_add(struct timespec time1, struct timespec time2);
267267

268+
struct timespec
269+
timespec_sub(struct timespec time1, struct timespec time2);
270+
268271
int
269272
timespec_cmp(struct timespec time1, struct timespec time2);
270273

src/outputs.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "player.h" //TODO remove me when player_pmap is removed again
3939
#include "worker.h"
4040
#include "outputs.h"
41+
#include "conffile.h"
4142

4243
extern struct output_definition output_raop;
4344
extern struct output_definition output_airplay;
@@ -114,6 +115,7 @@ static struct output_buffer output_buffer;
114115

115116
static struct output_device *outputs_device_list;
116117
static int outputs_master_volume;
118+
static uint64_t outputs_buffer_duration_ms;
117119

118120
static struct outputs_callback_register outputs_cb_register[OUTPUTS_MAX_CALLBACKS];
119121
static struct event *outputs_deferredev;
@@ -614,6 +616,12 @@ outputs_device_get(uint64_t device_id)
614616
return NULL;
615617
}
616618

619+
uint64_t
620+
outputs_buffer_duration_ms_get(void)
621+
{
622+
return outputs_buffer_duration_ms;
623+
}
624+
617625
/* ----------------------- Called by backend modules ------------------------ */
618626

619627
// Sessions free their sessions themselves, but should not touch the device,
@@ -1299,6 +1307,14 @@ outputs_init(void)
12991307

13001308
outputs_master_volume = -1;
13011309

1310+
// Number of milliseconds the outputs should buffer before starting playback.
1311+
// This value cannot freely be changed because 1) some Airplay devices ignore
1312+
// the values we give and stick to 2 seconds, 2) those devices that can handle
1313+
// different values can only do so within a limited range (maybe max 3 secs)
1314+
outputs_buffer_duration_ms = cfg_getint(cfg_getsec(cfg, "general"), "start_buffer_ms");
1315+
if (outputs_buffer_duration_ms != 2000)
1316+
DPRINTF(E_WARN, L_PLAYER, "Output buffer duration is configured to a non-standard value %" PRIu64 "\n", outputs_buffer_duration_ms);
1317+
13021318
CHECK_NULL(L_PLAYER, outputs_deferredev = evtimer_new(evbase_player, deferred_cb, NULL));
13031319

13041320
no_output = 1;

src/outputs.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@
3535
// as one.
3636
#define OUTPUTS_MAX_QUALITY_SUBSCRIPTIONS 5
3737

38-
// Number of seconds the outputs should buffer before starting playback. Note
39-
// this value cannot freely be changed because 1) some Airplay devices ignore
40-
// the values we give and stick to 2 seconds, 2) those devices that can handle
41-
// different values can only do so within a limited range (maybe max 3 secs)
42-
#define OUTPUTS_BUFFER_DURATION 2
43-
4438
// Whether the device should be *displayed* as selected is not given by
4539
// device->selected, since that means "has the user selected the device",
4640
// without taking into account whether it is working or available. This macro
@@ -276,6 +270,9 @@ struct output_definition
276270
struct output_device *
277271
outputs_device_get(uint64_t device_id);
278272

273+
uint64_t
274+
outputs_buffer_duration_ms_get(void);
275+
279276
/* ----------------------- Called by backend modules ------------------------ */
280277

281278
int

src/outputs/airplay.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ struct airplay_extra
202202
struct airplay_master_session
203203
{
204204
struct evbuffer *input_buffer;
205-
int input_buffer_samples;
205+
uint32_t input_buffer_samples;
206206

207207
// ALAC encoder and buffer for encoded data
208208
struct encode_ctx *encode_ctx;
@@ -214,14 +214,14 @@ struct airplay_master_session
214214

215215
uint8_t *rawbuf;
216216
size_t rawbuf_size;
217-
int samples_per_packet;
217+
uint32_t samples_per_packet;
218218

219219
struct media_quality quality;
220220

221221
// Number of samples that we tell the output to buffer (this will mean that
222222
// the position that we send in the sync packages are offset by this amount
223223
// compared to the rtptimes of the corresponding RTP packages we are sending)
224-
int output_buffer_samples;
224+
uint32_t output_buffer_samples;
225225

226226
struct airplay_master_session *next;
227227
};
@@ -1168,7 +1168,7 @@ master_session_make(struct media_quality *quality)
11681168
rms->quality = *quality;
11691169
rms->samples_per_packet = AIRPLAY_SAMPLES_PER_PACKET;
11701170
rms->rawbuf_size = STOB(rms->samples_per_packet, quality->bits_per_sample, quality->channels);
1171-
rms->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate;
1171+
rms->output_buffer_samples = outputs_buffer_duration_ms_get() * quality->sample_rate / 1000;
11721172

11731173
CHECK_NULL(L_AIRPLAY, rms->rawbuf = malloc(rms->rawbuf_size));
11741174
CHECK_NULL(L_AIRPLAY, rms->input_buffer = evbuffer_new());

src/outputs/alsa.c

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ playback_session_add(struct alsa_session *as, struct media_quality *quality, str
722722
struct alsa_playback_session *pb;
723723
struct alsa_playback_session *tail_pb;
724724
struct timespec ts;
725-
snd_pcm_sframes_t offset_nsamp;
725+
uint64_t buffer_duration_ms;
726726
size_t size;
727727
int ret;
728728

@@ -766,19 +766,17 @@ playback_session_add(struct alsa_session *as, struct media_quality *quality, str
766766
dump_config(pb->pcm);
767767

768768
// Time stamps used for syncing, here we set when playback should start
769-
ts.tv_sec = OUTPUTS_BUFFER_DURATION;
770-
ts.tv_nsec = (uint64_t)as->offset_ms * 1000000UL;
769+
buffer_duration_ms = outputs_buffer_duration_ms_get();
770+
ts.tv_sec = buffer_duration_ms / 1000;
771+
ts.tv_nsec = (buffer_duration_ms + (uint64_t)as->offset_ms) % 1000 * 1000000UL;
771772
pb->stamp_pts = timespec_add(pts, ts);
772773

773774
// The difference between pos and start pos should match the 2 second buffer
774-
// that AirPlay uses (OUTPUTS_BUFFER_DURATION) + user configured offset_ms. We
775-
// will not use alsa's buffer for the initial buffering, because my sound
776-
// card's start_threshold is not to be counted on. Instead we allocate our own
777-
// buffer, and when it is time to play we write as much as we can to alsa's
778-
// buffer.
779-
offset_nsamp = (as->offset_ms * pb->quality.sample_rate / 1000);
780-
781-
pb->buffer_nsamp = OUTPUTS_BUFFER_DURATION * pb->quality.sample_rate + offset_nsamp;
775+
// that e.g. AirPlay uses + user configured offset_ms. We will not use alsa's
776+
// buffer for the initial buffering, because my sound card's start_threshold
777+
// is not to be counted on. Instead we allocate our own buffer, and when it is
778+
// time to play we write as much as we can to alsa's buffer.
779+
pb->buffer_nsamp = (buffer_duration_ms + as->offset_ms) * pb->quality.sample_rate / 1000;
782780
size = STOB(pb->buffer_nsamp, pb->quality.bits_per_sample, pb->quality.channels);
783781
ringbuffer_init(&pb->prebuf, size);
784782

src/outputs/cast.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1948,7 +1948,7 @@ cast_session_make(struct output_device *device, int family, int callback_id)
19481948
offset_ms = 0;
19491949
}
19501950

1951-
offset_ms += OUTPUTS_BUFFER_DURATION * 1000 + CAST_DEVICE_START_DELAY_MS;
1951+
offset_ms += outputs_buffer_duration_ms_get() + CAST_DEVICE_START_DELAY_MS;
19521952

19531953
cs->offset_ts.tv_sec = (offset_ms / 1000);
19541954
cs->offset_ts.tv_nsec = (offset_ms % 1000) * 1000000UL;

src/outputs/fifo.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,8 @@ fifo_write(struct output_buffer *obuf)
393393
struct fifo_session *fifo_session = sessions;
394394
struct fifo_packet *packet;
395395
struct timespec now;
396+
struct timespec delay;
397+
uint64_t buffer_duration_ms;
396398
ssize_t bytes;
397399
int i;
398400

@@ -430,8 +432,12 @@ fifo_write(struct output_buffer *obuf)
430432
if (!buffer.tail)
431433
buffer.tail = packet;
432434

433-
now.tv_sec = obuf->pts.tv_sec - OUTPUTS_BUFFER_DURATION;
434-
now.tv_nsec = obuf->pts.tv_sec;
435+
buffer_duration_ms = outputs_buffer_duration_ms_get();
436+
437+
delay.tv_sec = buffer_duration_ms / 1000;
438+
delay.tv_nsec = (buffer_duration_ms % 1000) * 1000000UL;
439+
440+
now = timespec_sub(obuf->pts, delay);
435441

436442
while (buffer.tail && (timespec_cmp(buffer.tail->pts, now) == -1))
437443
{

src/outputs/pulse.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ stream_open(struct pulse_session *ps, struct media_quality *quality, pa_stream_n
570570
pa_stream_flags_t flags;
571571
pa_sample_spec ss;
572572
pa_cvolume cvol;
573+
uint64_t buffer_duration_ms;
573574
int offset_ms;
574575
int ret;
575576

@@ -602,8 +603,9 @@ stream_open(struct pulse_session *ps, struct media_quality *quality, pa_stream_n
602603
pa_stream_set_state_callback(ps->stream, cb, ps);
603604

604605
flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
606+
buffer_duration_ms = outputs_buffer_duration_ms_get();
605607

606-
ps->attr.tlength = STOB((OUTPUTS_BUFFER_DURATION * 1000 + offset_ms) * ss.rate / 1000, quality->bits_per_sample, quality->channels);
608+
ps->attr.tlength = STOB((buffer_duration_ms + offset_ms) * ss.rate / 1000, quality->bits_per_sample, quality->channels);
607609
ps->attr.maxlength = 2 * ps->attr.tlength;
608610
ps->attr.prebuf = (uint32_t)-1;
609611
ps->attr.minreq = (uint32_t)-1;

0 commit comments

Comments
 (0)