From 3291666b570b1d20f59db42936eaa37dbeb9ca65 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 9 Mar 2009 19:25:26 +0100 Subject: output: play from a music_pipe object Instead of passing individual buffers to audio_output_all_play(), pass music_chunk objects. Append all those chunks asynchronously to a music_pipe instance. All output threads may then read chunks from this pipe. This reduces MPD's internal latency by an order of magnitude. --- src/output_all.c | 224 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 182 insertions(+), 42 deletions(-) (limited to 'src/output_all.c') diff --git a/src/output_all.c b/src/output_all.c index 7b308a428..c1353fa71 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -20,6 +20,12 @@ #include "output_internal.h" #include "output_control.h" #include "conf.h" +#include "pipe.h" +#include "buffer.h" + +#ifndef NDEBUG +#include "chunk.h" +#endif #include #include @@ -32,6 +38,17 @@ static struct audio_format input_audio_format; static struct audio_output *audio_outputs; static unsigned int num_audio_outputs; +/** + * The #music_buffer object where consumed chunks are returned. + */ +static struct music_buffer *g_music_buffer; + +/** + * The #music_pipe object which feeds all audio outputs. It is filled + * by audio_output_all_play(). + */ +static struct music_pipe *g_mp; + unsigned int audio_output_count(void) { return num_audio_outputs; @@ -179,60 +196,63 @@ audio_output_all_update(void) for (i = 0; i < num_audio_outputs; ++i) ret = audio_output_update(&audio_outputs[i], - &input_audio_format) || ret; + &input_audio_format, g_mp) || ret; return ret; } bool -audio_output_all_play(const char *buffer, size_t length) +audio_output_all_play(struct music_chunk *chunk) { - bool ret = false; + bool ret; unsigned int i; - assert(length > 0); - /* no partial frames allowed */ - assert((length % audio_format_frame_size(&input_audio_format)) == 0); + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + assert(chunk != NULL); + assert(music_chunk_check_format(chunk, &input_audio_format)); - audio_output_all_update(); + ret = audio_output_all_update(); + if (!ret) + return false; + + music_pipe_push(g_mp, chunk); for (i = 0; i < num_audio_outputs; ++i) if (audio_output_is_open(&audio_outputs[i])) - audio_output_play(&audio_outputs[i], - buffer, length); - - while (true) { - bool finished = true; - - for (i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = &audio_outputs[i]; - - if (!audio_output_is_open(ao)) - continue; - - if (audio_output_command_is_finished(ao)) - ret = true; - else { - finished = false; - audio_output_signal(ao); - } - } - - if (finished) - break; - - notify_wait(&audio_output_client_notify); - }; + audio_output_play(&audio_outputs[i]); return ret; } bool -audio_output_all_open(const struct audio_format *audio_format) +audio_output_all_open(const struct audio_format *audio_format, + struct music_buffer *buffer) { bool ret = false, enabled = false; unsigned int i; + assert(buffer != NULL); + assert(g_music_buffer == NULL || g_music_buffer == buffer); + assert((g_mp == NULL) == (g_music_buffer == NULL)); + + g_music_buffer = buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(audio_format == NULL || g_mp == NULL || + music_pipe_check_format(g_mp, audio_format)); + + if (g_mp == NULL) + g_mp = music_pipe_new(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(music_pipe_size(g_mp) == 0 || + audio_format == NULL || + audio_format_equals(audio_format, + &input_audio_format)); + if (audio_format != NULL) input_audio_format = *audio_format; @@ -257,6 +277,121 @@ audio_output_all_open(const struct audio_format *audio_format) return ret; } +/** + * Has the specified audio output already consumed this chunk? + */ +static bool +chunk_is_consumed_in(const struct audio_output *ao, + const struct music_chunk *chunk) +{ + if (ao->chunk == NULL) + return false; + + assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk)); + + if (chunk != ao->chunk) { + assert(chunk->next != NULL); + return true; + } + + return ao->chunk_finished && chunk->next == NULL; +} + +/** + * Has this chunk been consumed by all audio outputs? + */ +static bool +chunk_is_consumed(const struct music_chunk *chunk) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + const struct audio_output *ao = &audio_outputs[i]; + bool consumed; + + if (!ao->open) + continue; + + g_mutex_lock(ao->mutex); + consumed = chunk_is_consumed_in(ao, chunk); + g_mutex_unlock(ao->mutex); + + if (!consumed) + return false; + } + + return true; +} + +/** + * There's only one chunk left in the pipe (#g_mp), and all audio + * outputs have consumed it already. Clear the reference. + */ +static void +clear_tail_chunk(const struct music_chunk *chunk, bool *locked) +{ + assert(chunk->next == NULL); + assert(music_pipe_contains(g_mp, chunk)); + + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = &audio_outputs[i]; + + locked[i] = ao->open; + + if (!locked[i]) + continue; + + /* this mutex will be unlocked by the caller when it's + ready */ + g_mutex_lock(ao->mutex); + + assert(ao->chunk == chunk); + assert(ao->chunk_finished); + ao->chunk = NULL; + } +} + +unsigned +audio_output_all_check(void) +{ + const struct music_chunk *chunk; + bool is_tail; + struct music_chunk *shifted; + bool locked[num_audio_outputs]; + + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + + while ((chunk = music_pipe_peek(g_mp)) != NULL) { + assert(music_pipe_size(g_mp) > 0); + + if (!chunk_is_consumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return music_pipe_size(g_mp) - 1; + + is_tail = chunk->next == NULL; + if (is_tail) + /* this is the tail of the pipe - clear the + chunk reference in all outputs */ + clear_tail_chunk(chunk, locked); + + /* remove the chunk from the pipe */ + shifted = music_pipe_shift(g_mp); + assert(shifted == chunk); + + if (is_tail) + /* unlock all audio outputs which were locked + by clear_tail_chunk() */ + for (unsigned i = 0; i < num_audio_outputs; ++i) + if (locked[i]) + g_mutex_unlock(audio_outputs[i].mutex); + + /* return the chunk to the buffer */ + music_buffer_return(g_music_buffer, shifted); + } + + return 0; +} + void audio_output_all_pause(void) { @@ -278,12 +413,19 @@ audio_output_all_cancel(void) audio_output_all_update(); + /* send the cancel() command to all audio outputs */ + for (i = 0; i < num_audio_outputs; ++i) { if (audio_output_is_open(&audio_outputs[i])) audio_output_cancel(&audio_outputs[i]); } audio_output_wait_all(); + + /* clear the music pipe and return all chunks to the buffer */ + + if (g_mp != NULL) + music_pipe_clear(g_mp, g_music_buffer); } void @@ -293,16 +435,14 @@ audio_output_all_close(void) for (i = 0; i < num_audio_outputs; ++i) audio_output_close(&audio_outputs[i]); -} -void -audio_output_all_tag(const struct tag *tag) -{ - unsigned int i; + if (g_mp != NULL) { + assert(g_music_buffer != NULL); - for (i = 0; i < num_audio_outputs; ++i) - if (audio_output_is_open(&audio_outputs[i])) - audio_output_send_tag(&audio_outputs[i], tag); + music_pipe_clear(g_mp, g_music_buffer); + music_pipe_free(g_mp); + g_mp = NULL; + } - audio_output_wait_all(); + g_music_buffer = NULL; } -- cgit v1.2.3