diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 9a77da80f7f7b0..5b0c81c996429c 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -98,8 +98,6 @@ target_sources( multitrack-video-error.hpp multitrack-video-output.cpp multitrack-video-output.hpp - qt-helpers.cpp - qt-helpers.hpp system-info.hpp) if(OS_WINDOWS) diff --git a/UI/goliveapi-network.hpp b/UI/goliveapi-network.hpp index 2fd3def43c8cd2..e543640d97ff7e 100644 --- a/UI/goliveapi-network.hpp +++ b/UI/goliveapi-network.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include "models/multitrack-video.hpp" diff --git a/UI/multitrack-video-output.cpp b/UI/multitrack-video-output.cpp index 4f773d003e7003..3a1f3430bb85ab 100644 --- a/UI/multitrack-video-output.cpp +++ b/UI/multitrack-video-output.cpp @@ -35,7 +35,6 @@ #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) diff --git a/UI/multitrack-video-output.hpp b/UI/multitrack-video-output.hpp index 83aa48e182ac43..71f590aca7c3bf 100644 --- a/UI/multitrack-video-output.hpp +++ b/UI/multitrack-video-output.hpp @@ -4,12 +4,11 @@ #include #include +#include #include #include -#include -#include -#include +#include #define NOMINMAX diff --git a/UI/qt-helpers.cpp b/UI/qt-helpers.cpp deleted file mode 100644 index ad76bd5a1bbd27..00000000000000 --- a/UI/qt-helpers.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "qt-helpers.hpp" - -QFuture CreateFuture() -{ - QPromise promise; - auto future = promise.future(); - promise.start(); - promise.finish(); - return future; -} diff --git a/UI/qt-helpers.hpp b/UI/qt-helpers.hpp deleted file mode 100644 index c0d7328c08215d..00000000000000 --- a/UI/qt-helpers.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include - -template struct FutureHolder { - std::function cancelAll; - QFuture future; -}; - -QFuture CreateFuture(); - -template inline QFuture PreventFutureDeadlock(QFuture future) -{ - /* - * QFutures deadlock if there are continuations on the same thread that - * need to wait for the previous continuation to finish, see - * https://github.com/qt/qtbase/commit/59e21a536f7f81625216dc7a621e7be59919da33 - * - * related bugs: - * https://bugreports.qt.io/browse/QTBUG-119406 - * https://bugreports.qt.io/browse/QTBUG-119103 - * https://bugreports.qt.io/browse/QTBUG-117918 - * https://bugreports.qt.io/browse/QTBUG-119579 - * https://bugreports.qt.io/browse/QTBUG-119810 - * @RytoEX's summary: - * QTBUG-119406 and QTBUG-119103 affect Qt 6.6.0 and are fixed in Qt 6.6.2 and 6.7.0+. - * QTBUG-119579 and QTBUG-119810 affect Qt 6.6.1 and are fixed in Qt 6.6.2 and 6.7.0+. - * QTBUG-117918 is the only strange one that seems to possibly affect all Qt 6.x versions - * until 6.6.2, but only in Debug builds. - * - * To fix this, move relevant QFutures to another thread before resuming - * on main thread for affected Qt versions - */ -#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) && \ - (QT_VERSION < QT_VERSION_CHECK(6, 6, 2)) - if (future.isFinished()) { - return future; - } - - return future.then(QtFuture::Launch::Async, [](T val) { return val; }); -#else - return future; -#endif -} diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 4ba3b996385e45..9e644e90acb8df 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "qt-wrappers.hpp" #include "audio-encoders.hpp" #include "multitrack-video-error.hpp" @@ -183,6 +183,28 @@ static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */) /* ------------------------------------------------------------------------ */ +struct StartMultitrackVideoStreamingGuard { + StartMultitrackVideoStreamingGuard() + { + future = guard.get_future().share(); + }; + ~StartMultitrackVideoStreamingGuard() { guard.set_value(); } + + std::shared_future GetFuture() const { return future; } + + static std::shared_future MakeReadyFuture() + { + StartMultitrackVideoStreamingGuard guard; + return guard.GetFuture(); + } + +private: + std::promise guard; + std::shared_future future; +}; + +/* ------------------------------------------------------------------------ */ + static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) { @@ -520,8 +542,9 @@ struct SimpleOutput : BasicOutputHandler { bool IsVodTrackEnabled(obs_service_t *service); void SetupVodTrack(obs_service_t *service); - virtual FutureHolder - SetupStreaming(obs_service_t *service) override; + virtual std::shared_future + SetupStreaming(obs_service_t *service, + SetupStreamingContinuation_t continuation) override; virtual bool StartStreaming(obs_service_t *service) override; virtual bool StartRecording() override; virtual bool StartReplayBuffer() override; @@ -1118,7 +1141,9 @@ const char *FindAudioEncoderFromCodec(const char *type) return nullptr; } -FutureHolder SimpleOutput::SetupStreaming(obs_service_t *service) +std::shared_future +SimpleOutput::SetupStreaming(obs_service_t *service, + SetupStreamingContinuation_t continuation) { if (!Active()) SetupOutputs(); @@ -1130,8 +1155,10 @@ FutureHolder SimpleOutput::SetupStreaming(obs_service_t *service) /* --------------------- */ const char *type = GetStreamOutputType(service); - if (!type) - return {[] {}, CreateFuture().then([] { return false; })}; + if (!type) { + continuation(false); + return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); + } auto audio_bitrate = GetAudioBitrate(); auto vod_track_mixer = IsVodTrackEnabled(service) ? std::optional{1} @@ -1182,12 +1209,11 @@ FutureHolder SimpleOutput::SetupStreaming(obs_service_t *service) return true; }; - auto holder = SetupMultitrackVideo( + return SetupMultitrackVideo( service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, - vod_track_mixer); - auto future = PreventFutureDeadlock(holder.future) - .then(main, handle_multitrack_video_result); - return {holder.cancelAll, future}; + vod_track_mixer, [&, continuation](std::optional res) { + continuation(handle_multitrack_video_result(res)); + }); } static inline bool ServiceSupportsVodTrack(const char *service); @@ -1567,8 +1593,9 @@ struct AdvancedOutput : BasicOutputHandler { void SetupOutputs() override; int GetAudioBitrate(size_t i, const char *id) const; - virtual FutureHolder - SetupStreaming(obs_service_t *service) override; + virtual std::shared_future + SetupStreaming(obs_service_t *service, + SetupStreamingContinuation_t continuation) override; virtual bool StartStreaming(obs_service_t *service) override; virtual bool StartRecording() override; virtual bool StartReplayBuffer() override; @@ -2247,7 +2274,9 @@ inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) clear_archive_encoder(streamOutput, ADV_ARCHIVE_NAME); } -FutureHolder AdvancedOutput::SetupStreaming(obs_service_t *service) +std::shared_future +AdvancedOutput::SetupStreaming(obs_service_t *service, + SetupStreamingContinuation_t continuation) { int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); @@ -2272,8 +2301,10 @@ FutureHolder AdvancedOutput::SetupStreaming(obs_service_t *service) /* --------------------- */ const char *type = GetStreamOutputType(service); - if (!type) - return {[] {}, CreateFuture().then(main, [] { return false; })}; + if (!type) { + continuation(false); + return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); + } const char *audio_encoder_id = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); @@ -2339,13 +2370,13 @@ FutureHolder AdvancedOutput::SetupStreaming(obs_service_t *service) return true; }; - auto holder = - SetupMultitrackVideo(service, audio_encoder_id, - static_cast(streamTrackIndex), - VodTrackMixerIdx(service)); - auto future = PreventFutureDeadlock(holder.future) - .then(main, handle_multitrack_video_result); - return {holder.cancelAll, future}; + return SetupMultitrackVideo( + service, audio_encoder_id, + static_cast(streamTrackIndex), + VodTrackMixerIdx(service), + [&, continuation](std::optional res) { + continuation(handle_multitrack_video_result(res)); + }); } bool AdvancedOutput::StartStreaming(obs_service_t *service) @@ -2683,14 +2714,17 @@ std::string BasicOutputHandler::GetRecordingFilename( extern std::string DeserializeConfigText(const char *text); -FutureHolder> BasicOutputHandler::SetupMultitrackVideo( +std::shared_future BasicOutputHandler::SetupMultitrackVideo( obs_service_t *service, std::string audio_encoder_id, - size_t main_audio_mixer, std::optional vod_track_mixer) + size_t main_audio_mixer, std::optional vod_track_mixer, + std::function)> continuation) { - if (!multitrackVideo) - return {[] {}, CreateFuture().then([] { - return std::optional{std::nullopt}; - })}; + auto start_streaming_guard = + std::make_shared(); + if (!multitrackVideo) { + continuation(std::nullopt); + return start_streaming_guard->GetFuture(); + } multitrackVideoActive = false; @@ -2751,10 +2785,11 @@ FutureHolder> BasicOutputHandler::SetupMultitrackVideo( auto stream_dump_config = GenerateMultitrackVideoStreamDumpConfig(); - auto continue_on_main_thread = - [&, service = OBSService{service}]( - std::optional error) - -> std::optional { + auto continue_on_main_thread = [&, start_streaming_guard, + service = OBSService{service}, + continuation = std::move(continuation)]( + std::optional + error) { if (error) { OBSDataAutoRelease service_settings = obs_service_get_settings(service); @@ -2769,8 +2804,8 @@ FutureHolder> BasicOutputHandler::SetupMultitrackVideo( multitrackVideoActive = false; if (!error->ShowDialog(main, multitrack_video_name)) - return false; - return std::nullopt; + return continuation(false); + return continuation(std::nullopt); } multitrackVideoActive = true; @@ -2786,16 +2821,16 @@ FutureHolder> BasicOutputHandler::SetupMultitrackVideo( OBSStartStreaming, this); stopStreaming.Connect(signal_handler, "stop", OBSStopStreaming, this); - return true; + return continuation(true); }; - auto firstFuture = CreateFuture().then( - QThreadPool::globalInstance(), + QThreadPool::globalInstance()->start( [=, multitrackVideo = multitrackVideo.get(), service_name = std::string{service_name}, service = OBSService{service}, - stream_dump_config = std::move(stream_dump_config)]() - -> std::optional { + stream_dump_config = OBSData{stream_dump_config}, + start_streaming_guard = start_streaming_guard]() mutable { + std::optional error; try { multitrackVideo->PrepareStreaming( main, service_name.c_str(), service, @@ -2805,16 +2840,16 @@ FutureHolder> BasicOutputHandler::SetupMultitrackVideo( maximum_video_tracks, custom_config, stream_dump_config, main_audio_mixer, vod_track_mixer); - } catch (const MultitrackVideoError &error) { - return error; + } catch (const MultitrackVideoError &error_) { + error.emplace(error_); } - return std::nullopt; - }); - auto secondFuture = firstFuture.then(main, continue_on_main_thread); + QMetaObject::invokeMethod(main, [=] { + continue_on_main_thread(error); + }); + }); - return {[=]() mutable { firstFuture.cancel(); }, - PreventFutureDeadlock(secondFuture)}; + return start_streaming_guard->GetFuture(); } OBSDataAutoRelease BasicOutputHandler::GenerateMultitrackVideoStreamDumpConfig() diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index abf40d66ff4d87..870f720a225ae3 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -1,15 +1,15 @@ #pragma once +#include #include #include -#include - -#include "qt-helpers.hpp" #include "multitrack-video-output.hpp" class OBSBasic; +using SetupStreamingContinuation_t = std::function; + struct BasicOutputHandler { OBSOutputAutoRelease fileOutput; OBSOutputAutoRelease streamOutput; @@ -63,7 +63,9 @@ struct BasicOutputHandler { virtual ~BasicOutputHandler(){}; - virtual FutureHolder SetupStreaming(obs_service_t *service) = 0; + virtual std::shared_future + SetupStreaming(obs_service_t *service, + SetupStreamingContinuation_t continuation) = 0; virtual bool StartStreaming(obs_service_t *service) = 0; virtual bool StartRecording() = 0; virtual bool StartReplayBuffer() { return false; } @@ -98,9 +100,10 @@ struct BasicOutputHandler { bool overwrite, const char *format, bool ffmpeg); - FutureHolder> SetupMultitrackVideo( + std::shared_future SetupMultitrackVideo( obs_service_t *service, std::string audio_encoder_id, - size_t main_audio_mixer, std::optional vod_track_mixer); + size_t main_audio_mixer, std::optional vod_track_mixer, + std::function)> continuation); OBSDataAutoRelease GenerateMultitrackVideoStreamDumpConfig(); }; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 36015b6131beb4..20e2d01a7b26f0 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -2038,7 +2038,9 @@ void OBSBasic::ResetOutputs() bool advOut = astrcmpi(mode, "Advanced") == 0; if ((!outputHandler || !outputHandler->Active()) && - startStreamingFuture.future.isFinished()) { + (!setupStreamingGuard.valid() || + setupStreamingGuard.wait_for(std::chrono::seconds{0}) == + std::future_status::ready)) { outputHandler.reset(); outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); @@ -5170,18 +5172,11 @@ void OBSBasic::ClearSceneData() void OBSBasic::closeEvent(QCloseEvent *event) { - if (!startStreamingFuture.future.isFinished() && - !startStreamingFuture.future.isCanceled()) { - startStreamingFuture.future.onCanceled( - this, [basic = QPointer{this}] { - if (basic) - basic->close(); - }); - startStreamingFuture.cancelAll(); - event->ignore(); - return; - } else if (startStreamingFuture.future.isCanceled() && - !startStreamingFuture.future.isFinished()) { + /* Wait for multitrack video stream to start/finish processing in the background */ + if (setupStreamingGuard.valid() && + setupStreamingGuard.wait_for(std::chrono::seconds{0}) != + std::future_status::ready) { + QTimer::singleShot(1000, this, &OBSBasic::close); event->ignore(); return; } @@ -7153,9 +7148,8 @@ void OBSBasic::StartStreaming() #endif }; - auto holder = outputHandler->SetupStreaming(service); - auto future = holder.future.then(this, finish_stream_setup); - startStreamingFuture = {holder.cancelAll, future}; + setupStreamingGuard = + outputHandler->SetupStreaming(service, finish_stream_setup); } void OBSBasic::BroadcastButtonClicked() diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index f68075ba3013ee..eb55d2f051a2a3 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -23,10 +23,10 @@ #include #include #include -#include #include #include #include +#include #include "window-main.hpp" #include "window-basic-interaction.hpp" #include "window-basic-vcam.hpp" @@ -43,7 +43,6 @@ #include "auth-base.hpp" #include "log-viewer.hpp" #include "undo-stack-obs.hpp" -#include "qt-helpers.hpp" #include @@ -286,7 +285,7 @@ class OBSBasic : public OBSMainWindow { OBSService service; std::unique_ptr outputHandler; - FutureHolder startStreamingFuture; + std::shared_future setupStreamingGuard; bool streamingStopping = false; bool recordingStopping = false; bool replayBufferStopping = false;