aboutsummaryrefslogtreecommitdiffstats
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/output_all.c224
-rw-r--r--src/output_all.h23
-rw-r--r--src/output_control.c36
-rw-r--r--src/output_control.h9
-rw-r--r--src/output_init.c1
-rw-r--r--src/output_internal.h29
-rw-r--r--src/output_thread.c97
7 files changed, 319 insertions, 100 deletions
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 <assert.h>
#include <string.h>
@@ -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;
}
diff --git a/src/output_all.h b/src/output_all.h
index 6f55809f3..deb33e0e4 100644
--- a/src/output_all.h
+++ b/src/output_all.h
@@ -29,7 +29,8 @@
#include <stddef.h>
struct audio_format;
-struct tag;
+struct music_buffer;
+struct music_chunk;
/**
* Global initialization: load audio outputs from the configuration
@@ -68,10 +69,13 @@ audio_output_find(const char *name);
*
* @param audio_format the preferred audio format, or NULL to reuse
* the previous format
+ * @param buffer the #music_buffer where consumed #music_chunk objects
+ * should be returned
* @return true on success, false on failure
*/
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);
/**
* Closes all audio outputs.
@@ -80,19 +84,24 @@ void
audio_output_all_close(void);
/**
- * Play a chunk of audio data.
+ * Enqueue a #music_chunk object for playing, i.e. pushes it to a
+ * #music_pipe.
*
+ * @param chunk the #music_chunk object to be played
* @return true on success, false if no audio output was able to play
* (all closed then)
*/
bool
-audio_output_all_play(const char *data, size_t size);
+audio_output_all_play(struct music_chunk *chunk);
/**
- * Send metadata for the next chunk.
+ * Checks if the output devices have drained their music pipe, and
+ * returns the consumed music chunks to the #music_buffer.
+ *
+ * @return the number of chunks to play left in the #music_pipe
*/
-void
-audio_output_all_tag(const struct tag *tag);
+unsigned
+audio_output_all_check(void);
/**
* Puts all audio outputs into pause mode. Most implementations will
diff --git a/src/output_control.c b/src/output_control.c
index 47bb844d5..85bbea329 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -57,8 +57,11 @@ static void ao_command_async(struct audio_output *ao,
static bool
audio_output_open(struct audio_output *ao,
- const struct audio_format *audio_format)
+ const struct audio_format *audio_format,
+ const struct music_pipe *mp)
{
+ assert(mp != NULL);
+
if (ao->fail_timer != NULL) {
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
@@ -66,10 +69,13 @@ audio_output_open(struct audio_output *ao,
if (ao->open &&
audio_format_equals(audio_format, &ao->in_audio_format)) {
+ assert(ao->pipe == mp);
+
return true;
}
ao->in_audio_format = *audio_format;
+ ao->chunk = NULL;
if (audio_format_defined(&ao->config_audio_format)) {
/* copy config_audio_format to out_audio_format only if the
@@ -85,6 +91,8 @@ audio_output_open(struct audio_output *ao,
audio_output_close(ao);
}
+ ao->pipe = mp;
+
if (ao->thread == NULL)
audio_output_thread_start(ao);
@@ -96,12 +104,15 @@ audio_output_open(struct audio_output *ao,
bool
audio_output_update(struct audio_output *ao,
- const struct audio_format *audio_format)
+ const struct audio_format *audio_format,
+ const struct music_pipe *mp)
{
+ assert(mp != NULL);
+
if (ao->enabled) {
if (ao->fail_timer == NULL ||
g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER)
- return audio_output_open(ao, audio_format);
+ return audio_output_open(ao, audio_format, mp);
} else if (audio_output_is_open(ao))
audio_output_close(ao);
@@ -115,16 +126,12 @@ audio_output_signal(struct audio_output *ao)
}
void
-audio_output_play(struct audio_output *ao, const void *chunk, size_t size)
+audio_output_play(struct audio_output *ao)
{
- assert(size > 0);
-
if (!ao->open)
return;
- ao->args.play.data = chunk;
- ao->args.play.size = size;
- ao_command_async(ao, AO_COMMAND_PLAY);
+ notify_signal(&ao->notify);
}
void audio_output_pause(struct audio_output *ao)
@@ -163,14 +170,5 @@ void audio_output_finish(struct audio_output *ao)
ao_plugin_finish(ao->plugin, ao->data);
notify_deinit(&ao->notify);
-}
-
-void
-audio_output_send_tag(struct audio_output *ao, const struct tag *tag)
-{
- if (ao->plugin->send_tag == NULL)
- return;
-
- ao->args.tag = tag;
- ao_command_async(ao, AO_COMMAND_SEND_TAG);
+ g_mutex_free(ao->mutex);
}
diff --git a/src/output_control.h b/src/output_control.h
index fe3233e69..511aa1204 100644
--- a/src/output_control.h
+++ b/src/output_control.h
@@ -26,8 +26,8 @@
struct audio_output;
struct audio_format;
-struct tag;
struct config_param;
+struct music_pipe;
static inline GQuark
audio_output_quark(void)
@@ -46,7 +46,8 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
*/
bool
audio_output_update(struct audio_output *ao,
- const struct audio_format *audio_format);
+ const struct audio_format *audio_format,
+ const struct music_pipe *mp);
/**
* Wakes up the audio output thread. This is part of a workaround for
@@ -57,14 +58,12 @@ void
audio_output_signal(struct audio_output *ao);
void
-audio_output_play(struct audio_output *ao, const void *chunk, size_t size);
+audio_output_play(struct audio_output *ao);
void audio_output_pause(struct audio_output *ao);
void audio_output_cancel(struct audio_output *ao);
void audio_output_close(struct audio_output *ao);
void audio_output_finish(struct audio_output *ao);
-void
-audio_output_send_tag(struct audio_output *ao, const struct tag *tag);
#endif
diff --git a/src/output_init.c b/src/output_init.c
index 664ca9eb4..5681256d1 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -124,6 +124,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
ao->thread = NULL;
notify_init(&ao->notify);
ao->command = AO_COMMAND_NONE;
+ ao->mutex = g_mutex_new();
ao->data = ao_plugin_init(plugin,
format ? &ao->config_audio_format : NULL,
diff --git a/src/output_internal.h b/src/output_internal.h
index 498f45d06..7e1ac9b7a 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -30,10 +30,8 @@ enum audio_output_command {
AO_COMMAND_NONE = 0,
AO_COMMAND_OPEN,
AO_COMMAND_CLOSE,
- AO_COMMAND_PLAY,
AO_COMMAND_PAUSE,
AO_COMMAND_CANCEL,
- AO_COMMAND_SEND_TAG,
AO_COMMAND_KILL
};
@@ -110,16 +108,27 @@ struct audio_output {
enum audio_output_command command;
/**
- * Command arguments, depending on the command.
+ * The music pipe which provides music chunks to be played.
*/
- union {
- struct {
- const void *data;
- size_t size;
- } play;
+ const struct music_pipe *pipe;
- const struct tag *tag;
- } args;
+ /**
+ * This mutex protects #chunk and #chunk_finished.
+ */
+ GMutex *mutex;
+
+ /**
+ * The #music_chunk which is currently being played. All
+ * chunks before this one may be returned to the
+ * #music_buffer, because they are not going to be used by
+ * this output anymore.
+ */
+ const struct music_chunk *chunk;
+
+ /**
+ * Has the output finished playing #chunk?
+ */
+ bool chunk_finished;
};
/**
diff --git a/src/output_thread.c b/src/output_thread.c
index a55260785..786b04204 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -19,6 +19,8 @@
#include "output_thread.h"
#include "output_api.h"
#include "output_internal.h"
+#include "chunk.h"
+#include "pipe.h"
#include <glib.h>
@@ -41,6 +43,12 @@ ao_close(struct audio_output *ao)
{
assert(ao->open);
+ ao->pipe = NULL;
+
+ g_mutex_lock(ao->mutex);
+ ao->chunk = NULL;
+ g_mutex_unlock(ao->mutex);
+
ao_plugin_close(ao->plugin, ao->data);
pcm_convert_deinit(&ao->convert_state);
ao->open = false;
@@ -48,16 +56,25 @@ ao_close(struct audio_output *ao)
g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
}
-static void ao_play(struct audio_output *ao)
+static bool
+ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
{
- const char *data = ao->args.play.data;
- size_t size = ao->args.play.size;
+ const char *data = chunk->data;
+ size_t size = chunk->length;
GError *error = NULL;
- assert(size > 0);
+ assert(!music_chunk_is_empty(chunk));
+ assert(music_chunk_check_format(chunk, &ao->in_audio_format));
assert(size % audio_format_frame_size(&ao->in_audio_format) == 0);
- if (!audio_format_equals(&ao->in_audio_format, &ao->out_audio_format)) {
+ if (chunk->tag != NULL)
+ ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag);
+
+ if (size == 0)
+ return true;
+
+ if (!audio_format_equals(&ao->in_audio_format,
+ &ao->out_audio_format)) {
data = pcm_convert(&ao->convert_state,
&ao->in_audio_format, data, size,
&ao->out_audio_format, &size);
@@ -67,7 +84,7 @@ static void ao_play(struct audio_output *ao)
investigated further, but for now, do this check as
a workaround: */
if (data == NULL)
- return;
+ return true;
}
while (size > 0) {
@@ -87,7 +104,7 @@ static void ao_play(struct audio_output *ao)
/* don't automatically reopen this device for
10 seconds */
ao->fail_timer = g_timer_new();
- break;
+ return false;
}
assert(nbytes <= size);
@@ -97,7 +114,46 @@ static void ao_play(struct audio_output *ao)
size -= nbytes;
}
- ao_command_finished(ao);
+ return true;
+}
+
+static void ao_play(struct audio_output *ao)
+{
+ bool success;
+ const struct music_chunk *chunk;
+
+ assert(ao->pipe != NULL);
+
+ g_mutex_lock(ao->mutex);
+ chunk = ao->chunk;
+ if (chunk != NULL)
+ /* continue the previous play() call */
+ chunk = chunk->next;
+ else
+ chunk = music_pipe_peek(ao->pipe);
+ ao->chunk_finished = false;
+
+ while (chunk != NULL && ao->command == AO_COMMAND_NONE) {
+ assert(!ao->chunk_finished);
+
+ ao->chunk = chunk;
+ g_mutex_unlock(ao->mutex);
+
+ success = ao_play_chunk(ao, chunk);
+
+ g_mutex_lock(ao->mutex);
+
+ if (!success) {
+ assert(ao->chunk == NULL);
+ break;
+ }
+
+ assert(ao->chunk == chunk);
+ chunk = chunk->next;
+ }
+
+ ao->chunk_finished = true;
+ g_mutex_unlock(ao->mutex);
}
static void ao_pause(struct audio_output *ao)
@@ -130,6 +186,8 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_OPEN:
assert(!ao->open);
assert(ao->fail_timer == NULL);
+ assert(ao->pipe != NULL);
+ assert(ao->chunk == NULL);
error = NULL;
ret = ao_plugin_open(ao->plugin, ao->data,
@@ -170,35 +228,40 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_CLOSE:
assert(ao->open);
+ assert(ao->pipe != NULL);
+
+ ao->pipe = NULL;
+ ao->chunk = NULL;
ao_plugin_cancel(ao->plugin, ao->data);
ao_close(ao);
ao_command_finished(ao);
break;
- case AO_COMMAND_PLAY:
- ao_play(ao);
- break;
-
case AO_COMMAND_PAUSE:
ao_pause(ao);
break;
case AO_COMMAND_CANCEL:
+ ao->chunk = NULL;
ao_plugin_cancel(ao->plugin, ao->data);
ao_command_finished(ao);
- break;
- case AO_COMMAND_SEND_TAG:
- ao_plugin_send_tag(ao->plugin, ao->data, ao->args.tag);
- ao_command_finished(ao);
- break;
+ /* the player thread will now clear our music
+ pipe - wait for a notify, to give it some
+ time */
+ notify_wait(&ao->notify);
+ continue;
case AO_COMMAND_KILL:
+ ao->chunk = NULL;
ao_command_finished(ao);
return NULL;
}
+ if (ao->open)
+ ao_play(ao);
+
notify_wait(&ao->notify);
}
}