aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2009-03-06 00:42:03 +0100
committerMax Kellermann <max@duempel.org>2009-03-06 00:42:03 +0100
commit01cf7feac7bef8b28605b98ef1e7438a995fc554 (patch)
treee1c4b7f5d0550d60d7fda8b4909353a47490fa4e
parent000b2d4f3a9c4f761ab918aaff4705621bb8559f (diff)
downloadmpd-01cf7feac7bef8b28605b98ef1e7438a995fc554.tar.gz
mpd-01cf7feac7bef8b28605b98ef1e7438a995fc554.tar.xz
mpd-01cf7feac7bef8b28605b98ef1e7438a995fc554.zip
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.
Diffstat (limited to '')
-rw-r--r--Makefile.am2
-rw-r--r--src/buffer.c127
-rw-r--r--src/buffer.h66
-rw-r--r--src/chunk.h3
-rw-r--r--src/decoder_api.c37
-rw-r--r--src/decoder_control.h6
-rw-r--r--src/decoder_internal.c36
-rw-r--r--src/decoder_internal.h13
-rw-r--r--src/decoder_thread.c5
-rw-r--r--src/main.c5
-rw-r--r--src/pipe.c221
-rw-r--r--src/pipe.h149
-rw-r--r--src/player_control.c3
-rw-r--r--src/player_control.h4
-rw-r--r--src/player_thread.c141
15 files changed, 417 insertions, 401 deletions
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 <glib.h>
+
+#include <assert.h>
+
+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 <assert.h>
@@ -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 <glib.h>
-#include <assert.h>
-#include <string.h>
-
-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 <assert.h>
- 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 <stddef.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-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 <glib.h>
@@ -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)