]> git.sesse.net Git - nageru/commitdiff
If a FFmpeg input does not give a frame for at least ten seconds, consider it a fatal...
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Thu, 7 Sep 2023 20:12:05 +0000 (22:12 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Thu, 7 Sep 2023 20:12:05 +0000 (22:12 +0200)
nageru/ffmpeg_capture.cpp
nageru/ffmpeg_capture.h

index 79fba7b9ab4ebf4135e8e9f34c145e78a676e7b4..9af4e833f76340bfa3b92717f78e3763fad02776 100644 (file)
@@ -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<double>(steady_clock::now() - frame_timeout_started).count() >= 10.0) {
+               string filename_copy;
+               {
+                       lock_guard<mutex> 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();
 }
 
index 2ab9481aad6d351e5597f86cc0df3315a39d6606..1fcd82b10d9182ad6e57f63ad53e714f5900623b 100644 (file)
@@ -288,6 +288,10 @@ private:
        std::atomic<bool> 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<void()> dequeue_init_callback = nullptr;
        std::function<void()> dequeue_cleanup_callback = nullptr;