Merge branch 'castfix2'

This commit is contained in:
ejurgensen 2020-11-18 23:29:00 +01:00
commit e3308a464b
15 changed files with 986 additions and 467 deletions

View File

@ -317,6 +317,10 @@ audio {
# Chromecast settings
# (make sure you get the capitalization of the device name right)
#chromecast "My Chromecast device" {
# forked-daapd's volume goes to 11! If that's more than you can handle
# you can set a lower value here
# max_volume = 11
# Enable this option to exclude a particular device from the speaker
# list
# exclude = false

View File

@ -60,8 +60,7 @@
*
* An artwork source handler must return one of the following:
*
* ART_FMT_JPEG (positive) Found a jpeg
* ART_FMT_PNG (positive) Found a png
* ART_FMT_XXXX (positive) An image, see possible formats in artwork.h
* ART_E_NONE (zero) No artwork found
* ART_E_ERROR (negative) An error occurred while searching for artwork
* ART_E_ABORT (negative) Caller should abort artwork search (may be returned by cache)
@ -81,6 +80,14 @@ enum artwork_cache
ON_FAILURE = 2, // Cache if artwork not found (so we don't keep asking)
};
// Input data to handlers, requested width, height and format. Can be set to
// zero then source is returned.
struct artwork_req_params {
int max_w;
int max_h;
int format;
};
/* This struct contains the data available to the handler, as well as a char
* buffer where the handler should output the path to the artwork (if it is
* local - otherwise the buffer can be left empty). The purpose of supplying the
@ -93,9 +100,9 @@ struct artwork_ctx {
// Handler should output artwork data to this evbuffer
struct evbuffer *evbuf;
// Input data to handler, requested width and height
int max_w;
int max_h;
// Requested size and format
struct artwork_req_params req_params;
// Input data to handler, did user configure to look for individual artwork
int individual;
@ -576,26 +583,26 @@ rescale_calculate(int *target_w, int *target_h, int width, int height, int max_w
* @out evbuf Image data (rescaled if needed)
* @in path Path to the artwork file (alternative to inbuf)
* @in inbuf Buffer with the artwork (alternative to path)
* @in max_w Requested width
* @in max_h Requested height
* @in is_embedded Whether the artwork in file is embedded or raw jpeg/png
* @in data_kind Used by the transcode module to determine e.g. probe size
* @in req_params Requested max size/format
* @return ART_FMT_* on success, ART_E_ERROR on error
*/
static int
artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_w, int max_h, bool is_embedded, enum data_kind data_kind)
artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, bool is_embedded, enum data_kind data_kind, struct artwork_req_params req_params)
{
struct decode_ctx *xcode_decode;
struct encode_ctx *xcode_encode;
void *frame;
int width;
int height;
int target_w;
int target_h;
int format_ok;
int src_width;
int src_height;
int src_format;
int dst_width;
int dst_height;
int dst_format;
int ret;
DPRINTF(E_SPAM, L_ART, "Getting artwork (max destination width %d height %d)\n", max_w, max_h);
DPRINTF(E_SPAM, L_ART, "Getting artwork (max destination width %d height %d)\n", req_params.max_w, req_params.max_h);
xcode_decode = transcode_decode_setup(XCODE_JPEG, NULL, data_kind, path, inbuf, 0); // Covers XCODE_PNG too
if (!xcode_decode)
@ -608,9 +615,9 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
}
if (transcode_decode_query(xcode_decode, "is_jpeg"))
format_ok = ART_FMT_JPEG;
src_format = ART_FMT_JPEG;
else if (transcode_decode_query(xcode_decode, "is_png"))
format_ok = ART_FMT_PNG;
src_format = ART_FMT_PNG;
else
{
if (is_embedded)
@ -623,18 +630,18 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
goto fail_free_decode;
}
width = transcode_decode_query(xcode_decode, "width");
height = transcode_decode_query(xcode_decode, "height");
src_width = transcode_decode_query(xcode_decode, "width");
src_height = transcode_decode_query(xcode_decode, "height");
ret = rescale_calculate(&target_w, &target_h, width, height, max_w, max_h);
ret = rescale_calculate(&dst_width, &dst_height, src_width, src_height, req_params.max_w, req_params.max_h);
if (ret < 0)
{
if (is_embedded)
{
target_w = width;
target_h = height;
dst_width = src_width;
dst_height = src_height;
}
else if (path)
else if (path && src_format == req_params.format)
{
// No rescaling required, just read the raw file into the evbuf
ret = artwork_read_bypath(evbuf, path);
@ -642,7 +649,7 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
goto fail_free_decode;
transcode_decode_cleanup(&xcode_decode);
return format_ok;
return src_format;
}
else
{
@ -650,10 +657,15 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
}
}
if (format_ok == ART_FMT_JPEG)
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, target_w, target_h);
dst_format = req_params.format ? req_params.format : src_format;
if (dst_format == ART_FMT_JPEG)
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, dst_width, dst_height);
else if (dst_format == ART_FMT_PNG)
xcode_encode = transcode_encode_setup(XCODE_PNG, NULL, xcode_decode, NULL, dst_width, dst_height);
else if (dst_format == ART_FMT_VP8)
xcode_encode = transcode_encode_setup(XCODE_VP8, NULL, xcode_decode, NULL, dst_width, dst_height);
else
xcode_encode = transcode_encode_setup(XCODE_PNG, NULL, xcode_decode, NULL, target_w, target_h);
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, dst_width, dst_height);
if (!xcode_encode)
{
@ -680,7 +692,7 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
return ART_E_ERROR;
}
return format_ok;
return dst_format;
fail_free_encode:
transcode_encode_cleanup(&xcode_encode);
@ -689,16 +701,15 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, int max_
return ART_E_ERROR;
}
/* Rescales an image in an evbuf (if required)
/* Rescales and reformats an image in an evbuf (if required)
*
* @out artwork Rescaled image data (or original, if not rescaled)
* @in raw Original image data
* @in max_w Requested max width
* @in max_h Requested max height
* @in req_params Requested max size/format
* @return 0 on success, -1 on error
*/
static int
artwork_evbuf_rescale(struct evbuffer *artwork, struct evbuffer *raw, int max_w, int max_h)
artwork_evbuf_rescale(struct evbuffer *artwork, struct evbuffer *raw, struct artwork_req_params req_params)
{
struct evbuffer *refbuf;
int ret;
@ -726,7 +737,7 @@ artwork_evbuf_rescale(struct evbuffer *artwork, struct evbuffer *raw, int max_w,
}
// For non-file input, artwork_get() will also fail if no rescaling is required
ret = artwork_get(artwork, NULL, refbuf, max_w, max_h, false, 0);
ret = artwork_get(artwork, NULL, refbuf, false, 0, req_params);
if (ret == ART_E_ERROR)
{
DPRINTF(E_DBG, L_ART, "No rescaling required\n");
@ -878,28 +889,27 @@ parent_dir_image_find(char *out_path, size_t len, const char *dir)
/* Looks for an artwork file in a directory. Will rescale if needed.
*
* @out evbuf Image data
* @in dir Directory to search
* @in max_w Requested width
* @in max_h Requested height
* @out out_path Path to the artwork file if found, must be a char[PATH_MAX] buffer
* @in len Max size of "out_path"
* @in dir Directory to search
* @in req_params Requested max size/format
* @return ART_FMT_* on success, ART_E_NONE on nothing found, ART_E_ERROR on error
*/
static int
artwork_get_bydir(struct evbuffer *evbuf, char *dir, int max_w, int max_h, char *out_path, size_t len)
artwork_get_bydir(struct evbuffer *evbuf, char *out_path, size_t len, char *dir, struct artwork_req_params req_params)
{
int ret;
ret = dir_image_find(out_path, len, dir);
if (ret >= 0)
{
return artwork_get(evbuf, out_path, NULL, max_w, max_h, false, DATA_KIND_FILE);
return artwork_get(evbuf, out_path, NULL, false, DATA_KIND_FILE, req_params);
}
ret = parent_dir_image_find(out_path, len, dir);
if (ret >= 0)
{
return artwork_get(evbuf, out_path, NULL, max_w, max_h, false, DATA_KIND_FILE);
return artwork_get(evbuf, out_path, NULL, false, DATA_KIND_FILE, req_params);
}
return ART_E_NONE;
@ -910,12 +920,11 @@ artwork_get_bydir(struct evbuffer *evbuf, char *dir, int max_w, int max_h, char
*
* @out artwork Image data
* @in url URL of the artwork
* @in max_w Requested max width
* @in max_h Requested max height
* @in req_params Requested max size/format
* @return ART_FMT_* on success, ART_E_NONE or ART_E_ERROR
*/
static int
artwork_get_byurl(struct evbuffer *artwork, const char *url, int max_w, int max_h)
artwork_get_byurl(struct evbuffer *artwork, const char *url, struct artwork_req_params req_params)
{
struct evbuffer *raw;
int format;
@ -935,7 +944,7 @@ artwork_get_byurl(struct evbuffer *artwork, const char *url, int max_w, int max_
if (format <= 0)
goto out;
ret = artwork_evbuf_rescale(artwork, raw, max_w, max_h);
ret = artwork_evbuf_rescale(artwork, raw, req_params);
if (ret < 0)
format = ART_E_ERROR;
@ -1250,7 +1259,7 @@ online_source_search(struct online_source *src, struct artwork_ctx *ctx)
// Be nice to our peer + improve response times by not repeating search requests
hash = djb_hash(url, strlen(url));
ret = online_source_search_check_last(&artwork_url, src, hash, ctx->max_w, ctx->max_h);
ret = online_source_search_check_last(&artwork_url, src, hash, ctx->req_params.max_w, ctx->req_params.max_h);
if (ret == 0)
{
return artwork_url; // Will be NULL if we are repeating a search that failed
@ -1288,7 +1297,7 @@ online_source_search(struct online_source *src, struct artwork_ctx *ctx)
goto error;
}
ret = online_source_response_parse(&artwork_url, src, client.input_body, ctx->max_w, ctx->max_h);
ret = online_source_response_parse(&artwork_url, src, client.input_body, ctx->req_params.max_w, ctx->req_params.max_h);
if (ret == ONLINE_SOURCE_PARSE_NOT_FOUND)
DPRINTF(E_DBG, L_ART, "No image tag found in response from source '%s'\n", src->name);
else if (ret == ONLINE_SOURCE_PARSE_INVALID)
@ -1338,7 +1347,7 @@ source_group_cache_get(struct artwork_ctx *ctx)
int cached;
int ret;
ret = cache_artwork_get(CACHE_ARTWORK_GROUP, ctx->persistentid, ctx->max_w, ctx->max_h, &cached, &format, ctx->evbuf);
ret = cache_artwork_get(CACHE_ARTWORK_GROUP, ctx->persistentid, ctx->req_params.max_w, ctx->req_params.max_h, &cached, &format, ctx->evbuf);
if (ret < 0)
return ART_E_ERROR;
@ -1381,7 +1390,7 @@ source_group_dir_get(struct artwork_ctx *ctx)
if (access(dir, F_OK) < 0)
continue;
ret = artwork_get_bydir(ctx->evbuf, dir, ctx->max_w, ctx->max_h, ctx->path, sizeof(ctx->path));
ret = artwork_get_bydir(ctx->evbuf, ctx->path, sizeof(ctx->path), dir, ctx->req_params);
if (ret > 0)
{
db_query_end(&qp);
@ -1413,7 +1422,7 @@ source_item_cache_get(struct artwork_ctx *ctx)
if (!ctx->individual)
return ART_E_NONE;
ret = cache_artwork_get(CACHE_ARTWORK_INDIVIDUAL, ctx->id, ctx->max_w, ctx->max_h, &cached, &format, ctx->evbuf);
ret = cache_artwork_get(CACHE_ARTWORK_INDIVIDUAL, ctx->id, ctx->req_params.max_w, ctx->req_params.max_h, &cached, &format, ctx->evbuf);
if (ret < 0)
return ART_E_ERROR;
@ -1446,7 +1455,7 @@ source_item_embedded_get(struct artwork_ctx *ctx)
snprintf(ctx->path, sizeof(ctx->path), "%s", ctx->dbmfi->path);
return artwork_get(ctx->evbuf, ctx->path, NULL, ctx->max_w, ctx->max_h, true, ctx->data_kind);
return artwork_get(ctx->evbuf, ctx->path, NULL, true, ctx->data_kind, ctx->req_params);
}
/* Looks for basename(in_path).{png,jpg}, so if in_path is /foo/bar.mp3 it
@ -1500,7 +1509,7 @@ source_item_own_get(struct artwork_ctx *ctx)
snprintf(ctx->path, sizeof(ctx->path), "%s", path);
return artwork_get(ctx->evbuf, path, NULL, ctx->max_w, ctx->max_h, false, ctx->data_kind);
return artwork_get(ctx->evbuf, path, NULL, false, ctx->data_kind, ctx->req_params);
}
/*
@ -1521,7 +1530,7 @@ source_item_artwork_url_get(struct artwork_ctx *ctx)
return ART_E_NONE;
}
ret = artwork_get_byurl(ctx->evbuf, queue_item->artwork_url, ctx->max_w, ctx->max_h);
ret = artwork_get_byurl(ctx->evbuf, queue_item->artwork_url, ctx->req_params);
snprintf(ctx->path, sizeof(ctx->path), "%s", queue_item->artwork_url);
@ -1557,7 +1566,7 @@ source_item_pipe_get(struct artwork_ctx *ctx)
free_queue_item(queue_item, 0);
return artwork_get(ctx->evbuf, ctx->path, NULL, ctx->max_w, ctx->max_h, false, ctx->data_kind);
return artwork_get(ctx->evbuf, ctx->path, NULL, false, ctx->data_kind, ctx->req_params);
}
static int
@ -1575,7 +1584,7 @@ source_item_discogs_get(struct artwork_ctx *ctx)
snprintf(ctx->path, sizeof(ctx->path), "%s", url);
ret = artwork_get_byurl(ctx->evbuf, url, ctx->max_w, ctx->max_h);
ret = artwork_get_byurl(ctx->evbuf, url, ctx->req_params);
free(url);
return ret;
@ -1598,7 +1607,7 @@ source_item_coverartarchive_get(struct artwork_ctx *ctx)
snprintf(ctx->path, sizeof(ctx->path), "%s", url);
ret = artwork_get_byurl(ctx->evbuf, url, ctx->max_w, ctx->max_h);
ret = artwork_get_byurl(ctx->evbuf, url, ctx->req_params);
free(url);
return ret;
@ -1611,14 +1620,14 @@ source_item_spotifywebapi_track_get(struct artwork_ctx *ctx)
char *artwork_url;
int ret;
artwork_url = spotifywebapi_artwork_url_get(ctx->dbmfi->path, ctx->max_w, ctx->max_h);
artwork_url = spotifywebapi_artwork_url_get(ctx->dbmfi->path, ctx->req_params.max_w, ctx->req_params.max_h);
if (!artwork_url)
{
DPRINTF(E_WARN, L_ART, "No artwork from Spotify for %s\n", ctx->dbmfi->path);
return ART_E_NONE;
}
ret = artwork_get_byurl(ctx->evbuf, artwork_url, ctx->max_w, ctx->max_h);
ret = artwork_get_byurl(ctx->evbuf, artwork_url, ctx->req_params);
free(artwork_url);
return ret;
@ -1652,7 +1661,7 @@ source_item_spotifywebapi_search_get(struct artwork_ctx *ctx)
snprintf(ctx->path, sizeof(ctx->path), "%s", url);
ret = artwork_get_byurl(ctx->evbuf, url, ctx->max_w, ctx->max_h);
ret = artwork_get_byurl(ctx->evbuf, url, ctx->req_params);
free(url);
return ret;
@ -1716,7 +1725,7 @@ source_item_ownpl_get(struct artwork_ctx *ctx)
if (dbpli.artwork_url)
{
format = artwork_get_byurl(ctx->evbuf, dbpli.artwork_url, ctx->max_w, ctx->max_h);
format = artwork_get_byurl(ctx->evbuf, dbpli.artwork_url, ctx->req_params);
if (format > 0)
break;
}
@ -1894,7 +1903,7 @@ process_group(struct artwork_ctx *ctx)
/* ------------------------------ ARTWORK API ------------------------------ */
int
artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h)
artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int format)
{
struct artwork_ctx ctx;
char filter[32];
@ -1910,8 +1919,9 @@ artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h)
ctx.qp.type = Q_ITEMS;
ctx.qp.filter = filter;
ctx.evbuf = evbuf;
ctx.max_w = max_w;
ctx.max_h = max_h;
ctx.req_params.max_w = max_w;
ctx.req_params.max_h = max_h;
ctx.req_params.format = format;
ctx.cache = ON_FAILURE;
ctx.individual = cfg_getbool(cfg_getsec(cfg, "library"), "artwork_individual");
@ -1954,7 +1964,7 @@ artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h)
}
int
artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h)
artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h, int format)
{
struct artwork_ctx ctx;
int ret;
@ -1974,8 +1984,9 @@ artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h)
ctx.qp.type = Q_GROUP_ITEMS;
ctx.qp.persistentid = ctx.persistentid;
ctx.evbuf = evbuf;
ctx.max_w = max_w;
ctx.max_h = max_h;
ctx.req_params.max_w = max_w;
ctx.req_params.max_h = max_h;
ctx.req_params.format = format;
ctx.cache = ON_FAILURE;
ctx.individual = cfg_getbool(cfg_getsec(cfg, "library"), "artwork_individual");

