Merge branch 'player_refactor2'

Major refactor of player, input and output
This commit is contained in:
ejurgensen 2019-04-10 22:41:38 +02:00
commit 4dcbb2f24f
37 changed files with 6624 additions and 8516 deletions

View File

@ -210,7 +210,7 @@ AC_ARG_WITH([libav], [AS_HELP_STRING([--with-libav],
[[LIBAV=-libav]], [[LIBAV=]])
dnl libav/ffmpeg requires many feature checks
FORK_MODULES_CHECK([FORKED], [LIBAV],
[libavformat$LIBAV libavcodec$LIBAV libswscale$LIBAV libavutil$LIBAV libavfilter$LIBAV],
[libavformat$LIBAV libavcodec$LIBAV libavutil$LIBAV libavfilter$LIBAV],
[av_init_packet], [libavcodec/avcodec.h],
[dnl Checks for misc libav and ffmpeg API differences
AC_MSG_CHECKING([whether libav libraries are ffmpeg])
@ -231,37 +231,7 @@ FORK_MODULES_CHECK([FORKED], [LIBAV],
[libavutil/avutil.h])
FORK_CHECK_DECLS([avformat_network_init],
[libavformat/avformat.h])
dnl Check if we have modern or legacy AV api
FORK_CHECK_DECLS([avcodec_send_packet, avcodec_parameters_from_context],
[libavcodec/avcodec.h], [[libav_modern_api=yes]], [[libav_modern_api=no]])
dnl The below we need to know only if we are going to use the legacy AV api
FORK_CHECK_DECLS([av_buffersrc_add_frame_flags],
[libavfilter/buffersrc.h])
FORK_CHECK_DECLS([av_buffersink_get_frame],
[libavfilter/buffersink.h])
FORK_CHECK_DECLS([avfilter_graph_parse_ptr],
[libavfilter/avfilter.h])
FORK_CHECK_DECLS([av_packet_unref],
[libavcodec/avcodec.h])
FORK_CHECK_DECLS([av_packet_rescale_ts],
[libavcodec/avcodec.h])
FORK_CHECK_DECLS([avformat_alloc_output_context2],
[libavformat/avformat.h])
FORK_CHECK_DECLS([av_frame_alloc],
[libavutil/frame.h])
FORK_CHECK_DECLS([av_frame_get_best_effort_timestamp],
[libavutil/frame.h])
FORK_CHECK_DECLS([av_image_fill_arrays],
[libavutil/imgutils.h])
FORK_CHECK_DECLS([av_image_get_buffer_size],
[libavutil/imgutils.h])
AC_CHECK_HEADERS([libavutil/channel_layout.h libavutil/mathematics.h])
])
dnl Option to choose old ffmpeg/libav API even if modern api was found
FORK_ARG_DISABLE([use of ffmpeg/libav API with avcodec_send_packet() and family],
[avcodecsend], [USE_AVCODEC_SEND])
AM_CONDITIONAL([COND_FFMPEG_LEGACY],
[[test "x$libav_modern_api" = "xno" || test "x$enable_avcodecsend" = "xno" ]])
AC_CHECK_SIZEOF([void *])

View File

@ -217,17 +217,23 @@ audio {
# If not set, the value for "card" will be used.
# mixer_device = ""
# Synchronization
# If your local audio is out of sync with AirPlay, you can adjust this
# value. Positive values correspond to moving local audio ahead,
# negative correspond to delaying it. The unit is samples, where is
# 44100 = 1 second. The offset must be between -44100 and 44100.
# offset = 0
# Enable or disable audio resampling to keep local audio in sync with
# e.g. Airplay. This feature relies on accurate ALSA measurements of
# delay, and some devices don't provide that. If that is the case you
# are better off disabling the feature.
# sync_disable = false
# How often to check and correct for drift between ALSA and AirPlay.
# The value is an integer expressed in seconds.
# Clamped to the range 1..20.
# adjust_period_seconds = 10
# Here you can adjust when local audio is started relative to other
# speakers, e.g. Airplay. Negative values correspond to moving local
# audio ahead, positive correspond to delaying it. The unit is
# milliseconds. The offset must be between -1000 and 1000 (+/- 1 sec).
# offset_ms = 0
# To calculate what and if resampling is required, local audio delay is
# measured each second. After a period the collected measurements are
# used to estimate drift and latency, which determines if corrections
# are required. This setting sets the length of that period in seconds.
# adjust_period_seconds = 100
}
# Pipe output

View File

@ -47,12 +47,6 @@ if COND_LIBWEBSOCKETS
LIBWEBSOCKETS_SRC=websocket.c websocket.h
endif
if COND_FFMPEG_LEGACY
FFMPEG_SRC=transcode_legacy.c artwork_legacy.c ffmpeg-compat.h
else
FFMPEG_SRC=transcode.c artwork.c
endif
GPERF_FILES = \
daap_query.gperf \
rsp_query.gperf \
@ -118,8 +112,8 @@ forked_daapd_SOURCES = main.c \
httpd_artworkapi.c httpd_artworkapi.h \
http.c http.h \
dmap_common.c dmap_common.h \
transcode.h artwork.h \
$(FFMPEG_SRC) \
transcode.c transcode.h \
artwork.c artwork.h \
misc.c misc.h \
misc_json.c misc_json.h \
rng.c rng.h \
@ -131,6 +125,7 @@ forked_daapd_SOURCES = main.c \
input.h input.c \
inputs/file_http.c inputs/pipe.c \
outputs.h outputs.c \
outputs/rtp_common.h outputs/rtp_common.c \
outputs/raop.c $(RAOP_VERIFICATION_SRC) \
outputs/streaming.c outputs/dummy.c outputs/fifo.c \
$(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \

View File

@ -408,7 +408,7 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
DPRINTF(E_SPAM, L_ART, "Getting artwork (max destination width %d height %d)\n", max_w, max_h);
xcode_decode = transcode_decode_setup(XCODE_JPEG, DATA_KIND_FILE, path, inbuf, 0); // Covers XCODE_PNG too
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, DATA_KIND_FILE, path, inbuf, 0); // Covers XCODE_PNG too
if (!xcode_decode)
{
if (path)
@ -462,9 +462,9 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
}
if (format_ok == ART_FMT_JPEG)
xcode_encode = transcode_encode_setup(XCODE_JPEG, xcode_decode, NULL, target_w, target_h);
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, target_w, target_h);
else
xcode_encode = transcode_encode_setup(XCODE_PNG, xcode_decode, NULL, target_w, target_h);
xcode_encode = transcode_encode_setup(XCODE_PNG, NULL, xcode_decode, NULL, target_w, target_h);
if (!xcode_encode)
{

File diff suppressed because it is too large Load Diff

View File

@ -100,6 +100,8 @@ static cfg_opt_t sec_library[] =
CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
CFG_STR_LIST("force_decode", NULL, CFGF_NONE),
CFG_BOOL("pipe_autostart", cfg_true, CFGF_NONE),
CFG_INT("pipe_sample_rate", 44100, CFGF_NONE),
CFG_INT("pipe_bits_per_sample", 16, CFGF_NONE),
CFG_BOOL("rating_updates", cfg_false, CFGF_NONE),
CFG_END()
};
@ -113,8 +115,10 @@ static cfg_opt_t sec_audio[] =
CFG_STR("card", "default", CFGF_NONE),
CFG_STR("mixer", NULL, CFGF_NONE),
CFG_STR("mixer_device", NULL, CFGF_NONE),
CFG_INT("offset", 0, CFGF_NONE),
CFG_INT("adjust_period_seconds", 10, CFGF_NONE),
CFG_BOOL("sync_disable", cfg_false, CFGF_NONE),
CFG_INT("offset", 0, CFGF_NONE), // deprecated
CFG_INT("offset_ms", 0, CFGF_NONE),
CFG_INT("adjust_period_seconds", 100, CFGF_NONE),
CFG_END()
};

View File

@ -1,105 +0,0 @@
#ifndef __FFMPEG_COMPAT_H__
#define __FFMPEG_COMPAT_H__
#ifdef HAVE_LIBAVUTIL_CHANNEL_LAYOUT_H
# include <libavutil/channel_layout.h>
#endif
#ifdef HAVE_LIBAVUTIL_MATHEMATICS_H
# include <libavutil/mathematics.h>
#endif
#ifndef HAVE_FFMPEG
# define avcodec_find_best_pix_fmt_of_list(a, b, c, d) avcodec_find_best_pix_fmt2((enum AVPixelFormat *)(a), (b), (c), (d))
#endif
#if !HAVE_DECL_AV_FRAME_ALLOC
# define av_frame_alloc() avcodec_alloc_frame()
# define av_frame_free(x) avcodec_free_frame((x))
#endif
#if !HAVE_DECL_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP
# define av_frame_get_best_effort_timestamp(x) (x)->pts
#endif
#if !HAVE_DECL_AV_IMAGE_GET_BUFFER_SIZE
# define av_image_get_buffer_size(a, b, c, d) avpicture_get_size((a), (b), (c))
#endif
#if !HAVE_DECL_AV_PACKET_UNREF
# define av_packet_unref(a) av_free_packet((a))
#endif
#if !HAVE_DECL_AV_PACKET_RESCALE_TS
__attribute__((unused)) static void
av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
{
if (pkt->pts != AV_NOPTS_VALUE)
pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
if (pkt->dts != AV_NOPTS_VALUE)
pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
if (pkt->duration > 0)
pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);
if (pkt->convergence_duration > 0)
pkt->convergence_duration = av_rescale_q(pkt->convergence_duration, src_tb, dst_tb);
}
#endif
#if !HAVE_DECL_AVFORMAT_ALLOC_OUTPUT_CONTEXT2
# include <libavutil/opt.h>
__attribute__((unused)) static int
avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename)
{
AVFormatContext *s = avformat_alloc_context();
int ret = 0;
*avctx = NULL;
if (!s)
goto nomem;
if (!oformat) {
if (format) {
oformat = av_guess_format(format, NULL, NULL);
if (!oformat) {
av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
ret = AVERROR(EINVAL);
goto error;
}
} else {
oformat = av_guess_format(NULL, filename, NULL);
if (!oformat) {
ret = AVERROR(EINVAL);
av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
filename);
goto error;
}
}
}
s->oformat = oformat;
if (s->oformat->priv_data_size > 0) {
s->priv_data = av_mallocz(s->oformat->priv_data_size);
if (!s->priv_data)
goto nomem;
if (s->oformat->priv_class) {
*(const AVClass**)s->priv_data= s->oformat->priv_class;
av_opt_set_defaults(s->priv_data);
}
} else
s->priv_data = NULL;
if (filename)
snprintf(s->filename, sizeof(s->filename), "%s", filename);
*avctx = s;
return 0;
nomem:
av_log(s, AV_LOG_ERROR, "Out of memory\n");
ret = AVERROR(ENOMEM);
error:
avformat_free_context(s);
return ret;
}
#endif
#endif /* !__FFMPEG_COMPAT_H__ */

View File

