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/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 \

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.
*/
struct music_chunk {
/** the next chunk in a linked list */
struct music_chunk *next;
/** number of bytes stored in this chunk */
uint16_t length;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
#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
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);
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);
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;
g_mutex_free(mp->mutex);
g_free(mp);
}
struct music_chunk *
music_pipe_get_chunk(const unsigned i)
{
assert(i < music_pipe.num_chunks);
return &music_pipe.chunks[i];
}
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);
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;
}
bool
music_pipe_push(struct music_chunk *chunk)
void
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));
next = successor(music_pipe.end);
if (music_pipe.begin == next)
/* no room */
return false;
output_buffer_expand(next);
return true;
while ((chunk = music_pipe_shift(mp)) != NULL)
music_buffer_return(buffer, chunk);
}
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);
if (i < 0)
return;
while (music_pipe.begin != (unsigned)i)
music_pipe_shift();
return mp->size;
}
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
#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.
* A queue of #music_chunk objects. One party appends chunks at the
* tail, and the other consumes them from the head.
*/
struct music_pipe {
struct music_chunk *chunks;
unsigned num_chunks;
struct music_pipe;
/** 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;
/**
* Creates a new #music_pipe object. It is empty.
*/
struct music_pipe *
music_pipe_new(void);
/**
* Frees the object. It must be empty now.
*/
void
music_pipe_init(unsigned int size, struct notify *notify);
void music_pipe_free(void);
void music_pipe_clear(void);
music_pipe_free(struct music_pipe *mp);
/**
* 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.
*/
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
* Removes the first chunk from the head, and returns it.
*/
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()
* @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()
* @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

View File

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

View File

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

View File

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