mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-26 06:03:20 -05:00
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:
parent
da3d32902e
commit
2a610812a5
58
README.md
58
README.md
@ -4,8 +4,8 @@ forked-daapd is a Linux/FreeBSD DAAP (iTunes), MPD (Music Player Daemon) and
|
||||
RSP (Roku) media server.
|
||||
|
||||
It has support for AirPlay devices/speakers, Apple Remote (and compatibles),
|
||||
MPD clients, internet radio, Spotify and LastFM. It does not support AirPlay
|
||||
video.
|
||||
MPD clients, network streaming, internet radio, Spotify and LastFM. It does not
|
||||
support AirPlay video.
|
||||
|
||||
DAAP stands for Digital Audio Access Protocol, and is the protocol used
|
||||
by iTunes and friends to share/stream media libraries over the network.
|
||||
@ -31,6 +31,7 @@ forked-daapd is a complete rewrite of mt-daapd (Firefly Media Server).
|
||||
- [Using Remote](#using-remote)
|
||||
- [AirPlay devices/speakers](#airplay-devicesspeakers)
|
||||
- [Local audio output](#local-audio-output)
|
||||
- [MP3 network streaming (streaming to iOS)](#MP3-network-streaming-(streaming-to-iOS))
|
||||
- [Supported formats](#supported-formats)
|
||||
- [Streaming MPEG4](#streaming-mpeg4)
|
||||
- [Playlists and internet radio](#playlists-and-internet-radio)
|
||||
@ -62,8 +63,9 @@ forked-daapd supports these kinds of clients:
|
||||
- DAAP clients, like iTunes or Rhythmbox
|
||||
- Remote clients, like Apple Remote or compatibles for Android/Windows Phone
|
||||
- AirPlay devices, like AirPort Express, Shairport and various AirPlay speakers
|
||||
- RSP clients, like Roku Soundbridge
|
||||
- MPD clients, like mpc (see [mpd-clients](#mpd-clients))
|
||||
- MP3 network stream clients, like VLC and almost any other music player
|
||||
- RSP clients, like Roku Soundbridge
|
||||
|
||||
Like iTunes, you can control forked-daapd with Remote and stream your music
|
||||
to AirPlay devices.
|
||||
@ -76,14 +78,14 @@ probably obsolete when you read it :-)
|
||||
|
||||
| Client | Developer | Type | Platform | Working (vers.) |
|
||||
| ------------------------ | ---------- | ------ | ------------- | --------------- |
|
||||
| iTunes | Apple | DAAP | Win, OSX | Yes (12.1) |
|
||||
| iTunes | Apple | DAAP | Win, OSX | Yes (12.3) |
|
||||
| Rhythmbox | Gnome | DAAP | Linux | Yes |
|
||||
| WinAmp DAAPClient | WardFamily | DAAP | WinAmp | Yes |
|
||||
| Amarok w/DAAP plugin | KDE | DAAP | Linux/Win | Yes (2.8.0) |
|
||||
| Banshee | | DAAP | Linux/Win/OSX | No (2.6.2) |
|
||||
| jtunes4 | | DAAP | Java | No |
|
||||
| Firefly Client | | (DAAP) | Java | No |
|
||||
| Remote | Apple | Remote | iOS | Yes (4.2.1) |
|
||||
| Remote | Apple | Remote | iOS | Yes (4.2.2) |
|
||||
| Retune | SquallyDoc | Remote | Android | Yes (3.5.23) |
|
||||
| TunesRemote+ | Melloware | Remote | Android | Yes (2.5.3) |
|
||||
| Remote for iTunes | Hyperfine | Remote | Android | Yes |
|
||||
@ -146,7 +148,7 @@ local path. See [Libraries on network mounts](#libraries-on-network-mounts).
|
||||
|
||||
#### You did not enter the correct name or pairing code
|
||||
You will see an error in the log about pairing failure with a HTTP response code
|
||||
that is not 0.
|
||||
that is *not* 0.
|
||||
Solution: Copy-paste the name to be sure to get specials characters right. You
|
||||
can also try the pairinghelper script located in the scripts-folder of the
|
||||
source.
|
||||
@ -158,8 +160,10 @@ If you see an error in the log with either:
|
||||
it means that forked-daapd could not establish a connection to Remote. This
|
||||
might be a network issue.
|
||||
Solution: Sometimes it resolves the issue if you force Remote to quit, restart
|
||||
it and do the pairing proces again. Otherwise try using avahi-browse for
|
||||
troubleshooting:
|
||||
it and do the pairing proces again. Another trick is to establish some other
|
||||
connection (eg SSH) from the iPod/iPhone/iPad to the host.
|
||||
|
||||
Otherwise try using avahi-browse for troubleshooting:
|
||||
- in a terminal, run `avahi-browse -r -k _touch-remote._tcp`
|
||||
- start Remote, goto Settings, Add Library
|
||||
- after a couple seconds at most, you should get something similar to this:
|
||||
@ -214,10 +218,29 @@ audio device:
|
||||
OSS4.
|
||||
|
||||
|
||||
## MP3 network streaming (streaming to iOS)
|
||||
|
||||
You can listen to audio being played by forked-daapd by opening this network
|
||||
stream address in pretty much any music player:
|
||||
|
||||
http://[your hostname/ip address]:3689/stream.mp3
|
||||
|
||||
This is currently the only way of listening to your audio on iOS devices, since
|
||||
Apple does not allow AirPlay receiver apps, and because Apple Home Sharing
|
||||
cannot be supported by forked-daapd. So what you can do instead is install a
|
||||
music player app like VLC, connect to the stream and control playback with
|
||||
Remote. You can also use MPoD in "On the go"-mode, where control and playback is
|
||||
integrated in one app (see (#mpd-clients)).
|
||||
|
||||
Note that MP3 encoding must be supported by ffmpeg/libav for this to work. If
|
||||
it is not available you will see a message in the log file. In Debian/Ubuntu you
|
||||
get MP3 encoding support by installing the package "libavcodec-extra".
|
||||
|
||||
|
||||
## Supported formats
|
||||
|
||||
forked-daapd should support pretty much all media formats. It relies on libav
|
||||
(ffmpeg) to extract metadata and decode the files on the fly when the client
|
||||
(or ffmpeg) to extract metadata and decode the files on the fly when the client
|
||||
doesn't support the format.
|
||||
|
||||
Formats are attributed a code, so any new format will need to be explicitely
|
||||
@ -338,6 +361,9 @@ configuration file a rescan is required before the new setting will take effect.
|
||||
Currently, this will not be done automatically, so you need to trigger the
|
||||
rescan as described below.
|
||||
|
||||
|
||||
### Symlinks and pipes
|
||||
|
||||
Symlinks are supported and dereferenced. This does interact in tricky ways
|
||||
with the above monitoring and rescanning, so you've been warned. Changes to
|
||||
symlinks themselves won't be taken into account, or not the way you'd expect.
|
||||
@ -395,15 +421,15 @@ not necessary during normal operation.
|
||||
forked-daapd is meant to be used with the clients mentioned above, so it does
|
||||
not have a command line interface nor does it have a web interface. You can,
|
||||
however, to some extent control forked-daapd with [MPD clients](#mpd-clients) or
|
||||
from the command line by issuing DAAP/DACP commands with a program like curl. Here
|
||||
is an example of how to do that.
|
||||
from the command line by issuing DAAP/DACP commands with a program like curl.
|
||||
Here is an example of how to do that.
|
||||
|
||||
Say you have a playlist with a radio station, and you want to make a script that
|
||||
starts playback of that station:
|
||||
|
||||
1. Run 'sqlite3 [your forked-daapd db]'. Use 'select id,title from files' to get
|
||||
the id of the radio station, and use 'select id,title from playlists' to get the
|
||||
id of the playlist.
|
||||
the id of the radio station, and use 'select id,title from playlists' to get
|
||||
the id of the playlist.
|
||||
2. Convert the two ids to hex.
|
||||
3. Put the following lines in the script with the relevant ids inserted (also
|
||||
observe that you must use a session-id < 100, and that you must login and
|
||||
@ -420,9 +446,9 @@ curl "http://localhost:3689/logout?session-id=50"
|
||||
## Spotify
|
||||
|
||||
forked-daapd has *some* support for Spotify. It must be compiled with the
|
||||
`--enable-spotify option` (see [INSTALL](INSTALL)). You must have also have libspotify
|
||||
installed, otherwise the Spotify integration will not be available. You can
|
||||
get libspotify here:
|
||||
`--enable-spotify option` (see [INSTALL](INSTALL)). You must have also have
|
||||
libspotify installed, otherwise the Spotify integration will not be available.
|
||||
You can get libspotify here:
|
||||
|
||||
- Original (binary) tar.gz, see https://developer.spotify.com
|
||||
- Debian package (libspotify-dev), see https://apt.mopidy.com
|
||||
|
39
configure.ac
39
configure.ac
@ -83,25 +83,32 @@ AC_RUN_IFELSE(
|
||||
AC_LANG_POP([C])
|
||||
LIBS="$save_LIBS"
|
||||
|
||||
PKG_CHECK_EXISTS([ libavcodec >= 54.35 ],
|
||||
[PKG_CHECK_EXISTS([ libswresample ],
|
||||
libxxresample=libswresample;
|
||||
,
|
||||
libxxresample=libavresample;
|
||||
)]
|
||||
)
|
||||
|
||||
if test x$libxxresample = xlibswresample; then
|
||||
AC_DEFINE(HAVE_LIBSWRESAMPLE, 1, [Define to 1 if you have ffmpeg's libswresample])
|
||||
elif test x$libxxresample = xlibavresample; then
|
||||
AC_DEFINE(HAVE_LIBAVRESAMPLE, 1, [Define to 1 if you have libav's libavresample])
|
||||
fi
|
||||
|
||||
PKG_CHECK_MODULES(LIBAV, [ libavformat libavcodec libswscale libavutil $libxxresample ])
|
||||
PKG_CHECK_MODULES(LIBAV, [ libavformat libavcodec libswscale libavutil libavfilter ])
|
||||
|
||||
dnl Checks for misc libav and ffmpeg API differences
|
||||
save_LIBS="$LIBS"
|
||||
AC_CHECK_LIB([avcodec], [avcodec_find_best_pix_fmt_of_list],
|
||||
AC_DEFINE(HAVE_FFMPEG, 1, [Define to 1 if you have ffmpeg (and not libav).]))
|
||||
AC_DEFINE(HAVE_FFMPEG, 1, [Define to 1 if you have ffmpeg/libav with avcodec_find_best_pix_fmt_of_list]))
|
||||
|
||||
AC_CHECK_LIB([avfilter], [av_buffersrc_add_frame_flags],
|
||||
AC_DEFINE(HAVE_LIBAV_BUFFERSRC_ADD_FRAME_FLAGS, 1, [Define to 1 if you have ffmpeg/libav with av_buffersrc_add_frame_flags]))
|
||||
AC_CHECK_LIB([avfilter], [av_buffersink_get_frame],
|
||||
AC_DEFINE(HAVE_LIBAV_BUFFERSINK_GET_FRAME, 1, [Define to 1 if you have ffmpeg/libav with av_buffersink_get_frame]))
|
||||
AC_CHECK_LIB([avfilter], [avfilter_graph_parse_ptr],
|
||||
AC_DEFINE(HAVE_LIBAV_GRAPH_PARSE_PTR, 1, [Define to 1 if you have ffmpeg/libav with avfilter_graph_parse_ptr]))
|
||||
AC_CHECK_LIB([avcodec], [av_copy_packet],
|
||||
AC_DEFINE(HAVE_LIBAV_COPY_PACKET, 1, [Define to 1 if you have ffmpeg/libav with av_copy_packet]))
|
||||
AC_CHECK_LIB([avcodec], [av_packet_rescale_ts],
|
||||
AC_DEFINE(HAVE_LIBAV_PACKET_RESCALE_TS, 1, [Define to 1 if you have ffmpeg/libav with av_packet_rescale_ts]))
|
||||
AC_CHECK_LIB([avformat], [avformat_alloc_output_context2],
|
||||
AC_DEFINE(HAVE_LIBAV_ALLOC_OUTPUT_CONTEXT2, 1, [Define to 1 if you have ffmpeg/libav with avformat_alloc_output_context2]))
|
||||
AC_CHECK_LIB([avutil], [av_frame_alloc],
|
||||
AC_DEFINE(HAVE_LIBAV_FRAME_ALLOC, 1, [Define to 1 if you have ffmpeg/libav with av_frame_alloc]))
|
||||
AC_CHECK_LIB([avutil], [av_frame_get_best_effort_timestamp],
|
||||
AC_DEFINE(HAVE_LIBAV_BEST_EFFORT_TIMESTAMP, 1, [Define to 1 if you have ffmpeg/libav with av_frame_get_best_effort_timestamp]))
|
||||
|
||||
AC_CHECK_HEADERS([libavutil/channel_layout.h])
|
||||
AC_CHECK_HEADERS([libavutil/mathematics.h])
|
||||
LIBS="$save_LIBS"
|
||||
|
||||
PKG_CHECK_MODULES(MINIXML, [ mxml ])
|
||||
|
@ -128,11 +128,18 @@ library {
|
||||
# Should iTunes metadata override ours?
|
||||
# itunes_overrides = false
|
||||
|
||||
# Decoding options for DAAP clients
|
||||
# Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav,
|
||||
# such files will be sent as they are. Any other formats will be decoded
|
||||
# to raw wav. If forked-daapd detects a non-iTunes DAAP client, it is
|
||||
# assumed to only support mpeg and wav, other formats will be decoded.
|
||||
# Here you can change when to decode. Note that these settings have no
|
||||
# effect on AirPlay.
|
||||
# Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav
|
||||
# Formats that should never be transcoded
|
||||
# no_transcode = { "alac", "mp4a" }
|
||||
# Formats that should always be transcoded
|
||||
# force_transcode = { "ogg", "flac" }
|
||||
# Formats that should never be decoded
|
||||
# no_decode = { "format", "format" }
|
||||
# Formats that should always be decoded
|
||||
# force_decode = { "format", "format" }
|
||||
}
|
||||
|
||||
# Local audio output
|
||||
|
@ -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 \
|
||||
|
@ -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
93
src/ffmpeg-compat.c
Normal 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
|
16
src/httpd.c
16
src/httpd.c
@ -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
417
src/httpd_streaming.c
Normal 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
25
src/httpd_streaming.h
Normal 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__ */
|
@ -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" };
|
||||
|
||||
|
||||
|
53
src/logger.h
53
src/logger.h
@ -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
|
||||
|
@ -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
|
||||
|
48
src/player.c
48
src/player.c
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
2133
src/transcode.c
2133
src/transcode.c
File diff suppressed because it is too large
Load Diff
@ -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__ */
|
||||
|
Loading…
x
Reference in New Issue
Block a user