@ -80,6 +80,11 @@
"<h1>%s</h1>\n" \
"</body>\n</html>\n"
#define HTTPD_STREAM_SAMPLE_RATE 44100
#define HTTPD_STREAM_BPS 16
#define HTTPD_STREAM_CHANNELS 2
struct content_type_map {
char *ext;
char *ctype;
@ -1029,6 +1034,7 @@ httpd_request_parse(struct evhttp_request *req, struct httpd_uri_parsed *uri_par
void
httpd_stream_file(struct evhttp_request *req, int id)
{
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS };
struct media_file_info *mfi;
struct stream_ctx *st;
void (*stream_cb)(int fd, short event, void *arg);
@ -1128,7 +1134,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
stream_cb = stream_chunk_xcode_cb;
st->xcode = transcode_setup(XCODE_PCM16_HEADER, mfi->data_kind, mfi->path, mfi->song_length, &st->size);
st->xcode = transcode_setup(XCODE_PCM16_HEADER, &quality, mfi->data_kind, mfi->path, mfi->song_length, &st->size);
if (!st->xcode)
{
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");

View File

@ -88,7 +88,7 @@ artworkapi_reply_nowplaying(struct httpd_request *hreq)
if (ret != 0)
return ret;
ret = player_now_playing(&id);
ret = player_playing_now(&id);
if (ret != 0)
return HTTP_NOTFOUND;

View File

@ -1083,7 +1083,7 @@ dacp_propset_userrating(const char *value, struct httpd_request *hreq)
{
DPRINTF(E_WARN, L_DACP, "Invalid id %d for rating, defaulting to player id\n", itemid);
ret = player_now_playing(&itemid);
ret = player_playing_now(&itemid);
if (ret < 0)
{
DPRINTF(E_WARN, L_DACP, "Could not find an id for rating\n");
@ -2277,7 +2277,7 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq)
goto error;
}
ret = player_now_playing(&id);
ret = player_playing_now(&id);
if (ret < 0)
goto no_artwork;

View File

@ -44,8 +44,13 @@ 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
// Buffer size for transmitting from player to httpd thread
#define STREAMING_RAWBUF_SIZE (STOB(AIRTUNES_V2_PACKET_SAMPLES))
// How many bytes we try to read at a time from the httpd pipe
#define STREAMING_READ_SIZE STOB(352, 16, 2)
#define STREAMING_MP3_SAMPLE_RATE 44100
#define STREAMING_MP3_BPS 16
#define STREAMING_MP3_CHANNELS 2
// Linked list of mp3 streaming requests
struct streaming_session {
@ -54,23 +59,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
@ -111,15 +117,110 @@ streaming_fail_cb(struct evhttp_connection *evcon, void *arg)
{
DPRINTF(E_INFO, L_STREAMING, "No more clients, will stop streaming\n");
event_del(streamingev);
event_del(metaev);
}
}
static void
streaming_end(void)
{
struct streaming_session *session;
struct evhttp_connection *evcon;
for (session = streaming_sessions; streaming_sessions; session = streaming_sessions)
{
evcon = evhttp_request_get_connection(session->req);
if (evcon)
evhttp_connection_set_closecb(evcon, NULL, NULL);
evhttp_send_reply_end(session->req);
streaming_sessions = session->next;
free(session);
}
event_del(streamingev);
event_del(metaev);
}
static void
streaming_meta_cb(evutil_socket_t fd, short event, void *arg)
{
struct media_quality mp3_quality = { STREAMING_MP3_SAMPLE_RATE, STREAMING_MP3_BPS, STREAMING_MP3_CHANNELS };
struct media_quality quality;
struct decode_ctx *decode_ctx;
int ret;
transcode_encode_cleanup(&streaming_encode_ctx);
ret = read(fd, &quality, sizeof(struct media_quality));
if (ret != sizeof(struct media_quality))
goto error;
decode_ctx = NULL;
if (quality.bits_per_sample == 16)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, &quality);
else if (quality.bits_per_sample == 24)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM24, &quality);
else if (quality.bits_per_sample == 32)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM32, &quality);
if (!decode_ctx)
goto error;
streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, &mp3_quality, 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_quality = quality;
streaming_not_supported = 0;
return;
error:
DPRINTF(E_LOG, L_STREAMING, "Unknown or unsupported quality of input data (%d/%d/%d), cannot MP3 encode\n", quality.sample_rate, quality.bits_per_sample, quality.channels);
streaming_not_supported = 1;
streaming_end();
}
static int
encode_buffer(uint8_t *buffer, size_t size)
{
transcode_frame *frame;
int samples;
int ret;
if (streaming_not_supported || streaming_quality.channels == 0)
{
DPRINTF(E_LOG, L_STREAMING, "Streaming unsuppored or quality is zero\n");
return -1;
}
samples = BTOS(size, streaming_quality.bits_per_sample, streaming_quality.channels);
frame = transcode_frame_new(buffer, size, samples, &streaming_quality);
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;
@ -127,24 +228,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(XCODE_MP3, streaming_rawbuf, STREAMING_RAWBUF_SIZE);
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
@ -155,16 +248,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();
@ -179,6 +274,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);
}
@ -191,14 +287,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->data[0].quality, &streaming_quality))
{
ret = write(streaming_meta[1], &obuf->data[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->data[0].buffer, obuf->data[0].bufsize);
if (ret < 0)
{
if (errno == EAGAIN)
@ -219,9 +325,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;
@ -258,7 +364,10 @@ streaming_request(struct evhttp_request *req, struct httpd_uri_parsed *uri_parse
}
if (!streaming_sessions)
event_add(streamingev, &streaming_silence_tv);
{
event_add(streamingev, &streaming_silence_tv);
event_add(metaev, NULL);
}
session->req = req;
session->next = streaming_sessions;
@ -284,26 +393,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();
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);
@ -318,7 +409,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
@ -326,77 +433,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);
while (remaining > STREAMING_RAWBUF_SIZE)
{
frame = transcode_frame_new(XCODE_MP3, streaming_rawbuf, STREAMING_RAWBUF_SIZE);
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;
}
@ -407,9 +459,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
@ -428,8 +477,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);

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,8 @@
# include <config.h>
#endif
#include <event2/buffer.h>
#include "transcode.h"
#include "db.h"
#include "misc.h"
// Must be in sync with inputs[] in input.c
enum input_types
@ -21,83 +22,74 @@ enum input_types
enum input_flags
{
// Write to input buffer must not block
INPUT_FLAG_NONBLOCK = (1 << 0),
// Flags that input is closing current source
INPUT_FLAG_START_NEXT = (1 << 0),
// Flags end of file
INPUT_FLAG_EOF = (1 << 1),
INPUT_FLAG_EOF = (1 << 1),
// Flags error reading file
INPUT_FLAG_ERROR = (1 << 2),
INPUT_FLAG_ERROR = (1 << 2),
// Flags possible new stream metadata
INPUT_FLAG_METADATA = (1 << 3),
INPUT_FLAG_METADATA = (1 << 3),
// Flags new stream quality
INPUT_FLAG_QUALITY = (1 << 4),
};
struct player_source
struct input_source
{
/* Id of the file/item in the files database */
uint32_t id;
// Type of input
enum input_types type;
/* Item-Id of the file/item in the queue */
// Item-Id of the file/item in the queue
uint32_t item_id;
/* Length of the file/item in milliseconds */
// Id of the file/item in the files database
uint32_t id;
// Length of the file/item in milliseconds
uint32_t len_ms;
enum data_kind data_kind;
enum media_kind media_kind;
char *path;
/* Start time of the media item as rtp-time
The stream-start is the rtp-time the media item did or would have
started playing (after seek or pause), therefor the elapsed time of the
media item is always:
elapsed time = current rtptime - stream-start */
uint64_t stream_start;
// Flags that the input has been opened (i.e. needs to be closed)
bool open;
/* Output start time of the media item as rtp-time
The output start time is the rtp-time of the first audio packet send
to the audio outputs.
It differs from stream-start especially after a seek, where the first audio
packet has the next rtp-time as output start and stream start becomes the
rtp-time the media item would have been started playing if the seek did
not happen. */
uint64_t output_start;
/* End time of media item as rtp-time
The end time is set if the reading (source_read) of the media item reached
end of file, until then it is 0. */
uint64_t end;
/* Opaque pointer to data that the input sets up when called with setup(), and
* which is cleaned up by the input with stop()
*/
// The below is private data for the input backend. It is optional for the
// backend to use, so nothing in the input or player should depend on it!
//
// Opaque pointer to data that the input backend sets up when start() is
// called, and that is cleaned up by the backend when stop() is called
void *input_ctx;
/* Input has completed setup of the source
*/
int setup_done;
struct player_source *play_next;
// Private evbuf. Alloc'ed by backend at start() and free'd at stop()
struct evbuffer *evbuf;
// Private source quality storage
struct media_quality quality;
};
typedef int (*input_cb)(void);
struct input_metadata
{
// queue_item id
uint32_t item_id;
int startup;
// Input can override the default player progress by setting this
// FIXME only implemented for Airplay speakers currently
uint32_t pos_ms;
uint64_t rtptime;
uint64_t offset;
// The player will update queue_item with the below
uint32_t song_length;
// Sets new song length (input will also update queue_item)
uint32_t len_ms;
// Input can update queue_item with the below
char *artist;
char *title;
char *album;
char *genre;
char *artwork_url;
// Indicates whether we are starting playback. Just passed on to output.
int startup;
};
struct input_definition
@ -112,65 +104,75 @@ struct input_definition
char disabled;
// Prepare a playback session
int (*setup)(struct player_source *ps);
int (*setup)(struct input_source *source);
// Starts playback loop (must be defined)
int (*start)(struct player_source *ps);
// One iteration of the playback loop (= a read operation from source)
int (*play)(struct input_source *source);
// Cleans up when playback loop has ended
int (*stop)(struct player_source *ps);
// Cleans up (only required when stopping source before it ends itself)
int (*stop)(struct input_source *source);
// Changes the playback position
int (*seek)(struct player_source *ps, int seek_ms);
int (*seek)(struct input_source *source, int seek_ms);
// Return metadata
int (*metadata_get)(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime);
int (*metadata_get)(struct input_metadata *metadata, struct input_source *source);
// Initialization function called during startup
int (*init)(void);
// Deinitialization function called at shutdown
void (*deinit)(void);
};
/*
* Input modules should use this to test if playback should end
*/
int input_loop_break;
/* ---------------------- Interface towards input backends ------------------ */
/* Thread: input and spotify */
/*
* Transfer stream data to the player's input buffer. The input evbuf will be
* drained on succesful write. This is to avoid copying memory. If the player's
* input buffer is full the function will block until the write can be made
* (unless INPUT_FILE_NONBLOCK is set).
* Transfer stream data to the player's input buffer. Data must be PCM-LE
* samples. The input evbuf will be drained on succesful write. This is to avoid
* copying memory.
*
* @in evbuf Raw audio data to write
* @in evbuf Raw PCM_LE audio data to write
* @in evbuf Quality of the PCM (sample rate etc.)
* @in flags One or more INPUT_FLAG_*
* @return 0 on success, EAGAIN if buffer was full (and _NONBLOCK is set),
* -1 on error
* @return 0 on success, EAGAIN if buffer was full, -1 on error
*/
int
input_write(struct evbuffer *evbuf, short flags);
input_write(struct evbuffer *evbuf, struct media_quality *quality, short flags);
/*
* Input modules can use this to wait in the playback loop (like input_write()
* would have done)
* Input modules can use this to wait for the input_buffer to be ready for
* writing. The wait is max INPUT_LOOP_TIMEOUT, which allows the event base to
* loop and process pending commands once in a while.
*/
void
int
input_wait(void);
/*
* Async switch to the next song in the queue. Mostly for internal use, but
* might be relevant some day externally?
*/
//void
//input_next(void);
/* ---------------------- Interface towards player thread ------------------- */
/* Thread: player */
/*
* Move a chunk of stream data from the player's input buffer to an output
* buffer. Should only be called by the player thread. Will not block.
*
* @in data Output buffer
* @in size How much data to move to the output buffer
* @out flags Flags INPUT_FLAG_*
* @out flag Flag INPUT_FLAG_*
* @out flagdata Data associated with the flag, e.g. quality or metadata struct
* @return Number of bytes moved, -1 on error
*/
int
input_read(void *data, size_t size, short *flags);
input_read(void *data, size_t size, short *flag, void **flagdata);
/*
* Player can set this to get a callback from the input when the input buffer
@ -182,39 +184,31 @@ void
input_buffer_full_cb(input_cb cb);
/*
* Initializes the given player source for playback
* Tells the input to start, i.e. after calling this function the input buffer
* will begin to fill up, and should be read periodically with input_read(). If
* called while another item is still open, it will be closed and the input
* buffer will be flushed. This operation blocks.
*
* @in item_id Queue item id to start playing
* @in seek_ms Position to start playing
* @return Actual seek position if seekable, 0 otherwise, -1 on error
*/
int
input_setup(struct player_source *ps);
input_seek(uint32_t item_id, int seek_ms);
/*
* Tells the input to start or resume playback, i.e. after calling this function
* the input buffer will begin to fill up, and should be read periodically with
* input_read(). Before calling this input_setup() must have been called.
* Same as input_seek(), just non-blocking and does not offer seek.
*
* @in item_id Queue item id to start playing
*/
int
input_start(struct player_source *ps);
void
input_start(uint32_t item_id);
/*
* Pauses playback of the given player source (stops playback loop) and flushes
* the input buffer
* Stops the input and clears everything. Flushes the input buffer.
*/
int
input_pause(struct player_source *ps);
/*
* Stops playback loop (if running), flushes input buffer and cleans up the
* player source
*/
int
input_stop(struct player_source *ps);
/*
* Seeks playback position to seek_ms. Returns actual seek position, 0 on
* unseekable, -1 on error. May block.
*/
int
input_seek(struct player_source *ps, int seek_ms);
void
input_stop(void);
/*
* Flush input buffer. Output flags will be the same as input_read().
@ -222,12 +216,6 @@ input_seek(struct player_source *ps, int seek_ms);
void
input_flush(short *flags);
/*
* Gets metadata from the input, returns 0 if metadata is set, otherwise -1
*/
int
input_metadata_get(struct input_metadata *metadata, struct player_source *ps, int startup, uint64_t rtptime);
/*
* Free the entire struct
*/

View File

@ -26,93 +26,107 @@
#include "transcode.h"
#include "http.h"
#include "misc.h"
#include "logger.h"
#include "input.h"
static int
setup(struct player_source *ps)
setup(struct input_source *source)
{
ps->input_ctx = transcode_setup(XCODE_PCM16_NOHEADER, ps->data_kind, ps->path, ps->len_ms, NULL);
if (!ps->input_ctx)
struct transcode_ctx *ctx;
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms, NULL);
if (!ctx)
return -1;
ps->setup_done = 1;
CHECK_NULL(L_PLAYER, source->evbuf = evbuffer_new());
source->quality.sample_rate = transcode_encode_query(ctx->encode_ctx, "sample_rate");
source->quality.bits_per_sample = transcode_encode_query(ctx->encode_ctx, "bits_per_sample");
source->quality.channels = transcode_encode_query(ctx->encode_ctx, "channels");
source->input_ctx = ctx;
return 0;
}
static int
setup_http(struct player_source *ps)
setup_http(struct input_source *source)
{
char *url;
if (http_stream_setup(&url, ps->path) < 0)
if (http_stream_setup(&url, source->path) < 0)
return -1;
free(ps->path);
ps->path = url;
free(source->path);
source->path = url;
return setup(ps);
return setup(source);
}
static int
start(struct player_source *ps)
stop(struct input_source *source)
{
struct evbuffer *evbuf;
short flags;
int ret;
int icy_timer;
evbuf = evbuffer_new();
ret = -1;
flags = 0;
while (!input_loop_break && !(flags & INPUT_FLAG_EOF))
{
// We set "wanted" to 1 because the read size doesn't matter to us
// TODO optimize?
ret = transcode(evbuf, &icy_timer, ps->input_ctx, 1);
if (ret < 0)
break;
flags = ((ret == 0) ? INPUT_FLAG_EOF : 0) |
(icy_timer ? INPUT_FLAG_METADATA : 0);
ret = input_write(evbuf, flags);
if (ret < 0)
break;
}
evbuffer_free(evbuf);
return ret;
}
static int
stop(struct player_source *ps)
{
struct transcode_ctx *ctx = ps->input_ctx;
struct transcode_ctx *ctx = source->input_ctx;
transcode_cleanup(&ctx);
ps->input_ctx = NULL;
ps->setup_done = 0;
if (source->evbuf)
evbuffer_free(source->evbuf);
source->input_ctx = NULL;
source->evbuf = NULL;
return 0;
}
static int
seek(struct player_source *ps, int seek_ms)
play(struct input_source *source)
{
return transcode_seek(ps->input_ctx, seek_ms);
struct transcode_ctx *ctx = source->input_ctx;
int icy_timer;
int ret;
short flags;
ret = input_wait();
if (ret < 0)
return 0; // Loop, input_buffer is not ready for writing
// We set "wanted" to 1 because the read size doesn't matter to us
// TODO optimize?
ret = transcode(source->evbuf, &icy_timer, ctx, 1);
if (ret == 0)
{
input_write(source->evbuf, &source->quality, INPUT_FLAG_EOF);
stop(source);
return -1;
}
else if (ret < 0)
{
input_write(NULL, NULL, INPUT_FLAG_ERROR);
stop(source);
return -1;
}
flags = (icy_timer ? INPUT_FLAG_METADATA : 0);
input_write(source->evbuf, &source->quality, flags);
return 0;
}
static int
metadata_get_http(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
seek(struct input_source *source, int seek_ms)
{
return transcode_seek(source->input_ctx, seek_ms);
}
static int
metadata_get_http(struct input_metadata *metadata, struct input_source *source)
{
struct http_icy_metadata *m;
int changed;
m = transcode_metadata(ps->input_ctx, &changed);
m = transcode_metadata(source->input_ctx, &changed);
if (!m)
return -1;
@ -140,7 +154,7 @@ struct input_definition input_file =
.type = INPUT_TYPE_FILE,
.disabled = 0,
.setup = setup,
.start = start,
.play = play,
.stop = stop,
.seek = seek,
};
@ -151,7 +165,7 @@ struct input_definition input_http =
.type = INPUT_TYPE_HTTP,
.disabled = 0,
.setup = setup_http,
.start = start,
.play = play,
.stop = stop,
.metadata_get = metadata_get_http,
};

View File

