mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Add user feedback for AVRecorder
- real time monitoring of duration and esimated file size - estimated frame rate of final video (encoder speed) - print brief details of audio and video tracks upon starting recording - print exact file size and duration once recording is finished
This commit is contained in:
parent
840c11577e
commit
304e57cbf0
10 changed files with 206 additions and 0 deletions
|
|
@ -10,6 +10,7 @@
|
|||
#include "../discord.h"
|
||||
#endif
|
||||
#include "../doomstat.h"
|
||||
#include "../m_avrecorder.h"
|
||||
#include "../st_stuff.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../v_video.h"
|
||||
|
|
@ -121,6 +122,8 @@ static void temp_legacy_finishupdate_draws()
|
|||
}
|
||||
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
|
||||
SCR_DisplayLocalPing();
|
||||
|
||||
M_AVRecorder_DrawFrameRate();
|
||||
}
|
||||
|
||||
if (marathonmode)
|
||||
|
|
|
|||
|
|
@ -206,6 +206,13 @@ const char* M_AVRecorder_GetCurrentFormat(void)
|
|||
return g_av_recorder->format_name();
|
||||
}
|
||||
|
||||
void M_AVRecorder_PrintCurrentConfiguration(void)
|
||||
{
|
||||
SRB2_ASSERT(g_av_recorder != nullptr);
|
||||
|
||||
g_av_recorder->print_configuration();
|
||||
}
|
||||
|
||||
boolean M_AVRecorder_IsExpired(void)
|
||||
{
|
||||
SRB2_ASSERT(g_av_recorder != nullptr);
|
||||
|
|
@ -213,6 +220,16 @@ boolean M_AVRecorder_IsExpired(void)
|
|||
return g_av_recorder->invalid();
|
||||
}
|
||||
|
||||
void M_AVRecorder_DrawFrameRate(void)
|
||||
{
|
||||
if (!cv_movie_showfps.value || !g_av_recorder)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_av_recorder->draw_statistics();
|
||||
}
|
||||
|
||||
// TODO: remove once hwr2 twodee is finished
|
||||
void M_AVRecorder_CopySoftwareScreen(void)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ boolean M_AVRecorder_IsExpired(void);
|
|||
|
||||
const char *M_AVRecorder_GetCurrentFormat(void);
|
||||
|
||||
void M_AVRecorder_PrintCurrentConfiguration(void);
|
||||
|
||||
void M_AVRecorder_DrawFrameRate(void);
|
||||
|
||||
// TODO: remove once hwr2 twodee is finished
|
||||
void M_AVRecorder_CopySoftwareScreen(void);
|
||||
|
||||
|
|
|
|||
|
|
@ -1366,7 +1366,10 @@ void M_StartMovie(void)
|
|||
else if (moviemode == MM_SCREENSHOT)
|
||||
CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "screenshots");
|
||||
else if (moviemode == MM_AVRECORDER)
|
||||
{
|
||||
CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), M_AVRecorder_GetCurrentFormat());
|
||||
M_AVRecorder_PrintCurrentConfiguration();
|
||||
}
|
||||
|
||||
//singletics = (moviemode != MM_OFF);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ target_sources(SRB2SDL2 PRIVATE
|
|||
audio_encoder.hpp
|
||||
avrecorder.cpp
|
||||
avrecorder.hpp
|
||||
avrecorder_feedback.cpp
|
||||
avrecorder_impl.hpp
|
||||
avrecorder_indexed.cpp
|
||||
avrecorder_queue.cpp
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ Impl::Impl(Config cfg) :
|
|||
|
||||
container_(std::make_unique<WebmContainer>(MediaContainer::Config {
|
||||
.file_name = cfg.file_name,
|
||||
.destructor_callback = [this](const MediaContainer& container) { container_dtor_handler(container); },
|
||||
})),
|
||||
|
||||
audio_encoder_(make_audio_encoder(cfg)),
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ public:
|
|||
AVRecorder(Config config);
|
||||
~AVRecorder();
|
||||
|
||||
void print_configuration() const;
|
||||
void draw_statistics() const;
|
||||
|
||||
void push_audio_samples(audio_buffer_t buffer);
|
||||
|
||||
// May return nullptr in case called between units of
|
||||
|
|
|
|||
149
src/media/avrecorder_feedback.cpp
Normal file
149
src/media/avrecorder_feedback.cpp
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// RING RACERS
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2023 by James Robert Roman
|
||||
//
|
||||
// This program is free software distributed under the
|
||||
// terms of the GNU General Public License, version 2.
|
||||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
#include "avrecorder_impl.hpp"
|
||||
|
||||
#include "../v_video.h"
|
||||
|
||||
using namespace srb2::media;
|
||||
|
||||
using Impl = AVRecorder::Impl;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr float kMb = 1024.f * 1024.f;
|
||||
|
||||
}; // namespace
|
||||
|
||||
void Impl::container_dtor_handler(const MediaContainer& container) const
|
||||
{
|
||||
// Note that because this method is called from
|
||||
// container_'s destructor, any member variables declared
|
||||
// after Impl::container_ should not be accessed by now
|
||||
// (since they would have already destructed).
|
||||
|
||||
if (max_size_ && container.size() > *max_size_)
|
||||
{
|
||||
const std::string line = fmt::format(
|
||||
"Video size has exceeded limit {} > {} ({}%)."
|
||||
" This should not happen, please report this bug.\n",
|
||||
container.size(),
|
||||
*max_size_,
|
||||
100.f * (*max_size_ / static_cast<float>(container.size()))
|
||||
);
|
||||
|
||||
CONS_Alert(CONS_WARNING, "%s\n", line.c_str());
|
||||
}
|
||||
|
||||
std::ostringstream msg;
|
||||
|
||||
msg << "Video saved: " << std::filesystem::path(container.file_name()).filename().string()
|
||||
<< fmt::format(" ({:.2f}", container.size() / kMb);
|
||||
|
||||
if (max_size_)
|
||||
{
|
||||
msg << fmt::format("/{:.2f}", *max_size_ / kMb);
|
||||
}
|
||||
|
||||
msg << fmt::format(" MB, {:.1f}", container.duration().count());
|
||||
|
||||
if (max_duration_config_)
|
||||
{
|
||||
msg << fmt::format("/{:.1f}", max_duration_config_->count());
|
||||
}
|
||||
|
||||
msg << " seconds)";
|
||||
|
||||
CONS_Printf("%s\n", msg.str().c_str());
|
||||
}
|
||||
|
||||
void AVRecorder::print_configuration() const
|
||||
{
|
||||
if (impl_->audio_encoder_)
|
||||
{
|
||||
const auto& a = *impl_->audio_encoder_;
|
||||
|
||||
CONS_Printf("Audio: %s %dch %d Hz\n", a.name(), a.channels(), a.sample_rate());
|
||||
}
|
||||
|
||||
if (impl_->video_encoder_)
|
||||
{
|
||||
const auto& v = *impl_->video_encoder_;
|
||||
|
||||
CONS_Printf(
|
||||
"Video: %s %dx%d %d fps %d threads\n",
|
||||
v.name(),
|
||||
v.width(),
|
||||
v.height(),
|
||||
v.frame_rate(),
|
||||
v.thread_count()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void AVRecorder::draw_statistics() const
|
||||
{
|
||||
SRB2_ASSERT(impl_->video_encoder_ != nullptr);
|
||||
|
||||
auto draw = [](int x, std::string text, int32_t flags = 0)
|
||||
{
|
||||
V_DrawThinString(
|
||||
x,
|
||||
190,
|
||||
(V_6WIDTHSPACE | V_ALLOWLOWERCASE | V_SNAPTOBOTTOM | V_SNAPTORIGHT) | flags,
|
||||
text.c_str()
|
||||
);
|
||||
};
|
||||
|
||||
const float fps = impl_->video_frame_rate_avg_;
|
||||
const float size = impl_->container_->size();
|
||||
|
||||
const int32_t fps_color = [&]
|
||||
{
|
||||
const int cap = impl_->video_encoder_->frame_rate();
|
||||
|
||||
// red when dropped below 60% of the target
|
||||
if (fps > 0.f && fps < (0.6f * cap))
|
||||
{
|
||||
return V_REDMAP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
const int32_t mb_color = [&]
|
||||
{
|
||||
if (!impl_->max_size_)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::size_t cap = *impl_->max_size_;
|
||||
|
||||
// yellow when within 1 MB of the limit
|
||||
if (size >= (cap - kMb))
|
||||
{
|
||||
return V_YELLOWMAP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
draw(200, fmt::format("{:.0f}", fps), fps_color);
|
||||
draw(230, fmt::format("{:.1f}s", impl_->container_->duration().count()));
|
||||
draw(260, fmt::format("{:.1f} MB", size / kMb), mb_color);
|
||||
}
|
||||
|
|
@ -110,6 +110,9 @@ public:
|
|||
// 2) the object has begun destructing
|
||||
std::atomic<bool> valid_ = true;
|
||||
|
||||
// Average number of frames actually encoded per second.
|
||||
std::atomic<float> video_frame_rate_avg_ = 0.f;
|
||||
|
||||
Impl(Config config);
|
||||
~Impl();
|
||||
|
||||
|
|
@ -132,6 +135,8 @@ private:
|
|||
|
||||
const tic_t epoch_;
|
||||
|
||||
VideoEncoder::FrameCount video_frame_count_reference_ = {};
|
||||
|
||||
std::thread thread_;
|
||||
mutable std::recursive_mutex queue_mutex_; // guards audio and video queues
|
||||
std::condition_variable_any queue_cond_;
|
||||
|
|
@ -140,9 +145,12 @@ private:
|
|||
std::unique_ptr<VideoEncoder> make_video_encoder(const Config cfg) const;
|
||||
|
||||
QueueState encode_queues();
|
||||
void update_video_frame_rate_avg();
|
||||
|
||||
void worker();
|
||||
|
||||
void container_dtor_handler(const MediaContainer& container) const;
|
||||
|
||||
// TODO: remove once hwr2 twodee is finished
|
||||
VideoFrame::instance_t convert_indexed_video_frame(const IndexedVideoFrame& indexed);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ Impl::QueueState Impl::encode_queues()
|
|||
|
||||
video_encoder_->encode(std::move(frame));
|
||||
}
|
||||
|
||||
update_video_frame_rate_avg();
|
||||
};
|
||||
|
||||
check(audio_queue_, encode_audio);
|
||||
|
|
@ -146,5 +148,20 @@ Impl::QueueState Impl::encode_queues()
|
|||
}
|
||||
}
|
||||
|
||||
void Impl::update_video_frame_rate_avg()
|
||||
{
|
||||
constexpr auto period = std::chrono::duration<float>(1.f);
|
||||
|
||||
auto& ref = video_frame_count_reference_;
|
||||
const auto count = video_encoder_->frame_count();
|
||||
const auto t = (count.duration - ref.duration);
|
||||
|
||||
if (t >= period)
|
||||
{
|
||||
video_frame_rate_avg_ = (count.frames - ref.frames) * (period / t);
|
||||
ref = count;
|
||||
}
|
||||
}
|
||||
|
||||
template class Impl::Queue<AudioEncoder>;
|
||||
template class Impl::Queue<VideoEncoder>;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue