From 3e24f857fad41787a58bc564fb060b308f1255fe Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 26 Dec 2016 19:30:29 +0100 Subject: [PATCH] [input] Add input interface to player - WIP --- src/Makefile.am | 2 + src/input.c | 470 +++++++++++++++++++++++++++++++++++++++++ src/input.h | 176 +++++++++++++++ src/inputs/file_http.c | 125 +++++++++++ src/player.c | 375 +++++--------------------------- src/player.h | 6 - 6 files changed, 824 insertions(+), 330 deletions(-) create mode 100644 src/input.c create mode 100644 src/input.h create mode 100644 src/inputs/file_http.c diff --git a/src/Makefile.am b/src/Makefile.am index dd7f40fe..faa62afc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -108,6 +108,8 @@ forked_daapd_SOURCES = main.c \ daap_query.c daap_query.h \ player.c player.h \ worker.c worker.h \ + input.h input.c \ + inputs/file_http.c \ outputs.h outputs.c \ outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \ $(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \ diff --git a/src/input.c b/src/input.c new file mode 100644 index 00000000..828bdc92 --- /dev/null +++ b/src/input.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2017 Espen Jürgensen + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef HAVE_PTHREAD_NP_H +# include +#endif + +#include "misc.h" +#include "logger.h" +#include "input.h" + +// Disallow further writes to the buffer when its size is larger than this threshold +#define INPUT_BUFFER_THRESHOLD STOB(44100) + +#define DEBUG 1 //TODO disable + +extern struct input_definition input_file; +extern struct input_definition input_http; + +// Must be in sync with enum input_types +static struct input_definition *inputs[] = { + &input_file, + &input_http, + NULL +}; + +struct input_buffer +{ + // Raw pcm stream data + struct evbuffer *evbuf; + + // If non-zero, remaining length of buffer until EOF + size_t eof; + // If non-zero, remaining length of buffer until (possible) new metadata + size_t metadata; + + // Locks for sharing the buffer between input and player thread + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +/* --- Globals --- */ +// Input thread +static pthread_t tid_input; + +// Input buffer +static struct input_buffer input_buffer; + +#ifdef DEBUG +static size_t debug_elapsed; +#endif + + +/* ------------------------------ MISC HELPERS ---------------------------- */ + +static short +flags_set(size_t len) +{ + short flags = 0; + + if (input_buffer.eof) + { + if (len >= input_buffer.eof) + { + flags |= INPUT_FLAG_EOF; + input_buffer.eof = 0; + } + else + input_buffer.eof -= len; + } + + if (input_buffer.metadata) + { + if (len >= input_buffer.metadata) + { + flags |= INPUT_FLAG_METADATA; + input_buffer.metadata = 0; + } + else + input_buffer.metadata -= len; + } + + return flags; +} + +static int +map_data_kind(int data_kind) +{ + switch (data_kind) + { + case DATA_KIND_FILE: + return INPUT_TYPE_FILE; + + case DATA_KIND_HTTP: + return INPUT_TYPE_HTTP; + + default: + return -1; + } +} + +static int +source_check_and_map(struct player_source *ps, const char *action, char check_setup) +{ + int type; + +#ifdef DEBUG + DPRINTF(E_DBG, L_PLAYER, "Action is %s\n", action); +#endif + + if (!ps) + { + DPRINTF(E_LOG, L_PLAYER, "Stream %s called with invalid player source\n", action); + return -1; + } + + if (check_setup && !ps->setup_done) + { + DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, %s not possible\n", action); + return -1; + } + + type = map_data_kind(ps->data_kind); + if (type < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Unsupported input type, %s not possible\n", action); + return -1; + } + + return type; +} + +/* ----------------------------- PLAYBACK LOOP ---------------------------- */ +/* Thread: input */ + +// TODO Thread safety of ps? Do we need the return of the loop? +static void * +playback(void *arg) +{ + struct player_source *ps = arg; + int type; + + type = source_check_and_map(ps, "start", 1); + if ((type < 0) || (inputs[type]->disabled)) + goto thread_exit; + + // Loops until input_loop_break is set or no more input, e.g. EOF + inputs[type]->start(ps); + +#ifdef DEBUG + DPRINTF(E_DBG, L_PLAYER, "Playback loop stopped (break is %d)\n", input_loop_break); +#endif + + thread_exit: + pthread_exit(NULL); +} + +// Called by input modules from within the playback loop +int +input_write(struct evbuffer *evbuf, short flags) +{ + int ret; + + pthread_mutex_lock(&input_buffer.mutex); + + while ( (!input_loop_break) && (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) ) + { + if (flags & INPUT_FLAG_NONBLOCK) + { + pthread_mutex_unlock(&input_buffer.mutex); + return EAGAIN; + } + + pthread_cond_wait(&input_buffer.cond, &input_buffer.mutex); + + // TODO protect against infinite looping and waiting? + } + + if (!input_loop_break) + { + ret = evbuffer_add_buffer(input_buffer.evbuf, evbuf); + if (ret < 0) + DPRINTF(E_LOG, L_PLAYER, "Error adding stream data to input buffer\n"); + + if (!input_buffer.eof && (flags & INPUT_FLAG_EOF)) + input_buffer.eof = evbuffer_get_length(input_buffer.evbuf); + if (!input_buffer.metadata && (flags & INPUT_FLAG_METADATA)) + input_buffer.metadata = evbuffer_get_length(input_buffer.evbuf); + } + else + ret = 0; + + pthread_mutex_unlock(&input_buffer.mutex); + + return ret; +} + + +/* -------------------- Interface towards player thread ------------------- */ +/* Thread: player */ + +int +input_read(struct evbuffer *evbuf, size_t want, short *flags) +{ + int len; + + *flags = 0; + + pthread_mutex_lock(&input_buffer.mutex); + +#ifdef DEBUG + debug_elapsed += want; + if (debug_elapsed > STOB(441000)) // 10 sec + { + DPRINTF(E_DBG, L_PLAYER, "Input buffer has %zu bytes\n", evbuffer_get_length(input_buffer.evbuf)); + debug_elapsed = 0; + } +#endif + + len = evbuffer_remove_buffer(input_buffer.evbuf, evbuf, want); + if (len < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Error reading stream data from input buffer\n"); + goto out_unlock; + } + + *flags = flags_set(len); + + out_unlock: + pthread_cond_signal(&input_buffer.cond); + pthread_mutex_unlock(&input_buffer.mutex); + + return len; +} + +int +input_setup(struct player_source *ps) +{ + int type; + + type = source_check_and_map(ps, "setup", 0); + if ((type < 0) || (inputs[type]->disabled)) + return -1; + + if (!inputs[type]->setup) + return 0; + + return inputs[type]->setup(ps); +} + +int +input_start(struct player_source *ps) +{ + int ret; + + if (tid_input) + { + DPRINTF(E_LOG, L_PLAYER, "Bug! Input start called, but playback already running\n"); + input_pause(ps); + } + + input_loop_break = 0; + + ret = pthread_create(&tid_input, NULL, playback, ps); + if (ret < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Could not spawn input thread: %s\n", strerror(errno)); + return -1; + } + +#if defined(HAVE_PTHREAD_SETNAME_NP) + pthread_setname_np(tid_input, "input"); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_set_name_np(tid_input, "input"); +#endif + + return 0; +} + +int +input_pause(struct player_source *ps) +{ + short flags; + int ret; + +#ifdef DEBUG + DPRINTF(E_DBG, L_PLAYER, "Pause called, stopping playback loop\n"); +#endif + + if (!tid_input) + return -1; + + pthread_mutex_lock(&input_buffer.mutex); + + input_loop_break = 1; + + pthread_cond_signal(&input_buffer.cond); + pthread_mutex_unlock(&input_buffer.mutex); + + ret = pthread_join(tid_input, NULL); + if (ret != 0) + { + DPRINTF(E_LOG, L_PLAYER, "Could not join input thread: %s\n", strerror(errno)); + return -1; + } + + tid_input = 0; + + input_flush(&flags); + + return 0; +} + +int +input_stop(struct player_source *ps) +{ + int type; + + if (tid_input) + input_pause(ps); + + if (!ps) + return 0; + + type = source_check_and_map(ps, "stop", 1); + if ((type < 0) || (inputs[type]->disabled)) + return -1; + + if (!inputs[type]->stop) + return 0; + + return inputs[type]->stop(ps); +} + +int +input_seek(struct player_source *ps, int seek_ms) +{ + int type; + + type = source_check_and_map(ps, "seek", 1); + if ((type < 0) || (inputs[type]->disabled)) + return -1; + + if (!inputs[type]->seek) + return 0; + + if (tid_input) + input_pause(ps); + + return inputs[type]->seek(ps, seek_ms); +} + +void +input_flush(short *flags) +{ + size_t len; + + pthread_mutex_lock(&input_buffer.mutex); + + len = evbuffer_get_length(input_buffer.evbuf); + + evbuffer_drain(input_buffer.evbuf, len); + + *flags = flags_set(len); + + input_buffer.eof = 0; + input_buffer.metadata = 0; + + pthread_mutex_unlock(&input_buffer.mutex); + +#ifdef DEBUG + DPRINTF(E_DBG, L_PLAYER, "Flush with flags %d\n", *flags); +#endif +} + +int +input_init(void) +{ + int no_input; + int ret; + int i; + + // Prepare input buffer + pthread_mutex_init(&input_buffer.mutex, NULL); + pthread_cond_init(&input_buffer.cond, NULL); + + input_buffer.evbuf = evbuffer_new(); + if (!input_buffer.evbuf) + { + DPRINTF(E_LOG, L_PLAYER, "Out of memory for input buffer\n"); + return -1; + } + + no_input = 1; + for (i = 0; inputs[i]; i++) + { + if (inputs[i]->type != i) + { + DPRINTF(E_FATAL, L_PLAYER, "BUG! Input definitions are misaligned with input enum\n"); + return -1; + } + + if (!inputs[i]->init) + { + no_input = 0; + continue; + } + + ret = inputs[i]->init(); + if (ret < 0) + inputs[i]->disabled = 1; + else + no_input = 0; + } + + if (no_input) + return -1; + + return 0; +} + +void +input_deinit(void) +{ + int i; + + input_stop(NULL); + + for (i = 0; inputs[i]; i++) + { + if (inputs[i]->disabled) + continue; + + if (inputs[i]->deinit) + inputs[i]->deinit(); + } + + evbuffer_free(input_buffer.evbuf); +} + diff --git a/src/input.h b/src/input.h new file mode 100644 index 00000000..4b6f3a57 --- /dev/null +++ b/src/input.h @@ -0,0 +1,176 @@ + +#ifndef __INPUT_H__ +#define __INPUT_H__ + +#include +#include "transcode.h" + +// Must be in sync with inputs[] in input.c +enum input_types +{ + INPUT_TYPE_FILE, + INPUT_TYPE_HTTP, +}; + +enum input_flags +{ + // Write to input buffer must not block + INPUT_FLAG_NONBLOCK = (1 << 0), + // Flags end of file + INPUT_FLAG_EOF = (1 << 1), + // Flags possible new stream metadata + INPUT_FLAG_METADATA = (1 << 2), +}; + +struct player_source +{ + /* Id of the file/item in the files database */ + uint32_t id; + + /* Item-Id of the file/item in the queue */ + uint32_t item_id; + + /* Length of the file/item in milliseconds */ + uint32_t len_ms; + + enum data_kind data_kind; + enum media_kind media_kind; + char *path; + + /* Start time of the media item as rtp-time + The stream-start is the rtp-time the media item did or would have + started playing (after seek or pause), therefor the elapsed time of the + media item is always: + elapsed time = current rtptime - stream-start */ + uint64_t stream_start; + + /* Output start time of the media item as rtp-time + The output start time is the rtp-time of the first audio packet send + to the audio outputs. + It differs from stream-start especially after a seek, where the first audio + packet has the next rtp-time as output start and stream start becomes the + rtp-time the media item would have been started playing if the seek did + not happen. */ + uint64_t output_start; + + /* End time of media item as rtp-time + The end time is set if the reading (source_read) of the media item reached + end of file, until then it is 0. */ + uint64_t end; + + struct transcode_ctx *xcode; + int setup_done; + + struct player_source *play_next; +}; + +struct input_definition +{ + // Name of the input + const char *name; + + // Type of input + enum input_types type; + + // Set to 1 if the input initialization failed + char disabled; + + // Prepare a playback session + int (*setup)(struct player_source *ps); + + // Starts playback loop (must be defined) + int (*start)(struct player_source *ps); + + // Cleans up when playback loop has ended + int (*stop)(struct player_source *ps); + + // Changes the playback position + int (*seek)(struct player_source *ps, int seek_ms); + + // Initialization function called during startup + int (*init)(void); + + // Deinitialization function called at shutdown + void (*deinit)(void); + +}; + +/* + * Input modules use this to test if playback should be stopped or seeked + */ +int input_loop_break; + +/* + * Transfer stream data to the player's input buffer. The input evbuf will be + * drained on succesful write. This is to avoid copying memory. If the player's + * input buffer is full the function will block until the write can be made + * (unless INPUT_FILE_NONBLOCK is set). + * + * @in evbuf Raw audio data to write + * @in flags One or more INPUT_FLAG_* + * @return 0 on success, EAGAIN if buffer was full (and _NONBLOCK is set), + * -1 on error + */ +int +input_write(struct evbuffer *evbuf, short flags); + +/* + * Move a chunk of stream data from the player's input buffer to an output + * buffer. Should only be called by the player thread. Will not block. + * + * @in evbuf Output buffer + * @in want How much data to move to the output buffer + * @out flags Flags INPUT_FLAG_EOF or INPUT_FLAG_METADATA + * @return Number of bytes moved + */ +int +input_read(struct evbuffer *evbuf, size_t want, short *flags); + +/* + * Initializes the given player source for playback + */ +int +input_setup(struct player_source *ps); + +/* + * Tells the input to start or resume playback, i.e. after calling this function + * the input buffer will begin to fill up, and should be read periodically with + * input_read(). Before calling this input_setup() must have been called. + */ +int +input_start(struct player_source *ps); + +/* + * Pauses playback of the given player source (stops playback loop) and flushes + * the input buffer + */ +int +input_pause(struct player_source *ps); + +/* + * Stops playback loop (if running), flushes input buffer and cleans up the + * player source + */ +int +input_stop(struct player_source *ps); + +/* + * Seeks playback position to seek_ms. Returns actual seek position, 0 on + * unseekable, -1 on error. May block. + */ +int +input_seek(struct player_source *ps, int seek_ms); + +/* + * Flush input buffer. Output flags will be the same as input_read(). + */ +void +input_flush(short *flags); + +int +input_init(void); + +void +input_deinit(void); + +#endif /* !__INPUT_H__ */ diff --git a/src/inputs/file_http.c b/src/inputs/file_http.c new file mode 100644 index 00000000..956982b5 --- /dev/null +++ b/src/inputs/file_http.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 Espen Jurgensen + * + * 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 +#include +#include +#include + +#include + +#include "transcode.h" +#include "http.h" +#include "input.h" + +static int +setup(struct player_source *ps) +{ + ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL); + if (!ps->xcode) + return -1; + + ps->setup_done = 1; + + return 0; +} + +static int +http_setup(struct player_source *ps) +{ + char *url; + + if (http_stream_setup(&url, ps->path) < 0) + return -1; + + free(ps->path); + ps->path = url; + + return setup(ps); +} + +static int +start(struct player_source *ps) +{ + struct evbuffer *evbuf; + short flags; + int ret; + int icy_timer; + + evbuf = evbuffer_new(); + + ret = -1; + flags = 0; + while (!input_loop_break && !(flags & INPUT_FLAG_EOF)) + { + // We set "wanted" to 1 because the read size doesn't matter to us + // TODO optimize? + ret = transcode(evbuf, 1, ps->xcode, &icy_timer); + if (ret < 0) + break; + + flags = ((ret == 0) ? INPUT_FLAG_EOF : 0) | + (icy_timer ? INPUT_FLAG_METADATA : 0); + + ret = input_write(evbuf, flags); + if (ret < 0) + break; + } + + evbuffer_free(evbuf); + + return ret; +} + +static int +stop(struct player_source *ps) +{ + transcode_cleanup(ps->xcode); + + ps->xcode = NULL; + ps->setup_done = 0; + + return 0; +} + +static int +seek(struct player_source *ps, int seek_ms) +{ + return transcode_seek(ps->xcode, seek_ms); +} + +struct input_definition input_file = +{ + .name = "file", + .type = INPUT_TYPE_FILE, + .disabled = 0, + .setup = setup, + .start = start, + .stop = stop, + .seek = seek, +}; + +struct input_definition input_http = +{ + .name = "http", + .type = INPUT_TYPE_HTTP, + .disabled = 0, + .setup = http_setup, + .start = start, + .stop = stop, +}; diff --git a/src/player.c b/src/player.c index 32225ca2..87478dc4 100644 --- a/src/player.c +++ b/src/player.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2010-2011 Julien BLACHE - * Copyright (C) 2016 Espen Jürgensen + * Copyright (C) 2016-2017 Espen Jürgensen * * 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 @@ -15,6 +15,22 @@ * 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 + + + * About player.c + * -------------- + * The main tasks of the player are the following: + * - handle playback commands, status checks and events from other threads + * - receive audio from the input thread and to own the playback buffer + * - feed the outputs at the appropriate rate (controlled by the playback timer) + * - output device handling (partly outsourced to outputs.c) + * - notify about playback status changes + * - maintain the playback queue + * + * The player thread should *never* be making operations that may block, since + * that could block callers requesting status (effectively making forked-daapd + * unresponsive) and it could also starve the outputs. + * */ #ifdef HAVE_CONFIG_H @@ -55,18 +71,13 @@ #include "listener.h" #include "commands.h" -/* Audio outputs */ +/* Audio and metadata outputs */ #include "outputs.h" -/* Audio inputs */ -#include "transcode.h" -#include "pipe.h" -#ifdef HAVE_SPOTIFY_H -# include "spotify.h" -#endif +/* Audio and metadata input */ +#include "input.h" -/* Metadata input/output */ -#include "http.h" +/* Scrobbling */ #ifdef LASTFM # include "lastfm.h" #endif @@ -84,48 +95,6 @@ // Used to keep the player from getting ahead of a rate limited source (see below) #define PLAYER_TICKS_MAX_OVERRUN 2 -struct player_source -{ - /* Id of the file/item in the files database */ - uint32_t id; - - /* Item-Id of the file/item in the queue */ - uint32_t item_id; - - /* Length of the file/item in milliseconds */ - uint32_t len_ms; - - enum data_kind data_kind; - enum media_kind media_kind; - char *path; - - /* Start time of the media item as rtp-time - The stream-start is the rtp-time the media item did or would have - started playing (after seek or pause), therefor the elapsed time of the - media item is always: - elapsed time = current rtptime - stream-start */ - uint64_t stream_start; - - /* Output start time of the media item as rtp-time - The output start time is the rtp-time of the first audio packet send - to the audio outputs. - It differs from stream-start especially after a seek, where the first audio - packet has the next rtp-time as output start and stream start becomes the - rtp-time the media item would have been started playing if the seek did - not happen. */ - uint64_t output_start; - - /* End time of media item as rtp-time - The end time is set if the reading (source_read) of the media item reached - end of file, until then it is 0. */ - uint64_t end; - - struct transcode_ctx *xcode; - int setup_done; - - struct player_source *play_next; -}; - struct volume_param { int volume; uint64_t spk_id; @@ -594,125 +563,11 @@ history_add(uint32_t id, uint32_t item_id) /* Audio sources */ -/* - * Initializes the given player source for playback - */ -static int -stream_setup(struct player_source *ps) -{ - char *url; - int ret; - - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "No player source given to stream_setup\n"); - return -1; - } - - if (ps->setup_done) - { - DPRINTF(E_LOG, L_PLAYER, "Given player source already setup (id = %d)\n", ps->id); - return -1; - } - - // Setup depending on data kind - switch (ps->data_kind) - { - case DATA_KIND_FILE: - ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL); - ret = ps->xcode ? 0 : -1; - break; - - case DATA_KIND_HTTP: - ret = http_stream_setup(&url, ps->path); - if (ret < 0) - break; - - free(ps->path); - ps->path = url; - - ps->xcode = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL); - ret = ps->xcode ? 0 : -1; - break; - - case DATA_KIND_SPOTIFY: -#ifdef HAVE_SPOTIFY_H - ret = spotify_playback_setup(ps->path); -#else - DPRINTF(E_LOG, L_PLAYER, "Player source has data kind 'spotify' (%d), but forked-daapd is compiled without spotify support - cannot setup source '%s'\n", - ps->data_kind, ps->path); - ret = -1; -#endif - break; - - case DATA_KIND_PIPE: - ret = pipe_setup(ps->path); - break; - - default: - DPRINTF(E_LOG, L_PLAYER, "Unknown data kind (%d) for player source - cannot setup source '%s'\n", - ps->data_kind, ps->path); - ret = -1; - } - - if (ret == 0) - ps->setup_done = 1; - else - DPRINTF(E_LOG, L_PLAYER, "Failed to setup player source (id = %d)\n", ps->id); - - return ret; -} - -/* - * Starts or resumes plaback for the given player source - */ -static int -stream_play(struct player_source *ps) -{ - int ret; - - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Stream play called with no active streaming player source\n"); - return -1; - } - - if (!ps->setup_done) - { - DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, play not possible\n"); - return -1; - } - - // Start/resume playback depending on data kind - switch (ps->data_kind) - { - case DATA_KIND_HTTP: - case DATA_KIND_FILE: - ret = 0; - break; - -#ifdef HAVE_SPOTIFY_H - case DATA_KIND_SPOTIFY: - ret = spotify_playback_play(); - break; -#endif - - case DATA_KIND_PIPE: - ret = 0; - break; - - default: - ret = -1; - } - - return ret; -} - /* * Read up to "len" data from the given player source and returns * the actual amount of data read. */ -static int +/*static int stream_read(struct player_source *ps, int len) { int icy_timer; @@ -759,149 +614,7 @@ stream_read(struct player_source *ps, int len) } return ret; -} - -/* - * Pauses playback of the given player source - */ -static int -stream_pause(struct player_source *ps) -{ - int ret; - - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Stream pause called with no active streaming player source\n"); - return -1; - } - - if (!ps->setup_done) - { - DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, pause not possible\n"); - return -1; - } - - // Pause playback depending on data kind - switch (ps->data_kind) - { - case DATA_KIND_HTTP: - ret = 0; - break; - - case DATA_KIND_FILE: - ret = 0; - break; - -#ifdef HAVE_SPOTIFY_H - case DATA_KIND_SPOTIFY: - ret = spotify_playback_pause(); - break; -#endif - - case DATA_KIND_PIPE: - ret = 0; - break; - - default: - ret = -1; - } - - return ret; -} - -/* - * Seeks to the given position in milliseconds of the given player source - */ -static int -stream_seek(struct player_source *ps, int seek_ms) -{ - int ret; - - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Stream seek called with no active streaming player source\n"); - return -1; - } - - if (!ps->setup_done) - { - DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, seek not possible\n"); - return -1; - } - - // Seek depending on data kind - switch (ps->data_kind) - { - case DATA_KIND_HTTP: - ret = 0; - break; - - case DATA_KIND_FILE: - ret = transcode_seek(ps->xcode, seek_ms); - break; - -#ifdef HAVE_SPOTIFY_H - case DATA_KIND_SPOTIFY: - ret = spotify_playback_seek(seek_ms); - break; -#endif - - case DATA_KIND_PIPE: - ret = 0; - break; - - default: - ret = -1; - } - - return ret; -} - -/* - * Stops playback and cleanup for the given player source - */ -static int -stream_stop(struct player_source *ps) -{ - if (!ps) - { - DPRINTF(E_LOG, L_PLAYER, "Stream cleanup called with no active streaming player source\n"); - return -1; - } - - if (!ps->setup_done) - { - DPRINTF(E_LOG, L_PLAYER, "Given player source not setup, cleanup not possible\n"); - return -1; - } - - switch (ps->data_kind) - { - case DATA_KIND_FILE: - case DATA_KIND_HTTP: - if (ps->xcode) - { - transcode_cleanup(ps->xcode); - ps->xcode = NULL; - } - break; - - case DATA_KIND_SPOTIFY: -#ifdef HAVE_SPOTIFY_H - spotify_playback_stop(); -#endif - break; - - case DATA_KIND_PIPE: - pipe_cleanup(); - break; - } - - ps->setup_done = 0; - - return 0; -} - +}*/ static struct player_source * source_now_playing() @@ -959,7 +672,7 @@ source_stop() struct player_source *ps_temp; if (cur_streaming) - stream_stop(cur_streaming); + input_stop(cur_streaming); ps_playing = source_now_playing(); @@ -997,20 +710,20 @@ source_pause(uint64_t pos) if (!ps_playing) return -1; - if (cur_streaming) + if (cur_streaming && (cur_streaming == ps_playing)) { if (ps_playing != cur_streaming) { DPRINTF(E_DBG, L_PLAYER, "Pause called on playing source (id=%d) and streaming source already " "switched to the next item (id=%d)\n", ps_playing->id, cur_streaming->id); - ret = stream_stop(cur_streaming); + ret = input_stop(cur_streaming); if (ret < 0) return -1; } else { - ret = stream_pause(cur_streaming); + ret = input_pause(cur_streaming); if (ret < 0) return -1; } @@ -1034,7 +747,7 @@ source_pause(uint64_t pos) { DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path); - ret = stream_setup(cur_streaming); + ret = input_setup(cur_streaming); if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s'\n", cur_streaming->path); @@ -1045,7 +758,9 @@ source_pause(uint64_t pos) /* Seek back to the pause position */ seek_frames = (pos - cur_streaming->stream_start); seek_ms = (int)((seek_frames * 1000) / 44100); - ret = stream_seek(cur_streaming, seek_ms); + ret = input_seek(cur_streaming, seek_ms); + +// TODO what if ret < 0? /* Adjust start_pos to take into account the pause and seek back */ cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000; @@ -1067,7 +782,7 @@ source_seek(int seek_ms) { int ret; - ret = stream_seek(cur_streaming, seek_ms); + ret = input_seek(cur_streaming, seek_ms); if (ret < 0) return -1; @@ -1086,7 +801,7 @@ source_play() { int ret; - ret = stream_play(cur_streaming); + ret = input_start(cur_streaming); ticks_skip = 0; memset(rawbuf, 0, sizeof(rawbuf)); @@ -1116,7 +831,7 @@ source_open(struct player_source *ps, uint64_t start_pos, int seek_ms) return -1; } - ret = stream_setup(ps); + ret = input_setup(ps); if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s' (id=%d, item-id=%d)\n", ps->path, ps->id, ps->item_id); @@ -1151,7 +866,7 @@ source_open(struct player_source *ps, uint64_t start_pos, int seek_ms) static int source_close(uint64_t end_pos) { - stream_stop(cur_streaming); + input_stop(cur_streaming); cur_streaming->end = end_pos; @@ -1339,6 +1054,7 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) { int ret; int nbytes; + short flags; char *silence_buf; struct player_source *ps; @@ -1350,9 +1066,10 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) { if (evbuffer_get_length(audio_buf) == 0) { + flags = 0; if (cur_streaming) { - ret = stream_read(cur_streaming, len - nbytes); + ret = input_read(audio_buf, len - nbytes, &flags); } else if (cur_playing) { @@ -1369,9 +1086,10 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) return -1; } - if (ret <= 0) +// if (ret == 0) +//TODO Underrun -> pause + if ((ret < 0) || (flags & INPUT_FLAG_EOF)) { - /* EOF or error */ source_close(rtptime + BTOS(nbytes) - 1); DPRINTF(E_DBG, L_PLAYER, "New file\n"); @@ -3224,7 +2942,6 @@ player_playback_prev(void) return ret; } - void player_speaker_enumerate(spk_enum_cb cb, void *arg) { @@ -3539,6 +3256,13 @@ player_init(void) goto outputs_fail; } + ret = input_init(); + if (ret < 0) + { + DPRINTF(E_FATAL, L_PLAYER, "Input initiation failed\n"); + goto input_fail; + } + ret = pthread_create(&tid_player, NULL, player, NULL); if (ret < 0) { @@ -3554,6 +3278,8 @@ player_init(void) return 0; thread_fail: + input_deinit(); + input_fail: outputs_deinit(); outputs_fail: commands_base_free(cmdbase); @@ -3599,6 +3325,7 @@ player_deinit(void) evbuffer_free(audio_buf); + input_deinit(); outputs_deinit(); event_base_free(evbase_player); diff --git a/src/player.h b/src/player.h index 000d3c77..a22f1820 100644 --- a/src/player.h +++ b/src/player.h @@ -13,11 +13,6 @@ /* AirTunes v2 number of samples per packet */ #define AIRTUNES_V2_PACKET_SAMPLES 352 - -/* Samples to bytes, bytes to samples */ -#define STOB(s) ((s) * 4) -#define BTOS(b) ((b) / 4) - /* Maximum number of previously played songs that are remembered */ #define MAX_HISTORY_COUNT 20 @@ -115,7 +110,6 @@ player_playback_next(void); int player_playback_prev(void); - int player_volume_set(int vol);