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:
parent
000b2d4f3a
commit
01cf7feac7
@ -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
127
src/buffer.c
Normal 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
66
src/buffer.h
Normal 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
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
247
src/pipe.c
247
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;
|
||||
#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
|
||||
|
155
src/pipe.h
155
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.
|
||||
* 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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user