Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ffmpeg/decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions ffmpeg/encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
#include <string.h>

static int add_video_stream(struct output_ctx *octx, struct input_ctx *ictx)
{
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions ffmpeg/ffmpeg_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions ffmpeg/ffmpeg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions ffmpeg/transcoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -552,6 +555,7 @@ int transcode(struct transcode_thread *h,
avformat_close_input(&ictx->ic);
ictx->ic = NULL;
}
ictx->decoded_res = NULL;
return 0;
}

Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions ffmpeg/transcoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading