mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 07:05:57 -05:00
[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:
parent
14a6d318f0
commit
e99f20992e
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include "outputs.h"
|
||||
#include "httpd_streaming.h"
|
||||
|
||||
|
||||
struct output_definition output_streaming =
|
||||
{
|
||||
.name = "mp3 streaming",
|
||||
|
131
src/player.c
131
src/player.c
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user