Refactor transcode.c so it can actually transcode + use new capability to support mp3 streaming.

Also includes the skeleton for perhaps supporting video in the future. Adds more fine-grained
ffmpeg/libav compability checks. Dependency on libavresample/libswresample exchanged with
dependency on libavfilter, which seems more versatile.
This commit is contained in:
ejurgensen
2015-10-09 23:58:27 +02:00
parent da3d32902e
commit 2a610812a5
16 changed files with 2180 additions and 810 deletions

View File

@@ -92,6 +92,7 @@ forked_daapd_SOURCES = main.c \
httpd_rsp.c httpd_rsp.h \
httpd_daap.c httpd_daap.h \
httpd_dacp.c httpd_dacp.h \
httpd_streaming.c httpd_streaming.h \
http.c http.h \
dmap_common.c dmap_common.h \
transcode.c transcode.h \

View File

@@ -83,8 +83,8 @@ static cfg_opt_t sec_library[] =
CFG_STR_LIST("filepath_ignore", NULL, CFGF_NONE),
CFG_BOOL("filescan_disable", cfg_false, CFGF_NONE),
CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE),
CFG_STR_LIST("no_transcode", NULL, CFGF_NONE),
CFG_STR_LIST("force_transcode", NULL, CFGF_NONE),
CFG_STR_LIST("no_decode", NULL, CFGF_NONE),
CFG_STR_LIST("force_decode", NULL, CFGF_NONE),
CFG_END()
};

93
src/ffmpeg-compat.c Normal file
View File

@@ -0,0 +1,93 @@
#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
#ifndef HAVE_LIBAV_COPY_PACKET
# define av_copy_packet(dst, src) memcpy(dst, src, sizeof(AVPacket))
#endif
#ifndef HAVE_LIBAV_FRAME_ALLOC
# define av_frame_alloc() avcodec_alloc_frame()
# define av_frame_free(x) avcodec_free_frame((x))
#endif
#ifndef HAVE_LIBAV_BEST_EFFORT_TIMESTAMP
# define av_frame_get_best_effort_timestamp(x) (x)->pts
#endif
#ifndef HAVE_LIBAV_PACKET_RESCALE_TS
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
#ifndef HAVE_LIBAV_ALLOC_OUTPUT_CONTEXT2
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

View File

