Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/linux/
/macos/
/macos-aarch64/
/windows/
/gst-delayimp/
/cross.txt
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ Prebuilt
---

Experimental prebuilt 64-bit Windows plugin is available. You still require the
official [GStreamer run-time] (MinGW version) to be installed. Make sure the
run-time `bin` path is added to Windows's `PATH` environment.
official [GStreamer run-time] (MinGW version) to be installed. No further setup
is needed: the plugin locates the run-time itself via the
`GSTREAMER_1_0_ROOT_MINGW_X86_64` environment variable the official installer
sets, so adding it to `PATH` is not required.

Experimental prebuilt macOS plugin available. You still require the GStreamer
run-time installed via [Macports] (not Homebrew).
Expand Down
37 changes: 37 additions & 0 deletions docker/build-windows.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
set -e

export PKG_CONFIG_PATH=/c/gstreamer/1.0/x86_64/lib/pkgconfig/

rm -f cross.txt
{
echo "[binaries]"
echo "c = 'x86_64-w64-mingw32-gcc'"
echo "cpp = 'x86_64-w64-mingw32-g++'"
echo "ar = 'x86_64-w64-mingw32-ar'"
echo "strip = 'x86_64-w64-mingw32-strip'"
echo "pkgconfig = 'x86_64-w64-mingw32-pkg-config'"
echo "windres = 'x86_64-w64-mingw32-windres'"
echo ""
echo "[properties]"
echo "c_link_args = ['-static-libgcc', '-L/bin/64bit']"
echo "pkg_config_libdir = '/c/gstreamer/1.0/mingw_x86_64/lib/pkgconfig'"
echo ""
echo "[host_machine]"
echo "system = 'windows'"
echo "cpu_family = 'x86_64'"
echo "cpu = 'x86_64'"
echo "endian = 'little'"
} >> cross.txt

export C_INCLUDE_PATH=/

rm -rf gst-delayimp
bash docker/gen-delayimp-libs.sh /c/gstreamer/1.0/mingw_x86_64/bin gst-delayimp

meson --buildtype release --cross-file cross.txt -Dpkg_config_path=/c/gstreamer/1.0/x86_64/lib/pkgconfig/ \
-Dgst_delayimp_dir="$(pwd)/gst-delayimp" windows
ln -sf /c/ "windows/c:"
ninja -C windows

echo "Build complete: windows/obs-gstreamer.dll"
31 changes: 31 additions & 0 deletions docker/gen-delayimp-libs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash
# Generates delay-import libraries for the GStreamer/GLib DLLs obs-gstreamer
# directly links against on Windows, so obs-gstreamer.dll itself doesn't need
# them resolvable at LoadLibrary time (see gstreamer.c's
# configure_gstreamer_windows_paths()).
set -e

BIN_DIR="$1"
OUT_DIR="$2"

if [ -z "$BIN_DIR" ] || [ -z "$OUT_DIR" ]; then
echo "usage: $0 <gstreamer-bin-dir> <output-dir>" >&2
exit 1
fi

mkdir -p "$OUT_DIR"

DLLS="libgstreamer-1.0-0 libgobject-2.0-0 libglib-2.0-0 libintl-8 libgstvideo-1.0-0 libgstbase-1.0-0 libgstaudio-1.0-0 libgsttag-1.0-0 libgstapp-1.0-0 libgstnet-1.0-0 libgio-2.0-0"

for name in $DLLS; do
dll="$BIN_DIR/$name.dll"
if [ ! -f "$dll" ]; then
echo "missing $dll" >&2
exit 1
fi
( cd "$OUT_DIR" && gendef "$dll" )
x86_64-w64-mingw32-dlltool \
-d "$OUT_DIR/$name.def" \
--dllname "$name.dll" \
--output-delaylib "$OUT_DIR/lib${name}_delay.a"
done
12 changes: 6 additions & 6 deletions docker/mingw
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ RUN wget https://github.com/obsproject/obs-studio/releases/download/26.0.2/OBS-S
&& rm OBS-Studio-26.0.2-Full-x64.zip \
&& rm 26.0.2.tar.gz

