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.
This commit is contained in:
Max Kellermann 2009-03-06 00:42:03 +01:00
parent 000b2d4f3a
commit 01cf7feac7
15 changed files with 433 additions and 417 deletions

View File

@ -84,6 +84,7 @@ mpd_headers = \
src/daemon.h \ src/daemon.h \
src/normalize.h \ src/normalize.h \
src/compress.h \ src/compress.h \
src/buffer.h \
src/pipe.h \ src/pipe.h \
src/chunk.h \ src/chunk.h \
src/path.h \ src/path.h \
@ -180,6 +181,7 @@ src_mpd_SOURCES = \
src/daemon.c \ src/daemon.c \
src/normalize.c \ src/normalize.c \
src/compress.c \ src/compress.c \
src/buffer.c \
src/pipe.c \ src/pipe.c \
src/chunk.c \ src/chunk.c \
src/path.c \ src/path.c \

127
src/buffer.c Normal file
View File

@ -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);
}

66
src/buffer.h Normal file
View File

@ -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

View File

@ -35,6 +35,9 @@ struct audio_format;
* music_pipe_append() caller. * music_pipe_append() caller.
*/ */
struct music_chunk { struct music_chunk {
/** the next chunk in a linked list */
struct music_chunk *next;
/** number of bytes stored in this chunk */ /** number of bytes stored in this chunk */
uint16_t length; uint16_t length;

View File

@ -23,6 +23,7 @@
#include "player_control.h" #include "player_control.h"
#include "audio.h" #include "audio.h"
#include "song.h" #include "song.h"
#include "buffer.h"
#include "normalize.h" #include "normalize.h"
#include "pipe.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 */ /* delete frames from the old song position */
if (decoder->chunk != NULL) { if (decoder->chunk != NULL) {
music_pipe_cancel(decoder->chunk); music_buffer_return(dc.buffer, decoder->chunk);
decoder->chunk = NULL; decoder->chunk = NULL;
} }
music_pipe_clear(); music_pipe_clear(dc.pipe, dc.buffer);
} }
dc.command = DECODE_COMMAND_NONE; dc.command = DECODE_COMMAND_NONE;
@ -167,15 +168,18 @@ do_send_tag(struct decoder *decoder, struct input_stream *is,
if (decoder->chunk != NULL) { if (decoder->chunk != NULL) {
/* there is a partial chunk - flush it, we want the /* there is a partial chunk - flush it, we want the
tag in a new chunk */ tag in a new chunk */
enum decoder_command cmd = decoder_flush_chunk(decoder);
decoder_flush_chunk(decoder, is); notify_signal(&pc.notify);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
} }
assert(decoder->chunk == NULL); 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); chunk->tag = tag_dup(tag);
return DECODE_COMMAND_NONE; return DECODE_COMMAND_NONE;
} }
@ -256,15 +260,18 @@ decoder_data(struct decoder *decoder,
size_t nbytes; size_t nbytes;
bool full; 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, dest = music_chunk_write(chunk, &dc.out_audio_format,
data_time, bitRate, &nbytes); data_time, bitRate, &nbytes);
if (dest == NULL) { if (dest == NULL) {
/* the chunk is full, flush it */ /* the chunk is full, flush it */
enum decoder_command cmd = decoder_flush_chunk(decoder);
decoder_flush_chunk(decoder, is); notify_signal(&pc.notify);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
continue; continue;
} }
@ -291,10 +298,8 @@ decoder_data(struct decoder *decoder,
full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes); full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes);
if (full) { if (full) {
/* the chunk is full, flush it */ /* the chunk is full, flush it */
enum decoder_command cmd = decoder_flush_chunk(decoder);
decoder_flush_chunk(decoder, is); notify_signal(&pc.notify);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
} }
data += nbytes; data += nbytes;

View File

