From 01cf7feac7bef8b28605b98ef1e7438a995fc554 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 6 Mar 2009 00:42:03 +0100 Subject: pipe: added music_buffer, rewrite music_pipe Turn the music_pipe into a simple music_chunk queue. The music_chunk allocation code is moved to music_buffer, and is now managed with a linked list instead of a ring buffer. Two separate music_pipe objects are used by the decoder for the "current" and the "next" song, which greatly simplifies the cross-fading code. --- Makefile.am | 2 + src/buffer.c | 127 ++++++++++++++++++++++++++++ src/buffer.h | 66 +++++++++++++++ src/chunk.h | 3 + src/decoder_api.c | 37 +++++---- src/decoder_control.h | 6 ++ src/decoder_internal.c | 36 ++++---- src/decoder_internal.h | 13 ++- src/decoder_thread.c | 5 +- src/main.c | 5 +- src/pipe.c | 221 +++++++++++++------------------------------------ src/pipe.h | 149 +++++---------------------------- src/player_control.c | 3 +- src/player_control.h | 4 +- src/player_thread.c | 141 ++++++++++++++++++------------- 15 files changed, 417 insertions(+), 401 deletions(-) create mode 100644 src/buffer.c create mode 100644 src/buffer.h diff --git a/Makefile.am b/Makefile.am index cbd2fc9bf..25478f810 100644 --- a/Makefile.am +++ b/Makefile.am @@ -84,6 +84,7 @@ mpd_headers = \ src/daemon.h \ src/normalize.h \ src/compress.h \ + src/buffer.h \ src/pipe.h \ src/chunk.h \ src/path.h \ @@ -180,6 +181,7 @@ src_mpd_SOURCES = \ src/daemon.c \ src/normalize.c \ src/compress.c \ + src/buffer.c \ src/pipe.c \ src/chunk.c \ src/path.c \ diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 000000000..1b300887f --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "buffer.h" +#include "chunk.h" + +#include + +#include + +struct music_buffer { + struct music_chunk *chunks; + unsigned num_chunks; + + struct music_chunk *available; + + /** a mutex which protects #available */ + GMutex *mutex; + +#ifndef NDEBUG + unsigned num_allocated; +#endif +}; + +struct music_buffer * +music_buffer_new(unsigned num_chunks) +{ + struct music_buffer *buffer; + struct music_chunk *chunk; + + assert(num_chunks > 0); + + buffer = g_new(struct music_buffer, 1); + + buffer->chunks = g_new(struct music_chunk, num_chunks); + buffer->num_chunks = num_chunks; + + chunk = buffer->available = buffer->chunks; + + for (unsigned i = 1; i < num_chunks; ++i) { + chunk->next = &buffer->chunks[i]; + chunk = chunk->next; + } + + chunk->next = NULL; + + buffer->mutex = g_mutex_new(); + +#ifndef NDEBUG + buffer->num_allocated = 0; +#endif + + return buffer; +} + +void +music_buffer_free(struct music_buffer *buffer) +{ + assert(buffer->chunks != NULL); + assert(buffer->num_chunks > 0); + assert(buffer->num_allocated == 0); + + g_mutex_free(buffer->mutex); + g_free(buffer->chunks); + g_free(buffer); +} + +unsigned +music_buffer_size(const struct music_buffer *buffer) +{ + return buffer->num_chunks; +} + +struct music_chunk * +music_buffer_allocate(struct music_buffer *buffer) +{ + struct music_chunk *chunk; + + g_mutex_lock(buffer->mutex); + + chunk = buffer->available; + if (chunk != NULL) { + buffer->available = chunk->next; + music_chunk_init(chunk); + +#ifndef NDEBUG + ++buffer->num_allocated; +#endif + } + + g_mutex_unlock(buffer->mutex); + return chunk; +} + +void +music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) +{ + assert(buffer != NULL); + assert(chunk != NULL); + + g_mutex_lock(buffer->mutex); + + music_chunk_free(chunk); + chunk->next = buffer->available; + buffer->available = chunk; + +#ifndef NDEBUG + --buffer->num_allocated; +#endif + + g_mutex_unlock(buffer->mutex); +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 000000000..3bbee2e6d --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MPD_MUSIC_BUFFER_H +#define MPD_MUSIC_BUFFER_H + +/** + * An allocator for #music_chunk objects. + */ +struct music_buffer; + +/** + * Creates a new #music_buffer object. + * + * @param num_chunks the number of #music_chunk reserved in this + * buffer + */ +struct music_buffer * +music_buffer_new(unsigned num_chunks); + +/** + * Frees the #music_buffer object + */ +void +music_buffer_free(struct music_buffer *buffer); + +/** + * Returns the total number of reserved chunks in this buffer. This + * is the same value which was passed to the constructor + * music_buffer_new(). + */ +unsigned +music_buffer_size(const struct music_buffer *buffer); + +/** + * Allocates a chunk from the buffer. When it is not used anymore, + * call music_buffer_return(). + * + * @return an empty chunk or NULL if there are no chunks available + */ +struct music_chunk * +music_buffer_allocate(struct music_buffer *buffer); + +/** + * Returns a chunk to the buffer. It can be reused by + * music_buffer_allocate() then. + */ +void +music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk); + +#endif diff --git a/src/chunk.h b/src/chunk.h index 58ed7be6f..b63466102 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -35,6 +35,9 @@ struct audio_format; * music_pipe_append() caller. */ struct music_chunk { + /** the next chunk in a linked list */ + struct music_chunk *next; + /** number of bytes stored in this chunk */ uint16_t length; diff --git a/src/decoder_api.c b/src/decoder_api.c index 88864befa..0992eac9a 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -23,6 +23,7 @@ #include "player_control.h" #include "audio.h" #include "song.h" +#include "buffer.h" #include "normalize.h" #include "pipe.h" @@ -90,11 +91,11 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder) /* delete frames from the old song position */ if (decoder->chunk != NULL) { - music_pipe_cancel(decoder->chunk); + music_buffer_return(dc.buffer, decoder->chunk); decoder->chunk = NULL; } - music_pipe_clear(); + music_pipe_clear(dc.pipe, dc.buffer); } dc.command = DECODE_COMMAND_NONE; @@ -167,15 +168,18 @@ do_send_tag(struct decoder *decoder, struct input_stream *is, if (decoder->chunk != NULL) { /* there is a partial chunk - flush it, we want the tag in a new chunk */ - enum decoder_command cmd = - decoder_flush_chunk(decoder, is); - if (cmd != DECODE_COMMAND_NONE) - return cmd; + decoder_flush_chunk(decoder); + notify_signal(&pc.notify); } assert(decoder->chunk == NULL); - chunk = decoder_get_chunk(decoder); + chunk = decoder_get_chunk(decoder, is); + if (chunk == NULL) { + assert(dc.command != DECODE_COMMAND_NONE); + return dc.command; + } + chunk->tag = tag_dup(tag); return DECODE_COMMAND_NONE; } @@ -256,15 +260,18 @@ decoder_data(struct decoder *decoder, size_t nbytes; bool full; - chunk = decoder_get_chunk(decoder); + chunk = decoder_get_chunk(decoder, is); + if (chunk == NULL) { + assert(dc.command != DECODE_COMMAND_NONE); + return dc.command; + } + dest = music_chunk_write(chunk, &dc.out_audio_format, data_time, bitRate, &nbytes); if (dest == NULL) { /* the chunk is full, flush it */ - enum decoder_command cmd = - decoder_flush_chunk(decoder, is); - if (cmd != DECODE_COMMAND_NONE) - return cmd; + decoder_flush_chunk(decoder); + notify_signal(&pc.notify); continue; } @@ -291,10 +298,8 @@ decoder_data(struct decoder *decoder, full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes); if (full) { /* the chunk is full, flush it */ - enum decoder_command cmd = - decoder_flush_chunk(decoder, is); - if (cmd != DECODE_COMMAND_NONE) - return cmd; + decoder_flush_chunk(decoder); + notify_signal(&pc.notify); } data += nbytes; diff --git a/src/decoder_control.h b/src/decoder_control.h index d1d0625e1..f981074d4 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -66,6 +66,12 @@ struct decoder_control { struct song *current_song; struct song *next_song; float total_time; + + /** the #music_chunk allocator */ + struct music_buffer *buffer; + + /** the destination pipe for decoded chunks */ + struct music_pipe *pipe; }; extern struct decoder_control dc; diff --git a/src/decoder_internal.c b/src/decoder_internal.c index 93ad80e5e..120115ed2 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -21,6 +21,7 @@ #include "player_control.h" #include "pipe.h" #include "input_stream.h" +#include "buffer.h" #include @@ -46,35 +47,32 @@ need_chunks(struct input_stream *is, bool do_wait) } struct music_chunk * -decoder_get_chunk(struct decoder *decoder) +decoder_get_chunk(struct decoder *decoder, struct input_stream *is) { + enum decoder_command cmd; + assert(decoder != NULL); if (decoder->chunk != NULL) return decoder->chunk; - decoder->chunk = music_pipe_allocate(); - return decoder->chunk; + do { + decoder->chunk = music_buffer_allocate(dc.buffer); + if (decoder->chunk != NULL) + return decoder->chunk; + + cmd = need_chunks(is, true); + } while (cmd == DECODE_COMMAND_NONE); + + return NULL; } -enum decoder_command -decoder_flush_chunk(struct decoder *decoder, struct input_stream *is) +void +decoder_flush_chunk(struct decoder *decoder) { - bool success; - enum decoder_command cmd; - assert(decoder != NULL); assert(decoder->chunk != NULL); - while (true) { - success = music_pipe_push(decoder->chunk); - if (success) { - decoder->chunk = NULL; - return DECODE_COMMAND_NONE; - } - - cmd = need_chunks(is, true); - if (cmd != DECODE_COMMAND_NONE) - return cmd; - } + music_pipe_push(dc.pipe, decoder->chunk); + decoder->chunk = NULL; } diff --git a/src/decoder_internal.h b/src/decoder_internal.h index ab52ab037..cf46eb2c9 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -42,17 +42,16 @@ struct decoder { /** * Returns the current chunk the decoder writes to, or allocates a new * chunk if there is none. + * + * @return the chunk, or NULL if we have received a decoder command */ struct music_chunk * -decoder_get_chunk(struct decoder *decoder); +decoder_get_chunk(struct decoder *decoder, struct input_stream *is); /** - * Flushes a chunk. Waits for room in the music pipe if required. - * - * @return DECODE_COMMAND_NONE on success, any other command if we - * have received a decoder command while waiting + * Flushes the current chunk. */ -enum decoder_command -decoder_flush_chunk(struct decoder *decoder, struct input_stream *is); +void +decoder_flush_chunk(struct decoder *decoder); #endif diff --git a/src/decoder_thread.c b/src/decoder_thread.c index 97e92d295..a48f4160b 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -196,9 +196,8 @@ static void decoder_run_song(const struct song *song, const char *uri) pcm_convert_deinit(&decoder.conv_state); /* flush the last chunk */ - if (decoder.chunk != NULL && - decoder_flush_chunk(&decoder, NULL) != DECODE_COMMAND_NONE) - music_pipe_cancel(decoder.chunk); + if (decoder.chunk != NULL) + decoder_flush_chunk(&decoder); if (close_instream) input_stream_close(&input_stream); diff --git a/src/main.c b/src/main.c index 39c6a3550..a3f613d0e 100644 --- a/src/main.c +++ b/src/main.c @@ -31,7 +31,6 @@ #include "conf.h" #include "path.h" #include "mapper.h" -#include "pipe.h" #include "chunk.h" #include "decoder_control.h" #include "player_control.h" @@ -179,8 +178,7 @@ initialize_decoder_and_player(void) if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; - pc_init(buffered_before_play); - music_pipe_init(buffered_chunks, &pc.notify); + pc_init(buffered_chunks, buffered_before_play); dc_init(); } @@ -333,7 +331,6 @@ int main(int argc, char *argv[]) #ifdef ENABLE_ARCHIVE archive_plugin_deinit_all(); #endif - music_pipe_free(); config_global_finish(); tag_pool_deinit(); songvec_deinit(); diff --git a/src/pipe.c b/src/pipe.c index 6f6fd816a..d02a00a04 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -17,208 +17,103 @@ */ #include "pipe.h" +#include "buffer.h" #include "chunk.h" -#include "notify.h" -#include "audio_format.h" -#include "tag.h" #include -#include -#include - -struct music_pipe music_pipe; - -void -music_pipe_init(unsigned int size, struct notify *notify) -{ - assert(size > 0); - - music_pipe.chunks = g_new(struct music_chunk, size); - music_pipe.num_chunks = size; - music_pipe.begin = 0; - music_pipe.end = 0; - music_pipe.lazy = false; - music_pipe.notify = notify; - music_chunk_init(&music_pipe.chunks[0]); -} - -void music_pipe_free(void) -{ - assert(music_pipe.chunks != NULL); - - music_pipe_clear(); - - g_free(music_pipe.chunks); -} - -/** return the index of the chunk after i */ -static inline unsigned successor(unsigned i) -{ - assert(i < music_pipe.num_chunks); - - ++i; - return i == music_pipe.num_chunks ? 0 : i; -} - -void music_pipe_clear(void) -{ - unsigned i; - - for (i = music_pipe.begin; i != music_pipe.end; i = successor(i)) - music_chunk_free(&music_pipe.chunks[i]); - - music_chunk_free(&music_pipe.chunks[music_pipe.end]); - - music_pipe.end = music_pipe.begin; - music_chunk_init(&music_pipe.chunks[music_pipe.end]); -} - -/** - * Mark the tail chunk as "full" and wake up the player if is waiting - * for the decoder. - */ -static void output_buffer_expand(unsigned i) -{ - int was_empty = music_pipe.notify != NULL && (!music_pipe.lazy || music_pipe_is_empty()); - - assert(i == (music_pipe.end + 1) % music_pipe.num_chunks); - assert(i != music_pipe.end); - - music_pipe.end = i; - music_chunk_init(&music_pipe.chunks[i]); - - if (was_empty) - /* if the buffer was empty, the player thread might be - waiting for us; wake it up now that another decoded - buffer has become available. */ - notify_signal(music_pipe.notify); -} -void music_pipe_set_lazy(bool lazy) -{ - music_pipe.lazy = lazy; -} - -void music_pipe_shift(void) -{ - assert(music_pipe.begin != music_pipe.end); - assert(music_pipe.begin < music_pipe.num_chunks); +#include - music_chunk_free(&music_pipe.chunks[music_pipe.begin]); +struct music_pipe { + /** the first chunk */ + struct music_chunk *head; - music_pipe.begin = successor(music_pipe.begin); -} + /** a pointer to the tail of the chunk */ + struct music_chunk **tail_r; -unsigned int music_pipe_relative(const unsigned i) -{ - if (i >= music_pipe.begin) - return i - music_pipe.begin; - else - return i + music_pipe.num_chunks - music_pipe.begin; -} + /** the current number of chunks */ + unsigned size; -unsigned music_pipe_available(void) -{ - return music_pipe_relative(music_pipe.end); -} + /** a mutex which protects #head and #tail_r */ + GMutex *mutex; +}; -int music_pipe_absolute(const unsigned relative) +struct music_pipe * +music_pipe_new(void) { - unsigned i, max; - - max = music_pipe.end; - if (max < music_pipe.begin) - max += music_pipe.num_chunks; - i = (unsigned)music_pipe.begin + relative; - if (i >= max) - return -1; + struct music_pipe *mp = g_new(struct music_pipe, 1); - if (i >= music_pipe.num_chunks) - i -= music_pipe.num_chunks; + mp->head = NULL; + mp->tail_r = &mp->head; + mp->size = 0; + mp->mutex = g_mutex_new(); - return (int)i; + return mp; } -struct music_chunk * -music_pipe_get_chunk(const unsigned i) +void +music_pipe_free(struct music_pipe *mp) { - assert(i < music_pipe.num_chunks); + assert(mp->head == NULL); + assert(mp->tail_r == &mp->head); - return &music_pipe.chunks[i]; + g_mutex_free(mp->mutex); + g_free(mp); } struct music_chunk * -music_pipe_allocate(void) +music_pipe_shift(struct music_pipe *mp) { struct music_chunk *chunk; - /* the music_pipe.end chunk is always kept initialized */ - chunk = music_pipe_get_chunk(music_pipe.end); - assert(chunk->length == 0); + g_mutex_lock(mp->mutex); - return chunk; -} + chunk = mp->head; + if (chunk != NULL) { + mp->head = chunk->next; + --mp->size; -bool -music_pipe_push(struct music_chunk *chunk) -{ - unsigned int next; + if (mp->head == NULL) { + assert(mp->size == 0); + assert(mp->tail_r == &chunk->next); - assert(chunk == music_pipe_get_chunk(music_pipe.end)); + mp->tail_r = &mp->head; + } else { + assert(mp->size > 0); + assert(mp->tail_r != &chunk->next); + } + } - next = successor(music_pipe.end); - if (music_pipe.begin == next) - /* no room */ - return false; + g_mutex_unlock(mp->mutex); - output_buffer_expand(next); - return true; + return chunk; } void -music_pipe_cancel(struct music_chunk *chunk) -{ - assert(chunk == music_pipe_get_chunk(music_pipe.end)); - - music_chunk_free(chunk); -} - -void music_pipe_skip(unsigned num) +music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer) { - int i = music_pipe_absolute(num); - if (i < 0) - return; + struct music_chunk *chunk; - while (music_pipe.begin != (unsigned)i) - music_pipe_shift(); + while ((chunk = music_pipe_shift(mp)) != NULL) + music_buffer_return(buffer, chunk); } -void music_pipe_chop(unsigned first) +void +music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk) { - for (unsigned i = first; i != music_pipe.end; i = successor(i)) - music_chunk_free(&music_pipe.chunks[i]); + g_mutex_lock(mp->mutex); - music_chunk_free(&music_pipe.chunks[music_pipe.end]); + chunk->next = NULL; + *mp->tail_r = chunk; + mp->tail_r = &chunk->next; - music_pipe.end = first; - music_chunk_init(&music_pipe.chunks[first]); + ++mp->size; + g_mutex_unlock(mp->mutex); } -#ifndef NDEBUG -void music_pipe_check_format(const struct audio_format *current, - int next_index, const struct audio_format *next) +unsigned +music_pipe_size(const struct music_pipe *mp) { - const struct audio_format *audio_format = current; - - for (unsigned i = music_pipe.begin; i != music_pipe.end; - i = successor(i)) { - const struct music_chunk *chunk = music_pipe_get_chunk(i); - - if (next_index > 0 && i == (unsigned)next_index) - audio_format = next; - - assert(chunk->length % audio_format_frame_size(audio_format) == 0); - } + return mp->size; } -#endif diff --git a/src/pipe.h b/src/pipe.h index 01214dc51..f1a231542 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -19,160 +19,51 @@ #ifndef MPD_PIPE_H #define MPD_PIPE_H -#include -#include -#include - -struct audio_format; -struct tag; struct music_chunk; +struct music_buffer; /** - * A ring set of buffers where the decoder appends data after the end, - * and the player consumes data from the beginning. - */ -struct music_pipe { - struct music_chunk *chunks; - unsigned num_chunks; - - /** the index of the first decoded chunk */ - unsigned begin; - - /** the index after the last decoded chunk */ - unsigned end; - - /** non-zero if the player thread should only we woken up if - the buffer becomes non-empty */ - bool lazy; - - struct notify *notify; -}; - -extern struct music_pipe music_pipe; - -void -music_pipe_init(unsigned int size, struct notify *notify); - -void music_pipe_free(void); - -void music_pipe_clear(void); - -/** - * When a chunk is decoded, we wake up the player thread to tell him - * about it. In "lazy" mode, we only wake him up when the buffer was - * previously empty, i.e. when the player thread has really been - * waiting for us. + * A queue of #music_chunk objects. One party appends chunks at the + * tail, and the other consumes them from the head. */ -void music_pipe_set_lazy(bool lazy); - -static inline unsigned -music_pipe_size(void) -{ - return music_pipe.num_chunks; -} - -/** is the buffer empty? */ -static inline bool music_pipe_is_empty(void) -{ - return music_pipe.begin == music_pipe.end; -} - -static inline bool -music_pipe_head_is(unsigned i) -{ - return !music_pipe_is_empty() && music_pipe.begin == i; -} - -static inline unsigned -music_pipe_tail_index(void) -{ - return music_pipe.end; -} - -void music_pipe_shift(void); +struct music_pipe; /** - * what is the position of the specified chunk number, relative to - * the first chunk in use? + * Creates a new #music_pipe object. It is empty. */ -unsigned int music_pipe_relative(const unsigned i); - -/** determine the number of decoded chunks */ -unsigned music_pipe_available(void); +struct music_pipe * +music_pipe_new(void); /** - * Get the absolute index of the nth used chunk after the first one. - * Returns -1 if there is no such chunk. + * Frees the object. It must be empty now. */ -int music_pipe_absolute(const unsigned relative); - -struct music_chunk * -music_pipe_get_chunk(const unsigned i); - -static inline struct music_chunk * -music_pipe_peek(void) -{ - if (music_pipe_is_empty()) - return NULL; - - return music_pipe_get_chunk(music_pipe.begin); -} +void +music_pipe_free(struct music_pipe *mp); /** - * Allocates a chunk for writing. When you are finished, append it - * with music_pipe_push(). - * - * @return an empty chunk + * Removes the first chunk from the head, and returns it. */ struct music_chunk * -music_pipe_allocate(void); - -/** - * Appends a chunk at the end of the music pipe. - * - * @param chunk a chunk allocated with music_pipe_allocate() - * @return true on success, false if there is no room - */ -bool -music_pipe_push(struct music_chunk *chunk); +music_pipe_shift(struct music_pipe *mp); /** - * Cancels a chunk that has been allocated with music_pipe_allocate(). + * Clears the whole pipe and returns the chunks to the buffer. * - * @param chunk a chunk allocated with music_pipe_allocate() + * @param buffer the buffer object to return the chunks to */ void -music_pipe_cancel(struct music_chunk *chunk); +music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer); /** - * Prepares appending to the music pipe. Returns a buffer where you - * may write into. After you are finished, call music_pipe_expand(). - * - * @return a writable buffer - */ -void * -music_pipe_write(const struct audio_format *audio_format, - float data_time, uint16_t bit_rate, - size_t *max_length_r); - -/** - * Tells the music pipe to move the end pointer, after you have - * written to the buffer returned by music_pipe_write(). + * Pushes a chunk to the tail of the pipe. */ void -music_pipe_expand(const struct audio_format *audio_format, size_t length); - -void music_pipe_skip(unsigned num); +music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk); /** - * Chop off the tail of the music pipe, starting with the chunk at - * index "first". + * Returns the number of chunks currently in this pipe. */ -void music_pipe_chop(unsigned first); - -#ifndef NDEBUG -void music_pipe_check_format(const struct audio_format *current, - int next_index, const struct audio_format *next); -#endif +unsigned +music_pipe_size(const struct music_pipe *mp); #endif diff --git a/src/player_control.c b/src/player_control.c index 5d95fb093..4dfc810c1 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -30,8 +30,9 @@ struct player_control pc; -void pc_init(unsigned int buffered_before_play) +void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play) { + pc.buffer_chunks = buffer_chunks; pc.buffered_before_play = buffered_before_play; notify_init(&pc.notify); pc.command = PLAYER_COMMAND_NONE; diff --git a/src/player_control.h b/src/player_control.h index 508798fff..b1356705c 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -60,6 +60,8 @@ enum player_error { }; struct player_control { + unsigned buffer_chunks; + unsigned int buffered_before_play; /** the handle of the player thread, or NULL if the player @@ -84,7 +86,7 @@ struct player_control { extern struct player_control pc; -void pc_init(unsigned int _buffered_before_play); +void pc_init(unsigned buffer_chunks, unsigned buffered_before_play); void pc_deinit(void); diff --git a/src/player_thread.c b/src/player_thread.c index 678a6a3ee..bb59facf9 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -31,6 +31,7 @@ #include "chunk.h" #include "idle.h" #include "main.h" +#include "buffer.h" #include @@ -44,6 +45,9 @@ enum xfade_state { }; struct player { + struct music_buffer *buffer; + struct music_pipe *pipe; + /** * are we waiting for buffered_before_play? */ @@ -74,12 +78,6 @@ struct player { * is cross fading enabled? */ enum xfade_state xfade; - - /** - * index of the first chunk of the next song, -1 if there is - * no next song - */ - int next_song_chunk; }; static void player_command_finished(void) @@ -90,6 +88,21 @@ static void player_command_finished(void) notify_signal(&main_notify); } +static void +player_dc_stop(struct player *player) +{ + dc_stop(&pc.notify); + + if (dc.pipe != NULL) { + music_pipe_clear(dc.pipe, player->buffer); + + if (dc.pipe != player->pipe) + music_pipe_free(dc.pipe); + + dc.pipe = NULL; + } +} + static void player_stop_decoder(void) { dc_stop(&pc.notify); @@ -133,9 +146,10 @@ static bool player_seek_decoder(struct player *player) bool ret; if (decoder_current_song() != pc.next_song) { - dc_stop(&pc.notify); - player->next_song_chunk = -1; - music_pipe_clear(); + player_dc_stop(player); + + music_pipe_clear(player->pipe, player->buffer); + dc.pipe = player->pipe; dc_start_async(pc.next_song); ret = player_wait_for_decoder(player); @@ -174,7 +188,7 @@ static void player_process_command(struct player *player) case PLAYER_COMMAND_QUEUE: assert(pc.next_song != NULL); assert(!player->queued); - assert(player->next_song_chunk == -1); + assert(dc.pipe == NULL || dc.pipe == player->pipe); player->queued = true; player_command_finished(); @@ -220,12 +234,11 @@ static void player_process_command(struct player *player) return; } - if (player->next_song_chunk != -1) { + if (dc.pipe != NULL && dc.pipe != player->pipe) { /* the decoder is already decoding the song - stop it and reset the position */ + player_dc_stop(player); dc_stop(&pc.notify); - music_pipe_chop(player->next_song_chunk); - player->next_song_chunk = -1; } pc.next_song = NULL; @@ -298,23 +311,25 @@ static void do_play(void) .queued = false, .song = NULL, .xfade = XFADE_UNKNOWN, - .next_song_chunk = -1, }; unsigned int crossFadeChunks = 0; - /** the position of the next cross-faded chunk in the next - song */ - int nextChunk = 0; + /** has cross-fading begun? */ + bool cross_fading = false; static const char silence[CHUNK_SIZE]; struct audio_format play_audio_format; double sizeToTime = 0.0; - music_pipe_clear(); - music_pipe_set_lazy(false); + player.buffer = music_buffer_new(pc.buffer_chunks); + player.pipe = music_pipe_new(); + dc.buffer = player.buffer; + dc.pipe = player.pipe; dc_start(&pc.notify, pc.next_song); if (!player_wait_for_decoder(&player)) { player_stop_decoder(); player_command_finished(); + music_pipe_free(player.pipe); + music_buffer_free(player.buffer); return; } @@ -332,7 +347,7 @@ static void do_play(void) } if (player.buffering) { - if (music_pipe_available() < pc.buffered_before_play && + if (music_pipe_size(player.pipe) < pc.buffered_before_play && !decoder_is_idle()) { /* not enough decoded buffer space yet */ notify_wait(&pc.notify); @@ -340,7 +355,6 @@ static void do_play(void) } else { /* buffering is complete */ player.buffering = false; - music_pipe_set_lazy(true); } } @@ -395,13 +409,14 @@ static void do_play(void) /* the decoder has finished the current song; make it decode the next song */ assert(pc.next_song != NULL); - assert(player.next_song_chunk == -1); + assert(dc.pipe == NULL || dc.pipe == player.pipe); player.queued = false; - player.next_song_chunk = music_pipe_tail_index(); + dc.pipe = music_pipe_new(); dc_start_async(pc.next_song); } - if (player.next_song_chunk >= 0 && + + if (dc.pipe != NULL && dc.pipe != player.pipe && player.xfade == XFADE_UNKNOWN && !decoder_is_starting()) { /* enable cross fading in this song? if yes, @@ -411,11 +426,11 @@ static void do_play(void) cross_fade_calc(pc.cross_fade_seconds, dc.total_time, &dc.out_audio_format, &play_audio_format, - music_pipe_size() - + music_buffer_size(player.buffer) - pc.buffered_before_play); if (crossFadeChunks > 0) { player.xfade = XFADE_ENABLED; - nextChunk = -1; + cross_fading = false; } else /* cross fading is disabled or the next song is too short */ @@ -424,31 +439,36 @@ static void do_play(void) if (player.paused) notify_wait(&pc.notify); - else if (!music_pipe_is_empty() && - !music_pipe_head_is(player.next_song_chunk)) { - struct music_chunk *beginChunk = music_pipe_peek(); + else if (music_pipe_size(player.pipe) > 0) { + struct music_chunk *chunk = NULL; unsigned int fadePosition; + bool success; + if (player.xfade == XFADE_ENABLED && - player.next_song_chunk >= 0 && - (fadePosition = music_pipe_relative(player.next_song_chunk)) + dc.pipe != NULL && dc.pipe != player.pipe && + (fadePosition = music_pipe_size(player.pipe)) <= crossFadeChunks) { /* perform cross fade */ - if (nextChunk < 0) { + struct music_chunk *other_chunk = + music_pipe_shift(dc.pipe); + + if (!cross_fading) { /* beginning of the cross fade - adjust crossFadeChunks which might be bigger than the remaining number of chunks in the old song */ crossFadeChunks = fadePosition; + cross_fading = true; } - nextChunk = music_pipe_absolute(crossFadeChunks); - if (nextChunk >= 0) { - music_pipe_set_lazy(true); - cross_fade_apply(beginChunk, - music_pipe_get_chunk(nextChunk), + + if (other_chunk != NULL) { + chunk = music_pipe_shift(player.pipe); + cross_fade_apply(chunk, other_chunk, &dc.out_audio_format, fadePosition, crossFadeChunks); + music_buffer_return(player.buffer, other_chunk); } else { /* there are not enough decoded chunks yet */ @@ -460,47 +480,42 @@ static void do_play(void) } else { /* wait for the decoder */ - music_pipe_set_lazy(false); notify_signal(&dc.notify); notify_wait(&pc.notify); - /* set nextChunk to a - non-negative value - so the next - iteration doesn't - assume crossfading - hasn't begun yet */ - nextChunk = 0; continue; } } } + if (chunk == NULL) + chunk = music_pipe_shift(player.pipe); + /* play the current chunk */ - if (!play_chunk(player.song, beginChunk, - &play_audio_format, sizeToTime)) + + success = play_chunk(player.song, chunk, + &play_audio_format, sizeToTime); + music_buffer_return(player.buffer, chunk); + + if (!success) break; - music_pipe_shift(); /* this formula should prevent that the decoder gets woken up with each chunk; it is more efficient to make it decode a larger block at a time */ - if (music_pipe_available() <= (pc.buffered_before_play + music_pipe_size() * 3) / 4) + if (!decoder_is_idle() && + music_pipe_size(dc.pipe) <= (pc.buffered_before_play + + music_buffer_size(player.buffer) * 3) / 4) notify_signal(&dc.notify); - } else if (music_pipe_head_is(player.next_song_chunk)) { + } else if (dc.pipe != NULL && dc.pipe != player.pipe) { /* at the beginning of a new song */ - if (player.xfade == XFADE_ENABLED && nextChunk >= 0) { - /* the cross-fade is finished; skip - the section which was cross-faded - (and thus already played) */ - music_pipe_skip(crossFadeChunks); - } - player.xfade = XFADE_UNKNOWN; - player.next_song_chunk = -1; + music_pipe_free(player.pipe); + player.pipe = dc.pipe; + if (!player_wait_for_decoder(&player)) break; } else if (decoder_is_idle()) { @@ -524,6 +539,16 @@ static void do_play(void) } player_stop_decoder(); + + if (dc.pipe != NULL && dc.pipe != player.pipe) { + music_pipe_clear(dc.pipe, player.buffer); + music_pipe_free(dc.pipe); + } + + music_pipe_clear(player.pipe, player.buffer); + music_pipe_free(player.pipe); + + music_buffer_free(player.buffer); } static gpointer player_task(G_GNUC_UNUSED gpointer arg) -- cgit v1.2.3