Revert "mac-capture: Improve window capture performance"

This reverts commit 257715d31f300aa4838784954a8caf567a04855d.

Was merged prematurely by mistake (clicked the merge button on the wrong
tab).
This commit is contained in:
jp9000 2021-12-20 14:02:58 -08:00
parent 447b17e75e
commit 51050b3075
4 changed files with 145 additions and 167 deletions

View File

@ -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;

View File

@ -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 = {

View File

@ -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);

View File

@ -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;
}