@ -66,6 +66,12 @@ struct decoder_control {
struct song *current_song; struct song *current_song;
struct song *next_song; struct song *next_song;
float total_time; 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; extern struct decoder_control dc;

View File

@ -21,6 +21,7 @@
#include "player_control.h" #include "player_control.h"
#include "pipe.h" #include "pipe.h"
#include "input_stream.h" #include "input_stream.h"
#include "buffer.h"
#include <assert.h> #include <assert.h>
@ -46,35 +47,32 @@ need_chunks(struct input_stream *is, bool do_wait)
} }
struct music_chunk * 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); assert(decoder != NULL);
if (decoder->chunk != NULL) if (decoder->chunk != NULL)
return decoder->chunk; return decoder->chunk;
decoder->chunk = music_pipe_allocate(); do {
return decoder->chunk; 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 void
decoder_flush_chunk(struct decoder *decoder, struct input_stream *is) decoder_flush_chunk(struct decoder *decoder)
{ {
bool success;
enum decoder_command cmd;
assert(decoder != NULL); assert(decoder != NULL);
assert(decoder->chunk != NULL); assert(decoder->chunk != NULL);
while (true) { music_pipe_push(dc.pipe, decoder->chunk);
success = music_pipe_push(decoder->chunk); decoder->chunk = NULL;
if (success) {
decoder->chunk = NULL;
return DECODE_COMMAND_NONE;
}
cmd = need_chunks(is, true);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
}
} }

View File

@ -42,17 +42,16 @@ struct decoder {
/** /**
* Returns the current chunk the decoder writes to, or allocates a new * Returns the current chunk the decoder writes to, or allocates a new
* chunk if there is none. * chunk if there is none.
*
* @return the chunk, or NULL if we have received a decoder command
*/ */
struct music_chunk * 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. * Flushes the current chunk.
*
* @return DECODE_COMMAND_NONE on success, any other command if we
* have received a decoder command while waiting
*/ */
enum decoder_command void
decoder_flush_chunk(struct decoder *decoder, struct input_stream *is); decoder_flush_chunk(struct decoder *decoder);
#endif #endif

View File

@ -196,9 +196,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
pcm_convert_deinit(&decoder.conv_state); pcm_convert_deinit(&decoder.conv_state);
/* flush the last chunk */ /* flush the last chunk */
if (decoder.chunk != NULL && if (decoder.chunk != NULL)
decoder_flush_chunk(&decoder, NULL) != DECODE_COMMAND_NONE) decoder_flush_chunk(&decoder);
music_pipe_cancel(decoder.chunk);
if (close_instream) if (close_instream)
input_stream_close(&input_stream); input_stream_close(&input_stream);

View File

@ -31,7 +31,6 @@
#include "conf.h" #include "conf.h"
#include "path.h" #include "path.h"
#include "mapper.h" #include "mapper.h"
#include "pipe.h"
#include "chunk.h" #include "chunk.h"
#include "decoder_control.h" #include "decoder_control.h"
#include "player_control.h" #include "player_control.h"
@ -179,8 +178,7 @@ initialize_decoder_and_player(void)
if (buffered_before_play > buffered_chunks) if (buffered_before_play > buffered_chunks)
buffered_before_play = buffered_chunks; buffered_before_play = buffered_chunks;
pc_init(buffered_before_play); pc_init(buffered_chunks, buffered_before_play);
music_pipe_init(buffered_chunks, &pc.notify);
dc_init(); dc_init();
} }
@ -333,7 +331,6 @@ int main(int argc, char *argv[])
#ifdef ENABLE_ARCHIVE #ifdef ENABLE_ARCHIVE
archive_plugin_deinit_all(); archive_plugin_deinit_all();
#endif #endif
music_pipe_free();
config_global_finish(); config_global_finish();
tag_pool_deinit(); tag_pool_deinit();
songvec_deinit(); songvec_deinit();

View File

