-
-
Notifications
You must be signed in to change notification settings - Fork 8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mac-virtualcam: Avoid transcoding where possible #6573
mac-virtualcam: Avoid transcoding where possible #6573
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial impressions, didn’t look too deeply into the code or build or test yet.
Regarding your comments about this being breaking: Changing the communication protocol is completely fine, OBS will automatically update the DAL plugin when a new version comes out, running the old one isn’t a supported use case.
9523546
to
9f85bb6
Compare
@gxalpha Thanks for the comments. I have updated the pull request according to your suggestions. |
Looks fine to me overall - using a |
I did some tests on my local machines:
Both Macs run macOS 12.4. |
Seems I spoke to soon, it seized to work on my M1 Mac as well - will have to investigate.. |
@PatTheMav How did you test the virtual cam? Hopefully I can reproduce your issue as well. |
Nothing special, pulled the code, built OBS, activated the cam, had it overwrite the preexisting one. Tested with https://github.com/lvsti/Cameo. I got crashes in Cameo as well as OBS now, both of which I have to further investigate right now. |
@PatTheMav Looks like I am able to reproduce the issue with Cameo. Will investigate. |
This change fixes an issue where the DAL plugin would not load due to not supporting the architecture arm64e. We update the build configuration to build a universal binary that includes arm64e as well. See obsproject#6285 for more information regarding this issue.
This change updates the virtual camera implementation on macOS to utilize IOSurface to share the output feed with the virtual cameras. By using IOSurface, we remove the need for copying the frames across multiple buffers, since they can be shared across Mach connections using zero-copy.
This change updates the mac-virtualcam implementation to pool the CVPixelBuffers used to share the output frames. This allows the plugin to recycle the pixel buffers used by the plugin.
This change updates the plugin to support video formats that contain multiple planes (such as NV12). Such functionality is necessary to prevent transcoding the raw video data, which is often delivered in a planar format.
This change updates the mac-virtualcam implementation to conditionally enable conversion of the output video format. Previously, the output video was always converted into UYVY. However, this conversion exhibits high CPU usage, as reported in: johnboiles/obs-mac-virtualcam#102 Therefore, we disable conversion when the selected output format (e.g., NV12) is natively supported by CoreVideo's pixel buffers.
This change removes the unused CMSampleBuffer utility functions that were still left from the previous implementation. Since we construct the CMSampleBuffer directly from an IOSurface, we do not need any custom construction logic anymore, since that is now performed by the OBS plugin.
This change updates the implementation of the mac-virtualcam plugin to not use any global state and instead rely on the state object that is passed by the OBS module system. This approach is similar to the virtual camera implementations for Linux and Windows.
This change fixes an issue in the Mach server exposed by the macOS virtual camera for OBS, where it would not invalidate ports that were disconnected by the remote application, causing sporadic crashes. These crashes can be reproduced in the previous builds by opening the virtual camera in a remote application and closing the application (without stopping the virtual camera).
This change fixes an issue with the CMIO DAL plugin where the CMIO subsystem would log multiple errors when starting the virtual camera, due to certain properties that could not be set (frame rate and format). For now, we just ignore the assignment, but mark the property as settable to suppress the error messages that are reported by the CMIO subsystem.
4731cd3
to
37c76ab
Compare
@PatTheMav I think I found the cause of the issues (which the updated pull request should fix):
Cameo now works on my device as well (where it did not before). |
Great, seems to work in my tests on both architectures now as well. |
@gxalpha did you test this already? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went through the code again, looks fine to me, changes are straight-forward enough.
At some point we should move to a HelperTool + XPCService setup, probably around the time we add a modern VirtualCamera output, but should be fine for now.
@PatTheMav I have already worked a bit on a prototype using XPC as well as one using the new CMIO System Extension framework (in Swift). I could create a (draft) pull request once those changes are ready for integration. Using the new CMIO framework vastly simplifies the implementation of the virtual camera, but the development experience is quite painful, since System Extensions require special entitlements and other strict requirements (e.g., application must be located in |
@fabianishere that'd be amazing, @gxalpha and I have been looking at that precisely and we'd be very happy to check out what you did so far. 👍🏻 |
Hi all, after running the build with this PR merged, I get a massive memory leak on my M1 Ultra Mac Studio when using the virtual camera. The virtual camera must be accessed by an application (in my case, latest version of Zoom - 5.11.0). The second I start my mac virtualcam with this build and enable the camera within zoom, OBS memory usage will gradually climb to enormous amounts until the video hangs in zoom and OBS crashes. Stopping and restarting the video capture in zoom does not resolve the problem. Closing/exiting zoom completely BEFORE OBS crashes will release the memory, however. The second I start zoom back up and use virtual cam output, the memory starts climbing again until crash. Have not tested other apps that can leverage the virtual cam yet, only zoom so far. DAL plug-in completely removed via rm -rf /Library/CoreMediaIO/Plug-Ins/DAL/obs-mac-virtualcam.plugin before each build is installed/ran. Decklink Quad HDMI, capturing from a Panasonic Lumix GH5M2: 3840x2160, 59.94 FPS, Input format: UYVY - 422YpCbCr8, Color space Auto (autoselect: Rec. 709), video range: Full, Buffering On or Off Rolling back to a previous build without this PR merged resolves the issue. |
Thanks for reporting, we'll investigate. |
I can only reproduce this with Zoom, not with Chrome or Cameo. It looks like Zoom is somehow not releasing the EDIT: I updated the plugin to explicitly invalidate the Mach port, but it does not look like it resolves the issue. diff --git a/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm b/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm
index 452ee6fbc..f13e4dd51 100644
--- a/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm
+++ b/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm
@@ -132,6 +132,8 @@
CVPixelBufferRelease(frame);
CFRelease(surface);
+
+ [framePort invalidate];
}
break;
case MachMsgIdStop:
diff --git a/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm b/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm
index d2857766e..8df468598 100644
--- a/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm
+++ b/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm
@@ -164,6 +164,8 @@
fpsNumeratorData,
fpsDenominatorData
]];
+
+ [framePort invalidate];
}
} |
FYI: I've just confirmed the same issue in Microsoft Edge + Teams Progressive Web App in a video call. |
Looks like OBS is leaking Mach ports and we need to manually deallocate the ports using diff --git a/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm b/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm
index 452ee6fbc..f781b29f5 100644
--- a/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm
+++ b/plugins/mac-virtualcam/src/dal-plugin/OBSDALMachClient.mm
@@ -101,14 +101,32 @@
break;
case MachMsgIdFrame:
VLog(@"Received frame message");
- if (components.count >= 4) {
+
+ if (components.count < 4) {
+ break;
+ }
+
+ @autoreleasepool {
NSMachPort *framePort = (NSMachPort *)components[0];
+
+ if (!framePort) {
+ return;
+ }
+
IOSurfaceRef surface = IOSurfaceLookupFromMachPort(
[framePort machPort]);
+ mach_port_deallocate(mach_task_self(),
+ [framePort machPort]);
+
+ if (!surface) {
+ ELog(@"Failed to obtain IOSurface from Mach port");
+ return;
+ }
CVPixelBufferRef frame;
CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault,
surface, NULL, &frame);
+ CFRelease(surface);
uint64_t timestamp;
[components[1] getBytes:×tamp
@@ -131,7 +149,6 @@
fpsDenominator:fpsDenominator];
CVPixelBufferRelease(frame);
- CFRelease(surface);
}
break;
case MachMsgIdStop:
diff --git a/plugins/mac-virtualcam/src/dal-plugin/OBSDALStream.mm b/plugins/mac-virtualcam/src/dal-plugin/OBSDALStream.mm
index b318f6fb7..571eefd70 100644
--- a/plugins/mac-virtualcam/src/dal-plugin/OBSDALStream.mm
+++ b/plugins/mac-virtualcam/src/dal-plugin/OBSDALStream.mm
@@ -397,7 +397,10 @@
}
err = CMSimpleQueueEnqueue(self.queue, sampleBuffer);
+
if (err != noErr) {
+ CFRelease(sampleBuffer);
+
DLog(@"CMSimpleQueueEnqueue err %d", err);
return;
}
diff --git a/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm b/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm
index d2857766e..efd33c1b4 100644
--- a/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm
+++ b/plugins/mac-virtualcam/src/obs-plugin/OBSDALMachServer.mm
@@ -147,23 +147,33 @@
dataWithBytes:&fpsDenominator
length:sizeof(fpsDenominator)];
- NSPort *framePort = [NSMachPort
- portWithMachPort:IOSurfaceCreateMachPort(
- CVPixelBufferGetIOSurface(
- frame))];
+ IOSurfaceRef surface = CVPixelBufferGetIOSurface(frame);
+
+ 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 pixel buffer");
+ "unable to allocate mach port for IOSurface");
return;
}
[self sendMessageToClientsWithMsgId:MachMsgIdFrame
components:@[
- framePort, timestampData,
+ [NSMachPort
+ portWithMachPort:framePort
+ options:NSMachPortDeallocateNone],
+ timestampData,
fpsNumeratorData,
fpsDenominatorData
]];
+
+ mach_port_deallocate(mach_task_self(), framePort);
}
} |
@fabianishere Thanks for the quick fix, would you be interested in submitting another PR? Otherwise we could test and submit it as well. |
This change fixes a memory leak in the mac-virtualcam plugin that causes OBS to not release the CVPixelBuffers (and underlying IOSurfaces) it emits to the virtual camera consumers. Pull request obsproject#6573 (Avoid transcoding where possible) updated the mac-virtualcam to share the virtual camera feed with other processes via IOSurfaces. Although the changes work correctly, users have observed that OBS memory usage keeps increasing when the virtual camera is active until OBS runs out of memory or the consuming application is closed. See the report by @SciTechNick for more information: obsproject#6573 (comment) After some debugging, I have found that the plugin is leaking Mach ports associated with IOSurfaces, preventing them from being re-used. The previous approach using `NSMachPort` does not seem to properly release the Mach port allocated via `CVPixelBufferGetIOSurface` and `IOSurfaceLookupFromMachPort`. Instead, we must explicitly deallocate the port using `mach_port_deallocate`. I have tested the changes on a Macbook Pro (M1) running macOS Monterey with Google Chrome, Zoom, and Cameo. OBS shows no signs of memory leakage after multiple minutes.
This change fixes a memory leak in the mac-virtualcam plugin that causes OBS to not release the CVPixelBuffers (and underlying IOSurfaces) it emits to the virtual camera consumers. Pull request obsproject#6573 (Avoid transcoding where possible) updated the mac-virtualcam to share the virtual camera feed with other processes via IOSurfaces. Although the changes work correctly, users have observed that OBS memory usage keeps increasing when the virtual camera is active until OBS runs out of memory or the consuming application is closed. See the report by @SciTechNick for more information: obsproject#6573 (comment) After some debugging, I have found that the plugin is leaking Mach ports associated with IOSurfaces, preventing them from being re-used. The previous approach using `NSMachPort` does not seem to properly release the Mach port allocated via `CVPixelBufferGetIOSurface` and `IOSurfaceLookupFromMachPort`. Instead, we must explicitly deallocate the port using `mach_port_deallocate`. I have tested the changes on a Macbook Pro (M1) running macOS Monterey with Google Chrome, Zoom, and Cameo. OBS shows no signs of memory leakage after multiple minutes.
This change fixes a memory leak in the mac-virtualcam plugin that causes OBS to not release the CVPixelBuffers (and underlying IOSurfaces) it emits to the virtual camera consumers. Pull request #6573 (Avoid transcoding where possible) updated the mac-virtualcam to share the virtual camera feed with other processes via IOSurfaces. Although the changes work correctly, users have observed that OBS memory usage keeps increasing when the virtual camera is active until OBS runs out of memory or the consuming application is closed. See the report by @SciTechNick for more information: #6573 (comment) After some debugging, I have found that the plugin is leaking Mach ports associated with IOSurfaces, preventing them from being re-used. The previous approach using `NSMachPort` does not seem to properly release the Mach port allocated via `CVPixelBufferGetIOSurface` and `IOSurfaceLookupFromMachPort`. Instead, we must explicitly deallocate the port using `mach_port_deallocate`. I have tested the changes on a Macbook Pro (M1) running macOS Monterey with Google Chrome, Zoom, and Cameo. OBS shows no signs of memory leakage after multiple minutes.
This pull request updates the
mac-virtualcam
plugin to avoid transcoding the video feed if possible.Additionally, I have updated the DAL plugin to build for
ARM64e
as well (trying to fix #6285)Motivation and Context
Currently, the
mac-virtualcam
plugin transcodes the video feed intoUYVY
, since the DAL plugin exposes that format to the consuming application. However, having to do this transformation incurs higher CPU usage (see johnboiles/obs-mac-virtualcam#102 for more information).This pull request updates the plugin to support transferring the raw video feed to the DAL plugin without transformation for the pixel formats most commonly used in OBS (NV12 and I444, I believe). We achieve this by communicating with
CVPixelBuffer
s between the OBS plugin and the DAL plugin, as opposed to raw data buffers.CVPixelBuffer
objects carry additional information (such as pixel format and video dimensions) and can be converted quite easily toCMSampleBuffer
objects that are submitted to the consuming application.How Has This Been Tested?
These changes have been tested on a Macbook Pro (M1) running macOS Monterey and have been verified to work with Google Chrome and Zoom.
Types of changes
Checklist: