Skip to content

Commit 7f3d51b

Browse files
authored
MacOS getDevice() optimization, set CI env var, & add getVideoDevices() test (#1691)
* Revert "settings: revert 01b09d9 / fix device list (#1609)" This reverts commit 4434fda. * set target 12.0, add video test, replay-buffer test skip CI, getDevices() * rename legacy getDevices() -> getDevicesUsingDummySource(). Adding more error checking * set CI flag for MacOS test runner so it can use obs.isCI()
1 parent 7df3538 commit 7f3d51b

9 files changed

Lines changed: 97 additions & 39 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ jobs:
151151
OSN_ACCESS_KEY_ID: ${{secrets.AWS_RELEASE_ACCESS_KEY_ID}}
152152
OSN_SECRET_ACCESS_KEY: ${{secrets.AWS_RELEASE_SECRET_ACCESS_KEY}}
153153
RELEASE_NAME: ${{matrix.ReleaseName}}
154+
CI: true
154155
# Run even after test failures so the PR still gets the flaky summary.
155156
- name: Publish flaky test check
156157
if: ${{ always() }}

CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ ENDIF()
2929

3030
# Comfigure macos architecture and min version
3131
iF(APPLE)
32-
set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=arm64] "11.0")
33-
set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=x86_64] "10.15")
32+
set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=arm64] "12.0")
33+
set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=x86_64] "12.0")
3434
if (NOT CMAKE_OSX_ARCHITECTURES)
3535
set(CMAKE_OSX_ARCHITECTURES "${CMAKE_HOST_SYSTEM_PROCESSOR}")
3636
endif()
3737
if ("${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64")
38-
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0")
38+
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
3939
else()
40-
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15")
40+
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
4141
endif()
4242
# See CMake issue # 21854
4343
# https://gitlab.kitware.com/cmake/cmake/-/issues/21854

ci/configure-osn-osx.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ cd ..
88
[ -n "${ARCHITECTURE}" ] && CMAKE_OSX_ARCHITECTURES_PARAM="-DCMAKE_OSX_ARCHITECTURES=${ARCHITECTURE}" || CMAKE_OSX_ARCHITECTURES_PARAM=""
99

1010
cmake .. \
11-
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
11+
-DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 \
1212
-DCMAKE_INSTALL_PREFIX=$PWD/../${SLFullDistributePath}/${InstallPath} \
1313
-DSTREAMLABS_BUILD=OFF \
1414
-DNODEJS_NAME=${RuntimeName} \

obs-studio-server/source/nodeobs_settings.cpp

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "osn-error.hpp"
2121
#include "nodeobs_api.h"
2222
#include "shared.hpp"
23+
#include <sstream>
2324
#include "memory-manager.h"
2425
#include "osn-video.hpp"
2526
#include "osn-encoders.hpp"
@@ -3864,46 +3865,87 @@ void OBS_settings::saveGenericSettings(std::vector<SubCategory> genericSettings,
38643865
config_save_safe(config, "tmp", nullptr);
38653866
}
38663867