@ -17,208 +17,103 @@
*/ */
#include "pipe.h" #include "pipe.h"
#include "buffer.h"
#include "chunk.h" #include "chunk.h"
#include "notify.h"
#include "audio_format.h"
#include "tag.h"
#include <glib.h> #include <glib.h>
#include <assert.h>
#include <string.h>
struct music_pipe music_pipe; #include <assert.h>
struct music_pipe {
/** the first chunk */
struct music_chunk *head;
/** a pointer to the tail of the chunk */
struct music_chunk **tail_r;
/** the current number of chunks */
unsigned size;
/** a mutex which protects #head and #tail_r */
GMutex *mutex;
};
struct music_pipe *
music_pipe_new(void)
{
struct music_pipe *mp = g_new(struct music_pipe, 1);
mp->head = NULL;
mp->tail_r = &mp->head;
mp->size = 0;
mp->mutex = g_mutex_new();
return mp;
}
void void
music_pipe_init(unsigned int size, struct notify *notify) music_pipe_free(struct music_pipe *mp)
{ {
assert(size > 0); assert(mp->head == NULL);
assert(mp->tail_r == &mp->head);
music_pipe.chunks = g_new(struct music_chunk, size); g_mutex_free(mp->mutex);
music_pipe.num_chunks = size; g_free(mp);
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);
music_chunk_free(&music_pipe.chunks[music_pipe.begin]);
music_pipe.begin = successor(music_pipe.begin);
}
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;
}
unsigned music_pipe_available(void)
{
return music_pipe_relative(music_pipe.end);
}
int music_pipe_absolute(const unsigned relative)
{
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;
if (i >= music_pipe.num_chunks)
i -= music_pipe.num_chunks;
return (int)i;
} }
struct music_chunk * struct music_chunk *
music_pipe_get_chunk(const unsigned i) music_pipe_shift(struct music_pipe *mp)
{
assert(i < music_pipe.num_chunks);
return &music_pipe.chunks[i];
}
struct music_chunk *
music_pipe_allocate(void)
{ {
struct music_chunk *chunk; struct music_chunk *chunk;
/* the music_pipe.end chunk is always kept initialized */ g_mutex_lock(mp->mutex);
chunk = music_pipe_get_chunk(music_pipe.end);
assert(chunk->length == 0); chunk = mp->head;
if (chunk != NULL) {
mp->head = chunk->next;
--mp->size;
if (mp->head == NULL) {
assert(mp->size == 0);
assert(mp->tail_r == &chunk->next);
mp->tail_r = &mp->head;
} else {
assert(mp->size > 0);
assert(mp->tail_r != &chunk->next);
}
}
g_mutex_unlock(mp->mutex);
return chunk; return chunk;
} }
bool void
music_pipe_push(struct music_chunk *chunk) music_pipe_clear(struct music_pipe *mp, struct music_buffer *buffer)
{ {
unsigned int next; struct music_chunk *chunk;
assert(chunk == music_pipe_get_chunk(music_pipe.end)); while ((chunk = music_pipe_shift(mp)) != NULL)
music_buffer_return(buffer, chunk);
next = successor(music_pipe.end);
if (music_pipe.begin == next)
/* no room */
return false;
output_buffer_expand(next);
return true;
} }
void void
music_pipe_cancel(struct music_chunk *chunk) music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk)
{ {
assert(chunk == music_pipe_get_chunk(music_pipe.end)); g_mutex_lock(mp->mutex);
music_chunk_free(chunk); chunk->next = NULL;
*mp->tail_r = chunk;
mp->tail_r = &chunk->next;
++mp->size;
g_mutex_unlock(mp->mutex);
} }
void music_pipe_skip(unsigned num) unsigned
music_pipe_size(const struct music_pipe *mp)
{ {
int i = music_pipe_absolute(num); return mp->size;
if (i < 0)
return;
while (music_pipe.begin != (unsigned)i)
music_pipe_shift();
} }
void music_pipe_chop(unsigned first)
{
for (unsigned i = first; 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 = first;
music_chunk_init(&music_pipe.chunks[first]);
}
#ifndef NDEBUG
void music_pipe_check_format(const struct audio_format *current,
int next_index, const struct audio_format *next)
{
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);
}
}
#endif

View File

@ -19,160 +19,51 @@
#ifndef MPD_PIPE_H #ifndef MPD_PIPE_H
#define 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_chunk;
struct music_buffer;
/** /**
* A ring set of buffers where the decoder appends data after the end, * A queue of #music_chunk objects. One party appends chunks at the
* and the player consumes data from the beginning. * tail, and the other consumes them from the head.
*/ */
struct music_pipe { struct music_pipe;
struct music_chunk *chunks;
unsigned num_chunks;
/** the index of the first decoded chunk */ /**
unsigned begin; * Creates a new #music_pipe object. It is empty.
*/
/** the index after the last decoded chunk */ struct music_pipe *
unsigned end; music_pipe_new(void);
/** 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;
/**
* Frees the object. It must be empty now.
*/
void void
music_pipe_init(unsigned int size, struct notify *notify); music_pipe_free(struct music_pipe *mp);
void music_pipe_free(void);
void music_pipe_clear(void);
/** /**
* When a chunk is decoded, we wake up the player thread to tell him * Removes the first chunk from the head, and returns it.
* 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.
*/
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);
/**
* what is the position of the specified chunk number, relative to
* the first chunk in use?
*/
unsigned int music_pipe_relative(const unsigned i);
/** determine the number of decoded chunks */
unsigned music_pipe_available(void);
/**
* Get the absolute index of the nth used chunk after the first one.
* Returns -1 if there is no such chunk.
*/
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);
}
/**
* Allocates a chunk for writing. When you are finished, append it
* with music_pipe_push().
*
* @return an empty chunk
*/ */
struct music_chunk * struct music_chunk *
music_pipe_allocate(void); music_pipe_shift(struct music_pipe *mp);
/** /**
* Appends a chunk at the end of the music pipe. * 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
* @return true on success, false if there is no room
*/
bool
music_pipe_push(struct music_chunk *chunk);
/**
* Cancels a chunk that has been allocated with music_pipe_allocate().
*
* @param chunk a chunk allocated with music_pipe_allocate()
*/ */
void 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 * Pushes a chunk to the tail of the pipe.
* 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().
*/ */
void void
music_pipe_expand(const struct audio_format *audio_format, size_t length); music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
void music_pipe_skip(unsigned num);
/** /**
* Chop off the tail of the music pipe, starting with the chunk at * Returns the number of chunks currently in this pipe.
* index "first".
*/ */
void music_pipe_chop(unsigned first); unsigned
music_pipe_size(const struct music_pipe *mp);
#ifndef NDEBUG
void music_pipe_check_format(const struct audio_format *current,
int next_index, const struct audio_format *next);
#endif
#endif #endif

View File

@ -30,8 +30,9 @@
struct player_control pc; 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; pc.buffered_before_play = buffered_before_play;
notify_init(&pc.notify); notify_init(&pc.notify);
pc.command = PLAYER_COMMAND_NONE; pc.command = PLAYER_COMMAND_NONE;

View File

