mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 01:44:49 -05:00
Based on the OBS stats dock implementation, which queries the drive to which outputs would write to
614 lines
15 KiB
C++
614 lines
15 KiB
C++
#include "macro-condition-obs-stats.hpp"
|
|
#include "layout-helpers.hpp"
|
|
#include "math-helpers.hpp"
|
|
|
|
#include <obs-frontend-api.h>
|
|
#include <QListView>
|
|
#include <util/config-file.h>
|
|
|
|
namespace advss {
|
|
|
|
const std::string MacroConditionStats::id = "obs_stats";
|
|
|
|
bool MacroConditionStats::_registered = MacroConditionFactory::Register(
|
|
MacroConditionStats::id,
|
|
{MacroConditionStats::Create, MacroConditionStatsEdit::Create,
|
|
"AdvSceneSwitcher.condition.stats"});
|
|
|
|
const static std::map<MacroConditionStats::Type, std::string> statsTypes = {
|
|
{MacroConditionStats::Type::FPS,
|
|
"AdvSceneSwitcher.condition.stats.type.fps"},
|
|
{MacroConditionStats::Type::CPU_USAGE,
|
|
"AdvSceneSwitcher.condition.stats.type.CPUUsage"},
|
|
{MacroConditionStats::Type::DISK_USAGE,
|
|
"AdvSceneSwitcher.condition.stats.type.HDDSpaceAvailable"},
|
|
{MacroConditionStats::Type::MEM_USAGE,
|
|
"AdvSceneSwitcher.condition.stats.type.memoryUsage"},
|
|
{MacroConditionStats::Type::AVG_FRAMETIME,
|
|
"AdvSceneSwitcher.condition.stats.type.averageTimeToRender"},
|
|
{MacroConditionStats::Type::RENDER_LAG,
|
|
"AdvSceneSwitcher.condition.stats.type.missedFrames"},
|
|
{MacroConditionStats::Type::ENCODE_LAG,
|
|
"AdvSceneSwitcher.condition.stats.type.skippedFrames"},
|
|
{MacroConditionStats::Type::STREAM_DROPPED_FRAMES,
|
|
"AdvSceneSwitcher.condition.stats.type.droppedFrames.stream"},
|
|
{MacroConditionStats::Type::STREAM_BITRATE,
|
|
"AdvSceneSwitcher.condition.stats.type.bitrate.stream"},
|
|
{MacroConditionStats::Type::STREAM_MB_SENT,
|
|
"AdvSceneSwitcher.condition.stats.type.megabytesSent.stream"},
|
|
{MacroConditionStats::Type::RECORDING_DROPPED_FRAMES,
|
|
"AdvSceneSwitcher.condition.stats.type.droppedFrames.recording"},
|
|
{MacroConditionStats::Type::RECORDING_BITRATE,
|
|
"AdvSceneSwitcher.condition.stats.type.bitrate.recording"},
|
|
{MacroConditionStats::Type::RECORDING_MB_SENT,
|
|
"AdvSceneSwitcher.condition.stats.type.megabytesSent.recording"},
|
|
};
|
|
|
|
const static std::map<MacroConditionStats::Condition, std::string>
|
|
statsConditionTypes = {
|
|
{MacroConditionStats::Condition::ABOVE,
|
|
"AdvSceneSwitcher.condition.stats.condition.above"},
|
|
{MacroConditionStats::Condition::EQUALS,
|
|
"AdvSceneSwitcher.condition.stats.condition.equals"},
|
|
{MacroConditionStats::Condition::BELOW,
|
|
"AdvSceneSwitcher.condition.stats.condition.below"},
|
|
};
|
|
|
|
MacroConditionStats::MacroConditionStats(Macro *m)
|
|
: MacroCondition(m),
|
|
_cpu_info(os_cpu_usage_info_start())
|
|
{
|
|
}
|
|
|
|
MacroConditionStats::~MacroConditionStats()
|
|
{
|
|
os_cpu_usage_info_destroy(_cpu_info);
|
|
}
|
|
|
|
bool MacroConditionStats::CheckFPS() const
|
|
{
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return obs_get_active_fps() > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(obs_get_active_fps(), _value, 0.01);
|
|
case Condition::BELOW:
|
|
return obs_get_active_fps() < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckCPU() const
|
|
{
|
|
double usage = os_cpu_usage_info_query(_cpu_info);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return usage > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(usage, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return usage < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckMemory() const
|
|
{
|
|
auto rss =
|
|
(long double)os_get_proc_resident_size() / (1024.0l * 1024.0l);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return rss > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(rss, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return rss < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckAvgFrametime() const
|
|
{
|
|
auto num = (long double)obs_get_average_frame_time_ns() / 1000000.0l;
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return num > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(num, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return num < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckRenderLag()
|
|
{
|
|
uint32_t total_rendered = obs_get_total_frames();
|
|
uint32_t total_lagged = obs_get_lagged_frames();
|
|
|
|
if (total_rendered < _first_rendered || total_lagged < _first_lagged) {
|
|
_first_rendered = total_rendered;
|
|
_first_lagged = total_lagged;
|
|
}
|
|
total_rendered -= _first_rendered;
|
|
total_lagged -= _first_lagged;
|
|
|
|
auto num = total_rendered ? (long double)total_lagged /
|
|
(long double)total_rendered
|
|
: 0.0l;
|
|
num *= 100.0l;
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return num > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(num, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return num < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckEncodeLag()
|
|
{
|
|
video_t *video = obs_get_video();
|
|
uint32_t total_encoded = video_output_get_total_frames(video);
|
|
uint32_t total_skipped = video_output_get_skipped_frames(video);
|
|
|
|
if (total_encoded < _first_encoded || total_skipped < _first_encoded) {
|
|
_first_encoded = total_encoded;
|
|
_first_skipped = total_skipped;
|
|
}
|
|
total_encoded -= _first_encoded;
|
|
total_skipped -= _first_skipped;
|
|
|
|
auto num = total_encoded ? (long double)total_skipped /
|
|
(long double)total_encoded
|
|
: 0.0l;
|
|
num *= 100.0l;
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return num > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(num, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return num < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MacroConditionStats::OutputInfo::Update(obs_output_t *output)
|
|
{
|
|
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
|
uint64_t curTime = os_gettime_ns();
|
|
uint64_t bytesSent = totalBytes;
|
|
|
|
if (bytesSent < lastBytesSent)
|
|
bytesSent = 0;
|
|
if (bytesSent == 0)
|
|
lastBytesSent = 0;
|
|
|
|
uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8;
|
|
long double timePassed =
|
|
(long double)(curTime - lastBytesSentTime) / 1000000000.0l;
|
|
kbps = (long double)bitsBetween / timePassed / 1000.0l;
|
|
|
|
if (timePassed < 0.01l)
|
|
kbps = 0.0l;
|
|
|
|
int total = output ? obs_output_get_total_frames(output) : 0;
|
|
int dropped = output ? obs_output_get_frames_dropped(output) : 0;
|
|
|
|
if (total < first_total || dropped < first_dropped) {
|
|
first_total = 0;
|
|
first_dropped = 0;
|
|
}
|
|
|
|
total -= first_total;
|
|
dropped -= first_dropped;
|
|
|
|
dropped_relative =
|
|
total ? (long double)dropped / (long double)total * 100.0l
|
|
: 0.0l;
|
|
|
|
lastBytesSent = bytesSent;
|
|
lastBytesSentTime = curTime;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckStreamDroppedFrames()
|
|
{
|
|
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
|
|
_streamInfo.Update(output);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return _streamInfo.dropped_relative > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(_streamInfo.dropped_relative, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return _streamInfo.dropped_relative < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckStreamBitrate()
|
|
{
|
|
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
|
|
_streamInfo.Update(output);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return _streamInfo.kbps > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(_streamInfo.kbps, _value, 1.0);
|
|
case Condition::BELOW:
|
|
return _streamInfo.kbps < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckStreamMBSent() const
|
|
{
|
|
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
|
|
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
|
long double num = (long double)totalBytes / (1024.0l * 1024.0l);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return num > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(num, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return num < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckRecordingDroppedFrames()
|
|
{
|
|
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
|
|
_recordingInfo.Update(output);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return _recordingInfo.dropped_relative > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(_recordingInfo.dropped_relative, _value,
|
|
0.1);
|
|
case Condition::BELOW:
|
|
return _recordingInfo.dropped_relative < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckRecordingBitrate()
|
|
{
|
|
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
|
|
_recordingInfo.Update(output);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return _recordingInfo.kbps > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(_recordingInfo.kbps, _value, 1.0);
|
|
case Condition::BELOW:
|
|
return _recordingInfo.kbps < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckRecordingMBSent() const
|
|
{
|
|
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
|
|
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
|
long double num = (long double)totalBytes / (1024.0l * 1024.0l);
|
|
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return num > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(num, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return num < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Based on OBSBasic::GetCurrentOutputPath()
|
|
static const char *getCurrentOutputPath()
|
|
{
|
|
const char *path = nullptr;
|
|
auto config = obs_frontend_get_profile_config();
|
|
if (!config) {
|
|
return path;
|
|
}
|
|
|
|
const char *mode = config_get_string(config, "Output", "Mode");
|
|
|
|
if (strcmp(mode, "Advanced") == 0) {
|
|
const char *advanced_mode =
|
|
config_get_string(config, "AdvOut", "RecType");
|
|
|
|
if (strcmp(advanced_mode, "FFmpeg") == 0) {
|
|
path = config_get_string(config, "AdvOut",
|
|
"FFFilePath");
|
|
} else {
|
|
path = config_get_string(config, "AdvOut",
|
|
"RecFilePath");
|
|
}
|
|
} else {
|
|
path = config_get_string(config, "SimpleOutput", "FilePath");
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckDiskUsage() const
|
|
{
|
|
#define MBYTE (1024ULL * 1024ULL)
|
|
auto path = getCurrentOutputPath();
|
|
auto mb = os_get_free_disk_space(path) / MBYTE;
|
|
switch (_condition) {
|
|
case Condition::ABOVE:
|
|
return mb > _value;
|
|
case Condition::EQUALS:
|
|
return DoubleEquals(mb, _value, 0.1);
|
|
case Condition::BELOW:
|
|
return mb < _value;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::CheckCondition()
|
|
{
|
|
switch (_type) {
|
|
case Type::FPS:
|
|
return CheckFPS();
|
|
case Type::CPU_USAGE:
|
|
return CheckCPU();
|
|
case Type::DISK_USAGE:
|
|
return CheckDiskUsage();
|
|
case Type::MEM_USAGE:
|
|
return CheckMemory();
|
|
case Type::AVG_FRAMETIME:
|
|
return CheckAvgFrametime();
|
|
case Type::RENDER_LAG:
|
|
return CheckRenderLag();
|
|
case Type::ENCODE_LAG:
|
|
return CheckEncodeLag();
|
|
case Type::STREAM_DROPPED_FRAMES:
|
|
return CheckStreamDroppedFrames();
|
|
case Type::STREAM_BITRATE:
|
|
return CheckStreamBitrate();
|
|
case Type::STREAM_MB_SENT:
|
|
return CheckStreamMBSent();
|
|
case Type::RECORDING_DROPPED_FRAMES:
|
|
return CheckRecordingDroppedFrames();
|
|
case Type::RECORDING_BITRATE:
|
|
return CheckRecordingBitrate();
|
|
case Type::RECORDING_MB_SENT:
|
|
return CheckRecordingMBSent();
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MacroConditionStats::Save(obs_data_t *obj) const
|
|
{
|
|
MacroCondition::Save(obj);
|
|
_value.Save(obj, "value");
|
|
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
|
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
|
obs_data_set_int(obj, "version", 1);
|
|
return true;
|
|
}
|
|
|
|
bool MacroConditionStats::Load(obs_data_t *obj)
|
|
{
|
|
|
|
MacroCondition::Load(obj);
|
|
if (!obs_data_has_user_value(obj, "version")) {
|
|
_value = obs_data_get_double(obj, "value");
|
|
} else {
|
|
_value.Load(obj, "value");
|
|
}
|
|
_type = static_cast<MacroConditionStats::Type>(
|
|
obs_data_get_int(obj, "type"));
|
|
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
|
|
return true;
|
|
}
|
|
|
|
std::string MacroConditionStats::GetShortDesc() const
|
|
{
|
|
auto it = statsTypes.find(_type);
|
|
if (it != statsTypes.end()) {
|
|
return obs_module_text(it->second.c_str());
|
|
}
|
|
return "";
|
|
}
|
|
|
|
template<class T>
|
|
static inline void populateList(QComboBox *list,
|
|
const std::map<T, std::string> &map)
|
|
{
|
|
list->clear();
|
|
for (const auto &[_, name] : map) {
|
|
list->addItem(obs_module_text(name.c_str()));
|
|
}
|
|
}
|
|
|
|
MacroConditionStatsEdit::MacroConditionStatsEdit(
|
|
QWidget *parent, std::shared_ptr<MacroConditionStats> entryData)
|
|
: QWidget(parent),
|
|
_stats(new QComboBox()),
|
|
_condition(new QComboBox()),
|
|
_value(new VariableDoubleSpinBox())
|
|
{
|
|
_value->setMaximum(999999999999);
|
|
|
|
populateList(_stats, statsTypes);
|
|
populateList(_condition, statsConditionTypes);
|
|
|
|
setToolTip(
|
|
obs_module_text("AdvSceneSwitcher.condition.stats.dockHint"));
|
|
|
|
QWidget::connect(
|
|
_value,
|
|
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
|
|
this, SLOT(ValueChanged(const NumberVariable<double> &)));
|
|
QWidget::connect(_stats, SIGNAL(currentIndexChanged(int)), this,
|
|
SLOT(StatsTypeChanged(int)));
|
|
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
|
SLOT(ConditionChanged(int)));
|
|
|
|
auto layout = new QHBoxLayout;
|
|
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.stats.entry"),
|
|
layout,
|
|
{{"{{value}}", _value},
|
|
{"{{stats}}", _stats},
|
|
{"{{condition}}", _condition}});
|
|
setLayout(layout);
|
|
|
|
_entryData = entryData;
|
|
UpdateEntryData();
|
|
_loading = false;
|
|
}
|
|
|
|
void MacroConditionStatsEdit::ValueChanged(const NumberVariable<double> &value)
|
|
{
|
|
GUARD_LOADING_AND_LOCK();
|
|
_entryData->_value = value;
|
|
}
|
|
|
|
void MacroConditionStatsEdit::StatsTypeChanged(int type)
|
|
{
|
|
{
|
|
GUARD_LOADING_AND_LOCK();
|
|
_entryData->_type =
|
|
static_cast<MacroConditionStats::Type>(type);
|
|
}
|
|
SetWidgetVisibility();
|
|
_value->SetFixedValue(0);
|
|
emit HeaderInfoChanged(
|
|
QString::fromStdString(_entryData->GetShortDesc()));
|
|
}
|
|
|
|
void MacroConditionStatsEdit::ConditionChanged(int cond)
|
|
{
|
|
GUARD_LOADING_AND_LOCK();
|
|
_entryData->_condition =
|
|
static_cast<MacroConditionStats::Condition>(cond);
|
|
}
|
|
|
|
void MacroConditionStatsEdit::UpdateEntryData()
|
|
{
|
|
if (!_entryData) {
|
|
return;
|
|
}
|
|
|
|
_value->SetValue(_entryData->_value);
|
|
_stats->setCurrentIndex(static_cast<int>(_entryData->_type));
|
|
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
|
SetWidgetVisibility();
|
|
}
|
|
|
|
void MacroConditionStatsEdit::SetWidgetVisibility()
|
|
{
|
|
if (!_entryData) {
|
|
return;
|
|
}
|
|
|
|
switch (_entryData->_type) {
|
|
case MacroConditionStats::Type::FPS:
|
|
_value->setMaximum(1000);
|
|
_value->setSuffix("");
|
|
break;
|
|
case MacroConditionStats::Type::CPU_USAGE:
|
|
_value->setMaximum(100);
|
|
_value->setSuffix("%");
|
|
break;
|
|
case MacroConditionStats::Type::DISK_USAGE:
|
|
_value->setMaximum(999999999999);
|
|
_value->setSuffix("MB");
|
|
break;
|
|
case MacroConditionStats::Type::MEM_USAGE:
|
|
_value->setMaximum(999999999999);
|
|
_value->setSuffix("MB");
|
|
break;
|
|
case MacroConditionStats::Type::AVG_FRAMETIME:
|
|
_value->setMaximum(999999999999);
|
|
_value->setSuffix("ms");
|
|
break;
|
|
case MacroConditionStats::Type::RENDER_LAG:
|
|
_value->setMaximum(100);
|
|
_value->setSuffix("%");
|
|
break;
|
|
case MacroConditionStats::Type::ENCODE_LAG:
|
|
_value->setMaximum(100);
|
|
_value->setSuffix("%");
|
|
break;
|
|
case MacroConditionStats::Type::STREAM_DROPPED_FRAMES:
|
|
_value->setMaximum(100);
|
|
_value->setSuffix("%");
|
|
break;
|
|
case MacroConditionStats::Type::STREAM_BITRATE:
|
|
_value->setMaximum(999999999999);
|
|
_value->setSuffix("kb/s");
|
|
break;
|
|
case MacroConditionStats::Type::STREAM_MB_SENT:
|
|
_value->setMaximum(999999999999);
|
|
_value->setSuffix("MB");
|
|
break;
|
|
case MacroConditionStats::Type::RECORDING_DROPPED_FRAMES:
|
|
_value->setMaximum(100);
|
|
_value->setSuffix("%");
|
|
break;
|
|
case MacroConditionStats::Type::RECORDING_BITRATE:
|
|
_value->setMaximum(999999999999);
|
|
_value->setSuffix("kb/s");
|
|
break;
|
|
case MacroConditionStats::Type::RECORDING_MB_SENT:
|
|
_value->setMaximum(999999999999);
|
|
_value->setSuffix("MB");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
adjustSize();
|
|
}
|
|
|
|
} // namespace advss
|