223 lines
6.0 KiB
Plaintext
223 lines
6.0 KiB
Plaintext
#include <obs-module.h>
|
|
#include <AppKit/AppKit.h>
|
|
#include "OBSDALMachServer.h"
|
|
#include "Defines.h"
|
|
|
|
OBS_DECLARE_MODULE()
|
|
OBS_MODULE_USE_DEFAULT_LOCALE("mac-virtualcam", "en-US")
|
|
MODULE_EXPORT const char *obs_module_description(void)
|
|
{
|
|
return "macOS virtual webcam output";
|
|
}
|
|
|
|
obs_output_t *outputRef;
|
|
obs_video_info videoInfo;
|
|
static OBSDALMachServer *sMachServer;
|
|
|
|
static bool 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";
|
|
|
|
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"]];
|
|
NSString *dalPluginVersion = [dalPluginInfoPlist
|
|
valueForKey:@"CFBundleShortVersionString"];
|
|
const char *obsVersion = obs_get_version_string();
|
|
|
|
dalPluginUpdateNeeded =
|
|
![dalPluginVersion isEqualToString:@(obsVersion)];
|
|
}
|
|
|
|
if (!dalPluginInstalled || dalPluginUpdateNeeded) {
|
|
// TODO: Remove this distinction once OBS is built into an app bundle by cmake by default
|
|
NSString *dalPluginSourcePath;
|
|
NSRunningApplication *app =
|
|
[NSRunningApplication currentApplication];
|
|
|
|
if ([app bundleIdentifier] != nil) {
|
|
NSURL *bundleURL = [app bundleURL];
|
|
NSString *pluginPath =
|
|
@"Contents/Resources/data/obs-plugins/mac-virtualcam/obs-mac-virtualcam.plugin";
|
|
|
|
NSURL *pluginUrl = [bundleURL
|
|
URLByAppendingPathComponent:pluginPath];
|
|
dalPluginSourcePath = [pluginUrl path];
|
|
} else {
|
|
dalPluginSourcePath = [[[[app executableURL]
|
|
URLByAppendingPathComponent:
|
|
@"../data/obs-plugins/mac-virtualcam/obs-mac-virtualcam.plugin"]
|
|
path]
|
|
stringByReplacingOccurrencesOfString:@"obs/"
|
|
withString:@""];
|
|
}
|
|
|
|
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;
|
|
NSAppleEventDescriptor *returnDescriptor = NULL;
|
|
NSAppleScript *scriptObject =
|
|
[[NSAppleScript alloc] initWithSource:copyCmd];
|
|
returnDescriptor =
|
|
[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 {
|
|
blog(LOG_INFO,
|
|
"[macOS] VirtualCam DAL Plugin not shipped with OBS");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static const char *virtualcam_output_get_name(void *type_data)
|
|
{
|
|
(void)type_data;
|
|
return obs_module_text("Plugin_Name");
|
|
}
|
|
|
|
// This is a dummy pointer so we have something to return from virtualcam_output_create
|
|
static void *data = &data;
|
|
|
|
static void *virtualcam_output_create(obs_data_t *settings,
|
|
obs_output_t *output)
|
|
{
|
|
UNUSED_PARAMETER(settings);
|
|
|
|
outputRef = output;
|
|
|
|
blog(LOG_DEBUG, "output_create");
|
|
sMachServer = [[OBSDALMachServer alloc] init];
|
|
return data;
|
|
}
|
|
|
|
static void virtualcam_output_destroy(void *data)
|
|
{
|
|
UNUSED_PARAMETER(data);
|
|
blog(LOG_DEBUG, "output_destroy");
|
|
sMachServer = nil;
|
|
}
|
|
|
|
static bool virtualcam_output_start(void *data)
|
|
{
|
|
UNUSED_PARAMETER(data);
|
|
|
|
bool hasDalPlugin = check_dal_plugin();
|
|
|
|
if (!hasDalPlugin) {
|
|
return false;
|
|
}
|
|
|
|
blog(LOG_DEBUG, "output_start");
|
|
|
|
[sMachServer run];
|
|
|
|
obs_get_video_info(&videoInfo);
|
|
|
|
struct video_scale_info conversion = {};
|
|
conversion.format = VIDEO_FORMAT_UYVY;
|
|
conversion.width = videoInfo.output_width;
|
|
conversion.height = videoInfo.output_height;
|
|
obs_output_set_video_conversion(outputRef, &conversion);
|
|
if (!obs_output_begin_data_capture(outputRef, 0)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void virtualcam_output_stop(void *data, uint64_t ts)
|
|
{
|
|
UNUSED_PARAMETER(data);
|
|
UNUSED_PARAMETER(ts);
|
|
|
|
blog(LOG_DEBUG, "output_stop");
|
|
obs_output_end_data_capture(outputRef);
|
|
[sMachServer stop];
|
|
}
|
|
|
|
static void virtualcam_output_raw_video(void *data, struct video_data *frame)
|
|
{
|
|
UNUSED_PARAMETER(data);
|
|
|
|
uint8_t *outData = frame->data[0];
|
|
if (frame->linesize[0] != (videoInfo.output_width * 2)) {
|
|
blog(LOG_ERROR,
|
|
"unexpected frame->linesize (expected:%d actual:%d)",
|
|
(videoInfo.output_width * 2), frame->linesize[0]);
|
|
}
|
|
|
|
CGFloat width = videoInfo.output_width;
|
|
CGFloat height = videoInfo.output_height;
|
|
|
|
[sMachServer sendFrameWithSize:NSMakeSize(width, height)
|
|
timestamp:frame->timestamp
|
|
fpsNumerator:videoInfo.fps_num
|
|
fpsDenominator:videoInfo.fps_den
|
|
frameBytes:outData];
|
|
}
|
|
|
|
struct obs_output_info virtualcam_output_info = {
|
|
.id = "virtualcam_output",
|
|
.flags = OBS_OUTPUT_VIDEO,
|
|
.get_name = virtualcam_output_get_name,
|
|
.create = virtualcam_output_create,
|
|
.destroy = virtualcam_output_destroy,
|
|
.start = virtualcam_output_start,
|
|
.stop = virtualcam_output_stop,
|
|
.raw_video = virtualcam_output_raw_video,
|
|
};
|
|
|
|
bool obs_module_load(void)
|
|
{
|
|
obs_register_output(&virtualcam_output_info);
|
|
|
|
obs_data_t *obs_settings = obs_data_create();
|
|
obs_data_set_bool(obs_settings, "vcamEnabled", true);
|
|
obs_apply_private_data(obs_settings);
|
|
obs_data_release(obs_settings);
|
|
|
|
return true;
|
|
}
|