3867-
void getDevices(const char *source_id, const char *property_name, std::vector<ipc::value> &rval)
3868+
// Gets the devices using a "dummy" source object that does not exist. For most
3869+
// plugins this is fine but should be avoided for plugins like mac-coreaudio
3870+
// because it will create a reconnect_thread. Triggers the following callbacks
3871+
// on the obs_source_info: get_properties, get_defaults, and get_updates.
3872+
void getDevicesUsingDummySource(const char *source_id, const char *property_name, std::vector<ipc::value> &rval)
38683873
{
3869-
auto settings = obs_get_source_defaults(source_id);
3870-
if (!settings)
3871-
return;
3874+
OBSDataAutoRelease settings = obs_get_source_defaults(source_id);
3875+
if (!settings) {
3876+
std::ostringstream ss;
3877+
ss << "Could not get settings for source id: " << source_id;
3878+
PRETTY_ERROR_RETURN(ErrorCode::Error, ss.str());
3879+
}
38723880

38733881
const char *dummy_device_name = "does_not_exist";
38743882
obs_data_set_string(settings, property_name, dummy_device_name);
3875-
if (strcmp(source_id, "dshow_input") == 0) {
3876-
obs_data_set_string(settings, "video_device_id", dummy_device_name);
3877-
obs_data_set_string(settings, "audio_device_id", dummy_device_name);
3878-
}
38793883

38803884
// Create a dummy source so that the "device" property can be init
3881-
auto dummy_source = obs_source_create(source_id, dummy_device_name, settings, nullptr);
3885+
OBSSourceAutoRelease dummy_source = obs_source_create(source_id, dummy_device_name, settings, nullptr);
38823886
if (!dummy_source) {
3883-
obs_data_release(settings);
3884-
return;
3887+
std::ostringstream ss;
3888+
ss << "Could not create source: " << source_id;
3889+
PRETTY_ERROR_RETURN(ErrorCode::Error, ss.str());
38853890
}
38863891

38873892
auto props = obs_source_properties(dummy_source);
38883893
if (!props) {
3889-
obs_source_release(dummy_source);
3890-
obs_data_release(settings);
3891-
return;
3894+
std::ostringstream ss;
3895+
ss << "Could not get source properties for source id: " << source_id;
3896+
PRETTY_ERROR_RETURN(ErrorCode::Error, ss.str());
38923897
}
38933898

38943899
auto prop = obs_properties_get(props, property_name);
38953900
if (!prop) {
38963901
obs_properties_destroy(props);
3897-
obs_source_release(dummy_source);
3898-
obs_data_release(settings);
3899-
return;
3902+
std::ostringstream ss;
3903+
ss << "Could not get source property " << property_name << " for source id: " << source_id;
3904+
PRETTY_ERROR_RETURN(ErrorCode::Error, ss.str());
39003905
}
39013906

3907+
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
39023908
size_t items = obs_property_list_item_count(prop);
3903-
if (rval.size() > 1)
3904-
rval[1].value_union.ui64 += items;
3905-
else
3906-
rval.push_back(ipc::value((uint64_t)items));
3909+
rval.push_back(ipc::value((uint64_t)items));
3910+
3911+
for (size_t idx = 0; idx < items; idx++) {
3912+
const char *description = obs_property_list_item_name(prop, idx);
3913+
const char *device_id = obs_property_list_item_string(prop, idx);
3914+
3915+
if (!description || !strcmp(description, "") || !device_id || !strcmp(device_id, "")) {
3916+
rval[1].value_union.ui64--;
3917+
continue;
3918+
}
3919+
3920+
rval.push_back(ipc::value(description));
3921+
rval.push_back(ipc::value(device_id));
3922+
}
3923+
3924+
obs_properties_destroy(props);
3925+
}
3926+
3927+
// Gets the devices from the source using a property list without triggering
3928+
// obs_source_info.get_updates callback.
3929+
void getDevices(const char *source_id, const char *property_name, std::vector<ipc::value> &rval)
3930+
{
3931+
auto props = obs_get_source_properties(source_id);
3932+
if (!props) {
3933+
std::ostringstream ss;
3934+
ss << "Could not get source properties for source id: " << source_id;
3935+
PRETTY_ERROR_RETURN(ErrorCode::Error, ss.str());
3936+
}
3937+
3938+
auto prop = obs_properties_get(props, property_name);
3939+
if (!prop) {
3940+
obs_properties_destroy(props);
3941+
std::ostringstream ss;
3942+
ss << "Could not get source property " << property_name << " for source id: " << source_id;
3943+
PRETTY_ERROR_RETURN(ErrorCode::Error, ss.str());
3944+
}
3945+
3946+
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
3947+
size_t items = obs_property_list_item_count(prop);
3948+
rval.push_back(ipc::value((uint64_t)items));
39073949

39083950
for (size_t idx = 0; idx < items; idx++) {
39093951
const char *description = obs_property_list_item_name(prop, idx);
@@ -3919,8 +3961,6 @@ void getDevices(const char *source_id, const char *property_name, std::vector<ip
39193961
}
39203962

39213963
obs_properties_destroy(props);
3922-
obs_data_release(settings);
3923-
obs_source_release(dummy_source);
39243964
}
39253965

39263966
#ifdef WIN32
@@ -4057,9 +4097,8 @@ void enumAudioDevices(std::vector<ipc::value> &rval, EDataFlow dataFlow)
40574097

40584098
void OBS_settings::OBS_settings_getInputAudioDevices(void *data, const int64_t id, const std::vector<ipc::value> &args, std::vector<ipc::value> &rval)
40594099
{
4060-
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
4061-
40624100
#ifdef WIN32
4101+
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
40634102
rval.push_back(ipc::value((uint32_t)1));
40644103
rval.push_back(ipc::value("Default"));
40654104
rval.push_back(ipc::value("default"));
@@ -4074,9 +4113,8 @@ void OBS_settings::OBS_settings_getInputAudioDevices(void *data, const int64_t i
40744113

40754114
void OBS_settings::OBS_settings_getOutputAudioDevices(void *data, const int64_t id, const std::vector<ipc::value> &args, std::vector<ipc::value> &rval)
40764115
{
4077-
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
4078-
40794116
#ifdef WIN32
4117+
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
40804118
rval.push_back(ipc::value((uint32_t)1));
40814119
rval.push_back(ipc::value("Default"));
40824120
rval.push_back(ipc::value("default"));
@@ -4091,15 +4129,12 @@ void OBS_settings::OBS_settings_getOutputAudioDevices(void *data, const int64_t
40914129

40924130
void OBS_settings::OBS_settings_getVideoDevices(void *data, const int64_t id, const std::vector<ipc::value> &args, std::vector<ipc::value> &rval)
40934131
{
4094-
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
4095-
40964132
#ifdef WIN32
4133+
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
40974134
rval.push_back(ipc::value((uint32_t)0));
40984135
enumVideoDevices(rval);
40994136
#elif __APPLE__
4100-
const char *source_id = "macos_avcapture";
4101-
const char *property_name = "device";
4102-
getDevices(source_id, property_name, rval);
4137+
getDevicesUsingDummySource("macos_avcapture", "device", rval);
41034138
#endif
41044139

41054140
AUTO_DEBUG;

tests/osn-tests/src/test_osn_advanced_replayBuffer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ describe(testName, () => {
131131
});
132132

133133
it('Start advanced replay buffer - Use Recording', async function() {
134+
if (obs.isDarwin() && obs.isCI()) {
135+
this.skip();
136+
}
134137
replayBuffer = osn.AdvancedReplayBufferFactory.create();
135138
replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData');
136139
replayBuffer.format = osn.ERecordingFormat.MP4;
@@ -258,6 +261,9 @@ describe(testName, () => {
258261
});
259262

260263
it('Start advanced replay buffer - Use Stream through Recording', async function() {
264+
if (obs.isDarwin() && obs.isCI()) {
265+
this.skip();
266+
}
261267
replayBuffer = osn.AdvancedReplayBufferFactory.create();
262268
replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData');
263269
replayBuffer.format = osn.ERecordingFormat.MP4;

tests/osn-tests/src/test_osn_audio.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ describe(testName, () => {
7070
foundDefaultDevice = true;
7171
}
7272
}
73-
if (!obs.isDarwin()) { // On virtual mac the default output device is not included in the list of output devices
73+
// On Darwin CI, skip the default-device assertion only when there are no audio devices.
74+
// mac-coreaudio only returns the default device when there is at least one audio device, but on CI there may be no audio devices, so the default device is not returned.
75+
const shouldSkipDefaultDeviceAssertion = obs.isDarwin() && obs.isCI() && devices.length === 0;
76+
if (!shouldSkipDefaultDeviceAssertion) {
7477
expect(foundDefaultDevice).to.equal(true, GetErrorMessage(ETestErrorMsg.DefaultDeviceNotFound));
7578
}
7679
});

tests/osn-tests/src/test_osn_video.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,15 @@ describe(testName, () => {
191191

192192
context.destroy();
193193
});
194+
195+
it('Get video capture devices', function() {
196+
const devices = osn.NodeObs.OBS_settings_getVideoDevices();
197+
expect(devices).to.not.equal(undefined, GetErrorMessage(ETestErrorMsg.VideoDevices));
198+
expect(Array.isArray(devices)).to.equal(true, GetErrorMessage(ETestErrorMsg.VideoDevicesIsArray));
199+
for (const device of devices) {
200+
expect(device).to.have.property('id');
201+
expect(device).to.have.property('description');
202+
logInfo(testName, `Video Capture Device Found: ${device.description} with id: ${device.id}`);
203+
}
204+
});
194205
});

tests/osn-tests/util/error_messages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ export const enum ETestErrorMsg {
203203
AudioDevices = 'Failed to get audio devices',
204204
AudioDevicesIsArray = 'Returned audio devices value is not an array',
205205
DefaultDeviceNotFound = 'Did not find the default audio device in the list of audio devices',
206+
VideoDevices = 'Failed to get video devices',
207+
VideoDevicesIsArray = 'Returned video devices value is not an array',
206208
}
207209

208210
export function GetErrorMessage(message: string, value1?: string, value2?: string, value3?: string): string {

tests/osn-tests/util/obs_handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ export class OBSHandler {
553553

554554
isDarwin()
555555
{
556-
// Wrapped this in a function- just incase we want to add more conditions later or disable only within the build agent.
556+
// Wrapped this in a function- just in case we want to add more conditions later or disable only within the build agent.
557557
return this.os === 'darwin';
558558
}
559559

0 commit comments

Comments
 (0)