Merge branch 'rsp_daap_format3'

This commit is contained in:
ejurgensen 2024-06-17 22:37:52 +02:00
commit 4cbce79a0f
37 changed files with 2033 additions and 845 deletions

View File

@ -341,6 +341,8 @@ GET /api/outputs
| requires_auth | boolean | `true` if output requires authentication | | requires_auth | boolean | `true` if output requires authentication |
| needs_auth_key | boolean | `true` if output requires an authorization key (device verification) | | needs_auth_key | boolean | `true` if output requires an authorization key (device verification) |
| volume | integer | Volume in percent (0 - 100) | | volume | integer | Volume in percent (0 - 100) |
| format | string | Stream format |
| supported_formats | array | Array of formats supported by output |
**Example** **Example**
@ -359,7 +361,9 @@ curl -X GET "http://localhost:3689/api/outputs"
"has_password": false, "has_password": false,
"requires_auth": false, "requires_auth": false,
"needs_auth_key": false, "needs_auth_key": false,
"volume": 0 "volume": 0,
"format": "alac",
"supported_formats": [ "alac" ]
}, },
{ {
"id": "0", "id": "0",
@ -369,7 +373,9 @@ curl -X GET "http://localhost:3689/api/outputs"
"has_password": false, "has_password": false,
"requires_auth": false, "requires_auth": false,
"needs_auth_key": false, "needs_auth_key": false,
"volume": 19 "volume": 19,
"format": "pcm",
"supported_formats": [ "pcm" ]
}, },
{ {
"id": "100", "id": "100",
@ -379,7 +385,9 @@ curl -X GET "http://localhost:3689/api/outputs"
"has_password": false, "has_password": false,
"requires_auth": false, "requires_auth": false,
"needs_auth_key": false, "needs_auth_key": false,
"volume": 0 "volume": 0,
"format": "pcm",
"supported_formats": [ "pcm" ]
} }
] ]
} }
@ -448,6 +456,8 @@ curl -X GET "http://localhost:3689/api/outputs/0"
"requires_auth": false, "requires_auth": false,
"needs_auth_key": false, "needs_auth_key": false,
"volume": 3 "volume": 3
"format": "pcm",
"supported_formats": [ "pcm" ]
} }
``` ```
@ -474,6 +484,7 @@ PUT /api/outputs/{id}
| selected | boolean | *(Optional)* `true` to enable and `false` to disable the output | | selected | boolean | *(Optional)* `true` to enable and `false` to disable the output |
| volume | integer | *(Optional)* Volume in percent (0 - 100) | | volume | integer | *(Optional)* Volume in percent (0 - 100) |
| pin | string | *(Optional)* PIN for device verification | | pin | string | *(Optional)* PIN for device verification |
| format | string | *(Optional)* Stream format |
**Response** **Response**

View File

