[input] Add input interface to player - WIP

This commit is contained in:
ejurgensen 2016-12-26 19:30:29 +01:00
parent c50b038397
commit 3e24f857fa
6 changed files with 824 additions and 330 deletions

View File

@ -108,6 +108,8 @@ forked_daapd_SOURCES = main.c \
daap_query.c daap_query.h \ daap_query.c daap_query.h \
player.c player.h \ player.c player.h \
worker.c worker.h \ worker.c worker.h \
input.h input.c \
inputs/file_http.c \
outputs.h outputs.c \ outputs.h outputs.c \
outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \ outputs/raop.c outputs/streaming.c outputs/dummy.c outputs/fifo.c \
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \ $(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \

470
src/input.c Normal file
View File

@ -0,0 +1,470 @@
/*
* Copyright (C) 2017 Espen Jürgensen <espenjurgensen@gmail.com>
*
* 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 <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
# include <pthread_np.h>
#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);
}

176
src/input.h Normal file
View File

@ -0,0 +1,176 @@
#ifndef __INPUT_H__
#define __INPUT_H__
#include <event2/buffer.h>
#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__ */

125
src/inputs/file_http.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <event2/buffer.h>
#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,
};

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2010-2011 Julien BLACHE <jb@jblache.org> * Copyright (C) 2010-2011 Julien BLACHE <jb@jblache.org>
* Copyright (C) 2016 Espen Jürgensen <espenjurgensen@gmail.com> * Copyright (C) 2016-2017 Espen Jürgensen <espenjurgensen@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * 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 * You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * 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 #ifdef HAVE_CONFIG_H
@ -55,18 +71,13 @@
#include "listener.h" #include "listener.h"
#include "commands.h" #include "commands.h"
/* Audio outputs */ /* Audio and metadata outputs */
#include "outputs.h" #include "outputs.h"
/* Audio inputs */ /* Audio and metadata input */
#include "transcode.h" #include "input.h"
#include "pipe.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
/* Metadata input/output */ /* Scrobbling */
#include "http.h"
#ifdef LASTFM #ifdef LASTFM
# include "lastfm.h" # include "lastfm.h"
#endif #endif
@ -84,48 +95,6 @@
// Used to keep the player from getting ahead of a rate limited source (see below) // Used to keep the player from getting ahead of a rate limited source (see below)
#define PLAYER_TICKS_MAX_OVERRUN 2 #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 { struct volume_param {
int volume; int volume;
uint64_t spk_id; uint64_t spk_id;
@ -594,125 +563,11 @@ history_add(uint32_t id, uint32_t item_id)
/* Audio sources */ /* 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 * Read up to "len" data from the given player source and returns
* the actual amount of data read. * the actual amount of data read.
*/ */
static int /*static int
stream_read(struct player_source *ps, int len) stream_read(struct player_source *ps, int len)
{ {
int icy_timer; int icy_timer;
@ -759,149 +614,7 @@ stream_read(struct player_source *ps, int len)
} }
return ret; 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 * static struct player_source *
source_now_playing() source_now_playing()
@ -959,7 +672,7 @@ source_stop()
struct player_source *ps_temp; struct player_source *ps_temp;
if (cur_streaming) if (cur_streaming)
stream_stop(cur_streaming); input_stop(cur_streaming);
ps_playing = source_now_playing(); ps_playing = source_now_playing();
@ -997,20 +710,20 @@ source_pause(uint64_t pos)
if (!ps_playing) if (!ps_playing)
return -1; return -1;
if (cur_streaming) if (cur_streaming && (cur_streaming == ps_playing))
{ {
if (ps_playing != cur_streaming) if (ps_playing != cur_streaming)
{ {
DPRINTF(E_DBG, L_PLAYER, DPRINTF(E_DBG, L_PLAYER,
"Pause called on playing source (id=%d) and streaming source already " "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); "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) if (ret < 0)
return -1; return -1;
} }
else else
{ {
ret = stream_pause(cur_streaming); ret = input_pause(cur_streaming);
if (ret < 0) if (ret < 0)
return -1; return -1;
} }
@ -1034,7 +747,7 @@ source_pause(uint64_t pos)
{ {
DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path); DPRINTF(E_INFO, L_PLAYER, "Opening '%s'\n", cur_streaming->path);
ret = stream_setup(cur_streaming); ret = input_setup(cur_streaming);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_PLAYER, "Failed to open '%s'\n", cur_streaming->path); 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 back to the pause position */
seek_frames = (pos - cur_streaming->stream_start); seek_frames = (pos - cur_streaming->stream_start);
seek_ms = (int)((seek_frames * 1000) / 44100); 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 */ /* 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; 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; int ret;
ret = stream_seek(cur_streaming, seek_ms); ret = input_seek(cur_streaming, seek_ms);
if (ret < 0) if (ret < 0)
return -1; return -1;
@ -1086,7 +801,7 @@ source_play()
{ {
int ret; int ret;
ret = stream_play(cur_streaming); ret = input_start(cur_streaming);
ticks_skip = 0; ticks_skip = 0;
memset(rawbuf, 0, sizeof(rawbuf)); memset(rawbuf, 0, sizeof(rawbuf));
@ -1116,7 +831,7 @@ source_open(struct player_source *ps, uint64_t start_pos, int seek_ms)
return -1; return -1;
} }
ret = stream_setup(ps); ret = input_setup(ps);
if (ret < 0) 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); 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 static int
source_close(uint64_t end_pos) source_close(uint64_t end_pos)
{ {
stream_stop(cur_streaming); input_stop(cur_streaming);
cur_streaming->end = end_pos; cur_streaming->end = end_pos;
@ -1339,6 +1054,7 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
{ {
int ret; int ret;
int nbytes; int nbytes;
short flags;
char *silence_buf; char *silence_buf;
struct player_source *ps; 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) if (evbuffer_get_length(audio_buf) == 0)
{ {
flags = 0;
if (cur_streaming) if (cur_streaming)
{ {
ret = stream_read(cur_streaming, len - nbytes); ret = input_read(audio_buf, len - nbytes, &flags);
} }
else if (cur_playing) else if (cur_playing)
{ {
@ -1369,9 +1086,10 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
return -1; 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); source_close(rtptime + BTOS(nbytes) - 1);
DPRINTF(E_DBG, L_PLAYER, "New file\n"); DPRINTF(E_DBG, L_PLAYER, "New file\n");
@ -3224,7 +2942,6 @@ player_playback_prev(void)
return ret; return ret;
} }
void void
player_speaker_enumerate(spk_enum_cb cb, void *arg) player_speaker_enumerate(spk_enum_cb cb, void *arg)
{ {
@ -3539,6 +3256,13 @@ player_init(void)
goto outputs_fail; 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); ret = pthread_create(&tid_player, NULL, player, NULL);
if (ret < 0) if (ret < 0)
{ {
@ -3554,6 +3278,8 @@ player_init(void)
return 0; return 0;
thread_fail: thread_fail:
input_deinit();
input_fail:
outputs_deinit(); outputs_deinit();
outputs_fail: outputs_fail:
commands_base_free(cmdbase); commands_base_free(cmdbase);
@ -3599,6 +3325,7 @@ player_deinit(void)
evbuffer_free(audio_buf); evbuffer_free(audio_buf);
input_deinit();
outputs_deinit(); outputs_deinit();
event_base_free(evbase_player); event_base_free(evbase_player);

View File

@ -13,11 +13,6 @@
/* AirTunes v2 number of samples per packet */ /* AirTunes v2 number of samples per packet */
#define AIRTUNES_V2_PACKET_SAMPLES 352 #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 */ /* Maximum number of previously played songs that are remembered */
#define MAX_HISTORY_COUNT 20 #define MAX_HISTORY_COUNT 20
@ -115,7 +110,6 @@ player_playback_next(void);
int int
player_playback_prev(void); player_playback_prev(void);
int int
player_volume_set(int vol); player_volume_set(int vol);