| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928 | 
							- #include "multitrack-video-output.hpp"
 
- #include <util/dstr.hpp>
 
- #include <util/platform.h>
 
- #include <util/profiler.hpp>
 
- #include <util/util.hpp>
 
- #include <obs-frontend-api.h>
 
- #include <obs-app.hpp>
 
- #include <obs.hpp>
 
- #include <remote-text.hpp>
 
- #include <algorithm>
 
- #include <cinttypes>
 
- #include <cmath>
 
- #include <numeric>
 
- #include <optional>
 
- #include <string>
 
- #include <vector>
 
- #include <QAbstractButton>
 
- #include <QMessageBox>
 
- #include <QObject>
 
- #include <QPushButton>
 
- #include <QScopeGuard>
 
- #include <QString>
 
- #include <QThreadPool>
 
- #include <QUrl>
 
- #include <QUrlQuery>
 
- #include <QUuid>
 
- #include <nlohmann/json.hpp>
 
- #include "system-info.hpp"
 
- #include "goliveapi-postdata.hpp"
 
- #include "goliveapi-network.hpp"
 
- #include "multitrack-video-error.hpp"
 
- #include "qt-helpers.hpp"
 
- #include "models/multitrack-video.hpp"
 
- Qt::ConnectionType BlockingConnectionTypeFor(QObject *object)
 
- {
 
- 	return object->thread() == QThread::currentThread()
 
- 		       ? Qt::DirectConnection
 
- 		       : Qt::BlockingQueuedConnection;
 
- }
 
- bool MultitrackVideoDeveloperModeEnabled()
 
- {
 
- 	static bool developer_mode = [] {
 
- 		auto args = qApp->arguments();
 
- 		for (const auto &arg : args) {
 
- 			if (arg == "--enable-multitrack-video-dev") {
 
- 				return true;
 
- 			}
 
- 		}
 
- 		return false;
 
- 	}();
 
- 	return developer_mode;
 
- }
 
- static OBSServiceAutoRelease
 
- create_service(const GoLiveApi::Config &go_live_config,
 
- 	       const std::optional<std::string> &rtmp_url,
 
- 	       const QString &in_stream_key)
 
- {
 
- 	const char *url = nullptr;
 
- 	QString stream_key = in_stream_key;
 
- 	const auto &ingest_endpoints = go_live_config.ingest_endpoints;
 
- 	for (auto &endpoint : ingest_endpoints) {
 
- 		if (qstrnicmp("RTMP", endpoint.protocol.c_str(), 4))
 
- 			continue;
 
- 		url = endpoint.url_template.c_str();
 
- 		if (endpoint.authentication &&
 
- 		    !endpoint.authentication->empty()) {
 
- 			blog(LOG_INFO,
 
- 			     "Using stream key supplied by autoconfig");
 
- 			stream_key = QString::fromStdString(
 
- 				*endpoint.authentication);
 
- 		}
 
- 		break;
 
- 	}
 
- 	if (rtmp_url.has_value()) {
 
- 		// Despite being set by user, it was set to a ""
 
- 		if (rtmp_url->empty()) {
 
- 			throw MultitrackVideoError::warning(QTStr(
 
- 				"FailedToStartStream.NoCustomRTMPURLInSettings"));
 
- 		}
 
- 		url = rtmp_url->c_str();
 
- 		blog(LOG_INFO, "Using custom RTMP URL: '%s'", url);
 
- 	} else {
 
- 		if (!url) {
 
- 			blog(LOG_ERROR, "No RTMP URL in go live config");
 
- 			throw MultitrackVideoError::warning(
 
- 				QTStr("FailedToStartStream.NoRTMPURLInConfig"));
 
- 		}
 
- 		blog(LOG_INFO, "Using URL template: '%s'", url);
 
- 	}
 
- 	DStr str;
 
- 	dstr_cat(str, url);
 
- 	// dstr_find does not protect against null, and dstr_cat will
 
- 	// not initialize str if cat'ing with a null url
 
- 	if (!dstr_is_empty(str)) {
 
- 		auto found = dstr_find(str, "/{stream_key}");
 
- 		if (found)
 
- 			dstr_remove(str, found - str->array,
 
- 				    str->len - (found - str->array));
 
- 	}
 
- 	QUrl parsed_url{url};
 
- 	QUrlQuery parsed_query{parsed_url};
 
- 	if (!go_live_config.meta.config_id.empty()) {
 
- 		parsed_query.addQueryItem(
 
- 			"obsConfigId",
 
- 			QString::fromStdString(go_live_config.meta.config_id));
 
- 	}
 
- 	auto key_with_param = stream_key;
 
- 	if (!parsed_query.isEmpty())
 
- 		key_with_param += "?" + parsed_query.toString();
 
- 	OBSDataAutoRelease settings = obs_data_create();
 
- 	obs_data_set_string(settings, "server", str->array);
 
- 	obs_data_set_string(settings, "key",
 
- 			    key_with_param.toUtf8().constData());
 
- 	auto service = obs_service_create(
 
- 		"rtmp_custom", "multitrack video service", settings, nullptr);
 
- 	if (!service) {
 
- 		blog(LOG_WARNING, "Failed to create multitrack video service");
 
- 		throw MultitrackVideoError::warning(QTStr(
 
- 			"FailedToStartStream.FailedToCreateMultitrackVideoService"));
 
- 	}
 
- 	return service;
 
- }
 
