Add C game interface to construct and destroy global AVRecorder instance

This commit is contained in:
James R 2023-02-11 01:50:11 -08:00
parent 82251f6fb6
commit 79b1a8fd63
4 changed files with 255 additions and 0 deletions

View file

@ -32,6 +32,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
m_aatree.c
m_anigif.c
m_argv.c
m_avrecorder.cpp
m_bbox.c
m_cheat.c
m_cond.c

193
src/m_avrecorder.cpp Normal file
View file

@ -0,0 +1,193 @@
// 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 <chrono>
#include <exception>
#include <memory>
#include <stdexcept>
#include <fmt/format.h>
#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
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
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}};
CV_PossibleValue_t movie_limit_cons_t[] = {{1, "MIN"}, {INT32_MAX, "MAX"}, {0, "Unlimited"}, {0, NULL}};
}; // namespace
consvar_t cv_movie_resolution = CVAR_INIT("movie_resolution", "Medium", CV_SAVE, movie_resolution_cons_t, NULL);
consvar_t cv_movie_custom_resolution = CVAR_INIT("movie_custom_resolution", "640x400", CV_SAVE, NULL, NULL);
consvar_t cv_movie_fps = CVAR_INIT("movie_fps", "60", CV_SAVE, CV_Natural, NULL);
consvar_t cv_movie_showfps = CVAR_INIT("movie_showfps", "Yes", CV_SAVE, CV_YesNo, NULL);
consvar_t cv_movie_sound = CVAR_INIT("movie_sound", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_movie_duration = CVAR_INIT("movie_duration", "Unlimited", CV_SAVE | CV_FLOAT, movie_limit_cons_t, NULL);
consvar_t cv_movie_size = CVAR_INIT("movie_size", "8.0", CV_SAVE | CV_FLOAT, movie_limit_cons_t, NULL);
std::shared_ptr<AVRecorder> g_av_recorder;
void M_AVRecorder_AddCommands(void)
{
CV_RegisterVar(&cv_movie_custom_resolution);
CV_RegisterVar(&cv_movie_duration);
CV_RegisterVar(&cv_movie_fps);
CV_RegisterVar(&cv_movie_resolution);
CV_RegisterVar(&cv_movie_showfps);
CV_RegisterVar(&cv_movie_size);
CV_RegisterVar(&cv_movie_sound);
srb2::media::register_options();
}
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 = {
.sample_rate = 44100,
};
}
cfg.video = {
.frame_rate = cv_movie_fps.value,
};
AVRecorder::Config::Video& v = *cfg.video;
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);
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();
}
boolean M_AVRecorder_IsExpired(void)
{
SRB2_ASSERT(g_av_recorder != nullptr);
return g_av_recorder->invalid();
}

42
src/m_avrecorder.h Normal file
View file

@ -0,0 +1,42 @@
// 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.
//-----------------------------------------------------------------------------
#ifndef M_AVRECORDER_H
#define M_AVRECORDER_H
#include "typedef.h" // consvar_t
#ifdef __cplusplus
extern "C" {
#endif
void M_AVRecorder_AddCommands(void);
// True if successully opened.
boolean M_AVRecorder_Open(const char *filename);
void M_AVRecorder_Close(void);
// Check whether AVRecorder is still valid. Call M_AVRecorder_Close if expired.
boolean M_AVRecorder_IsExpired(void);
extern consvar_t
cv_movie_custom_resolution,
cv_movie_duration,
cv_movie_fps,
cv_movie_resolution,
cv_movie_showfps,
cv_movie_size,
cv_movie_sound;
#ifdef __cplusplus
}; // extern "C"
#endif
#endif/*M_AVRECORDER_H*/

19
src/m_avrecorder.hpp Normal file
View file

@ -0,0 +1,19 @@
// 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.
//-----------------------------------------------------------------------------
#ifndef __M_AVRECORDER_HPP__
#define __M_AVRECORDER_HPP__
#include <memory> // shared_ptr
#include "media/avrecorder.hpp"
extern std::shared_ptr<srb2::media::AVRecorder> g_av_recorder;
#endif // __M_AVRECORDER_HPP__