mirror of
https://github.com/obsproject/obs-studio.git
synced 2025-12-24 23:18:06 -05:00
Implicitly capturing "this" with the capture default "=" is deprecated
with C++20. We fix this by either explicitly passing this, or by copying
the required members manually.
While this exposes some rather expensive copies like the QList
selectedItems in OBSBasic_Preview, it doesn't introduce them ("=" copies
implicitly).
595 lines
19 KiB
C++
595 lines
19 KiB
C++
#include "BasicOutputHandler.hpp"
|
|
#include "AdvancedOutput.hpp"
|
|
#include "SimpleOutput.hpp"
|
|
|
|
#include <utility/MultitrackVideoError.hpp>
|
|
#include <utility/StartMultiTrackVideoStreamingGuard.hpp>
|
|
#include <utility/VCamConfig.hpp>
|
|
#include <widgets/OBSBasic.hpp>
|
|
|
|
#include <qt-wrappers.hpp>
|
|
|
|
#include <QThreadPool>
|
|
|
|
using namespace std;
|
|
|
|
extern bool EncoderAvailable(const char *encoder);
|
|
|
|
volatile bool streaming_active = false;
|
|
volatile bool recording_active = false;
|
|
volatile bool recording_paused = false;
|
|
volatile bool replaybuf_active = false;
|
|
volatile bool virtualcam_active = false;
|
|
|
|
void OBSStreamStarting(void *data, calldata_t *params)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output");
|
|
|
|
int sec = (int)obs_output_get_active_delay(obj);
|
|
if (sec == 0)
|
|
return;
|
|
|
|
output->delayActive = true;
|
|
QMetaObject::invokeMethod(output->main, "StreamDelayStarting", Q_ARG(int, sec));
|
|
}
|
|
|
|
void OBSStreamStopping(void *data, calldata_t *params)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output");
|
|
|
|
int sec = (int)obs_output_get_active_delay(obj);
|
|
if (sec == 0)
|
|
QMetaObject::invokeMethod(output->main, "StreamStopping");
|
|
else
|
|
QMetaObject::invokeMethod(output->main, "StreamDelayStopping", Q_ARG(int, sec));
|
|
}
|
|
|
|
void OBSStartStreaming(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
output->streamingActive = true;
|
|
os_atomic_set_bool(&streaming_active, true);
|
|
QMetaObject::invokeMethod(output->main, "StreamingStart");
|
|
}
|
|
|
|
void OBSStopStreaming(void *data, calldata_t *params)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
int code = (int)calldata_int(params, "code");
|
|
const char *last_error = calldata_string(params, "last_error");
|
|
|
|
QString arg_last_error = QString::fromUtf8(last_error);
|
|
|
|
output->streamingActive = false;
|
|
output->delayActive = false;
|
|
output->multitrackVideoActive = false;
|
|
os_atomic_set_bool(&streaming_active, false);
|
|
QMetaObject::invokeMethod(output->main, "StreamingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error));
|
|
}
|
|
|
|
void OBSStartRecording(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
|
|
output->recordingActive = true;
|
|
os_atomic_set_bool(&recording_active, true);
|
|
QMetaObject::invokeMethod(output->main, "RecordingStart");
|
|
}
|
|
|
|
void OBSStopRecording(void *data, calldata_t *params)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
int code = (int)calldata_int(params, "code");
|
|
const char *last_error = calldata_string(params, "last_error");
|
|
|
|
QString arg_last_error = QString::fromUtf8(last_error);
|
|
|
|
output->recordingActive = false;
|
|
os_atomic_set_bool(&recording_active, false);
|
|
os_atomic_set_bool(&recording_paused, false);
|
|
QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error));
|
|
}
|
|
|
|
void OBSRecordStopping(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
QMetaObject::invokeMethod(output->main, "RecordStopping");
|
|
}
|
|
|
|
void OBSRecordFileChanged(void *data, calldata_t *params)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
const char *next_file = calldata_string(params, "next_file");
|
|
|
|
QString arg_last_file = QString::fromUtf8(output->lastRecordingPath.c_str());
|
|
|
|
QMetaObject::invokeMethod(output->main, "RecordingFileChanged", Q_ARG(QString, arg_last_file));
|
|
|
|
output->lastRecordingPath = next_file;
|
|
}
|
|
|
|
void OBSStartReplayBuffer(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
|
|
output->replayBufferActive = true;
|
|
os_atomic_set_bool(&replaybuf_active, true);
|
|
QMetaObject::invokeMethod(output->main, "ReplayBufferStart");
|
|
}
|
|
|
|
void OBSStopReplayBuffer(void *data, calldata_t *params)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
int code = (int)calldata_int(params, "code");
|
|
|
|
output->replayBufferActive = false;
|
|
os_atomic_set_bool(&replaybuf_active, false);
|
|
QMetaObject::invokeMethod(output->main, "ReplayBufferStop", Q_ARG(int, code));
|
|
}
|
|
|
|
void OBSReplayBufferStopping(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
QMetaObject::invokeMethod(output->main, "ReplayBufferStopping");
|
|
}
|
|
|
|
void OBSReplayBufferSaved(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
QMetaObject::invokeMethod(output->main, "ReplayBufferSaved", Qt::QueuedConnection);
|
|
}
|
|
|
|
static void OBSStartVirtualCam(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
|
|
output->virtualCamActive = true;
|
|
os_atomic_set_bool(&virtualcam_active, true);
|
|
QMetaObject::invokeMethod(output->main, "OnVirtualCamStart");
|
|
}
|
|
|
|
static void OBSStopVirtualCam(void *data, calldata_t *params)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
int code = (int)calldata_int(params, "code");
|
|
|
|
output->virtualCamActive = false;
|
|
os_atomic_set_bool(&virtualcam_active, false);
|
|
QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code));
|
|
}
|
|
|
|
static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */)
|
|
{
|
|
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
|
|
output->DestroyVirtualCamView();
|
|
}
|
|
|
|
bool return_first_id(void *data, const char *id)
|
|
{
|
|
const char **output = (const char **)data;
|
|
|
|
*output = id;
|
|
return false;
|
|
}
|
|
|
|
const char *GetStreamOutputType(const obs_service_t *service)
|
|
{
|
|
const char *protocol = obs_service_get_protocol(service);
|
|
const char *output = nullptr;
|
|
|
|
if (!protocol) {
|
|
blog(LOG_WARNING, "The service '%s' has no protocol set", obs_service_get_id(service));
|
|
return nullptr;
|
|
}
|
|
|
|
if (!obs_is_output_protocol_registered(protocol)) {
|
|
blog(LOG_WARNING, "The protocol '%s' is not registered", protocol);
|
|
return nullptr;
|
|
}
|
|
|
|
/* Check if the service has a preferred output type */
|
|
output = obs_service_get_preferred_output_type(service);
|
|
if (output) {
|
|
if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0)
|
|
return output;
|
|
|
|
blog(LOG_WARNING, "The output '%s' is not registered, fallback to another one", output);
|
|
}
|
|
|
|
/* Otherwise, prefer first-party output types */
|
|
if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) {
|
|
return "rtmp_output";
|
|
} else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) {
|
|
return "ffmpeg_hls_muxer";
|
|
} else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) {
|
|
return "ffmpeg_mpegts_muxer";
|
|
}
|
|
|
|
/* If third-party protocol, use the first enumerated type */
|
|
obs_enum_output_types_with_protocol(protocol, &output, return_first_id);
|
|
if (output)
|
|
return output;
|
|
|
|
blog(LOG_WARNING, "No output compatible with the service '%s' is registered", obs_service_get_id(service));
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_)
|
|
{
|
|
if (main->vcamEnabled) {
|
|
virtualCam = obs_output_create(VIRTUAL_CAM_ID, "virtualcam_output", nullptr, nullptr);
|
|
|
|
signal_handler_t *signal = obs_output_get_signal_handler(virtualCam);
|
|
startVirtualCam.Connect(signal, "start", OBSStartVirtualCam, this);
|
|
stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this);
|
|
deactivateVirtualCam.Connect(signal, "deactivate", OBSDeactivateVirtualCam, this);
|
|
}
|
|
|
|
auto service = main_->GetService();
|
|
OBSDataAutoRelease settings = obs_service_get_settings(service);
|
|
auto multitrack_enabled = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo") &&
|
|
(obs_data_has_user_value(settings, "multitrack_video_configuration_url") ||
|
|
strcmp(obs_service_get_id(service), "rtmp_custom") == 0);
|
|
|
|
if (multitrack_enabled)
|
|
multitrackVideo = make_unique<MultitrackVideoOutput>();
|
|
|
|
if (config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers") > 1)
|
|
whipSimulcastEncoders = make_unique<WHIPSimulcastEncoders>();
|
|
}
|
|
|
|
extern void log_vcam_changed(const VCamConfig &config, bool starting);
|
|
|
|
bool BasicOutputHandler::StartVirtualCam()
|
|
{
|
|
if (!main->vcamEnabled)
|
|
return false;
|
|
|
|
bool typeIsProgram = main->vcamConfig.type == VCamOutputType::ProgramView;
|
|
|
|
if (!virtualCamView && !typeIsProgram)
|
|
virtualCamView = obs_view_create();
|
|
|
|
UpdateVirtualCamOutputSource();
|
|
|
|
if (!virtualCamVideo) {
|
|
virtualCamVideo = typeIsProgram ? obs_get_video() : obs_view_add(virtualCamView);
|
|
|
|
if (!virtualCamVideo)
|
|
return false;
|
|
}
|
|
|
|
obs_output_set_media(virtualCam, virtualCamVideo, obs_get_audio());
|
|
if (!Active())
|
|
SetupOutputs();
|
|
|
|
bool success = obs_output_start(virtualCam);
|
|
if (!success) {
|
|
QString errorReason;
|
|
|
|
const char *error = obs_output_get_last_error(virtualCam);
|
|
if (error) {
|
|
errorReason = QT_UTF8(error);
|
|
} else {
|
|
errorReason = QTStr("Output.StartFailedGeneric");
|
|
}
|
|
|
|
QMessageBox::critical(main, QTStr("Output.StartVirtualCamFailed"), errorReason);
|
|
|
|
DestroyVirtualCamView();
|
|
}
|
|
|
|
log_vcam_changed(main->vcamConfig, true);
|
|
|
|
return success;
|
|
}
|
|
|
|
void BasicOutputHandler::StopVirtualCam()
|
|
{
|
|
if (main->vcamEnabled) {
|
|
obs_output_stop(virtualCam);
|
|
}
|
|
}
|
|
|
|
bool BasicOutputHandler::VirtualCamActive() const
|
|
{
|
|
if (main->vcamEnabled) {
|
|
return obs_output_active(virtualCam);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BasicOutputHandler::UpdateVirtualCamOutputSource()
|
|
{
|
|
if (!main->vcamEnabled || !virtualCamView)
|
|
return;
|
|
|
|
OBSSourceAutoRelease source;
|
|
|
|
switch (main->vcamConfig.type) {
|
|
case VCamOutputType::Invalid:
|
|
case VCamOutputType::ProgramView:
|
|
DestroyVirtualCameraScene();
|
|
return;
|
|
case VCamOutputType::PreviewOutput: {
|
|
DestroyVirtualCameraScene();
|
|
OBSSource s = main->GetCurrentSceneSource();
|
|
obs_source_get_ref(s);
|
|
source = s.Get();
|
|
break;
|
|
}
|
|
case VCamOutputType::SceneOutput:
|
|
DestroyVirtualCameraScene();
|
|
source = obs_get_source_by_name(main->vcamConfig.scene.c_str());
|
|
break;
|
|
case VCamOutputType::SourceOutput:
|
|
OBSSourceAutoRelease s = obs_get_source_by_name(main->vcamConfig.source.c_str());
|
|
|
|
if (!vCamSourceScene)
|
|
vCamSourceScene = obs_scene_create_private("vcam_source");
|
|
source = obs_source_get_ref(obs_scene_get_source(vCamSourceScene));
|
|
|
|
if (vCamSourceSceneItem && (obs_sceneitem_get_source(vCamSourceSceneItem) != s)) {
|
|
obs_sceneitem_remove(vCamSourceSceneItem);
|
|
vCamSourceSceneItem = nullptr;
|
|
}
|
|
|
|
if (!vCamSourceSceneItem) {
|
|
vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s);
|
|
|
|
obs_sceneitem_set_bounds_type(vCamSourceSceneItem, OBS_BOUNDS_SCALE_INNER);
|
|
obs_sceneitem_set_bounds_alignment(vCamSourceSceneItem, OBS_ALIGN_CENTER);
|
|
|
|
const struct vec2 size = {
|
|
(float)obs_source_get_width(source),
|
|
(float)obs_source_get_height(source),
|
|
};
|
|
obs_sceneitem_set_bounds(vCamSourceSceneItem, &size);
|
|
}
|
|
break;
|
|
}
|
|
|
|
OBSSourceAutoRelease current = obs_view_get_source(virtualCamView, 0);
|
|
if (source != current)
|
|
obs_view_set_source(virtualCamView, 0, source);
|
|
}
|
|
|
|
void BasicOutputHandler::DestroyVirtualCamView()
|
|
{
|
|
if (main->vcamConfig.type == VCamOutputType::ProgramView) {
|
|
virtualCamVideo = nullptr;
|
|
return;
|
|
}
|
|
|
|
obs_view_remove(virtualCamView);
|
|
obs_view_set_source(virtualCamView, 0, nullptr);
|
|
virtualCamVideo = nullptr;
|
|
|
|
obs_view_destroy(virtualCamView);
|
|
virtualCamView = nullptr;
|
|
|
|
DestroyVirtualCameraScene();
|
|
}
|
|
|
|
void BasicOutputHandler::DestroyVirtualCameraScene()
|
|
{
|
|
if (!vCamSourceScene)
|
|
return;
|
|
|
|
obs_scene_release(vCamSourceScene);
|
|
vCamSourceScene = nullptr;
|
|
vCamSourceSceneItem = nullptr;
|
|
}
|
|
|
|
const char *FindAudioEncoderFromCodec(const char *type)
|
|
{
|
|
const char *alt_enc_id = nullptr;
|
|
size_t i = 0;
|
|
|
|
while (obs_enum_encoder_types(i++, &alt_enc_id)) {
|
|
const char *codec = obs_get_encoder_codec(alt_enc_id);
|
|
if (strcmp(type, codec) == 0) {
|
|
return alt_enc_id;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void clear_archive_encoder(obs_output_t *output, const char *expected_name)
|
|
{
|
|
obs_encoder_t *last = obs_output_get_audio_encoder(output, 1);
|
|
bool clear = false;
|
|
|
|
/* ensures that we don't remove twitch's soundtrack encoder */
|
|
if (last) {
|
|
const char *name = obs_encoder_get_name(last);
|
|
clear = name && strcmp(name, expected_name) == 0;
|
|
obs_encoder_release(last);
|
|
}
|
|
|
|
if (clear)
|
|
obs_output_set_audio_encoder(output, nullptr, 1);
|
|
}
|
|
|
|
void BasicOutputHandler::SetupAutoRemux(const char *&container)
|
|
{
|
|
bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux");
|
|
if (autoRemux && strcmp(container, "mp4") == 0)
|
|
container = "mkv";
|
|
}
|
|
|
|
std::string BasicOutputHandler::GetRecordingFilename(const char *path, const char *container, bool noSpace,
|
|
bool overwrite, const char *format, bool ffmpeg)
|
|
{
|
|
if (!ffmpeg)
|
|
SetupAutoRemux(container);
|
|
|
|
string dst = GetOutputFilename(path, container, noSpace, overwrite, format);
|
|
lastRecordingPath = dst;
|
|
return dst;
|
|
}
|
|
|
|
extern std::string DeserializeConfigText(const char *text);
|
|
|
|
std::shared_future<void> BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service, std::string audio_encoder_id,
|
|
size_t main_audio_mixer,
|
|
std::optional<size_t> vod_track_mixer,
|
|
std::function<void(std::optional<bool>)> continuation)
|
|
{
|
|
auto start_streaming_guard = std::make_shared<StartMultitrackVideoStreamingGuard>();
|
|
if (!multitrackVideo) {
|
|
continuation(std::nullopt);
|
|
return start_streaming_guard->GetFuture();
|
|
}
|
|
|
|
multitrackVideoActive = false;
|
|
|
|
streamDelayStarting.Disconnect();
|
|
streamStopping.Disconnect();
|
|
startStreaming.Disconnect();
|
|
stopStreaming.Disconnect();
|
|
|
|
bool is_custom = strncmp("rtmp_custom", obs_service_get_type(service), 11) == 0;
|
|
|
|
std::optional<std::string> custom_config = std::nullopt;
|
|
if (config_get_bool(main->Config(), "Stream1", "MultitrackVideoConfigOverrideEnabled"))
|
|
custom_config = DeserializeConfigText(
|
|
config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride"));
|
|
|
|
std::optional<QString> extraCanvasUUID;
|
|
const char *uuid = config_get_string(main->Config(), "Stream1", "MultitrackExtraCanvas");
|
|
if (uuid && *uuid) {
|
|
extraCanvasUUID = uuid;
|
|
}
|
|
|
|
OBSDataAutoRelease settings = obs_service_get_settings(service);
|
|
QString key = obs_data_get_string(settings, "key");
|
|
|
|
const char *service_name = "<unknown>";
|
|
if (is_custom && obs_data_has_user_value(settings, "service_name")) {
|
|
service_name = obs_data_get_string(settings, "service_name");
|
|
} else if (!is_custom) {
|
|
service_name = obs_data_get_string(settings, "service");
|
|
}
|
|
|
|
std::optional<std::string> custom_rtmp_url;
|
|
std::optional<bool> use_rtmps;
|
|
auto server = obs_data_get_string(settings, "server");
|
|
if (strncmp(server, "auto", 4) != 0) {
|
|
custom_rtmp_url = server;
|
|
} else {
|
|
QString server_ = server;
|
|
use_rtmps = server_.contains("rtmps", Qt::CaseInsensitive);
|
|
}
|
|
|
|
auto service_custom_server = obs_data_get_bool(settings, "using_custom_server");
|
|
if (custom_rtmp_url.has_value()) {
|
|
blog(LOG_INFO, "Using %sserver '%s'", service_custom_server ? "custom " : "", custom_rtmp_url->c_str());
|
|
}
|
|
|
|
auto maximum_aggregate_bitrate =
|
|
config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto")
|
|
? std::nullopt
|
|
: std::make_optional<uint32_t>(
|
|
config_get_int(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrate"));
|
|
|
|
auto maximum_video_tracks = config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracksAuto")
|
|
? std::nullopt
|
|
: std::make_optional<uint32_t>(config_get_int(
|
|
main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracks"));
|
|
|
|
auto stream_dump_config = GenerateMultitrackVideoStreamDumpConfig();
|
|
|
|
auto continue_on_main_thread = [&, start_streaming_guard, service = OBSService{service},
|
|
continuation =
|
|
std::move(continuation)](std::optional<MultitrackVideoError> error) {
|
|
if (error) {
|
|
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");
|
|
}
|
|
|
|
multitrackVideoActive = false;
|
|
if (!error->ShowDialog(main, multitrack_video_name))
|
|
return continuation(false);
|
|
return continuation(std::nullopt);
|
|
}
|
|
|
|
multitrackVideoActive = true;
|
|
|
|
auto signal_handler = multitrackVideo->StreamingSignalHandler();
|
|
|
|
streamDelayStarting.Connect(signal_handler, "starting", OBSStreamStarting, this);
|
|
streamStopping.Connect(signal_handler, "stopping", OBSStreamStopping, this);
|
|
|
|
startStreaming.Connect(signal_handler, "start", OBSStartStreaming, this);
|
|
stopStreaming.Connect(signal_handler, "stop", OBSStopStreaming, this);
|
|
return continuation(true);
|
|
};
|
|
|
|
QThreadPool::globalInstance()->start([=, main = main, multitrackVideo = multitrackVideo.get(),
|
|
service_name = std::string{service_name}, service = OBSService{service},
|
|
stream_dump_config = OBSData{stream_dump_config},
|
|
start_streaming_guard = start_streaming_guard]() mutable {
|
|
std::optional<MultitrackVideoError> error;
|
|
try {
|
|
multitrackVideo->PrepareStreaming(main, service_name.c_str(), service, custom_rtmp_url, key,
|
|
audio_encoder_id.c_str(), maximum_aggregate_bitrate,
|
|
maximum_video_tracks, custom_config, stream_dump_config,
|
|
main_audio_mixer, vod_track_mixer, use_rtmps,
|
|
extraCanvasUUID);
|
|
} catch (const MultitrackVideoError &error_) {
|
|
error.emplace(error_);
|
|
}
|
|
|
|
QMetaObject::invokeMethod(main, [=] { continue_on_main_thread(error); });
|
|
});
|
|
|
|
return start_streaming_guard->GetFuture();
|
|
}
|
|
|
|
OBSDataAutoRelease BasicOutputHandler::GenerateMultitrackVideoStreamDumpConfig()
|
|
{
|
|
auto stream_dump_enabled = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpEnabled");
|
|
|
|
if (!stream_dump_enabled)
|
|
return nullptr;
|
|
|
|
const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath");
|
|
bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace");
|
|
const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting");
|
|
bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists");
|
|
bool useMP4 = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpAsMP4");
|
|
|
|
string f;
|
|
|
|
OBSDataAutoRelease settings = obs_data_create();
|
|
f = GetFormatString(filenameFormat, nullptr, nullptr);
|
|
string strPath = GetRecordingFilename(path, useMP4 ? "mp4" : "flv", noSpace, overwriteIfExists, f.c_str(),
|
|
// never remux stream dump
|
|
false);
|
|
obs_data_set_string(settings, "path", strPath.c_str());
|
|
|
|
if (useMP4) {
|
|
obs_data_set_bool(settings, "use_mp4", true);
|
|
obs_data_set_string(settings, "muxer_settings", "write_encoder_info=1");
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main)
|
|
{
|
|
return new SimpleOutput(main);
|
|
}
|
|
|
|
BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main)
|
|
{
|
|
return new AdvancedOutput(main);
|
|
}
|