@ -52,8 +52,8 @@ general {
# IP addresses. # IP addresses.
# bind_address = "::" # bind_address = "::"
# Location of cache database # Directory where the server keeps cached data
# cache_path = "@localstatedir@/cache/@PACKAGE@/cache.db" # cache_dir = "@localstatedir@/cache/@PACKAGE@"
# DAAP requests that take longer than this threshold (in msec) get their # DAAP requests that take longer than this threshold (in msec) get their
# replies cached for next time. Set to 0 to disable caching. # replies cached for next time. Set to 0 to disable caching.
@ -187,19 +187,25 @@ library {
# Should we import the content of iTunes smart playlists? # Should we import the content of iTunes smart playlists?
# itunes_smartpl = false # itunes_smartpl = false
# Decoding options for DAAP and RSP clients # Transcoding options for DAAP and RSP clients
# Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav, # 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 # such files will be sent as they are. Any other formats will be
# to raw wav. If OwnTone detects a non-iTunes DAAP client, it is # transcoded. Some other clients, including Roku/RSP, announce what
# assumed to only support mpeg and wav, other formats will be decoded. # formats they support, and the server will transcode to one of those if
# Here you can change when to decode. Note that these settings only # necessary. Clients that don't announce supported formats are assumed
# affect serving media to DAAP and RSP clients, they have no effect on # to support mpeg (mp3), wav and alac.
# Here you can change when and how to transcode. The settings *only*
# affect serving audio to DAAP and RSP clients, they have no effect on
# direct AirPlay, Chromecast and local audio playback. # direct AirPlay, Chromecast and local audio playback.
# Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav # Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav
# Formats that should never be decoded # Formats that should never be transcoded
# no_decode = { "format", "format" } # no_decode = { "format", "format" }
# Formats that should always be decoded # Formats that should always be transcoded
# force_decode = { "format", "format" } # force_decode = { "format", "format" }
# Prefer transcode to wav (default), alac or mpeg (mp3 with the bit rate
# configured below in the streaming section). Note that alac requires
# precomputing and caching mp4 headers, which takes both cpu and disk.
# prefer_format = "format"
# Set ffmpeg filters (similar to 'ffmpeg -af xxx') that you want the # Set ffmpeg filters (similar to 'ffmpeg -af xxx') that you want the
# server to use when decoding files from your library. Examples: # server to use when decoding files from your library. Examples:

View File

@ -604,6 +604,8 @@ size_calculate(int *dst_w, int *dst_h, int src_w, int src_h, int max_w, int max_
static int static int
artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is_embedded, enum data_kind data_kind, struct artwork_req_params req_params) artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is_embedded, enum data_kind data_kind, struct artwork_req_params req_params)
{ {
struct transcode_decode_setup_args xcode_decode_args = { .profile = XCODE_JPEG }; // Covers XCODE_PNG too
struct transcode_encode_setup_args xcode_encode_args = { 0 };
struct decode_ctx *xcode_decode = NULL; struct decode_ctx *xcode_decode = NULL;
struct encode_ctx *xcode_encode = NULL; struct encode_ctx *xcode_encode = NULL;
struct transcode_evbuf_io xcode_evbuf_io = { 0 }; struct transcode_evbuf_io xcode_evbuf_io = { 0 };
@ -637,13 +639,16 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is
} }
xcode_evbuf_io.evbuf = xcode_buf; xcode_evbuf_io.evbuf = xcode_buf;
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, NULL, &xcode_evbuf_io, 0); // Covers XCODE_PNG too xcode_decode_args.evbuf_io = &xcode_evbuf_io;
xcode_decode_args.is_http = (data_kind == DATA_KIND_HTTP);
} }
else else
{ {
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, path, NULL, 0); // Covers XCODE_PNG too xcode_decode_args.path = path;
xcode_decode_args.is_http = (data_kind == DATA_KIND_HTTP);
} }
xcode_decode = transcode_decode_setup(xcode_decode_args);
if (!xcode_decode) if (!xcode_decode)
{ {
if (path) if (path)
@ -702,15 +707,19 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is
goto out; goto out;
} }
xcode_encode_args.src_ctx = xcode_decode;
xcode_encode_args.width = dst_width;
xcode_encode_args.height = dst_height;
if (dst_format == ART_FMT_JPEG) if (dst_format == ART_FMT_JPEG)
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, dst_width, dst_height); xcode_encode_args.profile = XCODE_JPEG;
else if (dst_format == ART_FMT_PNG) else if (dst_format == ART_FMT_PNG)
xcode_encode = transcode_encode_setup(XCODE_PNG, NULL, xcode_decode, dst_width, dst_height); xcode_encode_args.profile = XCODE_PNG;
else if (dst_format == ART_FMT_VP8) else if (dst_format == ART_FMT_VP8)
xcode_encode = transcode_encode_setup(XCODE_VP8, NULL, xcode_decode, dst_width, dst_height); xcode_encode_args.profile = XCODE_VP8;
else else
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, dst_width, dst_height); xcode_encode_args.profile = XCODE_JPEG;
xcode_encode = transcode_encode_setup(xcode_encode_args);
if (!xcode_encode) if (!xcode_encode)
{ {
if (path) if (path)

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
#include <event2/buffer.h> #include <event2/buffer.h>
/* ---------------------------- DAAP cache API --------------------------- */ /* ----------------------------- DAAP cache API ---------------------------- */
void void
cache_daap_suspend(void); cache_daap_suspend(void);
@ -19,10 +19,19 @@ void
cache_daap_add(const char *query, const char *ua, int is_remote, int msec); cache_daap_add(const char *query, const char *ua, int is_remote, int msec);
int int
cache_daap_threshold(void); cache_daap_threshold_get(void);
/* ---------------------------- Artwork cache API --------------------------- */ /* --------------------------- Transcode cache API ------------------------- */
int
cache_xcode_header_get(struct evbuffer *evbuf, int *cached, uint32_t id, const char *format);
int
cache_xcode_toggle(bool enable);
/* ---------------------------- Artwork cache API -------------------------- */
#define CACHE_ARTWORK_GROUP 0 #define CACHE_ARTWORK_GROUP 0
#define CACHE_ARTWORK_INDIVIDUAL 1 #define CACHE_ARTWORK_INDIVIDUAL 1
@ -48,7 +57,7 @@ cache_artwork_stash(struct evbuffer *evbuf, const char *path, int format);
int int
cache_artwork_read(struct evbuffer *evbuf, const char *path, int *format); cache_artwork_read(struct evbuffer *evbuf, const char *path, int *format);
/* ---------------------------- Cache API --------------------------- */ /* ------------------------------- Cache API ------------------------------- */
int int
cache_init(void); cache_init(void);

View File

@ -55,7 +55,7 @@ static cfg_opt_t sec_general[] =
CFG_STR_LIST("trusted_networks", "{lan}", CFGF_NONE), CFG_STR_LIST("trusted_networks", "{lan}", CFGF_NONE),
CFG_BOOL("ipv6", cfg_false, CFGF_NONE), CFG_BOOL("ipv6", cfg_false, CFGF_NONE),
CFG_STR("bind_address", NULL, CFGF_NONE), CFG_STR("bind_address", NULL, CFGF_NONE),
CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE), CFG_STR("cache_dir", STATEDIR "/cache/" PACKAGE, CFGF_NONE),
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE), CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE), CFG_BOOL("speaker_autoselect", cfg_false, CFGF_NONE),
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
@ -67,6 +67,9 @@ static cfg_opt_t sec_general[] =
CFG_INT("db_pragma_cache_size", -1, CFGF_NONE), CFG_INT("db_pragma_cache_size", -1, CFGF_NONE),
CFG_STR("db_pragma_journal_mode", NULL, CFGF_NONE), CFG_STR("db_pragma_journal_mode", NULL, CFGF_NONE),
CFG_INT("db_pragma_synchronous", -1, CFGF_NONE), CFG_INT("db_pragma_synchronous", -1, CFGF_NONE),
CFG_STR("cache_daap_filename", "daap.db", CFGF_NONE),
CFG_STR("cache_artwork_filename", "artwork.db", CFGF_NONE),
CFG_STR("cache_xcode_filename", "xcode.db", CFGF_NONE),
CFG_STR("allow_origin", "*", CFGF_NONE), CFG_STR("allow_origin", "*", CFGF_NONE),
CFG_STR("user_agent", PACKAGE_NAME "/" PACKAGE_VERSION, CFGF_NONE), CFG_STR("user_agent", PACKAGE_NAME "/" PACKAGE_VERSION, CFGF_NONE),
CFG_BOOL("ssl_verifypeer", cfg_true, CFGF_NONE), CFG_BOOL("ssl_verifypeer", cfg_true, CFGF_NONE),
@ -312,6 +315,31 @@ cb_loglevel(cfg_t *config, cfg_opt_t *opt, const char *value, void *result)
return 0; return 0;
} }
// Makes sure cache_dir ends with a slash
static int
sanitize_cache_dir(cfg_t *general)
{
char *dir;
const char *s;
char *appended;
size_t len;
dir = cfg_getstr(general, "cache_dir");
len = strlen(dir);
s = strrchr(dir, '/');
if (s && (s + 1 == dir + len))
return 0;
appended = safe_asprintf("%s/", dir);
cfg_setstr(general, "cache_dir", appended);
free(appended);
return 0;
}
static int static int
conffile_expand_libname(cfg_t *lib) conffile_expand_libname(cfg_t *lib)
{ {
@ -425,7 +453,6 @@ conffile_expand_libname(cfg_t *lib)
return 0; return 0;
} }
int int
conffile_load(char *file) conffile_load(char *file)
{ {
@ -466,6 +493,14 @@ conffile_load(char *file)
runas_uid = pw->pw_uid; runas_uid = pw->pw_uid;
runas_gid = pw->pw_gid; runas_gid = pw->pw_gid;
ret = sanitize_cache_dir(cfg_getsec(cfg, "general"));
if (ret != 0)
{
DPRINTF(E_FATAL, L_CONF, "Invalid configuration of cache_dir\n");
goto out_fail;
}
lib = cfg_getsec(cfg, "library"); lib = cfg_getsec(cfg, "library");
if (cfg_size(lib, "directories") == 0) if (cfg_size(lib, "directories") == 0)

View File

@ -4787,10 +4787,10 @@ db_admin_delete(const char *key)
int int
db_speaker_save(struct output_device *device) db_speaker_save(struct output_device *device)
{ {
#define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key) VALUES (%" PRIi64 ", %d, %d, %Q, %Q);" #define Q_TMPL "INSERT OR REPLACE INTO speakers (id, selected, volume, name, auth_key, format) VALUES (%" PRIi64 ", %d, %d, %Q, %Q, %d);"
char *query; char *query;
query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key); query = sqlite3_mprintf(Q_TMPL, device->id, device->selected, device->volume, device->name, device->auth_key, device->selected_format);
return db_query_run(query, 1, 0); return db_query_run(query, 1, 0);
#undef Q_TMPL #undef Q_TMPL
@ -4799,7 +4799,7 @@ db_speaker_save(struct output_device *device)
int int
db_speaker_get(struct output_device *device, uint64_t id) db_speaker_get(struct output_device *device, uint64_t id)
{ {
#define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key FROM speakers s WHERE s.id = %" PRIi64 ";" #define Q_TMPL "SELECT s.selected, s.volume, s.name, s.auth_key, s.format FROM speakers s WHERE s.id = %" PRIi64 ";"
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
char *query; char *query;
int ret; int ret;
@ -4841,6 +4841,8 @@ db_speaker_get(struct output_device *device, uint64_t id)
free(device->auth_key); free(device->auth_key);
device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3)); device->auth_key = safe_strdup((char *)sqlite3_column_text(stmt, 3));
device->selected_format = sqlite3_column_int(stmt, 4);
#ifdef DB_PROFILE #ifdef DB_PROFILE
while (db_blocking_step(stmt) == SQLITE_ROW) while (db_blocking_step(stmt) == SQLITE_ROW)
; /* EMPTY */ ; /* EMPTY */
@ -6892,9 +6894,6 @@ db_open(void)
int synchronous; int synchronous;
int mmap_size; int mmap_size;
if (!db_path)
return -1;
ret = sqlite3_open(db_path, &hdl); ret = sqlite3_open(db_path, &hdl);
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
@ -7343,35 +7342,35 @@ db_init(void)
db_path = cfg_getstr(cfg_getsec(cfg, "general"), "db_path"); db_path = cfg_getstr(cfg_getsec(cfg, "general"), "db_path");
db_rating_updates = cfg_getbool(cfg_getsec(cfg, "library"), "rating_updates"); db_rating_updates = cfg_getbool(cfg_getsec(cfg, "library"), "rating_updates");
DPRINTF(E_LOG, L_DB, "Configured to use database file '%s'\n", db_path); DPRINTF(E_INFO, L_DB, "Configured to use database file '%s'\n", db_path);
ret = sqlite3_config(SQLITE_CONFIG_MULTITHREAD); ret = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
DPRINTF(E_FATAL, L_DB, "Could not switch SQLite3 to multithread mode\n"); DPRINTF(E_FATAL, L_DB, "Could not switch SQLite3 to multithread mode\n");
DPRINTF(E_FATAL, L_DB, "Check that SQLite3 has been configured for thread-safe operations\n"); DPRINTF(E_FATAL, L_DB, "Check that SQLite3 has been configured for thread-safe operations\n");
return -1; goto error;
} }
ret = sqlite3_enable_shared_cache(1); ret = sqlite3_enable_shared_cache(1);
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
DPRINTF(E_FATAL, L_DB, "Could not enable SQLite3 shared-cache mode\n"); DPRINTF(E_FATAL, L_DB, "Could not enable SQLite3 shared-cache mode\n");
return -1; goto error;
} }
ret = sqlite3_initialize(); ret = sqlite3_initialize();
if (ret != SQLITE_OK) if (ret != SQLITE_OK)
{ {
DPRINTF(E_FATAL, L_DB, "SQLite3 failed to initialize\n"); DPRINTF(E_FATAL, L_DB, "SQLite3 failed to initialize\n");
return -1; goto error;
} }
ret = db_open(); ret = db_open();
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_FATAL, L_DB, "Could not open database\n"); DPRINTF(E_FATAL, L_DB, "Could not open database\n");
return -1; goto error;
} }
ret = db_check_version(); ret = db_check_version();
@ -7380,7 +7379,7 @@ db_init(void)
DPRINTF(E_FATAL, L_DB, "Database version check errored out, incompatible database\n"); DPRINTF(E_FATAL, L_DB, "Database version check errored out, incompatible database\n");
db_perthread_deinit(); db_perthread_deinit();
return -1; goto error;
} }
else if (ret > 0) else if (ret > 0)
{ {
@ -7391,7 +7390,7 @@ db_init(void)
{ {
DPRINTF(E_FATAL, L_DB, "Could not create tables\n"); DPRINTF(E_FATAL, L_DB, "Could not create tables\n");
db_perthread_deinit(); db_perthread_deinit();
return -1; goto error;
} }
} }
@ -7409,6 +7408,9 @@ db_init(void)
rng_init(&shuffle_rng); rng_init(&shuffle_rng);
return 0; return 0;
error:
return -1;
} }
void void

View File

@ -151,8 +151,9 @@
" id INTEGER PRIMARY KEY NOT NULL," \ " id INTEGER PRIMARY KEY NOT NULL," \
" selected INTEGER NOT NULL," \ " selected INTEGER NOT NULL," \
" volume INTEGER NOT NULL," \ " volume INTEGER NOT NULL," \
" name VARCHAR(255) DEFAULT NULL," \ " name VARCHAR(255) DEFAULT NULL," \
" auth_key VARCHAR(2048) DEFAULT NULL" \ " auth_key VARCHAR(2048) DEFAULT NULL," \
" format INTEGER DEFAULT 0" \
");" ");"
#define T_INOTIFY \ #define T_INOTIFY \

View File

@ -26,7 +26,7 @@
* is a major upgrade. In other words minor version upgrades permit downgrading * is a major upgrade. In other words minor version upgrades permit downgrading
* the server after the database was upgraded. */ * the server after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 22 #define SCHEMA_VERSION_MAJOR 22
#define SCHEMA_VERSION_MINOR 1 #define SCHEMA_VERSION_MINOR 2
int int
db_init_indices(sqlite3 *hdl); db_init_indices(sqlite3 *hdl);

View File

