[player/outputs] Implement changed output interfaces in most backends

Still missing cast, alsa and pulseaudio, but these can so far just be
disabled with configure.

Otherwise still mostly untested.
This commit is contained in:
ejurgensen 2019-02-10 23:27:29 +01:00
parent 14a6d318f0
commit e99f20992e
12 changed files with 429 additions and 529 deletions

View File

@ -44,10 +44,8 @@ extern struct event_base *evbase_httpd;
// Seconds between sending silence when player is idle
// (to prevent client from hanging up)
#define STREAMING_SILENCE_INTERVAL 1
// How many samples we store in the buffer used for transmitting from player to httpd thread
#define STREAMING_RAWBUF_SAMPLES 352
// Buffer size
#define STREAMING_RAWBUF_SIZE (STOB(STREAMING_RAWBUF_SAMPLES, 16, 2))
// How many bytes we try to read at a time from the httpd pipe
#define STREAMING_READ_SIZE STOB(352, 16, 2)
// Linked list of mp3 streaming requests
struct streaming_session {
@ -56,23 +54,24 @@ struct streaming_session {
};
static struct streaming_session *streaming_sessions;
static int streaming_initialized;
// Means we're not able to encode to mp3
static bool streaming_not_supported;
// Buffers and interval for sending silence when playback is paused
static uint8_t *streaming_silence_data;
static size_t streaming_silence_size;
// Interval for sending silence when playback is paused
static struct timeval streaming_silence_tv = { STREAMING_SILENCE_INTERVAL, 0 };
// Input buffer, output buffer and encoding ctx for transcode
static uint8_t streaming_rawbuf[STREAMING_RAWBUF_SIZE];
static struct encode_ctx *streaming_encode_ctx;
static struct evbuffer *streaming_encoded_data;
static struct media_quality streaming_quality;
// Used for pushing events and data from the player
static struct event *streamingev;
static struct event *metaev;
static struct player_status streaming_player_status;
static int streaming_player_changed;
static int streaming_pipe[2];
static int streaming_meta[2];
static void
@ -116,12 +115,77 @@ streaming_fail_cb(struct evhttp_connection *evcon, void *arg)
}
}
static void
streaming_meta_cb(evutil_socket_t fd, short event, void *arg)
{
struct media_quality quality;
struct decode_ctx *decode_ctx;
int ret;
ret = read(fd, &quality, sizeof(struct media_quality));
if (ret != sizeof(struct media_quality))
goto error;
streaming_quality = quality;
decode_ctx = NULL;
if (quality.sample_rate == 44100 && quality.bits_per_sample == 16)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16_44100);
else if (quality.sample_rate == 44100 && quality.bits_per_sample == 24)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM24_44100);
else if (quality.sample_rate == 48000 && quality.bits_per_sample == 16)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16_48000);
else if (quality.sample_rate == 48000 && quality.bits_per_sample == 24)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM24_48000);
if (!decode_ctx)
goto error;
streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL, 0, 0);
transcode_decode_cleanup(&decode_ctx);
if (!streaming_encode_ctx)
{
DPRINTF(E_LOG, L_STREAMING, "Will not be able to stream MP3, libav does not support MP3 encoding\n");
streaming_not_supported = 1;
return;
}
streaming_not_supported = 0;
error:
DPRINTF(E_LOG, L_STREAMING, "Unknown or unsupported quality of input data, cannot MP3 encode\n");
transcode_encode_cleanup(&streaming_encode_ctx);
streaming_not_supported = 1;
}
static int
encode_buffer(uint8_t *buffer, size_t size)
{
transcode_frame *frame;
int samples;
int ret;
samples = BTOS(size, streaming_quality.bits_per_sample, streaming_quality.channels);
frame = transcode_frame_new(buffer, size, samples, streaming_quality.sample_rate, streaming_quality.bits_per_sample);
if (!frame)
{
DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
return -1;
}
ret = transcode_encode(streaming_encoded_data, streaming_encode_ctx, frame, 0);
transcode_frame_free(frame);
return ret;
}
static void
streaming_send_cb(evutil_socket_t fd, short event, void *arg)
{
struct streaming_session *session;
struct evbuffer *evbuf;
void *frame;
uint8_t rawbuf[STREAMING_READ_SIZE];
uint8_t *buf;
int len;
int ret;
@ -129,24 +193,16 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
// Player wrote data to the pipe (EV_READ)
if (event & EV_READ)
{
ret = read(streaming_pipe[0], &streaming_rawbuf, STREAMING_RAWBUF_SIZE);
if (ret < 0)
return;
if (!streaming_sessions)
return;
frame = transcode_frame_new(streaming_rawbuf, STREAMING_RAWBUF_SIZE, STREAMING_RAWBUF_SAMPLES, 44100, 16);
if (!frame)
while (1)
{
DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
return;
}
ret = read(fd, &rawbuf, sizeof(rawbuf));
if (ret <= 0)
break;
ret = transcode_encode(streaming_encoded_data, streaming_encode_ctx, frame, 0);
transcode_frame_free(frame);
if (ret < 0)
return;
ret = encode_buffer(rawbuf, ret);
if (ret < 0)
return;
}
}
// Event timed out, let's see what the player is doing and send silence if it is paused
else
@ -157,16 +213,18 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
player_get_status(&streaming_player_status);
}
if (!streaming_sessions)
return;
if (streaming_player_status.status != PLAY_PAUSED)
return;
evbuffer_add(streaming_encoded_data, streaming_silence_data, streaming_silence_size);
memset(&rawbuf, 0, sizeof(rawbuf));
ret = encode_buffer(rawbuf, sizeof(rawbuf));
if (ret < 0)
return;
}
len = evbuffer_get_length(streaming_encoded_data);
if (len == 0)
return;
// Send data
evbuf = evbuffer_new();
@ -181,6 +239,7 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
else
evhttp_send_reply_chunk(session->req, streaming_encoded_data);
}
evbuffer_free(evbuf);
}
@ -193,14 +252,24 @@ player_change_cb(short event_mask)
// Thread: player (also prone to race conditions, mostly during deinit)
void
streaming_write(uint8_t *buf, uint64_t rtptime)
streaming_write(struct output_buffer *obuf)
{
int ret;
if (!streaming_sessions)
return;
ret = write(streaming_pipe[1], buf, STREAMING_RAWBUF_SIZE);
if (!quality_is_equal(&obuf->frames[0].quality, &streaming_quality))
{
ret = write(streaming_meta[1], &obuf->frames[0].quality, sizeof(struct media_quality));
if (ret < 0)
{
DPRINTF(E_LOG, L_STREAMING, "Error writing to streaming pipe: %s\n", strerror(errno));
return;
}
}
ret = write(streaming_pipe[1], obuf->frames[0].buffer, obuf->frames[0].bufsize);
if (ret < 0)
{
if (errno == EAGAIN)
@ -221,9 +290,9 @@ streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parse
char *address;
ev_uint16_t port;
if (!streaming_initialized)
if (!streaming_not_supported)
{
DPRINTF(E_LOG, L_STREAMING, "Got mp3 streaming request, but cannot encode to mp3\n");
DPRINTF(E_LOG, L_STREAMING, "Got MP3 streaming request, but cannot encode to MP3\n");
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
return -1;
@ -286,26 +355,8 @@ streaming_is_request(const char *path)
int
streaming_init(void)
{
struct decode_ctx *decode_ctx;
void *frame;
int remaining;
int ret;
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16_44100);
if (!decode_ctx)
{
DPRINTF(E_LOG, L_STREAMING, "Could not create decoding context\n");
return -1;
}
streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL, 0, 0);
transcode_decode_cleanup(&decode_ctx);
if (!streaming_encode_ctx)
{
DPRINTF(E_LOG, L_STREAMING, "Will not be able to stream mp3, libav does not support mp3 encoding\n");
return -1;
}
// Non-blocking because otherwise httpd and player thread may deadlock
#ifdef HAVE_PIPE2
ret = pipe2(streaming_pipe, O_CLOEXEC | O_NONBLOCK);
@ -320,7 +371,23 @@ streaming_init(void)
if (ret < 0)
{
DPRINTF(E_FATAL, L_STREAMING, "Could not create pipe: %s\n", strerror(errno));
goto pipe_fail;
goto error;
}
#ifdef HAVE_PIPE2
ret = pipe2(streaming_meta, O_CLOEXEC | O_NONBLOCK);
#else
if ( pipe(streaming_meta) < 0 ||
fcntl(streaming_meta[0], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 ||
fcntl(streaming_meta[1], F_SETFL, O_CLOEXEC | O_NONBLOCK) < 0 )
ret = -1;
else
ret = 0;
#endif
if (ret < 0)
{
DPRINTF(E_FATAL, L_STREAMING, "Could not create pipe: %s\n", strerror(errno));
goto error;
}
// Listen to playback changes so we don't have to poll to check for pausing
@ -328,77 +395,22 @@ streaming_init(void)
if (ret < 0)
{
DPRINTF(E_FATAL, L_STREAMING, "Could not add listener\n");
goto listener_fail;
goto error;
}
// Initialize buffer for encoded mp3 audio and event for pipe reading
streaming_encoded_data = evbuffer_new();
streamingev = event_new(evbase_httpd, streaming_pipe[0], EV_TIMEOUT | EV_READ | EV_PERSIST, streaming_send_cb, NULL);
if (!streaming_encoded_data || !streamingev)
{
DPRINTF(E_LOG, L_STREAMING, "Out of memory for encoded_data or event\n");
goto event_fail;
}
CHECK_NULL(L_STREAMING, streaming_encoded_data = evbuffer_new());
// Encode some silence which will be used for playback pause and put in a permanent buffer
remaining = STREAMING_SILENCE_INTERVAL * STOB(44100, 16, 2);
while (remaining > STREAMING_RAWBUF_SIZE)
{
frame = transcode_frame_new(streaming_rawbuf, STREAMING_RAWBUF_SIZE, STREAMING_RAWBUF_SAMPLES, 44100, 16);
if (!frame)
{
DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
goto silence_fail;
}
ret = transcode_encode(streaming_encoded_data, streaming_encode_ctx, frame, 0);
transcode_frame_free(frame);
if (ret < 0)
{
DPRINTF(E_LOG, L_STREAMING, "Could not encode silence buffer\n");
goto silence_fail;
}
remaining -= STREAMING_RAWBUF_SIZE;
}
streaming_silence_size = evbuffer_get_length(streaming_encoded_data);
if (streaming_silence_size == 0)
{
DPRINTF(E_LOG, L_STREAMING, "The encoder didn't encode any silence\n");
goto silence_fail;
}
streaming_silence_data = malloc(streaming_silence_size);
if (!streaming_silence_data)
{
DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming_silence_data\n");
goto silence_fail;
}
ret = evbuffer_remove(streaming_encoded_data, streaming_silence_data, streaming_silence_size);
if (ret != streaming_silence_size)
{
DPRINTF(E_LOG, L_STREAMING, "Unknown error while copying silence buffer\n");
free(streaming_silence_data);
goto silence_fail;
}
// All done
streaming_initialized = 1;
CHECK_NULL(L_STREAMING, streamingev = event_new(evbase_httpd, streaming_pipe[0], EV_TIMEOUT | EV_READ | EV_PERSIST, streaming_send_cb, NULL));
CHECK_NULL(L_STREAMING, metaev = event_new(evbase_httpd, streaming_meta[0], EV_READ | EV_PERSIST, streaming_meta_cb, NULL));
return 0;
silence_fail:
event_free(streamingev);
evbuffer_free(streaming_encoded_data);
event_fail:
listener_remove(player_change_cb);
listener_fail:
error:
close(streaming_pipe[0]);
close(streaming_pipe[1]);
pipe_fail:
transcode_encode_cleanup(&streaming_encode_ctx);
close(streaming_meta[0]);
close(streaming_meta[1]);
return -1;
}
@ -409,9 +421,6 @@ streaming_deinit(void)
struct streaming_session *session;
struct streaming_session *next;
if (!streaming_initialized)
return;
session = streaming_sessions;
streaming_sessions = NULL; // Stops writing and sending
@ -430,8 +439,9 @@ streaming_deinit(void)
close(streaming_pipe[0]);
close(streaming_pipe[1]);
close(streaming_meta[0]);
close(streaming_meta[1]);
transcode_encode_cleanup(&streaming_encode_ctx);
evbuffer_free(streaming_encoded_data);
free(streaming_silence_data);
}

View File

@ -3,6 +3,7 @@
#define __HTTPD_STREAMING_H__
#include "httpd.h"
#include "outputs.h"
/* httpd_streaming takes care of incoming requests to /stream.mp3
* It will receive decoded audio from the player, and encode it, and
@ -11,7 +12,7 @@
*/
void
streaming_write(uint8_t *buf, uint64_t rtptime);
streaming_write(struct output_buffer *obuf);
int
streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parsed);

