167 lines
5.2 KiB
Text
167 lines
5.2 KiB
Text
//
|
|
// MachServer.m
|
|
// mac-virtualcam
|
|
//
|
|
// Created by John Boiles on 5/5/20.
|
|
//
|
|
|
|
#import "OBSDALMachServer.h"
|
|
#include <obs-module.h>
|
|
#include "MachProtocol.h"
|
|
#include "Defines.h"
|
|
|
|
@interface OBSDALMachServer () <NSPortDelegate>
|
|
@property NSPort *port;
|
|
@property NSMutableSet *clientPorts;
|
|
@property NSRunLoop *runLoop;
|
|
@end
|
|
|
|
@implementation OBSDALMachServer
|
|
|
|
- (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 (![port isValid] || ![message sendBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]) {
|
|
blog(LOG_DEBUG, "failed to send message to %d, removing it from the clients!",
|
|
((NSMachPort *) port).machPort);
|
|
|
|
[port invalidate];
|
|
[removedPorts addObject:port];
|
|
}
|
|
} @catch (NSException *exception) {
|
|
blog(LOG_DEBUG, "failed to send message (exception) to %d, removing it from the clients!",
|
|
((NSMachPort *) port).machPort);
|
|
|
|
[port invalidate];
|
|
[removedPorts addObject:port];
|
|
}
|
|
}
|
|
|
|
// Remove dead ports if necessary
|
|
[self.clientPorts minusSet:removedPorts];
|
|
}
|
|
|
|
- (void)sendPixelBuffer:(CVPixelBufferRef)frame
|
|
timestamp:(uint64_t)timestamp
|
|
fpsNumerator:(uint32_t)fpsNumerator
|
|
fpsDenominator:(uint32_t)fpsDenominator
|
|
{
|
|
if ([self.clientPorts count] <= 0) {
|
|
return;
|
|
}
|
|
|
|
@autoreleasepool {
|
|
NSData *timestampData = [NSData dataWithBytes:×tamp length:sizeof(timestamp)];
|
|
NSData *fpsNumeratorData = [NSData dataWithBytes:&fpsNumerator length:sizeof(fpsNumerator)];
|
|
NSData *fpsDenominatorData = [NSData dataWithBytes:&fpsDenominator length:sizeof(fpsDenominator)];
|
|
|
|
IOSurfaceRef surface = CVPixelBufferGetIOSurface(frame);
|
|
#ifndef __aarch64__
|
|
IOSurfaceLock(surface, 0, NULL);
|
|
#endif
|
|
|
|
if (!surface) {
|
|
blog(LOG_ERROR, "unable to access IOSurface associated with CVPixelBuffer");
|
|
return;
|
|
}
|
|
|
|
mach_port_t framePort = IOSurfaceCreateMachPort(surface);
|
|
|
|
if (!framePort) {
|
|
blog(LOG_ERROR, "unable to allocate mach port for IOSurface");
|
|
return;
|
|
}
|
|
|
|
[self sendMessageToClientsWithMsgId:MachMsgIdFrame components:@[
|
|
[NSMachPort portWithMachPort:framePort options:NSMachPortDeallocateNone], timestampData, fpsNumeratorData,
|
|
fpsDenominatorData
|
|
]];
|
|
|
|
mach_port_deallocate(mach_task_self(), framePort);
|
|
|
|
#ifndef __aarch64__
|
|
IOSurfaceUnlock(surface, 0, NULL);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
- (void)stop
|
|
{
|
|
blog(LOG_DEBUG, "sending stop message to %lu clients", self.clientPorts.count);
|
|
[self sendMessageToClientsWithMsgId:MachMsgIdStop components:nil];
|
|
}
|
|
|
|
@end
|