diff --git a/configure.ac b/configure.ac index 5d4d7252..9b149beb 100644 --- a/configure.ac +++ b/configure.ac @@ -237,23 +237,30 @@ FORK_MODULES_CHECK([FORKED], [LIBAV], [Define to 1 if you have ffmpeg (not libav)])], [[is_ffmpeg=no]]) AC_MSG_RESULT([$is_ffmpeg]) - FORK_CHECK_DECLS([av_buffersrc_add_frame_flags], - [libavfilter/buffersrc.h]) - FORK_CHECK_DECLS([av_buffersink_get_frame], - [libavfilter/buffersink.h]) - FORK_CHECK_DECLS([avfilter_graph_parse_ptr], - [libavfilter/avfilter.h]) - FORK_CHECK_DECLS([av_packet_unref], [libavcodec/avcodec.h]) - FORK_CHECK_DECLS([av_packet_rescale_ts], [libavcodec/avcodec.h]) - FORK_CHECK_DECLS([avformat_alloc_output_context2], - [libavformat/avformat.h]) - FORK_CHECK_DECLS([av_frame_alloc], [libavutil/frame.h]) - FORK_CHECK_DECLS([av_frame_get_best_effort_timestamp], - [libavutil/frame.h]) - FORK_CHECK_DECLS([av_image_fill_arrays], [libavutil/imgutils.h]) - FORK_CHECK_DECLS([av_image_get_buffer_size], [libavutil/imgutils.h]) - AC_CHECK_HEADERS([libavutil/channel_layout.h libavutil/mathematics.h]) + dnl Check if we have modern or legacy AV apis + FORK_CHECK_DECLS([avcodec_send_packet, avcodec_parameters_from_context], + [libavcodec/avcodec.h], + [[modern_av_apis=yes]], + [[modern_av_apis=no] + FORK_CHECK_DECLS([av_buffersrc_add_frame_flags], + [libavfilter/buffersrc.h]) + FORK_CHECK_DECLS([av_buffersink_get_frame], + [libavfilter/buffersink.h]) + FORK_CHECK_DECLS([avfilter_graph_parse_ptr], + [libavfilter/avfilter.h]) + FORK_CHECK_DECLS([av_packet_unref], [libavcodec/avcodec.h]) + FORK_CHECK_DECLS([av_packet_rescale_ts], [libavcodec/avcodec.h]) + FORK_CHECK_DECLS([avformat_alloc_output_context2], + [libavformat/avformat.h]) + FORK_CHECK_DECLS([av_frame_alloc], [libavutil/frame.h]) + FORK_CHECK_DECLS([av_frame_get_best_effort_timestamp], + [libavutil/frame.h]) + FORK_CHECK_DECLS([av_image_fill_arrays], [libavutil/imgutils.h]) + FORK_CHECK_DECLS([av_image_get_buffer_size], [libavutil/imgutils.h]) + AC_CHECK_HEADERS([libavutil/channel_layout.h libavutil/mathematics.h]) + ]) ]) +AM_CONDITIONAL([COND_FFMPEG_LEGACY], [[test "x$modern_av_apis" = "xno"]]) AC_CHECK_SIZEOF([void *]) diff --git a/src/Makefile.am b/src/Makefile.am index b27e68db..f10e02f6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,6 +43,12 @@ else MDNS_SRC=mdns_dnssd.c endif +if COND_FFMPEG_LEGACY +FFMPEG_SRC=transcode_legacy.c artwork_legacy.c ffmpeg-compat.h +else +FFMPEG_SRC=transcode.c artwork.c +endif + GPERF_FILES = \ daap_query.gperf \ rsp_query.gperf \ @@ -105,8 +111,7 @@ forked_daapd_SOURCES = main.c \ httpd_streaming.c httpd_streaming.h \ http.c http.h \ dmap_common.c dmap_common.h \ - transcode.c transcode.h \ - artwork.c artwork.h \ + $(FFMPEG_SRC) \ misc.c misc.h \ rng.c rng.h \ rsp_query.c rsp_query.h \ @@ -125,7 +130,7 @@ forked_daapd_SOURCES = main.c \ $(MPD_SRC) \ listener.c listener.h \ commands.c commands.h \ - ffmpeg-compat.h mxml-compat.h \ + mxml-compat.h \ $(GPERF_SRC) \ $(ANTLR_SRC) diff --git a/src/artwork.c b/src/artwork.c index ed8471d5..b56ae720 100644 --- a/src/artwork.c +++ b/src/artwork.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2016 Espen Jürgensen + * Copyright (C) 2015-2017 Espen Jürgensen * Copyright (C) 2010-2011 Julien BLACHE * * This program is free software; you can redistribute it and/or modify @@ -30,27 +30,20 @@ #include #include -#include -#include -#include -#include - #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,341 +316,28 @@ 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. * * @out evbuf Image data - * @in path Path to the artwork + * @in path Path to the artwork (alternative to inbuf) + * @in inbuf Buffer with the artwork (alternative to path) * @in max_w Requested width * @in max_h Requested height * @return ART_FMT_* on success, ART_E_ERROR on error */ static int -artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h) +artwork_get(struct evbuffer *evbuf, char *path, struct evbuffer *inbuf, 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 +345,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_JPEG, DATA_KIND_FILE, path, inbuf, 0); // Covers XCODE_PNG 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 (!path || 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. @@ -840,7 +519,7 @@ artwork_get_dir_image(struct evbuffer *evbuf, char *dir, int max_w, int max_h, c snprintf(out_path, PATH_MAX, "%s", path); - return artwork_get(evbuf, path, max_w, max_h); + return artwork_get(evbuf, path, NULL, max_w, max_h); } @@ -949,92 +628,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, NULL, ctx->max_w, ctx->max_h); } /* Looks for basename(in_path).{png,jpg}, so if in_path is /foo/bar.mp3 it @@ -1088,7 +686,7 @@ source_item_own_get(struct artwork_ctx *ctx) snprintf(ctx->path, sizeof(ctx->path), "%s", path); - return artwork_get(ctx->evbuf, path, ctx->max_w, ctx->max_h); + return artwork_get(ctx->evbuf, path, NULL, ctx->max_w, ctx->max_h); } /* @@ -1177,13 +775,8 @@ source_item_stream_get(struct artwork_ctx *ctx) static int source_item_spotify_get(struct artwork_ctx *ctx) { - AVFormatContext *src_ctx; - AVIOContext *avio; - AVInputFormat *ifmt; struct evbuffer *raw; struct evbuffer *evbuf; - int target_w; - int target_h; int ret; raw = evbuffer_new(); @@ -1223,75 +816,29 @@ source_item_spotify_get(struct artwork_ctx *ctx) goto out_free_evbuf; } - // Now evbuf will be processed by ffmpeg, since it probably needs to be rescaled - src_ctx = avformat_alloc_context(); - if (!src_ctx) + // For non-file input, artwork_get() will also fail if no rescaling is required + ret = artwork_get(ctx->evbuf, NULL, evbuf, ctx->max_w, ctx->max_h); + if (ret == ART_E_ERROR) { - DPRINTF(E_LOG, L_ART, "Out of memory for source context\n"); - goto out_free_evbuf; + DPRINTF(E_DBG, L_ART, "Not rescaling Spotify image\n"); + ret = evbuffer_add_buffer(ctx->evbuf, raw); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not add or rescale image to output evbuf\n"); + goto out_free_evbuf; + } } - avio = avio_input_evbuffer_open(evbuf); - if (!avio) - { - DPRINTF(E_LOG, L_ART, "Could not alloc input evbuffer\n"); - goto out_free_ctx; - } - - src_ctx->pb = avio; - - ifmt = av_find_input_format("mjpeg"); - if (!ifmt) - { - DPRINTF(E_LOG, L_ART, "Could not find mjpeg input format\n"); - goto out_close_avio; - } - - ret = avformat_open_input(&src_ctx, NULL, ifmt, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_ART, "Could not open input\n"); - goto out_close_avio; - } - - ret = avformat_find_stream_info(src_ctx, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_ART, "Could not find stream info\n"); - goto out_close_input; - } - - ret = rescale_needed(src_ctx->streams[0]->codec, ctx->max_w, ctx->max_h, &target_w, &target_h); - if (!ret) - ret = evbuffer_add_buffer(ctx->evbuf, raw); - else - ret = artwork_rescale(ctx->evbuf, src_ctx, 0, target_w, target_h); - if (ret < 0) - { - DPRINTF(E_LOG, L_ART, "Could not add or rescale image to output evbuf\n"); - goto out_close_input; - } - - avformat_close_input(&src_ctx); - avio_evbuffer_close(avio); evbuffer_free(evbuf); evbuffer_free(raw); return ART_FMT_JPEG; - out_close_input: - avformat_close_input(&src_ctx); - out_close_avio: - avio_evbuffer_close(avio); - out_free_ctx: - if (src_ctx) - avformat_free_context(src_ctx); out_free_evbuf: evbuffer_free(evbuf); evbuffer_free(raw); return ART_E_ERROR; - } #else static int diff --git a/src/artwork_legacy.c b/src/artwork_legacy.c new file mode 100644 index 00000000..e25d502c --- /dev/null +++ b/src/artwork_legacy.c @@ -0,0 +1,1627 @@ +/* + * Copyright (C) 2015-2016 Espen Jürgensen + * Copyright (C) 2010-2011 Julien BLACHE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "db.h" +#include "misc.h" +#include "logger.h" +#include "conffile.h" +#include "cache.h" +#include "http.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 + * actual work of getting the artwork. + * + * There are two types of handlers: item and group. Item handlers are capable of + * finding artwork for a single item (a dbmfi), while group handlers can get for + * an album or artist (a persistentid). + * + * An artwork source handler must return one of the following: + * + * ART_FMT_JPEG (positive) Found a jpeg + * ART_FMT_PNG (positive) Found a png + * ART_E_NONE (zero) No artwork found + * ART_E_ERROR (negative) An error occurred while searching for artwork + * ART_E_ABORT (negative) Caller should abort artwork search (may be returned by cache) + */ +#define ART_E_NONE 0 +#define ART_E_ERROR -1 +#define ART_E_ABORT -2 + +enum artwork_cache +{ + NEVER = 0, // No caching of any results + ON_SUCCESS = 1, // Cache if artwork found + ON_FAILURE = 2, // Cache if artwork not found (so we don't keep asking) +}; + +/* This struct contains the data available to the handler, as well as a char + * buffer where the handler should output the path to the artwork (if it is + * local - otherwise the buffer can be left empty). The purpose of supplying the + * path is that the filescanner can then clear the cache in case the file + * changes. + */ +struct artwork_ctx { + // Handler should output path here if artwork is local + char path[PATH_MAX]; + // Handler should output artwork data to this evbuffer + struct evbuffer *evbuf; + + // Input data to handler, requested width and height + int max_w; + int max_h; + // Input data to handler, did user configure to look for individual artwork + int individual; + + // Input data for item handlers + struct db_media_file_info *dbmfi; + int id; + // Input data for group handlers + int64_t persistentid; + + // Not to be used by handler - query for item or group + struct query_params qp; + // Not to be used by handler - should the result be cached + enum artwork_cache cache; +}; + +/* Definition of an artwork source. Covers both item and group sources. + */ +struct artwork_source { + // Name of the source, e.g. "cache" + const char *name; + + // The handler + int (*handler)(struct artwork_ctx *ctx); + + // What data_kinds the handler can work with, combined with (1 << A) | (1 << B) + int data_kinds; + + // When should results from the source be cached? + enum artwork_cache cache; +}; + +/* File extensions that we look for or accept + */ +static const char *cover_extension[] = + { + "jpg", "png", + }; + + +/* ----------------- DECLARE AND CONFIGURE SOURCE HANDLERS ----------------- */ + +/* Forward - group handlers */ +static int source_group_cache_get(struct artwork_ctx *ctx); +static int source_group_dir_get(struct artwork_ctx *ctx); +/* Forward - item handlers */ +static int source_item_cache_get(struct artwork_ctx *ctx); +static int source_item_embedded_get(struct artwork_ctx *ctx); +static int source_item_own_get(struct artwork_ctx *ctx); +static int source_item_stream_get(struct artwork_ctx *ctx); +static int source_item_spotify_get(struct artwork_ctx *ctx); +static int source_item_ownpl_get(struct artwork_ctx *ctx); + +/* List of sources that can provide artwork for a group (i.e. usually an album + * identified by a persistentid). The source handlers will be called in the + * order of this list. Must be terminated by a NULL struct. + */ +static struct artwork_source artwork_group_source[] = + { + { + .name = "cache", + .handler = source_group_cache_get, + .cache = ON_FAILURE, + }, + { + .name = "directory", + .handler = source_group_dir_get, + .cache = ON_SUCCESS | ON_FAILURE, + }, + { + .name = NULL, + .handler = NULL, + .cache = 0, + } + }; + +/* List of sources that can provide artwork for an item (a track characterized + * by a dbmfi). The source handlers will be called in the order of this list. + * The handler will only be called if the data_kind matches. Must be terminated + * by a NULL struct. + */ +static struct artwork_source artwork_item_source[] = + { + { + .name = "cache", + .handler = source_item_cache_get, + .data_kinds = (1 << DATA_KIND_FILE) | (1 << DATA_KIND_SPOTIFY), + .cache = ON_FAILURE, + }, + { + .name = "embedded", + .handler = source_item_embedded_get, + .data_kinds = (1 << DATA_KIND_FILE), + .cache = ON_SUCCESS | ON_FAILURE, + }, + { + .name = "own", + .handler = source_item_own_get, + .data_kinds = (1 << DATA_KIND_FILE), + .cache = ON_SUCCESS | ON_FAILURE, + }, + { + .name = "stream", + .handler = source_item_stream_get, + .data_kinds = (1 << DATA_KIND_HTTP), + .cache = NEVER, + }, + { + .name = "Spotify", + .handler = source_item_spotify_get, + .data_kinds = (1 << DATA_KIND_SPOTIFY), + .cache = ON_SUCCESS, + }, + { + .name = "playlist own", + .handler = source_item_ownpl_get, + .data_kinds = (1 << DATA_KIND_HTTP), + .cache = ON_SUCCESS | ON_FAILURE, + }, + { + .name = NULL, + .handler = NULL, + .data_kinds = 0, + .cache = 0, + } + }; + + + +/* -------------------------------- HELPERS -------------------------------- */ + +/* Reads an artwork file from the filesystem straight into an evbuf + * TODO Use evbuffer_add_file or evbuffer_read? + * + * @out evbuf Image data + * @in path Path to the artwork + * @return 0 on success, -1 on error + */ +static int +artwork_read(struct evbuffer *evbuf, char *path) +{ + uint8_t buf[4096]; + struct stat sb; + int fd; + int ret; + + fd = open(path, O_RDONLY); + if (fd < 0) + { + DPRINTF(E_WARN, L_ART, "Could not open artwork file '%s': %s\n", path, strerror(errno)); + + return -1; + } + + ret = fstat(fd, &sb); + if (ret < 0) + { + DPRINTF(E_WARN, L_ART, "Could not stat() artwork file '%s': %s\n", path, strerror(errno)); + + goto out_fail; + } + + ret = evbuffer_expand(evbuf, sb.st_size); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Out of memory for artwork\n"); + + goto out_fail; + } + + while ((ret = read(fd, buf, sizeof(buf))) > 0) + evbuffer_add(evbuf, buf, ret); + + close(fd); + + return 0; + + out_fail: + close(fd); + return -1; +} + +/* 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 + */ +static int +rescale_needed(AVCodecContext *src, int max_w, int max_h, int *target_w, int *target_h) +{ + DPRINTF(E_DBG, L_ART, "Original image dimensions: w %d h %d\n", src->width, src->height); + + *target_w = src->width; + *target_h = src->height; + + if ((src->width == 0) || (src->height == 0)) /* Unknown source size, can't rescale */ + return 0; + + if ((max_w <= 0) || (max_h <= 0)) /* No valid target dimensions, use original */ + return 0; + + if ((src->width <= max_w) && (src->height <= max_h)) /* Smaller than target */ + return 0; + + if (src->width * max_h > src->height * max_w) /* Wider aspect ratio than target */ + { + *target_w = max_w; + *target_h = (double)max_w * ((double)src->height / (double)src->width); + } + else /* Taller or equal aspect ratio */ + { + *target_w = (double)max_h * ((double)src->width / (double)src->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; + + /* PNG prefers even row count */ + *target_w += *target_w % 2; + + 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); + + 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; +} + +/* Get an artwork file from the filesystem. Will rescale if needed. + * + * @out evbuf Image data + * @in path Path to the artwork + * @in max_w Requested width + * @in max_h Requested height + * @return ART_FMT_* on success, ART_E_ERROR on error + */ +static int +artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h) +{ + AVFormatContext *src_ctx; + int s; + int target_w; + int target_h; + int format_ok; + int ret; + + 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) + { + DPRINTF(E_WARN, L_ART, "Cannot open artwork file '%s': %s\n", 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_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) + { + 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; + } + + ret = rescale_needed(src_ctx->streams[s]->codec, max_w, max_h, &target_w, &target_h); + + /* Fastpath */ + if (!ret && format_ok) + { + ret = artwork_read(evbuf, path); + if (ret == 0) + ret = format_ok; + } + else + ret = artwork_rescale(evbuf, src_ctx, s, target_w, target_h); + + avformat_close_input(&src_ctx); + + if (ret < 0) + { + if (evbuffer_get_length(evbuf) > 0) + evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); + + ret = ART_E_ERROR; + } + + return ret; +} + +/* Looks for an artwork file in a directory. Will rescale if needed. + * + * @out evbuf Image data + * @in dir Directory to search + * @in max_w Requested width + * @in max_h Requested height + * @out out_path Path to the artwork file if found, must be a char[PATH_MAX] buffer + * @return ART_FMT_* on success, ART_E_NONE on nothing found, ART_E_ERROR on error + */ +static int +artwork_get_dir_image(struct evbuffer *evbuf, char *dir, int max_w, int max_h, char *out_path) +{ + char path[PATH_MAX]; + char parentdir[PATH_MAX]; + int i; + int j; + int len; + int ret; + cfg_t *lib; + int nbasenames; + int nextensions; + char *ptr; + + ret = snprintf(path, sizeof(path), "%s", dir); + if ((ret < 0) || (ret >= sizeof(path))) + { + DPRINTF(E_LOG, L_ART, "Artwork path exceeds PATH_MAX (%s)\n", dir); + return ART_E_ERROR; + } + + len = strlen(path); + + lib = cfg_getsec(cfg, "library"); + nbasenames = cfg_size(lib, "artwork_basenames"); + + if (nbasenames == 0) + return ART_E_NONE; + + nextensions = sizeof(cover_extension) / sizeof(cover_extension[0]); + + for (i = 0; i < nbasenames; i++) + { + for (j = 0; j < nextensions; j++) + { + ret = snprintf(path + len, sizeof(path) - len, "/%s.%s", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]); + if ((ret < 0) || (ret >= sizeof(path) - len)) + { + DPRINTF(E_LOG, L_ART, "Artwork path will exceed PATH_MAX (%s/%s)\n", dir, cfg_getnstr(lib, "artwork_basenames", i)); + continue; + } + + DPRINTF(E_SPAM, L_ART, "Trying directory artwork file %s\n", path); + + ret = access(path, F_OK); + if (ret < 0) + continue; + + // If artwork file exists (ret == 0), exit the loop + break; + } + + // In case the previous loop exited early, we found an existing artwork file and exit the outer loop + if (j < nextensions) + break; + } + + // If the loop for directory artwork did not exit early, look for parent directory artwork + if (i == nbasenames) + { + ptr = strrchr(path, '/'); + if (ptr) + *ptr = '\0'; + + ptr = strrchr(path, '/'); + if ((!ptr) || (strlen(ptr) <= 1)) + { + DPRINTF(E_LOG, L_ART, "Could not find parent dir name (%s)\n", path); + return ART_E_ERROR; + } + strcpy(parentdir, ptr + 1); + + len = strlen(path); + + for (i = 0; i < nextensions; i++) + { + ret = snprintf(path + len, sizeof(path) - len, "/%s.%s", parentdir, cover_extension[i]); + if ((ret < 0) || (ret >= sizeof(path) - len)) + { + DPRINTF(E_LOG, L_ART, "Artwork path will exceed PATH_MAX (%s)\n", parentdir); + continue; + } + + DPRINTF(E_SPAM, L_ART, "Trying parent directory artwork file %s\n", path); + + ret = access(path, F_OK); + if (ret < 0) + continue; + + break; + } + + if (i == nextensions) + return ART_E_NONE; + } + + snprintf(out_path, PATH_MAX, "%s", path); + + return artwork_get(evbuf, path, max_w, max_h); +} + + +/* ---------------------- SOURCE HANDLER IMPLEMENTATION -------------------- */ + +/* Looks in the cache for group artwork + */ +static int +source_group_cache_get(struct artwork_ctx *ctx) +{ + int format; + int cached; + int ret; + + ret = cache_artwork_get(CACHE_ARTWORK_GROUP, ctx->persistentid, ctx->max_w, ctx->max_h, &cached, &format, ctx->evbuf); + if (ret < 0) + return ART_E_ERROR; + + if (!cached) + return ART_E_NONE; + + if (!format) + return ART_E_ABORT; + + return format; +} + +/* Looks for cover files in a directory, so if dir is /foo/bar and the user has + * configured the cover file names "cover" and "artwork" it will look for + * /foo/bar/cover.{png,jpg}, /foo/bar/artwork.{png,jpg} and also + * /foo/bar/bar.{png,jpg} (so-called parentdir artwork) + */ +static int +source_group_dir_get(struct artwork_ctx *ctx) +{ + struct query_params qp; + char *dir; + int ret; + + /* Image is not in the artwork cache. Try directory artwork first */ + memset(&qp, 0, sizeof(struct query_params)); + + qp.type = Q_GROUP_DIRS; + qp.persistentid = ctx->persistentid; + + ret = db_query_start(&qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not start Q_GROUP_DIRS query\n"); + return ART_E_ERROR; + } + + while (((ret = db_query_fetch_string(&qp, &dir)) == 0) && (dir)) + { + /* The db query may return non-directories (eg if item is an internet stream or Spotify) */ + if (access(dir, F_OK) < 0) + continue; + + ret = artwork_get_dir_image(ctx->evbuf, dir, ctx->max_w, ctx->max_h, ctx->path); + if (ret > 0) + { + db_query_end(&qp); + return ret; + } + } + + db_query_end(&qp); + + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Error fetching Q_GROUP_DIRS results\n"); + return ART_E_ERROR; + } + + return ART_E_NONE; +} + +/* Looks in the cache for item artwork. Only relevant if configured to look for + * individual artwork. + */ +static int +source_item_cache_get(struct artwork_ctx *ctx) +{ + int format; + int cached; + int ret; + + if (!ctx->individual) + return ART_E_NONE; + + ret = cache_artwork_get(CACHE_ARTWORK_INDIVIDUAL, ctx->id, ctx->max_w, ctx->max_h, &cached, &format, ctx->evbuf); + if (ret < 0) + return ART_E_ERROR; + + if (!cached) + return ART_E_NONE; + + if (!format) + return ART_E_ABORT; + + return format; +} + +/* Get an embedded artwork file from a media file. Will rescale if needed. + */ +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; + + 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; +} + +/* Looks for basename(in_path).{png,jpg}, so if in_path is /foo/bar.mp3 it + * will look for /foo/bar.png and /foo/bar.jpg + */ +static int +source_item_own_get(struct artwork_ctx *ctx) +{ + char path[PATH_MAX]; + char *ptr; + int len; + int nextensions; + int i; + int ret; + + ret = snprintf(path, sizeof(path), "%s", ctx->dbmfi->path); + if ((ret < 0) || (ret >= sizeof(path))) + { + DPRINTF(E_LOG, L_ART, "Artwork path exceeds PATH_MAX (%s)\n", ctx->dbmfi->path); + return ART_E_ERROR; + } + + ptr = strrchr(path, '.'); + if (ptr) + *ptr = '\0'; + + len = strlen(path); + + nextensions = sizeof(cover_extension) / sizeof(cover_extension[0]); + + for (i = 0; i < nextensions; i++) + { + ret = snprintf(path + len, sizeof(path) - len, ".%s", cover_extension[i]); + if ((ret < 0) || (ret >= sizeof(path) - len)) + { + DPRINTF(E_LOG, L_ART, "Artwork path will exceed PATH_MAX (%s)\n", ctx->dbmfi->path); + continue; + } + + DPRINTF(E_SPAM, L_ART, "Trying own artwork file %s\n", path); + + ret = access(path, F_OK); + if (ret < 0) + continue; + + break; + } + + if (i == nextensions) + return ART_E_NONE; + + snprintf(ctx->path, sizeof(ctx->path), "%s", path); + + return artwork_get(ctx->evbuf, path, ctx->max_w, ctx->max_h); +} + +/* + * Downloads the artwork pointed to by the ICY metadata tag in an internet radio + * stream (the StreamUrl tag). The path will be converted back to the id, which + * is given to the player. If the id is currently being played, and there is a + * valid ICY metadata artwork URL available, it will be returned to this + * function, which will then use the http client to get the artwork. Notice: No + * rescaling is done. + */ +static int +source_item_stream_get(struct artwork_ctx *ctx) +{ + struct http_client_ctx client; + struct db_queue_item *queue_item; + struct keyval *kv; + const char *content_type; + char *url; + char *ext; + int len; + int ret; + + DPRINTF(E_SPAM, L_ART, "Trying internet stream artwork in %s\n", ctx->dbmfi->path); + + ret = ART_E_NONE; + + queue_item = db_queue_fetch_byfileid(ctx->id); + if (!queue_item || !queue_item->artwork_url) + { + free_queue_item(queue_item, 0); + return ART_E_NONE; + } + + url = strdup(queue_item->artwork_url); + free_queue_item(queue_item, 0); + + len = strlen(url); + if ((len < 14) || (len > PATH_MAX)) // Can't be shorter than http://a/1.jpg + goto out_url; + + ext = strrchr(url, '.'); + if (!ext) + goto out_url; + if ((strcmp(ext, ".jpg") != 0) && (strcmp(ext, ".png") != 0)) + goto out_url; + + cache_artwork_read(ctx->evbuf, url, &ret); + if (ret > 0) + goto out_url; + + kv = keyval_alloc(); + if (!kv) + goto out_url; + + memset(&client, 0, sizeof(struct http_client_ctx)); + client.url = url; + client.input_headers = kv; + client.input_body = ctx->evbuf; + + if (http_client_request(&client) < 0) + goto out_kv; + + content_type = keyval_get(kv, "Content-Type"); + if (content_type && (strcmp(content_type, "image/jpeg") == 0)) + ret = ART_FMT_JPEG; + else if (content_type && (strcmp(content_type, "image/png") == 0)) + ret = ART_FMT_PNG; + + if (ret > 0) + { + DPRINTF(E_SPAM, L_ART, "Found internet stream artwork in %s (%s)\n", url, content_type); + cache_artwork_stash(ctx->evbuf, url, ret); + } + + out_kv: + keyval_clear(kv); + free(kv); + + out_url: + free(url); + + return ret; +} + +#ifdef HAVE_SPOTIFY_H +static int +source_item_spotify_get(struct artwork_ctx *ctx) +{ + AVFormatContext *src_ctx; + AVIOContext *avio; + AVInputFormat *ifmt; + struct evbuffer *raw; + struct evbuffer *evbuf; + int target_w; + int target_h; + int ret; + + raw = evbuffer_new(); + evbuf = evbuffer_new(); + if (!raw || !evbuf) + { + DPRINTF(E_LOG, L_ART, "Out of memory for Spotify evbuf\n"); + return ART_E_ERROR; + } + + ret = spotify_artwork_get(raw, ctx->dbmfi->path, ctx->max_w, ctx->max_h); + if (ret < 0) + { + DPRINTF(E_WARN, L_ART, "No artwork from Spotify for %s\n", ctx->dbmfi->path); + evbuffer_free(raw); + evbuffer_free(evbuf); + return ART_E_NONE; + } + + // Make a refbuf of raw for ffmpeg image size probing and possibly rescaling. + // We keep raw around in case rescaling is not necessary. +#ifdef HAVE_LIBEVENT2_OLD + uint8_t *buf = evbuffer_pullup(raw, -1); + if (!buf) + { + DPRINTF(E_LOG, L_ART, "Could not pullup raw artwork\n"); + goto out_free_evbuf; + } + + ret = evbuffer_add_reference(evbuf, buf, evbuffer_get_length(raw), NULL, NULL); +#else + ret = evbuffer_add_buffer_reference(evbuf, raw); +#endif + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not copy/ref raw image for ffmpeg\n"); + goto out_free_evbuf; + } + + // Now evbuf will be processed by ffmpeg, since it probably needs to be rescaled + src_ctx = avformat_alloc_context(); + if (!src_ctx) + { + DPRINTF(E_LOG, L_ART, "Out of memory for source context\n"); + goto out_free_evbuf; + } + + avio = avio_input_evbuffer_open(evbuf); + if (!avio) + { + DPRINTF(E_LOG, L_ART, "Could not alloc input evbuffer\n"); + goto out_free_ctx; + } + + src_ctx->pb = avio; + + ifmt = av_find_input_format("mjpeg"); + if (!ifmt) + { + DPRINTF(E_LOG, L_ART, "Could not find mjpeg input format\n"); + goto out_close_avio; + } + + ret = avformat_open_input(&src_ctx, NULL, ifmt, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not open input\n"); + goto out_close_avio; + } + + ret = avformat_find_stream_info(src_ctx, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not find stream info\n"); + goto out_close_input; + } + + ret = rescale_needed(src_ctx->streams[0]->codec, ctx->max_w, ctx->max_h, &target_w, &target_h); + if (!ret) + ret = evbuffer_add_buffer(ctx->evbuf, raw); + else + ret = artwork_rescale(ctx->evbuf, src_ctx, 0, target_w, target_h); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not add or rescale image to output evbuf\n"); + goto out_close_input; + } + + avformat_close_input(&src_ctx); + avio_evbuffer_close(avio); + evbuffer_free(evbuf); + evbuffer_free(raw); + + return ART_FMT_JPEG; + + out_close_input: + avformat_close_input(&src_ctx); + out_close_avio: + avio_evbuffer_close(avio); + out_free_ctx: + if (src_ctx) + avformat_free_context(src_ctx); + out_free_evbuf: + evbuffer_free(evbuf); + evbuffer_free(raw); + + return ART_E_ERROR; + +} +#else +static int +source_item_spotify_get(struct artwork_ctx *ctx) +{ + return ART_E_ERROR; +} +#endif + +/* First looks of the mfi->path is in any playlist, and if so looks in the dir + * of the playlist file (m3u et al) to see if there is any artwork. So if the + * playlist is /foo/bar.m3u it will look for /foo/bar.png and /foo/bar.jpg. + */ +static int +source_item_ownpl_get(struct artwork_ctx *ctx) +{ + struct query_params qp; + struct db_playlist_info dbpli; + char filter[PATH_MAX + 64]; + char *mfi_path; + int format; + int ret; + + ret = snprintf(filter, sizeof(filter), "(filepath = '%s')", ctx->dbmfi->path); + if ((ret < 0) || (ret >= sizeof(filter))) + { + DPRINTF(E_LOG, L_ART, "Artwork path exceeds PATH_MAX (%s)\n", ctx->dbmfi->path); + return ART_E_ERROR; + } + + memset(&qp, 0, sizeof(struct query_params)); + qp.type = Q_FIND_PL; + qp.filter = filter; + + ret = db_query_start(&qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not start ownpl query\n"); + return ART_E_ERROR; + } + + mfi_path = ctx->dbmfi->path; + + format = ART_E_NONE; + while (((ret = db_query_fetch_pl(&qp, &dbpli, 0)) == 0) && (dbpli.id) && (format == ART_E_NONE)) + { + if (!dbpli.path) + continue; + + ctx->dbmfi->path = dbpli.path; + format = source_item_own_get(ctx); + } + + ctx->dbmfi->path = mfi_path; + + if ((ret < 0) || (format < 0)) + format = ART_E_ERROR; + + db_query_end(&qp); + + return format; +} + + +/* --------------------------- SOURCE PROCESSING --------------------------- */ + +static int +process_items(struct artwork_ctx *ctx, int item_mode) +{ + struct db_media_file_info dbmfi; + uint32_t data_kind; + int i; + int ret; + + ret = db_query_start(&ctx->qp); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Could not start query (type=%d)\n", ctx->qp.type); + ctx->cache = NEVER; + return -1; + } + + while (((ret = db_query_fetch_file(&ctx->qp, &dbmfi)) == 0) && (dbmfi.id)) + { + // Save the first songalbumid, might need it for process_group() if this search doesn't give anything + if (!ctx->persistentid) + safe_atoi64(dbmfi.songalbumid, &ctx->persistentid); + + if (item_mode && !ctx->individual) + goto no_artwork; + + ret = (safe_atoi32(dbmfi.id, &ctx->id) < 0) || + (safe_atou32(dbmfi.data_kind, &data_kind) < 0) || + (data_kind > 30); + if (ret) + { + DPRINTF(E_LOG, L_ART, "Error converting dbmfi id or data_kind to number\n"); + continue; + } + + for (i = 0; artwork_item_source[i].handler; i++) + { + if ((artwork_item_source[i].data_kinds & (1 << data_kind)) == 0) + continue; + + // If just one handler says we should not cache a negative result then we obey that + if ((artwork_item_source[i].cache & ON_FAILURE) == 0) + ctx->cache = NEVER; + + DPRINTF(E_SPAM, L_ART, "Checking item source '%s'\n", artwork_item_source[i].name); + + ctx->dbmfi = &dbmfi; + ret = artwork_item_source[i].handler(ctx); + ctx->dbmfi = NULL; + + if (ret > 0) + { + DPRINTF(E_DBG, L_ART, "Artwork for '%s' found in source '%s'\n", dbmfi.title, artwork_item_source[i].name); + ctx->cache = (artwork_item_source[i].cache & ON_SUCCESS); + db_query_end(&ctx->qp); + return ret; + } + else if (ret == ART_E_ABORT) + { + DPRINTF(E_DBG, L_ART, "Source '%s' stopped search for artwork for '%s'\n", artwork_item_source[i].name, dbmfi.title); + ctx->cache = NEVER; + break; + } + else if (ret == ART_E_ERROR) + { + DPRINTF(E_LOG, L_ART, "Source '%s' returned an error for '%s'\n", artwork_item_source[i].name, dbmfi.title); + ctx->cache = NEVER; + } + } + } + + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Error fetching results\n"); + ctx->cache = NEVER; + } + + no_artwork: + db_query_end(&ctx->qp); + + return -1; +} + +static int +process_group(struct artwork_ctx *ctx) +{ + int i; + int ret; + + if (!ctx->persistentid) + { + DPRINTF(E_LOG, L_ART, "Bug! No persistentid in call to process_group()\n"); + ctx->cache = NEVER; + return -1; + } + + for (i = 0; artwork_group_source[i].handler; i++) + { + // If just one handler says we should not cache a negative result then we obey that + if ((artwork_group_source[i].cache & ON_FAILURE) == 0) + ctx->cache = NEVER; + + DPRINTF(E_SPAM, L_ART, "Checking group source '%s'\n", artwork_group_source[i].name); + + ret = artwork_group_source[i].handler(ctx); + if (ret > 0) + { + DPRINTF(E_DBG, L_ART, "Artwork for group %" PRIi64 " found in source '%s'\n", ctx->persistentid, artwork_group_source[i].name); + ctx->cache = (artwork_group_source[i].cache & ON_SUCCESS); + return ret; + } + else if (ret == ART_E_ABORT) + { + DPRINTF(E_DBG, L_ART, "Source '%s' stopped search for artwork for group %" PRIi64 "\n", artwork_group_source[i].name, ctx->persistentid); + ctx->cache = NEVER; + return -1; + } + else if (ret == ART_E_ERROR) + { + DPRINTF(E_LOG, L_ART, "Source '%s' returned an error for group %" PRIi64 "\n", artwork_group_source[i].name, ctx->persistentid); + ctx->cache = NEVER; + } + } + + ret = process_items(ctx, 0); + + return ret; +} + + +/* ------------------------------ ARTWORK API ------------------------------ */ + +int +artwork_get_item(struct evbuffer *evbuf, int id, int max_w, int max_h) +{ + struct artwork_ctx ctx; + char filter[32]; + int ret; + + DPRINTF(E_DBG, L_ART, "Artwork request for item %d\n", id); + + memset(&ctx, 0, sizeof(struct artwork_ctx)); + + ctx.qp.type = Q_ITEMS; + ctx.qp.filter = filter; + ctx.evbuf = evbuf; + ctx.max_w = max_w; + ctx.max_h = max_h; + ctx.cache = ON_FAILURE; + ctx.individual = cfg_getbool(cfg_getsec(cfg, "library"), "artwork_individual"); + + ret = snprintf(filter, sizeof(filter), "id = %d", id); + if ((ret < 0) || (ret >= sizeof(filter))) + { + DPRINTF(E_LOG, L_ART, "Could not build filter for file id %d; no artwork will be sent\n", id); + return -1; + } + + // Note: process_items will set ctx.persistentid for the following process_group() + // - and do nothing else if artwork_individual is not configured by user + ret = process_items(&ctx, 1); + if (ret > 0) + { + if (ctx.cache == ON_SUCCESS) + cache_artwork_add(CACHE_ARTWORK_INDIVIDUAL, id, max_w, max_h, ret, ctx.path, evbuf); + + return ret; + } + + ctx.qp.type = Q_GROUP_ITEMS; + ctx.qp.persistentid = ctx.persistentid; + + ret = process_group(&ctx); + if (ret > 0) + { + if (ctx.cache == ON_SUCCESS) + cache_artwork_add(CACHE_ARTWORK_GROUP, ctx.persistentid, max_w, max_h, ret, ctx.path, evbuf); + + return ret; + } + + DPRINTF(E_DBG, L_ART, "No artwork found for item %d\n", id); + + if (ctx.cache == ON_FAILURE) + cache_artwork_add(CACHE_ARTWORK_GROUP, ctx.persistentid, max_w, max_h, 0, "", evbuf); + + return -1; +} + +int +artwork_get_group(struct evbuffer *evbuf, int id, int max_w, int max_h) +{ + struct artwork_ctx ctx; + int ret; + + DPRINTF(E_DBG, L_ART, "Artwork request for group %d\n", id); + + memset(&ctx, 0, sizeof(struct artwork_ctx)); + + /* Get the persistent id for the given group id */ + ret = db_group_persistentid_byid(id, &ctx.persistentid); + if (ret < 0) + { + DPRINTF(E_LOG, L_ART, "Error fetching persistent id for group id %d\n", id); + return -1; + } + + ctx.qp.type = Q_GROUP_ITEMS; + ctx.qp.persistentid = ctx.persistentid; + ctx.evbuf = evbuf; + ctx.max_w = max_w; + ctx.max_h = max_h; + ctx.cache = ON_FAILURE; + ctx.individual = cfg_getbool(cfg_getsec(cfg, "library"), "artwork_individual"); + + ret = process_group(&ctx); + if (ret > 0) + { + if (ctx.cache == ON_SUCCESS) + cache_artwork_add(CACHE_ARTWORK_GROUP, ctx.persistentid, max_w, max_h, ret, ctx.path, evbuf); + + return ret; + } + + DPRINTF(E_DBG, L_ART, "No artwork found for group %d\n", id); + + if (ctx.cache == ON_FAILURE) + cache_artwork_add(CACHE_ARTWORK_GROUP, ctx.persistentid, max_w, max_h, 0, "", evbuf); + + return -1; +} + +/* Checks if the file is an artwork file */ +int +artwork_file_is_artwork(const char *filename) +{ + cfg_t *lib; + int n; + int i; + int j; + int ret; + char artwork[PATH_MAX]; + + lib = cfg_getsec(cfg, "library"); + n = cfg_size(lib, "artwork_basenames"); + + for (i = 0; i < n; i++) + { + for (j = 0; j < (sizeof(cover_extension) / sizeof(cover_extension[0])); j++) + { + ret = snprintf(artwork, sizeof(artwork), "%s.%s", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]); + if ((ret < 0) || (ret >= sizeof(artwork))) + { + DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX (%s.%s)\n", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]); + continue; + } + + if (strcmp(artwork, filename) == 0) + return 1; + } + + if (j < (sizeof(cover_extension) / sizeof(cover_extension[0]))) + break; + } + + return 0; +} diff --git a/src/avio_evbuffer.c b/src/avio_evbuffer.c index 47551331..454acd7a 100644 --- a/src/avio_evbuffer.c +++ b/src/avio_evbuffer.c @@ -125,6 +125,9 @@ avio_evbuffer_close(AVIOContext *s) { struct avio_evbuffer *ae; + if (!s) + return; + ae = (struct avio_evbuffer *)s->opaque; avio_flush(s); diff --git a/src/httpd.c b/src/httpd.c index af45ecc4..b165270e 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -167,7 +167,7 @@ stream_end(struct stream_ctx *st, int failed) event_free(st->ev); if (st->xcode) - transcode_cleanup(st->xcode); + transcode_cleanup(&st->xcode); else { free(st->buf); @@ -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) @@ -552,7 +551,7 @@ httpd_stream_file(struct evhttp_request *req, int id) stream_cb = stream_chunk_xcode_cb; - st->xcode = transcode_setup(mfi->data_kind, mfi->path, mfi->song_length, XCODE_PCM16_HEADER, &st->size); + st->xcode = transcode_setup(XCODE_PCM16_HEADER, mfi->data_kind, mfi->path, mfi->song_length, &st->size); if (!st->xcode) { DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n"); @@ -750,7 +749,7 @@ httpd_stream_file(struct evhttp_request *req, int id) if (st->evbuf) evbuffer_free(st->evbuf); if (st->xcode) - transcode_cleanup(st->xcode); + transcode_cleanup(&st->xcode); if (st->buf) free(st->buf); if (st->fd > 0) diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index b9340fca..721509c3 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -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,8 +299,8 @@ streaming_init(void) return -1; } - streaming_encode_ctx = transcode_encode_setup(decode_ctx, XCODE_MP3, NULL); - transcode_decode_cleanup(decode_ctx); + streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL, 0, 0); + transcode_decode_cleanup(&decode_ctx); if (!streaming_encode_ctx) { DPRINTF(E_LOG, L_STREAMING, "Will not be able to stream mp3, libav does not support mp3 encoding\n"); @@ -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"); @@ -399,7 +399,7 @@ streaming_init(void) close(streaming_pipe[0]); close(streaming_pipe[1]); pipe_fail: - transcode_encode_cleanup(streaming_encode_ctx); + transcode_encode_cleanup(&streaming_encode_ctx); return -1; } @@ -432,7 +432,7 @@ streaming_deinit(void) close(streaming_pipe[0]); close(streaming_pipe[1]); - transcode_encode_cleanup(streaming_encode_ctx); + transcode_encode_cleanup(&streaming_encode_ctx); evbuffer_free(streaming_encoded_data); free(streaming_silence_data); } diff --git a/src/input.c b/src/input.c index 863b4706..8c8dd3d5 100644 --- a/src/input.c +++ b/src/input.c @@ -483,7 +483,7 @@ input_flush(short *flags) pthread_mutex_unlock(&input_buffer.mutex); #ifdef DEBUG - DPRINTF(E_DBG, L_PLAYER, "Flush with flags %d\n", *flags); + DPRINTF(E_DBG, L_PLAYER, "Flushing %zu bytes with flags %d\n", len, *flags); #endif } diff --git a/src/inputs/file_http.c b/src/inputs/file_http.c index 220c7a94..7422eacb 100644 --- a/src/inputs/file_http.c +++ b/src/inputs/file_http.c @@ -31,7 +31,7 @@ static int setup(struct player_source *ps) { - ps->input_ctx = transcode_setup(ps->data_kind, ps->path, ps->len_ms, XCODE_PCM16_NOHEADER, NULL); + ps->input_ctx = transcode_setup(XCODE_PCM16_NOHEADER, ps->data_kind, ps->path, ps->len_ms, NULL); if (!ps->input_ctx) return -1; @@ -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; @@ -90,7 +90,9 @@ start(struct player_source *ps) static int stop(struct player_source *ps) { - transcode_cleanup(ps->input_ctx); + struct transcode_ctx *ctx = ps->input_ctx; + + transcode_cleanup(&ctx); ps->input_ctx = NULL; ps->setup_done = 0; diff --git a/src/library/filescanner_ffmpeg.c b/src/library/filescanner_ffmpeg.c index 975233de..d38adee4 100644 --- a/src/library/filescanner_ffmpeg.c +++ b/src/library/filescanner_ffmpeg.c @@ -240,17 +240,9 @@ static const struct metadata_map md_map_id3[] = static int -#if LIBAVUTIL_VERSION_MAJOR >= 52 || (LIBAVUTIL_VERSION_MAJOR == 51 && LIBAVUTIL_VERSION_MINOR >= 5) extract_metadata_core(struct media_file_info *mfi, AVDictionary *md, const struct metadata_map *md_map) -#else -extract_metadata_core(struct media_file_info *mfi, AVMetadata *md, const struct metadata_map *md_map) -#endif { -#if LIBAVUTIL_VERSION_MAJOR >= 52 || (LIBAVUTIL_VERSION_MAJOR == 51 && LIBAVUTIL_VERSION_MINOR >= 5) AVDictionaryEntry *mdt; -#else - AVMetadataTag *mdt; -#endif char **strval; uint32_t *intval; int mdcount; @@ -260,11 +252,7 @@ extract_metadata_core(struct media_file_info *mfi, AVMetadata *md, const struct #if 0 /* Dump all the metadata reported by ffmpeg */ mdt = NULL; -#if LIBAVUTIL_VERSION_MAJOR >= 52 || (LIBAVUTIL_VERSION_MAJOR == 51 && LIBAVUTIL_VERSION_MINOR >= 5) while ((mdt = av_dict_get(md, "", mdt, AV_DICT_IGNORE_SUFFIX)) != NULL) -#else - while ((mdt = av_metadata_get(md, "", mdt, AV_METADATA_IGNORE_SUFFIX)) != NULL) -#endif fprintf(stderr, " -> %s = %s\n", mdt->key, mdt->value); #endif @@ -273,11 +261,7 @@ extract_metadata_core(struct media_file_info *mfi, AVMetadata *md, const struct /* Extract actual metadata */ for (i = 0; md_map[i].key != NULL; i++) { -#if LIBAVUTIL_VERSION_MAJOR >= 52 || (LIBAVUTIL_VERSION_MAJOR == 51 && LIBAVUTIL_VERSION_MINOR >= 5) mdt = av_dict_get(md, md_map[i].key, NULL, 0); -#else - mdt = av_metadata_get(md, md_map[i].key, NULL, 0); -#endif if (mdt == NULL) continue; @@ -367,19 +351,16 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) AVDictionary *options; const struct metadata_map *extra_md_map; struct http_icy_metadata *icy_metadata; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) + enum AVMediaType codec_type; enum AVCodecID codec_id; enum AVCodecID video_codec_id; enum AVCodecID audio_codec_id; -#else - enum CodecID codec_id; - enum CodecID video_codec_id; - enum CodecID audio_codec_id; -#endif + enum AVSampleFormat sample_fmt; AVStream *video_stream; AVStream *audio_stream; char *path; int mdcount; + int sample_rate; int i; int ret; @@ -387,14 +368,13 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) options = NULL; path = strdup(file); -#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3) if (mfi->data_kind == DATA_KIND_HTTP) { -# ifndef HAVE_FFMPEG +#ifndef HAVE_FFMPEG // Without this, libav is slow to probe some internet streams ctx = avformat_alloc_context(); ctx->probesize = 64000; -# endif +#endif free(path); ret = http_stream_setup(&path, file); @@ -409,9 +389,7 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) if (options) av_dict_free(&options); -#else - ret = av_open_input_file(&ctx, path, NULL, 0, NULL); -#endif + if (ret != 0) { DPRINTF(E_WARN, L_SCAN, "Cannot open media file '%s': %s\n", path, err2str(ret)); @@ -422,20 +400,12 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) free(path); -#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3) ret = avformat_find_stream_info(ctx, NULL); -#else - ret = av_find_stream_info(ctx); -#endif if (ret < 0) { DPRINTF(E_WARN, L_SCAN, "Cannot get stream info of '%s': %s\n", path, err2str(ret)); -#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21) avformat_close_input(&ctx); -#else - av_close_input_file(ctx); -#endif return -1; } @@ -447,29 +417,28 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) DPRINTF(E_DBG, L_SCAN, "File has %d streams\n", ctx->nb_streams); /* Extract codec IDs, check for video */ -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) video_codec_id = AV_CODEC_ID_NONE; video_stream = NULL; audio_codec_id = AV_CODEC_ID_NONE; audio_stream = NULL; -#else - video_codec_id = CODEC_ID_NONE; - video_stream = NULL; - - audio_codec_id = CODEC_ID_NONE; - audio_stream = NULL; -#endif for (i = 0; i < ctx->nb_streams; i++) { - switch (ctx->streams[i]->codec->codec_type) - { -#if LIBAVCODEC_VERSION_MAJOR >= 53 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR >= 64) - case AVMEDIA_TYPE_VIDEO: +#if HAVE_DECL_AVCODEC_PARAMETERS_FROM_CONTEXT + codec_type = ctx->streams[i]->codecpar->codec_type; + codec_id = ctx->streams[i]->codecpar->codec_id; + sample_rate = ctx->streams[i]->codecpar->sample_rate; + sample_fmt = ctx->streams[i]->codecpar->format; #else - case CODEC_TYPE_VIDEO: + codec_type = ctx->streams[i]->codec->codec_type; + codec_id = ctx->streams[i]->codec->codec_id; + sample_rate = ctx->streams[i]->codec->sample_rate; + sample_fmt = ctx->streams[i]->codec->sample_fmt; #endif + switch (codec_type) + { + case AVMEDIA_TYPE_VIDEO: #if LIBAVFORMAT_VERSION_MAJOR >= 55 || (LIBAVFORMAT_VERSION_MAJOR == 54 && LIBAVFORMAT_VERSION_MINOR >= 6) if (ctx->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { @@ -487,21 +456,23 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) { DPRINTF(E_DBG, L_SCAN, "File has video (stream %d)\n", i); - mfi->has_video = 1; video_stream = ctx->streams[i]; - video_codec_id = video_stream->codec->codec_id; + video_codec_id = codec_id; + + mfi->has_video = 1; } break; -#if LIBAVCODEC_VERSION_MAJOR >= 53 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR >= 64) case AVMEDIA_TYPE_AUDIO: -#else - case CODEC_TYPE_AUDIO: -#endif if (!audio_stream) { audio_stream = ctx->streams[i]; - audio_codec_id = audio_stream->codec->codec_id; + audio_codec_id = codec_id; + + mfi->samplerate = sample_rate; + mfi->bits_per_sample = 8 * av_get_bytes_per_sample(sample_fmt); + if (mfi->bits_per_sample == 0) + mfi->bits_per_sample = av_get_bits_per_sample(codec_id); } break; @@ -510,19 +481,11 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) } } -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) if (audio_codec_id == AV_CODEC_ID_NONE) -#else - if (audio_codec_id == CODEC_ID_NONE) -#endif { DPRINTF(E_DBG, L_SCAN, "File has no audio streams, discarding\n"); -#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21) avformat_close_input(&ctx); -#else - av_close_input_file(ctx); -#endif return -1; } @@ -578,61 +541,26 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) http_icy_metadata_free(icy_metadata, 0); } - /* Get some more information on the audio stream */ - if (audio_stream) - { - if (audio_stream->codec->sample_rate != 0) - mfi->samplerate = audio_stream->codec->sample_rate; - - /* Try sample format first */ -#if LIBAVUTIL_VERSION_MAJOR >= 52 || (LIBAVUTIL_VERSION_MAJOR == 51 && LIBAVUTIL_VERSION_MINOR >= 4) - mfi->bits_per_sample = 8 * av_get_bytes_per_sample(audio_stream->codec->sample_fmt); -#elif LIBAVCODEC_VERSION_MAJOR >= 53 - mfi->bits_per_sample = av_get_bits_per_sample_fmt(audio_stream->codec->sample_fmt); -#else - mfi->bits_per_sample = av_get_bits_per_sample_format(audio_stream->codec->sample_fmt); -#endif - if (mfi->bits_per_sample == 0) - { - /* Try codec */ - mfi->bits_per_sample = av_get_bits_per_sample(audio_codec_id); - } - - DPRINTF(E_DBG, L_SCAN, "samplerate %d, bps %d\n", mfi->samplerate, mfi->bits_per_sample); - } - /* Check codec */ extra_md_map = NULL; codec_id = (mfi->has_video) ? video_codec_id : audio_codec_id; switch (codec_id) { -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_AAC: -#else - case CODEC_ID_AAC: -#endif DPRINTF(E_DBG, L_SCAN, "AAC\n"); mfi->type = strdup("m4a"); mfi->codectype = strdup("mp4a"); mfi->description = strdup("AAC audio file"); break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_ALAC: -#else - case CODEC_ID_ALAC: -#endif DPRINTF(E_DBG, L_SCAN, "ALAC\n"); mfi->type = strdup("m4a"); mfi->codectype = strdup("alac"); mfi->description = strdup("Apple Lossless audio file"); break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_FLAC: -#else - case CODEC_ID_FLAC: -#endif DPRINTF(E_DBG, L_SCAN, "FLAC\n"); mfi->type = strdup("flac"); mfi->codectype = strdup("flac"); @@ -641,37 +569,23 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) extra_md_map = md_map_vorbis; break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_APE: -#else - case CODEC_ID_APE: -#endif DPRINTF(E_DBG, L_SCAN, "APE\n"); mfi->type = strdup("ape"); mfi->codectype = strdup("ape"); mfi->description = strdup("Monkey's audio"); break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_MUSEPACK7: case AV_CODEC_ID_MUSEPACK8: -#else - case CODEC_ID_MUSEPACK7: - case CODEC_ID_MUSEPACK8: -#endif DPRINTF(E_DBG, L_SCAN, "Musepack\n"); mfi->type = strdup("mpc"); mfi->codectype = strdup("mpc"); mfi->description = strdup("Musepack audio file"); break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_MPEG4: /* Video */ case AV_CODEC_ID_H264: -#else - case CODEC_ID_MPEG4: /* Video */ - case CODEC_ID_H264: -#endif DPRINTF(E_DBG, L_SCAN, "MPEG4 video\n"); mfi->type = strdup("m4v"); mfi->codectype = strdup("mp4v"); @@ -680,11 +594,7 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) extra_md_map = md_map_tv; break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_MP3: -#else - case CODEC_ID_MP3: -#endif DPRINTF(E_DBG, L_SCAN, "MP3\n"); mfi->type = strdup("mp3"); mfi->codectype = strdup("mpeg"); @@ -693,11 +603,7 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) extra_md_map = md_map_id3; break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_VORBIS: -#else - case CODEC_ID_VORBIS: -#endif DPRINTF(E_DBG, L_SCAN, "VORBIS\n"); mfi->type = strdup("ogg"); mfi->codectype = strdup("ogg"); @@ -706,48 +612,30 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) extra_md_map = md_map_vorbis; break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_WMAV1: case AV_CODEC_ID_WMAV2: case AV_CODEC_ID_WMAVOICE: -#else - case CODEC_ID_WMAV1: - case CODEC_ID_WMAV2: - case CODEC_ID_WMAVOICE: -#endif DPRINTF(E_DBG, L_SCAN, "WMA Voice\n"); mfi->type = strdup("wma"); mfi->codectype = strdup("wmav"); mfi->description = strdup("WMA audio file"); break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_WMAPRO: -#else - case CODEC_ID_WMAPRO: -#endif DPRINTF(E_DBG, L_SCAN, "WMA Pro\n"); mfi->type = strdup("wmap"); mfi->codectype = strdup("wma"); mfi->description = strdup("WMA audio file"); break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_WMALOSSLESS: -#else - case CODEC_ID_WMALOSSLESS: -#endif DPRINTF(E_DBG, L_SCAN, "WMA Lossless\n"); mfi->type = strdup("wma"); mfi->codectype = strdup("wmal"); mfi->description = strdup("WMA audio file"); break; -#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35) case AV_CODEC_ID_PCM_S16LE ... AV_CODEC_ID_PCM_F64LE: -#else - case CODEC_ID_PCM_S16LE ... CODEC_ID_PCM_F64LE: -#endif if (strcmp(ctx->iformat->name, "aiff") == 0) { DPRINTF(E_DBG, L_SCAN, "AIFF\n"); @@ -818,11 +706,7 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) } skip_extract: -#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21) avformat_close_input(&ctx); -#else - av_close_input_file(ctx); -#endif if (mdcount == 0) DPRINTF(E_WARN, L_SCAN, "ffmpeg/libav could not extract any metadata\n"); diff --git a/src/logger.c b/src/logger.c index 6d7ae1f3..e4a7ecc7 100644 --- a/src/logger.c +++ b/src/logger.c @@ -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; diff --git a/src/outputs/raop.c b/src/outputs/raop.c index e0a5736c..ec4acc60 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -1271,6 +1271,8 @@ raop_make_sdp(struct raop_session *rs, struct evrtsp_request *req, char *address return -1; } + DPRINTF(E_INFO, L_RAOP, "Setting up AirPlay session %u (%s -> %s)\n", session_id, address, rs->address); + return 0; #undef SDP_PLD_FMT diff --git a/src/transcode.c b/src/transcode.c index 3d1de189..e1369ee4 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Espen Jurgensen + * Copyright (C) 2015-17 Espen Jurgensen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ #endif #include +#include #include #include @@ -32,8 +33,8 @@ #include #include #include - -#include "ffmpeg-compat.h" +#include +#include #include "logger.h" #include "conffile.h" @@ -57,20 +58,68 @@ static const char *itunes_codecs = "mpeg,mp4a,mp4v,alac,wav"; // Used for passing errors to DPRINTF (can't count on av_err2str being present) static char errbuf[64]; -struct filter_ctx { +// The settings struct will be filled out based on the profile enum +struct settings_ctx +{ + bool encode_video; + bool encode_audio; + + // Silence some log messages + bool silent; + + // Output format (for the muxer) + const char *format; + + // Input format (for the demuxer) + const char *in_format; + + // Audio settings + enum AVCodecID audio_codec; + const char *audio_codec_name; + int sample_rate; + uint64_t channel_layout; + int channels; + enum AVSampleFormat sample_format; + int byte_depth; + bool wavheader; + bool icy; + + // Video settings + enum AVCodecID video_codec; + const char *video_codec_name; + enum AVPixelFormat pix_fmt; + int height; + int width; +}; + +struct stream_ctx +{ + AVStream *stream; + AVCodecContext *codec; + AVFilterContext *buffersink_ctx; AVFilterContext *buffersrc_ctx; AVFilterGraph *filter_graph; + + // Used for seeking + int64_t prev_pts; + int64_t offset_pts; }; -struct decode_ctx { +struct decode_ctx +{ + // Settings derived from the profile + struct settings_ctx settings; + // Input format context AVFormatContext *ifmt_ctx; - // Will point to the max 3 streams that we will transcode - AVStream *audio_stream; - AVStream *video_stream; - AVStream *subtitle_stream; + // IO Context for non-file input + AVIOContext *avio; + + // Stream and decoder data + struct stream_ctx audio_stream; + struct stream_ctx video_stream; // Duration (used to make wav header) uint32_t duration; @@ -78,55 +127,45 @@ struct decode_ctx { // Data kind (used to determine if ICY metadata is relevant to look for) enum data_kind data_kind; - // Contains the most recent packet from av_read_frame - // Used for resuming after seek and for freeing correctly - // in transcode_decode() - AVPacket packet; - int resume; - int resume_offset; + // Set to true if we just seeked + bool resume; + + // 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; + + // Contains the most recent frame from avcodec_receive_frame() + AVFrame *decoded_frame; // Used to measure if av_read_frame is taking too long int64_t timestamp; }; -struct encode_ctx { +struct encode_ctx +{ + // Settings derived from the profile + struct settings_ctx settings; + // Output format context AVFormatContext *ofmt_ctx; - // We use filters to resample - struct filter_ctx *filter_ctx; + // Stream, filter and decoder data + struct stream_ctx audio_stream; + struct stream_ctx video_stream; // The ffmpeg muxer writes to this buffer using the avio_evbuffer interface struct evbuffer *obuf; - // Maps input stream number -> output stream number - // So if we are decoding audio stream 3 and encoding it to 0, then - // out_stream_map[3] is 0. A value of -1 means the stream is ignored. - int out_stream_map[MAX_STREAMS]; + // Contains the most recent packet from av_buffersink_get_frame() + AVFrame *filt_frame; - // Maps output stream number -> input stream number - unsigned int in_stream_map[MAX_STREAMS]; - - // Used for seeking - int64_t prev_pts[MAX_STREAMS]; - int64_t offset_pts[MAX_STREAMS]; - - // Settings for encoding and muxing - const char *format; - int encode_video; - - // Audio settings - enum AVCodecID audio_codec; - int sample_rate; - uint64_t channel_layout; - int channels; - enum AVSampleFormat sample_format; - int byte_depth; - - // Video settings - enum AVCodecID video_codec; - int video_height; - int video_width; + // Contains the most recent packet from avcodec_receive_packet() + AVPacket *encoded_pkt; // How many output bytes we have processed in total off_t total_bytes; @@ -136,60 +175,105 @@ struct encode_ctx { uint32_t icy_hash; // WAV header - int wavhdr; uint8_t header[44]; }; -struct transcode_ctx { +struct transcode_ctx +{ struct decode_ctx *decode_ctx; struct encode_ctx *encode_ctx; }; -struct decoded_frame -{ - AVFrame *frame; - unsigned int stream_index; -}; - /* -------------------------- PROFILE CONFIGURATION ------------------------ */ static int -init_profile(struct encode_ctx *ctx, enum transcode_profile profile) +init_settings(struct settings_ctx *settings, enum transcode_profile profile) { + const AVCodecDescriptor *codec_desc; + + memset(settings, 0, sizeof(struct settings_ctx)); + switch (profile) { - case XCODE_PCM16_NOHEADER: case XCODE_PCM16_HEADER: - ctx->encode_video = 0; - ctx->format = "s16le"; - ctx->audio_codec = AV_CODEC_ID_PCM_S16LE; - ctx->sample_rate = 44100; - ctx->channel_layout = AV_CH_LAYOUT_STEREO; - ctx->channels = 2; - ctx->sample_format = AV_SAMPLE_FMT_S16; - ctx->byte_depth = 2; // Bytes per sample = 16/8 - return 0; + settings->wavheader = 1; + case XCODE_PCM16_NOHEADER: + settings->encode_audio = 1; + settings->format = "s16le"; + settings->audio_codec = AV_CODEC_ID_PCM_S16LE; + settings->sample_rate = 44100; + settings->channel_layout = AV_CH_LAYOUT_STEREO; + 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: - ctx->encode_video = 0; - ctx->format = "mp3"; - ctx->audio_codec = AV_CODEC_ID_MP3; - ctx->sample_rate = 44100; - ctx->channel_layout = AV_CH_LAYOUT_STEREO; - ctx->channels = 2; - ctx->sample_format = AV_SAMPLE_FMT_S16P; - ctx->byte_depth = 2; // Bytes per sample = 16/8 - return 0; + settings->encode_audio = 1; + settings->format = "mp3"; + settings->audio_codec = AV_CODEC_ID_MP3; + settings->sample_rate = 44100; + settings->channel_layout = AV_CH_LAYOUT_STEREO; + settings->channels = 2; + settings->sample_format = AV_SAMPLE_FMT_S16P; + settings->byte_depth = 2; // Bytes per sample = 16/8 + break; - case XCODE_H264_AAC: - ctx->encode_video = 1; - return 0; + case XCODE_JPEG: + settings->encode_video = 1; + settings->silent = 1; + settings->format = "image2"; + settings->in_format = "mjpeg"; + 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; default: DPRINTF(E_LOG, L_XCODE, "Bug! Unknown transcoding profile\n"); return -1; } + + if (settings->audio_codec) + { + codec_desc = avcodec_descriptor_get(settings->audio_codec); + settings->audio_codec_name = codec_desc->name; + } + + if (settings->video_codec) + { + codec_desc = avcodec_descriptor_get(settings->video_codec); + settings->video_codec_name = codec_desc->name; + } + + return 0; +} + +static void +stream_settings_set(struct stream_ctx *s, struct settings_ctx *settings, enum AVMediaType type) +{ + if (type == AVMEDIA_TYPE_AUDIO) + { + s->codec->sample_rate = settings->sample_rate; + s->codec->channel_layout = settings->channel_layout; + s->codec->channels = settings->channels; + s->codec->sample_fmt = settings->sample_format; + s->codec->time_base = (AVRational){1, settings->sample_rate}; + } + else if (type == AVMEDIA_TYPE_VIDEO) + { + s->codec->height = settings->height; + s->codec->width = settings->width; + s->codec->pix_fmt = settings->pix_fmt; + s->codec->time_base = (AVRational){1, 25}; + } } @@ -229,7 +313,7 @@ make_wav_header(struct encode_ctx *ctx, struct decode_ctx *src_ctx, off_t *est_s else duration = 3 * 60 * 1000; /* 3 minutes, in ms */ - wav_len = ctx->channels * ctx->byte_depth * ctx->sample_rate * (duration / 1000); + wav_len = ctx->settings.channels * ctx->settings.byte_depth * ctx->settings.sample_rate * (duration / 1000); *est_size = wav_len + sizeof(ctx->header); @@ -238,28 +322,88 @@ make_wav_header(struct encode_ctx *ctx, struct decode_ctx *src_ctx, off_t *est_s memcpy(ctx->header + 8, "WAVEfmt ", 8); add_le32(ctx->header + 16, 16); add_le16(ctx->header + 20, 1); - add_le16(ctx->header + 22, ctx->channels); /* channels */ - add_le32(ctx->header + 24, ctx->sample_rate); /* samplerate */ - add_le32(ctx->header + 28, ctx->sample_rate * ctx->channels * ctx->byte_depth); /* byte rate */ - add_le16(ctx->header + 32, ctx->channels * ctx->byte_depth); /* block align */ - add_le16(ctx->header + 34, ctx->byte_depth * 8); /* bits per sample */ + add_le16(ctx->header + 22, ctx->settings.channels); /* channels */ + add_le32(ctx->header + 24, ctx->settings.sample_rate); /* samplerate */ + add_le32(ctx->header + 28, ctx->settings.sample_rate * ctx->settings.channels * ctx->settings.byte_depth); /* byte rate */ + add_le16(ctx->header + 32, ctx->settings.channels * ctx->settings.byte_depth); /* block align */ + add_le16(ctx->header + 34, ctx->settings.byte_depth * 8); /* bits per sample */ memcpy(ctx->header + 36, "data", 4); add_le32(ctx->header + 40, wav_len); } /* - * Returns true if in_stream is a stream we should decode, otherwise false + * Checks if this stream index is one that we are decoding * * @in ctx Decode context - * @in in_stream Pointer to AVStream - * @return True if stream should be decoded, otherwise false + * @in stream_index Index of stream to check + * @return Type of stream, unknown if we are not decoding the stream + */ +static enum AVMediaType +stream_find(struct decode_ctx *ctx, unsigned int stream_index) +{ + if (ctx->audio_stream.stream && (stream_index == ctx->audio_stream.stream->index)) + return AVMEDIA_TYPE_AUDIO; + + if (ctx->video_stream.stream && (stream_index == ctx->video_stream.stream->index)) + return AVMEDIA_TYPE_VIDEO; + + return AVMEDIA_TYPE_UNKNOWN; +} + +/* + * Adds a stream to an output + * + * @out ctx A pre-allocated stream ctx where we save stream and codec info + * @in output Output to add the stream to + * @in codec_id What kind of codec should we use + * @in codec_name Name of codec (only used for logging) + * @return Negative on failure, otherwise zero */ static int -decode_stream(struct decode_ctx *ctx, AVStream *in_stream) +stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id, const char *codec_name) { - return ((in_stream == ctx->audio_stream) || - (in_stream == ctx->video_stream) || - (in_stream == ctx->subtitle_stream)); + AVCodec *encoder; + int ret; + + encoder = avcodec_find_encoder(codec_id); + if (!encoder) + { + DPRINTF(E_LOG, L_XCODE, "Necessary encoder (%s) not found\n", codec_name); + return -1; + } + + CHECK_NULL(L_XCODE, s->stream = avformat_new_stream(ctx->ofmt_ctx, NULL)); + CHECK_NULL(L_XCODE, s->codec = avcodec_alloc_context3(encoder)); + + 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; + + ret = avcodec_open2(s->codec, NULL, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot open encoder (%s): %s\n", codec_name, err2str(ret)); + avcodec_free_context(&s->codec); + return -1; + } + + // Copy the codec parameters we just set to the stream, so the muxer knows them + ret = avcodec_parameters_from_context(s->stream->codecpar, s->codec); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot copy stream parameters (%s): %s\n", codec_name, err2str(ret)); + avcodec_free_context(&s->codec); + return -1; + } + + return 0; } /* @@ -285,329 +429,338 @@ static int decode_interrupt_cb(void *arg) return 0; } -/* Will read the next packet from the source, unless we are in resume mode, in - * which case the most recent packet will be returned, but with an adjusted data - * pointer. Use ctx->resume and ctx->resume_offset to make the function resume - * from the most recent packet. +/* Will read the next packet from the source, unless we are resuming after a + * seek in which case the most recent packet found by transcode_seek() will be + * returned. The packet will be put in ctx->packet. * - * @out packet Pointer to an already allocated AVPacket. The content of the - * packet will be updated, and packet->data is pointed to the data - * returned by av_read_frame(). The packet struct is owned by the - * caller, but *not* packet->data, so don't free the packet with - * av_free_packet()/av_packet_unref() - * @out stream Set to the input AVStream corresponding to the packet - * @out stream_index - * Set to the input stream index corresponding to the packet + * @out type Media type of packet * @in ctx Decode context * @return 0 if OK, < 0 on error or end of file */ static int -read_packet(AVPacket *packet, AVStream **stream, unsigned int *stream_index, struct decode_ctx *ctx) +read_packet(enum AVMediaType *type, struct decode_ctx *dec_ctx) { - AVStream *in_stream; int ret; + // We just seeked, so transcode_seek() will have found a new ctx->packet and + // we should just use start with that (if the stream is one are ok with) + if (dec_ctx->resume) + { + dec_ctx->resume = 0; + *type = stream_find(dec_ctx, dec_ctx->packet->stream_index); + if (*type != AVMEDIA_TYPE_UNKNOWN) + return 0; + } + do { - if (ctx->resume) + dec_ctx->timestamp = av_gettime(); + + av_packet_unref(dec_ctx->packet); + ret = av_read_frame(dec_ctx->ifmt_ctx, dec_ctx->packet); + if (ret < 0) { - // Copies packet struct, but not actual packet payload, and adjusts - // data pointer to somewhere inside the payload if resume_offset is set - *packet = ctx->packet; - packet->data += ctx->resume_offset; - packet->size -= ctx->resume_offset; - ctx->resume = 0; - } - else - { - // We are going to read a new packet from source, so now it is safe to - // discard the previous packet and reset resume_offset - av_packet_unref(&ctx->packet); - - ctx->resume_offset = 0; - ctx->timestamp = av_gettime(); - - ret = av_read_frame(ctx->ifmt_ctx, &ctx->packet); - if (ret < 0) - { - DPRINTF(E_WARN, L_XCODE, "Could not read frame: %s\n", err2str(ret)); - return ret; - } - - *packet = ctx->packet; + DPRINTF(E_WARN, L_XCODE, "Could not read frame: %s\n", err2str(ret)); + return ret; } - in_stream = ctx->ifmt_ctx->streams[packet->stream_index]; + *type = stream_find(dec_ctx, dec_ctx->packet->stream_index); } - while (!decode_stream(ctx, in_stream)); - - av_packet_rescale_ts(packet, in_stream->time_base, in_stream->codec->time_base); - - *stream = in_stream; - *stream_index = packet->stream_index; + while (*type == AVMEDIA_TYPE_UNKNOWN); return 0; } -static int -encode_write_frame(struct encode_ctx *ctx, AVFrame *filt_frame, unsigned int stream_index, int *got_frame) +// Prepares a packet from the encoder for muxing +static void +packet_prepare(AVPacket *pkt, struct stream_ctx *s) { - AVStream *out_stream; - AVPacket enc_pkt; - int ret; - int got_frame_local; + pkt->stream_index = s->stream->index; - if (!got_frame) - got_frame = &got_frame_local; - - out_stream = ctx->ofmt_ctx->streams[stream_index]; - - // Encode filtered frame - enc_pkt.data = NULL; - enc_pkt.size = 0; - av_init_packet(&enc_pkt); - - if (out_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) - ret = avcodec_encode_audio2(out_stream->codec, &enc_pkt, filt_frame, got_frame); - else if (out_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) - ret = avcodec_encode_video2(out_stream->codec, &enc_pkt, filt_frame, got_frame); - else - return -1; - - if (ret < 0) - return -1; - if (!(*got_frame)) - return 0; - - // Prepare packet for muxing - enc_pkt.stream_index = stream_index; - - // This "wonderful" peace of code makes sure that the timestamp never decreases, - // even if the user seeked backwards. The muxer will not accept decreasing - // timestamps - enc_pkt.pts += ctx->offset_pts[stream_index]; - if (enc_pkt.pts < ctx->prev_pts[stream_index]) + // This "wonderful" peace of code makes sure that the timestamp always increases, + // even if the user seeked backwards. The muxer will not accept non-increasing + // timestamps. + pkt->pts += s->offset_pts; + if (pkt->pts < s->prev_pts) { - ctx->offset_pts[stream_index] += ctx->prev_pts[stream_index] - enc_pkt.pts; - enc_pkt.pts = ctx->prev_pts[stream_index]; + s->offset_pts += s->prev_pts - pkt->pts; + pkt->pts = s->prev_pts; } - ctx->prev_pts[stream_index] = enc_pkt.pts; - enc_pkt.dts = enc_pkt.pts; //FIXME + s->prev_pts = pkt->pts; + pkt->dts = pkt->pts; //FIXME - av_packet_rescale_ts(&enc_pkt, out_stream->codec->time_base, out_stream->time_base); - - // Mux encoded frame - ret = av_interleaved_write_frame(ctx->ofmt_ctx, &enc_pkt); - return ret; + av_packet_rescale_ts(pkt, s->codec->time_base, s->stream->time_base); } -#if HAVE_DECL_AV_BUFFERSRC_ADD_FRAME_FLAGS && HAVE_DECL_AV_BUFFERSINK_GET_FRAME -static int -filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int stream_index) -{ - AVFrame *filt_frame; - int ret; - - // Push the decoded frame into the filtergraph - if (frame) - { - ret = av_buffersrc_add_frame_flags(ctx->filter_ctx[stream_index].buffersrc_ctx, frame, 0); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret)); - return -1; - } - } - - // Pull filtered frames from the filtergraph - while (1) - { - filt_frame = av_frame_alloc(); - if (!filt_frame) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for filt_frame\n"); - return -1; - } - - ret = av_buffersink_get_frame(ctx->filter_ctx[stream_index].buffersink_ctx, filt_frame); - if (ret < 0) - { - /* if no more frames for output - returns AVERROR(EAGAIN) - * if flushed and no more frames for output - returns AVERROR_EOF - * rewrite retcode to 0 to show it as normal procedure completion - */ - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) - ret = 0; - av_frame_free(&filt_frame); - break; - } - - filt_frame->pict_type = AV_PICTURE_TYPE_NONE; - ret = encode_write_frame(ctx, filt_frame, stream_index, NULL); - av_frame_free(&filt_frame); - if (ret < 0) - break; - } - - return ret; -} -#else -static int -filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int stream_index) -{ - AVFilterBufferRef *picref; - AVCodecContext *enc_ctx; - AVFrame *filt_frame; - int ret; - - enc_ctx = ctx->ofmt_ctx->streams[stream_index]->codec; - - // Push the decoded frame into the filtergraph - if (frame) - { - ret = av_buffersrc_write_frame(ctx->filter_ctx[stream_index].buffersrc_ctx, frame); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret)); - return -1; - } - } - - // Pull filtered frames from the filtergraph - while (1) - { - filt_frame = av_frame_alloc(); - if (!filt_frame) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for filt_frame\n"); - return -1; - } - - if (enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO && !(enc_ctx->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)) - ret = av_buffersink_read_samples(ctx->filter_ctx[stream_index].buffersink_ctx, &picref, enc_ctx->frame_size); - else - ret = av_buffersink_read(ctx->filter_ctx[stream_index].buffersink_ctx, &picref); - - if (ret < 0) - { - /* if no more frames for output - returns AVERROR(EAGAIN) - * if flushed and no more frames for output - returns AVERROR_EOF - * rewrite retcode to 0 to show it as normal procedure completion - */ - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) - ret = 0; - av_frame_free(&filt_frame); - break; - } - - avfilter_copy_buf_props(filt_frame, picref); - ret = encode_write_frame(ctx, filt_frame, stream_index, NULL); - av_frame_free(&filt_frame); - avfilter_unref_buffer(picref); - if (ret < 0) - break; - } - - return ret; -} -#endif - -/* Will step through each stream and feed the stream decoder with empty packets - * to see if the decoder has more frames lined up. Will return non-zero if a - * frame is found. Should be called until it stops returning anything. +/* + * Part 4+5 of the conversion chain: read -> decode -> filter -> encode -> write * - * @out frame AVFrame if there was anything to flush, otherwise undefined - * @out stream Set to the AVStream where a decoder returned a frame - * @out stream_index - * Set to the stream index of the stream returning a frame - * @in ctx Decode context - * @return Non-zero (true) if frame found, otherwise 0 (false) */ static int -flush_decoder(AVFrame *frame, AVStream **stream, unsigned int *stream_index, struct decode_ctx *ctx) -{ - AVStream *in_stream; - AVPacket dummypacket; - int got_frame; - int i; - - memset(&dummypacket, 0, sizeof(AVPacket)); - - for (i = 0; i < ctx->ifmt_ctx->nb_streams; i++) - { - in_stream = ctx->ifmt_ctx->streams[i]; - if (!decode_stream(ctx, in_stream)) - continue; - - if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) - avcodec_decode_audio4(in_stream->codec, frame, &got_frame, &dummypacket); - else - avcodec_decode_video2(in_stream->codec, frame, &got_frame, &dummypacket); - - if (!got_frame) - continue; - - DPRINTF(E_DBG, L_XCODE, "Flushing decoders produced a frame from stream %d\n", i); - - *stream = in_stream; - *stream_index = i; - return got_frame; - } - - return 0; -} - -static void -flush_encoder(struct encode_ctx *ctx, unsigned int stream_index) +encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_frame) { int ret; - int got_frame; - DPRINTF(E_DBG, L_XCODE, "Flushing output stream #%u encoder\n", stream_index); + // If filt_frame is null then flushing will be initiated by the codec + ret = avcodec_send_frame(s->codec, filt_frame); + if (ret < 0) + return ret; - if (!(ctx->ofmt_ctx->streams[stream_index]->codec->codec->capabilities & CODEC_CAP_DELAY)) - return; - - do + while (1) { - ret = encode_write_frame(ctx, NULL, stream_index, &got_frame); + ret = avcodec_receive_packet(s->codec, ctx->encoded_pkt); + if (ret < 0) + { + if (ret == AVERROR(EAGAIN)) + ret = 0; + + break; + } + + packet_prepare(ctx->encoded_pkt, s); + + ret = av_interleaved_write_frame(ctx->ofmt_ctx, ctx->encoded_pkt); + if (ret < 0) + break; } - while ((ret == 0) && got_frame); + + return ret; +} + +/* + * Part 3 of the conversion chain: read -> decode -> filter -> encode -> write + * + * transcode_encode() starts here since the caller already has a frame + * + */ +static int +filter_encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame) +{ + int ret; + + // Push the decoded frame into the filtergraph + if (frame) + { + ret = av_buffersrc_add_frame(s->buffersrc_ctx, frame); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret)); + return -1; + } + } + + // Pull filtered frames from the filtergraph and pass to encoder + while (1) + { + ret = av_buffersink_get_frame(s->buffersink_ctx, ctx->filt_frame); + if (ret < 0) + { + if (!frame) // We are flushing + ret = encode_write(ctx, s, NULL); + else if (ret == AVERROR(EAGAIN)) + ret = 0; + + break; + } + + ret = encode_write(ctx, s, ctx->filt_frame); + av_frame_unref(ctx->filt_frame); + if (ret < 0) + break; + } + + return ret; +} + +/* + * Part 2 of the conversion chain: read -> decode -> filter -> encode -> write + * + * If there is no encode_ctx the chain will aborted here + * + */ +static int +decode_filter_encode_write(struct transcode_ctx *ctx, struct stream_ctx *s, AVPacket *pkt, enum AVMediaType type) +{ + struct decode_ctx *dec_ctx = ctx->decode_ctx; + struct stream_ctx *out_stream = NULL; + int ret; + + ret = avcodec_send_packet(s->codec, pkt); + if (ret < 0) + return ret; + + if (ctx->encode_ctx) + { + if (type == AVMEDIA_TYPE_AUDIO) + out_stream = &ctx->encode_ctx->audio_stream; + else if (type == AVMEDIA_TYPE_VIDEO) + out_stream = &ctx->encode_ctx->video_stream; + else + return -1; + } + + while (1) + { + ret = avcodec_receive_frame(s->codec, dec_ctx->decoded_frame); + if (ret < 0) + { + if (ret == AVERROR(EAGAIN)) + ret = 0; + else if (out_stream) + ret = filter_encode_write(ctx->encode_ctx, out_stream, NULL); // Flush + + break; + } + + dec_ctx->got_frame = 1; + + if (!out_stream) + break; + + ret = filter_encode_write(ctx->encode_ctx, out_stream, dec_ctx->decoded_frame); + if (ret < 0) + break; + } + + return ret; +} + +/* + * Part 1 of the conversion chain: read -> decode -> filter -> encode -> write + * + * Will read exactly one packet from the input and put it in the chain. You + * cannot count on anything coming out of the other end from just one packet, + * so you probably should loop when calling this and check the contents of + * enc_ctx->obuf. + * + */ +static int +read_decode_filter_encode_write(struct transcode_ctx *ctx) +{ + struct decode_ctx *dec_ctx = ctx->decode_ctx; + enum AVMediaType type; + int ret; + + ret = read_packet(&type, dec_ctx); + if (ret < 0) + { + if (ret == AVERROR_EOF) + dec_ctx->eof = 1; + + if (dec_ctx->audio_stream.stream) + decode_filter_encode_write(ctx, &dec_ctx->audio_stream, NULL, AVMEDIA_TYPE_AUDIO); + if (dec_ctx->video_stream.stream) + decode_filter_encode_write(ctx, &dec_ctx->video_stream, NULL, AVMEDIA_TYPE_VIDEO); + + // Flush muxer + if (ctx->encode_ctx) + { + av_interleaved_write_frame(ctx->encode_ctx->ofmt_ctx, NULL); + av_write_trailer(ctx->encode_ctx->ofmt_ctx); + } + + return ret; + } + + if (type == AVMEDIA_TYPE_AUDIO) + ret = decode_filter_encode_write(ctx, &dec_ctx->audio_stream, dec_ctx->packet, type); + else if (type == AVMEDIA_TYPE_VIDEO) + ret = decode_filter_encode_write(ctx, &dec_ctx->video_stream, dec_ctx->packet, type); + + return ret; } /* --------------------------- INPUT/OUTPUT INIT --------------------------- */ -static int -open_input(struct decode_ctx *ctx, const char *path, int decode_video) +static AVCodecContext * +open_decoder(unsigned int *stream_index, struct decode_ctx *ctx, enum AVMediaType type) { - AVDictionary *options; + AVCodecContext *dec_ctx; AVCodec *decoder; - int stream_index; int ret; - options = NULL; - ctx->ifmt_ctx = avformat_alloc_context();; - if (!ctx->ifmt_ctx) + *stream_index = av_find_best_stream(ctx->ifmt_ctx, type, -1, -1, &decoder, 0); + if ((*stream_index < 0) || (!decoder)) { - DPRINTF(E_LOG, L_XCODE, "Out of memory for input format context\n"); - return -1; + 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, struct evbuffer *evbuf) +{ + AVDictionary *options = NULL; + AVCodecContext *dec_ctx; + AVInputFormat *ifmt; + unsigned int stream_index; + int ret; + + CHECK_NULL(L_XCODE, ctx->ifmt_ctx = avformat_alloc_context()); + + if (ctx->data_kind == DATA_KIND_HTTP) + { # ifndef HAVE_FFMPEG - // Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts - if (ctx->data_kind == DATA_KIND_HTTP) - ctx->ifmt_ctx->probesize = 64000; + // Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts + ctx->ifmt_ctx->probesize = 64000; # endif - if (ctx->data_kind == DATA_KIND_HTTP) - av_dict_set(&options, "icy", "1", 0); + av_dict_set(&options, "icy", "1", 0); + } // TODO Newest versions of ffmpeg have timeout and reconnect options we should use ctx->ifmt_ctx->interrupt_callback.callback = decode_interrupt_cb; ctx->ifmt_ctx->interrupt_callback.opaque = ctx; ctx->timestamp = av_gettime(); - ret = avformat_open_input(&ctx->ifmt_ctx, path, NULL, &options); + if (evbuf) + { + ifmt = av_find_input_format(ctx->settings.in_format); + if (!ifmt) + { + DPRINTF(E_LOG, L_XCODE, "Could not find input format: '%s'\n", ctx->settings.in_format); + return -1; + } + + CHECK_NULL(L_XCODE, ctx->avio = avio_input_evbuffer_open(evbuf)); + + ctx->ifmt_ctx->pb = ctx->avio; + ret = avformat_open_input(&ctx->ifmt_ctx, NULL, ifmt, &options); + } + else + { + ret = avformat_open_input(&ctx->ifmt_ctx, path, NULL, &options); + } if (options) av_dict_free(&options); @@ -631,61 +784,32 @@ open_input(struct decode_ctx *ctx, const char *path, int decode_video) goto out_fail; } - // 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)) + if (ctx->settings.encode_audio) { - DPRINTF(E_LOG, L_XCODE, "Did not find audio stream or suitable decoder for %s\n", path); - 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]; } - ctx->ifmt_ctx->streams[stream_index]->codec->request_sample_fmt = AV_SAMPLE_FMT_S16; - ctx->ifmt_ctx->streams[stream_index]->codec->request_channel_layout = AV_CH_LAYOUT_STEREO; - -// Disabled to see if it is still required -// if (decoder->capabilities & CODEC_CAP_TRUNCATED) -// ctx->ifmt_ctx->streams[stream_index]->codec->flags |= CODEC_FLAG_TRUNCATED; - - ret = avcodec_open2(ctx->ifmt_ctx->streams[stream_index]->codec, decoder, NULL); - if (ret < 0) + if (ctx->settings.encode_video) { - 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->audio_stream = ctx->ifmt_ctx->streams[stream_index]; - - // If no video then we are all done - if (!decode_video) - return 0; - - // 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)); - return 0; - } - - ret = avcodec_open2(ctx->ifmt_ctx->streams[stream_index]->codec, decoder, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret)); - return 0; - } - - ctx->video_stream = ctx->ifmt_ctx->streams[stream_index]; - - // Find a (random) subtitle stream which will be remuxed - stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, NULL, 0); - if (stream_index >= 0) - { - ctx->subtitle_stream = ctx->ifmt_ctx->streams[stream_index]; + ctx->video_stream.codec = dec_ctx; + ctx->video_stream.stream = ctx->ifmt_ctx->streams[stream_index]; } return 0; out_fail: + avio_evbuffer_close(ctx->avio); + avcodec_free_context(&ctx->audio_stream.codec); + avcodec_free_context(&ctx->video_stream.codec); avformat_close_input(&ctx->ifmt_ctx); return -1; @@ -694,122 +818,58 @@ open_input(struct decode_ctx *ctx, const char *path, int decode_video) static void close_input(struct decode_ctx *ctx) { - if (ctx->audio_stream) - avcodec_close(ctx->audio_stream->codec); - if (ctx->video_stream) - avcodec_close(ctx->video_stream->codec); - + avio_evbuffer_close(ctx->avio); + avcodec_free_context(&ctx->audio_stream.codec); + avcodec_free_context(&ctx->video_stream.codec); avformat_close_input(&ctx->ifmt_ctx); } static int open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) { - AVStream *out_stream; - AVStream *in_stream; - AVCodecContext *dec_ctx; - AVCodecContext *enc_ctx; - AVCodec *encoder; - const AVCodecDescriptor *codec_desc; - enum AVCodecID codec_id; + AVOutputFormat *oformat; int ret; - int i; - ctx->ofmt_ctx = NULL; - avformat_alloc_output_context2(&ctx->ofmt_ctx, NULL, ctx->format, NULL); - if (!ctx->ofmt_ctx) + oformat = av_guess_format(ctx->settings.format, NULL, NULL); + if (!oformat) { - DPRINTF(E_LOG, L_XCODE, "Could not create output context\n"); + DPRINTF(E_LOG, L_XCODE, "ffmpeg/libav could not find the '%s' output format\n", ctx->settings.format); return -1; } + // Clear AVFMT_NOFILE bit, it is not allowed as we will set our own AVIOContext + oformat->flags = ~AVFMT_NOFILE; + + CHECK_NULL(L_XCODE, ctx->ofmt_ctx = avformat_alloc_context()); + + ctx->ofmt_ctx->oformat = oformat; + ctx->obuf = evbuffer_new(); if (!ctx->obuf) { DPRINTF(E_LOG, L_XCODE, "Could not create output evbuffer\n"); - goto out_fail_evbuf; + goto out_free_output; } ctx->ofmt_ctx->pb = avio_output_evbuffer_open(ctx->obuf); if (!ctx->ofmt_ctx->pb) { DPRINTF(E_LOG, L_XCODE, "Could not create output avio pb\n"); - goto out_fail_pb; + goto out_free_evbuf; } - for (i = 0; i < src_ctx->ifmt_ctx->nb_streams; i++) - { - in_stream = src_ctx->ifmt_ctx->streams[i]; - if (!decode_stream(src_ctx, in_stream)) - { - ctx->out_stream_map[i] = -1; - continue; - } - - out_stream = avformat_new_stream(ctx->ofmt_ctx, NULL); - if (!out_stream) - { - DPRINTF(E_LOG, L_XCODE, "Failed allocating output stream\n"); - goto out_fail_stream; - } - - ctx->out_stream_map[i] = out_stream->index; - ctx->in_stream_map[out_stream->index] = i; - - dec_ctx = in_stream->codec; - enc_ctx = out_stream->codec; - - // TODO Enough to just remux subtitles? - if (dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) - { - avcodec_copy_context(enc_ctx, dec_ctx); - continue; - } - - if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) - codec_id = ctx->audio_codec; - else if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) - codec_id = ctx->video_codec; - else - continue; - - codec_desc = avcodec_descriptor_get(codec_id); - encoder = avcodec_find_encoder(codec_id); - if (!encoder) - { - if (codec_desc) - DPRINTF(E_LOG, L_XCODE, "Necessary encoder (%s) for input stream %u not found\n", codec_desc->name, i); - else - DPRINTF(E_LOG, L_XCODE, "Necessary encoder (unknown) for input stream %u not found\n", i); - goto out_fail_stream; - } - - if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) - { - enc_ctx->sample_rate = ctx->sample_rate; - enc_ctx->channel_layout = ctx->channel_layout; - enc_ctx->channels = ctx->channels; - enc_ctx->sample_fmt = ctx->sample_format; - enc_ctx->time_base = (AVRational){1, ctx->sample_rate}; - } - else - { - enc_ctx->height = ctx->video_height; - enc_ctx->width = ctx->video_width; - enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; //FIXME - enc_ctx->pix_fmt = avcodec_find_best_pix_fmt_of_list(encoder->pix_fmts, dec_ctx->pix_fmt, 1, NULL); - enc_ctx->time_base = dec_ctx->time_base; - } - - ret = avcodec_open2(enc_ctx, encoder, NULL); + if (ctx->settings.encode_audio) + { + ret = stream_add(ctx, &ctx->audio_stream, ctx->settings.audio_codec, ctx->settings.audio_codec_name); if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot open encoder (%s) for input stream #%u: %s\n", codec_desc->name, i, err2str(ret)); - goto out_fail_codec; - } + goto out_free_streams; + } - if (ctx->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) - enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; + if (ctx->settings.encode_video) + { + ret = stream_add(ctx, &ctx->video_stream, ctx->settings.video_codec, ctx->settings.video_codec_name); + if (ret < 0) + goto out_free_streams; } // Notice, this will not write WAV header (so we do that manually) @@ -817,24 +877,24 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Error writing header to output buffer: %s\n", err2str(ret)); - goto out_fail_write; + goto out_free_streams; + } + + if (ctx->settings.wavheader) + { + evbuffer_add(ctx->obuf, ctx->header, sizeof(ctx->header)); } return 0; - out_fail_write: - out_fail_codec: - for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) - { - enc_ctx = ctx->ofmt_ctx->streams[i]->codec; - if (enc_ctx) - avcodec_close(enc_ctx); - } - out_fail_stream: + out_free_streams: + avcodec_free_context(&ctx->audio_stream.codec); + avcodec_free_context(&ctx->video_stream.codec); + avio_evbuffer_close(ctx->ofmt_ctx->pb); - out_fail_pb: + out_free_evbuf: evbuffer_free(ctx->obuf); - out_fail_evbuf: + out_free_output: avformat_free_context(ctx->ofmt_ctx); return -1; @@ -843,245 +903,33 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) static void close_output(struct encode_ctx *ctx) { - int i; - - for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) - { - if (ctx->ofmt_ctx->streams[i]->codec) - avcodec_close(ctx->ofmt_ctx->streams[i]->codec); - } + avcodec_free_context(&ctx->audio_stream.codec); + avcodec_free_context(&ctx->video_stream.codec); avio_evbuffer_close(ctx->ofmt_ctx->pb); evbuffer_free(ctx->obuf); + avformat_free_context(ctx->ofmt_ctx); } -#if HAVE_DECL_AVFILTER_GRAPH_PARSE_PTR static int -open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, const char *filter_spec) +open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) { - AVFilter *buffersrc = NULL; - AVFilter *buffersink = NULL; - AVFilterContext *buffersrc_ctx = NULL; - AVFilterContext *buffersink_ctx = NULL; - AVFilterInOut *outputs = avfilter_inout_alloc(); - AVFilterInOut *inputs = avfilter_inout_alloc(); - AVFilterGraph *filter_graph = avfilter_graph_alloc(); + AVFilter *buffersrc; + AVFilter *format; + AVFilter *scale; + AVFilter *buffersink; + AVFilterContext *buffersrc_ctx; + AVFilterContext *format_ctx; + AVFilterContext *scale_ctx; + AVFilterContext *buffersink_ctx; + AVFilterGraph *filter_graph; char args[512]; int ret; - if (!outputs || !inputs || !filter_graph) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for filter_graph, input or output\n"); - goto out_fail; - } + CHECK_NULL(L_XCODE, filter_graph = avfilter_graph_alloc()); - if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) - { - buffersrc = avfilter_get_by_name("buffer"); - buffersink = avfilter_get_by_name("buffersink"); - if (!buffersrc || !buffersink) - { - DPRINTF(E_LOG, L_XCODE, "Filtering source or sink element not found\n"); - goto out_fail; - } - - snprintf(args, sizeof(args), - "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, - dec_ctx->time_base.num, dec_ctx->time_base.den, - dec_ctx->sample_aspect_ratio.num, - dec_ctx->sample_aspect_ratio.den); - - 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)); - goto out_fail; - } - - ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot create buffer sink: %s\n", err2str(ret)); - goto out_fail; - } - - ret = av_opt_set_bin(buffersink_ctx, "pix_fmts", (uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt), AV_OPT_SEARCH_CHILDREN); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot set output pixel format: %s\n", err2str(ret)); - goto out_fail; - } - } - else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) - { - buffersrc = avfilter_get_by_name("abuffer"); - buffersink = avfilter_get_by_name("abuffersink"); - if (!buffersrc || !buffersink) - { - DPRINTF(E_LOG, L_XCODE, "Filtering source or sink element not found\n"); - goto out_fail; - } - - if (!dec_ctx->channel_layout) - dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels); - - snprintf(args, sizeof(args), - "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, - dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate, - av_get_sample_fmt_name(dec_ctx->sample_fmt), - dec_ctx->channel_layout); - - 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)); - goto out_fail; - } - - ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink: %s\n", err2str(ret)); - goto out_fail; - } - - ret = av_opt_set_bin(buffersink_ctx, "sample_fmts", - (uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt), AV_OPT_SEARCH_CHILDREN); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot set output sample format: %s\n", err2str(ret)); - goto out_fail; - } - - ret = av_opt_set_bin(buffersink_ctx, "channel_layouts", - (uint8_t*)&enc_ctx->channel_layout, sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot set output channel layout: %s\n", err2str(ret)); - goto out_fail; - } - - ret = av_opt_set_bin(buffersink_ctx, "sample_rates", - (uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate), AV_OPT_SEARCH_CHILDREN); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot set output sample rate: %s\n", err2str(ret)); - goto out_fail; - } - } - else - { - DPRINTF(E_LOG, L_XCODE, "Bug! Unknown type passed to filter graph init\n"); - goto out_fail; - } - - /* Endpoints for the filter graph. */ - outputs->name = av_strdup("in"); - outputs->filter_ctx = buffersrc_ctx; - outputs->pad_idx = 0; - outputs->next = NULL; - inputs->name = av_strdup("out"); - inputs->filter_ctx = buffersink_ctx; - inputs->pad_idx = 0; - inputs->next = NULL; - if (!outputs->name || !inputs->name) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for outputs/inputs\n"); - goto out_fail; - } - - ret = avfilter_graph_parse_ptr(filter_graph, filter_spec, &inputs, &outputs, NULL); - if (ret < 0) - goto out_fail; - - ret = avfilter_graph_config(filter_graph, NULL); - if (ret < 0) - goto out_fail; - - /* Fill filtering context */ - filter_ctx->buffersrc_ctx = buffersrc_ctx; - filter_ctx->buffersink_ctx = buffersink_ctx; - filter_ctx->filter_graph = filter_graph; - - avfilter_inout_free(&inputs); - avfilter_inout_free(&outputs); - - return 0; - - out_fail: - avfilter_graph_free(&filter_graph); - avfilter_inout_free(&inputs); - avfilter_inout_free(&outputs); - - return -1; -} -#else -static int -open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, const char *filter_spec) -{ - - AVFilter *buffersrc = NULL; - AVFilter *format = NULL; - AVFilter *buffersink = NULL; - AVFilterContext *buffersrc_ctx = NULL; - AVFilterContext *format_ctx = NULL; - AVFilterContext *buffersink_ctx = NULL; - AVFilterGraph *filter_graph = avfilter_graph_alloc(); - char args[512]; - int ret; - - if (!filter_graph) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for filter_graph\n"); - goto out_fail; - } - - if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) - { - buffersrc = avfilter_get_by_name("buffer"); - format = avfilter_get_by_name("format"); - buffersink = avfilter_get_by_name("buffersink"); - if (!buffersrc || !format || !buffersink) - { - DPRINTF(E_LOG, L_XCODE, "Filtering source, format or sink element not found\n"); - goto out_fail; - } - - snprintf(args, sizeof(args), - "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, - dec_ctx->time_base.num, dec_ctx->time_base.den, - dec_ctx->sample_aspect_ratio.num, - dec_ctx->sample_aspect_ratio.den); - - 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)); - goto out_fail; - } - - snprintf(args, sizeof(args), - "pix_fmt=%d", - enc_ctx->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)); - goto out_fail; - } - - ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Cannot create buffer sink: %s\n", err2str(ret)); - goto out_fail; - } - } - else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) + if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) { buffersrc = avfilter_get_by_name("abuffer"); format = avfilter_get_by_name("aformat"); @@ -1092,31 +940,31 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte goto out_fail; } - if (!dec_ctx->channel_layout) - dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels); + if (!in_stream->codec->channel_layout) + in_stream->codec->channel_layout = av_get_default_channel_layout(in_stream->codec->channels); snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, - dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate, - av_get_sample_fmt_name(dec_ctx->sample_fmt), - dec_ctx->channel_layout); + in_stream->stream->time_base.num, in_stream->stream->time_base.den, + in_stream->codec->sample_rate, av_get_sample_fmt_name(in_stream->codec->sample_fmt), + in_stream->codec->channel_layout); 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; } snprintf(args, sizeof(args), "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%"PRIx64, - av_get_sample_fmt_name(enc_ctx->sample_fmt), enc_ctx->sample_rate, - enc_ctx->channel_layout); + av_get_sample_fmt_name(out_stream->codec->sample_fmt), out_stream->codec->sample_rate, + out_stream->codec->channel_layout); 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; } @@ -1126,6 +974,73 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink: %s\n", err2str(ret)); goto out_fail; } + + if ( (ret = avfilter_link(buffersrc_ctx, 0, format_ctx, 0)) < 0 || + (ret = avfilter_link(format_ctx, 0, buffersink_ctx, 0)) < 0 ) + { + DPRINTF(E_LOG, L_XCODE, "Error connecting audio filters: %s\n", err2str(ret)); + goto out_fail; + } + } + else if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) + { + buffersrc = avfilter_get_by_name("buffer"); + format = avfilter_get_by_name("format"); + scale = avfilter_get_by_name("scale"); + buffersink = avfilter_get_by_name("buffersink"); + if (!buffersrc || !format || !buffersink) + { + DPRINTF(E_LOG, L_XCODE, "Filtering source, format, scale or sink element not found\n"); + goto out_fail; + } + + snprintf(args, sizeof(args), + "width=%d:height=%d:pix_fmt=%s:time_base=%d/%d:sar=%d/%d", + in_stream->codec->width, in_stream->codec->height, av_get_pix_fmt_name(in_stream->codec->pix_fmt), + in_stream->stream->time_base.num, in_stream->stream->time_base.den, + in_stream->codec->sample_aspect_ratio.num, in_stream->codec->sample_aspect_ratio.den); + + 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): %s\n", args, err2str(ret)); + goto out_fail; + } + + snprintf(args, sizeof(args), + "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): %s\n", args, err2str(ret)); + goto out_fail; + } + + snprintf(args, sizeof(args), + "w=%d:h=%d", out_stream->codec->width, out_stream->codec->height); + + 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): %s\n", args, err2str(ret)); + goto out_fail; + } + + ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot create buffer sink: %s\n", err2str(ret)); + goto out_fail; + } + + if ( (ret = avfilter_link(buffersrc_ctx, 0, format_ctx, 0)) < 0 || + (ret = avfilter_link(format_ctx, 0, scale_ctx, 0)) < 0 || + (ret = avfilter_link(scale_ctx, 0, buffersink_ctx, 0)) < 0 ) + { + DPRINTF(E_LOG, L_XCODE, "Error connecting video filters: %s\n", err2str(ret)); + goto out_fail; + } } else { @@ -1133,20 +1048,14 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte goto out_fail; } - ret = avfilter_link(buffersrc_ctx, 0, format_ctx, 0); - if (ret >= 0) - ret = avfilter_link(format_ctx, 0, buffersink_ctx, 0); - if (ret < 0) - DPRINTF(E_LOG, L_XCODE, "Error connecting filters: %s\n", err2str(ret)); - ret = avfilter_graph_config(filter_graph, NULL); if (ret < 0) goto out_fail; /* Fill filtering context */ - filter_ctx->buffersrc_ctx = buffersrc_ctx; - filter_ctx->buffersink_ctx = buffersink_ctx; - filter_ctx->filter_graph = filter_graph; + out_stream->buffersrc_ctx = buffersrc_ctx; + out_stream->buffersink_ctx = buffersink_ctx; + out_stream->filter_graph = filter_graph; return 0; @@ -1155,44 +1064,22 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte return -1; } -#endif static int open_filters(struct encode_ctx *ctx, struct decode_ctx *src_ctx) { - AVCodecContext *enc_ctx; - AVCodecContext *dec_ctx; - const char *filter_spec; - unsigned int stream_index; - int i; int ret; - ctx->filter_ctx = av_malloc_array(ctx->ofmt_ctx->nb_streams, sizeof(*ctx->filter_ctx)); - if (!ctx->filter_ctx) + if (ctx->settings.encode_audio) { - DPRINTF(E_LOG, L_XCODE, "Out of memory for outputs/inputs\n"); - return -1; + ret = open_filter(&ctx->audio_stream, &src_ctx->audio_stream); + if (ret < 0) + goto out_fail; } - for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) + if (ctx->settings.encode_video) { - ctx->filter_ctx[i].buffersrc_ctx = NULL; - ctx->filter_ctx[i].buffersink_ctx = NULL; - ctx->filter_ctx[i].filter_graph = NULL; - - stream_index = ctx->in_stream_map[i]; - - enc_ctx = ctx->ofmt_ctx->streams[i]->codec; - dec_ctx = src_ctx->ifmt_ctx->streams[stream_index]->codec; - - if (enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO) - filter_spec = "null"; /* passthrough (dummy) filter for video */ - else if (enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO) - filter_spec = "anull"; /* passthrough (dummy) filter for audio */ - else - continue; - - ret = open_filter(&ctx->filter_ctx[i], dec_ctx, enc_ctx, filter_spec); + ret = open_filter(&ctx->video_stream, &src_ctx->video_stream); if (ret < 0) goto out_fail; } @@ -1200,12 +1087,8 @@ open_filters(struct encode_ctx *ctx, struct decode_ctx *src_ctx) return 0; out_fail: - for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) - { - if (ctx->filter_ctx && ctx->filter_ctx[i].filter_graph) - avfilter_graph_free(&ctx->filter_ctx[i].filter_graph); - } - av_free(ctx->filter_ctx); + avfilter_graph_free(&ctx->audio_stream.filter_graph); + avfilter_graph_free(&ctx->video_stream.filter_graph); return -1; } @@ -1213,14 +1096,8 @@ open_filters(struct encode_ctx *ctx, struct decode_ctx *src_ctx) static void close_filters(struct encode_ctx *ctx) { - int i; - - for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) - { - if (ctx->filter_ctx && ctx->filter_ctx[i].filter_graph) - avfilter_graph_free(&ctx->filter_ctx[i].filter_graph); - } - av_free(ctx->filter_ctx); + avfilter_graph_free(&ctx->audio_stream.filter_graph); + avfilter_graph_free(&ctx->video_stream.filter_graph); } @@ -1229,91 +1106,85 @@ close_filters(struct encode_ctx *ctx) /* Setup */ struct decode_ctx * -transcode_decode_setup(enum data_kind data_kind, const char *path, uint32_t song_length, int decode_video) +transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length) { struct decode_ctx *ctx; - ctx = calloc(1, sizeof(struct decode_ctx)); - if (!ctx) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for decode ctx\n"); - return NULL; - } + CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct decode_ctx))); + CHECK_NULL(L_XCODE, ctx->decoded_frame = av_frame_alloc()); + CHECK_NULL(L_XCODE, ctx->packet = av_packet_alloc()); ctx->duration = song_length; ctx->data_kind = data_kind; - if (open_input(ctx, path, decode_video) < 0) - { - free(ctx); - return NULL; - } - - av_init_packet(&ctx->packet); + if ((init_settings(&ctx->settings, profile) < 0) || (open_input(ctx, path, evbuf) < 0)) + goto fail_free; return ctx; + + fail_free: + av_packet_free(&ctx->packet); + av_frame_free(&ctx->decoded_frame); + free(ctx); + return NULL; } struct encode_ctx * -transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profile, 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; - ctx = calloc(1, sizeof(struct encode_ctx)); - if (!ctx) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for encode ctx\n"); - return NULL; - } + CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct encode_ctx))); + CHECK_NULL(L_XCODE, ctx->filt_frame = av_frame_alloc()); + CHECK_NULL(L_XCODE, ctx->encoded_pkt = av_packet_alloc()); - if ((init_profile(ctx, profile) < 0) || (open_output(ctx, src_ctx) < 0)) - { - free(ctx); - return NULL; - } + 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); + + if (open_output(ctx, src_ctx) < 0) + goto fail_free; if (open_filters(ctx, src_ctx) < 0) - { - close_output(ctx); - free(ctx); - return NULL; - } + goto fail_close; - if (src_ctx->data_kind == DATA_KIND_HTTP) - ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->channels * ctx->byte_depth * ctx->sample_rate; - - if (profile == XCODE_PCM16_HEADER) - { - ctx->wavhdr = 1; - make_wav_header(ctx, src_ctx, est_size); - } + 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; + + fail_close: + close_output(ctx); + fail_free: + av_packet_free(&ctx->encoded_pkt); + av_frame_free(&ctx->filt_frame); + free(ctx); + return NULL; } struct transcode_ctx * -transcode_setup(enum data_kind data_kind, const char *path, uint32_t song_length, enum transcode_profile profile, off_t *est_size) +transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size) { struct transcode_ctx *ctx; - ctx = malloc(sizeof(struct transcode_ctx)); - if (!ctx) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for transcode ctx\n"); - return NULL; - } + CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct transcode_ctx))); - ctx->decode_ctx = transcode_decode_setup(data_kind, path, song_length, profile & XCODE_HAS_VIDEO); + ctx->decode_ctx = transcode_decode_setup(profile, data_kind, path, NULL, song_length); if (!ctx->decode_ctx) { free(ctx); return NULL; } - ctx->encode_ctx = transcode_encode_setup(ctx->decode_ctx, profile, 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); + transcode_decode_cleanup(&ctx->decode_ctx); free(ctx); return NULL; } @@ -1325,41 +1196,49 @@ struct decode_ctx * transcode_decode_setup_raw(void) { struct decode_ctx *ctx; - struct AVCodec *decoder; + AVCodec *decoder; + int ret; - ctx = calloc(1, sizeof(struct decode_ctx)); - if (!ctx) + CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct decode_ctx))); + + if (init_settings(&ctx->settings, XCODE_PCM16_NOHEADER) < 0) { - DPRINTF(E_LOG, L_XCODE, "Out of memory for decode ctx\n"); - return NULL; + goto out_free_ctx; } - ctx->ifmt_ctx = avformat_alloc_context(); - if (!ctx->ifmt_ctx) + // In raw mode we won't actually need to read or decode, but we still setup + // the decode_ctx because transcode_encode_setup() gets info about the input + // through this structure (TODO dont' do that) + decoder = avcodec_find_decoder(ctx->settings.audio_codec); + if (!decoder) { - DPRINTF(E_LOG, L_XCODE, "Out of memory for decode format ctx\n"); - free(ctx); - return NULL; + DPRINTF(E_LOG, L_XCODE, "Could not find decoder for: %s\n", ctx->settings.audio_codec_name); + goto out_free_ctx; } - decoder = avcodec_find_decoder(AV_CODEC_ID_PCM_S16LE); + CHECK_NULL(L_XCODE, ctx->ifmt_ctx = avformat_alloc_context()); + CHECK_NULL(L_XCODE, ctx->audio_stream.codec = avcodec_alloc_context3(decoder)); + CHECK_NULL(L_XCODE, ctx->audio_stream.stream = avformat_new_stream(ctx->ifmt_ctx, NULL)); - ctx->audio_stream = avformat_new_stream(ctx->ifmt_ctx, decoder); - if (!ctx->audio_stream) + stream_settings_set(&ctx->audio_stream, &ctx->settings, decoder->type); + + // Copy the data we just set to the structs we will be querying later, e.g. in open_filter + ctx->audio_stream.stream->time_base = ctx->audio_stream.codec->time_base; + ret = avcodec_parameters_from_context(ctx->audio_stream.stream->codecpar, ctx->audio_stream.codec); + if (ret < 0) { - DPRINTF(E_LOG, L_XCODE, "Could not create stream with PCM16 decoder\n"); - avformat_free_context(ctx->ifmt_ctx); - free(ctx); - return NULL; + DPRINTF(E_LOG, L_XCODE, "Cannot copy stream parameters (%s): %s\n", ctx->settings.audio_codec_name, err2str(ret)); + goto out_free_codec; } - ctx->audio_stream->codec->time_base.num = 1; - ctx->audio_stream->codec->time_base.den = 44100; - ctx->audio_stream->codec->sample_rate = 44100; - ctx->audio_stream->codec->sample_fmt = AV_SAMPLE_FMT_S16; - ctx->audio_stream->codec->channel_layout = AV_CH_LAYOUT_STEREO; - return ctx; + + out_free_codec: + avcodec_free_context(&ctx->audio_stream.codec); + avformat_free_context(ctx->ifmt_ctx); + out_free_ctx: + free(ctx); + return NULL; } int @@ -1447,269 +1326,193 @@ transcode_needed(const char *user_agent, const char *client_codecs, char *file_c /* Cleanup */ void -transcode_decode_cleanup(struct decode_ctx *ctx) +transcode_decode_cleanup(struct decode_ctx **ctx) { - av_packet_unref(&ctx->packet); - close_input(ctx); - free(ctx); + if (!(*ctx)) + return; + + close_input(*ctx); + + av_packet_free(&(*ctx)->packet); + av_frame_free(&(*ctx)->decoded_frame); + free(*ctx); + *ctx = NULL; } void -transcode_encode_cleanup(struct encode_ctx *ctx) +transcode_encode_cleanup(struct encode_ctx **ctx) { - int i; + if (!*ctx) + return; - // Flush filters and encoders - for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) - { - if (!ctx->filter_ctx[i].filter_graph) - continue; - filter_encode_write_frame(ctx, NULL, i); - flush_encoder(ctx, i); - } + close_filters(*ctx); + close_output(*ctx); - av_write_trailer(ctx->ofmt_ctx); - - close_filters(ctx); - close_output(ctx); - free(ctx); + av_packet_free(&(*ctx)->encoded_pkt); + av_frame_free(&(*ctx)->filt_frame); + free(*ctx); + *ctx = NULL; } void -transcode_cleanup(struct transcode_ctx *ctx) +transcode_cleanup(struct transcode_ctx **ctx) { - transcode_encode_cleanup(ctx->encode_ctx); - transcode_decode_cleanup(ctx->decode_ctx); - free(ctx); -} - -void -transcode_decoded_free(struct decoded_frame *decoded) -{ - av_frame_free(&decoded->frame); - free(decoded); + transcode_encode_cleanup(&(*ctx)->encode_ctx); + transcode_decode_cleanup(&(*ctx)->decode_ctx); + free(*ctx); + *ctx = NULL; } /* Encoding, decoding and transcoding */ - int -transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) +transcode_decode(void **frame, struct decode_ctx *dec_ctx) { - AVPacket packet; - AVStream *in_stream; - AVFrame *frame; - unsigned int stream_index; - int got_frame; - int retry; + struct transcode_ctx ctx; int ret; - int used; - // Alloc the frame we will return on success - frame = av_frame_alloc(); - if (!frame) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for decode frame\n"); + if (dec_ctx->got_frame) + DPRINTF(E_LOG, L_XCODE, "Bug! Currently no support for multiple calls to transcode_decode()\n"); - return -1; - } + ctx.decode_ctx = dec_ctx; + ctx.encode_ctx = NULL; - // Loop until we either fail or get a frame - retry = 0; do { - ret = read_packet(&packet, &in_stream, &stream_index, ctx); - if (ret < 0) - { - // Some decoders need to be flushed, meaning the decoder is to be called - // with empty input until no more frames are returned - DPRINTF(E_DBG, L_XCODE, "Could not read packet, will flush decoders\n"); - - got_frame = flush_decoder(frame, &in_stream, &stream_index, ctx); - if (got_frame) - break; - - av_frame_free(&frame); - if (ret == AVERROR_EOF) - return 0; - else - return -1; - } - - // "used" will tell us how much of the packet was decoded. We may - // not get a frame because of insufficient input, in which case we loop to - // read another packet. - if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) - used = avcodec_decode_audio4(in_stream->codec, frame, &got_frame, &packet); - else - used = avcodec_decode_video2(in_stream->codec, frame, &got_frame, &packet); - - // decoder returned an error, but maybe the packet was just a bad apple, - // so let's try MAX_BAD_PACKETS times before giving up - if (used < 0) - { - DPRINTF(E_DBG, L_XCODE, "Couldn't decode packet\n"); - - retry += 1; - if (retry < MAX_BAD_PACKETS) - continue; - - DPRINTF(E_LOG, L_XCODE, "Couldn't decode packet after %i retries\n", MAX_BAD_PACKETS); - - av_frame_free(&frame); - return -1; - } - - // decoder didn't process the entire packet, so flag a resume, meaning - // that the next read_packet() will return this same packet, but where the - // data pointer is adjusted with an offset - if (used < packet.size) - { - DPRINTF(E_SPAM, L_XCODE, "Decoder did not finish packet, packet will be resumed\n"); - - ctx->resume_offset += used; - ctx->resume = 1; - } + // This function stops after decoding because ctx->encode_ctx is NULL + ret = read_decode_filter_encode_write(&ctx); } - while (!got_frame); + while ((ret == 0) && (!dec_ctx->got_frame)); - if (got_frame > 0) - { - // Return the decoded frame and stream index - *decoded = malloc(sizeof(struct decoded_frame)); - if (!(*decoded)) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded result\n"); + if (ret < 0) + return -1; - av_frame_free(&frame); - return -1; - } + *frame = dec_ctx->decoded_frame; - (*decoded)->frame = frame; - (*decoded)->stream_index = stream_index; - } + if (dec_ctx->eof) + return 0; - return got_frame; + 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) { - int stream_index; - int encoded_length; + AVFrame *f = frame; + struct stream_ctx *s; + size_t start_length; int ret; - encoded_length = 0; + start_length = evbuffer_get_length(ctx->obuf); - stream_index = ctx->out_stream_map[decoded->stream_index]; - if (stream_index < 0) - return -1; - - if (ctx->wavhdr) + // 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 (f->width && f->height) + s = &ctx->video_stream; + else { - encoded_length += sizeof(ctx->header); - evbuffer_add(evbuf, ctx->header, sizeof(ctx->header)); - ctx->wavhdr = 0; + DPRINTF(E_LOG, L_XCODE, "Bug! Encoder could not detect frame type\n"); + return -1; } - ret = filter_encode_write_frame(ctx, decoded->frame, stream_index); + ret = filter_encode_write(ctx, s, f); if (ret < 0) { - DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", err2str(ret)); + DPRINTF(E_LOG, L_XCODE, "Error occurred while encoding: %s\n", err2str(ret)); return ret; } - encoded_length += evbuffer_get_length(ctx->obuf); + // Flush + if (eof) + { + filter_encode_write(ctx, s, NULL); + av_write_trailer(ctx->ofmt_ctx); + } + + ret = evbuffer_get_length(ctx->obuf) - start_length; + evbuffer_add_buffer(evbuf, ctx->obuf); - return encoded_length; + return ret; } int -transcode(struct evbuffer *evbuf, int wanted, struct transcode_ctx *ctx, int *icy_timer) +transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int want_bytes) { - struct decoded_frame *decoded; - int processed; + size_t start_length; + int processed = 0; int ret; - *icy_timer = 0; + if (icy_timer) + *icy_timer = 0; - processed = 0; - while (processed < wanted) + if (ctx->decode_ctx->eof) + return 0; + + start_length = evbuffer_get_length(ctx->encode_ctx->obuf); + + do { - ret = transcode_decode(&decoded, ctx->decode_ctx); - if (ret <= 0) - return ret; - - ret = transcode_encode(evbuf, decoded, ctx->encode_ctx); - transcode_decoded_free(decoded); - if (ret < 0) - return -1; - - processed += ret; + ret = read_decode_filter_encode_write(ctx); + processed = evbuffer_get_length(ctx->encode_ctx->obuf) - start_length; } + while ((ret == 0) && (!want_bytes || (processed < want_bytes))); + + 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 < 0) && (ret != AVERROR_EOF)) + return ret; + 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->stream_index = 0; - 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; -/* TODO remux this frame without reencoding - av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base); - - ret = av_interleaved_write_frame(ctx->ofmt_ctx, &packet); - if (ret < 0) - goto end;*/ + av_frame_free(&f); +} /* Seeking */ @@ -1717,78 +1520,75 @@ transcode_raw2frame(uint8_t *data, size_t size) int transcode_seek(struct transcode_ctx *ctx, int ms) { - struct decode_ctx *decode_ctx; - AVStream *in_stream; + struct decode_ctx *dec_ctx = ctx->decode_ctx; + struct stream_ctx *s; int64_t start_time; int64_t target_pts; int64_t got_pts; int got_ms; int ret; - int i; - decode_ctx = ctx->decode_ctx; - in_stream = ctx->decode_ctx->audio_stream; - start_time = in_stream->start_time; + s = &dec_ctx->audio_stream; + if (!s->stream) + { + DPRINTF(E_LOG, L_XCODE, "Could not seek in non-audio input\n"); + return -1; + } + + start_time = s->stream->start_time; target_pts = ms; target_pts = target_pts * AV_TIME_BASE / 1000; - target_pts = av_rescale_q(target_pts, AV_TIME_BASE_Q, in_stream->time_base); + target_pts = av_rescale_q(target_pts, AV_TIME_BASE_Q, s->stream->time_base); if ((start_time != AV_NOPTS_VALUE) && (start_time > 0)) target_pts += start_time; - ret = av_seek_frame(decode_ctx->ifmt_ctx, in_stream->index, target_pts, AVSEEK_FLAG_BACKWARD); + ret = av_seek_frame(dec_ctx->ifmt_ctx, s->stream->index, target_pts, AVSEEK_FLAG_BACKWARD); if (ret < 0) { DPRINTF(E_WARN, L_XCODE, "Could not seek into stream: %s\n", err2str(ret)); return -1; } - for (i = 0; i < decode_ctx->ifmt_ctx->nb_streams; i++) - { - if (decode_stream(decode_ctx, decode_ctx->ifmt_ctx->streams[i])) - avcodec_flush_buffers(decode_ctx->ifmt_ctx->streams[i]->codec); -// avcodec_flush_buffers(ctx->ofmt_ctx->streams[stream_nb]->codec); - } + avcodec_flush_buffers(s->codec); // Fast forward until first packet with a timestamp is found - in_stream->codec->skip_frame = AVDISCARD_NONREF; + s->codec->skip_frame = AVDISCARD_NONREF; while (1) { - av_packet_unref(&decode_ctx->packet); + dec_ctx->timestamp = av_gettime(); - decode_ctx->timestamp = av_gettime(); - - ret = av_read_frame(decode_ctx->ifmt_ctx, &decode_ctx->packet); + av_packet_unref(dec_ctx->packet); + ret = av_read_frame(dec_ctx->ifmt_ctx, dec_ctx->packet); if (ret < 0) { DPRINTF(E_WARN, L_XCODE, "Could not read more data while seeking: %s\n", err2str(ret)); - in_stream->codec->skip_frame = AVDISCARD_DEFAULT; + s->codec->skip_frame = AVDISCARD_DEFAULT; return -1; } - if (decode_ctx->packet.stream_index != in_stream->index) + if (stream_find(dec_ctx, dec_ctx->packet->stream_index) == AVMEDIA_TYPE_UNKNOWN) continue; // Need a pts to return the real position - if (decode_ctx->packet.pts == AV_NOPTS_VALUE) + if (dec_ctx->packet->pts == AV_NOPTS_VALUE) continue; break; } - in_stream->codec->skip_frame = AVDISCARD_DEFAULT; + s->codec->skip_frame = AVDISCARD_DEFAULT; - // Tell transcode_decode() to resume with ctx->packet - decode_ctx->resume = 1; - decode_ctx->resume_offset = 0; + // Tell read_packet() to resume with dec_ctx->packet + dec_ctx->resume = 1; // Compute position in ms from pts - got_pts = decode_ctx->packet.pts; + got_pts = dec_ctx->packet->pts; if ((start_time != AV_NOPTS_VALUE) && (start_time > 0)) got_pts -= start_time; - got_pts = av_rescale_q(got_pts, in_stream->time_base, AV_TIME_BASE_Q); + got_pts = av_rescale_q(got_pts, s->stream->time_base, AV_TIME_BASE_Q); got_ms = got_pts / (AV_TIME_BASE / 1000); // Since negative return would mean error, we disallow it here @@ -1800,6 +1600,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 */ diff --git a/src/transcode.h b/src/transcode.h index 3a8614f0..3e6cfc86 100644 --- a/src/transcode.h +++ b/src/transcode.h @@ -6,35 +6,32 @@ #include "db.h" #include "http.h" -#define XCODE_WAVHEADER (1 << 14) -#define XCODE_HAS_VIDEO (1 << 15) - enum transcode_profile { - // Transcodes the best available audio stream into PCM16 (does not add wav header) - XCODE_PCM16_NOHEADER = 1, - // Transcodes the best available audio stream into PCM16 (with wav header) - XCODE_PCM16_HEADER = XCODE_WAVHEADER | 2, - // Transcodes the best available audio stream into MP3 - XCODE_MP3 = 3, - // Transcodes video + audio + subtitle streams (not tested - for future use) - XCODE_H264_AAC = XCODE_HAS_VIDEO | 4, + // Transcodes the best audio stream into PCM16 (does not add wav header) + XCODE_PCM16_NOHEADER, + // Transcodes the best audio stream into PCM16 (with wav header) + XCODE_PCM16_HEADER, + // Transcodes the best audio stream into MP3 + XCODE_MP3, + // Transcodes the best video stream into JPEG/PNG + XCODE_JPEG, + XCODE_PNG, }; struct decode_ctx; struct encode_ctx; struct transcode_ctx; -struct decoded_frame; // Setting up struct decode_ctx * -transcode_decode_setup(enum data_kind data_kind, const char *path, uint32_t song_length, int decode_video); +transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length); struct encode_ctx * -transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profile, 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 data_kind data_kind, const char *path, uint32_t song_length, enum transcode_profile profile, off_t *est_size); +transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size); struct decode_ctx * transcode_decode_setup_raw(void); @@ -44,58 +41,82 @@ transcode_needed(const char *user_agent, const char *client_codecs, char *file_c // Cleaning up void -transcode_decode_cleanup(struct decode_ctx *ctx); +transcode_decode_cleanup(struct decode_ctx **ctx); void -transcode_encode_cleanup(struct encode_ctx *ctx); +transcode_encode_cleanup(struct encode_ctx **ctx); void -transcode_cleanup(struct transcode_ctx *ctx); - -void -transcode_decoded_free(struct decoded_frame *decoded); +transcode_cleanup(struct transcode_ctx **ctx); // 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(). - * @in ctx Decode context - * @return Positive if OK, negative if error, 0 if EOF + * @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 wanted Bytes that the caller wants processed - * @in ctx Encode context - * @return Length of evbuf if OK, negative if error + * @out evbuf An evbuffer filled with remuxed data + * @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 the next packet from the input. +/* Demuxes, decodes, encodes and remuxes from the input. * - * @out evbuf An evbuffer filled with remuxed data - * @in wanted Bytes that the caller wants processed - * @in ctx Transcode context - * @out icy_timer True if METADATA_ICY_INTERVAL has elapsed - * @return Bytes processed if OK, negative if error, 0 if EOF + * @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 + * @return Bytes added if OK, negative if error, 0 if EOF */ int -transcode(struct evbuffer *evbuf, int wanted, 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); diff --git a/src/transcode_legacy.c b/src/transcode_legacy.c new file mode 100644 index 00000000..104a5a19 --- /dev/null +++ b/src/transcode_legacy.c @@ -0,0 +1,1677 @@ +/* + * Copyright (C) 2015 Espen Jurgensen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ffmpeg-compat.h" + +#include "logger.h" +#include "conffile.h" +#include "db.h" +#include "avio_evbuffer.h" +#include "transcode.h" + +// Interval between ICY metadata checks for streams, in seconds +#define METADATA_ICY_INTERVAL 5 +// Maximum number of streams in a file that we will accept +#define MAX_STREAMS 64 +// Maximum number of times we retry when we encounter bad packets +#define MAX_BAD_PACKETS 5 +// How long to wait (in microsec) before interrupting av_read_frame +#define READ_TIMEOUT 15000000 + +static const char *default_codecs = "mpeg,wav"; +static const char *roku_codecs = "mpeg,mp4a,wma,wav"; +static const char *itunes_codecs = "mpeg,mp4a,mp4v,alac,wav"; + +// Used for passing errors to DPRINTF (can't count on av_err2str being present) +static char errbuf[64]; + +struct filter_ctx { + AVFilterContext *buffersink_ctx; + AVFilterContext *buffersrc_ctx; + AVFilterGraph *filter_graph; +}; + +struct decode_ctx { + // Input format context + AVFormatContext *ifmt_ctx; + + // Will point to the stream that we will transcode + AVStream *audio_stream; + + // Duration (used to make wav header) + uint32_t duration; + + // Data kind (used to determine if ICY metadata is relevant to look for) + enum data_kind data_kind; + + // Contains the most recent packet from av_read_frame + // Used for resuming after seek and for freeing correctly + // in transcode_decode() + AVPacket packet; + int resume; + int resume_offset; + + // Used to measure if av_read_frame is taking too long + int64_t timestamp; +}; + +struct encode_ctx { + // Output format context + AVFormatContext *ofmt_ctx; + + // We use filters to resample + struct filter_ctx *filter_ctx; + + // The ffmpeg muxer writes to this buffer using the avio_evbuffer interface + struct evbuffer *obuf; + + // Maps input stream number -> output stream number + // So if we are decoding audio stream 3 and encoding it to 0, then + // out_stream_map[3] is 0. A value of -1 means the stream is ignored. + int out_stream_map[MAX_STREAMS]; + + // Maps output stream number -> input stream number + unsigned int in_stream_map[MAX_STREAMS]; + + // Used for seeking + int64_t prev_pts[MAX_STREAMS]; + int64_t offset_pts[MAX_STREAMS]; + + // Settings for encoding and muxing + const char *format; + + // Audio settings + enum AVCodecID audio_codec; + int sample_rate; + uint64_t channel_layout; + int channels; + enum AVSampleFormat sample_format; + int byte_depth; + + // How many output bytes we have processed in total + off_t total_bytes; + + // Used to check for ICY metadata changes at certain intervals + uint32_t icy_interval; + uint32_t icy_hash; + + // WAV header + int wavhdr; + uint8_t header[44]; +}; + +struct transcode_ctx { + struct decode_ctx *decode_ctx; + struct encode_ctx *encode_ctx; +}; + +struct decoded_frame +{ + AVFrame *frame; + unsigned int stream_index; +}; + + +/* -------------------------- PROFILE CONFIGURATION ------------------------ */ + +static int +init_profile(struct encode_ctx *ctx, enum transcode_profile profile) +{ + switch (profile) + { + case XCODE_PCM16_NOHEADER: + case XCODE_PCM16_HEADER: + ctx->format = "s16le"; + ctx->audio_codec = AV_CODEC_ID_PCM_S16LE; + ctx->sample_rate = 44100; + ctx->channel_layout = AV_CH_LAYOUT_STEREO; + ctx->channels = 2; + ctx->sample_format = AV_SAMPLE_FMT_S16; + ctx->byte_depth = 2; // Bytes per sample = 16/8 + return 0; + + case XCODE_MP3: + ctx->format = "mp3"; + ctx->audio_codec = AV_CODEC_ID_MP3; + ctx->sample_rate = 44100; + ctx->channel_layout = AV_CH_LAYOUT_STEREO; + ctx->channels = 2; + ctx->sample_format = AV_SAMPLE_FMT_S16P; + ctx->byte_depth = 2; // Bytes per sample = 16/8 + return 0; + + default: + DPRINTF(E_LOG, L_XCODE, "Bug! Unknown transcoding profile\n"); + return -1; + } +} + + +/* -------------------------------- HELPERS -------------------------------- */ + +static inline char * +err2str(int errnum) +{ + av_strerror(errnum, errbuf, sizeof(errbuf)); + return errbuf; +} + +static inline void +add_le16(uint8_t *dst, uint16_t val) +{ + dst[0] = val & 0xff; + dst[1] = (val >> 8) & 0xff; +} + +static inline void +add_le32(uint8_t *dst, uint32_t val) +{ + dst[0] = val & 0xff; + dst[1] = (val >> 8) & 0xff; + dst[2] = (val >> 16) & 0xff; + dst[3] = (val >> 24) & 0xff; +} + +static void +make_wav_header(struct encode_ctx *ctx, struct decode_ctx *src_ctx, off_t *est_size) +{ + uint32_t wav_len; + int duration; + + if (src_ctx->duration) + duration = src_ctx->duration; + else + duration = 3 * 60 * 1000; /* 3 minutes, in ms */ + + wav_len = ctx->channels * ctx->byte_depth * ctx->sample_rate * (duration / 1000); + + *est_size = wav_len + sizeof(ctx->header); + + memcpy(ctx->header, "RIFF", 4); + add_le32(ctx->header + 4, 36 + wav_len); + memcpy(ctx->header + 8, "WAVEfmt ", 8); + add_le32(ctx->header + 16, 16); + add_le16(ctx->header + 20, 1); + add_le16(ctx->header + 22, ctx->channels); /* channels */ + add_le32(ctx->header + 24, ctx->sample_rate); /* samplerate */ + add_le32(ctx->header + 28, ctx->sample_rate * ctx->channels * ctx->byte_depth); /* byte rate */ + add_le16(ctx->header + 32, ctx->channels * ctx->byte_depth); /* block align */ + add_le16(ctx->header + 34, ctx->byte_depth * 8); /* bits per sample */ + memcpy(ctx->header + 36, "data", 4); + add_le32(ctx->header + 40, wav_len); +} + +/* + * Returns true if in_stream is a stream we should decode, otherwise false + * + * @in ctx Decode context + * @in in_stream Pointer to AVStream + * @return True if stream should be decoded, otherwise false + */ +static int +decode_stream(struct decode_ctx *ctx, AVStream *in_stream) +{ + return (in_stream == ctx->audio_stream); +} + +/* + * Called by libavformat while demuxing. Used to interrupt/unblock av_read_frame + * in case a source (especially a network stream) becomes unavailable. + * + * @in arg Will point to the decode context + * @return Non-zero if av_read_frame should be interrupted + */ +static int decode_interrupt_cb(void *arg) +{ + struct decode_ctx *ctx; + + ctx = (struct decode_ctx *)arg; + + if (av_gettime() - ctx->timestamp > READ_TIMEOUT) + { + DPRINTF(E_LOG, L_XCODE, "Timeout while reading source (connection problem?)\n"); + + return 1; + } + + return 0; +} + +/* Will read the next packet from the source, unless we are in resume mode, in + * which case the most recent packet will be returned, but with an adjusted data + * pointer. Use ctx->resume and ctx->resume_offset to make the function resume + * from the most recent packet. + * + * @out packet Pointer to an already allocated AVPacket. The content of the + * packet will be updated, and packet->data is pointed to the data + * returned by av_read_frame(). The packet struct is owned by the + * caller, but *not* packet->data, so don't free the packet with + * av_free_packet()/av_packet_unref() + * @out stream Set to the input AVStream corresponding to the packet + * @out stream_index + * Set to the input stream index corresponding to the packet + * @in ctx Decode context + * @return 0 if OK, < 0 on error or end of file + */ +static int +read_packet(AVPacket *packet, AVStream **stream, unsigned int *stream_index, struct decode_ctx *ctx) +{ + AVStream *in_stream; + int ret; + + do + { + if (ctx->resume) + { + // Copies packet struct, but not actual packet payload, and adjusts + // data pointer to somewhere inside the payload if resume_offset is set + *packet = ctx->packet; + packet->data += ctx->resume_offset; + packet->size -= ctx->resume_offset; + ctx->resume = 0; + } + else + { + // We are going to read a new packet from source, so now it is safe to + // discard the previous packet and reset resume_offset + av_packet_unref(&ctx->packet); + + ctx->resume_offset = 0; + ctx->timestamp = av_gettime(); + + ret = av_read_frame(ctx->ifmt_ctx, &ctx->packet); + if (ret < 0) + { + DPRINTF(E_WARN, L_XCODE, "Could not read frame: %s\n", err2str(ret)); + return ret; + } + + *packet = ctx->packet; + } + + in_stream = ctx->ifmt_ctx->streams[packet->stream_index]; + } + while (!decode_stream(ctx, in_stream)); + + av_packet_rescale_ts(packet, in_stream->time_base, in_stream->codec->time_base); + + *stream = in_stream; + *stream_index = packet->stream_index; + + return 0; +} + +static int +encode_write_frame(struct encode_ctx *ctx, AVFrame *filt_frame, unsigned int stream_index, int *got_frame) +{ + AVStream *out_stream; + AVPacket enc_pkt; + int ret; + int got_frame_local; + + if (!got_frame) + got_frame = &got_frame_local; + + out_stream = ctx->ofmt_ctx->streams[stream_index]; + + // Encode filtered frame + enc_pkt.data = NULL; + enc_pkt.size = 0; + av_init_packet(&enc_pkt); + + if (out_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) + ret = avcodec_encode_audio2(out_stream->codec, &enc_pkt, filt_frame, got_frame); + else + return -1; + + if (ret < 0) + return -1; + if (!(*got_frame)) + return 0; + + // Prepare packet for muxing + enc_pkt.stream_index = stream_index; + + // This "wonderful" peace of code makes sure that the timestamp never decreases, + // even if the user seeked backwards. The muxer will not accept decreasing + // timestamps + enc_pkt.pts += ctx->offset_pts[stream_index]; + if (enc_pkt.pts < ctx->prev_pts[stream_index]) + { + ctx->offset_pts[stream_index] += ctx->prev_pts[stream_index] - enc_pkt.pts; + enc_pkt.pts = ctx->prev_pts[stream_index]; + } + ctx->prev_pts[stream_index] = enc_pkt.pts; + enc_pkt.dts = enc_pkt.pts; //FIXME + + av_packet_rescale_ts(&enc_pkt, out_stream->codec->time_base, out_stream->time_base); + + // Mux encoded frame + ret = av_interleaved_write_frame(ctx->ofmt_ctx, &enc_pkt); + return ret; +} + +#if HAVE_DECL_AV_BUFFERSRC_ADD_FRAME_FLAGS && HAVE_DECL_AV_BUFFERSINK_GET_FRAME +static int +filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int stream_index) +{ + AVFrame *filt_frame; + int ret; + + // Push the decoded frame into the filtergraph + if (frame) + { + ret = av_buffersrc_add_frame_flags(ctx->filter_ctx[stream_index].buffersrc_ctx, frame, 0); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret)); + return -1; + } + } + + // Pull filtered frames from the filtergraph + while (1) + { + filt_frame = av_frame_alloc(); + if (!filt_frame) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for filt_frame\n"); + return -1; + } + + ret = av_buffersink_get_frame(ctx->filter_ctx[stream_index].buffersink_ctx, filt_frame); + if (ret < 0) + { + /* if no more frames for output - returns AVERROR(EAGAIN) + * if flushed and no more frames for output - returns AVERROR_EOF + * rewrite retcode to 0 to show it as normal procedure completion + */ + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + ret = 0; + av_frame_free(&filt_frame); + break; + } + + filt_frame->pict_type = AV_PICTURE_TYPE_NONE; + ret = encode_write_frame(ctx, filt_frame, stream_index, NULL); + av_frame_free(&filt_frame); + if (ret < 0) + break; + } + + return ret; +} +#else +static int +filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int stream_index) +{ + AVFilterBufferRef *picref; + AVCodecContext *enc_ctx; + AVFrame *filt_frame; + int ret; + + enc_ctx = ctx->ofmt_ctx->streams[stream_index]->codec; + + // Push the decoded frame into the filtergraph + if (frame) + { + ret = av_buffersrc_write_frame(ctx->filter_ctx[stream_index].buffersrc_ctx, frame); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret)); + return -1; + } + } + + // Pull filtered frames from the filtergraph + while (1) + { + filt_frame = av_frame_alloc(); + if (!filt_frame) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for filt_frame\n"); + return -1; + } + + if (enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO && !(enc_ctx->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)) + ret = av_buffersink_read_samples(ctx->filter_ctx[stream_index].buffersink_ctx, &picref, enc_ctx->frame_size); + else + ret = av_buffersink_read(ctx->filter_ctx[stream_index].buffersink_ctx, &picref); + + if (ret < 0) + { + /* if no more frames for output - returns AVERROR(EAGAIN) + * if flushed and no more frames for output - returns AVERROR_EOF + * rewrite retcode to 0 to show it as normal procedure completion + */ + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + ret = 0; + av_frame_free(&filt_frame); + break; + } + + avfilter_copy_buf_props(filt_frame, picref); + ret = encode_write_frame(ctx, filt_frame, stream_index, NULL); + av_frame_free(&filt_frame); + avfilter_unref_buffer(picref); + if (ret < 0) + break; + } + + return ret; +} +#endif + +/* Will step through each stream and feed the stream decoder with empty packets + * to see if the decoder has more frames lined up. Will return non-zero if a + * frame is found. Should be called until it stops returning anything. + * + * @out frame AVFrame if there was anything to flush, otherwise undefined + * @out stream Set to the AVStream where a decoder returned a frame + * @out stream_index + * Set to the stream index of the stream returning a frame + * @in ctx Decode context + * @return Non-zero (true) if frame found, otherwise 0 (false) + */ +static int +flush_decoder(AVFrame *frame, AVStream **stream, unsigned int *stream_index, struct decode_ctx *ctx) +{ + AVStream *in_stream; + AVPacket dummypacket; + int got_frame; + int i; + + memset(&dummypacket, 0, sizeof(AVPacket)); + + for (i = 0; i < ctx->ifmt_ctx->nb_streams; i++) + { + in_stream = ctx->ifmt_ctx->streams[i]; + if (!decode_stream(ctx, in_stream)) + continue; + + avcodec_decode_audio4(in_stream->codec, frame, &got_frame, &dummypacket); + + if (!got_frame) + continue; + + DPRINTF(E_DBG, L_XCODE, "Flushing decoders produced a frame from stream %d\n", i); + + *stream = in_stream; + *stream_index = i; + return got_frame; + } + + return 0; +} + +static void +flush_encoder(struct encode_ctx *ctx, unsigned int stream_index) +{ + int ret; + int got_frame; + + DPRINTF(E_DBG, L_XCODE, "Flushing output stream #%u encoder\n", stream_index); + + if (!(ctx->ofmt_ctx->streams[stream_index]->codec->codec->capabilities & CODEC_CAP_DELAY)) + return; + + do + { + ret = encode_write_frame(ctx, NULL, stream_index, &got_frame); + } + while ((ret == 0) && got_frame); +} + + +/* --------------------------- INPUT/OUTPUT INIT --------------------------- */ + +static int +open_input(struct decode_ctx *ctx, const char *path) +{ + AVDictionary *options; + AVCodec *decoder; + int stream_index; + int ret; + + options = NULL; + ctx->ifmt_ctx = avformat_alloc_context();; + if (!ctx->ifmt_ctx) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for input format context\n"); + return -1; + } + +# ifndef HAVE_FFMPEG + // Without this, libav is slow to probe some internet streams, which leads to RAOP timeouts + if (ctx->data_kind == DATA_KIND_HTTP) + ctx->ifmt_ctx->probesize = 64000; +# endif + if (ctx->data_kind == DATA_KIND_HTTP) + av_dict_set(&options, "icy", "1", 0); + + // TODO Newest versions of ffmpeg have timeout and reconnect options we should use + ctx->ifmt_ctx->interrupt_callback.callback = decode_interrupt_cb; + ctx->ifmt_ctx->interrupt_callback.opaque = ctx; + ctx->timestamp = av_gettime(); + + ret = avformat_open_input(&ctx->ifmt_ctx, path, NULL, &options); + + if (options) + av_dict_free(&options); + + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot open '%s': %s\n", path, err2str(ret)); + return -1; + } + + ret = avformat_find_stream_info(ctx->ifmt_ctx, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot find stream information: %s\n", err2str(ret)); + goto out_fail; + } + + if (ctx->ifmt_ctx->nb_streams > MAX_STREAMS) + { + DPRINTF(E_LOG, L_XCODE, "File '%s' has too many streams (%u)\n", path, ctx->ifmt_ctx->nb_streams); + goto out_fail; + } + + // 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; + } + + ctx->ifmt_ctx->streams[stream_index]->codec->request_sample_fmt = AV_SAMPLE_FMT_S16; + ctx->ifmt_ctx->streams[stream_index]->codec->request_channel_layout = AV_CH_LAYOUT_STEREO; + +// Disabled to see if it is still required +// if (decoder->capabilities & CODEC_CAP_TRUNCATED) +// ctx->ifmt_ctx->streams[stream_index]->codec->flags |= CODEC_FLAG_TRUNCATED; + + ret = avcodec_open2(ctx->ifmt_ctx->streams[stream_index]->codec, decoder, 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; + } + + ctx->audio_stream = ctx->ifmt_ctx->streams[stream_index]; + + return 0; + + out_fail: + avformat_close_input(&ctx->ifmt_ctx); + + return -1; +} + +static void +close_input(struct decode_ctx *ctx) +{ + if (ctx->audio_stream) + avcodec_close(ctx->audio_stream->codec); + + avformat_close_input(&ctx->ifmt_ctx); +} + +static int +open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) +{ + AVStream *out_stream; + AVStream *in_stream; + AVCodecContext *dec_ctx; + AVCodecContext *enc_ctx; + AVCodec *encoder; + const AVCodecDescriptor *codec_desc; + enum AVCodecID codec_id; + int ret; + int i; + + ctx->ofmt_ctx = NULL; + avformat_alloc_output_context2(&ctx->ofmt_ctx, NULL, ctx->format, NULL); + if (!ctx->ofmt_ctx) + { + DPRINTF(E_LOG, L_XCODE, "Could not create output context\n"); + return -1; + } + + ctx->obuf = evbuffer_new(); + if (!ctx->obuf) + { + DPRINTF(E_LOG, L_XCODE, "Could not create output evbuffer\n"); + goto out_fail_evbuf; + } + + ctx->ofmt_ctx->pb = avio_output_evbuffer_open(ctx->obuf); + if (!ctx->ofmt_ctx->pb) + { + DPRINTF(E_LOG, L_XCODE, "Could not create output avio pb\n"); + goto out_fail_pb; + } + + for (i = 0; i < src_ctx->ifmt_ctx->nb_streams; i++) + { + in_stream = src_ctx->ifmt_ctx->streams[i]; + if (!decode_stream(src_ctx, in_stream)) + { + ctx->out_stream_map[i] = -1; + continue; + } + + out_stream = avformat_new_stream(ctx->ofmt_ctx, NULL); + if (!out_stream) + { + DPRINTF(E_LOG, L_XCODE, "Failed allocating output stream\n"); + goto out_fail_stream; + } + + ctx->out_stream_map[i] = out_stream->index; + ctx->in_stream_map[out_stream->index] = i; + + dec_ctx = in_stream->codec; + enc_ctx = out_stream->codec; + + // TODO Enough to just remux subtitles? + if (dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE) + { + avcodec_copy_context(enc_ctx, dec_ctx); + continue; + } + + if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) + codec_id = ctx->audio_codec; + else + continue; + + codec_desc = avcodec_descriptor_get(codec_id); + encoder = avcodec_find_encoder(codec_id); + if (!encoder) + { + if (codec_desc) + DPRINTF(E_LOG, L_XCODE, "Necessary encoder (%s) for input stream %u not found\n", codec_desc->name, i); + else + DPRINTF(E_LOG, L_XCODE, "Necessary encoder (unknown) for input stream %u not found\n", i); + goto out_fail_stream; + } + + enc_ctx->sample_rate = ctx->sample_rate; + enc_ctx->channel_layout = ctx->channel_layout; + enc_ctx->channels = ctx->channels; + enc_ctx->sample_fmt = ctx->sample_format; + enc_ctx->time_base = (AVRational){1, ctx->sample_rate}; + + ret = avcodec_open2(enc_ctx, encoder, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot open encoder (%s) for input stream #%u: %s\n", codec_desc->name, i, err2str(ret)); + goto out_fail_codec; + } + + if (ctx->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) + enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + + // Notice, this will not write WAV header (so we do that manually) + ret = avformat_write_header(ctx->ofmt_ctx, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Error writing header to output buffer: %s\n", err2str(ret)); + goto out_fail_write; + } + + return 0; + + out_fail_write: + out_fail_codec: + for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) + { + enc_ctx = ctx->ofmt_ctx->streams[i]->codec; + if (enc_ctx) + avcodec_close(enc_ctx); + } + out_fail_stream: + avio_evbuffer_close(ctx->ofmt_ctx->pb); + out_fail_pb: + evbuffer_free(ctx->obuf); + out_fail_evbuf: + avformat_free_context(ctx->ofmt_ctx); + + return -1; +} + +static void +close_output(struct encode_ctx *ctx) +{ + int i; + + for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) + { + if (ctx->ofmt_ctx->streams[i]->codec) + avcodec_close(ctx->ofmt_ctx->streams[i]->codec); + } + + avio_evbuffer_close(ctx->ofmt_ctx->pb); + evbuffer_free(ctx->obuf); + avformat_free_context(ctx->ofmt_ctx); +} + +#if HAVE_DECL_AVFILTER_GRAPH_PARSE_PTR +static int +open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, const char *filter_spec) +{ + AVFilter *buffersrc = NULL; + AVFilter *buffersink = NULL; + AVFilterContext *buffersrc_ctx = NULL; + AVFilterContext *buffersink_ctx = NULL; + AVFilterInOut *outputs = avfilter_inout_alloc(); + AVFilterInOut *inputs = avfilter_inout_alloc(); + AVFilterGraph *filter_graph = avfilter_graph_alloc(); + char args[512]; + int ret; + + if (!outputs || !inputs || !filter_graph) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for filter_graph, input or output\n"); + goto out_fail; + } + + if (dec_ctx->codec_type != AVMEDIA_TYPE_AUDIO) + { + DPRINTF(E_LOG, L_XCODE, "Bug! Unknown type passed to filter graph init\n"); + goto out_fail; + } + + buffersrc = avfilter_get_by_name("abuffer"); + buffersink = avfilter_get_by_name("abuffersink"); + if (!buffersrc || !buffersink) + { + DPRINTF(E_LOG, L_XCODE, "Filtering source or sink element not found\n"); + goto out_fail; + } + + if (!dec_ctx->channel_layout) + dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels); + + snprintf(args, sizeof(args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, + dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate, + av_get_sample_fmt_name(dec_ctx->sample_fmt), + dec_ctx->channel_layout); + + 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)); + goto out_fail; + } + + ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink: %s\n", err2str(ret)); + goto out_fail; + } + + ret = av_opt_set_bin(buffersink_ctx, "sample_fmts", + (uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot set output sample format: %s\n", err2str(ret)); + goto out_fail; + } + + ret = av_opt_set_bin(buffersink_ctx, "channel_layouts", + (uint8_t*)&enc_ctx->channel_layout, sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot set output channel layout: %s\n", err2str(ret)); + goto out_fail; + } + + ret = av_opt_set_bin(buffersink_ctx, "sample_rates", + (uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate), AV_OPT_SEARCH_CHILDREN); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot set output sample rate: %s\n", err2str(ret)); + goto out_fail; + } + + /* Endpoints for the filter graph. */ + outputs->name = av_strdup("in"); + outputs->filter_ctx = buffersrc_ctx; + outputs->pad_idx = 0; + outputs->next = NULL; + inputs->name = av_strdup("out"); + inputs->filter_ctx = buffersink_ctx; + inputs->pad_idx = 0; + inputs->next = NULL; + if (!outputs->name || !inputs->name) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for outputs/inputs\n"); + goto out_fail; + } + + ret = avfilter_graph_parse_ptr(filter_graph, filter_spec, &inputs, &outputs, NULL); + if (ret < 0) + goto out_fail; + + ret = avfilter_graph_config(filter_graph, NULL); + if (ret < 0) + goto out_fail; + + /* Fill filtering context */ + filter_ctx->buffersrc_ctx = buffersrc_ctx; + filter_ctx->buffersink_ctx = buffersink_ctx; + filter_ctx->filter_graph = filter_graph; + + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + + return 0; + + out_fail: + avfilter_graph_free(&filter_graph); + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + + return -1; +} +#else +static int +open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, const char *filter_spec) +{ + + AVFilter *buffersrc = NULL; + AVFilter *format = NULL; + AVFilter *buffersink = NULL; + AVFilterContext *buffersrc_ctx = NULL; + AVFilterContext *format_ctx = NULL; + AVFilterContext *buffersink_ctx = NULL; + AVFilterGraph *filter_graph = avfilter_graph_alloc(); + char args[512]; + int ret; + + if (!filter_graph) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for filter_graph\n"); + goto out_fail; + } + + if (dec_ctx->codec_type != AVMEDIA_TYPE_AUDIO) + { + DPRINTF(E_LOG, L_XCODE, "Bug! Unknown type passed to filter graph init\n"); + goto out_fail; + } + + + buffersrc = avfilter_get_by_name("abuffer"); + format = avfilter_get_by_name("aformat"); + buffersink = avfilter_get_by_name("abuffersink"); + if (!buffersrc || !format || !buffersink) + { + DPRINTF(E_LOG, L_XCODE, "Filtering source, format or sink element not found\n"); + goto out_fail; + } + + if (!dec_ctx->channel_layout) + dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels); + + snprintf(args, sizeof(args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, + dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate, + av_get_sample_fmt_name(dec_ctx->sample_fmt), + dec_ctx->channel_layout); + + 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)); + goto out_fail; + } + + snprintf(args, sizeof(args), + "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%"PRIx64, + av_get_sample_fmt_name(enc_ctx->sample_fmt), enc_ctx->sample_rate, + enc_ctx->channel_layout); + + 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)); + goto out_fail; + } + + ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer sink: %s\n", err2str(ret)); + goto out_fail; + } + + ret = avfilter_link(buffersrc_ctx, 0, format_ctx, 0); + if (ret >= 0) + ret = avfilter_link(format_ctx, 0, buffersink_ctx, 0); + if (ret < 0) + DPRINTF(E_LOG, L_XCODE, "Error connecting filters: %s\n", err2str(ret)); + + ret = avfilter_graph_config(filter_graph, NULL); + if (ret < 0) + goto out_fail; + + /* Fill filtering context */ + filter_ctx->buffersrc_ctx = buffersrc_ctx; + filter_ctx->buffersink_ctx = buffersink_ctx; + filter_ctx->filter_graph = filter_graph; + + return 0; + + out_fail: + avfilter_graph_free(&filter_graph); + + return -1; +} +#endif + +static int +open_filters(struct encode_ctx *ctx, struct decode_ctx *src_ctx) +{ + AVCodecContext *enc_ctx; + AVCodecContext *dec_ctx; + const char *filter_spec; + unsigned int stream_index; + int i; + int ret; + + ctx->filter_ctx = av_malloc_array(ctx->ofmt_ctx->nb_streams, sizeof(*ctx->filter_ctx)); + if (!ctx->filter_ctx) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for outputs/inputs\n"); + return -1; + } + + for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) + { + ctx->filter_ctx[i].buffersrc_ctx = NULL; + ctx->filter_ctx[i].buffersink_ctx = NULL; + ctx->filter_ctx[i].filter_graph = NULL; + + stream_index = ctx->in_stream_map[i]; + + enc_ctx = ctx->ofmt_ctx->streams[i]->codec; + dec_ctx = src_ctx->ifmt_ctx->streams[stream_index]->codec; + + if (enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO) + filter_spec = "anull"; /* passthrough (dummy) filter for audio */ + else + continue; + + ret = open_filter(&ctx->filter_ctx[i], dec_ctx, enc_ctx, filter_spec); + if (ret < 0) + goto out_fail; + } + + return 0; + + out_fail: + for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) + { + if (ctx->filter_ctx && ctx->filter_ctx[i].filter_graph) + avfilter_graph_free(&ctx->filter_ctx[i].filter_graph); + } + av_free(ctx->filter_ctx); + + return -1; +} + +static void +close_filters(struct encode_ctx *ctx) +{ + int i; + + for (i = 0; i < ctx->ofmt_ctx->nb_streams; i++) + { + if (ctx->filter_ctx && ctx->filter_ctx[i].filter_graph) + avfilter_graph_free(&ctx->filter_ctx[i].filter_graph); + } + av_free(ctx->filter_ctx); +} + + +/* ----------------------------- TRANSCODE API ----------------------------- */ + +/* Setup */ + +struct decode_ctx * +transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, struct evbuffer *evbuf, uint32_t song_length) +{ + struct decode_ctx *ctx; + + ctx = calloc(1, sizeof(struct decode_ctx)); + if (!ctx) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for decode ctx\n"); + return NULL; + } + + ctx->duration = song_length; + ctx->data_kind = data_kind; + + if (open_input(ctx, path) < 0) + { + free(ctx); + return NULL; + } + + av_init_packet(&ctx->packet); + + return ctx; +} + +struct encode_ctx * +transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height) +{ + struct encode_ctx *ctx; + + ctx = calloc(1, sizeof(struct encode_ctx)); + if (!ctx) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for encode ctx\n"); + return NULL; + } + + if ((init_profile(ctx, profile) < 0) || (open_output(ctx, src_ctx) < 0)) + { + free(ctx); + return NULL; + } + + if (open_filters(ctx, src_ctx) < 0) + { + close_output(ctx); + free(ctx); + return NULL; + } + + if (src_ctx->data_kind == DATA_KIND_HTTP) + ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->channels * ctx->byte_depth * ctx->sample_rate; + + if (profile == XCODE_PCM16_HEADER) + { + ctx->wavhdr = 1; + make_wav_header(ctx, src_ctx, est_size); + } + + return ctx; +} + +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) +{ + struct transcode_ctx *ctx; + + ctx = malloc(sizeof(struct transcode_ctx)); + if (!ctx) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for transcode ctx\n"); + return NULL; + } + + ctx->decode_ctx = transcode_decode_setup(profile, data_kind, path, NULL, song_length); + if (!ctx->decode_ctx) + { + free(ctx); + return NULL; + } + + ctx->encode_ctx = transcode_encode_setup(profile, ctx->decode_ctx, est_size, 0, 0); + if (!ctx->encode_ctx) + { + transcode_decode_cleanup(&ctx->decode_ctx); + free(ctx); + return NULL; + } + + return ctx; +} + +struct decode_ctx * +transcode_decode_setup_raw(void) +{ + struct decode_ctx *ctx; + struct AVCodec *decoder; + + ctx = calloc(1, sizeof(struct decode_ctx)); + if (!ctx) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for decode ctx\n"); + return NULL; + } + + ctx->ifmt_ctx = avformat_alloc_context(); + if (!ctx->ifmt_ctx) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for decode format ctx\n"); + free(ctx); + return NULL; + } + + decoder = avcodec_find_decoder(AV_CODEC_ID_PCM_S16LE); + + ctx->audio_stream = avformat_new_stream(ctx->ifmt_ctx, decoder); + if (!ctx->audio_stream) + { + DPRINTF(E_LOG, L_XCODE, "Could not create stream with PCM16 decoder\n"); + avformat_free_context(ctx->ifmt_ctx); + free(ctx); + return NULL; + } + + ctx->audio_stream->codec->time_base.num = 1; + ctx->audio_stream->codec->time_base.den = 44100; + ctx->audio_stream->codec->sample_rate = 44100; + ctx->audio_stream->codec->sample_fmt = AV_SAMPLE_FMT_S16; + ctx->audio_stream->codec->channel_layout = AV_CH_LAYOUT_STEREO; + + return ctx; +} + +int +transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype) +{ + char *codectype; + cfg_t *lib; + int size; + int i; + + if (!file_codectype) + { + DPRINTF(E_LOG, L_XCODE, "Can't determine decode status, codec type is unknown\n"); + return -1; + } + + lib = cfg_getsec(cfg, "library"); + + size = cfg_size(lib, "no_decode"); + if (size > 0) + { + for (i = 0; i < size; i++) + { + codectype = cfg_getnstr(lib, "no_decode", i); + + if (strcmp(file_codectype, codectype) == 0) + return 0; // Codectype is in no_decode + } + } + + size = cfg_size(lib, "force_decode"); + if (size > 0) + { + for (i = 0; i < size; i++) + { + codectype = cfg_getnstr(lib, "force_decode", i); + + if (strcmp(file_codectype, codectype) == 0) + return 1; // Codectype is in force_decode + } + } + + if (!client_codecs) + { + if (user_agent) + { + if (strncmp(user_agent, "iTunes", strlen("iTunes")) == 0) + client_codecs = itunes_codecs; + else if (strncmp(user_agent, "QuickTime", strlen("QuickTime")) == 0) + client_codecs = itunes_codecs; // Use iTunes codecs + else if (strncmp(user_agent, "Front%20Row", strlen("Front%20Row")) == 0) + client_codecs = itunes_codecs; // Use iTunes codecs + else if (strncmp(user_agent, "AppleCoreMedia", strlen("AppleCoreMedia")) == 0) + client_codecs = itunes_codecs; // Use iTunes codecs + else if (strncmp(user_agent, "Roku", strlen("Roku")) == 0) + client_codecs = roku_codecs; + else if (strncmp(user_agent, "Hifidelio", strlen("Hifidelio")) == 0) + /* Allegedly can't transcode for Hifidelio because their + * HTTP implementation doesn't honour Connection: close. + * At least, that's why mt-daapd didn't do it. + */ + return 0; + } + } + else + DPRINTF(E_DBG, L_XCODE, "Client advertises codecs: %s\n", client_codecs); + + if (!client_codecs) + { + DPRINTF(E_DBG, L_XCODE, "Could not identify client, using default codectype set\n"); + client_codecs = default_codecs; + } + + if (strstr(client_codecs, file_codectype)) + { + DPRINTF(E_DBG, L_XCODE, "Codectype supported by client, no decoding needed\n"); + return 0; + } + + DPRINTF(E_DBG, L_XCODE, "Will decode\n"); + return 1; +} + + +/* Cleanup */ + +void +transcode_decode_cleanup(struct decode_ctx **ctx) +{ + av_packet_unref(&(*ctx)->packet); + close_input(*ctx); + free(*ctx); + *ctx = NULL; +} + +void +transcode_encode_cleanup(struct encode_ctx **ctx) +{ + int i; + + // Flush filters and encoders + for (i = 0; i < (*ctx)->ofmt_ctx->nb_streams; i++) + { + if (!(*ctx)->filter_ctx[i].filter_graph) + continue; + filter_encode_write_frame((*ctx), NULL, i); + flush_encoder((*ctx), i); + } + + av_write_trailer((*ctx)->ofmt_ctx); + + close_filters(*ctx); + close_output(*ctx); + free(*ctx); + *ctx = NULL; +} + +void +transcode_cleanup(struct transcode_ctx **ctx) +{ + transcode_encode_cleanup(&(*ctx)->encode_ctx); + transcode_decode_cleanup(&(*ctx)->decode_ctx); + free(*ctx); + *ctx = NULL; +} + +void +transcode_frame_free(void *frame) +{ + struct decoded_frame *decoded = frame; + + av_frame_free(&decoded->frame); + free(decoded); +} + + +/* Encoding, decoding and transcoding */ + + +int +transcode_decode(void **frame, struct decode_ctx *ctx) +{ + struct decoded_frame *decoded; + AVPacket packet; + AVStream *in_stream; + AVFrame *f; + unsigned int stream_index; + int got_frame; + int retry; + int ret; + int used; + + // Alloc the frame we will return on success + f = av_frame_alloc(); + if (!f) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for decode frame\n"); + return -1; + } + + // Loop until we either fail or get a frame + retry = 0; + do + { + ret = read_packet(&packet, &in_stream, &stream_index, ctx); + if (ret < 0) + { + // Some decoders need to be flushed, meaning the decoder is to be called + // with empty input until no more frames are returned + DPRINTF(E_DBG, L_XCODE, "Could not read packet, will flush decoders\n"); + + got_frame = flush_decoder(f, &in_stream, &stream_index, ctx); + if (got_frame) + break; + + av_frame_free(&f); + if (ret == AVERROR_EOF) + return 0; + else + return -1; + } + + // "used" will tell us how much of the packet was decoded. We may + // not get a frame because of insufficient input, in which case we loop to + // read another packet. + used = avcodec_decode_audio4(in_stream->codec, f, &got_frame, &packet); + + // decoder returned an error, but maybe the packet was just a bad apple, + // so let's try MAX_BAD_PACKETS times before giving up + if (used < 0) + { + DPRINTF(E_DBG, L_XCODE, "Couldn't decode packet\n"); + + retry += 1; + if (retry < MAX_BAD_PACKETS) + continue; + + DPRINTF(E_LOG, L_XCODE, "Couldn't decode packet after %i retries\n", MAX_BAD_PACKETS); + + av_frame_free(&f); + return -1; + } + + // decoder didn't process the entire packet, so flag a resume, meaning + // that the next read_packet() will return this same packet, but where the + // data pointer is adjusted with an offset + if (used < packet.size) + { + DPRINTF(E_SPAM, L_XCODE, "Decoder did not finish packet, packet will be resumed\n"); + + ctx->resume_offset += used; + ctx->resume = 1; + } + } + while (!got_frame); + + if (got_frame > 0) + { + // Return the decoded frame and stream index + decoded = malloc(sizeof(struct decoded_frame)); + if (!decoded) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded result\n"); + + av_frame_free(&f); + return -1; + } + + decoded->frame = f; + decoded->stream_index = stream_index; + *frame = decoded; + } + else + *frame = NULL; + + return got_frame; +} + +// Filters and encodes +int +transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, int eof) +{ + struct decoded_frame *decoded = frame; + int stream_index; + int encoded_length; + int ret; + + encoded_length = 0; + + stream_index = ctx->out_stream_map[decoded->stream_index]; + if (stream_index < 0) + return -1; + + if (ctx->wavhdr) + { + encoded_length += sizeof(ctx->header); + evbuffer_add(evbuf, ctx->header, sizeof(ctx->header)); + ctx->wavhdr = 0; + } + + ret = filter_encode_write_frame(ctx, decoded->frame, stream_index); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", err2str(ret)); + return ret; + } + + encoded_length += evbuffer_get_length(ctx->obuf); + evbuffer_add_buffer(evbuf, ctx->obuf); + + return encoded_length; +} + +int +transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int want_bytes) +{ + void *frame; + int processed; + int ret; + + *icy_timer = 0; + + processed = 0; + while (processed < want_bytes) + { + ret = transcode_decode(&frame, ctx->decode_ctx); + if (ret <= 0) + return ret; + + ret = transcode_encode(evbuf, ctx->encode_ctx, frame, 0); + transcode_frame_free(frame); + if (ret < 0) + return -1; + + processed += ret; + } + + ctx->encode_ctx->total_bytes += processed; + if (ctx->encode_ctx->icy_interval) + *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed); + + return processed; +} + +void * +transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size) +{ + struct decoded_frame *decoded; + 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; + } + + f = av_frame_alloc(); + if (!f) + { + DPRINTF(E_LOG, L_XCODE, "Out of memory for frame\n"); + free(decoded); + return NULL; + } + + decoded->stream_index = 0; + decoded->frame = f; + + f->nb_samples = size / 4; + f->format = AV_SAMPLE_FMT_S16; + f->channel_layout = AV_CH_LAYOUT_STEREO; +#ifdef HAVE_FFMPEG + f->channels = 2; +#endif + f->pts = AV_NOPTS_VALUE; + f->sample_rate = 44100; + + 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_frame_free(decoded); + return NULL; + } + + return decoded; +} + + +/* Seeking */ + +int +transcode_seek(struct transcode_ctx *ctx, int ms) +{ + struct decode_ctx *decode_ctx; + AVStream *in_stream; + int64_t start_time; + int64_t target_pts; + int64_t got_pts; + int got_ms; + int ret; + int i; + + decode_ctx = ctx->decode_ctx; + in_stream = ctx->decode_ctx->audio_stream; + start_time = in_stream->start_time; + + target_pts = ms; + target_pts = target_pts * AV_TIME_BASE / 1000; + target_pts = av_rescale_q(target_pts, AV_TIME_BASE_Q, in_stream->time_base); + + if ((start_time != AV_NOPTS_VALUE) && (start_time > 0)) + target_pts += start_time; + + ret = av_seek_frame(decode_ctx->ifmt_ctx, in_stream->index, target_pts, AVSEEK_FLAG_BACKWARD); + if (ret < 0) + { + DPRINTF(E_WARN, L_XCODE, "Could not seek into stream: %s\n", err2str(ret)); + return -1; + } + + for (i = 0; i < decode_ctx->ifmt_ctx->nb_streams; i++) + { + if (decode_stream(decode_ctx, decode_ctx->ifmt_ctx->streams[i])) + avcodec_flush_buffers(decode_ctx->ifmt_ctx->streams[i]->codec); +// avcodec_flush_buffers(ctx->ofmt_ctx->streams[stream_nb]->codec); + } + + // Fast forward until first packet with a timestamp is found + in_stream->codec->skip_frame = AVDISCARD_NONREF; + while (1) + { + av_packet_unref(&decode_ctx->packet); + + decode_ctx->timestamp = av_gettime(); + + ret = av_read_frame(decode_ctx->ifmt_ctx, &decode_ctx->packet); + if (ret < 0) + { + DPRINTF(E_WARN, L_XCODE, "Could not read more data while seeking: %s\n", err2str(ret)); + in_stream->codec->skip_frame = AVDISCARD_DEFAULT; + return -1; + } + + if (decode_ctx->packet.stream_index != in_stream->index) + continue; + + // Need a pts to return the real position + if (decode_ctx->packet.pts == AV_NOPTS_VALUE) + continue; + + break; + } + in_stream->codec->skip_frame = AVDISCARD_DEFAULT; + + // Tell transcode_decode() to resume with ctx->packet + decode_ctx->resume = 1; + decode_ctx->resume_offset = 0; + + // Compute position in ms from pts + got_pts = decode_ctx->packet.pts; + + if ((start_time != AV_NOPTS_VALUE) && (start_time > 0)) + got_pts -= start_time; + + got_pts = av_rescale_q(got_pts, in_stream->time_base, AV_TIME_BASE_Q); + got_ms = got_pts / (AV_TIME_BASE / 1000); + + // Since negative return would mean error, we disallow it here + if (got_ms < 0) + got_ms = 0; + + DPRINTF(E_DBG, L_XCODE, "Seek wanted %d ms, got %d ms\n", ms, got_ms); + + return got_ms; +} + +int +transcode_decode_query(struct decode_ctx *ctx, const char *query) +{ + return -1; // Not implemented +} + +/* Metadata */ + +struct http_icy_metadata * +transcode_metadata(struct transcode_ctx *ctx, int *changed) +{ + struct http_icy_metadata *m; + + if (!ctx->decode_ctx->ifmt_ctx) + return NULL; + + m = http_icy_metadata_get(ctx->decode_ctx->ifmt_ctx, 1); + if (!m) + return NULL; + + *changed = (m->hash != ctx->encode_ctx->icy_hash); + + ctx->encode_ctx->icy_hash = m->hash; + + return m; +} +