View File

@ -105,9 +105,7 @@ struct alsa_session
struct event *deferredev;
output_status_cb defer_cb;
/* Do not dereference - only passed to the status cb */
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
struct alsa_session *next;
@ -147,7 +145,6 @@ alsa_session_free(struct alsa_session *as)
prebuf_free(as);
free(as->output_session);
free(as);
}
@ -169,6 +166,8 @@ alsa_session_cleanup(struct alsa_session *as)
s->next = as->next;
}
as->device->session = NULL;
alsa_session_free(as);
}
@ -177,21 +176,7 @@ alsa_session_make(struct output_device *device, output_status_cb cb)
{
struct alsa_session *as;
as = calloc(1, sizeof(struct alsa_session));
if (!as)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session (as)\n");
return NULL;
}
as->output_session = calloc(1, sizeof(struct output_session));
if (!as->output_session)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session (output_session)\n");
goto failure_cleanup;
}
as->output_session->session = as;
as->output_session->type = device->type;
CHECK_NULL(L_LAUDIO, as = calloc(1, sizeof(struct alsa_session)));
as->deferredev = evtimer_new(evbase_player, defer_cb, as);
if (!as->deferredev)
@ -210,6 +195,9 @@ alsa_session_make(struct output_device *device, output_status_cb cb)
as->next = sessions;
sessions = as;
device->session = as;
return as;
failure_cleanup:

View File