- static OBSOutputAutoRelease create_output()
 
- {
 
- 	OBSOutputAutoRelease output = obs_output_create(
 
- 		"rtmp_output", "rtmp multitrack video", nullptr, nullptr);
 
- 	if (!output) {
 
- 		blog(LOG_ERROR,
 
- 		     "Failed to create multitrack video rtmp output");
 
- 		throw MultitrackVideoError::warning(QTStr(
 
- 			"FailedToStartStream.FailedToCreateMultitrackVideoOutput"));
 
- 	}
 
- 	return output;
 
- }
 
- static OBSOutputAutoRelease create_recording_output(obs_data_t *settings)
 
- {
 
- 	OBSOutputAutoRelease output;
 
- 	bool useMP4 = obs_data_get_bool(settings, "use_mp4");
 
- 	if (useMP4) {
 
- 		output = obs_output_create("mp4_output", "mp4 multitrack video",
 
- 					   settings, nullptr);
 
- 	} else {
 
- 		output = obs_output_create("flv_output", "flv multitrack video",
 
- 					   settings, nullptr);
 
- 	}
 
- 	if (!output) {
 
- 		blog(LOG_ERROR, "Failed to create multitrack video %s output",
 
- 		     useMP4 ? "mp4" : "flv");
 
- 	}
 
- 	return output;
 
- }
 
- static void adjust_video_encoder_scaling(
 
- 	const obs_video_info &ovi, obs_encoder_t *video_encoder,
 
- 	const GoLiveApi::VideoEncoderConfiguration &encoder_config,
 
- 	size_t encoder_index)
 
- {
 
- 	auto requested_width = encoder_config.width;
 
- 	auto requested_height = encoder_config.height;
 
- 	if (ovi.output_width == requested_width ||
 
- 	    ovi.output_height == requested_height)
 
- 		return;
 
- 	if (ovi.base_width < requested_width ||
 
- 	    ovi.base_height < requested_height) {
 
- 		blog(LOG_WARNING,
 
- 		     "Requested resolution exceeds canvas/available resolution for encoder %zu: %" PRIu32
 
- 		     "x%" PRIu32 " > %" PRIu32 "x%" PRIu32,
 
- 		     encoder_index, requested_width, requested_height,
 
- 		     ovi.base_width, ovi.base_height);
 
- 	}
 
- 	obs_encoder_set_scaled_size(video_encoder, requested_width,
 
- 				    requested_height);
 
- 	obs_encoder_set_gpu_scale_type(
 
- 		video_encoder,
 
- 		encoder_config.gpu_scale_type.value_or(OBS_SCALE_BICUBIC));
 
- }
 
- static uint32_t closest_divisor(const obs_video_info &ovi,
 
- 				const media_frames_per_second &target_fps)
 
- {
 
- 	auto target = (uint64_t)target_fps.numerator * ovi.fps_den;
 
- 	auto source = (uint64_t)ovi.fps_num * target_fps.denominator;
 
- 	return std::max(1u, static_cast<uint32_t>(source / target));
 
- }
 
- static void adjust_encoder_frame_rate_divisor(
 
- 	const obs_video_info &ovi, obs_encoder_t *video_encoder,
 
- 	const GoLiveApi::VideoEncoderConfiguration &encoder_config,
 
- 	const size_t encoder_index)
 
- {
 
- 	if (!encoder_config.framerate) {
 
- 		blog(LOG_WARNING, "`framerate` not specified for encoder %zu",
 
- 		     encoder_index);
 
- 		return;
 
- 	}
 
- 	media_frames_per_second requested_fps = *encoder_config.framerate;
 
- 	if (ovi.fps_num == requested_fps.numerator &&
 
- 	    ovi.fps_den == requested_fps.denominator)
 
- 		return;
 
- 	auto divisor = closest_divisor(ovi, requested_fps);
 
- 	if (divisor <= 1)
 
- 		return;
 
- 	blog(LOG_INFO, "Setting frame rate divisor to %u for encoder %zu",
 
- 	     divisor, encoder_index);
 
- 	obs_encoder_set_frame_rate_divisor(video_encoder, divisor);
 
- }
 
- static const std::vector<const char *> &get_available_encoders()
 