@@ -51,6 +51,7 @@
#include "httpd_rsp.h"
#include "httpd_daap.h"
#include "httpd_dacp.h"
#include "httpd_streaming.h"
#include "transcode.h"
/*
@@ -126,6 +127,7 @@ static pthread_t tid_httpd;
struct stream_ctx *g_st;
#endif
static void
stream_end(struct stream_ctx *st, int failed)
{
@@ -447,8 +449,8 @@ httpd_stream_file(struct evhttp_request *req, int id)
stream_cb = stream_chunk_xcode_cb;
ret = transcode_setup(&st->xcode, mfi, &st->size, 1);
if (ret < 0)
st->xcode = transcode_setup(mfi, XCODE_PCM16_HEADER, &st->size);
if (!st->xcode)
{
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
@@ -1034,6 +1036,12 @@ httpd_gen_cb(struct evhttp_request *req, void *arg)
{
dacp_request(req);
goto out;
}
else if (streaming_is_request(req, uri))
{
streaming_request(req);
goto out;
}
@@ -1311,6 +1319,8 @@ httpd_init(void)
goto dacp_fail;
}
streaming_init();
#ifdef USE_EVENTFD
exit_efd = eventfd(0, EFD_CLOEXEC);
if (exit_efd < 0)
@@ -1387,6 +1397,7 @@ httpd_init(void)
close(exit_pipe[1]);
#endif
pipe_fail:
streaming_deinit();
dacp_deinit();
dacp_fail:
daap_deinit();
@@ -1432,6 +1443,7 @@ httpd_deinit(void)
return;
}
streaming_deinit();
rsp_deinit();
dacp_deinit();
daap_deinit();

417
src/httpd_streaming.c Normal file
View File

@@ -0,0 +1,417 @@
/*
* Copyright (C) 2015 Espen Jürgensen <espenjurgensen@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <uninorm.h>
#include <unistd.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>
#include "logger.h"
#include "conffile.h"
#include "transcode.h"
#include "player.h"
#include "listener.h"
#include "httpd.h"
#include "httpd_streaming.h"
/* httpd event base, from httpd.c */
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))
// Should prevent that we keep transcoding to dead connections
#define STREAMING_CONNECTION_TIMEOUT 60
// Linked list of Icecast requests
struct streaming_session {
struct evhttp_request *req;
struct streaming_session *next;
};
static struct streaming_session *streaming_sessions;
static int streaming_initialized;
// Buffers and interval for sending silence when playback is paused
static uint8_t *streaming_silence_data;
static size_t streaming_silence_size;
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;
// Used for pushing events and data from the player
static struct event *streamingev;
static struct player_status streaming_player_status;
static int streaming_player_changed;
static int streaming_pipe[2];
static void
streaming_fail_cb(struct evhttp_connection *evcon, void *arg)
{
struct streaming_session *this;
struct streaming_session *session;
struct streaming_session *prev;
this = (struct streaming_session *)arg;
DPRINTF(E_WARN, L_STREAMING, "Connection failed; stopping mp3 streaming to client\n");
prev = NULL;
for (session = streaming_sessions; session; session = session->next)
{
if (session->req == this->req)
break;
prev = session;
}
if (!prev)
streaming_sessions = session->next;
else
prev->next = session->next;
free(session);
if (!streaming_sessions)
{
DPRINTF(E_INFO, L_STREAMING, "No more clients, will stop streaming\n");
event_del(streamingev);
player_streaming_stop();
}
}
static void
streaming_send_cb(evutil_socket_t fd, short event, void *arg)
{
struct streaming_session *session;
struct evbuffer *evbuf;
struct decoded_frame *decoded;
uint8_t *buf;
int len;
int ret;
if (!streaming_sessions)
return;
// Callback from player (EV_READ)
if (event & EV_READ)
{
ret = read(streaming_pipe[0], &streaming_rawbuf, STREAMING_RAWBUF_SIZE);
if (ret < 0)
return;
decoded = transcode_raw2frame(streaming_rawbuf, STREAMING_RAWBUF_SIZE);
if (!decoded)
{
DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
return;
}
ret = transcode_encode(streaming_encoded_data, decoded, streaming_encode_ctx);
transcode_decoded_free(decoded);
if (ret < 0)
return;
}
// Event timed out, let's see what the player is doing and send silence if it is paused
else
{
if (streaming_player_changed)
{
streaming_player_changed = 0;
player_get_status(&streaming_player_status);
}
if (streaming_player_status.status != PLAY_PAUSED)
return;
evbuffer_add(streaming_encoded_data, streaming_silence_data, streaming_silence_size);
}
len = evbuffer_get_length(streaming_encoded_data);
// Send data
evbuf = evbuffer_new();
for (session = streaming_sessions; session; session = session->next)
{
if (session->next)
{
buf = evbuffer_pullup(streaming_encoded_data, -1);
evbuffer_add(evbuf, buf, len);
evhttp_send_reply_chunk(session->req, evbuf);
}
else
evhttp_send_reply_chunk(session->req, streaming_encoded_data);
}
evbuffer_free(evbuf);
}
// Thread: player
static int
streaming_cb(uint8_t *rawbuf, size_t size)
{
if (size != STREAMING_RAWBUF_SIZE)
{
DPRINTF(E_LOG, L_STREAMING, "Bug! Buffer size in streaming_cb does not equal input from player\n");
return -1;
}
if (write(streaming_pipe[1], rawbuf, size) < 0)
return -1;
return 0;
}
// Thread: player (not fully thread safe, but hey...)
static void
player_change_cb(enum listener_event_type type)
{
streaming_player_changed = 1;
}
int
streaming_is_request(struct evhttp_request *req, char *uri)
{
char *ptr;
ptr = strrchr(uri, '/');
if (!ptr || (strcasecmp(ptr, "/stream.mp3") != 0))
return 0;
return 1;
}
int
streaming_request(struct evhttp_request *req)
{
struct streaming_session *session;
struct evhttp_connection *evcon;
struct evkeyvalq *output_headers;
cfg_t *lib;
const char *name;
char *address;
ev_uint16_t port;
if (!streaming_initialized)
{
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;
}
evcon = evhttp_request_get_connection(req);
evhttp_connection_get_peer(evcon, &address, &port);
DPRINTF(E_INFO, L_STREAMING, "Beginning mp3 streaming to %s:%d\n", address, (int)port);
lib = cfg_getsec(cfg, "library");
name = cfg_getstr(lib, "name");
output_headers = evhttp_request_get_output_headers(req);
evhttp_add_header(output_headers, "Content-Type", "audio/mpeg");
evhttp_add_header(output_headers, "Server", "forked-daapd/" VERSION);
evhttp_add_header(output_headers, "Cache-Control", "no-cache");
evhttp_add_header(output_headers, "Pragma", "no-cache");
evhttp_add_header(output_headers, "Expires", "Mon, 31 Aug 2015 06:00:00 GMT");
evhttp_add_header(output_headers, "icy-name", name);
// TODO ICY metaint
evhttp_send_reply_start(req, HTTP_OK, "OK");
session = malloc(sizeof(struct streaming_session));
if (!session)
{
DPRINTF(E_LOG, L_STREAMING, "Out of memory for streaming request\n");
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
return -1;
}
if (!streaming_sessions)
event_add(streamingev, &streaming_silence_tv);
session->req = req;
session->next = streaming_sessions;
streaming_sessions = session;
evhttp_connection_set_timeout(evcon, STREAMING_CONNECTION_TIMEOUT);
evhttp_connection_set_closecb(evcon, streaming_fail_cb, session);
player_streaming_start(streaming_cb);
return 0;
}
int
streaming_init(void)
{
struct decode_ctx *decode_ctx;
struct decoded_frame *decoded;
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(decode_ctx, XCODE_MP3, NULL);
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
ret = pipe2(streaming_pipe, O_CLOEXEC | O_NONBLOCK);
if (ret < 0)
{
DPRINTF(E_FATAL, L_STREAMING, "Could not create pipe: %s\n", strerror(errno));
goto pipe_fail;
}
// Listen to playback changes so we don't have to poll to check for pausing
ret = listener_add(player_change_cb, LISTENER_PLAYER);
if (ret < 0)
{
DPRINTF(E_FATAL, L_STREAMING, "Could not add listener\n");
goto listener_fail;
}
// 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;
}
// 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)
{
decoded = transcode_raw2frame(streaming_rawbuf, STREAMING_RAWBUF_SIZE);
if (!decoded)
{
DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
goto silence_fail;
}
ret = transcode_encode(streaming_encoded_data, decoded, streaming_encode_ctx);
transcode_decoded_free(decoded);
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;
return 0;
silence_fail:
event_free(streamingev);
evbuffer_free(streaming_encoded_data);
event_fail:
listener_remove(player_change_cb);
listener_fail:
close(streaming_pipe[0]);
close(streaming_pipe[1]);
pipe_fail:
transcode_encode_cleanup(streaming_encode_ctx);
return -1;
}
void
streaming_deinit(void)
{
struct streaming_session *session;
struct streaming_session *next;
if (!streaming_initialized)
return;
player_streaming_stop();
event_free(streamingev);
next = NULL;
for (session = streaming_sessions; session; session = next)
{
evhttp_send_reply_end(session->req);
next = session->next;
free(session);
}
listener_remove(player_change_cb);
close(streaming_pipe[0]);
close(streaming_pipe[1]);
transcode_encode_cleanup(streaming_encode_ctx);
evbuffer_free(streaming_encoded_data);
free(streaming_silence_data);
}