@ -177,9 +177,7 @@ struct cast_session
char *session_id;
int media_session_id;
/* Do not dereference - only passed to the status cb */
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
struct cast_session *next;
@ -1327,7 +1325,6 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha
static struct cast_session *
cast_session_make(struct output_device *device, int family, output_status_cb cb)
{
struct output_session *os;
struct cast_session *cs;
const char *proto;
const char *err;
@ -1359,25 +1356,8 @@ cast_session_make(struct output_device *device, int family, output_status_cb cb)
return NULL;
}
os = calloc(1, sizeof(struct output_session));
if (!os)
{
DPRINTF(E_LOG, L_CAST, "Out of memory (os)\n");
return NULL;
}
CHECK_NULL(L_CAST, cs = calloc(1, sizeof(struct cast_session)));
cs = calloc(1, sizeof(struct cast_session));
if (!cs)
{
DPRINTF(E_LOG, L_CAST, "Out of memory (cs)\n");
free(os);
return NULL;
}
os->session = cs;
os->type = device->type;
cs->output_session = os;
cs->state = CAST_STATE_DISCONNECTED;
cs->device = device;
cs->status_cb = cb;
@ -1440,6 +1420,8 @@ cast_session_make(struct output_device *device, int family, output_status_cb cb)
cs->next = sessions;
sessions = cs;
device->session = cs;
proto = gnutls_protocol_get_name(gnutls_protocol_get_version(cs->tls_session));
DPRINTF(E_INFO, L_CAST, "Connection to '%s' established using %s\n", cs->devname, proto);

View File

@ -47,9 +47,7 @@ struct dummy_session
struct event *deferredev;
output_status_cb defer_cb;
/* Do not dereference - only passed to the status cb */
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
};
@ -72,7 +70,6 @@ dummy_session_free(struct dummy_session *ds)
event_free(ds->deferredev);
free(ds->output_session);
free(ds);
}
@ -82,27 +79,20 @@ dummy_session_cleanup(struct dummy_session *ds)
// Normally some here code to remove from linked list - here we just say:
sessions = NULL;
ds->device->session = NULL;
dummy_session_free(ds);
}
static struct dummy_session *
dummy_session_make(struct output_device *device, output_status_cb cb)
{
struct output_session *os;
struct dummy_session *ds;
os = calloc(1, sizeof(struct output_session));
if (!os)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy session (os)\n");
return NULL;
}
ds = calloc(1, sizeof(struct dummy_session));
if (!ds)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy session (as)\n");
free(os);
return NULL;
}
@ -110,21 +100,18 @@ dummy_session_make(struct output_device *device, output_status_cb cb)
if (!ds->deferredev)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy deferred event\n");
free(os);
free(ds);
return NULL;
}
os->session = ds;
os->type = device->type;
ds->output_session = os;
ds->state = OUTPUT_STATE_CONNECTED;
ds->device = device;
ds->status_cb = cb;
sessions = ds;
device->session = ds;
return ds;
}
@ -139,7 +126,7 @@ defer_cb(int fd, short what, void *arg)
struct dummy_session *ds = arg;
if (ds->defer_cb)
ds->defer_cb(ds->device, ds->output_session, ds->state);
ds->defer_cb(ds->device, ds->state);
if (ds->state == OUTPUT_STATE_STOPPED)
dummy_session_cleanup(ds);
@ -157,7 +144,7 @@ dummy_status(struct dummy_session *ds)
/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */
static int
dummy_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime)
dummy_device_start(struct output_device *device, output_status_cb cb)
{
struct dummy_session *ds;
@ -170,13 +157,15 @@ dummy_device_start(struct output_device *device, output_status_cb cb, uint64_t r
return 0;
}
static void
dummy_device_stop(struct output_session *session)
static int
dummy_device_stop(struct output_device *device, output_status_cb cb)
{
struct dummy_session *ds = session->session;
struct dummy_session *ds = device->session;
ds->state = OUTPUT_STATE_STOPPED;
dummy_status(ds);
return 0;
}
static int
@ -199,13 +188,11 @@ dummy_device_probe(struct output_device *device, output_status_cb cb)
static int
dummy_device_volume_set(struct output_device *device, output_status_cb cb)
{
struct dummy_session *ds;
struct dummy_session *ds = device->session;
if (!device->session || !device->session->session)
if (!ds)
return 0;
ds = device->session->session;
ds->status_cb = cb;
dummy_status(ds);
@ -213,15 +200,11 @@ dummy_device_volume_set(struct output_device *device, output_status_cb cb)
}
static void
dummy_playback_start(uint64_t next_pkt, struct timespec *ts)
dummy_device_set_cb(struct output_device *device, output_status_cb cb)
{
struct dummy_session *ds = sessions;
struct dummy_session *ds = device->session;
if (!sessions)
return;
ds->state = OUTPUT_STATE_STREAMING;
dummy_status(ds);
ds->status_cb = cb;
}
static void
@ -236,14 +219,6 @@ dummy_playback_stop(void)
dummy_status(ds);
}
static void
dummy_set_status_cb(struct output_session *session, output_status_cb cb)
{
struct dummy_session *ds = session->session;
ds->status_cb = cb;
}
static int
dummy_init(void)
{
@ -298,7 +273,6 @@ struct output_definition output_dummy =
.device_stop = dummy_device_stop,
.device_probe = dummy_device_probe,
.device_volume_set = dummy_device_volume_set,
.playback_start = dummy_playback_start,
.device_set_cb = dummy_device_set_cb,
.playback_stop = dummy_playback_stop,
.status_cb = dummy_set_status_cb,
};

View File