- {
 
- 	// encoders are currently only registered during startup, so keeping
 
- 	// a static vector around shouldn't be a problem
 
- 	static std::vector<const char *> available_encoders = [] {
 
- 		std::vector<const char *> available_encoders;
 
- 		for (size_t i = 0;; i++) {
 
- 			const char *id = nullptr;
 
- 			if (!obs_enum_encoder_types(i, &id))
 
- 				break;
 
- 			available_encoders.push_back(id);
 
- 		}
 
- 		return available_encoders;
 
- 	}();
 
- 	return available_encoders;
 
- }
 
- static bool encoder_available(const char *type)
 
- {
 
- 	auto &encoders = get_available_encoders();
 
- 	return std::find_if(std::begin(encoders), std::end(encoders),
 
- 			    [=](const char *encoder) {
 
- 				    return strcmp(type, encoder) == 0;
 
- 			    }) != std::end(encoders);
 
- }
 
- static OBSEncoderAutoRelease create_video_encoder(
 
- 	DStr &name_buffer, size_t encoder_index,
 
- 	const GoLiveApi::EncoderConfiguration<
 
- 		GoLiveApi::VideoEncoderConfiguration> &encoder_config)
 
- {
 
- 	auto encoder_type = encoder_config.config.type.c_str();
 
- 	if (!encoder_available(encoder_type)) {
 
- 		blog(LOG_ERROR, "Encoder type '%s' not available",
 
- 		     encoder_type);
 
- 		throw MultitrackVideoError::warning(
 
- 			QTStr("FailedToStartStream.EncoderNotAvailable")
 
- 				.arg(encoder_type));
 
- 	}
 
- 	dstr_printf(name_buffer, "multitrack video video encoder %zu",
 
- 		    encoder_index);
 
- 	OBSDataAutoRelease encoder_settings =
 
- 		obs_data_create_from_json(encoder_config.data.dump().c_str());
 
- 	obs_data_set_bool(encoder_settings, "disable_scenecut", true);
 
- 	OBSEncoderAutoRelease video_encoder = obs_video_encoder_create(
 
- 		encoder_type, name_buffer, encoder_settings, nullptr);
 
- 	if (!video_encoder) {
 
- 		blog(LOG_ERROR, "Failed to create video encoder '%s'",
 
- 		     name_buffer->array);
 
- 		throw MultitrackVideoError::warning(
 
- 			QTStr("FailedToStartStream.FailedToCreateVideoEncoder")
 
- 				.arg(name_buffer->array, encoder_type));
 
- 	}
 
- 	obs_encoder_set_video(video_encoder, obs_get_video());
 
- 	obs_video_info ovi;
 
- 	if (!obs_get_video_info(&ovi)) {
 
- 		blog(LOG_WARNING,
 
- 		     "Failed to get obs_video_info while creating encoder %zu",
 
- 		     encoder_index);
 
- 		throw MultitrackVideoError::warning(
 
- 			QTStr("FailedToStartStream.FailedToGetOBSVideoInfo")
 
- 				.arg(name_buffer->array, encoder_type));
 
- 	}
 
- 	adjust_video_encoder_scaling(ovi, video_encoder, encoder_config.config,
 
- 				     encoder_index);
 
- 	adjust_encoder_frame_rate_divisor(ovi, video_encoder,
 
- 					  encoder_config.config, encoder_index);
 
- 	return video_encoder;
 
- }
 
- static OBSEncoderAutoRelease create_audio_encoder(const char *name,
 
- 						  const char *audio_encoder_id,
 
- 						  uint32_t audio_bitrate,
 
- 						  size_t mixer_idx)
 
- {
 
- 	OBSDataAutoRelease settings = obs_data_create();
 
- 	obs_data_set_int(settings, "bitrate", audio_bitrate);
 
- 	OBSEncoderAutoRelease audio_encoder = obs_audio_encoder_create(
 
- 		audio_encoder_id, name, settings, mixer_idx, nullptr);
 
- 	if (!audio_encoder) {
 
- 		blog(LOG_ERROR, "Failed to create audio encoder");
 
- 		throw MultitrackVideoError::warning(QTStr(
 
- 			"FailedToStartStream.FailedToCreateAudioEncoder"));
 
- 	}
 
- 	obs_encoder_set_audio(audio_encoder, obs_get_audio());
 
- 	return audio_encoder;
 
- }
 
- struct OBSOutputs {
 
- 	OBSOutputAutoRelease output, recording_output;
 
- };
 
- static OBSOutputs
 
- SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
 
- 	       const GoLiveApi::Config &go_live_config,
 
- 	       std::vector<OBSEncoderAutoRelease> &audio_encoders,
 
- 	       std::vector<OBSEncoderAutoRelease> &video_encoders,
 
- 	       const char *audio_encoder_id,
 
- 	       std::optional<size_t> vod_track_mixer);
 
- static void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self,
 
- 				obs_output_t *output, OBSSignal &start,
 
- 				OBSSignal &stop, OBSSignal &deactivate);
 
