]> git.sesse.net Git - nageru/commitdiff
Fix a problem where change_rate() would hang until the next frame arrived.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 28 May 2017 15:38:26 +0000 (17:38 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 28 May 2017 15:38:26 +0000 (17:38 +0200)
ffmpeg_capture.cpp
ffmpeg_capture.h
quittable_sleeper.h

index 7ff8226de4900b10be564f5ec85813010533d417..97363a7a82a3ab686717ed3542eaa9a021537dc3 100644 (file)
@@ -200,7 +200,7 @@ YCbCrFormat decode_ycbcr_format(const AVPixFmtDescriptor *desc, const AVFrame *f
 }  // namespace
 
 FFmpegCapture::FFmpegCapture(const string &filename, unsigned width, unsigned height)
-       : filename(filename), width(width), height(height)
+       : filename(filename), width(width), height(height), video_timebase{1, 1}
 {
        // Not really used for anything.
        description = "Video: " + filename;
@@ -351,7 +351,7 @@ bool FFmpegCapture::play_video(const string &pathname)
        }
 
        const AVCodecParameters *codecpar = format_ctx->streams[video_stream_index]->codecpar;
-       AVRational video_timebase = format_ctx->streams[video_stream_index]->time_base;
+       video_timebase = format_ctx->streams[video_stream_index]->time_base;
        AVCodecContextWithDeleter codec_ctx = avcodec_alloc_context3_unique(nullptr);
        if (avcodec_parameters_to_context(codec_ctx.get(), codecpar) < 0) {
                fprintf(stderr, "%s: Cannot fill codec parameters\n", pathname.c_str());
@@ -373,7 +373,7 @@ bool FFmpegCapture::play_video(const string &pathname)
 
        // Main loop.
        while (!producer_thread_should_quit.should_quit()) {
-               if (process_queued_commands(format_ctx.get(), pathname, last_modified)) {
+               if (process_queued_commands(format_ctx.get(), pathname, last_modified, /*rewound=*/nullptr)) {
                        return true;
                }
 
@@ -398,7 +398,6 @@ bool FFmpegCapture::play_video(const string &pathname)
                        internal_rewind();
                        continue;
                }
-               last_pts = frame->pts;
 
                VideoFormat video_format = construct_video_format(frame.get(), video_timebase);
                FrameAllocator::Frame video_frame = make_video_frame(frame.get(), pathname, &error);
@@ -411,12 +410,30 @@ bool FFmpegCapture::play_video(const string &pathname)
                 audio_format.bits_per_sample = 32;
                 audio_format.num_channels = 8;
 
-               next_frame_start = compute_frame_start(frame->pts, pts_origin, video_timebase, start, rate);
-               video_frame.received_timestamp = next_frame_start;
-               producer_thread_should_quit.sleep_until(next_frame_start);
-               frame_callback(timecode++,
-                       video_frame, 0, video_format,
-                       audio_frame, 0, audio_format);
+               for ( ;; ) {
+                       next_frame_start = compute_frame_start(frame->pts, pts_origin, video_timebase, start, rate);
+                       video_frame.received_timestamp = next_frame_start;
+                       bool finished_wakeup = producer_thread_should_quit.sleep_until(next_frame_start);
+                       if (finished_wakeup) {
+                               frame_callback(timecode++,
+                                       video_frame, 0, video_format,
+                                       audio_frame, 0, audio_format);
+                               break;
+                       } else {
+                               bool rewound = false;
+                               if (process_queued_commands(format_ctx.get(), pathname, last_modified, &rewound)) {
+                                       return true;
+                               }
+                               // If we just rewound, drop this frame on the floor and be done.
+                               if (rewound) {
+                                       video_frame_allocator->release_frame(video_frame);
+                                       break;
+                               }
+                               // OK, we didn't, so probably a rate change. We'll recalculate next_frame_start
+                               // in the next run.
+                       }
+               }
+               last_pts = frame->pts;
        }
        return true;
 }
@@ -427,7 +444,7 @@ void FFmpegCapture::internal_rewind()
        start = next_frame_start = steady_clock::now();
 }
 