RUN wget https://gstreamer.freedesktop.org/data/pkg/windows/1.18.1/mingw/gstreamer-1.0-devel-mingw-x86_64-1.18.1.msi \
&& wget https://gstreamer.freedesktop.org/data/pkg/windows/1.18.1/mingw/gstreamer-1.0-mingw-x86_64-1.18.1.msi \
&& msiexec -passive -i gstreamer-1.0-mingw-x86_64-1.18.1.msi \
&& msiexec -passive -i gstreamer-1.0-devel-mingw-x86_64-1.18.1.msi \
RUN wget https://gstreamer.freedesktop.org/data/pkg/windows/1.18.6/mingw/gstreamer-1.0-devel-mingw-x86_64-1.18.6.msi \
&& wget https://gstreamer.freedesktop.org/data/pkg/windows/1.18.6/mingw/gstreamer-1.0-mingw-x86_64-1.18.6.msi \
&& msiexec -passive -i gstreamer-1.0-mingw-x86_64-1.18.6.msi \
&& msiexec -passive -i gstreamer-1.0-devel-mingw-x86_64-1.18.6.msi \
&& ln -s ~/.wine/drive_c/ /c \
&& rm gstreamer-1.0-mingw-x86_64-1.18.1.msi \
&& rm gstreamer-1.0-devel-mingw-x86_64-1.18.1.msi \
&& rm gstreamer-1.0-mingw-x86_64-1.18.6.msi \
&& rm gstreamer-1.0-devel-mingw-x86_64-1.18.6.msi \
&& sed -i.bak 's/c:\/projects\/repos\/cerbero.git\/1.18\/build\/dist/\/c\/gstreamer\/1.0/i' /c/gstreamer/1.0/mingw_x86_64/lib/pkgconfig/*.pc
74 changes: 74 additions & 0 deletions gstreamer.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,70 @@
#include <obs/obs-module.h>
#include <gst/gst.h>

#ifdef _WIN32
#include <windows.h>

// OBS 32.2 (obsproject/obs-studio#11569) dropped %PATH% from the DLL search
// order, breaking obs-gstreamer on Windows in two ways (issue #124):
// - obs-gstreamer.dll's own direct GStreamer/GLib imports could no longer
// be resolved at LoadLibrary time at all, so the module failed to load.
// Fixed by delay-loading those imports (see meson.build) so the loader
// doesn't need them until first use, by which point this has run.
// - element support DLLs pulled in later via g_module_open() could no
// longer be found, so register the GStreamer install's bin directory
// explicitly, and point GST_PLUGIN_PATH at its lib\gstreamer-1.0 so the
// element registry scan doesn't fall back to a guess relative to wherever
// the delay-loaded core DLLs happened to resolve from.
//
// Returns false if no usable GStreamer install was found, so the caller can
// bail out before making any delay-loaded GStreamer call - a call that fails
// to resolve hits the delay-load runtime's default failure handling, which
// is not a controlled error path (see mingw-w64-crt/misc/delayimp.c).
static bool configure_gstreamer_windows_paths(void)
{
static const wchar_t *const roots[] = {
L"GSTREAMER_1_0_ROOT_MINGW_X86_64",
L"GSTREAMER_1_0_ROOT_MSVC_X86_64",
};

SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugins should not be calling this, this is owned by the process.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4044627 - dropped the SetDefaultDllDirectories() call entirely. OBS 32.2 already makes that call itself (it's what actually dropped %PATH% from the process-wide search order), so the plugin doesn't need to re-assert it - AddDllDirectory() alone is enough to add to the search path OBS already established.


for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); i++) {
wchar_t root[MAX_PATH];
DWORD len = GetEnvironmentVariableW(roots[i], root, MAX_PATH);
if (len == 0 || len >= MAX_PATH - 32)
continue;
if (root[len - 1] != L'\\') {
wcscat(root, L"\\");
len++;
}

wchar_t bin_path[MAX_PATH];
wcscpy(bin_path, root);
wcscat(bin_path, L"bin");

wchar_t core_dll[MAX_PATH];
wcscpy(core_dll, bin_path);
wcscat(core_dll, L"\\libgstreamer-1.0-0.dll");
if (GetFileAttributesW(core_dll) == INVALID_FILE_ATTRIBUTES)
continue;

if (!AddDllDirectory(bin_path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value from AddDllDirectory must be saved and passed to RemoveDllDirectory after the loading has completed, otherwise it permanently alters the DLL search path, which belongs to the process.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4044627 - the cookie is now saved in a static DLL_DIRECTORY_COOKIE and released via RemoveDllDirectory() in a new obs_module_unload(). Kept it alive for the plugin's whole lifetime rather than removing it right after this function returns, since GStreamer element modules can be g_module_open()'d lazily at any later point (e.g. the first time a given codec is actually used), not just during the initial load.

continue;

if (GetEnvironmentVariableW(L"GST_PLUGIN_PATH", NULL, 0) == 0) {
wchar_t plugin_path[MAX_PATH];
wcscpy(plugin_path, root);
wcscat(plugin_path, L"lib\\gstreamer-1.0");
SetEnvironmentVariableW(L"GST_PLUGIN_PATH", plugin_path);
}
return true;
}

return false;
}
#endif

extern const char *obs_gstreamer_version;

OBS_DECLARE_MODULE()
Expand Down Expand Up @@ -82,6 +146,16 @@ bool obs_module_load(void)
{
guint major, minor, micro, nano;

#ifdef _WIN32
if (!configure_gstreamer_windows_paths()) {
blog(LOG_ERROR,
"[obs-gstreamer] Could not locate a GStreamer runtime install (checked "
"GSTREAMER_1_0_ROOT_MINGW_X86_64 and GSTREAMER_1_0_ROOT_MSVC_X86_64). Install the official "
"GStreamer Windows runtime: https://gstreamer.freedesktop.org/download/");
return false;
}
#endif

gst_version(&major, &minor, &micro, &nano);

blog(LOG_INFO, "[obs-gstreamer] build: %s, gst-runtime: %u.%u.%u", obs_gstreamer_version, major, minor, micro);
Expand Down
48 changes: 40 additions & 8 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,44 @@ if host_machine.system() == 'darwin'
name_suffix = 'so'
endif

gst_deps = [
dependency('gstreamer-1.0', version : '>=1.16.0'),
dependency('gstreamer-video-1.0'),
dependency('gstreamer-audio-1.0'),
dependency('gstreamer-app-1.0'),
dependency('gstreamer-net-1.0'),
]

link_deps = [obs_dep] + gst_deps
extra_link_args = []

if host_machine.system() == 'windows'
# obs-gstreamer.dll must be loadable by OBS even when the GStreamer/GLib
# runtime it links against isn't resolvable yet (OBS 32.2 dropped %PATH%
# from DLL search, see gstreamer.c's configure_gstreamer_windows_paths()).
# Take compile args from pkg-config as usual, but link against pre-generated
# delay-import archives (docker/gen-delayimp-libs.sh) instead of the normal
# eagerly-loaded import libraries.
gst_compile_deps = []
foreach d : gst_deps
gst_compile_deps += d.partial_dependency(compile_args : true, includes : true)
endforeach
link_deps = [obs_dep] + gst_compile_deps

delayimp_dir = get_option('gst_delayimp_dir')
if delayimp_dir == ''
error('gst_delayimp_dir must be set when building for Windows (see docker/gen-delayimp-libs.sh)')
endif

foreach name : ['libgstreamer-1.0-0', 'libgobject-2.0-0', 'libglib-2.0-0', 'libintl-8',
'libgstvideo-1.0-0', 'libgstbase-1.0-0', 'libgstaudio-1.0-0', 'libgsttag-1.0-0',
'libgstapp-1.0-0', 'libgstnet-1.0-0', 'libgio-2.0-0']
extra_link_args += [delayimp_dir / 'lib' + name + '_delay.a']
endforeach

extra_link_args += ['-ldelayimp']
endif

shared_library('obs-gstreamer',
'gstreamer.c',
'gstreamer-source.c',
Expand All @@ -48,13 +86,7 @@ shared_library('obs-gstreamer',
),
name_prefix : '',
name_suffix : name_suffix,
dependencies : [
obs_dep,
dependency('gstreamer-1.0', version : '>=1.16.0'),
dependency('gstreamer-video-1.0'),
dependency('gstreamer-audio-1.0'),
dependency('gstreamer-app-1.0'),
dependency('gstreamer-net-1.0'),
],
dependencies : link_deps,
link_args : extra_link_args,
install : true,
)
2 changes: 2 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
option('gst_delayimp_dir', type : 'string', value : '',
description : 'Directory containing delay-import archives generated by docker/gen-delayimp-libs.sh (Windows only)')