- void MultitrackVideoOutput::PrepareStreaming(
 
- 	QWidget *parent, const char *service_name, obs_service_t *service,
 
- 	const std::optional<std::string> &rtmp_url, const QString &stream_key,
 
- 	const char *audio_encoder_id,
 
- 	std::optional<uint32_t> maximum_aggregate_bitrate,
 
- 	std::optional<uint32_t> maximum_video_tracks,
 
- 	std::optional<std::string> custom_config,
 
- 	obs_data_t *dump_stream_to_file_config,
 
- 	std::optional<size_t> vod_track_mixer)
 
- {
 
- 	{
 
- 		const std::lock_guard<std::mutex> current_lock{current_mutex};
 
- 		const std::lock_guard<std::mutex> current_stream_dump_lock{
 
- 			current_stream_dump_mutex};
 
- 		if (current || current_stream_dump) {
 
- 			blog(LOG_WARNING,
 
- 			     "Tried to prepare multitrack video output while it's already active");
 
- 			return;
 
- 		}
 
- 	}
 
- 	std::optional<GoLiveApi::Config> go_live_config;
 
- 	std::optional<GoLiveApi::Config> custom;
 
- 	bool is_custom_config = custom_config.has_value();
 
- 	auto auto_config_url = MultitrackVideoAutoConfigURL(service);
 
- 	OBSDataAutoRelease service_settings = obs_service_get_settings(service);
 
- 	auto multitrack_video_name =
 
- 		QTStr("Basic.Settings.Stream.MultitrackVideoLabel");
 
- 	if (obs_data_has_user_value(service_settings,
 
- 				    "multitrack_video_name")) {
 
- 		multitrack_video_name = obs_data_get_string(
 
- 			service_settings, "multitrack_video_name");
 
- 	}
 
- 	auto auto_config_url_data = auto_config_url.toUtf8();
 
- 	DStr vod_track_info_storage;
 
- 	if (vod_track_mixer.has_value())
 
- 		dstr_printf(vod_track_info_storage, "Yes (mixer: %zu)",
 
- 			    vod_track_mixer.value());
 
- 	blog(LOG_INFO,
 
- 	     "Preparing enhanced broadcasting stream for:\n"
 
- 	     "    custom config:  %s\n"
 
- 	     "    config url:     %s\n"
 
- 	     "  settings:\n"
 
- 	     "    service:               %s\n"
 
- 	     "    max aggregate bitrate: %s (%" PRIu32 ")\n"
 
- 	     "    max video tracks:      %s (%" PRIu32 ")\n"
 
- 	     "    custom rtmp url:       %s ('%s')\n"
 
- 	     "    vod track:             %s",
 
- 	     is_custom_config ? "Yes" : "No",
 
- 	     !auto_config_url.isEmpty() ? auto_config_url_data.constData()
 
- 					: "(null)",
 
- 	     service_name,
 
- 	     maximum_aggregate_bitrate.has_value() ? "Set" : "Auto",
 
- 	     maximum_aggregate_bitrate.value_or(0),
 
- 	     maximum_video_tracks.has_value() ? "Set" : "Auto",
 
- 	     maximum_video_tracks.value_or(0),
 
- 	     rtmp_url.has_value() ? "Yes" : "No",
 
- 	     rtmp_url.has_value() ? rtmp_url->c_str() : "",
 
- 	     vod_track_info_storage->array ? vod_track_info_storage->array
 
- 					   : "No");
 
- 	const bool custom_config_only =
 
- 		auto_config_url.isEmpty() &&
 
- 		MultitrackVideoDeveloperModeEnabled() &&
 
- 		custom_config.has_value() &&
 
- 		strcmp(obs_service_get_id(service), "rtmp_custom") == 0;
 
- 	if (!custom_config_only) {
 
- 		auto go_live_post = constructGoLivePost(
 
- 			stream_key, maximum_aggregate_bitrate,
 
- 			maximum_video_tracks, vod_track_mixer.has_value());
 
- 		go_live_config = DownloadGoLiveConfig(parent, auto_config_url,
 
- 						      go_live_post,
 
- 						      multitrack_video_name);
 
- 	}
 
- 	if (custom_config.has_value()) {
 
- 		GoLiveApi::Config parsed_custom;
 
- 		try {
 
- 			parsed_custom = nlohmann::json::parse(*custom_config);
 
- 		} catch (const nlohmann::json::exception &exception) {
 
- 			blog(LOG_WARNING, "Failed to parse custom config: %s",
 
- 			     exception.what());
 
- 			throw MultitrackVideoError::critical(QTStr(
 
- 				"FailedToStartStream.InvalidCustomConfig"));
 
- 		}
 
- 		// copy unique ID from go live request
 
- 		if (go_live_config.has_value()) {
 
- 			parsed_custom.meta.config_id =
 
- 				go_live_config->meta.config_id;
 
- 			blog(LOG_INFO,
 
- 			     "Using config_id from go live config with custom config: %s",
 
- 			     parsed_custom.meta.config_id.c_str());
 
- 		}
 
- 		nlohmann::json custom_data = parsed_custom;
 
- 		blog(LOG_INFO, "Using custom go live config: %s",
 
- 		     custom_data.dump(4).c_str());
 
- 		custom.emplace(std::move(parsed_custom));
 
- 	}
 
- 	if (go_live_config.has_value()) {
 
- 		blog(LOG_INFO, "Enhanced broadcasting config_id: '%s'",
 
- 		     go_live_config->meta.config_id.c_str());
 
- 	}
 
- 	if (!go_live_config && !custom) {
 
- 		blog(LOG_ERROR,
 
- 		     "MultitrackVideoOutput: no config set, this should never happen");
 
- 		throw MultitrackVideoError::warning(
 
- 			QTStr("FailedToStartStream.NoConfig"));
 
- 	}
 
- 	const auto &output_config = custom ? *custom : *go_live_config;
 
- 	const auto &service_config = go_live_config ? *go_live_config : *custom;
 
- 	auto audio_encoders = std::vector<OBSEncoderAutoRelease>();
 
- 	auto video_encoders = std::vector<OBSEncoderAutoRelease>();
 
- 	auto outputs = SetupOBSOutput(dump_stream_to_file_config, output_config,
 
- 				      audio_encoders, video_encoders,
 
- 				      audio_encoder_id, vod_track_mixer);
 
- 	auto output = std::move(outputs.output);
 
- 	auto recording_output = std::move(outputs.recording_output);
 
- 	if (!output)
 
- 		throw MultitrackVideoError::warning(
 
- 			QTStr("FailedToStartStream.FallbackToDefault")
 
- 				.arg(multitrack_video_name));
 
- 	auto multitrack_video_service =
 
- 		create_service(service_config, rtmp_url, stream_key);
 
- 	if (!multitrack_video_service)
 
- 		throw MultitrackVideoError::warning(
 
- 			QTStr("FailedToStartStream.FallbackToDefault")
 
- 				.arg(multitrack_video_name));
 
- 	obs_output_set_service(output, multitrack_video_service);
 
- 	OBSSignal start_streaming;
 
- 	OBSSignal stop_streaming;
 
- 	OBSSignal deactivate_stream;
 
- 	SetupSignalHandlers(false, this, output, start_streaming,
 
- 			    stop_streaming, deactivate_stream);
 
- 	if (dump_stream_to_file_config && recording_output) {
 
- 		OBSSignal start_recording;
 
- 		OBSSignal stop_recording;
 
- 		OBSSignal deactivate_recording;
 
- 		SetupSignalHandlers(true, this, recording_output,
 
- 				    start_recording, stop_recording,
 
- 				    deactivate_recording);
 
- 		decltype(video_encoders) recording_video_encoders;
 
- 		recording_video_encoders.reserve(video_encoders.size());
 
- 		for (auto &encoder : video_encoders) {
 
- 			recording_video_encoders.emplace_back(
 
- 				obs_encoder_get_ref(encoder));
 
- 		}
 
- 		decltype(audio_encoders) recording_audio_encoders;
 
- 		recording_audio_encoders.reserve(audio_encoders.size());
 
- 		for (auto &encoder : audio_encoders) {
 
- 			recording_audio_encoders.emplace_back(
 
- 				obs_encoder_get_ref(encoder));
 
- 		}
 
- 		{
 
- 			const std::lock_guard current_stream_dump_lock{
 
- 				current_stream_dump_mutex};
 
- 			current_stream_dump.emplace(OBSOutputObjects{
 
- 				std::move(recording_output),
 
- 				std::move(recording_video_encoders),
 
- 				std::move(recording_audio_encoders),
 
- 				nullptr,
 
- 				std::move(start_recording),
 
- 				std::move(stop_recording),
 
- 				std::move(deactivate_recording),
 
- 			});
 
- 		}
 
- 	}
 
- 	const std::lock_guard current_lock{current_mutex};
 
- 	current.emplace(OBSOutputObjects{
 
- 		std::move(output),
 
- 		std::move(video_encoders),
 
- 		std::move(audio_encoders),
 
- 		std::move(multitrack_video_service),
 
- 		std::move(start_streaming),
 
- 		std::move(stop_streaming),
 
- 		std::move(deactivate_stream),
 
- 	});
 
- }
 