View File

@ -4,6 +4,7 @@
#define ART_FMT_PNG 1
#define ART_FMT_JPEG 2
#define ART_FMT_VP8 3
#define ART_DEFAULT_HEIGHT 600
#define ART_DEFAULT_WIDTH 600
@ -18,10 +19,11 @@
* @in id The mfi item id
* @in max_w Requested maximum image width (may not be obeyed)
* @in max_h Requested maximum image height (may not be obeyed)
* @in format Requested format (may not be obeyed), 0 for default
* @return ART_FMT_* on success, -1 on error or no artwork found
*/
int
artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h);
artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h, int format);
/*
* Get the artwork image for a group (an album or an artist)
@ -30,10 +32,11 @@ artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h);
* @in id The group id (not the persistentid)
* @in max_w Requested maximum image width (may not be obeyed)
* @in max_h Requested maximum image height (may not be obeyed)
* @in format Requested format (may not be obeyed), 0 for default
* @return ART_FMT_* on success, -1 on error or no artwork found
*/
int
artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h);
artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h, int format);
/*
* Checks if the file is an artwork file (based on user config)

View File

@ -166,6 +166,7 @@ static cfg_opt_t sec_airplay[] =
/* Chromecast device section structure */
static cfg_opt_t sec_chromecast[] =
{
CFG_INT("max_volume", 11, CFGF_NONE),
CFG_BOOL("exclude", cfg_false, CFGF_NONE),
CFG_INT("offset_ms", 0, CFGF_NONE),
CFG_END()

View File

@ -92,7 +92,7 @@ artworkapi_reply_nowplaying(struct httpd_request *hreq)
if (ret != 0)
return HTTP_NOTFOUND;
ret = artwork_get_item(hreq->reply, id, max_w, max_h);
ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@ -113,7 +113,7 @@ artworkapi_reply_item(struct httpd_request *hreq)
if (ret != 0)
return HTTP_BADREQUEST;
ret = artwork_get_item(hreq->reply, id, max_w, max_h);
ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
return response_process(hreq, ret);
}
@ -134,7 +134,7 @@ artworkapi_reply_group(struct httpd_request *hreq)
if (ret != 0)
return HTTP_BADREQUEST;
ret = artwork_get_group(hreq->reply, id, max_w, max_h);
ret = artwork_get_group(hreq->reply, id, max_w, max_h, 0);
return response_process(hreq, ret);
}

