diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9becab0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/linux/ +/macos/ +/macos-aarch64/ +/windows/ +/gst-delayimp/ +/cross.txt diff --git a/README.md b/README.md index 3f4dadc..5da3995 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/docker/build-windows.sh b/docker/build-windows.sh new file mode 100644 index 0000000..bcdd623 --- /dev/null +++ b/docker/build-windows.sh @@ -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" diff --git a/docker/gen-delayimp-libs.sh b/docker/gen-delayimp-libs.sh new file mode 100644 index 0000000..857c1c8 --- /dev/null +++ b/docker/gen-delayimp-libs.sh @@ -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 " >&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 diff --git a/docker/mingw b/docker/mingw index 53d6ef3..2090146 100644 --- a/docker/mingw +++ b/docker/mingw @@ -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 diff --git a/gstreamer.c b/gstreamer.c index ec56bcb..0c02f45 100644 --- a/gstreamer.c +++ b/gstreamer.c @@ -21,6 +21,80 @@ #include #include +#ifdef _WIN32 +#include + +// 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. +// +// AddDllDirectory() only affects searches made under default-dirs search +// semantics, which requires the process to have opted in via +// SetDefaultDllDirectories() - that call is what actually dropped %PATH% +// above, and it's the host process's call to make, not a plugin's, so we +// don't repeat it here; we only add to the search path OBS already +// established. The added directory stays in effect for the plugin's +// lifetime, since GStreamer element modules can be g_module_open()'d +// lazily at any later point, and is released in obs_module_unload(). +// +// 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 DLL_DIRECTORY_COOKIE gstreamer_dll_directory_cookie = NULL; + +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", + }; + + 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; + + gstreamer_dll_directory_cookie = AddDllDirectory(bin_path); + if (!gstreamer_dll_directory_cookie) + 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() @@ -82,6 +156,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, µ, &nano); blog(LOG_INFO, "[obs-gstreamer] build: %s, gst-runtime: %u.%u.%u", obs_gstreamer_version, major, minor, micro); @@ -214,3 +298,13 @@ bool obs_module_load(void) return true; } + +#ifdef _WIN32 +void obs_module_unload(void) +{ + if (gstreamer_dll_directory_cookie) { + RemoveDllDirectory(gstreamer_dll_directory_cookie); + gstreamer_dll_directory_cookie = NULL; + } +} +#endif diff --git a/meson.build b/meson.build index f36860b..242ffbd 100644 --- a/meson.build +++ b/meson.build @@ -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', @@ -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, ) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..b06bd39 --- /dev/null +++ b/meson_options.txt @@ -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)')