25
src/httpd_streaming.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef __HTTPD_STREAMING_H__
#define __HTTPD_STREAMING_H__
#include <event2/http.h>
/* httpd_streaming takes care of incoming requests to /stream.mp3
* It will receive decoded audio from the player, and encode it, and
* stream it to one or more clients. It will not be available
* if a suitable ffmpeg/libav encoder is not present at runtime.
*/
int
streaming_is_request(struct evhttp_request *req, char *uri);
int
streaming_request(struct evhttp_request *req);
int
streaming_init(void);
void
streaming_deinit(void);
#endif /* !__HTTPD_STREAMING_H__ */

View File

@@ -43,7 +43,7 @@ static int threshold;
static int console;
static char *logfilename;
static FILE *logfile;
static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd" };
static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream" };
static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" };

View File

@@ -5,34 +5,35 @@
#include <stdarg.h>
/* Log domains */
#define L_CONF 0
#define L_DAAP 1
#define L_DB 2
#define L_HTTPD 3
#define L_HTTP 4
#define L_MAIN 5
#define L_MDNS 6
#define L_MISC 7
#define L_RSP 8
#define L_SCAN 9
#define L_XCODE 10
#define L_CONF 0
#define L_DAAP 1
#define L_DB 2
#define L_HTTPD 3
#define L_HTTP 4
#define L_MAIN 5
#define L_MDNS 6
#define L_MISC 7
#define L_RSP 8
#define L_SCAN 9
#define L_XCODE 10
/* libevent logging */
#define L_EVENT 11
#define L_REMOTE 12
#define L_DACP 13
#define L_FFMPEG 14
#define L_ART 15
#define L_PLAYER 16
#define L_RAOP 17
#define L_LAUDIO 18
#define L_DMAP 19
#define L_DBPERF 20
#define L_SPOTIFY 21
#define L_LASTFM 22
#define L_CACHE 23
#define L_MPD 24
#define L_EVENT 11
#define L_REMOTE 12
#define L_DACP 13
#define L_FFMPEG 14
#define L_ART 15
#define L_PLAYER 16
#define L_RAOP 17
#define L_LAUDIO 18
#define L_DMAP 19
#define L_DBPERF 20
#define L_SPOTIFY 21
#define L_LASTFM 22
#define L_CACHE 23
#define L_MPD 24
#define L_STREAMING 25
#define N_LOGDOMAINS 25
#define N_LOGDOMAINS 26
/* Severities */
#define E_FATAL 0

