From 951709f276e0b5d5ec6d200c080516d57f82eb84 Mon Sep 17 00:00:00 2001 From: Ward from fusion-voyager-3 Date: Tue, 30 Jul 2024 20:49:10 +0300 Subject: [PATCH] port to pika os 4 --- .github/build-canary-v3 | 1 + .github/build-nest-v3 | 1 + .github/release-canary-v3 | 1 + .github/release-nest-v3 | 1 + .github/workflows/build-canaryv3.yml | 37 + .github/workflows/build-nestv3.yml | 37 + .github/workflows/release-canaryv3.yml | 40 + .github/workflows/release-nestv3.yml | 40 + .github/workflows/release.yml | 49 - LICENSE.md | 24 + debian/changelog | 6 + debian/control | 28 +- debian/source/format | 2 +- main.sh | 19 +- orig.source.txt | 2 - ...rt-Disable-browser-panels-on-Wayland.patch | 43 - ...registering-CEF-OAuth-integrations-o.patch | 47 - patches/01_use-sysytem-libsimde.patch | 47 + patches/02_disable-obs-browser.patch | 9 + patches/03_FTBFS-Werror.patch | 10 + patches/04_AMF-encoder.patch | 612 +++++ patches/6207.patch | 2272 ----------------- patches/8051.patch | 283 -- patches/8794.patch | 275 -- patches/8832.patch | 784 ------ patches/9475.patch | 213 -- patches/add-plugins.patch | 14 - patches/encoder-rename.patch | 97 - patches/obs-studio-30-cmake-3.20.patch | 423 --- ...obs-studio-UI-use-fdk-aac-by-default.patch | 36 - ...o-deps-Add-license-declaration-files.patch | 218 -- patches/series | 3 + pika-build-config/amd64-v3.sh | 10 + pika-build-config/i386.sh | 5 + release.sh | 8 +- 35 files changed, 924 insertions(+), 4773 deletions(-) create mode 100644 .github/build-canary-v3 create mode 100644 .github/build-nest-v3 create mode 100644 .github/release-canary-v3 create mode 100644 .github/release-nest-v3 create mode 100644 .github/workflows/build-canaryv3.yml create mode 100644 .github/workflows/build-nestv3.yml create mode 100644 .github/workflows/release-canaryv3.yml create mode 100644 .github/workflows/release-nestv3.yml delete mode 100644 .github/workflows/release.yml create mode 100644 LICENSE.md delete mode 100644 orig.source.txt delete mode 100644 patches/0001-Revert-Disable-browser-panels-on-Wayland.patch delete mode 100644 patches/0001-Revert-UI-Avoid-registering-CEF-OAuth-integrations-o.patch create mode 100644 patches/01_use-sysytem-libsimde.patch create mode 100644 patches/02_disable-obs-browser.patch create mode 100644 patches/03_FTBFS-Werror.patch create mode 100644 patches/04_AMF-encoder.patch delete mode 100644 patches/6207.patch delete mode 100644 patches/8051.patch delete mode 100644 patches/8794.patch delete mode 100644 patches/8832.patch delete mode 100644 patches/9475.patch delete mode 100644 patches/add-plugins.patch delete mode 100644 patches/encoder-rename.patch delete mode 100644 patches/obs-studio-30-cmake-3.20.patch delete mode 100644 patches/obs-studio-UI-use-fdk-aac-by-default.patch delete mode 100644 patches/obs-studio-deps-Add-license-declaration-files.patch create mode 100644 patches/series create mode 100755 pika-build-config/amd64-v3.sh create mode 100755 pika-build-config/i386.sh diff --git a/.github/build-canary-v3 b/.github/build-canary-v3 new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/.github/build-canary-v3 @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/.github/build-nest-v3 b/.github/build-nest-v3 new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/.github/build-nest-v3 @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/.github/release-canary-v3 b/.github/release-canary-v3 new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/.github/release-canary-v3 @@ -0,0 +1 @@ +1 diff --git a/.github/release-nest-v3 b/.github/release-nest-v3 new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/.github/release-nest-v3 @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/.github/workflows/build-canaryv3.yml b/.github/workflows/build-canaryv3.yml new file mode 100644 index 0000000..bff3d86 --- /dev/null +++ b/.github/workflows/build-canaryv3.yml @@ -0,0 +1,37 @@ +name: PikaOS Package Build Only (Canary) (amd64-v3) + +on: + push: + branches: + - main + paths: + - '.github/build-canary-v3' + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/pikaos-linux/pikaos-builder:canaryv3 + volumes: + - /proc:/proc + options: --privileged -it + + steps: + - uses: actions/checkout@v3 + + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_KEY }} + name: id_rsa + known_hosts: ${{ secrets.KNOWN_HOSTS }} + if_key_exists: replace + + - name: Update APT Cache + run: apt-get update -y + + - name: Set Build Config + run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh + + - name: Build Package + run: ./main.sh diff --git a/.github/workflows/build-nestv3.yml b/.github/workflows/build-nestv3.yml new file mode 100644 index 0000000..3c9c34d --- /dev/null +++ b/.github/workflows/build-nestv3.yml @@ -0,0 +1,37 @@ +name: PikaOS Package Build Only (amd64-v3) + +on: + push: + branches: + - main + paths: + - '.github/build-nest-v3' + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/pikaos-linux/pikaos-builder:nestv3 + volumes: + - /proc:/proc + options: --privileged -it + + steps: + - uses: actions/checkout@v3 + + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_KEY }} + name: id_rsa + known_hosts: ${{ secrets.KNOWN_HOSTS }} + if_key_exists: replace + + - name: Update APT Cache + run: apt-get update -y + + - name: Set Build Config + run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh + + - name: Build Package + run: ./main.sh diff --git a/.github/workflows/release-canaryv3.yml b/.github/workflows/release-canaryv3.yml new file mode 100644 index 0000000..436c8ff --- /dev/null +++ b/.github/workflows/release-canaryv3.yml @@ -0,0 +1,40 @@ +name: PikaOS Package Build & Release (Canary) (amd64-v3) + +on: + push: + branches: + - main + paths: + - '.github/release-canary-v3' + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/pikaos-linux/pikaos-builder:canaryv3 + volumes: + - /proc:/proc + options: --privileged -it + + steps: + - uses: actions/checkout@v3 + + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_KEY }} + name: id_rsa + known_hosts: ${{ secrets.KNOWN_HOSTS }} + if_key_exists: replace + + - name: Update APT Cache + run: apt-get update -y + + - name: Set Build Config + run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh + + - name: Build Package + run: ./main.sh + + - name: Release Package + run: ./release.sh diff --git a/.github/workflows/release-nestv3.yml b/.github/workflows/release-nestv3.yml new file mode 100644 index 0000000..ed42bb7 --- /dev/null +++ b/.github/workflows/release-nestv3.yml @@ -0,0 +1,40 @@ +name: PikaOS Package Build & Release (amd64-v3) + +on: + push: + branches: + - main + paths: + - '.github/release-nest-v3' + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/pikaos-linux/pikaos-builder:nestv3 + volumes: + - /proc:/proc + options: --privileged -it + + steps: + - uses: actions/checkout@v3 + + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_KEY }} + name: id_rsa + known_hosts: ${{ secrets.KNOWN_HOSTS }} + if_key_exists: replace + + - name: Update APT Cache + run: apt-get update -y + + - name: Set Build Config + run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh + + - name: Build Package + run: ./main.sh + + - name: Release Package + run: ./release.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 9f6bbad..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: PikaOS Package Release - -on: - workflow_dispatch - -jobs: - build: - runs-on: self-hosted - container: - image: ghcr.io/pikaos-linux/pika-package-container:latest - volumes: - - /proc:/proc - options: --privileged -it - - steps: - - uses: actions/checkout@v3 - - - name: Import GPG key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@v5 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.PASSPHRASE }} - - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.SSH_KEY }} - name: id_rsa - known_hosts: ${{ secrets.KNOWN_HOSTS }} - if_key_exists: replace - - - name: Update apt cache - run: apt-get update - - - name: Build Package - run: ./main.sh - - - name: Release Package - run: ./release.sh - - - name: Purge cache - uses: strrife/cloudflare-chunked-purge-action@master - env: - # Zone is required by both authentication methods - CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }} - - CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} - PURGE_URLS: ${{ vars.PURGE_URLS }} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..16ff30b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,24 @@ +MIT License (With DPKG packaging compatibility) + +Copyright (c) 2024 PikaOS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Notes: +The files covered by this license are any files and directories in the root of this repository (including but not limited to: `main.sh`, `release.sh`, and `.github`), with the exception of the `debian` directory and its contents if `debian/copyright` exists, and declares any files or directories as a different LICENSE/COPYRIGHT. diff --git a/debian/changelog b/debian/changelog index 0a6495f..7283bc1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +obs-studio (30.2.1-101pika1) pika; urgency=low + + Update to 30.2.1 + + -- Kevin Henkel Sat, 04 Feb 2023 12:48:06 +0100 + obs-studio (30.0.2-100pika3) pikauwu; urgency=low Update to 30.0.2 diff --git a/debian/control b/debian/control index 6a13a7a..f845c42 100644 --- a/debian/control +++ b/debian/control @@ -8,9 +8,29 @@ Homepage: http://obsproject.org Package: obs-studio Architecture: amd64 -Depends: ${shlibs:Depends}, ${misc:Depends}, qt6-wayland, qt6-qpa-plugins, obs-gstreamer-vaapi-plugin, obs-gamecapture-plugin +Depends: + ${shlibs:Depends}, + ${misc:Depends}, + ${python3:Depends}, + qt6-wayland, + qt6-qpa-plugins, + obs-gstreamer-vaapi-plugin, + obs-gamecapture-plugin, + libqt6svg6, Recommends: obs-gamecapture-plugin:i386 -Provides: libobs0 (= ${binary:Version}), obs-plugins (= ${binary:Version}) -Conflicts: libobs0, obs-plugins -Replaces: libobs0, obs-plugins +Provides: + libobs0 (= ${binary:Version}), + obs-plugins (= ${binary:Version}), + libobs0t64 (= ${binary:Version}), + libobs-dev (= ${binary:Version}) +Conflicts: + libobs0, + obs-plugins, + libobs0t64, + libobs-dev, +Replaces: + libobs0, + obs-plugins, + libobs0t64, + libobs-dev, Description: OBS Studio for Pika diff --git a/debian/source/format b/debian/source/format index 89ae9db..163aaf8 100644 --- a/debian/source/format +++ b/debian/source/format @@ -1 +1 @@ -3.0 (native) +3.0 (quilt) diff --git a/main.sh b/main.sh index 5f14c25..fe29add 100755 --- a/main.sh +++ b/main.sh @@ -1,8 +1,15 @@ #! /bin/bash + set -e +VERSION="30.2.1" + +source ./pika-build-config.sh + +echo "$PIKA_BUILD_ARCH" > pika-build-arch + # Clone Upstream -git clone --recursive https://github.com/obsproject/obs-studio.git -b 30.0.2 +git clone --recursive https://github.com/obsproject/obs-studio.git -b "$VERSION" cp -rvf ./debian ./obs-studio/ cd ./obs-studio @@ -12,17 +19,19 @@ tar -xf ./cef_binary_5060_linux64.tar.bz2 -C ./ #git clone https://github.com/aja-video/ntv2.git # remove -Werror flag to mitigate FTBFS with ffmpeg -sed -i 's|-Werror-implicit-function-declaration||g' CMakeLists.txt +#sed -i 's|-Werror-implicit-function-declaration||g' CMakeLists.txt ## remove Werror to fix compile error -sed -i 's| -Werror||g' cmake/Modules/CompilerConfig.cmake +#sed -i 's| -Werror||g' cmake/Modules/CompilerConfig.cmake sed -i 's| -Wswitch||g' cmake/Modules/CompilerConfig.cmake for i in ../patches/* ; do echo "Applying Patch: $i" && patch -Np1 -i $i || bash -c "echo "Applying Patch $i Failed!" && exit 2"; done -# Get build deps brute force -apt-get build-dep -y ./ +# Get build deps +apt-get build-dep ./ -y +# Build package +LOGNAME=root dh_make --createorig -y -l -p obs-studio_"$VERSION" || echo "dh-make: Ignoring Last Error" dpkg-buildpackage --no-sign # Move the debs to output diff --git a/orig.source.txt b/orig.source.txt deleted file mode 100644 index 125effc..0000000 --- a/orig.source.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Warning cef_binary_5060_linux64 & aja must be pasted in the $(BASE_DIR)/build_dependencies -https://github.com/obsproject/obs-studio diff --git a/patches/0001-Revert-Disable-browser-panels-on-Wayland.patch b/patches/0001-Revert-Disable-browser-panels-on-Wayland.patch deleted file mode 100644 index b3f06e1..0000000 --- a/patches/0001-Revert-Disable-browser-panels-on-Wayland.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0ccb3579b3e8a2f86ac29da9695b91374cfd96c4 Mon Sep 17 00:00:00 2001 -From: Thomas Crider -Date: Fri, 22 Dec 2023 18:25:09 -0500 -Subject: [PATCH] Revert "Disable browser panels on Wayland" - -This reverts commit 7402271392a6a74fb49f16d84fb3c04c1b603d9a. ---- - panel/browser-panel.hpp | 12 ------------ - 1 file changed, 12 deletions(-) - -diff --git a/panel/browser-panel.hpp b/panel/browser-panel.hpp -index dd61f38..5fb9d35 100644 ---- a/panel/browser-panel.hpp -+++ b/panel/browser-panel.hpp -@@ -12,10 +12,6 @@ - #include - #endif - --#ifdef ENABLE_WAYLAND --#include --#endif -- - struct QCefCookieManager { - virtual ~QCefCookieManager() {} - -@@ -81,14 +77,6 @@ struct QCef { - - static inline void *get_browser_lib() - { -- // Disable panels on Wayland for now -- bool isWayland = false; --#ifdef ENABLE_WAYLAND -- isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; --#endif -- if (isWayland) -- return nullptr; -- - obs_module_t *browserModule = obs_get_module("obs-browser"); - - if (!browserModule) --- -2.43.0 - diff --git a/patches/0001-Revert-UI-Avoid-registering-CEF-OAuth-integrations-o.patch b/patches/0001-Revert-UI-Avoid-registering-CEF-OAuth-integrations-o.patch deleted file mode 100644 index af949c5..0000000 --- a/patches/0001-Revert-UI-Avoid-registering-CEF-OAuth-integrations-o.patch +++ /dev/null @@ -1,47 +0,0 @@ -From fcd09096796a259c8c438627528c92829be39c01 Mon Sep 17 00:00:00 2001 -From: Thomas Crider -Date: Fri, 22 Dec 2023 19:10:34 -0500 -Subject: [PATCH] Revert "UI: Avoid registering CEF OAuth integrations on - Wayland" - -This reverts commit 169cd07c42ddf1d0693094badb75f6255c052fac. ---- - UI/auth-restream.cpp | 5 ----- - UI/auth-twitch.cpp | 5 ----- - 2 files changed, 10 deletions(-) - -diff --git a/UI/auth-restream.cpp b/UI/auth-restream.cpp -index bedc10c70..cf8a3e098 100644 ---- a/UI/auth-restream.cpp -+++ b/UI/auth-restream.cpp -@@ -282,11 +282,6 @@ static void DeleteCookies() - - void RegisterRestreamAuth() - { --#if !defined(__APPLE__) && !defined(_WIN32) -- if (QApplication::platformName().contains("wayland")) -- return; --#endif -- - OAuth::RegisterOAuth(restreamDef, CreateRestreamAuth, - RestreamAuth::Login, DeleteCookies); - } -diff --git a/UI/auth-twitch.cpp b/UI/auth-twitch.cpp -index 88e14e0be..abc9cae66 100644 ---- a/UI/auth-twitch.cpp -+++ b/UI/auth-twitch.cpp -@@ -512,11 +512,6 @@ static void DeleteCookies() - - void RegisterTwitchAuth() - { --#if !defined(__APPLE__) && !defined(_WIN32) -- if (QApplication::platformName().contains("wayland")) -- return; --#endif -- - OAuth::RegisterOAuth(twitchDef, CreateTwitchAuth, TwitchAuth::Login, - DeleteCookies); - } --- -2.43.0 - diff --git a/patches/01_use-sysytem-libsimde.patch b/patches/01_use-sysytem-libsimde.patch new file mode 100644 index 0000000..5b2f9e6 --- /dev/null +++ b/patches/01_use-sysytem-libsimde.patch @@ -0,0 +1,47 @@ +--- a/libobs/CMakeLists.txt ++++ b/libobs/CMakeLists.txt +@@ -145,24 +145,6 @@ target_sources( + target_sources( + libobs + PRIVATE # cmake-format: sortable +- util/simde/check.h +- util/simde/debug-trap.h +- util/simde/hedley.h +- util/simde/simde-align.h +- util/simde/simde-arch.h +- util/simde/simde-common.h +- util/simde/simde-constify.h +- util/simde/simde-detect-clang.h +- util/simde/simde-diagnostic.h +- util/simde/simde-features.h +- util/simde/simde-math.h +- util/simde/x86/mmx.h +- util/simde/x86/sse.h +- util/simde/x86/sse2.h) +- +-target_sources( +- libobs +- PRIVATE # cmake-format: sortable + callback/calldata.c + callback/calldata.h + callback/decl.c +--- a/libobs/util/sse-intrin.h ++++ b/libobs/util/sse-intrin.h +@@ -28,6 +28,6 @@ + #endif + #define SIMDE_ENABLE_NATIVE_ALIASES + PRAGMA_WARN_PUSH +-#include "simde/x86/sse2.h" ++#include + PRAGMA_WARN_POP + #endif +--- a/cmake/Modules/CompilerConfig.cmake ++++ b/cmake/Modules/CompilerConfig.cmake +@@ -157,7 +157,6 @@ if(LOWERCASE_CMAKE_SYSTEM_PROCESSOR MATC + endif() + elseif(LOWERCASE_CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64(le)?") + set(ARCH_SIMD_DEFINES -DNO_WARN_X86_INTRINSICS) +- set(ARCH_SIMD_FLAGS -mvsx) + else() + if(CMAKE_C_COMPILER_ID MATCHES "^(Apple)?Clang|GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang|GNU") + check_c_compiler_flag("-fopenmp-simd" C_COMPILER_SUPPORTS_OPENMP_SIMD) diff --git a/patches/02_disable-obs-browser.patch b/patches/02_disable-obs-browser.patch new file mode 100644 index 0000000..5392165 --- /dev/null +++ b/patches/02_disable-obs-browser.patch @@ -0,0 +1,9 @@ +--- /dev/null ++++ b/plugins/obs-browser/CMakeLists.txt +@@ -0,0 +1,6 @@ ++project(obs-browser) ++ ++option( ++ ENABLE_BROWSER ++ "Enable building OBS with browser source plugin (required Chromium Embedded Framework)" ++ ${OS_LINUX}) diff --git a/patches/03_FTBFS-Werror.patch b/patches/03_FTBFS-Werror.patch new file mode 100644 index 0000000..760a767 --- /dev/null +++ b/patches/03_FTBFS-Werror.patch @@ -0,0 +1,10 @@ +--- a/cmake/Modules/CompilerConfig.cmake ++++ b/cmake/Modules/CompilerConfig.cmake +@@ -108,7 +108,6 @@ else() + - https://github.com/obsproject/obs-studio/issues/8850 for 13.1.1 + ]] + add_compile_options( +- -Werror + -Wextra + -Wvla + -Wswitch diff --git a/patches/04_AMF-encoder.patch b/patches/04_AMF-encoder.patch new file mode 100644 index 0000000..a757e51 --- /dev/null +++ b/patches/04_AMF-encoder.patch @@ -0,0 +1,612 @@ +From dbd2bca8060de57c3a8309de38556371aa60182f Mon Sep 17 00:00:00 2001 +From: David Rosca +Date: Sun, 28 Aug 2022 10:15:16 +0200 +Subject: [PATCH] obs-ffmpeg: Make AMF encoder work on Linux + +Only the fallback encoders are available (no texture support). + +Requires AMD proprietary Vulkan driver, using different driver +will be detected on startup and the encoders disabled. + +--- + +--- a/plugins/obs-ffmpeg/CMakeLists.txt ++++ b/plugins/obs-ffmpeg/CMakeLists.txt +@@ -72,7 +72,10 @@ target_link_libraries( + + if(OS_WINDOWS) + configure_file(cmake/windows/obs-module.rc.in obs-ffmpeg.rc) +- target_sources(obs-ffmpeg PRIVATE obs-ffmpeg.rc) ++ target_sources(obs-ffmpeg PRIVATE obs-ffmpeg.rc texture-amf.cpp) ++ ++elseif(OS_LINUX OR OS_FREEBSD) ++ add_subdirectory(obs-amf-test) + endif() + + # cmake-format: off +--- a/plugins/obs-ffmpeg/cmake/legacy.cmake ++++ b/plugins/obs-ffmpeg/cmake/legacy.cmake +@@ -109,10 +109,11 @@ if(OS_WINDOWS) + obs-ffmpeg.rc) + + elseif(OS_POSIX AND NOT OS_MACOS) ++ add_subdirectory(obs-amf-test) + find_package(Libva REQUIRED) + find_package(Libpci REQUIRED) + find_package(Libdrm REQUIRED) +- target_sources(obs-ffmpeg PRIVATE obs-ffmpeg-vaapi.c vaapi-utils.c vaapi-utils.h) ++ target_sources(obs-ffmpeg PRIVATE obs-ffmpeg-vaapi.c vaapi-utils.c vaapi-utils.h texture-amf.cpp) + target_link_libraries(obs-ffmpeg PRIVATE Libva::va Libva::drm LIBPCI::LIBPCI Libdrm::Libdrm) + + if(ENABLE_NATIVE_NVENC) +--- a/plugins/obs-ffmpeg/obs-amf-test/CMakeLists.txt ++++ b/plugins/obs-ffmpeg/obs-amf-test/CMakeLists.txt +@@ -1,15 +1,20 @@ +-cmake_minimum_required(VERSION 3.24...3.25) ++project(obs-amf-test) + +-legacy_check() ++add_executable(obs-amf-test) + + find_package(AMF 1.4.29 REQUIRED) + +-add_executable(obs-amf-test) +-add_executable(OBS::amf-test ALIAS obs-amf-test) ++target_include_directories(obs-amf-test PRIVATE ${CMAKE_SOURCE_DIR}/libobs) ++ ++if(OS_WINDOWS) ++ target_sources(obs-amf-test PRIVATE obs-amf-test.cpp) ++ target_link_libraries(obs-amf-test d3d11 dxgi dxguid AMF::AMF) ++elseif(OS_POSIX AND NOT OS_MACOS) ++ find_package(Vulkan REQUIRED) ++ target_sources(obs-amf-test PRIVATE obs-amf-test-linux.cpp) ++ target_link_libraries(obs-amf-test dl Vulkan::Vulkan AMF::AMF) ++endif() + +-target_sources(obs-amf-test PRIVATE obs-amf-test.cpp) +-target_link_libraries(obs-amf-test PRIVATE OBS::COMutils AMF::AMF d3d11 dxgi dxguid) ++set_target_properties(obs-amf-test PROPERTIES FOLDER "plugins/obs-ffmpeg") + +-# cmake-format: off +-set_target_properties_obs(obs-amf-test PROPERTIES FOLDER plugins/obs-ffmpeg) +-# cmake-format: on ++setup_binary_target(obs-amf-test) +--- /dev/null ++++ b/plugins/obs-ffmpeg/obs-amf-test/obs-amf-test-linux.cpp +@@ -0,0 +1,140 @@ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++using namespace amf; ++ ++struct adapter_caps { ++ bool is_amd = false; ++ bool supports_avc = false; ++ bool supports_hevc = false; ++ bool supports_av1 = false; ++}; ++ ++static AMFFactory *amf_factory = nullptr; ++static std::map adapter_info; ++ ++static bool has_encoder(AMFContextPtr &amf_context, const wchar_t *encoder_name) ++{ ++ AMFComponentPtr encoder; ++ AMF_RESULT res = amf_factory->CreateComponent(amf_context, encoder_name, ++ &encoder); ++ return res == AMF_OK; ++} ++ ++static bool get_adapter_caps(uint32_t adapter_idx) ++{ ++ if (adapter_idx) ++ return false; ++ ++ adapter_caps &caps = adapter_info[adapter_idx]; ++ ++ AMF_RESULT res; ++ AMFContextPtr amf_context; ++ res = amf_factory->CreateContext(&amf_context); ++ if (res != AMF_OK) ++ return true; ++ ++ AMFContext1 *context1 = NULL; ++ res = amf_context->QueryInterface(AMFContext1::IID(), ++ (void **)&context1); ++ if (res != AMF_OK) ++ return false; ++ res = context1->InitVulkan(nullptr); ++ context1->Release(); ++ if (res != AMF_OK) ++ return false; ++ ++ caps.is_amd = true; ++ caps.supports_avc = has_encoder(amf_context, AMFVideoEncoderVCE_AVC); ++ caps.supports_hevc = has_encoder(amf_context, AMFVideoEncoder_HEVC); ++ caps.supports_av1 = has_encoder(amf_context, AMFVideoEncoder_AV1); ++ ++ return true; ++} ++ ++int main(void) ++try { ++ AMF_RESULT res; ++ VkResult vkres; ++ ++ VkApplicationInfo app_info = {}; ++ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; ++ app_info.pApplicationName = "obs-amf-test"; ++ app_info.apiVersion = VK_API_VERSION_1_2; ++ ++ VkInstanceCreateInfo info = {}; ++ info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; ++ info.pApplicationInfo = &app_info; ++ ++ VkInstance instance; ++ vkres = vkCreateInstance(&info, nullptr, &instance); ++ if (vkres != VK_SUCCESS) ++ throw "Failed to initialize Vulkan"; ++ ++ uint32_t device_count; ++ vkres = vkEnumeratePhysicalDevices(instance, &device_count, nullptr); ++ if (vkres != VK_SUCCESS || !device_count) ++ throw "Failed to enumerate Vulkan devices"; ++ ++ VkPhysicalDevice *devices = new VkPhysicalDevice[device_count]; ++ vkres = vkEnumeratePhysicalDevices(instance, &device_count, devices); ++ if (vkres != VK_SUCCESS) ++ throw "Failed to enumerate Vulkan devices"; ++ ++ VkPhysicalDeviceDriverProperties driver_props = {}; ++ driver_props.sType = ++ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; ++ VkPhysicalDeviceProperties2 device_props = {}; ++ device_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; ++ device_props.pNext = &driver_props; ++ vkGetPhysicalDeviceProperties2(devices[0], &device_props); ++ ++ if (strcmp(driver_props.driverName, "AMD proprietary driver")) ++ throw "Not running AMD proprietary driver"; ++ ++ vkDestroyInstance(instance, nullptr); ++ ++ /* --------------------------------------------------------- */ ++ /* try initializing amf, I guess */ ++ ++ void *amf_module = dlopen(AMF_DLL_NAMEA, RTLD_LAZY); ++ if (!amf_module) ++ throw "Failed to load AMF lib"; ++ ++ auto init = (AMFInit_Fn)dlsym(amf_module, AMF_INIT_FUNCTION_NAME); ++ if (!init) ++ throw "Failed to get init func"; ++ ++ res = init(AMF_FULL_VERSION, &amf_factory); ++ if (res != AMF_OK) ++ throw "AMFInit failed"; ++ ++ uint32_t idx = 0; ++ while (get_adapter_caps(idx++)) ++ ; ++ ++ for (auto &[idx, caps] : adapter_info) { ++ printf("[%u]\n", idx); ++ printf("is_amd=%s\n", caps.is_amd ? "true" : "false"); ++ printf("supports_avc=%s\n", ++ caps.supports_avc ? "true" : "false"); ++ printf("supports_hevc=%s\n", ++ caps.supports_hevc ? "true" : "false"); ++ printf("supports_av1=%s\n", ++ caps.supports_av1 ? "true" : "false"); ++ } ++ ++ return 0; ++} catch (const char *text) { ++ printf("[error]\nstring=%s\n", text); ++ return 0; ++} +--- a/plugins/obs-ffmpeg/obs-ffmpeg.c ++++ b/plugins/obs-ffmpeg/obs-ffmpeg.c +@@ -393,7 +393,7 @@ bool obs_module_load(void) + #endif + } + +-#ifdef _WIN32 ++#if defined(_WIN32) || defined(__linux__) + amf_load(); + #endif + +--- a/plugins/obs-ffmpeg/texture-amf-opts.hpp ++++ b/plugins/obs-ffmpeg/texture-amf-opts.hpp +@@ -321,7 +321,7 @@ static void amf_apply_opt(amf_base *enc, + val = atoi(opt->value); + } + +- os_utf8_to_wcs(opt->name, 0, wname, _countof(wname)); ++ os_utf8_to_wcs(opt->name, 0, wname, amf_countof(wname)); + if (is_bool) { + bool bool_val = (bool)val; + set_amf_property(enc, wname, bool_val); +--- a/plugins/obs-ffmpeg/texture-amf.cpp ++++ b/plugins/obs-ffmpeg/texture-amf.cpp +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -18,6 +19,7 @@ + #include + #include + ++#ifdef _WIN32 + #include + #include + #include +@@ -25,6 +27,8 @@ + #include + #include + #include ++#endif ++ + #include + #include + #include +@@ -55,8 +59,10 @@ struct amf_error { + + struct handle_tex { + uint32_t handle; ++#ifdef _WIN32 + ComPtr tex; + ComPtr km; ++#endif + }; + + struct adapter_caps { +@@ -72,7 +78,7 @@ static std::map + static bool h264_supported = false; + static AMFFactory *amf_factory = nullptr; + static AMFTrace *amf_trace = nullptr; +-static HMODULE amf_module = nullptr; ++static void *amf_module = nullptr; + static uint64_t amf_version = 0; + + /* ========================================================================= */ +@@ -124,9 +130,11 @@ struct amf_base { + virtual void init() = 0; + }; + +-using d3dtex_t = ComPtr; + using buf_t = std::vector; + ++#ifdef _WIN32 ++using d3dtex_t = ComPtr; ++ + struct amf_texencode : amf_base, public AMFSurfaceObserver { + volatile bool destroying = false; + +@@ -163,6 +171,7 @@ struct amf_texencode : amf_base, public + throw amf_error("InitDX11 failed", res); + } + }; ++#endif + + struct amf_fallback : amf_base, public AMFSurfaceObserver { + volatile bool destroying = false; +@@ -190,9 +199,21 @@ struct amf_fallback : amf_base, public A + + void init() override + { ++#if defined(_WIN32) + AMF_RESULT res = amf_context->InitDX11(nullptr, AMF_DX11_1); + if (res != AMF_OK) + throw amf_error("InitDX11 failed", res); ++#elif defined(__linux__) ++ AMFContext1 *context1 = NULL; ++ AMF_RESULT res = amf_context->QueryInterface( ++ AMFContext1::IID(), (void **)&context1); ++ if (res != AMF_OK) ++ throw amf_error("CreateContext1 failed", res); ++ res = context1->InitVulkan(nullptr); ++ context1->Release(); ++ if (res != AMF_OK) ++ throw amf_error("InitVulkan failed", res); ++#endif + } + }; + +@@ -234,13 +255,18 @@ static void set_amf_property(amf_base *e + : (enc->codec == amf_codec_type::HEVC) \ + ? AMF_VIDEO_ENCODER_HEVC_##name \ + : AMF_VIDEO_ENCODER_AV1_##name) ++#define get_opt_name_enum(name) \ ++ ((enc->codec == amf_codec_type::AVC) ? (int)AMF_VIDEO_ENCODER_##name \ ++ : (enc->codec == amf_codec_type::HEVC) \ ++ ? (int)AMF_VIDEO_ENCODER_HEVC_##name \ ++ : (int)AMF_VIDEO_ENCODER_AV1_##name) + #define set_opt(name, value) set_amf_property(enc, get_opt_name(name), value) + #define get_opt(name, value) get_amf_property(enc, get_opt_name(name), value) + #define set_avc_opt(name, value) set_avc_property(enc, name, value) + #define set_hevc_opt(name, value) set_hevc_property(enc, name, value) + #define set_av1_opt(name, value) set_av1_property(enc, name, value) + #define set_enum_opt(name, value) \ +- set_amf_property(enc, get_opt_name(name), get_opt_name(name##_##value)) ++ set_amf_property(enc, get_opt_name(name), get_opt_name_enum(name##_##value)) + #define set_avc_enum(name, value) \ + set_avc_property(enc, name, AMF_VIDEO_ENCODER_##name##_##value) + #define set_hevc_enum(name, value) \ +@@ -251,6 +277,7 @@ static void set_amf_property(amf_base *e + /* ------------------------------------------------------------------------- */ + /* Implementation */ + ++#ifdef _WIN32 + static HMODULE get_lib(const char *lib) + { + HMODULE mod = GetModuleHandleA(lib); +@@ -397,6 +424,7 @@ static void get_tex_from_handle(amf_texe + *km_out = km.Detach(); + *tex_out = tex.Detach(); + } ++#endif + + static constexpr amf_int64 macroblock_size = 16; + +@@ -513,7 +541,7 @@ static void convert_to_encoder_packet(am + enc->packet_data = AMFBufferPtr(data); + data->GetProperty(L"PTS", &packet->pts); + +- const wchar_t *get_output_type; ++ const wchar_t *get_output_type = NULL; + switch (enc->codec) { + case amf_codec_type::AVC: + get_output_type = AMF_VIDEO_ENCODER_OUTPUT_DATA_TYPE; +@@ -746,6 +774,7 @@ static void amf_encode_base(amf_base *en + static bool amf_encode_tex(void *data, uint32_t handle, int64_t pts, + uint64_t lock_key, uint64_t *next_key, + encoder_packet *packet, bool *received_packet) ++#ifdef _WIN32 + try { + amf_texencode *enc = (amf_texencode *)data; + ID3D11DeviceContext *context = enc->context; +@@ -822,6 +851,18 @@ try { + *received_packet = false; + return false; + } ++#else ++{ ++ UNUSED_PARAMETER(data); ++ UNUSED_PARAMETER(handle); ++ UNUSED_PARAMETER(pts); ++ UNUSED_PARAMETER(lock_key); ++ UNUSED_PARAMETER(next_key); ++ UNUSED_PARAMETER(packet); ++ UNUSED_PARAMETER(received_packet); ++ return false; ++} ++#endif + + static buf_t alloc_buf(amf_fallback *enc) + { +@@ -1286,6 +1327,7 @@ static const char *amf_avc_get_name(void + + static inline int get_avc_preset(amf_base *enc, const char *preset) + { ++ UNUSED_PARAMETER(enc); + if (astrcmpi(preset, "quality") == 0) + return AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY; + else if (astrcmpi(preset, "speed") == 0) +@@ -1408,7 +1450,7 @@ static bool amf_avc_init(void *data, obs + } + + } else if (bf != 0) { +- warn("B-Frames set to %lld but b-frames are not " ++ warn("B-Frames set to %" PRId64 " but b-frames are not " + "supported by this device", + bf); + bf = 0; +@@ -1453,12 +1495,12 @@ static bool amf_avc_init(void *data, obs + + info("settings:\n" + "\trate_control: %s\n" +- "\tbitrate: %d\n" +- "\tcqp: %d\n" ++ "\tbitrate: %" PRId64 "\n" ++ "\tcqp: %" PRId64 "\n" + "\tkeyint: %d\n" + "\tpreset: %s\n" + "\tprofile: %s\n" +- "\tb-frames: %d\n" ++ "\tb-frames: %" PRId64 "\n" + "\twidth: %d\n" + "\theight: %d\n" + "\tparams: %s", +@@ -1531,6 +1573,7 @@ static void amf_avc_create_internal(amf_ + + static void *amf_avc_create_texencode(obs_data_t *settings, + obs_encoder_t *encoder) ++#ifdef _WIN32 + try { + check_texture_encode_capability(encoder, amf_codec_type::AVC); + +@@ -1553,6 +1596,12 @@ try { + blog(LOG_ERROR, "[texture-amf-h264] %s: %s", __FUNCTION__, err); + return obs_encoder_create_rerouted(encoder, "h264_fallback_amf"); + } ++#else ++{ ++ UNUSED_PARAMETER(settings); ++ return obs_encoder_create_rerouted(encoder, "h264_fallback_amf"); ++} ++#endif + + static void *amf_avc_create_fallback(obs_data_t *settings, + obs_encoder_t *encoder) +@@ -1647,6 +1696,7 @@ static const char *amf_hevc_get_name(voi + + static inline int get_hevc_preset(amf_base *enc, const char *preset) + { ++ UNUSED_PARAMETER(enc); + if (astrcmpi(preset, "balanced") == 0) + return AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED; + else if (astrcmpi(preset, "speed") == 0) +@@ -1770,8 +1820,8 @@ static bool amf_hevc_init(void *data, ob + + info("settings:\n" + "\trate_control: %s\n" +- "\tbitrate: %d\n" +- "\tcqp: %d\n" ++ "\tbitrate: %" PRId64 "\n" ++ "\tcqp: %" PRId64 "\n" + "\tkeyint: %d\n" + "\tpreset: %s\n" + "\tprofile: %s\n" +@@ -1892,6 +1942,7 @@ static void amf_hevc_create_internal(amf + + static void *amf_hevc_create_texencode(obs_data_t *settings, + obs_encoder_t *encoder) ++#ifdef _WIN32 + try { + check_texture_encode_capability(encoder, amf_codec_type::HEVC); + +@@ -1914,6 +1965,12 @@ try { + blog(LOG_ERROR, "[texture-amf-h265] %s: %s", __FUNCTION__, err); + return obs_encoder_create_rerouted(encoder, "h265_fallback_amf"); + } ++#else ++{ ++ UNUSED_PARAMETER(settings); ++ return obs_encoder_create_rerouted(encoder, "h265_fallback_amf"); ++} ++#endif + + static void *amf_hevc_create_fallback(obs_data_t *settings, + obs_encoder_t *encoder) +@@ -2004,6 +2061,7 @@ static const char *amf_av1_get_name(void + + static inline int get_av1_preset(amf_base *enc, const char *preset) + { ++ UNUSED_PARAMETER(enc); + if (astrcmpi(preset, "highquality") == 0) + return AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_HIGH_QUALITY; + else if (astrcmpi(preset, "quality") == 0) +@@ -2142,8 +2200,8 @@ static bool amf_av1_init(void *data, obs + + info("settings:\n" + "\trate_control: %s\n" +- "\tbitrate: %d\n" +- "\tcqp: %d\n" ++ "\tbitrate: %" PRId64 "\n" ++ "\tcqp: %" PRId64 "\n" + "\tkeyint: %d\n" + "\tpreset: %s\n" + "\tprofile: %s\n" +@@ -2211,6 +2269,7 @@ static void amf_av1_create_internal(amf_ + + static void *amf_av1_create_texencode(obs_data_t *settings, + obs_encoder_t *encoder) ++#ifdef _WIN32 + try { + check_texture_encode_capability(encoder, amf_codec_type::AV1); + +@@ -2233,6 +2292,12 @@ try { + blog(LOG_ERROR, "[texture-amf-av1] %s: %s", __FUNCTION__, err); + return obs_encoder_create_rerouted(encoder, "av1_fallback_amf"); + } ++#else ++{ ++ UNUSED_PARAMETER(settings); ++ return obs_encoder_create_rerouted(encoder, "av1_fallback_amf"); ++} ++#endif + + static void *amf_av1_create_fallback(obs_data_t *settings, + obs_encoder_t *encoder) +@@ -2332,9 +2397,16 @@ static bool enum_luids(void *param, uint + return true; + } + ++#ifdef _WIN32 ++#define OBS_AMF_TEST "obs-amf-test.exe" ++#else ++#define OBS_AMF_TEST "obs-amf-test" ++#endif ++ + extern "C" void amf_load(void) + try { + AMF_RESULT res; ++#ifdef _WIN32 + HMODULE amf_module_test; + + /* Check if the DLL is present before running the more expensive */ +@@ -2344,18 +2416,26 @@ try { + if (!amf_module_test) + throw "No AMF library"; + FreeLibrary(amf_module_test); ++#else ++ void *amf_module_test = os_dlopen(AMF_DLL_NAMEA); ++ if (!amf_module_test) ++ throw "No AMF library"; ++ os_dlclose(amf_module_test); ++#endif + + /* ----------------------------------- */ + /* Check for supported codecs */ + +- BPtr test_exe = os_get_executable_path_ptr("obs-amf-test.exe"); ++ BPtr test_exe = os_get_executable_path_ptr(OBS_AMF_TEST); + std::stringstream cmd; + std::string caps_str; + + cmd << '"'; + cmd << test_exe; + cmd << '"'; ++#ifdef _WIN32 + enum_graphics_device_luids(enum_luids, &cmd); ++#endif + + os_process_pipe_t *pp = os_process_pipe_create(cmd.str().c_str(), "r"); + if (!pp) +@@ -2415,12 +2495,12 @@ try { + /* ----------------------------------- */ + /* Init AMF */ + +- amf_module = LoadLibraryW(AMF_DLL_NAME); ++ amf_module = os_dlopen(AMF_DLL_NAMEA); + if (!amf_module) + throw "AMF library failed to load"; + + AMFInit_Fn init = +- (AMFInit_Fn)GetProcAddress(amf_module, AMF_INIT_FUNCTION_NAME); ++ (AMFInit_Fn)os_dlsym(amf_module, AMF_INIT_FUNCTION_NAME); + if (!init) + throw "Failed to get AMFInit address"; + +@@ -2432,7 +2512,7 @@ try { + if (res != AMF_OK) + throw amf_error("GetTrace failed", res); + +- AMFQueryVersion_Fn get_ver = (AMFQueryVersion_Fn)GetProcAddress( ++ AMFQueryVersion_Fn get_ver = (AMFQueryVersion_Fn)os_dlsym( + amf_module, AMF_QUERY_VERSION_FUNCTION_NAME); + if (!get_ver) + throw "Failed to get AMFQueryVersion address"; +@@ -2471,7 +2551,7 @@ try { + } catch (const amf_error &err) { + /* doing an error here because it means at least the library has loaded + * successfully, so they probably have AMD at this point */ +- blog(LOG_ERROR, "%s: %s: 0x%lX", __FUNCTION__, err.str, ++ blog(LOG_ERROR, "%s: %s: 0x%uX", __FUNCTION__, err.str, + (uint32_t)err.res); + } + diff --git a/patches/6207.patch b/patches/6207.patch deleted file mode 100644 index aff32b4..0000000 --- a/patches/6207.patch +++ /dev/null @@ -1,2272 +0,0 @@ -From 4d25fde40080c4fb55583b23703a8bbc8b507924 Mon Sep 17 00:00:00 2001 -From: Dimitris Papaioannou -Date: Sun, 26 Jun 2022 17:14:24 +0300 -Subject: [PATCH] linux-pipewire: Add PipeWire audio captures - ---- - plugins/linux-pipewire/CMakeLists.txt | 6 +- - plugins/linux-pipewire/data/locale/en-US.ini | 6 + - plugins/linux-pipewire/linux-pipewire.c | 5 + - .../pipewire-audio-capture-app.c | 918 ++++++++++++++++++ - .../pipewire-audio-capture-device.c | 520 ++++++++++ - plugins/linux-pipewire/pipewire-audio.c | 574 +++++++++++ - plugins/linux-pipewire/pipewire-audio.h | 155 +++ - 7 files changed, 2183 insertions(+), 1 deletion(-) - create mode 100644 plugins/linux-pipewire/pipewire-audio-capture-app.c - create mode 100644 plugins/linux-pipewire/pipewire-audio-capture-device.c - create mode 100644 plugins/linux-pipewire/pipewire-audio.c - create mode 100644 plugins/linux-pipewire/pipewire-audio.h - -diff --git a/plugins/linux-pipewire/CMakeLists.txt b/plugins/linux-pipewire/CMakeLists.txt -index 31a55e68b60d7..1e23a3163d26b 100644 ---- a/plugins/linux-pipewire/CMakeLists.txt -+++ b/plugins/linux-pipewire/CMakeLists.txt -@@ -38,7 +38,11 @@ target_sources( - portal.c - portal.h - screencast-portal.c -- screencast-portal.h) -+ screencast-portal.h -+ pipewire-audio.c -+ pipewire-audio.h -+ pipewire-audio-capture-device.c -+ pipewire-audio-capture-app.c) - - target_link_libraries( - linux-pipewire PRIVATE OBS::libobs OBS::obsglad PipeWire::PipeWire GIO::GIO -diff --git a/plugins/linux-pipewire/data/locale/en-US.ini b/plugins/linux-pipewire/data/locale/en-US.ini -index a9e222a995686..edde250b57c76 100644 ---- a/plugins/linux-pipewire/data/locale/en-US.ini -+++ b/plugins/linux-pipewire/data/locale/en-US.ini -@@ -3,3 +3,9 @@ PipeWireSelectMonitor="Select Monitor" - PipeWireSelectWindow="Select Window" - PipeWireWindowCapture="Window Capture (PipeWire)" - ShowCursor="Show Cursor" -+PipeWireAudioCaptureInput="Audio Input Capture (PipeWire)" -+PipeWireAudioCaptureOutput="Audio Output Capture (PipeWire)" -+PipeWireAudioCaptureApplication="Application Audio Capture (PipeWire)" -+Device="Device" -+Application="Application" -+ExceptApp="Capture all apps except selected" -diff --git a/plugins/linux-pipewire/linux-pipewire.c b/plugins/linux-pipewire/linux-pipewire.c -index 798ae2fe8ca9f..253256dfd469f 100644 ---- a/plugins/linux-pipewire/linux-pipewire.c -+++ b/plugins/linux-pipewire/linux-pipewire.c -@@ -2,6 +2,7 @@ - * - * Copyright 2021 columbarius - * Copyright 2021 Georges Basile Stavracas Neto -+ * Copyright 2022 Dimitris Papaioannou - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by -@@ -24,6 +25,7 @@ - - #include - #include "screencast-portal.h" -+#include "pipewire-audio.h" - - OBS_DECLARE_MODULE() - OBS_MODULE_USE_DEFAULT_LOCALE("linux-pipewire", "en-US") -@@ -38,6 +40,9 @@ bool obs_module_load(void) - - screencast_portal_load(); - -+ pipewire_audio_capture_load(); -+ pipewire_audio_capture_app_load(); -+ - return true; - } - -diff --git a/plugins/linux-pipewire/pipewire-audio-capture-app.c b/plugins/linux-pipewire/pipewire-audio-capture-app.c -new file mode 100644 -index 0000000000000..461d2c8f55f26 ---- /dev/null -+++ b/plugins/linux-pipewire/pipewire-audio-capture-app.c -@@ -0,0 +1,918 @@ -+/* pipewire-audio-capture-app.c -+ * -+ * Copyright 2022 Dimitris Papaioannou -+ * -+ * This program is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation, either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program. If not, see . -+ * -+ * SPDX-License-Identifier: GPL-2.0-or-later -+ */ -+ -+#include "pipewire-audio.h" -+ -+#include -+ -+/* Source for capturing applciation audio using PipeWire */ -+ -+struct target_node_port { -+ const char *channel; -+ uint32_t id; -+ -+ struct obs_pw_audio_proxied_object obj; -+}; -+ -+struct target_node { -+ const char *name; -+ const char *app_name; -+ const char *binary; -+ uint32_t id; -+ struct spa_list ports; -+ uint32_t *p_n_targets; -+ -+ struct spa_hook node_listener; -+ -+ struct obs_pw_audio_proxied_object obj; -+}; -+ -+struct system_sink { -+ const char *name; -+ uint32_t id; -+ -+ struct obs_pw_audio_proxied_object obj; -+}; -+ -+struct capture_sink_link { -+ uint32_t id; -+ -+ struct obs_pw_audio_proxied_object obj; -+}; -+ -+struct capture_sink_port { -+ const char *channel; -+ uint32_t id; -+}; -+ -+/** This source basically works like this: -+ - Keep track of output streams and their ports, system sinks and the default sink -+ -+ - Keep track of the channels of the default system sink and create a new virtual sink, -+ destroying the previously made one, with the same channels, then connect the stream to it -+ -+ - Connect any registered or new stream ports to the sink -+*/ -+struct obs_pw_audio_capture_app { -+ struct obs_pw_audio_instance pw; -+ -+ /** The app capture sink automatically mixes -+ * the audio of all the app streams */ -+ struct { -+ struct pw_proxy *proxy; -+ struct spa_hook proxy_listener; -+ bool autoconnect_targets; -+ uint32_t id; -+ uint32_t channels; -+ struct dstr position; -+ DARRAY(struct capture_sink_port) ports; -+ -+ /* Links between app streams and the capture sink */ -+ struct spa_list links; -+ } sink; -+ -+ /** Need the default system sink to create -+ * the app capture sink with the same audio channels */ -+ struct spa_list system_sinks; -+ struct { -+ struct obs_pw_audio_default_node_metadata metadata; -+ struct pw_proxy *proxy; -+ struct spa_hook node_listener; -+ struct spa_hook proxy_listener; -+ } default_sink; -+ -+ struct spa_list targets; -+ uint32_t n_targets; -+ -+ struct dstr target; -+ bool except_app; -+}; -+ -+/* System sinks */ -+static void system_sink_destroy_cb(void *data) -+{ -+ struct system_sink *s = data; -+ bfree((void *)s->name); -+} -+ -+static void register_system_sink(struct obs_pw_audio_capture_app *pwac, -+ uint32_t global_id, const char *name) -+{ -+ struct pw_proxy *sink_proxy = -+ pw_registry_bind(pwac->pw.registry, global_id, -+ PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); -+ if (!sink_proxy) { -+ return; -+ } -+ -+ struct system_sink *sink = bmalloc(sizeof(struct system_sink)); -+ sink->name = bstrdup(name); -+ sink->id = global_id; -+ -+ obs_pw_audio_proxied_object_init(&sink->obj, sink_proxy, -+ &pwac->system_sinks, NULL, -+ system_sink_destroy_cb, sink); -+} -+/* ------------------------------------------------- */ -+ -+/* Target nodes and ports */ -+static void port_destroy_cb(void *data) -+{ -+ struct target_node_port *p = data; -+ bfree((void *)p->channel); -+} -+ -+static void node_destroy_cb(void *data) -+{ -+ struct target_node *node = data; -+ -+ spa_hook_remove(&node->node_listener); -+ -+ struct target_node_port *p, *tp; -+ spa_list_for_each_safe(p, tp, &node->ports, obj.link) -+ { -+ pw_proxy_destroy(p->obj.proxy); -+ } -+ -+ (*node->p_n_targets)--; -+ -+ bfree((void *)node->binary); -+ bfree((void *)node->app_name); -+ bfree((void *)node->name); -+} -+ -+static struct target_node_port *node_register_port(struct target_node *node, -+ uint32_t global_id, -+ struct pw_registry *registry, -+ const char *channel) -+{ -+ struct pw_proxy *port_proxy = pw_registry_bind(registry, global_id, -+ PW_TYPE_INTERFACE_Port, -+ PW_VERSION_PORT, 0); -+ if (!port_proxy) { -+ return NULL; -+ } -+ -+ struct target_node_port *port = -+ bmalloc(sizeof(struct target_node_port)); -+ port->channel = bstrdup(channel); -+ port->id = global_id; -+ -+ obs_pw_audio_proxied_object_init(&port->obj, port_proxy, &node->ports, -+ NULL, port_destroy_cb, port); -+ -+ return port; -+} -+ -+static void on_node_info_cb(void *data, const struct pw_node_info *info) -+{ -+ if ((info->change_mask & PW_NODE_CHANGE_MASK_PROPS) == 0 || -+ !info->props || !info->props->n_items) { -+ return; -+ } -+ -+ const char *binary = -+ spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_BINARY); -+ if (!binary) { -+ return; -+ } -+ -+ struct target_node *node = data; -+ bfree((void *)node->binary); -+ node->binary = bstrdup(binary); -+} -+ -+static const struct pw_node_events node_events = { -+ PW_VERSION_NODE_EVENTS, -+ .info = on_node_info_cb, -+}; -+ -+static void register_target_node(struct obs_pw_audio_capture_app *pwac, -+ uint32_t global_id, const char *app_name, -+ const char *name) -+{ -+ struct pw_proxy *node_proxy = -+ pw_registry_bind(pwac->pw.registry, global_id, -+ PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); -+ if (!node_proxy) { -+ return; -+ } -+ -+ struct target_node *node = bmalloc(sizeof(struct target_node)); -+ node->name = bstrdup(name); -+ node->app_name = bstrdup(app_name); -+ node->binary = NULL; -+ node->id = global_id; -+ node->p_n_targets = &pwac->n_targets; -+ spa_list_init(&node->ports); -+ -+ pwac->n_targets++; -+ -+ obs_pw_audio_proxied_object_init(&node->obj, node_proxy, &pwac->targets, -+ NULL, node_destroy_cb, node); -+ pw_proxy_add_object_listener(node_proxy, &node->node_listener, -+ &node_events, node); -+} -+ -+static bool node_is_targeted(struct obs_pw_audio_capture_app *pwac, -+ struct target_node *node) -+{ -+ if (dstr_is_empty(&pwac->target)) { -+ return false; -+ } -+ -+ return (dstr_cmpi(&pwac->target, node->binary) == 0 || -+ dstr_cmpi(&pwac->target, node->app_name) == 0 || -+ dstr_cmpi(&pwac->target, node->name) == 0) ^ -+ pwac->except_app; -+} -+/* ------------------------------------------------- */ -+ -+/* App streams <-> Capture sink links */ -+static void link_bound_cb(void *data, uint32_t global_id) -+{ -+ struct capture_sink_link *link = data; -+ link->id = global_id; -+} -+ -+static void link_destroy_cb(void *data) -+{ -+ struct capture_sink_link *link = data; -+ blog(LOG_DEBUG, "[pipewire] Link %u destroyed", link->id); -+} -+ -+static void link_port_to_sink(struct obs_pw_audio_capture_app *pwac, -+ struct target_node_port *port, uint32_t node_id) -+{ -+ blog(LOG_DEBUG, -+ "[pipewire] Connecting port %u of node %u to app capture sink", -+ port->id, node_id); -+ -+ uint32_t p = 0; -+ if (pwac->sink.channels == 1 && /* Mono capture sink */ -+ pwac->sink.ports.num >= 1) { -+ p = pwac->sink.ports.array[0].id; -+ } else { -+ for (size_t i = 0; i < pwac->sink.ports.num; i++) { -+ if (astrcmpi(pwac->sink.ports.array[i].channel, -+ port->channel) == 0) { -+ p = pwac->sink.ports.array[i].id; -+ break; -+ } -+ } -+ } -+ -+ if (!p) { -+ blog(LOG_WARNING, -+ "[pipewire] Could not connect port %u of node %u to app capture sink. No port of app capture sink has channel %s", -+ port->id, node_id, port->channel); -+ return; -+ } -+ -+ struct pw_properties *link_props = -+ pw_properties_new(PW_KEY_OBJECT_LINGER, "false", -+ PW_KEY_FACTORY_NAME, "link-factory", NULL); -+ -+ pw_properties_setf(link_props, PW_KEY_LINK_OUTPUT_NODE, "%u", node_id); -+ pw_properties_setf(link_props, PW_KEY_LINK_OUTPUT_PORT, "%u", port->id); -+ -+ pw_properties_setf(link_props, PW_KEY_LINK_INPUT_NODE, "%u", -+ pwac->sink.id); -+ pw_properties_setf(link_props, PW_KEY_LINK_INPUT_PORT, "%u", p); -+ -+ struct pw_proxy *link_proxy = pw_core_create_object( -+ pwac->pw.core, "link-factory", PW_TYPE_INTERFACE_Link, -+ PW_VERSION_LINK, &link_props->dict, 0); -+ -+ obs_pw_audio_instance_sync(&pwac->pw); -+ -+ pw_properties_free(link_props); -+ -+ if (!link_proxy) { -+ blog(LOG_WARNING, -+ "[pipewire] Could not connect port %u of node %u to app capture sink", -+ port->id, node_id); -+ return; -+ } -+ -+ struct capture_sink_link *link = -+ bmalloc(sizeof(struct capture_sink_link)); -+ link->id = SPA_ID_INVALID; -+ -+ obs_pw_audio_proxied_object_init(&link->obj, link_proxy, -+ &pwac->sink.links, link_bound_cb, -+ link_destroy_cb, link); -+} -+ -+static void link_node_to_sink(struct obs_pw_audio_capture_app *pwac, -+ struct target_node *node) -+{ -+ struct target_node_port *p; -+ spa_list_for_each(p, &node->ports, obj.link) -+ { -+ link_port_to_sink(pwac, p, node->id); -+ } -+} -+/* ------------------------------------------------- */ -+ -+/* App capture sink */ -+ -+/** The app capture sink is created when there -+ * is info about the system's default sink. -+ * See the on_metadata and on_default_sink callbacks */ -+ -+static void on_sink_proxy_bound_cb(void *data, uint32_t global_id) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ pwac->sink.id = global_id; -+ da_init(pwac->sink.ports); -+} -+ -+static void on_sink_proxy_removed_cb(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ blog(LOG_WARNING, -+ "[pipewire] App capture sink %u has been destroyed by the PipeWire remote", -+ pwac->sink.id); -+ pw_proxy_destroy(pwac->sink.proxy); -+} -+ -+static void on_sink_proxy_destroy_cb(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ -+ spa_hook_remove(&pwac->sink.proxy_listener); -+ spa_zero(pwac->sink.proxy_listener); -+ -+ for (size_t i = 0; i < pwac->sink.ports.num; i++) { -+ struct capture_sink_port *p = &pwac->sink.ports.array[i]; -+ bfree((void *)p->channel); -+ } -+ da_free(pwac->sink.ports); -+ -+ pwac->sink.channels = 0; -+ dstr_free(&pwac->sink.position); -+ -+ pwac->sink.autoconnect_targets = false; -+ pwac->sink.proxy = NULL; -+ -+ blog(LOG_DEBUG, "[pipewire] App capture sink %u destroyed", -+ pwac->sink.id); -+ -+ pwac->sink.id = SPA_ID_INVALID; -+} -+ -+static void on_sink_proxy_error_cb(void *data, int seq, int res, -+ const char *message) -+{ -+ UNUSED_PARAMETER(data); -+ blog(LOG_ERROR, "[pipewire] App capture sink error: seq:%d res:%d :%s", -+ seq, res, message); -+} -+ -+static const struct pw_proxy_events sink_proxy_events = { -+ PW_VERSION_PROXY_EVENTS, -+ .bound = on_sink_proxy_bound_cb, -+ .removed = on_sink_proxy_removed_cb, -+ .destroy = on_sink_proxy_destroy_cb, -+ .error = on_sink_proxy_error_cb, -+}; -+ -+static void register_capture_sink_port(struct obs_pw_audio_capture_app *pwac, -+ uint32_t global_id, const char *channel) -+{ -+ struct capture_sink_port *port = da_push_back_new(pwac->sink.ports); -+ port->channel = bstrdup(channel); -+ port->id = global_id; -+} -+ -+static void destroy_sink_links(struct obs_pw_audio_capture_app *pwac) -+{ -+ struct capture_sink_link *l, *t; -+ spa_list_for_each_safe(l, t, &pwac->sink.links, obj.link) -+ { -+ pw_proxy_destroy(l->obj.proxy); -+ } -+} -+ -+static void connect_targets(struct obs_pw_audio_capture_app *pwac, -+ const char *target, bool except) -+{ -+ pwac->except_app = except; -+ -+ if (target) { -+ dstr_copy(&pwac->target, target); -+ } -+ -+ if (!pwac->sink.proxy) { -+ return; -+ } -+ -+ destroy_sink_links(pwac); -+ -+ if (dstr_is_empty(&pwac->target)) { -+ return; -+ } -+ -+ struct target_node *n; -+ spa_list_for_each(n, &pwac->targets, obj.link) -+ { -+ if (node_is_targeted(pwac, n)) { -+ link_node_to_sink(pwac, n); -+ } -+ } -+} -+ -+static bool make_capture_sink(struct obs_pw_audio_capture_app *pwac, -+ uint32_t channels, const char *position) -+{ -+ struct pw_properties *sink_props = pw_properties_new( -+ PW_KEY_NODE_NAME, "OBS Studio", PW_KEY_NODE_DESCRIPTION, -+ "OBS App Audio Capture Sink", PW_KEY_FACTORY_NAME, -+ "support.null-audio-sink", PW_KEY_MEDIA_CLASS, -+ "Audio/Sink/Virtual", PW_KEY_NODE_VIRTUAL, "true", -+ SPA_KEY_AUDIO_POSITION, position, NULL); -+ -+ pw_properties_setf(sink_props, PW_KEY_AUDIO_CHANNELS, "%u", channels); -+ -+ pwac->sink.proxy = pw_core_create_object(pwac->pw.core, "adapter", -+ PW_TYPE_INTERFACE_Node, -+ PW_VERSION_NODE, -+ &sink_props->dict, 0); -+ -+ obs_pw_audio_instance_sync(&pwac->pw); -+ -+ pw_properties_free(sink_props); -+ -+ if (!pwac->sink.proxy) { -+ blog(LOG_WARNING, -+ "[pipewire] Failed to create app capture sink"); -+ return false; -+ } -+ -+ pwac->sink.channels = channels; -+ dstr_copy(&pwac->sink.position, position); -+ -+ pwac->sink.id = SPA_ID_INVALID; -+ -+ pw_proxy_add_listener(pwac->sink.proxy, &pwac->sink.proxy_listener, -+ &sink_proxy_events, pwac); -+ -+ while (pwac->sink.id == SPA_ID_INVALID || -+ pwac->sink.ports.num != channels) { -+ /* Iterate until the sink is bound and all the ports are registered */ -+ pw_loop_iterate(pw_thread_loop_get_loop(pwac->pw.thread_loop), -+ -1); -+ } -+ -+ blog(LOG_INFO, -+ "[pipewire] Created app capture sink %u with %u channels and position %s", -+ pwac->sink.id, channels, position); -+ -+ connect_targets(pwac, NULL, pwac->except_app); -+ -+ pwac->sink.autoconnect_targets = true; -+ -+ if (obs_pw_audio_stream_connect(&pwac->pw.audio, pwac->sink.id, -+ channels) < 0) { -+ blog(LOG_WARNING, -+ "[pipewire] Error connecting stream %p to app capture sink %u", -+ pwac->pw.audio.stream, pwac->sink.id); -+ } -+ -+ return true; -+} -+ -+static void destroy_capture_sink(struct obs_pw_audio_capture_app *pwac) -+{ -+ /* Links are automatically destroyed by PipeWire */ -+ -+ if (!pwac->sink.proxy) { -+ return; -+ } -+ -+ if (pw_stream_get_state(pwac->pw.audio.stream, NULL) != -+ PW_STREAM_STATE_UNCONNECTED) { -+ pw_stream_disconnect(pwac->pw.audio.stream); -+ } -+ -+ pwac->sink.autoconnect_targets = false; -+ pw_proxy_destroy(pwac->sink.proxy); -+ obs_pw_audio_instance_sync(&pwac->pw); -+} -+/* ------------------------------------------------- */ -+ -+/* Default system sink */ -+static void on_default_sink_info_cb(void *data, const struct pw_node_info *info) -+{ -+ if ((info->change_mask & PW_NODE_CHANGE_MASK_PROPS) == 0 || -+ !info->props || !info->props->n_items) { -+ return; -+ } -+ -+ struct obs_pw_audio_capture_app *pwac = data; -+ -+ /** Use stereo if -+ * - The default sink uses the Pro Audio profile, since all streams will be configured to use stereo -+ * https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#what-is-the-pro-audio-profile -+ * - The default sink doesn't have the needed props and there isn't already an app capture sink */ -+ -+ const char *channels = -+ spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS); -+ const char *position = -+ spa_dict_lookup(info->props, SPA_KEY_AUDIO_POSITION); -+ if (!channels || !position) { -+ if (pwac->sink.proxy) { -+ return; -+ } -+ channels = "2"; -+ position = "FL,FR"; -+ } else if (astrstri(position, "AUX")) { -+ /* Pro Audio sinks use AUX0,AUX1... and so on as their position (see link above) */ -+ channels = "2"; -+ position = "FL,FR"; -+ } -+ -+ uint32_t c = strtoul(channels, NULL, 10); -+ if (!c) { -+ return; -+ } -+ -+ /* No need to create a new capture sink if the channels are the same */ -+ if (pwac->sink.channels == c && !dstr_is_empty(&pwac->sink.position) && -+ dstr_cmp(&pwac->sink.position, position) == 0) { -+ return; -+ } -+ -+ destroy_capture_sink(pwac); -+ -+ make_capture_sink(pwac, c, position); -+} -+ -+static const struct pw_node_events default_sink_events = { -+ PW_VERSION_NODE_EVENTS, -+ .info = on_default_sink_info_cb, -+}; -+ -+static void on_default_sink_proxy_removed_cb(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ pw_proxy_destroy(pwac->default_sink.proxy); -+} -+ -+static void on_default_sink_proxy_destroy_cb(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ spa_hook_remove(&pwac->default_sink.node_listener); -+ spa_zero(pwac->default_sink.node_listener); -+ -+ spa_hook_remove(&pwac->default_sink.proxy_listener); -+ spa_zero(pwac->default_sink.proxy_listener); -+ -+ pwac->default_sink.proxy = NULL; -+} -+ -+static const struct pw_proxy_events default_sink_proxy_events = { -+ PW_VERSION_PROXY_EVENTS, -+ .removed = on_default_sink_proxy_removed_cb, -+ .destroy = on_default_sink_proxy_destroy_cb, -+}; -+ -+static void default_node_cb(void *data, const char *name) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ -+ blog(LOG_DEBUG, "[pipewire] New default sink %s", name); -+ -+ /* Find the new default sink and bind to it to get its channel info */ -+ struct system_sink *t, *s = NULL; -+ spa_list_for_each(t, &pwac->system_sinks, obj.link) -+ { -+ if (strcmp(name, t->name) == 0) { -+ s = t; -+ break; -+ } -+ } -+ if (!s) { -+ return; -+ } -+ -+ if (pwac->default_sink.proxy) { -+ pw_proxy_destroy(pwac->default_sink.proxy); -+ } -+ -+ pwac->default_sink.proxy = pw_registry_bind(pwac->pw.registry, s->id, -+ PW_TYPE_INTERFACE_Node, -+ PW_VERSION_NODE, 0); -+ if (!pwac->default_sink.proxy) { -+ if (!pwac->sink.proxy) { -+ blog(LOG_WARNING, -+ "[pipewire] Failed to get default sink info, app capture sink defaulting to stereo"); -+ make_capture_sink(pwac, 2, "FL,FR"); -+ } -+ return; -+ } -+ -+ pw_proxy_add_object_listener(pwac->default_sink.proxy, -+ &pwac->default_sink.node_listener, -+ &default_sink_events, pwac); -+ pw_proxy_add_listener(pwac->default_sink.proxy, -+ &pwac->default_sink.proxy_listener, -+ &default_sink_proxy_events, pwac); -+} -+/* ------------------------------------------------- */ -+ -+/* Registry */ -+static void on_global_cb(void *data, uint32_t id, uint32_t permissions, -+ const char *type, uint32_t version, -+ const struct spa_dict *props) -+{ -+ UNUSED_PARAMETER(permissions); -+ UNUSED_PARAMETER(version); -+ -+ if (!props || !type) { -+ return; -+ } -+ -+ struct obs_pw_audio_capture_app *pwac = data; -+ -+ if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) { -+ const char *nid, *dir, *chn; -+ if (!(nid = spa_dict_lookup(props, PW_KEY_NODE_ID)) || -+ !(dir = spa_dict_lookup(props, PW_KEY_PORT_DIRECTION)) || -+ !(chn = spa_dict_lookup(props, PW_KEY_AUDIO_CHANNEL))) { -+ return; -+ } -+ -+ uint32_t node_id = strtoul(nid, NULL, 10); -+ -+ if (astrcmpi(dir, "in") == 0 && node_id == pwac->sink.id) { -+ register_capture_sink_port(pwac, id, chn); -+ } else if (astrcmpi(dir, "out") == 0) { -+ /* Possibly a target port */ -+ struct target_node *t, *n = NULL; -+ spa_list_for_each(t, &pwac->targets, obj.link) -+ { -+ if (t->id == node_id) { -+ n = t; -+ break; -+ } -+ } -+ if (!n) { -+ return; -+ } -+ -+ struct target_node_port *p = node_register_port( -+ n, id, pwac->pw.registry, chn); -+ -+ if (p && pwac->sink.autoconnect_targets && -+ node_is_targeted(pwac, n)) { -+ link_port_to_sink(pwac, p, n->id); -+ } -+ } -+ } else if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { -+ const char *node_name, *media_class; -+ if (!(node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME)) || -+ !(media_class = -+ spa_dict_lookup(props, PW_KEY_MEDIA_CLASS))) { -+ return; -+ } -+ -+ if (strcmp(media_class, "Stream/Output/Audio") == 0) { -+ /* Target node */ -+ const char *node_app_name = -+ spa_dict_lookup(props, PW_KEY_APP_NAME); -+ -+ if (!node_app_name) { -+ node_app_name = node_name; -+ } -+ -+ register_target_node(pwac, id, node_app_name, -+ node_name); -+ } else if (strcmp(media_class, "Audio/Sink") == 0) { -+ register_system_sink(pwac, id, node_name); -+ } -+ } else if (strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) { -+ const char *name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); -+ if (!name || strcmp(name, "default") != 0) { -+ return; -+ } -+ -+ if (!obs_pw_audio_default_node_metadata_listen( -+ &pwac->default_sink.metadata, &pwac->pw, id, true, -+ default_node_cb, pwac) && -+ !pwac->sink.proxy) { -+ blog(LOG_WARNING, -+ "[pipewire] Failed to get default metadata, app capture sink defaulting to stereo"); -+ make_capture_sink(pwac, 2, "FL,FR"); -+ } -+ } -+} -+ -+static const struct pw_registry_events registry_events = { -+ PW_VERSION_REGISTRY_EVENTS, -+ .global = on_global_cb, -+}; -+/* ------------------------------------------------- */ -+ -+/* Source */ -+static void *pipewire_audio_capture_app_create(obs_data_t *settings, -+ obs_source_t *source) -+{ -+ struct obs_pw_audio_capture_app *pwac = -+ bzalloc(sizeof(struct obs_pw_audio_capture_app)); -+ -+ if (!obs_pw_audio_instance_init(&pwac->pw, ®istry_events, pwac, true, -+ false, source)) { -+ obs_pw_audio_instance_destroy(&pwac->pw); -+ -+ bfree(pwac); -+ return NULL; -+ } -+ -+ spa_list_init(&pwac->targets); -+ spa_list_init(&pwac->sink.links); -+ spa_list_init(&pwac->system_sinks); -+ -+ pwac->sink.id = SPA_ID_INVALID; -+ dstr_init(&pwac->sink.position); -+ -+ dstr_init_copy(&pwac->target, obs_data_get_string(settings, "Target")); -+ pwac->except_app = obs_data_get_bool(settings, "ExceptApp"); -+ -+ obs_pw_audio_instance_sync(&pwac->pw); -+ pw_thread_loop_wait(pwac->pw.thread_loop); -+ pw_thread_loop_unlock(pwac->pw.thread_loop); -+ -+ return pwac; -+} -+ -+static void pipewire_audio_capture_app_defaults(obs_data_t *settings) -+{ -+ obs_data_set_default_bool(settings, "ExceptApp", false); -+} -+ -+static int cmp_targets(const void *a, const void *b) -+{ -+ const char *a_str = *(char **)a; -+ const char *b_str = *(char **)b; -+ return strcmp(a_str, b_str); -+} -+ -+static obs_properties_t *pipewire_audio_capture_app_properties(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ -+ obs_properties_t *p = obs_properties_create(); -+ -+ obs_property_t *prop_targets_list = obs_properties_add_list( -+ p, "Target", obs_module_text("Application"), -+ OBS_COMBO_TYPE_EDITABLE, OBS_COMBO_FORMAT_STRING); -+ -+ obs_properties_add_bool(p, "ExceptApp", obs_module_text("ExceptApp")); -+ -+ DARRAY(char *) targets_arr; -+ da_init(targets_arr); -+ -+ pw_thread_loop_lock(pwac->pw.thread_loop); -+ -+ da_reserve(targets_arr, pwac->n_targets); -+ -+ struct target_node *node; -+ spa_list_for_each(node, &pwac->targets, obj.link) -+ { -+ da_push_back(targets_arr, node->binary ? &node->binary -+ : node->app_name ? &node->app_name -+ : &node->name); -+ } -+ -+ /* Show just one entry per app */ -+ -+ qsort(targets_arr.array, targets_arr.num, sizeof(char *), cmp_targets); -+ -+ for (size_t i = 0; i < targets_arr.num; i++) { -+ if (i == 0 || strcmp(targets_arr.array[i - 1], -+ targets_arr.array[i]) != 0) { -+ obs_property_list_add_string( -+ prop_targets_list, targets_arr.array[i], NULL); -+ } -+ } -+ -+ pw_thread_loop_unlock(pwac->pw.thread_loop); -+ -+ da_free(targets_arr); -+ -+ return p; -+} -+ -+static void pipewire_audio_capture_app_update(void *data, obs_data_t *settings) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ -+ bool except = obs_data_get_bool(settings, "ExceptApp"); -+ -+ const char *new_target = obs_data_get_string(settings, "Target"); -+ -+ pw_thread_loop_lock(pwac->pw.thread_loop); -+ -+ if (except == pwac->except_app && -+ dstr_cmpi(&pwac->target, new_target) == 0) { -+ pw_thread_loop_unlock(pwac->pw.thread_loop); -+ return; -+ } -+ -+ connect_targets(pwac, new_target, except); -+ -+ obs_pw_audio_instance_sync(&pwac->pw); -+ pw_thread_loop_wait(pwac->pw.thread_loop); -+ pw_thread_loop_unlock(pwac->pw.thread_loop); -+} -+ -+static void pipewire_audio_capture_app_show(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ pw_stream_set_active(pwac->pw.audio.stream, true); -+} -+ -+static void pipewire_audio_capture_app_hide(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ pw_stream_set_active(pwac->pw.audio.stream, false); -+} -+ -+static void pipewire_audio_capture_app_destroy(void *data) -+{ -+ struct obs_pw_audio_capture_app *pwac = data; -+ -+ pw_thread_loop_lock(pwac->pw.thread_loop); -+ -+ struct target_node *n, *tn; -+ spa_list_for_each_safe(n, tn, &pwac->targets, obj.link) -+ { -+ pw_proxy_destroy(n->obj.proxy); -+ } -+ struct system_sink *s, *ts; -+ spa_list_for_each_safe(s, ts, &pwac->system_sinks, obj.link) -+ { -+ pw_proxy_destroy(s->obj.proxy); -+ } -+ -+ destroy_capture_sink(pwac); -+ -+ if (pwac->default_sink.proxy) { -+ pw_proxy_destroy(pwac->default_sink.proxy); -+ } -+ if (pwac->default_sink.metadata.proxy) { -+ pw_proxy_destroy(pwac->default_sink.metadata.proxy); -+ } -+ -+ obs_pw_audio_instance_destroy(&pwac->pw); -+ -+ dstr_free(&pwac->sink.position); -+ dstr_free(&pwac->target); -+ -+ bfree(pwac); -+} -+ -+static const char *pipewire_audio_capture_app_name(void *data) -+{ -+ UNUSED_PARAMETER(data); -+ return obs_module_text("PipeWireAudioCaptureApplication"); -+} -+ -+void pipewire_audio_capture_app_load(void) -+{ -+ const struct obs_source_info pipewire_audio_capture_application = { -+ .id = "pipewire-audio-capture-application", -+ .type = OBS_SOURCE_TYPE_INPUT, -+ .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE, -+ .get_name = pipewire_audio_capture_app_name, -+ .create = pipewire_audio_capture_app_create, -+ .get_defaults = pipewire_audio_capture_app_defaults, -+ .get_properties = pipewire_audio_capture_app_properties, -+ .update = pipewire_audio_capture_app_update, -+ .show = pipewire_audio_capture_app_show, -+ .hide = pipewire_audio_capture_app_hide, -+ .destroy = pipewire_audio_capture_app_destroy, -+ .icon_type = OBS_ICON_TYPE_PROCESS_AUDIO_OUTPUT, -+ }; -+ -+ obs_register_source(&pipewire_audio_capture_application); -+} -diff --git a/plugins/linux-pipewire/pipewire-audio-capture-device.c b/plugins/linux-pipewire/pipewire-audio-capture-device.c -new file mode 100644 -index 0000000000000..b474ddad39fac ---- /dev/null -+++ b/plugins/linux-pipewire/pipewire-audio-capture-device.c -@@ -0,0 +1,520 @@ -+/* pipewire-audio-capture-device.c -+ * -+ * Copyright 2022 Dimitris Papaioannou -+ * -+ * This program is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation, either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program. If not, see . -+ * -+ * SPDX-License-Identifier: GPL-2.0-or-later -+ */ -+ -+#include "pipewire-audio.h" -+ -+#include -+ -+/* Source for capturing device audio using PipeWire */ -+ -+enum obs_pw_audio_capture_device_type { -+ PIPEWIRE_AUDIO_CAPTURE_DEVICE_INPUT, -+ PIPEWIRE_AUDIO_CAPTURE_DEVICE_OUTPUT, -+}; -+ -+struct target_node { -+ const char *friendly_name; -+ const char *name; -+ uint32_t id; -+ uint32_t channels; -+ -+ struct spa_hook node_listener; -+ -+ struct obs_pw_audio_capture_device *pwac; -+ -+ struct obs_pw_audio_proxied_object obj; -+}; -+ -+struct obs_pw_audio_capture_device { -+ obs_source_t *source; -+ -+ enum obs_pw_audio_capture_device_type capture_type; -+ -+ struct obs_pw_audio_instance pw; -+ -+ struct { -+ struct obs_pw_audio_default_node_metadata metadata; -+ bool autoconnect; -+ uint32_t node_id; -+ struct dstr name; -+ } default_info; -+ -+ struct spa_list targets; -+ -+ struct dstr target_name; -+ uint32_t connected_id; -+}; -+ -+static void start_streaming(struct obs_pw_audio_capture_device *pwac, -+ struct target_node *node) -+{ -+ if (!node || !node->channels) { -+ return; -+ } -+ -+ dstr_copy(&pwac->target_name, node->name); -+ -+ if (pw_stream_get_state(pwac->pw.audio.stream, NULL) != -+ PW_STREAM_STATE_UNCONNECTED) { -+ if (node->id == pwac->connected_id) { -+ /* Already connected to this node */ -+ return; -+ } -+ pw_stream_disconnect(pwac->pw.audio.stream); -+ } -+ -+ if (obs_pw_audio_stream_connect(&pwac->pw.audio, node->id, -+ node->channels) == 0) { -+ pwac->connected_id = node->id; -+ blog(LOG_INFO, "[pipewire] %p streaming from %u", -+ pwac->pw.audio.stream, node->id); -+ } else { -+ pwac->connected_id = SPA_ID_INVALID; -+ blog(LOG_WARNING, "[pipewire] Error connecting stream %p", -+ pwac->pw.audio.stream); -+ } -+ -+ pw_stream_set_active(pwac->pw.audio.stream, -+ obs_source_active(pwac->source)); -+} -+ -+struct target_node *get_node_by_name(struct obs_pw_audio_capture_device *pwac, -+ const char *name) -+{ -+ struct target_node *n; -+ spa_list_for_each(n, &pwac->targets, obj.link) -+ { -+ if (strcmp(n->name, name) == 0) { -+ return n; -+ } -+ } -+ return NULL; -+} -+ -+struct target_node *get_node_by_id(struct obs_pw_audio_capture_device *pwac, -+ uint32_t id) -+{ -+ struct target_node *n; -+ spa_list_for_each(n, &pwac->targets, obj.link) -+ { -+ if (n->id == id) { -+ return n; -+ } -+ } -+ return NULL; -+} -+ -+/* Target node */ -+static void on_node_info_cb(void *data, const struct pw_node_info *info) -+{ -+ if ((info->change_mask & PW_NODE_CHANGE_MASK_PROPS) == 0 || -+ !info->props || !info->props->n_items) { -+ return; -+ } -+ -+ const char *channels = -+ spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS); -+ if (!channels) { -+ return; -+ } -+ -+ uint32_t c = strtoul(channels, NULL, 10); -+ -+ struct target_node *n = data; -+ if (n->channels == c) { -+ return; -+ } -+ n->channels = c; -+ -+ struct obs_pw_audio_capture_device *pwac = n->pwac; -+ -+ /** If this is the default device and the stream is not already connected to it -+ * or the stream is unconnected and this node has the desired target name */ -+ if ((pwac->default_info.autoconnect && pwac->connected_id != n->id && -+ !dstr_is_empty(&pwac->default_info.name) && -+ dstr_cmp(&pwac->default_info.name, n->name) == 0) || -+ (pw_stream_get_state(pwac->pw.audio.stream, NULL) == -+ PW_STREAM_STATE_UNCONNECTED && -+ !dstr_is_empty(&pwac->target_name) && -+ dstr_cmp(&pwac->target_name, n->name) == 0)) { -+ start_streaming(pwac, n); -+ } -+} -+ -+static const struct pw_node_events node_events = { -+ PW_VERSION_NODE_EVENTS, -+ .info = on_node_info_cb, -+}; -+ -+static void node_destroy_cb(void *data) -+{ -+ struct target_node *n = data; -+ -+ spa_hook_remove(&n->node_listener); -+ -+ bfree((void *)n->friendly_name); -+ bfree((void *)n->name); -+} -+ -+static void register_target_node(struct obs_pw_audio_capture_device *pwac, -+ const char *friendly_name, const char *name, -+ uint32_t global_id) -+{ -+ struct pw_proxy *node_proxy = -+ pw_registry_bind(pwac->pw.registry, global_id, -+ PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); -+ if (!node_proxy) { -+ return; -+ } -+ -+ struct target_node *n = bmalloc(sizeof(struct target_node)); -+ n->friendly_name = bstrdup(friendly_name); -+ n->name = bstrdup(name); -+ n->id = global_id; -+ n->channels = 0; -+ n->pwac = pwac; -+ -+ obs_pw_audio_proxied_object_init(&n->obj, node_proxy, &pwac->targets, -+ NULL, node_destroy_cb, n); -+ -+ spa_zero(n->node_listener); -+ pw_proxy_add_object_listener(n->obj.proxy, &n->node_listener, -+ &node_events, n); -+} -+/* ------------------------------------------------- */ -+ -+/* Default device metadata */ -+static void default_node_cb(void *data, const char *name) -+{ -+ struct obs_pw_audio_capture_device *pwac = data; -+ -+ blog(LOG_DEBUG, "[pipewire] New default device %s", name); -+ -+ dstr_copy(&pwac->default_info.name, name); -+ -+ struct target_node *n = get_node_by_name(pwac, name); -+ if (n) { -+ pwac->default_info.node_id = n->id; -+ if (pwac->default_info.autoconnect) { -+ start_streaming(pwac, n); -+ } -+ } -+} -+/* ------------------------------------------------- */ -+ -+/* Registry */ -+static void on_global_cb(void *data, uint32_t id, uint32_t permissions, -+ const char *type, uint32_t version, -+ const struct spa_dict *props) -+{ -+ UNUSED_PARAMETER(permissions); -+ UNUSED_PARAMETER(version); -+ -+ struct obs_pw_audio_capture_device *pwac = data; -+ -+ if (!props || !type) { -+ return; -+ } -+ -+ if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { -+ const char *node_name, *media_class; -+ if (!(node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME)) || -+ !(media_class = -+ spa_dict_lookup(props, PW_KEY_MEDIA_CLASS))) { -+ return; -+ } -+ -+ /* Target device */ -+ if ((pwac->capture_type == -+ PIPEWIRE_AUDIO_CAPTURE_DEVICE_INPUT && -+ (strcmp(media_class, "Audio/Source") == 0 || -+ strcmp(media_class, "Audio/Source/Virtual") == 0)) || -+ (pwac->capture_type == -+ PIPEWIRE_AUDIO_CAPTURE_DEVICE_OUTPUT && -+ strcmp(media_class, "Audio/Sink") == 0)) { -+ const char *node_friendly_name = -+ spa_dict_lookup(props, PW_KEY_NODE_NICK); -+ if (!node_friendly_name) { -+ node_friendly_name = spa_dict_lookup( -+ props, PW_KEY_NODE_DESCRIPTION); -+ if (!node_friendly_name) { -+ node_friendly_name = node_name; -+ } -+ } -+ -+ register_target_node(pwac, node_friendly_name, -+ node_name, id); -+ } -+ } else if (strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) { -+ const char *name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); -+ if (!name || strcmp(name, "default") != 0) { -+ return; -+ } -+ -+ if (!obs_pw_audio_default_node_metadata_listen( -+ &pwac->default_info.metadata, &pwac->pw, id, -+ pwac->capture_type == -+ PIPEWIRE_AUDIO_CAPTURE_DEVICE_OUTPUT, -+ default_node_cb, pwac)) { -+ blog(LOG_WARNING, -+ "[pipewire] Failed to get default metadata, cannot detect default audio devices"); -+ } -+ } -+} -+ -+static void on_global_remove_cb(void *data, uint32_t id) -+{ -+ struct obs_pw_audio_capture_device *pwac = data; -+ -+ if (pwac->default_info.node_id == id) { -+ pwac->default_info.node_id = SPA_ID_INVALID; -+ } -+ -+ /** If the node we're connected to is removed, -+ * try to find one with the same name and connect to it. */ -+ if (id == pwac->connected_id) { -+ pwac->connected_id = SPA_ID_INVALID; -+ -+ pw_stream_disconnect(pwac->pw.audio.stream); -+ -+ if (!pwac->default_info.autoconnect && -+ !dstr_is_empty(&pwac->target_name)) { -+ start_streaming(pwac, -+ get_node_by_name( -+ pwac, pwac->target_name.array)); -+ } -+ } -+} -+ -+static const struct pw_registry_events registry_events = { -+ PW_VERSION_REGISTRY_EVENTS, -+ .global = on_global_cb, -+ .global_remove = on_global_remove_cb, -+}; -+/* ------------------------------------------------- */ -+ -+/* Source */ -+static void *pipewire_audio_capture_create( -+ obs_data_t *settings, obs_source_t *source, -+ enum obs_pw_audio_capture_device_type capture_type) -+{ -+ struct obs_pw_audio_capture_device *pwac = -+ bzalloc(sizeof(struct obs_pw_audio_capture_device)); -+ -+ if (!obs_pw_audio_instance_init( -+ &pwac->pw, ®istry_events, pwac, -+ capture_type == PIPEWIRE_AUDIO_CAPTURE_DEVICE_OUTPUT, true, -+ source)) { -+ obs_pw_audio_instance_destroy(&pwac->pw); -+ -+ bfree(pwac); -+ return NULL; -+ } -+ -+ pwac->source = source; -+ pwac->capture_type = capture_type; -+ pwac->default_info.node_id = SPA_ID_INVALID; -+ pwac->connected_id = SPA_ID_INVALID; -+ -+ spa_list_init(&pwac->targets); -+ -+ if (obs_data_get_int(settings, "TargetId") != PW_ID_ANY) { -+ /** Reset id setting, PipeWire node ids may not persist between sessions. -+ * Connecting to saved target will happen based on the TargetName setting -+ * once target has connected */ -+ obs_data_set_int(settings, "TargetId", 0); -+ } else { -+ pwac->default_info.autoconnect = true; -+ } -+ -+ dstr_init_copy(&pwac->target_name, -+ obs_data_get_string(settings, "TargetName")); -+ -+ obs_pw_audio_instance_sync(&pwac->pw); -+ pw_thread_loop_wait(pwac->pw.thread_loop); -+ pw_thread_loop_unlock(pwac->pw.thread_loop); -+ -+ return pwac; -+} -+ -+static void *pipewire_audio_capture_input_create(obs_data_t *settings, -+ obs_source_t *source) -+{ -+ return pipewire_audio_capture_create( -+ settings, source, PIPEWIRE_AUDIO_CAPTURE_DEVICE_INPUT); -+} -+ -+static void *pipewire_audio_capture_output_create(obs_data_t *settings, -+ obs_source_t *source) -+{ -+ return pipewire_audio_capture_create( -+ settings, source, PIPEWIRE_AUDIO_CAPTURE_DEVICE_OUTPUT); -+} -+ -+static void pipewire_audio_capture_defaults(obs_data_t *settings) -+{ -+ obs_data_set_default_int(settings, "TargetId", PW_ID_ANY); -+} -+ -+static obs_properties_t *pipewire_audio_capture_properties(void *data) -+{ -+ struct obs_pw_audio_capture_device *pwac = data; -+ -+ obs_properties_t *p = obs_properties_create(); -+ -+ obs_property_t *prop_targets_list = obs_properties_add_list( -+ p, "TargetId", obs_module_text("Device"), OBS_COMBO_TYPE_LIST, -+ OBS_COMBO_FORMAT_INT); -+ -+ obs_property_list_add_int(prop_targets_list, obs_module_text("Default"), -+ PW_ID_ANY); -+ -+ pw_thread_loop_lock(pwac->pw.thread_loop); -+ -+ struct target_node *n; -+ spa_list_for_each(n, &pwac->targets, obj.link) -+ { -+ obs_property_list_add_int(prop_targets_list, n->friendly_name, -+ n->id); -+ } -+ -+ pw_thread_loop_unlock(pwac->pw.thread_loop); -+ -+ return p; -+} -+ -+static void pipewire_audio_capture_update(void *data, obs_data_t *settings) -+{ -+ struct obs_pw_audio_capture_device *pwac = data; -+ -+ uint32_t new_node_id = obs_data_get_int(settings, "TargetId"); -+ -+ pw_thread_loop_lock(pwac->pw.thread_loop); -+ -+ if (new_node_id == PW_ID_ANY) { -+ pwac->default_info.autoconnect = true; -+ -+ if (pwac->default_info.node_id != SPA_ID_INVALID) { -+ start_streaming( -+ pwac, -+ get_node_by_id(pwac, -+ pwac->default_info.node_id)); -+ } -+ goto unlock; -+ } -+ -+ pwac->default_info.autoconnect = false; -+ -+ struct target_node *new_node = get_node_by_id(pwac, new_node_id); -+ if (!new_node) { -+ goto unlock; -+ } -+ -+ start_streaming(pwac, new_node); -+ -+ obs_data_set_string(settings, "TargetName", pwac->target_name.array); -+ -+unlock: -+ pw_thread_loop_unlock(pwac->pw.thread_loop); -+} -+ -+static void pipewire_audio_capture_show(void *data) -+{ -+ struct obs_pw_audio_capture_device *pwac = data; -+ pw_stream_set_active(pwac->pw.audio.stream, true); -+} -+ -+static void pipewire_audio_capture_hide(void *data) -+{ -+ struct obs_pw_audio_capture_device *pwac = data; -+ pw_stream_set_active(pwac->pw.audio.stream, false); -+} -+ -+static void pipewire_audio_capture_destroy(void *data) -+{ -+ struct obs_pw_audio_capture_device *pwac = data; -+ -+ pw_thread_loop_lock(pwac->pw.thread_loop); -+ -+ struct target_node *n, *tn; -+ spa_list_for_each_safe(n, tn, &pwac->targets, obj.link) -+ { -+ pw_proxy_destroy(n->obj.proxy); -+ } -+ -+ if (pwac->default_info.metadata.proxy) { -+ pw_proxy_destroy(pwac->default_info.metadata.proxy); -+ } -+ -+ obs_pw_audio_instance_destroy(&pwac->pw); -+ -+ dstr_free(&pwac->default_info.name); -+ dstr_free(&pwac->target_name); -+ -+ bfree(pwac); -+} -+ -+static const char *pipewire_audio_capture_input_name(void *data) -+{ -+ UNUSED_PARAMETER(data); -+ return obs_module_text("PipeWireAudioCaptureInput"); -+} -+ -+static const char *pipewire_audio_capture_output_name(void *data) -+{ -+ UNUSED_PARAMETER(data); -+ return obs_module_text("PipeWireAudioCaptureOutput"); -+} -+ -+void pipewire_audio_capture_load(void) -+{ -+ const struct obs_source_info pipewire_audio_capture_input = { -+ .id = "pipewire-audio-capture-input", -+ .type = OBS_SOURCE_TYPE_INPUT, -+ .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE, -+ .get_name = pipewire_audio_capture_input_name, -+ .create = pipewire_audio_capture_input_create, -+ .get_defaults = pipewire_audio_capture_defaults, -+ .get_properties = pipewire_audio_capture_properties, -+ .update = pipewire_audio_capture_update, -+ .show = pipewire_audio_capture_show, -+ .hide = pipewire_audio_capture_hide, -+ .destroy = pipewire_audio_capture_destroy, -+ .icon_type = OBS_ICON_TYPE_AUDIO_INPUT, -+ }; -+ const struct obs_source_info pipewire_audio_capture_output = { -+ .id = "pipewire-audio-capture-output", -+ .type = OBS_SOURCE_TYPE_INPUT, -+ .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE | -+ OBS_SOURCE_DO_NOT_SELF_MONITOR, -+ .get_name = pipewire_audio_capture_output_name, -+ .create = pipewire_audio_capture_output_create, -+ .get_defaults = pipewire_audio_capture_defaults, -+ .get_properties = pipewire_audio_capture_properties, -+ .update = pipewire_audio_capture_update, -+ .show = pipewire_audio_capture_show, -+ .hide = pipewire_audio_capture_hide, -+ .destroy = pipewire_audio_capture_destroy, -+ .icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT, -+ }; -+ -+ obs_register_source(&pipewire_audio_capture_input); -+ obs_register_source(&pipewire_audio_capture_output); -+} -diff --git a/plugins/linux-pipewire/pipewire-audio.c b/plugins/linux-pipewire/pipewire-audio.c -new file mode 100644 -index 0000000000000..ea761221ba10f ---- /dev/null -+++ b/plugins/linux-pipewire/pipewire-audio.c -@@ -0,0 +1,574 @@ -+/* pipewire-audio.c -+ * -+ * Copyright 2022 Dimitris Papaioannou -+ * -+ * This program is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation, either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program. If not, see . -+ * -+ * SPDX-License-Identifier: GPL-2.0-or-later -+ */ -+ -+#include "pipewire-audio.h" -+ -+#include -+ -+#include -+ -+/* Utilities */ -+bool json_object_find(const char *obj, const char *key, char *value, size_t len) -+{ -+ /* From PipeWire's source */ -+ -+ struct spa_json it[2]; -+ const char *v; -+ char k[128]; -+ -+ spa_json_init(&it[0], obj, strlen(obj)); -+ if (spa_json_enter_object(&it[0], &it[1]) <= 0) { -+ return false; -+ } -+ -+ while (spa_json_get_string(&it[1], k, sizeof(k)) > 0) { -+ if (spa_streq(k, key)) { -+ if (spa_json_get_string(&it[1], value, len) > 0) { -+ return true; -+ } -+ } else if (spa_json_next(&it[1], &v) <= 0) { -+ break; -+ } -+ } -+ return false; -+} -+/* ------------------------------------------------- */ -+ -+/* PipeWire stream wrapper */ -+void obs_channels_to_spa_audio_position(enum spa_audio_channel *position, -+ uint32_t channels) -+{ -+ switch (channels) { -+ case 1: -+ position[0] = SPA_AUDIO_CHANNEL_MONO; -+ break; -+ case 2: -+ position[0] = SPA_AUDIO_CHANNEL_FL; -+ position[1] = SPA_AUDIO_CHANNEL_FR; -+ break; -+ case 3: -+ position[0] = SPA_AUDIO_CHANNEL_FL; -+ position[1] = SPA_AUDIO_CHANNEL_FR; -+ position[2] = SPA_AUDIO_CHANNEL_LFE; -+ break; -+ case 4: -+ position[0] = SPA_AUDIO_CHANNEL_FL; -+ position[1] = SPA_AUDIO_CHANNEL_FR; -+ position[2] = SPA_AUDIO_CHANNEL_FC; -+ position[3] = SPA_AUDIO_CHANNEL_RC; -+ break; -+ case 5: -+ position[0] = SPA_AUDIO_CHANNEL_FL; -+ position[1] = SPA_AUDIO_CHANNEL_FR; -+ position[2] = SPA_AUDIO_CHANNEL_FC; -+ position[3] = SPA_AUDIO_CHANNEL_LFE; -+ position[4] = SPA_AUDIO_CHANNEL_RC; -+ break; -+ case 6: -+ position[0] = SPA_AUDIO_CHANNEL_FL; -+ position[1] = SPA_AUDIO_CHANNEL_FR; -+ position[2] = SPA_AUDIO_CHANNEL_FC; -+ position[3] = SPA_AUDIO_CHANNEL_LFE; -+ position[4] = SPA_AUDIO_CHANNEL_RL; -+ position[5] = SPA_AUDIO_CHANNEL_RR; -+ break; -+ case 8: -+ position[0] = SPA_AUDIO_CHANNEL_FL; -+ position[1] = SPA_AUDIO_CHANNEL_FR; -+ position[2] = SPA_AUDIO_CHANNEL_FC; -+ position[3] = SPA_AUDIO_CHANNEL_LFE; -+ position[4] = SPA_AUDIO_CHANNEL_RL; -+ position[5] = SPA_AUDIO_CHANNEL_RR; -+ position[6] = SPA_AUDIO_CHANNEL_SL; -+ position[7] = SPA_AUDIO_CHANNEL_SR; -+ break; -+ default: -+ for (size_t i = 0; i < channels; i++) { -+ position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; -+ } -+ break; -+ } -+} -+ -+enum audio_format spa_to_obs_audio_format(enum spa_audio_format format) -+{ -+ switch (format) { -+ case SPA_AUDIO_FORMAT_U8: -+ return AUDIO_FORMAT_U8BIT; -+ case SPA_AUDIO_FORMAT_S16_LE: -+ return AUDIO_FORMAT_16BIT; -+ case SPA_AUDIO_FORMAT_S32_LE: -+ return AUDIO_FORMAT_32BIT; -+ case SPA_AUDIO_FORMAT_F32_LE: -+ return AUDIO_FORMAT_FLOAT; -+ case SPA_AUDIO_FORMAT_U8P: -+ return AUDIO_FORMAT_U8BIT_PLANAR; -+ case SPA_AUDIO_FORMAT_S16P: -+ return AUDIO_FORMAT_16BIT_PLANAR; -+ case SPA_AUDIO_FORMAT_S32P: -+ return AUDIO_FORMAT_32BIT_PLANAR; -+ case SPA_AUDIO_FORMAT_F32P: -+ return AUDIO_FORMAT_FLOAT_PLANAR; -+ default: -+ return AUDIO_FORMAT_UNKNOWN; -+ } -+} -+ -+enum speaker_layout spa_to_obs_speakers(uint32_t channels) -+{ -+ switch (channels) { -+ case 1: -+ return SPEAKERS_MONO; -+ case 2: -+ return SPEAKERS_STEREO; -+ case 3: -+ return SPEAKERS_2POINT1; -+ case 4: -+ return SPEAKERS_4POINT0; -+ case 5: -+ return SPEAKERS_4POINT1; -+ case 6: -+ return SPEAKERS_5POINT1; -+ case 8: -+ return SPEAKERS_7POINT1; -+ default: -+ return SPEAKERS_UNKNOWN; -+ } -+} -+ -+bool spa_to_obs_pw_audio_info(struct obs_pw_audio_info *info, -+ const struct spa_pod *param) -+{ -+ struct spa_audio_info_raw audio_info; -+ -+ if (spa_format_audio_raw_parse(param, &audio_info) < 0) { -+ info->sample_rate = 0; -+ info->format = AUDIO_FORMAT_UNKNOWN; -+ info->speakers = SPEAKERS_UNKNOWN; -+ -+ return false; -+ } -+ -+ info->sample_rate = audio_info.rate; -+ info->speakers = spa_to_obs_speakers(audio_info.channels); -+ info->format = spa_to_obs_audio_format(audio_info.format); -+ -+ return true; -+} -+ -+static void on_process_cb(void *data) -+{ -+ uint64_t now = os_gettime_ns(); -+ -+ struct obs_pw_audio_stream *s = data; -+ -+ struct pw_buffer *b = pw_stream_dequeue_buffer(s->stream); -+ -+ if (!b) { -+ return; -+ } -+ -+ struct spa_buffer *buf = b->buffer; -+ -+ if (!s->info.sample_rate || buf->datas[0].type != SPA_DATA_MemPtr) { -+ goto queue; -+ } -+ -+ struct obs_source_audio out = { -+ .frames = s->pos->clock.duration, -+ .speakers = s->info.speakers, -+ .format = s->info.format, -+ .samples_per_sec = s->info.sample_rate, -+ }; -+ -+ for (size_t i = 0; i < buf->n_datas && i < 8; i++) { -+ out.data[i] = buf->datas[i].data; -+ } -+ -+ if (s->pos && (s->info.sample_rate * s->pos->clock.rate_diff)) { -+ /** Taken from PipeWire's implementation of JACK's jack_get_cycle_times -+ * (https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/0.3.52/pipewire-jack/src/pipewire-jack.c#L5639) -+ * which is used in the linux-jack plugin to correctly set the timestamp -+ * (https://github.com/obsproject/obs-studio/blob/27.2.4/plugins/linux-jack/jack-wrapper.c#L87) */ -+ -+ float period_usecs = -+ s->pos->clock.duration * (float)SPA_USEC_PER_SEC / -+ (s->info.sample_rate * s->pos->clock.rate_diff); -+ -+ out.timestamp = now - (uint64_t)(period_usecs * 1000); -+ } else { -+ out.timestamp = now - audio_frames_to_ns(s->info.sample_rate, -+ out.frames); -+ } -+ -+ obs_source_output_audio(s->output, &out); -+ -+queue: -+ pw_stream_queue_buffer(s->stream, b); -+} -+ -+static void on_state_changed_cb(void *data, enum pw_stream_state old, -+ enum pw_stream_state state, const char *error) -+{ -+ UNUSED_PARAMETER(old); -+ -+ struct obs_pw_audio_stream *s = data; -+ -+ blog(LOG_DEBUG, "[pipewire] Stream %p state: \"%s\" (error: %s)", -+ s->stream, pw_stream_state_as_string(state), -+ error ? error : "none"); -+} -+ -+static void on_param_changed_cb(void *data, uint32_t id, -+ const struct spa_pod *param) -+{ -+ if (!param || id != SPA_PARAM_Format) { -+ return; -+ } -+ -+ struct obs_pw_audio_stream *s = data; -+ -+ if (!spa_to_obs_pw_audio_info(&s->info, param)) { -+ blog(LOG_WARNING, -+ "[pipewire] Stream %p failed to parse audio format info", -+ s->stream); -+ } else { -+ blog(LOG_INFO, -+ "[pipewire] %p Got format: rate %u - channels %u - format %u", -+ s->stream, s->info.sample_rate, s->info.speakers, -+ s->info.format); -+ } -+} -+ -+static void on_io_changed_cb(void *data, uint32_t id, void *area, uint32_t size) -+{ -+ UNUSED_PARAMETER(size); -+ -+ struct obs_pw_audio_stream *s = data; -+ -+ if (id == SPA_IO_Position) { -+ s->pos = area; -+ } -+} -+ -+static const struct pw_stream_events stream_events = { -+ PW_VERSION_STREAM_EVENTS, -+ .process = on_process_cb, -+ .state_changed = on_state_changed_cb, -+ .param_changed = on_param_changed_cb, -+ .io_changed = on_io_changed_cb, -+}; -+ -+int obs_pw_audio_stream_connect(struct obs_pw_audio_stream *s, -+ uint32_t target_id, uint32_t audio_channels) -+{ -+ enum spa_audio_channel pos[8]; -+ obs_channels_to_spa_audio_position(pos, audio_channels); -+ -+ uint8_t buffer[2048]; -+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); -+ const struct spa_pod *params[1]; -+ -+ params[0] = spa_pod_builder_add_object( -+ &b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, -+ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), -+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), -+ SPA_FORMAT_AUDIO_channels, SPA_POD_Int(audio_channels), -+ SPA_FORMAT_AUDIO_position, -+ SPA_POD_Array(sizeof(enum spa_audio_channel), SPA_TYPE_Id, -+ audio_channels, pos), -+ SPA_FORMAT_AUDIO_format, -+ SPA_POD_CHOICE_ENUM_Id( -+ 8, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16_LE, -+ SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_F32_LE, -+ SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_S16P, -+ SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32P)); -+ -+ return pw_stream_connect(s->stream, PW_DIRECTION_INPUT, target_id, -+ PW_STREAM_FLAG_AUTOCONNECT | -+ PW_STREAM_FLAG_MAP_BUFFERS | -+ PW_STREAM_FLAG_DONT_RECONNECT, -+ params, 1); -+} -+/* ------------------------------------------------- */ -+ -+/* Common PipeWire components */ -+static void on_core_done_cb(void *data, uint32_t id, int seq) -+{ -+ struct obs_pw_audio_instance *pw = data; -+ -+ if (id == PW_ID_CORE && pw->seq == seq) { -+ pw_thread_loop_signal(pw->thread_loop, false); -+ } -+} -+ -+static void on_core_error_cb(void *data, uint32_t id, int seq, int res, -+ const char *message) -+{ -+ struct obs_pw_audio_instance *pw = data; -+ -+ blog(LOG_ERROR, "[pipewire] Error id:%u seq:%d res:%d :%s", id, seq, -+ res, message); -+ -+ pw_thread_loop_signal(pw->thread_loop, false); -+} -+ -+static const struct pw_core_events core_events = { -+ PW_VERSION_CORE_EVENTS, -+ .done = on_core_done_cb, -+ .error = on_core_error_cb, -+}; -+ -+bool obs_pw_audio_instance_init( -+ struct obs_pw_audio_instance *pw, -+ const struct pw_registry_events *registry_events, -+ void *registry_cb_data, bool stream_capture_sink, -+ bool stream_want_driver, obs_source_t *stream_output) -+{ -+ pw->thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL); -+ pw->context = pw_context_new(pw_thread_loop_get_loop(pw->thread_loop), -+ NULL, 0); -+ -+ pw_thread_loop_lock(pw->thread_loop); -+ -+ if (pw_thread_loop_start(pw->thread_loop) < 0) { -+ blog(LOG_WARNING, -+ "[pipewire] Error starting threaded mainloop"); -+ return false; -+ } -+ -+ pw->core = pw_context_connect(pw->context, NULL, 0); -+ if (!pw->core) { -+ blog(LOG_WARNING, "[pipewire] Error creating PipeWire core"); -+ return false; -+ } -+ -+ pw_core_add_listener(pw->core, &pw->core_listener, &core_events, pw); -+ -+ pw->registry = pw_core_get_registry(pw->core, PW_VERSION_REGISTRY, 0); -+ if (!pw->registry) { -+ return false; -+ } -+ pw_registry_add_listener(pw->registry, &pw->registry_listener, -+ registry_events, registry_cb_data); -+ -+ pw->audio.output = stream_output; -+ pw->audio.stream = pw_stream_new( -+ pw->core, "OBS Studio", -+ pw_properties_new( -+ PW_KEY_NODE_NAME, "OBS Studio", PW_KEY_NODE_DESCRIPTION, -+ "OBS Audio Capture", PW_KEY_MEDIA_TYPE, "Audio", -+ PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, -+ "Production", PW_KEY_NODE_WANT_DRIVER, -+ stream_want_driver ? "true" : "false", -+ PW_KEY_STREAM_CAPTURE_SINK, -+ stream_capture_sink ? "true" : "false", NULL)); -+ -+ if (!pw->audio.stream) { -+ blog(LOG_WARNING, "[pipewire] Failed to create stream"); -+ return false; -+ } -+ blog(LOG_INFO, "[pipewire] Created stream %p", pw->audio.stream); -+ -+ pw_stream_add_listener(pw->audio.stream, &pw->audio.stream_listener, -+ &stream_events, &pw->audio); -+ -+ return true; -+} -+ -+void obs_pw_audio_instance_destroy(struct obs_pw_audio_instance *pw) -+{ -+ if (pw->audio.stream) { -+ spa_hook_remove(&pw->audio.stream_listener); -+ if (pw_stream_get_state(pw->audio.stream, NULL) != -+ PW_STREAM_STATE_UNCONNECTED) { -+ pw_stream_disconnect(pw->audio.stream); -+ } -+ pw_stream_destroy(pw->audio.stream); -+ } -+ -+ if (pw->registry) { -+ spa_hook_remove(&pw->registry_listener); -+ spa_zero(pw->registry_listener); -+ pw_proxy_destroy((struct pw_proxy *)pw->registry); -+ } -+ -+ pw_thread_loop_unlock(pw->thread_loop); -+ pw_thread_loop_stop(pw->thread_loop); -+ -+ if (pw->core) { -+ spa_hook_remove(&pw->core_listener); -+ spa_zero(pw->core_listener); -+ pw_core_disconnect(pw->core); -+ } -+ -+ if (pw->context) { -+ pw_context_destroy(pw->context); -+ } -+ -+ pw_thread_loop_destroy(pw->thread_loop); -+} -+ -+void obs_pw_audio_instance_sync(struct obs_pw_audio_instance *pw) -+{ -+ pw->seq = pw_core_sync(pw->core, PW_ID_CORE, pw->seq); -+} -+/* ------------------------------------------------- */ -+ -+/* PipeWire metadata */ -+static int on_metadata_property_cb(void *data, uint32_t id, const char *key, -+ const char *type, const char *value) -+{ -+ UNUSED_PARAMETER(type); -+ -+ struct obs_pw_audio_default_node_metadata *metadata = data; -+ -+ if (metadata->default_node_callback && id == PW_ID_CORE && key && -+ value && -+ strcmp(key, metadata->wants_sink ? "default.audio.sink" -+ : "default.audio.source") == 0) { -+ char val[128]; -+ if (!json_object_find(value, "name", val, sizeof(val)) || -+ !*val) { -+ return 0; -+ } -+ -+ metadata->default_node_callback(metadata->data, val); -+ } -+ -+ return 0; -+} -+ -+static const struct pw_metadata_events metadata_events = { -+ PW_VERSION_METADATA_EVENTS, -+ .property = on_metadata_property_cb, -+}; -+ -+static void on_metadata_proxy_removed_cb(void *data) -+{ -+ struct obs_pw_audio_default_node_metadata *metadata = data; -+ pw_proxy_destroy(metadata->proxy); -+} -+ -+static void on_metadata_proxy_destroy_cb(void *data) -+{ -+ struct obs_pw_audio_default_node_metadata *metadata = data; -+ -+ spa_hook_remove(&metadata->metadata_listener); -+ spa_hook_remove(&metadata->proxy_listener); -+ spa_zero(metadata->metadata_listener); -+ spa_zero(metadata->proxy_listener); -+ -+ metadata->proxy = NULL; -+} -+ -+static const struct pw_proxy_events metadata_proxy_events = { -+ PW_VERSION_PROXY_EVENTS, -+ .removed = on_metadata_proxy_removed_cb, -+ .destroy = on_metadata_proxy_destroy_cb, -+}; -+ -+bool obs_pw_audio_default_node_metadata_listen( -+ struct obs_pw_audio_default_node_metadata *metadata, -+ struct obs_pw_audio_instance *pw, uint32_t global_id, bool wants_sink, -+ void (*default_node_callback)(void *data, const char *name), void *data) -+{ -+ if (metadata->proxy) { -+ pw_proxy_destroy(metadata->proxy); -+ } -+ -+ struct pw_proxy *metadata_proxy = pw_registry_bind( -+ pw->registry, global_id, PW_TYPE_INTERFACE_Metadata, -+ PW_VERSION_METADATA, 0); -+ if (!metadata_proxy) { -+ return false; -+ } -+ -+ metadata->proxy = metadata_proxy; -+ -+ metadata->wants_sink = wants_sink; -+ -+ metadata->default_node_callback = default_node_callback; -+ metadata->data = data; -+ -+ pw_proxy_add_object_listener(metadata->proxy, -+ &metadata->metadata_listener, -+ &metadata_events, metadata); -+ pw_proxy_add_listener(metadata->proxy, &metadata->proxy_listener, -+ &metadata_proxy_events, metadata); -+ -+ return true; -+} -+/* ------------------------------------------------- */ -+ -+/* Proxied objects */ -+static void on_proxy_bound_cb(void *data, uint32_t global_id) -+{ -+ struct obs_pw_audio_proxied_object *obj = data; -+ if (obj->bound_callback) { -+ obj->bound_callback(obj->data, global_id); -+ } -+} -+ -+static void on_proxy_removed_cb(void *data) -+{ -+ struct obs_pw_audio_proxied_object *obj = data; -+ pw_proxy_destroy(obj->proxy); -+} -+ -+static void on_proxy_destroy_cb(void *data) -+{ -+ struct obs_pw_audio_proxied_object *obj = data; -+ spa_hook_remove(&obj->proxy_listener); -+ -+ spa_list_remove(&obj->link); -+ -+ if (obj->destroy_callback) { -+ obj->destroy_callback(obj->data); -+ } -+ -+ bfree(obj->data); -+} -+ -+static const struct pw_proxy_events proxy_events = { -+ PW_VERSION_PROXY_EVENTS, -+ .bound = on_proxy_bound_cb, -+ .removed = on_proxy_removed_cb, -+ .destroy = on_proxy_destroy_cb, -+}; -+ -+void obs_pw_audio_proxied_object_init( -+ struct obs_pw_audio_proxied_object *obj, struct pw_proxy *proxy, -+ struct spa_list *list, -+ void (*bound_callback)(void *data, uint32_t global_id), -+ void (*destroy_callback)(void *data), void *data) -+{ -+ obj->proxy = proxy; -+ obj->bound_callback = bound_callback; -+ obj->destroy_callback = destroy_callback; -+ obj->data = data; -+ -+ spa_list_append(list, &obj->link); -+ -+ spa_zero(obj->proxy_listener); -+ pw_proxy_add_listener(obj->proxy, &obj->proxy_listener, &proxy_events, -+ obj); -+} -+/* ------------------------------------------------- */ -diff --git a/plugins/linux-pipewire/pipewire-audio.h b/plugins/linux-pipewire/pipewire-audio.h -new file mode 100644 -index 0000000000000..316276df75e54 ---- /dev/null -+++ b/plugins/linux-pipewire/pipewire-audio.h -@@ -0,0 +1,155 @@ -+/* pipewire-audio.h -+ * -+ * Copyright 2022 Dimitris Papaioannou -+ * -+ * This program is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation, either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program. If not, see . -+ * -+ * SPDX-License-Identifier: GPL-2.0-or-later -+ */ -+ -+/* Stuff used by the PipeWire audio capture sources */ -+ -+#pragma once -+ -+#include -+ -+#include -+#include -+#include -+ -+/* PipeWire Stream wrapper */ -+ -+/** -+ * Audio metadata -+ */ -+struct obs_pw_audio_info { -+ uint32_t sample_rate; -+ enum audio_format format; -+ enum speaker_layout speakers; -+}; -+ -+/** -+ * PipeWire stream wrapper that outputs to an OBS source -+ */ -+struct obs_pw_audio_stream { -+ struct pw_stream *stream; -+ struct spa_hook stream_listener; -+ struct obs_pw_audio_info info; -+ struct spa_io_position *pos; -+ -+ obs_source_t *output; -+}; -+ -+/** -+ * Connect a stream with the default params -+ * @return 0 on success, < 0 on error -+ */ -+int obs_pw_audio_stream_connect(struct obs_pw_audio_stream *s, -+ uint32_t target_id, uint32_t channels); -+/* ------------------------------------------------- */ -+ -+/** -+ * Common PipeWire components -+ */ -+struct obs_pw_audio_instance { -+ struct pw_thread_loop *thread_loop; -+ struct pw_context *context; -+ -+ struct pw_core *core; -+ struct spa_hook core_listener; -+ int seq; -+ -+ struct pw_registry *registry; -+ struct spa_hook registry_listener; -+ -+ struct obs_pw_audio_stream audio; -+}; -+ -+/** -+ * Initialize a PipeWire instance -+ * @warning The thread loop is left locked -+ * @return true on success, false on error -+ */ -+bool obs_pw_audio_instance_init( -+ struct obs_pw_audio_instance *pw, -+ const struct pw_registry_events *registry_events, -+ void *registry_cb_data, bool stream_capture_sink, -+ bool stream_want_driver, obs_source_t *stream_output); -+ -+/** -+ * Destroy a PipeWire instance -+ * @warning Call with the thread loop locked -+ */ -+void obs_pw_audio_instance_destroy(struct obs_pw_audio_instance *pw); -+ -+/** -+ * Trigger a PipeWire core sync -+ */ -+void obs_pw_audio_instance_sync(struct obs_pw_audio_instance *pw); -+/* ------------------------------------------------- */ -+ -+/** -+ * PipeWire metadata -+ */ -+struct obs_pw_audio_default_node_metadata { -+ struct pw_proxy *proxy; -+ struct spa_hook proxy_listener; -+ struct spa_hook metadata_listener; -+ -+ bool wants_sink; -+ -+ void (*default_node_callback)(void *data, const char *name); -+ void *data; -+}; -+ -+/** -+ * Add listeners to the metadata -+ * @return true on success, false on error -+ */ -+bool obs_pw_audio_default_node_metadata_listen( -+ struct obs_pw_audio_default_node_metadata *metadata, -+ struct obs_pw_audio_instance *pw, uint32_t global_id, bool wants_sink, -+ void (*default_node_callback)(void *data, const char *name), -+ void *data); -+/* ------------------------------------------------- */ -+ -+/** -+ * Generic proxy handler for PipeWire objects tracked in lists -+ */ -+struct obs_pw_audio_proxied_object { -+ void *data; -+ -+ void (*bound_callback)(void *data, uint32_t global_id); -+ void (*destroy_callback)(void *data); -+ -+ struct pw_proxy *proxy; -+ struct spa_hook proxy_listener; -+ -+ struct spa_list link; -+}; -+ -+/** -+ * Initialize a proxied object -+ */ -+void obs_pw_audio_proxied_object_init( -+ struct obs_pw_audio_proxied_object *obj, struct pw_proxy *proxy, -+ struct spa_list *list, -+ void (*bound_callback)(void *data, uint32_t global_id), -+ void (*destroy_callback)(void *data), void *data); -+/* ------------------------------------------------- */ -+ -+/* Sources */ -+void pipewire_audio_capture_load(void); -+void pipewire_audio_capture_app_load(void); -+/* ------------------------------------------------- */ - diff --git a/patches/8051.patch b/patches/8051.patch deleted file mode 100644 index 4977c00..0000000 --- a/patches/8051.patch +++ /dev/null @@ -1,283 +0,0 @@ -From 01f0cab97d222f5cd4d7468f730370e76f08b42a Mon Sep 17 00:00:00 2001 -From: CodeYan01 <65320293+CodeYan01@users.noreply.github.com> -Date: Thu, 12 Jan 2023 01:01:14 +0800 -Subject: [PATCH] UI: Refactor editable list modification - -Previously would unnecessarily recreating the data array for every -modification (adding/removing/reordering/editing). This clears the -fields in the associated data array that other code/scripts would add, -such as an ID field for each item that could be used to distinguish -each item from their duplicates in the list. - -I initially wanted to remove EditableListChanged, but it no longer saved -the item selection whenever the list is modified, so I kept the function -and just changed it to update the selected state in the data array. ---- - UI/properties-view.cpp | 97 +++++++++++++++++++++++++++++++++++++----- - UI/properties-view.hpp | 6 +++ - 2 files changed, 93 insertions(+), 10 deletions(-) - -diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp -index 7d8838b3603a2..025b104d0db27 100644 ---- a/UI/properties-view.cpp -+++ b/UI/properties-view.cpp -@@ -709,6 +709,11 @@ void OBSPropertiesView::AddEditableList(obs_property_t *prop, - { - const char *name = obs_property_name(prop); - OBSDataArrayAutoRelease array = obs_data_get_array(settings, name); -+ if (array == NULL) { -+ array = obs_data_array_create(); -+ obs_data_set_array(settings, name, array); -+ } -+ - QListWidget *list = new QListWidget(); - size_t count = obs_data_array_count(array); - -@@ -731,8 +736,8 @@ void OBSPropertiesView::AddEditableList(obs_property_t *prop, - WidgetInfo *info = new WidgetInfo(this, prop, list); - - list->setDragDropMode(QAbstractItemView::InternalMove); -- connect(list->model(), &QAbstractItemModel::rowsMoved, -- [info]() { info->EditableListChanged(); }); -+ connect(list->model(), &QAbstractItemModel::rowsMoved, info, -+ &WidgetInfo::EditListReordered); - - QVBoxLayout *sideLayout = new QVBoxLayout(); - NewButton(sideLayout, info, "addIconSmall", &WidgetInfo::EditListAdd); -@@ -1992,24 +1997,52 @@ void WidgetInfo::GroupChanged(const char *setting) - : true); - } - -+void WidgetInfo::EditListReordered(const QModelIndex &sourceParent, -+ int sourceStart, int sourceEnd, -+ const QModelIndex &destinationParent, -+ int destinationRow) -+{ -+ UNUSED_PARAMETER(sourceParent); -+ UNUSED_PARAMETER(destinationParent); -+ -+ const char *setting = obs_property_name(property); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); -+ -+ for (int i = sourceStart; i <= sourceEnd; i++) { -+ OBSDataAutoRelease arrayItem = obs_data_array_item(array, i); -+ obs_data_array_insert(array, destinationRow, arrayItem); -+ // if moved to top, destination row increases -+ obs_data_array_erase(array, (i > destinationRow) ? i + 1 : i); -+ ++destinationRow; -+ } -+ EditableListChanged(); -+} -+ -+void WidgetInfo::EditableListArrayPushBack(obs_data_array_t *array, -+ const char *text) -+{ -+ OBSDataAutoRelease arrayItem = obs_data_create(); -+ obs_data_set_string(arrayItem, "value", text); -+ obs_data_set_bool(arrayItem, "selected", false); -+ obs_data_set_bool(arrayItem, "hidden", false); -+ obs_data_array_push_back(array, arrayItem); -+} -+ - void WidgetInfo::EditableListChanged() - { - const char *setting = obs_property_name(property); - QListWidget *list = reinterpret_cast(widget); -- OBSDataArrayAutoRelease array = obs_data_array_create(); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); - - for (int i = 0; i < list->count(); i++) { - QListWidgetItem *item = list->item(i); -- OBSDataAutoRelease arrayItem = obs_data_create(); -- obs_data_set_string(arrayItem, "value", -- QT_TO_UTF8(item->text())); -+ OBSDataAutoRelease arrayItem = obs_data_array_item(array, i); - obs_data_set_bool(arrayItem, "selected", item->isSelected()); - obs_data_set_bool(arrayItem, "hidden", item->isHidden()); -- obs_data_array_push_back(array, arrayItem); - } - -- obs_data_set_array(view->settings, setting, array); -- - ControlChanged(); - } - -@@ -2263,6 +2296,9 @@ void WidgetInfo::EditListAddText() - { - QListWidget *list = reinterpret_cast(widget); - const char *desc = obs_property_description(property); -+ const char *setting = obs_property_name(property); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); - - EditableItemDialog dialog(widget->window(), QString(), false); - auto title = QTStr("Basic.PropertiesWindow.AddEditableListEntry") -@@ -2276,6 +2312,7 @@ void WidgetInfo::EditListAddText() - return; - - list->addItem(text); -+ EditableListArrayPushBack(array, QT_TO_UTF8(text)); - EditableListChanged(); - } - -@@ -2286,6 +2323,9 @@ void WidgetInfo::EditListAddFiles() - const char *filter = obs_property_editable_list_filter(property); - const char *default_path = - obs_property_editable_list_default_path(property); -+ const char *setting = obs_property_name(property); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); - - QString title = QTStr("Basic.PropertiesWindow.AddEditableListFiles") - .arg(QT_UTF8(desc)); -@@ -2301,6 +2341,9 @@ void WidgetInfo::EditListAddFiles() - return; - - list->addItems(files); -+ for (QString &file : files) { -+ EditableListArrayPushBack(array, QT_TO_UTF8(file)); -+ } - EditableListChanged(); - } - -@@ -2310,6 +2353,9 @@ void WidgetInfo::EditListAddDir() - const char *desc = obs_property_description(property); - const char *default_path = - obs_property_editable_list_default_path(property); -+ const char *setting = obs_property_name(property); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); - - QString title = QTStr("Basic.PropertiesWindow.AddEditableListDir") - .arg(QT_UTF8(desc)); -@@ -2325,6 +2371,7 @@ void WidgetInfo::EditListAddDir() - return; - - list->addItem(dir); -+ EditableListArrayPushBack(array, QT_TO_UTF8(dir)); - EditableListChanged(); - } - -@@ -2332,9 +2379,15 @@ void WidgetInfo::EditListRemove() - { - QListWidget *list = reinterpret_cast(widget); - QList items = list->selectedItems(); -+ const char *setting = obs_property_name(property); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); - -- for (QListWidgetItem *item : items) -+ for (qsizetype i = items.size() - 1; i >= 0; i--) { -+ QListWidgetItem *item = items.at(i); -+ obs_data_array_erase(array, list->row(item)); - delete item; -+ } - EditableListChanged(); - } - -@@ -2343,6 +2396,7 @@ void WidgetInfo::EditListEdit() - QListWidget *list = reinterpret_cast(widget); - enum obs_editable_list_type type = - obs_property_editable_list_type(property); -+ const char *setting = obs_property_name(property); - const char *desc = obs_property_description(property); - const char *filter = obs_property_editable_list_filter(property); - QList selectedItems = list->selectedItems(); -@@ -2351,6 +2405,10 @@ void WidgetInfo::EditListEdit() - return; - - QListWidgetItem *item = selectedItems[0]; -+ int row = list->row(item); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); -+ OBSDataAutoRelease arrayItem = obs_data_array_item(array, row); - - if (type == OBS_EDITABLE_LIST_TYPE_FILES) { - QDir pathDir(item->text()); -@@ -2367,6 +2425,7 @@ void WidgetInfo::EditListEdit() - return; - - item->setText(path); -+ obs_data_set_string(arrayItem, "value", QT_TO_UTF8(path)); - EditableListChanged(); - return; - } -@@ -2385,6 +2444,7 @@ void WidgetInfo::EditListEdit() - return; - - item->setText(text); -+ obs_data_set_string(arrayItem, "value", QT_TO_UTF8(text)); - EditableListChanged(); - } - -@@ -2392,6 +2452,9 @@ void WidgetInfo::EditListUp() - { - QListWidget *list = reinterpret_cast(widget); - int lastItemRow = -1; -+ const char *setting = obs_property_name(property); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); - - for (int i = 0; i < list->count(); i++) { - QListWidgetItem *item = list->item(i); -@@ -2405,6 +2468,11 @@ void WidgetInfo::EditListUp() - list->takeItem(row); - list->insertItem(lastItemRow, item); - item->setSelected(true); -+ -+ OBSDataAutoRelease arrayItem = -+ obs_data_array_item(array, row); -+ obs_data_array_insert(array, lastItemRow, arrayItem); -+ obs_data_array_erase(array, row + 1); - } else { - lastItemRow = row; - } -@@ -2417,6 +2485,9 @@ void WidgetInfo::EditListDown() - { - QListWidget *list = reinterpret_cast(widget); - int lastItemRow = list->count(); -+ const char *setting = obs_property_name(property); -+ OBSDataArrayAutoRelease array = -+ obs_data_get_array(view->settings, setting); - - for (int i = list->count() - 1; i >= 0; i--) { - QListWidgetItem *item = list->item(i); -@@ -2430,6 +2501,12 @@ void WidgetInfo::EditListDown() - list->takeItem(row); - list->insertItem(lastItemRow, item); - item->setSelected(true); -+ -+ OBSDataAutoRelease arrayItem = -+ obs_data_array_item(array, row); -+ obs_data_array_insert(array, lastItemRow + 1, -+ arrayItem); -+ obs_data_array_erase(array, row); - } else { - lastItemRow = row; - } -diff --git a/UI/properties-view.hpp b/UI/properties-view.hpp -index e520dcdde2c90..89546b1317fba 100644 ---- a/UI/properties-view.hpp -+++ b/UI/properties-view.hpp -@@ -47,6 +47,8 @@ class WidgetInfo : public QObject { - void ButtonClicked(); - - void TogglePasswordText(bool checked); -+ void EditableListArrayPushBack(obs_data_array_t *array, -+ const char *text); - - public: - inline WidgetInfo(OBSPropertiesView *view_, obs_property_t *prop, -@@ -79,6 +81,10 @@ public slots: - void EditListEdit(); - void EditListUp(); - void EditListDown(); -+ void EditListReordered(const QModelIndex &sourceParent, int sourceStart, -+ int sourceEnd, -+ const QModelIndex &destinationParent, -+ int destinationRow); - }; - - /* ------------------------------------------------------------------------- */ diff --git a/patches/8794.patch b/patches/8794.patch deleted file mode 100644 index aeb89da..0000000 --- a/patches/8794.patch +++ /dev/null @@ -1,275 +0,0 @@ -From 38031cd5d1e9486f06c782fbc0162e9f95e3d116 Mon Sep 17 00:00:00 2001 -From: Bleuzen <12885163+Bleuzen@users.noreply.github.com> -Date: Sat, 19 Aug 2023 02:50:09 +0200 -Subject: [PATCH 1/2] obs-ffmpeg: Add NVENC AV1 FFmpeg encoder, part 1 - ---- - plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c | 109 ++++++++++++++++++++++++-- - plugins/obs-ffmpeg/obs-ffmpeg.c | 9 ++- - 2 files changed, 109 insertions(+), 9 deletions(-) - -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -index cfd4ea91916a2..80db3ba85ef39 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -@@ -35,6 +35,7 @@ struct nvenc_encoder { - #ifdef ENABLE_HEVC - bool hevc; - #endif -+ bool av1; - int gpu; - DARRAY(uint8_t) header; - DARRAY(uint8_t) sei; -@@ -61,6 +62,13 @@ static const char *hevc_nvenc_getname(void *unused) - } - #endif - -+#define ENCODER_NAME_AV1 "GPU: NVIDIA NVENC AV1 (FFmpeg)" -+static const char *av1_nvenc_getname(void *unused) -+{ -+ UNUSED_PARAMETER(unused); -+ return ENCODER_NAME_AV1; -+} -+ - static inline bool valid_format(enum video_format format) - { - switch (format) { -@@ -281,7 +289,9 @@ static void on_first_packet(void *data, AVPacket *pkt, struct darray *da) - &enc->sei.array, &enc->sei.num); - } else - #endif -- { -+ if (enc->av1) { -+ // TODO: add obs_extract_av1_headers -+ } else { - obs_extract_avc_headers(pkt->data, pkt->size, - (uint8_t **)&da->array, &da->num, - &enc->header.array, &enc->header.num, -@@ -300,10 +310,11 @@ static void on_first_packet(void *data, AVPacket *pkt, struct darray *da) - } - - static void *nvenc_create_internal(obs_data_t *settings, obs_encoder_t *encoder, -- bool psycho_aq, bool hevc) -+ bool psycho_aq, bool hevc, bool av1) - { - struct nvenc_encoder *enc = bzalloc(sizeof(*enc)); - -+ enc->av1 = av1; - #ifdef ENABLE_HEVC - enc->hevc = hevc; - if (hevc) { -@@ -316,7 +327,12 @@ static void *nvenc_create_internal(obs_data_t *settings, obs_encoder_t *encoder, - #else - UNUSED_PARAMETER(hevc); - #endif -- { -+ if (av1) { -+ if (!ffmpeg_video_encoder_init( -+ &enc->ffve, enc, encoder, "av1_nvenc", NULL, -+ ENCODER_NAME_AV1, on_init_error, on_first_packet)) -+ goto fail; -+ } else { - if (!ffmpeg_video_encoder_init(&enc->ffve, enc, encoder, - "h264_nvenc", "nvenc_h264", - ENCODER_NAME_H264, on_init_error, -@@ -368,12 +384,14 @@ static void *h264_nvenc_create(obs_data_t *settings, obs_encoder_t *encoder) - } - - bool psycho_aq = obs_data_get_bool(settings, "psycho_aq"); -- void *enc = nvenc_create_internal(settings, encoder, psycho_aq, false); -+ void *enc = nvenc_create_internal(settings, encoder, psycho_aq, false, -+ false); - if ((enc == NULL) && psycho_aq) { - blog(LOG_WARNING, - "[NVENC encoder] nvenc_create_internal failed, " - "trying again without Psycho Visual Tuning"); -- enc = nvenc_create_internal(settings, encoder, false, false); -+ enc = nvenc_create_internal(settings, encoder, false, false, -+ false); - } - - return enc; -@@ -415,18 +433,68 @@ static void *hevc_nvenc_create(obs_data_t *settings, obs_encoder_t *encoder) - } - - bool psycho_aq = obs_data_get_bool(settings, "psycho_aq"); -- void *enc = nvenc_create_internal(settings, encoder, psycho_aq, true); -+ void *enc = nvenc_create_internal(settings, encoder, psycho_aq, true, -+ false); - if ((enc == NULL) && psycho_aq) { - blog(LOG_WARNING, - "[NVENC encoder] nvenc_create_internal failed, " - "trying again without Psycho Visual Tuning"); -- enc = nvenc_create_internal(settings, encoder, false, true); -+ enc = nvenc_create_internal(settings, encoder, false, true, -+ false); - } - - return enc; - } - #endif - -+static void *av1_nvenc_create(obs_data_t *settings, obs_encoder_t *encoder) -+{ -+ video_t *video = obs_encoder_video(encoder); -+ const struct video_output_info *voi = video_output_get_info(video); -+ switch (voi->format) { -+ case VIDEO_FORMAT_I010: { -+ const char *const text = -+ obs_module_text("NVENC.I010Unsupported"); -+ obs_encoder_set_last_error(encoder, text); -+ blog(LOG_ERROR, "[NVENC encoder] %s", text); -+ return NULL; -+ } -+ case VIDEO_FORMAT_P010: -+ break; -+ case VIDEO_FORMAT_P216: -+ case VIDEO_FORMAT_P416: { -+ const char *const text = -+ obs_module_text("NVENC.16bitUnsupported"); -+ obs_encoder_set_last_error(encoder, text); -+ blog(LOG_ERROR, "[NVENC encoder] %s", text); -+ return NULL; -+ } -+ default: -+ if (voi->colorspace == VIDEO_CS_2100_PQ || -+ voi->colorspace == VIDEO_CS_2100_HLG) { -+ const char *const text = -+ obs_module_text("NVENC.8bitUnsupportedHdr"); -+ obs_encoder_set_last_error(encoder, text); -+ blog(LOG_ERROR, "[NVENC encoder] %s", text); -+ return NULL; -+ } -+ break; -+ } -+ -+ bool psycho_aq = obs_data_get_bool(settings, "psycho_aq"); -+ void *enc = nvenc_create_internal(settings, encoder, psycho_aq, false, -+ true); -+ if ((enc == NULL) && psycho_aq) { -+ blog(LOG_WARNING, -+ "[NVENC encoder] nvenc_create_internal failed, " -+ "trying again without Psycho Visual Tuning"); -+ enc = nvenc_create_internal(settings, encoder, false, false, -+ true); -+ } -+ -+ return enc; -+} -+ - static bool nvenc_encode(void *data, struct encoder_frame *frame, - struct encoder_packet *packet, bool *received_packet) - { -@@ -669,6 +737,12 @@ obs_properties_t *hevc_nvenc_properties_ffmpeg(void *unused) - } - #endif - -+obs_properties_t *av1_nvenc_properties_ffmpeg(void *unused) -+{ -+ UNUSED_PARAMETER(unused); -+ return nvenc_properties_internal(CODEC_AV1, true); -+} -+ - static bool nvenc_extra_data(void *data, uint8_t **extra_data, size_t *size) - { - struct nvenc_encoder *enc = data; -@@ -730,3 +804,24 @@ struct obs_encoder_info hevc_nvenc_encoder_info = { - #endif - }; - #endif -+ -+struct obs_encoder_info av1_nvenc_encoder_info = { -+ .id = "ffmpeg_av1_nvenc", -+ .type = OBS_ENCODER_VIDEO, -+ .codec = "av1", -+ .get_name = av1_nvenc_getname, -+ .create = av1_nvenc_create, -+ .destroy = nvenc_destroy, -+ .encode = nvenc_encode, -+ .update = nvenc_reconfigure, -+ .get_defaults = av1_nvenc_defaults, -+ .get_properties = av1_nvenc_properties_ffmpeg, -+ .get_extra_data = nvenc_extra_data, -+ .get_sei_data = nvenc_sei_data, -+ .get_video_info = nvenc_video_info, -+#ifdef _WIN32 -+ .caps = OBS_ENCODER_CAP_DYN_BITRATE | OBS_ENCODER_CAP_INTERNAL, -+#else -+ .caps = OBS_ENCODER_CAP_DYN_BITRATE, -+#endif -+}; -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c -index 7eb9a876d82ff..07973ffc73b40 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg.c -@@ -42,6 +42,7 @@ extern struct obs_encoder_info h264_nvenc_encoder_info; - #ifdef ENABLE_HEVC - extern struct obs_encoder_info hevc_nvenc_encoder_info; - #endif -+extern struct obs_encoder_info av1_nvenc_encoder_info; - extern struct obs_encoder_info svt_av1_encoder_info; - extern struct obs_encoder_info aom_av1_encoder_info; - -@@ -272,7 +273,7 @@ static void do_nvenc_check_for_ubuntu_20_04(void) - static bool nvenc_codec_exists(const char *name, const char *fallback) - { - const AVCodec *nvenc = avcodec_find_encoder_by_name(name); -- if (!nvenc) -+ if (!nvenc && fallback) - nvenc = avcodec_find_encoder_by_name(fallback); - - return nvenc != NULL; -@@ -302,8 +303,10 @@ static bool nvenc_supported(bool *out_h264, bool *out_hevc, bool *out_av1) - if (success) { - void *const lib = os_dlopen("libnvidia-encode.so.1"); - success = lib != NULL; -- if (success) -+ if (success) { - os_dlclose(lib); -+ av1 = nvenc_codec_exists("av1_nvenc", NULL); -+ } - } - #else - void *const lib = os_dlopen("libnvidia-encode.so.1"); -@@ -427,6 +430,8 @@ bool obs_module_load(void) - if (hevc) - obs_register_encoder(&hevc_nvenc_encoder_info); - #endif -+ if (av1) -+ obs_register_encoder(&av1_nvenc_encoder_info); - } - - #ifdef _WIN32 - -From c973af782104d8ac471e08da6b243ceac102f6f2 Mon Sep 17 00:00:00 2001 -From: Bleuzen <12885163+Bleuzen@users.noreply.github.com> -Date: Mon, 13 Nov 2023 00:02:14 +0100 -Subject: [PATCH 2/2] obs-ffmpeg: Add NVENC AV1 FFmpeg encoder, part 2 - ---- - plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -index 80db3ba85ef39..72455e23b744f 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -@@ -19,6 +19,7 @@ - #ifdef ENABLE_HEVC - #include - #endif -+#include - - #include "obs-ffmpeg-video-encoders.h" - -@@ -290,7 +291,9 @@ static void on_first_packet(void *data, AVPacket *pkt, struct darray *da) - } else - #endif - if (enc->av1) { -- // TODO: add obs_extract_av1_headers -+ obs_extract_av1_headers(pkt->data, pkt->size, -+ (uint8_t **)&da->array, &da->num, -+ &enc->header.array, &enc->header.num); - } else { - obs_extract_avc_headers(pkt->data, pkt->size, - (uint8_t **)&da->array, &da->num, diff --git a/patches/8832.patch b/patches/8832.patch deleted file mode 100644 index f58224f..0000000 --- a/patches/8832.patch +++ /dev/null @@ -1,784 +0,0 @@ -From 27565d8ffc8fa7407381595f56c2f1aebb03d3ec Mon Sep 17 00:00:00 2001 -From: David Rosca -Date: Tue, 2 May 2023 13:39:13 +0200 -Subject: [PATCH 1/2] obs-ffmpeg: Add AV1 support for VA-API - ---- - plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c | 284 ++++++++++++++++---------- - plugins/obs-ffmpeg/obs-ffmpeg.c | 19 ++ - plugins/obs-ffmpeg/vaapi-utils.c | 55 +++++ - plugins/obs-ffmpeg/vaapi-utils.h | 4 + - 4 files changed, 254 insertions(+), 108 deletions(-) - -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -index ec2a8651a850b..88c906e5dc98d 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -@@ -24,6 +24,7 @@ - #include - #include - #include -+#include - #ifdef ENABLE_HEVC - #include - #endif -@@ -51,8 +52,15 @@ - #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) - #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__) - -+enum codec_type { -+ CODEC_H264, -+ CODEC_HEVC, -+ CODEC_AV1, -+}; -+ - struct vaapi_encoder { - obs_encoder_t *encoder; -+ enum codec_type codec; - - AVBufferRef *vadevice_ref; - AVBufferRef *vaframes_ref; -@@ -83,59 +91,48 @@ static const char *h264_vaapi_getname(void *unused) - return "FFmpeg VAAPI H.264"; - } - --#ifdef ENABLE_HEVC --static const char *hevc_vaapi_getname(void *unused) -+static const char *av1_vaapi_getname(void *unused) - { - UNUSED_PARAMETER(unused); -- return "FFmpeg VAAPI HEVC"; --} --#endif -- --static inline bool h264_valid_format(enum video_format format) --{ -- return format == VIDEO_FORMAT_NV12; -+ return "FFmpeg VAAPI AV1"; - } - - #ifdef ENABLE_HEVC --static inline bool hevc_valid_format(enum video_format format) -+static const char *hevc_vaapi_getname(void *unused) - { -- return (format == VIDEO_FORMAT_NV12) || (format == VIDEO_FORMAT_P010); -+ UNUSED_PARAMETER(unused); -+ return "FFmpeg VAAPI HEVC"; - } - #endif - --static void h264_vaapi_video_info(void *data, struct video_scale_info *info) -+static inline bool vaapi_valid_format(struct vaapi_encoder *enc, -+ enum video_format format) - { -- struct vaapi_encoder *enc = data; -- enum video_format pref_format; -- -- pref_format = obs_encoder_get_preferred_video_format(enc->encoder); -- -- if (!h264_valid_format(pref_format)) { -- pref_format = h264_valid_format(info->format) -- ? info->format -- : VIDEO_FORMAT_NV12; -+ if (enc->codec == CODEC_H264) { -+ return format == VIDEO_FORMAT_NV12; -+ } else if (enc->codec == CODEC_HEVC || enc->codec == CODEC_AV1) { -+ return (format == VIDEO_FORMAT_NV12) || -+ (format == VIDEO_FORMAT_P010); -+ } else { -+ return false; - } -- -- info->format = pref_format; - } - --#ifdef ENABLE_HEVC --static void hevc_vaapi_video_info(void *data, struct video_scale_info *info) -+static void vaapi_video_info(void *data, struct video_scale_info *info) - { - struct vaapi_encoder *enc = data; - enum video_format pref_format; - - pref_format = obs_encoder_get_preferred_video_format(enc->encoder); - -- if (!hevc_valid_format(pref_format)) { -- pref_format = hevc_valid_format(info->format) -+ if (!vaapi_valid_format(enc, pref_format)) { -+ pref_format = vaapi_valid_format(enc, info->format) - ? info->format - : VIDEO_FORMAT_NV12; - } - - info->format = pref_format; - } --#endif - - static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path) - { -@@ -232,7 +229,7 @@ static const rc_mode_t *get_rc_mode(const char *name) - return rc_mode ? rc_mode : RC_MODES; - } - --static bool vaapi_update(void *data, obs_data_t *settings, bool hevc) -+static bool vaapi_update(void *data, obs_data_t *settings) - { - struct vaapi_encoder *enc = data; - -@@ -247,7 +244,7 @@ static bool vaapi_update(void *data, obs_data_t *settings, bool hevc) - int bf = (int)obs_data_get_int(settings, "bf"); - int qp = rc_mode->qp ? (int)obs_data_get_int(settings, "qp") : 0; - -- av_opt_set_int(enc->context->priv_data, "qp", qp, 0); -+ enc->context->global_quality = enc->codec == CODEC_AV1 ? qp * 5 : qp; - - int level = (int)obs_data_get_int(settings, "level"); - int bitrate = rc_mode->bitrate -@@ -277,21 +274,15 @@ static bool vaapi_update(void *data, obs_data_t *settings, bool hevc) - info.range = voi->range; - - #ifdef ENABLE_HEVC -- if (hevc) { -+ if (enc->codec == CODEC_HEVC) { - if ((profile == FF_PROFILE_HEVC_MAIN) && - (info.format == VIDEO_FORMAT_P010)) { - warn("Forcing Main10 for P010"); - profile = FF_PROFILE_HEVC_MAIN_10; - } -- -- hevc_vaapi_video_info(enc, &info); -- } else --#else -- UNUSED_PARAMETER(hevc); --#endif -- { -- h264_vaapi_video_info(enc, &info); - } -+#endif -+ vaapi_video_info(enc, &info); - - enc->context->profile = profile; - enc->context->max_b_frames = bf; -@@ -414,8 +405,20 @@ static void vaapi_destroy(void *data) - bfree(enc); - } - -+static inline const char *vaapi_encoder_name(enum codec_type codec) -+{ -+ if (codec == CODEC_H264) { -+ return "h264_vaapi"; -+ } else if (codec == CODEC_HEVC) { -+ return "hevc_vaapi"; -+ } else if (codec == CODEC_AV1) { -+ return "av1_vaapi"; -+ } -+ return NULL; -+} -+ - static void *vaapi_create_internal(obs_data_t *settings, obs_encoder_t *encoder, -- bool hevc) -+ enum codec_type codec) - { - struct vaapi_encoder *enc; - #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100) -@@ -425,8 +428,8 @@ static void *vaapi_create_internal(obs_data_t *settings, obs_encoder_t *encoder, - enc = bzalloc(sizeof(*enc)); - enc->encoder = encoder; - -- const char *const name = hevc ? "hevc_vaapi" : "h264_vaapi"; -- enc->vaapi = avcodec_find_encoder_by_name(name); -+ enc->codec = codec; -+ enc->vaapi = avcodec_find_encoder_by_name(vaapi_encoder_name(codec)); - - enc->first_packet = true; - -@@ -444,7 +447,7 @@ static void *vaapi_create_internal(obs_data_t *settings, obs_encoder_t *encoder, - goto fail; - } - -- if (!vaapi_update(enc, settings, hevc)) -+ if (!vaapi_update(enc, settings)) - goto fail; - - return enc; -@@ -456,13 +459,18 @@ static void *vaapi_create_internal(obs_data_t *settings, obs_encoder_t *encoder, - - static void *h264_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder) - { -- return vaapi_create_internal(settings, encoder, false); -+ return vaapi_create_internal(settings, encoder, CODEC_H264); -+} -+ -+static void *av1_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder) -+{ -+ return vaapi_create_internal(settings, encoder, CODEC_AV1); - } - - #ifdef ENABLE_HEVC - static void *hevc_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder) - { -- return vaapi_create_internal(settings, encoder, true); -+ return vaapi_create_internal(settings, encoder, CODEC_HEVC); - } - #endif - -@@ -492,9 +500,8 @@ static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame, - } - } - --static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, -- struct encoder_packet *packet, -- bool *received_packet, bool hevc) -+static bool vaapi_encode(void *data, struct encoder_frame *frame, -+ struct encoder_packet *packet, bool *received_packet) - { - struct vaapi_encoder *enc = data; - AVFrame *hwframe = NULL; -@@ -556,22 +563,26 @@ static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, - - enc->first_packet = false; - #ifdef ENABLE_HEVC -- if (hevc) { -+ if (enc->codec == CODEC_HEVC) { - obs_extract_hevc_headers( - enc->packet->data, enc->packet->size, - &new_packet, &size, &enc->header, - &enc->header_size, &enc->sei, - &enc->sei_size); - } else --#else -- UNUSED_PARAMETER(hevc); - #endif -- { -+ if (enc->codec == CODEC_H264) { - obs_extract_avc_headers( - enc->packet->data, enc->packet->size, - &new_packet, &size, &enc->header, - &enc->header_size, &enc->sei, - &enc->sei_size); -+ } else if (enc->codec == CODEC_AV1) { -+ obs_extract_av1_headers(enc->packet->data, -+ enc->packet->size, -+ &new_packet, &size, -+ &enc->header, -+ &enc->header_size); - } - - da_copy_array(enc->buffer, new_packet, size); -@@ -587,14 +598,17 @@ static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, - packet->size = enc->buffer.num; - packet->type = OBS_ENCODER_VIDEO; - #ifdef ENABLE_HEVC -- if (hevc) { -+ if (enc->codec == CODEC_HEVC) { - packet->keyframe = - obs_hevc_keyframe(packet->data, packet->size); - } else - #endif -- { -+ if (enc->codec == CODEC_H264) { - packet->keyframe = - obs_avc_keyframe(packet->data, packet->size); -+ } else if (enc->codec == CODEC_AV1) { -+ packet->keyframe = -+ obs_av1_keyframe(packet->data, packet->size); - } - *received_packet = true; - } else { -@@ -610,54 +624,61 @@ static bool vaapi_encode_internal(void *data, struct encoder_frame *frame, - return false; - } - --static bool h264_vaapi_encode(void *data, struct encoder_frame *frame, -- struct encoder_packet *packet, -- bool *received_packet) -+static void set_visible(obs_properties_t *ppts, const char *name, bool visible) - { -- return vaapi_encode_internal(data, frame, packet, received_packet, -- false); -+ obs_property_t *p = obs_properties_get(ppts, name); -+ obs_property_set_visible(p, visible); - } - --#ifdef ENABLE_HEVC --static bool hevc_vaapi_encode(void *data, struct encoder_frame *frame, -- struct encoder_packet *packet, -- bool *received_packet) -+static inline VAProfile vaapi_profile(enum codec_type codec) - { -- return vaapi_encode_internal(data, frame, packet, received_packet, -- true); --} -+ if (codec == CODEC_H264) { -+ return VAProfileH264ConstrainedBaseline; -+ } else if (codec == CODEC_AV1) { -+ return VAProfileAV1Profile0; -+#if ENABLE_HEVC -+ } else if (codec == CODEC_HEVC) { -+ return VAProfileHEVCMain; - #endif -- --static void set_visible(obs_properties_t *ppts, const char *name, bool visible) --{ -- obs_property_t *p = obs_properties_get(ppts, name); -- obs_property_set_visible(p, visible); -+ } -+ return VAProfileNone; - } - --static void vaapi_defaults_internal(obs_data_t *settings, bool hevc) -+static inline const char *vaapi_default_device(enum codec_type codec) - { --#ifdef ENABLE_HEVC -- const char *device = hevc ? vaapi_get_hevc_default_device() -- : vaapi_get_h264_default_device(); --#else -- const char *const device = vaapi_get_h264_default_device(); -+ if (codec == CODEC_H264) { -+ return vaapi_get_h264_default_device(); -+ } else if (codec == CODEC_AV1) { -+ return vaapi_get_av1_default_device(); -+#if ENABLE_HEVC -+ } else if (codec == CODEC_HEVC) { -+ return vaapi_get_hevc_default_device(); - #endif -+ } -+ return NULL; -+} - -+static void vaapi_defaults_internal(obs_data_t *settings, enum codec_type codec) -+{ -+ const char *const device = vaapi_default_device(codec); - obs_data_set_default_string(settings, "vaapi_device", device); - #ifdef ENABLE_HEVC -- if (hevc) { -+ if (codec == CODEC_HEVC) { - obs_data_set_default_int(settings, "profile", - FF_PROFILE_HEVC_MAIN); -+ obs_data_set_default_int(settings, "level", 120); - - } else --#else -- UNUSED_PARAMETER(hevc); - #endif -- { -+ if (codec == CODEC_H264) { - obs_data_set_default_int(settings, "profile", - FF_PROFILE_H264_CONSTRAINED_BASELINE); -+ obs_data_set_default_int(settings, "level", 40); -+ } else if (codec == CODEC_AV1) { -+ obs_data_set_default_int(settings, "profile", -+ FF_PROFILE_AV1_MAIN); -+ obs_data_set_default_int(settings, "level", 8); - } -- obs_data_set_default_int(settings, "level", 40); - obs_data_set_default_int(settings, "bitrate", 2500); - obs_data_set_default_int(settings, "keyint_sec", 0); - obs_data_set_default_int(settings, "bf", 0); -@@ -670,12 +691,7 @@ static void vaapi_defaults_internal(obs_data_t *settings, bool hevc) - if (!va_dpy) - return; - --#ifdef ENABLE_HEVC -- const VAProfile profile = hevc ? VAProfileHEVCMain -- : VAProfileH264ConstrainedBaseline; --#else -- const VAProfile profile = VAProfileH264ConstrainedBaseline; --#endif -+ const VAProfile profile = vaapi_profile(codec); - if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_CBR, device)) - obs_data_set_default_string(settings, "rate_control", "CBR"); - else if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_VBR, device)) -@@ -688,12 +704,17 @@ static void vaapi_defaults_internal(obs_data_t *settings, bool hevc) - - static void h264_vaapi_defaults(obs_data_t *settings) - { -- vaapi_defaults_internal(settings, false); -+ vaapi_defaults_internal(settings, CODEC_H264); -+} -+ -+static void av1_vaapi_defaults(obs_data_t *settings) -+{ -+ vaapi_defaults_internal(settings, CODEC_AV1); - } - - static void hevc_vaapi_defaults(obs_data_t *settings) - { -- vaapi_defaults_internal(settings, true); -+ vaapi_defaults_internal(settings, CODEC_HEVC); - } - - static bool vaapi_device_modified(obs_properties_t *ppts, obs_property_t *p, -@@ -729,6 +750,11 @@ static bool vaapi_device_modified(obs_properties_t *ppts, obs_property_t *p, - goto fail; - profile = VAProfileH264High; - break; -+ case FF_PROFILE_AV1_MAIN: -+ if (!vaapi_display_av1_supported(va_dpy, device)) -+ goto fail; -+ profile = VAProfileAV1Profile0; -+ break; - #ifdef ENABLE_HEVC - case FF_PROFILE_HEVC_MAIN: - if (!vaapi_display_hevc_supported(va_dpy, device)) -@@ -802,7 +828,7 @@ static bool get_device_name_from_pci(struct pci_access *pacc, char *pci_slot, - return false; - } - --static obs_properties_t *vaapi_properties_internal(bool hevc) -+static obs_properties_t *vaapi_properties_internal(enum codec_type codec) - { - obs_properties_t *props = obs_properties_create(); - obs_property_t *list; -@@ -893,16 +919,18 @@ static obs_properties_t *vaapi_properties_internal(bool hevc) - obs_module_text("Profile"), - OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_INT); -- if (hevc) { -+ if (codec == CODEC_HEVC) { - obs_property_list_add_int(list, "Main", FF_PROFILE_HEVC_MAIN); - obs_property_list_add_int(list, "Main10", - FF_PROFILE_HEVC_MAIN_10); -- } else { -+ } else if (codec == CODEC_H264) { - obs_property_list_add_int(list, - "Constrained Baseline (default)", - FF_PROFILE_H264_CONSTRAINED_BASELINE); - obs_property_list_add_int(list, "Main", FF_PROFILE_H264_MAIN); - obs_property_list_add_int(list, "High", FF_PROFILE_H264_HIGH); -+ } else if (codec == CODEC_AV1) { -+ obs_property_list_add_int(list, "Main", FF_PROFILE_AV1_MAIN); - } - - obs_property_set_modified_callback(list, vaapi_device_modified); -@@ -911,15 +939,34 @@ static obs_properties_t *vaapi_properties_internal(bool hevc) - OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_INT); - obs_property_list_add_int(list, "Auto", FF_LEVEL_UNKNOWN); -- obs_property_list_add_int(list, "3.0", 30); -- obs_property_list_add_int(list, "3.1", 31); -- obs_property_list_add_int(list, "4.0 (default) (Compatibility mode)", -- 40); -- obs_property_list_add_int(list, "4.1", 41); -- obs_property_list_add_int(list, "4.2", 42); -- obs_property_list_add_int(list, "5.0", 50); -- obs_property_list_add_int(list, "5.1", 51); -- obs_property_list_add_int(list, "5.2", 52); -+ if (codec == CODEC_H264) { -+ obs_property_list_add_int(list, "3.0", 30); -+ obs_property_list_add_int(list, "3.1", 31); -+ obs_property_list_add_int( -+ list, "4.0 (default) (Compatibility mode)", 40); -+ obs_property_list_add_int(list, "4.1", 41); -+ obs_property_list_add_int(list, "4.2", 42); -+ obs_property_list_add_int(list, "5.0", 50); -+ obs_property_list_add_int(list, "5.1", 51); -+ obs_property_list_add_int(list, "5.2", 52); -+ } else if (codec == CODEC_HEVC) { -+ obs_property_list_add_int(list, "3.0", 90); -+ obs_property_list_add_int(list, "3.1", 93); -+ obs_property_list_add_int(list, "4.0 (default)", 120); -+ obs_property_list_add_int(list, "4.1", 123); -+ obs_property_list_add_int(list, "5.0", 150); -+ obs_property_list_add_int(list, "5.1", 153); -+ obs_property_list_add_int(list, "5.2", 156); -+ } else if (codec == CODEC_AV1) { -+ obs_property_list_add_int(list, "3.0", 4); -+ obs_property_list_add_int(list, "3.1", 5); -+ obs_property_list_add_int(list, "4.0 (default)", 8); -+ obs_property_list_add_int(list, "4.1", 9); -+ obs_property_list_add_int(list, "5.0", 12); -+ obs_property_list_add_int(list, "5.1", 13); -+ obs_property_list_add_int(list, "5.2", 14); -+ obs_property_list_add_int(list, "5.3", 15); -+ } - - list = obs_properties_add_list(props, "rate_control", - obs_module_text("RateControl"), -@@ -957,14 +1004,20 @@ static obs_properties_t *vaapi_properties_internal(bool hevc) - static obs_properties_t *h264_vaapi_properties(void *unused) - { - UNUSED_PARAMETER(unused); -- return vaapi_properties_internal(false); -+ return vaapi_properties_internal(CODEC_H264); -+} -+ -+static obs_properties_t *av1_vaapi_properties(void *unused) -+{ -+ UNUSED_PARAMETER(unused); -+ return vaapi_properties_internal(CODEC_AV1); - } - - #ifdef ENABLE_HEVC - static obs_properties_t *hevc_vaapi_properties(void *unused) - { - UNUSED_PARAMETER(unused); -- return vaapi_properties_internal(true); -+ return vaapi_properties_internal(CODEC_HEVC); - } - #endif - -@@ -993,12 +1046,27 @@ struct obs_encoder_info h264_vaapi_encoder_info = { - .get_name = h264_vaapi_getname, - .create = h264_vaapi_create, - .destroy = vaapi_destroy, -- .encode = h264_vaapi_encode, -+ .encode = vaapi_encode, - .get_defaults = h264_vaapi_defaults, - .get_properties = h264_vaapi_properties, - .get_extra_data = vaapi_extra_data, - .get_sei_data = vaapi_sei_data, -- .get_video_info = h264_vaapi_video_info, -+ .get_video_info = vaapi_video_info, -+}; -+ -+struct obs_encoder_info av1_vaapi_encoder_info = { -+ .id = "av1_ffmpeg_vaapi", -+ .type = OBS_ENCODER_VIDEO, -+ .codec = "av1", -+ .get_name = av1_vaapi_getname, -+ .create = av1_vaapi_create, -+ .destroy = vaapi_destroy, -+ .encode = vaapi_encode, -+ .get_defaults = av1_vaapi_defaults, -+ .get_properties = av1_vaapi_properties, -+ .get_extra_data = vaapi_extra_data, -+ .get_sei_data = vaapi_sei_data, -+ .get_video_info = vaapi_video_info, - }; - - #ifdef ENABLE_HEVC -@@ -1009,11 +1077,11 @@ struct obs_encoder_info hevc_vaapi_encoder_info = { - .get_name = hevc_vaapi_getname, - .create = hevc_vaapi_create, - .destroy = vaapi_destroy, -- .encode = hevc_vaapi_encode, -+ .encode = vaapi_encode, - .get_defaults = hevc_vaapi_defaults, - .get_properties = hevc_vaapi_properties, - .get_extra_data = vaapi_extra_data, - .get_sei_data = vaapi_sei_data, -- .get_video_info = hevc_vaapi_video_info, -+ .get_video_info = vaapi_video_info, - }; - #endif -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c -index 7eb9a876d82ff..981bb9c6550ff 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg.c -@@ -47,6 +47,7 @@ extern struct obs_encoder_info aom_av1_encoder_info; - - #ifdef LIBAVUTIL_VAAPI_AVAILABLE - extern struct obs_encoder_info h264_vaapi_encoder_info; -+extern struct obs_encoder_info av1_vaapi_encoder_info; - #ifdef ENABLE_HEVC - extern struct obs_encoder_info hevc_vaapi_encoder_info; - #endif -@@ -337,6 +338,17 @@ static bool h264_vaapi_supported(void) - * that support H264. */ - return vaapi_get_h264_default_device() != NULL; - } -+static bool av1_vaapi_supported(void) -+{ -+ const AVCodec *vaenc = avcodec_find_encoder_by_name("av1_vaapi"); -+ -+ if (!vaenc) -+ return false; -+ -+ /* NOTE: If default device is NULL, it means there is no device -+ * that support AV1. */ -+ return vaapi_get_av1_default_device() != NULL; -+} - #ifdef ENABLE_HEVC - static bool hevc_vaapi_supported(void) - { -@@ -447,6 +459,13 @@ bool obs_module_load(void) - blog(LOG_INFO, "FFmpeg VAAPI H264 encoding not supported"); - } - -+ if (av1_vaapi_supported()) { -+ blog(LOG_INFO, "FFmpeg VAAPI AV1 encoding supported"); -+ obs_register_encoder(&av1_vaapi_encoder_info); -+ } else { -+ blog(LOG_INFO, "FFmpeg VAAPI AV1 encoding not supported"); -+ } -+ - #ifdef ENABLE_HEVC - if (hevc_vaapi_supported()) { - blog(LOG_INFO, "FFmpeg VAAPI HEVC encoding supported"); -diff --git a/plugins/obs-ffmpeg/vaapi-utils.c b/plugins/obs-ffmpeg/vaapi-utils.c -index d020e2a1d8704..2ca7e468cc485 100644 ---- a/plugins/obs-ffmpeg/vaapi-utils.c -+++ b/plugins/obs-ffmpeg/vaapi-utils.c -@@ -293,6 +293,61 @@ const char *vaapi_get_h264_default_device() - return default_h264_device; - } - -+bool vaapi_display_av1_supported(VADisplay dpy, const char *device_path) -+{ -+ bool ret = false; -+ -+ CHECK_PROFILE(ret, VAProfileAV1Profile0, dpy, device_path); -+ -+ if (!ret) { -+ CHECK_PROFILE_LP(ret, VAProfileAV1Profile0, dpy, device_path); -+ } -+ -+ return ret; -+} -+ -+bool vaapi_device_av1_supported(const char *device_path) -+{ -+ bool ret = false; -+ VADisplay va_dpy; -+ -+ int drm_fd = -1; -+ -+ va_dpy = vaapi_open_device(&drm_fd, device_path, -+ "vaapi_device_av1_supported"); -+ if (!va_dpy) -+ return false; -+ -+ ret = vaapi_display_av1_supported(va_dpy, device_path); -+ -+ vaapi_close_device(&drm_fd, va_dpy); -+ -+ return ret; -+} -+ -+const char *vaapi_get_av1_default_device() -+{ -+ static const char *default_av1_device = NULL; -+ -+ if (!default_av1_device) { -+ bool ret = false; -+ char path[32] = "/dev/dri/renderD1"; -+ for (int i = 28;; i++) { -+ sprintf(path, "/dev/dri/renderD1%d", i); -+ if (access(path, F_OK) != 0) -+ break; -+ -+ ret = vaapi_device_av1_supported(path); -+ if (ret) { -+ default_av1_device = strdup(path); -+ break; -+ } -+ } -+ } -+ -+ return default_av1_device; -+} -+ - #ifdef ENABLE_HEVC - - bool vaapi_display_hevc_supported(VADisplay dpy, const char *device_path) -diff --git a/plugins/obs-ffmpeg/vaapi-utils.h b/plugins/obs-ffmpeg/vaapi-utils.h -index b8a8800331266..d458bad2c5d82 100644 ---- a/plugins/obs-ffmpeg/vaapi-utils.h -+++ b/plugins/obs-ffmpeg/vaapi-utils.h -@@ -20,6 +20,10 @@ bool vaapi_display_h264_supported(VADisplay dpy, const char *device_path); - bool vaapi_device_h264_supported(const char *device_path); - const char *vaapi_get_h264_default_device(void); - -+bool vaapi_display_av1_supported(VADisplay dpy, const char *device_path); -+bool vaapi_device_av1_supported(const char *device_path); -+const char *vaapi_get_av1_default_device(void); -+ - #ifdef ENABLE_HEVC - bool vaapi_display_hevc_supported(VADisplay dpy, const char *device_path); - bool vaapi_device_hevc_supported(const char *device_path); - -From 871212adda660968cff324e1ab1053e19b342c11 Mon Sep 17 00:00:00 2001 -From: David Rosca -Date: Fri, 22 Sep 2023 08:11:46 +0200 -Subject: [PATCH 2/2] obs-ffmpeg: Set better VA-API defaults - -Use High profile for H264 and auto level with all codecs. -Remove setting default value for unused "rendermode" option. ---- - plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c | 29 ++++++++++----------------- - 1 file changed, 11 insertions(+), 18 deletions(-) - -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -index 88c906e5dc98d..b7e5d5eddb4a0 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -@@ -663,26 +663,21 @@ static void vaapi_defaults_internal(obs_data_t *settings, enum codec_type codec) - const char *const device = vaapi_default_device(codec); - obs_data_set_default_string(settings, "vaapi_device", device); - #ifdef ENABLE_HEVC -- if (codec == CODEC_HEVC) { -+ if (codec == CODEC_HEVC) - obs_data_set_default_int(settings, "profile", - FF_PROFILE_HEVC_MAIN); -- obs_data_set_default_int(settings, "level", 120); -- -- } else -+ else - #endif -- if (codec == CODEC_H264) { -+ if (codec == CODEC_H264) - obs_data_set_default_int(settings, "profile", -- FF_PROFILE_H264_CONSTRAINED_BASELINE); -- obs_data_set_default_int(settings, "level", 40); -- } else if (codec == CODEC_AV1) { -+ FF_PROFILE_H264_HIGH); -+ else if (codec == CODEC_AV1) - obs_data_set_default_int(settings, "profile", - FF_PROFILE_AV1_MAIN); -- obs_data_set_default_int(settings, "level", 8); -- } -+ obs_data_set_default_int(settings, "level", FF_LEVEL_UNKNOWN); - obs_data_set_default_int(settings, "bitrate", 2500); - obs_data_set_default_int(settings, "keyint_sec", 0); - obs_data_set_default_int(settings, "bf", 0); -- obs_data_set_default_int(settings, "rendermode", 0); - obs_data_set_default_int(settings, "qp", 20); - obs_data_set_default_int(settings, "maxrate", 0); - -@@ -770,7 +765,7 @@ static bool vaapi_device_modified(obs_properties_t *ppts, obs_property_t *p, - } - - if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_CBR, device)) -- obs_property_list_add_string(rc_p, "CBR (default)", "CBR"); -+ obs_property_list_add_string(rc_p, "CBR", "CBR"); - - if (vaapi_device_rc_supported(profile, va_dpy, VA_RC_VBR, device)) - obs_property_list_add_string(rc_p, "VBR", "VBR"); -@@ -924,8 +919,7 @@ static obs_properties_t *vaapi_properties_internal(enum codec_type codec) - obs_property_list_add_int(list, "Main10", - FF_PROFILE_HEVC_MAIN_10); - } else if (codec == CODEC_H264) { -- obs_property_list_add_int(list, -- "Constrained Baseline (default)", -+ obs_property_list_add_int(list, "Constrained Baseline", - FF_PROFILE_H264_CONSTRAINED_BASELINE); - obs_property_list_add_int(list, "Main", FF_PROFILE_H264_MAIN); - obs_property_list_add_int(list, "High", FF_PROFILE_H264_HIGH); -@@ -942,8 +936,7 @@ static obs_properties_t *vaapi_properties_internal(enum codec_type codec) - if (codec == CODEC_H264) { - obs_property_list_add_int(list, "3.0", 30); - obs_property_list_add_int(list, "3.1", 31); -- obs_property_list_add_int( -- list, "4.0 (default) (Compatibility mode)", 40); -+ obs_property_list_add_int(list, "4.0", 40); - obs_property_list_add_int(list, "4.1", 41); - obs_property_list_add_int(list, "4.2", 42); - obs_property_list_add_int(list, "5.0", 50); -@@ -952,7 +945,7 @@ static obs_properties_t *vaapi_properties_internal(enum codec_type codec) - } else if (codec == CODEC_HEVC) { - obs_property_list_add_int(list, "3.0", 90); - obs_property_list_add_int(list, "3.1", 93); -- obs_property_list_add_int(list, "4.0 (default)", 120); -+ obs_property_list_add_int(list, "4.0", 120); - obs_property_list_add_int(list, "4.1", 123); - obs_property_list_add_int(list, "5.0", 150); - obs_property_list_add_int(list, "5.1", 153); -@@ -960,7 +953,7 @@ static obs_properties_t *vaapi_properties_internal(enum codec_type codec) - } else if (codec == CODEC_AV1) { - obs_property_list_add_int(list, "3.0", 4); - obs_property_list_add_int(list, "3.1", 5); -- obs_property_list_add_int(list, "4.0 (default)", 8); -+ obs_property_list_add_int(list, "4.0", 8); - obs_property_list_add_int(list, "4.1", 9); - obs_property_list_add_int(list, "5.0", 12); - obs_property_list_add_int(list, "5.1", 13); diff --git a/patches/9475.patch b/patches/9475.patch deleted file mode 100644 index 162676b..0000000 --- a/patches/9475.patch +++ /dev/null @@ -1,213 +0,0 @@ -From 9880ec62822d86d97491bac181329907f625b52f Mon Sep 17 00:00:00 2001 -From: David Rosca -Date: Tue, 2 May 2023 13:14:18 +0200 -Subject: [PATCH] libobs: Add AV1 parsing functions - -One notable difference from the AVC/HEVC code is that it also inserts -the METADATA and SEQUENCE_HEADER OBUs into new_packet, otherwise the -resulting video file wouldn't play. ---- - libobs/CMakeLists.txt | 2 + - libobs/cmake/legacy.cmake | 2 + - libobs/obs-av1.c | 123 ++++++++++++++++++++++++++++++++++++++ - libobs/obs-av1.h | 35 +++++++++++ - 4 files changed, 162 insertions(+) - create mode 100644 libobs/obs-av1.c - create mode 100644 libobs/obs-av1.h - -diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt -index 46112abe71f9d..36a75713c001f 100644 ---- a/libobs/CMakeLists.txt -+++ b/libobs/CMakeLists.txt -@@ -33,6 +33,8 @@ target_sources( - obs-audio-controls.c - obs-audio-controls.h - obs-audio.c -+ obs-av1.c -+ obs-av1.h - obs-avc.c - obs-avc.h - obs-data.c -diff --git a/libobs/cmake/legacy.cmake b/libobs/cmake/legacy.cmake -index 5f13c4c0f04ab..dd77cdfb94229 100644 ---- a/libobs/cmake/legacy.cmake -+++ b/libobs/cmake/legacy.cmake -@@ -32,6 +32,8 @@ target_sources( - obs-audio.c - obs-audio-controls.c - obs-audio-controls.h -+ obs-av1.c -+ obs-av1.h - obs-avc.c - obs-avc.h - obs-data.c -diff --git a/libobs/obs-av1.c b/libobs/obs-av1.c -new file mode 100644 -index 0000000000000..a015130537cf2 ---- /dev/null -+++ b/libobs/obs-av1.c -@@ -0,0 +1,123 @@ -+// SPDX-FileCopyrightText: 2023 David Rosca -+// -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include "obs-av1.h" -+ -+#include "obs.h" -+ -+static inline uint64_t leb128(const uint8_t *buf, size_t size, size_t *len) -+{ -+ uint64_t value = 0; -+ uint8_t leb128_byte; -+ -+ *len = 0; -+ -+ for (int i = 0; i < 8; i++) { -+ if (size-- < 1) -+ break; -+ (*len)++; -+ leb128_byte = buf[i]; -+ value |= (leb128_byte & 0x7f) << (i * 7); -+ if (!(leb128_byte & 0x80)) -+ break; -+ } -+ -+ return value; -+} -+ -+static inline unsigned int get_bits(uint8_t val, unsigned int n, -+ unsigned int count) -+{ -+ return (val >> (8 - n - count)) & ((1 << (count - 1)) * 2 - 1); -+} -+ -+static void parse_obu_header(const uint8_t *buf, size_t size, size_t *obu_start, -+ size_t *obu_size, int *obu_type) -+{ -+ int extension_flag, has_size_field; -+ size_t size_len = 0; -+ -+ *obu_start = 0; -+ *obu_size = 0; -+ *obu_type = 0; -+ -+ if (size < 1) -+ return; -+ -+ *obu_type = get_bits(*buf, 1, 4); -+ extension_flag = get_bits(*buf, 5, 1); -+ has_size_field = get_bits(*buf, 6, 1); -+ -+ if (extension_flag) -+ (*obu_start)++; -+ -+ (*obu_start)++; -+ -+ if (has_size_field) -+ *obu_size = (size_t)leb128(buf + *obu_start, size - *obu_start, -+ &size_len); -+ else -+ *obu_size = size - 1; -+ -+ *obu_start += size_len; -+} -+ -+bool obs_av1_keyframe(const uint8_t *data, size_t size) -+{ -+ const uint8_t *start = data, *end = data + size; -+ -+ while (start < end) { -+ size_t obu_start, obu_size; -+ int obu_type; -+ parse_obu_header(start, end - start, &obu_start, &obu_size, -+ &obu_type); -+ -+ if (obu_size) { -+ if (obu_type == OBS_OBU_FRAME || -+ obu_type == OBS_OBU_FRAME_HEADER) { -+ uint8_t val = *(start + obu_start); -+ if (!get_bits(val, 0, 1)) // show_existing_frame -+ return get_bits(val, 1, 2) == -+ 0; // frame_type -+ return false; -+ } -+ } -+ -+ start += obu_start + obu_size; -+ } -+ -+ return false; -+} -+ -+void obs_extract_av1_headers(const uint8_t *packet, size_t size, -+ uint8_t **new_packet_data, size_t *new_packet_size, -+ uint8_t **header_data, size_t *header_size) -+{ -+ DARRAY(uint8_t) new_packet; -+ DARRAY(uint8_t) header; -+ const uint8_t *start = packet, *end = packet + size; -+ -+ da_init(new_packet); -+ da_init(header); -+ -+ while (start < end) { -+ size_t obu_start, obu_size; -+ int obu_type; -+ parse_obu_header(start, end - start, &obu_start, &obu_size, -+ &obu_type); -+ -+ if (obu_type == OBS_OBU_METADATA || -+ obu_type == OBS_OBU_SEQUENCE_HEADER) { -+ da_push_back_array(header, start, obu_start + obu_size); -+ } -+ da_push_back_array(new_packet, start, obu_start + obu_size); -+ -+ start += obu_start + obu_size; -+ } -+ -+ *new_packet_data = new_packet.array; -+ *new_packet_size = new_packet.num; -+ *header_data = header.array; -+ *header_size = header.num; -+} -diff --git a/libobs/obs-av1.h b/libobs/obs-av1.h -new file mode 100644 -index 0000000000000..031299da0a415 ---- /dev/null -+++ b/libobs/obs-av1.h -@@ -0,0 +1,35 @@ -+// SPDX-FileCopyrightText: 2023 David Rosca -+// -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#pragma once -+ -+#include "util/c99defs.h" -+ -+#ifdef __cplusplus -+extern "C" { -+#endif -+ -+enum { -+ OBS_OBU_SEQUENCE_HEADER = 1, -+ OBS_OBU_TEMPORAL_DELIMITER = 2, -+ OBS_OBU_FRAME_HEADER = 3, -+ OBS_OBU_TILE_GROUP = 4, -+ OBS_OBU_METADATA = 5, -+ OBS_OBU_FRAME = 6, -+ OBS_OBU_REDUNDANT_FRAME_HEADER = 7, -+ OBS_OBU_TILE_LIST = 8, -+ OBS_OBU_PADDING = 15, -+}; -+ -+/* Helpers for parsing AV1 OB units. */ -+ -+EXPORT bool obs_av1_keyframe(const uint8_t *data, size_t size); -+EXPORT void obs_extract_av1_headers(const uint8_t *packet, size_t size, -+ uint8_t **new_packet_data, -+ size_t *new_packet_size, -+ uint8_t **header_data, size_t *header_size); -+ -+#ifdef __cplusplus -+} -+#endif diff --git a/patches/add-plugins.patch b/patches/add-plugins.patch deleted file mode 100644 index f673dca..0000000 --- a/patches/add-plugins.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt -index d20bce142..febeff6b7 100644 ---- a/plugins/CMakeLists.txt -+++ b/plugins/CMakeLists.txt -@@ -60,6 +60,9 @@ elseif(OS_LINUX) - add_subdirectory(vlc-video) - add_subdirectory(sndio) - add_subdirectory(obs-vst) -+ add_subdirectory(obs-source-record) -+ -+ - - check_obs_browser() - elseif(OS_FREEBSD) diff --git a/patches/encoder-rename.patch b/patches/encoder-rename.patch deleted file mode 100644 index a356e3f..0000000 --- a/patches/encoder-rename.patch +++ /dev/null @@ -1,97 +0,0 @@ -From 11dae1dd5a7600534807e254dfa30c9613779dd7 Mon Sep 17 00:00:00 2001 -From: GloriousEggroll -Date: Sat, 29 Apr 2023 14:46:16 -0600 -Subject: [PATCH] encoder rename - ---- - plugins/obs-ffmpeg/obs-ffmpeg-av1.c | 4 ++-- - plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c | 4 ++-- - plugins/obs-x264/obs-x264.c | 2 +- - 5 files changed, 10 insertions(+), 10 deletions(-) - -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-av1.c b/plugins/obs-ffmpeg/obs-ffmpeg-av1.c -index 0b671ae..681f728 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg-av1.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg-av1.c -@@ -41,13 +41,13 @@ struct av1_encoder { - static const char *aom_av1_getname(void *unused) - { - UNUSED_PARAMETER(unused); -- return "AOM AV1"; -+ return "CPU: AOM AV1 (FFmpeg)"; - } - - static const char *svt_av1_getname(void *unused) - { - UNUSED_PARAMETER(unused); -- return "SVT-AV1"; -+ return "CPU: SVT-AV1 (FFmpeg)"; - } - - static void av1_video_info(void *data, struct video_scale_info *info) -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -index 8cd8138..add8e02 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg-nvenc.c -@@ -45,7 +45,7 @@ struct nvenc_encoder { - extern bool ubuntu_20_04_nvenc_fallback; - #endif - --#define ENCODER_NAME_H264 "NVIDIA NVENC H.264 (FFmpeg)" -+#define ENCODER_NAME_H264 "GPU: NVIDIA NVENC H.264 (FFmpeg)" - static const char *h264_nvenc_getname(void *unused) - { - UNUSED_PARAMETER(unused); -@@ -53,7 +53,7 @@ static const char *h264_nvenc_getname(void *unused) - } - - #ifdef ENABLE_HEVC --#define ENCODER_NAME_HEVC "NVIDIA NVENC HEVC (FFmpeg)" -+#define ENCODER_NAME_HEVC "GPU: NVIDIA NVENC H.265/HEVC (FFmpeg)" - static const char *hevc_nvenc_getname(void *unused) - { - UNUSED_PARAMETER(unused); -diff --git a/plugins/obs-x264/obs-x264.c b/plugins/obs-x264/obs-x264.c -index 1de88d3..d4c377d 100644 ---- a/plugins/obs-x264/obs-x264.c -+++ b/plugins/obs-x264/obs-x264.c -@@ -68,7 +68,7 @@ struct obs_x264 { - static const char *obs_x264_getname(void *unused) - { - UNUSED_PARAMETER(unused); -- return "x264"; -+ return "CPU: x264"; - } - - static void obs_x264_stop(void *data); -diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -index 370f9b7..e0bbd82 100644 ---- a/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -+++ b/plugins/obs-ffmpeg/obs-ffmpeg-vaapi.c -@@ -89,20 +89,20 @@ struct vaapi_encoder { - static const char *h264_vaapi_getname(void *unused) - { - UNUSED_PARAMETER(unused); -- return "FFmpeg VAAPI H.264"; -+ return "GPU: AMD/Intel VAAPI H.264 (FFmpeg)"; - } - - static const char *av1_vaapi_getname(void *unused) - { - UNUSED_PARAMETER(unused); -- return "FFmpeg VAAPI AV1"; -+ return "GPU: AMD/Intel VAAPI AV1 (FFmpeg)"; - } - - #ifdef ENABLE_HEVC - static const char *hevc_vaapi_getname(void *unused) - { - UNUSED_PARAMETER(unused); -- return "FFmpeg VAAPI HEVC"; -+ return "GPU: AMD/Intel VAAPI H.265/HEVC (FFmpeg)"; - } - #endif - --- -2.40.0 - diff --git a/patches/obs-studio-30-cmake-3.20.patch b/patches/obs-studio-30-cmake-3.20.patch deleted file mode 100644 index bac02da..0000000 --- a/patches/obs-studio-30-cmake-3.20.patch +++ /dev/null @@ -1,423 +0,0 @@ -From 09fbac02fa36e945480edb1cf8547ba8ce4d08ff Mon Sep 17 00:00:00 2001 -From: Neal Gompa -Date: Mon, 11 Sep 2023 15:50:55 -0400 -Subject: [PATCH] cmake: Allow CMake 3.20 and 3.21 to be used to build OBS - -CentOS Stream 9 (and thus Red Hat Enterprise Linux 9) have CMake 3.20. - -There does not appear to be anything preventing it from building -on CMake 3.20, so let's permit it so that it can still be built -for Fedora Extra Packages for Enterprise Linux 9. ---- - CMakeLists.txt | 2 +- - UI/CMakeLists.txt | 2 +- - UI/frontend-plugins/aja-output-ui/CMakeLists.txt | 2 +- - UI/frontend-plugins/decklink-captions/CMakeLists.txt | 2 +- - UI/frontend-plugins/decklink-output-ui/CMakeLists.txt | 2 +- - UI/frontend-plugins/frontend-tools/CMakeLists.txt | 2 +- - UI/obs-frontend-api/CMakeLists.txt | 2 +- - deps/blake2/CMakeLists.txt | 2 +- - deps/file-updater/CMakeLists.txt | 2 +- - deps/glad/CMakeLists.txt | 2 +- - deps/happy-eyeballs/CMakeLists.txt | 2 +- - deps/json11/CMakeLists.txt | 2 +- - deps/libcaption/CMakeLists.txt | 2 +- - deps/media-playback/CMakeLists.txt | 2 +- - deps/obs-scripting/CMakeLists.txt | 2 +- - deps/obs-scripting/obslua/CMakeLists.txt | 2 +- - deps/obs-scripting/obspython/CMakeLists.txt | 2 +- - deps/opts-parser/CMakeLists.txt | 2 +- - deps/uthash/CMakeLists.txt | 2 +- - libobs-opengl/CMakeLists.txt | 2 +- - libobs/CMakeLists.txt | 2 +- - plugins/CMakeLists.txt | 2 +- - plugins/aja/CMakeLists.txt | 2 +- - plugins/decklink/CMakeLists.txt | 2 +- - plugins/image-source/CMakeLists.txt | 2 +- - plugins/obs-ffmpeg/CMakeLists.txt | 2 +- - plugins/obs-ffmpeg/ffmpeg-mux/CMakeLists.txt | 2 +- - plugins/obs-filters/CMakeLists.txt | 2 +- - plugins/obs-outputs/CMakeLists.txt | 2 +- - plugins/obs-qsv11/CMakeLists.txt | 2 +- - plugins/obs-transitions/CMakeLists.txt | 2 +- - plugins/obs-vst/CMakeLists.txt | 2 +- - plugins/obs-webrtc/CMakeLists.txt | 2 +- - plugins/obs-x264/CMakeLists.txt | 2 +- - plugins/rtmp-services/CMakeLists.txt | 2 +- - plugins/text-freetype2/CMakeLists.txt | 2 +- - plugins/vlc-video/CMakeLists.txt | 2 +- - 37 files changed, 37 insertions(+), 37 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index b6f157fbe..3e628c792 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - if(CMAKE_HOST_SYSTEM_NAME MATCHES "(Darwin)" OR OBS_CMAKE_VERSION VERSION_GREATER_EQUAL 3.0.0) - include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake" NO_POLICY_SCOPE) -diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt -index cbebcd84d..b5660e662 100644 ---- a/UI/CMakeLists.txt -+++ b/UI/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/UI/frontend-plugins/aja-output-ui/CMakeLists.txt b/UI/frontend-plugins/aja-output-ui/CMakeLists.txt -index 3555192e6..e9b9ed0ba 100644 ---- a/UI/frontend-plugins/aja-output-ui/CMakeLists.txt -+++ b/UI/frontend-plugins/aja-output-ui/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/UI/frontend-plugins/decklink-captions/CMakeLists.txt b/UI/frontend-plugins/decklink-captions/CMakeLists.txt -index 3fc910b6c..0969dd7b0 100644 ---- a/UI/frontend-plugins/decklink-captions/CMakeLists.txt -+++ b/UI/frontend-plugins/decklink-captions/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/UI/frontend-plugins/decklink-output-ui/CMakeLists.txt b/UI/frontend-plugins/decklink-output-ui/CMakeLists.txt -index a5e697e88..1fc0af541 100644 ---- a/UI/frontend-plugins/decklink-output-ui/CMakeLists.txt -+++ b/UI/frontend-plugins/decklink-output-ui/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/UI/frontend-plugins/frontend-tools/CMakeLists.txt b/UI/frontend-plugins/frontend-tools/CMakeLists.txt -index 5a0eeba45..b29d335a3 100644 ---- a/UI/frontend-plugins/frontend-tools/CMakeLists.txt -+++ b/UI/frontend-plugins/frontend-tools/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/UI/obs-frontend-api/CMakeLists.txt b/UI/obs-frontend-api/CMakeLists.txt -index ca4eae513..fd7c9433b 100644 ---- a/UI/obs-frontend-api/CMakeLists.txt -+++ b/UI/obs-frontend-api/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/deps/blake2/CMakeLists.txt b/deps/blake2/CMakeLists.txt -index b9fb338a8..6e31ec857 100644 ---- a/deps/blake2/CMakeLists.txt -+++ b/deps/blake2/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - add_library(blake2 STATIC EXCLUDE_FROM_ALL ) - add_library(OBS::blake2 ALIAS blake2) -diff --git a/deps/file-updater/CMakeLists.txt b/deps/file-updater/CMakeLists.txt -index e07cd2f0f..fbb358110 100644 ---- a/deps/file-updater/CMakeLists.txt -+++ b/deps/file-updater/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - find_package(CURL REQUIRED) - -diff --git a/deps/glad/CMakeLists.txt b/deps/glad/CMakeLists.txt -index 64b05a2cc..c6d7ea19c 100644 ---- a/deps/glad/CMakeLists.txt -+++ b/deps/glad/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - find_package(OpenGL REQUIRED) - -diff --git a/deps/happy-eyeballs/CMakeLists.txt b/deps/happy-eyeballs/CMakeLists.txt -index b3a5692cd..ff3be48ff 100644 ---- a/deps/happy-eyeballs/CMakeLists.txt -+++ b/deps/happy-eyeballs/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - add_library(happy-eyeballs INTERFACE) - add_library(OBS::happy-eyeballs ALIAS happy-eyeballs) -diff --git a/deps/json11/CMakeLists.txt b/deps/json11/CMakeLists.txt -index 78cb7c94a..82d0a063b 100644 ---- a/deps/json11/CMakeLists.txt -+++ b/deps/json11/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - add_library(json11 INTERFACE) - add_library(OBS::json11 ALIAS json11) -diff --git a/deps/libcaption/CMakeLists.txt b/deps/libcaption/CMakeLists.txt -index 53cd79196..2770b6746 100644 ---- a/deps/libcaption/CMakeLists.txt -+++ b/deps/libcaption/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - add_library(caption STATIC EXCLUDE_FROM_ALL ) - add_library(OBS::caption ALIAS caption) -diff --git a/deps/media-playback/CMakeLists.txt b/deps/media-playback/CMakeLists.txt -index a84900e47..351f4bb42 100644 ---- a/deps/media-playback/CMakeLists.txt -+++ b/deps/media-playback/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - find_package(FFmpeg REQUIRED COMPONENTS avcodec avdevice avutil avformat) - -diff --git a/deps/obs-scripting/CMakeLists.txt b/deps/obs-scripting/CMakeLists.txt -index 9096e4b27..988d2235c 100644 ---- a/deps/obs-scripting/CMakeLists.txt -+++ b/deps/obs-scripting/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/deps/obs-scripting/obslua/CMakeLists.txt b/deps/obs-scripting/obslua/CMakeLists.txt -index 98517ee7d..22edd1e1c 100644 ---- a/deps/obs-scripting/obslua/CMakeLists.txt -+++ b/deps/obs-scripting/obslua/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/deps/obs-scripting/obspython/CMakeLists.txt b/deps/obs-scripting/obspython/CMakeLists.txt -index f0db72f75..f0a07f66c 100644 ---- a/deps/obs-scripting/obspython/CMakeLists.txt -+++ b/deps/obs-scripting/obspython/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/deps/opts-parser/CMakeLists.txt b/deps/opts-parser/CMakeLists.txt -index a87ee641e..72d61be53 100644 ---- a/deps/opts-parser/CMakeLists.txt -+++ b/deps/opts-parser/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - add_library(opts-parser INTERFACE) - add_library(OBS::opts-parser ALIAS opts-parser) -diff --git a/deps/uthash/CMakeLists.txt b/deps/uthash/CMakeLists.txt -index aecb0d74e..7ebb524f0 100644 ---- a/deps/uthash/CMakeLists.txt -+++ b/deps/uthash/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - add_library(uthash INTERFACE) - add_library(OBS::uthash ALIAS uthash) -diff --git a/libobs-opengl/CMakeLists.txt b/libobs-opengl/CMakeLists.txt -index 7ee9478ea..51751719d 100644 ---- a/libobs-opengl/CMakeLists.txt -+++ b/libobs-opengl/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt -index 46112abe7..28551f61d 100644 ---- a/libobs/CMakeLists.txt -+++ b/libobs/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt -index 90bea35be..26e6b57ec 100644 ---- a/plugins/CMakeLists.txt -+++ b/plugins/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - option(ENABLE_PLUGINS "Enable building OBS plugins" ON) - -diff --git a/plugins/aja/CMakeLists.txt b/plugins/aja/CMakeLists.txt -index 423d9d099..daaa57f51 100644 ---- a/plugins/aja/CMakeLists.txt -+++ b/plugins/aja/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/decklink/CMakeLists.txt b/plugins/decklink/CMakeLists.txt -index e93c7c772..8423fe52a 100644 ---- a/plugins/decklink/CMakeLists.txt -+++ b/plugins/decklink/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/image-source/CMakeLists.txt b/plugins/image-source/CMakeLists.txt -index a7682454e..2f5fd3790 100644 ---- a/plugins/image-source/CMakeLists.txt -+++ b/plugins/image-source/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-ffmpeg/CMakeLists.txt b/plugins/obs-ffmpeg/CMakeLists.txt -index 04530ef39..16c4d1e1a 100644 ---- a/plugins/obs-ffmpeg/CMakeLists.txt -+++ b/plugins/obs-ffmpeg/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-ffmpeg/ffmpeg-mux/CMakeLists.txt b/plugins/obs-ffmpeg/ffmpeg-mux/CMakeLists.txt -index afa22b7a5..5644f8213 100644 ---- a/plugins/obs-ffmpeg/ffmpeg-mux/CMakeLists.txt -+++ b/plugins/obs-ffmpeg/ffmpeg-mux/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-filters/CMakeLists.txt b/plugins/obs-filters/CMakeLists.txt -index 22d79388c..84b337c40 100644 ---- a/plugins/obs-filters/CMakeLists.txt -+++ b/plugins/obs-filters/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-outputs/CMakeLists.txt b/plugins/obs-outputs/CMakeLists.txt -index cedc77da0..f131ca671 100644 ---- a/plugins/obs-outputs/CMakeLists.txt -+++ b/plugins/obs-outputs/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-qsv11/CMakeLists.txt b/plugins/obs-qsv11/CMakeLists.txt -index cae152765..f1c39cc6a 100644 ---- a/plugins/obs-qsv11/CMakeLists.txt -+++ b/plugins/obs-qsv11/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-transitions/CMakeLists.txt b/plugins/obs-transitions/CMakeLists.txt -index 65e3b13ac..c18ac96ed 100644 ---- a/plugins/obs-transitions/CMakeLists.txt -+++ b/plugins/obs-transitions/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-vst/CMakeLists.txt b/plugins/obs-vst/CMakeLists.txt -index bb5c32a00..78530783b 100644 ---- a/plugins/obs-vst/CMakeLists.txt -+++ b/plugins/obs-vst/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-webrtc/CMakeLists.txt b/plugins/obs-webrtc/CMakeLists.txt -index de943062f..84b0f1aa5 100644 ---- a/plugins/obs-webrtc/CMakeLists.txt -+++ b/plugins/obs-webrtc/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/obs-x264/CMakeLists.txt b/plugins/obs-x264/CMakeLists.txt -index 4c293fb33..c75491a51 100644 ---- a/plugins/obs-x264/CMakeLists.txt -+++ b/plugins/obs-x264/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/rtmp-services/CMakeLists.txt b/plugins/rtmp-services/CMakeLists.txt -index e8d304ae0..9dc0b2396 100644 ---- a/plugins/rtmp-services/CMakeLists.txt -+++ b/plugins/rtmp-services/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/text-freetype2/CMakeLists.txt b/plugins/text-freetype2/CMakeLists.txt -index bb30f99a7..b19b0cd57 100644 ---- a/plugins/text-freetype2/CMakeLists.txt -+++ b/plugins/text-freetype2/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - -diff --git a/plugins/vlc-video/CMakeLists.txt b/plugins/vlc-video/CMakeLists.txt -index 0a35ae284..40a62b076 100644 ---- a/plugins/vlc-video/CMakeLists.txt -+++ b/plugins/vlc-video/CMakeLists.txt -@@ -1,4 +1,4 @@ --cmake_minimum_required(VERSION 3.22...3.25) -+cmake_minimum_required(VERSION 3.20...3.25) - - legacy_check() - --- -2.41.0 - diff --git a/patches/obs-studio-UI-use-fdk-aac-by-default.patch b/patches/obs-studio-UI-use-fdk-aac-by-default.patch deleted file mode 100644 index 5f9d1e1..0000000 --- a/patches/obs-studio-UI-use-fdk-aac-by-default.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 841fd01b7a529b7c1d0e0a509190a26eb1220d5a Mon Sep 17 00:00:00 2001 -From: Neal Gompa -Date: Thu, 4 May 2023 13:52:01 -0400 -Subject: [PATCH] UI: Default to fdk-aac for AAC when auto-configuring settings - -We always have the FDK-AAC codec available, and it's a better -codec anyway. ---- - UI/window-basic-auto-config-test.cpp | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/UI/window-basic-auto-config-test.cpp b/UI/window-basic-auto-config-test.cpp -index c791c8b8b..7dabfd93f 100644 ---- a/UI/window-basic-auto-config-test.cpp -+++ b/UI/window-basic-auto-config-test.cpp -@@ -202,7 +202,7 @@ void AutoConfigTestPage::TestBandwidthThread() - (wiz->x264Available ? "obs_x264" : "ffmpeg_openh264"), - "test_h264", nullptr, nullptr); - OBSEncoderAutoRelease aencoder = obs_audio_encoder_create( -- "ffmpeg_aac", "test_aac", nullptr, 0, nullptr); -+ "libfdk_aac", "test_aac", nullptr, 0, nullptr); - OBSServiceAutoRelease service = obs_service_create( - serverType, "test_service", nullptr, nullptr); - -@@ -572,7 +572,7 @@ bool AutoConfigTestPage::TestSoftwareEncoding() - (wiz->x264Available ? "obs_x264" : "ffmpeg_openh264"), - "test_h264", nullptr, nullptr); - OBSEncoderAutoRelease aencoder = obs_audio_encoder_create( -- "ffmpeg_aac", "test_aac", nullptr, 0, nullptr); -+ "libfdk_aac", "test_aac", nullptr, 0, nullptr); - OBSOutputAutoRelease output = - obs_output_create("null_output", "null", nullptr, nullptr); - --- -2.40.0 - diff --git a/patches/obs-studio-deps-Add-license-declaration-files.patch b/patches/obs-studio-deps-Add-license-declaration-files.patch deleted file mode 100644 index d1e460c..0000000 --- a/patches/obs-studio-deps-Add-license-declaration-files.patch +++ /dev/null @@ -1,218 +0,0 @@ -From b6be0b4bf5b0b2f26c8143641881cde23ac8ba9c Mon Sep 17 00:00:00 2001 -From: Neal Gompa -Date: Wed, 26 Apr 2023 07:56:35 -0400 -Subject: [PATCH 1/2] deps: Add license declaration files - ---- - deps/blake2/LICENSE.blake2 | 19 ++++++++++ - deps/libff/LICENSE.libff | 18 ++++++++++ - deps/media-playback/LICENSE.media-playback | 19 ++++++++++ - libobs/graphics/libnsgif/LICENSE.libnsgif | 33 ++++++++++++++++++ - libobs/util/simde/LICENSE.simde | 40 ++++++++++++++++++++++ - plugins/decklink/LICENSE.decklink-sdk | 30 ++++++++++++++++ - 6 files changed, 159 insertions(+) - create mode 100644 deps/blake2/LICENSE.blake2 - create mode 100644 deps/libff/LICENSE.libff - create mode 100644 deps/media-playback/LICENSE.media-playback - create mode 100644 libobs/graphics/libnsgif/LICENSE.libnsgif - create mode 100644 libobs/util/simde/LICENSE.simde - create mode 100644 plugins/decklink/LICENSE.decklink-sdk - -diff --git a/deps/blake2/LICENSE.blake2 b/deps/blake2/LICENSE.blake2 -new file mode 100644 -index 000000000..3a8abc787 ---- /dev/null -+++ b/deps/blake2/LICENSE.blake2 -@@ -0,0 +1,19 @@ -+BLAKE2 is licensed to permit usage under the terms of the CC0, the OpenSSL License, -+ or the Apache Public License 2.0 at the user's option. -+ -+The license grant is reproduced in full below. -+ -+/* -+ BLAKE2 reference source code package - reference C implementations -+ -+ Copyright 2012, Samuel Neves . You may use this under the -+ terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at -+ your option. The terms of these licenses can be found at: -+ -+ - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 -+ - OpenSSL license : https://www.openssl.org/source/license.html -+ - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 -+ -+ More information about the BLAKE2 hash function can be found at -+ https://blake2.net. -+*/ -diff --git a/deps/libff/LICENSE.libff b/deps/libff/LICENSE.libff -new file mode 100644 -index 000000000..b6f51157a ---- /dev/null -+++ b/deps/libff/LICENSE.libff -@@ -0,0 +1,18 @@ -+libff is licensed under the ISC license. The license terms are fully -+reproduced below: -+ -+/* -+ * Copyright (c) 2015 John R. Bradley -+ * -+ * Permission to use, copy, modify, and distribute this software for any -+ * purpose with or without fee is hereby granted, provided that the above -+ * copyright notice and this permission notice appear in all copies. -+ * -+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -+ */ -diff --git a/deps/media-playback/LICENSE.media-playback b/deps/media-playback/LICENSE.media-playback -new file mode 100644 -index 000000000..17d74f4b3 ---- /dev/null -+++ b/deps/media-playback/LICENSE.media-playback -@@ -0,0 +1,19 @@ -+media-playback is licensed under the ISC license. The license terms are fully -+reproduced below: -+ -+ -+/* -+ * Copyright (c) 2023 Hugh Bailey -+ * -+ * Permission to use, copy, modify, and distribute this software for any -+ * purpose with or without fee is hereby granted, provided that the above -+ * copyright notice and this permission notice appear in all copies. -+ * -+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -+ */ -diff --git a/libobs/graphics/libnsgif/LICENSE.libnsgif b/libobs/graphics/libnsgif/LICENSE.libnsgif -new file mode 100644 -index 000000000..9b28c0b45 ---- /dev/null -+++ b/libobs/graphics/libnsgif/LICENSE.libnsgif -@@ -0,0 +1,33 @@ -+libnsgif is licensed under the MIT license. The licensing statement -+and the full license are reproduced below. -+ -+/* -+ * Copyright 2003 James Bursa -+ * Copyright 2004 John Tytgat -+ * Copyright 2004 Richard Wilson -+ * Copyright 2008 Sean Fox -+ * -+ * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/ -+ * Licenced under the MIT License, -+ * http://www.opensource.org/licenses/mit-license.php -+ */ -+ -+The MIT License -+ -+Permission is hereby granted, free of charge, to any person obtaining a copy -+of this software and associated documentation files (the "Software"), to deal -+in the Software without restriction, including without limitation the rights -+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -+copies of the Software, and to permit persons to whom the Software is -+furnished to do so, subject to the following conditions: -+ -+The above copyright notice and this permission notice shall be included in -+all copies or substantial portions of the Software. -+ -+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -+THE SOFTWARE. -diff --git a/libobs/util/simde/LICENSE.simde b/libobs/util/simde/LICENSE.simde -new file mode 100644 -index 000000000..78d482e75 ---- /dev/null -+++ b/libobs/util/simde/LICENSE.simde -@@ -0,0 +1,40 @@ -+simde is licensed as a combination of MIT and CC0 code. -+ -+License notices for both are reproduced below: -+ -+/* SPDX-License-Identifier: MIT -+ * -+ * Permission is hereby granted, free of charge, to any person -+ * obtaining a copy of this software and associated documentation -+ * files (the "Software"), to deal in the Software without -+ * restriction, including without limitation the rights to use, copy, -+ * modify, merge, publish, distribute, sublicense, and/or sell copies -+ * of the Software, and to permit persons to whom the Software is -+ * furnished to do so, subject to the following conditions: -+ * -+ * The above copyright notice and this permission notice shall be -+ * included in all copies or substantial portions of the Software. -+ * -+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -+ * SOFTWARE. -+ * -+ * Copyright: -+ * 2017-2020 Evan Nemerson -+ */ -+ -+/* Portable Snippets - https://gitub.com/nemequ/portable-snippets -+ * Created by Evan Nemerson -+ * -+ * To the extent possible under law, the authors have waived all -+ * copyright and related or neighboring rights to this code. For -+ * details, see the Creative Commons Zero 1.0 Universal license at -+ * https://creativecommons.org/publicdomain/zero/1.0/ -+ * -+ * SPDX-License-Identifier: CC0-1.0 -+ */ -diff --git a/plugins/decklink/LICENSE.decklink-sdk b/plugins/decklink/LICENSE.decklink-sdk -new file mode 100644 -index 000000000..d320d805b ---- /dev/null -+++ b/plugins/decklink/LICENSE.decklink-sdk -@@ -0,0 +1,30 @@ -+decklink-sdk is licensed under to Boost Software License 1.0 (BSL-1.0). -+ -+The license text is reproduced in full below: -+ -+/* -LICENSE-START- -+** Copyright (c) 2020 Blackmagic Design -+** -+** Permission is hereby granted, free of charge, to any person or organization -+** obtaining a copy of the software and accompanying documentation covered by -+** this license (the "Software") to use, reproduce, display, distribute, -+** execute, and transmit the Software, and to prepare derivative works of the -+** Software, and to permit third-parties to whom the Software is furnished to -+** do so, all subject to the following: -+** -+** The copyright notices in the Software and this entire statement, including -+** the above license grant, this restriction and the following disclaimer, -+** must be included in all copies of the Software, in whole or in part, and -+** all derivative works of the Software, unless such copies or derivative -+** works are solely in the form of machine-executable object code generated by -+** a source language processor. -+** -+** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -+** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -+** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -+** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -+** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -+** DEALINGS IN THE SOFTWARE. -+** -LICENSE-END- -+*/ --- -2.40.0 - diff --git a/patches/series b/patches/series new file mode 100644 index 0000000..ebfba13 --- /dev/null +++ b/patches/series @@ -0,0 +1,3 @@ +01_use-sysytem-libsimde.patch +02_disable-obs-browser.patch +03_FTBFS-Werror.patch diff --git a/pika-build-config/amd64-v3.sh b/pika-build-config/amd64-v3.sh new file mode 100755 index 0000000..10285b4 --- /dev/null +++ b/pika-build-config/amd64-v3.sh @@ -0,0 +1,10 @@ +#! /bin/bash +export PIKA_BUILD_ARCH="amd64-v3" +export DEBIAN_FRONTEND="noninteractive" +export DEB_BUILD_MAINT_OPTIONS="optimize=+lto -march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32" +export DEB_CFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32" +export DEB_CPPFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32" +export DEB_CXXFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32" +export DEB_LDFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32" +export DEB_BUILD_OPTIONS="nocheck notest terse" +export DPKG_GENSYMBOLS_CHECK_LEVEL=0 diff --git a/pika-build-config/i386.sh b/pika-build-config/i386.sh new file mode 100755 index 0000000..7629d66 --- /dev/null +++ b/pika-build-config/i386.sh @@ -0,0 +1,5 @@ +#! /bin/bash +export PIKA_BUILD_ARCH="i386" +export DEBIAN_FRONTEND="noninteractive" +export DEB_BUILD_OPTIONS="nocheck notest terse" +export DPKG_GENSYMBOLS_CHECK_LEVEL=0 diff --git a/release.sh b/release.sh index 21ee3e7..660f48f 100755 --- a/release.sh +++ b/release.sh @@ -1,8 +1,2 @@ # send debs to server -rsync -azP --include './' --include '*.deb' --exclude '*' ./output/ ferreo@direct.pika-os.com:/srv/www/incoming/ - -# add debs to repo -ssh ferreo@direct.pika-os.com 'aptly repo add -force-replace -remove-files pikauwu-main /srv/www/incoming/' - -# publish the repo -ssh ferreo@direct.pika-os.com 'aptly publish update -batch -skip-contents -force-overwrite pikauwu filesystem:pikarepo:' +rsync -azP --include './' --include '*.deb' --exclude '*' ./output/ ferreo@direct.pika-os.com:/srv/www/cockatiel-incoming/