diff --git a/rpcs3/Emu/Cell/Modules/cellRec.cpp b/rpcs3/Emu/Cell/Modules/cellRec.cpp index d5beb5aa39..c793fbca88 100644 --- a/rpcs3/Emu/Cell/Modules/cellRec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellRec.cpp @@ -178,11 +178,30 @@ public: std::lock_guard lock(m_mtx); m_flush = flush; + m_paused = false; m_frames_to_encode.clear(); m_samples_to_encode.clear(); has_error = false; } + void pause(bool flush = true) override + { + cellRec.notice("Pausing video sink. flush=%d", flush); + + std::lock_guard lock(m_mtx); + m_flush = flush; + m_paused = true; + } + + void resume() override + { + cellRec.notice("Resuming video sink"); + + std::lock_guard lock(m_mtx); + m_flush = false; + m_paused = false; + } + encoder_frame get_frame() { std::lock_guard lock(m_mtx); @@ -563,7 +582,7 @@ void rec_info::start_video_provider() const u64 pause_time_end = get_system_time(); ensure(pause_time_end > pause_time_start); pause_time_total += (pause_time_end - pause_time_start); - video_provider.set_pause_time(pause_time_total / 1000); + video_provider.set_pause_time_us(pause_time_total); cellRec.notice("Resuming video provider."); return; } @@ -573,7 +592,7 @@ void rec_info::start_video_provider() recording_time_start = get_system_time(); pause_time_start = 0; pause_time_total = 0; - video_provider.set_pause_time(0); + video_provider.set_pause_time_us(0); video_provider_thread = std::make_unique>>("cellRec video provider", [this]() { @@ -785,7 +804,7 @@ void rec_info::start_video_provider() void rec_info::pause_video_provider() { - cellRec.notice("Pausing image provider."); + cellRec.notice("Pausing video provider."); if (video_provider_thread) { @@ -1393,20 +1412,14 @@ error_code cellRecStop() sysutil_register_cb([&rec](ppu_thread& ppu) -> s32 { - // Disable video sink if it was used - if (rec.param.use_internal_video() || rec.param.use_internal_audio()) - { - const recording_mode old_mode = g_recording_mode.exchange(recording_mode::stopped); - - if (old_mode != recording_mode::cell && old_mode != recording_mode::stopped) - { - cellRec.error("cellRecStop: Unexpected recording mode %s found while stopping video capture. (ring_sec=%d)", old_mode, rec.param.ring_sec); - } - } - // cellRecStop actually just pauses the recording rec.pause_video_provider(); + if (rec.sink) + { + rec.sink->pause(true); + } + ensure(!!rec.encoder); rec.encoder->pause(true); @@ -1465,6 +1478,11 @@ error_code cellRecStart() rec.start_video_provider(); + if (rec.sink) + { + rec.sink->resume(); + } + if (rec.encoder->has_error) { rec.cb(ppu, CELL_REC_STATUS_ERR, CELL_REC_ERROR_FILE_OPEN, rec.cbUserData); diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 1b3b3eb03c..498e4ac3f5 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -525,7 +525,7 @@ void gs_frame::toggle_recording() return; } - video_provider.set_pause_time(0); + video_provider.set_pause_time_us(0); g_recording_mode = recording_mode::rpcs3; diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index 25b4c5b1f2..9e21298dab 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -790,13 +790,20 @@ namespace utils m_running = false; } + void video_encoder::resume() + { + media_log.notice("video_encoder: Resuming video encoder"); + + m_flush = false; + m_paused = false; + } + void video_encoder::encode() { if (m_running) { // Resume - m_flush = false; - m_paused = false; + resume(); media_log.success("video_encoder: resuming recording of '%s'", m_path); return; } diff --git a/rpcs3/util/media_utils.h b/rpcs3/util/media_utils.h index e0d7cfd2ba..9666e53d86 100644 --- a/rpcs3/util/media_utils.h +++ b/rpcs3/util/media_utils.h @@ -120,8 +120,9 @@ namespace utils void set_audio_channels(u32 channels); void set_audio_bitrate(u32 bitrate); void set_audio_codec(s32 codec_id); - void pause(bool flush = true); + void pause(bool flush = true) override; void stop(bool flush = true) override; + void resume() override; void encode(); private: @@ -132,7 +133,6 @@ namespace utils // Thread control std::unique_ptr>> m_thread; atomic_t m_running = false; - atomic_t m_paused = false; // Video parameters u32 m_video_bitrate_bps = 0; diff --git a/rpcs3/util/video_provider.cpp b/rpcs3/util/video_provider.cpp index 046b7d12b0..a5888dadde 100644 --- a/rpcs3/util/video_provider.cpp +++ b/rpcs3/util/video_provider.cpp @@ -74,22 +74,10 @@ namespace utils return true; } - void video_provider::set_pause_time(usz pause_time_ms) + void video_provider::set_pause_time_us(usz pause_time_us) { std::lock_guard lock(m_mutex); - m_pause_time_ms = pause_time_ms; - } - - bool video_provider::can_consume_frame() - { - std::lock_guard lock(m_mutex); - - if (!m_video_sink || !m_video_sink->use_internal_video) - return false; - - const usz timestamp_ms = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count() - m_pause_time_ms; - const s64 pts = m_video_sink->get_pts(timestamp_ms); - return pts > m_last_video_pts_incoming; + m_pause_time_us = pause_time_us; } recording_mode video_provider::check_mode() @@ -122,6 +110,21 @@ namespace utils return g_recording_mode; } + bool video_provider::can_consume_frame() + { + std::lock_guard lock(m_mutex); + + if (!m_video_sink || !m_video_sink->use_internal_video) + return false; + + const usz elapsed_us = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count(); + ensure(elapsed_us >= m_pause_time_us); + + const usz timestamp_ms = (elapsed_us - m_pause_time_us) / 1000; + const s64 pts = m_video_sink->get_pts(timestamp_ms); + return pts > m_last_video_pts_incoming; + } + void video_provider::present_frame(std::vector& data, u32 pitch, u32 width, u32 height, bool is_bgra) { std::lock_guard lock(m_mutex); @@ -132,7 +135,10 @@ namespace utils } // Calculate presentation timestamp. - const usz timestamp_ms = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count() - m_pause_time_ms; + const usz elapsed_us = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count(); + ensure(elapsed_us >= m_pause_time_us); + + const usz timestamp_ms = (elapsed_us - m_pause_time_us) / 1000; const s64 pts = m_video_sink->get_pts(timestamp_ms); // We can just skip this frame if it has the same timestamp. @@ -141,9 +147,11 @@ namespace utils return; } - m_last_video_pts_incoming = pts; - m_current_encoder_frame++; - m_video_sink->add_frame(data, pitch, width, height, is_bgra ? AVPixelFormat::AV_PIX_FMT_BGRA : AVPixelFormat::AV_PIX_FMT_RGBA, timestamp_ms); + if (m_video_sink->add_frame(data, pitch, width, height, is_bgra ? AVPixelFormat::AV_PIX_FMT_BGRA : AVPixelFormat::AV_PIX_FMT_RGBA, timestamp_ms)) + { + m_last_video_pts_incoming = pts; + m_current_encoder_frame++; + } } bool video_provider::can_consume_sample() @@ -153,7 +161,10 @@ namespace utils if (!m_video_sink || !m_video_sink->use_internal_audio) return false; - const usz timestamp_us = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count() - (m_pause_time_ms * 1000ull); + const usz elapsed_us = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count(); + ensure(elapsed_us >= m_pause_time_us); + + const usz timestamp_us = elapsed_us - m_pause_time_us; const s64 pts = m_video_sink->get_audio_pts(timestamp_us); return pts > m_last_audio_pts_incoming; } @@ -173,7 +184,10 @@ namespace utils } // Calculate presentation timestamp. - const usz timestamp_us = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count() - (m_pause_time_ms * 1000ull); + const usz elapsed_us = std::chrono::duration_cast(steady_clock::now() - m_encoder_start).count(); + ensure(elapsed_us >= m_pause_time_us); + + const usz timestamp_us = elapsed_us - m_pause_time_us; const s64 pts = m_video_sink->get_audio_pts(timestamp_us); // We can just skip this sample if it has the same timestamp. @@ -182,8 +196,10 @@ namespace utils return; } - m_last_audio_pts_incoming = pts; - m_current_encoder_sample += sample_count; - m_video_sink->add_audio_samples(buf, sample_count, channels, timestamp_us); + if (m_video_sink->add_audio_samples(buf, sample_count, channels, timestamp_us)) + { + m_last_audio_pts_incoming = pts; + m_current_encoder_sample += sample_count; + } } } diff --git a/rpcs3/util/video_provider.h b/rpcs3/util/video_provider.h index 80cf38ffe6..0e30b01f7e 100644 --- a/rpcs3/util/video_provider.h +++ b/rpcs3/util/video_provider.h @@ -18,7 +18,7 @@ namespace utils ~video_provider(); bool set_video_sink(std::shared_ptr sink, recording_mode type); - void set_pause_time(usz pause_time_ms); + void set_pause_time_us(usz pause_time_us); bool can_consume_frame(); void present_frame(std::vector& data, u32 pitch, u32 width, u32 height, bool is_bgra); @@ -38,7 +38,7 @@ namespace utils steady_clock::time_point m_encoder_start{}; s64 m_last_video_pts_incoming = -1; s64 m_last_audio_pts_incoming = -1; - usz m_pause_time_ms = 0; + usz m_pause_time_us = 0; }; } // namespace utils diff --git a/rpcs3/util/video_sink.h b/rpcs3/util/video_sink.h index 5b33c3b8e5..9f1aadd65e 100644 --- a/rpcs3/util/video_sink.h +++ b/rpcs3/util/video_sink.h @@ -15,26 +15,30 @@ namespace utils video_sink() = default; virtual void stop(bool flush = true) = 0; + virtual void pause(bool flush = true) = 0; + virtual void resume() = 0; - void add_frame(std::vector& frame, u32 pitch, u32 width, u32 height, s32 pixel_format, usz timestamp_ms) + bool add_frame(std::vector& frame, u32 pitch, u32 width, u32 height, s32 pixel_format, usz timestamp_ms) { - // Do not allow new frames while flushing - if (m_flush) - return; + // Do not allow new frames while flushing or paused + if (m_flush || m_paused) + return false; std::lock_guard lock(m_mtx); m_frames_to_encode.emplace_back(timestamp_ms, pitch, width, height, pixel_format, std::move(frame)); + return true; } - void add_audio_samples(const u8* buf, u32 sample_count, u16 channels, usz timestamp_us) + bool add_audio_samples(const u8* buf, u32 sample_count, u16 channels, usz timestamp_us) { - // Do not allow new samples while flushing - if (m_flush || !buf || !sample_count || !channels) - return; + // Do not allow new samples while flushing or paused + if (m_flush || m_paused || !buf || !sample_count || !channels) + return false; std::vector sample(buf, buf + sample_count * channels * sizeof(f32)); std::lock_guard lock(m_audio_mtx); m_samples_to_encode.emplace_back(timestamp_us, sample_count, channels, std::move(sample)); + return true; } s64 get_pts(usz timestamp_ms) const @@ -102,6 +106,7 @@ namespace utils std::deque m_frames_to_encode; shared_mutex m_audio_mtx; std::deque m_samples_to_encode; + atomic_t m_paused = false; atomic_t m_flush = false; u32 m_framerate = 30; u32 m_sample_rate = 48000;