diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16be4349e..c2fe9d9e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ env: SLGenerator: Visual Studio 17 2022 SLDistributeDirectory: distribute SLFullDistributePath: "streamlabs-build.app/distribute" # The .app extension is required to run macOS tests correctly. - LibOBSVersion: 31.1.2sl19 + LibOBSVersion: 31.1.2sl19b5 PACKAGE_NAME: osn jobs: diff --git a/obs-studio-client/source/advanced-recording.cpp b/obs-studio-client/source/advanced-recording.cpp index 00177ec76..b466ec497 100644 --- a/obs-studio-client/source/advanced-recording.cpp +++ b/obs-studio-client/source/advanced-recording.cpp @@ -19,6 +19,7 @@ #include "advanced-recording.hpp" #include "utility.hpp" #include "advanced-streaming.hpp" +#include "enhanced-broadcasting-advanced-streaming.hpp" Napi::FunctionReference osn::AdvancedRecording::constructor; @@ -310,22 +311,44 @@ void osn::AdvancedRecording::SetStreaming(const Napi::CallbackInfo &info, const return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a AdvancedStreaming").ThrowAsJavaScriptException(); + return; + } - osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) { + osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingAdvancedStreaming::constructor.Value())) { + osn::EnhancedBroadcastingAdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a AdvancedStreaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; if (!streamingRef.IsEmpty()) streamingRef.Reset(); streamingRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/obs-studio-client/source/advanced-replay-buffer.cpp b/obs-studio-client/source/advanced-replay-buffer.cpp index 0bc628e39..94f34beeb 100644 --- a/obs-studio-client/source/advanced-replay-buffer.cpp +++ b/obs-studio-client/source/advanced-replay-buffer.cpp @@ -21,6 +21,7 @@ #include "audio-encoder.hpp" #include "advanced-streaming.hpp" #include "advanced-recording.hpp" +#include "enhanced-broadcasting-advanced-streaming.hpp" Napi::FunctionReference osn::AdvancedReplayBuffer::constructor; @@ -209,18 +210,40 @@ void osn::AdvancedReplayBuffer::SetStreaming(const Napi::CallbackInfo &info, con return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a valid Streaming").ThrowAsJavaScriptException(); + return; + } - osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) { + osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingAdvancedStreaming::constructor.Value())) { + osn::EnhancedBroadcastingAdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a valid Streaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; @@ -272,4 +295,4 @@ void osn::AdvancedReplayBuffer::SetRecording(const Napi::CallbackInfo &info, con if (!parentOutputRef.IsEmpty()) parentOutputRef.Reset(); parentOutputRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/obs-studio-client/source/simple-recording.cpp b/obs-studio-client/source/simple-recording.cpp index fc9223320..059a73e8d 100644 --- a/obs-studio-client/source/simple-recording.cpp +++ b/obs-studio-client/source/simple-recording.cpp @@ -24,6 +24,7 @@ #include "reconnect.hpp" #include "network.hpp" #include "simple-streaming.hpp" +#include "enhanced-broadcasting-simple-streaming.hpp" Napi::FunctionReference osn::SimpleRecording::constructor; @@ -282,22 +283,44 @@ void osn::SimpleRecording::SetStreaming(const Napi::CallbackInfo &info, const Na return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) { + osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingSimpleStreaming::constructor.Value())) { + osn::EnhancedBroadcastingSimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; if (!streamingRef.IsEmpty()) streamingRef.Reset(); streamingRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/obs-studio-client/source/simple-replay-buffer.cpp b/obs-studio-client/source/simple-replay-buffer.cpp index 5c987257d..52f07cd91 100644 --- a/obs-studio-client/source/simple-replay-buffer.cpp +++ b/obs-studio-client/source/simple-replay-buffer.cpp @@ -21,6 +21,7 @@ #include "audio-encoder.hpp" #include "simple-streaming.hpp" #include "simple-recording.hpp" +#include "enhanced-broadcasting-simple-streaming.hpp" Napi::FunctionReference osn::SimpleReplayBuffer::constructor; @@ -184,18 +185,40 @@ void osn::SimpleReplayBuffer::SetStreaming(const Napi::CallbackInfo &info, const return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) { + osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingSimpleStreaming::constructor.Value())) { + osn::EnhancedBroadcastingSimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; @@ -247,4 +270,4 @@ void osn::SimpleReplayBuffer::SetRecording(const Napi::CallbackInfo &info, const if (!parentOutputRef.IsEmpty()) parentOutputRef.Reset(); parentOutputRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index 69c75b905..216b8d485 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -269,6 +269,17 @@ void osn::IAdvancedRecording::Start(void *data, const int64_t id, const std::vec PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); } + const char *encoderId = obs_encoder_get_id(recording->videoEncoder); + const bool encoderIsOwned = !recording->useStreamEncoders || obs_get_multiple_rendering(); + const bool encoderSupportsScaling = encoderId && strcmp(encoderId, ENCODER_NVENC_H264_TEX) != 0; + if (encoderIsOwned && encoderSupportsScaling) { + const bool applyRescale = recording->rescaling && recording->outputWidth > 0 && recording->outputHeight > 0; + const uint32_t cx = applyRescale ? recording->outputWidth : 0; + const uint32_t cy = applyRescale ? recording->outputHeight : 0; + obs_encoder_set_scaled_size(recording->videoEncoder, cx, cy); + obs_encoder_set_gpu_scale_type(recording->videoEncoder, applyRescale ? OBS_SCALE_BILINEAR : OBS_SCALE_DISABLE); + } + obs_output_set_video_encoder(recording->GetOutput(), recording->videoEncoder); std::string path = recording->path; diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index 250b5d0be..b91f8b0c7 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -68,7 +68,7 @@ const std::vector osn::EncoderUtils::videoEn APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, // get_simple_output_encoder had Apple HEVC so add it here, never used with an advanced name but follow the pattern of M1 above {"Apple VT HEVC Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "Hardware (Apple, HEVC)", SIMPLE_ENCODER_APPLE_HEVC, - APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, true, true, false, PRESET_APPLE, FAMILY_APPLE}, // AMD HW H.264 {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, "", true, true, true, false, true, false, PRESET_AMD, FAMILY_AMD}, @@ -212,6 +212,9 @@ bool osn::EncoderUtils::isEncoderCompatible(std::string encoderName, obs_service } if (!containerSupportsCodec(container, codec)) return false; + if (container == "flv" && videoEncoderOptions[checkIndex].family == FAMILY_APPLE) { + return false; + } } return true; diff --git a/obs-studio-server/source/util-osx-impl.mm b/obs-studio-server/source/util-osx-impl.mm index bd08a39c4..352b727b5 100644 --- a/obs-studio-server/source/util-osx-impl.mm +++ b/obs-studio-server/source/util-osx-impl.mm @@ -62,7 +62,7 @@ @implementation UtilImplObj -UtilObjCInt::UtilObjCInt(void) : self(NULL) {} +UtilObjCInt::UtilObjCInt(void) : self(NULL), state(EState::Idle), worker(nullptr) {} UtilObjCInt::~UtilObjCInt(void) { diff --git a/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts b/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts index dd963b931..b2a3c1b57 100644 --- a/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts +++ b/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts @@ -120,6 +120,34 @@ describe(testName, () => { } }); + it('Can be used as the parent stream for advanced recording', function() { + const stream = osn.EnhancedBroadcastingAdvancedStreamingFactory.create(); + const recording = osn.AdvancedRecordingFactory.create(); + + try { + recording.streaming = stream; + + expect(recording.streaming).to.equal(stream); + } finally { + osn.AdvancedRecordingFactory.destroy(recording); + osn.EnhancedBroadcastingAdvancedStreamingFactory.destroy(stream); + } + }); + + it('Can be used as the parent stream for advanced replay buffer', function() { + const stream = osn.EnhancedBroadcastingAdvancedStreamingFactory.create(); + const replayBuffer = osn.AdvancedReplayBufferFactory.create(); + + try { + replayBuffer.streaming = stream; + + expect(replayBuffer.streaming).to.equal(stream); + } finally { + osn.AdvancedReplayBufferFactory.destroy(replayBuffer); + osn.EnhancedBroadcastingAdvancedStreamingFactory.destroy(stream); + } + }); + it('Enhanced Broadcasting Advanced Streaming Single Canvas', async function() { if (obs.isDarwin()) { this.skip(); diff --git a/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts b/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts index 0b9527603..65f1a2b36 100644 --- a/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts +++ b/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts @@ -69,6 +69,34 @@ describe(testName, () => { hasTestFailed = (await obs.finalizeRetryableTest(this)) || hasTestFailed; }); + it('Can be used as the parent stream for simple recording', function() { + const stream = osn.EnhancedBroadcastingSimpleStreamingFactory.create(); + const recording = osn.SimpleRecordingFactory.create(); + + try { + recording.streaming = stream; + + expect(recording.streaming).to.equal(stream); + } finally { + osn.SimpleRecordingFactory.destroy(recording); + osn.EnhancedBroadcastingSimpleStreamingFactory.destroy(stream); + } + }); + + it('Can be used as the parent stream for simple replay buffer', function() { + const stream = osn.EnhancedBroadcastingSimpleStreamingFactory.create(); + const replayBuffer = osn.SimpleReplayBufferFactory.create(); + + try { + replayBuffer.streaming = stream; + + expect(replayBuffer.streaming).to.equal(stream); + } finally { + osn.SimpleReplayBufferFactory.destroy(replayBuffer); + osn.EnhancedBroadcastingSimpleStreamingFactory.destroy(stream); + } + }); + it('Enhanced Broadcasting Simple Streaming honors stream delay', async function() { if (obs.isDarwin()) { this.skip();