From 25c1795af27afb41708a2be94e473db49ba3f6b5 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 26 Feb 2017 15:32:37 +0100 Subject: [PATCH 01/16] [transcode] Update to new ffmpeg api - part 1 - no more use of AVStream.codec - ditch some backwards compability - move closer to being able do video, at least for artwork --- src/httpd.c | 2 +- src/httpd_streaming.c | 2 +- src/inputs/file_http.c | 2 +- src/transcode.c | 1210 +++++++++++++++++----------------------- src/transcode.h | 26 +- 5 files changed, 531 insertions(+), 711 deletions(-) diff --git a/src/httpd.c b/src/httpd.c index af45ecc4..5b01681a 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -552,7 +552,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"); diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index b9340fca..115f4021 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -299,7 +299,7 @@ streaming_init(void) return -1; } - streaming_encode_ctx = transcode_encode_setup(decode_ctx, XCODE_MP3, NULL); + streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL); transcode_decode_cleanup(decode_ctx); if (!streaming_encode_ctx) { diff --git a/src/inputs/file_http.c b/src/inputs/file_http.c index 220c7a94..6335a5b3 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; diff --git a/src/transcode.c b/src/transcode.c index 3d1de189..29d4b9a4 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 @@ -57,20 +58,54 @@ 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; + + // Output format (for the muxer) + const char *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; + + // 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; }; -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; + // Stream and decoder data + struct stream_ctx audio_stream; + struct stream_ctx video_stream; // Duration (used to make wav header) uint32_t duration; @@ -89,45 +124,25 @@ struct decode_ctx { 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]; - - // 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; - // How many output bytes we have processed in total off_t total_bytes; @@ -136,11 +151,11 @@ 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; }; @@ -148,48 +163,95 @@ struct transcode_ctx { struct decoded_frame { AVFrame *frame; - unsigned int stream_index; + enum AVMediaType type; }; /* -------------------------- 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 + 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->format = "image2"; + settings->video_codec = AV_CODEC_ID_MJPEG; + break; + + case XCODE_PNG: + settings->encode_video = 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_AUDIO) + { + 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 +291,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 +300,82 @@ 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 (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; } /* @@ -295,16 +411,13 @@ static int decode_interrupt_cb(void *arg) * 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(AVPacket *packet, enum AVMediaType *type, struct decode_ctx *ctx) { - AVStream *in_stream; int ret; do @@ -337,40 +450,35 @@ read_packet(AVPacket *packet, AVStream **stream, unsigned int *stream_index, str *packet = ctx->packet; } - in_stream = ctx->ifmt_ctx->streams[packet->stream_index]; + *type = stream_find(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) +encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_frame, int *got_frame) { - AVStream *out_stream; AVPacket enc_pkt; + unsigned int stream_index; int ret; int got_frame_local; if (!got_frame) got_frame = &got_frame_local; - out_stream = ctx->ofmt_ctx->streams[stream_index]; + stream_index = s->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); + if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) + ret = avcodec_encode_audio2(s->codec, &enc_pkt, filt_frame, got_frame); + else if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) + ret = avcodec_encode_video2(s->codec, &enc_pkt, filt_frame, got_frame); else return -1; @@ -394,7 +502,7 @@ encode_write_frame(struct encode_ctx *ctx, AVFrame *filt_frame, unsigned int str 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); + av_packet_rescale_ts(&enc_pkt, s->codec->time_base, s->stream->time_base); // Mux encoded frame ret = av_interleaved_write_frame(ctx->ofmt_ctx, &enc_pkt); @@ -403,7 +511,7 @@ encode_write_frame(struct encode_ctx *ctx, AVFrame *filt_frame, unsigned int str #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) +filter_encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame) { AVFrame *filt_frame; int ret; @@ -411,7 +519,7 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s // Push the decoded frame into the filtergraph if (frame) { - ret = av_buffersrc_add_frame_flags(ctx->filter_ctx[stream_index].buffersrc_ctx, frame, 0); + ret = av_buffersrc_add_frame_flags(s->buffersrc_ctx, frame, 0); if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret)); @@ -429,7 +537,7 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s return -1; } - ret = av_buffersink_get_frame(ctx->filter_ctx[stream_index].buffersink_ctx, filt_frame); + ret = av_buffersink_get_frame(s->buffersink_ctx, filt_frame); if (ret < 0) { /* if no more frames for output - returns AVERROR(EAGAIN) @@ -443,7 +551,7 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s } filt_frame->pict_type = AV_PICTURE_TYPE_NONE; - ret = encode_write_frame(ctx, filt_frame, stream_index, NULL); + ret = encode_write_frame(ctx, s, filt_frame, NULL); av_frame_free(&filt_frame); if (ret < 0) break; @@ -453,19 +561,16 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s } #else static int -filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int stream_index) +filter_encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame) { 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); + ret = av_buffersrc_write_frame(s->buffersrc_ctx, frame); if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Error while feeding the filtergraph: %s\n", err2str(ret)); @@ -483,10 +588,10 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s 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); + if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO && !(s->codec->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)) + ret = av_buffersink_read_samples(s->buffersink_ctx, &picref, s->codec->frame_size); else - ret = av_buffersink_read(ctx->filter_ctx[stream_index].buffersink_ctx, &picref); + ret = av_buffersink_read(s->buffersink_ctx, &picref); if (ret < 0) { @@ -501,7 +606,7 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s } avfilter_copy_buf_props(filt_frame, picref); - ret = encode_write_frame(ctx, filt_frame, stream_index, NULL); + ret = encode_write_frame(ctx, s, filt_frame, NULL); av_frame_free(&filt_frame); avfilter_unref_buffer(picref); if (ret < 0) @@ -518,59 +623,44 @@ filter_encode_write_frame(struct encode_ctx *ctx, AVFrame *frame, unsigned int s * * @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) +flush_decoder(AVFrame *frame, enum AVMediaType *type, struct decode_ctx *ctx) { - AVStream *in_stream; - AVPacket dummypacket; - int got_frame; - int i; + AVPacket dummypacket = { 0 }; + int got_frame = 0; - memset(&dummypacket, 0, sizeof(AVPacket)); - - for (i = 0; i < ctx->ifmt_ctx->nb_streams; i++) + if (ctx->audio_stream.codec) { - 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; + *type = AVMEDIA_TYPE_AUDIO; + avcodec_decode_audio4(ctx->audio_stream.codec, frame, &got_frame, &dummypacket); } - return 0; + if (!got_frame && ctx->video_stream.codec) + { + *type = AVMEDIA_TYPE_VIDEO; + avcodec_decode_video2(ctx->video_stream.codec, frame, &got_frame, &dummypacket); + } + + return got_frame; } static void -flush_encoder(struct encode_ctx *ctx, unsigned int stream_index) +flush_encoder(struct encode_ctx *ctx, struct stream_ctx *s) { int ret; int got_frame; - DPRINTF(E_DBG, L_XCODE, "Flushing output stream #%u encoder\n", stream_index); + DPRINTF(E_DBG, L_XCODE, "Flushing output stream #%u encoder\n", s->stream->index); - if (!(ctx->ofmt_ctx->streams[stream_index]->codec->codec->capabilities & CODEC_CAP_DELAY)) + if (!(s->codec->codec->capabilities & CODEC_CAP_DELAY)) return; do { - ret = encode_write_frame(ctx, NULL, stream_index, &got_frame); + ret = encode_write_frame(ctx, s, NULL, &got_frame); } while ((ret == 0) && got_frame); } @@ -579,28 +669,24 @@ flush_encoder(struct encode_ctx *ctx, unsigned int stream_index) /* --------------------------- INPUT/OUTPUT INIT --------------------------- */ static int -open_input(struct decode_ctx *ctx, const char *path, int decode_video) +open_input(struct decode_ctx *ctx, const char *path) { - AVDictionary *options; + AVDictionary *options = NULL; AVCodec *decoder; + AVCodecContext *dec_ctx; 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; - } + 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; @@ -631,61 +717,70 @@ 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; + // Find audio stream and open decoder + stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0); + if ((stream_index < 0) || (!decoder)) + { + DPRINTF(E_LOG, L_XCODE, "Did not find audio stream or suitable decoder for %s\n", path); + goto out_fail; + } + + CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder)); + + // In open_filter() we need to tell the sample rate and format that the decoder + // is giving us - however sample rate of dec_ctx will be 0 if we don't prime it + // with the streams codecpar data. + ret = avcodec_parameters_to_context(dec_ctx, ctx->ifmt_ctx->streams[stream_index]->codecpar); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Failed to copy codecpar for stream #%d: %s\n", stream_index, err2str(ret)); + goto out_fail; + } + + dec_ctx->request_sample_fmt = ctx->settings.sample_format; + dec_ctx->request_channel_layout = ctx->settings.channel_layout; + + ret = avcodec_open2(dec_ctx, NULL, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret)); + goto out_fail; + } + + 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; - } + // Find video stream and open decoder + stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0); + if ((stream_index < 0) || (!decoder)) + { + DPRINTF(E_LOG, L_XCODE, "Did not find video stream or suitable decoder for '%s': %s\n", path, err2str(ret)); + goto out_fail; + } - ctx->audio_stream = ctx->ifmt_ctx->streams[stream_index]; + CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder)); - // If no video then we are all done - if (!decode_video) - return 0; + ret = avcodec_open2(dec_ctx, NULL, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret)); + goto out_fail; + } - // 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: + avcodec_free_context(&ctx->audio_stream.codec); + avcodec_free_context(&ctx->video_stream.codec); avformat_close_input(&ctx->ifmt_ctx); return -1; @@ -694,29 +789,18 @@ 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); - + 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; int ret; - int i; ctx->ofmt_ctx = NULL; - avformat_alloc_output_context2(&ctx->ofmt_ctx, NULL, ctx->format, NULL); + avformat_alloc_output_context2(&ctx->ofmt_ctx, NULL, ctx->settings.format, NULL); if (!ctx->ofmt_ctx) { DPRINTF(E_LOG, L_XCODE, "Could not create output context\n"); @@ -727,89 +811,28 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) 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 +840,19 @@ 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; } 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 +861,32 @@ 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, 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(); + 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,14 +897,14 @@ 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) @@ -1110,8 +915,8 @@ open_filter(struct filter_ctx *filter_ctx, AVCodecContext *dec_ctx, AVCodecConte 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) @@ -1126,6 +931,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), + "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + in_stream->codec->width, in_stream->codec->height, 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\n", err2str(ret)); + goto out_fail; + } + + snprintf(args, sizeof(args), + "pix_fmt=%d", out_stream->codec->pix_fmt); + + ret = avfilter_graph_create_filter(&format_ctx, format, "format", args, NULL, filter_graph); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Cannot create format filter: %s\n", err2str(ret)); + goto out_fail; + } + + snprintf(args, sizeof(args), + "width=%d:height=%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\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; + } + + 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 +1005,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 +1021,24 @@ 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; + // anull is a passthrough (dummy) filter for audio + ret = open_filter(&ctx->audio_stream, &src_ctx->audio_stream, "anull"); + 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); + // null is a passthrough (dummy) filter for video + ret = open_filter(&ctx->video_stream, &src_ctx->video_stream, "null"); if (ret < 0) goto out_fail; } @@ -1200,12 +1046,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 +1055,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,21 +1065,16 @@ 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, 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))); ctx->duration = song_length; ctx->data_kind = data_kind; - if (open_input(ctx, path, decode_video) < 0) + if ((init_settings(&ctx->settings, profile) < 0) || (open_input(ctx, path) < 0)) { free(ctx); return NULL; @@ -1255,18 +1086,13 @@ transcode_decode_setup(enum data_kind data_kind, const char *path, uint32_t song } 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) { 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))); - if ((init_profile(ctx, profile) < 0) || (open_output(ctx, src_ctx) < 0)) + if ((init_settings(&ctx->settings, profile) < 0) || (open_output(ctx, src_ctx) < 0)) { free(ctx); return NULL; @@ -1280,37 +1106,29 @@ transcode_encode_setup(struct decode_ctx *src_ctx, enum transcode_profile profil } if (src_ctx->data_kind == DATA_KIND_HTTP) - ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->channels * ctx->byte_depth * ctx->sample_rate; + ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->settings.channels * ctx->settings.byte_depth * ctx->settings.sample_rate; - if (profile == XCODE_PCM16_HEADER) - { - ctx->wavhdr = 1; - make_wav_header(ctx, src_ctx, est_size); - } + if (ctx->settings.wavheader) + make_wav_header(ctx, src_ctx, est_size); return ctx; } 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 = malloc(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, 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); if (!ctx->encode_ctx) { transcode_decode_cleanup(ctx->decode_ctx); @@ -1325,41 +1143,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 @@ -1457,15 +1283,18 @@ transcode_decode_cleanup(struct decode_ctx *ctx) 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->audio_stream.stream) { - if (!ctx->filter_ctx[i].filter_graph) - continue; - filter_encode_write_frame(ctx, NULL, i); - flush_encoder(ctx, i); + if (ctx->audio_stream.filter_graph) + filter_encode_write_frame(ctx, &ctx->audio_stream, NULL); + flush_encoder(ctx, &ctx->audio_stream); + } + + if (ctx->video_stream.stream) + { + if (ctx->video_stream.filter_graph) + filter_encode_write_frame(ctx, &ctx->video_stream, NULL); + flush_encoder(ctx, &ctx->video_stream); } av_write_trailer(ctx->ofmt_ctx); @@ -1498,9 +1327,8 @@ int transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) { AVPacket packet; - AVStream *in_stream; AVFrame *frame; - unsigned int stream_index; + enum AVMediaType type; int got_frame; int retry; int ret; @@ -1519,14 +1347,14 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) retry = 0; do { - ret = read_packet(&packet, &in_stream, &stream_index, ctx); + ret = read_packet(&packet, &type, 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); + got_frame = flush_decoder(frame, &type, ctx); if (got_frame) break; @@ -1540,22 +1368,22 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) // "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); + if (type == AVMEDIA_TYPE_AUDIO) + used = avcodec_decode_audio4(ctx->audio_stream.codec, frame, &got_frame, &packet); else - used = avcodec_decode_video2(in_stream->codec, frame, &got_frame, &packet); + used = avcodec_decode_video2(ctx->video_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"); + DPRINTF(E_DBG, L_XCODE, "Couldn't decode packet: %s\n", err2str(used)); retry += 1; if (retry < MAX_BAD_PACKETS) continue; - DPRINTF(E_LOG, L_XCODE, "Couldn't decode packet after %i retries\n", MAX_BAD_PACKETS); + DPRINTF(E_LOG, L_XCODE, "Couldn't decode packet after %i retries: %s\n", MAX_BAD_PACKETS, err2str(used)); av_frame_free(&frame); return -1; @@ -1587,7 +1415,7 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) } (*decoded)->frame = frame; - (*decoded)->stream_index = stream_index; + (*decoded)->type = type; } return got_frame; @@ -1597,24 +1425,27 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) int transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx) { - int stream_index; + struct stream_ctx *s; int encoded_length; int ret; encoded_length = 0; - stream_index = ctx->out_stream_map[decoded->stream_index]; - if (stream_index < 0) + if (decoded->type == AVMEDIA_TYPE_AUDIO) + s = &ctx->audio_stream; + else if (decoded->type == AVMEDIA_TYPE_VIDEO) + s = &ctx->video_stream; + else return -1; - if (ctx->wavhdr) + if (ctx->settings.wavheader) { encoded_length += sizeof(ctx->header); evbuffer_add(evbuf, ctx->header, sizeof(ctx->header)); - ctx->wavhdr = 0; + ctx->settings.wavheader = 0; } - ret = filter_encode_write_frame(ctx, decoded->frame, stream_index); + ret = filter_encode_write_frame(ctx, s, decoded->frame); if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", err2str(ret)); @@ -1680,8 +1511,8 @@ transcode_raw2frame(uint8_t *data, size_t size) return NULL; } - decoded->stream_index = 0; - decoded->frame = frame; + decoded->type = AVMEDIA_TYPE_AUDIO; + decoded->frame = frame; frame->nb_samples = size / 4; frame->format = AV_SAMPLE_FMT_S16; @@ -1704,91 +1535,82 @@ transcode_raw2frame(uint8_t *data, size_t size) } -/* 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;*/ - - /* Seeking */ 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); + av_packet_unref(&dec_ctx->packet); - decode_ctx->timestamp = av_gettime(); + dec_ctx->timestamp = av_gettime(); - ret = av_read_frame(decode_ctx->ifmt_ctx, &decode_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; + dec_ctx->resume = 1; + dec_ctx->resume_offset = 0; // 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 diff --git a/src/transcode.h b/src/transcode.h index 3a8614f0..c6d60cd7 100644 --- a/src/transcode.h +++ b/src/transcode.h @@ -6,19 +6,17 @@ #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; @@ -28,13 +26,13 @@ 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, 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); 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); From 7c8eba74bbdaa6a202cf9f5c44face5f5b0bbea5 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 26 Feb 2017 15:40:37 +0100 Subject: [PATCH 02/16] [transcode] Remove unused param to open_filter() --- src/transcode.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/transcode.c b/src/transcode.c index 29d4b9a4..6b7dc00f 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -870,7 +870,7 @@ close_output(struct encode_ctx *ctx) } static int -open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream, const char *filter_spec) +open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) { AVFilter *buffersrc; AVFilter *format; @@ -1029,16 +1029,14 @@ open_filters(struct encode_ctx *ctx, struct decode_ctx *src_ctx) if (ctx->settings.encode_audio) { - // anull is a passthrough (dummy) filter for audio - ret = open_filter(&ctx->audio_stream, &src_ctx->audio_stream, "anull"); + ret = open_filter(&ctx->audio_stream, &src_ctx->audio_stream); if (ret < 0) goto out_fail; } if (ctx->settings.encode_video) { - // null is a passthrough (dummy) filter for video - ret = open_filter(&ctx->video_stream, &src_ctx->video_stream, "null"); + ret = open_filter(&ctx->video_stream, &src_ctx->video_stream); if (ret < 0) goto out_fail; } From 5afed60a42fef25693cd678c20b62343cd978394 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 26 Feb 2017 17:50:04 +0100 Subject: [PATCH 03/16] [transcode] Implement new ffmpeg encoding methods: avcodec_send_frame/avcodec_receive_packet --- src/transcode.c | 229 ++++++++++++++++-------------------------------- 1 file changed, 75 insertions(+), 154 deletions(-) diff --git a/src/transcode.c b/src/transcode.c index 6b7dc00f..a587081a 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -93,6 +93,10 @@ struct stream_ctx AVFilterContext *buffersink_ctx; AVFilterContext *buffersrc_ctx; AVFilterGraph *filter_graph; + + // Used for seeking + int64_t prev_pts; + int64_t offset_pts; }; struct decode_ctx @@ -139,9 +143,11 @@ struct encode_ctx // The ffmpeg muxer writes to this buffer using the avio_evbuffer interface struct evbuffer *obuf; - // Used for seeking - int64_t prev_pts[MAX_STREAMS]; - int64_t offset_pts[MAX_STREAMS]; + // Contains the most recent packet from av_buffersink_get_frame() + AVFrame *filt_frame; + + // 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; @@ -457,69 +463,67 @@ read_packet(AVPacket *packet, enum AVMediaType *type, struct decode_ctx *ctx) return 0; } -static int -encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_frame, int *got_frame) +// Prepares a packet from the encoder for muxing +static void +packet_prepare(AVPacket *pkt, struct stream_ctx *s) +{ + pkt->stream_index = s->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) + { + s->offset_pts += s->prev_pts - pkt->pts; + pkt->pts = s->prev_pts; + } + s->prev_pts = pkt->pts; + pkt->dts = pkt->pts; //FIXME + + av_packet_rescale_ts(pkt, s->codec->time_base, s->stream->time_base); +} + +static int +encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_frame) { - AVPacket enc_pkt; - unsigned int stream_index; int ret; - int got_frame_local; - - if (!got_frame) - got_frame = &got_frame_local; - - stream_index = s->stream->index; - - // Encode filtered frame - enc_pkt.data = NULL; - enc_pkt.size = 0; - av_init_packet(&enc_pkt); - - if (s->codec->codec_type == AVMEDIA_TYPE_AUDIO) - ret = avcodec_encode_audio2(s->codec, &enc_pkt, filt_frame, got_frame); - else if (s->codec->codec_type == AVMEDIA_TYPE_VIDEO) - ret = avcodec_encode_video2(s->codec, &enc_pkt, filt_frame, got_frame); - else - return -1; + // 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 -1; - if (!(*got_frame)) - return 0; + return ret; - // 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]) + while (1) { - ctx->offset_pts[stream_index] += ctx->prev_pts[stream_index] - enc_pkt.pts; - enc_pkt.pts = ctx->prev_pts[stream_index]; + 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; } - ctx->prev_pts[stream_index] = enc_pkt.pts; - enc_pkt.dts = enc_pkt.pts; //FIXME - av_packet_rescale_ts(&enc_pkt, s->codec->time_base, s->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, struct stream_ctx *s, AVFrame *frame) +filter_encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame) { - AVFrame *filt_frame; int ret; // Push the decoded frame into the filtergraph if (frame) { - ret = av_buffersrc_add_frame_flags(s->buffersrc_ctx, frame, 0); + 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)); @@ -527,95 +531,28 @@ filter_encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame } } - // Pull filtered frames from the filtergraph + // Pull filtered frames from the filtergraph and pass to encoder 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(s->buffersink_ctx, filt_frame); + ret = av_buffersink_get_frame(s->buffersink_ctx, 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) + if (!frame) // We are flushing + ret = encode_write(ctx, s, NULL); + else if (ret == AVERROR(EAGAIN)) ret = 0; - av_frame_free(&filt_frame); + break; } - filt_frame->pict_type = AV_PICTURE_TYPE_NONE; - ret = encode_write_frame(ctx, s, filt_frame, NULL); - av_frame_free(&filt_frame); + ret = encode_write(ctx, s, ctx->filt_frame); + av_frame_unref(ctx->filt_frame); if (ret < 0) break; } return ret; } -#else -static int -filter_encode_write_frame(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame) -{ - AVFilterBufferRef *picref; - AVFrame *filt_frame; - int ret; - - // Push the decoded frame into the filtergraph - if (frame) - { - ret = av_buffersrc_write_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 - 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 (s->codec->codec_type == AVMEDIA_TYPE_AUDIO && !(s->codec->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)) - ret = av_buffersink_read_samples(s->buffersink_ctx, &picref, s->codec->frame_size); - else - ret = av_buffersink_read(s->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, s, filt_frame, 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 @@ -647,24 +584,6 @@ flush_decoder(AVFrame *frame, enum AVMediaType *type, struct decode_ctx *ctx) return got_frame; } -static void -flush_encoder(struct encode_ctx *ctx, struct stream_ctx *s) -{ - int ret; - int got_frame; - - DPRINTF(E_DBG, L_XCODE, "Flushing output stream #%u encoder\n", s->stream->index); - - if (!(s->codec->codec->capabilities & CODEC_CAP_DELAY)) - return; - - do - { - ret = encode_write_frame(ctx, s, NULL, &got_frame); - } - while ((ret == 0) && got_frame); -} - /* --------------------------- INPUT/OUTPUT INIT --------------------------- */ @@ -1069,6 +988,8 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct decode_ctx))); + av_init_packet(&ctx->packet); + ctx->duration = song_length; ctx->data_kind = data_kind; @@ -1078,8 +999,6 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, return NULL; } - av_init_packet(&ctx->packet); - return ctx; } @@ -1089,6 +1008,8 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct struct encode_ctx *ctx; 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_settings(&ctx->settings, profile) < 0) || (open_output(ctx, src_ctx) < 0)) { @@ -1281,24 +1202,24 @@ transcode_decode_cleanup(struct decode_ctx *ctx) void transcode_encode_cleanup(struct encode_ctx *ctx) { + // Flush audio encoder if (ctx->audio_stream.stream) - { - if (ctx->audio_stream.filter_graph) - filter_encode_write_frame(ctx, &ctx->audio_stream, NULL); - flush_encoder(ctx, &ctx->audio_stream); - } + filter_encode_write(ctx, &ctx->audio_stream, NULL); + // Flush video encoder if (ctx->video_stream.stream) - { - if (ctx->video_stream.filter_graph) - filter_encode_write_frame(ctx, &ctx->video_stream, NULL); - flush_encoder(ctx, &ctx->video_stream); - } + filter_encode_write(ctx, &ctx->video_stream, NULL); + + // Flush muxer + av_interleaved_write_frame(ctx->ofmt_ctx, NULL); av_write_trailer(ctx->ofmt_ctx); close_filters(ctx); close_output(ctx); + + av_packet_free(&ctx->encoded_pkt); + av_frame_free(&ctx->filt_frame); free(ctx); } @@ -1443,7 +1364,7 @@ transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct e ctx->settings.wavheader = 0; } - ret = filter_encode_write_frame(ctx, s, decoded->frame); + ret = filter_encode_write(ctx, s, decoded->frame); if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Error occurred: %s\n", err2str(ret)); From e96b9500dbec61236317567db4834dc095db1c2b Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 26 Feb 2017 23:41:30 +0100 Subject: [PATCH 04/16] [transcode] Implement new ffmpeg decoding methods: avcodec_send_packet/avcodec_receive_frame --- src/httpd.c | 4 +- src/httpd_streaming.c | 6 +- src/input.c | 2 +- src/inputs/file_http.c | 4 +- src/transcode.c | 452 ++++++++++++++++++++--------------------- src/transcode.h | 39 ++-- 6 files changed, 249 insertions(+), 258 deletions(-) diff --git a/src/httpd.c b/src/httpd.c index 5b01681a..79ab8b86 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); @@ -750,7 +750,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 115f4021..f5262b9c 100644 --- a/src/httpd_streaming.c +++ b/src/httpd_streaming.c @@ -300,7 +300,7 @@ streaming_init(void) } streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL); - transcode_decode_cleanup(decode_ctx); + 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"); @@ -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 6335a5b3..de0517c4 100644 --- a/src/inputs/file_http.c +++ b/src/inputs/file_http.c @@ -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/transcode.c b/src/transcode.c index a587081a..e529df64 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -117,12 +117,14 @@ 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; + + // 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; @@ -164,6 +166,8 @@ struct transcode_ctx { struct decode_ctx *decode_ctx; struct encode_ctx *encode_ctx; + + bool eof; }; struct decoded_frame @@ -407,56 +411,42 @@ 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 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, enum AVMediaType *type, struct decode_ctx *ctx) +read_packet(enum AVMediaType *type, struct decode_ctx *dec_ctx) { 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; } - *type = stream_find(ctx, packet->stream_index); + *type = stream_find(dec_ctx, dec_ctx->packet->stream_index); } while (*type == AVMEDIA_TYPE_UNKNOWN); @@ -484,6 +474,10 @@ packet_prepare(AVPacket *pkt, struct stream_ctx *s) av_packet_rescale_ts(pkt, s->codec->time_base, s->stream->time_base); } +/* + * Part 4 of the conversion chain: read -> decode -> filter -> encode -> write + * + */ static int encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_frame) { @@ -515,6 +509,12 @@ encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *filt_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) { @@ -554,34 +554,92 @@ filter_encode_write(struct encode_ctx *ctx, struct stream_ctx *s, AVFrame *frame return ret; } -/* 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 2 of the conversion chain: read -> decode -> filter -> encode -> write + * + * If there is no encode_ctx the chain will aborted here * - * @out frame AVFrame if there was anything to flush, otherwise undefined - * @out stream Set to the AVStream where a decoder returned a frame - * @in ctx Decode context - * @return Non-zero (true) if frame found, otherwise 0 (false) */ static int -flush_decoder(AVFrame *frame, enum AVMediaType *type, struct decode_ctx *ctx) +decode_filter_encode_write(struct transcode_ctx *ctx, struct stream_ctx *s, AVPacket *pkt, enum AVMediaType type) { - AVPacket dummypacket = { 0 }; - int got_frame = 0; + struct decode_ctx *dec_ctx = ctx->decode_ctx; + struct stream_ctx *out_stream = NULL; + int ret; - if (ctx->audio_stream.codec) + ret = avcodec_send_packet(s->codec, pkt); + if (ret < 0) + return ret; + + if (ctx->encode_ctx) { - *type = AVMEDIA_TYPE_AUDIO; - avcodec_decode_audio4(ctx->audio_stream.codec, frame, &got_frame, &dummypacket); + 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; } - if (!got_frame && ctx->video_stream.codec) + while (1) { - *type = AVMEDIA_TYPE_VIDEO; - avcodec_decode_video2(ctx->video_stream.codec, frame, &got_frame, &dummypacket); + 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; + } + + if (!out_stream) + break; + + ret = filter_encode_write(ctx->encode_ctx, out_stream, dec_ctx->decoded_frame); + if (ret < 0) + break; } - return got_frame; + 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) + { + DPRINTF(E_DBG, L_XCODE, "No more input, flushing codecs\n"); + + 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); + + 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; } @@ -762,6 +820,11 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) goto out_free_streams; } + if (ctx->settings.wavheader) + { + evbuffer_add(ctx->obuf, ctx->header, sizeof(ctx->header)); + } + return 0; out_free_streams: @@ -987,19 +1050,22 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, struct decode_ctx *ctx; CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct decode_ctx))); - - av_init_packet(&ctx->packet); + 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 ((init_settings(&ctx->settings, profile) < 0) || (open_input(ctx, path) < 0)) - { - free(ctx); - return NULL; - } + 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 * @@ -1011,26 +1077,30 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct CHECK_NULL(L_XCODE, ctx->filt_frame = av_frame_alloc()); CHECK_NULL(L_XCODE, ctx->encoded_pkt = av_packet_alloc()); - if ((init_settings(&ctx->settings, 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->settings.channels * ctx->settings.byte_depth * ctx->settings.sample_rate; + if (init_settings(&ctx->settings, profile) < 0) + goto fail_free; 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) + goto fail_close; + + if (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 * @@ -1038,7 +1108,7 @@ transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const { struct transcode_ctx *ctx; - CHECK_NULL(L_XCODE, ctx = malloc(sizeof(struct transcode_ctx))); + CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct transcode_ctx))); ctx->decode_ctx = transcode_decode_setup(profile, data_kind, path, song_length); if (!ctx->decode_ctx) @@ -1050,7 +1120,7 @@ transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const ctx->encode_ctx = transcode_encode_setup(profile, ctx->decode_ctx, est_size); if (!ctx->encode_ctx) { - transcode_decode_cleanup(ctx->decode_ctx); + transcode_decode_cleanup(&ctx->decode_ctx); free(ctx); return NULL; } @@ -1192,43 +1262,54 @@ 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) { + if (!*ctx) + return; + // Flush audio encoder - if (ctx->audio_stream.stream) - filter_encode_write(ctx, &ctx->audio_stream, NULL); + if ((*ctx)->audio_stream.stream) + filter_encode_write(*ctx, &(*ctx)->audio_stream, NULL); // Flush video encoder - if (ctx->video_stream.stream) - filter_encode_write(ctx, &ctx->video_stream, NULL); + if ((*ctx)->video_stream.stream) + filter_encode_write(*ctx, &(*ctx)->video_stream, NULL); // Flush muxer - av_interleaved_write_frame(ctx->ofmt_ctx, NULL); + av_interleaved_write_frame((*ctx)->ofmt_ctx, NULL); - av_write_trailer(ctx->ofmt_ctx); + av_write_trailer((*ctx)->ofmt_ctx); - close_filters(ctx); - close_output(ctx); + close_filters(*ctx); + close_output(*ctx); - av_packet_free(&ctx->encoded_pkt); - av_frame_free(&ctx->filt_frame); - 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); + transcode_encode_cleanup(&(*ctx)->encode_ctx); + transcode_decode_cleanup(&(*ctx)->decode_ctx); + free(*ctx); + *ctx = NULL; } void @@ -1241,103 +1322,11 @@ transcode_decoded_free(struct decoded_frame *decoded) /* Encoding, decoding and transcoding */ - int -transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) +transcode_decode(struct decoded_frame **decoded, struct decode_ctx *dec_ctx) { - AVPacket packet; - AVFrame *frame; - enum AVMediaType type; - int got_frame; - int retry; - 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"); - - return -1; - } - - // Loop until we either fail or get a frame - retry = 0; - do - { - ret = read_packet(&packet, &type, 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, &type, 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 (type == AVMEDIA_TYPE_AUDIO) - used = avcodec_decode_audio4(ctx->audio_stream.codec, frame, &got_frame, &packet); - else - used = avcodec_decode_video2(ctx->video_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: %s\n", err2str(used)); - - retry += 1; - if (retry < MAX_BAD_PACKETS) - continue; - - DPRINTF(E_LOG, L_XCODE, "Couldn't decode packet after %i retries: %s\n", MAX_BAD_PACKETS, err2str(used)); - - 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; - } - } - 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(&frame); - return -1; - } - - (*decoded)->frame = frame; - (*decoded)->type = type; - } - - return got_frame; + DPRINTF(E_LOG, L_XCODE, "Bug! Call to transcode_decode(), but the lazy programmer didn't implement it\n"); + return -1; } // Filters and encodes @@ -1345,10 +1334,10 @@ int transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx) { struct stream_ctx *s; - int encoded_length; + size_t start_length; int ret; - encoded_length = 0; + start_length = evbuffer_get_length(ctx->obuf); if (decoded->type == AVMEDIA_TYPE_AUDIO) s = &ctx->audio_stream; @@ -1357,54 +1346,55 @@ transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct e else return -1; - if (ctx->settings.wavheader) - { - encoded_length += sizeof(ctx->header); - evbuffer_add(evbuf, ctx->header, sizeof(ctx->header)); - ctx->settings.wavheader = 0; - } - ret = filter_encode_write(ctx, s, decoded->frame); 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); + 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 want_bytes, struct transcode_ctx *ctx, int *icy_timer) { - struct decoded_frame *decoded; - int processed; + size_t start_length; + int processed = 0; int ret; *icy_timer = 0; - processed = 0; - while (processed < wanted) + if (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) *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed); + if (ret == AVERROR_EOF) + { + ctx->eof = 1; + ret = 0; + } + else if (ret < 0) + return ret; + return processed; } @@ -1496,11 +1486,10 @@ transcode_seek(struct transcode_ctx *ctx, int ms) s->codec->skip_frame = AVDISCARD_NONREF; while (1) { - av_packet_unref(&dec_ctx->packet); - dec_ctx->timestamp = av_gettime(); - ret = av_read_frame(dec_ctx->ifmt_ctx, &dec_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)); @@ -1508,23 +1497,22 @@ transcode_seek(struct transcode_ctx *ctx, int ms) return -1; } - if (stream_find(dec_ctx, dec_ctx->packet.stream_index) == AVMEDIA_TYPE_UNKNOWN) + if (stream_find(dec_ctx, dec_ctx->packet->stream_index) == AVMEDIA_TYPE_UNKNOWN) continue; // Need a pts to return the real position - if (dec_ctx->packet.pts == AV_NOPTS_VALUE) + if (dec_ctx->packet->pts == AV_NOPTS_VALUE) continue; break; } s->codec->skip_frame = AVDISCARD_DEFAULT; - // Tell transcode_decode() to resume with ctx->packet + // Tell read_packet() to resume with dec_ctx->packet dec_ctx->resume = 1; - dec_ctx->resume_offset = 0; // Compute position in ms from pts - got_pts = dec_ctx->packet.pts; + got_pts = dec_ctx->packet->pts; if ((start_time != AV_NOPTS_VALUE) && (start_time > 0)) got_pts -= start_time; diff --git a/src/transcode.h b/src/transcode.h index c6d60cd7..e55128bd 100644 --- a/src/transcode.h +++ b/src/transcode.h @@ -42,13 +42,13 @@ 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); +transcode_cleanup(struct transcode_ctx **ctx); void transcode_decoded_free(struct decoded_frame *decoded); @@ -57,35 +57,36 @@ transcode_decoded_free(struct decoded_frame *decoded); /* 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 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 */ int transcode_decode(struct decoded_frame **decoded, 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 frame The frame to encode, e.g. from transcode_decode + * @in ctx Encode context + * @return Bytes added if OK, negative if error */ int transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx); -/* 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 + * @in want_bytes Minimum number of bytes the caller wants added to the evbuffer + * - set want_bytes to 0 to transcode everything until EOF/error + * - set want_bytes to 1 to get one encoded packet + * @in ctx Transcode context + * @out icy_timer True if METADATA_ICY_INTERVAL has elapsed + * @return Bytes added if OK, negative if error, 0 if EOF */ int -transcode(struct evbuffer *evbuf, int wanted, struct transcode_ctx *ctx, int *icy_timer); +transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int *icy_timer); struct decoded_frame * transcode_raw2frame(uint8_t *data, size_t size); From 1e180b5ce8d0c1dc0a7324bc23e8b5359379b336 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 27 Feb 2017 20:42:07 +0100 Subject: [PATCH 05/16] [transcode] Call av_write_trailer before cleanup so that any flushed data will be written to encode_ctx->obuf, where it can be passed to the caller --- src/transcode.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/transcode.c b/src/transcode.c index e529df64..6c89dd88 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -120,6 +120,9 @@ struct decode_ctx // Set to true if we just seeked bool resume; + // Set to true if we have reached eof + bool eof; + // Contains the most recent packet from av_read_frame() AVPacket *packet; @@ -166,8 +169,6 @@ struct transcode_ctx { struct decode_ctx *decode_ctx; struct encode_ctx *encode_ctx; - - bool eof; }; struct decoded_frame @@ -475,7 +476,7 @@ packet_prepare(AVPacket *pkt, struct stream_ctx *s) } /* - * Part 4 of the conversion chain: read -> decode -> filter -> encode -> write + * Part 4+5 of the conversion chain: read -> decode -> filter -> encode -> write * */ static int @@ -624,13 +625,21 @@ read_decode_filter_encode_write(struct transcode_ctx *ctx) ret = read_packet(&type, dec_ctx); if (ret < 0) { - DPRINTF(E_DBG, L_XCODE, "No more input, flushing codecs\n"); + 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; } @@ -848,6 +857,7 @@ close_output(struct encode_ctx *ctx) avio_evbuffer_close(ctx->ofmt_ctx->pb); evbuffer_free(ctx->obuf); + avformat_free_context(ctx->ofmt_ctx); } @@ -1281,19 +1291,6 @@ transcode_encode_cleanup(struct encode_ctx **ctx) if (!*ctx) return; - // Flush audio encoder - if ((*ctx)->audio_stream.stream) - filter_encode_write(*ctx, &(*ctx)->audio_stream, NULL); - - // Flush video encoder - if ((*ctx)->video_stream.stream) - filter_encode_write(*ctx, &(*ctx)->video_stream, NULL); - - // Flush muxer - av_interleaved_write_frame((*ctx)->ofmt_ctx, NULL); - - av_write_trailer((*ctx)->ofmt_ctx); - close_filters(*ctx); close_output(*ctx); @@ -1369,7 +1366,7 @@ transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int *icy_timer = 0; - if (ctx->eof) + if (ctx->decode_ctx->eof) return 0; start_length = evbuffer_get_length(ctx->encode_ctx->obuf); @@ -1388,10 +1385,7 @@ transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed); if (ret == AVERROR_EOF) - { - ctx->eof = 1; - ret = 0; - } + ret = 0; else if (ret < 0) return ret; From d933e171d44e43a6a6061ac6ce23951a46ddeb0a Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 27 Feb 2017 21:34:58 +0100 Subject: [PATCH 06/16] [filescanner_ffmpeg] Update to use ffmpeg's new stream->codecpar - keep some compability with older ffmpeg/libav, but also remove some --- configure.ac | 1 + src/library/filescanner_ffmpeg.c | 166 +++++-------------------------- 2 files changed, 26 insertions(+), 141 deletions(-) diff --git a/configure.ac b/configure.ac index 5d4d7252..9ee25476 100644 --- a/configure.ac +++ b/configure.ac @@ -245,6 +245,7 @@ FORK_MODULES_CHECK([FORKED], [LIBAV], [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([avcodec_parameters_alloc], [libavcodec/avcodec.h]) FORK_CHECK_DECLS([avformat_alloc_output_context2], [libavformat/avformat.h]) FORK_CHECK_DECLS([av_frame_alloc], [libavutil/frame.h]) diff --git a/src/library/filescanner_ffmpeg.c b/src/library/filescanner_ffmpeg.c index 975233de..0b276f20 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,7 +368,6 @@ 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 @@ -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_ALLOC + 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"); From e7f888645fd6273941a7fe156e93ebb62413eaa8 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Tue, 28 Feb 2017 23:06:01 +0100 Subject: [PATCH 07/16] [artwork/transcode] Adjust transcode.c so it can take care of artwork rescaling, meaning we can do without parallel ffmpeg interfaces. This also moves artwork rescaling from libswscale to libavfilter, which seems to fix a problem with PNG rescaling. --- src/artwork.c | 559 ++++++----------------------------------- src/httpd.c | 3 +- src/httpd_streaming.c | 22 +- src/inputs/file_http.c | 2 +- src/logger.c | 4 +- src/transcode.c | 292 +++++++++++++-------- src/transcode.h | 54 ++-- 7 files changed, 316 insertions(+), 620 deletions(-) diff --git a/src/artwork.c b/src/artwork.c index ed8471d5..b5dbf0a1 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,326 +316,9 @@ rescale_needed(AVCodecContext *src, int max_w, int max_h, int *target_w, int *ta if ((*target_w > max_w) && (max_w > 0)) *target_w = max_w - (max_w % 2); - DPRINTF(E_DBG, L_ART, "Destination width %d height %d\n", *target_w, *target_h); + DPRINTF(E_DBG, L_ART, "Rescale required, destination width %d height %d\n", *target_w, *target_h); - return 1; -} - -/* Rescale an image - * - * @out evbuf Rescaled image data - * @in src_ctx Image source - * @in s Index of stream containing image - * @in out_w Rescaled width - * @in out_h Rescaled height - * @return ART_FMT_* on success, -1 on error - */ -static int -artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out_w, int out_h) -{ - uint8_t *buf; - - AVCodecContext *src; - - AVFormatContext *dst_ctx; - AVCodecContext *dst; - AVOutputFormat *dst_fmt; - AVStream *dst_st; - - AVCodec *img_decoder; - AVCodec *img_encoder; - - AVFrame *i_frame; - AVFrame *o_frame; - - struct SwsContext *swsctx; - - AVPacket pkt; - int have_frame; - int ret; - - src = src_ctx->streams[s]->codec; - - // Avoids threading issue in both ffmpeg and libav that prevents decoding embedded png's - src->thread_count = 1; - - img_decoder = avcodec_find_decoder(src->codec_id); - if (!img_decoder) - { - DPRINTF(E_LOG, L_ART, "No suitable decoder found for artwork %s\n", src_ctx->filename); - - return -1; - } - - ret = avcodec_open2(src, img_decoder, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_ART, "Could not open codec for decoding: %s\n", strerror(AVUNERROR(ret))); - - return -1; - } - - if (src->pix_fmt < 0) - { - DPRINTF(E_LOG, L_ART, "Unknown pixel format for artwork %s\n", src_ctx->filename); - - ret = -1; - goto out_close_src; - } - - /* Set up output */ - dst_fmt = av_guess_format("image2", NULL, NULL); - if (!dst_fmt) - { - DPRINTF(E_LOG, L_ART, "ffmpeg image2 muxer not available\n"); - - ret = -1; - goto out_close_src; - } - - dst_fmt->video_codec = AV_CODEC_ID_NONE; - - /* Try to keep same codec if possible */ - if (src->codec_id == AV_CODEC_ID_PNG) - dst_fmt->video_codec = AV_CODEC_ID_PNG; - else if (src->codec_id == AV_CODEC_ID_MJPEG) - dst_fmt->video_codec = AV_CODEC_ID_MJPEG; - - /* If not possible, select new codec */ - if (dst_fmt->video_codec == AV_CODEC_ID_NONE) - { - dst_fmt->video_codec = AV_CODEC_ID_PNG; - } - - img_encoder = avcodec_find_encoder(dst_fmt->video_codec); - if (!img_encoder) - { - DPRINTF(E_LOG, L_ART, "No suitable encoder found for codec ID %d\n", dst_fmt->video_codec); - - ret = -1; - goto out_close_src; - } - - dst_ctx = avformat_alloc_context(); - if (!dst_ctx) - { - DPRINTF(E_LOG, L_ART, "Out of memory for format context\n"); - - ret = -1; - goto out_close_src; - } - - dst_ctx->oformat = dst_fmt; - - dst_fmt->flags &= ~AVFMT_NOFILE; - - dst_st = avformat_new_stream(dst_ctx, NULL); - if (!dst_st) - { - DPRINTF(E_LOG, L_ART, "Out of memory for new output stream\n"); - - ret = -1; - goto out_free_dst_ctx; - } - - dst = dst_st->codec; - - avcodec_get_context_defaults3(dst, NULL); - - if (dst_fmt->flags & AVFMT_GLOBALHEADER) - dst->flags |= CODEC_FLAG_GLOBAL_HEADER; - - dst->codec_id = dst_fmt->video_codec; - dst->codec_type = AVMEDIA_TYPE_VIDEO; - - dst->pix_fmt = avcodec_default_get_format(dst, img_encoder->pix_fmts); - if (dst->pix_fmt < 0) - { - DPRINTF(E_LOG, L_ART, "Could not determine best pixel format\n"); - - ret = -1; - goto out_free_dst_ctx; - } - - dst->time_base.num = 1; - dst->time_base.den = 25; - - dst->width = out_w; - dst->height = out_h; - - /* Open encoder */ - ret = avcodec_open2(dst, img_encoder, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_ART, "Could not open codec for encoding: %s\n", strerror(AVUNERROR(ret))); - - ret = -1; - goto out_free_dst_ctx; - } - - i_frame = av_frame_alloc(); - o_frame = av_frame_alloc(); - if (!i_frame || !o_frame) - { - DPRINTF(E_LOG, L_ART, "Could not allocate input/output frame\n"); - - ret = -1; - goto out_free_frames; - } - - ret = av_image_get_buffer_size(dst->pix_fmt, src->width, src->height, 1); - - DPRINTF(E_DBG, L_ART, "Artwork buffer size: %d\n", ret); - - buf = (uint8_t *)av_malloc(ret); - if (!buf) - { - DPRINTF(E_LOG, L_ART, "Out of memory for artwork buffer\n"); - - ret = -1; - goto out_free_frames; - } - -#if HAVE_DECL_AV_IMAGE_FILL_ARRAYS - av_image_fill_arrays(o_frame->data, o_frame->linesize, buf, dst->pix_fmt, src->width, src->height, 1); -#else - avpicture_fill((AVPicture *)o_frame, buf, dst->pix_fmt, src->width, src->height); -#endif - o_frame->height = dst->height; - o_frame->width = dst->width; - o_frame->format = dst->pix_fmt; - - swsctx = sws_getContext(src->width, src->height, src->pix_fmt, - dst->width, dst->height, dst->pix_fmt, - SWS_BICUBIC, NULL, NULL, NULL); - if (!swsctx) - { - DPRINTF(E_LOG, L_ART, "Could not get SWS context\n"); - - ret = -1; - goto out_free_buf; - } - - /* Get frame */ - have_frame = 0; - while (av_read_frame(src_ctx, &pkt) == 0) - { - if (pkt.stream_index != s) - { - av_packet_unref(&pkt); - continue; - } - - avcodec_decode_video2(src, i_frame, &have_frame, &pkt); - break; - } - - if (!have_frame) - { - DPRINTF(E_LOG, L_ART, "Could not decode artwork\n"); - - av_packet_unref(&pkt); - sws_freeContext(swsctx); - - ret = -1; - goto out_free_buf; - } - - /* Scale */ - sws_scale(swsctx, (const uint8_t * const *)i_frame->data, i_frame->linesize, 0, src->height, o_frame->data, o_frame->linesize); - - sws_freeContext(swsctx); - av_packet_unref(&pkt); - - /* Open output file */ - dst_ctx->pb = avio_output_evbuffer_open(evbuf); - if (!dst_ctx->pb) - { - DPRINTF(E_LOG, L_ART, "Could not open artwork destination buffer\n"); - - ret = -1; - goto out_free_buf; - } - - /* Encode frame */ - av_init_packet(&pkt); - pkt.data = NULL; - pkt.size = 0; - - ret = avcodec_encode_video2(dst, &pkt, o_frame, &have_frame); - if (ret < 0) - { - DPRINTF(E_LOG, L_ART, "Could not encode artwork\n"); - - ret = -1; - goto out_fclose_dst; - } - - ret = avformat_write_header(dst_ctx, NULL); - if (ret != 0) - { - DPRINTF(E_LOG, L_ART, "Could not write artwork header: %s\n", strerror(AVUNERROR(ret))); - - ret = -1; - goto out_fclose_dst; - } - - ret = av_interleaved_write_frame(dst_ctx, &pkt); - - if (ret != 0) - { - DPRINTF(E_LOG, L_ART, "Error writing artwork\n"); - - ret = -1; - goto out_fclose_dst; - } - - ret = av_write_trailer(dst_ctx); - if (ret != 0) - { - DPRINTF(E_LOG, L_ART, "Could not write artwork trailer: %s\n", strerror(AVUNERROR(ret))); - - ret = -1; - goto out_fclose_dst; - } - - switch (dst_fmt->video_codec) - { - case AV_CODEC_ID_PNG: - ret = ART_FMT_PNG; - break; - - case AV_CODEC_ID_MJPEG: - ret = ART_FMT_JPEG; - break; - - default: - DPRINTF(E_LOG, L_ART, "Unhandled rescale output format\n"); - ret = -1; - break; - } - - out_fclose_dst: - avio_evbuffer_close(dst_ctx->pb); - av_packet_unref(&pkt); - - out_free_buf: - av_free(buf); - - out_free_frames: - if (i_frame) - av_frame_free(&i_frame); - if (o_frame) - av_frame_free(&o_frame); - avcodec_close(dst); - - out_free_dst_ctx: - avformat_free_context(dst_ctx); - - out_close_src: - avcodec_close(src); - - return ret; + return 0; } /* Get an artwork file from the filesystem. Will rescale if needed. @@ -657,8 +332,11 @@ artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out static int artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h) { - AVFormatContext *src_ctx; - int s; + struct decode_ctx *xcode_decode; + struct encode_ctx *xcode_encode; + void *frame; + int width; + int height; int target_w; int target_h; int format_ok; @@ -666,71 +344,71 @@ artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h) DPRINTF(E_SPAM, L_ART, "Getting artwork (max destination width %d height %d)\n", max_w, max_h); - src_ctx = NULL; - - ret = avformat_open_input(&src_ctx, path, NULL, NULL); - if (ret < 0) + xcode_decode = transcode_decode_setup(XCODE_PNG, DATA_KIND_FILE, path, 0); // Good for XCODE_JPEG too + if (!xcode_decode) { - DPRINTF(E_WARN, L_ART, "Cannot open artwork file '%s': %s\n", path, strerror(AVUNERROR(ret))); - - return ART_E_ERROR; + DPRINTF(E_DBG, L_ART, "No artwork found in '%s'\n", path); + return ART_E_NONE; } - ret = avformat_find_stream_info(src_ctx, NULL); - if (ret < 0) - { - DPRINTF(E_WARN, L_ART, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret))); - - avformat_close_input(&src_ctx); - return ART_E_ERROR; - } - - format_ok = 0; - for (s = 0; s < src_ctx->nb_streams; s++) - { - if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_PNG) - { - format_ok = ART_FMT_PNG; - break; - } - else if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_MJPEG) - { - format_ok = ART_FMT_JPEG; - break; - } - } - - if (s == src_ctx->nb_streams) + if (transcode_decode_query(xcode_decode, "is_jpeg")) + format_ok = ART_FMT_JPEG; + else if (transcode_decode_query(xcode_decode, "is_png")) + format_ok = ART_FMT_PNG; + else { DPRINTF(E_LOG, L_ART, "Artwork file '%s' not a PNG or JPEG file\n", path); - - avformat_close_input(&src_ctx); - return ART_E_ERROR; + goto fail_free_decode; } - ret = rescale_needed(src_ctx->streams[s]->codec, max_w, max_h, &target_w, &target_h); + width = transcode_decode_query(xcode_decode, "width"); + height = transcode_decode_query(xcode_decode, "height"); - /* Fastpath */ - if (!ret && format_ok) + ret = rescale_calculate(&target_w, &target_h, width, height, max_w, max_h); + if (ret < 0) { - ret = artwork_read(evbuf, path); - if (ret == 0) - ret = format_ok; - } - else - ret = artwork_rescale(evbuf, src_ctx, s, target_w, target_h); + // No rescaling required, just read the raw file into the evbuf + if (artwork_read(evbuf, path) != 0) + goto fail_free_decode; - avformat_close_input(&src_ctx); + transcode_decode_cleanup(&xcode_decode); + return format_ok; + } + + if (format_ok == ART_FMT_JPEG) + xcode_encode = transcode_encode_setup(XCODE_JPEG, xcode_decode, NULL, target_w, target_h); + else + xcode_encode = transcode_encode_setup(XCODE_PNG, xcode_decode, NULL, target_w, target_h); + + if (!xcode_encode) + { + DPRINTF(E_WARN, L_ART, "Cannot open artwork file for rescaling '%s'\n", path); + goto fail_free_decode; + } + + // We don't use transcode() because we just want to process one frame + ret = transcode_decode(&frame, xcode_decode); + if (ret < 0) + goto fail_free_encode; + + ret = transcode_encode(evbuf, xcode_encode, frame, 1); + + transcode_encode_cleanup(&xcode_encode); + transcode_decode_cleanup(&xcode_decode); if (ret < 0) { - if (evbuffer_get_length(evbuf) > 0) - evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); - - ret = ART_E_ERROR; + evbuffer_drain(evbuf, evbuffer_get_length(evbuf)); + return ART_E_ERROR; } - return ret; + return format_ok; + + fail_free_encode: + transcode_encode_cleanup(&xcode_encode); + fail_free_decode: + transcode_decode_cleanup(&xcode_decode); + return ART_E_ERROR; } /* Looks for an artwork file in a directory. Will rescale if needed. @@ -949,92 +627,11 @@ source_item_cache_get(struct artwork_ctx *ctx) static int source_item_embedded_get(struct artwork_ctx *ctx) { - AVFormatContext *src_ctx; - AVStream *src_st; - int s; - int target_w; - int target_h; - int format; - int ret; - DPRINTF(E_SPAM, L_ART, "Trying embedded artwork in %s\n", ctx->dbmfi->path); - src_ctx = NULL; + snprintf(ctx->path, sizeof(ctx->path), "%s", ctx->dbmfi->path); - ret = avformat_open_input(&src_ctx, ctx->dbmfi->path, NULL, NULL); - if (ret < 0) - { - DPRINTF(E_WARN, L_ART, "Cannot open media file '%s': %s\n", ctx->dbmfi->path, strerror(AVUNERROR(ret))); - return ART_E_ERROR; - } - - ret = avformat_find_stream_info(src_ctx, NULL); - if (ret < 0) - { - DPRINTF(E_WARN, L_ART, "Cannot get stream info: %s\n", strerror(AVUNERROR(ret))); - avformat_close_input(&src_ctx); - return ART_E_ERROR; - } - - format = 0; - for (s = 0; s < src_ctx->nb_streams; s++) - { - if (src_ctx->streams[s]->disposition & AV_DISPOSITION_ATTACHED_PIC) - { - if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_PNG) - { - format = ART_FMT_PNG; - break; - } - else if (src_ctx->streams[s]->codec->codec_id == AV_CODEC_ID_MJPEG) - { - format = ART_FMT_JPEG; - break; - } - } - } - - if (s == src_ctx->nb_streams) - { - avformat_close_input(&src_ctx); - return ART_E_NONE; - } - - src_st = src_ctx->streams[s]; - - ret = rescale_needed(src_st->codec, ctx->max_w, ctx->max_h, &target_w, &target_h); - - /* Fastpath */ - if (!ret && format) - { - DPRINTF(E_SPAM, L_ART, "Artwork not too large, using original image\n"); - - ret = evbuffer_add(ctx->evbuf, src_st->attached_pic.data, src_st->attached_pic.size); - if (ret < 0) - DPRINTF(E_LOG, L_ART, "Could not add embedded image to event buffer\n"); - else - ret = format; - } - else - { - DPRINTF(E_SPAM, L_ART, "Artwork too large, rescaling image\n"); - - ret = artwork_rescale(ctx->evbuf, src_ctx, s, target_w, target_h); - } - - avformat_close_input(&src_ctx); - - if (ret < 0) - { - if (evbuffer_get_length(ctx->evbuf) > 0) - evbuffer_drain(ctx->evbuf, evbuffer_get_length(ctx->evbuf)); - - ret = ART_E_ERROR; - } - else - snprintf(ctx->path, sizeof(ctx->path), "%s", ctx->dbmfi->path); - - return ret; + return artwork_get(ctx->evbuf, ctx->path, ctx->max_w, ctx->max_h); } /* Looks for basename(in_path).{png,jpg}, so if in_path is /foo/bar.mp3 it diff --git a/src/httpd.c b/src/httpd.c index 79ab8b86..b165270e 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -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) diff --git a/src/httpd_streaming.c b/src/httpd_streaming.c index f5262b9c..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,7 +299,7 @@ streaming_init(void) return -1; } - streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL); + streaming_encode_ctx = transcode_encode_setup(XCODE_MP3, decode_ctx, NULL, 0, 0); transcode_decode_cleanup(&decode_ctx); if (!streaming_encode_ctx) { @@ -345,15 +345,15 @@ streaming_init(void) remaining = STREAMING_SILENCE_INTERVAL * STOB(44100); while (remaining > STREAMING_RAWBUF_SIZE) { - decoded = transcode_raw2frame(streaming_rawbuf, STREAMING_RAWBUF_SIZE); - if (!decoded) + frame = transcode_frame_new(XCODE_MP3, streaming_rawbuf, STREAMING_RAWBUF_SIZE); + if (!frame) { DPRINTF(E_LOG, L_STREAMING, "Could not convert raw PCM to frame\n"); goto silence_fail; } - ret = transcode_encode(streaming_encoded_data, decoded, streaming_encode_ctx); - transcode_decoded_free(decoded); + ret = transcode_encode(streaming_encoded_data, streaming_encode_ctx, frame, 0); + transcode_frame_free(frame); if (ret < 0) { DPRINTF(E_LOG, L_STREAMING, "Could not encode silence buffer\n"); diff --git a/src/inputs/file_http.c b/src/inputs/file_http.c index de0517c4..7422eacb 100644 --- a/src/inputs/file_http.c +++ b/src/inputs/file_http.c @@ -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; 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/transcode.c b/src/transcode.c index 6c89dd88..0cfb37bf 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -64,6 +64,9 @@ struct settings_ctx bool encode_video; bool encode_audio; + // Silence some log messages + bool silent; + // Output format (for the muxer) const char *format; @@ -76,6 +79,7 @@ struct settings_ctx enum AVSampleFormat sample_format; int byte_depth; bool wavheader; + bool icy; // Video settings enum AVCodecID video_codec; @@ -123,6 +127,9 @@ struct decode_ctx // Set to true if we have reached eof bool eof; + // Set to true if avcodec_receive_frame() gave us a frame + bool got_frame; + // Contains the most recent packet from av_read_frame() AVPacket *packet; @@ -171,12 +178,6 @@ struct transcode_ctx struct encode_ctx *encode_ctx; }; -struct decoded_frame -{ - AVFrame *frame; - enum AVMediaType type; -}; - /* -------------------------- PROFILE CONFIGURATION ------------------------ */ @@ -200,6 +201,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile) settings->channels = 2; settings->sample_format = AV_SAMPLE_FMT_S16; settings->byte_depth = 2; // Bytes per sample = 16/8 + settings->icy = 1; break; case XCODE_MP3: @@ -215,12 +217,14 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile) case XCODE_JPEG: settings->encode_video = 1; + settings->silent = 1; settings->format = "image2"; settings->video_codec = AV_CODEC_ID_MJPEG; break; case XCODE_PNG: settings->encode_video = 1; + settings->silent = 1; settings->format = "image2"; settings->video_codec = AV_CODEC_ID_PNG; break; @@ -256,7 +260,7 @@ stream_settings_set(struct stream_ctx *s, struct settings_ctx *settings, enum AV s->codec->sample_fmt = settings->sample_format; s->codec->time_base = (AVRational){1, settings->sample_rate}; } - else if (type == AVMEDIA_TYPE_AUDIO) + else if (type == AVMEDIA_TYPE_VIDEO) { s->codec->height = settings->height; s->codec->width = settings->width; @@ -366,6 +370,12 @@ stream_add(struct encode_ctx *ctx, struct stream_ctx *s, enum AVCodecID codec_id stream_settings_set(s, &ctx->settings, encoder->type); + if (!s->codec->pix_fmt) + { + s->codec->pix_fmt = avcodec_default_get_format(s->codec, encoder->pix_fmts); + DPRINTF(E_DBG, L_XCODE, "Pixel format set to %d (encoder is %s)\n", s->codec->pix_fmt, codec_name); + } + if (ctx->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) s->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; @@ -595,6 +605,8 @@ decode_filter_encode_write(struct transcode_ctx *ctx, struct stream_ctx *s, AVPa break; } + dec_ctx->got_frame = 1; + if (!out_stream) break; @@ -654,13 +666,57 @@ read_decode_filter_encode_write(struct transcode_ctx *ctx) /* --------------------------- INPUT/OUTPUT INIT --------------------------- */ +static AVCodecContext * +open_decoder(unsigned int *stream_index, struct decode_ctx *ctx, enum AVMediaType type) +{ + AVCodecContext *dec_ctx; + AVCodec *decoder; + int ret; + + *stream_index = av_find_best_stream(ctx->ifmt_ctx, type, -1, -1, &decoder, 0); + if ((*stream_index < 0) || (!decoder)) + { + if (!ctx->settings.silent) + DPRINTF(E_LOG, L_XCODE, "No stream data or decoder for '%s'\n", ctx->ifmt_ctx->filename); + return NULL; + } + + CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder)); + + // In open_filter() we need to tell the sample rate and format that the decoder + // is giving us - however sample rate of dec_ctx will be 0 if we don't prime it + // with the streams codecpar data. + ret = avcodec_parameters_to_context(dec_ctx, ctx->ifmt_ctx->streams[*stream_index]->codecpar); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Failed to copy codecpar for stream #%d: %s\n", *stream_index, err2str(ret)); + avcodec_free_context(&dec_ctx); + return NULL; + } + + if (type == AVMEDIA_TYPE_AUDIO) + { + dec_ctx->request_sample_fmt = ctx->settings.sample_format; + dec_ctx->request_channel_layout = ctx->settings.channel_layout; + } + + ret = avcodec_open2(dec_ctx, NULL, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", *stream_index, err2str(ret)); + avcodec_free_context(&dec_ctx); + return NULL; + } + + return dec_ctx; +} + static int open_input(struct decode_ctx *ctx, const char *path) { AVDictionary *options = NULL; - AVCodec *decoder; AVCodecContext *dec_ctx; - int stream_index; + unsigned int stream_index; int ret; CHECK_NULL(L_XCODE, ctx->ifmt_ctx = avformat_alloc_context()); @@ -705,35 +761,9 @@ open_input(struct decode_ctx *ctx, const char *path) if (ctx->settings.encode_audio) { - // Find audio stream and open decoder - stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0); - if ((stream_index < 0) || (!decoder)) - { - DPRINTF(E_LOG, L_XCODE, "Did not find audio stream or suitable decoder for %s\n", path); - goto out_fail; - } - - CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder)); - - // In open_filter() we need to tell the sample rate and format that the decoder - // is giving us - however sample rate of dec_ctx will be 0 if we don't prime it - // with the streams codecpar data. - ret = avcodec_parameters_to_context(dec_ctx, ctx->ifmt_ctx->streams[stream_index]->codecpar); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Failed to copy codecpar for stream #%d: %s\n", stream_index, err2str(ret)); - goto out_fail; - } - - dec_ctx->request_sample_fmt = ctx->settings.sample_format; - dec_ctx->request_channel_layout = ctx->settings.channel_layout; - - ret = avcodec_open2(dec_ctx, NULL, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret)); - goto out_fail; - } + dec_ctx = open_decoder(&stream_index, ctx, AVMEDIA_TYPE_AUDIO); + if (!dec_ctx) + goto out_fail; ctx->audio_stream.codec = dec_ctx; ctx->audio_stream.stream = ctx->ifmt_ctx->streams[stream_index]; @@ -741,22 +771,9 @@ open_input(struct decode_ctx *ctx, const char *path) if (ctx->settings.encode_video) { - // Find video stream and open decoder - stream_index = av_find_best_stream(ctx->ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0); - if ((stream_index < 0) || (!decoder)) - { - DPRINTF(E_LOG, L_XCODE, "Did not find video stream or suitable decoder for '%s': %s\n", path, err2str(ret)); - goto out_fail; - } - - CHECK_NULL(L_XCODE, dec_ctx = avcodec_alloc_context3(decoder)); - - ret = avcodec_open2(dec_ctx, NULL, NULL); - if (ret < 0) - { - DPRINTF(E_LOG, L_XCODE, "Failed to open decoder for stream #%d: %s\n", stream_index, err2str(ret)); - goto out_fail; - } + dec_ctx = open_decoder(&stream_index, ctx, AVMEDIA_TYPE_VIDEO); + if (!dec_ctx) + goto out_fail; ctx->video_stream.codec = dec_ctx; ctx->video_stream.stream = ctx->ifmt_ctx->streams[stream_index]; @@ -793,6 +810,9 @@ open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) return -1; } + // Clear AVFMT_NOFILE bit, it is not allowed as we will set our own AVIOContext + ctx->ofmt_ctx->oformat->flags = ~AVFMT_NOFILE; + ctx->obuf = evbuffer_new(); if (!ctx->obuf) { @@ -901,7 +921,7 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { - DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source: %s\n", err2str(ret)); + DPRINTF(E_LOG, L_XCODE, "Cannot create audio buffer source (%s): %s\n", args, err2str(ret)); goto out_fail; } @@ -913,7 +933,7 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) ret = avfilter_graph_create_filter(&format_ctx, format, "format", args, NULL, filter_graph); if (ret < 0) { - DPRINTF(E_LOG, L_XCODE, "Cannot create audio format filter: %s\n", err2str(ret)); + DPRINTF(E_LOG, L_XCODE, "Cannot create audio format filter (%s): %s\n", args, err2str(ret)); goto out_fail; } @@ -952,17 +972,17 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { - DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source: %s\n", err2str(ret)); + DPRINTF(E_LOG, L_XCODE, "Cannot create buffer source (%s): %s\n", args, err2str(ret)); goto out_fail; } snprintf(args, sizeof(args), - "pix_fmt=%d", out_stream->codec->pix_fmt); + "pix_fmts=%s", av_get_pix_fmt_name(out_stream->codec->pix_fmt)); ret = avfilter_graph_create_filter(&format_ctx, format, "format", args, NULL, filter_graph); if (ret < 0) { - DPRINTF(E_LOG, L_XCODE, "Cannot create format filter: %s\n", err2str(ret)); + DPRINTF(E_LOG, L_XCODE, "Cannot create format filter (%s): %s\n", args, err2str(ret)); goto out_fail; } @@ -972,7 +992,7 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) ret = avfilter_graph_create_filter(&scale_ctx, scale, "scale", args, NULL, filter_graph); if (ret < 0) { - DPRINTF(E_LOG, L_XCODE, "Cannot create scale filter: %s\n", err2str(ret)); + DPRINTF(E_LOG, L_XCODE, "Cannot create scale filter (%s): %s\n", args, err2str(ret)); goto out_fail; } @@ -1079,7 +1099,7 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, } struct encode_ctx * -transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size) +transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height) { struct encode_ctx *ctx; @@ -1090,6 +1110,9 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct if (init_settings(&ctx->settings, profile) < 0) goto fail_free; + ctx->settings.width = width; + ctx->settings.height = height; + if (ctx->settings.wavheader) make_wav_header(ctx, src_ctx, est_size); @@ -1099,7 +1122,7 @@ transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ct if (open_filters(ctx, src_ctx) < 0) goto fail_close; - if (src_ctx->data_kind == DATA_KIND_HTTP) + if (ctx->settings.icy && src_ctx->data_kind == DATA_KIND_HTTP) ctx->icy_interval = METADATA_ICY_INTERVAL * ctx->settings.channels * ctx->settings.byte_depth * ctx->settings.sample_rate; return ctx; @@ -1127,7 +1150,7 @@ transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const return NULL; } - ctx->encode_ctx = transcode_encode_setup(profile, ctx->decode_ctx, est_size); + ctx->encode_ctx = transcode_encode_setup(profile, ctx->decode_ctx, est_size, 0, 0); if (!ctx->encode_ctx) { transcode_decode_cleanup(&ctx->decode_ctx); @@ -1309,62 +1332,93 @@ transcode_cleanup(struct transcode_ctx **ctx) *ctx = NULL; } -void -transcode_decoded_free(struct decoded_frame *decoded) -{ - av_frame_free(&decoded->frame); - free(decoded); -} - /* Encoding, decoding and transcoding */ int -transcode_decode(struct decoded_frame **decoded, struct decode_ctx *dec_ctx) +transcode_decode(void **frame, struct decode_ctx *dec_ctx) { - DPRINTF(E_LOG, L_XCODE, "Bug! Call to transcode_decode(), but the lazy programmer didn't implement it\n"); - return -1; + struct transcode_ctx ctx; + int ret; + + if (dec_ctx->got_frame) + DPRINTF(E_LOG, L_XCODE, "Bug! Currently no support for multiple calls to transcode_decode()\n"); + + ctx.decode_ctx = dec_ctx; + ctx.encode_ctx = NULL; + + do + { + // This function stops after decoding because ctx->encode_ctx is NULL + ret = read_decode_filter_encode_write(&ctx); + } + while ((ret == 0) && (!dec_ctx->got_frame)); + + if (ret < 0) + return -1; + + *frame = dec_ctx->decoded_frame; + + if (dec_ctx->eof) + return 0; + + return 1; } // Filters and encodes int -transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx) +transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, int eof) { + AVFrame *f = frame; struct stream_ctx *s; size_t start_length; int ret; start_length = evbuffer_get_length(ctx->obuf); - if (decoded->type == AVMEDIA_TYPE_AUDIO) + // Really crappy way of detecting if frame is audio, video or something else + if (f->channel_layout && f->sample_rate) s = &ctx->audio_stream; - else if (decoded->type == AVMEDIA_TYPE_VIDEO) + else if (f->width && f->height) s = &ctx->video_stream; else - return -1; + { + DPRINTF(E_LOG, L_XCODE, "Bug! Encoder could not detect frame type\n"); + return -1; + } - ret = filter_encode_write(ctx, s, decoded->frame); + ret = filter_encode_write(ctx, s, f); if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Error occurred while encoding: %s\n", err2str(ret)); return ret; } + // Flush + if (eof) + { + filter_encode_write(ctx, s, NULL); + av_write_trailer(ctx->ofmt_ctx); + } + ret = evbuffer_get_length(ctx->obuf) - start_length; + // TODO Shouldn't we flush and let the muxer write the trailer now? + evbuffer_add_buffer(evbuf, ctx->obuf); return ret; } int -transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int *icy_timer) +transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int want_bytes) { size_t start_length; int processed = 0; int ret; - *icy_timer = 0; + if (icy_timer) + *icy_timer = 0; if (ctx->decode_ctx->eof) return 0; @@ -1381,7 +1435,7 @@ transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int evbuffer_add_buffer(evbuf, ctx->encode_ctx->obuf); ctx->encode_ctx->total_bytes += processed; - if (ctx->encode_ctx->icy_interval) + if (icy_timer && ctx->encode_ctx->icy_interval) *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed); if (ret == AVERROR_EOF) @@ -1392,49 +1446,45 @@ transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int return processed; } -struct decoded_frame * -transcode_raw2frame(uint8_t *data, size_t size) +void * +transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size) { - struct decoded_frame *decoded; - AVFrame *frame; + AVFrame *f; int ret; - decoded = malloc(sizeof(struct decoded_frame)); - if (!decoded) - { - DPRINTF(E_LOG, L_XCODE, "Out of memory for decoded struct\n"); - return NULL; - } - - frame = av_frame_alloc(); - if (!frame) + f = av_frame_alloc(); + if (!f) { DPRINTF(E_LOG, L_XCODE, "Out of memory for frame\n"); - free(decoded); return NULL; } - decoded->type = AVMEDIA_TYPE_AUDIO; - decoded->frame = frame; - - frame->nb_samples = size / 4; - frame->format = AV_SAMPLE_FMT_S16; - frame->channel_layout = AV_CH_LAYOUT_STEREO; + f->nb_samples = size / 4; + f->format = AV_SAMPLE_FMT_S16; + f->channel_layout = AV_CH_LAYOUT_STEREO; #ifdef HAVE_FFMPEG - frame->channels = 2; + f->channels = 2; #endif - frame->pts = AV_NOPTS_VALUE; - frame->sample_rate = 44100; + f->pts = AV_NOPTS_VALUE; + f->sample_rate = 44100; - ret = avcodec_fill_audio_frame(frame, 2, frame->format, data, size, 0); + ret = avcodec_fill_audio_frame(f, 2, f->format, data, size, 0); if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Error filling frame with rawbuf: %s\n", err2str(ret)); - transcode_decoded_free(decoded); + av_frame_free(&f); return NULL; } - return decoded; + return f; +} + +void +transcode_frame_free(void *frame) +{ + AVFrame *f = frame; + + av_frame_free(&f); } @@ -1523,6 +1573,34 @@ transcode_seek(struct transcode_ctx *ctx, int ms) return got_ms; } +/* Querying */ + +int +transcode_decode_query(struct decode_ctx *ctx, const char *query) +{ + if (strcmp(query, "width") == 0) + { + if (ctx->video_stream.stream) + return ctx->video_stream.stream->codecpar->width; + } + else if (strcmp(query, "height") == 0) + { + if (ctx->video_stream.stream) + return ctx->video_stream.stream->codecpar->height; + } + else if (strcmp(query, "is_png") == 0) + { + if (ctx->video_stream.stream) + return (ctx->video_stream.stream->codecpar->codec_id == AV_CODEC_ID_PNG); + } + else if (strcmp(query, "is_jpeg") == 0) + { + if (ctx->video_stream.stream) + return (ctx->video_stream.stream->codecpar->codec_id == AV_CODEC_ID_MJPEG); + } + + return -1; +} /* Metadata */ diff --git a/src/transcode.h b/src/transcode.h index e55128bd..13e7aa63 100644 --- a/src/transcode.h +++ b/src/transcode.h @@ -22,14 +22,13 @@ enum transcode_profile struct decode_ctx; struct encode_ctx; struct transcode_ctx; -struct decoded_frame; // Setting up struct decode_ctx * transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length); struct encode_ctx * -transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size); +transcode_encode_setup(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height); struct transcode_ctx * transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length, off_t *est_size); @@ -50,51 +49,74 @@ transcode_encode_cleanup(struct encode_ctx **ctx); void transcode_cleanup(struct transcode_ctx **ctx); -void -transcode_decoded_free(struct decoded_frame *decoded); - // Transcoding /* Demuxes and decodes the next packet from the input. * - * @out decoded A newly allocated struct with a pointer to the frame and the - * stream. Must be freed with transcode_decoded_free(). + * @out frame A pointer to the frame. Caller should not free it, that will + * be done by the next call to the function or by the cleanup + * function. * @in ctx Decode context * @return Positive if OK, negative if error, 0 if EOF */ int -transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx); +transcode_decode(void **frame, struct decode_ctx *ctx); /* Encodes and remuxes a frame. Also resamples if needed. * * @out evbuf An evbuffer filled with remuxed data - * @in frame The frame to encode, e.g. from transcode_decode * @in ctx Encode context + * @in frame The decoded frame to encode, e.g. from transcode_decode + * @in eof If true the muxer will write a trailer to the output * @return Bytes added if OK, negative if error */ int -transcode_encode(struct evbuffer *evbuf, struct decoded_frame *decoded, struct encode_ctx *ctx); +transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, int eof); /* Demuxes, decodes, encodes and remuxes from the input. * * @out evbuf An evbuffer filled with remuxed data + * @out icy_timer True if METADATA_ICY_INTERVAL has elapsed + * @in ctx Transcode context * @in want_bytes Minimum number of bytes the caller wants added to the evbuffer * - set want_bytes to 0 to transcode everything until EOF/error * - set want_bytes to 1 to get one encoded packet - * @in ctx Transcode context - * @out icy_timer True if METADATA_ICY_INTERVAL has elapsed * @return Bytes added if OK, negative if error, 0 if EOF */ int -transcode(struct evbuffer *evbuf, int want_bytes, struct transcode_ctx *ctx, int *icy_timer); +transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int want_bytes); -struct decoded_frame * -transcode_raw2frame(uint8_t *data, size_t size); +/* Converts a buffer with raw data to a frame that can be passed directly to the + * transcode_encode() function + * + * @in profile Tells the function what kind of frame to create + * @in data Buffer with raw data + * @in size Size of buffer + * @return Opaque pointer to frame if OK, otherwise NULL + */ +void * +transcode_frame_new(enum transcode_profile profile, uint8_t *data, size_t size); +void +transcode_frame_free(void *frame); -// Seeking +/* Seek to the specified position - next transcode() will return this packet + * + * @in ctx Transcode context + * @in seek Requested seek position in ms + * @return Negative if error, otherwise actual seek position + */ int transcode_seek(struct transcode_ctx *ctx, int ms); +/* Query for information about a media file opened by transcode_decode_setup() + * + * @in ctx Decode context + * @in query Query - see implementation for supported queries + * @return Negative if error, otherwise query dependent + */ +int +transcode_decode_query(struct decode_ctx *ctx, const char *query); + // Metadata struct http_icy_metadata * transcode_metadata(struct transcode_ctx *ctx, int *changed); From 441ad006a6003e33b981470e07c2bacd2c066cb7 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 1 Mar 2017 21:29:08 +0100 Subject: [PATCH 08/16] [artwork/transcode] Also let transcode.c handle rescaling of non-file Spotify artwork --- src/artwork.c | 86 ++++++++++----------------------------------- src/avio_evbuffer.c | 3 ++ src/transcode.c | 39 ++++++++++++++++---- src/transcode.h | 2 +- 4 files changed, 54 insertions(+), 76 deletions(-) diff --git a/src/artwork.c b/src/artwork.c index b5dbf0a1..b56ae720 100644 --- a/src/artwork.c +++ b/src/artwork.c @@ -324,13 +324,14 @@ rescale_calculate(int *target_w, int *target_h, int width, int height, int max_w /* 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) { struct decode_ctx *xcode_decode; struct encode_ctx *xcode_encode; @@ -344,7 +345,7 @@ 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); - xcode_decode = transcode_decode_setup(XCODE_PNG, DATA_KIND_FILE, path, 0); // Good for XCODE_JPEG too + xcode_decode = transcode_decode_setup(XCODE_JPEG, DATA_KIND_FILE, path, inbuf, 0); // Covers XCODE_PNG too if (!xcode_decode) { DPRINTF(E_DBG, L_ART, "No artwork found in '%s'\n", path); @@ -368,7 +369,7 @@ artwork_get(struct evbuffer *evbuf, char *path, int max_w, int max_h) if (ret < 0) { // No rescaling required, just read the raw file into the evbuf - if (artwork_read(evbuf, path) != 0) + if (!path || artwork_read(evbuf, path) != 0) goto fail_free_decode; transcode_decode_cleanup(&xcode_decode); @@ -518,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); } @@ -631,7 +632,7 @@ source_item_embedded_get(struct artwork_ctx *ctx) snprintf(ctx->path, sizeof(ctx->path), "%s", ctx->dbmfi->path); - return artwork_get(ctx->evbuf, ctx->path, ctx->max_w, ctx->max_h); + 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 @@ -685,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); } /* @@ -774,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(); @@ -820,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/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/transcode.c b/src/transcode.c index 0cfb37bf..bbf59cee 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -70,6 +70,9 @@ struct settings_ctx // 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; @@ -111,6 +114,9 @@ struct decode_ctx // Input format context AVFormatContext *ifmt_ctx; + // IO Context for non-file input + AVIOContext *avio; + // Stream and decoder data struct stream_ctx audio_stream; struct stream_ctx video_stream; @@ -219,6 +225,7 @@ init_settings(struct settings_ctx *settings, enum transcode_profile profile) settings->encode_video = 1; settings->silent = 1; settings->format = "image2"; + settings->in_format = "mjpeg"; settings->video_codec = AV_CODEC_ID_MJPEG; break; @@ -712,10 +719,11 @@ open_decoder(unsigned int *stream_index, struct decode_ctx *ctx, enum AVMediaTyp } static int -open_input(struct decode_ctx *ctx, const char *path) +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; @@ -735,7 +743,24 @@ open_input(struct decode_ctx *ctx, const char *path) 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); @@ -782,6 +807,7 @@ open_input(struct decode_ctx *ctx, const char *path) 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); @@ -792,6 +818,7 @@ open_input(struct decode_ctx *ctx, const char *path) static void close_input(struct decode_ctx *ctx) { + 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); @@ -1075,7 +1102,7 @@ close_filters(struct encode_ctx *ctx) /* Setup */ struct decode_ctx * -transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length) +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; @@ -1086,7 +1113,7 @@ transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, ctx->duration = song_length; ctx->data_kind = data_kind; - if ((init_settings(&ctx->settings, profile) < 0) || (open_input(ctx, path) < 0)) + if ((init_settings(&ctx->settings, profile) < 0) || (open_input(ctx, path, evbuf) < 0)) goto fail_free; return ctx; @@ -1143,7 +1170,7 @@ transcode_setup(enum transcode_profile profile, enum data_kind data_kind, const CHECK_NULL(L_XCODE, ctx = calloc(1, sizeof(struct transcode_ctx))); - ctx->decode_ctx = transcode_decode_setup(profile, data_kind, path, song_length); + ctx->decode_ctx = transcode_decode_setup(profile, data_kind, path, NULL, song_length); if (!ctx->decode_ctx) { free(ctx); @@ -1403,8 +1430,6 @@ transcode_encode(struct evbuffer *evbuf, struct encode_ctx *ctx, void *frame, in ret = evbuffer_get_length(ctx->obuf) - start_length; - // TODO Shouldn't we flush and let the muxer write the trailer now? - evbuffer_add_buffer(evbuf, ctx->obuf); return ret; diff --git a/src/transcode.h b/src/transcode.h index 13e7aa63..3e6cfc86 100644 --- a/src/transcode.h +++ b/src/transcode.h @@ -25,7 +25,7 @@ struct transcode_ctx; // Setting up struct decode_ctx * -transcode_decode_setup(enum transcode_profile profile, enum data_kind data_kind, const char *path, uint32_t song_length); +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(enum transcode_profile profile, struct decode_ctx *src_ctx, off_t *est_size, int width, int height); From 6951639d24b1dcb0a1d9c48fdd44032f3acbf2f4 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 1 Mar 2017 22:32:41 +0100 Subject: [PATCH 09/16] [transcode] Adjustments for libav 12 --- src/transcode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transcode.c b/src/transcode.c index bbf59cee..dd6c322e 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -991,8 +991,8 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) } snprintf(args, sizeof(args), - "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - in_stream->codec->width, in_stream->codec->height, in_stream->codec->pix_fmt, + "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); @@ -1014,7 +1014,7 @@ open_filter(struct stream_ctx *out_stream, struct stream_ctx *in_stream) } snprintf(args, sizeof(args), - "width=%d:height=%d", out_stream->codec->width, out_stream->codec->height); + "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) From f9375ef915fb44a926c772fa9e4c6fcfb59135ac Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 4 Mar 2017 09:40:29 +0100 Subject: [PATCH 10/16] [transcode] More adjustments for libav 12 --- src/transcode.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/transcode.c b/src/transcode.c index dd6c322e..4df09e5a 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -33,8 +33,8 @@ #include #include #include - -#include "ffmpeg-compat.h" +#include +#include #include "logger.h" #include "conffile.h" @@ -827,18 +827,22 @@ close_input(struct decode_ctx *ctx) static int open_output(struct encode_ctx *ctx, struct decode_ctx *src_ctx) { + AVOutputFormat *oformat; int ret; - ctx->ofmt_ctx = NULL; - avformat_alloc_output_context2(&ctx->ofmt_ctx, NULL, ctx->settings.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 - ctx->ofmt_ctx->oformat->flags = ~AVFMT_NOFILE; + 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) From c13ed8761a97c3ac2744432c026a9e10532e969d Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 4 Mar 2017 11:10:43 +0100 Subject: [PATCH 11/16] [trancode/artwork] Put back compability with older versions of ffmpeg/libav --- configure.ac | 3 + src/Makefile.am | 11 +- src/artwork_legacy.c | 1627 ++++++++++++++++++++++++++++++++++++++ src/transcode_legacy.c | 1677 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 3315 insertions(+), 3 deletions(-) create mode 100644 src/artwork_legacy.c create mode 100644 src/transcode_legacy.c diff --git a/configure.ac b/configure.ac index 9ee25476..b78bd82d 100644 --- a/configure.ac +++ b/configure.ac @@ -256,6 +256,9 @@ FORK_MODULES_CHECK([FORKED], [LIBAV], AC_CHECK_HEADERS([libavutil/channel_layout.h libavutil/mathematics.h]) ]) +dnl TODO Actually test for this +AM_CONDITIONAL([COND_FFMPEG_LEGACY], [[test "yes" = "no"]]) + AC_CHECK_SIZEOF([void *]) dnl --- Begin configuring the options --- 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_legacy.c b/src/artwork_legacy.c new file mode 100644 index 00000000..420d9421 --- /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; + + if (dst_fmt->video_codec == AV_CODEC_ID_PNG) + dst->pix_fmt = AV_PIX_FMT_RGB24; + else + dst->pix_fmt = avcodec_find_best_pix_fmt_of_list((enum AVPixelFormat *)img_encoder->pix_fmts, src->pix_fmt, 1, NULL); + + 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 + + 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/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; +} + From 548d9ada2e54321f1e3841412fcb3e7fc8ceebe0 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 4 Mar 2017 18:30:19 +0100 Subject: [PATCH 12/16] [transcode] Fixup dead assignment --- src/transcode.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/transcode.c b/src/transcode.c index 4df09e5a..e1369ee4 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -1467,9 +1467,7 @@ transcode(struct evbuffer *evbuf, int *icy_timer, struct transcode_ctx *ctx, int if (icy_timer && ctx->encode_ctx->icy_interval) *icy_timer = (ctx->encode_ctx->total_bytes % ctx->encode_ctx->icy_interval < processed); - if (ret == AVERROR_EOF) - ret = 0; - else if (ret < 0) + if ((ret < 0) && (ret != AVERROR_EOF)) return ret; return processed; From 394dee2a86f6697cd2dec337a4ed7a4e7c55e8d0 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 4 Mar 2017 23:05:59 +0100 Subject: [PATCH 13/16] [raop] Log src and dst ip addresses (checking for ipv6, not sure it is allowed) --- src/outputs/raop.c | 2 ++ 1 file changed, 2 insertions(+) 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 From 6b94a3450107a7ed1f80c74a8a1f827621fcd3df Mon Sep 17 00:00:00 2001 From: Scott Shambarger Date: Sat, 4 Mar 2017 16:46:01 -0800 Subject: [PATCH 14/16] [config] Added check for modern AV apis --- configure.ac | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/configure.ac b/configure.ac index b78bd82d..6a68be95 100644 --- a/configure.ac +++ b/configure.ac @@ -237,27 +237,32 @@ 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([avcodec_parameters_alloc], [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([avcodec_parameters_alloc], [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 TODO Actually test for this -AM_CONDITIONAL([COND_FFMPEG_LEGACY], [[test "yes" = "no"]]) +AM_CONDITIONAL([COND_FFMPEG_LEGACY], [[test "x$modern_av_apis" = "xno"]]) AC_CHECK_SIZEOF([void *]) From e489f1a6a99be4f2069d02e9d9004408b571cfbd Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sun, 5 Mar 2017 10:05:21 +0100 Subject: [PATCH 15/16] [configure] Use avcodec_parameters_from_context as check for codecpar --- configure.ac | 2 -- src/library/filescanner_ffmpeg.c | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 6a68be95..9b149beb 100644 --- a/configure.ac +++ b/configure.ac @@ -250,7 +250,6 @@ FORK_MODULES_CHECK([FORKED], [LIBAV], [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([avcodec_parameters_alloc], [libavcodec/avcodec.h]) FORK_CHECK_DECLS([avformat_alloc_output_context2], [libavformat/avformat.h]) FORK_CHECK_DECLS([av_frame_alloc], [libavutil/frame.h]) @@ -261,7 +260,6 @@ FORK_MODULES_CHECK([FORKED], [LIBAV], 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/library/filescanner_ffmpeg.c b/src/library/filescanner_ffmpeg.c index 0b276f20..d38adee4 100644 --- a/src/library/filescanner_ffmpeg.c +++ b/src/library/filescanner_ffmpeg.c @@ -370,11 +370,11 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) 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); @@ -425,7 +425,7 @@ scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi) for (i = 0; i < ctx->nb_streams; i++) { -#if HAVE_DECL_AVCODEC_PARAMETERS_ALLOC +#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; From a975dabded0d9c538be5ac365192c9c4d2f015d4 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Mon, 7 Aug 2017 00:09:27 +0200 Subject: [PATCH 16/16] [artwork] Re-apply commit ef13abe to artwork_legacy.c --- src/artwork_legacy.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/artwork_legacy.c b/src/artwork_legacy.c index 420d9421..e25d502c 100644 --- a/src/artwork_legacy.c +++ b/src/artwork_legacy.c @@ -456,11 +456,7 @@ artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out dst->codec_id = dst_fmt->video_codec; dst->codec_type = AVMEDIA_TYPE_VIDEO; - if (dst_fmt->video_codec == AV_CODEC_ID_PNG) - dst->pix_fmt = AV_PIX_FMT_RGB24; - else - dst->pix_fmt = avcodec_find_best_pix_fmt_of_list((enum AVPixelFormat *)img_encoder->pix_fmts, src->pix_fmt, 1, NULL); - + 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"); @@ -514,6 +510,10 @@ artwork_rescale(struct evbuffer *evbuf, AVFormatContext *src_ctx, int s, int out 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);