Skip to content

Commit

Permalink
win-capture: Add audio capture option to window/game capture
Browse files Browse the repository at this point in the history
  • Loading branch information
derrod committed Dec 11, 2023
1 parent 48f9969 commit 4b28631
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 5 deletions.
2 changes: 2 additions & 0 deletions plugins/win-capture/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ target_sources(
PRIVATE # cmake-format: sortable
app-helpers.c
app-helpers.h
audio-helpers.c
audio-helpers.h
compat-format-ver.h
compat-helpers.c
compat-helpers.h
Expand Down
136 changes: 136 additions & 0 deletions plugins/win-capture/audio-helpers.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#include "audio-helpers.h"

#include <util/dstr.h>

static inline bool settings_changed(obs_data_t *old_settings,
obs_data_t *new_settings)
{
const char *old_window = obs_data_get_string(old_settings, "window");
const char *new_window = obs_data_get_string(new_settings, "window");

enum window_priority old_priority =
obs_data_get_int(old_settings, "priority");
enum window_priority new_priority =
obs_data_get_int(new_settings, "priority");

// Changes to priority only matter if a window is set
return (old_priority != new_priority && *new_window) ||
strcmp(old_window, new_window) != 0;
}

static inline void reroute_wasapi_source(obs_source_t *wasapi,
obs_source_t *target)
{
proc_handler_t *ph = obs_source_get_proc_handler(wasapi);
calldata_t *cd = calldata_create();
calldata_set_ptr(cd, "target", target);
proc_handler_call(ph, "reroute_audio", cd);
calldata_free(cd);
}

void setup_audio_source(obs_source_t *parent, obs_source_t **child,
const char *window, bool enabled,
enum window_priority priority)
{
if (enabled) {
obs_data_t *wasapi_settings = NULL;

if (window) {
wasapi_settings = obs_data_create();
obs_data_set_string(wasapi_settings, "window", window);
obs_data_set_int(wasapi_settings, "priority", priority);
}

if (!*child) {
struct dstr name = {0};
dstr_printf(&name, "%s (%s)",
obs_source_get_name(parent),
TEXT_CAPTURE_AUDIO_SUFFIX);

*child = obs_source_create_private(
AUDIO_SOURCE_TYPE, name.array, wasapi_settings);

// Ensure child gets activated/deactivated properly
obs_source_add_active_child(parent, *child);
// Reroute audio to come from window/game capture source
reroute_wasapi_source(*child, parent);
// Show source in mixer
obs_source_set_audio_active(parent, true);

dstr_free(&name);
} else if (wasapi_settings) {
obs_data_t *old_settings =
obs_source_get_settings(*child);
// Only bother updating if settings changed
if (settings_changed(old_settings, wasapi_settings))
obs_source_update(*child, wasapi_settings);

obs_data_release(old_settings);
}

obs_data_release(wasapi_settings);
} else {
obs_source_set_audio_active(parent, false);

if (*child) {
reroute_wasapi_source(*child, NULL);
obs_source_remove_active_child(parent, *child);
obs_source_release(*child);
*child = NULL;
}
}
}

static inline void encode_dstr(struct dstr *str)
{
dstr_replace(str, "#", "#22");
dstr_replace(str, ":", "#3A");
}

void reconfigure_audio_source(obs_source_t *source, HWND window)
{
struct dstr title = {0};
struct dstr class = {0};
struct dstr exe = {0};
struct dstr encoded = {0};

ms_get_window_title(&title, window);
ms_get_window_class(&class, window);
ms_get_window_exe(&exe, window);

encode_dstr(&title);
encode_dstr(&class);
encode_dstr(&exe);

dstr_cat_dstr(&encoded, &title);
dstr_cat(&encoded, ":");
dstr_cat_dstr(&encoded, &class);
dstr_cat(&encoded, ":");
dstr_cat_dstr(&encoded, &exe);

obs_data_t *audio_settings = obs_data_create();
obs_data_set_string(audio_settings, "window", encoded.array);
obs_data_set_int(audio_settings, "priority", WINDOW_PRIORITY_CLASS);

obs_source_update(source, audio_settings);

obs_data_release(audio_settings);
dstr_free(&encoded);
dstr_free(&title);
dstr_free(&class);
dstr_free(&exe);
}

void rename_audio_source(void *param, calldata_t *data)
{
obs_source_t *src = *(obs_source_t **)param;
if (!src)
return;

struct dstr name = {0};
dstr_printf(&name, "%s (%s)", calldata_string(data, "new_name"),
TEXT_CAPTURE_AUDIO_SUFFIX);

obs_source_set_name(src, name.array);
dstr_free(&name);
}
28 changes: 28 additions & 0 deletions plugins/win-capture/audio-helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include "obs-module.h"
#include <util/windows/window-helpers.h>

#include "windows.h"

#define SETTING_CAPTURE_AUDIO "capture_audio"
#define TEXT_CAPTURE_AUDIO obs_module_text("CaptureAudio")
#define TEXT_CAPTURE_AUDIO_TT obs_module_text("CaptureAudio.TT")
#define TEXT_CAPTURE_AUDIO_SUFFIX obs_module_text("AudioSuffix")
#define AUDIO_SOURCE_TYPE "wasapi_process_output_capture"

void setup_audio_source(obs_source_t *parent, obs_source_t **child,
const char *window, bool enabled,
enum window_priority priority);
void reconfigure_audio_source(obs_source_t *source, HWND window);
void rename_audio_source(void *param, calldata_t *data);

static bool audio_capture_available(void)
{
return obs_get_latest_input_type_id(AUDIO_SOURCE_TYPE) != NULL;
}

static void destroy_audio_source(obs_source_t *parent, obs_source_t **child)
{
setup_audio_source(parent, child, NULL, false, 0);
}
2 changes: 2 additions & 0 deletions plugins/win-capture/cmake/legacy.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ target_sources(
PRIVATE plugin-main.c
app-helpers.c
app-helpers.h
audio-helpers.c
audio-helpers.h
cursor-capture.c
cursor-capture.h
dc-capture.c
Expand Down
3 changes: 3 additions & 0 deletions plugins/win-capture/data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ GameCapture.Rgb10a2Space="RGB10A2 Color Space"
GameCapture.Rgb10a2Space.Srgb="sRGB"
GameCapture.Rgb10a2Space.2100PQ="Rec. 2100 (PQ)"
Mode="Mode"
CaptureAudio="Capture Audio (BETA)"
CaptureAudio.TT="When enabled, creates an \"Application Audio Capture\" source that automatically updates to the currently captured window/application. Note that if Desktop Audio is configured, this could result in doubled audio."
AudioSuffix="Audio"

# Generic compatibility messages
Compatibility.GameCapture.Admin="%name% may require OBS to be run as admin to use Game Capture."
Expand Down
46 changes: 44 additions & 2 deletions plugins/win-capture/game-capture.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "graphics-hook-ver.h"
#include "cursor-capture.h"
#include "app-helpers.h"
#include "audio-helpers.h"
#include "nt-stuff.h"

#define do_log(level, format, ...) \
Expand Down Expand Up @@ -117,6 +118,7 @@ struct game_capture_config {
bool anticheat_hook;
enum hook_rate hook_rate;
bool is_10a2_2100pq;
bool capture_audio;
};

typedef DPI_AWARENESS_CONTEXT(WINAPI *PFN_SetThreadDpiAwarenessContext)(
Expand All @@ -126,6 +128,7 @@ typedef DPI_AWARENESS_CONTEXT(WINAPI *PFN_GetWindowDpiAwarenessContext)(HWND);

struct game_capture {
obs_source_t *source;
obs_source_t *audio_source;

struct cursor_data cursor_data;
HANDLE injector_process;
Expand Down Expand Up @@ -398,6 +401,13 @@ static void game_capture_destroy(void *data)
struct game_capture *gc = data;
stop_capture(gc);

if (gc->audio_source)
destroy_audio_source(gc->source, &gc->audio_source);

signal_handler_t *sh = obs_source_get_signal_handler(gc->source);
signal_handler_disconnect(sh, "rename", rename_audio_source,
&gc->audio_source);

if (gc->hotkey_pair)
obs_hotkey_pair_unregister(gc->hotkey_pair);

Expand Down Expand Up @@ -457,6 +467,7 @@ static inline void get_config(struct game_capture_config *cfg,
cfg->is_10a2_2100pq =
strcmp(obs_data_get_string(settings, SETTING_RGBA10A2_SPACE),
"2100pq") == 0;
cfg->capture_audio = obs_data_get_bool(settings, SETTING_CAPTURE_AUDIO);
}

static inline int s_cmp(const char *str1, const char *str2)
Expand Down Expand Up @@ -592,6 +603,11 @@ static void game_capture_update(void *data, obs_data_t *settings)
} else {
gc->initial_config = false;
}

/* Linked audio capture source stuff */
setup_audio_source(gc->source, &gc->audio_source,
cfg.mode == CAPTURE_MODE_WINDOW ? window : NULL,
cfg.capture_audio, cfg.priority);
}

extern void wait_for_hook_initialization(void);
Expand Down Expand Up @@ -651,6 +667,9 @@ static void *game_capture_create(obs_data_t *settings, obs_source_t *source)
"void get_hooked(out bool hooked, out string title, out string class, out string executable)",
game_capture_get_hooked, gc);

signal_handler_connect(sh, "rename", rename_audio_source,
&gc->audio_source);

game_capture_update(gc, settings);
return gc;
}
Expand Down Expand Up @@ -1910,6 +1929,13 @@ static void game_capture_tick(void *data, float seconds)

