mac-virtualcam: Fix memory access issues for shared IOSurfaces

The DAL plugin-based virtualcamera shares data between OBS and the
plugin using an IOSurface. IOSurface locks are necessary to ensure
race conditions between data generation (OBS side) and consumption
(virtual camera side) and also that an IOSurface is not offloaded to
GPU memory when it is indeed needed in CPU memory.

Also moves the invalidation of the NSMachPort for the frames to after
the IOSurface data has been converted into a pixelbuffer and added to
the frame queue of the virtual camera, as an early invalidation will
cut off access to the pixel data shared with the DAL plugin.

(cherry picked from commit 447adfbe38)
This commit is contained in:
PatTheMav
2023-01-31 16:08:40 -08:00
committed by Jim
parent 8aa806adf8
commit 00b4c9c88a
2 changed files with 56 additions and 44 deletions
@@ -93,6 +93,8 @@
- (void)handlePortMessage:(NSPortMessage *)message
{
__strong id<MachClientDelegate> strongDelegate = self.delegate;
VLogFunc(@"");
NSArray *components = message.components;
switch (message.msgid) {
@@ -113,57 +115,60 @@
IOSurfaceRef surface = IOSurfaceLookupFromMachPort(
[framePort machPort]);
if (surface) {
/*
* IOSurfaceLocks are only necessary on non Apple Silicon devices, as those have
* unified memory. On Intel machines, the lock ensures that the IOSurface is copied back
* from GPU memory to CPU memory so we can process the pixel buffer.
*/
#ifndef __aarch64__
IOSurfaceLock(surface, kIOSurfaceLockReadOnly,
NULL);
#endif
CVPixelBufferRef frame;
CVPixelBufferCreateWithIOSurface(
kCFAllocatorDefault, surface, NULL,
&frame);
#ifndef __aarch64__
IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly,
NULL);
#endif
CFRelease(surface);
uint64_t timestamp;
[components[1] getBytes:&timestamp
length:sizeof(timestamp)];
VLog(@"Received frame data: %zux%zu (%llu)",
CVPixelBufferGetWidth(frame),
CVPixelBufferGetHeight(frame), timestamp);
uint32_t fpsNumerator;
[components[2] getBytes:&fpsNumerator
length:sizeof(fpsNumerator)];
uint32_t fpsDenominator;
[components[3] getBytes:&fpsDenominator
length:sizeof(fpsDenominator)];
[strongDelegate
receivedPixelBuffer:frame
timestamp:timestamp
fpsNumerator:fpsNumerator
fpsDenominator:fpsDenominator];
CVPixelBufferRelease(frame);
} else {
ELog(@"Failed to obtain IOSurface from Mach port");
}
[framePort invalidate];
mach_port_deallocate(mach_task_self(),
[framePort machPort]);
if (!surface) {
ELog(@"Failed to obtain IOSurface from Mach port");
return;
}
/*
* IOSurfaceLocks are only necessary on non Apple Silicon devices, as those have
* unified memory. On Intel machines, the lock ensures that the IOSurface is copied back
* from GPU memory to CPU memory so we can process the pixel buffer.
*/
#ifndef __aarch64__
IOSurfaceLock(surface, kIOSurfaceLockReadOnly, NULL);
#endif
CVPixelBufferRef frame;
CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault,
surface, NULL, &frame);
#ifndef __aarch64__
IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly, NULL);
#endif
CFRelease(surface);
uint64_t timestamp;
[components[1] getBytes:&timestamp
length:sizeof(timestamp)];
VLog(@"Received frame data: %zux%zu (%llu)",
CVPixelBufferGetWidth(frame),
CVPixelBufferGetHeight(frame), timestamp);
uint32_t fpsNumerator;
[components[2] getBytes:&fpsNumerator
length:sizeof(fpsNumerator)];
uint32_t fpsDenominator;
[components[3] getBytes:&fpsDenominator
length:sizeof(fpsDenominator)];
[self.delegate receivedPixelBuffer:frame
timestamp:timestamp
fpsNumerator:fpsNumerator
fpsDenominator:fpsDenominator];
CVPixelBufferRelease(frame);
}
break;
case MachMsgIdStop:
DLog(@"Received stop message");
[self.delegate receivedStop];
[strongDelegate receivedStop];
break;
default:
ELog(@"Received unexpected response msgid %u",
@@ -148,6 +148,9 @@
length:sizeof(fpsDenominator)];
IOSurfaceRef surface = CVPixelBufferGetIOSurface(frame);
#ifndef __aarch64__
IOSurfaceLock(surface, 0, NULL);
#endif
if (!surface) {
blog(LOG_ERROR,
@@ -174,6 +177,10 @@
]];
mach_port_deallocate(mach_task_self(), framePort);
#ifndef __aarch64__
IOSurfaceUnlock(surface, 0, NULL);
#endif
}
}