[xcode/daap/rsp] Default transcode to 320 kbps mp3 instead of wav
- Calculate size for both formats (+ move the return to transcode_encode_query) - Let transcode_needed() decide what format to output - Determine content-type from transcoding type - Add transcode-dependent ability to override file metadata in rsp/daap - Send file size matching format
This commit is contained in:
parent
9394d45de1
commit
3ee9204ff8
|
@ -703,13 +703,13 @@ artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *in_buf, bool is
|
|||
}
|
||||
|
||||
if (dst_format == ART_FMT_JPEG)
|
||||
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, dst_width, dst_height);
|
||||
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, 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);
|
||||
xcode_encode = transcode_encode_setup(XCODE_PNG, NULL, xcode_decode, 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);
|
||||
xcode_encode = transcode_encode_setup(XCODE_VP8, NULL, xcode_decode, dst_width, dst_height);
|
||||
else
|
||||
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, NULL, dst_width, dst_height);
|
||||
xcode_encode = transcode_encode_setup(XCODE_JPEG, NULL, xcode_decode, dst_width, dst_height);
|
||||
|
||||
if (!xcode_encode)
|
||||
{
|
||||
|
|
|
@ -360,12 +360,11 @@ dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errms
|
|||
}
|
||||
|
||||
int
|
||||
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav)
|
||||
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags)
|
||||
{
|
||||
const struct dmap_field_map *dfm;
|
||||
const struct dmap_field *df;
|
||||
char **strval;
|
||||
char *ptr;
|
||||
int32_t val;
|
||||
int want_mikd;
|
||||
int want_asdk;
|
||||
|
@ -444,40 +443,7 @@ dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, stru
|
|||
continue;
|
||||
}
|
||||
|
||||
val = 0;
|
||||
|
||||
if (force_wav)
|
||||
{
|
||||
switch (dfm->mfi_offset)
|
||||
{
|
||||
case dbmfi_offsetof(type):
|
||||
ptr = "wav";
|
||||
strval = &ptr;
|
||||
break;
|
||||
|
||||
case dbmfi_offsetof(bitrate):
|
||||
val = 0;
|
||||
ret = safe_atoi32(dbmfi->samplerate, &val);
|
||||
if ((ret < 0) || (val == 0))
|
||||
val = 1411;
|
||||
else
|
||||
val = (val * 8) / 250;
|
||||
|
||||
ptr = NULL;
|
||||
strval = &ptr;
|
||||
break;
|
||||
|
||||
case dbmfi_offsetof(description):
|
||||
ptr = "wav audio file";
|
||||
strval = &ptr;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dmap_add_field(song, df, *strval, val);
|
||||
dmap_add_field(song, df, *strval, 0);
|
||||
|
||||
DPRINTF(E_SPAM, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval);
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ void
|
|||
dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errmsg);
|
||||
|
||||
int
|
||||
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags, int force_wav);
|
||||
dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_media_file_info *dbmfi, const struct dmap_field **meta, int nmeta, int sort_tags);
|
||||
|
||||
int
|
||||
dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item);
|
||||
|
|
123
src/httpd.c
123
src/httpd.c
|
@ -66,10 +66,6 @@
|
|||
"<h1>%s</h1>\n" \
|
||||
"</body>\n</html>\n"
|
||||
|
||||
#define HTTPD_STREAM_SAMPLE_RATE 44100
|
||||
#define HTTPD_STREAM_BPS 16
|
||||
#define HTTPD_STREAM_CHANNELS 2
|
||||
|
||||
extern struct httpd_module httpd_dacp;
|
||||
extern struct httpd_module httpd_daap;
|
||||
extern struct httpd_module httpd_jsonapi;
|
||||
|
@ -93,6 +89,7 @@ static struct httpd_module *httpd_modules[] = {
|
|||
|
||||
struct content_type_map {
|
||||
char *ext;
|
||||
enum transcode_profile profile;
|
||||
char *ctype;
|
||||
};
|
||||
|
||||
|
@ -112,15 +109,18 @@ struct stream_ctx {
|
|||
|
||||
static const struct content_type_map ext2ctype[] =
|
||||
{
|
||||
{ ".html", "text/html; charset=utf-8" },
|
||||
{ ".xml", "text/xml; charset=utf-8" },
|
||||
{ ".css", "text/css; charset=utf-8" },
|
||||
{ ".txt", "text/plain; charset=utf-8" },
|
||||
{ ".js", "application/javascript; charset=utf-8" },
|
||||
{ ".gif", "image/gif" },
|
||||
{ ".ico", "image/x-ico" },
|
||||
{ ".png", "image/png" },
|
||||
{ NULL, NULL }
|
||||
{ ".html", XCODE_NONE, "text/html; charset=utf-8" },
|
||||
{ ".xml", XCODE_NONE, "text/xml; charset=utf-8" },
|
||||
{ ".css", XCODE_NONE, "text/css; charset=utf-8" },
|
||||
{ ".txt", XCODE_NONE, "text/plain; charset=utf-8" },
|
||||
{ ".js", XCODE_NONE, "application/javascript; charset=utf-8" },
|
||||
{ ".gif", XCODE_NONE, "image/gif" },
|
||||
{ ".ico", XCODE_NONE, "image/x-ico" },
|
||||
{ ".png", XCODE_PNG, "image/png" },
|
||||
{ ".jpg", XCODE_JPEG, "image/jpeg" },
|
||||
{ ".mp3", XCODE_MP3, "audio/mpeg" },
|
||||
{ ".wav", XCODE_WAV, "audio/wav" },
|
||||
{ NULL, XCODE_NONE, NULL }
|
||||
};
|
||||
|
||||
static char webroot_directory[PATH_MAX];
|
||||
|
@ -173,6 +173,40 @@ scrobble_cb(void *arg)
|
|||
}
|
||||
#endif
|
||||
|
||||
static const char *
|
||||
content_type_from_ext(const char *ext)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!ext)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; ext2ctype[i].ext; i++)
|
||||
{
|
||||
if (strcmp(ext, ext2ctype[i].ext) == 0)
|
||||
return ext2ctype[i].ctype;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *
|
||||
content_type_from_profile(enum transcode_profile profile)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (profile == XCODE_NONE)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; ext2ctype[i].ext; i++)
|
||||
{
|
||||
if (profile == ext2ctype[i].profile)
|
||||
return ext2ctype[i].ctype;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------- MODULES INTERFACE ---------------------------- */
|
||||
|
||||
|
@ -424,13 +458,11 @@ httpd_response_not_cachable(struct httpd_request *hreq)
|
|||
static void
|
||||
serve_file(struct httpd_request *hreq)
|
||||
{
|
||||
char *ext;
|
||||
char path[PATH_MAX];
|
||||
char deref[PATH_MAX];
|
||||
char *ctype;
|
||||
const char *ctype;
|
||||
struct stat sb;
|
||||
int fd;
|
||||
int i;
|
||||
uint8_t buf[4096];
|
||||
bool slashed;
|
||||
int ret;
|
||||
|
@ -544,19 +576,9 @@ serve_file(struct httpd_request *hreq)
|
|||
goto out_fail;
|
||||
}
|
||||
|
||||
ctype = "application/octet-stream";
|
||||
ext = strrchr(path, '.');
|
||||
if (ext)
|
||||
{
|
||||
for (i = 0; ext2ctype[i].ext; i++)
|
||||
{
|
||||
if (strcmp(ext, ext2ctype[i].ext) == 0)
|
||||
{
|
||||
ctype = ext2ctype[i].ctype;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctype = content_type_from_ext(strrchr(path, '.'));
|
||||
if (!ctype)
|
||||
ctype = "application/octet-stream";
|
||||
|
||||
httpd_header_add(hreq->out_headers, "Content-Type", ctype);
|
||||
|
||||
|
@ -570,7 +592,6 @@ serve_file(struct httpd_request *hreq)
|
|||
close(fd);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------- STREAM HANDLING ----------------------------- */
|
||||
|
||||
// This will triggered in a httpd thread, but since the reading may be in a
|
||||
|
@ -653,10 +674,11 @@ stream_new(struct media_file_info *mfi, struct httpd_request *hreq, event_callba
|
|||
}
|
||||
|
||||
static struct stream_ctx *
|
||||
stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, int64_t offset, int64_t end_offset, event_callback_fn stream_cb)
|
||||
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)
|
||||
{
|
||||
struct stream_ctx *st;
|
||||
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, 0 };
|
||||
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, HTTPD_STREAM_BIT_RATE };
|
||||
|
||||
st = stream_new(mfi, hreq, stream_cb);
|
||||
if (!st)
|
||||
|
@ -664,7 +686,7 @@ stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, in
|
|||
goto error;
|
||||
}
|
||||
|
||||
st->xcode = transcode_setup(XCODE_PCM16_HEADER, &quality, mfi->data_kind, mfi->path, mfi->song_length, &st->size);
|
||||
st->xcode = transcode_setup(profile, &quality, mfi->data_kind, mfi->path, mfi->song_length);
|
||||
if (!st->xcode)
|
||||
{
|
||||
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
|
||||
|
@ -673,6 +695,15 @@ stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, in
|
|||
goto error;
|
||||
}
|
||||
|
||||
st->size = transcode_encode_query(st->xcode->encode_ctx, "estimated_size");
|
||||
if (st->size < 0)
|
||||
{
|
||||
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, could not determine estimated size\n");
|
||||
|
||||
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
|
||||
goto error;
|
||||
}
|
||||
|
||||
st->stream_size = st->size - offset;
|
||||
if (end_offset > 0)
|
||||
st->stream_size -= (st->size - end_offset);
|
||||
|
@ -913,12 +944,13 @@ httpd_stream_file(struct httpd_request *hreq, int id)
|
|||
{
|
||||
struct media_file_info *mfi = NULL;
|
||||
struct stream_ctx *st = NULL;
|
||||
enum transcode_profile profile;
|
||||
const char *param;
|
||||
const char *param_end;
|
||||
const char *ctype;
|
||||
char buf[64];
|
||||
int64_t offset = 0;
|
||||
int64_t end_offset = 0;
|
||||
int transcode;
|
||||
int ret;
|
||||
|
||||
param = httpd_header_find(hreq->in_headers, "Range");
|
||||
|
@ -973,18 +1005,29 @@ httpd_stream_file(struct httpd_request *hreq, int id)
|
|||
}
|
||||
|
||||
param = httpd_header_find(hreq->in_headers, "Accept-Codecs");
|
||||
profile = transcode_needed(hreq->user_agent, param, mfi->codectype);
|
||||
if (profile == XCODE_UNKNOWN)
|
||||
{
|
||||
DPRINTF(E_LOG, L_HTTPD, "Could not serve '%s' to client, unable to determine output format\n", mfi->path);
|
||||
|
||||
transcode = transcode_needed(hreq->user_agent, param, mfi->codectype);
|
||||
if (transcode)
|
||||
httpd_send_error(hreq, HTTP_INTERNAL, "Cannot stream, unable to determine output format");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (profile != XCODE_NONE)
|
||||
{
|
||||
DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path);
|
||||
|
||||
st = stream_new_transcode(mfi, hreq, offset, end_offset, stream_chunk_xcode_cb);
|
||||
st = stream_new_transcode(mfi, profile, hreq, offset, end_offset, stream_chunk_xcode_cb);
|
||||
if (!st)
|
||||
goto error;
|
||||
|
||||
ctype = content_type_from_profile(profile);
|
||||
if (!ctype)
|
||||
goto error;
|
||||
|
||||
if (!httpd_header_find(hreq->out_headers, "Content-Type"))
|
||||
httpd_header_add(hreq->out_headers, "Content-Type", "audio/wav");
|
||||
httpd_header_add(hreq->out_headers, "Content-Type", ctype);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1027,7 +1070,7 @@ httpd_stream_file(struct httpd_request *hreq, int id)
|
|||
// If we are not decoding, send the Content-Length. We don't do that if we
|
||||
// are decoding because we can only guesstimate the size in this case and
|
||||
// the error margin is unknown and variable.
|
||||
if (!transcode)
|
||||
if (profile == XCODE_NONE)
|
||||
{
|
||||
ret = snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)st->size);
|
||||
if ((ret < 0) || (ret >= sizeof(buf)))
|
||||
|
@ -1059,7 +1102,7 @@ httpd_stream_file(struct httpd_request *hreq, int id)
|
|||
}
|
||||
|
||||
#ifdef HAVE_POSIX_FADVISE
|
||||
if (!transcode)
|
||||
if (profile == XCODE_NONE)
|
||||
{
|
||||
// Hint the OS
|
||||
if ( (ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_WILLNEED)) != 0 ||
|
||||
|
|
|
@ -1148,12 +1148,14 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
|
|||
const char *param;
|
||||
const char *client_codecs;
|
||||
const char *tag;
|
||||
char *last_codectype;
|
||||
size_t len;
|
||||
enum transcode_profile profile;
|
||||
struct transcode_metadata_string xcode_metadata;
|
||||
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, HTTPD_STREAM_BIT_RATE };
|
||||
uint32_t len_ms;
|
||||
int nmeta = 0;
|
||||
int sort_headers;
|
||||
int nsongs;
|
||||
int transcode;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist);
|
||||
|
@ -1221,30 +1223,31 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
|
|||
}
|
||||
|
||||
nsongs = 0;
|
||||
last_codectype = NULL;
|
||||
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
|
||||
{
|
||||
nsongs++;
|
||||
|
||||
if (!dbmfi.codectype)
|
||||
// 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()
|
||||
profile = s->is_remote ? XCODE_WAV : transcode_needed(hreq->user_agent, client_codecs, dbmfi.codectype);
|
||||
if (profile == XCODE_UNKNOWN)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname);
|
||||
|
||||
transcode = 0;
|
||||
}
|
||||
else if (s->is_remote)
|
||||
else if (profile != XCODE_NONE)
|
||||
{
|
||||
transcode = 1;
|
||||
}
|
||||
else if (!last_codectype || (strcmp(last_codectype, dbmfi.codectype) != 0))
|
||||
{
|
||||
transcode = transcode_needed(hreq->user_agent, client_codecs, dbmfi.codectype);
|
||||
if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
|
||||
len_ms = 3 * 60 * 1000; // just a fallback default
|
||||
|
||||
free(last_codectype);
|
||||
last_codectype = strdup(dbmfi.codectype);
|
||||
transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
|
||||
dbmfi.type = xcode_metadata.type;
|
||||
dbmfi.codectype = xcode_metadata.codectype;
|
||||
dbmfi.description = xcode_metadata.description;
|
||||
dbmfi.file_size = xcode_metadata.file_size;
|
||||
dbmfi.bitrate = xcode_metadata.bitrate;
|
||||
}
|
||||
|
||||
ret = dmap_encode_file_metadata(songlist, song, &dbmfi, meta, nmeta, sort_headers, transcode);
|
||||
ret = dmap_encode_file_metadata(songlist, song, &dbmfi, meta, nmeta, sort_headers);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Failed to encode song metadata\n");
|
||||
|
@ -1270,7 +1273,6 @@ daap_reply_songlist_generic(struct httpd_request *hreq, int playlist)
|
|||
|
||||
DPRINTF(E_DBG, L_DAAP, "Done with song list, %d songs\n", nsongs);
|
||||
|
||||
free(last_codectype);
|
||||
db_query_end(&qp);
|
||||
|
||||
if (ret == -100)
|
||||
|
|
|
@ -36,6 +36,11 @@
|
|||
#define HTTP_BADGATEWAY 502 /**< received an invalid response from the upstream */
|
||||
#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;
|
||||
|
||||
|
|
134
src/httpd_rsp.c
134
src/httpd_rsp.c
|
@ -407,28 +407,83 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
item_add(xml_node *parent, struct query_params *qp, const char *user_agent, const char *client_codecs, int mode)
|
||||
{
|
||||
struct media_quality quality = { HTTPD_STREAM_SAMPLE_RATE, HTTPD_STREAM_BPS, HTTPD_STREAM_CHANNELS, HTTPD_STREAM_BIT_RATE };
|
||||
struct db_media_file_info dbmfi;
|
||||
struct transcode_metadata_string xcode_metadata;
|
||||
enum transcode_profile profile;
|
||||
const char *orgcodec = NULL;
|
||||
uint32_t len_ms;
|
||||
xml_node *item;
|
||||
char **strval;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
ret = db_query_fetch_file(&dbmfi, qp);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
profile = transcode_needed(user_agent, client_codecs, dbmfi.codectype);
|
||||
if (profile == XCODE_UNKNOWN)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname);
|
||||
}
|
||||
else if (profile != XCODE_NONE)
|
||||
{
|
||||
orgcodec = dbmfi.codectype;
|
||||
|
||||
if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
|
||||
len_ms = 3 * 60 * 1000; // just a fallback default
|
||||
|
||||
transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
|
||||
dbmfi.type = xcode_metadata.type;
|
||||
dbmfi.codectype = xcode_metadata.codectype;
|
||||
dbmfi.description = xcode_metadata.description;
|
||||
dbmfi.file_size = xcode_metadata.file_size;
|
||||
dbmfi.bitrate = xcode_metadata.bitrate;
|
||||
}
|
||||
|
||||
// Now add block with content
|
||||
item = xml_new_node(parent, "item", NULL);
|
||||
|
||||
for (i = 0; rsp_fields[i].field; i++)
|
||||
{
|
||||
if (!(rsp_fields[i].flags & mode))
|
||||
continue;
|
||||
|
||||
strval = (char **) ((char *)&dbmfi + rsp_fields[i].offset);
|
||||
if (!(*strval) || (strlen(*strval) == 0))
|
||||
continue;
|
||||
|
||||
xml_new_node(item, rsp_fields[i].field, *strval);
|
||||
|
||||
// In case we are transcoding
|
||||
if (rsp_fields[i].offset == dbmfi_offsetof(codectype) && orgcodec)
|
||||
xml_new_node(item, "original_codec", orgcodec);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rsp_reply_playlist(struct httpd_request *hreq)
|
||||
{
|
||||
struct query_params qp;
|
||||
struct db_media_file_info dbmfi;
|
||||
const char *param;
|
||||
const char *ua;
|
||||
const char *client_codecs;
|
||||
char **strval;
|
||||
xml_node *xml;
|
||||
xml_node *response;
|
||||
xml_node *items;
|
||||
xml_node *item;
|
||||
int mode;
|
||||
int records;
|
||||
int transcode;
|
||||
int32_t bitrate;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
|
||||
client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
|
||||
|
||||
ret = safe_atoi32(hreq->path_parts[2], &qp.id);
|
||||
if (ret < 0)
|
||||
{
|
||||
|
@ -485,70 +540,15 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||
|
||||
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results);
|
||||
|
||||
// Add a parent items block (all items), and then one item per file
|
||||
items = xml_new_node(response, "items", NULL);
|
||||
|
||||
/* Items block (all items) */
|
||||
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
|
||||
do
|
||||
{
|
||||
ua = httpd_header_find(hreq->in_headers, "User-Agent");
|
||||
client_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
|
||||
|
||||
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
|
||||
|
||||
/* Item block (one item) */
|
||||
item = xml_new_node(items, "item", NULL);
|
||||
|
||||
for (i = 0; rsp_fields[i].field; i++)
|
||||
{
|
||||
if (!(rsp_fields[i].flags & mode))
|
||||
continue;
|
||||
|
||||
strval = (char **) ((char *)&dbmfi + rsp_fields[i].offset);
|
||||
|
||||
if (!(*strval) || (strlen(*strval) == 0))
|
||||
continue;
|
||||
|
||||
if (!transcode)
|
||||
{
|
||||
xml_new_node(item, rsp_fields[i].field, *strval);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (rsp_fields[i].offset)
|
||||
{
|
||||
case dbmfi_offsetof(type):
|
||||
xml_new_node(item, rsp_fields[i].field, "wav");
|
||||
break;
|
||||
|
||||
case dbmfi_offsetof(bitrate):
|
||||
bitrate = 0;
|
||||
ret = safe_atoi32(dbmfi.samplerate, &bitrate);
|
||||
if ((ret < 0) || (bitrate == 0))
|
||||
bitrate = 1411;
|
||||
else
|
||||
bitrate = (bitrate * 8) / 250;
|
||||
|
||||
xml_new_node_textf(item, rsp_fields[i].field, "%d", bitrate);
|
||||
break;
|
||||
|
||||
case dbmfi_offsetof(description):
|
||||
xml_new_node(item, rsp_fields[i].field, "wav audio file");
|
||||
break;
|
||||
|
||||
case dbmfi_offsetof(codectype):
|
||||
xml_new_node(item, rsp_fields[i].field, "wav");
|
||||
xml_new_node(item, "original_codec", *strval);
|
||||
break;
|
||||
|
||||
default:
|
||||
xml_new_node(item, rsp_fields[i].field, *strval);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret = item_add(items, &qp, hreq->user_agent, client_codecs, mode);
|
||||
}
|
||||
while (ret == 0);
|
||||
|
||||
if (qp.filter)
|
||||
free(qp.filter);
|
||||
free(qp.filter);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
|
|
|
@ -38,7 +38,7 @@ setup(struct input_source *source)
|
|||
{
|
||||
struct transcode_ctx *ctx;
|
||||
|
||||
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms, NULL);
|
||||
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms);
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
|
|
|
@ -304,7 +304,7 @@ setup(struct input_source *source)
|
|||
free(source->path);
|
||||
source->path = url;
|
||||
|
||||
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms, NULL);
|
||||
ctx = transcode_setup(XCODE_PCM_NATIVE, NULL, source->data_kind, source->path, source->len_ms);
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ download_xcode_setup(struct download_ctx *download)
|
|||
if (!xcode->decode_ctx)
|
||||
goto error;
|
||||
|
||||
xcode->encode_ctx = transcode_encode_setup(XCODE_PCM16, NULL, xcode->decode_ctx, NULL, 0, 0);
|
||||
xcode->encode_ctx = transcode_encode_setup(XCODE_PCM16, NULL, xcode->decode_ctx, 0, 0);
|
||||
if (!xcode->encode_ctx)
|
||||
goto error;
|
||||
|
||||
|
|
|
@ -311,7 +311,7 @@ encoding_reset(struct media_quality *quality)
|
|||
|
||||
profile = quality_to_xcode(&subscription->quality);
|
||||
if (profile != XCODE_UNKNOWN)
|
||||
subscription->encode_ctx = transcode_encode_setup(profile, &subscription->quality, decode_ctx, NULL, 0, 0);
|
||||
subscription->encode_ctx = transcode_encode_setup(profile, &subscription->quality, decode_ctx, 0, 0);
|
||||
else
|
||||
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);
|
||||
|
|
|
@ -1153,7 +1153,7 @@ master_session_make(struct media_quality *quality)
|
|||
goto error;
|
||||
}
|
||||
|
||||
rms->encode_ctx = transcode_encode_setup(XCODE_ALAC, quality, decode_ctx, NULL, 0, 0);
|
||||
rms->encode_ctx = transcode_encode_setup(XCODE_ALAC, quality, decode_ctx, 0, 0);
|
||||
transcode_decode_cleanup(&decode_ctx);
|
||||
if (!rms->encode_ctx)
|
||||
{
|
||||
|
|
|
@ -2399,7 +2399,7 @@ cast_init(void)
|
|||
goto out_tls_deinit;
|
||||
}
|
||||
|
||||
cast_encode_ctx = transcode_encode_setup(XCODE_OPUS, &cast_quality_default, decode_ctx, NULL, 0, 0);
|
||||
cast_encode_ctx = transcode_encode_setup(XCODE_OPUS, &cast_quality_default, decode_ctx, 0, 0);
|
||||
transcode_decode_cleanup(&decode_ctx);
|
||||
if (!cast_encode_ctx)
|
||||
{
|
||||
|
|
|
@ -1888,7 +1888,7 @@ master_session_make(struct media_quality *quality, bool encrypt)
|
|||
goto error;
|
||||
}
|
||||
|
||||
rms->encode_ctx = transcode_encode_setup(XCODE_ALAC, quality, decode_ctx, NULL, 0, 0);
|
||||
rms->encode_ctx = transcode_encode_setup(XCODE_ALAC, quality, decode_ctx, 0, 0);
|
||||
transcode_decode_cleanup(&decode_ctx);
|
||||
if (!rms->encode_ctx)
|
||||
{
|
||||
|
|
|
@ -133,7 +133,7 @@ encoder_setup(enum player_format format, struct media_quality *quality)
|
|||
}
|
||||
|
||||
if (format == PLAYER_FORMAT_MP3)
|
||||
encode_ctx = transcode_encode_setup(XCODE_MP3, quality, decode_ctx, NULL, 0, 0);
|
||||
encode_ctx = transcode_encode_setup(XCODE_MP3, quality, decode_ctx, 0, 0);
|
||||
|
||||
if (!encode_ctx)
|
||||
{
|
||||
|
|
245
src/transcode.c
245
src/transcode.c
|
@ -139,8 +139,8 @@ struct decode_ctx
|
|||
struct stream_ctx audio_stream;
|
||||
struct stream_ctx video_stream;
|
||||
|
||||
// Duration (used to make wav header)
|
||||
uint32_t duration;
|
||||
// Source duration in ms as provided by caller
|
||||
uint32_t len_ms;
|
||||
|
||||
// Data kind (used to determine if ICY metadata is relevant to look for)
|
||||
enum data_kind data_kind;
|
||||
|
@ -186,7 +186,10 @@ struct encode_ctx
|
|||
AVPacket *encoded_pkt;
|
||||
|
||||
// How many output bytes we have processed in total
|
||||
off_t total_bytes;
|
||||
off_t bytes_processed;
|
||||
|
||||
// Estimated total size of output
|
||||
off_t bytes_total;
|
||||
|
||||
// Used to check for ICY metadata changes at certain intervals
|
||||
uint32_t icy_interval;
|
||||
|
@ -240,7 +243,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile, str
|
|||
settings->with_user_filters = true;
|
||||
break;
|
||||
|
||||
case XCODE_PCM16_HEADER:
|
||||
case XCODE_WAV:
|
||||
settings->with_wav_header = true;
|
||||
settings->with_user_filters = true;
|
||||
case XCODE_PCM16:
|
||||
|
@ -435,32 +438,48 @@ add_le32(uint8_t *dst, uint32_t val)
|
|||
* header must have size WAV_HEADER_LEN (44 bytes)
|
||||
*/
|
||||
static void
|
||||
make_wav_header(uint8_t *header, off_t *est_size, int sample_rate, int bps, int channels, int duration)
|
||||
make_wav_header(uint8_t *header, int sample_rate, int bytes_per_sample, int channels, off_t bytes_total)
|
||||
{
|
||||
uint32_t wav_len;
|
||||
|
||||
if (duration == 0)
|
||||
duration = 3 * 60 * 1000; /* 3 minutes, in ms */
|
||||
|
||||
wav_len = channels * bps * sample_rate * (duration / 1000);
|
||||
|
||||
if (est_size)
|
||||
*est_size = wav_len + WAV_HEADER_LEN;
|
||||
uint32_t wav_size = bytes_total - WAV_HEADER_LEN;
|
||||
|
||||
memcpy(header, "RIFF", 4);
|
||||
add_le32(header + 4, 36 + wav_len);
|
||||
add_le32(header + 4, 36 + wav_size);
|
||||
memcpy(header + 8, "WAVEfmt ", 8);
|
||||
add_le32(header + 16, 16);
|
||||
add_le16(header + 20, 1);
|
||||
add_le16(header + 22, channels); /* channels */
|
||||
add_le32(header + 24, sample_rate); /* samplerate */
|
||||
add_le32(header + 28, sample_rate * channels * bps); /* byte rate */
|
||||
add_le16(header + 32, channels * bps); /* block align */
|
||||
add_le16(header + 34, 8 * bps); /* bits per sample */
|
||||
add_le32(header + 28, sample_rate * channels * bytes_per_sample); /* byte rate */
|
||||
add_le16(header + 32, channels * bytes_per_sample); /* block align */
|
||||
add_le16(header + 34, 8 * bytes_per_sample); /* bits per sample */
|
||||
memcpy(header + 36, "data", 4);
|
||||
add_le32(header + 40, wav_len);
|
||||
add_le32(header + 40, wav_size);
|
||||
}
|
||||
|
||||
static off_t
|
||||
size_estimate(enum transcode_profile profile, int bit_rate, int sample_rate, int bytes_per_sample, int channels, int len_ms)
|
||||
{
|
||||
off_t bytes;
|
||||
|
||||
// If the source has a number of samples that doesn't match an even len_ms
|
||||
// then the length may have been rounded up. We prefer an estimate that is on
|
||||
// the low side, otherwise ffprobe won't trust the length from our wav header.
|
||||
if (len_ms > 0)
|
||||
len_ms -= 1;
|
||||
else
|
||||
len_ms = 3 * 60 * 1000;
|
||||
|
||||
if (profile == XCODE_WAV)
|
||||
bytes = (int64_t)len_ms * channels * bytes_per_sample * sample_rate / 1000 + WAV_HEADER_LEN;
|
||||
else if (profile == XCODE_MP3)
|
||||
bytes = (int64_t)len_ms * bit_rate / 8000;
|
||||
else
|
||||
bytes = -1;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Checks if this stream index is one that we are decoding
|
||||
*
|
||||
|
@ -515,6 +534,8 @@ stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id
|
|||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_XCODE, "Selected encoder '%s'\n", encoder->long_name);
|
||||
|
||||
CHECK_NULL(L_XCODE, s->stream = avformat_new_stream(ctx->ofmt_ctx, NULL));
|
||||
CHECK_NULL(L_XCODE, s->codec = avcodec_alloc_context3(encoder));
|
||||
|
||||
|
@ -1529,6 +1550,11 @@ open_filters(struct encode_ctx *ctx, struct decode_ctx *src_ctx)
|
|||
ret = create_filtergraph(&ctx->audio_stream, filters, ARRAY_SIZE(filters), &src_ctx->audio_stream);
|
||||
if (ret < 0)
|
||||
goto out_fail;
|
||||
|
||||
// Many audio encoders require a fixed frame size. This will ensure that
|
||||
// the filt_frame from av_buffersink_get_frame has that size (except EOF).
|
||||
if (! (ctx->audio_stream.codec->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE))
|
||||
av_buffersink_set_frame_size(ctx->audio_stream.buffersink_ctx, ctx->audio_stream.codec->frame_size);
|
||||
}
|
||||
|
||||
if (ctx->settings.encode_video)
|
||||
|
@ -1563,7 +1589,7 @@ close_filters(struct encode_ctx *ctx)
|
|||
/* Setup */
|
||||
|
||||
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 song_length)
|
||||
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)
|
||||
{
|
||||
struct decode_ctx *ctx;
|
||||
int ret;
|
||||
|
@ -1572,7 +1598,7 @@ transcode_decode_setup(enum transcode_profile profile, struct media_quality *qua
|
|||
CHECK_NULL(L_XCODE, ctx->decoded_frame = av_frame_alloc());
|
||||
CHECK_NULL(L_XCODE, ctx->packet = av_packet_alloc());
|
||||
|
||||
ctx->duration = song_length;
|
||||
ctx->len_ms = len_ms;
|
||||
ctx->data_kind = data_kind;
|
||||
|
||||
ret = init_settings(&ctx->settings, profile, quality);
|
||||
|
@ -1603,11 +1629,11 @@ transcode_decode_setup(enum transcode_profile profile, struct media_quality *qua
|
|||
}
|
||||
|
||||
struct encode_ctx *
|
||||
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, off_t *est_size, int width, int height)
|
||||
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, int width, int height)
|
||||
{
|
||||
struct encode_ctx *ctx;
|
||||
int src_bps;
|
||||
int dst_bps;
|
||||
int src_bytes_per_sample;
|
||||
int dst_bytes_per_sample;
|
||||
int channels;
|
||||
|
||||
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct encode_ctx)));
|
||||
|
@ -1629,8 +1655,8 @@ transcode_encode_setup(enum transcode_profile profile, struct media_quality *qua
|
|||
// Caller did not specify a sample format -> determine from source
|
||||
if (!ctx->settings.sample_format && ctx->settings.encode_audio)
|
||||
{
|
||||
src_bps = av_get_bytes_per_sample(src_ctx->audio_stream.codec->sample_fmt);
|
||||
if (src_bps == 4)
|
||||
src_bytes_per_sample = av_get_bytes_per_sample(src_ctx->audio_stream.codec->sample_fmt);
|
||||
if (src_bytes_per_sample == 4)
|
||||
{
|
||||
ctx->settings.sample_format = AV_SAMPLE_FMT_S32;
|
||||
ctx->settings.audio_codec = AV_CODEC_ID_PCM_S32LE;
|
||||
|
@ -1663,17 +1689,14 @@ transcode_encode_setup(enum transcode_profile profile, struct media_quality *qua
|
|||
channels = ctx->settings.channels;
|
||||
#endif
|
||||
|
||||
if (ctx->settings.with_wav_header)
|
||||
{
|
||||
dst_bps = av_get_bytes_per_sample(ctx->settings.sample_format);
|
||||
make_wav_header(ctx->wav_header, est_size, ctx->settings.sample_rate, dst_bps, channels, src_ctx->duration);
|
||||
}
|
||||
dst_bytes_per_sample = av_get_bytes_per_sample(ctx->settings.sample_format);
|
||||
|
||||
ctx->bytes_total = size_estimate(profile, ctx->settings.bit_rate, ctx->settings.sample_rate, dst_bytes_per_sample, channels, src_ctx->len_ms);
|
||||
|
||||
if (ctx->settings.with_wav_header)
|
||||
make_wav_header(ctx->wav_header, ctx->settings.sample_rate, dst_bytes_per_sample, channels, ctx->bytes_total);
|
||||
if (ctx->settings.with_icy && src_ctx->data_kind == DATA_KIND_HTTP)
|
||||
{
|
||||
dst_bps = av_get_bytes_per_sample(ctx->settings.sample_format);
|
||||
ctx->icy_interval = METADATA_ICY_INTERVAL * channels * dst_bps * ctx->settings.sample_rate;
|
||||
}
|
||||
ctx->icy_interval = METADATA_ICY_INTERVAL * channels * dst_bytes_per_sample * ctx->settings.sample_rate;
|
||||
|
||||
if (open_output(ctx, src_ctx) < 0)
|
||||
goto fail_free;
|
||||
|
@ -1693,20 +1716,20 @@ transcode_encode_setup(enum transcode_profile profile, struct media_quality *qua
|
|||
}
|
||||
|
||||
struct transcode_ctx *
|
||||
transcode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size)
|
||||
transcode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, uint32_t len_ms)
|
||||
{
|
||||
struct transcode_ctx *ctx;
|
||||
|
||||
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct transcode_ctx)));
|
||||
|
||||
ctx->decode_ctx = transcode_decode_setup(profile, quality, data_kind, path, NULL, song_length);
|
||||
ctx->decode_ctx = transcode_decode_setup(profile, quality, data_kind, path, NULL, len_ms);
|
||||
if (!ctx->decode_ctx)
|
||||
{
|
||||
free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->encode_ctx = transcode_encode_setup(profile, quality, ctx->decode_ctx, est_size, 0, 0);
|
||||
ctx->encode_ctx = transcode_encode_setup(profile, quality, ctx->decode_ctx, 0, 0);
|
||||
if (!ctx->encode_ctx)
|
||||
{
|
||||
transcode_decode_cleanup(&ctx->decode_ctx);
|
||||
|
@ -1779,87 +1802,73 @@ transcode_decode_setup_raw(enum transcode_profile profile, struct media_quality
|
|||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
enum transcode_profile
|
||||
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype)
|
||||
{
|
||||
char *codectype;
|
||||
cfg_t *lib;
|
||||
int size;
|
||||
bool force_xcode;
|
||||
int count;
|
||||
int i;
|
||||
|
||||
if (!file_codectype)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Can't determine decode status, codec type is unknown\n");
|
||||
return -1;
|
||||
return XCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
lib = cfg_getsec(cfg, "library");
|
||||
|
||||
size = cfg_size(lib, "no_decode");
|
||||
if (size > 0)
|
||||
count = cfg_size(lib, "no_decode");
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
codectype = cfg_getnstr(lib, "no_decode", i);
|
||||
|
||||
if (strcmp(file_codectype, codectype) == 0)
|
||||
return 0; // Codectype is in no_decode
|
||||
}
|
||||
codectype = cfg_getnstr(lib, "no_decode", i);
|
||||
if (strcmp(file_codectype, codectype) == 0)
|
||||
return XCODE_NONE; // Codectype is in no_decode
|
||||
}
|
||||
|
||||
size = cfg_size(lib, "force_decode");
|
||||
if (size > 0)
|
||||
count = cfg_size(lib, "force_decode");
|
||||
for (i = 0, force_xcode = false; i < count && !force_xcode; i++)
|
||||
{
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
codectype = cfg_getnstr(lib, "force_decode", i);
|
||||
codectype = cfg_getnstr(lib, "force_decode", i);
|
||||
if (strcmp(file_codectype, codectype) == 0)
|
||||
force_xcode = true; // Codectype is in force_decode
|
||||
}
|
||||
|
||||
if (strcmp(file_codectype, codectype) == 0)
|
||||
return 1; // Codectype is in force_decode
|
||||
}
|
||||
if (!client_codecs && user_agent)
|
||||
{
|
||||
if (strncmp(user_agent, "iTunes", strlen("iTunes")) == 0)
|
||||
client_codecs = itunes_codecs;
|
||||
else if (strncmp(user_agent, "Music/", strlen("Music/")) == 0) // Apple Music, include slash because the name is generic
|
||||
client_codecs = itunes_codecs;
|
||||
else if (strncmp(user_agent, "QuickTime", strlen("QuickTime")) == 0)
|
||||
client_codecs = itunes_codecs; // Use iTunes codecs
|
||||
else if (strncmp(user_agent, "Front%20Row", strlen("Front%20Row")) == 0)
|
||||
client_codecs = itunes_codecs; // Use iTunes codecs
|
||||
else if (strncmp(user_agent, "AppleCoreMedia", strlen("AppleCoreMedia")) == 0)
|
||||
client_codecs = itunes_codecs; // Use iTunes codecs
|
||||
else if (strncmp(user_agent, "Roku", strlen("Roku")) == 0)
|
||||
client_codecs = roku_codecs;
|
||||
else if (strncmp(user_agent, "Hifidelio", strlen("Hifidelio")) == 0)
|
||||
/* Allegedly can't transcode for Hifidelio because their
|
||||
* HTTP implementation doesn't honour Connection: close.
|
||||
* At least, that's why mt-daapd didn't do it.
|
||||
*/
|
||||
return XCODE_NONE;
|
||||
}
|
||||
|
||||
if (!client_codecs)
|
||||
{
|
||||
if (user_agent)
|
||||
{
|
||||
if (strncmp(user_agent, "iTunes", strlen("iTunes")) == 0)
|
||||
client_codecs = itunes_codecs;
|
||||
else if (strncmp(user_agent, "Music/", strlen("Music/")) == 0) // Apple Music, include slash because the name is generic
|
||||
client_codecs = itunes_codecs;
|
||||
else if (strncmp(user_agent, "QuickTime", strlen("QuickTime")) == 0)
|
||||
client_codecs = itunes_codecs; // Use iTunes codecs
|
||||
else if (strncmp(user_agent, "Front%20Row", strlen("Front%20Row")) == 0)
|
||||
client_codecs = itunes_codecs; // Use iTunes codecs
|
||||
else if (strncmp(user_agent, "AppleCoreMedia", strlen("AppleCoreMedia")) == 0)
|
||||
client_codecs = itunes_codecs; // Use iTunes codecs
|
||||
else if (strncmp(user_agent, "Roku", strlen("Roku")) == 0)
|
||||
client_codecs = roku_codecs;
|
||||
else if (strncmp(user_agent, "Hifidelio", strlen("Hifidelio")) == 0)
|
||||
/* Allegedly can't transcode for Hifidelio because their
|
||||
* HTTP implementation doesn't honour Connection: close.
|
||||
* At least, that's why mt-daapd didn't do it.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
client_codecs = default_codecs;
|
||||
else
|
||||
DPRINTF(E_SPAM, L_XCODE, "Client advertises codecs: %s\n", client_codecs);
|
||||
|
||||
if (!client_codecs)
|
||||
{
|
||||
DPRINTF(E_SPAM, L_XCODE, "Could not identify client, using default codectype set\n");
|
||||
client_codecs = default_codecs;
|
||||
}
|
||||
|
||||
if (strstr(client_codecs, file_codectype))
|
||||
{
|
||||
DPRINTF(E_SPAM, L_XCODE, "Codectype supported by client, no decoding needed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM, L_XCODE, "Will decode\n");
|
||||
return 1;
|
||||
if (!force_xcode && strstr(client_codecs, file_codectype))
|
||||
return XCODE_NONE;
|
||||
else if (strstr(client_codecs, "mpeg"))
|
||||
return XCODE_MP3;
|
||||
else if (strstr(client_codecs, "wav"))
|
||||
return XCODE_WAV;
|
||||
else
|
||||
return XCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2010,9 +2019,9 @@ transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int
|
|||
|
||||
evbuffer_add_buffer(evbuf, ctx->encode_ctx->obuf);
|
||||
|
||||
ctx->encode_ctx->total_bytes += processed;
|
||||
ctx->encode_ctx->bytes_processed += processed;
|
||||
if (icy_timer && ctx->encode_ctx->icy_interval)
|
||||
*icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed);
|
||||
*icy_timer = (ctx->encode_ctx->bytes_processed % ctx->encode_ctx->icy_interval < processed);
|
||||
|
||||
if ((ret < 0) && (ret != AVERROR_EOF))
|
||||
return ret;
|
||||
|
@ -2218,6 +2227,11 @@ transcode_encode_query(struct encode_ctx *ctx, const char *query)
|
|||
if (ctx->audio_stream.stream)
|
||||
return ctx->audio_stream.stream->codecpar->frame_size;
|
||||
}
|
||||
else if (strcmp(query, "estimated_size") == 0)
|
||||
{
|
||||
if (ctx->audio_stream.stream)
|
||||
return ctx->bytes_total;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
@ -2244,3 +2258,38 @@ transcode_metadata(struct transcode_ctx *ctx, int *changed)
|
|||
return m;
|
||||
}
|
||||
|
||||
void
|
||||
transcode_metadata_strings_set(struct transcode_metadata_string *s, enum transcode_profile profile, struct media_quality *q, uint32_t len_ms)
|
||||
{
|
||||
off_t bytes;
|
||||
|
||||
memset(s, 0, sizeof(struct transcode_metadata_string));
|
||||
|
||||
switch (profile)
|
||||
{
|
||||
case XCODE_WAV:
|
||||
s->type = "wav";
|
||||
s->codectype = "wav";
|
||||
s->description = "WAV audio file";
|
||||
|
||||
snprintf(s->bitrate, sizeof(s->bitrate), "%d", 8 * STOB(q->sample_rate, q->bits_per_sample, q->channels) / 1000); // 44100/16/2 -> 1411
|
||||
|
||||
bytes = size_estimate(profile, q->bit_rate, q->sample_rate, q->bits_per_sample / 8, q->channels, len_ms);
|
||||
snprintf(s->file_size, sizeof(s->file_size), "%d", (int)bytes);
|
||||
break;
|
||||
|
||||
case XCODE_MP3:
|
||||
s->type = "mp3";
|
||||
s->codectype = "mpeg";
|
||||
s->description = "MPEG audio file";
|
||||
|
||||
snprintf(s->bitrate, sizeof(s->bitrate), "%d", q->bit_rate / 1000);
|
||||
|
||||
bytes = size_estimate(profile, q->bit_rate, q->sample_rate, q->bits_per_sample / 8, q->channels, len_ms);
|
||||
snprintf(s->file_size, sizeof(s->file_size), "%d", (int)bytes);
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(E_WARN, L_XCODE, "transcode_metadata_strings_set() called with unknown profile %d\n", profile);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
enum transcode_profile
|
||||
{
|
||||
// Used for errors
|
||||
XCODE_UNKNOWN = 0,
|
||||
XCODE_UNKNOWN,
|
||||
// No transcoding, send as-is
|
||||
XCODE_NONE,
|
||||
// Decodes the best audio stream into PCM16 or PCM24, no resampling (does not add wav header)
|
||||
XCODE_PCM_NATIVE,
|
||||
// Decodes/resamples the best audio stream into PCM16 (with wav header)
|
||||
XCODE_PCM16_HEADER,
|
||||
XCODE_WAV,
|
||||
// Decodes/resamples the best audio stream into PCM16/24/32 (no wav headers)
|
||||
XCODE_PCM16,
|
||||
XCODE_PCM24,
|
||||
|
@ -60,20 +62,30 @@ struct transcode_evbuf_io
|
|||
void *seekfn_arg;
|
||||
};
|
||||
|
||||
struct transcode_metadata_string
|
||||
{
|
||||
char *type;
|
||||
char *codectype;
|
||||
char *description;
|
||||
char file_size[64];
|
||||
char bitrate[32];
|
||||
};
|
||||
|
||||
|
||||
// Setting up
|
||||
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 song_length);
|
||||
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);
|
||||
|
||||
struct encode_ctx *
|
||||
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, off_t *est_size, int width, int height);
|
||||
transcode_encode_setup(enum transcode_profile profile, struct media_quality *quality, struct decode_ctx *src_ctx, int width, int height);
|
||||
|
||||
struct transcode_ctx *
|
||||
transcode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size);
|
||||
transcode_setup(enum transcode_profile profile, struct media_quality *quality, enum data_kind data_kind, const char *path, uint32_t len_ms);
|
||||
|
||||
struct decode_ctx *
|
||||
transcode_decode_setup_raw(enum transcode_profile profile, struct media_quality *quality);
|
||||
|
||||
int
|
||||
enum transcode_profile
|
||||
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype);
|
||||
|
||||
// Cleaning up
|
||||
|
@ -170,4 +182,10 @@ transcode_encode_query(struct encode_ctx *ctx, const char *query);
|
|||
struct http_icy_metadata *
|
||||
transcode_metadata(struct transcode_ctx *ctx, int *changed);
|
||||
|
||||
// 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
|
||||
// filescanner. This function creates strings to be used for override.
|
||||
void
|
||||
transcode_metadata_strings_set(struct transcode_metadata_string *s, enum transcode_profile profile, struct media_quality *q, uint32_t len_ms);
|
||||
|
||||
#endif /* !__TRANSCODE_H__ */
|
||||
|
|
Loading…
Reference in New Issue