Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion obs-studio-server/source/osn-source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ void osn::Source::Release(void *data, const int64_t id, const std::vector<ipc::v
PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Source reference is not valid.");
}

if (obs_source_get_type(src) == OBS_SOURCE_TYPE_TRANSITION) {
if (obs_source_get_type(src) == OBS_SOURCE_TYPE_TRANSITION || obs_source_get_type(src) == OBS_SOURCE_TYPE_FILTER) {
obs_source_release(src);
} else if (obs_source_get_type(src) == OBS_SOURCE_TYPE_SCENE) {
blog(LOG_INFO, "Releasing scene %s", obs_source_get_name(src));
Expand Down
88 changes: 88 additions & 0 deletions obs-studio-server/tests/test-osn-source.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <catch2/catch_test_macros.hpp>
#include "nodeobs_api.h"
#include "osn-error.hpp"
#include "osn-filter.hpp"
#include "osn-input.hpp"
#include "osn-scene.hpp"
#include "osn-source.hpp"
Expand Down Expand Up @@ -50,6 +51,39 @@ obs_source_info makeTestSourceInfo()
return info;
}

// A video filter can only be attached to a source that also outputs video.
constexpr char TEST_VIDEO_SOURCE_ID[] = "filter_release_test_video_source";
constexpr char TEST_VIDEO_FILTER_ID[] = "filter_release_test_video_filter";

obs_source_info makeTestVideoSourceInfo()
{
obs_source_info info = makeTestSourceInfo();
info.id = TEST_VIDEO_SOURCE_ID;
info.output_flags = OBS_SOURCE_VIDEO;
return info;
}

void *testFilterCreate(obs_data_t *settings, obs_source_t *source)
{
obs_source_update(source, settings);
return source;
}

void testFilterUpdate(void *, obs_data_t *) {}

obs_source_info makeTestVideoFilterInfo()
{
obs_source_info info{};
info.id = TEST_VIDEO_FILTER_ID;
info.type = OBS_SOURCE_TYPE_FILTER;
info.output_flags = OBS_SOURCE_VIDEO;
info.get_name = testSourceGetName;
info.create = testFilterCreate;
info.destroy = testSourceDestroy;
info.update = testFilterUpdate;
return info;
}

// These tests only need the OBS core and its deferred-destruction queue.
class ObsCoreSetup {
public:
Expand Down Expand Up @@ -308,3 +342,57 @@ TEST_CASE("Source manager safely promotes references during deferred destruction
CHECK(manager.size() == sourceCount);
}
}

TEST_CASE("Releasing a filter after attaching it keeps the filter active")
{
ObsCoreSetup setupOBS;
static const obs_source_info testVideoSourceInfo = makeTestVideoSourceInfo();
static const obs_source_info testVideoFilterInfo = makeTestVideoFilterInfo();
obs_register_source(&testVideoSourceInfo);
obs_register_source(&testVideoFilterInfo);
auto &manager = osn::Source::Manager::GetInstance();
const auto sourceCount = manager.size();

OBSSourceAutoRelease input = obs_source_create_private(TEST_VIDEO_SOURCE_ID, "filtered input", nullptr);
REQUIRE(input != nullptr);
const uint64_t inputId = manager.allocate(input);
osn::Source::attach_source_signals(input);
REQUIRE(inputId != UINT64_MAX);

// Follow the sequence used by Streamlabs Desktop when it adds a filter.
std::vector<ipc::value> response;
osn::Filter::Create(nullptr, 0, {ipc::value(TEST_VIDEO_FILTER_ID), ipc::value("released filter")}, response);
REQUIRE(response.size() >= 2);
REQUIRE((ErrorCode)response[0].value_union.ui64 == ErrorCode::Ok);
const uint64_t filterId = response[1].value_union.ui64;

response.clear();
osn::Input::AddFilter(nullptr, 0, {ipc::value(inputId), ipc::value(filterId)}, response);
REQUIRE(!response.empty());
REQUIRE((ErrorCode)response[0].value_union.ui64 == ErrorCode::Ok);

response.clear();
osn::Source::Release(nullptr, 0, {ipc::value(filterId)}, response);
REQUIRE(!response.empty());
CHECK((ErrorCode)response[0].value_union.ui64 == ErrorCode::Ok);

{
// The input now holds the only reference. The filter must not be marked
// removed since libobs skips removed sources when ticking.
OBSSourceAutoRelease filter = manager.findAndRef(filterId);
REQUIRE(filter != nullptr);
CHECK(!obs_source_removed(filter));
}

response.clear();
osn::Input::RemoveFilter(nullptr, 0, {ipc::value(inputId), ipc::value(filterId)}, response);
REQUIRE(!response.empty());
CHECK((ErrorCode)response[0].value_union.ui64 == ErrorCode::Ok);
obs_wait_for_destroy_queue();
CHECK(!manager.findAndRef(filterId));
CHECK(manager.size() == sourceCount + 1);

input = nullptr;
obs_wait_for_destroy_queue();
CHECK(manager.size() == sourceCount);
}
Loading