signal_handler_signal(sh, "hooked", &data);
calldata_free(&data);

// Update audio capture settings if not in window mode
if (gc->audio_source &&
gc->config.mode != CAPTURE_MODE_WINDOW) {
reconfigure_audio_source(gc->audio_source,
gc->window);
}
}
if (result != CAPTURE_RETRY && !gc->capturing) {
gc->retry_interval =
Expand Down Expand Up @@ -2442,6 +2468,12 @@ static obs_properties_t *game_capture_properties(void *data)
OBS_TEXT_INFO);
obs_property_set_enabled(p, false);

if (audio_capture_available()) {
p = obs_properties_add_bool(ppts, SETTING_CAPTURE_AUDIO,
TEXT_CAPTURE_AUDIO);
obs_property_set_long_description(p, TEXT_CAPTURE_AUDIO_TT);
}

obs_properties_add_bool(ppts, SETTING_COMPATIBILITY,
TEXT_SLI_COMPATIBILITY);

Expand Down Expand Up @@ -2516,18 +2548,28 @@ game_capture_get_color_space(void *data, size_t count,
return space;
}

static void game_capture_enum(void *data, obs_source_enum_proc_t cb,
void *param)
{
struct game_capture *gc = data;
if (gc->audio_source)
cb(gc->source, gc->audio_source, param);
}

struct obs_source_info game_capture_info = {
.id = "game_capture",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_SRGB,
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_AUDIO |
OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_DO_NOT_DUPLICATE |
OBS_SOURCE_SRGB,
.get_name = game_capture_name,
.create = game_capture_create,
.destroy = game_capture_destroy,
.get_width = game_capture_width,
.get_height = game_capture_height,
.get_defaults = game_capture_defaults,
.get_properties = game_capture_properties,
.enum_active_sources = game_capture_enum,
.update = game_capture_update,
.video_tick = game_capture_tick,
.video_render = game_capture_render,
Expand Down
Loading

0 comments on commit 4b28631

Please sign in to comment.