- signal_handler_t *MultitrackVideoOutput::StreamingSignalHandler()
 
- {
 
- 	const std::lock_guard current_lock{current_mutex};
 
- 	return current.has_value()
 
- 		       ? obs_output_get_signal_handler(current->output_)
 
- 		       : nullptr;
 
- }
 
- void MultitrackVideoOutput::StartedStreaming()
 
- {
 
- 	OBSOutputAutoRelease dump_output;
 
- 	{
 
- 		const std::lock_guard current_stream_dump_lock{
 
- 			current_stream_dump_mutex};
 
- 		if (current_stream_dump && current_stream_dump->output_) {
 
- 			dump_output = obs_output_get_ref(
 
- 				current_stream_dump->output_);
 
- 		}
 
- 	}
 
- 	if (!dump_output)
 
- 		return;
 
- 	auto result = obs_output_start(dump_output);
 
- 	blog(LOG_INFO, "MultitrackVideoOutput: starting recording%s",
 
- 	     result ? "" : " failed");
 
- }
 
- void MultitrackVideoOutput::StopStreaming()
 
- {
 
- 	OBSOutputAutoRelease current_output;
 
- 	{
 
- 		const std::lock_guard current_lock{current_mutex};
 
- 		if (current && current->output_)
 
- 			current_output = obs_output_get_ref(current->output_);
 
- 	}
 
- 	if (current_output)
 
- 		obs_output_stop(current_output);
 
- 	OBSOutputAutoRelease dump_output;
 
- 	{
 
- 		const std::lock_guard current_stream_dump_lock{
 
- 			current_stream_dump_mutex};
 
- 		if (current_stream_dump && current_stream_dump->output_)
 
- 			dump_output = obs_output_get_ref(
 
- 				current_stream_dump->output_);
 
- 	}
 
- 	if (dump_output)
 
- 		obs_output_stop(dump_output);
 
- }
 
