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"
|
#include "../discord.h"
|
||||||
#endif
|
#endif
|
||||||
#include "../doomstat.h"
|
#include "../doomstat.h"
|
||||||
|
#include "../m_avrecorder.h"
|
||||||
#include "../st_stuff.h"
|
#include "../st_stuff.h"
|
||||||
#include "../s_sound.h"
|
#include "../s_sound.h"
|
||||||
#include "../v_video.h"
|
#include "../v_video.h"
|
||||||
|
|
@ -121,6 +122,8 @@ static void temp_legacy_finishupdate_draws()
|
||||||
}
|
}
|
||||||
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
|
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
|
||||||
SCR_DisplayLocalPing();
|
SCR_DisplayLocalPing();
|
||||||
|
|
||||||
|
M_AVRecorder_DrawFrameRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (marathonmode)
|
if (marathonmode)
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,13 @@ const char* M_AVRecorder_GetCurrentFormat(void)
|
||||||
return g_av_recorder->format_name();
|
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)
|
boolean M_AVRecorder_IsExpired(void)
|
||||||
{
|
{
|
||||||
SRB2_ASSERT(g_av_recorder != nullptr);
|
SRB2_ASSERT(g_av_recorder != nullptr);
|
||||||
|
|
@ -213,6 +220,16 @@ boolean M_AVRecorder_IsExpired(void)
|
||||||
return g_av_recorder->invalid();
|
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
|
// TODO: remove once hwr2 twodee is finished
|
||||||
void M_AVRecorder_CopySoftwareScreen(void)
|
void M_AVRecorder_CopySoftwareScreen(void)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ boolean M_AVRecorder_IsExpired(void);
|
||||||
|
|
||||||
const char *M_AVRecorder_GetCurrentFormat(void);
|
const char *M_AVRecorder_GetCurrentFormat(void);
|
||||||
|
|
||||||
|
void M_AVRecorder_PrintCurrentConfiguration(void);
|
||||||
|
|
||||||
|
void M_AVRecorder_DrawFrameRate(void);
|
||||||
|
|
||||||
// TODO: remove once hwr2 twodee is finished
|
// TODO: remove once hwr2 twodee is finished
|
||||||
void M_AVRecorder_CopySoftwareScreen(void);
|
void M_AVRecorder_CopySoftwareScreen(void);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1366,7 +1366,10 @@ void M_StartMovie(void)
|
||||||
else if (moviemode == MM_SCREENSHOT)
|
else if (moviemode == MM_SCREENSHOT)
|
||||||
CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "screenshots");
|
CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), "screenshots");
|
||||||
else if (moviemode == MM_AVRECORDER)
|
else if (moviemode == MM_AVRECORDER)
|
||||||
|
{
|
||||||
CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), M_AVRecorder_GetCurrentFormat());
|
CONS_Printf(M_GetText("Movie mode enabled (%s).\n"), M_AVRecorder_GetCurrentFormat());
|
||||||
|
M_AVRecorder_PrintCurrentConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
//singletics = (moviemode != MM_OFF);
|
//singletics = (moviemode != MM_OFF);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ target_sources(SRB2SDL2 PRIVATE
|
||||||
audio_encoder.hpp
|
audio_encoder.hpp
|
||||||
avrecorder.cpp
|
avrecorder.cpp
|
||||||
avrecorder.hpp
|
avrecorder.hpp
|
||||||
|
avrecorder_feedback.cpp
|
||||||
avrecorder_impl.hpp
|
avrecorder_impl.hpp
|
||||||
avrecorder_indexed.cpp
|
avrecorder_indexed.cpp
|
||||||
avrecorder_queue.cpp
|
avrecorder_queue.cpp
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ Impl::Impl(Config cfg) :
|
||||||
|
|
||||||
container_(std::make_unique<WebmContainer>(MediaContainer::Config {
|
container_(std::make_unique<WebmContainer>(MediaContainer::Config {
|
||||||
.file_name = cfg.file_name,
|
.file_name = cfg.file_name,
|
||||||
|
.destructor_callback = [this](const MediaContainer& container) { container_dtor_handler(container); },
|
||||||
})),
|
})),
|
||||||
|
|
||||||
audio_encoder_(make_audio_encoder(cfg)),
|
audio_encoder_(make_audio_encoder(cfg)),
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,9 @@ public:
|
||||||
AVRecorder(Config config);
|
AVRecorder(Config config);
|
||||||
~AVRecorder();
|
~AVRecorder();
|
||||||
|
|
||||||
|
void print_configuration() const;
|
||||||
|
void draw_statistics() const;
|
||||||
|
|
||||||
void push_audio_samples(audio_buffer_t buffer);
|
void push_audio_samples(audio_buffer_t buffer);
|
||||||
|
|
||||||
// May return nullptr in case called between units of
|
// 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
|
// 2) the object has begun destructing
|
||||||
std::atomic<bool> valid_ = true;
|
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(Config config);
|
||||||
~Impl();
|
~Impl();
|
||||||
|
|
||||||
|
|
@ -132,6 +135,8 @@ private:
|
||||||
|
|
||||||
const tic_t epoch_;
|
const tic_t epoch_;
|
||||||
|
|
||||||
|
VideoEncoder::FrameCount video_frame_count_reference_ = {};
|
||||||
|
|
||||||
std::thread thread_;
|
std::thread thread_;
|
||||||
mutable std::recursive_mutex queue_mutex_; // guards audio and video queues
|
mutable std::recursive_mutex queue_mutex_; // guards audio and video queues
|
||||||
std::condition_variable_any queue_cond_;
|
std::condition_variable_any queue_cond_;
|
||||||
|
|
@ -140,9 +145,12 @@ private:
|
||||||
std::unique_ptr<VideoEncoder> make_video_encoder(const Config cfg) const;
|
std::unique_ptr<VideoEncoder> make_video_encoder(const Config cfg) const;
|
||||||
|
|
||||||
QueueState encode_queues();
|
QueueState encode_queues();
|
||||||
|
void update_video_frame_rate_avg();
|
||||||
|
|
||||||
void worker();
|
void worker();
|
||||||
|
|
||||||
|
void container_dtor_handler(const MediaContainer& container) const;
|
||||||
|
|
||||||
// TODO: remove once hwr2 twodee is finished
|
// TODO: remove once hwr2 twodee is finished
|
||||||
VideoFrame::instance_t convert_indexed_video_frame(const IndexedVideoFrame& indexed);
|
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));
|
video_encoder_->encode(std::move(frame));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_video_frame_rate_avg();
|
||||||
};
|
};
|
||||||
|
|
||||||
check(audio_queue_, encode_audio);
|
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<AudioEncoder>;
|
||||||
template class Impl::Queue<VideoEncoder>;
|
template class Impl::Queue<VideoEncoder>;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue