From 79639c73ed87cee83b7f1bed268a443fe0153259 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 29 Dec 2016 00:39:23 +0100 Subject: [PATCH] [input] Add Spotify input module --- src/Makefile.am | 2 +- src/input.c | 21 +++++ src/input.h | 15 ++- src/inputs/spotify.c | 96 +++++++++++++++++++ src/player.c | 6 +- src/spotify.c | 220 +++++-------------------------------------- src/spotify.h | 3 - 7 files changed, 161 insertions(+), 202 deletions(-) create mode 100644 src/inputs/spotify.c diff --git a/src/Makefile.am b/src/Makefile.am index faa62afc..ef5b1498 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,7 +6,7 @@ ITUNES_SRC=library/filescanner_itunes.c endif if COND_SPOTIFY -SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h +SPOTIFY_SRC=spotify.c spotify.h spotify_webapi.c spotify_webapi.h inputs/spotify.c endif if COND_LASTFM diff --git a/src/input.c b/src/input.c index c7e3cea6..f037ccce 100644 --- a/src/input.c +++ b/src/input.c @@ -46,11 +46,17 @@ extern struct input_definition input_file; extern struct input_definition input_http; +#ifdef HAVE_SPOTIFY_H +extern struct input_definition input_spotify; +#endif // Must be in sync with enum input_types static struct input_definition *inputs[] = { &input_file, &input_http, +#ifdef HAVE_SPOTIFY_H + &input_spotify, +#endif NULL }; @@ -124,6 +130,11 @@ map_data_kind(int data_kind) case DATA_KIND_HTTP: return INPUT_TYPE_HTTP; +#ifdef HAVE_SPOTIFY_H + case DATA_KIND_SPOTIFY: + return INPUT_TYPE_SPOTIFY; +#endif + default: return -1; } @@ -185,6 +196,15 @@ playback(void *arg) pthread_exit(NULL); } +void +input_wait(void) +{ + pthread_mutex_lock(&input_buffer.mutex); + pthread_cond_wait(&input_buffer.cond, &input_buffer.mutex); + + pthread_mutex_unlock(&input_buffer.mutex); +} + // Called by input modules from within the playback loop int input_write(struct evbuffer *evbuf, short flags) @@ -333,6 +353,7 @@ input_pause(struct player_source *ps) pthread_cond_signal(&input_buffer.cond); pthread_mutex_unlock(&input_buffer.mutex); + // TODO What if input thread is hanging waiting for source? Kill thread? ret = pthread_join(tid_input, NULL); if (ret != 0) { diff --git a/src/input.h b/src/input.h index 4b6f3a57..3190d864 100644 --- a/src/input.h +++ b/src/input.h @@ -2,6 +2,9 @@ #ifndef __INPUT_H__ #define __INPUT_H__ +#ifdef HAVE_CONFIG_H +# include +#endif #include #include "transcode.h" @@ -10,6 +13,9 @@ enum input_types { INPUT_TYPE_FILE, INPUT_TYPE_HTTP, +#ifdef HAVE_SPOTIFY_H + INPUT_TYPE_SPOTIFY, +#endif }; enum input_flags @@ -96,7 +102,7 @@ struct input_definition }; /* - * Input modules use this to test if playback should be stopped or seeked + * Input modules should use this to test if playback should end */ int input_loop_break; @@ -114,6 +120,13 @@ int input_loop_break; int input_write(struct evbuffer *evbuf, short flags); +/* + * Input modules can use this to wait in the playback loop (like input_write() + * would have done) + */ +void +input_wait(void); + /* * 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. diff --git a/src/inputs/spotify.c b/src/inputs/spotify.c new file mode 100644 index 00000000..f4d5cc8a --- /dev/null +++ b/src/inputs/spotify.c @@ -0,0 +1,96 @@ +/* + * 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 "input.h" +#include "spotify.h" + +static int +setup(struct player_source *ps) +{ + int ret; + + ret = spotify_playback_setup(ps->path); + if (ret < 0) + return -1; + + ps->setup_done = 1; + + return 0; +} + +static int +start(struct player_source *ps) +{ + int ret; + + ret = spotify_playback_play(); + if (ret < 0) + return -1; + + while (!input_loop_break) + { + input_wait(); + } + + ret = spotify_playback_pause(); + + return ret; +} + +static int +stop(struct player_source *ps) +{ + int ret; + + ret = spotify_playback_stop(); + if (ret < 0) + return -1; + + ps->setup_done = 0; + + return 0; +} + +static int +seek(struct player_source *ps, int seek_ms) +{ + int ret; + + ret = spotify_playback_seek(seek_ms); + if (ret < 0) + return -1; + + return ret; +} + +struct input_definition input_spotify = +{ + .name = "Spotify", + .type = INPUT_TYPE_SPOTIFY, + .disabled = 0, + .setup = setup, + .start = start, + .stop = stop, + .seek = seek, +}; + diff --git a/src/player.c b/src/player.c index cda99bc5..19f44304 100644 --- a/src/player.c +++ b/src/player.c @@ -1097,9 +1097,9 @@ source_read(uint8_t *buf, int len, uint64_t rtptime) return -1; } -// if (ret == 0) -//TODO Underrun -> pause - if ((ret < 0) || (flags & INPUT_FLAG_EOF)) + if (ret == 0) + sleep(1); // TODO Underrun -> proper pause + else if ((ret < 0) || (flags & INPUT_FLAG_EOF)) { source_close(rtptime + BTOS(nbytes) - 1); diff --git a/src/spotify.c b/src/spotify.c index ce3eba70..8e967380 100644 --- a/src/spotify.c +++ b/src/spotify.c @@ -55,6 +55,7 @@ #include "cache.h" #include "commands.h" #include "library.h" +#include "input.h" /* TODO for the web api: * - UI should be prettier @@ -88,22 +89,6 @@ #define SPOTIFY_WEB_REQUESTS_MAX 20 /* --- Types --- */ -typedef struct audio_fifo_data -{ - TAILQ_ENTRY(audio_fifo_data) link; - int nsamples; - int16_t samples[0]; -} audio_fifo_data_t; - -typedef struct audio_fifo -{ - TAILQ_HEAD(, audio_fifo_data) q; - int qlen; - int fullcount; - pthread_mutex_t mutex; - pthread_cond_t cond; -} audio_fifo_t; - enum spotify_state { SPOTIFY_STATE_INACTIVE, @@ -114,12 +99,6 @@ enum spotify_state SPOTIFY_STATE_STOPPED, }; -struct audio_get_param -{ - struct evbuffer *evbuf; - int wanted; -}; - struct artwork_get_param { struct evbuffer *evbuf; @@ -164,8 +143,8 @@ static int spotify_saved_plid; // Flag to avoid triggering playlist change events while the (re)scan is running static bool scanning; -// Audio fifo -static audio_fifo_t *g_audio_fifo; +// Audio buffer +static struct evbuffer *spotify_audio_buffer; /** * The application key is specific to forked-daapd, and allows Spotify @@ -1180,25 +1159,6 @@ mk_reltime(struct timespec *ts, time_t sec) ts->tv_sec += sec; } -static void -audio_fifo_flush(void) -{ - audio_fifo_data_t *afd; - - DPRINTF(E_DBG, L_SPOTIFY, "Flushing audio fifo\n"); - - CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex)); - - while((afd = TAILQ_FIRST(&g_audio_fifo->q))) { - TAILQ_REMOVE(&g_audio_fifo->q, afd, link); - free(afd); - } - - g_audio_fifo->qlen = 0; - g_audio_fifo->fullcount = 0; - CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex)); -} - static enum command_state playback_setup(void *arg, int *retval) { @@ -1240,8 +1200,6 @@ playback_setup(void *arg, int *retval) return COMMAND_END; } - audio_fifo_flush(); - *retval = 0; return COMMAND_END; } @@ -1330,8 +1288,6 @@ playback_seek(void *arg, int *retval) return COMMAND_END; } - audio_fifo_flush(); - *retval = 0; return COMMAND_END; } @@ -1353,92 +1309,14 @@ playback_eot(void *arg, int *retval) g_state = SPOTIFY_STATE_STOPPING; + // TODO 1) This will block for a while, but perhaps ok? + // 2) spotify_audio_buffer not entirely thread safe here (but unlikely risk...) + input_write(spotify_audio_buffer, INPUT_FLAG_EOF); + *retval = 0; return COMMAND_END; } -static enum command_state -audio_get(void *arg, int *retval) -{ - struct audio_get_param *audio; - struct timespec ts; - audio_fifo_data_t *afd; - int processed; - int timeout; - int ret; - int err; - int s; - - audio = (struct audio_get_param *) arg; - afd = NULL; - processed = 0; - - // If spotify was paused begin by resuming playback - if (g_state == SPOTIFY_STATE_PAUSED) - playback_play(NULL, retval); - - CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex)); - - while ((processed < audio->wanted) && (g_state != SPOTIFY_STATE_STOPPED)) - { - // If track has ended and buffer is empty - if ((g_state == SPOTIFY_STATE_STOPPING) && (g_audio_fifo->qlen <= 0)) - { - DPRINTF(E_DBG, L_SPOTIFY, "Track finished\n"); - g_state = SPOTIFY_STATE_STOPPED; - break; - } - - // If buffer is empty, wait for audio, but use timed wait so we don't - // risk waiting forever (maybe the player stopped while we were waiting) - timeout = 0; - while ( !(afd = TAILQ_FIRST(&g_audio_fifo->q)) && - (g_state != SPOTIFY_STATE_STOPPED) && - (g_state != SPOTIFY_STATE_STOPPING) && - (timeout < SPOTIFY_TIMEOUT) ) - { - DPRINTF(E_DBG, L_SPOTIFY, "Waiting for audio\n"); - timeout += 5; - mk_reltime(&ts, 5); - CHECK_ERR_EXCEPT(L_SPOTIFY, pthread_cond_timedwait(&g_audio_fifo->cond, &g_audio_fifo->mutex, &ts), err, ETIMEDOUT); - } - - if ((!afd) && (timeout >= SPOTIFY_TIMEOUT)) - { - DPRINTF(E_LOG, L_SPOTIFY, "Timeout waiting for audio (waited %d sec)\n", timeout); - - spotify_playback_stop_nonblock(); - } - - if (!afd) - break; - - TAILQ_REMOVE(&g_audio_fifo->q, afd, link); - g_audio_fifo->qlen -= afd->nsamples; - - s = afd->nsamples * sizeof(int16_t) * 2; - - ret = evbuffer_add(audio->evbuf, afd->samples, s); - free(afd); - afd = NULL; - if (ret < 0) - { - DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for evbuffer (tried to add %d bytes)\n", s); - CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex)); - *retval = -1; - return COMMAND_END; - } - - processed += s; - } - - CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex)); - - - *retval = processed; - return COMMAND_END; -} - static void artwork_loaded_cb(sp_image *image, void *userdata) { @@ -1681,8 +1559,8 @@ logged_out(sp_session *sess) static int music_delivery(sp_session *sess, const sp_audioformat *format, const void *frames, int num_frames) { - audio_fifo_data_t *afd; - size_t s; + size_t size; + int ret; /* No support for resampling right now */ if ((format->sample_rate != 44100) || (format->channels != 2)) @@ -1692,44 +1570,26 @@ static int music_delivery(sp_session *sess, const sp_audioformat *format, return num_frames; } + // Audio discontinuity, e.g. seek if (num_frames == 0) - return 0; // Audio discontinuity, do nothing - - CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&g_audio_fifo->mutex)); - - /* Buffer three seconds of audio */ - if (g_audio_fifo->qlen > (3 * format->sample_rate)) { - // If the buffer has been full the last 300 times (~about a minute) we - // assume the player thread paused/died without telling us, so we signal pause - if (g_audio_fifo->fullcount < 300) - g_audio_fifo->fullcount++; - else - { - DPRINTF(E_WARN, L_SPOTIFY, "Buffer full more than 300 times, pausing\n"); - spotify_playback_pause_nonblock(); - g_audio_fifo->fullcount = 0; - } - - CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex)); - + evbuffer_drain(spotify_audio_buffer, evbuffer_get_length(spotify_audio_buffer)); return 0; } - else - g_audio_fifo->fullcount = 0; - s = num_frames * sizeof(int16_t) * format->channels; + size = num_frames * sizeof(int16_t) * format->channels; - afd = malloc(sizeof(*afd) + s); - memcpy(afd->samples, frames, s); + ret = evbuffer_add(spotify_audio_buffer, frames, size); + if (ret < 0) + { + DPRINTF(E_LOG, L_SPOTIFY, "Out of memory adding audio to buffer\n"); + return num_frames; + } - afd->nsamples = num_frames; - - TAILQ_INSERT_TAIL(&g_audio_fifo->q, afd, link); - g_audio_fifo->qlen += num_frames; - - CHECK_ERR(L_SPOTIFY, pthread_cond_signal(&g_audio_fifo->cond)); - CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&g_audio_fifo->mutex)); + // The input buffer only accepts writing when it is approaching depletion, and + // because we use NONBLOCK it will just return if this is not the case. So in + // most cases no actual write is made and spotify_audio_buffer will just grow. + input_write(spotify_audio_buffer, INPUT_FLAG_NONBLOCK); return num_frames; } @@ -1975,18 +1835,6 @@ spotify_playback_seek(int ms) return -1; } -/* Thread: player */ -int -spotify_audio_get(struct evbuffer *evbuf, int wanted) -{ - struct audio_get_param audio; - - audio.evbuf = evbuf; - audio.wanted = wanted; - - return commands_exec_sync(cmdbase, audio_get, NULL, &audio); -} - /* Thread: httpd (artwork) and worker */ int spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h) @@ -2613,7 +2461,6 @@ spotify_init(void) event_add(g_notifyev, NULL); - cmdbase = commands_base_new(evbase_spotify, exit_cb); if (!cmdbase) { @@ -2651,17 +2498,7 @@ spotify_init(void) break; } - /* Prepare audio buffer */ - g_audio_fifo = (audio_fifo_t *)malloc(sizeof(audio_fifo_t)); - if (!g_audio_fifo) - { - DPRINTF(E_LOG, L_SPOTIFY, "Out of memory for audio buffer\n"); - goto audio_fifo_fail; - } - TAILQ_INIT(&g_audio_fifo->q); - g_audio_fifo->qlen = 0; - CHECK_ERR(L_SPOTIFY, mutex_init(&g_audio_fifo->mutex)); - CHECK_ERR(L_SPOTIFY, pthread_cond_init(&g_audio_fifo->cond, NULL)); + spotify_audio_buffer = evbuffer_new(); CHECK_ERR(L_SPOTIFY, mutex_init(&login_lck)); CHECK_ERR(L_SPOTIFY, pthread_cond_init(&login_cond, NULL)); @@ -2687,11 +2524,8 @@ spotify_init(void) CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond)); CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck)); - CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&g_audio_fifo->cond)); - CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&g_audio_fifo->mutex)); - free(g_audio_fifo); + evbuffer_free(spotify_audio_buffer); - audio_fifo_fail: fptr_sp_session_release(g_sess); g_sess = NULL; @@ -2751,10 +2585,8 @@ spotify_deinit(void) CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&login_cond)); CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&login_lck)); - /* Clear audio fifo */ - CHECK_ERR(L_SPOTIFY, pthread_cond_destroy(&g_audio_fifo->cond)); - CHECK_ERR(L_SPOTIFY, pthread_mutex_destroy(&g_audio_fifo->mutex)); - free(g_audio_fifo); + /* Free audio buffer */ + evbuffer_free(spotify_audio_buffer); /* Release libspotify handle */ dlclose(g_libhandle); diff --git a/src/spotify.h b/src/spotify.h index 7b4b6f9d..ca028e14 100644 --- a/src/spotify.h +++ b/src/spotify.h @@ -27,9 +27,6 @@ spotify_playback_stop_nonblock(void); int spotify_playback_seek(int ms); -int -spotify_audio_get(struct evbuffer *evbuf, int wanted); - int spotify_artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h);