@ -1246,6 +1246,24 @@ static const struct db_upgrade_query db_upgrade_v2201_queries[] =
}; };
/* ---------------------------- 22.01 -> 22.02 ------------------------------ */
#define U_v2202_ALTER_SPEAKERS_ADD_FORMAT \
"ALTER TABLE speakers ADD COLUMN format INTEGER DEFAULT 0;"
#define U_v2202_SCVER_MAJOR \
"UPDATE admin SET value = '22' WHERE key = 'schema_version_major';"
#define U_v2202_SCVER_MINOR \
"UPDATE admin SET value = '02' WHERE key = 'schema_version_minor';"
static const struct db_upgrade_query db_upgrade_v2202_queries[] =
{
{ U_v2202_ALTER_SPEAKERS_ADD_FORMAT, "alter table speakers add column format" },
{ U_v2202_SCVER_MAJOR, "set schema_version_major to 22" },
{ U_v2202_SCVER_MINOR, "set schema_version_minor to 02" },
};
/* -------------------------- Main upgrade handler -------------------------- */ /* -------------------------- Main upgrade handler -------------------------- */
@ -1464,6 +1482,13 @@ db_upgrade(sqlite3 *hdl, int db_ver)
if (ret < 0) if (ret < 0)
return -1; return -1;
/* FALLTHROUGH */
case 2201:
ret = db_generic_upgrade(hdl, db_upgrade_v2202_queries, ARRAY_SIZE(db_upgrade_v2202_queries));
if (ret < 0)
return -1;
/* Last case statement is the only one that ends with a break statement! */ /* Last case statement is the only one that ends with a break statement! */
break; break;

View File

@ -48,6 +48,9 @@
#include "httpd.h" #include "httpd.h"
#include "httpd_internal.h" #include "httpd_internal.h"
#include "transcode.h" #include "transcode.h"
#include "cache.h"
#include "listener.h"
#include "player.h"
#ifdef LASTFM #ifdef LASTFM
# include "lastfm.h" # include "lastfm.h"
#endif #endif
@ -105,18 +108,19 @@ struct stream_ctx {
static const struct content_type_map ext2ctype[] = static const struct content_type_map ext2ctype[] =
{ {
{ ".html", XCODE_NONE, "text/html; charset=utf-8" }, { ".html", XCODE_NONE, "text/html; charset=utf-8" },
{ ".xml", XCODE_NONE, "text/xml; charset=utf-8" }, { ".xml", XCODE_NONE, "text/xml; charset=utf-8" },
{ ".css", XCODE_NONE, "text/css; charset=utf-8" }, { ".css", XCODE_NONE, "text/css; charset=utf-8" },
{ ".txt", XCODE_NONE, "text/plain; charset=utf-8" }, { ".txt", XCODE_NONE, "text/plain; charset=utf-8" },
{ ".js", XCODE_NONE, "application/javascript; charset=utf-8" }, { ".js", XCODE_NONE, "application/javascript; charset=utf-8" },
{ ".gif", XCODE_NONE, "image/gif" }, { ".gif", XCODE_NONE, "image/gif" },
{ ".ico", XCODE_NONE, "image/x-ico" }, { ".ico", XCODE_NONE, "image/x-ico" },
{ ".png", XCODE_PNG, "image/png" }, { ".png", XCODE_PNG, "image/png" },
{ ".jpg", XCODE_JPEG, "image/jpeg" }, { ".jpg", XCODE_JPEG, "image/jpeg" },
{ ".mp3", XCODE_MP3, "audio/mpeg" }, { ".mp3", XCODE_MP3, "audio/mpeg" },
{ ".wav", XCODE_WAV, "audio/wav" }, { ".m4a", XCODE_MP4_ALAC, "audio/mp4" },
{ NULL, XCODE_NONE, NULL } { ".wav", XCODE_WAV, "audio/wav" },
{ NULL, XCODE_NONE, NULL }
}; };
static char webroot_directory[PATH_MAX]; static char webroot_directory[PATH_MAX];
@ -672,8 +676,16 @@ static struct stream_ctx *
stream_new_transcode(struct media_file_info *mfi, enum transcode_profile profile, struct httpd_request *hreq, stream_new_transcode(struct media_file_info *mfi, enum transcode_profile profile, struct httpd_request *hreq,
int64_t offset, int64_t end_offset, event_callback_fn stream_cb) int64_t offset, int64_t end_offset, event_callback_fn stream_cb)
{ {
struct transcode_decode_setup_args decode_args = { 0 };
struct transcode_encode_setup_args encode_args = { 0 };
struct media_quality quality = { 0 };
struct evbuffer *prepared_header = NULL;
struct stream_ctx *st; struct stream_ctx *st;
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, HTTPD_STREAM_BIT_RATE }; int cached;
int ret;
// We use source sample rate etc, but for MP3 we must set a bit rate
quality.bit_rate = 1000 * cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");
st = stream_new(mfi, hreq, stream_cb); st = stream_new(mfi, hreq, stream_cb);
if (!st) if (!st)
@ -681,7 +693,27 @@ stream_new_transcode(struct media_file_info *mfi, enum transcode_profile profile
goto error; goto error;
} }
st->xcode = transcode_setup(profile, &quality, mfi->data_kind, mfi->path, mfi->song_length); if (profile == XCODE_MP4_ALAC)
{
CHECK_NULL(L_HTTPD, prepared_header = evbuffer_new());
ret = cache_xcode_header_get(prepared_header, &cached, mfi->id, "mp4");
if (ret < 0 || !cached) // Error or not found
{
evbuffer_free(prepared_header);
prepared_header = NULL;
}
}
decode_args.profile = profile;
decode_args.is_http = (mfi->data_kind == DATA_KIND_HTTP);
decode_args.path = mfi->path;
decode_args.len_ms = mfi->song_length;
encode_args.profile = profile;
encode_args.quality = &quality;
encode_args.prepared_header = prepared_header;
st->xcode = transcode_setup(decode_args, encode_args);
if (!st->xcode) if (!st->xcode)
{ {
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n"); DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
@ -705,9 +737,13 @@ stream_new_transcode(struct media_file_info *mfi, enum transcode_profile profile
st->start_offset = offset; st->start_offset = offset;
if (prepared_header)
evbuffer_free(prepared_header);
return st; return st;
error: error:
if (prepared_header)
evbuffer_free(prepared_header);
stream_free(st); stream_free(st);
return NULL; return NULL;
} }
@ -871,6 +907,39 @@ stream_fail_cb(void *arg)
} }
/* -------------------------- SPEAKER/CACHE HANDLING ------------------------ */
// Thread: player (must not block)
static void
speaker_enum_cb(struct player_speaker_info *spk, void *arg)
{
bool *want_mp4 = arg;
*want_mp4 = *want_mp4 || (spk->format == MEDIA_FORMAT_ALAC && strcmp(spk->output_type, "RCP/SoundBridge") == 0);
}
// Thread: worker
static void
speaker_update_handler_cb(void *arg)
{
const char *prefer_format = cfg_getstr(cfg_getsec(cfg, "library"), "prefer_format");
bool want_mp4;
want_mp4 = (prefer_format && (strcmp(prefer_format, "alac") == 0));
if (!want_mp4)
player_speaker_enumerate(speaker_enum_cb, &want_mp4);
cache_xcode_toggle(want_mp4);
}
// Thread: player (must not block)
static void
httpd_speaker_update_handler(short event_mask)
{
worker_execute(speaker_update_handler_cb, NULL, 0, 0);
}
/* ---------------------------- REQUEST CALLBACKS --------------------------- */ /* ---------------------------- REQUEST CALLBACKS --------------------------- */
// Worker thread, invoked by request_cb() below // Worker thread, invoked by request_cb() below
@ -940,6 +1009,7 @@ httpd_stream_file(struct httpd_request *hreq, int id)
struct media_file_info *mfi = NULL; struct media_file_info *mfi = NULL;
struct stream_ctx *st = NULL; struct stream_ctx *st = NULL;
enum transcode_profile profile; enum transcode_profile profile;
enum transcode_profile spk_profile;
const char *param; const char *param;
const char *param_end; const char *param_end;
const char *ctype; const char *ctype;
@ -1013,6 +1083,10 @@ httpd_stream_file(struct httpd_request *hreq, int id)
{ {
DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path); DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path);
spk_profile = httpd_xcode_profile_get(hreq);
if (spk_profile != XCODE_NONE)
profile = spk_profile;
st = stream_new_transcode(mfi, profile, hreq, offset, end_offset, stream_chunk_xcode_cb); st = stream_new_transcode(mfi, profile, hreq, offset, end_offset, stream_chunk_xcode_cb);
if (!st) if (!st)
goto error; goto error;
@ -1119,6 +1193,32 @@ httpd_stream_file(struct httpd_request *hreq, int id)
free_mfi(mfi, 0); free_mfi(mfi, 0);
} }
// Returns enum transcode_profile, but is just declared with int so we don't
// need to include transcode.h in httpd_internal.h
int
httpd_xcode_profile_get(struct httpd_request *hreq)
{
struct player_speaker_info spk;
int ret;
DPRINTF(E_DBG, L_HTTPD, "Checking if client '%s' is a speaker\n", hreq->peer_address);
// A Roku Soundbridge may also be RCP device/speaker for which the user may
// have set a prefered streaming format
ret = player_speaker_get_byaddress(&spk, hreq->peer_address);
if (ret < 0)
return XCODE_NONE;
if (spk.format == MEDIA_FORMAT_WAV)
return XCODE_WAV;
if (spk.format == MEDIA_FORMAT_MP3)
return XCODE_MP3;
if (spk.format == MEDIA_FORMAT_ALAC)
return XCODE_MP4_ALAC;
return XCODE_NONE;
}
struct evbuffer * struct evbuffer *
httpd_gzip_deflate(struct evbuffer *in) httpd_gzip_deflate(struct evbuffer *in)
{ {
@ -1186,7 +1286,6 @@ httpd_gzip_deflate(struct evbuffer *in)
return NULL; return NULL;
} }
// The httpd_send functions below can be called from a worker thread (with // The httpd_send functions below can be called from a worker thread (with
// hreq->is_async) or directly from the httpd thread. In the former case, they // hreq->is_async) or directly from the httpd thread. In the former case, they
// will command sending from the httpd thread, since it is not safe to access // will command sending from the httpd thread, since it is not safe to access
@ -1501,6 +1600,10 @@ httpd_init(const char *webroot)
goto error; goto error;
} }
// We need to know about speaker format changes so we can ask the cache to
// start preparing headers for mp4/alac if selected
listener_add(httpd_speaker_update_handler, LISTENER_SPEAKER);
return 0; return 0;
error: error:
@ -1512,6 +1615,8 @@ httpd_init(const char *webroot)
void void
httpd_deinit(void) httpd_deinit(void)
{ {
listener_remove(httpd_speaker_update_handler);
// Give modules a chance to hang up connections nicely // Give modules a chance to hang up connections nicely
modules_deinit(); modules_deinit();

View File

@ -1146,12 +1146,13 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
const struct dmap_field **meta = NULL; const struct dmap_field **meta = NULL;
struct sort_ctx *sctx; struct sort_ctx *sctx;
const char *param; const char *param;
const char *client_codecs; const char *accept_codecs;
const char *tag; const char *tag;
size_t len; size_t len;
enum transcode_profile spk_profile;
enum transcode_profile profile; enum transcode_profile profile;
struct transcode_metadata_string xcode_metadata; struct transcode_metadata_string xcode_metadata;
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, HTTPD_STREAM_BIT_RATE }; struct media_quality quality = { 0 };
uint32_t len_ms; uint32_t len_ms;
int nmeta = 0; int nmeta = 0;
int sort_headers; int sort_headers;
@ -1216,12 +1217,14 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
goto error; goto error;
} }
client_codecs = NULL; accept_codecs = NULL;
if (!s->is_remote && hreq->in_headers) if (!s->is_remote && hreq->in_headers)
{ {
client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs"); accept_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
} }
spk_profile = httpd_xcode_profile_get(hreq);
nsongs = 0; nsongs = 0;
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0) while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
{ {
@ -1229,16 +1232,24 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
// Not sure if the is_remote path is really needed. Note that if you // Not sure if the is_remote path is really needed. Note that if you
// change the below you might need to do the same in rsp_reply_playlist() // change the below you might need to do the same in rsp_reply_playlist()
profile = s->is_remote ? XCODE_WAV : transcode_needed(hreq->user_agent, client_codecs, dbmfi.codectype); profile = s->is_remote ? XCODE_WAV : transcode_needed(hreq->user_agent, accept_codecs, dbmfi.codectype);
if (profile == XCODE_UNKNOWN) if (profile == XCODE_UNKNOWN)
{ {
DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname); DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname);
} }
else if (profile != XCODE_NONE) else if (profile != XCODE_NONE)
{ {
if (spk_profile != XCODE_NONE)
profile = spk_profile;
if (safe_atou32(dbmfi.song_length, &len_ms) < 0) if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
len_ms = 3 * 60 * 1000; // just a fallback default len_ms = 3 * 60 * 1000; // just a fallback default
safe_atoi32(dbmfi.samplerate, &quality.sample_rate);
safe_atoi32(dbmfi.bits_per_sample, &quality.bits_per_sample);
safe_atoi32(dbmfi.channels, &quality.channels);
quality.bit_rate = cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");
transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms); transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
dbmfi.type = xcode_metadata.type; dbmfi.type = xcode_metadata.type;
dbmfi.codectype = xcode_metadata.codectype; dbmfi.codectype = xcode_metadata.codectype;
@ -2271,7 +2282,7 @@ daap_request(struct httpd_request *hreq)
DPRINTF(E_DBG, L_DAAP, "DAAP request handled in %d milliseconds\n", msec); DPRINTF(E_DBG, L_DAAP, "DAAP request handled in %d milliseconds\n", msec);
if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold() && hreq->user_agent) if (ret == DAAP_REPLY_OK && msec > cache_daap_threshold_get() && hreq->user_agent)
cache_daap_add(hreq->uri, hreq->user_agent, ((struct daap_session *)hreq->extra_data)->is_remote, msec); cache_daap_add(hreq->uri, hreq->user_agent, ((struct daap_session *)hreq->extra_data)->is_remote, msec);
daap_reply_send(hreq, ret); // hreq is deallocted daap_reply_send(hreq, ret); // hreq is deallocted