@ -103,6 +103,9 @@ static pthread_t tid_pipe;
static struct event_base *evbase_pipe;
static struct commands_base *cmdbase;
// From config - the sample rate and bps of the pipe input
static int pipe_sample_rate;
static int pipe_bits_per_sample;
// From config - should we watch library pipes for data or only start on request
static int pipe_autostart;
// The mfi id of the pipe autostarted by the pipe thread
@ -122,7 +125,7 @@ static pthread_mutex_t pipe_metadata_lock;
// True if there is new metadata to push to the player
static bool pipe_metadata_is_new;
/* -------------------------------- HELPERS ------------------------------- */
/* -------------------------------- HELPERS --------------------------------- */
static struct pipe *
pipe_create(const char *path, int id, enum pipetype type, event_callback_fn cb)
@ -305,9 +308,10 @@ parse_progress(struct input_metadata *m, char *progress)
if (!start || !pos || !end)
return;
m->rtptime = start; // Not actually used - we have our own rtptime
m->offset = (pos > start) ? (pos - start) : 0;
m->song_length = (end - start) * 10 / 441; // Convert to ms based on 44100
if (pos > start)
m->pos_ms = (pos - start) * 1000 / pipe_sample_rate;
if (end > start)
m->len_ms = (end - start) * 1000 / pipe_sample_rate;
}
static void
@ -497,8 +501,8 @@ pipe_metadata_parse(struct input_metadata *m, struct evbuffer *evbuf)
}
/* ----------------------------- PIPE WATCHING ---------------------------- */
/* Thread: pipe */
/* ------------------------------ PIPE WATCHING ----------------------------- */
/* Thread: pipe */
// Some data arrived on a pipe we watch - let's autostart playback
static void
@ -614,8 +618,8 @@ pipe_thread_run(void *arg)
}
/* -------------------------- METADATA PIPE HANDLING ---------------------- */
/* Thread: worker */
/* --------------------------- METADATA PIPE HANDLING ----------------------- */
/* Thread: worker */
static void
pipe_metadata_watch_del(void *arg)
@ -703,8 +707,8 @@ pipe_metadata_watch_add(void *arg)
}
/* ---------------------- PIPE WATCH THREAD START/STOP -------------------- */
/* Thread: filescanner */
/* ----------------------- PIPE WATCH THREAD START/STOP --------------------- */
/* Thread: filescanner */
static void
pipe_thread_start(void)
@ -799,87 +803,47 @@ pipe_listener_cb(short event_mask)
}
/* -------------------------- PIPE INPUT INTERFACE ------------------------ */
/* Thread: player/input */
/* --------------------------- PIPE INPUT INTERFACE ------------------------- */
/* Thread: input */
static int
setup(struct player_source *ps)
setup(struct input_source *source)
{
struct pipe *pipe;
int fd;
fd = pipe_open(ps->path, 0);
fd = pipe_open(source->path, 0);
if (fd < 0)
return -1;
CHECK_NULL(L_PLAYER, pipe = pipe_create(ps->path, ps->id, PIPE_PCM, NULL));
CHECK_NULL(L_PLAYER, pipe = pipe_create(source->path, source->id, PIPE_PCM, NULL));
CHECK_NULL(L_PLAYER, source->evbuf = evbuffer_new());
pipe->fd = fd;
pipe->is_autostarted = (ps->id == pipe_autostart_id);
pipe->is_autostarted = (source->id == pipe_autostart_id);
worker_execute(pipe_metadata_watch_add, ps->path, strlen(ps->path) + 1, 0);
worker_execute(pipe_metadata_watch_add, source->path, strlen(source->path) + 1, 0);
ps->input_ctx = pipe;
ps->setup_done = 1;
source->input_ctx = pipe;
source->quality.sample_rate = pipe_sample_rate;
source->quality.bits_per_sample = pipe_bits_per_sample;
source->quality.channels = 2;
return 0;
}
static int
start(struct player_source *ps)
stop(struct input_source *source)
{
struct pipe *pipe = ps->input_ctx;
struct evbuffer *evbuf;
short flags;
int ret;
evbuf = evbuffer_new();
if (!evbuf)
{
DPRINTF(E_LOG, L_PLAYER, "Out of memory for pipe evbuf\n");
return -1;
}
ret = -1;
while (!input_loop_break)
{
ret = evbuffer_read(evbuf, pipe->fd, PIPE_READ_MAX);
if ((ret == 0) && (pipe->is_autostarted))
{
input_write(evbuf, INPUT_FLAG_EOF); // Autostop
break;
}
else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN)))
{
input_wait();
continue;
}
else if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe '%s': %s\n", ps->path, strerror(errno));
break;
}
flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0);
pipe_metadata_is_new = 0;
ret = input_write(evbuf, flags);
if (ret < 0)
break;
}
evbuffer_free(evbuf);
return ret;
}
static int
stop(struct player_source *ps)
{
struct pipe *pipe = ps->input_ctx;
struct pipe *pipe = source->input_ctx;
union pipe_arg *cmdarg;
DPRINTF(E_DBG, L_PLAYER, "Stopping pipe\n");
if (source->evbuf)
evbuffer_free(source->evbuf);
pipe_close(pipe->fd);
// Reset the pipe and start watching it again for new data. Must be async or
@ -895,37 +859,56 @@ stop(struct player_source *ps)
pipe_free(pipe);
ps->input_ctx = NULL;
ps->setup_done = 0;
source->input_ctx = NULL;
source->evbuf = NULL;
return 0;
}
static int
metadata_get(struct input_metadata *metadata, struct player_source *ps, uint64_t rtptime)
play(struct input_source *source)
{
struct pipe *pipe = source->input_ctx;
short flags;
int ret;
ret = input_wait();
if (ret < 0)
return 0; // Loop, input_buffer is not ready for writing
ret = evbuffer_read(source->evbuf, pipe->fd, PIPE_READ_MAX);
if ((ret == 0) && (pipe->is_autostarted))
{
input_write(source->evbuf, NULL, INPUT_FLAG_EOF); // Autostop
stop(source);
return -1;
}
else if ((ret == 0) || ((ret < 0) && (errno == EAGAIN)))
{
return 0; // Loop
}
else if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not read from pipe '%s': %s\n", source->path, strerror(errno));
input_write(NULL, NULL, INPUT_FLAG_ERROR);
stop(source);
return -1;
}
flags = (pipe_metadata_is_new ? INPUT_FLAG_METADATA : 0);
pipe_metadata_is_new = 0;
input_write(source->evbuf, &source->quality, flags);
return 0;
}
static int
metadata_get(struct input_metadata *metadata, struct input_source *source)
{
pthread_mutex_lock(&pipe_metadata_lock);
if (pipe_metadata_parsed.artist)
swap_pointers(&metadata->artist, &pipe_metadata_parsed.artist);
if (pipe_metadata_parsed.title)
swap_pointers(&metadata->title, &pipe_metadata_parsed.title);
if (pipe_metadata_parsed.album)
swap_pointers(&metadata->album, &pipe_metadata_parsed.album);
if (pipe_metadata_parsed.genre)
swap_pointers(&metadata->genre, &pipe_metadata_parsed.genre);
if (pipe_metadata_parsed.artwork_url)
swap_pointers(&metadata->artwork_url, &pipe_metadata_parsed.artwork_url);
if (pipe_metadata_parsed.song_length)
{
if (rtptime > ps->stream_start)
metadata->rtptime = rtptime - pipe_metadata_parsed.offset;
metadata->offset = pipe_metadata_parsed.offset;
metadata->song_length = pipe_metadata_parsed.song_length;
}
input_metadata_free(&pipe_metadata_parsed, 1);
*metadata = pipe_metadata_parsed;
pthread_mutex_unlock(&pipe_metadata_lock);
@ -945,6 +928,20 @@ init(void)
CHECK_ERR(L_PLAYER, listener_add(pipe_listener_cb, LISTENER_DATABASE));
}
pipe_sample_rate = cfg_getint(cfg_getsec(cfg, "library"), "pipe_sample_rate");
if (pipe_sample_rate != 44100 && pipe_sample_rate != 48000 && pipe_sample_rate != 96000)
{
DPRINTF(E_FATAL, L_PLAYER, "The configuration of pipe_sample_rate is invalid: %d\n", pipe_sample_rate);
return -1;
}
pipe_bits_per_sample = cfg_getint(cfg_getsec(cfg, "library"), "pipe_bits_per_sample");
if (pipe_bits_per_sample != 16 && pipe_bits_per_sample != 24)
{
DPRINTF(E_FATAL, L_PLAYER, "The configuration of pipe_bits_per_sample is invalid: %d\n", pipe_bits_per_sample);
return -1;
}
return 0;
}
@ -966,7 +963,7 @@ struct input_definition input_pipe =
.type = INPUT_TYPE_PIPE,
.disabled = 0,
.setup = setup,
.start = start,
.play = play,
.stop = stop,
.metadata_get = metadata_get,
.init = init,

View File

@ -31,12 +31,12 @@
#define SPOTIFY_SETUP_RETRY_WAIT 500000
static int
setup(struct player_source *ps)
setup(struct input_source *source)
{
int i = 0;
int ret;
while((ret = spotify_playback_setup(ps->path)) == SPOTIFY_SETUP_ERROR_IS_LOADING)
while((ret = spotify_playback_setup(source->path)) == SPOTIFY_SETUP_ERROR_IS_LOADING)
{
if (i >= SPOTIFY_SETUP_RETRIES)
break;
@ -49,32 +49,15 @@ setup(struct player_source *ps)
if (ret < 0)
return -1;
ps->setup_done = 1;
ret = spotify_playback_play();
if (ret < 0)
return -1;
return 0;
}
static int
start(struct player_source *ps)
{
int ret;
ret = spotify_playback_play();
if (ret < 0)
return -1;
while (!input_loop_break)
{
input_wait();
}
ret = spotify_playback_pause();
return ret;
}
static int
stop(struct player_source *ps)
stop(struct input_source *source)
{
int ret;
@ -82,13 +65,11 @@ stop(struct player_source *ps)
if (ret < 0)
return -1;
ps->setup_done = 0;
return 0;
}
static int
seek(struct player_source *ps, int seek_ms)
seek(struct input_source *source, int seek_ms)
{
int ret;
@ -105,7 +86,6 @@ struct input_definition input_spotify =
.type = INPUT_TYPE_SPOTIFY,
.disabled = 0,
.setup = setup,
.start = start,
.stop = stop,
.seek = seek,
};

View File

@ -416,21 +416,13 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi)
for (i = 0; i < ctx->nb_streams; i++)
{
#if HAVE_DECL_AVCODEC_PARAMETERS_FROM_CONTEXT
codec_type = ctx->streams[i]->codecpar->codec_type;
codec_id = ctx->streams[i]->codecpar->codec_id;
sample_rate = ctx->streams[i]->codecpar->sample_rate;
sample_fmt = ctx->streams[i]->codecpar->format;
#else
codec_type = ctx->streams[i]->codec->codec_type;
codec_id = ctx->streams[i]->codec->codec_id;
sample_rate = ctx->streams[i]->codec->sample_rate;
sample_fmt = ctx->streams[i]->codec->sample_fmt;
#endif
switch (codec_type)
{
case AVMEDIA_TYPE_VIDEO:
#if LIBAVFORMAT_VERSION_MAJOR >= 55 || (LIBAVFORMAT_VERSION_MAJOR == 54 && LIBAVFORMAT_VERSION_MINOR >= 6)
if (ctx->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)
{
DPRINTF(E_DBG, L_SCAN, "Found embedded artwork (stream %d)\n", i);
@ -438,7 +430,7 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi)
break;
}
#endif
// We treat these as audio no matter what
if (mfi->compilation || (mfi->media_kind & (MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK)))
break;

View File

@ -1040,6 +1040,46 @@ murmur_hash64(const void *key, int len, uint32_t seed)
#endif
int
linear_regression(double *m, double *b, double *r2, const double *x, const double *y, int n)
{
double x_val;
double sum_x = 0;
double sum_x2 = 0;
double sum_y = 0;
double sum_y2 = 0;
double sum_xy = 0;
double denom;
int i;
for (i = 0; i < n; i++)
{
x_val = x ? x[i] : (double)i;
sum_x += x_val;
sum_x2 += x_val * x_val;
sum_y += y[i];
sum_y2 += y[i] * y[i];
sum_xy += x_val * y[i];
}
denom = (n * sum_x2 - sum_x * sum_x);
if (denom == 0)
return -1;
*m = (n * sum_xy - sum_x * sum_y) / denom;
*b = (sum_y * sum_x2 - sum_x * sum_xy) / denom;
if (r2)
*r2 = (sum_xy - (sum_x * sum_y)/n) * (sum_xy - (sum_x * sum_y)/n) / ((sum_x2 - (sum_x * sum_x)/n) * (sum_y2 - (sum_y * sum_y)/n));
return 0;
}
bool
quality_is_equal(struct media_quality *a, struct media_quality *b)
{
return (a->sample_rate == b->sample_rate && a->bits_per_sample == b->bits_per_sample && a->channels == b->channels);
}
bool
peer_address_is_trusted(const char *addr)
{
@ -1077,6 +1117,86 @@ peer_address_is_trusted(const char *addr)
return false;
}
int
ringbuffer_init(struct ringbuffer *buf, size_t size)
{
memset(buf, 0, sizeof(struct ringbuffer));
CHECK_NULL(L_MISC, buf->buffer = malloc(size));
buf->size = size;
buf->write_avail = size;
return 0;
}
void
ringbuffer_free(struct ringbuffer *buf, bool content_only)
{
if (!buf)
return;
free(buf->buffer);
if (content_only)
memset(buf, 0, sizeof(struct ringbuffer));
else
free(buf);
}
size_t
ringbuffer_write(struct ringbuffer *buf, const void* src, size_t srclen)
{
int remaining;
if (buf->write_avail == 0 || srclen == 0)
return 0;
if (srclen > buf->write_avail)
srclen = buf->write_avail;
remaining = buf->size - buf->write_pos;
if (srclen > remaining)
{
memcpy(buf->buffer + buf->write_pos, src, remaining);
memcpy(buf->buffer, src + remaining, srclen - remaining);
}
else
{
memcpy(buf->buffer + buf->write_pos, src, srclen);
}
buf->write_pos = (buf->write_pos + srclen) % buf->size;
buf->write_avail -= srclen;
buf->read_avail += srclen;
return srclen;
}
size_t
ringbuffer_read(uint8_t **dst, size_t dstlen, struct ringbuffer *buf)
{
int remaining;
*dst = buf->buffer + buf->read_pos;
if (buf->read_avail == 0 || dstlen == 0)
return 0;
remaining = buf->size - buf->read_pos;
// The number of bytes we will return will be MIN(dstlen, remaining, read_avail)
if (dstlen > remaining)
dstlen = remaining;
if (dstlen > buf->read_avail)
dstlen = buf->read_avail;
buf->read_pos = (buf->read_pos + dstlen) % buf->size;
buf->write_avail += dstlen;
buf->read_avail -= dstlen;
return dstlen;
}
int
clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res)

View File

