mirror of
https://github.com/owntone/owntone-server.git
synced 2025-04-21 03:04:04 -04:00
[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)
|
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)
|
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)
|
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
|
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)
|
if (!xcode_encode)
|
||||||
{
|
{
|
||||||
|
@ -360,12 +360,11 @@ dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errms
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
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_map *dfm;
|
||||||
const struct dmap_field *df;
|
const struct dmap_field *df;
|
||||||
char **strval;
|
char **strval;
|
||||||
char *ptr;
|
|
||||||
int32_t val;
|
int32_t val;
|
||||||
int want_mikd;
|
int want_mikd;
|
||||||
int want_asdk;
|
int want_asdk;
|
||||||
@ -444,40 +443,7 @@ dmap_encode_file_metadata(struct evbuffer *songlist, struct evbuffer *song, stru
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
val = 0;
|
dmap_add_field(song, df, *strval, 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);
|
|
||||||
|
|
||||||
DPRINTF(E_SPAM, L_DAAP, "Done with meta tag %s (%s)\n", df->desc, *strval);
|
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);
|
dmap_error_make(struct evbuffer *evbuf, const char *container, const char *errmsg);
|
||||||
|
|
||||||
int
|
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
|
int
|
||||||
dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item);
|
dmap_encode_queue_metadata(struct evbuffer *songlist, struct evbuffer *song, struct db_queue_item *queue_item);
|
||||||
|
121
src/httpd.c
121
src/httpd.c
@ -66,10 +66,6 @@
|
|||||||
"<h1>%s</h1>\n" \
|
"<h1>%s</h1>\n" \
|
||||||
"</body>\n</html>\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_dacp;
|
||||||
extern struct httpd_module httpd_daap;
|
extern struct httpd_module httpd_daap;
|
||||||
extern struct httpd_module httpd_jsonapi;
|
extern struct httpd_module httpd_jsonapi;
|
||||||
@ -93,6 +89,7 @@ static struct httpd_module *httpd_modules[] = {
|
|||||||
|
|
||||||
struct content_type_map {
|
struct content_type_map {
|
||||||
char *ext;
|
char *ext;
|
||||||
|
enum transcode_profile profile;
|
||||||
char *ctype;
|
char *ctype;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -112,15 +109,18 @@ struct stream_ctx {
|
|||||||
|
|
||||||
static const struct content_type_map ext2ctype[] =
|
static const struct content_type_map ext2ctype[] =
|
||||||
{
|
{
|
||||||
{ ".html", "text/html; charset=utf-8" },
|
{ ".html", XCODE_NONE, "text/html; charset=utf-8" },
|
||||||
{ ".xml", "text/xml; charset=utf-8" },
|
{ ".xml", XCODE_NONE, "text/xml; charset=utf-8" },
|
||||||
{ ".css", "text/css; charset=utf-8" },
|
{ ".css", XCODE_NONE, "text/css; charset=utf-8" },
|
||||||
{ ".txt", "text/plain; charset=utf-8" },
|
{ ".txt", XCODE_NONE, "text/plain; charset=utf-8" },
|
||||||
{ ".js", "application/javascript; charset=utf-8" },
|
{ ".js", XCODE_NONE, "application/javascript; charset=utf-8" },
|
||||||
{ ".gif", "image/gif" },
|
{ ".gif", XCODE_NONE, "image/gif" },
|
||||||
{ ".ico", "image/x-ico" },
|
{ ".ico", XCODE_NONE, "image/x-ico" },
|
||||||
{ ".png", "image/png" },
|
{ ".png", XCODE_PNG, "image/png" },
|
||||||
{ NULL, NULL }
|
{ ".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];
|
static char webroot_directory[PATH_MAX];
|
||||||
@ -173,6 +173,40 @@ scrobble_cb(void *arg)
|
|||||||
}
|
}
|
||||||
#endif
|
#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 ---------------------------- */
|
/* --------------------------- MODULES INTERFACE ---------------------------- */
|
||||||
|
|
||||||
@ -424,13 +458,11 @@ httpd_response_not_cachable(struct httpd_request *hreq)
|
|||||||
static void
|
static void
|
||||||
serve_file(struct httpd_request *hreq)
|
serve_file(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
char *ext;
|
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX];
|
||||||
char deref[PATH_MAX];
|
char deref[PATH_MAX];
|
||||||
char *ctype;
|
const char *ctype;
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
int fd;
|
int fd;
|
||||||
int i;
|
|
||||||
uint8_t buf[4096];
|
uint8_t buf[4096];
|
||||||
bool slashed;
|
bool slashed;
|
||||||
int ret;
|
int ret;
|
||||||
@ -544,19 +576,9 @@ serve_file(struct httpd_request *hreq)
|
|||||||
goto out_fail;
|
goto out_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctype = content_type_from_ext(strrchr(path, '.'));
|
||||||
|
if (!ctype)
|
||||||
ctype = "application/octet-stream";
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
httpd_header_add(hreq->out_headers, "Content-Type", ctype);
|
httpd_header_add(hreq->out_headers, "Content-Type", ctype);
|
||||||
|
|
||||||
@ -570,7 +592,6 @@ serve_file(struct httpd_request *hreq)
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ---------------------------- STREAM HANDLING ----------------------------- */
|
/* ---------------------------- STREAM HANDLING ----------------------------- */
|
||||||
|
|
||||||
// This will triggered in a httpd thread, but since the reading may be in a
|
// 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 *
|
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 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);
|
st = stream_new(mfi, hreq, stream_cb);
|
||||||
if (!st)
|
if (!st)
|
||||||
@ -664,7 +686,7 @@ stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, in
|
|||||||
goto error;
|
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)
|
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");
|
||||||
@ -673,6 +695,15 @@ stream_new_transcode(struct media_file_info *mfi, struct httpd_request *hreq, in
|
|||||||
goto error;
|
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;
|
st->stream_size = st->size - offset;
|
||||||
if (end_offset > 0)
|
if (end_offset > 0)
|
||||||
st->stream_size -= (st->size - end_offset);
|
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 media_file_info *mfi = NULL;
|
||||||
struct stream_ctx *st = NULL;
|
struct stream_ctx *st = NULL;
|
||||||
|
enum transcode_profile profile;
|
||||||
const char *param;
|
const char *param;
|
||||||
const char *param_end;
|
const char *param_end;
|
||||||
|
const char *ctype;
|
||||||
char buf[64];
|
char buf[64];
|
||||||
int64_t offset = 0;
|
int64_t offset = 0;
|
||||||
int64_t end_offset = 0;
|
int64_t end_offset = 0;
|
||||||
int transcode;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
param = httpd_header_find(hreq->in_headers, "Range");
|
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");
|
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);
|
httpd_send_error(hreq, HTTP_INTERNAL, "Cannot stream, unable to determine output format");
|
||||||
if (transcode)
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile != XCODE_NONE)
|
||||||
{
|
{
|
||||||
DPRINTF(E_INFO, L_HTTPD, "Preparing to transcode %s\n", mfi->path);
|
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)
|
if (!st)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
ctype = content_type_from_profile(profile);
|
||||||
|
if (!ctype)
|
||||||
|
goto error;
|
||||||
|
|
||||||
if (!httpd_header_find(hreq->out_headers, "Content-Type"))
|
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
|
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
|
// 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
|
// are decoding because we can only guesstimate the size in this case and
|
||||||
// the error margin is unknown and variable.
|
// the error margin is unknown and variable.
|
||||||
if (!transcode)
|
if (profile == XCODE_NONE)
|
||||||
{
|
{
|
||||||
ret = snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)st->size);
|
ret = snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)st->size);
|
||||||
if ((ret < 0) || (ret >= sizeof(buf)))
|
if ((ret < 0) || (ret >= sizeof(buf)))
|
||||||
@ -1059,7 +1102,7 @@ httpd_stream_file(struct httpd_request *hreq, int id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_POSIX_FADVISE
|
#ifdef HAVE_POSIX_FADVISE
|
||||||
if (!transcode)
|
if (profile == XCODE_NONE)
|
||||||
{
|
{
|
||||||
// Hint the OS
|
// Hint the OS
|
||||||
if ( (ret = posix_fadvise(st->fd, st->start_offset, st->stream_size, POSIX_FADV_WILLNEED)) != 0 ||
|
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 *param;
|
||||||
const char *client_codecs;
|
const char *client_codecs;
|
||||||
const char *tag;
|
const char *tag;
|
||||||
char *last_codectype;
|
|
||||||
size_t len;
|
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 nmeta = 0;
|
||||||
int sort_headers;
|
int sort_headers;
|
||||||
int nsongs;
|
int nsongs;
|
||||||
int transcode;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist);
|
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;
|
nsongs = 0;
|
||||||
last_codectype = NULL;
|
|
||||||
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
|
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
|
||||||
{
|
{
|
||||||
nsongs++;
|
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);
|
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;
|
if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
|
||||||
}
|
len_ms = 3 * 60 * 1000; // just a fallback default
|
||||||
else if (!last_codectype || (strcmp(last_codectype, dbmfi.codectype) != 0))
|
|
||||||
{
|
|
||||||
transcode = transcode_needed(hreq->user_agent, client_codecs, dbmfi.codectype);
|
|
||||||
|
|
||||||
free(last_codectype);
|
transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
|
||||||
last_codectype = strdup(dbmfi.codectype);
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_DAAP, "Failed to encode song metadata\n");
|
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);
|
DPRINTF(E_DBG, L_DAAP, "Done with song list, %d songs\n", nsongs);
|
||||||
|
|
||||||
free(last_codectype);
|
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
|
|
||||||
if (ret == -100)
|
if (ret == -100)
|
||||||
|
@ -36,6 +36,11 @@
|
|||||||
#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;
|
||||||
|
|
||||||
|
132
src/httpd_rsp.c
132
src/httpd_rsp.c
@ -407,28 +407,83 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
return 0;
|
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
|
static int
|
||||||
rsp_reply_playlist(struct httpd_request *hreq)
|
rsp_reply_playlist(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
struct db_media_file_info dbmfi;
|
|
||||||
const char *param;
|
const char *param;
|
||||||
const char *ua;
|
|
||||||
const char *client_codecs;
|
const char *client_codecs;
|
||||||
char **strval;
|
|
||||||
xml_node *xml;
|
xml_node *xml;
|
||||||
xml_node *response;
|
xml_node *response;
|
||||||
xml_node *items;
|
xml_node *items;
|
||||||
xml_node *item;
|
|
||||||
int mode;
|
int mode;
|
||||||
int records;
|
int records;
|
||||||
int transcode;
|
|
||||||
int32_t bitrate;
|
|
||||||
int i;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
memset(&qp, 0, sizeof(struct query_params));
|
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);
|
ret = safe_atoi32(hreq->path_parts[2], &qp.id);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@ -485,69 +540,14 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
|
|
||||||
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results);
|
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 = xml_new_node(response, "items", NULL);
|
||||||
|
do
|
||||||
/* Items block (all items) */
|
|
||||||
while ((ret = db_query_fetch_file(&dbmfi, &qp)) == 0)
|
|
||||||
{
|
{
|
||||||
ua = httpd_header_find(hreq->in_headers, "User-Agent");
|
ret = item_add(items, &qp, hreq->user_agent, client_codecs, mode);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
while (ret == 0);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qp.filter)
|
|
||||||
free(qp.filter);
|
free(qp.filter);
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
@ -38,7 +38,7 @@ setup(struct input_source *source)
|
|||||||
{
|
{
|
||||||
struct transcode_ctx *ctx;
|
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)
|
if (!ctx)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
@ -304,7 +304,7 @@ setup(struct input_source *source)
|
|||||||
free(source->path);
|
free(source->path);
|
||||||
source->path = url;
|
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)
|
if (!ctx)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
@ -404,7 +404,7 @@ download_xcode_setup(struct download_ctx *download)
|
|||||||
if (!xcode->decode_ctx)
|
if (!xcode->decode_ctx)
|
||||||
goto error;
|
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)
|
if (!xcode->encode_ctx)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
@ -311,7 +311,7 @@ encoding_reset(struct media_quality *quality)
|
|||||||
|
|
||||||
profile = quality_to_xcode(&subscription->quality);
|
profile = quality_to_xcode(&subscription->quality);
|
||||||
if (profile != XCODE_UNKNOWN)
|
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
|
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);
|
||||||
|
@ -1153,7 +1153,7 @@ master_session_make(struct media_quality *quality)
|
|||||||
goto error;
|
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);
|
transcode_decode_cleanup(&decode_ctx);
|
||||||
if (!rms->encode_ctx)
|
if (!rms->encode_ctx)
|
||||||
{
|
{
|
||||||
|
@ -2399,7 +2399,7 @@ cast_init(void)
|
|||||||
goto out_tls_deinit;
|
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);
|
transcode_decode_cleanup(&decode_ctx);
|
||||||
if (!cast_encode_ctx)
|
if (!cast_encode_ctx)
|
||||||
{
|
{
|
||||||
|
@ -1888,7 +1888,7 @@ master_session_make(struct media_quality *quality, bool encrypt)
|
|||||||
goto error;
|
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);
|
transcode_decode_cleanup(&decode_ctx);
|
||||||
if (!rms->encode_ctx)
|
if (!rms->encode_ctx)
|
||||||
{
|
{
|
||||||
|
@ -133,7 +133,7 @@ encoder_setup(enum player_format format, struct media_quality *quality)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (format == PLAYER_FORMAT_MP3)
|
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)
|
if (!encode_ctx)
|
||||||
{
|
{
|
||||||
|
203
src/transcode.c
203
src/transcode.c
@ -139,8 +139,8 @@ struct decode_ctx
|
|||||||
struct stream_ctx audio_stream;
|
struct stream_ctx audio_stream;
|
||||||
struct stream_ctx video_stream;
|
struct stream_ctx video_stream;
|
||||||
|
|
||||||
// Duration (used to make wav header)
|
// Source duration in ms as provided by caller
|
||||||
uint32_t duration;
|
uint32_t len_ms;
|
||||||
|
|
||||||
// Data kind (used to determine if ICY metadata is relevant to look for)
|
// Data kind (used to determine if ICY metadata is relevant to look for)
|
||||||
enum data_kind data_kind;
|
enum data_kind data_kind;
|
||||||
@ -186,7 +186,10 @@ struct encode_ctx
|
|||||||
AVPacket *encoded_pkt;
|
AVPacket *encoded_pkt;
|
||||||
|
|
||||||
// How many output bytes we have processed in total
|
// 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
|
// Used to check for ICY metadata changes at certain intervals
|
||||||
uint32_t icy_interval;
|
uint32_t icy_interval;
|
||||||
@ -240,7 +243,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile, str
|
|||||||
settings->with_user_filters = true;
|
settings->with_user_filters = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case XCODE_PCM16_HEADER:
|
case XCODE_WAV:
|
||||||
settings->with_wav_header = true;
|
settings->with_wav_header = true;
|
||||||
settings->with_user_filters = true;
|
settings->with_user_filters = true;
|
||||||
case XCODE_PCM16:
|
case XCODE_PCM16:
|
||||||
@ -435,32 +438,48 @@ add_le32(uint8_t *dst, uint32_t val)
|
|||||||
* header must have size WAV_HEADER_LEN (44 bytes)
|
* header must have size WAV_HEADER_LEN (44 bytes)
|
||||||
*/
|
*/
|
||||||
static void
|
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;
|
uint32_t wav_size = bytes_total - WAV_HEADER_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;
|
|
||||||
|
|
||||||
memcpy(header, "RIFF", 4);
|
memcpy(header, "RIFF", 4);
|
||||||
add_le32(header + 4, 36 + wav_len);
|
add_le32(header + 4, 36 + wav_size);
|
||||||
memcpy(header + 8, "WAVEfmt ", 8);
|
memcpy(header + 8, "WAVEfmt ", 8);
|
||||||
add_le32(header + 16, 16);
|
add_le32(header + 16, 16);
|
||||||
add_le16(header + 20, 1);
|
add_le16(header + 20, 1);
|
||||||
add_le16(header + 22, channels); /* channels */
|
add_le16(header + 22, channels); /* channels */
|
||||||
add_le32(header + 24, sample_rate); /* samplerate */
|
add_le32(header + 24, sample_rate); /* samplerate */
|
||||||
add_le32(header + 28, sample_rate * channels * bps); /* byte rate */
|
add_le32(header + 28, sample_rate * channels * bytes_per_sample); /* byte rate */
|
||||||
add_le16(header + 32, channels * bps); /* block align */
|
add_le16(header + 32, channels * bytes_per_sample); /* block align */
|
||||||
add_le16(header + 34, 8 * bps); /* bits per sample */
|
add_le16(header + 34, 8 * bytes_per_sample); /* bits per sample */
|
||||||
memcpy(header + 36, "data", 4);
|
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
|
* 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;
|
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->stream = avformat_new_stream(ctx->ofmt_ctx, NULL));
|
||||||
CHECK_NULL(L_XCODE, s->codec = avcodec_alloc_context3(encoder));
|
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);
|
ret = create_filtergraph(&ctx->audio_stream, filters, ARRAY_SIZE(filters), &src_ctx->audio_stream);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto out_fail;
|
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)
|
if (ctx->settings.encode_video)
|
||||||
@ -1563,7 +1589,7 @@ close_filters(struct encode_ctx *ctx)
|
|||||||
/* Setup */
|
/* Setup */
|
||||||
|
|
||||||
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 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;
|
struct decode_ctx *ctx;
|
||||||
int ret;
|
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->decoded_frame = av_frame_alloc());
|
||||||
CHECK_NULL(L_XCODE, ctx->packet = av_packet_alloc());
|
CHECK_NULL(L_XCODE, ctx->packet = av_packet_alloc());
|
||||||
|
|
||||||
ctx->duration = song_length;
|
ctx->len_ms = len_ms;
|
||||||
ctx->data_kind = data_kind;
|
ctx->data_kind = data_kind;
|
||||||
|
|
||||||
ret = init_settings(&ctx->settings, profile, quality);
|
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 *
|
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;
|
struct encode_ctx *ctx;
|
||||||
int src_bps;
|
int src_bytes_per_sample;
|
||||||
int dst_bps;
|
int dst_bytes_per_sample;
|
||||||
int channels;
|
int channels;
|
||||||
|
|
||||||
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct encode_ctx)));
|
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
|
// Caller did not specify a sample format -> determine from source
|
||||||
if (!ctx->settings.sample_format && ctx->settings.encode_audio)
|
if (!ctx->settings.sample_format && ctx->settings.encode_audio)
|
||||||
{
|
{
|
||||||
src_bps = av_get_bytes_per_sample(src_ctx->audio_stream.codec->sample_fmt);
|
src_bytes_per_sample = av_get_bytes_per_sample(src_ctx->audio_stream.codec->sample_fmt);
|
||||||
if (src_bps == 4)
|
if (src_bytes_per_sample == 4)
|
||||||
{
|
{
|
||||||
ctx->settings.sample_format = AV_SAMPLE_FMT_S32;
|
ctx->settings.sample_format = AV_SAMPLE_FMT_S32;
|
||||||
ctx->settings.audio_codec = AV_CODEC_ID_PCM_S32LE;
|
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;
|
channels = ctx->settings.channels;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (ctx->settings.with_wav_header)
|
dst_bytes_per_sample = av_get_bytes_per_sample(ctx->settings.sample_format);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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)
|
if (ctx->settings.with_icy && src_ctx->data_kind == DATA_KIND_HTTP)
|
||||||
{
|
ctx->icy_interval = METADATA_ICY_INTERVAL * channels * dst_bytes_per_sample * ctx->settings.sample_rate;
|
||||||
dst_bps = av_get_bytes_per_sample(ctx->settings.sample_format);
|
|
||||||
ctx->icy_interval = METADATA_ICY_INTERVAL * channels * dst_bps * ctx->settings.sample_rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (open_output(ctx, src_ctx) < 0)
|
if (open_output(ctx, src_ctx) < 0)
|
||||||
goto fail_free;
|
goto fail_free;
|
||||||
@ -1693,20 +1716,20 @@ transcode_encode_setup(enum transcode_profile profile, struct media_quality *qua
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 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;
|
struct transcode_ctx *ctx;
|
||||||
|
|
||||||
CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct transcode_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)
|
if (!ctx->decode_ctx)
|
||||||
{
|
{
|
||||||
free(ctx);
|
free(ctx);
|
||||||
return NULL;
|
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)
|
if (!ctx->encode_ctx)
|
||||||
{
|
{
|
||||||
transcode_decode_cleanup(&ctx->decode_ctx);
|
transcode_decode_cleanup(&ctx->decode_ctx);
|
||||||
@ -1779,49 +1802,39 @@ transcode_decode_setup_raw(enum transcode_profile profile, struct media_quality
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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, char *file_codectype)
|
||||||
{
|
{
|
||||||
char *codectype;
|
char *codectype;
|
||||||
cfg_t *lib;
|
cfg_t *lib;
|
||||||
int size;
|
bool force_xcode;
|
||||||
|
int count;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (!file_codectype)
|
if (!file_codectype)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_XCODE, "Can't determine decode status, codec type is unknown\n");
|
return XCODE_UNKNOWN;
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lib = cfg_getsec(cfg, "library");
|
lib = cfg_getsec(cfg, "library");
|
||||||
|
|
||||||
size = cfg_size(lib, "no_decode");
|
count = cfg_size(lib, "no_decode");
|
||||||
if (size > 0)
|
for (i = 0; i < count; i++)
|
||||||
{
|
|
||||||
for (i = 0; i < size; i++)
|
|
||||||
{
|
{
|
||||||
codectype = cfg_getnstr(lib, "no_decode", i);
|
codectype = cfg_getnstr(lib, "no_decode", i);
|
||||||
|
|
||||||
if (strcmp(file_codectype, codectype) == 0)
|
if (strcmp(file_codectype, codectype) == 0)
|
||||||
return 0; // Codectype is in no_decode
|
return XCODE_NONE; // Codectype is in no_decode
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size = cfg_size(lib, "force_decode");
|
count = cfg_size(lib, "force_decode");
|
||||||
if (size > 0)
|
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)
|
if (strcmp(file_codectype, codectype) == 0)
|
||||||
return 1; // Codectype is in force_decode
|
force_xcode = true; // Codectype is in force_decode
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client_codecs)
|
if (!client_codecs && user_agent)
|
||||||
{
|
|
||||||
if (user_agent)
|
|
||||||
{
|
{
|
||||||
if (strncmp(user_agent, "iTunes", strlen("iTunes")) == 0)
|
if (strncmp(user_agent, "iTunes", strlen("iTunes")) == 0)
|
||||||
client_codecs = itunes_codecs;
|
client_codecs = itunes_codecs;
|
||||||
@ -1840,26 +1853,22 @@ transcode_needed(const char *user_agent, const char *client_codecs, char *file_c
|
|||||||
* HTTP implementation doesn't honour Connection: close.
|
* HTTP implementation doesn't honour Connection: close.
|
||||||
* At least, that's why mt-daapd didn't do it.
|
* At least, that's why mt-daapd didn't do it.
|
||||||
*/
|
*/
|
||||||
return 0;
|
return XCODE_NONE;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!client_codecs)
|
||||||
|
client_codecs = default_codecs;
|
||||||
else
|
else
|
||||||
DPRINTF(E_SPAM, L_XCODE, "Client advertises codecs: %s\n", client_codecs);
|
DPRINTF(E_SPAM, L_XCODE, "Client advertises codecs: %s\n", client_codecs);
|
||||||
|
|
||||||
if (!client_codecs)
|
if (!force_xcode && strstr(client_codecs, file_codectype))
|
||||||
{
|
return XCODE_NONE;
|
||||||
DPRINTF(E_SPAM, L_XCODE, "Could not identify client, using default codectype set\n");
|
else if (strstr(client_codecs, "mpeg"))
|
||||||
client_codecs = default_codecs;
|
return XCODE_MP3;
|
||||||
}
|
else if (strstr(client_codecs, "wav"))
|
||||||
|
return XCODE_WAV;
|
||||||
if (strstr(client_codecs, file_codectype))
|
else
|
||||||
{
|
return XCODE_UNKNOWN;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2010,9 +2019,9 @@ transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int
|
|||||||
|
|
||||||
evbuffer_add_buffer(evbuf, ctx->encode_ctx->obuf);
|
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)
|
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))
|
if ((ret < 0) && (ret != AVERROR_EOF))
|
||||||
return ret;
|
return ret;
|
||||||
@ -2218,6 +2227,11 @@ transcode_encode_query(struct encode_ctx *ctx, const char *query)
|
|||||||
if (ctx->audio_stream.stream)
|
if (ctx->audio_stream.stream)
|
||||||
return ctx->audio_stream.stream->codecpar->frame_size;
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -2244,3 +2258,38 @@ transcode_metadata(struct transcode_ctx *ctx, int *changed)
|
|||||||
return m;
|
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
|
enum transcode_profile
|
||||||
{
|
{
|
||||||
// Used for errors
|
// 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)
|
// Decodes the best audio stream into PCM16 or PCM24, no resampling (does not add wav header)
|
||||||
XCODE_PCM_NATIVE,
|
XCODE_PCM_NATIVE,
|
||||||
// Decodes/resamples the best audio stream into PCM16 (with wav header)
|
// 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)
|
// Decodes/resamples the best audio stream into PCM16/24/32 (no wav headers)
|
||||||
XCODE_PCM16,
|
XCODE_PCM16,
|
||||||
XCODE_PCM24,
|
XCODE_PCM24,
|
||||||
@ -60,20 +62,30 @@ struct transcode_evbuf_io
|
|||||||
void *seekfn_arg;
|
void *seekfn_arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct transcode_metadata_string
|
||||||
|
{
|
||||||
|
char *type;
|
||||||
|
char *codectype;
|
||||||
|
char *description;
|
||||||
|
char file_size[64];
|
||||||
|
char bitrate[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// 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 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 *
|
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 *
|
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 *
|
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);
|
||||||
|
|
||||||
int
|
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, char *file_codectype);
|
||||||
|
|
||||||
// Cleaning up
|
// Cleaning up
|
||||||
@ -170,4 +182,10 @@ 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
|
||||||
|
// 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__ */
|
#endif /* !__TRANSCODE_H__ */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user