View File

@ -2020,9 +2020,9 @@ daap_reply_extra_data(struct httpd_request *hreq)
}
if (strcmp(hreq->uri_parsed->path_parts[2], "groups") == 0)
ret = artwork_get_group(hreq->reply, id, max_w, max_h);
ret = artwork_get_group(hreq->reply, id, max_w, max_h, 0);
else if (strcmp(hreq->uri_parsed->path_parts[2], "items") == 0)
ret = artwork_get_item(hreq->reply, id, max_w, max_h);
ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
len = evbuffer_get_length(hreq->reply);

View File

@ -2363,7 +2363,7 @@ dacp_reply_nowplayingartwork(struct httpd_request *hreq)
if (ret < 0)
goto no_artwork;
ret = artwork_get_item(hreq->reply, id, max_w, max_h);
ret = artwork_get_item(hreq->reply, id, max_w, max_h, 0);
len = evbuffer_get_length(hreq->reply);
switch (ret)

View File

@ -25,6 +25,11 @@
# define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
// If you have something like "#define MYNUMBER 3" then NTOSTR(MYNUMBER) will be
// the string value, so "3".
#define NTOSTR_HELPER(x) #x
#define NTOSTR(x) NTOSTR_HELPER(x)
// Remember to adjust quality_is_equal() if adding elements
struct media_quality {

View File

@ -4694,7 +4694,7 @@ artwork_cb(struct evhttp_request *req, void *arg)
return;
}
format = artwork_get_item(evbuffer, itemid, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT);
format = artwork_get_item(evbuffer, itemid, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT, 0);
if (format < 0)
{
httpd_send_error(req, HTTP_NOTFOUND, "Document was not found");

File diff suppressed because it is too large Load Diff

View File

@ -45,17 +45,6 @@
#include <fcntl.h>
#include <time.h>
#ifdef HAVE_ENDIAN_H
# include <endian.h>
#elif defined(HAVE_SYS_ENDIAN_H)
# include <sys/endian.h>
#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#endif
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
@ -91,6 +80,8 @@
// alignment, which improves performance of some encoders
#define RAOP_SAMPLES_PER_PACKET 352
#define RAOP_RTP_PAYLOADTYPE 0x60
// How many RTP packets keep in a buffer for retransmission
#define RAOP_PACKET_BUFFER_SIZE 1000
@ -2210,7 +2201,7 @@ raop_metadata_prepare(struct output_metadata *metadata)
CHECK_NULL(L_RAOP, rmd->metadata = evbuffer_new());
CHECK_NULL(L_RAOP, tmp = evbuffer_new());
ret = artwork_get_item(rmd->artwork, queue_item->file_id, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT);
ret = artwork_get_item(rmd->artwork, queue_item->file_id, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT, 0);
if (ret < 0)
{
DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file '%s'; no artwork will be sent\n", queue_item->path);
@ -2859,7 +2850,7 @@ packets_send(struct raop_master_session *rms)
struct raop_session *rs;
int ret;
pkt = rtp_packet_next(rms->rtp_session, ALAC_HEADER_LEN + rms->rawbuf_size, rms->samples_per_packet, 0x60);
pkt = rtp_packet_next(rms->rtp_session, ALAC_HEADER_LEN + rms->rawbuf_size, rms->samples_per_packet, RAOP_RTP_PAYLOADTYPE, 0);
ret = packet_prepare(pkt, rms->rawbuf, rms->rawbuf_size, rms->encrypt);
if (ret < 0)

View File

@ -31,17 +31,6 @@
#include <limits.h>
#include <sys/param.h>
#ifdef HAVE_ENDIAN_H
# include <endian.h>
#elif defined(HAVE_SYS_ENDIAN_H)
# include <sys/endian.h>
#elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#endif
#include <gcrypt.h>
#include "logger.h"
@ -55,12 +44,6 @@
#define FRAC 4294967296. // 2^32 as a double
#define NTP_EPOCH_DELTA 0x83aa7e80 // 2208988800 - that's 1970 - 1900 in seconds
struct ntp_timestamp
{
uint32_t sec;
uint32_t frac;
};
static inline void
timespec_to_ntp(struct timespec *ts, struct ntp_timestamp *ns)
@ -92,6 +75,7 @@ rtp_session_new(struct media_quality *quality, int pktbuf_size, int sync_each_ns
gcry_randomize(&session->pos, sizeof(session->pos), GCRY_STRONG_RANDOM);
gcry_randomize(&session->seqnum, sizeof(session->seqnum), GCRY_STRONG_RANDOM);
if (quality)
session->quality = *quality;
session->pktbuf_size = pktbuf_size;
@ -99,7 +83,7 @@ rtp_session_new(struct media_quality *quality, int pktbuf_size, int sync_each_ns
if (sync_each_nsamples > 0)
session->sync_each_nsamples = sync_each_nsamples;
else if (sync_each_nsamples == 0)
else if (sync_each_nsamples == 0 && quality)
session->sync_each_nsamples = quality->sample_rate;
return session;
@ -128,7 +112,7 @@ rtp_session_flush(struct rtp_session *session)
// We don't want the caller to malloc payload for every packet, so instead we
// will get him a packet from the ring buffer, thus in most cases reusing memory
struct rtp_packet *
rtp_packet_next(struct rtp_session *session, size_t payload_len, int samples, char type)
rtp_packet_next(struct rtp_session *session, size_t payload_len, int samples, char payload_type, char marker_bit)
{
struct rtp_packet *pkt;
uint16_t seq;
@ -168,7 +152,7 @@ rtp_packet_next(struct rtp_session *session, size_t payload_len, int samples, ch
// | synchronization source (SSRC) identifier |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
pkt->header[0] = 0x80; // Version = 2, P, X and CC are 0
pkt->header[1] = type; // RTP payload type
pkt->header[1] = (marker_bit << 7) | payload_type; // M and payload type
seq = htobe16(session->seqnum);
memcpy(pkt->header + 2, &seq, 2);
@ -288,3 +272,78 @@ rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp cur_stam
return &session->sync_packet_next;
}
int
rtcp_packet_parse(struct rtcp_packet *pkt, uint8_t *data, size_t size)
{
if (size < 8) // Must be large enough for at least SSRC
goto packet_malformed;
memset(pkt, 0, sizeof(struct rtcp_packet));
pkt->version = (data[0] & 0xc0) >> 6; // AND 11000000
if (pkt->version != 2)
goto packet_malformed;
pkt->padding = (data[0] & 0x20) >> 5; // AND 00100000
memcpy(&pkt->len, data + 2, 2);
pkt->len = 4 * (be16toh(pkt->len) + 1); // Input len is 32-bit words excl. the 32-bit header
memcpy(&pkt->ssrc, data + 4, 4);
pkt->ssrc = be32toh(pkt->ssrc);
if (size < pkt->len)
goto packet_malformed; // Possibly a partial read?
pkt->payload = data + pkt->len;
pkt->payload_len = size - pkt->len;
pkt->packet_type = data[1];
// TODO use a switch()
if (pkt->packet_type == RTCP_PACKET_RR) // 201, see RFC 1889
{
pkt->rr.report_count = data[0] & 0x1f; // AND 00011111
// TODO check total size of reports is smaller than size?
}
else if (pkt->packet_type == RTCP_PACKET_APP && size >= 12) // 204, see RFC 1889
{
pkt->app.subtype = data[0] & 0x1f; // AND 00011111
memcpy(pkt->app.name, data + 8, 4);
}
else if (pkt->packet_type == RTCP_PACKET_PSFB && size >= 12) // 206, see RFC 4585, payload specific feedback
{
pkt->psfb.message_type = data[0] & 0x1f; // AND 00011111
memcpy(&pkt->psfb.media_src, data + 8, 4);
pkt->psfb.media_src = be32toh(pkt->psfb.media_src);
pkt->psfb.fci = data + 12;
pkt->psfb.fci_len = size - 12;
}
else if (pkt->packet_type == RTCP_PACKET_XR && size >= 24) // 207, see RFC 3611, however we can handle only 1 block
{
pkt->xr.block_type = data[8];
pkt->xr.block_specific = data[9];
memcpy(&pkt->xr.block_len, data + 10, 2);
pkt->xr.block_len = 4 * be16toh(pkt->xr.block_len);
if (pkt->xr.block_type != 4 || pkt->xr.block_len != 8)
return 0; // We can only parse handle Receiver Reference Time Report with 8 byte NTP timestamp
memcpy(&pkt->xr.ntp.sec, data + 12, 4);
pkt->xr.ntp.sec = be32toh(pkt->xr.ntp.sec);
memcpy(&pkt->xr.ntp.frac, data + 16, 4);
pkt->xr.ntp.frac = be32toh(pkt->xr.ntp.frac);
}
else
return -1; // Don't know how to parse
/*
DPRINTF(E_DBG, L_PLAYER, "RTCP PACKET vers=%d, padding=%d, len=%" PRIu16 ", payload_len=%zu, ssrc=%" PRIu32 "\n", pkt->version, pkt->padding, pkt->len, pkt->payload_len, pkt->ssrc);
if (pkt->packet_type == RTCP_PACKET_APP)
DPRINTF(E_DBG, L_PLAYER, "RTCP APP PACKET subtype=%d, name=%s\n", pkt->app.subtype, pkt->app.name);
else if (pkt->packet_type == RTCP_PACKET_XR)
DPRINTF(E_DBG, L_PLAYER, "RTCP XR PACKET block_type=%d, block_len=%" PRIu16 "\n", pkt->xr.block_type, pkt->xr.block_len);
*/
return 0;
packet_malformed:
DPRINTF(E_SPAM, L_PLAYER, "Ignoring incoming packet, packet is non-RTCP, malformed or partial (size=%zu)\n", size);
return -1;
}

View File

@ -5,12 +5,30 @@
#include <inttypes.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
{
uint32_t pos;
struct timespec ts;
};
struct ntp_timestamp
{
uint32_t sec;
uint32_t frac;
};
struct rtp_packet
{
uint16_t seqnum; // Sequence number
@ -27,6 +45,52 @@ struct rtp_packet
size_t data_len; // Length of actual packet data
};
struct rtcp_packet
{
uint8_t version; // Always 2
bool padding;
uint16_t len;
uint32_t ssrc;
uint8_t *payload;
size_t payload_len;
enum rtcp_packet_type
{
RTCP_PACKET_RR = 201, // RFC 3550
RTCP_PACKET_APP = 204, // RFC 1889
RTCP_PACKET_PSFB = 206, // RFC 4585
RTCP_PACKET_XR = 207, // RFC 3611
} packet_type;
union
{
struct rtcp_packet_rr
{
uint8_t report_count;
} rr;
struct rtcp_packet_app
{
uint8_t subtype;
char name[5]; // Zero-terminated
} app;
struct rtcp_packet_psfb
{
uint8_t message_type;
uint32_t media_src;
uint8_t *fci;
size_t fci_len;
} psfb;
struct rtcp_packet_xr
{
uint8_t block_type;
uint8_t block_specific;
uint16_t block_len;
struct ntp_timestamp ntp;
} xr;
};
};
// An RTP session is characterised by all the receivers belonging to the session
// getting the same RTP and RTCP packets. So if you have clients that require
// different sample rates or where only some can accept encrypted payloads then
@ -62,12 +126,36 @@ rtp_session_free(struct rtp_session *session);
void
rtp_session_flush(struct rtp_session *session);
struct rtp_packet *
rtp_packet_next(struct rtp_session *session, size_t payload_len, int samples, char type);
/* Gets the next packet from the packet buffer, pkt->payload will be allocated
* to a size of payload_len (or larger).
*
* @in session RTP session
* @in payload_len Length of payload the packet needs to hold
* @in samples Number of samples in packet
* @in payload_type RTP payload type
* @in marker_bit Marker bit, see RFC3551
* @return Pointer to the next packet in the packet buffer
*/
struct rtp_packet *
rtp_packet_next(struct rtp_session *session, size_t payload_len, int samples, char payload_type, char marker_bit);
/* Call this after finalizing a packet, i.e. writing the payload and possibly
* sending. Registers the packet as final, i.e. it can now be retrieved with
* rtp_packet_get() for retransmission, if required. Also advances RTP position
* (seqnum and RTP time).
*
* @in session RTP session
* @in pkt RTP packet to commit
*/
void
rtp_packet_commit(struct rtp_session *session, struct rtp_packet *pkt);
/* Get a previously committed packet from the packet buffer
*
* @in session RTP session
* @in seqnum Packet sequence number
*/
struct rtp_packet *
rtp_packet_get(struct rtp_session *session, uint16_t seqnum);
@ -77,4 +165,7 @@ rtp_sync_is_time(struct rtp_session *session);
struct rtp_packet *
rtp_sync_packet_next(struct rtp_session *session, struct rtcp_timestamp cur_stamp, char type);
int
rtcp_packet_parse(struct rtcp_packet *pkt, uint8_t *data, size_t size);
#endif /* !__RTP_COMMON_H__ */

View File

@ -260,6 +260,19 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile, str
settings->video_codec = AV_CODEC_ID_PNG;
break;
case XCODE_VP8:
settings->encode_video = 1;
settings->silent = 1;
// See explanation above
#if (LIBAVFORMAT_VERSION_MAJOR > 58) || ((LIBAVFORMAT_VERSION_MAJOR == 58) && (LIBAVFORMAT_VERSION_MINOR > 29))
settings->format = "image2pipe";
#else
settings->format = "image2";
#endif
settings->pix_fmt = AV_PIX_FMT_YUVJ420P;
settings->video_codec = AV_CODEC_ID_VP8;
break;
default:
DPRINTF(E_LOG, L_XCODE, "Bug! Unknown transcoding profile\n");
return -1;
@ -283,7 +296,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile, str
if (quality && quality->bits_per_sample && (quality->bits_per_sample != 8 * av_get_bytes_per_sample(settings->sample_format)))
{
DPRINTF(E_LOG, L_XCODE, "Bug! Mismatch between profile and media quality\n");
DPRINTF(E_LOG, L_XCODE, "Bug! Mismatch between profile (%d bps) and media quality (%d bps)\n", 8 * av_get_bytes_per_sample(settings->sample_format), quality->bits_per_sample);
return -1;
}
@ -449,6 +462,12 @@ stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id
if (codec_id == AV_CODEC_ID_MJPEG)
av_dict_set(&options, "huffman", "default", 0);
// 20 ms frames is the current ffmpeg default, but we set it anyway, so that
// we don't risk issues if future versions change the default (it would become
// an issue because outputs/cast.c relies on 20 ms frames)
if (codec_id == AV_CODEC_ID_OPUS)
av_dict_set(&options, "frame_duration", "20", 0);
ret = avcodec_open2(s->codec, NULL, &options);
if (ret < 0)
{

View File

@ -23,9 +23,10 @@ enum transcode_profile
XCODE_MP3,
// Transcodes the best audio stream into OPUS
XCODE_OPUS,
// Transcodes the best video stream into JPEG/PNG
// Transcodes the best video stream into JPEG/PNG/VP8
XCODE_JPEG,
XCODE_PNG,
XCODE_VP8,
};
struct decode_ctx;