Files
UltraGrid/gui/QT/option/settings.cpp
Martin Piatka 111ee21dcd GUI: Eliminate a few copies
Get's rid of a few copies that Coverity was complaining about
2023-05-23 12:49:06 +02:00

521 lines
15 KiB
C++

#include <iostream>
#include <algorithm>
#include <random>
#include "settings.hpp"
#include "available_settings.hpp"
Option::Callback::Callback(fcn_type func_ptr, void *opaque) :
func_ptr(func_ptr),
opaque(opaque)
{
}
void Option::Callback::operator()(Option& opt, bool suboption) const{
func_ptr(opt, suboption, opaque);
}
bool Option::Callback::operator==(const Callback& o) const{
return func_ptr == o.func_ptr && opaque == o.opaque;
}
std::string Option::getName() const{
return name;
}
std::string Option::getValue() const{
return value;
}
std::string Option::getParam() const{
return param;
}
std::string Option::getSubVals() const{
std::string out;
for(const auto &sub : suboptions){
if(sub.first.empty() || sub.first == value){
out += sub.second->getLaunchOption();
}
}
return out;
}
std::string Option::getLaunchOption() const{
if(type == OptType::BoolOpt){
return enabled ? param : "";
}
std::string out = "";
if(enabled && !value.empty() && type != OptType::SilentOpt)
out = param + value;
out += getSubVals();
return out;
}
void Option::changed(){
for(const auto &callback : onChangeCallbacks){
callback(*this, false);
}
}
void Option::suboptionChanged(Option &opt, bool, void *opaque){
Option *obj = static_cast<Option *>(opaque);
for(const auto &callback : obj->onChangeCallbacks){
callback(opt, true);
}
}
void Option::setValue(const std::string &val, bool suppressCallback){
value = val;
if(type == OptType::BoolOpt){
enabled = val == "t";
} else {
enabled = !val.empty();
}
if(!suppressCallback)
changed();
}
void Option::setDefaultValue(const std::string &val){
defaultValue = val;
}
void Option::setEnabled(bool enable, bool suppressCallback){
enabled = enable;
if(!suppressCallback)
changed();
}
void Option::addSuboption(Option *sub, const std::string &limit){
auto pair = std::make_pair(limit, sub);
auto it = std::find(suboptions.begin(), suboptions.end(), pair);
if(it != suboptions.end())
return;
sub->addOnChangeCallback(Callback(&Option::suboptionChanged, this));
suboptions.push_back(pair);
}
void Option::addOnChangeCallback(Callback callback){
onChangeCallbacks.push_back(callback);
}
void Option::removeOnChangeCallback(const Callback& callback){
auto it = std::find(onChangeCallbacks.begin(), onChangeCallbacks.end(), callback);
if(it == onChangeCallbacks.end()){
std::cout << "Attempted to remove non-existent callback "
<< name
<< std::endl;
return;
}
#ifdef DEBUG
std::cout << "Removing callback "
<< name
<< std::endl;
#endif
onChangeCallbacks.erase(it);
}
Settings *Option::getSettings(){
return settings;
}
#ifdef DEBUG
static void test_callback(Option &opt, bool, void *){
std::cout << "Callback: " << opt.getName()
<< ": " << opt.getValue()
<< " (" << opt.isEnabled() << ")" << std::endl;
}
#endif
static void fec_builder_callback(Option &opt, bool subopt, void *){
if(!subopt)
return;
Settings *settings = opt.getSettings();
Option &fecOpt = settings->getOption("network.fec");
bool enabled = settings->getOption("network.fec.enabled").isEnabled();
std::string type = settings->getOption("network.fec.type").getValue();
if(type.empty() || !enabled){
fecOpt.setValue("");
} else if(type == "mult"){
fecOpt.setValue("mult:" + settings->getOption("network.fec.mult.factor").getValue());
} else if(type == "rs"){
fecOpt.setValue("rs:"
+ settings->getOption("network.fec.rs.k").getValue()
+ ":" + settings->getOption("network.fec.rs.n").getValue());
} else if(type == "ldgm"){
fecOpt.setValue("ldgm:"
+ settings->getOption("network.fec.ldgm.k").getValue()
+ ":" + settings->getOption("network.fec.ldgm.m").getValue()
+ ":" + settings->getOption("network.fec.ldgm.c").getValue()
+ settings->getOption("network.fec.ldgm.device").getParam()
+ settings->getOption("network.fec.ldgm.device").getValue());
}
}
static void fec_auto_callback(Option &opt, bool /*subopt*/, void *){
Settings *settings = opt.getSettings();
if(!settings->getOption("network.fec.auto").isEnabled())
return;
Option &fecOpt = settings->getOption("network.fec.type");
const std::string &video_compress = settings->getOption("video.compress").getValue();
if(video_compress == ""){
fecOpt.setValue("");
} else if(video_compress == "jpeg"){
fecOpt.setValue("ldgm");
} else if(video_compress == "libavcodec"
&& settings->getOption("video.compress.libavcodec.codec").getValue() == "MJPEG"){
fecOpt.setValue("ldgm");
} else {
fecOpt.setValue("rs");
}
}
const static struct{
const char *name;
Option::OptType type;
const char *param;
const char *defaultVal;
bool enabled;
const char *parent;
const char *limit;
} optionList[] = {
{"video.source", Option::StringOpt, " -t ", "", false, "", ""},
{"video.source.embeddedAudioAvailable", Option::SilentOpt, "", "", false, "", ""},
{"testcard.width", Option::StringOpt, ":", "", false, "video.source", "testcard"},
{"testcard.height", Option::StringOpt, ":", "", false, "video.source", "testcard"},
{"testcard.fps", Option::StringOpt, ":", "", false, "video.source", "testcard"},
{"testcard.format", Option::StringOpt, ":", "", false, "video.source", "testcard"},
{"screen.fps", Option::StringOpt, ":fps=", "", false, "video.source", "screen"},
{"v4l2.codec", Option::StringOpt, ":codec=", "", false, "video.source", "v4l2"},
{"v4l2.size", Option::StringOpt, ":size=", "", false, "video.source", "v4l2"},
{"v4l2.tpf", Option::StringOpt, ":tpf=", "", false, "video.source", "v4l2"},
{"v4l2.force_rgb", Option::BoolOpt, ":convert=RGB", "t", true, "video.source", "v4l2"},
{"dshow.mode", Option::StringOpt, ":mode=", "", false, "video.source", "dshow"},
{"dshow.force_rgb", Option::BoolOpt, ":RGB", "t", true, "video.source", "dshow"},
{"avfoundation.mode", Option::StringOpt, ":mode=", "", false, "video.source", "avfoundation"},
{"avfoundation.fps", Option::StringOpt, ":fps=", "", false, "video.source", "avfoundation"},
{"decklink.modeOpt", Option::StringOpt, ":", "", false, "video.source", "decklink"},
{"video.display", Option::StringOpt, " -d ", "", false, "", ""},
{"video.display.embeddedAudioAvailable", Option::SilentOpt, "", "", false, "", ""},
{"video.compress", Option::StringOpt, " -c ", "", false, "", ""},
{"audio.source", Option::StringOpt, " -s ", "", false, "", ""},
{"audio.source.channels", Option::StringOpt, " --audio-capture-format channels=", "", false, "", ""},
{"audio.playback", Option::StringOpt, " -r ", "", false, "", ""},
{"audio.compress", Option::StringOpt, " --audio-codec ", "", false, "", ""},
{"bitrate", Option::StringOpt, ":bitrate=", "", false, "audio.compress", ""},
{"network.destination", Option::StringOpt, " ", "", false, "", ""},
{"network.port", Option::StringOpt, " -P ", "5004", false, "", ""},
{"network.control_port", Option::StringOpt, " --control-port ", "8888", true, "", ""},
{"network.control_port.random", Option::BoolOpt, "", "", true, "", ""},
{"network.fec", Option::StringOpt, " -f ", "", false, "", ""},
{"type", Option::SilentOpt, "", "", false, "network.fec", ""},
{"enabled", Option::BoolOpt, "", "f", false, "network.fec", ""},
{"mult.factor", Option::SilentOpt, ":", "2", false, "network.fec", ""},
{"ldgm.k", Option::SilentOpt, ":", "256", false, "network.fec", ""},
{"ldgm.m", Option::SilentOpt, ":", "192", false, "network.fec", ""},
{"ldgm.c", Option::SilentOpt, ":", "5", false, "network.fec", ""},
{"ldgm.device", Option::SilentOpt, " --param ldgm-device=", "CPU", false, "network.fec", ""},
{"rs.k", Option::SilentOpt, ":", "200", false, "network.fec", ""},
{"rs.n", Option::SilentOpt, "", "220", false, "network.fec", ""},
{"network.fec.auto", Option::BoolOpt, "", "t", true, "", ""},
{"decode.hwaccel", Option::BoolOpt, " --param use-hw-accel", "f", false, "", ""},
{"advanced", Option::BoolOpt, "", "f", false, "", ""},
{"preview", Option::BoolOpt, "", "t", true, "", ""},
{"vuMeter", Option::BoolOpt, "", "t", true, "", ""},
{"errors_fatal", Option::BoolOpt, " --param errors-fatal", "t", true, "", ""},
{"encryption", Option::StringOpt, " --encryption ", "", false, "", ""},
};
const struct {
const char *name;
Option::Callback::fcn_type callback;
} optionCallbacks[] = {
{"network.fec", fec_builder_callback},
{"video.compress", fec_auto_callback},
{"network.fec.auto", fec_auto_callback},
};
Settings::Settings() : dummy(this){
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> bounds(0, ('9' - '0') + ('z' - 'a'));
for(int i = 0; i < 8; i++){
int rand = bounds(gen);
if(rand <= 9)
sessRndKey += '0' + rand;
else
sessRndKey += 'a' + (rand - 9);
}
for(const auto &i : optionList){
auto &opt = addOption(i.name,
i.type,
i.param,
i.defaultVal,
i.enabled,
i.parent,
i.limit);
#ifdef DEBUG
if(!i.parent[0])
opt.addOnChangeCallback(Option::Callback(test_callback, nullptr));
#endif
(void) opt; //suppress unused warning
}
for(const auto &i : optionCallbacks){
getOption(i.name).addOnChangeCallback(Option::Callback(i.callback, nullptr));
}
std::cout << getLaunchParams() << std::endl;
}
std::string Settings::getControlPort() const{
if(getOption("network.control_port.random").isEnabled())
return "0";
return getOption("network.control_port").getValue();
}
std::string Settings::getLaunchParams() const{
std::string out;
if(getOption("preview").isEnabled()){
out += "--capture-filter preview";
out += ":key=" + getSessRndKey();
}
out += getOption("video.source").getLaunchOption();
out += getOption("video.compress").getLaunchOption();
const auto &dispOpt = getOption("video.display");
if(dispOpt.isEnabled()){
if(getOption("preview").isEnabled()){
out += dispOpt.getParam();
out += "multiplier:" + dispOpt.getValue() + dispOpt.getSubVals();
out += "#preview";
out += ":key=" + getSessRndKey();
} else {
out += getOption("video.display").getLaunchOption();
}
} else {
if(getOption("preview").isEnabled()){
out += dispOpt.getParam();
out += "preview";
out += ":key=" + getSessRndKey();
}
}
out += " --audio-filter controlport_stats";
out += getOption("audio.source").getLaunchOption();
out += getOption("audio.source.channels").getLaunchOption();
out += getOption("audio.compress").getLaunchOption();
std::string audioPlay = getOption("audio.playback").getLaunchOption();
if(audioPlay.empty() && getOption("preview").isEnabled()){
audioPlay = " -r dummy";
}
out += audioPlay;
out += getOption("network.fec").getLaunchOption();
out += getOption("encryption").getLaunchOption();
out += getOption("network.port").getLaunchOption();
out += getOption("network.control_port").getParam();
out += getControlPort();
out += getOption("network.destination").getLaunchOption();
out += getOption("decode.hwaccel").getLaunchOption();
out += getOption("errors_fatal").getLaunchOption();
return out;
}
std::string Settings::getPreviewParams() const{
std::string out;
out += " --capture-filter preview";
out += ":key=" + getSessRndKey();
out += ",every:0";
out += getOption("video.source").getLaunchOption();
out += " -d preview";
out += ":key=" + getSessRndKey();
out += " --audio-filter controlport_stats#discard";
out += getOption("audio.source").getLaunchOption();
out += getOption("audio.source.channels").getLaunchOption();
out += " -r dummy";
out += getOption("network.control_port").getParam();
out += getControlPort();
out += getOption("encryption").getLaunchOption();
return out;
}
Option& Settings::getOption(const std::string &opt){
auto search = options.find(opt);
if(search == options.end()){
std::unique_ptr<Option> newOption(new Option(this, opt));
auto p = newOption.get();
options[opt] = std::move(newOption);
return *p;
}
return *search->second;
}
const Option& Settings::getOption(const std::string &opt) const{
auto search = options.find(opt);
if(search == options.end())
return dummy;
return *search->second;
}
const std::map<std::string, std::unique_ptr<Option>>& Settings::getOptionMap() const{
return options;
}
Option& Settings::addOption(std::string name,
Option::OptType type,
const std::string &param,
const std::string &value,
bool enabled,
const std::string &parent,
const std::string &limit)
{
if(!parent.empty())
name = parent + "." + name;
auto search = options.find(name);
Option *opt;
if(search == options.end()){
std::unique_ptr<Option> newOption(new Option(this, name));
opt = newOption.get();
options[name] = std::move(newOption);
} else {
opt = search->second.get();
}
opt->setParam(param);
opt->setType(type);
opt->setValue(value);
opt->setDefaultValue(value);
opt->setEnabled(enabled);
if(!parent.empty()){
getOption(parent).addSuboption(opt, limit);
}
return *opt;
}
bool Settings::isAdvancedMode(){
return getOption("advanced").getValue() == "t";
}
void Settings::changedAll(){
for(auto &i : options){
i.second->changed();
}
}
static void addDevOpt(Settings* settings, const Device& dev, const char *parent){
settings->addOption(dev.type + ".device",
Option::StringOpt,
"",
"",
false,
parent,
dev.type);
}
void Settings::populateDeviceSettings(AvailableSettings *availSettings){
struct{
SettingType type;
const char *optPrefix;
} devTypes[] = {
{VIDEO_SRC, "video.source"},
{VIDEO_DISPLAY, "video.display"},
{AUDIO_SRC, "audio.source"},
{AUDIO_PLAYBACK, "audio.playback"},
};
for(const auto& devType : devTypes){
for(const auto& dev : availSettings->getDevices(devType.type)){
addDevOpt(this, dev, devType.optPrefix);
for(const auto& devOpt : dev.opts){
addOption(dev.deviceOpt + "." + devOpt.key,
devOpt.booleanOpt ? Option::BoolOpt : Option::StringOpt,
devOpt.optStr,
"",
false,
std::string(devType.optPrefix) + "." + dev.type + ".device",
dev.deviceOpt);
}
}
}
}
void Settings::populateVideoCompressSettings(AvailableSettings *availSettings){
for(const auto& mod : availSettings->getVideoCompressModules()){
addOption(mod.name + ".codec",
Option::SilentOpt,
"",
"",
false,
"video.compress",
mod.name);
for(const auto& modOption: mod.opts){
addOption(mod.name + "." + modOption.key,
modOption.booleanOpt ? Option::BoolOpt : Option::StringOpt,
modOption.optStr,
"",
false,
"video.compress",
mod.name
);
}
}
for(const auto& codec : availSettings->getVideoCompressCodecs()){
std::string optName = "video.compress.";
optName += codec.module_name;
optName += ".codec";
addOption(codec.name + ".encoder",
Option::StringOpt,
"",
codec.encoders[0].optStr,
true,
optName,
codec.name);
}
}
void Settings::populateSettingsFromCapabilities(AvailableSettings *availSettings){
populateDeviceSettings(availSettings);
populateVideoCompressSettings(availSettings);
}
/* vim: set noexpandtab: */