RingRacers/src/m_avrecorder.cpp
2025-02-13 15:32:26 -06:00

211 lines
4.1 KiB
C++

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by James Robert Roman
// Copyright (C) 2025 by Kart Krew
//
// 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 <algorithm>
#include <chrono>
#include <cstdint>
#include <exception>
#include <memory>
#include <stdexcept>
#include <fmt/format.h>
#include <tcb/span.hpp>
#include "cxxutil.hpp"
#include "m_avrecorder.hpp"
#include "media/options.hpp"
#include "command.h"
#include "i_sound.h"
#include "m_avrecorder.h"
#include "m_fixed.h"
#include "screen.h" // vid global
#include "st_stuff.h" // st_palette
#include "v_video.h" // pLocalPalette
using namespace srb2::media;
namespace
{
namespace Res
{
// Using an unscoped enum here so it can implicitly cast to
// int (in CV_PossibleValue_t). Wrap this in a namespace so
// access is still scoped. E.g. Res::kGame
enum : int32_t
{
kGame, // user chosen resolution, vid.width
kBase, // smallest version maintaining aspect ratio, vid.width / vid.dupx
kBase2x,
kBase4x,
kWindow, // window size (monitor in fullscreen), vid.realwidth
kCustom, // movie_custom_resolution
};
}; // namespace Res
}; // namespace
extern "C" {
CV_PossibleValue_t movie_resolution_cons_t[] = {
{Res::kGame, "Native"},
{Res::kBase, "Small"},
{Res::kBase2x, "Medium"},
{Res::kBase4x, "Large"},
{Res::kWindow, "Window"},
{Res::kCustom, "Custom"},
{0, NULL}};
}; // extern "C"
std::shared_ptr<AVRecorder> g_av_recorder;
void M_AVRecorder_AddCommands(void)
{
srb2::media::Options::register_all();
}
static AVRecorder::Config configure()
{
AVRecorder::Config cfg {};
if (cv_movie_duration.value > 0)
{
cfg.max_duration = std::chrono::duration<float>(FixedToFloat(cv_movie_duration.value));
}
if (cv_movie_size.value > 0)
{
cfg.max_size = FixedToFloat(cv_movie_size.value) * 1024 * 1024;
}
if (sound_started && cv_movie_sound.value)
{
cfg.audio = AVRecorder::Config::Audio { 44100 };
}
cfg.video = AVRecorder::Config::Video { };
AVRecorder::Config::Video& v = *cfg.video;
v.frame_rate = cv_movie_fps.value;
auto basex = [&v](int scale)
{
v.width = vid.width / vid.dupx * scale;
v.height = vid.height / vid.dupy * scale;
};
switch (cv_movie_resolution.value)
{
case Res::kGame:
v.width = vid.width;
v.height = vid.height;
break;
case Res::kBase:
basex(1);
break;
case Res::kBase2x:
basex(2);
break;
case Res::kBase4x:
basex(4);
break;
case Res::kWindow:
v.width = vid.realwidth;
v.height = vid.realheight;
break;
case Res::kCustom:
if (sscanf(cv_movie_custom_resolution.string, "%dx%d", &v.width, &v.height) != 2)
{
throw std::invalid_argument(fmt::format(
"Bad movie_custom_resolution '{}', should be <width>x<height> (e.g. 640x400)",
cv_movie_custom_resolution.string
));
}
break;
default:
SRB2_ASSERT(false);
}
return cfg;
}
boolean M_AVRecorder_Open(const char* filename)
{
try
{
AVRecorder::Config cfg = configure();
cfg.file_name = filename;
g_av_recorder = std::make_shared<AVRecorder>(cfg);
I_UpdateAudioRecorder();
return true;
}
catch (const std::exception& ex)
{
CONS_Alert(CONS_ERROR, "Exception starting video recorder: %s\n", ex.what());
return false;
}
}
void M_AVRecorder_Close(void)
{
g_av_recorder.reset();
I_UpdateAudioRecorder();
}
const char* M_AVRecorder_GetFileExtension(void)
{
return AVRecorder::file_extension();
}
const char* M_AVRecorder_GetCurrentFormat(void)
{
SRB2_ASSERT(g_av_recorder != nullptr);
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);
return g_av_recorder->invalid();
}
void M_AVRecorder_DrawFrameRate(void)
{
if (!cv_movie_showfps.value || !g_av_recorder)
{
return;
}
g_av_recorder->draw_statistics();
}