obs-studio/plugins/mac-virtualcam/src/obs-plugin/MachServer.mm
John Boiles 2700db9ff9 Add virtualcam plugin to OBS codebase
Co-authored-by: lvsti <lvsti@users.noreply.github.com>
Co-authored-by: Sebastian Beckmann <beckmann.sebastian@outlook.de>
Co-authored-by: Stefan Huber <sh@signalwerk.ch>
Co-authored-by: Ryohei Ikegami <iofg2100@gmail.com>
Co-authored-by: Colin Dean <colin.dean@target.com>
Co-authored-by: Wolfgang Ladermann <extern.ladermann_wolfgang@allianz.de>
Co-authored-by: Simon Eves <simon.eves@omnisci.com>
Co-authored-by: Colin Nelson <colnnelson@google.com>
Co-authored-by: Yoshimasa Niwa <niw@niw.at>
Co-authored-by: Michael Karliner <mike@modern-industry.com>
Co-authored-by: Jason Grout <jgrout6@bloomberg.net>
Co-authored-by: Alfredo Inostroza <jadenguy@gmail.com>
Co-authored-by: Daniel Kennett <daniel@cascable.se>
Co-authored-by: Gary Ewan Park <gep13@gep13.co.uk>
Co-authored-by: José Carlos Cieni Júnior <cienijr@outlook.com>
2020-10-30 23:36:57 -07:00

178 lines
4.9 KiB
Text

//
// MachServer.m
// mac-virtualcam
//
// Created by John Boiles on 5/5/20.
//
#import "MachServer.h"
#import <Foundation/Foundation.h>
#include <obs-module.h>
#include "MachProtocol.h"
#include "Defines.h"
@interface MachServer () <NSPortDelegate>
@property NSPort *port;
@property NSMutableSet *clientPorts;
@property NSRunLoop *runLoop;
@end
@implementation MachServer
- (id)init
{
if (self = [super init]) {
self.clientPorts = [[NSMutableSet alloc] init];
}
return self;
}
- (void)dealloc
{
blog(LOG_DEBUG, "tearing down MachServer");
[self.runLoop removePort:self.port forMode:NSDefaultRunLoopMode];
[self.port invalidate];
self.port.delegate = nil;
}
- (void)run
{
if (self.port != nil) {
blog(LOG_DEBUG, "mach server already running!");
return;
}
// It's a bummer this is deprecated. The replacement, NSXPCConnection, seems to require
// an assistant process that lives inside the .app bundle. This would be more modern, but adds
// complexity and I think makes it impossible to just run the `obs` binary from the commandline.
// So let's stick with NSMachBootstrapServer at least until it fully goes away.
// At that point we can decide between NSXPCConnection and using the CoreFoundation versions of
// these APIs (which are, interestingly, not deprecated)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
self.port = [[NSMachBootstrapServer sharedInstance]
servicePortWithName:@MACH_SERVICE_NAME];
#pragma clang diagnostic pop
if (self.port == nil) {
// This probably means another instance is running.
blog(LOG_ERROR, "Unable to open mach server port.");
return;
}
self.port.delegate = self;
self.runLoop = [NSRunLoop currentRunLoop];
[self.runLoop addPort:self.port forMode:NSDefaultRunLoopMode];
blog(LOG_DEBUG, "mach server running!");
}
- (void)handlePortMessage:(NSPortMessage *)message
{
switch (message.msgid) {
case MachMsgIdConnect:
if (message.sendPort != nil) {
blog(LOG_DEBUG,
"mach server received connect message from port %d!",
((NSMachPort *)message.sendPort).machPort);
[self.clientPorts addObject:message.sendPort];
}
break;
default:
blog(LOG_ERROR, "Unexpected mach message ID %u",
(unsigned)message.msgid);
break;
}
}
- (void)sendMessageToClientsWithMsgId:(uint32_t)msgId
components:(nullable NSArray *)components
{
if ([self.clientPorts count] <= 0) {
return;
}
NSMutableSet *removedPorts = [NSMutableSet set];
for (NSPort *port in self.clientPorts) {
@try {
NSPortMessage *message = [[NSPortMessage alloc]
initWithSendPort:port
receivePort:nil
components:components];
message.msgid = msgId;
if (![message
sendBeforeDate:
[NSDate dateWithTimeIntervalSinceNow:
1.0]]) {
blog(LOG_DEBUG,
"failed to send message to %d, removing it from the clients!",
((NSMachPort *)port).machPort);
[removedPorts addObject:port];
}
} @catch (NSException *exception) {
blog(LOG_DEBUG,
"failed to send message (exception) to %d, removing it from the clients!",
((NSMachPort *)port).machPort);
[removedPorts addObject:port];
}
}
// Remove dead ports if necessary
[self.clientPorts minusSet:removedPorts];
}
- (void)sendFrameWithSize:(NSSize)size
timestamp:(uint64_t)timestamp
fpsNumerator:(uint32_t)fpsNumerator
fpsDenominator:(uint32_t)fpsDenominator
frameBytes:(uint8_t *)frameBytes
{
if ([self.clientPorts count] <= 0) {
return;
}
@autoreleasepool {
CGFloat width = size.width;
NSData *widthData = [NSData dataWithBytes:&width
length:sizeof(width)];
CGFloat height = size.height;
NSData *heightData = [NSData dataWithBytes:&height
length:sizeof(height)];
NSData *timestampData = [NSData
dataWithBytes:&timestamp
length:sizeof(timestamp)];
NSData *fpsNumeratorData = [NSData
dataWithBytes:&fpsNumerator
length:sizeof(fpsNumerator)];
NSData *fpsDenominatorData = [NSData
dataWithBytes:&fpsDenominator
length:sizeof(fpsDenominator)];
// NOTE: I'm not totally sure about the safety of dataWithBytesNoCopy in this context.
// Seems like there could potentially be an issue if the frameBuffer went away before the
// mach message finished sending. But it seems to be working and avoids a memory copy. Alternately
// we could do something like
// NSData *frameData = [NSData dataWithBytes:(void *)frameBytes length:size.width * size.height * 2];
NSData *frameData = [NSData
dataWithBytesNoCopy:(void *)frameBytes
length:size.width * size.height * 2
freeWhenDone:NO];
[self sendMessageToClientsWithMsgId:MachMsgIdFrame
components:@[
widthData, heightData,
timestampData, frameData,
fpsNumeratorData,
fpsDenominatorData
]];
}
}
- (void)stop
{
blog(LOG_DEBUG, "sending stop message to %lu clients",
self.clientPorts.count);
[self sendMessageToClientsWithMsgId:MachMsgIdStop components:nil];
}
@end