From 93a6765c8c72e645f50209c6b6148fe3de2ea234 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 9 Dec 2015 20:00:09 +0100 Subject: [PATCH] [transcode] Fix freeze problem where av_read_frame would block on broken connections --- src/transcode.c | 54 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/transcode.c b/src/transcode.c index f7b70aa2..d37d46da 100644 --- a/src/transcode.c +++ b/src/transcode.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #ifdef HAVE_LIBAVFILTER @@ -50,6 +51,8 @@ #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 10000000 static char *default_codecs = "mpeg,wav"; static char *roku_codecs = "mpeg,mp4a,wma,wav"; @@ -79,6 +82,9 @@ struct decode_ctx { AVPacket packet; int resume; int resume_offset; + + // Used to measure if av_read_frame is taking too long + int64_t timestamp; }; struct encode_ctx { @@ -247,6 +253,29 @@ decode_stream(struct decode_ctx *ctx, AVStream *in_stream) (in_stream == ctx->subtitle_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 @@ -286,7 +315,9 @@ read_packet(AVPacket *packet, AVStream **stream, unsigned int *stream_index, str // 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_free_packet(&ctx->packet); + ctx->resume_offset = 0; + ctx->timestamp = av_gettime(); ret = av_read_frame(ctx->ifmt_ctx, &ctx->packet); if (ret < 0) @@ -551,22 +582,31 @@ open_input(struct decode_ctx *ctx, struct media_file_info *mfi, int decode_video int ret; options = NULL; - ctx->ifmt_ctx = 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 (mfi->data_kind == DATA_KIND_HTTP) - { - ctx->ifmt_ctx = avformat_alloc_context(); - ctx->ifmt_ctx->probesize = 64000; - } + ctx->ifmt_ctx->probesize = 64000; # endif if (mfi->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, mfi->path, NULL, &options); + if (options) av_dict_free(&options); + if (ret < 0) { DPRINTF(E_LOG, L_XCODE, "Cannot open input path\n"); @@ -1476,7 +1516,7 @@ transcode_decode(struct decoded_frame **decoded, struct decode_ctx *ctx) { // 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 (eof?), will flush decoders\n"); + DPRINTF(E_DBG, L_XCODE, "Could not read packet, will flush decoders\n"); used = 1; got_frame = flush_decoder(frame, &in_stream, &stream_index, ctx); @@ -1697,6 +1737,8 @@ transcode_seek(struct transcode_ctx *ctx, int ms) { av_free_packet(&decode_ctx->packet); + decode_ctx->timestamp = av_gettime(); + ret = av_read_frame(decode_ctx->ifmt_ctx, &decode_ctx->packet); if (ret < 0) {