- bool MultitrackVideoOutput::HandleIncompatibleSettings(
 
- 	QWidget *parent, config_t *config, obs_service_t *service,
 
- 	bool &useDelay, bool &enableNewSocketLoop, bool &enableDynBitrate)
 
- {
 
- 	QString incompatible_settings;
 
- 	QString where_to_disable;
 
- 	QString incompatible_settings_list;
 
- 	size_t num = 1;
 
- 	auto check_setting = [&](bool setting, const char *name,
 
- 				 const char *section) {
 
- 		if (!setting)
 
- 			return;
 
- 		incompatible_settings +=
 
- 			QString(" %1. %2\n").arg(num).arg(QTStr(name));
 
- 		where_to_disable +=
 
- 			QString(" %1. [%2 → %3 → %4]\n")
 
- 				.arg(num)
 
- 				.arg(QTStr("Settings"))
 
- 				.arg(QTStr("Basic.Settings.Advanced"))
 
- 				.arg(QTStr(section));
 
- 		incompatible_settings_list += QString("%1, ").arg(name);
 
- 		num += 1;
 
- 	};
 
- 	check_setting(useDelay, "Basic.Settings.Advanced.StreamDelay",
 
- 		      "Basic.Settings.Advanced.StreamDelay");
 
- #ifdef _WIN32
 
- 	check_setting(enableNewSocketLoop,
 
- 		      "Basic.Settings.Advanced.Network.EnableNewSocketLoop",
 
- 		      "Basic.Settings.Advanced.Network");
 
- #endif
 
- 	check_setting(enableDynBitrate,
 
- 		      "Basic.Settings.Output.DynamicBitrate.Beta",
 
- 		      "Basic.Settings.Advanced.Network");
 
- 	if (incompatible_settings.isEmpty())
 
- 		return true;
 
- 	OBSDataAutoRelease service_settings = obs_service_get_settings(service);
 
- 	QMessageBox mb(parent);
 
- 	mb.setIcon(QMessageBox::Critical);
 
- 	mb.setWindowTitle(QTStr("MultitrackVideo.IncompatibleSettings.Title"));
 
- 	mb.setText(QString(QTStr("MultitrackVideo.IncompatibleSettings.Text"))
 
- 			   .arg(obs_data_get_string(service_settings,
 
- 						    "multitrack_video_name"))
 
- 			   .arg(incompatible_settings)
 
- 			   .arg(where_to_disable));
 
- 	auto this_stream = mb.addButton(
 
- 		QTStr("MultitrackVideo.IncompatibleSettings.DisableAndStartStreaming"),
 
- 		QMessageBox::AcceptRole);
 
- 	auto all_streams = mb.addButton(
 
- 		QString(QTStr(
 
- 			"MultitrackVideo.IncompatibleSettings.UpdateAndStartStreaming")),
 
- 		QMessageBox::AcceptRole);
 
- 	mb.setStandardButtons(QMessageBox::StandardButton::Cancel);
 
- 	mb.exec();
 
- 	const char *action = "cancel";
 
- 	if (mb.clickedButton() == this_stream) {
 
- 		action = "DisableAndStartStreaming";
 
- 	} else if (mb.clickedButton() == all_streams) {
 
- 		action = "UpdateAndStartStreaming";
 
- 	}
 
- 	blog(LOG_INFO,
 
- 	     "MultitrackVideoOutput: attempted to start stream with incompatible"
 
- 	     "settings (%s); action taken: %s",
 
- 	     incompatible_settings_list.toUtf8().constData(), action);
 
- 	if (mb.clickedButton() == this_stream ||
 
- 	    mb.clickedButton() == all_streams) {
 
- 		useDelay = false;
 
- 		enableNewSocketLoop = false;
 
- 		enableDynBitrate = false;
 
- 		if (mb.clickedButton() == all_streams) {
 
- 			config_set_bool(config, "Output", "DelayEnable", false);
 
- #ifdef _WIN32
 
- 			config_set_bool(config, "Output", "NewSocketLoopEnable",
 
- 					false);
 
- #endif
 
- 			config_set_bool(config, "Output", "DynamicBitrate",
 
- 					false);
 
- 		}
 
- 		return true;
 
- 	}
 
- 	return false;
 
- }
 