@ -45,10 +45,11 @@
struct fifo_packet
{
/* pcm data */
uint8_t samples[FIFO_PACKET_SIZE];
uint8_t *samples;
size_t samples_size;
/* RTP-time of the first sample*/
uint64_t rtptime;
/* Presentation timestamp of the first sample */
struct timespec pts;
struct fifo_packet *next;
struct fifo_packet *prev;
@ -59,8 +60,11 @@ struct fifo_buffer
struct fifo_packet *head;
struct fifo_packet *tail;
};
static struct fifo_buffer buffer;
static struct media_quality fifo_quality = { 44100, 16, 2 };
static void
free_buffer()
@ -94,9 +98,7 @@ struct fifo_session
struct event *deferredev;
output_status_cb defer_cb;
/* Do not dereference - only passed to the status cb */
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
};
@ -242,7 +244,6 @@ fifo_session_free(struct fifo_session *fifo_session)
event_free(fifo_session->deferredev);
free(fifo_session->output_session);
free(fifo_session);
free_buffer();
}
@ -253,43 +254,26 @@ fifo_session_cleanup(struct fifo_session *fifo_session)
// Normally some here code to remove from linked list - here we just say:
sessions = NULL;
fifo_session->device->session = NULL;
fifo_session_free(fifo_session);
}
static struct fifo_session *
fifo_session_make(struct output_device *device, output_status_cb cb)
{
struct output_session *output_session;
struct fifo_session *fifo_session;
output_session = calloc(1, sizeof(struct output_session));
if (!output_session)
{
DPRINTF(E_LOG, L_FIFO, "Out of memory (os)\n");
return NULL;
}
fifo_session = calloc(1, sizeof(struct fifo_session));
if (!fifo_session)
{
DPRINTF(E_LOG, L_FIFO, "Out of memory (fs)\n");
free(output_session);
return NULL;
}
CHECK_NULL(L_FIFO, fifo_session = calloc(1, sizeof(struct fifo_session)));
fifo_session->deferredev = evtimer_new(evbase_player, defer_cb, fifo_session);
if (!fifo_session->deferredev)
{
DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo deferred event\n");
free(output_session);
free(fifo_session);
return NULL;
}
output_session->session = fifo_session;
output_session->type = device->type;
fifo_session->output_session = output_session;
fifo_session->state = OUTPUT_STATE_CONNECTED;
fifo_session->device = device;
fifo_session->status_cb = cb;
@ -301,6 +285,8 @@ fifo_session_make(struct output_device *device, output_status_cb cb)
sessions = fifo_session;
device->session = fifo_session;
return fifo_session;
}
@ -315,7 +301,7 @@ defer_cb(int fd, short what, void *arg)
struct fifo_session *ds = arg;
if (ds->defer_cb)
ds->defer_cb(ds->device, ds->output_session, ds->state);
ds->defer_cb(ds->device, ds->state);
if (ds->state == OUTPUT_STATE_STOPPED)
fifo_session_cleanup(ds);
@ -333,11 +319,15 @@ fifo_status(struct fifo_session *fifo_session)
/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */
static int
fifo_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime)
fifo_device_start(struct output_device *device, output_status_cb cb)
{
struct fifo_session *fifo_session;
int ret;
ret = outputs_quality_subscribe(&fifo_quality);
if (ret < 0)
return -1;
fifo_session = fifo_session_make(device, cb);
if (!fifo_session)
return -1;
@ -351,16 +341,22 @@ fifo_device_start(struct output_device *device, output_status_cb cb, uint64_t rt
return 0;
}
static void
fifo_device_stop(struct output_session *output_session)
static int
fifo_device_stop(struct output_device *device, output_status_cb cb)
{
struct fifo_session *fifo_session = output_session->session;
struct fifo_session *fifo_session = device->session;
outputs_quality_unsubscribe(&fifo_quality);
fifo_session->status_cb = cb;
fifo_close(fifo_session);
free_buffer();
fifo_session->state = OUTPUT_STATE_STOPPED;
fifo_status(fifo_session);
return 0;
}
static int
@ -393,13 +389,11 @@ fifo_device_probe(struct output_device *device, output_status_cb cb)
static int
fifo_device_volume_set(struct output_device *device, output_status_cb cb)
{
struct fifo_session *fifo_session;
struct fifo_session *fifo_session = device->session;
if (!device->session || !device->session->session)
if (!fifo_session)
return 0;
fifo_session = device->session->session;
fifo_session->status_cb = cb;
fifo_status(fifo_session);
@ -407,15 +401,11 @@ fifo_device_volume_set(struct output_device *device, output_status_cb cb)
}
static void
fifo_playback_start(uint64_t next_pkt, struct timespec *ts)
fifo_device_set_cb(struct output_device *device, output_status_cb cb)
{
struct fifo_session *fifo_session = sessions;
struct fifo_session *fifo_session = device->session;
if (!fifo_session)
return;
fifo_session->state = OUTPUT_STATE_STREAMING;
fifo_status(fifo_session);
fifo_session->status_cb = cb;
}
static void
@ -433,7 +423,7 @@ fifo_playback_stop(void)
}
static int
fifo_flush(output_status_cb cb, uint64_t rtptime)
fifo_flush(output_status_cb cb)
{
struct fifo_session *fifo_session = sessions;
@ -450,45 +440,59 @@ fifo_flush(output_status_cb cb, uint64_t rtptime)
}
static void
fifo_write(uint8_t *buf, uint64_t rtptime)
fifo_write(struct output_buffer *obuf)
{
struct fifo_session *fifo_session = sessions;
size_t length = FIFO_PACKET_SIZE;
ssize_t bytes;
struct fifo_packet *packet;
uint64_t cur_pos;
struct timespec now;
int ret;
ssize_t bytes;
int i;
if (!fifo_session || !fifo_session->device->selected)
return;
packet = (struct fifo_packet *) calloc(1, sizeof(struct fifo_packet));
memcpy(packet->samples, buf, sizeof(packet->samples));
packet->rtptime = rtptime;
for (i = 0; obuf->frames[i].buffer; i++)
{
if (quality_is_equal(&fifo_quality, &obuf->frames[i].quality))
break;
}
if (!obuf->frames[i].buffer)
{
DPRINTF(E_LOG, L_FIFO, "Bug! Did not get audio in quality required\n");
return;
}
fifo_session->state = OUTPUT_STATE_STREAMING;
CHECK_NULL(L_FIFO, packet = calloc(1, sizeof(struct fifo_packet)));
CHECK_NULL(L_FIFO, packet->samples = malloc(obuf->frames[i].bufsize));
memcpy(packet->samples, obuf->frames[i].buffer, obuf->frames[i].bufsize);
packet->samples_size = obuf->frames[i].bufsize;
packet->pts = obuf->pts;
if (buffer.head)
{
buffer.head->next = packet;
packet->prev = buffer.head;
}
buffer.head = packet;
if (!buffer.tail)
buffer.tail = packet;
ret = player_get_current_pos(&cur_pos, &now, 0);
if (ret < 0)
{
DPRINTF(E_LOG, L_FIFO, "Could not get playback position\n");
return;
}
now.tv_sec = obuf->pts.tv_sec - OUTPUTS_BUFFER_DURATION;
now.tv_nsec = obuf->pts.tv_sec;
while (buffer.tail && buffer.tail->rtptime <= cur_pos)
while (buffer.tail && (timespec_cmp(buffer.tail->pts, now) == -1))
{
bytes = write(fifo_session->output_fd, buffer.tail->samples, length);
bytes = write(fifo_session->output_fd, buffer.tail->samples, buffer.tail->samples_size);
if (bytes > 0)
{
packet = buffer.tail;
buffer.tail = buffer.tail->next;
free(packet->samples);
free(packet);
return;
}
@ -511,14 +515,6 @@ fifo_write(uint8_t *buf, uint64_t rtptime)
}
}
static void
fifo_set_status_cb(struct output_session *session, output_status_cb cb)
{
struct fifo_session *fifo_session = session->session;
fifo_session->status_cb = cb;
}
static int
fifo_init(void)
{
@ -578,9 +574,8 @@ struct output_definition output_fifo =
.device_stop = fifo_device_stop,
.device_probe = fifo_device_probe,
.device_volume_set = fifo_device_volume_set,
.playback_start = fifo_playback_start,
.device_set_cb = fifo_device_set_cb,
.playback_stop = fifo_playback_stop,
.write = fifo_write,
.flush = fifo_flush,
.status_cb = fifo_set_status_cb,
};

View File

@ -70,9 +70,7 @@ struct pulse_session
char *devname;
/* Do not dereference - only passed to the status cb */
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
struct pulse_session *next;
@ -141,34 +139,18 @@ pulse_session_cleanup(struct pulse_session *ps)
p->next = ps->next;
}
ps->device->session = NULL;
pulse_session_free(ps);
}
static struct pulse_session *
pulse_session_make(struct output_device *device, output_status_cb cb)
{
struct output_session *os;
struct pulse_session *ps;
os = calloc(1, sizeof(struct output_session));
if (!os)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory (os)\n");
return NULL;
}
CHECK_NULL(L_LAUDIO, ps = calloc(1, sizeof(struct pulse_session)));
ps = calloc(1, sizeof(struct pulse_session));
if (!ps)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory (ps)\n");
free(os);
return NULL;
}
os->session = ps;
os->type = device->type;
ps->output_session = os;
ps->state = PA_STREAM_UNCONNECTED;
ps->device = device;
ps->status_cb = cb;
@ -178,6 +160,8 @@ pulse_session_make(struct output_device *device, output_status_cb cb)
ps->next = sessions;
sessions = ps;
device->session = ps;
return ps;
}