View File

@@ -50,6 +50,7 @@
#include <event.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <gcrypt.h>
GCRY_THREAD_OPTION_PTHREAD_IMPL;
@@ -585,6 +586,7 @@ main(int argc, char **argv)
}
av_register_all();
avfilter_register_all();
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 13)
avformat_network_init();
#endif

View File

@@ -116,7 +116,7 @@ struct player_source
end of file, until then it is 0. */
uint64_t end;
struct transcode_ctx *ctx;
struct transcode_ctx *xcode;
int setup_done;
struct player_source *play_next;
@@ -267,12 +267,15 @@ static uint64_t last_rtptime;
static int dev_autoselect; //TODO [player] Is this still necessary?
static struct raop_device *dev_list;
/* Device status */
/* Output status */
static enum laudio_state laudio_status;
static int laudio_selected;
static int laudio_volume;
static int laudio_relvol;
static int raop_sessions;
static int streaming_selected;
static player_streaming_cb streaming_write;
/* Commands */
static struct player_command *cur_cmd;
@@ -755,7 +758,7 @@ metadata_check_icy(void)
struct http_icy_metadata *metadata;
int changed;
transcode_metadata(cur_streaming->ctx, &metadata, &changed);
metadata = transcode_metadata(cur_streaming->xcode, &changed);
if (!metadata)
return;
@@ -847,7 +850,8 @@ stream_setup(struct player_source *ps, struct media_file_info *mfi)
switch (ps->data_kind)
{
case DATA_KIND_FILE:
ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
ps->xcode = transcode_setup(mfi, XCODE_PCM16_NOHEADER, NULL);
ret = ps->xcode ? 0 : -1;
break;
case DATA_KIND_HTTP:
@@ -858,7 +862,8 @@ stream_setup(struct player_source *ps, struct media_file_info *mfi)
free(mfi->path);
mfi->path = url;
ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
ps->xcode = transcode_setup(mfi, XCODE_PCM16_NOHEADER, NULL);
ret = ps->xcode ? 0 : -1;
break;
case DATA_KIND_SPOTIFY:
@@ -960,14 +965,14 @@ stream_read(struct player_source *ps, int len)
switch (ps->data_kind)
{
case DATA_KIND_HTTP:
ret = transcode(ps->ctx, audio_buf, len, &icy_timer);
ret = transcode(ps->xcode, audio_buf, len, &icy_timer);
if (icy_timer)
metadata_check_icy();
break;
case DATA_KIND_FILE:
ret = transcode(ps->ctx, audio_buf, len, &icy_timer);
ret = transcode(ps->xcode, audio_buf, len, &icy_timer);
break;
#ifdef HAVE_SPOTIFY_H
@@ -1060,7 +1065,7 @@ stream_seek(struct player_source *ps, int seek_ms)
break;
case DATA_KIND_FILE:
ret = transcode_seek(ps->ctx, seek_ms);
ret = transcode_seek(ps->xcode, seek_ms);
break;
#ifdef HAVE_SPOTIFY_H
@@ -1102,10 +1107,10 @@ stream_stop(struct player_source *ps)
{
case DATA_KIND_FILE:
case DATA_KIND_HTTP:
if (ps->ctx)
if (ps->xcode)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
transcode_cleanup(ps->xcode);
ps->xcode = NULL;
}
break;
@@ -1584,6 +1589,9 @@ playback_write(void)
return;
}
if (streaming_selected)
streaming_write(rawbuf, sizeof(rawbuf));
if (laudio_status & LAUDIO_F_STARTED)
laudio_write(rawbuf, last_rtptime);
@@ -2289,10 +2297,10 @@ artwork_url_get(struct player_command *cmd)
return -1;
/* Check that we are playing a viable stream, and that it has the requested id */
if (!ps->ctx || ps->data_kind != DATA_KIND_HTTP || ps->id != cmd->arg.icy.id)
if (!ps->xcode || ps->data_kind != DATA_KIND_HTTP || ps->id != cmd->arg.icy.id)
return -1;
transcode_metadata_artwork_url(ps->ctx, &cmd->arg.icy.artwork_url);
cmd->arg.icy.artwork_url = transcode_metadata_artwork_url(ps->xcode);
return 0;
}
@@ -3904,6 +3912,20 @@ player_playback_prev(void)
return ret;
}
void
player_streaming_start(player_streaming_cb cb)
{
streaming_write = cb;
streaming_selected = 1;
}
void
player_streaming_stop(void)
{
streaming_selected = 0;
}
void
player_speaker_enumerate(spk_enum_cb cb, void *arg)
{

View File

@@ -70,6 +70,7 @@ struct player_status {
};
typedef void (*spk_enum_cb)(uint64_t id, const char *name, int relvol, struct spk_flags flags, void *arg);
typedef int (*player_streaming_cb)(uint8_t *rawbuf, size_t size);
struct player_history
{
@@ -130,6 +131,12 @@ player_playback_next(void);
int
player_playback_prev(void);
void
player_streaming_start(player_streaming_cb cb);
void
player_streaming_stop(void);
int
player_volume_set(int vol);

File diff suppressed because it is too large Load Diff

View File

@@ -2,34 +2,81 @@
#ifndef __TRANSCODE_H__
#define __TRANSCODE_H__
#ifdef HAVE_LIBEVENT2
# include <event2/buffer.h>
#else
# include <event.h>
#endif
#include <event2/buffer.h>
#include "db.h"
#include "http.h"
#define XCODE_WAVHEADER (1 << 14)
#define XCODE_HAS_VIDEO (1 << 15)
enum transcode_profile
{
// Transcodes the best available audio stream into PCM16 (does not add wav header)
XCODE_PCM16_NOHEADER = 1,
// Transcodes the best available audio stream into PCM16 (with wav header)
XCODE_PCM16_HEADER = XCODE_WAVHEADER | 2,
// Transcodes the best available audio stream into MP3
XCODE_MP3 = 3,
// Transcodes video + audio + subtitle streams (not tested - for future use)
XCODE_H264_AAC = XCODE_HAS_VIDEO | 4,
};
struct decode_ctx;
struct encode_ctx;
struct transcode_ctx;
struct decoded_frame;
int
transcode(struct transcode_ctx *ctx, struct evbuffer *evbuf, int wanted, int *icy_timer);
// Setting up
struct decode_ctx *
transcode_decode_setup(struct media_file_info *mfi, int decode_video);
int
transcode_seek(struct transcode_ctx *ctx, int ms);
struct encode_ctx *
transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profile, off_t *est_size);
int
transcode_setup(struct transcode_ctx **nctx, struct media_file_info *mfi, off_t *est_size, int wavhdr);
struct transcode_ctx *
transcode_setup(struct media_file_info *mfi, enum transcode_profile profile, off_t *est_size);
void
transcode_cleanup(struct transcode_ctx *ctx);
struct decode_ctx *
transcode_decode_setup_raw(void);
int
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype);
// Cleaning up
void
transcode_metadata(struct transcode_ctx *ctx, struct http_icy_metadata **metadata, int *changed);
transcode_decode_cleanup(struct decode_ctx *ctx);
void
transcode_metadata_artwork_url(struct transcode_ctx *ctx, char **artwork_url);
transcode_encode_cleanup(struct encode_ctx *ctx);
void
transcode_cleanup(struct transcode_ctx *ctx);
void
transcode_decoded_free(struct decoded_frame *decoded);
// Transcoding
struct decoded_frame *
transcode_decode(struct decode_ctx *ctx);
int
transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx);
int
transcode(struct transcode_ctx *ctx, struct evbuffer *evbuf, int wanted, int *icy_timer);
struct decoded_frame *
transcode_raw2frame(uint8_t *data, size_t size);
// Seeking
int
transcode_seek(struct transcode_ctx *ctx, int ms);
// Metadata
struct http_icy_metadata *
transcode_metadata(struct transcode_ctx *ctx, int *changed);
char *
transcode_metadata_artwork_url(struct transcode_ctx *ctx);
#endif /* !__TRANSCODE_H__ */