- static bool
 
- create_video_encoders(const GoLiveApi::Config &go_live_config,
 
- 		      std::vector<OBSEncoderAutoRelease> &video_encoders,
 
- 		      obs_output_t *output, obs_output_t *recording_output)
 
- {
 
- 	DStr video_encoder_name_buffer;
 
- 	obs_encoder_t *first_encoder = nullptr;
 
- 	if (go_live_config.encoder_configurations.empty()) {
 
- 		blog(LOG_WARNING,
 
- 		     "MultitrackVideoOutput: Missing video encoder configurations");
 
- 		throw MultitrackVideoError::warning(
 
- 			QTStr("FailedToStartStream.MissingEncoderConfigs"));
 
- 	}
 
- 	for (size_t i = 0; i < go_live_config.encoder_configurations.size();
 
- 	     i++) {
 
- 		auto encoder = create_video_encoder(
 
- 			video_encoder_name_buffer, i,
 
- 			go_live_config.encoder_configurations[i]);
 
- 		if (!encoder)
 
- 			return false;
 
- 		if (!first_encoder)
 
- 			first_encoder = encoder;
 
- 		else
 
- 			obs_encoder_group_keyframe_aligned_encoders(
 
- 				first_encoder, encoder);
 
- 		obs_output_set_video_encoder2(output, encoder, i);
 
- 		if (recording_output)
 
- 			obs_output_set_video_encoder2(recording_output, encoder,
 
- 						      i);
 
- 		video_encoders.emplace_back(std::move(encoder));
 
- 	}
 
- 	return true;
 
- }
 
- static void
 
- create_audio_encoders(const GoLiveApi::Config &go_live_config,
 
- 		      std::vector<OBSEncoderAutoRelease> &audio_encoders,
 
- 		      obs_output_t *output, obs_output_t *recording_output,
 
- 		      const char *audio_encoder_id,
 
- 		      std::optional<size_t> vod_track_mixer)
 
- {
 
- 	using encoder_configs_type =
 
- 		decltype(go_live_config.audio_configurations.live);
 
- 	DStr encoder_name_buffer;
 
- 	size_t output_encoder_index = 0;
 
- 	auto create_encoders = [&](const char *name_prefix,
 
- 				   const encoder_configs_type &configs,
 
- 				   size_t mixer_idx) {
 
- 		if (configs.empty()) {
 
- 			blog(LOG_WARNING,
 
- 			     "MultitrackVideoOutput: Missing audio encoder configurations (for '%s')",
 
- 			     name_prefix);
 
- 			throw MultitrackVideoError::warning(QTStr(
 
- 				"FailedToStartStream.MissingEncoderConfigs"));
 
- 		}
 
- 		for (size_t i = 0; i < configs.size(); i++) {
 
- 			dstr_printf(encoder_name_buffer, "%s %zu", name_prefix,
 
- 				    i);
 
- 			OBSEncoderAutoRelease audio_encoder =
 
- 				create_audio_encoder(encoder_name_buffer->array,
 
- 						     audio_encoder_id,
 
- 						     configs[i].config.bitrate,
 
- 						     mixer_idx);
 
- 			obs_output_set_audio_encoder(output, audio_encoder,
 
- 						     output_encoder_index);
 
- 			if (recording_output)
 
- 				obs_output_set_audio_encoder(
 
- 					recording_output, audio_encoder,
 
- 					output_encoder_index);
 
- 			output_encoder_index += 1;
 
- 			audio_encoders.emplace_back(std::move(audio_encoder));
 
- 		}
 
- 	};
 
- 	create_encoders("multitrack video live audio",
 
- 			go_live_config.audio_configurations.live, 0);
 
- 	if (!vod_track_mixer.has_value())
 
- 		return;
 
- 	create_encoders("multitrack video vod audio",
 
- 			go_live_config.audio_configurations.vod,
 
- 			*vod_track_mixer);
 
- 	return;
 
- }
 
- static OBSOutputs
 
- SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
 
- 	       const GoLiveApi::Config &go_live_config,
 
- 	       std::vector<OBSEncoderAutoRelease> &audio_encoders,
 
- 	       std::vector<OBSEncoderAutoRelease> &video_encoders,
 
- 	       const char *audio_encoder_id,
 
- 	       std::optional<size_t> vod_track_mixer)
 
