diff --git a/plugins/mac-capture/mac-display-capture.m b/plugins/mac-capture/mac-display-capture.m index 8301de3c8..9ebd197c2 100644 --- a/plugins/mac-capture/mac-display-capture.m +++ b/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)) + if (find_window(&dc->window, NULL, false)) update_window_params(dc); else dc->on_screen = false; diff --git a/plugins/mac-capture/mac-window-capture.m b/plugins/mac-capture/mac-window-capture.m index fa8108970..17c0c283e 100644 --- a/plugins/mac-capture/mac-window-capture.m +++ b/plugins/mac-capture/mac-window-capture.m @@ -13,8 +13,14 @@ 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; @@ -22,66 +28,56 @@ struct window_capture { static CGImageRef get_image(struct window_capture *wc) { - CFMutableArrayRef arr = - CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); - CFArrayAppendValue(arr, (void *)(uintptr_t)wc->window.window_id); + NSArray *arr = (NSArray *)CGWindowListCreate( + kCGWindowListOptionIncludingWindow, wc->window.window_id); + [arr autorelease]; - CGImageRef image = CGWindowListCreateImageFromArray( - CGRectNull, (CFArrayRef)arr, wc->image_option); - CFRelease(arr); + if (!arr.count && !find_window(&wc->window, NULL, false)) + return NULL; - if (!image && find_window(&wc->window, NULL)) - image = get_image(wc); - - return image; + return CGWindowListCreateImage(CGRectNull, + kCGWindowListOptionIncludingWindow, + wc->window.window_id, wc->image_option); } static inline void capture_frame(struct window_capture *wc) { uint64_t ts = os_gettime_ns(); - CGImageRef image = get_image(wc); - - if (!image) + CGImageRef img = get_image(wc); + if (!img) return; - size_t width = CGImageGetWidth(image); - size_t height = CGImageGetHeight(image); - size_t bytes_per_row = CGImageGetBytesPerRow(image); + size_t width = CGImageGetWidth(img); + size_t height = CGImageGetHeight(img); - if ((!width && !height) || CGImageGetBitsPerPixel(image) != 32 || - CGImageGetBitsPerComponent(image) != 8) { - CGImageRelease(image); - return; - } + CGRect rect = {{0, 0}, {width, height}}; + da_resize(wc->buffer, width * height * 4); + uint8_t *data = wc->buffer.array; - // 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); + 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); struct obs_source_frame frame = { .format = VIDEO_FORMAT_BGRA, .width = width, .height = height, - .data[0] = (uint8_t *)buffer, - .linesize[0] = bytes_per_row, + .data[0] = data, + .linesize[0] = width * 4, .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) @@ -102,6 +98,10 @@ 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,21 +111,7 @@ 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); - /* - * "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(); + pthread_create(&wc->capture_thread, NULL, capture_thread, wc); return wc; } @@ -139,26 +125,29 @@ static void *window_capture_create(obs_data_t *settings, obs_source_t *source) static void window_capture_destroy(void *data) { - struct window_capture *wc = data; + struct window_capture *cap = data; - os_event_signal(wc->stop_event); - os_event_signal(wc->capture_event); + os_event_signal(cap->stop_event); + os_event_signal(cap->capture_event); - pthread_join(wc->capture_thread, NULL); + pthread_join(cap->capture_thread, NULL); - os_event_destroy(wc->capture_event); - os_event_destroy(wc->stop_event); + CGColorSpaceRelease(cap->color_space); - destroy_window(&wc->window); + da_free(cap->buffer); - bfree(wc); + os_event_destroy(cap->capture_event); + os_event_destroy(cap->stop_event); + + destroy_window(&cap->window); + + bfree(cap); } static void window_capture_defaults(obs_data_t *settings) { - window_defaults(settings); - obs_data_set_default_bool(settings, "show_shadow", false); + window_defaults(settings); } static obs_properties_t *window_capture_properties(void *unused) @@ -184,14 +173,15 @@ static inline void window_capture_update_internal(struct window_capture *wc, update_window(&wc->window, settings); - 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]); + 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]); + } } static void window_capture_update(void *data, obs_data_t *settings) @@ -207,16 +197,23 @@ static const char *window_capture_getname(void *unused) return obs_module_text("WindowCapture"); } -static void window_capture_tick(void *data, float seconds) +static inline void window_capture_tick_internal(struct window_capture *wc, + 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; - os_event_signal(wc->capture_event); + @autoreleasepool { + return window_capture_tick_internal(data, seconds); + } } struct obs_source_info window_capture_info = { diff --git a/plugins/mac-capture/window-utils.h b/plugins/mac-capture/window-utils.h index be3173e6b..588e9eda3 100644 --- a/plugins/mac-capture/window-utils.h +++ b/plugins/mac-capture/window-utils.h @@ -8,18 +8,17 @@ struct cocoa_window { CGWindowID window_id; int owner_pid; + pthread_mutex_t name_lock; NSString *owner_name; NSString *window_name; - uint64_t last_search_time; - - pthread_mutex_t mutex; + uint64_t next_search_time; }; typedef struct cocoa_window *cocoa_window_t; -NSArray *enumerate_windows(void); +NSArray *enumerate_cocoa_windows(void); -bool find_window(cocoa_window_t cw, obs_data_t *settings); +bool find_window(cocoa_window_t cw, obs_data_t *settings, bool force); void init_window(cocoa_window_t cw, obs_data_t *settings); diff --git a/plugins/mac-capture/window-utils.m b/plugins/mac-capture/window-utils.m index 33547ae9e..aa2a390f8 100644 --- a/plugins/mac-capture/window-utils.m +++ b/plugins/mac-capture/window-utils.m @@ -27,6 +27,7 @@ NSArray *enumerate_windows(void) { NSArray *arr = (NSArray *)CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + [arr autorelease]; return [arr sortedArrayUsingComparator:win_info_cmp]; @@ -36,205 +37,186 @@ 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 find_window(cocoa_window_t cw, obs_data_t *settings, bool force) { - uint64_t ts = os_gettime_ns(); - - if (cw->last_search_time + WAIT_TIME_NS > ts) + if (!force && cw->next_search_time > os_gettime_ns()) return false; - cw->last_search_time = ts; + cw->next_search_time = os_gettime_ns() + WAIT_TIME_NS; - if (pthread_mutex_lock(&cw->mutex)) - abort(); + pthread_mutex_lock(&cw->name_lock); if (!cw->window_name.length && !cw->owner_name.length) - goto unlock; + goto invalid_name; 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 window_names_match = - (!cw->window_name.length && !dict[WINDOW_NAME]) || - [cw->window_name isEqualToString:dict[WINDOW_NAME]]; - - if (!owner_names_match || !window_names_match) + if (![cw->owner_name isEqualToString:dict[OWNER_NAME]]) continue; - cw->window_id = [dict[WINDOW_NUMBER] intValue]; - cw->owner_pid = [dict[OWNER_PID] intValue]; + if (![cw->window_name isEqualToString:dict[WINDOW_NAME]]) + 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; 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; } -unlock: - if (pthread_mutex_unlock(&cw->mutex)) - abort(); - +invalid_name: + pthread_mutex_unlock(&cw->name_lock); return false; } void init_window(cocoa_window_t cw, obs_data_t *settings) { - 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"); + pthread_mutex_init(&cw->name_lock, NULL); 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]; - cw->last_search_time = 0; + // Find initial window. + pthread_mutex_lock(&cw->name_lock); if (!cw->window_name.length && !cw->owner_name.length) - return; - - NSNumber *window_id = @(cw->window_id); - NSNumber *owner_pid = @(cw->owner_pid); + goto invalid_name; + 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)) { - cw->window_id = [dict[WINDOW_NUMBER] intValue]; - cw->owner_pid = [dict[OWNER_PID] intValue]; + 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; obs_data_set_int(settings, "window", cw->window_id); obs_data_set_int(settings, "owner_pid", cw->owner_pid); - - break; + return; } } + +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) { - if (pthread_mutex_lock(&cw->mutex)) - abort(); - + pthread_mutex_lock(&cw->name_lock); [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"); - - if (pthread_mutex_unlock(&cw->mutex)) - abort(); } -static inline NSString *make_name(NSString *owner, NSString *name) +static inline const char *make_name(NSString *owner, NSString *name) { if (!owner.length) - return @""; + return ""; - return [NSString stringWithFormat:@"[%@] %@", owner, name]; + NSString *str = [NSString stringWithFormat:@"[%@] %@", owner, name]; + return str.UTF8String; +} + +static inline NSDictionary *find_window_dict(NSArray *arr, int window_id) +{ + for (NSDictionary *dict in arr) { + NSNumber *wid = (NSNumber *)dict[WINDOW_NUMBER]; + if (wid.intValue == window_id) + return dict; + } + + return nil; } static inline bool window_changed_internal(obs_property_t *p, obs_data_t *settings) { - 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")); + int window_id = obs_data_get_int(settings, "window"); + NSString *window_owner = @(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: owner_name, - OWNER_PID: owner_pid, + OWNER_NAME: window_owner, WINDOW_NAME: window_name, - WINDOW_NUMBER: window_id, }; NSArray *arr = enumerate_windows(); - NSDictionary *cur = nil; - for (NSDictionary *dict in arr) { - if ([window_id isEqualToNumber:dict[WINDOW_NUMBER]]) { - cur = dict; - break; - } - } + bool show_empty_names = obs_data_get_bool(settings, "show_empty_names"); + 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(owner_name, window_name).UTF8String, - window_id.intValue); - + p, make_name(window_owner, window_name), + window_id); obs_property_list_item_disable(p, idx, true); } - if (!show_empty_names && - (!dict[WINDOW_NAME] || ![dict[WINDOW_NAME] length]) && - ![window_id isEqualToNumber:dict[WINDOW_NUMBER]]) + if (!show_empty_names && !name.length && + window_id != wid.intValue) continue; - obs_property_list_add_int(p, - make_name(dict[OWNER_NAME], - dict[WINDOW_NAME]) - .UTF8String, - [dict[WINDOW_NUMBER] intValue]); + obs_property_list_add_int(p, make_name(owner, name), + wid.intValue); } if (!window_added) { size_t idx = obs_property_list_add_int( - p, make_name(owner_name, window_name).UTF8String, - window_id.intValue); - + p, make_name(window_owner, window_name), window_id); obs_property_list_item_disable(p, idx, true); } if (!window_found) return true; - 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]); + 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); return true; }