media: fix undefined behavior with options initialization order

Cvar vector was not guaranteed to initialize before
options for each encoder, potentially leading to no
encoder cvars being registered.
This commit is contained in:
James R 2023-02-13 06:57:28 -08:00
parent 860693936f
commit 02fe7ec744
8 changed files with 87 additions and 75 deletions

View file

@ -89,7 +89,7 @@ void M_AVRecorder_AddCommands(void)
CV_RegisterVar(&cv_movie_size);
CV_RegisterVar(&cv_movie_sound);
srb2::media::register_options();
srb2::media::Options::register_all();
}
static AVRecorder::Config configure()

View file

@ -12,6 +12,7 @@ target_sources(SRB2SDL2 PRIVATE
encoder.hpp
options.cpp
options.hpp
options_values.cpp
video_encoder.hpp
video_frame.hpp
vorbis.cpp

View file

@ -19,14 +19,12 @@
using namespace srb2::media;
static std::vector<consvar_t*> g_cvars;
Options::Options(const char* prefix, map_t map) : prefix_(prefix), map_(map)
{
for (auto& [suffix, cvar] : map_)
{
cvar.name = strdup(fmt::format("{}_{}", prefix_, suffix).c_str());
g_cvars.emplace_back(&cvar);
cvars_.emplace_back(&cvar);
}
}
@ -107,12 +105,12 @@ consvar_t Options::value_map<int>(const char* default_value, std::map<const char
return CVAR_INIT(nullptr, default_value, CV_SAVE, arr, nullptr);
}
void srb2::media::register_options()
void Options::register_all()
{
for (auto cvar : g_cvars)
for (auto cvar : cvars_)
{
CV_RegisterVar(cvar);
}
g_cvars = {};
cvars_ = {};
}

View file

@ -11,7 +11,9 @@
#define __SRB2_MEDIA_OPTIONS_HPP__
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
#include "../command.h"
@ -21,7 +23,10 @@ namespace srb2::media
class Options
{
public:
using map_t = std::unordered_map<const char*, consvar_t>;
using map_t = std::unordered_map<std::string, consvar_t>;
// Registers all options as cvars.
static void register_all();
Options(const char* prefix, map_t map);
@ -38,14 +43,14 @@ public:
static consvar_t value_map(const char* default_value, std::map<const char*, T> values);
private:
static std::vector<consvar_t*> cvars_;
const char* prefix_;
map_t map_;
const consvar_t& cvar(const char* option) const;
};
void register_options();
}; // namespace srb2::media
#endif // __SRB2_MEDIA_OPTIONS_HPP__

View file

@ -0,0 +1,62 @@
// 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 <cstdint>
#include <vpx/vpx_encoder.h>
#include "options.hpp"
#include "vorbis.hpp"
#include "vp8.hpp"
using namespace srb2::media;
// NOTE: Options::cvars_ MUST be initialized before any
// Options instances construct. For static objects, they have
// to be defined in the same translation unit as
// Options::cvars_ to guarantee initialization order.
std::vector<consvar_t*> Options::cvars_;
// clang-format off
const Options VorbisEncoder::options_("vorbis", {
{"quality", Options::range<float>("0", -0.1f, 1.f)},
{"max_bitrate", Options::range_min<int>("-1", -1)},
{"nominal_bitrate", Options::range_min<int>("-1", -1)},
{"min_bitrate", Options::range_min<int>("-1", -1)},
});
const Options VP8Encoder::options_("vp8", {
{"quality_mode", Options::value_map<int>("q", {
{"vbr", VPX_VBR},
{"cbr", VPX_CBR},
{"cq", VPX_CQ},
{"q", VPX_Q},
})},
{"target_bitrate", Options::range_min<int>("800", 1)},
{"min_q", Options::range<int>("4", 4, 63)},
{"max_q", Options::range<int>("55", 4, 63)},
{"kf_min", Options::range_min<int>("0", 0)},
{"kf_max", Options::value_map<int>("auto", {
{"auto", static_cast<int>(KeyFrameOption::kAuto)},
{"MIN", 0},
{"MAX", INT32_MAX},
})},
{"cpu_used", Options::range<int>("0", -16, 16)},
{"cq_level", Options::range<int>("10", 0, 63)},
{"deadline", Options::value_map<int>("10", {
{"infinite", static_cast<int>(DeadlineOption::kInfinite)},
{"MIN", 1},
{"MAX", INT32_MAX},
})},
{"sharpness", Options::range<int>("7", 0, 7)},
{"token_parts", Options::range<int>("0", 0, 3)},
{"threads", Options::range_min<int>("1", 1)},
});
// clang-format on

View file

@ -20,15 +20,6 @@
using namespace srb2::media;
// clang-format off
const Options VorbisEncoder::options_("vorbis", {
{"quality", Options::range<float>("0", -0.1f, 1.f)},
{"max_bitrate", Options::range_min<int>("-1", -1)},
{"nominal_bitrate", Options::range_min<int>("-1", -1)},
{"min_bitrate", Options::range_min<int>("-1", -1)},
});
// clang-format on
VorbisEncoder::VorbisEncoder(Config cfg)
{
const long max_bitrate = options_.get<int>("max_bitrate");

View file

@ -24,61 +24,6 @@
using namespace srb2::media;
namespace
{
namespace KeyFrameOption
{
enum : int
{
kAuto = -1,
};
}; // namespace KeyFrameOption
namespace DeadlineOption
{
enum : int
{
kInfinite = 0,
};
}; // namespace DeadlineOption
}; // namespace
// clang-format off
const Options VP8Encoder::options_("vp8", {
{"quality_mode", Options::value_map<int>("q", {
{"vbr", VPX_VBR},
{"cbr", VPX_CBR},
{"cq", VPX_CQ},
{"q", VPX_Q},
})},
{"target_bitrate", Options::range_min<int>("800", 1)},
{"min_q", Options::range<int>("4", 4, 63)},
{"max_q", Options::range<int>("55", 4, 63)},
{"kf_min", Options::range_min<int>("0", 0)},
{"kf_max", Options::value_map<int>("auto", {
{"auto", KeyFrameOption::kAuto},
{"MIN", 0},
{"MAX", INT32_MAX},
})},
{"cpu_used", Options::range<int>("0", -16, 16)},
{"cq_level", Options::range<int>("10", 0, 63)},
{"deadline", Options::value_map<int>("10", {
{"infinite", DeadlineOption::kInfinite},
{"MIN", 1},
{"MAX", INT32_MAX},
})},
{"sharpness", Options::range<int>("7", 0, 7)},
{"token_parts", Options::range<int>("0", 0, 3)},
{"threads", Options::range_min<int>("1", 1)},
});
// clang-format on
vpx_codec_iface_t* VP8Encoder::kCodec = vpx_codec_vp8_cx();
const vpx_codec_enc_cfg_t VP8Encoder::configure(const Config user)
@ -110,7 +55,7 @@ const vpx_codec_enc_cfg_t VP8Encoder::configure(const Config user)
int kf_max = options_.get<int>("kf_max");
if (kf_max == KeyFrameOption::kAuto)
if (kf_max == static_cast<int>(KeyFrameOption::kAuto))
{
// Automatically pick a good rate
kf_max = (user.frame_rate / 2); // every .5s

View file

@ -69,6 +69,16 @@ private:
vpx_image_t img_;
};
enum class KeyFrameOption : int
{
kAuto = -1,
};
enum class DeadlineOption : int
{
kInfinite = 0,
};
static vpx_codec_iface_t* kCodec;
static const vpx_codec_enc_cfg_t configure(const Config config);