@ -12,11 +12,27 @@
#include <pthread.h>
/* Samples to bytes, bytes to samples */
#define STOB(s) ((s) * 4)
#define BTOS(b) ((b) / 4)
#define STOB(s, bits, c) ((s) * (c) * (bits) / 8)
#define BTOS(b, bits, c) ((b) / ((c) * (bits) / 8))
#define ARRAY_SIZE(x) ((unsigned int)(sizeof(x) / sizeof((x)[0])))
#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
# define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
// Remember to adjust quality_is_equal() if adding elements
struct media_quality {
int sample_rate;
int bits_per_sample;
int channels;
};
struct onekeyval {
char *name;
char *value;
@ -30,6 +46,15 @@ struct keyval {
struct onekeyval *tail;
};
struct ringbuffer {
uint8_t *buffer;
size_t size;
size_t write_avail;
size_t read_avail;
size_t write_pos;
size_t read_pos;
};
char **
buildopts_get(void);
@ -114,10 +139,28 @@ b64_encode(const uint8_t *in, size_t len);
uint64_t
murmur_hash64(const void *key, int len, uint32_t seed);
int
linear_regression(double *m, double *b, double *r, const double *x, const double *y, int n);
bool
quality_is_equal(struct media_quality *a, struct media_quality *b);
// Checks if the address is in a network that is configured as trusted
bool
peer_address_is_trusted(const char *addr);
int
ringbuffer_init(struct ringbuffer *buf, size_t size);
void
ringbuffer_free(struct ringbuffer *buf, bool content_only);
size_t
ringbuffer_write(struct ringbuffer *buf, const void* src, size_t srclen);
size_t
ringbuffer_read(uint8_t **dst, size_t dstlen, struct ringbuffer *buf);
#ifndef HAVE_CLOCK_GETTIME

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,11 @@
#ifndef __OUTPUTS_H__
#define __OUTPUTS_H__
#include <stdbool.h>
#include <time.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include "misc.h"
/* Outputs is a generic interface between the player and a media output method,
* like for instance AirPlay (raop) or ALSA. The purpose of the interface is to
@ -19,7 +23,6 @@
* Here is the sequence of commands from the player to the outputs, and the
* callback from the output once the command has been executed. Commands marked
* with * may make multiple callbacks if multiple sessions are affected.
* (TODO should callbacks always be deferred?)
*
* PLAYER OUTPUT PLAYER CB
* speaker_activate -> device_start -> device_activate_cb
@ -46,6 +49,28 @@
*
*/
// If an output requires a specific quality (like Airplay 1 devices often
// require 44100/16) then it should make a subscription request to the output
// module, which will then make sure to include this quality when it writes the
// audio. The below sets the maximum number of *different* subscriptions
// allowed. Note that multiple outputs requesting the *same* quality only counts
// as one.
#define OUTPUTS_MAX_QUALITY_SUBSCRIPTIONS 5
// Number of seconds the outputs should buffer before starting playback. Note
// this value cannot freely be changed because 1) some Airplay devices ignore
// the values we give and stick to 2 seconds, 2) those devices that can handle
// different values can only do so within a limited range (maybe max 3 secs)
#define OUTPUTS_BUFFER_DURATION 2
// Forward declarations
struct output_device;
struct output_metadata;
enum output_device_state;
typedef void (*output_status_cb)(struct output_device *device, enum output_device_state status);
typedef int (*output_metadata_finalize_cb)(struct output_metadata *metadata);
// Must be in sync with outputs[] in outputs.c
enum output_types
{
@ -113,35 +138,59 @@ struct output_device
int volume;
int relvol;
// Quality of audio output
struct media_quality quality;
// Address
char *v4_address;
char *v6_address;
short v4_port;
short v6_port;
struct event *stop_timer;
// Opaque pointers to device and session data
void *extra_device_info;
struct output_session *session;
void *session;
struct output_device *next;
};
// Except for the type, sessions are opaque outside of the output backend
struct output_session
{
enum output_types type;
void *session;
};
// Linked list of metadata prepared by each output backend
struct output_metadata
{
enum output_types type;
void *metadata;
struct output_metadata *next;
uint32_t item_id;
// Progress data, filled out by finalize_cb()
uint32_t pos_ms;
uint32_t len_ms;
struct timespec pts;
bool startup;
// Private output data made by the metadata_prepare()
void *priv;
struct event *ev;
// Finalize before right before sending, e.g. set playback position
output_metadata_finalize_cb finalize_cb;
};
typedef void (*output_status_cb)(struct output_device *device, struct output_session *session, enum output_device_state status);
struct output_data
{
struct media_quality quality;
struct evbuffer *evbuf;
uint8_t *buffer;
size_t bufsize;
int samples;
};
struct output_buffer
{
uint32_t write_counter; // REMOVE ME? not used for anything
struct timespec pts;
struct output_data data[OUTPUTS_MAX_QUALITY_SUBSCRIPTIONS + 1];
} output_buffer;
struct output_definition
{
@ -166,94 +215,137 @@ struct output_definition
void (*deinit)(void);
// Prepare a playback session on device and call back
int (*device_start)(struct output_device *device, output_status_cb cb, uint64_t rtptime);
int (*device_start)(struct output_device *device, int callback_id);
// Close a session prepared by device_start
void (*device_stop)(struct output_session *session);
// Close a session prepared by device_start and call back
int (*device_stop)(struct output_device *device, int callback_id);
// Flush device session and call back
int (*device_flush)(struct output_device *device, int callback_id);
// Test the connection to a device and call back
int (*device_probe)(struct output_device *device, output_status_cb cb);
// Free the private device data
void (*device_free_extra)(struct output_device *device);
int (*device_probe)(struct output_device *device, int callback_id);
// Set the volume and call back
int (*device_volume_set)(struct output_device *device, output_status_cb cb);
int (*device_volume_set)(struct output_device *device, int callback_id);
// Convert device internal representation of volume to our pct scale
int (*device_volume_to_pct)(struct output_device *device, const char *volume);
// Start/stop playback on devices that were started
void (*playback_start)(uint64_t next_pkt, struct timespec *ts);
void (*playback_stop)(void);
// Request a change of quality from the device
int (*device_quality_set)(struct output_device *device, struct media_quality *quality, int callback_id);
// Change the call back associated with a device
void (*device_cb_set)(struct output_device *device, int callback_id);
// Free the private device data
void (*device_free_extra)(struct output_device *device);
// Write stream data to the output devices
void (*write)(uint8_t *buf, uint64_t rtptime);
// Flush all sessions, the return must be number of sessions pending the flush
int (*flush)(output_status_cb cb, uint64_t rtptime);
void (*write)(struct output_buffer *buffer);
// Authorize an output with a pin-code (probably coming from the filescanner)
void (*authorize)(const char *pin);
// Change the call back associated with a session
void (*status_cb)(struct output_session *session, output_status_cb cb);
// Called from worker thread for async preparation of metadata (e.g. getting
// artwork, which might involce downloading image data). The prepared data is
// saved to metadata->data, which metadata_send() can use.
void *(*metadata_prepare)(struct output_metadata *metadata);
// Metadata
void *(*metadata_prepare)(int id);
void (*metadata_send)(void *metadata, uint64_t rtptime, uint64_t offset, int startup);
// Send metadata to outputs. Ownership of *metadata is transferred.
void (*metadata_send)(struct output_metadata *metadata);
// Output will cleanup all metadata (so basically like flush but for metadata)
void (*metadata_purge)(void);
void (*metadata_prune)(uint64_t rtptime);
};
// Our main list of devices, not for use by backend modules
struct output_device *output_device_list;
/* ------------------------------- General use ------------------------------ */
struct output_device *
outputs_device_get(uint64_t device_id);
/* ----------------------- Called by backend modules ------------------------ */
int
outputs_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime);
outputs_device_session_add(uint64_t device_id, void *session);
void
outputs_device_stop(struct output_session *session);
outputs_device_session_remove(uint64_t device_id);
int
outputs_quality_subscribe(struct media_quality *quality);
void
outputs_quality_unsubscribe(struct media_quality *quality);
void
outputs_cb(int callback_id, uint64_t device_id, enum output_device_state);
void
outputs_listener_notify(void);
/* ---------------------------- Called by player ---------------------------- */
// Ownership of *add is transferred, so don't address after calling. Instead you
// can address the return value (which is not the same if the device was already
// in the list.
struct output_device *
outputs_device_add(struct output_device *add, bool new_deselect, int default_volume);
void
outputs_device_remove(struct output_device *remove);
int
outputs_device_start(struct output_device *device, output_status_cb cb);
int
outputs_device_stop(struct output_device *device, output_status_cb cb);
int
outputs_device_stop_delayed(struct output_device *device, output_status_cb cb);
int
outputs_device_flush(struct output_device *device, output_status_cb cb);
int
outputs_device_probe(struct output_device *device, output_status_cb cb);
void
outputs_device_free(struct output_device *device);
int
outputs_device_volume_set(struct output_device *device, output_status_cb cb);
int
outputs_device_volume_to_pct(struct output_device *device, const char *value);
void
outputs_playback_start(uint64_t next_pkt, struct timespec *ts);
int
outputs_device_quality_set(struct output_device *device, struct media_quality *quality, output_status_cb cb);
void
outputs_playback_stop(void);
outputs_device_cb_set(struct output_device *device, output_status_cb cb);
void
outputs_write(uint8_t *buf, uint64_t rtptime);
outputs_device_free(struct output_device *device);
int
outputs_flush(output_status_cb cb, uint64_t rtptime);
outputs_flush(output_status_cb cb);
int
outputs_stop(output_status_cb cb);
int
outputs_stop_delayed_cancel(void);
void
outputs_status_cb(struct output_session *session, output_status_cb cb);
struct output_metadata *
outputs_metadata_prepare(int id);
outputs_write(void *buf, size_t bufsize, int nsamples, struct media_quality *quality, struct timespec *pts);
void
outputs_metadata_send(struct output_metadata *omd, uint64_t rtptime, uint64_t offset, int startup);
outputs_metadata_send(uint32_t item_id, bool startup, output_metadata_finalize_cb cb);
void
outputs_metadata_purge(void);
void
outputs_metadata_prune(uint64_t rtptime);
void
outputs_metadata_free(struct output_metadata *omd);
void
outputs_authorize(enum output_types type, const char *pin);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -33,8 +33,7 @@
#include <stdint.h>
#include <inttypes.h>
#include <event2/event.h>
#include "misc.h"
#include "conffile.h"
#include "logger.h"
#include "player.h"
@ -44,24 +43,12 @@ struct dummy_session
{
enum output_device_state state;
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;
uint64_t device_id;
int callback_id;
};
/* From player.c */
extern struct event_base *evbase_player;
struct dummy_session *sessions;
/* Forwards */
static void
defer_cb(int fd, short what, void *arg);
/* ---------------------------- SESSION HANDLING ---------------------------- */
static void
@ -70,9 +57,6 @@ dummy_session_free(struct dummy_session *ds)
if (!ds)
return;
event_free(ds->deferredev);
free(ds->output_session);
free(ds);
}
@ -82,86 +66,50 @@ dummy_session_cleanup(struct dummy_session *ds)
// Normally some here code to remove from linked list - here we just say:
sessions = NULL;
outputs_device_session_remove(ds->device_id);
dummy_session_free(ds);
}
static struct dummy_session *
dummy_session_make(struct output_device *device, output_status_cb cb)
dummy_session_make(struct output_device *device, int callback_id)
{
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;
}
CHECK_NULL(L_LAUDIO, ds = calloc(1, sizeof(struct dummy_session)));
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;
}
ds->deferredev = evtimer_new(evbase_player, defer_cb, ds);
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;
ds->device_id = device->id;
ds->callback_id = callback_id;
sessions = ds;
outputs_device_session_add(device->id, ds);
return ds;
}
/* ---------------------------- STATUS HANDLERS ----------------------------- */
// Maps our internal state to the generic output state and then makes a callback
// to the player to tell that state
static void
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);
if (ds->state == OUTPUT_STATE_STOPPED)
dummy_session_cleanup(ds);
}
static void
dummy_status(struct dummy_session *ds)
{
ds->defer_cb = ds->status_cb;
event_active(ds->deferredev, 0, 0);
ds->status_cb = NULL;
outputs_cb(ds->callback_id, ds->device_id, ds->state);
if (ds->state == OUTPUT_STATE_STOPPED)
dummy_session_cleanup(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, int callback_id)
{
struct dummy_session *ds;
ds = dummy_session_make(device, cb);
ds = dummy_session_make(device, callback_id);
if (!ds)
return -1;
@ -170,25 +118,12 @@ 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)
{
struct dummy_session *ds = session->session;
ds->state = OUTPUT_STATE_STOPPED;
dummy_status(ds);
}
static int
dummy_device_probe(struct output_device *device, output_status_cb cb)
dummy_device_stop(struct output_device *device, int callback_id)
{
struct dummy_session *ds;
struct dummy_session *ds = device->session;
ds = dummy_session_make(device, cb);
if (!ds)
return -1;
ds->status_cb = cb;
ds->callback_id = callback_id;
ds->state = OUTPUT_STATE_STOPPED;
dummy_status(ds);
@ -197,51 +132,55 @@ 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)
dummy_device_flush(struct output_device *device, int callback_id)
{
struct dummy_session *ds = device->session;
ds->callback_id = callback_id;
ds->state = OUTPUT_STATE_STOPPED;
dummy_status(ds);
return 0;
}
static int
dummy_device_probe(struct output_device *device, int callback_id)
{
struct dummy_session *ds;
if (!device->session || !device->session->session)
ds = dummy_session_make(device, callback_id);
if (!ds)
return -1;
ds->callback_id = callback_id;
ds->state = OUTPUT_STATE_STOPPED;
dummy_status(ds);
return 0;
}
static int
dummy_device_volume_set(struct output_device *device, int callback_id)
{
struct dummy_session *ds = device->session;
if (!ds)
return 0;
ds = device->session->session;
ds->status_cb = cb;
ds->callback_id = callback_id;
dummy_status(ds);
return 1;
}
static void
dummy_playback_start(uint64_t next_pkt, struct timespec *ts)
dummy_device_cb_set(struct output_device *device, int callback_id)
{
struct dummy_session *ds = sessions;
struct dummy_session *ds = device->session;
if (!sessions)
return;
ds->state = OUTPUT_STATE_STREAMING;
dummy_status(ds);
}
static void
dummy_playback_stop(void)
{
struct dummy_session *ds = sessions;
if (!sessions)
return;
ds->state = OUTPUT_STATE_CONNECTED;
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;
ds->callback_id = callback_id;
}
static int
@ -259,18 +198,12 @@ dummy_init(void)
nickname = cfg_getstr(cfg_audio, "nickname");
device = calloc(1, sizeof(struct output_device));
if (!device)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for dummy device\n");
return -1;
}
CHECK_NULL(L_LAUDIO, device = calloc(1, sizeof(struct output_device)));
device->id = 0;
device->name = strdup(nickname);
device->type = OUTPUT_TYPE_DUMMY;
device->type_name = outputs_name(device->type);
device->advertised = 1;
device->has_video = 0;
DPRINTF(E_INFO, L_LAUDIO, "Adding dummy output device '%s'\n", nickname);
@ -296,9 +229,8 @@ struct output_definition output_dummy =
.deinit = dummy_deinit,
.device_start = dummy_device_start,
.device_stop = dummy_device_stop,
.device_flush = dummy_device_flush,
.device_probe = dummy_device_probe,
.device_volume_set = dummy_device_volume_set,
.playback_start = dummy_playback_start,
.playback_stop = dummy_playback_stop,
.status_cb = dummy_set_status_cb,
.device_cb_set = dummy_device_cb_set,
};

View File

