From 22d74c9f49f8602fd33c499a575a3fa605f43dee Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Thu, 7 Sep 2023 22:12:05 +0200 Subject: [PATCH] If a FFmpeg input does not give a frame for at least ten seconds, consider it a fatal error (causing a restart). --- nageru/ffmpeg_capture.cpp | 27 ++++++++++++++++++++++++++- nageru/ffmpeg_capture.h | 4 ++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/nageru/ffmpeg_capture.cpp b/nageru/ffmpeg_capture.cpp index 79fba7b..9af4e83 100644 --- a/nageru/ffmpeg_capture.cpp +++ b/nageru/ffmpeg_capture.cpp @@ -531,10 +531,13 @@ bool FFmpegCapture::play_video(const string &pathname) AVFormatContextWithCloser format_ctx; if (srt_sock == -1) { - // Regular file. + // Regular file (or stream). + frame_timeout_started = steady_clock::now(); + frame_timeout_valid = true; format_ctx = avformat_open_input_unique(pathname.c_str(), /*fmt=*/nullptr, /*options=*/nullptr, AVIOInterruptCB{ &FFmpegCapture::interrupt_cb_thunk, this }); + frame_timeout_valid = false; } else { #ifdef HAVE_SRT // SRT socket, already opened. @@ -653,8 +656,15 @@ bool FFmpegCapture::play_video(const string &pathname) int64_t audio_pts; bool error; + frame_timeout_started = steady_clock::now(); + frame_timeout_valid = true; AVFrameWithDeleter frame = decode_frame(format_ctx.get(), video_codec_ctx.get(), audio_codec_ctx.get(), pathname, video_stream_index, audio_stream_index, subtitle_stream_index, audio_frame.get(), &audio_format, &audio_pts, &error); + frame_timeout_valid = false; + if (should_interrupt.load()) { + // Abort no matter whether we got a frame or not. + return false; + } if (error) { if (++consecutive_errors >= 100) { fprintf(stderr, "More than 100 consecutive error video frames, aborting playback.\n"); @@ -1166,6 +1176,21 @@ int FFmpegCapture::interrupt_cb_thunk(void *opaque) int FFmpegCapture::interrupt_cb() { + // If ten seconds is gone without anything happening, we assume that + // we are in a network stream that died and FFmpeg just didn't + // pick it up (or perhaps it just hung, keeping the connection open). + // Called back approximately every 100 ms if something is hanging, + // so we get more than enough accuracy for our purposes. + if (!should_interrupt && frame_timeout_valid && + duration(steady_clock::now() - frame_timeout_started).count() >= 10.0) { + string filename_copy; + { + lock_guard lock(filename_mu); + filename_copy = filename; + } + fprintf(stderr, "%s: No frame for more than 10 seconds, restarting stream.\n", filename.c_str()); + should_interrupt = true; + } return should_interrupt.load(); } diff --git a/nageru/ffmpeg_capture.h b/nageru/ffmpeg_capture.h index 2ab9481..1fcd82b 100644 --- a/nageru/ffmpeg_capture.h +++ b/nageru/ffmpeg_capture.h @@ -288,6 +288,10 @@ private: std::atomic should_interrupt{false}; bool last_frame_was_connected = true; + // TODO: Replace with std::optional if we go C++17. + bool frame_timeout_valid = false; // If true, will time out any reads after ten seconds. + std::chrono::steady_clock::time_point frame_timeout_started; // Only relevant if frame_timeout_valid == true. + bool has_dequeue_callbacks = false; std::function dequeue_init_callback = nullptr; std::function dequeue_cleanup_callback = nullptr; -- 2.39.2