@ -60,6 +60,8 @@ enum player_error {
}; };
struct player_control { struct player_control {
unsigned buffer_chunks;
unsigned int buffered_before_play; unsigned int buffered_before_play;
/** the handle of the player thread, or NULL if the player /** the handle of the player thread, or NULL if the player
@ -84,7 +86,7 @@ struct player_control {
extern struct player_control pc; 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); void pc_deinit(void);

View File

@ -31,6 +31,7 @@
#include "chunk.h" #include "chunk.h"
#include "idle.h" #include "idle.h"
#include "main.h" #include "main.h"
#include "buffer.h"
#include <glib.h> #include <glib.h>
@ -44,6 +45,9 @@ enum xfade_state {
}; };
struct player { struct player {
struct music_buffer *buffer;
struct music_pipe *pipe;
/** /**
* are we waiting for buffered_before_play? * are we waiting for buffered_before_play?
*/ */
@ -74,12 +78,6 @@ struct player {
* is cross fading enabled? * is cross fading enabled?
*/ */
enum xfade_state xfade; 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) static void player_command_finished(void)
@ -90,6 +88,21 @@ static void player_command_finished(void)
notify_signal(&main_notify); 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) static void player_stop_decoder(void)
{ {
dc_stop(&pc.notify); dc_stop(&pc.notify);
@ -133,9 +146,10 @@ static bool player_seek_decoder(struct player *player)
bool ret; bool ret;
if (decoder_current_song() != pc.next_song) { if (decoder_current_song() != pc.next_song) {
dc_stop(&pc.notify); player_dc_stop(player);
player->next_song_chunk = -1;
music_pipe_clear(); music_pipe_clear(player->pipe, player->buffer);
dc.pipe = player->pipe;
dc_start_async(pc.next_song); dc_start_async(pc.next_song);
ret = player_wait_for_decoder(player); ret = player_wait_for_decoder(player);
@ -174,7 +188,7 @@ static void player_process_command(struct player *player)
case PLAYER_COMMAND_QUEUE: case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL); assert(pc.next_song != NULL);
assert(!player->queued); assert(!player->queued);
assert(player->next_song_chunk == -1); assert(dc.pipe == NULL || dc.pipe == player->pipe);
player->queued = true; player->queued = true;
player_command_finished(); player_command_finished();
@ -220,12 +234,11 @@ static void player_process_command(struct player *player)
return; return;
} }
if (player->next_song_chunk != -1) { if (dc.pipe != NULL && dc.pipe != player->pipe) {
/* the decoder is already decoding the song - /* the decoder is already decoding the song -
stop it and reset the position */ stop it and reset the position */
player_dc_stop(player);
dc_stop(&pc.notify); dc_stop(&pc.notify);
music_pipe_chop(player->next_song_chunk);
player->next_song_chunk = -1;
} }
pc.next_song = NULL; pc.next_song = NULL;
@ -298,23 +311,25 @@ static void do_play(void)
.queued = false, .queued = false,
.song = NULL, .song = NULL,
.xfade = XFADE_UNKNOWN, .xfade = XFADE_UNKNOWN,
.next_song_chunk = -1,
}; };
unsigned int crossFadeChunks = 0; unsigned int crossFadeChunks = 0;
/** the position of the next cross-faded chunk in the next /** has cross-fading begun? */
song */ bool cross_fading = false;
int nextChunk = 0;
static const char silence[CHUNK_SIZE]; static const char silence[CHUNK_SIZE];
struct audio_format play_audio_format; struct audio_format play_audio_format;
double sizeToTime = 0.0; double sizeToTime = 0.0;
music_pipe_clear(); player.buffer = music_buffer_new(pc.buffer_chunks);
music_pipe_set_lazy(false); player.pipe = music_pipe_new();
dc.buffer = player.buffer;
dc.pipe = player.pipe;
dc_start(&pc.notify, pc.next_song); dc_start(&pc.notify, pc.next_song);
if (!player_wait_for_decoder(&player)) { if (!player_wait_for_decoder(&player)) {
player_stop_decoder(); player_stop_decoder();
player_command_finished(); player_command_finished();
music_pipe_free(player.pipe);
music_buffer_free(player.buffer);
return; return;
} }
@ -332,7 +347,7 @@ static void do_play(void)
} }
if (player.buffering) { if (player.buffering) {
if (music_pipe_available() < pc.buffered_before_play && if (music_pipe_size(player.pipe) < pc.buffered_before_play &&
!decoder_is_idle()) { !decoder_is_idle()) {
/* not enough decoded buffer space yet */ /* not enough decoded buffer space yet */
notify_wait(&pc.notify); notify_wait(&pc.notify);
@ -340,7 +355,6 @@ static void do_play(void)
} else { } else {
/* buffering is complete */ /* buffering is complete */
player.buffering = false; player.buffering = false;
music_pipe_set_lazy(true);
} }
} }
@ -395,13 +409,14 @@ static void do_play(void)
/* the decoder has finished the current song; /* the decoder has finished the current song;
make it decode the next song */ make it decode the next song */
assert(pc.next_song != NULL); assert(pc.next_song != NULL);
assert(player.next_song_chunk == -1); assert(dc.pipe == NULL || dc.pipe == player.pipe);
player.queued = false; player.queued = false;
player.next_song_chunk = music_pipe_tail_index(); dc.pipe = music_pipe_new();
dc_start_async(pc.next_song); dc_start_async(pc.next_song);
} }
if (player.next_song_chunk >= 0 &&
if (dc.pipe != NULL && dc.pipe != player.pipe &&
player.xfade == XFADE_UNKNOWN && player.xfade == XFADE_UNKNOWN &&
!decoder_is_starting()) { !decoder_is_starting()) {
/* enable cross fading in this song? if yes, /* 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, cross_fade_calc(pc.cross_fade_seconds, dc.total_time,
&dc.out_audio_format, &dc.out_audio_format,
&play_audio_format, &play_audio_format,
music_pipe_size() - music_buffer_size(player.buffer) -
pc.buffered_before_play); pc.buffered_before_play);
if (crossFadeChunks > 0) { if (crossFadeChunks > 0) {
player.xfade = XFADE_ENABLED; player.xfade = XFADE_ENABLED;
nextChunk = -1; cross_fading = false;
} else } else
/* cross fading is disabled or the /* cross fading is disabled or the
next song is too short */ next song is too short */
@ -424,31 +439,36 @@ static void do_play(void)
if (player.paused) if (player.paused)
notify_wait(&pc.notify); notify_wait(&pc.notify);
else if (!music_pipe_is_empty() && else if (music_pipe_size(player.pipe) > 0) {
!music_pipe_head_is(player.next_song_chunk)) { struct music_chunk *chunk = NULL;
struct music_chunk *beginChunk = music_pipe_peek();
unsigned int fadePosition; unsigned int fadePosition;
bool success;
if (player.xfade == XFADE_ENABLED && if (player.xfade == XFADE_ENABLED &&
player.next_song_chunk >= 0 && dc.pipe != NULL && dc.pipe != player.pipe &&
(fadePosition = music_pipe_relative(player.next_song_chunk)) (fadePosition = music_pipe_size(player.pipe))
<= crossFadeChunks) { <= crossFadeChunks) {
/* perform cross fade */ /* perform cross fade */
if (nextChunk < 0) { struct music_chunk *other_chunk =
music_pipe_shift(dc.pipe);
if (!cross_fading) {
/* beginning of the cross fade /* beginning of the cross fade
- adjust crossFadeChunks - adjust crossFadeChunks
which might be bigger than which might be bigger than
the remaining number of the remaining number of
chunks in the old song */ chunks in the old song */
crossFadeChunks = fadePosition; crossFadeChunks = fadePosition;
cross_fading = true;
} }
nextChunk = music_pipe_absolute(crossFadeChunks);
if (nextChunk >= 0) { if (other_chunk != NULL) {
music_pipe_set_lazy(true); chunk = music_pipe_shift(player.pipe);
cross_fade_apply(beginChunk, cross_fade_apply(chunk, other_chunk,
music_pipe_get_chunk(nextChunk),
&dc.out_audio_format, &dc.out_audio_format,
fadePosition, fadePosition,
crossFadeChunks); crossFadeChunks);
music_buffer_return(player.buffer, other_chunk);
} else { } else {
/* there are not enough /* there are not enough
decoded chunks yet */ decoded chunks yet */
@ -460,47 +480,42 @@ static void do_play(void)
} else { } else {
/* wait for the /* wait for the
decoder */ decoder */
music_pipe_set_lazy(false);
notify_signal(&dc.notify); notify_signal(&dc.notify);
notify_wait(&pc.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; continue;
} }
} }
} }
if (chunk == NULL)
chunk = music_pipe_shift(player.pipe);
/* play the current chunk */ /* 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; break;
music_pipe_shift();
/* 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
is more efficient to make it decode a is more efficient to make it decode a
larger block at a time */ 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); 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 */ /* 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.xfade = XFADE_UNKNOWN;
player.next_song_chunk = -1; music_pipe_free(player.pipe);
player.pipe = dc.pipe;
if (!player_wait_for_decoder(&player)) if (!player_wait_for_decoder(&player))
break; break;
} else if (decoder_is_idle()) { } else if (decoder_is_idle()) {
@ -524,6 +539,16 @@ static void do_play(void)
} }
player_stop_decoder(); 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) static gpointer player_task(G_GNUC_UNUSED gpointer arg)