@ -31,24 +31,23 @@
#include <sys/stat.h>
#include <unistd.h>
#include <event2/event.h>
#include "misc.h"
#include "conffile.h"
#include "logger.h"
#include "player.h"
#include "outputs.h"
#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
#define FIFO_BUFFER_SIZE 65536 // pipe capacity on Linux >= 2.6.11
#define FIFO_PACKET_SIZE 1408 // 352 samples/packet * 16 bit/sample * 2 channels
struct fifo_packet
{
/* pcm data */
uint8_t samples[1408]; // STOB(AIRTUNES_V2_PACKET_SAMPLES)
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 +58,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()
@ -91,24 +93,12 @@ struct fifo_session
int created;
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;
uint64_t device_id;
int callback_id;
};
/* From player.c */
extern struct event_base *evbase_player;
static struct fifo_session *sessions;
/* Forwards */
static void
defer_cb(int fd, short what, void *arg);
/* ---------------------------- FIFO HANDLING ---------------------------- */
@ -240,9 +230,6 @@ fifo_session_free(struct fifo_session *fifo_session)
if (!fifo_session)
return;
event_free(fifo_session->deferredev);
free(fifo_session->output_session);
free(fifo_session);
free_buffer();
}
@ -253,46 +240,21 @@ fifo_session_cleanup(struct fifo_session *fifo_session)
// Normally some here code to remove from linked list - here we just say:
sessions = NULL;
outputs_device_session_remove(fifo_session->device_id);
fifo_session_free(fifo_session);
}
static struct fifo_session *
fifo_session_make(struct output_device *device, output_status_cb cb)
fifo_session_make(struct output_device *device, int callback_id)
{
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;
}
CHECK_NULL(L_FIFO, fifo_session = calloc(1, sizeof(struct fifo_session)));
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;
}
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;
fifo_session->device_id = device->id;
fifo_session->callback_id = callback_id;
fifo_session->created = 0;
fifo_session->path = device->extra_device_info;
@ -301,44 +263,36 @@ fifo_session_make(struct output_device *device, output_status_cb cb)
sessions = fifo_session;
outputs_device_session_add(device->id, fifo_session);
return fifo_session;
}
/* ---------------------------- STATUS HANDLERS ----------------------------- */
// Maps our internal state to the generic output state and then makes a callback
// to the player to tell that state
static void
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);
if (ds->state == OUTPUT_STATE_STOPPED)
fifo_session_cleanup(ds);
}
static void
fifo_status(struct fifo_session *fifo_session)
{
fifo_session->defer_cb = fifo_session->status_cb;
event_active(fifo_session->deferredev, 0, 0);
fifo_session->status_cb = NULL;
}
outputs_cb(fifo_session->callback_id, fifo_session->device_id, fifo_session->state);
if (fifo_session->state == OUTPUT_STATE_STOPPED)
fifo_session_cleanup(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, int callback_id)
{
struct fifo_session *fifo_session;
int ret;
fifo_session = fifo_session_make(device, cb);
ret = outputs_quality_subscribe(&fifo_quality);
if (ret < 0)
return -1;
fifo_session = fifo_session_make(device, callback_id);
if (!fifo_session)
return -1;
@ -351,25 +305,46 @@ 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, int callback_id)
{
struct fifo_session *fifo_session = output_session->session;
struct fifo_session *fifo_session = device->session;
outputs_quality_unsubscribe(&fifo_quality);
fifo_session->callback_id = callback_id;
fifo_close(fifo_session);
free_buffer();
fifo_session->state = OUTPUT_STATE_STOPPED;
fifo_status(fifo_session);
return 0;
}
static int
fifo_device_probe(struct output_device *device, output_status_cb cb)
fifo_device_flush(struct output_device *device, int callback_id)
{
struct fifo_session *fifo_session = device->session;
fifo_empty(fifo_session);
free_buffer();
fifo_session->callback_id = callback_id;
fifo_session->state = OUTPUT_STATE_CONNECTED;
fifo_status(fifo_session);
return 0;
}
static int
fifo_device_probe(struct output_device *device, int callback_id)
{
struct fifo_session *fifo_session;
int ret;
fifo_session = fifo_session_make(device, cb);
fifo_session = fifo_session_make(device, callback_id);
if (!fifo_session)
return -1;
@ -382,7 +357,7 @@ fifo_device_probe(struct output_device *device, output_status_cb cb)
fifo_close(fifo_session);
fifo_session->status_cb = cb;
fifo_session->callback_id = callback_id;
fifo_session->state = OUTPUT_STATE_STOPPED;
fifo_status(fifo_session);
@ -391,104 +366,81 @@ 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)
fifo_device_volume_set(struct output_device *device, int callback_id)
{
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_session->callback_id = callback_id;
fifo_status(fifo_session);
return 1;
}
static void
fifo_playback_start(uint64_t next_pkt, struct timespec *ts)
fifo_device_cb_set(struct output_device *device, int callback_id)
{
struct fifo_session *fifo_session = device->session;
fifo_session->callback_id = callback_id;
}
static void
fifo_write(struct output_buffer *obuf)
{
struct fifo_session *fifo_session = sessions;
struct fifo_packet *packet;
struct timespec now;
ssize_t bytes;
int i;
if (!fifo_session)
return;
for (i = 0; obuf->data[i].buffer; i++)
{
if (quality_is_equal(&fifo_quality, &obuf->data[i].quality))
break;
}
if (!obuf->data[i].buffer)
{
DPRINTF(E_LOG, L_FIFO, "Bug! Did not get audio in quality required\n");
return;
}
fifo_session->state = OUTPUT_STATE_STREAMING;
fifo_status(fifo_session);
}
static void
fifo_playback_stop(void)
{
struct fifo_session *fifo_session = sessions;
CHECK_NULL(L_FIFO, packet = calloc(1, sizeof(struct fifo_packet)));
CHECK_NULL(L_FIFO, packet->samples = malloc(obuf->data[i].bufsize));
if (!fifo_session)
return;
memcpy(packet->samples, obuf->data[i].buffer, obuf->data[i].bufsize);
packet->samples_size = obuf->data[i].bufsize;
packet->pts = obuf->pts;
free_buffer();
fifo_session->state = OUTPUT_STATE_CONNECTED;
fifo_status(fifo_session);
}
static int
fifo_flush(output_status_cb cb, uint64_t rtptime)
{
struct fifo_session *fifo_session = sessions;
if (!fifo_session)
return 0;
fifo_empty(fifo_session);
free_buffer();
fifo_session->status_cb = cb;
fifo_session->state = OUTPUT_STATE_CONNECTED;
fifo_status(fifo_session);
return 1;
}
static void
fifo_write(uint8_t *buf, uint64_t rtptime)
{
struct fifo_session *fifo_session = sessions;
size_t length = STOB(AIRTUNES_V2_PACKET_SAMPLES);
ssize_t bytes;
struct fifo_packet *packet;
uint64_t cur_pos;
struct timespec now;
int ret;
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;
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 +463,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)
{
@ -539,18 +483,12 @@ fifo_init(void)
memset(&buffer, 0, sizeof(struct fifo_buffer));
device = calloc(1, sizeof(struct output_device));
if (!device)
{
DPRINTF(E_LOG, L_FIFO, "Out of memory for fifo device\n");
return -1;
}
CHECK_NULL(L_FIFO, device = calloc(1, sizeof(struct output_device)));
device->id = 100;
device->name = strdup(nickname);
device->type = OUTPUT_TYPE_FIFO;
device->type_name = outputs_name(device->type);
device->advertised = 1;
device->has_video = 0;
device->extra_device_info = path;
DPRINTF(E_INFO, L_FIFO, "Adding fifo output device '%s' with path '%s'\n", nickname, path);
@ -576,11 +514,9 @@ struct output_definition output_fifo =
.deinit = fifo_deinit,
.device_start = fifo_device_start,
.device_stop = fifo_device_stop,
.device_flush = fifo_device_flush,
.device_probe = fifo_device_probe,
.device_volume_set = fifo_device_volume_set,
.playback_start = fifo_playback_start,
.playback_stop = fifo_playback_stop,
.device_cb_set = fifo_device_cb_set,
.write = fifo_write,
.flush = fifo_flush,
.status_cb = fifo_set_status_cb,
};

View File

@ -58,21 +58,21 @@ struct pulse
struct pulse_session
{
uint64_t device_id;
int callback_id;
char *devname;
pa_stream_state_t state;
pa_stream *stream;
pa_buffer_attr attr;
pa_volume_t volume;
struct media_quality quality;
int logcount;
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;
};
@ -85,6 +85,9 @@ static struct pulse_session *sessions;
// Internal list with indeces of the Pulseaudio devices (sinks) we have registered
static uint32_t pulse_known_devices[PULSE_MAX_DEVICES];
static struct media_quality pulse_last_quality;
static struct media_quality pulse_fallback_quality = { 44100, 16, 2 };
// Converts from 0 - 100 to Pulseaudio's scale
static inline pa_volume_t
pulse_from_device_volume(int device_volume)
@ -113,10 +116,9 @@ pulse_session_free(struct pulse_session *ps)
pa_threaded_mainloop_unlock(pulse.mainloop);
}
if (ps->devname)
free(ps->devname);
outputs_quality_unsubscribe(&pulse_fallback_quality);
free(ps->output_session);
free(ps->devname);
free(ps);
}
@ -139,43 +141,37 @@ pulse_session_cleanup(struct pulse_session *ps)
p->next = ps->next;
}
outputs_device_session_remove(ps->device_id);
pulse_session_free(ps);
}
static struct pulse_session *
pulse_session_make(struct output_device *device, output_status_cb cb)
pulse_session_make(struct output_device *device, int callback_id)
{
struct output_session *os;
struct pulse_session *ps;
int ret;
os = calloc(1, sizeof(struct output_session));
if (!os)
ret = outputs_quality_subscribe(&pulse_fallback_quality);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory (os)\n");
DPRINTF(E_LOG, L_LAUDIO, "Could not subscribe to fallback audio quality\n");
return NULL;
}
ps = calloc(1, sizeof(struct pulse_session));
if (!ps)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory (ps)\n");
free(os);
return NULL;
}
CHECK_NULL(L_LAUDIO, ps = calloc(1, sizeof(struct pulse_session)));
os->session = ps;
os->type = device->type;
ps->output_session = os;
ps->state = PA_STREAM_UNCONNECTED;
ps->device = device;
ps->status_cb = cb;
ps->device_id = device->id;
ps->callback_id = callback_id;
ps->volume = pulse_from_device_volume(device->volume);
ps->devname = strdup(device->extra_device_info);
ps->next = sessions;
sessions = ps;
outputs_device_session_add(device->id, ps);
return ps;
}
@ -187,7 +183,6 @@ static enum command_state
send_status(void *arg, int *ptr)
{
struct pulse_session *ps = arg;
output_status_cb status_cb;
enum output_device_state state;
switch (ps->state)
@ -210,10 +205,8 @@ send_status(void *arg, int *ptr)
state = OUTPUT_STATE_FAILED;
}
status_cb = ps->status_cb;
ps->status_cb = NULL;
if (status_cb)
status_cb(ps->device, ps->output_session, state);
outputs_cb(ps->callback_id, ps->device_id, state);
ps->callback_id = -1;
return COMMAND_PENDING; // Don't want the command module to clean up ps
}
@ -442,7 +435,6 @@ sinklist_cb(pa_context *ctx, const pa_sink_info *info, int eol, void *userdata)
device->name = strdup(name);
device->type = OUTPUT_TYPE_PULSE;
device->type_name = outputs_name(device->type);
device->advertised = 1;
device->extra_device_info = strdup(info->name);
player_device_add(device);
@ -572,25 +564,33 @@ pulse_free(void)
}
static int
stream_open(struct pulse_session *ps, pa_stream_notify_cb_t cb)
stream_open(struct pulse_session *ps, struct media_quality *quality, pa_stream_notify_cb_t cb)
{
pa_stream_flags_t flags;
pa_sample_spec ss;
pa_cvolume cvol;
int offset;
int offset_ms;
int ret;
DPRINTF(E_DBG, L_LAUDIO, "Opening Pulseaudio stream to '%s'\n", ps->devname);
ss.format = PA_SAMPLE_S16LE;
ss.channels = 2;
ss.rate = 44100;
if (quality->bits_per_sample == 16)
ss.format = PA_SAMPLE_S16LE;
else if (quality->bits_per_sample == 24)
ss.format = PA_SAMPLE_S24LE;
else if (quality->bits_per_sample == 32)
ss.format = PA_SAMPLE_S32LE;
else
ss.format = 0;
offset = cfg_getint(cfg_getsec(cfg, "audio"), "offset");
if (abs(offset) > 44100)
ss.channels = quality->channels;
ss.rate = quality->sample_rate;
offset_ms = cfg_getint(cfg_getsec(cfg, "audio"), "offset_ms");
if (abs(offset_ms) > 1000)
{
DPRINTF(E_LOG, L_LAUDIO, "The audio offset (%d) set in the configuration is out of bounds\n", offset);
offset = 44100 * (offset/abs(offset));
DPRINTF(E_LOG, L_LAUDIO, "The audio offset (%d) set in the configuration is out of bounds\n", offset_ms);
offset_ms = 1000 * (offset_ms/abs(offset_ms));
}
pa_threaded_mainloop_lock(pulse.mainloop);
@ -602,7 +602,7 @@ stream_open(struct pulse_session *ps, pa_stream_notify_cb_t cb)
flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
ps->attr.tlength = STOB(2 * ss.rate + AIRTUNES_V2_PACKET_SAMPLES - offset); // 2 second latency
ps->attr.tlength = STOB((OUTPUTS_BUFFER_DURATION * 1000 + offset_ms) * ss.rate / 1000, quality->bits_per_sample, quality->channels);
ps->attr.maxlength = 2 * ps->attr.tlength;
ps->attr.prebuf = (uint32_t)-1;
ps->attr.minreq = (uint32_t)-1;
@ -625,7 +625,8 @@ stream_open(struct pulse_session *ps, pa_stream_notify_cb_t cb)
unlock_and_fail:
ret = pa_context_errno(pulse.context);
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not start '%s': %s\n", ps->devname, pa_strerror(ret));
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not start '%s' using quality %d/%d/%d: %s\n",
ps->devname, quality->sample_rate, quality->bits_per_sample, quality->channels, pa_strerror(ret));
pa_threaded_mainloop_unlock(pulse.mainloop);
@ -635,11 +636,15 @@ stream_open(struct pulse_session *ps, pa_stream_notify_cb_t cb)
static void
stream_close(struct pulse_session *ps, pa_stream_notify_cb_t cb)
{
if (!ps->stream)
return;
pa_threaded_mainloop_lock(pulse.mainloop);
pa_stream_set_underflow_callback(ps->stream, NULL, NULL);
pa_stream_set_overflow_callback(ps->stream, NULL, NULL);
pa_stream_set_state_callback(ps->stream, cb, ps);
pa_stream_disconnect(ps->stream);
pa_stream_unref(ps->stream);
@ -649,54 +654,169 @@ stream_close(struct pulse_session *ps, pa_stream_notify_cb_t cb)
pa_threaded_mainloop_unlock(pulse.mainloop);
}
static void
playback_restart(struct pulse_session *ps, struct output_buffer *obuf)
{
int ret;
stream_close(ps, NULL);
// Negotiate quality (sample rate) with device - first we try to use the source quality
ps->quality = obuf->data[0].quality;
ret = stream_open(ps, &ps->quality, start_cb);
if (ret < 0)
{
DPRINTF(E_INFO, L_LAUDIO, "Input quality (%d/%d/%d) not supported, falling back to default\n",
ps->quality.sample_rate, ps->quality.bits_per_sample, ps->quality.channels);
ps->quality = pulse_fallback_quality;
ret = stream_open(ps, &ps->quality, start_cb);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio device failed setting fallback quality\n");
ps->state = PA_STREAM_FAILED;
pulse_session_shutdown(ps);
return;
}
}
}
static void
playback_write(struct pulse_session *ps, struct output_buffer *obuf)
{
int i;
int ret;
// Find the quality we want
for (i = 0; obuf->data[i].buffer; i++)
{
if (quality_is_equal(&ps->quality, &obuf->data[i].quality))
break;
}
if (!obuf->data[i].buffer)
{
DPRINTF(E_LOG, L_LAUDIO, "Output not delivering required data quality, aborting\n");
ps->state = PA_STREAM_FAILED;
pulse_session_shutdown(ps);
return;
}
pa_threaded_mainloop_lock(pulse.mainloop);
ret = pa_stream_write(ps->stream, obuf->data[i].buffer, obuf->data[i].bufsize, NULL, 0LL, PA_SEEK_RELATIVE);
if (ret < 0)
{
ret = pa_context_errno(pulse.context);
DPRINTF(E_LOG, L_LAUDIO, "Error writing Pulseaudio stream data to '%s': %s\n", ps->devname, pa_strerror(ret));
ps->state = PA_STREAM_FAILED;
pulse_session_shutdown(ps);
goto unlock;
}
unlock:
pa_threaded_mainloop_unlock(pulse.mainloop);
}
static void
playback_resume(struct pulse_session *ps)
{
pa_operation* o;
pa_threaded_mainloop_lock(pulse.mainloop);
o = pa_stream_cork(ps->stream, 0, NULL, NULL);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not resume '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
goto unlock;
}
pa_operation_unref(o);
unlock:
pa_threaded_mainloop_unlock(pulse.mainloop);
}
/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */
static int
pulse_device_start(struct output_device *device, output_status_cb cb, uint64_t rtptime)
pulse_device_start(struct output_device *device, int callback_id)
{
struct pulse_session *ps;
int ret;
DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio starting '%s'\n", device->name);
ps = pulse_session_make(device, cb);
ps = pulse_session_make(device, callback_id);
if (!ps)
return -1;
ret = stream_open(ps, start_cb);
if (ret < 0)
{
pulse_session_cleanup(ps);
return -1;
}
pulse_status(ps);
return 0;
}
static void
pulse_device_stop(struct output_session *session)
static int
pulse_device_stop(struct output_device *device, int callback_id)
{
struct pulse_session *ps = session->session;
struct pulse_session *ps = device->session;
DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio stopping '%s'\n", ps->devname);
ps->callback_id = callback_id;
stream_close(ps, close_cb);
return 0;
}
static int
pulse_device_probe(struct output_device *device, output_status_cb cb)
pulse_device_flush(struct output_device *device, int callback_id)
{
struct pulse_session *ps = device->session;
pa_operation* o;
DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio flush\n");
pa_threaded_mainloop_lock(pulse.mainloop);
ps->callback_id = callback_id;
o = pa_stream_cork(ps->stream, 1, NULL, NULL);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not pause '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
return -1;
}
pa_operation_unref(o);
o = pa_stream_flush(ps->stream, flush_cb, ps);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not flush '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
return -1;
}
pa_operation_unref(o);
pa_threaded_mainloop_unlock(pulse.mainloop);
return 0;
}
static int
pulse_device_probe(struct output_device *device, int callback_id)
{
struct pulse_session *ps;
int ret;
DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio probing '%s'\n", device->name);
ps = pulse_session_make(device, cb);
ps = pulse_session_make(device, callback_id);
if (!ps)
return -1;
ret = stream_open(ps, probe_cb);
ret = stream_open(ps, &pulse_fallback_quality, probe_cb);
if (ret < 0)
{
pulse_session_cleanup(ps);
@ -712,18 +832,25 @@ pulse_device_free_extra(struct output_device *device)
free(device->extra_device_info);
}
static int
pulse_device_volume_set(struct output_device *device, output_status_cb cb)
static void
pulse_device_cb_set(struct output_device *device, int callback_id)
{
struct pulse_session *ps;
struct pulse_session *ps = device->session;
ps->callback_id = callback_id;
}
static int
pulse_device_volume_set(struct output_device *device, int callback_id)
{
struct pulse_session *ps = device->session;
uint32_t idx;
pa_operation* o;
pa_cvolume cvol;
if (!sessions || !device->session || !device->session->session)
if (!ps)
return 0;
ps = device->session->session;
idx = pa_stream_get_index(ps->stream);
ps->volume = pulse_from_device_volume(device->volume);
@ -733,7 +860,7 @@ pulse_device_volume_set(struct output_device *device, output_status_cb cb)
pa_threaded_mainloop_lock(pulse.mainloop);
ps->status_cb = cb;
ps->callback_id = callback_id;
o = pa_context_set_sink_input_volume(pulse.context, idx, &cvol, volume_cb, ps);
if (!o)
@ -750,141 +877,33 @@ pulse_device_volume_set(struct output_device *device, output_status_cb cb)
}
static void
pulse_write(uint8_t *buf, uint64_t rtptime)
pulse_write(struct output_buffer *obuf)
{
struct pulse_session *ps;
struct pulse_session *next;
size_t length;
int ret;
if (!sessions)
return;
length = STOB(AIRTUNES_V2_PACKET_SAMPLES);
pa_threaded_mainloop_lock(pulse.mainloop);
for (ps = sessions; ps; ps = next)
{
next = ps->next;
if (ps->state != PA_STREAM_READY)
// We have not set up a stream OR the quality changed, so we need to set it up again
if (ps->state == PA_STREAM_UNCONNECTED || !quality_is_equal(&obuf->data[0].quality, &pulse_last_quality))
{
playback_restart(ps, obuf);
pulse_last_quality = obuf->data[0].quality;
continue; // Async, so the device won't be ready for writing just now
}
else if (ps->state != PA_STREAM_READY)
continue;
ret = pa_stream_write(ps->stream, buf, length, NULL, 0LL, PA_SEEK_RELATIVE);
if (ret < 0)
{
ret = pa_context_errno(pulse.context);
DPRINTF(E_LOG, L_LAUDIO, "Error writing Pulseaudio stream data to '%s': %s\n", ps->devname, pa_strerror(ret));
if (ps->stream && pa_stream_is_corked(ps->stream))
playback_resume(ps);
ps->state = PA_STREAM_FAILED;
pulse_session_shutdown(ps);
continue;
}
playback_write(ps, obuf);
}
pa_threaded_mainloop_unlock(pulse.mainloop);
}
static void
pulse_playback_start(uint64_t next_pkt, struct timespec *ts)
{
struct pulse_session *ps;
pa_operation* o;
pa_threaded_mainloop_lock(pulse.mainloop);
for (ps = sessions; ps; ps = ps->next)
{
o = pa_stream_cork(ps->stream, 0, NULL, NULL);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not resume '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
continue;
}
pa_operation_unref(o);
}
pa_threaded_mainloop_unlock(pulse.mainloop);
}
static void
pulse_playback_stop(void)
{
struct pulse_session *ps;
pa_operation* o;
pa_threaded_mainloop_lock(pulse.mainloop);
for (ps = sessions; ps; ps = ps->next)
{
o = pa_stream_cork(ps->stream, 1, NULL, NULL);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not pause '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
continue;
}
pa_operation_unref(o);
o = pa_stream_flush(ps->stream, NULL, NULL);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not flush '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
continue;
}
pa_operation_unref(o);
}
pa_threaded_mainloop_unlock(pulse.mainloop);
}
static int
pulse_flush(output_status_cb cb, uint64_t rtptime)
{
struct pulse_session *ps;
pa_operation* o;
int i;
DPRINTF(E_DBG, L_LAUDIO, "Pulseaudio flush\n");
pa_threaded_mainloop_lock(pulse.mainloop);
i = 0;
for (ps = sessions; ps; ps = ps->next)
{
i++;
ps->status_cb = cb;
o = pa_stream_cork(ps->stream, 1, NULL, NULL);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not pause '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
continue;
}
pa_operation_unref(o);
o = pa_stream_flush(ps->stream, flush_cb, ps);
if (!o)
{
DPRINTF(E_LOG, L_LAUDIO, "Pulseaudio could not flush '%s': %s\n", ps->devname, pa_strerror(pa_context_errno(pulse.context)));
continue;
}
pa_operation_unref(o);
}
pa_threaded_mainloop_unlock(pulse.mainloop);
return i;
}
static void
pulse_set_status_cb(struct output_session *session, output_status_cb cb)
{
struct pulse_session *ps = session->session;
ps->status_cb = cb;
}
static int
@ -977,13 +996,11 @@ struct output_definition output_pulse =
.deinit = pulse_deinit,
.device_start = pulse_device_start,
.device_stop = pulse_device_stop,
.device_flush = pulse_device_flush,
.device_probe = pulse_device_probe,
.device_free_extra = pulse_device_free_extra,
.device_cb_set = pulse_device_cb_set,
.device_volume_set = pulse_device_volume_set,
.playback_start = pulse_playback_start,
.playback_stop = pulse_playback_stop,
.write = pulse_write,
.flush = pulse_flush,
.status_cb = pulse_set_status_cb,
};

File diff suppressed because it is too large Load Diff

275
src/outputs/rtp_common.c Normal file
View File

@ -0,0 +1,275 @@
/*
* Copyright (C) 2019- Espen Jürgensen <espenjurgensen@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <limits.h>
#include <sys/param.h>
#ifdef HAVE_ENDIAN_H
# include <endian.h>
#elif defined(HAVE_SYS_ENDIAN_H)
# include <sys/endian.h>
#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#endif
#include <gcrypt.h>
#include "logger.h"
#include "misc.h"
#include "rtp_common.h"
#define RTP_HEADER_LEN 12
#define RTCP_SYNC_PACKET_LEN 20
// NTP timestamp definitions
#define FRAC 4294967296. // 2^32 as a double
#define NTP_EPOCH_DELTA 0x83aa7e80 // 2208988800 - that's 1970 - 1900 in seconds
struct ntp_timestamp
{
uint32_t sec;
uint32_t frac;
};
static inline void
timespec_to_ntp(struct timespec *ts, struct ntp_timestamp *ns)
{
/* Seconds since NTP Epoch (1900-01-01) */
ns->sec = ts->tv_sec + NTP_EPOCH_DELTA;
ns->frac = (uint32_t)((double)ts->tv_nsec * 1e-9 * FRAC);
}
static inline void
ntp_to_timespec(struct ntp_timestamp *ns, struct timespec *ts)
{
/* Seconds since Unix Epoch (1970-01-01) */
ts->tv_sec = ns->sec - NTP_EPOCH_DELTA;
ts->tv_nsec = (long)((double)ns->frac / (1e-9 * FRAC));
}
struct rtp_session *
rtp_session_new(struct media_quality *quality, int pktbuf_size, int sync_each_nsamples)
{
struct rtp_session *session;
CHECK_NULL(L_PLAYER, session = calloc(1, sizeof(struct rtp_session)));
// Random SSRC ID, RTP time start and sequence start
gcry_randomize(&session->ssrc_id, sizeof(session->ssrc_id), GCRY_STRONG_RANDOM);
gcry_randomize(&session->pos, sizeof(session->pos), GCRY_STRONG_RANDOM);
gcry_randomize(&session->seqnum, sizeof(session->seqnum), GCRY_STRONG_RANDOM);
session->quality = *quality;
session->pktbuf_size = pktbuf_size;
CHECK_NULL(L_PLAYER, session->pktbuf = calloc(session->pktbuf_size, sizeof(struct rtp_packet)));
if (sync_each_nsamples > 0)
session->sync_each_nsamples = sync_each_nsamples;
else if (sync_each_nsamples == 0)
session->sync_each_nsamples = quality->sample_rate;
return session;
}
void
rtp_session_free(struct rtp_session *session)
{
int i;
for (i = 0; i < session->pktbuf_size; i++)
free(session->pktbuf[i].data);
free(session->pktbuf);
free(session->sync_packet_next.data);
free(session);
}
void
rtp_session_flush(struct rtp_session *session)
{
session->pktbuf_len = 0;
session->sync_counter = 0;
}
// We don't want the caller to malloc payload for every packet, so instead we
// will get him a packet from the ring buffer, thus in most cases reusing memory
struct rtp_packet *
rtp_packet_next(struct rtp_session *session, size_t payload_len, int samples, char type)
{
struct rtp_packet *pkt;
uint16_t seq;
uint32_t rtptime;
uint32_t ssrc_id;
pkt = &session->pktbuf[session->pktbuf_next];
// When first filling up the buffer we malloc, but otherwise the existing data
// allocation should in most cases suffice. If not, we realloc.
if (!pkt->data || payload_len > pkt->payload_size)
{
pkt->data_size = RTP_HEADER_LEN + payload_len;
if (!pkt->data)
CHECK_NULL(L_PLAYER, pkt->data = malloc(pkt->data_size));
else
CHECK_NULL(L_PLAYER, pkt->data = realloc(pkt->data, pkt->data_size));
pkt->header = pkt->data;
pkt->payload = pkt->data + RTP_HEADER_LEN;
pkt->payload_size = payload_len;
}
pkt->samples = samples;
pkt->payload_len = payload_len;
pkt->data_len = RTP_HEADER_LEN + payload_len;
pkt->seqnum = session->seqnum;
// The RTP header is made of these 12 bytes (RFC 3550):
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P|X| CC |M| PT | sequence number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | timestamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | synchronization source (SSRC) identifier |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
pkt->header[0] = 0x80; // Version = 2, P, X and CC are 0
pkt->header[1] = type; // RTP payload type
seq = htobe16(session->seqnum);
memcpy(pkt->header + 2, &seq, 2);
rtptime = htobe32(session->pos);
memcpy(pkt->header + 4, &rtptime, 4);
ssrc_id = htobe32(session->ssrc_id);
memcpy(pkt->header + 8, &ssrc_id, 4);
return pkt;
}
void
rtp_packet_commit(struct rtp_session *session, struct rtp_packet *pkt)
{
// Increase size of retransmit buffer since we just wrote a packet
if (session->pktbuf_len < session->pktbuf_size)
session->pktbuf_len++;
// Advance counters to prepare for next packet
session->pktbuf_next = (session->pktbuf_next + 1) % session->pktbuf_size;
session->seqnum++;
session->pos += pkt->samples;
session->sync_counter += pkt->samples;
}
struct rtp_packet *
rtp_packet_get(struct rtp_session *session, uint16_t seqnum)
{
uint16_t first;
uint16_t last;
size_t idx;
if (!session->seqnum || !session->pktbuf_len)
return NULL;
last = session->seqnum - 1;
first = session->seqnum - session->pktbuf_len;
if (seqnum < first || seqnum > last)
{
DPRINTF(E_DBG, L_PLAYER, "Seqnum %" PRIu16 " not in buffer (have seqnum %" PRIu16 " to %" PRIu16 ")\n", seqnum, first, last);
return NULL;
}
idx = (session->pktbuf_next - (session->seqnum - seqnum)) % session->pktbuf_size;
return &session->pktbuf[idx];
}
bool
rtp_sync_is_time(struct rtp_session *session)
{
if (session->sync_each_nsamples && session->sync_counter > session->sync_each_nsamples)
{
session->sync_counter = 0;
return true;
}
return false;
}
struct rtp_packet *
rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp *cur_stamp, char type)
{
struct ntp_timestamp cur_ts;
uint32_t rtptime;
uint32_t cur_pos;
if (!session->sync_packet_next.data)
{
CHECK_NULL(L_PLAYER, session->sync_packet_next.data = malloc(RTCP_SYNC_PACKET_LEN));
session->sync_packet_next.data_len = RTCP_SYNC_PACKET_LEN;
}
session->sync_packet_next.data[0] = type;
session->sync_packet_next.data[1] = 0xd4;
session->sync_packet_next.data[2] = 0x00;
session->sync_packet_next.data[3] = 0x07;
timespec_to_ntp(&cur_stamp->ts, &cur_ts);
cur_pos = htobe32(cur_stamp->pos);
memcpy(session->sync_packet_next.data + 4, &cur_pos, 4);
cur_ts.sec = htobe32(cur_ts.sec);
cur_ts.frac = htobe32(cur_ts.frac);
memcpy(session->sync_packet_next.data + 8, &cur_ts.sec, 4);
memcpy(session->sync_packet_next.data + 12, &cur_ts.frac, 4);
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",
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;
}

