[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:
ejurgensen 2017-02-28 23:06:01 +01:00
parent d933e171d4
commit e7f888645f
7 changed files with 316 additions and 620 deletions

View File

@ -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

View File

@ -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)

View File

@ -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");

View File

@ -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;

View File

@ -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;

View File

@ -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 */

View File

@ -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);