Commit 3291666b authored by Max Kellermann's avatar Max Kellermann

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.
parent ab3d7c29
...@@ -20,6 +20,12 @@ ...@@ -20,6 +20,12 @@
#include "output_internal.h" #include "output_internal.h"
#include "output_control.h" #include "output_control.h"
#include "conf.h" #include "conf.h"
#include "pipe.h"
#include "buffer.h"
#ifndef NDEBUG
#include "chunk.h"
#endif
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
...@@ -32,6 +38,17 @@ static struct audio_format input_audio_format; ...@@ -32,6 +38,17 @@ static struct audio_format input_audio_format;
static struct audio_output *audio_outputs; static struct audio_output *audio_outputs;
static unsigned int num_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) unsigned int audio_output_count(void)
{ {
return num_audio_outputs; return num_audio_outputs;
...@@ -179,60 +196,63 @@ audio_output_all_update(void) ...@@ -179,60 +196,63 @@ audio_output_all_update(void)
for (i = 0; i < num_audio_outputs; ++i) for (i = 0; i < num_audio_outputs; ++i)
ret = audio_output_update(&audio_outputs[i], ret = audio_output_update(&audio_outputs[i],
&input_audio_format) || ret; &input_audio_format, g_mp) || ret;
return ret; return ret;
} }
bool 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; unsigned int i;
assert(length > 0); assert(g_music_buffer != NULL);
/* no partial frames allowed */ assert(g_mp != NULL);
assert((length % audio_format_frame_size(&input_audio_format)) == 0); 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) for (i = 0; i < num_audio_outputs; ++i)
if (audio_output_is_open(&audio_outputs[i])) if (audio_output_is_open(&audio_outputs[i]))
audio_output_play(&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);
};
return ret; return ret;
} }
bool 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; bool ret = false, enabled = false;
unsigned int i; 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) if (audio_format != NULL)
input_audio_format = *audio_format; input_audio_format = *audio_format;
...@@ -257,6 +277,121 @@ audio_output_all_open(const struct audio_format *audio_format) ...@@ -257,6 +277,121 @@ audio_output_all_open(const struct audio_format *audio_format)
return ret; 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 void
audio_output_all_pause(void) audio_output_all_pause(void)
{ {
...@@ -278,12 +413,19 @@ audio_output_all_cancel(void) ...@@ -278,12 +413,19 @@ audio_output_all_cancel(void)
audio_output_all_update(); audio_output_all_update();
/* send the cancel() command to all audio outputs */
for (i = 0; i < num_audio_outputs; ++i) { for (i = 0; i < num_audio_outputs; ++i) {
if (audio_output_is_open(&audio_outputs[i])) if (audio_output_is_open(&audio_outputs[i]))
audio_output_cancel(&audio_outputs[i]); audio_output_cancel(&audio_outputs[i]);
} }
audio_output_wait_all(); 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 void
...@@ -293,16 +435,14 @@ audio_output_all_close(void) ...@@ -293,16 +435,14 @@ audio_output_all_close(void)
for (i = 0; i < num_audio_outputs; ++i) for (i = 0; i < num_audio_outputs; ++i)
audio_output_close(&audio_outputs[i]); audio_output_close(&audio_outputs[i]);
}
void if (g_mp != NULL) {
audio_output_all_tag(const struct tag *tag) assert(g_music_buffer != NULL);
{
unsigned int i;
for (i = 0; i < num_audio_outputs; ++i) music_pipe_clear(g_mp, g_music_buffer);
if (audio_output_is_open(&audio_outputs[i])) music_pipe_free(g_mp);
audio_output_send_tag(&audio_outputs[i], tag); g_mp = NULL;
}
audio_output_wait_all(); g_music_buffer = NULL;
} }
...@@ -29,7 +29,8 @@ ...@@ -29,7 +29,8 @@
#include <stddef.h> #include <stddef.h>
struct audio_format; struct audio_format;
struct tag; struct music_buffer;
struct music_chunk;
/** /**
* Global initialization: load audio outputs from the configuration * Global initialization: load audio outputs from the configuration
...@@ -68,10 +69,13 @@ audio_output_find(const char *name); ...@@ -68,10 +69,13 @@ audio_output_find(const char *name);
* *
* @param audio_format the preferred audio format, or NULL to reuse * @param audio_format the preferred audio format, or NULL to reuse
* the previous format * the previous format
* @param buffer the #music_buffer where consumed #music_chunk objects
* should be returned
* @return true on success, false on failure * @return true on success, false on failure
*/ */
bool 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. * Closes all audio outputs.
...@@ -80,19 +84,24 @@ void ...@@ -80,19 +84,24 @@ void
audio_output_all_close(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 * @return true on success, false if no audio output was able to play
* (all closed then) * (all closed then)
*/ */
bool 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 unsigned
audio_output_all_tag(const struct tag *tag); audio_output_all_check(void);
/** /**
* Puts all audio outputs into pause mode. Most implementations will * Puts all audio outputs into pause mode. Most implementations will
......
...@@ -57,8 +57,11 @@ static void ao_command_async(struct audio_output *ao, ...@@ -57,8 +57,11 @@ static void ao_command_async(struct audio_output *ao,
static bool static bool
audio_output_open(struct audio_output *ao, 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) { if (ao->fail_timer != NULL) {
g_timer_destroy(ao->fail_timer); g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL; ao->fail_timer = NULL;
...@@ -66,10 +69,13 @@ audio_output_open(struct audio_output *ao, ...@@ -66,10 +69,13 @@ audio_output_open(struct audio_output *ao,
if (ao->open && if (ao->open &&
audio_format_equals(audio_format, &ao->in_audio_format)) { audio_format_equals(audio_format, &ao->in_audio_format)) {
assert(ao->pipe == mp);
return true; return true;
} }
ao->in_audio_format = *audio_format; ao->in_audio_format = *audio_format;
ao->chunk = NULL;
if (audio_format_defined(&ao->config_audio_format)) { if (audio_format_defined(&ao->config_audio_format)) {
/* copy config_audio_format to out_audio_format only if the /* copy config_audio_format to out_audio_format only if the
...@@ -85,6 +91,8 @@ audio_output_open(struct audio_output *ao, ...@@ -85,6 +91,8 @@ audio_output_open(struct audio_output *ao,
audio_output_close(ao); audio_output_close(ao);
} }
ao->pipe = mp;
if (ao->thread == NULL) if (ao->thread == NULL)
audio_output_thread_start(ao); audio_output_thread_start(ao);
...@@ -96,12 +104,15 @@ audio_output_open(struct audio_output *ao, ...@@ -96,12 +104,15 @@ audio_output_open(struct audio_output *ao,
bool bool
audio_output_update(struct audio_output *ao, 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->enabled) {
if (ao->fail_timer == NULL || if (ao->fail_timer == NULL ||
g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) 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)) } else if (audio_output_is_open(ao))
audio_output_close(ao); audio_output_close(ao);
...@@ -115,16 +126,12 @@ audio_output_signal(struct audio_output *ao) ...@@ -115,16 +126,12 @@ audio_output_signal(struct audio_output *ao)
} }
void 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) if (!ao->open)
return; return;
ao->args.play.data = chunk; notify_signal(&ao->notify);
ao->args.play.size = size;
ao_command_async(ao, AO_COMMAND_PLAY);
} }
void audio_output_pause(struct audio_output *ao) void audio_output_pause(struct audio_output *ao)
...@@ -163,14 +170,5 @@ void audio_output_finish(struct audio_output *ao) ...@@ -163,14 +170,5 @@ void audio_output_finish(struct audio_output *ao)
ao_plugin_finish(ao->plugin, ao->data); ao_plugin_finish(ao->plugin, ao->data);
notify_deinit(&ao->notify); notify_deinit(&ao->notify);
} g_mutex_free(ao->mutex);
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);
} }
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
struct audio_output; struct audio_output;
struct audio_format; struct audio_format;
struct tag;
struct config_param; struct config_param;
struct music_pipe;
static inline GQuark static inline GQuark
audio_output_quark(void) audio_output_quark(void)
...@@ -46,7 +46,8 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ...@@ -46,7 +46,8 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
*/ */
bool bool
audio_output_update(struct audio_output *ao, 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 * Wakes up the audio output thread. This is part of a workaround for
...@@ -57,14 +58,12 @@ void ...@@ -57,14 +58,12 @@ void
audio_output_signal(struct audio_output *ao); audio_output_signal(struct audio_output *ao);
void 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_pause(struct audio_output *ao);
void audio_output_cancel(struct audio_output *ao); void audio_output_cancel(struct audio_output *ao);
void audio_output_close(struct audio_output *ao); void audio_output_close(struct audio_output *ao);
void audio_output_finish(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 #endif
...@@ -124,6 +124,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ...@@ -124,6 +124,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
ao->thread = NULL; ao->thread = NULL;
notify_init(&ao->notify); notify_init(&ao->notify);
ao->command = AO_COMMAND_NONE; ao->command = AO_COMMAND_NONE;
ao->mutex = g_mutex_new();
ao->data = ao_plugin_init(plugin, ao->data = ao_plugin_init(plugin,
format ? &ao->config_audio_format : NULL, format ? &ao->config_audio_format : NULL,
......
...@@ -30,10 +30,8 @@ enum audio_output_command { ...@@ -30,10 +30,8 @@ enum audio_output_command {
AO_COMMAND_NONE = 0, AO_COMMAND_NONE = 0,
AO_COMMAND_OPEN, AO_COMMAND_OPEN,
AO_COMMAND_CLOSE, AO_COMMAND_CLOSE,
AO_COMMAND_PLAY,
AO_COMMAND_PAUSE, AO_COMMAND_PAUSE,
AO_COMMAND_CANCEL, AO_COMMAND_CANCEL,
AO_COMMAND_SEND_TAG,
AO_COMMAND_KILL AO_COMMAND_KILL
}; };
...@@ -110,16 +108,27 @@ struct audio_output { ...@@ -110,16 +108,27 @@ struct audio_output {
enum audio_output_command command; enum audio_output_command command;
/** /**
* Command arguments, depending on the command. * The music pipe which provides music chunks to be played.
*/ */
union { const struct music_pipe *pipe;
struct {
const void *data;
size_t size;
} play;
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;
}; };
/** /**
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#include "output_thread.h" #include "output_thread.h"
#include "output_api.h" #include "output_api.h"
#include "output_internal.h" #include "output_internal.h"
#include "chunk.h"
#include "pipe.h"
#include <glib.h> #include <glib.h>
...@@ -41,6 +43,12 @@ ao_close(struct audio_output *ao) ...@@ -41,6 +43,12 @@ ao_close(struct audio_output *ao)
{ {
assert(ao->open); 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); ao_plugin_close(ao->plugin, ao->data);
pcm_convert_deinit(&ao->convert_state); pcm_convert_deinit(&ao->convert_state);
ao->open = false; ao->open = false;
...@@ -48,16 +56,25 @@ ao_close(struct audio_output *ao) ...@@ -48,16 +56,25 @@ ao_close(struct audio_output *ao)
g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); 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; const char *data = chunk->data;
size_t size = ao->args.play.size; size_t size = chunk->length;
GError *error = NULL; 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); 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, data = pcm_convert(&ao->convert_state,
&ao->in_audio_format, data, size, &ao->in_audio_format, data, size,
&ao->out_audio_format, &size); &ao->out_audio_format, &size);
...@@ -67,7 +84,7 @@ static void ao_play(struct audio_output *ao) ...@@ -67,7 +84,7 @@ static void ao_play(struct audio_output *ao)
investigated further, but for now, do this check as investigated further, but for now, do this check as
a workaround: */ a workaround: */
if (data == NULL) if (data == NULL)
return; return true;
} }
while (size > 0) { while (size > 0) {
...@@ -87,7 +104,7 @@ static void ao_play(struct audio_output *ao) ...@@ -87,7 +104,7 @@ static void ao_play(struct audio_output *ao)
/* don't automatically reopen this device for /* don't automatically reopen this device for
10 seconds */ 10 seconds */
ao->fail_timer = g_timer_new(); ao->fail_timer = g_timer_new();
break; return false;
} }
assert(nbytes <= size); assert(nbytes <= size);
...@@ -97,7 +114,46 @@ static void ao_play(struct audio_output *ao) ...@@ -97,7 +114,46 @@ static void ao_play(struct audio_output *ao)
size -= nbytes; 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) static void ao_pause(struct audio_output *ao)
...@@ -130,6 +186,8 @@ static gpointer audio_output_task(gpointer arg) ...@@ -130,6 +186,8 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_OPEN: case AO_COMMAND_OPEN:
assert(!ao->open); assert(!ao->open);
assert(ao->fail_timer == NULL); assert(ao->fail_timer == NULL);
assert(ao->pipe != NULL);
assert(ao->chunk == NULL);
error = NULL; error = NULL;
ret = ao_plugin_open(ao->plugin, ao->data, ret = ao_plugin_open(ao->plugin, ao->data,
...@@ -170,35 +228,40 @@ static gpointer audio_output_task(gpointer arg) ...@@ -170,35 +228,40 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_CLOSE: case AO_COMMAND_CLOSE:
assert(ao->open); assert(ao->open);
assert(ao->pipe != NULL);
ao->pipe = NULL;
ao->chunk = NULL;
ao_plugin_cancel(ao->plugin, ao->data); ao_plugin_cancel(ao->plugin, ao->data);
ao_close(ao); ao_close(ao);
ao_command_finished(ao); ao_command_finished(ao);
break; break;
case AO_COMMAND_PLAY:
ao_play(ao);
break;
case AO_COMMAND_PAUSE: case AO_COMMAND_PAUSE:
ao_pause(ao); ao_pause(ao);
break; break;
case AO_COMMAND_CANCEL: case AO_COMMAND_CANCEL:
ao->chunk = NULL;
ao_plugin_cancel(ao->plugin, ao->data); ao_plugin_cancel(ao->plugin, ao->data);
ao_command_finished(ao); ao_command_finished(ao);
break;
case AO_COMMAND_SEND_TAG: /* the player thread will now clear our music
ao_plugin_send_tag(ao->plugin, ao->data, ao->args.tag); pipe - wait for a notify, to give it some
ao_command_finished(ao); time */
break; notify_wait(&ao->notify);
continue;
case AO_COMMAND_KILL: case AO_COMMAND_KILL:
ao->chunk = NULL;
ao_command_finished(ao); ao_command_finished(ao);
return NULL; return NULL;
} }
if (ao->open)
ao_play(ao);
notify_wait(&ao->notify); notify_wait(&ao->notify);
} }
} }
......
...@@ -177,10 +177,22 @@ player_check_decoder_startup(struct player *player) ...@@ -177,10 +177,22 @@ player_check_decoder_startup(struct player *player)
return false; return false;
} else if (!decoder_is_starting()) { } else if (!decoder_is_starting()) {
/* the decoder is ready and ok */ /* the decoder is ready and ok */
if (audio_format_defined(&player->play_audio_format) &&
audio_output_all_check() > 0) {
/* the output devices havn't finished playing
all chunks yet - wait for that */
/* XXX synchronize in a better way */
g_usleep(1000);
return true;
}
player->decoder_starting = false; player->decoder_starting = false;
if (!player->paused && if (!player->paused &&
!audio_output_all_open(&dc.out_audio_format)) { !audio_output_all_open(&dc.out_audio_format,
player_buffer)) {
char *uri = song_get_uri(dc.next_song); char *uri = song_get_uri(dc.next_song);
g_warning("problems opening audio device " g_warning("problems opening audio device "
"while playing \"%s\"", uri); "while playing \"%s\"", uri);
...@@ -268,7 +280,7 @@ static void player_process_command(struct player *player) ...@@ -268,7 +280,7 @@ static void player_process_command(struct player *player)
audio_output_all_pause(); audio_output_all_pause();
pc.state = PLAYER_STATE_PAUSE; pc.state = PLAYER_STATE_PAUSE;
} else { } else {
if (audio_output_all_open(NULL)) { if (audio_output_all_open(NULL, player_buffer)) {
pc.state = PLAYER_STATE_PLAY; pc.state = PLAYER_STATE_PLAY;
} else { } else {
assert(dc.next_song == NULL || dc.next_song->url != NULL); assert(dc.next_song == NULL || dc.next_song->url != NULL);
...@@ -328,8 +340,6 @@ play_chunk(struct song *song, struct music_chunk *chunk, ...@@ -328,8 +340,6 @@ play_chunk(struct song *song, struct music_chunk *chunk,
pc.bit_rate = chunk->bit_rate; pc.bit_rate = chunk->bit_rate;
if (chunk->tag != NULL) { if (chunk->tag != NULL) {
audio_output_all_tag(chunk->tag);
if (!song_is_file(song)) { if (!song_is_file(song)) {
/* always update the tag of remote streams */ /* always update the tag of remote streams */
struct tag *old_tag = song->tag; struct tag *old_tag = song->tag;
...@@ -349,9 +359,6 @@ play_chunk(struct song *song, struct music_chunk *chunk, ...@@ -349,9 +359,6 @@ play_chunk(struct song *song, struct music_chunk *chunk,
} }
} }
if (chunk->length == 0)
return true;
success = pcm_volume(chunk->data, chunk->length, success = pcm_volume(chunk->data, chunk->length,
format, pc.software_volume); format, pc.software_volume);
if (!success) { if (!success) {
...@@ -362,7 +369,7 @@ play_chunk(struct song *song, struct music_chunk *chunk, ...@@ -362,7 +369,7 @@ play_chunk(struct song *song, struct music_chunk *chunk,
return false; return false;
} }
if (!audio_output_all_play(chunk->data, chunk->length)) { if (!audio_output_all_play(chunk)) {
pc.errored_song = dc.current_song; pc.errored_song = dc.current_song;
pc.error = PLAYER_ERROR_AUDIO; pc.error = PLAYER_ERROR_AUDIO;
return false; return false;
...@@ -385,6 +392,15 @@ play_next_chunk(struct player *player) ...@@ -385,6 +392,15 @@ play_next_chunk(struct player *player)
unsigned cross_fade_position; unsigned cross_fade_position;
bool success; bool success;
if (audio_output_all_check() >= 64) {
/* the output pipe is still large
enough, don't send another chunk */
/* XXX synchronize in a better way */
g_usleep(1000);
return true;
}
if (player->xfade == XFADE_ENABLED && if (player->xfade == XFADE_ENABLED &&
dc.pipe != NULL && dc.pipe != player->pipe && dc.pipe != NULL && dc.pipe != player->pipe &&
(cross_fade_position = music_pipe_size(player->pipe)) (cross_fade_position = music_pipe_size(player->pipe))
...@@ -440,10 +456,11 @@ play_next_chunk(struct player *player) ...@@ -440,10 +456,11 @@ play_next_chunk(struct player *player)
success = play_chunk(player->song, chunk, &player->play_audio_format, success = play_chunk(player->song, chunk, &player->play_audio_format,
player->size_to_time); player->size_to_time);
music_buffer_return(player_buffer, chunk);
if (!success) if (!success) {
music_buffer_return(player_buffer, chunk);
return false; return false;
}
/* this formula should prevent that the /* this formula should prevent that the
decoder gets woken up with each chunk; it decoder gets woken up with each chunk; it
...@@ -472,7 +489,10 @@ player_song_border(struct player *player) ...@@ -472,7 +489,10 @@ player_song_border(struct player *player)
music_pipe_free(player->pipe); music_pipe_free(player->pipe);
player->pipe = dc.pipe; player->pipe = dc.pipe;
return player_wait_for_decoder(player); if (!player_wait_for_decoder(player))
return false;
return true;
} }
static void do_play(void) static void do_play(void)
...@@ -488,7 +508,6 @@ static void do_play(void) ...@@ -488,7 +508,6 @@ static void do_play(void)
.cross_fade_chunks = 0, .cross_fade_chunks = 0,
.size_to_time = 0.0, .size_to_time = 0.0,
}; };
static const char silence[CHUNK_SIZE];
player.pipe = music_pipe_new(); player.pipe = music_pipe_new();
...@@ -585,6 +604,13 @@ static void do_play(void) ...@@ -585,6 +604,13 @@ static void do_play(void)
if (!play_next_chunk(&player)) if (!play_next_chunk(&player))
break; break;
} else if (audio_output_all_check() > 0) {
/* not enough data from decoder, but the
output thread is still busy, so it's
okay */
/* XXX synchronize in a better way */
g_usleep(10000);
} else if (dc.pipe != NULL && dc.pipe != player.pipe) { } else if (dc.pipe != NULL && dc.pipe != player.pipe) {
/* at the beginning of a new song */ /* at the beginning of a new song */
...@@ -593,15 +619,32 @@ static void do_play(void) ...@@ -593,15 +619,32 @@ static void do_play(void)
} else if (decoder_is_idle()) { } else if (decoder_is_idle()) {
break; break;
} else { } else {
/* the decoder is too busy and hasn't provided
new PCM data in time: send silence (if the
output pipe is empty) */
struct music_chunk *chunk;
size_t frame_size = size_t frame_size =
audio_format_frame_size(&player.play_audio_format); audio_format_frame_size(&player.play_audio_format);
/* this formula ensures that we don't send /* this formula ensures that we don't send
partial frames */ partial frames */
unsigned num_frames = CHUNK_SIZE / frame_size; unsigned num_frames = CHUNK_SIZE / frame_size;
chunk = music_buffer_allocate(player_buffer);
if (chunk == NULL)
continue;
#ifndef NDEBUG
chunk->audio_format = player.play_audio_format;
#endif
chunk->length = num_frames * frame_size;
memset(chunk->data, 0, chunk->length);
/*DEBUG("waiting for decoded audio, play silence\n");*/ /*DEBUG("waiting for decoded audio, play silence\n");*/
if (!audio_output_all_play(silence, num_frames * frame_size)) if (!audio_output_all_play(chunk)) {
music_buffer_return(player_buffer, chunk);
break; break;
}
} }
} }
...@@ -637,6 +680,9 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg) ...@@ -637,6 +680,9 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
break; break;
case PLAYER_COMMAND_STOP: case PLAYER_COMMAND_STOP:
audio_output_all_cancel();
/* fall through */
case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE: case PLAYER_COMMAND_PAUSE:
pc.next_song = NULL; pc.next_song = NULL;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment