#include "macro-condition-obs-stats.hpp" #include "layout-helpers.hpp" #include "math-helpers.hpp" #include #include #include 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 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 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(_type)); obs_data_set_int(obj, "condition", static_cast(_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( obs_data_get_int(obj, "type")); _condition = static_cast(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 static inline void populateList(QComboBox *list, const std::map &map) { list->clear(); for (const auto &[_, name] : map) { list->addItem(obs_module_text(name.c_str())); } } MacroConditionStatsEdit::MacroConditionStatsEdit( QWidget *parent, std::shared_ptr 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 &)), this, SLOT(ValueChanged(const NumberVariable &))); 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 &value) { GUARD_LOADING_AND_LOCK(); _entryData->_value = value; } void MacroConditionStatsEdit::StatsTypeChanged(int type) { { GUARD_LOADING_AND_LOCK(); _entryData->_type = static_cast(type); } SetWidgetVisibility(); _value->SetFixedValue(0); emit HeaderInfoChanged( QString::fromStdString(_entryData->GetShortDesc())); } void MacroConditionStatsEdit::ConditionChanged(int cond) { GUARD_LOADING_AND_LOCK(); _entryData->_condition = static_cast(cond); } void MacroConditionStatsEdit::UpdateEntryData() { if (!_entryData) { return; } _value->SetValue(_entryData->_value); _stats->setCurrentIndex(static_cast(_entryData->_type)); _condition->setCurrentIndex(static_cast(_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