diff --git a/configure.ac b/configure.ac index dfc3ca1b..9ec5eb10 100644 --- a/configure.ac +++ b/configure.ac @@ -210,7 +210,7 @@ AC_ARG_WITH([libav], [AS_HELP_STRING([--with-libav], [[LIBAV=-libav]], [[LIBAV=]]) dnl libav/ffmpeg requires many feature checks FORK_MODULES_CHECK([FORKED], [LIBAV], - [libavformat$LIBAV libavcodec$LIBAV libswscale$LIBAV libavutil$LIBAV libavfilter$LIBAV], + [libavformat$LIBAV libavcodec$LIBAV libavutil$LIBAV libavfilter$LIBAV], [av_init_packet], [libavcodec/avcodec.h], [dnl Checks for misc libav and ffmpeg API differences AC_MSG_CHECKING([whether libav libraries are ffmpeg]) @@ -231,37 +231,7 @@ FORK_MODULES_CHECK([FORKED], [LIBAV], [libavutil/avutil.h]) FORK_CHECK_DECLS([avformat_network_init], [libavformat/avformat.h]) - dnl Check if we have modern or legacy AV api - FORK_CHECK_DECLS([avcodec_send_packet, avcodec_parameters_from_context], - [libavcodec/avcodec.h], [[libav_modern_api=yes]], [[libav_modern_api=no]]) - dnl The below we need to know only if we are going to use the legacy AV api - 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 Option to choose old ffmpeg/libav API even if modern api was found -FORK_ARG_DISABLE([use of ffmpeg/libav API with avcodec_send_packet() and family], - [avcodecsend], [USE_AVCODEC_SEND]) -AM_CONDITIONAL([COND_FFMPEG_LEGACY], - [[test "x$libav_modern_api" = "xno" || test "x$enable_avcodecsend" = "xno" ]]) AC_CHECK_SIZEOF([void *]) diff --git a/src/Makefile.am b/src/Makefile.am index 01ae5f64..36bfea6f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,12 +47,6 @@ if COND_LIBWEBSOCKETS LIBWEBSOCKETS_SRC=websocket.c websocket.h 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 \ @@ -118,8 +112,8 @@ forked_daapd_SOURCES = main.c \ httpd_artworkapi.c httpd_artworkapi.h \ http.c http.h \ dmap_common.c dmap_common.h \ - transcode.h artwork.h \ - $(FFMPEG_SRC) \ + transcode.c transcode.h \ + artwork.c artwork.h \ misc.c misc.h \ misc_json.c misc_json.h \ rng.c rng.h \ diff --git a/src/artwork_legacy.c b/src/artwork_legacy.c deleted file mode 100644 index 777ded57..00000000 --- a/src/artwork_legacy.c +++ /dev/null @@ -1,1630 +0,0 @@ -/* - * 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); - - if (id == DB_MEDIA_FILE_NON_PERSISTENT_ID) - return -1; - - 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/ffmpeg-compat.h b/src/ffmpeg-compat.h deleted file mode 100644 index 88d4868b..00000000 --- a/src/ffmpeg-compat.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef __FFMPEG_COMPAT_H__ -#define __FFMPEG_COMPAT_H__ - -#ifdef HAVE_LIBAVUTIL_CHANNEL_LAYOUT_H -# include -#endif - -#ifdef HAVE_LIBAVUTIL_MATHEMATICS_H -# include -#endif - -#ifndef HAVE_FFMPEG -# define avcodec_find_best_pix_fmt_of_list(a, b, c, d) avcodec_find_best_pix_fmt2((enum AVPixelFormat *)(a), (b), (c), (d)) -#endif - -#if !HAVE_DECL_AV_FRAME_ALLOC -# define av_frame_alloc() avcodec_alloc_frame() -# define av_frame_free(x) avcodec_free_frame((x)) -#endif - -#if !HAVE_DECL_AV_FRAME_GET_BEST_EFFORT_TIMESTAMP -# define av_frame_get_best_effort_timestamp(x) (x)->pts -#endif - -#if !HAVE_DECL_AV_IMAGE_GET_BUFFER_SIZE -# define av_image_get_buffer_size(a, b, c, d) avpicture_get_size((a), (b), (c)) -#endif - -#if !HAVE_DECL_AV_PACKET_UNREF -# define av_packet_unref(a) av_free_packet((a)) -#endif - -#if !HAVE_DECL_AV_PACKET_RESCALE_TS -__attribute__((unused)) static void -av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb) -{ - if (pkt->pts != AV_NOPTS_VALUE) - pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb); - if (pkt->dts != AV_NOPTS_VALUE) - pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb); - if (pkt->duration > 0) - pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb); - if (pkt->convergence_duration > 0) - pkt->convergence_duration = av_rescale_q(pkt->convergence_duration, src_tb, dst_tb); -} -#endif - -#if !HAVE_DECL_AVFORMAT_ALLOC_OUTPUT_CONTEXT2 -# include - -__attribute__((unused)) static int -avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) -{ - AVFormatContext *s = avformat_alloc_context(); - int ret = 0; - - *avctx = NULL; - if (!s) - goto nomem; - - if (!oformat) { - if (format) { - oformat = av_guess_format(format, NULL, NULL); - if (!oformat) { - av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); - ret = AVERROR(EINVAL); - goto error; - } - } else { - oformat = av_guess_format(NULL, filename, NULL); - if (!oformat) { - ret = AVERROR(EINVAL); - av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", - filename); - goto error; - } - } - } - - s->oformat = oformat; - if (s->oformat->priv_data_size > 0) { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if (!s->priv_data) - goto nomem; - if (s->oformat->priv_class) { - *(const AVClass**)s->priv_data= s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } - } else - s->priv_data = NULL; - - if (filename) - snprintf(s->filename, sizeof(s->filename), "%s", filename); - *avctx = s; - return 0; -nomem: - av_log(s, AV_LOG_ERROR, "Out of memory\n"); - ret = AVERROR(ENOMEM); -error: - avformat_free_context(s); - return ret; -} -#endif - -#endif /* !__FFMPEG_COMPAT_H__ */ diff --git a/src/library/filescanner_ffmpeg.c b/src/library/filescanner_ffmpeg.c index a19cfbd1..f0d14b81 100644 --- a/src/library/filescanner_ffmpeg.c +++ b/src/library/filescanner_ffmpeg.c @@ -416,21 +416,13 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) for (i = 0; i < ctx->nb_streams; i++) { -#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 - 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) { DPRINTF(E_DBG, L_SCAN, "Found embedded artwork (stream %d)\n", i); @@ -438,7 +430,7 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) break; } -#endif + // We treat these as audio no matter what if (mfi->compilation || (mfi->media_kind & (MEDIA_KIND_PODCAST | MEDIA_KIND_AUDIOBOOK))) break; diff --git a/src/transcode_legacy.c b/src/transcode_legacy.c deleted file mode 100644 index 80421c29..00000000 --- a/src/transcode_legacy.c +++ /dev/null @@ -1,1689 +0,0 @@ -/* - * 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 "misc.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_OPUS: - ctx->format = "data"; // Means we get the raw packet from the encoder, no muxing - ctx->audio_codec = AV_CODEC_ID_OPUS; - ctx->sample_rate = 48000; - ctx->channel_layout = AV_CH_LAYOUT_STEREO; - ctx->channels = 2; - ctx->sample_format = AV_SAMPLE_FMT_S16; // Only libopus support - 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(transcode_frame *frame) -{ - struct decoded_frame *decoded = frame; - - av_frame_free(&decoded->frame); - free(decoded); -} - - -/* Encoding, decoding and transcoding */ - - -int -transcode_decode(transcode_frame **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, transcode_frame *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) -{ - transcode_frame *frame; - int processed; - int ret; - - if (icy_timer) - *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 (icy_timer && ctx->encode_ctx->icy_interval) - *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed); - - return processed; -} - -transcode_frame * -transcode_frame_new(enum transcode_profile profile, void *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 = BTOS(size); - 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, 1); - 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; -} -