From: Steinar H. Gunderson Date: Sun, 28 May 2017 15:38:26 +0000 (+0200) Subject: Fix a problem where change_rate() would hang until the next frame arrived. X-Git-Tag: 1.6.0~2 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=f396212c454be6ddeaf3f04e71ae07f2e44cb831 Fix a problem where change_rate() would hang until the next frame arrived. --- diff --git a/ffmpeg_capture.cpp b/ffmpeg_capture.cpp index 7ff8226..97363a7 100644 --- a/ffmpeg_capture.cpp +++ b/ffmpeg_capture.cpp @@ -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 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; diff --git a/ffmpeg_capture.h b/ffmpeg_capture.h index 55eb897..328685b 100644 --- a/ffmpeg_capture.h +++ b/ffmpeg_capture.h @@ -32,6 +32,7 @@ extern "C" { #include +#include } #include "bmusb/bmusb.h" @@ -62,12 +63,14 @@ public: { std::lock_guard lock(queue_mu); command_queue.push_back(QueuedCommand { QueuedCommand::REWIND }); + producer_thread_should_quit.wakeup(); } void change_rate(double new_rate) { std::lock_guard 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; diff --git a/quittable_sleeper.h b/quittable_sleeper.h index ba00e73..71e49f7 100644 --- a/quittable_sleeper.h +++ b/quittable_sleeper.h @@ -26,33 +26,47 @@ public: should_quit_var = false; } + void wakeup() + { + std::lock_guard l(mu); + should_wakeup_var = true; + quit_cond.notify_all(); + } + bool should_quit() const { std::lock_guard l(mu); return should_quit_var; } + // Returns false if woken up early. template - void sleep_for(const std::chrono::duration &duration) + bool sleep_for(const std::chrono::duration &duration) { std::chrono::steady_clock::time_point t = std::chrono::steady_clock::now() + std::chrono::duration_cast(duration); - sleep_until(t); + return sleep_until(t); } + // Returns false if woken up early. template - void sleep_until(const std::chrono::time_point &t) + bool sleep_until(const std::chrono::time_point &t) { std::unique_lock 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; };