80
src/outputs/rtp_common.h Normal file
View File

@ -0,0 +1,80 @@
#ifndef __RTP_COMMON_H__
#define __RTP_COMMON_H__
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
struct rtcp_timestamp
{
uint32_t pos;
struct timespec ts;
};
struct rtp_packet
{
uint16_t seqnum; // Sequence number
int samples; // Number of samples in the packet
uint8_t *header; // Pointer to the RTP header
uint8_t *payload; // Pointer to the RTP payload
size_t payload_size; // Size of allocated memory for RTP payload
size_t payload_len; // Length of payload (must of course not exceed size)
uint8_t *data; // Pointer to the complete packet data
size_t data_size; // Size of packet data
size_t data_len; // Length of actual packet data
};
// An RTP session is characterised by all the receivers belonging to the session
// getting the same RTP and RTCP packets. So if you have clients that require
// different sample rates or where only some can accept encrypted payloads then
// you need multiple sessions.
struct rtp_session
{
uint32_t ssrc_id;
uint32_t pos;
uint16_t seqnum;
struct media_quality quality;
// Packet buffer (ring buffer), used for retransmission
struct rtp_packet *pktbuf;
size_t pktbuf_next;
size_t pktbuf_size;
size_t pktbuf_len;
// Number of samples to elapse before sync'ing. If 0 we set it to the s/r, so
// we sync once a second. If negative we won't sync.
int sync_each_nsamples;
int sync_counter;
struct rtp_packet sync_packet_next;
};
struct rtp_session *
rtp_session_new(struct media_quality *quality, int pktbuf_size, int sync_each_nsamples);
void
rtp_session_free(struct rtp_session *session);
void
rtp_session_flush(struct rtp_session *session);
struct rtp_packet *
rtp_packet_next(struct rtp_session *session, size_t payload_len, int samples, char type);
void
rtp_packet_commit(struct rtp_session *session, struct rtp_packet *pkt);
struct rtp_packet *
rtp_packet_get(struct rtp_session *session, uint16_t seqnum);
bool
rtp_sync_is_time(struct rtp_session *session);
struct rtp_packet *
rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp *cur_stamp, char type);
#endif /* !__RTP_COMMON_H__ */

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,7 @@
#include "db.h"
/* AirTunes v2 packet interval in ns */
/* (352 samples/packet * 1e9 ns/s) / 44100 samples/s = 7981859 ns/packet */
# define AIRTUNES_V2_STREAM_PERIOD 7981859
/* AirTunes v2 number of samples per packet */
#define AIRTUNES_V2_PACKET_SAMPLES 352
/* Maximum number of previously played songs that are remembered */
// Maximum number of previously played songs that are remembered
#define MAX_HISTORY_COUNT 20
enum play_status {
@ -79,15 +72,11 @@ 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_status(struct player_status *status);
int
player_now_playing(uint32_t *id);
player_playing_now(uint32_t *id);
void
player_speaker_enumerate(spk_enum_cb cb, void *arg);
@ -104,9 +93,6 @@ player_speaker_enable(uint64_t id);
int
player_speaker_disable(uint64_t id);
void
player_speaker_status_trigger(void);
int
player_playback_start(void);
@ -152,7 +138,6 @@ player_shuffle_set(int enable);
int
player_consume_set(int enable);
void
player_queue_clear_history(void);
@ -171,8 +156,8 @@ player_device_remove(void *device);
void
player_raop_verification_kickoff(char **arglist);
void
player_metadata_send(void *imd, void *omd);
const char *
player_pmap(void *p);
int
player_init(void);

View File

@ -718,8 +718,7 @@ playback_eot(void *arg, int *retval)
g_state = SPOTIFY_STATE_STOPPING;
// TODO 1) This will block for a while, but perhaps ok?
input_write(spotify_audio_buffer, INPUT_FLAG_EOF);
input_write(spotify_audio_buffer, NULL, INPUT_FLAG_EOF);
*retval = 0;
return COMMAND_END;
@ -1007,17 +1006,22 @@ logged_out(sp_session *sess)
static int music_delivery(sp_session *sess, const sp_audioformat *format,
const void *frames, int num_frames)
{
struct media_quality quality = { 0 };
size_t size;
int ret;
/* No support for resampling right now */
if ((format->sample_rate != 44100) || (format->channels != 2))
if ((format->sample_type != SP_SAMPLETYPE_INT16_NATIVE_ENDIAN) || (format->channels != 2))
{
DPRINTF(E_LOG, L_SPOTIFY, "Got music with unsupported samplerate or channels, stopping playback\n");
DPRINTF(E_LOG, L_SPOTIFY, "Got music with unsupported sample format or number of channels, stopping playback\n");
spotify_playback_stop_nonblock();
return num_frames;
}
quality.sample_rate = format->sample_rate;
quality.bits_per_sample = 16;
quality.channels = format->channels;
// Audio discontinuity, e.g. seek
if (num_frames == 0)
{
@ -1037,7 +1041,7 @@ static int music_delivery(sp_session *sess, const sp_audioformat *format,
// The input buffer only accepts writing when it is approaching depletion, and
// because we use NONBLOCK it will just return if this is not the case. So in
// most cases no actual write is made and spotify_audio_buffer will just grow.
input_write(spotify_audio_buffer, INPUT_FLAG_NONBLOCK);
input_write(spotify_audio_buffer, &quality, 0);
return num_frames;
}

View File

@ -40,6 +40,7 @@
#include "conffile.h"
#include "db.h"
#include "avio_evbuffer.h"
#include "misc.h"
#include "transcode.h"
// Interval between ICY metadata checks for streams, in seconds
@ -75,12 +76,10 @@ struct settings_ctx
// Audio settings
enum AVCodecID audio_codec;
const char *audio_codec_name;
int sample_rate;
uint64_t channel_layout;
int channels;
enum AVSampleFormat sample_format;
int byte_depth;
bool wavheader;
bool icy;
@ -178,47 +177,56 @@ struct encode_ctx
uint8_t header[44];
};
struct transcode_ctx
{
struct decode_ctx *decode_ctx;
struct encode_ctx *encode_ctx;
};
/* -------------------------- PROFILE CONFIGURATION ------------------------ */
static int
init_settings(struct settings_ctx *settings, enum transcode_profile profile)
init_settings(struct settings_ctx *settings, enum transcode_profile profile, struct media_quality *quality)
{
const AVCodecDescriptor *codec_desc;
memset(settings, 0, sizeof(struct settings_ctx));
switch (profile)
{
case XCODE_PCM_NATIVE: // Sample rate and bit depth determined by source
settings->encode_audio = 1;
settings->icy = 1;
break;
case XCODE_PCM16_HEADER:
settings->wavheader = 1;
case XCODE_PCM16_NOHEADER:
case XCODE_PCM16:
settings->encode_audio = 1;
settings->format = "s16le";
settings->audio_codec = AV_CODEC_ID_PCM_S16LE;
settings->sample_rate = 44100;
settings->channel_layout = AV_CH_LAYOUT_STEREO;
settings->channels = 2;
settings->sample_format = AV_SAMPLE_FMT_S16;
settings->byte_depth = 2; // Bytes per sample = 16/8
settings->icy = 1;
break;
case XCODE_PCM24:
settings->encode_audio = 1;
settings->format = "s24le";
settings->audio_codec = AV_CODEC_ID_PCM_S24LE;
settings->sample_format = AV_SAMPLE_FMT_S32;
break;
case XCODE_PCM32:
settings->encode_audio = 1;
settings->format = "s32le";
settings->audio_codec = AV_CODEC_ID_PCM_S32LE;
settings->sample_format = AV_SAMPLE_FMT_S32;
break;
case XCODE_MP3:
settings->encode_audio = 1;
settings->format = "mp3";
settings->audio_codec = AV_CODEC_ID_MP3;
settings->sample_rate = 44100;
settings->channel_layout = AV_CH_LAYOUT_STEREO;
settings->channels = 2;
settings->sample_format = AV_SAMPLE_FMT_S16P;
settings->byte_depth = 2; // Bytes per sample = 16/8
break;
case XCODE_OPUS:
settings->encode_audio = 1;
settings->format = "data"; // Means we get the raw packet from the encoder, no muxing
settings->audio_codec = AV_CODEC_ID_OPUS;
settings->sample_format = AV_SAMPLE_FMT_S16; // Only libopus support
break;
case XCODE_JPEG:
@ -226,6 +234,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile)
settings->silent = 1;
settings->format = "image2";
settings->in_format = "mjpeg";
settings->pix_fmt = AV_PIX_FMT_YUVJ420P;
settings->video_codec = AV_CODEC_ID_MJPEG;
break;
@ -233,6 +242,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile)
settings->encode_video = 1;
settings->silent = 1;
settings->format = "image2";
settings->pix_fmt = AV_PIX_FMT_RGB24;
settings->video_codec = AV_CODEC_ID_PNG;
break;
@ -241,16 +251,21 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile)
return -1;
}
if (settings->audio_codec)
if (quality && quality->sample_rate)
{
codec_desc = avcodec_descriptor_get(settings->audio_codec);
settings->audio_codec_name = codec_desc->name;
settings->sample_rate = quality->sample_rate;
}
if (settings->video_codec)
if (quality && quality->channels)
{
codec_desc = avcodec_descriptor_get(settings->video_codec);
settings->video_codec_name = codec_desc->name;
settings->channels = quality->channels;
settings->channel_layout = av_get_default_channel_layout(quality->channels);
}
if (quality && quality->bits_per_sample && (quality->bits_per_sample != 8 * av_get_bytes_per_sample(settings->sample_format)))
{
DPRINTF(E_LOG, L_XCODE, "Bug! Mismatch between profile and media quality\n");
return -1;
}
return 0;
@ -279,6 +294,19 @@ stream_settings_set(struct stream_ctx *s, struct settings_ctx *settings, enum AV
/* -------------------------------- HELPERS -------------------------------- */
static enum AVSampleFormat
bitdepth2format(int bits_per_sample)
{
if (bits_per_sample == 16)
return AV_SAMPLE_FMT_S16;
else if (bits_per_sample == 24)
return AV_SAMPLE_FMT_S32;
else if (bits_per_sample == 32)
return AV_SAMPLE_FMT_S32;
else
return AV_SAMPLE_FMT_NONE;
}
static inline char *
err2str(int errnum)
{
@ -307,15 +335,18 @@ make_wav_header(struct encode_ctx *ctx, struct decode_ctx *src_ctx, off_t *est_s
{
uint32_t wav_len;
int duration;
int bps;
if (src_ctx->duration)
duration = src_ctx->duration;
else
duration = 3 * 60 * 1000; /* 3 minutes, in ms */
wav_len = ctx->settings.channels * ctx->settings.byte_depth * ctx->settings.sample_rate * (duration / 1000);
bps = av_get_bytes_per_sample(ctx->settings.sample_format);
wav_len = ctx->settings.channels * bps * ctx->settings.sample_rate * (duration / 1000);
*est_size = wav_len + sizeof(ctx->header);
if (est_size)
*est_size = wav_len + sizeof(ctx->header);
memcpy(ctx->header, "RIFF", 4);
add_le32(ctx->header + 4, 36 + wav_len);
@ -324,9 +355,9 @@ make_wav_header(struct encode_ctx *ctx, struct decode_ctx *src_ctx, off_t *est_s
add_le16(ctx->header + 20, 1);
add_le16(ctx->header + 22, ctx->settings.channels); /* channels */
add_le32(ctx->header + 24, ctx->settings.sample_rate); /* samplerate */
add_le32(ctx->header + 28, ctx->settings.sample_rate * ctx->settings.channels * ctx->settings.byte_depth); /* byte rate */
add_le16(ctx->header + 32, ctx->settings.channels * ctx->settings.byte_depth); /* block align */
add_le16(ctx->header + 34, ctx->settings.byte_depth * 8); /* bits per sample */
add_le32(ctx->header + 28, ctx->settings.sample_rate * ctx->settings.channels * bps); /* byte rate */
add_le16(ctx->header + 32, ctx->settings.channels * bps); /* block align */
add_le16(ctx->header + 34, 8 * bps); /* bits per sample */
memcpy(ctx->header + 36, "data", 4);
add_le32(ctx->header + 40, wav_len);
}
@ -356,20 +387,27 @@ stream_find(struct decode_ctx *ctx, unsigned int stream_index)
* @out ctx A pre-allocated stream ctx where we save stream and codec info
* @in output Output to add the stream to
* @in codec_id What kind of codec should we use
* @in codec_name Name of codec (only used for logging)
* @return Negative on failure, otherwise zero
*/
static int
stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id, const char *codec_name)
stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id)
{
const AVCodecDescriptor *codec_desc;
AVCodec *encoder;
AVDictionary *options = NULL;
int ret;
codec_desc = avcodec_descriptor_get(codec_id);
if (!codec_desc)
{
DPRINTF(E_LOG, L_XCODE, "Invalid codec ID (%d)\n", codec_id);
return -1;
}
encoder = avcodec_find_encoder(codec_id);
if (!encoder)
{
DPRINTF(E_LOG, L_XCODE, "Necessary encoder (%s) not found\n", codec_name);
DPRINTF(E_LOG, L_XCODE, "Necessary encoder (%s) not found\n", codec_desc->name);
return -1;
}
@ -381,7 +419,7 @@ stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id
if (!s->codec->pix_fmt)
{
s->codec->pix_fmt = avcodec_default_get_format(s->codec, encoder->pix_fmts);
DPRINTF(E_DBG, L_XCODE, "Pixel format set to %s (encoder is %s)\n", av_get_pix_fmt_name(s->codec->pix_fmt), codec_name);
DPRINTF(E_DBG, L_XCODE, "Pixel format set to %s (encoder is %s)\n", av_get_pix_fmt_name(s->codec->pix_fmt), codec_desc->name);
}
if (ctx->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
@ -394,7 +432,7 @@ stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id
ret = avcodec_open2(s->codec, NULL, &options);
if (ret < 0)
{
DPRINTF(E_LOG, L_XCODE, "Cannot open encoder (%s): %s\n", codec_name, err2str(ret));
DPRINTF(E_LOG, L_XCODE, "Cannot open encoder (%s): %s\n", codec_desc->name, err2str(ret));
avcodec_free_context(&s->codec);
return -1;
}
@ -403,7 +441,7 @@ stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id
ret = avcodec_parameters_from_context(s->stream->codecpar, s->codec);
if (ret < 0)
{
DPRINTF(E_LOG, L_XCODE, "Cannot copy stream parameters (%s): %s\n", codec_name, err2str(ret));
DPRINTF(E_LOG, L_XCODE, "Cannot copy stream parameters (%s): %s\n", codec_desc->name, err2str(ret));
avcodec_free_context(&s->codec);
return -1;
}
@ -854,7 +892,7 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx)
}
// Clear AVFMT_NOFILE bit, it is not allowed as we will set our own AVIOContext
oformat->flags = ~AVFMT_NOFILE;
oformat->flags &= ~AVFMT_NOFILE;
CHECK_NULL(L_XCODE, ctx->ofmt_ctx = avformat_alloc_context());
@ -876,14 +914,14 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx)
if (ctx->settings.encode_audio)
{
ret = stream_add(ctx, &ctx->audio_stream, ctx->settings.audio_codec, ctx->settings.audio_codec_name);
ret = stream_add(ctx, &ctx->audio_stream, ctx->settings.audio_codec);
if (ret < 0)
goto out_free_streams;
}
if (ctx->settings.encode_video)
{
ret = stream_add(ctx, &ctx->video_stream, ctx->settings.video_codec, ctx->settings.video_codec_name);
ret = stream_add(ctx, &ctx->video_stream, ctx->settings.video_codec);
if (ret < 0)
goto out_free_streams;
}
@ -972,6 +1010,8 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream)
goto out_fail;
}
DPRINTF(E_DBG, L_XCODE, "Created 'in' filter: %s\n", args);
snprintf(args, sizeof(args),
"sample_fmts=%s:sample_rates=%d:channel_layouts=0x%"PRIx64,
av_get_sample_fmt_name(out_stream->codec->sample_fmt), out_stream->codec->sample_rate,
@ -984,6 +1024,8 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream)
goto out_fail;
}
DPRINTF(E_DBG, L_XCODE, "Created 'format' filter: %s\n", args);
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph);
if (ret < 0)
{
@ -1122,7 +1164,7 @@ close_filters(struct encode_ctx *ctx)
/* Setup */
struct decode_ctx *
transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length)
transcode_decode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length)
{
struct decode_ctx *ctx;
@ -1133,7 +1175,7 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind,
ctx->duration = song_length;
ctx->data_kind = data_kind;
if ((init_settings(&ctx->settings, profile) < 0) || (open_input(ctx, path, evbuf) < 0))
if ((init_settings(&ctx->settings, profile, quality) < 0) || (open_input(ctx, path, evbuf) < 0))
goto fail_free;
return ctx;
@ -1146,20 +1188,52 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind,
}
struct encode_ctx *
transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height)
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, off_t *est_size, int width, int height)
{
struct encode_ctx *ctx;
int bps;
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct encode_ctx)));
CHECK_NULL(L_XCODE, ctx->filt_frame = av_frame_alloc());
CHECK_NULL(L_XCODE, ctx->encoded_pkt = av_packet_alloc());
if (init_settings(&ctx->settings, profile) < 0)
if (init_settings(&ctx->settings, profile, quality) < 0)
goto fail_free;
ctx->settings.width = width;
ctx->settings.height = height;
// Caller did not specify a sample rate -> use same as source
if (!ctx->settings.sample_rate && ctx->settings.encode_audio)
{
ctx->settings.sample_rate = src_ctx->audio_stream.codec->sample_rate;
}
// Caller did not specify a sample format -> use same as source
if (!ctx->settings.sample_format && ctx->settings.encode_audio)
{
bps = av_get_bytes_per_sample(src_ctx->audio_stream.codec->sample_fmt);
if (bps == 4)
{
ctx->settings.sample_format = AV_SAMPLE_FMT_S32;
ctx->settings.audio_codec = AV_CODEC_ID_PCM_S32LE;
ctx->settings.format = "s32le";
}
else
{
ctx->settings.sample_format = AV_SAMPLE_FMT_S16;
ctx->settings.audio_codec = AV_CODEC_ID_PCM_S16LE;
ctx->settings.format = "s16le";
}
}
// Caller did not specify channels -> use same as source
if (!ctx->settings.channels && ctx->settings.encode_audio)
{
ctx->settings.channels = src_ctx->audio_stream.codec->channels;
ctx->settings.channel_layout = src_ctx->audio_stream.codec->channel_layout;
}
if (ctx->settings.wavheader)
make_wav_header(ctx, src_ctx, est_size);
@ -1170,7 +1244,10 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct
goto fail_close;
if (ctx->settings.icy && src_ctx->data_kind == DATA_KIND_HTTP)
ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->settings.channels * ctx->settings.byte_depth * ctx->settings.sample_rate;
{
bps = av_get_bytes_per_sample(ctx->settings.sample_format);
ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->settings.channels * bps * ctx->settings.sample_rate;
}
return ctx;
@ -1184,20 +1261,20 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct
}
struct transcode_ctx *
transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size)
transcode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size)
{
struct transcode_ctx *ctx;
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct transcode_ctx)));
ctx->decode_ctx = transcode_decode_setup(profile, data_kind, path, NULL, song_length);
ctx->decode_ctx = transcode_decode_setup(profile, quality, data_kind, path, NULL, song_length);
if (!ctx->decode_ctx)
{
free(ctx);
return NULL;
}
ctx->encode_ctx = transcode_encode_setup(profile, ctx->decode_ctx, est_size, 0, 0);
ctx->encode_ctx = transcode_encode_setup(profile, quality, ctx->decode_ctx, est_size, 0, 0);
if (!ctx->encode_ctx)
{
transcode_decode_cleanup(&ctx->decode_ctx);
@ -1209,26 +1286,34 @@ transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const
}
struct decode_ctx *
transcode_decode_setup_raw(void)
transcode_decode_setup_raw(enum transcode_profile profile, struct media_quality *quality)
{
const AVCodecDescriptor *codec_desc;
struct decode_ctx *ctx;
AVCodec *decoder;
int ret;
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct decode_ctx)));
if (init_settings(&ctx->settings, XCODE_PCM16_NOHEADER) < 0)
if (init_settings(&ctx->settings, profile, quality) < 0)
{
goto out_free_ctx;
}
codec_desc = avcodec_descriptor_get(ctx->settings.audio_codec);
if (!codec_desc)
{
DPRINTF(E_LOG, L_XCODE, "Invalid codec ID (%d)\n", ctx->settings.audio_codec);
goto out_free_ctx;
}
// In raw mode we won't actually need to read or decode, but we still setup
// the decode_ctx because transcode_encode_setup() gets info about the input
// through this structure (TODO dont' do that)
decoder = avcodec_find_decoder(ctx->settings.audio_codec);
if (!decoder)
{
DPRINTF(E_LOG, L_XCODE, "Could not find decoder for: %s\n", ctx->settings.audio_codec_name);
DPRINTF(E_LOG, L_XCODE, "Could not find decoder for: %s\n", codec_desc->name);
goto out_free_ctx;
}
@ -1243,7 +1328,7 @@ transcode_decode_setup_raw(void)
ret = avcodec_parameters_from_context(ctx->audio_stream.stream->codecpar, ctx->audio_stream.codec);
if (ret < 0)
{
DPRINTF(E_LOG, L_XCODE, "Cannot copy stream parameters (%s): %s\n", ctx->settings.audio_codec_name, err2str(ret));
DPRINTF(E_LOG, L_XCODE, "Cannot copy stream parameters (%s): %s\n", codec_desc->name, err2str(ret));
goto out_free_codec;
}
@ -1373,6 +1458,9 @@ transcode_encode_cleanup(struct encode_ctx **ctx)
void
transcode_cleanup(struct transcode_ctx **ctx)
{
if (!*ctx)
return;
transcode_encode_cleanup(&(*ctx)->encode_ctx);
transcode_decode_cleanup(&(*ctx)->decode_ctx);
free(*ctx);
@ -1383,7 +1471,7 @@ transcode_cleanup(struct transcode_ctx **ctx)
/* Encoding, decoding and transcoding */
int
transcode_decode(void **frame, struct decode_ctx *dec_ctx)
transcode_decode(transcode_frame **frame, struct decode_ctx *dec_ctx)
{
struct transcode_ctx ctx;
int ret;
@ -1414,7 +1502,7 @@ transcode_decode(void **frame, struct decode_ctx *dec_ctx)
// Filters and encodes
int
transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, int eof)
transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, transcode_frame *frame, int eof)
{
AVFrame *f = frame;
struct stream_ctx *s;
@ -1489,8 +1577,8 @@ transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int
return processed;
}
void *
transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size)
transcode_frame *
transcode_frame_new(void *data, size_t size, int nsamples, struct media_quality *quality)
{
AVFrame *f;
int ret;
@ -1502,19 +1590,28 @@ transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size)
return NULL;
}
f->nb_samples = size / 4;
f->format = AV_SAMPLE_FMT_S16;
f->channel_layout = AV_CH_LAYOUT_STEREO;
f->format = bitdepth2format(quality->bits_per_sample);
if (f->format == AV_SAMPLE_FMT_NONE)
{
DPRINTF(E_LOG, L_XCODE, "transcode_frame_new() called with unsupported bps (%d)\n", quality->bits_per_sample);
av_frame_free(&f);
return NULL;
}
f->sample_rate = quality->sample_rate;
f->nb_samples = nsamples;
f->channel_layout = av_get_default_channel_layout(quality->channels);
#ifdef HAVE_FFMPEG
f->channels = 2;
f->channels = quality->channels;
#endif
f->pts = AV_NOPTS_VALUE;
f->sample_rate = 44100;
ret = avcodec_fill_audio_frame(f, 2, f->format, data, size, 0);
// We don't align because the frame won't be given directly to the encoder
// anyway, it will first go through the filter (which might align it...?)
ret = avcodec_fill_audio_frame(f, 2, f->format, data, size, 1);
if (ret < 0)
{
DPRINTF(E_LOG, L_XCODE, "Error filling frame with rawbuf: %s\n", err2str(ret));
DPRINTF(E_LOG, L_XCODE, "Error filling frame with rawbuf, size %zu, samples %d (%d/%d/2): %s\n", size, nsamples, quality->sample_rate, quality->bits_per_sample, err2str(ret));
av_frame_free(&f);
return NULL;
}
@ -1523,7 +1620,7 @@ transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size)
}
void
transcode_frame_free(void *frame)
transcode_frame_free(transcode_frame *frame)
{
AVFrame *f = frame;
@ -1645,6 +1742,29 @@ transcode_decode_query(struct decode_ctx *ctx, const char *query)
return -1;
}
int
transcode_encode_query(struct encode_ctx *ctx, const char *query)
{
if (strcmp(query, "sample_rate") == 0)
{
if (ctx->audio_stream.stream)
return ctx->audio_stream.stream->codecpar->sample_rate;
}
else if (strcmp(query, "bits_per_sample") == 0)
{
if (ctx->audio_stream.stream)
return av_get_bits_per_sample(ctx->audio_stream.stream->codecpar->codec_id);
}
else if (strcmp(query, "channels") == 0)
{
if (ctx->audio_stream.stream)
return ctx->audio_stream.stream->codecpar->channels;
}
return -1;
}
/* Metadata */
struct http_icy_metadata *