- {
 
- 	auto output = create_output();
 
- 	OBSOutputAutoRelease recording_output;
 
- 	if (dump_stream_to_file_config)
 
- 		recording_output =
 
- 			create_recording_output(dump_stream_to_file_config);
 
- 	if (!create_video_encoders(go_live_config, video_encoders, output,
 
- 				   recording_output))
 
- 		return {nullptr, nullptr};
 
- 	create_audio_encoders(go_live_config, audio_encoders, output,
 
- 			      recording_output, audio_encoder_id,
 
- 			      vod_track_mixer);
 
- 	return {std::move(output), std::move(recording_output)};
 
- }
 
- void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self,
 
- 			 obs_output_t *output, OBSSignal &start,
 
- 			 OBSSignal &stop, OBSSignal &deactivate)
 
- {
 
- 	auto handler = obs_output_get_signal_handler(output);
 
- 	if (recording)
 
- 		start.Connect(handler, "start", RecordingStartHandler, self);
 
- 	stop.Connect(handler, "stop",
 
- 		     !recording ? StreamStopHandler : RecordingStopHandler,
 
- 		     self);
 
- 	deactivate.Connect(handler, "deactivate",
 
- 			   !recording ? StreamDeactivateHandler
 
- 				      : RecordingDeactivateHandler,
 
- 			   self);
 
- }
 
- std::optional<MultitrackVideoOutput::OBSOutputObjects>
 
- MultitrackVideoOutput::take_current()
 
- {
 
- 	const std::lock_guard<std::mutex> current_lock{current_mutex};
 
- 	auto val = std::move(current);
 
- 	current.reset();
 
- 	return val;
 
- }
 
- std::optional<MultitrackVideoOutput::OBSOutputObjects>
 
- MultitrackVideoOutput::take_current_stream_dump()
 
- {
 
- 	const std::lock_guard<std::mutex> current_stream_dump_lock{
 
- 		current_stream_dump_mutex};
 
- 	auto val = std::move(current_stream_dump);
 
- 	current_stream_dump.reset();
 
- 	return val;
 
- }
 
- void MultitrackVideoOutput::ReleaseOnMainThread(
 
- 	std::optional<OBSOutputObjects> objects)
 
- {
 
- 	if (!objects.has_value())
 
- 		return;
 
- 	QMetaObject::invokeMethod(
 
- 		QApplication::instance()->thread(),
 
- 		[objects = std::move(objects)] {}, Qt::QueuedConnection);
 
- }
 
- void StreamStopHandler(void *arg, calldata_t *params)
 
- {
 
- 	auto self = static_cast<MultitrackVideoOutput *>(arg);
 
- 	OBSOutputAutoRelease stream_dump_output;
 
- 	{
 
- 		const std::lock_guard<std::mutex> current_stream_dump_lock{
 
- 			self->current_stream_dump_mutex};
 
- 		if (self->current_stream_dump &&
 
- 		    self->current_stream_dump->output_)
 
- 			stream_dump_output = obs_output_get_ref(
 
- 				self->current_stream_dump->output_);
 
- 	}
 
- 	if (stream_dump_output)
 
- 		obs_output_stop(stream_dump_output);
 
- 	if (obs_output_active(static_cast<obs_output_t *>(
 
- 		    calldata_ptr(params, "output"))))
 
- 		return;
 
- 	MultitrackVideoOutput::ReleaseOnMainThread(self->take_current());
 
- }
 
- void StreamDeactivateHandler(void *arg, calldata_t *params)
 
- {
 
- 	auto self = static_cast<MultitrackVideoOutput *>(arg);
 
- 	if (obs_output_reconnecting(static_cast<obs_output_t *>(
 
- 		    calldata_ptr(params, "output"))))
 
- 		return;
 
- 	MultitrackVideoOutput::ReleaseOnMainThread(self->take_current());
 
- }
 
- void RecordingStartHandler(void * /* arg */, calldata_t * /* data */)
 
- {
 
- 	blog(LOG_INFO, "MultitrackVideoOutput: recording started");
 
- }
 
- void RecordingStopHandler(void *arg, calldata_t *params)
 
- {
 
- 	auto self = static_cast<MultitrackVideoOutput *>(arg);
 
- 	blog(LOG_INFO, "MultitrackVideoOutput: recording stopped");
 
- 	if (obs_output_active(static_cast<obs_output_t *>(
 
- 		    calldata_ptr(params, "output"))))
 
- 		return;
 
- 	MultitrackVideoOutput::ReleaseOnMainThread(
 
- 		self->take_current_stream_dump());
 
- }
 
- void RecordingDeactivateHandler(void *arg, calldata_t * /*data*/)
 
- {
 
- 	auto self = static_cast<MultitrackVideoOutput *>(arg);
 
- 	MultitrackVideoOutput::ReleaseOnMainThread(
 
- 		self->take_current_stream_dump());
 
- }
 
 
  |