瀏覽代碼

mac-capture: Improve window capture performance

Dossy Shiobara 3 年之前
父節點
當前提交
257715d31f

+ 1 - 1
plugins/mac-capture/mac-display-capture.m

@@ -134,7 +134,7 @@ static inline void update_window_params(struct display_capture *dc)
 			[dc->screen convertRectToBacking:dc->window_rect];
 
 	} else {
-		if (find_window(&dc->window, NULL, false))
+		if (find_window(&dc->window, NULL))
 			update_window_params(dc);
 		else
 			dc->on_screen = false;

+ 69 - 66
plugins/mac-capture/mac-window-capture.m

@@ -13,14 +13,8 @@ struct window_capture {
 
 	struct cocoa_window window;
 
-	//CGRect              bounds;
-	//CGWindowListOption  window_option;
 	CGWindowImageOption image_option;
 
-	CGColorSpaceRef color_space;
-
-	DARRAY(uint8_t) buffer;
-
 	pthread_t capture_thread;
 	os_event_t *capture_event;
 	os_event_t *stop_event;
@@ -28,56 +22,66 @@ struct window_capture {
 
 static CGImageRef get_image(struct window_capture *wc)
 {
-	NSArray *arr = (NSArray *)CGWindowListCreate(
-		kCGWindowListOptionIncludingWindow, wc->window.window_id);
-	[arr autorelease];
+	CFMutableArrayRef arr =
+		CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
+	CFArrayAppendValue(arr, (void *)(uintptr_t)wc->window.window_id);
 
-	if (!arr.count && !find_window(&wc->window, NULL, false))
-		return NULL;
+	CGImageRef image = CGWindowListCreateImageFromArray(
+		CGRectNull, (CFArrayRef)arr, wc->image_option);
+	CFRelease(arr);
 
-	return CGWindowListCreateImage(CGRectNull,
-				       kCGWindowListOptionIncludingWindow,
-				       wc->window.window_id, wc->image_option);
+	if (!image && find_window(&wc->window, NULL))
+		image = get_image(wc);
+
+	return image;
 }
 
 static inline void capture_frame(struct window_capture *wc)
 {
 	uint64_t ts = os_gettime_ns();
-	CGImageRef img = get_image(wc);
-	if (!img)
+	CGImageRef image = get_image(wc);
+
+	if (!image)
 		return;
 
-	size_t width = CGImageGetWidth(img);
-	size_t height = CGImageGetHeight(img);
+	size_t width = CGImageGetWidth(image);
+	size_t height = CGImageGetHeight(image);
+	size_t bytes_per_row = CGImageGetBytesPerRow(image);
 
-	CGRect rect = {{0, 0}, {width, height}};
-	da_resize(wc->buffer, width * height * 4);
-	uint8_t *data = wc->buffer.array;
+	if ((!width && !height) || CGImageGetBitsPerPixel(image) != 32 ||
+	    CGImageGetBitsPerComponent(image) != 8) {
+		CGImageRelease(image);
+		return;
+	}
 
-	CGContextRef cg_context = CGBitmapContextCreate(
-		data, width, height, 8, width * 4, wc->color_space,
-		kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
-	CGContextSetBlendMode(cg_context, kCGBlendModeCopy);
-	CGContextDrawImage(cg_context, rect, img);
-	CGContextRelease(cg_context);
-	CGImageRelease(img);
+	// float fps = 1e9 / (ts - wc->frame.timestamp);
+	// NSLog(@"FPS %.4f", fps);
+
+	CGDataProviderRef provider = CGImageGetDataProvider(image);
+	CFDataRef data = CGDataProviderCopyData(provider);
+	const uint8_t *buffer = CFDataGetBytePtr(data);
 
 	struct obs_source_frame frame = {
 		.format = VIDEO_FORMAT_BGRA,
 		.width = width,
 		.height = height,
-		.data[0] = data,
-		.linesize[0] = width * 4,
+		.data[0] = (uint8_t *)buffer,
+		.linesize[0] = bytes_per_row,
 		.timestamp = ts,
 	};
 
 	obs_source_output_video(wc->source, &frame);
+
+	CFRelease(data);
+	CGImageRelease(image);
 }
 
 static void *capture_thread(void *data)
 {
 	struct window_capture *wc = data;
 
+	os_set_thread_name(obs_source_get_name(wc->source));
+
 	for (;;) {
 		os_event_wait(wc->capture_event);
 		if (os_event_try(wc->stop_event) != EAGAIN)
@@ -98,10 +102,6 @@ static inline void *window_capture_create_internal(obs_data_t *settings,
 
 	wc->source = source;
 
-	wc->color_space = CGColorSpaceCreateDeviceRGB();
-
-	da_init(wc->buffer);
-
 	init_window(&wc->window, settings);
 
 	wc->image_option = obs_data_get_bool(settings, "show_shadow")
@@ -111,7 +111,21 @@ static inline void *window_capture_create_internal(obs_data_t *settings,
 	os_event_init(&wc->capture_event, OS_EVENT_TYPE_AUTO);
 	os_event_init(&wc->stop_event, OS_EVENT_TYPE_MANUAL);
 
-	pthread_create(&wc->capture_thread, NULL, capture_thread, wc);
+	/*
+	 * "To let Cocoa know that you intend to use multiple threads, all you
+	 * have to do is spawn a single thread using the NSThread class and
+	 * let that thread immediately exit."
+	 *
+	 * @see https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW21
+	 */
+
+	[[NSThread new] start];
+
+	if ([NSThread isMultiThreaded] != 1)
+		abort();
+
+	if (pthread_create(&wc->capture_thread, NULL, capture_thread, wc))
+		abort();
 
 	return wc;
 }
@@ -125,29 +139,26 @@ static void *window_capture_create(obs_data_t *settings, obs_source_t *source)
 
 static void window_capture_destroy(void *data)
 {
-	struct window_capture *cap = data;
-
-	os_event_signal(cap->stop_event);
-	os_event_signal(cap->capture_event);
-
-	pthread_join(cap->capture_thread, NULL);
+	struct window_capture *wc = data;
 
-	CGColorSpaceRelease(cap->color_space);
+	os_event_signal(wc->stop_event);
+	os_event_signal(wc->capture_event);
 
-	da_free(cap->buffer);
+	pthread_join(wc->capture_thread, NULL);
 
-	os_event_destroy(cap->capture_event);
-	os_event_destroy(cap->stop_event);
+	os_event_destroy(wc->capture_event);
+	os_event_destroy(wc->stop_event);
 
-	destroy_window(&cap->window);
+	destroy_window(&wc->window);
 
-	bfree(cap);
+	bfree(wc);
 }
 
 static void window_capture_defaults(obs_data_t *settings)
 {
-	obs_data_set_default_bool(settings, "show_shadow", false);
 	window_defaults(settings);
+
+	obs_data_set_default_bool(settings, "show_shadow", false);
 }
 
 static obs_properties_t *window_capture_properties(void *unused)
@@ -173,15 +184,14 @@ static inline void window_capture_update_internal(struct window_capture *wc,
 
 	update_window(&wc->window, settings);
 
-	if (wc->window.window_name.length) {
-		blog(LOG_INFO,
-		     "[window-capture: '%s'] update settings:\n"
-		     "\twindow: %s\n"
-		     "\towner:  %s",
-		     obs_source_get_name(wc->source),
-		     [wc->window.window_name UTF8String],
-		     [wc->window.owner_name UTF8String]);
-	}
+	blog(LOG_INFO,
+	     "[window-capture: '%s'] update settings:\n"
+	     "\twindow: %s\n"
+	     "\towner:  %s",
+	     obs_source_get_name(wc->source),
+	     wc->window.window_name.length ? [wc->window.window_name UTF8String]
+					   : "(null)",
+	     [wc->window.owner_name UTF8String]);
 }
 
 static void window_capture_update(void *data, obs_data_t *settings)
@@ -197,23 +207,16 @@ static const char *window_capture_getname(void *unused)
 	return obs_module_text("WindowCapture");
 }
 
-static inline void window_capture_tick_internal(struct window_capture *wc,
-						float seconds)
+static void window_capture_tick(void *data, float seconds)
 {
 	UNUSED_PARAMETER(seconds);
-	os_event_signal(wc->capture_event);
-}
 
-static void window_capture_tick(void *data, float seconds)
-{
 	struct window_capture *wc = data;
 
 	if (!obs_source_showing(wc->source))
 		return;
 
-	@autoreleasepool {
-		return window_capture_tick_internal(data, seconds);
-	}
+	os_event_signal(wc->capture_event);
 }
 
 struct obs_source_info window_capture_info = {

+ 5 - 4
plugins/mac-capture/window-utils.h

@@ -8,17 +8,18 @@ struct cocoa_window {
 	CGWindowID window_id;
 	int owner_pid;
 
-	pthread_mutex_t name_lock;
 	NSString *owner_name;
 	NSString *window_name;
 
-	uint64_t next_search_time;
+	uint64_t last_search_time;
+
+	pthread_mutex_t mutex;
 };
 typedef struct cocoa_window *cocoa_window_t;
 
-NSArray *enumerate_cocoa_windows(void);
+NSArray *enumerate_windows(void);
 
-bool find_window(cocoa_window_t cw, obs_data_t *settings, bool force);
+bool find_window(cocoa_window_t cw, obs_data_t *settings);
 
 void init_window(cocoa_window_t cw, obs_data_t *settings);
 

+ 91 - 73
plugins/mac-capture/window-utils.m

@@ -27,7 +27,6 @@ NSArray *enumerate_windows(void)
 {
 	NSArray *arr = (NSArray *)CGWindowListCopyWindowInfo(
 		kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
-
 	[arr autorelease];
 
 	return [arr sortedArrayUsingComparator:win_info_cmp];
@@ -37,186 +36,205 @@ NSArray *enumerate_windows(void)
 #define WAIT_TIME_US WAIT_TIME_MS * 1000
 #define WAIT_TIME_NS WAIT_TIME_US * 1000
 
-bool find_window(cocoa_window_t cw, obs_data_t *settings, bool force)
+bool find_window(cocoa_window_t cw, obs_data_t *settings)
 {
-	if (!force && cw->next_search_time > os_gettime_ns())
+	uint64_t ts = os_gettime_ns();
+
+	if (cw->last_search_time + WAIT_TIME_NS > ts)
 		return false;
 
-	cw->next_search_time = os_gettime_ns() + WAIT_TIME_NS;
+	cw->last_search_time = ts;
 
-	pthread_mutex_lock(&cw->name_lock);
+	if (pthread_mutex_lock(&cw->mutex))
+		abort();
 
 	if (!cw->window_name.length && !cw->owner_name.length)
-		goto invalid_name;
+		goto unlock;
 
 	for (NSDictionary *dict in enumerate_windows()) {
-		if (![cw->owner_name isEqualToString:dict[OWNER_NAME]])
-			continue;
+		bool owner_names_match =
+			(!cw->owner_name.length && !dict[OWNER_NAME]) ||
+			[cw->owner_name isEqualToString:dict[OWNER_NAME]];
+		bool window_names_match =
+			(!cw->window_name.length && !dict[WINDOW_NAME]) ||
+			[cw->window_name isEqualToString:dict[WINDOW_NAME]];
 
-		if (![cw->window_name isEqualToString:dict[WINDOW_NAME]])
+		if (!owner_names_match || !window_names_match)
 			continue;
 
-		pthread_mutex_unlock(&cw->name_lock);
-
-		NSNumber *window_id = (NSNumber *)dict[WINDOW_NUMBER];
-		cw->window_id = window_id.intValue;
-		NSNumber *owner_pid = (NSNumber *)dict[OWNER_PID];
-		cw->owner_pid = owner_pid.intValue;
+		cw->window_id = [dict[WINDOW_NUMBER] intValue];
+		cw->owner_pid = [dict[OWNER_PID] intValue];
 
 		obs_data_set_int(settings, "window", cw->window_id);
 		obs_data_set_int(settings, "owner_pid", cw->owner_pid);
+
+		if (pthread_mutex_unlock(&cw->mutex))
+			abort();
+
 		return true;
 	}
 
-invalid_name:
-	pthread_mutex_unlock(&cw->name_lock);
+unlock:
+	if (pthread_mutex_unlock(&cw->mutex))
+		abort();
+
 	return false;
 }
 
 void init_window(cocoa_window_t cw, obs_data_t *settings)
 {
-	pthread_mutex_init(&cw->name_lock, NULL);
+	if (pthread_mutex_init(&cw->mutex, NULL))
+		abort();
+
+	cw->window_id = obs_data_get_int(settings, "window");
+	cw->owner_pid = obs_data_get_int(settings, "owner_pid");
 
 	cw->owner_name = @(obs_data_get_string(settings, "owner_name"));
 	cw->window_name = @(obs_data_get_string(settings, "window_name"));
 	[cw->owner_name retain];
 	[cw->window_name retain];
 
-	// Find initial window.
-	pthread_mutex_lock(&cw->name_lock);
+	cw->last_search_time = 0;
 
 	if (!cw->window_name.length && !cw->owner_name.length)
-		goto invalid_name;
+		return;
+
+	NSNumber *window_id = @(cw->window_id);
+	NSNumber *owner_pid = @(cw->owner_pid);
 
-	NSNumber *owner_pid = @(obs_data_get_int(settings, "owner_pid"));
-	NSNumber *window_id = @(obs_data_get_int(settings, "window"));
 	for (NSDictionary *dict in enumerate_windows()) {
 		bool owner_names_match =
+			(!cw->owner_name.length && !dict[OWNER_NAME]) ||
 			[cw->owner_name isEqualToString:dict[OWNER_NAME]];
 		bool ids_match =
 			[owner_pid isEqualToNumber:dict[OWNER_PID]] &&
 			[window_id isEqualToNumber:dict[WINDOW_NUMBER]];
 		bool window_names_match =
+			(!cw->window_name.length && !dict[WINDOW_NAME]) ||
 			[cw->window_name isEqualToString:dict[WINDOW_NAME]];
 
 		if (owner_names_match && (ids_match || window_names_match)) {
-			pthread_mutex_unlock(&cw->name_lock);
-
-			NSNumber *window_id = (NSNumber *)dict[WINDOW_NUMBER];
-			cw->window_id = window_id.intValue;
-			NSNumber *owner_pid = (NSNumber *)dict[OWNER_PID];
-			cw->owner_pid = owner_pid.intValue;
+			cw->window_id = [dict[WINDOW_NUMBER] intValue];
+			cw->owner_pid = [dict[OWNER_PID] intValue];
 
 			obs_data_set_int(settings, "window", cw->window_id);
 			obs_data_set_int(settings, "owner_pid", cw->owner_pid);
-			return;
+
+			break;
 		}
 	}
-
-invalid_name:
-	pthread_mutex_unlock(&cw->name_lock);
-	return;
 }
 
 void destroy_window(cocoa_window_t cw)
 {
-	pthread_mutex_destroy(&cw->name_lock);
 	[cw->owner_name release];
 	[cw->window_name release];
+
+	if (pthread_mutex_destroy(&cw->mutex))
+		abort();
 }
 
 void update_window(cocoa_window_t cw, obs_data_t *settings)
 {
-	pthread_mutex_lock(&cw->name_lock);
+	if (pthread_mutex_lock(&cw->mutex))
+		abort();
+
 	[cw->owner_name release];
 	[cw->window_name release];
+
 	cw->owner_name = @(obs_data_get_string(settings, "owner_name"));
 	cw->window_name = @(obs_data_get_string(settings, "window_name"));
 	[cw->owner_name retain];
 	[cw->window_name retain];
-	pthread_mutex_unlock(&cw->name_lock);
 
 	cw->owner_pid = obs_data_get_int(settings, "owner_pid");
 	cw->window_id = obs_data_get_int(settings, "window");
-}
-
-static inline const char *make_name(NSString *owner, NSString *name)
-{
-	if (!owner.length)
-		return "";
 
-	NSString *str = [NSString stringWithFormat:@"[%@] %@", owner, name];
-	return str.UTF8String;
+	if (pthread_mutex_unlock(&cw->mutex))
+		abort();
 }
 
-static inline NSDictionary *find_window_dict(NSArray *arr, int window_id)
+static inline NSString *make_name(NSString *owner, NSString *name)
 {
-	for (NSDictionary *dict in arr) {
-		NSNumber *wid = (NSNumber *)dict[WINDOW_NUMBER];
-		if (wid.intValue == window_id)
-			return dict;
-	}
+	if (!owner.length)
+		return @"";
 
-	return nil;
+	return [NSString stringWithFormat:@"[%@] %@", owner, name];
 }
 
 static inline bool window_changed_internal(obs_property_t *p,
 					   obs_data_t *settings)
 {
-	int window_id = obs_data_get_int(settings, "window");
-	NSString *window_owner = @(obs_data_get_string(settings, "owner_name"));
+	NSNumber *window_id = @(obs_data_get_int(settings, "window"));
+	NSNumber *owner_pid = @(obs_data_get_int(settings, "owner_pid"));
+	NSString *owner_name = @(obs_data_get_string(settings, "owner_name"));
 	NSString *window_name = @(obs_data_get_string(settings, "window_name"));
 
+	bool show_empty_names = obs_data_get_bool(settings, "show_empty_names");
+
 	NSDictionary *win_info = @{
-		OWNER_NAME: window_owner,
+		OWNER_NAME: owner_name,
+		OWNER_PID: owner_pid,
 		WINDOW_NAME: window_name,
+		WINDOW_NUMBER: window_id,
 	};
 
 	NSArray *arr = enumerate_windows();
+	NSDictionary *cur = nil;
 
-	bool show_empty_names = obs_data_get_bool(settings, "show_empty_names");
+	for (NSDictionary *dict in arr) {
+		if ([window_id isEqualToNumber:dict[WINDOW_NUMBER]]) {
+			cur = dict;
+			break;
+		}
+	}
 
-	NSDictionary *cur = find_window_dict(arr, window_id);
 	bool window_found = cur != nil;
 	bool window_added = window_found;
 
 	obs_property_list_clear(p);
 	for (NSDictionary *dict in arr) {
-		NSString *owner = (NSString *)dict[OWNER_NAME];
-		NSString *name = (NSString *)dict[WINDOW_NAME];
-		NSNumber *wid = (NSNumber *)dict[WINDOW_NUMBER];
-
 		if (!window_added &&
 		    win_info_cmp(win_info, dict) == NSOrderedAscending) {
 			window_added = true;
+
 			size_t idx = obs_property_list_add_int(
-				p, make_name(window_owner, window_name),
-				window_id);
+				p,
+				make_name(owner_name, window_name).UTF8String,
+				window_id.intValue);
+
 			obs_property_list_item_disable(p, idx, true);
 		}
 
-		if (!show_empty_names && !name.length &&
-		    window_id != wid.intValue)
+		if (!show_empty_names &&
+		    (!dict[WINDOW_NAME] || ![dict[WINDOW_NAME] length]) &&
+		    ![window_id isEqualToNumber:dict[WINDOW_NUMBER]])
 			continue;
 
-		obs_property_list_add_int(p, make_name(owner, name),
-					  wid.intValue);
+		obs_property_list_add_int(p,
+					  make_name(dict[OWNER_NAME],
+						    dict[WINDOW_NAME])
+						  .UTF8String,
+					  [dict[WINDOW_NUMBER] intValue]);
 	}
 
 	if (!window_added) {
 		size_t idx = obs_property_list_add_int(
-			p, make_name(window_owner, window_name), window_id);
+			p, make_name(owner_name, window_name).UTF8String,
+			window_id.intValue);
+
 		obs_property_list_item_disable(p, idx, true);
 	}
 
 	if (!window_found)
 		return true;
 
-	NSString *owner = (NSString *)cur[OWNER_NAME];
-	NSString *window = (NSString *)cur[WINDOW_NAME];
-
-	obs_data_set_string(settings, "owner_name", owner.UTF8String);
-	obs_data_set_string(settings, "window_name", window.UTF8String);
+	obs_data_set_int(settings, "window", [cur[WINDOW_NUMBER] intValue]);
+	obs_data_set_int(settings, "owner_pid", [cur[OWNER_PID] intValue]);
+	obs_data_set_string(settings, "owner_name",
+			    [cur[OWNER_NAME] UTF8String]);
+	obs_data_set_string(settings, "window_name",
+			    [cur[WINDOW_NAME] UTF8String]);
 
 	return true;
 }