Reimplement monitor capture
- Implement windows monitor capture (code is so much cleaner than in OBS1). Will implement duplication capture later - Add GDI texture support to d3d11 graphics library - Fix precision issue with sleep timing, you have to call timeBeginPeriod otherwise windows sleep will be totally erratic.
This commit is contained in:
@@ -3,6 +3,7 @@ include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs")
|
||||
if(WIN32)
|
||||
add_subdirectory(dshow)
|
||||
add_subdirectory(win-wasapi)
|
||||
add_subdirectory(win-capture)
|
||||
elseif(APPLE)
|
||||
add_subdirectory(mac-capture)
|
||||
endif()
|
||||
|
18
plugins/win-capture/CMakeLists.txt
Normal file
18
plugins/win-capture/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
project(win-capture)
|
||||
|
||||
set(win-capture_HEADERS
|
||||
dc-capture.h)
|
||||
|
||||
set(win-capture_SOURCES
|
||||
dc-capture.c
|
||||
monitor-capture.c
|
||||
plugin-main.c)
|
||||
|
||||
add_library(win-capture MODULE
|
||||
${win-capture_SOURCES}
|
||||
${win-capture_HEADERS})
|
||||
target_link_libraries(win-capture
|
||||
libobs)
|
||||
|
||||
install_obs_plugin(win-capture)
|
||||
install_obs_plugin_data(win-capture ../../build/data/obs-plugins/win-capture)
|
239
plugins/win-capture/dc-capture.c
Normal file
239
plugins/win-capture/dc-capture.c
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "dc-capture.h"
|
||||
|
||||
#define WIN32_MEAN_AND_LEAN
|
||||
#include <windows.h>
|
||||
|
||||
static inline void init_textures(struct dc_capture *capture)
|
||||
{
|
||||
for (size_t i = 0; i < capture->num_textures; i++) {
|
||||
if (capture->compatibility)
|
||||
capture->textures[i] = gs_create_texture(
|
||||
capture->width, capture->height,
|
||||
GS_BGRA, 1, NULL, GS_DYNAMIC);
|
||||
else
|
||||
capture->textures[i] = gs_create_gdi_texture(
|
||||
capture->width, capture->height);
|
||||
|
||||
if (!capture->textures[i]) {
|
||||
blog(LOG_WARNING, "[dc_capture_init] Failed to "
|
||||
"create textures");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
capture->valid = true;
|
||||
}
|
||||
|
||||
void dc_capture_init(struct dc_capture *capture, int x, int y,
|
||||
uint32_t width, uint32_t height, bool cursor,
|
||||
bool compatibility)
|
||||
{
|
||||
capture->x = x;
|
||||
capture->y = y;
|
||||
capture->width = width;
|
||||
capture->height = height;
|
||||
capture->capture_cursor = cursor;
|
||||
|
||||
gs_entercontext(obs_graphics());
|
||||
|
||||
if (!gs_gdi_texture_available())
|
||||
compatibility = true;
|
||||
|
||||
capture->compatibility = compatibility;
|
||||
capture->num_textures = compatibility ? 1 : 2;
|
||||
|
||||
init_textures(capture);
|
||||
|
||||
gs_leavecontext();
|
||||
|
||||
if (!capture->valid)
|
||||
return;
|
||||
|
||||
if (compatibility) {
|
||||
BITMAPINFO bi = {0};
|
||||
BITMAPINFOHEADER *bih = &bi.bmiHeader;
|
||||
bih->biSize = sizeof(BITMAPINFOHEADER);
|
||||
bih->biBitCount = 32;
|
||||
bih->biWidth = width;
|
||||
bih->biHeight = height;
|
||||
bih->biPlanes = 1;
|
||||
|
||||
capture->hdc = CreateCompatibleDC(NULL);
|
||||
capture->bmp = CreateDIBSection(capture->hdc, &bi,
|
||||
DIB_RGB_COLORS, (void**)&capture->bits,
|
||||
NULL, 0);
|
||||
capture->old_bmp = SelectObject(capture->hdc, capture->bmp);
|
||||
}
|
||||
}
|
||||
|
||||
void dc_capture_free(struct dc_capture *capture)
|
||||
{
|
||||
if (capture->hdc) {
|
||||
SelectObject(capture->hdc, capture->old_bmp);
|
||||
DeleteDC(capture->hdc);
|
||||
DeleteObject(capture->bmp);
|
||||
}
|
||||
|
||||
gs_entercontext(obs_graphics());
|
||||
|
||||
for (size_t i = 0; i < capture->num_textures; i++)
|
||||
texture_destroy(capture->textures[i]);
|
||||
|
||||
gs_leavecontext();
|
||||
|
||||
memset(capture, 0, sizeof(struct dc_capture));
|
||||
}
|
||||
|
||||
static void draw_cursor(struct dc_capture *capture, HDC hdc)
|
||||
{
|
||||
HICON icon;
|
||||
ICONINFO ii;
|
||||
CURSORINFO *ci = &capture->ci;
|
||||
|
||||
if (!(capture->ci.flags & CURSOR_SHOWING))
|
||||
return;
|
||||
|
||||
icon = CopyIcon(capture->ci.hCursor);
|
||||
if (!icon)
|
||||
return;
|
||||
|
||||
if (GetIconInfo(icon, &ii)) {
|
||||
POINT pos;
|
||||
pos.x = ci->ptScreenPos.x - (int)ii.xHotspot - capture->x;
|
||||
pos.y = ci->ptScreenPos.y - (int)ii.yHotspot - capture->y;
|
||||
|
||||
DrawIcon(hdc, pos.x, pos.y, icon);
|
||||
|
||||
DeleteObject(ii.hbmColor);
|
||||
DeleteObject(ii.hbmMask);
|
||||
}
|
||||
|
||||
DestroyIcon(icon);
|
||||
}
|
||||
|
||||
static inline HDC dc_capture_get_dc(struct dc_capture *capture)
|
||||
{
|
||||
if (!capture->valid)
|
||||
return NULL;
|
||||
|
||||
if (capture->compatibility)
|
||||
return capture->hdc;
|
||||
else
|
||||
return texture_get_dc(capture->textures[capture->cur_tex]);
|
||||
}
|
||||
|
||||
static inline void dc_capture_release_dc(struct dc_capture *capture)
|
||||
{
|
||||
if (capture->compatibility) {
|
||||
texture_setimage(capture->textures[capture->cur_tex],
|
||||
capture->bits, capture->width*4, false);
|
||||
} else {
|
||||
texture_release_dc(capture->textures[capture->cur_tex]);
|
||||
}
|
||||
}
|
||||
|
||||
void dc_capture_capture(struct dc_capture *capture, HWND window)
|
||||
{
|
||||
HDC hdc_target;
|
||||
HDC hdc;
|
||||
|
||||
if (capture->capture_cursor) {
|
||||
memset(&capture->ci, 0, sizeof(CURSORINFO));
|
||||
capture->ci.cbSize = sizeof(CURSORINFO);
|
||||
capture->cursor_captured = GetCursorInfo(&capture->ci);
|
||||
}
|
||||
|
||||
if (++capture->cur_tex == capture->num_textures)
|
||||
capture->cur_tex = 0;
|
||||
|
||||
hdc = dc_capture_get_dc(capture);
|
||||
if (!hdc) {
|
||||
blog(LOG_WARNING, "[capture_screen] Failed to get "
|
||||
"texture DC");
|
||||
return;
|
||||
}
|
||||
|
||||
hdc_target = GetDC(window);
|
||||
|
||||
BitBlt(hdc, 0, 0, capture->width, capture->height,
|
||||
hdc_target, capture->x, capture->y, SRCCOPY);
|
||||
|
||||
ReleaseDC(NULL, hdc_target);
|
||||
|
||||
if (capture->cursor_captured)
|
||||
draw_cursor(capture, hdc);
|
||||
|
||||
dc_capture_release_dc(capture);
|
||||
|
||||
capture->textures_written[capture->cur_tex] = true;
|
||||
}
|
||||
|
||||
static void draw_texture(struct dc_capture *capture, int id, effect_t effect)
|
||||
{
|
||||
texture_t texture = capture->textures[id];
|
||||
technique_t tech = effect_gettechnique(effect, "Draw");
|
||||
eparam_t image = effect_getparambyname(effect, "image");
|
||||
size_t passes;
|
||||
|
||||
effect_settexture(effect, image, texture);
|
||||
|
||||
passes = technique_begin(tech);
|
||||
for (size_t i = 0; i < passes; i++) {
|
||||
if (technique_beginpass(tech, i)) {
|
||||
if (capture->compatibility)
|
||||
gs_draw_sprite(texture, GS_FLIP_V, 0, 0);
|
||||
else
|
||||
gs_draw_sprite(texture, 0, 0, 0);
|
||||
|
||||
technique_endpass(tech);
|
||||
}
|
||||
}
|
||||
technique_end(tech);
|
||||
}
|
||||
|
||||
void dc_capture_render(struct dc_capture *capture, effect_t effect)
|
||||
{
|
||||
int last_tex = (capture->cur_tex > 0) ?
|
||||
capture->cur_tex-1 : capture->num_textures-1;
|
||||
|
||||
if (!capture->valid)
|
||||
return;
|
||||
|
||||
if (capture->textures_written[last_tex])
|
||||
draw_texture(capture, last_tex, effect);
|
||||
}
|
||||
|
||||
effect_t create_opaque_effect(void)
|
||||
{
|
||||
effect_t opaque_effect;
|
||||
char *effect_file;
|
||||
char *error_string = NULL;
|
||||
|
||||
effect_file = obs_find_plugin_file("win-capture/opaque.effect");
|
||||
if (!effect_file) {
|
||||
blog(LOG_ERROR, "[create_opaque_effect] Could not find "
|
||||
"opaque effect file");
|
||||
return false;
|
||||
}
|
||||
|
||||
gs_entercontext(obs_graphics());
|
||||
|
||||
opaque_effect = gs_create_effect_from_file(effect_file, &error_string);
|
||||
|
||||
if (!opaque_effect) {
|
||||
if (error_string)
|
||||
blog(LOG_ERROR, "[create_opaque_effect] Failed to "
|
||||
"create opaque effect:\n%s",
|
||||
error_string);
|
||||
else
|
||||
blog(LOG_ERROR, "[create_opaque_effect] Failed to "
|
||||
"create opaque effect");
|
||||
}
|
||||
|
||||
bfree(effect_file);
|
||||
bfree(error_string);
|
||||
|
||||
gs_leavecontext();
|
||||
|
||||
return opaque_effect;
|
||||
}
|
39
plugins/win-capture/dc-capture.h
Normal file
39
plugins/win-capture/dc-capture.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <obs.h>
|
||||
|
||||
#define NUM_TEXTURES 2
|
||||
|
||||
struct dc_capture {
|
||||
int cur_tex;
|
||||
texture_t textures[NUM_TEXTURES];
|
||||
bool textures_written[NUM_TEXTURES];
|
||||
int x, y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
int num_textures;
|
||||
|
||||
bool compatibility;
|
||||
HDC hdc;
|
||||
HBITMAP bmp, old_bmp;
|
||||
BYTE *bits;
|
||||
|
||||
bool capture_cursor;
|
||||
bool cursor_captured;
|
||||
CURSORINFO ci;
|
||||
|
||||
bool valid;
|
||||
};
|
||||
|
||||
extern void dc_capture_init(struct dc_capture *capture, int x, int y,
|
||||
uint32_t width, uint32_t height, bool cursor,
|
||||
bool compatibility);
|
||||
extern void dc_capture_free(struct dc_capture *capture);
|
||||
|
||||
extern void dc_capture_capture(struct dc_capture *capture, HWND window);
|
||||
extern void dc_capture_render(struct dc_capture *capture, effect_t effect);
|
||||
|
||||
extern effect_t create_opaque_effect(void);
|
147
plugins/win-capture/monitor-capture.c
Normal file
147
plugins/win-capture/monitor-capture.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#include <util/dstr.h>
|
||||
#include "dc-capture.h"
|
||||
|
||||
struct monitor_capture {
|
||||
obs_source_t source;
|
||||
|
||||
int monitor;
|
||||
bool capture_cursor;
|
||||
bool compatibility;
|
||||
|
||||
struct dc_capture data;
|
||||
|
||||
effect_t opaque_effect;
|
||||
};
|
||||
|
||||
struct monitor_info {
|
||||
int cur_id;
|
||||
int desired_id;
|
||||
int id;
|
||||
RECT rect;
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static inline void do_log(int level, const char *msg, ...)
|
||||
{
|
||||
va_list args;
|
||||
struct dstr str = {0};
|
||||
|
||||
va_start(args, msg);
|
||||
|
||||
dstr_copy(&str, "[GDI monitor capture]: ");
|
||||
dstr_vcatf(&str, msg, args);
|
||||
blog(level, "%s", str.array);
|
||||
dstr_free(&str);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static BOOL CALLBACK enum_monitor(HMONITOR handle, HDC hdc, LPRECT rect,
|
||||
LPARAM param)
|
||||
{
|
||||
struct monitor_info *monitor = (struct monitor_info *)param;
|
||||
|
||||
if (monitor->cur_id == 0 || monitor->desired_id == monitor->cur_id) {
|
||||
monitor->rect = *rect;
|
||||
monitor->id = monitor->cur_id;
|
||||
}
|
||||
|
||||
return (monitor->desired_id < monitor->cur_id++);
|
||||
}
|
||||
|
||||
static void update_monitor(struct monitor_capture *capture,
|
||||
obs_data_t settings)
|
||||
{
|
||||
struct monitor_info monitor = {0};
|
||||
uint32_t width, height;
|
||||
|
||||
monitor.desired_id = (int)obs_data_getint(settings, "monitor");
|
||||
EnumDisplayMonitors(NULL, NULL, enum_monitor, (LPARAM)&monitor);
|
||||
|
||||
capture->monitor = monitor.id;
|
||||
|
||||
width = monitor.rect.right - monitor.rect.left;
|
||||
height = monitor.rect.bottom - monitor.rect.top;
|
||||
|
||||
dc_capture_init(&capture->data, monitor.rect.left, monitor.rect.top,
|
||||
width, height, capture->capture_cursor,
|
||||
capture->compatibility);
|
||||
}
|
||||
|
||||
static inline void update_settings(struct monitor_capture *capture,
|
||||
obs_data_t settings)
|
||||
{
|
||||
capture->capture_cursor = obs_data_getbool(settings, "capture_cursor");
|
||||
capture->compatibility = obs_data_getbool(settings, "compatibility");
|
||||
|
||||
dc_capture_free(&capture->data);
|
||||
update_monitor(capture, settings);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static const char *monitor_capture_getname(const char *locale)
|
||||
{
|
||||
/* TODO: translate */
|
||||
return "Monitor Capture";
|
||||
}
|
||||
|
||||
static void monitor_capture_destroy(void *data)
|
||||
{
|
||||
struct monitor_capture *capture = data;
|
||||
|
||||
gs_entercontext(obs_graphics());
|
||||
|
||||
dc_capture_free(&capture->data);
|
||||
effect_destroy(capture->opaque_effect);
|
||||
|
||||
gs_leavecontext();
|
||||
|
||||
bfree(capture);
|
||||
}
|
||||
|
||||
static void *monitor_capture_create(obs_data_t settings, obs_source_t source)
|
||||
{
|
||||
struct monitor_capture *capture;
|
||||
effect_t opaque_effect = create_opaque_effect();
|
||||
|
||||
if (!opaque_effect)
|
||||
return NULL;
|
||||
|
||||
capture = bzalloc(sizeof(struct monitor_capture));
|
||||
capture->opaque_effect = opaque_effect;
|
||||
|
||||
obs_data_set_default_int(settings, "monitor", 0);
|
||||
obs_data_set_default_bool(settings, "capture_cursor", true);
|
||||
obs_data_set_default_bool(settings, "compatibility", false);
|
||||
update_settings(capture, settings);
|
||||
|
||||
return capture;
|
||||
}
|
||||
|
||||
static void monitor_capture_tick(void *data, float seconds)
|
||||
{
|
||||
struct monitor_capture *capture = data;
|
||||
|
||||
gs_entercontext(obs_graphics());
|
||||
dc_capture_capture(&capture->data, NULL);
|
||||
gs_leavecontext();
|
||||
}
|
||||
|
||||
static void monitor_capture_render(void *data, effect_t effect)
|
||||
{
|
||||
struct monitor_capture *capture = data;
|
||||
dc_capture_render(&capture->data, capture->opaque_effect);
|
||||
}
|
||||
|
||||
struct obs_source_info monitor_capture_info = {
|
||||
.id = "monitor_capture",
|
||||
.type = OBS_SOURCE_TYPE_INPUT,
|
||||
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW,
|
||||
.getname = monitor_capture_getname,
|
||||
.create = monitor_capture_create,
|
||||
.destroy = monitor_capture_destroy,
|
||||
.video_render = monitor_capture_render,
|
||||
.video_tick = monitor_capture_tick
|
||||
};
|
13
plugins/win-capture/plugin-main.c
Normal file
13
plugins/win-capture/plugin-main.c
Normal file
@@ -0,0 +1,13 @@
|
||||
#include <obs-module.h>
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
|
||||
extern struct obs_source_info monitor_capture_info;
|
||||
|
||||
bool obs_module_load(uint32_t libobs_ver)
|
||||
{
|
||||
obs_register_source(&monitor_capture_info);
|
||||
|
||||
UNUSED_PARAMETER(libobs_ver);
|
||||
return true;
|
||||
}
|
Reference in New Issue
Block a user