From b274bf4d57d35834dfe89385c296250d2a237c89 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 18 Aug 2024 12:34:22 +1000 Subject: [PATCH] System: Switch to new capture file on video FPS change --- src/core/gpu.cpp | 6 ++-- src/core/system.cpp | 20 +++++++++-- src/util/media_capture.cpp | 70 +++++++++++++++++++++++++++++--------- src/util/media_capture.h | 2 ++ 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index e26d445d2..3c2fb95bc 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -2150,7 +2150,7 @@ bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const bool GPU::SendDisplayToMediaCapture(MediaCapture* cap) { GPUTexture* target = cap->GetRenderTexture(); - if (!target) + if (!target) [[unlikely]] return false; const bool apply_aspect_ratio = @@ -2163,11 +2163,9 @@ bool GPU::SendDisplayToMediaCapture(MediaCapture* cap) // Not cleared by RenderDisplay(). g_gpu_device->ClearRenderTarget(target, GPUDevice::DEFAULT_CLEAR_COLOR); - if (!RenderDisplay(target, display_rect, draw_rect, postfx)) + if (!RenderDisplay(target, display_rect, draw_rect, postfx)) [[unlikely]] return false; - // TODO: Check for frame rate change - return cap->DeliverVideoFrame(target); } diff --git a/src/core/system.cpp b/src/core/system.cpp index 9dfd53e53..bd50d5f9e 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2024,8 +2024,24 @@ void System::FrameDone() // Kick off media capture early, might take a while. if (s_media_capture && s_media_capture->IsCapturingVideo()) [[unlikely]] { - if (!g_gpu->SendDisplayToMediaCapture(s_media_capture.get())) [[unlikely]] + if (s_media_capture->GetVideoFPS() != GetThrottleFrequency()) [[unlikely]] + { + const std::string next_capture_path = s_media_capture->GetNextCapturePath(); + INFO_LOG("Video frame rate changed, switching to new capture file {}", Path::GetFileName(next_capture_path)); + + const bool was_capturing_audio = s_media_capture->IsCapturingAudio(); StopMediaCapture(); + if (StartMediaCapture(std::move(next_capture_path), true, was_capturing_audio) && + !g_gpu->SendDisplayToMediaCapture(s_media_capture.get())) [[unlikely]] + { + StopMediaCapture(); + } + } + else + { + if (!g_gpu->SendDisplayToMediaCapture(s_media_capture.get())) [[unlikely]] + StopMediaCapture(); + } } Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); @@ -4991,7 +5007,7 @@ bool System::StartMediaCapture(std::string path, bool capture_video, bool captur u32 capture_height = g_settings.media_capture_video_height; const GPUTexture::Format capture_format = g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8; - const float fps = g_gpu->ComputeVerticalFrequency(); + const float fps = System::GetThrottleFrequency(); if (capture_video) { // TODO: This will be a mess with GPU thread. diff --git a/src/util/media_capture.cpp b/src/util/media_capture.cpp index 05f8b3f35..1b4e799d5 100644 --- a/src/util/media_capture.cpp +++ b/src/util/media_capture.cpp @@ -89,21 +89,23 @@ public: bool BeginCapture(float fps, float aspect, u32 width, u32 height, GPUTexture::Format texture_format, u32 sample_rate, std::string path, bool capture_video, std::string_view video_codec, u32 video_bitrate, std::string_view video_codec_args, bool capture_audio, std::string_view audio_codec, - u32 audio_bitrate, std::string_view audio_codec_args, Error* error) override; + u32 audio_bitrate, std::string_view audio_codec_args, Error* error) override final; - const std::string& GetPath() const override; - u32 GetVideoWidth() const override; - u32 GetVideoHeight() const override; + const std::string& GetPath() const override final; + std::string GetNextCapturePath() const override final; + u32 GetVideoWidth() const override final; + u32 GetVideoHeight() const override final; + float GetVideoFPS() const override final; - float GetCaptureThreadUsage() const override; - float GetCaptureThreadTime() const override; - void UpdateCaptureThreadUsage(double pct_divider, double time_divider) override; + float GetCaptureThreadUsage() const override final; + float GetCaptureThreadTime() const override final; + void UpdateCaptureThreadUsage(double pct_divider, double time_divider) override final; - GPUTexture* GetRenderTexture() override; - bool DeliverVideoFrame(GPUTexture* stex) override; - bool DeliverAudioFrames(const s16* frames, u32 num_frames) override; - bool EndCapture(Error* error) override; - void Flush() override; + GPUTexture* GetRenderTexture() override final; + bool DeliverVideoFrame(GPUTexture* stex) override final; + bool DeliverAudioFrames(const s16* frames, u32 num_frames) override final; + bool EndCapture(Error* error) override final; + void Flush() override final; protected: struct PendingFrame @@ -147,9 +149,10 @@ protected: std::atomic_bool m_capturing{false}; std::atomic_bool m_encoding_error{false}; + GPUTexture::Format m_video_render_texture_format = GPUTexture::Format::Unknown; u32 m_video_width = 0; u32 m_video_height = 0; - GPUTexture::Format m_video_render_texture_format = GPUTexture::Format::Unknown; + float m_video_fps = 0; s64 m_next_video_pts = 0; std::unique_ptr m_render_texture; @@ -185,17 +188,19 @@ bool MediaCaptureBase::BeginCapture(float fps, float aspect, u32 width, u32 heig std::string_view audio_codec, u32 audio_bitrate, std::string_view audio_codec_args, Error* error) { + m_video_render_texture_format = texture_format; m_video_width = width; m_video_height = height; - m_video_render_texture_format = texture_format; + m_video_fps = fps; if (path.empty()) { Error::SetStringView(error, "No path specified."); return false; } - else if (fps == 0.0f || m_video_width == 0 || !Common::IsAlignedPow2(m_video_width, VIDEO_WIDTH_ALIGNMENT) || - m_video_height == 0 || !Common::IsAlignedPow2(m_video_height, VIDEO_HEIGHT_ALIGNMENT)) + else if (capture_video && + (fps == 0.0f || m_video_width == 0 || !Common::IsAlignedPow2(m_video_width, VIDEO_WIDTH_ALIGNMENT) || + m_video_height == 0 || !Common::IsAlignedPow2(m_video_height, VIDEO_HEIGHT_ALIGNMENT))) { Error::SetStringView(error, "Invalid video dimensions/rate."); return false; @@ -506,6 +511,34 @@ const std::string& MediaCaptureBase::GetPath() const return m_path; } +std::string MediaCaptureBase::GetNextCapturePath() const +{ + const std::string_view ext = Path::GetExtension(m_path); + std::string_view name = Path::GetFileTitle(m_path); + + // Should end with a number. + u32 partnum = 2; + std::string_view::size_type pos = name.rfind("_part"); + if (pos != std::string_view::npos) + { + std::string_view::size_type cpos = pos + 5; + for (; cpos < name.length(); cpos++) + { + if (name[cpos] < '0' || name[cpos] > '9') + break; + } + if (cpos == name.length()) + { + // Has existing part number, so add to it. + partnum = StringUtil::FromChars(name.substr(pos + 5)).value_or(1) + 1; + name = name.substr(0, pos); + } + } + + // If we haven't started a new file previously, add "_part2". + return Path::BuildRelativePath(m_path, fmt::format("{}_part{:03d}.{}", name, partnum, ext)); +} + u32 MediaCaptureBase::GetVideoWidth() const { return m_video_width; @@ -516,6 +549,11 @@ u32 MediaCaptureBase::GetVideoHeight() const return m_video_height; } +float MediaCaptureBase::GetVideoFPS() const +{ + return m_video_fps; +} + float MediaCaptureBase::GetCaptureThreadUsage() const { return m_encoder_thread_usage; diff --git a/src/util/media_capture.h b/src/util/media_capture.h index 7a3efe26e..72c4a5ff4 100644 --- a/src/util/media_capture.h +++ b/src/util/media_capture.h @@ -56,10 +56,12 @@ public: // TODO: make non-virtual? virtual const std::string& GetPath() const = 0; + virtual std::string GetNextCapturePath() const = 0; virtual bool IsCapturingAudio() const = 0; virtual bool IsCapturingVideo() const = 0; virtual u32 GetVideoWidth() const = 0; virtual u32 GetVideoHeight() const = 0; + virtual float GetVideoFPS() const = 0; /// Returns the elapsed time in seconds. virtual time_t GetElapsedTime() const = 0;