View File

@ -80,14 +80,6 @@
#include "raop_verification.h"
#endif
// AirTunes v2 packet interval in ns */
// (352 samples/packet * 1e9 ns/s) / 44100 samples/s = 7981859 ns/packet
// #define AIRTUNES_V2_STREAM_PERIOD 7981859
#ifndef MIN
# define MIN(a, b) ((a < b) ? a : b)
#endif
#define ALAC_HEADER_LEN 3
#define RAOP_QUALITY_SAMPLE_RATE_DEFAULT 44100
@ -220,9 +212,7 @@ struct raop_session
int volume;
uint64_t start_rtptime;
/* Do not dereference - only passed to the status cb */
struct output_device *device;
struct output_session *output_session;
output_status_cb status_cb;
/* AirTunes v2 */
@ -320,6 +310,14 @@ static const char *raop_devtype[] =
"Other",
};
/* Struct with default quality levels */
static struct media_quality raop_quality_default =
{
RAOP_QUALITY_SAMPLE_RATE_DEFAULT,
RAOP_QUALITY_BITS_PER_SAMPLE_DEFAULT,
RAOP_QUALITY_CHANNELS_DEFAULT
};
/* From player.c */
extern struct event_base *evbase_player;
@ -355,16 +353,10 @@ static struct timeval keep_alive_tv = { 30, 0 };
static struct raop_master_session *raop_master_sessions;
static struct raop_session *raop_sessions;
/* Struct with default quality levels */
static struct media_quality raop_quality_default = { RAOP_QUALITY_SAMPLE_RATE_DEFAULT, RAOP_QUALITY_BITS_PER_SAMPLE_DEFAULT, RAOP_QUALITY_CHANNELS_DEFAULT };
// Forwards
static int
raop_device_start(struct output_device *rd, output_status_cb cb);
static void
raop_device_stop(struct output_session *session);
/* ------------------------------- MISC HELPERS ----------------------------- */
@ -1763,7 +1755,7 @@ raop_status(struct raop_session *rs)
rs->status_cb = NULL;
if (status_cb)
status_cb(rs->device, rs->output_session, state);
status_cb(rs->device, state);
if (rs->state == RAOP_STATE_UNVERIFIED)
player_speaker_status_trigger();
@ -1882,8 +1874,6 @@ session_free(struct raop_session *rs)
if (rs->devname)
free(rs->devname);
free(rs->output_session);
free(rs);
}
@ -1905,6 +1895,8 @@ session_cleanup(struct raop_session *rs)
s->next = rs->next;
}
rs->device->session = NULL;
session_free(rs);
}
@ -1920,14 +1912,6 @@ session_failure(struct raop_session *rs)
session_cleanup(rs);
}
static void
session_failure_cb(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
session_failure(rs);
}
static void
deferredev_cb(int fd, short what, void *arg)
{
@ -1939,23 +1923,67 @@ deferredev_cb(int fd, short what, void *arg)
}
static void
raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg)
deferred_session_failure(struct raop_session *rs)
{
struct raop_session *rs = arg;
struct timeval tv;
DPRINTF(E_LOG, L_RAOP, "Device '%s' closed RTSP connection\n", rs->devname);
rs->state = RAOP_STATE_FAILED;
evutil_timerclear(&tv);
evtimer_add(rs->deferredev, &tv);
}
static void
raop_rtsp_close_cb(struct evrtsp_connection *evcon, void *arg)
{
struct raop_session *rs = arg;
DPRINTF(E_LOG, L_RAOP, "Device '%s' closed RTSP connection\n", rs->devname);
deferred_session_failure(rs);
}
static void
session_teardown_cb(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
struct output_device *rd = rs->device;
output_status_cb status_cb = rs->status_cb;
rs->reqs_in_flight--;
if (!req || req->response_code != RTSP_OK)
DPRINTF(E_LOG, L_RAOP, "TEARDOWN request failed in session shutdown: %d %s\n", req->response_code, req->response_code_line);
// Clean up session before giving status to the user (good to get rid of it if he
// calls back into us - e.g. tries to make a new session in the callback)
session_cleanup(rs);
// Can't use raop_status() here, sadly
if (status_cb)
status_cb(rd, OUTPUT_STATE_STOPPED);
return;
}
static int
session_teardown(struct raop_session *rs, const char *log_caller)
{
int ret;
ret = raop_send_req_teardown(rs, session_teardown_cb, log_caller);
if (ret < 0)
{
DPRINTF(E_LOG, L_RAOP, "%s: TEARDOWN request failed!\n", log_caller);
deferred_session_failure(rs);
}
return ret;
}
static struct raop_session *
session_make(struct output_device *rd, int family, output_status_cb cb, bool only_probe)
{
struct output_session *os;
struct raop_session *rs;
struct raop_extra *re;
char *address;
@ -1988,25 +2016,8 @@ session_make(struct output_device *rd, int family, output_status_cb cb, bool onl
return NULL;
}
os = calloc(1, sizeof(struct output_session));
if (!os)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory (os)\n");
return NULL;
}
CHECK_NULL(L_PLAYER, rs = calloc(1, sizeof(struct raop_session)));
rs = calloc(1, sizeof(struct raop_session));
if (!rs)
{
DPRINTF(E_LOG, L_RAOP, "Out of memory (rs)\n");
free(os);
return NULL;
}
os->session = rs;
os->type = rd->type;
rs->output_session = os;
rs->state = RAOP_STATE_STOPPED;
rs->only_probe = only_probe;
rs->reqs_in_flight = 0;
@ -2135,9 +2146,13 @@ session_make(struct output_device *rd, int family, output_status_cb cb, bool onl
goto out_free_evcon;
}
// Attach to list of sessions
rs->next = raop_sessions;
raop_sessions = rs;
// rs is now the official device session
rd->session = rs;
return rs;
out_free_evcon:
@ -2717,15 +2732,10 @@ raop_cb_set_volume(struct evrtsp_request *req, void *arg)
static int
raop_set_volume_one(struct output_device *rd, output_status_cb cb)
{
struct raop_session *rs;
struct raop_session *rs = rd->session;
int ret;
if (!rd->session || !rd->session->session)
return 0;
rs = rd->session->session;
if (!(rs->state & RAOP_STATE_F_CONNECTED))
if (!rs || !(rs->state & RAOP_STATE_F_CONNECTED))
return 0;
ret = raop_set_volume_internal(rs, rd->volume, raop_cb_set_volume);
@ -2813,12 +2823,7 @@ raop_flush_timer_cb(int fd, short what, void *arg)
DPRINTF(E_DBG, L_RAOP, "Flush timer expired; tearing down RAOP sessions\n");
for (rs = raop_sessions; rs; rs = rs->next)
{
if (!(rs->state & RAOP_STATE_F_CONNECTED))
continue;
raop_device_stop(rs->output_session);
}
session_teardown(rs, "raop_flush_timer_cb");
}
static void
@ -2882,7 +2887,6 @@ packet_prepare(struct rtp_packet *pkt, uint8_t *rawbuf, size_t rawbuf_size, bool
static int
packet_send(struct raop_session *rs, struct rtp_packet *pkt)
{
struct timeval tv;
int ret;
if (!rs)
@ -2893,12 +2897,9 @@ packet_send(struct raop_session *rs, struct rtp_packet *pkt)
{
DPRINTF(E_LOG, L_RAOP, "Send error for '%s': %s\n", rs->devname, strerror(errno));
rs->state = RAOP_STATE_FAILED;
// Can't free it right away, it would make the ->next in the calling
// master_session and session loops invalid
evutil_timerclear(&tv);
evtimer_add(rs->deferredev, &tv);
deferred_session_failure(rs);
return -1;
}
else if (ret != pkt->data_len)
@ -2943,8 +2944,6 @@ control_packet_send(struct raop_session *rs, struct rtp_packet *pkt)
ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->sa.sa, len);
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno));
DPRINTF(E_DBG, L_PLAYER, "SYNC PACKET SENT\n");
}
static void
@ -2990,7 +2989,6 @@ packets_send(struct raop_master_session *rms)
{
pkt->header[1] = 0xe0;
packet_send(rs, pkt);
rs->state = RAOP_STATE_STREAMING;
}
else if (rs->state == RAOP_STATE_STREAMING)
{
@ -3006,7 +3004,7 @@ packets_send(struct raop_master_session *rms)
}
static void
packets_sync_send(struct raop_master_session *rms, struct timespec *pts)
packets_sync_send(struct raop_master_session *rms, struct timespec pts)
{
struct rtp_packet *sync_pkt;
struct raop_session *rs;
@ -3022,9 +3020,9 @@ packets_sync_send(struct raop_master_session *rms, struct timespec *pts)
// OUTPUTS_BUFFER_DURATION secs into the future. However, in the sync packet
// we want to tell the device what it should be playing right now. So we give
// it a cur_time where we subtract this duration.
// TODO do we need this? could we just send the future timestamp?
cur_stamp.ts.tv_sec = pts->tv_sec - OUTPUTS_BUFFER_DURATION;
cur_stamp.ts.tv_nsec = pts->tv_nsec;
cur_stamp.ts.tv_sec = pts.tv_sec - OUTPUTS_BUFFER_DURATION;
cur_stamp.ts.tv_nsec = pts.tv_nsec;
// The cur_pos will be the rtptime of the coming packet, minus
// OUTPUTS_BUFFER_DURATION in samples (output_buffer_samples). Because we
// might also have some data lined up in rms->evbuf, we also need to account
@ -3041,6 +3039,8 @@ packets_sync_send(struct raop_master_session *rms, struct timespec *pts)
{
sync_pkt = rtp_sync_packet_next(rms->rtp_session, &cur_stamp, 0x90);
control_packet_send(rs, sync_pkt);
DPRINTF(E_DBG, L_PLAYER, "Start sync packet sent to '%s': cur_pos=%" PRIu32 ", rtptime=%" PRIu32 "\n", rs->devname, cur_stamp.pos, rms->rtp_session->pos);
}
else if (is_sync_time && rs->state == RAOP_STATE_STREAMING)
{
@ -3553,9 +3553,32 @@ raop_v2_control_start(int v6enabled)
/* ------------------------------ Session startup --------------------------- */
static void
raop_cb_startup_retry(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
struct output_device *rd = rs->device;
output_status_cb cb = rs->status_cb;
session_cleanup(rs);
raop_device_start(rd, cb);
}
static void
raop_cb_startup_cancel(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
session_failure(rs);
}
static void
raop_startup_cancel(struct raop_session *rs)
{
struct output_device *rd = rs->device;
output_status_cb cb;
int ret;
if (!rs->session)
{
session_failure(rs);
@ -3567,20 +3590,24 @@ raop_startup_cancel(struct raop_session *rs)
if (rs->family == AF_INET6 && !(rs->state & RAOP_STATE_F_FAILED))
{
// This flag is permanent and will not be overwritten by mdns advertisements
rs->device->v6_disabled = 1;
rd->v6_disabled = 1;
// Be nice to our peer + session_failure_cb() cleans up old session
raop_send_req_teardown(rs, session_failure_cb, "startup_cancel");
// Stop current session and wait for call back
ret = raop_send_req_teardown(rs, raop_cb_startup_retry, "startup_cancel");
if (ret < 0)
{
// No connection at all, lets clean up and try again
cb = rs->status_cb;
session_cleanup(rs);
raop_device_start(rd, cb);
}
// Try to start a new session
raop_device_start(rs->device, rs->status_cb);
// Don't let the failed session make a negative status callback
rs->status_cb = NULL;
return;
}
raop_send_req_teardown(rs, session_failure_cb, "startup_cancel");
ret = raop_send_req_teardown(rs, raop_cb_startup_cancel, "startup_cancel");
if (ret < 0)
session_failure(rs);
}
static void
@ -4069,41 +4096,6 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg)
raop_startup_cancel(rs);
}
static void
raop_cb_shutdown_teardown(struct evrtsp_request *req, void *arg)
{
struct raop_session *rs = arg;
int ret;
rs->reqs_in_flight--;
if (!req)
goto error;
if (req->response_code != RTSP_OK)
{
DPRINTF(E_LOG, L_RAOP, "TEARDOWN request failed in session shutdown: %d %s\n", req->response_code, req->response_code_line);
goto error;
}
ret = raop_check_cseq(rs, req);
if (ret < 0)
goto error;
rs->state = RAOP_STATE_STOPPED;
/* Session shut down, tell our user */
raop_status(rs);
session_cleanup(rs);
return;
error:
session_failure(rs);
}
/* ------------------------- tvOS device verification ----------------------- */
/* e.g. for the ATV4 (read it from the bottom and up) */
@ -4801,51 +4793,51 @@ raop_device_probe(struct output_device *rd, output_status_cb cb)
static int
raop_device_start(struct output_device *rd, output_status_cb cb)
{
event_del(flush_timer);
evtimer_add(keep_alive_timer, &keep_alive_tv);
return raop_device_start_generic(rd, cb, 0);
}
static void
raop_device_stop(struct output_session *session)
static int
raop_device_stop(struct output_device *rd, output_status_cb cb)
{
struct raop_session *rs = session->session;
struct raop_session *rs = rd->session;
if (rs->state & RAOP_STATE_F_CONNECTED)
raop_send_req_teardown(rs, raop_cb_shutdown_teardown, "device_stop");
else
session_cleanup(rs);
rs->status_cb = cb;
return session_teardown(rs, "device_stop");
}
static void
raop_device_free_extra(struct output_device *device)
raop_device_free_extra(struct output_device *rd)
{
struct raop_extra *re = device->extra_device_info;
struct raop_extra *re = rd->extra_device_info;
free(re);
}
static void
raop_device_set_cb(struct output_device *rd, output_status_cb cb)
{
struct raop_session *rs = rd->session;
rs->status_cb = cb;
}
static void
raop_playback_stop(void)
{
struct raop_session *rs;
int ret;
evtimer_del(keep_alive_timer);
for (rs = raop_sessions; rs; rs = rs->next)
{
ret = raop_send_req_teardown(rs, raop_cb_shutdown_teardown, "playback_stop");
if (ret < 0)
DPRINTF(E_LOG, L_RAOP, "playback_stop: TEARDOWN request failed!\n");
}
session_teardown(rs, "playback_stop");
}
static void
raop_write(struct output_buffer *obuf)
{
struct raop_master_session *rms;
struct raop_session *rs;
int i;
for (rms = raop_master_sessions; rms; rms = rms->next)
@ -4871,6 +4863,19 @@ raop_write(struct output_buffer *obuf)
}
}
}
// Check for devices that have joined since last write (we have already sent them
// initialization sync and rtp packets via packets_sync_send and packets_send)
for (rs = raop_sessions; rs; rs = rs->next)
{
if (rs->state != RAOP_STATE_CONNECTED)
continue;
event_del(flush_timer); // In case playback was stopped but then restarted again
rs->state = RAOP_STATE_STREAMING;
// Make a cb?
}
}
static int
@ -4894,7 +4899,6 @@ raop_flush(output_status_cb cb)
if (ret < 0)
{
session_failure(rs);
continue;
}
@ -4912,14 +4916,6 @@ raop_flush(output_status_cb cb)
return pending;
}
static void
raop_set_status_cb(struct output_session *session, output_status_cb cb)
{
struct raop_session *rs = session->session;
rs->status_cb = cb;
}
static int
raop_init(void)
{
@ -5035,7 +5031,6 @@ raop_init(void)
goto out_stop_control;
}
return 0;
out_stop_control:
@ -5087,16 +5082,16 @@ struct output_definition output_raop =
.disabled = 0,
.init = raop_init,
.deinit = raop_deinit,
.device_start2 = raop_device_start,
.device_start = raop_device_start,
.device_stop = raop_device_stop,
.device_probe = raop_device_probe,
.device_free_extra = raop_device_free_extra,
.device_volume_set = raop_set_volume_one,
.device_volume_to_pct = raop_volume_to_pct,
.device_set_cb = raop_device_set_cb,
.playback_stop = raop_playback_stop,
.write2 = raop_write,
.flush2 = raop_flush,
.status_cb = raop_set_status_cb,
.write = raop_write,
.flush = raop_flush,
.metadata_prepare = raop_metadata_prepare,
.metadata_send = raop_metadata_send,
.metadata_purge = raop_metadata_purge,