View File

@ -5,15 +5,24 @@
#include <event2/buffer.h>
#include "db.h"
#include "http.h"
#include "misc.h"
enum transcode_profile
{
// Transcodes the best audio stream into PCM16 (does not add wav header)
XCODE_PCM16_NOHEADER,
// Transcodes the best audio stream into PCM16 (with wav header)
// Used for errors
XCODE_UNKNOWN = 0,
// Decodes the best audio stream into PCM16 or PCM24, no resampling (does not add wav header)
XCODE_PCM_NATIVE,
// Decodes/resamples the best audio stream into PCM16 (with wav header)
XCODE_PCM16_HEADER,
// Decodes/resamples the best audio stream into PCM16/24/32 (no wav headers)
XCODE_PCM16,
XCODE_PCM24,
XCODE_PCM32,
// Transcodes the best audio stream into MP3
XCODE_MP3,
// Transcodes the best audio stream into OPUS
XCODE_OPUS,
// Transcodes the best video stream into JPEG/PNG
XCODE_JPEG,
XCODE_PNG,
@ -21,20 +30,26 @@ enum transcode_profile
struct decode_ctx;
struct encode_ctx;
struct transcode_ctx;
struct transcode_ctx
{
struct decode_ctx *decode_ctx;
struct encode_ctx *encode_ctx;
};
typedef void transcode_frame;
// Setting up
struct decode_ctx *
transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length);
transcode_decode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length);
struct encode_ctx *
transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height);
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, off_t *est_size, int width, int height);
struct transcode_ctx *
transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size);
transcode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size);
struct decode_ctx *
transcode_decode_setup_raw(void);
transcode_decode_setup_raw(enum transcode_profile profile, struct media_quality *quality);
int
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype);
@ -60,7 +75,7 @@ transcode_cleanup(struct transcode_ctx **ctx);
* @return Positive if OK, negative if error, 0 if EOF
*/
int
transcode_decode(void **frame, struct decode_ctx *ctx);
transcode_decode(transcode_frame **frame, struct decode_ctx *ctx);
/* Encodes and remuxes a frame. Also resamples if needed.
*
@ -71,7 +86,7 @@ transcode_decode(void **frame, struct decode_ctx *ctx);
* @return Bytes added if OK, negative if error
*/
int
transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, int eof);
transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, transcode_frame *frame, int eof);
/* Demuxes, decodes, encodes and remuxes from the input.
*
@ -87,17 +102,19 @@ int
transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int want_bytes);
/* Converts a buffer with raw data to a frame that can be passed directly to the
* transcode_encode() function
* transcode_encode() function. It does not copy, so if you free the data the
* frame will become invalid.
*
* @in profile Tells the function what kind of frame to create
* @in data Buffer with raw data
* @in size Size of buffer
* @in nsamples Number of samples in the buffer
* @in quality Sample rate, bits per sample and channels
* @return Opaque pointer to frame if OK, otherwise NULL
*/
void *
transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size);
transcode_frame *
transcode_frame_new(void *data, size_t size, int nsamples, struct media_quality *quality);
void
transcode_frame_free(void *frame);
transcode_frame_free(transcode_frame *frame);
/* Seek to the specified position - next transcode() will return this packet
*
@ -117,6 +134,16 @@ transcode_seek(struct transcode_ctx *ctx, int ms);
int
transcode_decode_query(struct decode_ctx *ctx, const char *query);
/* Query for information (e.g. sample rate) about the output being produced by
* the transcoding
*
* @in ctx Encode context
* @in query Query - see implementation for supported queries
* @return Negative if error, otherwise query dependent
*/
int
transcode_encode_query(struct encode_ctx *ctx, const char *query);
// Metadata
struct http_icy_metadata *
transcode_metadata(struct transcode_ctx *ctx, int *changed);

File diff suppressed because it is too large Load Diff