[artwork/transcode] Adjust transcode.c so it can take care of artwork
rescaling, meaning we can do without parallel ffmpeg interfaces. This also moves artwork rescaling from libswscale to libavfilter, which seems to fix a problem with PNG rescaling.
This commit is contained in:
parent
d933e171d4
commit
e7f888645f
559
src/artwork.c
559
src/artwork.c
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||
* Copyright (C) 2015-2017 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||
* Copyright (C) 2010-2011 Julien BLACHE <jb@jblache.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -30,27 +30,20 @@
|
|||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
|
||||
#include "db.h"
|
||||
#include "misc.h"
|
||||
#include "logger.h"
|
||||
#include "conffile.h"
|
||||
#include "cache.h"
|
||||
#include "http.h"
|
||||
#include "transcode.h"
|
||||
|
||||
#include "avio_evbuffer.h"
|
||||
#include "artwork.h"
|
||||
|
||||
#ifdef HAVE_SPOTIFY_H
|
||||
# include "spotify.h"
|
||||
#endif
|
||||
|
||||
#include "ffmpeg-compat.h"
|
||||
|
||||
/* This artwork module will look for artwork by consulting a set of sources one
|
||||
* at a time. A source is for instance the local library, the cache or a cover
|
||||
* art database. For each source there is a handler function, which will do the
|
||||
|
@ -278,43 +271,42 @@ artwork_read(struct evbuffer *evbuf, char *path)
|
|||
/* Will the source image fit inside requested size. If not, what size should it
|
||||
* be rescaled to to maintain aspect ratio.
|
||||
*
|
||||
* @in src Image source
|
||||
* @in max_w Requested width
|
||||
* @in max_h Requested height
|
||||
* @out target_w Rescaled width
|
||||
* @out target_h Rescaled height
|
||||
* @return 0 no rescaling needed, 1 rescaling needed
|
||||
* @in width Actual width
|
||||
* @in height Actual height
|
||||
* @in max_w Requested width
|
||||
* @in max_h Requested height
|
||||
* @return -1 no rescaling needed, otherwise 0
|
||||
*/
|
||||
static int
|
||||
rescale_needed(AVCodecContext *src, int max_w, int max_h, int *target_w, int *target_h)
|
||||
rescale_calculate(int *target_w, int *target_h, int width, int height, int max_w, int max_h)
|
||||
{
|
||||
DPRINTF(E_DBG, L_ART, "Original image dimensions: w %d h %d\n", src->width, src->height);
|
||||
DPRINTF(E_DBG, L_ART, "Original image dimensions: w %d h %d\n", width, height);
|
||||
|
||||
*target_w = src->width;
|
||||
*target_h = src->height;
|
||||
*target_w = width;
|
||||
*target_h = height;
|
||||
|
||||
if ((src->width == 0) || (src->height == 0)) /* Unknown source size, can't rescale */
|
||||
return 0;
|
||||
if ((width == 0) || (height == 0)) /* Unknown source size, can't rescale */
|
||||
return -1;
|
||||
|
||||
if ((max_w <= 0) || (max_h <= 0)) /* No valid target dimensions, use original */
|
||||
return 0;
|
||||
return -1;
|
||||
|
||||
if ((src->width <= max_w) && (src->height <= max_h)) /* Smaller than target */
|
||||
return 0;
|
||||
if ((width <= max_w) && (height <= max_h)) /* Smaller than target */
|
||||
return -1;
|
||||
|
||||
if (src->width * max_h > src->height * max_w) /* Wider aspect ratio than target */
|
||||
if (width * max_h > height * max_w) /* Wider aspect ratio than target */
|
||||
{
|
||||
*target_w = max_w;
|
||||
*target_h = (double)max_w * ((double)src->height / (double)src->width);
|
||||
*target_h = (double)max_w * ((double)height / (double)width);
|
||||
}
|
||||
else /* Taller or equal aspect ratio */
|
||||
{
|
||||
*target_w = (double)max_h * ((double)src->width / (double)src->height);
|
||||
*target_w = (double)max_h * ((double)width / (double)height);
|
||||
*target_h = max_h;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Raw destination width %d height %d\n", *target_w, *target_h);
|
||||
|
||||
if ((*target_h > max_h) && (max_h > 0))
|
||||
*target_h = max_h;
|
||||
|
||||
|
@ -324,326 +316,9 @@ rescale_needed(AVCodecContext *src, int max_w, int max_h, int *target_w, int *ta
|
|||
if ((*target_w > max_w) && (max_w > 0))
|
||||
*target_w = max_w - (max_w % 2);
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Destination width %d height %d\n", *target_w, *target_h);
|
||||
DPRINTF(E_DBG, L_ART, "Rescale required, destination width %d height %d\n", *target_w, *target_h);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Rescale an image
|
||||
*
|
||||
* @out evbuf Rescaled image data
|
||||
* @in src_ctx Image source
|
||||
* @in s Index of stream containing image
|
||||
* @in out_w Rescaled width
|
||||
* @in out_h Rescaled height
|
||||
* @return ART_FMT_* on success, -1 on error
|
||||
*/
|
||||
static int
|
||||
artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out_w, int out_h)
|
||||
{
|
||||
uint8_t *buf;
|
||||
|
||||
AVCodecContext *src;
|
||||
|
||||
AVFormatContext *dst_ctx;
|
||||
AVCodecContext *dst;
|
||||
AVOutputFormat *dst_fmt;
|
||||
AVStream *dst_st;
|
||||
|
||||
AVCodec *img_decoder;
|
||||
AVCodec *img_encoder;
|
||||
|
||||
AVFrame *i_frame;
|
||||
AVFrame *o_frame;
|
||||
|
||||
struct SwsContext *swsctx;
|
||||
|
||||
AVPacket pkt;
|
||||
int have_frame;
|
||||
int ret;
|
||||
|
||||
src = src_ctx->streams[s]->codec;
|
||||
|
||||
// Avoids threading issue in both ffmpeg and libav that prevents decoding embedded png's
|
||||
src->thread_count = 1;
|
||||
|
||||
img_decoder = avcodec_find_decoder(src->codec_id);
|
||||
if (!img_decoder)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "No suitable decoder found for artwork %s\n", src_ctx->filename);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = avcodec_open2(src, img_decoder, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not open codec for decoding: %s\n", strerror(AVUNERROR(ret)));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (src->pix_fmt < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Unknown pixel format for artwork %s\n", src_ctx->filename);
|
||||
|
||||
ret = -1;
|
||||
goto out_close_src;
|
||||
}
|
||||
|
||||
/* Set up output */
|
||||
dst_fmt = av_guess_format("image2", NULL, NULL);
|
||||
if (!dst_fmt)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "ffmpeg image2 muxer not available\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_close_src;
|
||||
}
|
||||
|
||||
dst_fmt->video_codec = AV_CODEC_ID_NONE;
|
||||
|
||||
/* Try to keep same codec if possible */
|
||||
if (src->codec_id == AV_CODEC_ID_PNG)
|
||||
dst_fmt->video_codec = AV_CODEC_ID_PNG;
|
||||
else if (src->codec_id == AV_CODEC_ID_MJPEG)
|
||||
dst_fmt->video_codec = AV_CODEC_ID_MJPEG;
|
||||
|
||||
/* If not possible, select new codec */
|
||||
if (dst_fmt->video_codec == AV_CODEC_ID_NONE)
|
||||
{
|
||||
dst_fmt->video_codec = AV_CODEC_ID_PNG;
|
||||
}
|
||||
|
||||
img_encoder = avcodec_find_encoder(dst_fmt->video_codec);
|
||||
if (!img_encoder)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "No suitable encoder found for codec ID %d\n", dst_fmt->video_codec);
|
||||
|
||||
ret = -1;
|
||||
goto out_close_src;
|
||||
}
|
||||
|
||||
dst_ctx = avformat_alloc_context();
|
||||
if (!dst_ctx)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Out of memory for format context\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_close_src;
|
||||
}
|
||||
|
||||
dst_ctx->oformat = dst_fmt;
|
||||
|
||||
dst_fmt->flags &= ~AVFMT_NOFILE;
|
||||
|
||||
dst_st = avformat_new_stream(dst_ctx, NULL);
|
||||
if (!dst_st)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Out of memory for new output stream\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_free_dst_ctx;
|
||||
}
|
||||
|
||||
dst = dst_st->codec;
|
||||
|
||||
avcodec_get_context_defaults3(dst, NULL);
|
||||
|
||||
if (dst_fmt->flags & AVFMT_GLOBALHEADER)
|
||||
dst->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
dst->codec_id = dst_fmt->video_codec;
|
||||
dst->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
|
||||
dst->pix_fmt = avcodec_default_get_format(dst, img_encoder->pix_fmts);
|
||||
if (dst->pix_fmt < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not determine best pixel format\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_free_dst_ctx;
|
||||
}
|
||||
|
||||
dst->time_base.num = 1;
|
||||
dst->time_base.den = 25;
|
||||
|
||||
dst->width = out_w;
|
||||
dst->height = out_h;
|
||||
|
||||
/* Open encoder */
|
||||
ret = avcodec_open2(dst, img_encoder, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not open codec for encoding: %s\n", strerror(AVUNERROR(ret)));
|
||||
|
||||
ret = -1;
|
||||
goto out_free_dst_ctx;
|
||||
}
|
||||
|
||||
i_frame = av_frame_alloc();
|
||||
o_frame = av_frame_alloc();
|
||||
if (!i_frame || !o_frame)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not allocate input/output frame\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_free_frames;
|
||||
}
|
||||
|
||||
ret = av_image_get_buffer_size(dst->pix_fmt, src->width, src->height, 1);
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Artwork buffer size: %d\n", ret);
|
||||
|
||||
buf = (uint8_t *)av_malloc(ret);
|
||||
if (!buf)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Out of memory for artwork buffer\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_free_frames;
|
||||
}
|
||||
|
||||
#if HAVE_DECL_AV_IMAGE_FILL_ARRAYS
|
||||
av_image_fill_arrays(o_frame->data, o_frame->linesize, buf, dst->pix_fmt, src->width, src->height, 1);
|
||||
#else
|
||||
avpicture_fill((AVPicture *)o_frame, buf, dst->pix_fmt, src->width, src->height);
|
||||
#endif
|
||||
o_frame->height = dst->height;
|
||||
o_frame->width = dst->width;
|
||||
o_frame->format = dst->pix_fmt;
|
||||
|
||||
swsctx = sws_getContext(src->width, src->height, src->pix_fmt,
|
||||
dst->width, dst->height, dst->pix_fmt,
|
||||
SWS_BICUBIC, NULL, NULL, NULL);
|
||||
if (!swsctx)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not get SWS context\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_free_buf;
|
||||
}
|
||||
|
||||
/* Get frame */
|
||||
have_frame = 0;
|
||||
while (av_read_frame(src_ctx, &pkt) == 0)
|
||||
{
|
||||
if (pkt.stream_index != s)
|
||||
{
|
||||
av_packet_unref(&pkt);
|
||||
continue;
|
||||
}
|
||||
|
||||
avcodec_decode_video2(src, i_frame, &have_frame, &pkt);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!have_frame)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not decode artwork\n");
|
||||
|
||||
av_packet_unref(&pkt);
|
||||
sws_freeContext(swsctx);
|
||||
|
||||
ret = -1;
|
||||
goto out_free_buf;
|
||||
}
|
||||
|
||||
/* Scale */
|
||||
sws_scale(swsctx, (const uint8_t * const *)i_frame->data, i_frame->linesize, 0, src->height, o_frame->data, o_frame->linesize);
|
||||
|
||||
sws_freeContext(swsctx);
|
||||
av_packet_unref(&pkt);
|
||||
|
||||
/* Open output file */
|
||||
dst_ctx->pb = avio_output_evbuffer_open(evbuf);
|
||||
if (!dst_ctx->pb)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not open artwork destination buffer\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_free_buf;
|
||||
}
|
||||
|
||||
/* Encode frame */
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = NULL;
|
||||
pkt.size = 0;
|
||||
|
||||
ret = avcodec_encode_video2(dst, &pkt, o_frame, &have_frame);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not encode artwork\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_fclose_dst;
|
||||
}
|
||||
|
||||
ret = avformat_write_header(dst_ctx, NULL);
|
||||
if (ret != 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not write artwork header: %s\n", strerror(AVUNERROR(ret)));
|
||||
|
||||
ret = -1;
|
||||
goto out_fclose_dst;
|
||||
}
|
||||
|
||||
ret = av_interleaved_write_frame(dst_ctx, &pkt);
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Error writing artwork\n");
|
||||
|
||||
ret = -1;
|
||||
goto out_fclose_dst;
|
||||
}
|
||||
|
||||
ret = av_write_trailer(dst_ctx);
|
||||
if (ret != 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Could not write artwork trailer: %s\n", strerror(AVUNERROR(ret)));
|
||||
|
||||
ret = -1;
|
||||
goto out_fclose_dst;
|
||||
}
|
||||
|
||||
switch (dst_fmt->video_codec)
|
||||
{
|
||||
case AV_CODEC_ID_PNG:
|
||||
ret = ART_FMT_PNG;
|
||||
break;
|
||||
|
||||
case AV_CODEC_ID_MJPEG:
|
||||
ret = ART_FMT_JPEG;
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(E_LOG, L_ART, "Unhandled rescale output format\n");
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
out_fclose_dst:
|
||||
avio_evbuffer_close(dst_ctx->pb);
|
||||
av_packet_unref(&pkt);
|
||||
|
||||
out_free_buf:
|
||||
av_free(buf);
|
||||
|
||||
out_free_frames:
|
||||
if (i_frame)
|
||||
av_frame_free(&i_frame);
|
||||
if (o_frame)
|
||||
av_frame_free(&o_frame);
|
||||
avcodec_close(dst);
|
||||
|
||||
out_free_dst_ctx:
|
||||
avformat_free_context(dst_ctx);
|
||||
|
||||
out_close_src:
|
||||
avcodec_close(src);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get an artwork file from the filesystem. Will rescale if needed.
|
||||
|
@ -657,8 +332,11 @@ artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out
|
|||
static int
|
||||
artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
|
||||
{
|
||||
AVFormatContext *src_ctx;
|
||||
int s;
|
||||
struct decode_ctx *xcode_decode;
|
||||
struct encode_ctx *xcode_encode;
|
||||
void *frame;
|
||||
int width;
|
||||
int height;
|
||||
int target_w;
|
||||
int target_h;
|
||||
int format_ok;
|
||||
|
@ -666,71 +344,71 @@ artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h)
|
|||
|
||||
DPRINTF(E_SPAM, L_ART, "Getting artwork (max destination width %d height %d)\n", max_w, max_h);
|
||||
|
||||
src_ctx = NULL;
|
||||
|
||||
ret = avformat_open_input(&src_ctx, path, NULL, NULL);
|
||||
if (ret < 0)
|
||||
xcode_decode = transcode_decode_setup(XCODE_PNG, DATA_KIND_FILE, path, 0); // Good for XCODE_JPEG too
|
||||
if (!xcode_decode)
|
||||
{
|
||||
DPRINTF(E_WARN, L_ART, "Cannot open artwork file '%s': %s\n", path, strerror(AVUNERROR(ret)));
|
||||
|
||||
return ART_E_ERROR;
|
||||
DPRINTF(E_DBG, L_ART, "No artwork found in '%s'\n", path);
|
||||
return ART_E_NONE;
|
||||
}
|
||||
|
||||
ret = avformat_find_stream_info(src_ctx, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_WARN, L_ART, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret)));
|
||||
|
||||
avformat_close_input(&src_ctx);
|
||||
return ART_E_ERROR;
|
||||
}
|
||||
|
||||
format_ok = 0;
|
||||
for (s = 0; s < src_ctx->nb_streams; s++)
|
||||
{
|
||||
if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_PNG)
|
||||
{
|
||||
format_ok = ART_FMT_PNG;
|
||||
break;
|
||||
}
|
||||
else if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_MJPEG)
|
||||
{
|
||||
format_ok = ART_FMT_JPEG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s == src_ctx->nb_streams)
|
||||
if (transcode_decode_query(xcode_decode, "is_jpeg"))
|
||||
format_ok = ART_FMT_JPEG;
|
||||
else if (transcode_decode_query(xcode_decode, "is_png"))
|
||||
format_ok = ART_FMT_PNG;
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_ART, "Artwork file '%s' not a PNG or JPEG file\n", path);
|
||||
|
||||
avformat_close_input(&src_ctx);
|
||||
return ART_E_ERROR;
|
||||
goto fail_free_decode;
|
||||
}
|
||||
|
||||
ret = rescale_needed(src_ctx->streams[s]->codec, max_w, max_h, &target_w, &target_h);
|
||||
width = transcode_decode_query(xcode_decode, "width");
|
||||
height = transcode_decode_query(xcode_decode, "height");
|
||||
|
||||
/* Fastpath */
|
||||
if (!ret && format_ok)
|
||||
ret = rescale_calculate(&target_w, &target_h, width, height, max_w, max_h);
|
||||
if (ret < 0)
|
||||
{
|
||||
ret = artwork_read(evbuf, path);
|
||||
if (ret == 0)
|
||||
ret = format_ok;
|
||||
}
|
||||
else
|
||||
ret = artwork_rescale(evbuf, src_ctx, s, target_w, target_h);
|
||||
// No rescaling required, just read the raw file into the evbuf
|
||||
if (artwork_read(evbuf, path) != 0)
|
||||
goto fail_free_decode;
|
||||
|
||||
avformat_close_input(&src_ctx);
|
||||
transcode_decode_cleanup(&xcode_decode);
|
||||
return format_ok;
|
||||
}
|
||||
|
||||
if (format_ok == ART_FMT_JPEG)
|
||||
xcode_encode = transcode_encode_setup(XCODE_JPEG, xcode_decode, NULL, target_w, target_h);
|
||||
else
|
||||
xcode_encode = transcode_encode_setup(XCODE_PNG, xcode_decode, NULL, target_w, target_h);
|
||||
|
||||
if (!xcode_encode)
|
||||
{
|
||||
DPRINTF(E_WARN, L_ART, "Cannot open artwork file for rescaling '%s'\n", path);
|
||||
goto fail_free_decode;
|
||||
}
|
||||
|
||||
// We don't use transcode() because we just want to process one frame
|
||||
ret = transcode_decode(&frame, xcode_decode);
|
||||
if (ret < 0)
|
||||
goto fail_free_encode;
|
||||
|
||||
ret = transcode_encode(evbuf, xcode_encode, frame, 1);
|
||||
|
||||
transcode_encode_cleanup(&xcode_encode);
|
||||
transcode_decode_cleanup(&xcode_decode);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
if (evbuffer_get_length(evbuf) > 0)
|
||||
evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
|
||||
|
||||
ret = ART_E_ERROR;
|
||||
evbuffer_drain(evbuf, evbuffer_get_length(evbuf));
|
||||
return ART_E_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return format_ok;
|
||||
|
||||
fail_free_encode:
|
||||
transcode_encode_cleanup(&xcode_encode);
|
||||
fail_free_decode:
|
||||
transcode_decode_cleanup(&xcode_decode);
|
||||
return ART_E_ERROR;
|
||||
}
|
||||
|
||||
/* Looks for an artwork file in a directory. Will rescale if needed.
|
||||
|
@ -949,92 +627,11 @@ source_item_cache_get(struct artwork_ctx *ctx)
|
|||
static int
|
||||
source_item_embedded_get(struct artwork_ctx *ctx)
|
||||
{
|
||||
AVFormatContext *src_ctx;
|
||||
AVStream *src_st;
|
||||
int s;
|
||||
int target_w;
|
||||
int target_h;
|
||||
int format;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_SPAM, L_ART, "Trying embedded artwork in %s\n", ctx->dbmfi->path);
|
||||
|
||||
src_ctx = NULL;
|
||||
snprintf(ctx->path, sizeof(ctx->path), "%s", ctx->dbmfi->path);
|
||||
|
||||
ret = avformat_open_input(&src_ctx, ctx->dbmfi->path, NULL, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_WARN, L_ART, "Cannot open media file '%s': %s\n", ctx->dbmfi->path, strerror(AVUNERROR(ret)));
|
||||
return ART_E_ERROR;
|
||||
}
|
||||
|
||||
ret = avformat_find_stream_info(src_ctx, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_WARN, L_ART, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret)));
|
||||
avformat_close_input(&src_ctx);
|
||||
return ART_E_ERROR;
|
||||
}
|
||||
|
||||
format = 0;
|
||||
for (s = 0; s < src_ctx->nb_streams; s++)
|
||||
{
|
||||
if (src_ctx->streams[s]->disposition & AV_DISPOSITION_ATTACHED_PIC)
|
||||
{
|
||||
if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_PNG)
|
||||
{
|
||||
format = ART_FMT_PNG;
|
||||
break;
|
||||
}
|
||||
else if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_MJPEG)
|
||||
{
|
||||
format = ART_FMT_JPEG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s == src_ctx->nb_streams)
|
||||
{
|
||||
avformat_close_input(&src_ctx);
|
||||
return ART_E_NONE;
|
||||
}
|
||||
|
||||
src_st = src_ctx->streams[s];
|
||||
|
||||
ret = rescale_needed(src_st->codec, ctx->max_w, ctx->max_h, &target_w, &target_h);
|
||||
|
||||
/* Fastpath */
|
||||
if (!ret && format)
|
||||
{
|
||||
DPRINTF(E_SPAM, L_ART, "Artwork not too large, using original image\n");
|
||||
|
||||
ret = evbuffer_add(ctx->evbuf, src_st->attached_pic.data, src_st->attached_pic.size);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_LOG, L_ART, "Could not add embedded image to event buffer\n");
|
||||
else
|
||||
ret = format;
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_SPAM, L_ART, "Artwork too large, rescaling image\n");
|
||||
|
||||
ret = artwork_rescale(ctx->evbuf, src_ctx, s, target_w, target_h);
|
||||
}
|
||||
|
||||
avformat_close_input(&src_ctx);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
if (evbuffer_get_length(ctx->evbuf) > 0)
|
||||
evbuffer_drain(ctx->evbuf, evbuffer_get_length(ctx->evbuf));
|
||||
|
||||
ret = ART_E_ERROR;
|
||||
}
|
||||
else
|
||||
snprintf(ctx->path, sizeof(ctx->path), "%s", ctx->dbmfi->path);
|
||||
|
||||
return ret;
|
||||
return artwork_get(ctx->evbuf, ctx->path, ctx->max_w, ctx->max_h);
|
||||
}
|
||||
|
||||
/* Looks for basename(in_path).{png,jpg}, so if in_path is /foo/bar.mp3 it
|
||||
|
|
|
@ -309,11 +309,10 @@ stream_chunk_xcode_cb(int fd, short event, void *arg)
|
|||
struct timeval tv;
|
||||
int xcoded;
|
||||
int ret;
|
||||
int dummy;
|
||||
|
||||
st = (struct stream_ctx *)arg;
|
||||
|
||||
xcoded = transcode(st->evbuf, STREAM_CHUNK_SIZE, st->xcode, &dummy);
|
||||
xcoded = transcode(st->evbuf, NULL, st->xcode, STREAM_CHUNK_SIZE);
|
||||
if (xcoded <= 0)
|
||||
{
|
||||
if (xcoded == 0)
|
||||
|
|
|
@ -123,7 +123,7 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
|
|||
{
|
||||
struct streaming_session *session;
|
||||
struct evbuffer *evbuf;
|
||||
struct decoded_frame *decoded;
|
||||
void *frame;
|
||||
uint8_t *buf;
|
||||
int len;
|
||||
int ret;
|
||||
|
@ -138,15 +138,15 @@ streaming_send_cb(evutil_socket_t fd, short event, void *arg)
|
|||
if (!streaming_sessions)
|
||||
return;
|
||||
|
||||
decoded = transcode_raw2frame(streaming_rawbuf, STREAMING_RAWBUF_SIZE);
|
||||
if (!decoded)
|
||||
frame = transcode_frame_new(XCODE_MP3, streaming_rawbuf, STREAMING_RAWBUF_SIZE);
|
||||
if (!frame)
|
||||
{
|
||||
DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = transcode_encode(streaming_encoded_data, decoded, streaming_encode_ctx);
|
||||
transcode_decoded_free(decoded);
|
||||
ret = transcode_encode(streaming_encoded_data, streaming_encode_ctx, frame, 0);
|
||||
transcode_frame_free(frame);
|
||||
if (ret < 0)
|
||||
return;
|
||||
}
|
||||
|
@ -288,7 +288,7 @@ int
|
|||
streaming_init(void)
|
||||
{
|
||||
struct decode_ctx *decode_ctx;
|
||||
struct decoded_frame *decoded;
|
||||
void *frame;
|
||||
int remaining;
|
||||
int ret;
|
||||
|
||||
|
@ -299,7 +299,7 @@ streaming_init(void)
|
|||
return -1;
|
||||
}
|
||||
|
||||
streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL);
|
||||
streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL, 0, 0);
|
||||
transcode_decode_cleanup(&decode_ctx);
|
||||
if (!streaming_encode_ctx)
|
||||
{
|
||||
|
@ -345,15 +345,15 @@ streaming_init(void)
|
|||
remaining = STREAMING_SILENCE_INTERVAL * STOB(44100);
|
||||
while (remaining > STREAMING_RAWBUF_SIZE)
|
||||
{
|
||||
decoded = transcode_raw2frame(streaming_rawbuf, STREAMING_RAWBUF_SIZE);
|
||||
if (!decoded)
|
||||
frame = transcode_frame_new(XCODE_MP3, streaming_rawbuf, STREAMING_RAWBUF_SIZE);
|
||||
if (!frame)
|
||||
{
|
||||
DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n");
|
||||
goto silence_fail;
|
||||
}
|
||||
|
||||
ret = transcode_encode(streaming_encoded_data, decoded, streaming_encode_ctx);
|
||||
transcode_decoded_free(decoded);
|
||||
ret = transcode_encode(streaming_encoded_data, streaming_encode_ctx, frame, 0);
|
||||
transcode_frame_free(frame);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_STREAMING, "Could not encode silence buffer\n");
|
||||
|
|
|
@ -70,7 +70,7 @@ start(struct player_source *ps)
|
|||
{
|
||||
// We set "wanted" to 1 because the read size doesn't matter to us
|
||||
// TODO optimize?
|
||||
ret = transcode(evbuf, 1, ps->input_ctx, &icy_timer);
|
||||
ret = transcode(evbuf, &icy_timer, ps->input_ctx, 1);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
|
|
|
@ -179,9 +179,9 @@ logger_ffmpeg(void *ptr, int level, const char *fmt, va_list ap)
|
|||
else if (level <= AV_LOG_WARNING)
|
||||
severity = E_WARN;
|
||||
else if (level <= AV_LOG_VERBOSE)
|
||||
severity = E_INFO;
|
||||
else if (level <= AV_LOG_DEBUG)
|
||||
severity = E_DBG;
|
||||
else if (level <= AV_LOG_DEBUG)
|
||||
severity = E_SPAM;
|
||||
else
|
||||
severity = E_SPAM;
|
||||
|
||||
|
|
292
src/transcode.c
292
src/transcode.c
|
@ -64,6 +64,9 @@ struct settings_ctx
|
|||
bool encode_video;
|
||||
bool encode_audio;
|
||||
|
||||
// Silence some log messages
|
||||
bool silent;
|
||||
|
||||
// Output format (for the muxer)
|
||||
const char *format;
|
||||
|
||||
|
@ -76,6 +79,7 @@ struct settings_ctx
|
|||
enum AVSampleFormat sample_format;
|
||||
int byte_depth;
|
||||
bool wavheader;
|
||||
bool icy;
|
||||
|
||||
// Video settings
|
||||
enum AVCodecID video_codec;
|
||||
|
@ -123,6 +127,9 @@ struct decode_ctx
|
|||
// Set to true if we have reached eof
|
||||
bool eof;
|
||||
|
||||
// Set to true if avcodec_receive_frame() gave us a frame
|
||||
bool got_frame;
|
||||
|
||||
// Contains the most recent packet from av_read_frame()
|
||||
AVPacket *packet;
|
||||
|
||||
|
@ -171,12 +178,6 @@ struct transcode_ctx
|
|||
struct encode_ctx *encode_ctx;
|
||||
};
|
||||
|
||||
struct decoded_frame
|
||||
{
|
||||
AVFrame *frame;
|
||||
enum AVMediaType type;
|
||||
};
|
||||
|
||||
|
||||
/* -------------------------- PROFILE CONFIGURATION ------------------------ */
|
||||
|
||||
|
@ -200,6 +201,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile)
|
|||
settings->channels = 2;
|
||||
settings->sample_format = AV_SAMPLE_FMT_S16;
|
||||
settings->byte_depth = 2; // Bytes per sample = 16/8
|
||||
settings->icy = 1;
|
||||
break;
|
||||
|
||||
case XCODE_MP3:
|
||||
|
@ -215,12 +217,14 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile)
|
|||
|
||||
case XCODE_JPEG:
|
||||
settings->encode_video = 1;
|
||||
settings->silent = 1;
|
||||
settings->format = "image2";
|
||||
settings->video_codec = AV_CODEC_ID_MJPEG;
|
||||
break;
|
||||
|
||||
case XCODE_PNG:
|
||||
settings->encode_video = 1;
|
||||
settings->silent = 1;
|
||||
settings->format = "image2";
|
||||
settings->video_codec = AV_CODEC_ID_PNG;
|
||||
break;
|
||||
|
@ -256,7 +260,7 @@ stream_settings_set(struct stream_ctx *s, struct settings_ctx *settings, enum AV
|
|||
s->codec->sample_fmt = settings->sample_format;
|
||||
s->codec->time_base = (AVRational){1, settings->sample_rate};
|
||||
}
|
||||
else if (type == AVMEDIA_TYPE_AUDIO)
|
||||
else if (type == AVMEDIA_TYPE_VIDEO)
|
||||
{
|
||||
s->codec->height = settings->height;
|
||||
s->codec->width = settings->width;
|
||||
|
@ -366,6 +370,12 @@ stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id
|
|||
|
||||
stream_settings_set(s, &ctx->settings, encoder->type);
|
||||
|
||||
if (!s->codec->pix_fmt)
|
||||
{
|
||||
s->codec->pix_fmt = avcodec_default_get_format(s->codec, encoder->pix_fmts);
|
||||
DPRINTF(E_DBG, L_XCODE, "Pixel format set to %d (encoder is %s)\n", s->codec->pix_fmt, codec_name);
|
||||
}
|
||||
|
||||
if (ctx->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
s->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
|
@ -595,6 +605,8 @@ decode_filter_encode_write(struct transcode_ctx *ctx, struct stream_ctx *s, AVPa
|
|||
break;
|
||||
}
|
||||
|
||||
dec_ctx->got_frame = 1;
|
||||
|
||||
if (!out_stream)
|
||||
break;
|
||||
|
||||
|
@ -654,13 +666,57 @@ read_decode_filter_encode_write(struct transcode_ctx *ctx)
|
|||
|
||||
/* --------------------------- INPUT/OUTPUT INIT --------------------------- */
|
||||
|
||||
static AVCodecContext *
|
||||
open_decoder(unsigned int *stream_index, struct decode_ctx *ctx, enum AVMediaType type)
|
||||
{
|
||||
AVCodecContext *dec_ctx;
|
||||
AVCodec *decoder;
|
||||
int ret;
|
||||
|
||||
*stream_index = av_find_best_stream(ctx->ifmt_ctx, type, -1, -1, &decoder, 0);
|
||||
if ((*stream_index < 0) || (!decoder))
|
||||
{
|
||||
if (!ctx->settings.silent)
|
||||
DPRINTF(E_LOG, L_XCODE, "No stream data or decoder for '%s'\n", ctx->ifmt_ctx->filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder));
|
||||
|
||||
// In open_filter() we need to tell the sample rate and format that the decoder
|
||||
// is giving us - however sample rate of dec_ctx will be 0 if we don't prime it
|
||||
// with the streams codecpar data.
|
||||
ret = avcodec_parameters_to_context(dec_ctx, ctx->ifmt_ctx->streams[*stream_index]->codecpar);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Failed to copy codecpar for stream #%d: %s\n", *stream_index, err2str(ret));
|
||||
avcodec_free_context(&dec_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (type == AVMEDIA_TYPE_AUDIO)
|
||||
{
|
||||
dec_ctx->request_sample_fmt = ctx->settings.sample_format;
|
||||
dec_ctx->request_channel_layout = ctx->settings.channel_layout;
|
||||
}
|
||||
|
||||
ret = avcodec_open2(dec_ctx, NULL, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", *stream_index, err2str(ret));
|
||||
avcodec_free_context(&dec_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dec_ctx;
|
||||
}
|
||||
|
||||
static int
|
||||
open_input(struct decode_ctx *ctx, const char *path)
|
||||
{
|
||||
AVDictionary *options = NULL;
|
||||
AVCodec *decoder;
|
||||
AVCodecContext *dec_ctx;
|
||||
int stream_index;
|
||||
unsigned int stream_index;
|
||||
int ret;
|
||||
|
||||
CHECK_NULL(L_XCODE, ctx->ifmt_ctx = avformat_alloc_context());
|
||||
|
@ -705,35 +761,9 @@ open_input(struct decode_ctx *ctx, const char *path)
|
|||
|
||||
if (ctx->settings.encode_audio)
|
||||
{
|
||||
// Find audio stream and open decoder
|
||||
stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0);
|
||||
if ((stream_index < 0) || (!decoder))
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Did not find audio stream or suitable decoder for %s\n", path);
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder));
|
||||
|
||||
// In open_filter() we need to tell the sample rate and format that the decoder
|
||||
// is giving us - however sample rate of dec_ctx will be 0 if we don't prime it
|
||||
// with the streams codecpar data.
|
||||
ret = avcodec_parameters_to_context(dec_ctx, ctx->ifmt_ctx->streams[stream_index]->codecpar);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Failed to copy codecpar for stream #%d: %s\n", stream_index, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
dec_ctx->request_sample_fmt = ctx->settings.sample_format;
|
||||
dec_ctx->request_channel_layout = ctx->settings.channel_layout;
|
||||
|
||||
ret = avcodec_open2(dec_ctx, NULL, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
dec_ctx = open_decoder(&stream_index, ctx, AVMEDIA_TYPE_AUDIO);
|
||||
if (!dec_ctx)
|
||||
goto out_fail;
|
||||
|
||||
ctx->audio_stream.codec = dec_ctx;
|
||||
ctx->audio_stream.stream = ctx->ifmt_ctx->streams[stream_index];
|
||||
|
@ -741,22 +771,9 @@ open_input(struct decode_ctx *ctx, const char *path)
|
|||
|
||||
if (ctx->settings.encode_video)
|
||||
{
|
||||
// Find video stream and open decoder
|
||||
stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
|
||||
if ((stream_index < 0) || (!decoder))
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Did not find video stream or suitable decoder for '%s': %s\n", path, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder));
|
||||
|
||||
ret = avcodec_open2(dec_ctx, NULL, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
dec_ctx = open_decoder(&stream_index, ctx, AVMEDIA_TYPE_VIDEO);
|
||||
if (!dec_ctx)
|
||||
goto out_fail;
|
||||
|
||||
ctx->video_stream.codec = dec_ctx;
|
||||
ctx->video_stream.stream = ctx->ifmt_ctx->streams[stream_index];
|
||||
|
@ -793,6 +810,9 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx)
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Clear AVFMT_NOFILE bit, it is not allowed as we will set our own AVIOContext
|
||||
ctx->ofmt_ctx->oformat->flags = ~AVFMT_NOFILE;
|
||||
|
||||
ctx->obuf = evbuffer_new();
|
||||
if (!ctx->obuf)
|
||||
{
|
||||
|
@ -901,7 +921,7 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream)
|
|||
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source: %s\n", err2str(ret));
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source (%s): %s\n", args, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
|
@ -913,7 +933,7 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream)
|
|||
ret = avfilter_graph_create_filter(&format_ctx, format, "format", args, NULL, filter_graph);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create audio format filter: %s\n", err2str(ret));
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create audio format filter (%s): %s\n", args, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
|
@ -952,17 +972,17 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream)
|
|||
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source: %s\n", err2str(ret));
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source (%s): %s\n", args, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
snprintf(args, sizeof(args),
|
||||
"pix_fmt=%d", out_stream->codec->pix_fmt);
|
||||
"pix_fmts=%s", av_get_pix_fmt_name(out_stream->codec->pix_fmt));
|
||||
|
||||
ret = avfilter_graph_create_filter(&format_ctx, format, "format", args, NULL, filter_graph);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create format filter: %s\n", err2str(ret));
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create format filter (%s): %s\n", args, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
|
@ -972,7 +992,7 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream)
|
|||
ret = avfilter_graph_create_filter(&scale_ctx, scale, "scale", args, NULL, filter_graph);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create scale filter: %s\n", err2str(ret));
|
||||
DPRINTF(E_LOG, L_XCODE, "Cannot create scale filter (%s): %s\n", args, err2str(ret));
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
|
@ -1079,7 +1099,7 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind,
|
|||
}
|
||||
|
||||
struct encode_ctx *
|
||||
transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size)
|
||||
transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height)
|
||||
{
|
||||
struct encode_ctx *ctx;
|
||||
|
||||
|
@ -1090,6 +1110,9 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct
|
|||
if (init_settings(&ctx->settings, profile) < 0)
|
||||
goto fail_free;
|
||||
|
||||
ctx->settings.width = width;
|
||||
ctx->settings.height = height;
|
||||
|
||||
if (ctx->settings.wavheader)
|
||||
make_wav_header(ctx, src_ctx, est_size);
|
||||
|
||||
|
@ -1099,7 +1122,7 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct
|
|||
if (open_filters(ctx, src_ctx) < 0)
|
||||
goto fail_close;
|
||||
|
||||
if (src_ctx->data_kind == DATA_KIND_HTTP)
|
||||
if (ctx->settings.icy && src_ctx->data_kind == DATA_KIND_HTTP)
|
||||
ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->settings.channels * ctx->settings.byte_depth * ctx->settings.sample_rate;
|
||||
|
||||
return ctx;
|
||||
|
@ -1127,7 +1150,7 @@ transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ctx->encode_ctx = transcode_encode_setup(profile, ctx->decode_ctx, est_size);
|
||||
ctx->encode_ctx = transcode_encode_setup(profile, ctx->decode_ctx, est_size, 0, 0);
|
||||
if (!ctx->encode_ctx)
|
||||
{
|
||||
transcode_decode_cleanup(&ctx->decode_ctx);
|
||||
|
@ -1309,62 +1332,93 @@ transcode_cleanup(struct transcode_ctx **ctx)
|
|||
*ctx = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
transcode_decoded_free(struct decoded_frame *decoded)
|
||||
{
|
||||
av_frame_free(&decoded->frame);
|
||||
free(decoded);
|
||||
}
|
||||
|
||||
|
||||
/* Encoding, decoding and transcoding */
|
||||
|
||||
int
|
||||
transcode_decode(struct decoded_frame **decoded, struct decode_ctx *dec_ctx)
|
||||
transcode_decode(void **frame, struct decode_ctx *dec_ctx)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Bug! Call to transcode_decode(), but the lazy programmer didn't implement it\n");
|
||||
return -1;
|
||||
struct transcode_ctx ctx;
|
||||
int ret;
|
||||
|
||||
if (dec_ctx->got_frame)
|
||||
DPRINTF(E_LOG, L_XCODE, "Bug! Currently no support for multiple calls to transcode_decode()\n");
|
||||
|
||||
ctx.decode_ctx = dec_ctx;
|
||||
ctx.encode_ctx = NULL;
|
||||
|
||||
do
|
||||
{
|
||||
// This function stops after decoding because ctx->encode_ctx is NULL
|
||||
ret = read_decode_filter_encode_write(&ctx);
|
||||
}
|
||||
while ((ret == 0) && (!dec_ctx->got_frame));
|
||||
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
*frame = dec_ctx->decoded_frame;
|
||||
|
||||
if (dec_ctx->eof)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Filters and encodes
|
||||
int
|
||||
transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx)
|
||||
transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, int eof)
|
||||
{
|
||||
AVFrame *f = frame;
|
||||
struct stream_ctx *s;
|
||||
size_t start_length;
|
||||
int ret;
|
||||
|
||||
start_length = evbuffer_get_length(ctx->obuf);
|
||||
|
||||
if (decoded->type == AVMEDIA_TYPE_AUDIO)
|
||||
// Really crappy way of detecting if frame is audio, video or something else
|
||||
if (f->channel_layout && f->sample_rate)
|
||||
s = &ctx->audio_stream;
|
||||
else if (decoded->type == AVMEDIA_TYPE_VIDEO)
|
||||
else if (f->width && f->height)
|
||||
s = &ctx->video_stream;
|
||||
else
|
||||
return -1;
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Bug! Encoder could not detect frame type\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = filter_encode_write(ctx, s, decoded->frame);
|
||||
ret = filter_encode_write(ctx, s, f);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Error occurred while encoding: %s\n", err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Flush
|
||||
if (eof)
|
||||
{
|
||||
filter_encode_write(ctx, s, NULL);
|
||||
av_write_trailer(ctx->ofmt_ctx);
|
||||
}
|
||||
|
||||
ret = evbuffer_get_length(ctx->obuf) - start_length;
|
||||
|
||||
// TODO Shouldn't we flush and let the muxer write the trailer now?
|
||||
|
||||
evbuffer_add_buffer(evbuf, ctx->obuf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int *icy_timer)
|
||||
transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int want_bytes)
|
||||
{
|
||||
size_t start_length;
|
||||
int processed = 0;
|
||||
int ret;
|
||||
|
||||
*icy_timer = 0;
|
||||
if (icy_timer)
|
||||
*icy_timer = 0;
|
||||
|
||||
if (ctx->decode_ctx->eof)
|
||||
return 0;
|
||||
|
@ -1381,7 +1435,7 @@ transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int
|
|||
evbuffer_add_buffer(evbuf, ctx->encode_ctx->obuf);
|
||||
|
||||
ctx->encode_ctx->total_bytes += processed;
|
||||
if (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);
|
||||
|
||||
if (ret == AVERROR_EOF)
|
||||
|
@ -1392,49 +1446,45 @@ transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int
|
|||
return processed;
|
||||
}
|
||||
|
||||
struct decoded_frame *
|
||||
transcode_raw2frame(uint8_t *data, size_t size)
|
||||
void *
|
||||
transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size)
|
||||
{
|
||||
struct decoded_frame *decoded;
|
||||
AVFrame *frame;
|
||||
AVFrame *f;
|
||||
int ret;
|
||||
|
||||
decoded = malloc(sizeof(struct decoded_frame));
|
||||
if (!decoded)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded struct\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
f = av_frame_alloc();
|
||||
if (!f)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Out of memory for frame\n");
|
||||
free(decoded);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
decoded->type = AVMEDIA_TYPE_AUDIO;
|
||||
decoded->frame = frame;
|
||||
|
||||
frame->nb_samples = size / 4;
|
||||
frame->format = AV_SAMPLE_FMT_S16;
|
||||
frame->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||
f->nb_samples = size / 4;
|
||||
f->format = AV_SAMPLE_FMT_S16;
|
||||
f->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||
#ifdef HAVE_FFMPEG
|
||||
frame->channels = 2;
|
||||
f->channels = 2;
|
||||
#endif
|
||||
frame->pts = AV_NOPTS_VALUE;
|
||||
frame->sample_rate = 44100;
|
||||
f->pts = AV_NOPTS_VALUE;
|
||||
f->sample_rate = 44100;
|
||||
|
||||
ret = avcodec_fill_audio_frame(frame, 2, frame->format, data, size, 0);
|
||||
ret = avcodec_fill_audio_frame(f, 2, f->format, data, size, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_XCODE, "Error filling frame with rawbuf: %s\n", err2str(ret));
|
||||
transcode_decoded_free(decoded);
|
||||
av_frame_free(&f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return decoded;
|
||||
return f;
|
||||
}
|
||||
|
||||
void
|
||||
transcode_frame_free(void *frame)
|
||||
{
|
||||
AVFrame *f = frame;
|
||||
|
||||
av_frame_free(&f);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1523,6 +1573,34 @@ transcode_seek(struct transcode_ctx *ctx, int ms)
|
|||
return got_ms;
|
||||
}
|
||||
|
||||
/* Querying */
|
||||
|
||||
int
|
||||
transcode_decode_query(struct decode_ctx *ctx, const char *query)
|
||||
{
|
||||
if (strcmp(query, "width") == 0)
|
||||
{
|
||||
if (ctx->video_stream.stream)
|
||||
return ctx->video_stream.stream->codecpar->width;
|
||||
}
|
||||
else if (strcmp(query, "height") == 0)
|
||||
{
|
||||
if (ctx->video_stream.stream)
|
||||
return ctx->video_stream.stream->codecpar->height;
|
||||
}
|
||||
else if (strcmp(query, "is_png") == 0)
|
||||
{
|
||||
if (ctx->video_stream.stream)
|
||||
return (ctx->video_stream.stream->codecpar->codec_id == AV_CODEC_ID_PNG);
|
||||
}
|
||||
else if (strcmp(query, "is_jpeg") == 0)
|
||||
{
|
||||
if (ctx->video_stream.stream)
|
||||
return (ctx->video_stream.stream->codecpar->codec_id == AV_CODEC_ID_MJPEG);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Metadata */
|
||||
|
||||
|
|
|
@ -22,14 +22,13 @@ enum transcode_profile
|
|||
struct decode_ctx;
|
||||
struct encode_ctx;
|
||||
struct transcode_ctx;
|
||||
struct decoded_frame;
|
||||
|
||||
// Setting up
|
||||
struct decode_ctx *
|
||||
transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length);
|
||||
|
||||
struct encode_ctx *
|
||||
transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size);
|
||||
transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height);
|
||||
|
||||
struct transcode_ctx *
|
||||
transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size);
|
||||
|
@ -50,51 +49,74 @@ transcode_encode_cleanup(struct encode_ctx **ctx);
|
|||
void
|
||||
transcode_cleanup(struct transcode_ctx **ctx);
|
||||
|
||||
void
|
||||
transcode_decoded_free(struct decoded_frame *decoded);
|
||||
|
||||
// Transcoding
|
||||
|
||||
/* Demuxes and decodes the next packet from the input.
|
||||
*
|
||||
* @out decoded A newly allocated struct with a pointer to the frame and the
|
||||
* stream. Must be freed with transcode_decoded_free().
|
||||
* @out frame A pointer to the frame. Caller should not free it, that will
|
||||
* be done by the next call to the function or by the cleanup
|
||||
* function.
|
||||
* @in ctx Decode context
|
||||
* @return Positive if OK, negative if error, 0 if EOF
|
||||
*/
|
||||
int
|
||||
transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx);
|
||||
transcode_decode(void **frame, struct decode_ctx *ctx);
|
||||
|
||||
/* Encodes and remuxes a frame. Also resamples if needed.
|
||||
*
|
||||
* @out evbuf An evbuffer filled with remuxed data
|
||||
* @in frame The frame to encode, e.g. from transcode_decode
|
||||
* @in ctx Encode context
|
||||
* @in frame The decoded frame to encode, e.g. from transcode_decode
|
||||
* @in eof If true the muxer will write a trailer to the output
|
||||
* @return Bytes added if OK, negative if error
|
||||
*/
|
||||
int
|
||||
transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx);
|
||||
transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, int eof);
|
||||
|
||||
/* Demuxes, decodes, encodes and remuxes from the input.
|
||||
*
|
||||
* @out evbuf An evbuffer filled with remuxed data
|
||||
* @out icy_timer True if METADATA_ICY_INTERVAL has elapsed
|
||||
* @in ctx Transcode context
|
||||
* @in want_bytes Minimum number of bytes the caller wants added to the evbuffer
|
||||
* - set want_bytes to 0 to transcode everything until EOF/error
|
||||
* - set want_bytes to 1 to get one encoded packet
|
||||
* @in ctx Transcode context
|
||||
* @out icy_timer True if METADATA_ICY_INTERVAL has elapsed
|
||||
* @return Bytes added if OK, negative if error, 0 if EOF
|
||||
*/
|
||||
int
|
||||
transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int *icy_timer);
|
||||
transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int want_bytes);
|
||||
|
||||
struct decoded_frame *
|
||||
transcode_raw2frame(uint8_t *data, size_t size);
|
||||
/* Converts a buffer with raw data to a frame that can be passed directly to the
|
||||
* transcode_encode() function
|
||||
*
|
||||
* @in profile Tells the function what kind of frame to create
|
||||
* @in data Buffer with raw data
|
||||
* @in size Size of buffer
|
||||
* @return Opaque pointer to frame if OK, otherwise NULL
|
||||
*/
|
||||
void *
|
||||
transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size);
|
||||
void
|
||||
transcode_frame_free(void *frame);
|
||||
|
||||
// Seeking
|
||||
/* Seek to the specified position - next transcode() will return this packet
|
||||
*
|
||||
* @in ctx Transcode context
|
||||
* @in seek Requested seek position in ms
|
||||
* @return Negative if error, otherwise actual seek position
|
||||
*/
|
||||
int
|
||||
transcode_seek(struct transcode_ctx *ctx, int ms);
|
||||
|
||||
/* Query for information about a media file opened by transcode_decode_setup()
|
||||
*
|
||||
* @in ctx Decode context
|
||||
* @in query Query - see implementation for supported queries
|
||||
* @return Negative if error, otherwise query dependent
|
||||
*/
|
||||
int
|
||||
transcode_decode_query(struct decode_ctx *ctx, const char *query);
|
||||
|
||||
// Metadata
|
||||
struct http_icy_metadata *
|
||||
transcode_metadata(struct transcode_ctx *ctx, int *changed);
|
||||
|
|
Loading…
Reference in New Issue