diff --git a/ffmpeg/decoder.h b/ffmpeg/decoder.h index 2c0d106689..ab82948aab 100755 --- a/ffmpeg/decoder.h +++ b/ffmpeg/decoder.h @@ -13,6 +13,9 @@ struct input_ctx { int vi, ai; // video and audio stream indices int dv, da; // flags whether to drop video or audio + // Decoded results for current transcode call (for early abort checks) + output_results *decoded_res; + // Hardware decoding support AVBufferRef *hw_device_ctx; enum AVHWDeviceType hw_type; diff --git a/ffmpeg/encoder.c b/ffmpeg/encoder.c index 1b2d026753..3d049e0cf5 100755 --- a/ffmpeg/encoder.c +++ b/ffmpeg/encoder.c @@ -4,6 +4,7 @@ #include #include #include +#include static int add_video_stream(struct output_ctx *octx, struct input_ctx *ictx) { @@ -625,6 +626,23 @@ int process_out(struct input_ctx *ictx, struct output_ctx *octx, AVCodecContext } } + // Check for runaway encodes where the FPS filter produces too many frames + // Unclear what causes these + if (is_video && frame && ictx->decoded_res && ictx->decoded_res->frames > 0) { + if (ictx->ic && ictx->ic->iformat && + !strcmp(ictx->ic->iformat->name, "image2")) { + // Image sequence input can legitimately expand frame counts. + goto after_runaway_check; + } + int64_t decoded_frames = ictx->decoded_res->frames; + if ((int64_t)octx->res->frames + 1 > 25 * decoded_frames) { + av_frame_unref(frame); + ret = lpms_ERR_ENC_RUNAWAY; + goto proc_cleanup; + } + } +after_runaway_check: + ret = encode(encoder, frame, octx, ost); skip: av_frame_unref(frame); diff --git a/ffmpeg/ffmpeg_errors.go b/ffmpeg/ffmpeg_errors.go index a3709aa28a..093d6a6d50 100644 --- a/ffmpeg/ffmpeg_errors.go +++ b/ffmpeg/ffmpeg_errors.go @@ -20,6 +20,7 @@ var lpmsErrors = []struct { {Code: C.lpms_ERR_INPUT_CODEC, Desc: "Unsupported input codec"}, {Code: C.lpms_ERR_INPUT_NOKF, Desc: "No keyframes in input"}, {Code: C.lpms_ERR_UNRECOVERABLE, Desc: "Unrecoverable state, restart process"}, + {Code: C.lpms_ERR_ENC_RUNAWAY, Desc: "Encoded frames runaway"}, } func error_map() map[int]error { diff --git a/ffmpeg/ffmpeg_test.go b/ffmpeg/ffmpeg_test.go index f39d1f56fb..f3896a9226 100644 --- a/ffmpeg/ffmpeg_test.go +++ b/ffmpeg/ffmpeg_test.go @@ -1536,6 +1536,33 @@ func TestTranscoder_PassthroughFPS(t *testing.T) { run(cmd) } +func TestTranscoder_EncodedFrameRunaway(t *testing.T) { + run, dir := setupTest(t) + defer os.RemoveAll(dir) + + // generate a sample with 10 seconds between frames + // transcode to 5 fps == 50 output frames per input frame + // should trigger runaway frame detection (output > 25x input) + + cmd := ` + cp "$1/../transcoder/test.ts" test.ts + ffmpeg -i test.ts -vf "setpts=N*10/TB" -frames:v 5 -c:v libx264 -bf 0 -fps_mode vfr -an lowfps.mp4 + ffprobe -v warning -select_streams v -show_entries format=duration lowfps.mp4 | grep duration=40.0 + ` + run(cmd) + + profile := P144p30fps16x9 + profile.Framerate = 5 + in := &TranscodeOptionsIn{Fname: dir + "/lowfps.mp4"} + out := []TranscodeOptions{{ + Oname: dir + "/out-60fps.ts", + Profile: profile, + }} + + _, err := Transcode3(in, out) + assert.EqualError(t, err, "Encoded frames runaway") +} + func TestTranscoder_PassthroughFPS_AdjustTimestamps(t *testing.T) { // check timestamp adjustments for fps passthrough diff --git a/ffmpeg/transcoder.c b/ffmpeg/transcoder.c index 8942f093e6..9afd5e63cd 100755 --- a/ffmpeg/transcoder.c +++ b/ffmpeg/transcoder.c @@ -20,6 +20,7 @@ const int lpms_ERR_PACKET_ONLY = FFERRTAG('P','K','O','N'); const int lpms_ERR_FILTER_FLUSHED = FFERRTAG('F','L','F','L'); const int lpms_ERR_OUTPUTS = FFERRTAG('O','U','T','P'); const int lpms_ERR_UNRECOVERABLE = FFERRTAG('U', 'N', 'R', 'V'); +const int lpms_ERR_ENC_RUNAWAY = FFERRTAG('E', 'N', 'R', 'W'); // // Notes on transcoder internals: @@ -326,6 +327,8 @@ int transcode(struct transcode_thread *h, int nb_outputs = h->nb_outputs; int outputs_ready = 0, hit_eof = 0; + ictx->decoded_res = decoded_results; + ipkt = av_packet_alloc(); if (!ipkt) LPMS_ERR(transcode_cleanup, "Unable to allocated packet"); dframe = av_frame_alloc(); @@ -552,6 +555,7 @@ int transcode(struct transcode_thread *h, avformat_close_input(&ictx->ic); ictx->ic = NULL; } + ictx->decoded_res = NULL; return 0; } @@ -562,6 +566,7 @@ int transcode(struct transcode_thread *h, } transcode_cleanup: + ictx->decoded_res = NULL; if (dframe) av_frame_free(&dframe); if (ipkt) av_packet_free(&ipkt); // needed for early exits if (frame_queue) queue_free(&frame_queue); diff --git a/ffmpeg/transcoder.h b/ffmpeg/transcoder.h index e0cca743b4..83864349f2 100755 --- a/ffmpeg/transcoder.h +++ b/ffmpeg/transcoder.h @@ -17,6 +17,7 @@ extern const int lpms_ERR_PACKET_ONLY; extern const int lpms_ERR_FILTER_FLUSHED; extern const int lpms_ERR_OUTPUTS; extern const int lpms_ERR_UNRECOVERABLE; +extern const int lpms_ERR_ENC_RUNAWAY; struct transcode_thread;