|
@@ -1,3 +1,6 @@
|
|
|
+@import CoreMediaIO;
|
|
|
+@import SystemExtensions;
|
|
|
+
|
|
|
#include <obs-module.h>
|
|
|
#include "OBSDALMachServer.h"
|
|
|
#include "Defines.h"
|
|
@@ -9,33 +12,164 @@ MODULE_EXPORT const char *obs_module_description(void)
|
|
|
return "macOS virtual webcam output";
|
|
|
}
|
|
|
|
|
|
+NSString *const OBSDalDestination = @"/Library/CoreMediaIO/Plug-Ins/DAL";
|
|
|
+
|
|
|
+static bool cmio_extension_supported()
|
|
|
+{
|
|
|
+ if (@available(macOS 13.0, *)) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
struct virtualcam_data {
|
|
|
obs_output_t *output;
|
|
|
obs_video_info videoInfo;
|
|
|
CVPixelBufferPoolRef pool;
|
|
|
+
|
|
|
+ // CMIO Extension (available with macOS 13)
|
|
|
+ CMSimpleQueueRef queue;
|
|
|
+ CMIODeviceID deviceID;
|
|
|
+ CMIOStreamID streamID;
|
|
|
+ CMFormatDescriptionRef formatDescription;
|
|
|
+ id extensionDelegate;
|
|
|
+
|
|
|
+ // Legacy DAL (deprecated since macOS 12.3)
|
|
|
OBSDALMachServer *machServer;
|
|
|
};
|
|
|
|
|
|
-static bool check_dal_plugin()
|
|
|
+@interface SystemExtensionActivationDelegate
|
|
|
+ : NSObject <OSSystemExtensionRequestDelegate> {
|
|
|
+@private
|
|
|
+ struct virtualcam_data *_vcam;
|
|
|
+}
|
|
|
+
|
|
|
+@property (getter=isInstalled) BOOL installed;
|
|
|
+@property NSString *lastErrorMessage;
|
|
|
+- (instancetype)init __unavailable;
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation SystemExtensionActivationDelegate
|
|
|
+
|
|
|
+- (id)initWithVcam:(virtualcam_data *)vcam
|
|
|
+{
|
|
|
+ self = [super init];
|
|
|
+
|
|
|
+ if (self) {
|
|
|
+ _vcam = vcam;
|
|
|
+ _installed = NO;
|
|
|
+ }
|
|
|
+
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+- (OSSystemExtensionReplacementAction)
|
|
|
+ request:(nonnull OSSystemExtensionRequest *)request
|
|
|
+ actionForReplacingExtension:
|
|
|
+ (nonnull OSSystemExtensionProperties *)existing
|
|
|
+ withExtension:(nonnull OSSystemExtensionProperties *)ext
|
|
|
+{
|
|
|
+ NSString *extVersion =
|
|
|
+ [NSString stringWithFormat:@"%@.%@", [ext bundleShortVersion],
|
|
|
+ [ext bundleVersion]];
|
|
|
+ NSString *existingVersion = [NSString
|
|
|
+ stringWithFormat:@"%@.%@", [existing bundleShortVersion],
|
|
|
+ [existing bundleVersion]];
|
|
|
+
|
|
|
+ if ([extVersion compare:existingVersion
|
|
|
+ options:NSNumericSearch] == NSOrderedDescending) {
|
|
|
+ return OSSystemExtensionReplacementActionReplace;
|
|
|
+ } else {
|
|
|
+ return OSSystemExtensionReplacementActionCancel;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)request:(nonnull OSSystemExtensionRequest *)request
|
|
|
+ didFailWithError:(nonnull NSError *)error
|
|
|
+{
|
|
|
+ NSString *errorMessage;
|
|
|
+ int severity;
|
|
|
+
|
|
|
+ switch (error.code) {
|
|
|
+ case OSSystemExtensionErrorRequestCanceled:
|
|
|
+ errorMessage =
|
|
|
+ @"macOS Camera Extension installation request cancelled.";
|
|
|
+ severity = LOG_INFO;
|
|
|
+ break;
|
|
|
+ case OSSystemExtensionErrorUnsupportedParentBundleLocation:
|
|
|
+ self.lastErrorMessage = [NSString
|
|
|
+ stringWithUTF8String:
|
|
|
+ obs_module_text(
|
|
|
+ "Error.SystemExtension.WrongLocation")];
|
|
|
+ errorMessage = self.lastErrorMessage;
|
|
|
+ severity = LOG_WARNING;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ self.lastErrorMessage = error.localizedDescription;
|
|
|
+ errorMessage = [NSString
|
|
|
+ stringWithFormat:
|
|
|
+ @"OSSystemExtensionErrorCode %ld (\"%s\")",
|
|
|
+ error.code,
|
|
|
+ error.localizedDescription.UTF8String];
|
|
|
+ severity = LOG_ERROR;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ blog(severity, "mac-camera-extension error: %s",
|
|
|
+ errorMessage.UTF8String);
|
|
|
+}
|
|
|
+
|
|
|
+- (void)request:(nonnull OSSystemExtensionRequest *)request
|
|
|
+ didFinishWithResult:(OSSystemExtensionRequestResult)result
|
|
|
+{
|
|
|
+ self.installed = YES;
|
|
|
+ blog(LOG_INFO, "macOS Camera Extension activated successfully.");
|
|
|
+}
|
|
|
+
|
|
|
+- (void)requestNeedsUserApproval:(nonnull OSSystemExtensionRequest *)request
|
|
|
+{
|
|
|
+ self.installed = NO;
|
|
|
+ blog(LOG_INFO, "macOS Camera Extension user approval required.");
|
|
|
+}
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+static void install_cmio_system_extension(struct virtualcam_data *vcam)
|
|
|
+{
|
|
|
+ OSSystemExtensionRequest *request = [OSSystemExtensionRequest
|
|
|
+ activationRequestForExtension:
|
|
|
+ @"com.obsproject.obs-studio.mac-camera-extension"
|
|
|
+ queue:dispatch_get_main_queue()];
|
|
|
+ request.delegate = vcam->extensionDelegate;
|
|
|
+
|
|
|
+ [[OSSystemExtensionManager sharedManager] submitRequest:request];
|
|
|
+}
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ OBSDalPluginNotInstalled,
|
|
|
+ OBSDalPluginInstalled,
|
|
|
+ OBSDalPluginNeedsUpdate
|
|
|
+} dal_plugin_status;
|
|
|
+
|
|
|
+static dal_plugin_status check_dal_plugin()
|
|
|
{
|
|
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
|
|
|
|
- NSString *dalPluginDestinationPath =
|
|
|
- @"/Library/CoreMediaIO/Plug-Ins/DAL/";
|
|
|
- NSString *dalPluginFileName =
|
|
|
- @"/Library/CoreMediaIO/Plug-Ins/DAL/obs-mac-virtualcam.plugin";
|
|
|
+ NSString *dalPluginFileName = [OBSDalDestination
|
|
|
+ stringByAppendingString:@"/obs-mac-virtualcam.plugin"];
|
|
|
|
|
|
- BOOL dalPluginDirExists =
|
|
|
- [fileManager fileExistsAtPath:dalPluginDestinationPath];
|
|
|
BOOL dalPluginInstalled =
|
|
|
[fileManager fileExistsAtPath:dalPluginFileName];
|
|
|
- BOOL dalPluginUpdateNeeded = NO;
|
|
|
|
|
|
if (dalPluginInstalled) {
|
|
|
NSDictionary *dalPluginInfoPlist = [NSDictionary
|
|
|
dictionaryWithContentsOfURL:
|
|
|
[NSURL fileURLWithPath:
|
|
|
- @"/Library/CoreMediaIO/Plug-Ins/DAL/obs-mac-virtualcam.plugin/Contents/Info.plist"]];
|
|
|
+ [OBSDalDestination
|
|
|
+ stringByAppendingString:
|
|
|
+ @"/obs-mac-virtualcam.plugin/Contents/Info.plist"]]];
|
|
|
+
|
|
|
NSString *dalPluginVersion = [dalPluginInfoPlist
|
|
|
valueForKey:@"CFBundleShortVersionString"];
|
|
|
NSString *dalPluginBuild =
|
|
@@ -45,64 +179,93 @@ static bool check_dal_plugin()
|
|
|
objectForKey:@"CFBundleShortVersionString"];
|
|
|
NSString *obsBuild = [[[NSBundle mainBundle] infoDictionary]
|
|
|
objectForKey:(NSString *)kCFBundleVersionKey];
|
|
|
- dalPluginUpdateNeeded =
|
|
|
+ BOOL dalPluginUpdateNeeded =
|
|
|
!([dalPluginVersion isEqualToString:obsVersion] &&
|
|
|
[dalPluginBuild isEqualToString:obsBuild]);
|
|
|
+
|
|
|
+ return dalPluginUpdateNeeded ? OBSDalPluginNeedsUpdate
|
|
|
+ : OBSDalPluginInstalled;
|
|
|
}
|
|
|
|
|
|
- if (!dalPluginInstalled || dalPluginUpdateNeeded) {
|
|
|
- NSString *dalPluginSourcePath;
|
|
|
-
|
|
|
- NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];
|
|
|
- NSString *pluginPath =
|
|
|
- @"Contents/Resources/obs-mac-virtualcam.plugin";
|
|
|
-
|
|
|
- NSURL *pluginUrl =
|
|
|
- [bundleURL URLByAppendingPathComponent:pluginPath];
|
|
|
- dalPluginSourcePath = [pluginUrl path];
|
|
|
-
|
|
|
- NSString *createPluginDirCmd =
|
|
|
- (!dalPluginDirExists)
|
|
|
- ? [NSString stringWithFormat:
|
|
|
- @"mkdir -p '%@' && ",
|
|
|
- dalPluginDestinationPath]
|
|
|
- : @"";
|
|
|
- NSString *deleteOldPluginCmd =
|
|
|
- (dalPluginUpdateNeeded)
|
|
|
- ? [NSString stringWithFormat:@"rm -rf '%@' && ",
|
|
|
- dalPluginFileName]
|
|
|
- : @"";
|
|
|
- NSString *copyPluginCmd =
|
|
|
- [NSString stringWithFormat:@"cp -R '%@' '%@'",
|
|
|
- dalPluginSourcePath,
|
|
|
- dalPluginDestinationPath];
|
|
|
- if ([fileManager fileExistsAtPath:dalPluginSourcePath]) {
|
|
|
- NSString *copyCmd = [NSString
|
|
|
- stringWithFormat:
|
|
|
- @"do shell script \"%@%@%@\" with administrator privileges",
|
|
|
- createPluginDirCmd, deleteOldPluginCmd,
|
|
|
- copyPluginCmd];
|
|
|
-
|
|
|
- NSDictionary *errorDict;
|
|
|
- NSAppleScript *scriptObject =
|
|
|
- [[NSAppleScript alloc] initWithSource:copyCmd];
|
|
|
- [scriptObject executeAndReturnError:&errorDict];
|
|
|
- if (errorDict != nil) {
|
|
|
- const char *errorMessage = [[errorDict
|
|
|
- objectForKey:@"NSAppleScriptErrorMessage"]
|
|
|
- UTF8String];
|
|
|
- blog(LOG_INFO,
|
|
|
- "[macOS] VirtualCam DAL Plugin Installation status: %s",
|
|
|
- errorMessage);
|
|
|
- return false;
|
|
|
- }
|
|
|
- } else {
|
|
|
+ return OBSDalPluginNotInstalled;
|
|
|
+}
|
|
|
+
|
|
|
+static bool install_dal_plugin(bool update)
|
|
|
+{
|
|
|
+ NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
|
+ BOOL dalPluginDirExists =
|
|
|
+ [fileManager fileExistsAtPath:OBSDalDestination];
|
|
|
+
|
|
|
+ NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];
|
|
|
+ NSString *pluginPath = @"Contents/Resources/obs-mac-virtualcam.plugin";
|
|
|
+
|
|
|
+ NSURL *pluginUrl = [bundleURL URLByAppendingPathComponent:pluginPath];
|
|
|
+ NSString *dalPluginSourcePath = [pluginUrl path];
|
|
|
+
|
|
|
+ NSString *createPluginDirCmd =
|
|
|
+ (!dalPluginDirExists)
|
|
|
+ ? [NSString stringWithFormat:@"mkdir -p '%@' && ",
|
|
|
+ OBSDalDestination]
|
|
|
+ : @"";
|
|
|
+ NSString *deleteOldPluginCmd =
|
|
|
+ (update) ? [NSString stringWithFormat:@"rm -rf '%@' && ",
|
|
|
+ OBSDalDestination]
|
|
|
+ : @"";
|
|
|
+ NSString *copyPluginCmd = [NSString
|
|
|
+ stringWithFormat:@"cp -R '%@' '%@'", dalPluginSourcePath,
|
|
|
+ OBSDalDestination];
|
|
|
+
|
|
|
+ if ([fileManager fileExistsAtPath:dalPluginSourcePath]) {
|
|
|
+ NSString *copyCmd = [NSString
|
|
|
+ stringWithFormat:
|
|
|
+ @"do shell script \"%@%@%@\" with administrator privileges",
|
|
|
+ createPluginDirCmd, deleteOldPluginCmd,
|
|
|
+ copyPluginCmd];
|
|
|
+
|
|
|
+ NSDictionary *errorDict;
|
|
|
+ NSAppleScript *scriptObject =
|
|
|
+ [[NSAppleScript alloc] initWithSource:copyCmd];
|
|
|
+ [scriptObject executeAndReturnError:&errorDict];
|
|
|
+ if (errorDict != nil) {
|
|
|
+ const char *errorMessage = [[errorDict
|
|
|
+ objectForKey:@"NSAppleScriptErrorMessage"]
|
|
|
+ UTF8String];
|
|
|
+
|
|
|
blog(LOG_INFO,
|
|
|
- "[macOS] VirtualCam DAL Plugin not shipped with OBS");
|
|
|
+ "[macOS] VirtualCam DAL Plugin Installation status: %s",
|
|
|
+ errorMessage);
|
|
|
return false;
|
|
|
+ } else {
|
|
|
+ return true;
|
|
|
}
|
|
|
+ } else {
|
|
|
+ blog(LOG_INFO,
|
|
|
+ "[macOS] VirtualCam DAL Plugin not shipped with OBS");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool uninstall_dal_plugin()
|
|
|
+{
|
|
|
+ NSAppleScript *scriptObject = [[NSAppleScript alloc]
|
|
|
+ initWithSource:
|
|
|
+ [NSString
|
|
|
+ stringWithFormat:
|
|
|
+ @"do shell script \"rm -rf %@/obs-mac-virtualcam.plugin\" with administrator privileges",
|
|
|
+ OBSDalDestination]];
|
|
|
+
|
|
|
+ NSDictionary *errorDict;
|
|
|
+
|
|
|
+ [scriptObject executeAndReturnError:&errorDict];
|
|
|
+ if (errorDict) {
|
|
|
+ blog(LOG_INFO,
|
|
|
+ "[macOS] VirtualCam DAL Plugin could not be uninstalled: %s",
|
|
|
+ [[errorDict objectForKey:NSAppleScriptErrorMessage]
|
|
|
+ UTF8String]);
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ return true;
|
|
|
}
|
|
|
- return true;
|
|
|
}
|
|
|
|
|
|
FourCharCode convert_video_format_to_mac(enum video_format format,
|
|
@@ -148,7 +311,16 @@ static void *virtualcam_output_create(obs_data_t *settings,
|
|
|
(struct virtualcam_data *)bzalloc(sizeof(*vcam));
|
|
|
|
|
|
vcam->output = output;
|
|
|
- vcam->machServer = [[OBSDALMachServer alloc] init];
|
|
|
+
|
|
|
+ if (cmio_extension_supported()) {
|
|
|
+ vcam->extensionDelegate =
|
|
|
+ [[SystemExtensionActivationDelegate alloc]
|
|
|
+ initWithVcam:vcam];
|
|
|
+ install_cmio_system_extension(vcam);
|
|
|
+ } else {
|
|
|
+ vcam->machServer = [[OBSDALMachServer alloc] init];
|
|
|
+ }
|
|
|
+
|
|
|
return vcam;
|
|
|
}
|
|
|
|
|
@@ -156,7 +328,12 @@ static void virtualcam_output_destroy(void *data)
|
|
|
{
|
|
|
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
|
|
|
|
|
|
- vcam->machServer = nil;
|
|
|
+ if (cmio_extension_supported()) {
|
|
|
+ vcam->extensionDelegate = nil;
|
|
|
+ } else {
|
|
|
+ vcam->machServer = nil;
|
|
|
+ }
|
|
|
+
|
|
|
bfree(vcam);
|
|
|
}
|
|
|
|
|
@@ -164,10 +341,57 @@ static bool virtualcam_output_start(void *data)
|
|
|
{
|
|
|
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
|
|
|
|
|
|
- bool hasDalPlugin = check_dal_plugin();
|
|
|
+ dal_plugin_status dal_status = check_dal_plugin();
|
|
|
|
|
|
- if (!hasDalPlugin) {
|
|
|
- return false;
|
|
|
+ if (cmio_extension_supported()) {
|
|
|
+ if (dal_status != OBSDalPluginNotInstalled) {
|
|
|
+ if (!uninstall_dal_plugin()) {
|
|
|
+ obs_output_set_last_error(
|
|
|
+ vcam->output,
|
|
|
+ obs_module_text(
|
|
|
+ "Error.DAL.NotUninstalled"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ SystemExtensionActivationDelegate *delegate =
|
|
|
+ vcam->extensionDelegate;
|
|
|
+
|
|
|
+ if (!delegate.installed) {
|
|
|
+ if (delegate.lastErrorMessage) {
|
|
|
+ obs_output_set_last_error(
|
|
|
+ vcam->output,
|
|
|
+ [NSString
|
|
|
+ stringWithFormat:
|
|
|
+ @"%s\n\n%@",
|
|
|
+ obs_module_text(
|
|
|
+ "Error.SystemExtension.InstallationError"),
|
|
|
+ delegate.lastErrorMessage]
|
|
|
+ .UTF8String);
|
|
|
+ } else {
|
|
|
+ obs_output_set_last_error(
|
|
|
+ vcam->output,
|
|
|
+ obs_module_text(
|
|
|
+ "Error.SystemExtension.NotInstalled"));
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ bool success = false;
|
|
|
+ if (dal_status == OBSDalPluginNotInstalled) {
|
|
|
+ success = install_dal_plugin(false);
|
|
|
+ } else if (dal_status == OBSDalPluginNeedsUpdate) {
|
|
|
+ success = install_dal_plugin(true);
|
|
|
+ } else {
|
|
|
+ success = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!success) {
|
|
|
+ obs_output_set_last_error(vcam->output,
|
|
|
+ "Error.DAL.NotInstalled");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
obs_get_video_info(&vcam->videoInfo);
|
|
@@ -205,13 +429,99 @@ static bool virtualcam_output_start(void *data)
|
|
|
CVReturn status = CVPixelBufferPoolCreate(
|
|
|
kCFAllocatorDefault, (__bridge CFDictionaryRef)pAttr,
|
|
|
(__bridge CFDictionaryRef)pbAttr, &vcam->pool);
|
|
|
+
|
|
|
if (status != kCVReturnSuccess) {
|
|
|
blog(LOG_ERROR,
|
|
|
"unable to allocate pixel buffer pool (error %d)", status);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- [vcam->machServer run];
|
|
|
+ if (cmio_extension_supported()) {
|
|
|
+ UInt32 size;
|
|
|
+ UInt32 used;
|
|
|
+
|
|
|
+ CMIOObjectPropertyAddress address{
|
|
|
+ .mSelector = kCMIOHardwarePropertyDevices,
|
|
|
+ .mScope = kCMIOObjectPropertyScopeGlobal,
|
|
|
+ .mElement = kCMIOObjectPropertyElementMain};
|
|
|
+ CMIOObjectGetPropertyDataSize(kCMIOObjectSystemObject, &address,
|
|
|
+ 0, NULL, &size);
|
|
|
+ size_t num_devices = size / sizeof(CMIOObjectID);
|
|
|
+ CMIOObjectID cmio_devices[num_devices];
|
|
|
+ CMIOObjectGetPropertyData(kCMIOObjectSystemObject, &address, 0,
|
|
|
+ NULL, size, &used, &cmio_devices);
|
|
|
+
|
|
|
+ vcam->deviceID = 0;
|
|
|
+
|
|
|
+ NSString *OBSVirtualCamUUID = [[NSBundle
|
|
|
+ bundleWithIdentifier:@"com.obsproject.mac-virtualcam"]
|
|
|
+ objectForInfoDictionaryKey:@"OBSCameraDeviceUUID"];
|
|
|
+ for (size_t i = 0; i < num_devices; i++) {
|
|
|
+ CMIOObjectID cmio_device = cmio_devices[i];
|
|
|
+ address.mSelector = kCMIODevicePropertyDeviceUID;
|
|
|
+
|
|
|
+ UInt32 device_name_size;
|
|
|
+ CMIOObjectGetPropertyDataSize(cmio_device, &address, 0,
|
|
|
+ NULL, &device_name_size);
|
|
|
+
|
|
|
+ CFStringRef uid;
|
|
|
+ CMIOObjectGetPropertyData(cmio_device, &address, 0,
|
|
|
+ NULL, device_name_size, &used,
|
|
|
+ &uid);
|
|
|
+
|
|
|
+ const char *uid_string = CFStringGetCStringPtr(
|
|
|
+ uid, kCFStringEncodingUTF8);
|
|
|
+ if (uid_string &&
|
|
|
+ strcmp(uid_string, OBSVirtualCamUUID.UTF8String) ==
|
|
|
+ 0) {
|
|
|
+ vcam->deviceID = cmio_device;
|
|
|
+ CFRelease(uid);
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ CFRelease(uid);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!vcam->deviceID) {
|
|
|
+ obs_output_set_last_error(
|
|
|
+ vcam->output,
|
|
|
+ obs_module_text(
|
|
|
+ "Error.SystemExtension.CameraUnavailable"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ address.mSelector = kCMIODevicePropertyStreams;
|
|
|
+ CMIOObjectGetPropertyDataSize(vcam->deviceID, &address, 0, NULL,
|
|
|
+ &size);
|
|
|
+ CMIOStreamID stream_ids[(size / sizeof(CMIOStreamID))];
|
|
|
+
|
|
|
+ CMIOObjectGetPropertyData(vcam->deviceID, &address, 0, NULL,
|
|
|
+ size, &used, &stream_ids);
|
|
|
+
|
|
|
+ vcam->streamID = stream_ids[1];
|
|
|
+
|
|
|
+ CMIOStreamCopyBufferQueue(
|
|
|
+ vcam->streamID, [](CMIOStreamID, void *, void *) {},
|
|
|
+ NULL, &vcam->queue);
|
|
|
+ CMVideoFormatDescriptionCreate(kCFAllocatorDefault,
|
|
|
+ video_format,
|
|
|
+ vcam->videoInfo.output_width,
|
|
|
+ vcam->videoInfo.output_height,
|
|
|
+ NULL, &vcam->formatDescription);
|
|
|
+
|
|
|
+ OSStatus result =
|
|
|
+ CMIODeviceStartStream(vcam->deviceID, vcam->streamID);
|
|
|
+
|
|
|
+ if (result != noErr) {
|
|
|
+ obs_output_set_last_error(
|
|
|
+ vcam->output,
|
|
|
+ obs_module_text(
|
|
|
+ "Error.SystemExtension.CameraNotStarted"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ [vcam->machServer run];
|
|
|
+ }
|
|
|
|
|
|
if (!obs_output_begin_data_capture(vcam->output, 0)) {
|
|
|
return false;
|
|
@@ -227,9 +537,13 @@ static void virtualcam_output_stop(void *data, uint64_t ts)
|
|
|
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
|
|
|
|
|
|
obs_output_end_data_capture(vcam->output);
|
|
|
- [vcam->machServer stop];
|
|
|
-
|
|
|
- CVPixelBufferPoolRelease(vcam->pool);
|
|
|
+ if (cmio_extension_supported()) {
|
|
|
+ CMIODeviceStopStream(vcam->deviceID, vcam->streamID);
|
|
|
+ CFRelease(vcam->formatDescription);
|
|
|
+ CVPixelBufferPoolRelease(vcam->pool);
|
|
|
+ } else {
|
|
|
+ [vcam->machServer stop];
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
static void virtualcam_output_raw_video(void *data, struct video_data *frame)
|
|
@@ -306,11 +620,23 @@ static void virtualcam_output_raw_video(void *data, struct video_data *frame)
|
|
|
|
|
|
CVPixelBufferUnlockBaseAddress(frameRef, 0);
|
|
|
|
|
|
- // Share pixel buffer with clients
|
|
|
- [vcam->machServer sendPixelBuffer:frameRef
|
|
|
- timestamp:frame->timestamp
|
|
|
- fpsNumerator:vcam->videoInfo.fps_num
|
|
|
- fpsDenominator:vcam->videoInfo.fps_den];
|
|
|
+ if (cmio_extension_supported()) {
|
|
|
+ CMSampleBufferRef sampleBuffer;
|
|
|
+ CMSampleTimingInfo timingInfo{
|
|
|
+ .presentationTimeStamp =
|
|
|
+ CMTimeMake(frame->timestamp, NSEC_PER_SEC)};
|
|
|
+
|
|
|
+ OSStatus result = CMSampleBufferCreateForImageBuffer(
|
|
|
+ kCFAllocatorDefault, frameRef, true, NULL, NULL,
|
|
|
+ vcam->formatDescription, &timingInfo, &sampleBuffer);
|
|
|
+ result = CMSimpleQueueEnqueue(vcam->queue, sampleBuffer);
|
|
|
+ } else {
|
|
|
+ // Share pixel buffer with clients
|
|
|
+ [vcam->machServer sendPixelBuffer:frameRef
|
|
|
+ timestamp:frame->timestamp
|
|
|
+ fpsNumerator:vcam->videoInfo.fps_num
|
|
|
+ fpsDenominator:vcam->videoInfo.fps_den];
|
|
|
+ }
|
|
|
|
|
|
CVPixelBufferRelease(frameRef);
|
|
|
}
|