-bool FFmpegCapture::process_queued_commands(AVFormatContext *format_ctx, const std::string &pathname, timespec last_modified)
+bool FFmpegCapture::process_queued_commands(AVFormatContext *format_ctx, const std::string &pathname, timespec last_modified, bool *rewound)
 {
        // Process any queued commands from other threads.
        vector<QueuedCommand> commands;
@@ -449,10 +466,14 @@ bool FFmpegCapture::process_queued_commands(AVFormatContext *format_ctx, const s
                                return true;
                        }
                        internal_rewind();
+                       if (rewound != nullptr) {
+                               *rewound = true;
+                       }
                        break;
 
                case QueuedCommand::CHANGE_RATE:
-                       start = next_frame_start;
+                       // Change the origin to the last played frame.
+                       start = compute_frame_start(last_pts, pts_origin, video_timebase, start, rate);
                        pts_origin = last_pts;
                        rate = cmd.new_rate;
                        break;
index 55eb8979632e62e65ca698dcd49e613c9f1be1ab..328685b30208ef9416a7e97047fe23137e57533b 100644 (file)
@@ -32,6 +32,7 @@
 
 extern "C" {
 #include <libavutil/pixfmt.h>
+#include <libavutil/rational.h>
 }
 
 #include "bmusb/bmusb.h"
@@ -62,12 +63,14 @@ public:
        {
                std::lock_guard<std::mutex> lock(queue_mu);
                command_queue.push_back(QueuedCommand { QueuedCommand::REWIND });
+               producer_thread_should_quit.wakeup();
        }
 
        void change_rate(double new_rate)
        {
                std::lock_guard<std::mutex> lock(queue_mu);
                command_queue.push_back(QueuedCommand { QueuedCommand::CHANGE_RATE, new_rate });
+               producer_thread_should_quit.wakeup();
        }
 
        // CaptureInterface.
@@ -160,7 +163,7 @@ private:
        void internal_rewind();
 
        // Returns true if there was an error.
-       bool process_queued_commands(AVFormatContext *format_ctx, const std::string &pathname, timespec last_modified);
+       bool process_queued_commands(AVFormatContext *format_ctx, const std::string &pathname, timespec last_modified, bool *rewound);
 
        // Returns nullptr if no frame was decoded (e.g. EOF).
        AVFrameWithDeleter decode_frame(AVFormatContext *format_ctx, AVCodecContext *codec_ctx, const std::string &pathname, int video_stream_index, bool *error);
@@ -190,6 +193,7 @@ private:
        SwsContextWithDeleter sws_ctx;
        int sws_last_width = -1, sws_last_height = -1, sws_last_src_format = -1;
        AVPixelFormat sws_dst_format = AVPixelFormat(-1);  // In practice, always initialized.
+       AVRational video_timebase;
 
        QuittableSleeper producer_thread_should_quit;
        std::thread producer_thread;
index ba00e73b1184e9d388c04867b080773247547820..71e49f7731de3d49359f32cf048bcb639a7294a0 100644 (file)
@@ -26,33 +26,47 @@ public:
                should_quit_var = false;
        }
 
+       void wakeup()
+       {
+               std::lock_guard<std::mutex> l(mu);
+               should_wakeup_var = true;
+               quit_cond.notify_all();
+       }
+
        bool should_quit() const
        {
                std::lock_guard<std::mutex> l(mu);
                return should_quit_var;
        }
 
+       // Returns false if woken up early.
        template<class Rep, class Period>
-       void sleep_for(const std::chrono::duration<Rep, Period> &duration)
+       bool sleep_for(const std::chrono::duration<Rep, Period> &duration)
        {
                std::chrono::steady_clock::time_point t =
                        std::chrono::steady_clock::now() +
                        std::chrono::duration_cast<std::chrono::steady_clock::duration>(duration);
-               sleep_until(t);
+               return sleep_until(t);
        }
 
+       // Returns false if woken up early.
        template<class Clock, class Duration>
-       void sleep_until(const std::chrono::time_point<Clock, Duration> &t)
+       bool sleep_until(const std::chrono::time_point<Clock, Duration> &t)
        {
                std::unique_lock<std::mutex> lock(mu);
                quit_cond.wait_until(lock, t, [this]{
-                       return should_quit_var;
+                       return should_quit_var || should_wakeup_var;
                });
+               if (should_wakeup_var) {
+                       should_wakeup_var = false;
+                       return false;
+               }
+               return !should_quit_var;
        }
 
 private:
        mutable std::mutex mu;
-       bool should_quit_var = false;
+       bool should_quit_var = false, should_wakeup_var = false;
        std::condition_variable quit_cond;
 };