View File

@ -36,11 +36,6 @@
#define HTTP_BADGATEWAY 502 /**< received an invalid response from the upstream */ #define HTTP_BADGATEWAY 502 /**< received an invalid response from the upstream */
#define HTTP_SERVUNAVAIL 503 /**< the server is not available */ #define HTTP_SERVUNAVAIL 503 /**< the server is not available */
#define HTTPD_STREAM_SAMPLE_RATE 44100
#define HTTPD_STREAM_BPS 16
#define HTTPD_STREAM_CHANNELS 2
#define HTTPD_STREAM_BIT_RATE 320000
struct httpd_request; struct httpd_request;
@ -211,6 +206,9 @@ struct httpd_request {
void void
httpd_stream_file(struct httpd_request *hreq, int id); httpd_stream_file(struct httpd_request *hreq, int id);
int
httpd_xcode_profile_get(struct httpd_request *hreq);
void void
httpd_request_handler_set(struct httpd_request *hreq); httpd_request_handler_set(struct httpd_request *hreq);

View File

@ -1526,10 +1526,19 @@ static json_object *
speaker_to_json(struct player_speaker_info *spk) speaker_to_json(struct player_speaker_info *spk)
{ {
json_object *output; json_object *output;
json_object *supported_formats;
char output_id[21]; char output_id[21];
enum media_format format;
output = json_object_new_object(); output = json_object_new_object();
supported_formats = json_object_new_array();
for (format = MEDIA_FORMAT_FIRST; format <= MEDIA_FORMAT_LAST; format = MEDIA_FORMAT_NEXT(format))
{
if (format & spk->supported_formats)
json_object_array_add(supported_formats, json_object_new_string(media_format_to_string(format)));
}
snprintf(output_id, sizeof(output_id), "%" PRIu64, spk->id); snprintf(output_id, sizeof(output_id), "%" PRIu64, spk->id);
json_object_object_add(output, "id", json_object_new_string(output_id)); json_object_object_add(output, "id", json_object_new_string(output_id));
json_object_object_add(output, "name", json_object_new_string(spk->name)); json_object_object_add(output, "name", json_object_new_string(spk->name));
@ -1539,6 +1548,8 @@ speaker_to_json(struct player_speaker_info *spk)
json_object_object_add(output, "requires_auth", json_object_new_boolean(spk->requires_auth)); json_object_object_add(output, "requires_auth", json_object_new_boolean(spk->requires_auth));
json_object_object_add(output, "needs_auth_key", json_object_new_boolean(spk->needs_auth_key)); json_object_object_add(output, "needs_auth_key", json_object_new_boolean(spk->needs_auth_key));
json_object_object_add(output, "volume", json_object_new_int(spk->absvol)); json_object_object_add(output, "volume", json_object_new_int(spk->absvol));
json_object_object_add(output, "format", json_object_new_string(media_format_to_string(spk->format)));
json_object_object_add(output, "supported_formats", supported_formats);
return output; return output;
} }
@ -1602,6 +1613,7 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
bool selected; bool selected;
int volume; int volume;
const char *pin; const char *pin;
const char *format;
int ret; int ret;
ret = safe_atou64(hreq->path_parts[2], &output_id); ret = safe_atou64(hreq->path_parts[2], &output_id);
@ -1644,10 +1656,17 @@ jsonapi_reply_outputs_put_byid(struct httpd_request *hreq)
ret = player_speaker_authorize(output_id, pin); ret = player_speaker_authorize(output_id, pin);
} }
if (ret == 0 && jparse_contains_key(request, "format", json_type_string))
{
format = jparse_str_from_obj(request, "format");
if (format)
ret = player_speaker_format_set(output_id, media_format_from_string(format));
}
jparse_free(request); jparse_free(request);
if (ret < 0) if (ret < 0)
return HTTP_INTERNAL; return HTTP_BADREQUEST;
return HTTP_NOCONTENT; return HTTP_NOCONTENT;
} }

View File

@ -525,6 +525,7 @@ httpd_backend_output_buffer_get(httpd_backend *backend)
int int
httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data) httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend, httpd_backend_data *backend_data)
{ {
#define IPV4_MAPPED_IPV6_PREFIX "::ffff:"
httpd_connection *conn = evhttp_request_get_connection(backend); httpd_connection *conn = evhttp_request_get_connection(backend);
if (!conn) if (!conn)
return -1; return -1;
@ -534,6 +535,11 @@ httpd_backend_peer_get(const char **addr, uint16_t *port, httpd_backend *backend
#else #else
evhttp_connection_get_peer(conn, (char **)addr, port); evhttp_connection_get_peer(conn, (char **)addr, port);
#endif #endif
// Just use the pure ipv4 address if it's mapped
if (strncmp(*addr, IPV4_MAPPED_IPV6_PREFIX, strlen(IPV4_MAPPED_IPV6_PREFIX)) == 0)
*addr += strlen(IPV4_MAPPED_IPV6_PREFIX);
return 0; return 0;
} }

View File

@ -415,9 +415,9 @@ rsp_reply_db(struct httpd_request *hreq)
} }
static int static int
item_add(xml_node *parent, struct query_params *qp, const char *user_agent, const char *client_codecs, int mode) item_add(xml_node *parent, struct query_params *qp, enum transcode_profile spk_profile, const char *user_agent, const char *accept_codecs, int mode)
{ {
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, HTTPD_STREAM_BIT_RATE }; struct media_quality quality = { 0 };
struct db_media_file_info dbmfi; struct db_media_file_info dbmfi;
struct transcode_metadata_string xcode_metadata; struct transcode_metadata_string xcode_metadata;
enum transcode_profile profile; enum transcode_profile profile;
@ -432,18 +432,26 @@ item_add(xml_node *parent, struct query_params *qp, const char *user_agent, cons
if (ret != 0) if (ret != 0)
return ret; return ret;
profile = transcode_needed(user_agent, client_codecs, dbmfi.codectype); profile = transcode_needed(user_agent, accept_codecs, dbmfi.codectype);
if (profile == XCODE_UNKNOWN) if (profile == XCODE_UNKNOWN)
{ {
DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname); DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname);
} }
else if (profile != XCODE_NONE) else if (profile != XCODE_NONE)
{ {
if (spk_profile != XCODE_NONE)
profile = spk_profile; // User has configured a specific transcode format for this speaker
orgcodec = dbmfi.codectype; orgcodec = dbmfi.codectype;
if (safe_atou32(dbmfi.song_length, &len_ms) < 0) if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
len_ms = 3 * 60 * 1000; // just a fallback default len_ms = 3 * 60 * 1000; // just a fallback default
safe_atoi32(dbmfi.samplerate, &quality.sample_rate);
safe_atoi32(dbmfi.bits_per_sample, &quality.bits_per_sample);
safe_atoi32(dbmfi.channels, &quality.channels);
quality.bit_rate = cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");
transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms); transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
dbmfi.type = xcode_metadata.type; dbmfi.type = xcode_metadata.type;
dbmfi.codectype = xcode_metadata.codectype; dbmfi.codectype = xcode_metadata.codectype;
@ -479,7 +487,8 @@ rsp_reply_playlist(struct httpd_request *hreq)
{ {
struct query_params qp; struct query_params qp;
const char *param; const char *param;
const char *client_codecs; const char *accept_codecs;
enum transcode_profile spk_profile;
xml_node *response; xml_node *response;
xml_node *items; xml_node *items;
int mode; int mode;
@ -488,7 +497,8 @@ rsp_reply_playlist(struct httpd_request *hreq)
memset(&qp, 0, sizeof(struct query_params)); memset(&qp, 0, sizeof(struct query_params));
client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs"); accept_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
spk_profile = httpd_xcode_profile_get(hreq);
ret = safe_atoi32(hreq->path_parts[2], &qp.id); ret = safe_atoi32(hreq->path_parts[2], &qp.id);
if (ret < 0) if (ret < 0)
@ -550,7 +560,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
items = xml_new_node(response, "items", NULL); items = xml_new_node(response, "items", NULL);
do do
{ {
ret = item_add(items, &qp, hreq->user_agent, client_codecs, mode); ret = item_add(items, &qp, spk_profile, hreq->user_agent, accept_codecs, mode);
} }
while (ret == 0); while (ret == 0);
@ -715,6 +725,16 @@ rsp_stream(struct httpd_request *hreq)
// /rsp/stream/36364 // /rsp/stream/36364
// /rsp/db/0?query=id%3D36365&type=full // /rsp/db/0?query=id%3D36365&type=full
// /rsp/stream/36365 // /rsp/stream/36365
//
// Headers sent from Roku M2000 and M1001 in stream requests (and other?):
//
// 'User-Agent': 'Roku SoundBridge/3.0'
// 'Host': '192.168.1.119:3689'
// 'Accept': '*/*'
// 'Pragma': 'no-cache'
// 'accept-codecs': 'wma,mpeg,wav,mp4a,alac'
// 'rsp-version': '0.1'
// 'transcode-codecs': 'wav,mp3'
static struct httpd_uri_map rsp_handlers[] = static struct httpd_uri_map rsp_handlers[] =
{ {
{ {

View File

@ -228,7 +228,7 @@ session_free(struct streaming_session *session)
} }
static struct streaming_session * static struct streaming_session *
session_new(struct httpd_request *hreq, bool icy_is_requested, enum player_format format, struct media_quality quality) session_new(struct httpd_request *hreq, bool icy_is_requested, enum media_format format, struct media_quality quality)
{ {
struct streaming_session *session; struct streaming_session *session;
int audio_fd; int audio_fd;
@ -279,7 +279,7 @@ streaming_mp3_handler(struct httpd_request *hreq)
httpd_header_add(hreq->out_headers, "icy-metaint", buf); httpd_header_add(hreq->out_headers, "icy-metaint", buf);
} }
session = session_new(hreq, icy_is_requested, PLAYER_FORMAT_MP3, streaming_default_quality); session = session_new(hreq, icy_is_requested, MEDIA_FORMAT_MP3, streaming_default_quality);
if (!session) if (!session)
return -1; // Error sent by caller return -1; // Error sent by caller

View File

@ -36,9 +36,11 @@
static int static int
setup(struct input_source *source) setup(struct input_source *source)
{ {
struct transcode_decode_setup_args decode_args = { .profile = XCODE_PCM_NATIVE, .path = source->path, .len_ms = source->len_ms };
struct transcode_encode_setup_args encode_args = { .profile = XCODE_PCM_NATIVE, };
struct transcode_ctx *ctx; struct transcode_ctx *ctx;
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms); ctx = transcode_setup(decode_args, encode_args);
if (!ctx) if (!ctx)
return -1; return -1;

View File

@ -295,6 +295,8 @@ metadata_prepare(struct input_source *source)
static int static int
setup(struct input_source *source) setup(struct input_source *source)
{ {
struct transcode_decode_setup_args decode_args = { .profile = XCODE_PCM_NATIVE, .is_http = true, .len_ms = source->len_ms };
struct transcode_encode_setup_args encode_args = { .profile = XCODE_PCM_NATIVE, };
struct transcode_ctx *ctx; struct transcode_ctx *ctx;
char *url; char *url;
@ -303,8 +305,9 @@ setup(struct input_source *source)
free(source->path); free(source->path);
source->path = url; source->path = url;
decode_args.path = url;
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms); ctx = transcode_setup(decode_args, encode_args);
if (!ctx) if (!ctx)
return -1; return -1;

View File

@ -391,21 +391,18 @@ download_seek(void *arg, int64_t offset, enum transcode_seek_type type)
static int static int
download_xcode_setup(struct download_ctx *download) download_xcode_setup(struct download_ctx *download)
{ {
struct transcode_ctx *xcode; struct transcode_decode_setup_args decode_args = { .profile = XCODE_OGG, .len_ms = download->len_ms };
struct transcode_encode_setup_args encode_args = { .profile = XCODE_PCM16, };
struct transcode_evbuf_io xcode_evbuf_io = { 0 }; struct transcode_evbuf_io xcode_evbuf_io = { 0 };
struct transcode_ctx *xcode;
CHECK_NULL(L_SPOTIFY, xcode = malloc(sizeof(struct transcode_ctx)));
xcode_evbuf_io.evbuf = download->read_buf; xcode_evbuf_io.evbuf = download->read_buf;
xcode_evbuf_io.seekfn = download_seek; xcode_evbuf_io.seekfn = download_seek;
xcode_evbuf_io.seekfn_arg = download; xcode_evbuf_io.seekfn_arg = download;
decode_args.evbuf_io = &xcode_evbuf_io;
xcode->decode_ctx = transcode_decode_setup(XCODE_OGG, NULL, DATA_KIND_SPOTIFY, NULL, &xcode_evbuf_io, download->len_ms); xcode = transcode_setup(decode_args, encode_args);
if (!xcode->decode_ctx) if (!xcode)
goto error;
xcode->encode_ctx = transcode_encode_setup(XCODE_PCM16, NULL, xcode->decode_ctx, 0, 0);
if (!xcode->encode_ctx)
goto error; goto error;
download->xcode = xcode; download->xcode = xcode;

View File

@ -1731,6 +1731,40 @@ 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 && a->bit_rate == b->bit_rate); return (a->sample_rate == b->sample_rate && a->bits_per_sample == b->bits_per_sample && a->channels == b->channels && a->bit_rate == b->bit_rate);
} }
enum media_format
media_format_from_string(const char *s)
{
if (strcmp(s, "pcm") == 0)
return MEDIA_FORMAT_PCM;
if (strcmp(s, "wav") == 0)
return MEDIA_FORMAT_WAV;
if (strcmp(s, "mp3") == 0)
return MEDIA_FORMAT_MP3;
if (strcmp(s, "alac") == 0)
return MEDIA_FORMAT_ALAC;
if (strcmp(s, "opus") == 0)
return MEDIA_FORMAT_OPUS;
return MEDIA_FORMAT_UNKNOWN;
}
const char *
media_format_to_string(enum media_format format)
{
if (format == MEDIA_FORMAT_PCM)
return "pcm";
if (format == MEDIA_FORMAT_WAV)
return "wav";
if (format == MEDIA_FORMAT_MP3)
return "mp3";
if (format == MEDIA_FORMAT_ALAC)
return "alac";
if (format == MEDIA_FORMAT_OPUS)
return "opus";
return "unknown";
}
/* -------------------------- Misc utility functions ------------------------ */ /* -------------------------- Misc utility functions ------------------------ */

View File

@ -59,6 +59,18 @@ net_is_http_or_https(const char *url);
/* ----------------------- Conversion/hashing/sanitizers -------------------- */ /* ----------------------- Conversion/hashing/sanitizers -------------------- */
#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)
#define be32toh(x) OSSwapBigToHostInt32(x)
#endif
// Samples to bytes, bytes to samples // Samples to bytes, bytes to samples
#define STOB(s, bits, c) ((s) * (c) * (bits) / 8) #define STOB(s, bits, c) ((s) * (c) * (bits) / 8)
#define BTOS(b, bits, c) ((b) / ((c) * (bits) / 8)) #define BTOS(b, bits, c) ((b) / ((c) * (bits) / 8))
@ -260,6 +272,21 @@ timespec_reltoabs(struct timespec relative);
/* ------------------------------- Media quality ---------------------------- */ /* ------------------------------- Media quality ---------------------------- */
// Bit flags for the sake of outputs announcing what they support
enum media_format {
MEDIA_FORMAT_UNKNOWN = 0,
MEDIA_FORMAT_PCM = (1 << 0),
MEDIA_FORMAT_WAV = (1 << 1),
MEDIA_FORMAT_MP3 = (1 << 2),
MEDIA_FORMAT_ALAC = (1 << 3),
MEDIA_FORMAT_OPUS = (1 << 4),
};
// For iteration
#define MEDIA_FORMAT_FIRST MEDIA_FORMAT_PCM
#define MEDIA_FORMAT_LAST MEDIA_FORMAT_OPUS
#define MEDIA_FORMAT_NEXT(f) (f << 1)
// Remember to adjust quality_is_equal() if adding elements // Remember to adjust quality_is_equal() if adding elements
struct media_quality { struct media_quality {
int sample_rate; int sample_rate;
@ -271,6 +298,12 @@ struct media_quality {
bool bool
quality_is_equal(struct media_quality *a, struct media_quality *b); quality_is_equal(struct media_quality *a, struct media_quality *b);
enum media_format
media_format_from_string(const char *s);
const char *
media_format_to_string(enum media_format format);
/* -------------------------- Misc utility functions ------------------------ */ /* -------------------------- Misc utility functions ------------------------ */

View File

@ -280,8 +280,8 @@ quality_to_xcode(struct media_quality *quality)
static int static int
encoding_reset(struct media_quality *quality) encoding_reset(struct media_quality *quality)
{ {
struct transcode_encode_setup_args encode_args = { 0 };
struct output_quality_subscription *subscription; struct output_quality_subscription *subscription;
struct decode_ctx *decode_ctx;
enum transcode_profile profile; enum transcode_profile profile;
int i; int i;
@ -293,8 +293,8 @@ encoding_reset(struct media_quality *quality)
return -1; return -1;
} }
decode_ctx = transcode_decode_setup_raw(profile, quality); encode_args.src_ctx = transcode_decode_setup_raw(profile, quality);
if (!decode_ctx) if (!encode_args.src_ctx)
{ {
DPRINTF(E_LOG, L_PLAYER, "Could not create subscription decoding context (profile %d)\n", profile); DPRINTF(E_LOG, L_PLAYER, "Could not create subscription decoding context (profile %d)\n", profile);
return -1; return -1;
@ -309,15 +309,16 @@ encoding_reset(struct media_quality *quality)
if (quality_is_equal(quality, &subscription->quality)) if (quality_is_equal(quality, &subscription->quality))
continue; // No resampling required continue; // No resampling required
profile = quality_to_xcode(&subscription->quality); encode_args.profile = quality_to_xcode(&subscription->quality);
if (profile != XCODE_UNKNOWN) encode_args.quality = &subscription->quality;
subscription->encode_ctx = transcode_encode_setup(profile, &subscription->quality, decode_ctx, 0, 0); if (encode_args.profile != XCODE_UNKNOWN)
subscription->encode_ctx = transcode_encode_setup(encode_args);
else else
DPRINTF(E_LOG, L_PLAYER, "Could not setup resampling to %d/%d/%d for output\n", DPRINTF(E_LOG, L_PLAYER, "Could not setup resampling to %d/%d/%d for output\n",
subscription->quality.sample_rate, subscription->quality.bits_per_sample, subscription->quality.channels); subscription->quality.sample_rate, subscription->quality.bits_per_sample, subscription->quality.channels);
} }
transcode_decode_cleanup(&decode_ctx); transcode_decode_cleanup(&encode_args.src_ctx);
return 0; return 0;
} }

View File

@ -134,7 +134,11 @@ struct output_device
// Quality of audio output // Quality of audio output
struct media_quality quality; struct media_quality quality;
int format;
// selected_format only set (not UNKNOWN) in case of active user selection
enum media_format selected_format;
enum media_format default_format;
uint32_t supported_formats;
// Address // Address
char *v4_address; char *v4_address;

View File

@ -1120,7 +1120,7 @@ static struct airplay_master_session *
master_session_make(struct media_quality *quality) master_session_make(struct media_quality *quality)
{ {
struct airplay_master_session *rms; struct airplay_master_session *rms;
struct decode_ctx *decode_ctx; struct transcode_encode_setup_args encode_args = { .profile = XCODE_ALAC, .quality = quality };
int ret; int ret;
// First check if we already have a suitable session // First check if we already have a suitable session
@ -1146,15 +1146,15 @@ master_session_make(struct media_quality *quality)
goto error; goto error;
} }
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, quality); encode_args.src_ctx = transcode_decode_setup_raw(XCODE_PCM16, quality);
if (!decode_ctx) if (!encode_args.src_ctx)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Could not create decoding context\n"); DPRINTF(E_LOG, L_AIRPLAY, "Could not create decoding context\n");
goto error; goto error;
} }
rms->encode_ctx = transcode_encode_setup(XCODE_ALAC, quality, decode_ctx, 0, 0); rms->encode_ctx = transcode_encode_setup(encode_args);
transcode_decode_cleanup(&decode_ctx); transcode_decode_cleanup(&encode_args.src_ctx);
if (!rms->encode_ctx) if (!rms->encode_ctx)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Will not be able to stream AirPlay 2, ffmpeg has no ALAC encoder\n"); DPRINTF(E_LOG, L_AIRPLAY, "Will not be able to stream AirPlay 2, ffmpeg has no ALAC encoder\n");
@ -3737,6 +3737,7 @@ airplay_device_cb(const char *name, const char *type, const char *domain, const
rd->type = OUTPUT_TYPE_AIRPLAY; rd->type = OUTPUT_TYPE_AIRPLAY;
rd->type_name = outputs_name(rd->type); rd->type_name = outputs_name(rd->type);
rd->extra_device_info = re; rd->extra_device_info = re;
rd->supported_formats = MEDIA_FORMAT_ALAC;
if (port < 0) if (port < 0)
{ {

View File

@ -1374,6 +1374,7 @@ alsa_device_add(cfg_t* cfg_audio, int id)
device->type = OUTPUT_TYPE_ALSA; device->type = OUTPUT_TYPE_ALSA;
device->type_name = outputs_name(device->type); device->type_name = outputs_name(device->type);
device->extra_device_info = ae; device->extra_device_info = ae;
device->supported_formats = MEDIA_FORMAT_PCM;
// The audio section will have no title, so there we get the value from the // The audio section will have no title, so there we get the value from the
// "card" option // "card" option

View File

@ -32,15 +32,7 @@
#include <ifaddrs.h> #include <ifaddrs.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.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 htobe32(x) OSSwapHostToBigInt32(x)
#define be32toh(x) OSSwapBigToHostInt32(x)
#endif
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include <event2/event.h> #include <event2/event.h>
#include <json.h> #include <json.h>
@ -55,6 +47,7 @@
#include "outputs.h" #include "outputs.h"
#include "db.h" #include "db.h"
#include "artwork.h" #include "artwork.h"
#include "misc.h"
#ifdef HAVE_PROTOBUF_OLD #ifdef HAVE_PROTOBUF_OLD
#include "cast_channel.v0.pb-c.h" #include "cast_channel.v0.pb-c.h"
@ -1785,6 +1778,7 @@ cast_device_cb(const char *name, const char *type, const char *domain, const cha
device->name = strdup(name); device->name = strdup(name);
device->type = OUTPUT_TYPE_CAST; device->type = OUTPUT_TYPE_CAST;
device->type_name = outputs_name(device->type); device->type_name = outputs_name(device->type);
device->supported_formats = MEDIA_FORMAT_OPUS;
if (port < 0) if (port < 0)
{ {
@ -2369,7 +2363,7 @@ cast_metadata_send(struct output_metadata *metadata)
static int static int
cast_init(void) cast_init(void)
{ {
struct decode_ctx *decode_ctx; struct transcode_encode_setup_args encode_args = { .profile = XCODE_OPUS, .quality = &cast_quality_default };
int i; int i;
int ret; int ret;
@ -2393,15 +2387,15 @@ cast_init(void)
return -1; return -1;
} }
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, &cast_quality_default); encode_args.src_ctx = transcode_decode_setup_raw(XCODE_PCM16, &cast_quality_default);
if (!decode_ctx) if (!encode_args.src_ctx)
{ {
DPRINTF(E_LOG, L_CAST, "Could not create decoding context\n"); DPRINTF(E_LOG, L_CAST, "Could not create decoding context\n");
goto out_tls_deinit; goto out_tls_deinit;
} }
cast_encode_ctx = transcode_encode_setup(XCODE_OPUS, &cast_quality_default, decode_ctx, 0, 0); cast_encode_ctx = transcode_encode_setup(encode_args);
transcode_decode_cleanup(&decode_ctx); transcode_decode_cleanup(&encode_args.src_ctx);
if (!cast_encode_ctx) if (!cast_encode_ctx)
{ {
DPRINTF(E_LOG, L_CAST, "Will not be able to stream Chromecast, libav does not support Opus encoding\n"); DPRINTF(E_LOG, L_CAST, "Will not be able to stream Chromecast, libav does not support Opus encoding\n");

View File

@ -491,6 +491,7 @@ fifo_init(void)
device->type_name = outputs_name(device->type); device->type_name = outputs_name(device->type);
device->has_video = 0; device->has_video = 0;
device->extra_device_info = path; device->extra_device_info = path;
device->supported_formats = MEDIA_FORMAT_PCM;
DPRINTF(E_INFO, L_FIFO, "Adding fifo output device '%s' with path '%s'\n", nickname, path); DPRINTF(E_INFO, L_FIFO, "Adding fifo output device '%s' with path '%s'\n", nickname, path);
player_device_add(device); player_device_add(device);

View File

@ -436,6 +436,7 @@ sinklist_cb(pa_context *ctx, const pa_sink_info *info, int eol, void *userdata)
device->type = OUTPUT_TYPE_PULSE; device->type = OUTPUT_TYPE_PULSE;
device->type_name = outputs_name(device->type); device->type_name = outputs_name(device->type);
device->extra_device_info = strdup(info->name); device->extra_device_info = strdup(info->name);
device->supported_formats = MEDIA_FORMAT_PCM;
player_device_add(device); player_device_add(device);
} }

View File

@ -1855,7 +1855,7 @@ static struct raop_master_session *
master_session_make(struct media_quality *quality, bool encrypt) master_session_make(struct media_quality *quality, bool encrypt)
{ {
struct raop_master_session *rms; struct raop_master_session *rms;
struct decode_ctx *decode_ctx; struct transcode_encode_setup_args encode_args = { .profile = XCODE_ALAC, .quality = quality };
int ret; int ret;
// First check if we already have a suitable session // First check if we already have a suitable session
@ -1883,15 +1883,15 @@ master_session_make(struct media_quality *quality, bool encrypt)
return NULL; return NULL;
} }
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, quality); encode_args.src_ctx = transcode_decode_setup_raw(XCODE_PCM16, quality);
if (!decode_ctx) if (!encode_args.src_ctx)
{ {
DPRINTF(E_LOG, L_RAOP, "Could not create decoding context\n"); DPRINTF(E_LOG, L_RAOP, "Could not create decoding context\n");
goto error; goto error;
} }
rms->encode_ctx = transcode_encode_setup(XCODE_ALAC, quality, decode_ctx, 0, 0); rms->encode_ctx = transcode_encode_setup(encode_args);
transcode_decode_cleanup(&decode_ctx); transcode_decode_cleanup(&encode_args.src_ctx);
if (!rms->encode_ctx) if (!rms->encode_ctx)
{ {
DPRINTF(E_LOG, L_RAOP, "Will not be able to stream AirPlay 2, ffmpeg has no ALAC encoder\n"); DPRINTF(E_LOG, L_RAOP, "Will not be able to stream AirPlay 2, ffmpeg has no ALAC encoder\n");
@ -4230,6 +4230,7 @@ raop_device_cb(const char *name, const char *type, const char *domain, const cha
rd->type = OUTPUT_TYPE_RAOP; rd->type = OUTPUT_TYPE_RAOP;
rd->type_name = outputs_name(rd->type); rd->type_name = outputs_name(rd->type);
rd->extra_device_info = re; rd->extra_device_info = re;
rd->supported_formats = MEDIA_FORMAT_ALAC;
if (port < 0) if (port < 0)
{ {

View File

@ -1295,6 +1295,8 @@ rcp_mdns_device_cb(const char *name, const char *type, const char *domain, const
device->name = strdup(name); device->name = strdup(name);
device->type = OUTPUT_TYPE_RCP; device->type = OUTPUT_TYPE_RCP;
device->type_name = outputs_name(device->type); device->type_name = outputs_name(device->type);
device->default_format = MEDIA_FORMAT_WAV;
device->supported_formats = MEDIA_FORMAT_WAV | MEDIA_FORMAT_MP3 | MEDIA_FORMAT_ALAC;
if (port < 0 || !address) if (port < 0 || !address)
{ {

View File

@ -5,18 +5,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h> #include <stdbool.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)
#define be32toh(x) OSSwapBigToHostInt32(x)
#endif
struct rtcp_timestamp struct rtcp_timestamp
{ {
uint32_t pos; uint32_t pos;

View File

@ -65,7 +65,7 @@ struct streaming_wanted
struct pipepair audio[WANTED_PIPES_MAX]; struct pipepair audio[WANTED_PIPES_MAX];
struct pipepair metadata[WANTED_PIPES_MAX]; struct pipepair metadata[WANTED_PIPES_MAX];
enum player_format format; enum media_format format;
struct media_quality quality; struct media_quality quality;
struct evbuffer *audio_in; struct evbuffer *audio_in;
@ -113,27 +113,27 @@ extern struct event_base *evbase_player;
/* ------------------------------- Helpers ---------------------------------- */ /* ------------------------------- Helpers ---------------------------------- */
static struct encode_ctx * static struct encode_ctx *
encoder_setup(enum player_format format, struct media_quality *quality) encoder_setup(enum media_format format, struct media_quality *quality)
{ {
struct decode_ctx *decode_ctx = NULL; struct transcode_encode_setup_args encode_args = { .profile = XCODE_MP3, .quality = quality };
struct encode_ctx *encode_ctx = NULL; struct encode_ctx *encode_ctx = NULL;
if (quality->bits_per_sample == 16) if (quality->bits_per_sample == 16)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, quality); encode_args.src_ctx = transcode_decode_setup_raw(XCODE_PCM16, quality);
else if (quality->bits_per_sample == 24) else if (quality->bits_per_sample == 24)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM24, quality); encode_args.src_ctx = transcode_decode_setup_raw(XCODE_PCM24, quality);
else if (quality->bits_per_sample == 32) else if (quality->bits_per_sample == 32)
decode_ctx = transcode_decode_setup_raw(XCODE_PCM32, quality); encode_args.src_ctx = transcode_decode_setup_raw(XCODE_PCM32, quality);
if (!decode_ctx) if (!encode_args.src_ctx)
{ {
DPRINTF(E_LOG, L_STREAMING, "Error setting up decoder for quality sr %d, bps %d, ch %d, cannot encode\n", DPRINTF(E_LOG, L_STREAMING, "Error setting up decoder for quality sr %d, bps %d, ch %d, cannot encode\n",
quality->sample_rate, quality->bits_per_sample, quality->channels); quality->sample_rate, quality->bits_per_sample, quality->channels);
goto out; goto out;
} }
if (format == PLAYER_FORMAT_MP3) if (format == MEDIA_FORMAT_MP3)
encode_ctx = transcode_encode_setup(XCODE_MP3, quality, decode_ctx, 0, 0); encode_ctx = transcode_encode_setup(encode_args);
if (!encode_ctx) if (!encode_ctx)
{ {
@ -143,7 +143,7 @@ encoder_setup(enum player_format format, struct media_quality *quality)
} }
out: out:
transcode_decode_cleanup(&decode_ctx); transcode_decode_cleanup(&encode_args.src_ctx);
return encode_ctx; return encode_ctx;
} }
@ -217,7 +217,7 @@ pipe_index_find_byreadfd(struct pipepair *p, int readfd)
} }
static struct streaming_wanted * static struct streaming_wanted *
wanted_new(enum player_format format, struct media_quality quality) wanted_new(enum media_format format, struct media_quality quality)
{ {
struct streaming_wanted *w; struct streaming_wanted *w;
@ -277,7 +277,7 @@ wanted_remove(struct streaming_wanted **wanted, struct streaming_wanted *remove)
} }
static struct streaming_wanted * static struct streaming_wanted *
wanted_add(struct streaming_wanted **wanted, enum player_format format, struct media_quality quality) wanted_add(struct streaming_wanted **wanted, enum media_format format, struct media_quality quality)
{ {
struct streaming_wanted *w; struct streaming_wanted *w;
@ -289,7 +289,7 @@ wanted_add(struct streaming_wanted **wanted, enum player_format format, struct m
} }
static struct streaming_wanted * static struct streaming_wanted *
wanted_find_byformat(struct streaming_wanted *wanted, enum player_format format, struct media_quality quality) wanted_find_byformat(struct streaming_wanted *wanted, enum media_format format, struct media_quality quality)
{ {
struct streaming_wanted *w; struct streaming_wanted *w;
@ -623,9 +623,9 @@ streaming_start(struct output_device *device, int callback_id)
int ret; int ret;
pthread_mutex_lock(&streaming_wanted_lck); pthread_mutex_lock(&streaming_wanted_lck);
w = wanted_find_byformat(streaming.wanted, device->format, device->quality); w = wanted_find_byformat(streaming.wanted, device->selected_format, device->quality);
if (!w) if (!w)
w = wanted_add(&streaming.wanted, device->format, device->quality); w = wanted_add(&streaming.wanted, device->selected_format, device->quality);
ret = wanted_session_add(&device->audio_fd, &device->metadata_fd, w); ret = wanted_session_add(&device->audio_fd, &device->metadata_fd, w);
if (ret < 0) if (ret < 0)
goto error; goto error;

View File

@ -152,7 +152,7 @@ struct speaker_attr_param
bool busy; bool busy;
struct media_quality quality; struct media_quality quality;
enum player_format format; enum media_format format;
int audio_fd; int audio_fd;
int metadata_fd; int metadata_fd;
@ -164,6 +164,7 @@ struct speaker_get_param
{ {
uint64_t spk_id; uint64_t spk_id;
uint32_t active_remote; uint32_t active_remote;
const char *address;
struct player_speaker_info *spk_info; struct player_speaker_info *spk_info;
}; };
@ -2527,6 +2528,15 @@ device_to_speaker_info(struct player_speaker_info *spk, struct output_device *de
spk->relvol = device->relvol; spk->relvol = device->relvol;
spk->absvol = device->volume; spk->absvol = device->volume;
spk->supported_formats = device->supported_formats;
// Devices supporting more than one format should at least have default_format set
if (device->selected_format != MEDIA_FORMAT_UNKNOWN)
spk->format = device->selected_format;
else if (device->default_format != MEDIA_FORMAT_UNKNOWN)
spk->format = device->default_format;
else
spk->format = device->supported_formats;
spk->selected = OUTPUTS_DEVICE_DISPLAY_SELECTED(device); spk->selected = OUTPUTS_DEVICE_DISPLAY_SELECTED(device);
spk->has_password = device->has_password; spk->has_password = device->has_password;
@ -2597,6 +2607,31 @@ speaker_get_byactiveremote(void *arg, int *retval)
return COMMAND_END; return COMMAND_END;
} }
static enum command_state
speaker_get_byaddress(void *arg, int *retval)
{
struct speaker_get_param *spk_param = arg;
struct output_device *device;
bool match_v4;
bool match_v6;
for (device = outputs_list(); device; device = device->next)
{
match_v4 = device->v4_address && (strcmp(spk_param->address, device->v4_address) == 0);
match_v6 = device->v6_address && (strcmp(spk_param->address, device->v6_address) == 0);
if (match_v4 || match_v6)
{
device_to_speaker_info(spk_param->spk_info, device);
*retval = 0;
return COMMAND_END;
}
}
// No output device found with matching id
*retval = -1;
return COMMAND_END;
}
static enum command_state static enum command_state
speaker_set(void *arg, int *retval) speaker_set(void *arg, int *retval)
{ {
@ -2730,6 +2765,8 @@ speaker_prevent_playback_set(void *arg, int *retval)
struct speaker_attr_param *param = arg; struct speaker_attr_param *param = arg;
struct output_device *device; struct output_device *device;
*retval = -1;
device = outputs_device_get(param->spk_id); device = outputs_device_get(param->spk_id);
if (!device) if (!device)
return COMMAND_END; return COMMAND_END;
@ -2778,6 +2815,8 @@ speaker_busy_set(void *arg, int *retval)
struct speaker_attr_param *param = arg; struct speaker_attr_param *param = arg;
struct output_device *device; struct output_device *device;
*retval = -1;
device = outputs_device_get(param->spk_id); device = outputs_device_get(param->spk_id);
if (!device) if (!device)
return COMMAND_END; return COMMAND_END;
@ -2803,6 +2842,33 @@ speaker_busy_set(void *arg, int *retval)
return COMMAND_END; return COMMAND_END;
} }
static enum command_state
speaker_format_set(void *arg, int *retval)
{
struct speaker_attr_param *param = arg;
struct output_device *device;
if (param->format == MEDIA_FORMAT_UNKNOWN)
goto error;
device = outputs_device_get(param->spk_id);
if (!device)
goto error;
if (!(param->format & device->supported_formats))
goto error;
device->selected_format = param->format;
*retval = 0;
return COMMAND_END;
error:
DPRINTF(E_LOG, L_PLAYER, "Error setting format '%s', device unknown or format unsupported\n", media_format_to_string(param->format));
*retval = -1;
return COMMAND_END;
}
// Attempts to reactivate a speaker that has failed. That includes restarting // Attempts to reactivate a speaker that has failed. That includes restarting
// playback if it was stopped. // playback if it was stopped.
static enum command_state static enum command_state
@ -2907,7 +2973,7 @@ streaming_register(void *arg, int *retval)
.type_name = "streaming", .type_name = "streaming",
.name = "streaming", .name = "streaming",
.quality = param->quality, .quality = param->quality,
.format = param->format, .selected_format = param->format,
}; };
*retval = outputs_device_start(&device, NULL, false); *retval = outputs_device_start(&device, NULL, false);
@ -3404,6 +3470,19 @@ player_speaker_get_byactiveremote(struct player_speaker_info *spk, uint32_t acti
return ret; return ret;
} }
int
player_speaker_get_byaddress(struct player_speaker_info *spk, const char *address)
{
struct speaker_get_param param;
int ret;
param.address = address;
param.spk_info = spk;
ret = commands_exec_sync(cmdbase, speaker_get_byaddress, NULL, &param);
return ret;
}
int int
player_speaker_enable(uint64_t id) player_speaker_enable(uint64_t id)
{ {
@ -3466,18 +3545,26 @@ int
player_speaker_authorize(uint64_t id, const char *pin) player_speaker_authorize(uint64_t id, const char *pin)
{ {
struct speaker_attr_param param; struct speaker_attr_param param;
int ret;
param.spk_id = id; param.spk_id = id;
param.pin = pin; param.pin = pin;
ret = commands_exec_sync(cmdbase, speaker_authorize, speaker_generic_bh, &param); return commands_exec_sync(cmdbase, speaker_authorize, speaker_generic_bh, &param);
return ret;
} }
int int
player_streaming_register(int *audio_fd, int *metadata_fd, enum player_format format, struct media_quality quality) player_speaker_format_set(uint64_t id, enum media_format format)
{
struct speaker_attr_param param;
param.spk_id = id;
param.format = format;
return commands_exec_sync(cmdbase, speaker_format_set, speaker_generic_bh, &param);
}
int
player_streaming_register(int *audio_fd, int *metadata_fd, enum media_format format, struct media_quality quality)
{ {
struct speaker_attr_param param; struct speaker_attr_param param;
int ret; int ret;

View File

@ -28,10 +28,6 @@ enum player_seek_mode {
PLAYER_SEEK_RELATIVE = 2, PLAYER_SEEK_RELATIVE = 2,
}; };
enum player_format {
PLAYER_FORMAT_MP3,
};
struct player_speaker_info { struct player_speaker_info {
uint64_t id; uint64_t id;
uint32_t active_remote; uint32_t active_remote;
@ -40,6 +36,9 @@ struct player_speaker_info {
int relvol; int relvol;
int absvol; int absvol;
enum media_format format;
uint32_t supported_formats;
bool selected; bool selected;
bool has_password; bool has_password;
bool requires_auth; bool requires_auth;
@ -104,6 +103,9 @@ player_speaker_get_byid(struct player_speaker_info *spk, uint64_t id);
int int
player_speaker_get_byactiveremote(struct player_speaker_info *spk, uint32_t active_remote); player_speaker_get_byactiveremote(struct player_speaker_info *spk, uint32_t active_remote);
int
player_speaker_get_byaddress(struct player_speaker_info *spk, const char *address);
int int
player_speaker_enable(uint64_t id); player_speaker_enable(uint64_t id);
@ -123,7 +125,10 @@ int
player_speaker_authorize(uint64_t id, const char *pin); player_speaker_authorize(uint64_t id, const char *pin);
int int
player_streaming_register(int *audio_fd, int *metadata_fd, enum player_format format, struct media_quality quality); player_speaker_format_set(uint64_t id, enum media_format format);
int
player_streaming_register(int *audio_fd, int *metadata_fd, enum media_format format, struct media_quality quality);
int int
player_streaming_deregister(int id); player_streaming_deregister(int id);

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,6 @@
#define __TRANSCODE_H__ #define __TRANSCODE_H__
#include <event2/buffer.h> #include <event2/buffer.h>
#include "db.h"
#include "http.h" #include "http.h"
#include "misc.h" #include "misc.h"
@ -23,10 +22,14 @@ enum transcode_profile
XCODE_PCM32, XCODE_PCM32,
// Transcodes the best audio stream to MP3 // Transcodes the best audio stream to MP3
XCODE_MP3, XCODE_MP3,
// Transcodes the best audio stream to OPUS // Transcodes the best audio stream to raw OPUS (no container)
XCODE_OPUS, XCODE_OPUS,
// Transcodes the best audio stream to ALAC // Transcodes the best audio stream to raw ALAC (no container)
XCODE_ALAC, XCODE_ALAC,
// Transcodes the best audio stream to ALAC in a MP4 container
XCODE_MP4_ALAC,
// Produces just the header for a MP4 container with ALAC
XCODE_MP4_ALAC_HEADER,
// Transcodes the best audio stream from OGG // Transcodes the best audio stream from OGG
XCODE_OGG, XCODE_OGG,
// Transcodes the best video stream to JPEG/PNG/VP8 // Transcodes the best video stream to JPEG/PNG/VP8
@ -62,6 +65,29 @@ struct transcode_evbuf_io
void *seekfn_arg; void *seekfn_arg;
}; };
struct transcode_decode_setup_args
{
enum transcode_profile profile;
struct media_quality *quality;
bool is_http;
uint32_t len_ms;
// Source must be either of these
const char *path;
struct transcode_evbuf_io *evbuf_io;
};
struct transcode_encode_setup_args
{
enum transcode_profile profile;
struct media_quality *quality;
struct decode_ctx *src_ctx;
struct transcode_evbuf_io *evbuf_io;
struct evbuffer *prepared_header;
int width;
int height;
};
struct transcode_metadata_string struct transcode_metadata_string
{ {
char *type; char *type;
@ -74,19 +100,19 @@ struct transcode_metadata_string
// Setting up // Setting up
struct decode_ctx * struct decode_ctx *
transcode_decode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, struct transcode_evbuf_io *evbuf_io, uint32_t len_ms); transcode_decode_setup(struct transcode_decode_setup_args args);
struct encode_ctx * struct encode_ctx *
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, int width, int height); transcode_encode_setup(struct transcode_encode_setup_args args);
struct transcode_ctx * struct transcode_ctx *
transcode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, uint32_t len_ms); transcode_setup(struct transcode_decode_setup_args decode_args, struct transcode_encode_setup_args encode_args);
struct decode_ctx * struct decode_ctx *
transcode_decode_setup_raw(enum transcode_profile profile, struct media_quality *quality); transcode_decode_setup_raw(enum transcode_profile profile, struct media_quality *quality);
enum transcode_profile enum transcode_profile
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype); transcode_needed(const char *user_agent, const char *client_codecs, const char *file_codectype);
// Cleaning up // Cleaning up
void void
@ -182,10 +208,27 @@ transcode_encode_query(struct encode_ctx *ctx, const char *query);
struct http_icy_metadata * struct http_icy_metadata *
transcode_metadata(struct transcode_ctx *ctx, int *changed); transcode_metadata(struct transcode_ctx *ctx, int *changed);
// When transcoding, we are in essence serving a different source file than the /* When transcoding, we are in essence serving a different source file than the
// original to the client. So we can't serve some of the file metadata from the * original to the client. So we can't serve some of the file metadata from the
// filescanner. This function creates strings to be used for override. * filescanner. This function creates strings to be used for override.
*
* @out s Structure with (non-allocated) strings
* @in profile Transcoding profile
* @in q Transcoding quality
* @in len_ms Length of source track
*/
void void
transcode_metadata_strings_set(struct transcode_metadata_string *s, enum transcode_profile profile, struct media_quality *q, uint32_t len_ms); transcode_metadata_strings_set(struct transcode_metadata_string *s, enum transcode_profile profile, struct media_quality *q, uint32_t len_ms);
/* Creates a header for later transcoding of a source file. This header can be
* given to transcode_encode_setup which in some cases will make it faster (MP4)
*
* @out header An evbuffer with the header
* @in profile Transcoding profile
* @in path Path to the source file
* @return Negative if error, otherwise zero
*/
int
transcode_prepare_header(struct evbuffer **header, enum transcode_profile profile, const char *path);
#endif /* !__TRANSCODE_H__ */ #endif /* !__TRANSCODE_H__ */