View File

@ -252,14 +252,14 @@ rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp *cur_sta
rtptime = htobe32(session->pos);
memcpy(session->sync_packet_next.data + 16, &rtptime, 4);
DPRINTF(E_DBG, L_PLAYER, "SYNC PACKET cur_ts:%ld.%ld, next_pkt:%u, cur_pos:%u, type:0x%x, sync_counter:%d\n",
/* DPRINTF(E_DBG, L_PLAYER, "SYNC PACKET cur_ts:%ld.%ld, next_pkt:%u, cur_pos:%u, type:0x%x, sync_counter:%d\n",
cur_stamp->ts.tv_sec, cur_stamp->ts.tv_nsec,
session->pos,
cur_stamp->pos,
session->sync_packet_next.data[0],
session->sync_counter
);
*/
return &session->sync_packet_next;
}

View File

@ -24,7 +24,6 @@
#include "outputs.h"
#include "httpd_streaming.h"
struct output_definition output_streaming =
{
.name = "mp3 streaming",

View File

@ -287,6 +287,8 @@ playback_abort(void);
static void
playback_suspend(void);
static int
player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit);
/* ----------------------------- Volume helpers ----------------------------- */
@ -1009,7 +1011,7 @@ source_switch(int nbytes)
}
static void
session_init(struct player_session *session, struct media_quality *quality)
session_reset(struct player_session *session, struct media_quality *quality)
{
session->samples_written = 0;
session->quality = *quality;
@ -1033,7 +1035,7 @@ session_init(struct player_session *session, struct media_quality *quality)
}
static void
session_deinit(struct player_session *session)
session_clear(struct player_session *session)
{
free(session->buffer);
memset(session, 0, sizeof(struct player_session));
@ -1083,7 +1085,9 @@ source_read(uint8_t *buf, int len)
else if (flags & INPUT_FLAG_QUALITY)
{
input_quality_get(&quality);
session_init(&pb_session, &quality);
if (!quality_is_equal(&quality, &pb_session.quality))
session_reset(&pb_session, &quality);
}
// We pad the output buffer with silence if we don't have enough data for a
@ -1171,7 +1175,7 @@ playback_cb(int fd, short what, void *arg)
}
nsamples = BTOS(got, pb_session.quality.bits_per_sample, pb_session.quality.channels);
outputs_write2(pb_session.buffer, pb_session.bufsize, &pb_session.quality, nsamples, &pb_session.pts);
outputs_write(pb_session.buffer, pb_session.bufsize, &pb_session.quality, nsamples, &pb_session.pts);
pb_session.samples_written += nsamples;
if (got < pb_session.bufsize)
@ -1468,11 +1472,11 @@ device_metadata_send(void *arg, int *retval)
/* -------- Output device callbacks executed in the player thread ----------- */
static void
device_streaming_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
device_streaming_cb(struct output_device *device, enum output_device_state status)
{
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_streaming_cb\n", outputs_name(device->type));
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_streaming_cb (status %d)\n", outputs_name(device->type), status);
ret = device_check(device);
if (ret < 0)
@ -1492,8 +1496,6 @@ device_streaming_cb(struct output_device *device, struct output_session *session
if (player_state == PLAY_PLAYING)
speaker_deselect_output(device);
device->session = NULL;
if (!device->advertised)
device_remove(device);
@ -1506,24 +1508,22 @@ device_streaming_cb(struct output_device *device, struct output_session *session
output_sessions--;
device->session = NULL;
if (!device->advertised)
device_remove(device);
}
else
outputs_status_cb(session, device_streaming_cb);
outputs_device_set_cb(device, device_streaming_cb);
}
static void
device_command_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
device_command_cb(struct output_device *device, enum output_device_state status)
{
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_command_cb\n", outputs_name(device->type));
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_command_cb (status %d)\n", outputs_name(device->type), status);
outputs_status_cb(session, device_streaming_cb);
outputs_device_set_cb(device, device_streaming_cb);
if (status == OUTPUT_STATE_FAILED)
device_streaming_cb(device, session, status);
device_streaming_cb(device, status);
// Used by playback_suspend - is basically the bottom half
if (player_flush_pending > 0)
@ -1537,12 +1537,12 @@ device_command_cb(struct output_device *device, struct output_session *session,
}
static void
device_shutdown_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
device_shutdown_cb(struct output_device *device, enum output_device_state status)
{
int retval;
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_shutdown_cb\n", outputs_name(device->type));
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_shutdown_cb (status %d)\n", outputs_name(device->type), status);
if (output_sessions)
output_sessions--;
@ -1558,8 +1558,6 @@ device_shutdown_cb(struct output_device *device, struct output_session *session,
goto out;
}
device->session = NULL;
if (!device->advertised)
device_remove(device);
@ -1572,9 +1570,9 @@ device_shutdown_cb(struct output_device *device, struct output_session *session,
}
static void
device_lost_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
device_lost_cb(struct output_device *device, enum output_device_state status)
{
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_lost_cb\n", outputs_name(device->type));
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_lost_cb (status %d)\n", outputs_name(device->type), status);
// We lost that device during startup for some reason, not much we can do here
if (status == OUTPUT_STATE_FAILED)
@ -1584,12 +1582,12 @@ device_lost_cb(struct output_device *device, struct output_session *session, enu
}
static void
device_activate_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
device_activate_cb(struct output_device *device, enum output_device_state status)
{
int retval;
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_activate_cb\n", outputs_name(device->type));
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_activate_cb (status %d)\n", outputs_name(device->type), status);
retval = commands_exec_returnvalue(cmdbase);
ret = device_check(device);
@ -1597,8 +1595,7 @@ device_activate_cb(struct output_device *device, struct output_session *session,
{
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during startup!\n");
outputs_status_cb(session, device_lost_cb);
outputs_device_stop(session);
outputs_device_stop(device, device_lost_cb);
if (retval != -2)
retval = -1;
@ -1623,11 +1620,9 @@ device_activate_cb(struct output_device *device, struct output_session *session,
goto out;
}
device->session = session;
output_sessions++;
outputs_status_cb(session, device_streaming_cb);
outputs_device_set_cb(device, device_streaming_cb);
out:
/* cur_cmd->ret already set
@ -1639,12 +1634,12 @@ device_activate_cb(struct output_device *device, struct output_session *session,
}
static void
device_probe_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
device_probe_cb(struct output_device *device, enum output_device_state status)
{
int retval;
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_probe_cb\n", outputs_name(device->type));
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_probe_cb (status %d)\n", outputs_name(device->type), status);
retval = commands_exec_returnvalue(cmdbase);
ret = device_check(device);
@ -1685,12 +1680,12 @@ device_probe_cb(struct output_device *device, struct output_session *session, en
}
static void
device_restart_cb(struct output_device *device, struct output_session *session, enum output_device_state status)
device_restart_cb(struct output_device *device, enum output_device_state status)
{
int retval;
int ret;
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb\n", outputs_name(device->type));
DPRINTF(E_DBG, L_PLAYER, "Callback from %s to device_restart_cb (status %d)\n", outputs_name(device->type), status);
retval = commands_exec_returnvalue(cmdbase);
ret = device_check(device);
@ -1698,8 +1693,7 @@ device_restart_cb(struct output_device *device, struct output_session *session,
{
DPRINTF(E_WARN, L_PLAYER, "Output device disappeared during restart!\n");
outputs_status_cb(session, device_lost_cb);
outputs_device_stop(session);
outputs_device_stop(device, device_lost_cb);
if (retval != -2)
retval = -1;
@ -1724,10 +1718,8 @@ device_restart_cb(struct output_device *device, struct output_session *session,
goto out;
}
device->session = session;
output_sessions++;
outputs_status_cb(session, device_streaming_cb);
outputs_device_set_cb(device, device_streaming_cb);
out:
commands_exec_end(cmdbase, retval);
@ -1786,19 +1778,30 @@ playback_timer_stop(void)
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not disarm playback timer: %s\n", strerror(errno));
return -1;
}
return 0;
}
static int
pb_session_stop(void)
{
int ret;
ret = playback_timer_stop();
session_clear(&pb_session);
return ret;
}
static void
playback_abort(void)
{
outputs_playback_stop();
playback_timer_stop();
pb_session_stop();
source_stop();
@ -1815,9 +1818,9 @@ playback_abort(void)
static void
playback_suspend(void)
{
player_flush_pending = outputs_flush2(device_command_cb);
player_flush_pending = outputs_flush(device_command_cb);
playback_timer_stop();
pb_session_stop();
status_update(PLAY_PAUSED);
@ -1949,9 +1952,9 @@ playback_stop(void *arg, int *retval)
// We may be restarting very soon, so we don't bring the devices to a full
// stop just yet; this saves time when restarting, which is nicer for the user
*retval = outputs_flush2(device_command_cb);
*retval = outputs_flush(device_command_cb);
playback_timer_stop();
pb_session_stop();
ps_playing = source_now_playing();
if (ps_playing)
@ -1975,7 +1978,6 @@ playback_stop(void *arg, int *retval)
static enum command_state
playback_start_bh(void *arg, int *retval)
{
struct timespec ts;
int ret;
// initialize the packet timer to the same relative time that we have
@ -1989,10 +1991,6 @@ playback_start_bh(void *arg, int *retval)
// pb_buffer_offset = 0;
pb_read_deficit = 0;
ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &player_timer_res);
if (ret < 0)
goto out_fail;
ret = playback_timer_start();
if (ret < 0)
goto out_fail;
@ -2098,7 +2096,7 @@ playback_start_item(void *arg, int *retval)
{
if (device->selected && !device->session)
{
ret = outputs_device_start2(device, device_restart_cb);
ret = outputs_device_start(device, device_restart_cb);
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not start selected %s device '%s'\n", device->type_name, device->name);
@ -2118,7 +2116,7 @@ playback_start_item(void *arg, int *retval)
continue;
speaker_select_output(device);
ret = outputs_device_start(device, device_restart_cb, TEMP_NEXT_RTPTIME);
ret = outputs_device_start(device, device_restart_cb);
if (ret < 0)
{
DPRINTF(E_DBG, L_PLAYER, "Could not autoselect %s device '%s'\n", device->type_name, device->name);
@ -2420,9 +2418,9 @@ playback_pause(void *arg, int *retval)
return COMMAND_END;
}
*retval = outputs_flush2(device_command_cb);
*retval = outputs_flush(device_command_cb);
playback_timer_stop();
pb_session_stop();
source_pause(pos);
@ -2530,7 +2528,7 @@ speaker_activate(struct output_device *device)
{
DPRINTF(E_DBG, L_PLAYER, "Activating %s device '%s'\n", device->type_name, device->name);
ret = outputs_device_start2(device, device_activate_cb);
ret = outputs_device_start(device, device_activate_cb);
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not start %s device '%s'\n", device->type_name, device->name);
@ -2568,8 +2566,7 @@ speaker_deactivate(struct output_device *device)
if (!device->session)
return 0;
outputs_status_cb(device->session, device_shutdown_cb);
outputs_device_stop(device->session);
outputs_device_stop(device, device_shutdown_cb);
return 1;
}
@ -2996,7 +2993,8 @@ playerqueue_plid(void *arg, int *retval)
/* ------------------------------- Player API ------------------------------- */
int
// TODO no longer part of API
static int
player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
{
uint64_t delta;
@ -3039,21 +3037,6 @@ player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
return 0;
}
int
player_get_time(struct timespec *ts)
{
int ret;
ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &player_timer_res);
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno));
return -1;
}
return 0;
}
int
player_get_status(struct player_status *status)
{
@ -3491,7 +3474,6 @@ player(void *arg)
int
player_init(void)
{
struct media_quality default_quality = { 44100, 16, 2 };
uint64_t interval;
uint32_t rnd;
int ret;
@ -3567,8 +3549,6 @@ player_init(void)
goto evnew_fail;
}
session_init(&pb_session, &default_quality);
cmdbase = commands_base_new(evbase_player, NULL);
ret = outputs_init();
@ -3605,7 +3585,6 @@ player_init(void)
outputs_deinit();
outputs_fail:
commands_base_free(cmdbase);
session_deinit(&pb_session);
evnew_fail:
event_base_free(evbase_player);
evbase_fail:
@ -3638,7 +3617,7 @@ player_deinit(void)
player_exit = 1;
commands_base_destroy(cmdbase);
session_deinit(&pb_session);
session_clear(&pb_session);
ret = pthread_join(tid_player, NULL);
if (ret != 0)

View File

@ -72,13 +72,6 @@ struct player_history
uint32_t item_id[MAX_HISTORY_COUNT];
};
int
player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit);
int
player_get_time(struct timespec *ts);
int
player_get_status(struct player_status *status);