diff --git a/.github/actions/setup-macos-codesigning/action.yaml b/.github/actions/setup-macos-codesigning/action.yaml
index 56cdba48..5f24114b 100644
--- a/.github/actions/setup-macos-codesigning/action.yaml
+++ b/.github/actions/setup-macos-codesigning/action.yaml
@@ -75,7 +75,7 @@ runs:
print -n "${MACOS_SIGNING_CERT}" | base64 --decode --output="${certificate_path}"
- : "${MACOS_KEYCHAIN_PASSWORD:="$(print ${RANDOM} | sha1sum | head -c 32)"}"
+ : "${MACOS_KEYCHAIN_PASSWORD:="$(print ${RANDOM} | shasum | head -c 32)"}"
print '::group::Keychain setup'
security create-keychain -p "${MACOS_KEYCHAIN_PASSWORD}" ${keychain_path}
diff --git a/.github/scripts/.build.zsh b/.github/scripts/.build.zsh
index 4d2f08a0..708fdcf2 100755
--- a/.github/scripts/.build.zsh
+++ b/.github/scripts/.build.zsh
@@ -242,6 +242,7 @@ ${_usage_host:-}"
-G "${generator}"
-DQT_VERSION=${QT_VERSION:-6}
-DCMAKE_BUILD_TYPE=${config}
+ -DCMAKE_INSTALL_PREFIX=/usr
)
local cmake_version
diff --git a/.github/workflows/build-project.yaml b/.github/workflows/build-project.yaml
index 556ec4f8..8f29b9a5 100644
--- a/.github/workflows/build-project.yaml
+++ b/.github/workflows/build-project.yaml
@@ -3,7 +3,7 @@ on:
workflow_call:
jobs:
check-event:
- name: Check GitHub Event Data 📡
+ name: Check GitHub Event Data 🔎
runs-on: ubuntu-22.04
defaults:
run:
@@ -29,7 +29,7 @@ jobs:
case "${GITHUB_EVENT_NAME}" in
pull_request)
config_data=('codesign:false' 'notarize:false' 'package:false' 'config:RelWithDebInfo')
- if gh pr view --json labels \
+ if gh pr view ${{ github.event.number }} --json labels \
| jq -e -r '.labels[] | select(.name == "Seeking Testers")' > /dev/null; then
config_data[0]='codesign:true'
config_data[2]='package:true'
@@ -77,9 +77,9 @@ jobs:
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
print '::group::Clean Homebrew Environment'
- typeset -a to_remove=()
+ local -a to_remove=()
- if (( #to_remove > 0 )) brew uninstall --ignore-dependencies ${to_remove}
+ if (( #to_remove )) brew uninstall --ignore-dependencies ${to_remove}
print '::endgroup::'
local product_name
@@ -100,7 +100,7 @@ jobs:
- name: Set Up Codesigning 🔑
uses: ./.github/actions/setup-macos-codesigning
- if: ${{ fromJSON(needs.check-event.outputs.codesign) }}
+ if: fromJSON(needs.check-event.outputs.codesign)
id: codesign
with:
codesignIdentity: ${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}
diff --git a/CI/libndi-create-deb.sh b/CI/libndi-create-deb.sh
index db0ad283..6ae6592d 100755
--- a/CI/libndi-create-deb.sh
+++ b/CI/libndi-create-deb.sh
@@ -1,4 +1,7 @@
#!/bin/bash
+
+# This script is called by CI/libndi-package.sh
+
set -ex
SDK_ROOT="/tmp/ndisdk"
diff --git a/CI/libndi-create-dev-deb.sh b/CI/libndi-create-dev-deb.sh
index d57d9b90..8da50209 100755
--- a/CI/libndi-create-dev-deb.sh
+++ b/CI/libndi-create-dev-deb.sh
@@ -1,4 +1,7 @@
#!/bin/bash
+
+# This script is called by CI/libndi-package.sh
+
set -ex
SDK_ROOT="/tmp/ndisdk"
diff --git a/CI/libndi-get.sh b/CI/libndi-get.sh
index 2070bca5..317c184b 100755
--- a/CI/libndi-get.sh
+++ b/CI/libndi-get.sh
@@ -1,16 +1,14 @@
#!/bin/bash
-
set -e
LIBNDI_INSTALLER_NAME="Install_NDI_SDK_v5_Linux"
LIBNDI_INSTALLER="$LIBNDI_INSTALLER_NAME.tar.gz"
-LIBNDI_INSTALLER_SHA256="00d0bedc2c72736d82883fc0fd6bc1a544e7958c7e46db79f326633d44e15153"
#sudo apt-get install curl
pushd /tmp
+
curl -L -o $LIBNDI_INSTALLER https://downloads.ndi.tv/SDK/NDI_SDK_Linux/$LIBNDI_INSTALLER -f --retry 5
-echo "$LIBNDI_INSTALLER_SHA256 $LIBNDI_INSTALLER" | sha256sum -c
tar -xf $LIBNDI_INSTALLER
yes | PAGER="cat" sh $LIBNDI_INSTALLER_NAME.sh
@@ -18,8 +16,10 @@ rm -rf /tmp/ndisdk
mv "/tmp/NDI SDK for Linux" /tmp/ndisdk
ls /tmp/ndisdk
-# NOTE: This does an actual local install...
-#sudo cp -P ndisdk/lib/x86_64-linux-gnu/* /usr/local/lib/
-#sudo ldconfig
+if [ $1 == "install" ]; then
+ # NOTE: This should do an actual local install...
+ sudo cp -P /tmp/ndisdk/lib/x86_64-linux-gnu/* /usr/local/lib/
+ sudo ldconfig
+fi
popd
diff --git a/CI/libndi-package.sh b/CI/libndi-package.sh
index 3ace482e..d6fb3a47 100755
--- a/CI/libndi-package.sh
+++ b/CI/libndi-package.sh
@@ -1,8 +1,12 @@
#!/bin/bash
+#
+# This script calls libndi-get.sh and then libndi-create-deb.sh and libndi-create-dev-deb.sh
+#
+
set -e
-LIBNDI_VERSION="5.5.3"
+LIBNDI_VERSION="5.6.0"
SCRIPT_DIR=$(dirname "$0")
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0a7b15ee..46d87a6f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,9 +4,6 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake" NO_POLICY_SCO
project(${_name} VERSION ${_version})
-option(ENABLE_FRONTEND_API "Use obs-frontend-api for UI functionality" ON)
-option(ENABLE_QT "Use Qt functionality" ON)
-
include(compilerconfig)
include(defaults)
include(helpers)
@@ -34,9 +31,13 @@ if(ENABLE_QT)
AUTORCC ON)
endif()
-# Add your custom source files here - header files are optional and only required for visibility
+# cmake-format: off
+# Add your custom source files here Header files are optional and only required for visibility.
# e.g. in Xcode or Visual Studio
-target_sources(${CMAKE_PROJECT_NAME} PRIVATE src/plugin-main.cpp
+# cmake-format: on
+target_sources(
+ ${CMAKE_PROJECT_NAME}
+ PRIVATE src/plugin-main.cpp
src/plugin-main.h
src/obs-ndi-source.cpp
src/obs-ndi-output.cpp
diff --git a/README.md b/README.md
index 9d476d8c..e68c65eb 100644
--- a/README.md
+++ b/README.md
@@ -16,52 +16,76 @@ Network A/V in OBS Studio with NewTek's NDI technology.
## Requirements
* OBS >=28
-* NDI 5 Runtime (optionally installed via NDI Tools)
-
-# Install
-1. Download and install the Linux, MacOS, or Windows version at [Releases](https://github.com/obs-ndi/obs-ndi/releases).
- * Linux:
- 1. Download [obs-ndi-4.11.1-linux-x86_64.deb](https://github.com/obs-ndi/obs-ndi/releases/download/4.11.1/obs-ndi-4.11.1-linux-x86_64.deb)
- 2. `sudo dpkg -i obs-ndi-4.11.1-linux-x86_64.deb`
- * MacOS:
- 1. Download [obs-ndi-4.11.1-macos-universal.pkg](https://github.com/obs-ndi/obs-ndi/releases/download/4.11.1/obs-ndi-4.11.1-macos-universal.pkg)
- 2. Run `obs-ndi-4.11.1-macos-universal.pkg`
- * Windows:
- 1. Download [obs-ndi-4.11.1-windows-x64-Installer.exe](https://github.com/obs-ndi/obs-ndi/releases/download/4.11.1/obs-ndi-4.11.1-windows-x64-Installer.exe)
- 2. Run `obs-ndi-4.11.1-windows-x64-Installer.exe`
-2. Download and install the NDI 5 runtime from:
- * Linux:
- ```
- #!/bin/bash
- set -e
- LIBNDI_INSTALLER_NAME="Install_NDI_SDK_v5_Linux"
- LIBNDI_INSTALLER="$LIBNDI_INSTALLER_NAME.tar.gz"
- LIBNDI_INSTALLER_SHA256="00d0bedc2c72736d82883fc0fd6bc1a544e7958c7e46db79f326633d44e15153"
- pushd /tmp
- sudo apt-get install curl
- curl -L -o $LIBNDI_INSTALLER https://downloads.ndi.tv/SDK/NDI_SDK_Linux/$LIBNDI_INSTALLER -f --retry 5
- echo "$LIBNDI_INSTALLER_SHA256 $LIBNDI_INSTALLER" | sha256sum -c
- tar -xf $LIBNDI_INSTALLER
- yes | PAGER="cat" sh $LIBNDI_INSTALLER_NAME.sh
- rm -rf ndisdk
- mv "NDI SDK for Linux" ndisdk
- sudo cp -P ndisdk/lib/x86_64-linux-gnu/* /usr/local/lib/
- sudo ldconfig
- echo libndi installed to /usr/local/lib/
- ls -la /usr/local/lib/libndi*
- rm -rf ndisdk
- popd
- ```
+* NDI 5 Runtime
+ We are not allowed to directly distribute the NDI runtime here, but you can get it from either
+ [the links below](#Download_&_Install_The_NDI_5_Runtime), or installing
+ [NDI Tools](https://ndi.video/tools/), or the [NDI SDK](https://ndi.video/download-ndi-sdk/).
+
+# Install OBS-NDI
+Download and install the Linux, MacOS, or Windows version at [Releases](https://github.com/obs-ndi/obs-ndi/releases).
+
+* Linux
+ 1. Download [obs-ndi-4.12.0-linux-x86_64.deb](https://github.com/obs-ndi/obs-ndi/releases/download/4.12.0/obs-ndi-4.12.0-linux-x86_64.deb)
+ 2. `sudo dpkg -i obs-ndi-4.12.0-linux-x86_64.deb`
+ 3. If this does not work then try:
+ ```
+ $ sudo ln -s /usr/local/lib/obs-plugins/obs-ndi.so /usr/lib/x86_64-linux-gnu/obs-plugins/obs-ndi.so
+ $ sudo ln -s /usr/local/share/obs/obs-plugins/obs-ndi/ /usr/share/obs/obs-plugins/obs-ndi
+ ```
+* MacOS:
+ 1. Download [obs-ndi-4.12.0-macos-universal.pkg](https://github.com/obs-ndi/obs-ndi/releases/download/4.12.0/obs-ndi-4.12.0-macos-universal.pkg)
+ 2. Run `obs-ndi-4.12.0-macos-universal.pkg`
+ If MacOS complains about the file, either:
+ 1. Allow it in `System Settings`->`Privacy & Security`
+ -or-
+ 2. Run
+ ```
+ % sudo xattr -r -d com.apple.quarantine obs-ndi-4.12.0-macos-universal.pkg
+ ```
+* Windows:
+ 1. Download [obs-ndi-4.12.0-windows-x64-Installer.exe](https://github.com/obs-ndi/obs-ndi/releases/download/4.12.0/obs-ndi-4.12.0-windows-x64-Installer.exe)
+ 2. Run `obs-ndi-4.12.0-windows-x64-Installer.exe`
+
+# Download & Install The NDI 5 Runtime
+* Linux - there is no redist, so you have to do something like the following (also at [./CI/libndi-get.sh](./CI/libndi-get.sh))
+```
+#!/bin/bash
+
+set -e
+
+LIBNDI_INSTALLER_NAME="Install_NDI_SDK_v5_Linux"
+LIBNDI_INSTALLER="$LIBNDI_INSTALLER_NAME.tar.gz"
+LIBNDI_INSTALLER_SHA256="7e5c54693d6aee6b6f1d6d49f48d4effd7281abd216d9ff601be2d55af12f7f5"
+
+#sudo apt-get install curl
+
+pushd /tmp
+curl -L -o $LIBNDI_INSTALLER https://downloads.ndi.tv/SDK/NDI_SDK_Linux/$LIBNDI_INSTALLER -f --retry 5
+echo "$LIBNDI_INSTALLER_SHA256 $LIBNDI_INSTALLER" | sha256sum -c
+tar -xf $LIBNDI_INSTALLER
+yes | PAGER="cat" sh $LIBNDI_INSTALLER_NAME.sh
+
+rm -rf /tmp/ndisdk
+mv "/tmp/NDI SDK for Linux" /tmp/ndisdk
+ls /tmp/ndisdk
+
+if [ $1 == "install" ]; then
+ # NOTE: This does an actual local install...
+ sudo cp -P /tmp/ndisdk/lib/x86_64-linux-gnu/* /usr/local/lib/
+ sudo ldconfig
+fi
+
+popd
+```
+* MacOS: http://ndi.link/NDIRedistV5Apple
+* Windows: http://ndi.link/NDIRedistV5
- * MacOS: http://ndi.link/NDIRedistV5Apple
- * Windows: http://ndi.link/NDIRedistV5
## Uninstall
-
Reference: https://obsproject.com/kb/plugins-guide#install-or-remove-plugins
### Linux
@@ -74,42 +98,54 @@ Reference: https://obsproject.com/kb/plugins-guide#install-or-remove-plugins
```
### MacOS
-
1. Open Finder
2. Show hidden files with `Command-Shift-.`
-3. Delete `~/Library/Application Support/obs-studio/plugins/obs-ndi.plugin`
-4. Optionally delete NDI Tools/Runtime:
+3. Delete `~/Library/Application Support/obs-studio/obs-plugins/obs-ndi.plugin`
+4. Delete `~/Library/Application Support/obs-studio/data/obs-plugins/obs-ndi/`
+5. Optionally delete NDI Tools/Runtime:
1. Finder->Applications: Delete all `NDI *` applications
2. Delete `/Library/Application Support/NewTek/NDI`
2. Delete `/usr/local/lib/libndi.*`
### Windows
-
1. Add/Remove Programs
2. Delete `%ProgramFiles%\obs-studio\obs-plugins\64-bit\obs-ndi.*`
-3. Optionally delete NDI Tools/Runtime:
+3. Delete `%ProgramFiles%\obs-studio\data\obs-plugins\obs-ndi\`
+4. Optionally delete NDI Tools/Runtime:
1. Add/Remove Programs
2. Delete `%ProgramFiles%\NDI\NDI 5 Runtime`
3. Delete `%ProgramFiles%\NDI\NDI 5 Tools`
# Development
-## Compiling
+## Building
### Windows
-In PowerShell v5+ terminal:
+In PowerShell Core 7+ terminal:
```
git clone https://github.com/obs-ndi/obs-ndi.git
cd obs-ndi
-.github/scripts/Build-Windows.ps1
-...
-tbd...
```
+First build:
+```
+.github/scripts/Build-Windows.ps1 && .github/scripts/Package-Windows.ps1 -BuildInstaller
+```
+Subsequent builds:
+```
+.github/scripts/Build-Windows.ps1 -SkipDeps && .github/scripts/Package-Windows.ps1 -BuildInstaller
+```
+See `Help .github/scripts/Build-Windows.ps1` for more details.
If you get `SecurityError/PSSecurityException/UnauthorizedAccess` error then:
```
-PS ...\obs-ndi\obs-ndi> Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser
+Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser
+```
+
+
### Linux
NOTE: Only Debian and Ubuntu are officially supported
@@ -118,7 +154,9 @@ In terminal:
```
git clone https://github.com/obs-ndi/obs-ndi.git
cd obs-ndi
-.github/scripts/build-linux.sh
+.github/scripts/build-linux
+...
+.github/scripts/package-linux
...
sudo cp -r release/obs-plugins/64bit/* /usr/local/lib/x86_64-linux-gnu/obs-plugins/
...
@@ -126,46 +164,45 @@ sudo cp -r release/data/obs-plugins/* /usr/local/share/obs/obs-plugins/
...
sudo ldconfig
```
+Subsequent builds can be sped up by using `build-linux --skip-deps`.
+See `build-linux --help` for more details.
### OS X
In terminal:
```
git clone https://github.com/obs-ndi/obs-ndi.git
cd obs-ndi
-.github/scripts/build-macos.zsh
+.github/scripts/build-macos
+...
+.github/scripts/package-macos
...
cp -r release/obs-ndi.plugin ~/Library/Application\ Support/obs-studio/plugins/
...
```
-Subsequent builds can be sped up by using `build-macos.zsh --skip-deps`.
-[For some reason `--skip-all` doesn't work.]
-See `build-macos.zsh --help` for more details.
+Subsequent builds can be sped up by using `build-macos --skip-deps`.
+See `build-macos --help` for more details.
## Formatting
-From a bash shell (confirmed also works on WSL):
-```
-.github/scripts/check-format.sh 1
-```
-NOTE: `obs-ndi` is based on [`obsplugin-template`](https://github.com/obsproject/obs-plugintemplate) that [requires `clang-format-13`](https://github.com/obsproject/obs-plugintemplate/blob/525650f97209450cf2dcc06ff28ad941cc1bbd7b/.github/scripts/check-format.sh#L29-L42):
-```
-if type clang-format-13 2> /dev/null ; then
- CLANG_FORMAT=clang-format-13
-elif type clang-format 2> /dev/null ; then
- # Clang format found, but need to check version
- CLANG_FORMAT=clang-format
- V=$(clang-format --version)
- if [[ $V != *"version 13.0"* ]]; then
- echo "clang-format is not 13.0 (returned ${V})"
- exit 1
- fi
-else
- echo "No appropriate clang-format found (expected clang-format-13.0.0, or clang-format)"
- exit 1
-fi
+Requires [obsproject/tools/]clang-format@13, cmakelang, and zsh installed.
+
+From a Linux or MacOS terminal:
```
-[MacOS brew only has formulaes for clang-format 15, 11, or 8.](https://formulae.brew.sh/formula/clang-format)
-If you want to check-format on MacOS then you will need to:
-1. brew install clang-format
-2. edit `.github/scripts/check-format.sh` and change all `13` to `15`
-3. !!DO NOT COMMIT THESE CHANGES!!
+./build-aux/run-clang-format
+./build-aux/run-cmake-format
+```
+
+
diff --git a/buildspec.json b/buildspec.json
index 6ed1b62a..ebf52337 100644
--- a/buildspec.json
+++ b/buildspec.json
@@ -22,24 +22,24 @@
}
},
"prebuilt": {
- "version": "2023-10-26",
+ "version": "2023-04-12",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built obs-deps",
"hashes": {
- "macos": "eebdcc77829206eb0fee46975f69cedcea8a67386d97f73ba277da4925ca0893",
- "windows-x64": "58b3164d94fa69054a7cb0d54fef93d93c0a6304721dce4e6de3eaf3403877a6"
+ "macos": "9535c6e1ad96f7d49960251e85a245774088d48da1d602bb82f734b10219125a",
+ "windows-x64": "c13a14a1acc4224b21304d97b63da4121de1ed6981297e50496fbc474abc0503"
}
},
"qt6": {
- "version": "2023-10-26",
+ "version": "2023-04-12",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built Qt6",
"hashes": {
- "macos": "f0e842934a87cec3cd02630e393ed78768a7f9b205c5a59e40a28db8d4659a01",
- "windows-x64": "6aad335e2d4632392895e46a8c31d73f6219ec0b71bec2c23ce42ac473d10a3e"
+ "macos": "eb7614544ab4f3d2c6052c797635602280ca5b028a6b987523d8484222ce45d1",
+ "windows-x64": "4d39364b8a8dee5aa24fcebd8440d5c22bb4551c6b440ffeacce7d61f2ed1add"
},
"debugSymbols": {
- "windows-x64": "f9adeb05441f21afb0cbcf33f273a7cd2a588ff8216a11e17865e2a5dc9801af"
+ "windows-x64": "f34ee5067be19ed370268b15c53684b7b8aaa867dc800b68931df905d679e31f"
}
}
},
diff --git a/cmake/common/buildspec_common.cmake b/cmake/common/buildspec_common.cmake
index 49e4efd3..0a4d2388 100644
--- a/cmake/common/buildspec_common.cmake
+++ b/cmake/common/buildspec_common.cmake
@@ -74,6 +74,7 @@ function(_setup_obs_studio)
endif()
message(STATUS "Patch libobs - begin")
+ # cmake-format: off
# Idea shared by RoyShilkrot on OBS Discord #plugins-and-tools:
# https://github.com/occ-ai/obs-polyglot/blob/a86b5778cd200b6aeb4ef65a49e621905b18e823/cmake/common/buildspec_common.cmake#L77-L82
# To create/update libobs.patch:
@@ -82,11 +83,12 @@ function(_setup_obs_studio)
# 3. Edit libobs/CMakeLists.txt as desired
# 4. git diff libobs/CMakeLists.txt > libobs.patch
# 5. Copy libobs.patch to obs-ndi root, verify build success, commit+push
+ # cmake-format: on
execute_process(
- COMMAND patch --forward "libobs/CMakeLists.txt" "${CMAKE_CURRENT_SOURCE_DIR}/libobs.patch" #| grep "hunks failed" && [ $? -eq 0 ] && false
+ COMMAND patch --forward "libobs/CMakeLists.txt" "${CMAKE_CURRENT_SOURCE_DIR}/libobs.patch"
OUTPUT_VARIABLE _patch_output
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}")
- if (_patch_output MATCHES ".*hunks failed.*")
+ if(_patch_output MATCHES ".*hunks failed.*")
message(FATAL_ERROR "Patch libobs failed")
endif()
message(STATUS "Patch libobs - end")
diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini
index cad5ce9f..e433c275 100644
--- a/data/locale/en-US.ini
+++ b/data/locale/en-US.ini
@@ -2,7 +2,8 @@ NDIPlugin.Default="Default"
NDIPlugin.NDISourceName="NDI™ Source"
NDIPlugin.SourceProps.SourceName="Source name"
NDIPlugin.SourceProps.Bandwidth="Bandwidth"
-NDIPlugin.SourceProps.Sync="Sync"
+NDIPlugin.SourceProps.Sync="Audio/Video Sync"
+NDIPlugin.NDIFrameSync="Framesync (experimental)"
NDIPlugin.SourceProps.HWAccel="Allow hardware acceleration"
NDIPlugin.SourceProps.AlphaBlendingFix="Fix alpha blending (adds a filter to this source)"
NDIPlugin.SourceProps.ColorRange="YUV Range"
@@ -11,7 +12,8 @@ NDIPlugin.SourceProps.ColorRange.Full="Full"
NDIPlugin.SourceProps.ColorSpace="YUV Color Space"
NDIPlugin.SourceProps.Latency="Latency Mode"
NDIPlugin.SourceProps.Latency.Normal="Normal (safe)"
-NDIPlugin.SourceProps.Latency.Low="Low (experimental)"
+NDIPlugin.SourceProps.Latency.Low="Low"
+NDIPlugin.SourceProps.Latency.Lowest="Lowest (unbuffered)"
NDIPlugin.SourceProps.Audio="Enable audio"
NDIPlugin.SourceProps.PTZ="Pan Tilt Zoom"
NDIPlugin.SourceProps.Pan="Pan"
diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini
index ae0f2006..38569351 100644
--- a/data/locale/es-ES.ini
+++ b/data/locale/es-ES.ini
@@ -11,7 +11,8 @@ NDIPlugin.SourceProps.ColorRange.Full="Completo"
NDIPlugin.SourceProps.ColorSpace="Espacio de color YUV"
NDIPlugin.SourceProps.Latency="Modo de latencia"
NDIPlugin.SourceProps.Latency.Normal="Normal (segura)"
-NDIPlugin.SourceProps.Latency.Low="Baja (experimental)"
+NDIPlugin.SourceProps.Latency.Low="Baja"
+NDIPlugin.SourceProps.Latency.Lowest="Mínima (sin búfer)"
NDIPlugin.BWMode.Highest="La más alta"
NDIPlugin.BWMode.Lowest="La más baja"
NDIPlugin.BWMode.AudioOnly="Solo Audio"
diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini
index a023ca9c..1cdf90cd 100644
--- a/data/locale/fr-FR.ini
+++ b/data/locale/fr-FR.ini
@@ -4,22 +4,23 @@ NDIPlugin.SourceProps.SourceName="Source"
NDIPlugin.SourceProps.Bandwidth="Bandwidth"
NDIPlugin.SourceProps.Sync="Synchronisation"
NDIPlugin.SourceProps.HWAccel="Autoriser l'accélération matérielle"
-NDIPlugin.SourceProps.AlphaBlendingFix="Correction du compositing alpha (ajoute un filtre à la source)"
+NDIPlugin.SourceProps.AlphaBlendingFix="Correction de simulation de transparence (ajoute un filtre à la source)"
NDIPlugin.SourceProps.ColorRange="Gamme de couleurs YUV"
NDIPlugin.SourceProps.ColorRange.Partial="Partielle"
NDIPlugin.SourceProps.ColorRange.Full="Complète"
-NDIPlugin.SourceProps.ColorSpace="Espace de couleurs YUV"
+NDIPlugin.SourceProps.ColorSpace="Espace colorimétrique YUV"
NDIPlugin.SourceProps.Latency="Latence"
NDIPlugin.SourceProps.Latency.Normal="Normale (sûr)"
-NDIPlugin.SourceProps.Latency.Low="Réduite (expérimental)"
+NDIPlugin.SourceProps.Latency.Low="Réduite"
+NDIPlugin.SourceProps.Latency.Lowest="Minimum (non-tamponné)"
NDIPlugin.BWMode.Highest="Haut débit"
NDIPlugin.BWMode.Lowest="Bas débit"
NDIPlugin.BWMode.AudioOnly="Audio seulement"
NDIPlugin.SyncMode.NDITimestamp="Réseau"
-NDIPlugin.SyncMode.NDISourceTimecode="Timing de la source"
+NDIPlugin.SyncMode.NDISourceTimecode="Code temporel de la source"
NDIPlugin.OutputName="Sortie NDI™"
NDIPlugin.OutputProps.NDIName="Nom de la sortie"
-NDIPlugin.FilterProps.NDIName="Nom NDI"
+NDIPlugin.FilterProps.NDIName="Nom NDI™"
NDIPlugin.FilterProps.NDIName.Default="Sortie NDI dédiée"
NDIPlugin.FilterProps.ApplySettings="Appliquer les changements"
NDIPlugin.Menu.OutputSettings="NDI™ - Sortie principale"
@@ -30,8 +31,8 @@ NDIPlugin.OutputSettings.Main.Name="Nom NDI™"
NDIPlugin.OutputSettings.Preview.Name="Nom NDI™"
NDIPlugin.FilterName="Sortie NDI™ dédiée"
NDIPlugin.AudioFilterName="Sortie NDI™ dédiée (Audio seul)"
-NDIPlugin.PremultipliedAlphaFilterName="obs-ndi - Correction du compositing alpha"
-NDIPlugin.LibError.Title="Runtime NDI™ introuvable"
-NDIPlugin.LibError.Message.Win="La bibliothèque NDI™ est introuvable sur votre système.
Téléchargez l'installateur du runtime ici : http://new.tk/NDIRedistV5"
-NDIPlugin.LibError.Message.macOS="Le bibliothèque NDI™ est introuvable sur votre système.
Téléchargez l'installateur du runtime ici : http://new.tk/NDIRedistV5Apple"
-NDIPlugin.LibError.Message.Linux="La bibliothèque NDI™ est introuvable. Installez libndi sur votre système.
Vous pouvez en trouvez une copie sur la page GitHub d'obs-ndi."
+NDIPlugin.PremultipliedAlphaFilterName="obs-ndi - Correction de simulation de transparence"
+NDIPlugin.LibError.Title="Bibliothèque NDI™ introuvable"
+NDIPlugin.LibError.Message.Win="La bibliothèque NDI™ est introuvable sur votre système.
Téléchargez l'installateur ici : http://new.tk/NDIRedistV5"
+NDIPlugin.LibError.Message.macOS="Le bibliothèque NDI™ est introuvable sur votre système.
Téléchargez l'installateur ici : http://new.tk/NDIRedistV5Apple"
+NDIPlugin.LibError.Message.Linux="La bibliothèque NDI™ est introuvable. Installez libndi sur votre système.
Une copie est disponible sur la page GitHub d'obs-ndi : Github OBS-NDI."
diff --git a/data/locale/ro-RO.ini b/data/locale/ro-RO.ini
new file mode 100644
index 00000000..a2e94333
--- /dev/null
+++ b/data/locale/ro-RO.ini
@@ -0,0 +1,42 @@
+NDIPlugin.Default="Implicit"
+NDIPlugin.NDISourceName="Sursă NDI™"
+NDIPlugin.SourceProps.SourceName="Numele sursei"
+NDIPlugin.SourceProps.Bandwidth="Lățimea de bandă"
+NDIPlugin.SourceProps.Sync="Sincronizare"
+NDIPlugin.SourceProps.HWAccel="Permite accelerarea hardware"
+NDIPlugin.SourceProps.AlphaBlendingFix="Corectează amestecul alfa (adaugă un filtru la această sursă)"
+NDIPlugin.SourceProps.ColorRange="Gama YUV"
+NDIPlugin.SourceProps.ColorRange.Partial="Parțială"
+NDIPlugin.SourceProps.ColorRange.Full="Completă"
+NDIPlugin.SourceProps.ColorSpace="Spațiu de culoare YUV"
+NDIPlugin.SourceProps.Latency="Mod de latență"
+NDIPlugin.SourceProps.Latency.Normal="Normal (sigur)"
+NDIPlugin.SourceProps.Latency.Low="Scăzut (experimental)"
+NDIPlugin.SourceProps.Audio="Activează audio"
+NDIPlugin.BWMode.Highest="Cea mai înaltă"
+NDIPlugin.BWMode.Lowest="Cea mai scăzută"
+NDIPlugin.BWMode.AudioOnly="Numai audio"
+NDIPlugin.SyncMode.NDITimestamp="Rețea"
+NDIPlugin.SyncMode.NDISourceTimecode="Timing-ul sursei"
+NDIPlugin.OutputName="Ieșire NDI™"
+NDIPlugin.OutputProps.NDIName="Nume ieșire"
+NDIPlugin.FilterProps.NDIName="Nume NDI"
+NDIPlugin.FilterProps.NDIName.Default="Ieșire NDI dedicată"
+NDIPlugin.FilterProps.ApplySettings="Apică modificările"
+NDIPlugin.Menu.OutputSettings="Setări ieșire NDI™"
+NDIPlugin.OutputSettings.DialogTitle="Setări pentru ieșirea NDI™"
+NDIPlugin.OutputSettings.GroupBox.Main="Ieșirea de program"
+NDIPlugin.OutputSettings.GroupBox.Preview="Ieșirea de previzualizare"
+NDIPlugin.OutputSettings.GroupBox.Tally="Semnalizare"
+NDIPlugin.OutputSettings.GroupBox.Tally.Enable="Activează"
+NDIPlugin.OutputSettings.GroupBox.Tally.Program="Program"
+NDIPlugin.OutputSettings.GroupBox.Tally.Preview="Previzualizare"
+NDIPlugin.OutputSettings.Main.Name="Numele ieșirii de program"
+NDIPlugin.OutputSettings.Preview.Name="Numele ieșirii de previzualizare"
+NDIPlugin.FilterName="Ieșire NDI™ dedicată"
+NDIPlugin.AudioFilterName="Ieșire NDI™ dedicată (numai audio)"
+NDIPlugin.PremultipliedAlphaFilterName="obs-ndi - Corectare amestec alfa"
+NDIPlugin.LibError.Title="NDI™ Runtime nu a fost găsit"
+NDIPlugin.LibError.Message.Win="NDI™ Runtime nu a fost găsit.
Descărcați programul de instalare aici: http://new.tk/NDIRedistV5"
+NDIPlugin.LibError.Message.macOS="NDI™ Runtime nu a fost găsit.
Descărcați programul de instalare aici: http://new.tk/NDIRedistV5Apple"
+NDIPlugin.LibError.Message.Linux="NDI™ Runtime nu a fost găsit. Instalați libndi pe sistemul dumneavoastră.
Puteți obține o copie libndi pentru Linux pe pagina obs-ndi de la GitHub."
\ No newline at end of file
diff --git a/data/locale/uk-UA.ini b/data/locale/uk-UA.ini
index dd5374ce..1940116a 100644
--- a/data/locale/uk-UA.ini
+++ b/data/locale/uk-UA.ini
@@ -11,7 +11,8 @@ NDIPlugin.SourceProps.ColorRange.Full="Повний"
NDIPlugin.SourceProps.ColorSpace="Колірний простір YUV"
NDIPlugin.SourceProps.Latency="Режим затримки"
NDIPlugin.SourceProps.Latency.Normal="Нормальний (безпечний)"
-NDIPlugin.SourceProps.Latency.Low="Низький (експериментальний)"
+NDIPlugin.SourceProps.Latency.Low="Низький"
+NDIPlugin.SourceProps.Latency.Lowest="Мінімум (небуферизований)"
NDIPlugin.BWMode.Highest="Найвищий"
NDIPlugin.BWMode.Lowest="Найнижчий"
NDIPlugin.BWMode.AudioOnly="Тільки аудіо"
diff --git a/src/forms/output-settings.cpp b/src/forms/output-settings.cpp
index 6a6df8d0..8f5d1f84 100644
--- a/src/forms/output-settings.cpp
+++ b/src/forms/output-settings.cpp
@@ -68,10 +68,8 @@ void OutputSettings::onFormAccepted()
}
}
-void OutputSettings::showEvent(QShowEvent *event)
+void OutputSettings::showEvent(QShowEvent *)
{
- UNUSED_PARAMETER(event);
-
Config *conf = Config::Current();
ui->mainOutputGroupBox->setChecked(conf->OutputEnabled);
diff --git a/src/main-output.cpp b/src/main-output.cpp
index 825be4e7..cb425172 100644
--- a/src/main-output.cpp
+++ b/src/main-output.cpp
@@ -32,7 +32,7 @@ void main_output_start(const char *output_name)
return;
blog(LOG_INFO,
- "main_output_start: starting NDI main output with name '%s'",
+ "[obs-ndi] main_output_start: starting NDI main output with name '%s'",
output_name);
obs_data_t *settings = obs_data_create();
@@ -46,10 +46,13 @@ void main_output_start(const char *output_name)
return;
}
- obs_output_start(main_out);
+ auto result = obs_output_start(main_out);
+ blog(LOG_INFO,
+ "[obs-ndi] main_output_start: obs_output_start result=%d", result);
+
main_output_running = true;
- blog(LOG_INFO, "main_output_start: started NDI main output");
+ blog(LOG_INFO, "[obs-ndi] main_output_start: started NDI main output");
}
void main_output_stop()
@@ -57,7 +60,7 @@ void main_output_stop()
if (!main_output_running)
return;
- blog(LOG_INFO, "main_output_stop: stopping NDI main output");
+ blog(LOG_INFO, "[obs-ndi] main_output_stop: stopping NDI main output");
obs_output_stop(main_out);
obs_output_release(main_out);
@@ -65,9 +68,8 @@ void main_output_stop()
main_output_running = false;
- blog(LOG_INFO, "main_output_stop: stopped NDI main output");
+ blog(LOG_INFO, "[obs-ndi] main_output_stop: stopped NDI main output");
}
-
bool main_output_is_running()
{
return main_output_running;
diff --git a/src/obs-ndi-filter.cpp b/src/obs-ndi-filter.cpp
index 421d5919..5b372734 100644
--- a/src/obs-ndi-filter.cpp
+++ b/src/obs-ndi-filter.cpp
@@ -33,13 +33,16 @@ with this program. If not, see
#define TEXFORMAT GS_BGRA
#define FLT_PROP_NAME "ndi_filter_ndiname"
-struct ndi_filter {
+typedef struct {
obs_source_t *context;
+
NDIlib_send_instance_t ndi_sender;
+
pthread_mutex_t ndi_sender_video_mutex;
pthread_mutex_t ndi_sender_audio_mutex;
- struct obs_video_info ovi;
- struct obs_audio_info oai;
+
+ obs_video_info ovi;
+ obs_audio_info oai;
uint32_t known_width;
uint32_t known_height;
@@ -52,27 +55,24 @@ struct ndi_filter {
video_t *video_output;
bool is_audioonly;
- os_performance_token_t *perf_token;
-};
+ uint8_t *audio_conv_buffer;
+ size_t audio_conv_buffer_size;
+} ndi_filter_t;
-const char *ndi_filter_getname(void *data)
+const char *ndi_filter_getname(void *)
{
- UNUSED_PARAMETER(data);
return obs_module_text("NDIPlugin.FilterName");
}
-const char *ndi_audiofilter_getname(void *data)
+const char *ndi_audiofilter_getname(void *)
{
- UNUSED_PARAMETER(data);
return obs_module_text("NDIPlugin.AudioFilterName");
}
void ndi_filter_update(void *data, obs_data_t *settings);
-obs_properties_t *ndi_filter_getproperties(void *data)
+obs_properties_t *ndi_filter_getproperties(void *)
{
- UNUSED_PARAMETER(data);
-
obs_properties_t *props = obs_properties_create();
obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);
@@ -84,35 +84,25 @@ obs_properties_t *ndi_filter_getproperties(void *data)
obs_properties_add_button(
props, "ndi_apply",
obs_module_text("NDIPlugin.FilterProps.ApplySettings"),
- [](obs_properties_t *pps, obs_property_t *prop,
- void *private_data) {
- UNUSED_PARAMETER(pps);
- UNUSED_PARAMETER(prop);
- struct ndi_filter *s =
- (struct ndi_filter *)private_data;
- obs_data_t *settings =
- obs_source_get_settings(s->context);
+ [](obs_properties_t *, obs_property_t *, void *private_data) {
+ auto s = (ndi_filter_t *)private_data;
+ auto settings = obs_source_get_settings(s->context);
ndi_filter_update(s, settings);
obs_data_release(settings);
return true;
});
- obs_properties_add_button(props, "ndi_website", "NDI.NewTek.com",
- [](obs_properties_t *pps,
- obs_property_t *prop, void *private_data) {
- UNUSED_PARAMETER(pps);
- UNUSED_PARAMETER(prop);
- UNUSED_PARAMETER(private_data);
+ obs_properties_add_button(
+ props, "ndi_website", "NDI.NewTek.com",
+ [](obs_properties_t *, obs_property_t *, void *) {
#if defined(_WIN32)
- ShellExecute(NULL, L"open",
- L"http://ndi.newtek.com",
- NULL, NULL,
- SW_SHOWNORMAL);
+ ShellExecute(NULL, L"open", L"http://ndi.newtek.com",
+ NULL, NULL, SW_SHOWNORMAL);
#elif defined(__linux__) || defined(__APPLE__)
- (void)!system("open http://ndi.newtek.com");
+ (void)!system("open http://ndi.newtek.com");
#endif
- return true;
- });
+ return true;
+ });
return props;
}
@@ -126,36 +116,33 @@ void ndi_filter_getdefaults(obs_data_t *defaults)
void ndi_filter_raw_video(void *data, video_data *frame)
{
- auto s = (struct ndi_filter *)data;
+ auto f = (ndi_filter_t *)data;
if (!frame || !frame->data[0])
return;
NDIlib_video_frame_v2_t video_frame = {0};
- video_frame.xres = s->known_width;
- video_frame.yres = s->known_height;
+ video_frame.xres = f->known_width;
+ video_frame.yres = f->known_height;
video_frame.FourCC = NDIlib_FourCC_type_BGRA;
- video_frame.frame_rate_N = s->ovi.fps_num;
- video_frame.frame_rate_D = s->ovi.fps_den;
+ video_frame.frame_rate_N = f->ovi.fps_num;
+ video_frame.frame_rate_D = f->ovi.fps_den;
video_frame.picture_aspect_ratio = 0; // square pixels
video_frame.frame_format_type = NDIlib_frame_format_type_progressive;
video_frame.timecode = (frame->timestamp / 100);
video_frame.p_data = frame->data[0];
video_frame.line_stride_in_bytes = frame->linesize[0];
- pthread_mutex_lock(&s->ndi_sender_video_mutex);
- ndiLib->send_send_video_v2(s->ndi_sender, &video_frame);
- pthread_mutex_unlock(&s->ndi_sender_video_mutex);
+ pthread_mutex_lock(&f->ndi_sender_video_mutex);
+ ndiLib->send_send_video_v2(f->ndi_sender, &video_frame);
+ pthread_mutex_unlock(&f->ndi_sender_video_mutex);
}
-void ndi_filter_offscreen_render(void *data, uint32_t cx, uint32_t cy)
+void ndi_filter_offscreen_render(void *data, uint32_t, uint32_t)
{
- UNUSED_PARAMETER(cx);
- UNUSED_PARAMETER(cy);
+ auto f = (ndi_filter_t *)data;
- auto s = (struct ndi_filter *)data;
-
- obs_source_t *target = obs_filter_get_parent(s->context);
+ obs_source_t *target = obs_filter_get_parent(f->context);
if (!target) {
return;
}
@@ -163,10 +150,10 @@ void ndi_filter_offscreen_render(void *data, uint32_t cx, uint32_t cy)
uint32_t width = obs_source_get_base_width(target);
uint32_t height = obs_source_get_base_height(target);
- gs_texrender_reset(s->texrender);
+ gs_texrender_reset(f->texrender);
- if (gs_texrender_begin(s->texrender, width, height)) {
- struct vec4 background;
+ if (gs_texrender_begin(f->texrender, width, height)) {
+ vec4 background;
vec4_zero(&background);
gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
@@ -179,67 +166,66 @@ void ndi_filter_offscreen_render(void *data, uint32_t cx, uint32_t cy)
obs_source_video_render(target);
gs_blend_state_pop();
- gs_texrender_end(s->texrender);
+ gs_texrender_end(f->texrender);
- if (s->known_width != width || s->known_height != height) {
+ if (f->known_width != width || f->known_height != height) {
- gs_stagesurface_destroy(s->stagesurface);
- s->stagesurface = gs_stagesurface_create(width, height,
+ gs_stagesurface_destroy(f->stagesurface);
+ f->stagesurface = gs_stagesurface_create(width, height,
TEXFORMAT);
video_output_info vi = {0};
vi.format = VIDEO_FORMAT_BGRA;
vi.width = width;
vi.height = height;
- vi.fps_den = s->ovi.fps_den;
- vi.fps_num = s->ovi.fps_num;
+ vi.fps_den = f->ovi.fps_den;
+ vi.fps_num = f->ovi.fps_num;
vi.cache_size = 16;
vi.colorspace = VIDEO_CS_DEFAULT;
vi.range = VIDEO_RANGE_DEFAULT;
- vi.name = obs_source_get_name(s->context);
+ vi.name = obs_source_get_name(f->context);
- video_output_close(s->video_output);
- video_output_open(&s->video_output, &vi);
- video_output_connect(s->video_output, nullptr,
- ndi_filter_raw_video, s);
+ video_output_close(f->video_output);
+ video_output_open(&f->video_output, &vi);
+ video_output_connect(f->video_output, nullptr,
+ ndi_filter_raw_video, f);
- s->known_width = width;
- s->known_height = height;
+ f->known_width = width;
+ f->known_height = height;
}
- struct video_frame output_frame;
- if (video_output_lock_frame(s->video_output, &output_frame, 1,
+ video_frame output_frame;
+ if (video_output_lock_frame(f->video_output, &output_frame, 1,
os_gettime_ns())) {
- if (s->video_data) {
- gs_stagesurface_unmap(s->stagesurface);
- s->video_data = nullptr;
+ if (f->video_data) {
+ gs_stagesurface_unmap(f->stagesurface);
+ f->video_data = nullptr;
}
gs_stage_texture(
- s->stagesurface,
- gs_texrender_get_texture(s->texrender));
- gs_stagesurface_map(s->stagesurface, &s->video_data,
- &s->video_linesize);
+ f->stagesurface,
+ gs_texrender_get_texture(f->texrender));
+ gs_stagesurface_map(f->stagesurface, &f->video_data,
+ &f->video_linesize);
uint32_t linesize = output_frame.linesize[0];
- for (uint32_t i = 0; i < s->known_height; ++i) {
+ for (uint32_t i = 0; i < f->known_height; ++i) {
uint32_t dst_offset = linesize * i;
- uint32_t src_offset = s->video_linesize * i;
+ uint32_t src_offset = f->video_linesize * i;
memcpy(output_frame.data[0] + dst_offset,
- s->video_data + src_offset, linesize);
+ f->video_data + src_offset, linesize);
}
- video_output_unlock_frame(s->video_output);
+ video_output_unlock_frame(f->video_output);
}
}
}
void ndi_filter_update(void *data, obs_data_t *settings)
{
- UNUSED_PARAMETER(settings);
- auto s = (struct ndi_filter *)data;
+ auto f = (ndi_filter_t *)data;
- obs_remove_main_render_callback(ndi_filter_offscreen_render, s);
+ obs_remove_main_render_callback(ndi_filter_offscreen_render, f);
NDIlib_send_create_t send_desc;
send_desc.p_ndi_name = obs_data_get_string(settings, FLT_PROP_NAME);
@@ -247,144 +233,176 @@ void ndi_filter_update(void *data, obs_data_t *settings)
send_desc.clock_video = false;
send_desc.clock_audio = false;
- pthread_mutex_lock(&s->ndi_sender_video_mutex);
- pthread_mutex_lock(&s->ndi_sender_audio_mutex);
-
- ndiLib->send_destroy(s->ndi_sender);
- s->ndi_sender = ndiLib->send_create(&send_desc);
-
- pthread_mutex_unlock(&s->ndi_sender_audio_mutex);
- pthread_mutex_unlock(&s->ndi_sender_video_mutex);
-
- if (!s->is_audioonly) {
- obs_add_main_render_callback(ndi_filter_offscreen_render, s);
+ if (!f->is_audioonly) {
+ pthread_mutex_lock(&f->ndi_sender_video_mutex);
+ }
+ pthread_mutex_lock(&f->ndi_sender_audio_mutex);
+ ndiLib->send_destroy(f->ndi_sender);
+ f->ndi_sender = ndiLib->send_create(&send_desc);
+ pthread_mutex_unlock(&f->ndi_sender_audio_mutex);
+ if (!f->is_audioonly) {
+ pthread_mutex_unlock(&f->ndi_sender_video_mutex);
+ obs_add_main_render_callback(ndi_filter_offscreen_render, f);
}
}
void *ndi_filter_create(obs_data_t *settings, obs_source_t *source)
{
- auto s = (struct ndi_filter *)bzalloc(sizeof(struct ndi_filter));
- s->is_audioonly = false;
- s->context = source;
- s->texrender = gs_texrender_create(TEXFORMAT, GS_ZS_NONE);
- s->video_data = nullptr;
- s->perf_token = os_request_high_performance("NDI Filter");
- pthread_mutex_init(&s->ndi_sender_video_mutex, NULL);
- pthread_mutex_init(&s->ndi_sender_audio_mutex, NULL);
-
- obs_get_video_info(&s->ovi);
- obs_get_audio_info(&s->oai);
-
- ndi_filter_update(s, settings);
- return s;
+ auto name = obs_data_get_string(settings, FLT_PROP_NAME);
+ blog(LOG_INFO, "[obs-ndi] +ndi_filter_create(name=\"%s\")", name);
+
+ auto f = (ndi_filter_t *)bzalloc(sizeof(ndi_filter_t));
+ f->context = source;
+ f->texrender = gs_texrender_create(TEXFORMAT, GS_ZS_NONE);
+ pthread_mutex_init(&f->ndi_sender_video_mutex, NULL);
+ pthread_mutex_init(&f->ndi_sender_audio_mutex, NULL);
+ obs_get_video_info(&f->ovi);
+ obs_get_audio_info(&f->oai);
+
+ ndi_filter_update(f, settings);
+
+ blog(LOG_INFO, "[obs-ndi] -ndi_filter_create(...)");
+
+ return f;
}
void *ndi_filter_create_audioonly(obs_data_t *settings, obs_source_t *source)
{
- auto s = (struct ndi_filter *)bzalloc(sizeof(struct ndi_filter));
- s->is_audioonly = true;
- s->context = source;
- s->perf_token = os_request_high_performance("NDI Filter (Audio Only)");
- pthread_mutex_init(&s->ndi_sender_audio_mutex, NULL);
- pthread_mutex_init(&s->ndi_sender_video_mutex, NULL);
+ auto name = obs_data_get_string(settings, FLT_PROP_NAME);
+ blog(LOG_INFO, "[obs-ndi] +ndi_filter_create_audioonly(name=\"%s\")",
+ name);
+
+ auto f = (ndi_filter_t *)bzalloc(sizeof(ndi_filter_t));
+ f->is_audioonly = true;
+ f->context = source;
+ pthread_mutex_init(&f->ndi_sender_audio_mutex, NULL);
+ obs_get_audio_info(&f->oai);
+
+ ndi_filter_update(f, settings);
- obs_get_audio_info(&s->oai);
+ blog(LOG_INFO, "[obs-ndi] -ndi_filter_create_audioonly(...)");
- ndi_filter_update(s, settings);
- return s;
+ return f;
}
void ndi_filter_destroy(void *data)
{
- auto s = (struct ndi_filter *)data;
+ blog(LOG_INFO, "[obs-ndi] +ndi_filter_destroy(...)");
- obs_remove_main_render_callback(ndi_filter_offscreen_render, s);
- video_output_close(s->video_output);
+ auto f = (ndi_filter_t *)data;
- pthread_mutex_lock(&s->ndi_sender_video_mutex);
- pthread_mutex_lock(&s->ndi_sender_audio_mutex);
+ obs_remove_main_render_callback(ndi_filter_offscreen_render, f);
+ video_output_close(f->video_output);
- ndiLib->send_destroy(s->ndi_sender);
+ pthread_mutex_lock(&f->ndi_sender_video_mutex);
+ pthread_mutex_lock(&f->ndi_sender_audio_mutex);
+ ndiLib->send_destroy(f->ndi_sender);
+ pthread_mutex_unlock(&f->ndi_sender_audio_mutex);
+ pthread_mutex_unlock(&f->ndi_sender_video_mutex);
- pthread_mutex_unlock(&s->ndi_sender_audio_mutex);
- pthread_mutex_unlock(&s->ndi_sender_video_mutex);
+ gs_stagesurface_unmap(f->stagesurface);
+ gs_stagesurface_destroy(f->stagesurface);
+ gs_texrender_destroy(f->texrender);
- gs_stagesurface_unmap(s->stagesurface);
- gs_stagesurface_destroy(s->stagesurface);
- gs_texrender_destroy(s->texrender);
-
- if (s->perf_token) {
- os_end_high_performance(s->perf_token);
+ if (f->audio_conv_buffer) {
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_filter_destroy: freeing %zu bytes",
+ f->audio_conv_buffer_size);
+ bfree(f->audio_conv_buffer);
+ f->audio_conv_buffer = nullptr;
}
- bfree(s);
+ bfree(f);
+
+ blog(LOG_INFO, "[obs-ndi] -ndi_filter_destroy(...)");
}
void ndi_filter_destroy_audioonly(void *data)
{
- auto s = (struct ndi_filter *)data;
+ blog(LOG_INFO, "[obs-ndi] +ndi_filter_destroy_audioonly(...)");
+
+ auto f = (ndi_filter_t *)data;
- pthread_mutex_lock(&s->ndi_sender_audio_mutex);
- ndiLib->send_destroy(s->ndi_sender);
- pthread_mutex_unlock(&s->ndi_sender_audio_mutex);
+ pthread_mutex_lock(&f->ndi_sender_audio_mutex);
+ ndiLib->send_destroy(f->ndi_sender);
+ pthread_mutex_unlock(&f->ndi_sender_audio_mutex);
- if (s->perf_token) {
- os_end_high_performance(s->perf_token);
+ if (f->audio_conv_buffer) {
+ bfree(f->audio_conv_buffer);
+ f->audio_conv_buffer = nullptr;
}
- bfree(s);
+ bfree(f);
+
+ blog(LOG_INFO, "[obs-ndi] -ndi_filter_destroy_audioonly(...)");
}
-void ndi_filter_tick(void *data, float seconds)
+void ndi_filter_tick(void *data, float)
{
- UNUSED_PARAMETER(seconds);
- auto s = (struct ndi_filter *)data;
- obs_get_video_info(&s->ovi);
+ auto f = (ndi_filter_t *)data;
+ obs_get_video_info(&f->ovi);
}
-void ndi_filter_videorender(void *data, gs_effect_t *effect)
+void ndi_filter_videorender(void *data, gs_effect_t *)
{
- UNUSED_PARAMETER(effect);
- auto s = (struct ndi_filter *)data;
- obs_source_skip_video_filter(s->context);
+ auto f = (ndi_filter_t *)data;
+ obs_source_skip_video_filter(f->context);
}
-struct obs_audio_data *ndi_filter_asyncaudio(void *data,
- struct obs_audio_data *audio_data)
+obs_audio_data *ndi_filter_asyncaudio(void *data, obs_audio_data *audio_data)
{
- auto s = (struct ndi_filter *)data;
+ // NOTE: The logic in this function should be similar to
+ // obs-ndi-output::ndi_output_raw_audio
+ auto f = (ndi_filter_t *)data;
- obs_get_audio_info(&s->oai);
+ obs_get_audio_info(&f->oai);
NDIlib_audio_frame_v2_t audio_frame = {0};
- audio_frame.sample_rate = s->oai.samples_per_sec;
- audio_frame.no_channels = s->oai.speakers;
- audio_frame.timecode = (int64_t)(audio_data->timestamp / 100);
+ audio_frame.sample_rate = f->oai.samples_per_sec;
+ audio_frame.no_channels = f->oai.speakers;
+ audio_frame.timecode = audio_data->timestamp / 100;
audio_frame.no_samples = audio_data->frames;
audio_frame.channel_stride_in_bytes = audio_frame.no_samples * 4;
- size_t data_size =
+ const size_t data_size =
audio_frame.no_channels * audio_frame.channel_stride_in_bytes;
- uint8_t *ndi_data = (uint8_t *)bmalloc(data_size);
+
+ if (data_size > f->audio_conv_buffer_size) {
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_filter_asyncaudio: growing audio_conv_buffer from %zu to %zu bytes",
+ f->audio_conv_buffer_size, data_size);
+ if (f->audio_conv_buffer) {
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_filter_asyncaudio: freeing %zu bytes",
+ f->audio_conv_buffer_size);
+ bfree(f->audio_conv_buffer);
+ }
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_filter_asyncaudio: allocating %zu bytes",
+ data_size);
+ f->audio_conv_buffer = (uint8_t *)bmalloc(data_size);
+ f->audio_conv_buffer_size = data_size;
+ }
for (int i = 0; i < audio_frame.no_channels; ++i) {
- memcpy(&ndi_data[i * audio_frame.channel_stride_in_bytes],
+ memcpy(f->audio_conv_buffer +
+ (i * audio_frame.channel_stride_in_bytes),
audio_data->data[i],
audio_frame.channel_stride_in_bytes);
}
- audio_frame.p_data = (float *)ndi_data;
- pthread_mutex_lock(&s->ndi_sender_audio_mutex);
- ndiLib->send_send_audio_v2(s->ndi_sender, &audio_frame);
- pthread_mutex_unlock(&s->ndi_sender_audio_mutex);
+ audio_frame.p_data = (float *)f->audio_conv_buffer;
+
+ pthread_mutex_lock(&f->ndi_sender_audio_mutex);
+ ndiLib->send_send_audio_v2(f->ndi_sender, &audio_frame);
+ pthread_mutex_unlock(&f->ndi_sender_audio_mutex);
- bfree(ndi_data);
return audio_data;
}
-struct obs_source_info create_ndi_filter_info()
+obs_source_info create_ndi_filter_info()
{
- struct obs_source_info ndi_filter_info = {};
+ obs_source_info ndi_filter_info = {};
ndi_filter_info.id = "ndi_filter";
ndi_filter_info.type = OBS_SOURCE_TYPE_FILTER;
ndi_filter_info.output_flags = OBS_SOURCE_VIDEO;
@@ -406,9 +424,9 @@ struct obs_source_info create_ndi_filter_info()
return ndi_filter_info;
}
-struct obs_source_info create_ndi_audiofilter_info()
+obs_source_info create_ndi_audiofilter_info()
{
- struct obs_source_info ndi_filter_info = {};
+ obs_source_info ndi_filter_info = {};
ndi_filter_info.id = "ndi_audiofilter";
ndi_filter_info.type = OBS_SOURCE_TYPE_FILTER;
ndi_filter_info.output_flags = OBS_SOURCE_AUDIO;
@@ -418,8 +436,8 @@ struct obs_source_info create_ndi_audiofilter_info()
ndi_filter_info.get_defaults = ndi_filter_getdefaults;
ndi_filter_info.create = ndi_filter_create_audioonly;
- ndi_filter_info.destroy = ndi_filter_destroy_audioonly;
ndi_filter_info.update = ndi_filter_update;
+ ndi_filter_info.destroy = ndi_filter_destroy_audioonly;
ndi_filter_info.filter_audio = ndi_filter_asyncaudio;
diff --git a/src/obs-ndi-output.cpp b/src/obs-ndi-output.cpp
index fff45f23..89254da3 100644
--- a/src/obs-ndi-output.cpp
+++ b/src/obs-ndi-output.cpp
@@ -61,13 +61,14 @@ static void convert_i444_to_uyvy(uint8_t *input[], uint32_t in_linesize[],
}
}
-struct ndi_output {
+typedef struct {
obs_output_t *output;
const char *ndi_name;
bool uses_video;
bool uses_audio;
bool started;
+
NDIlib_send_instance_t ndi_sender;
uint32_t frame_width;
@@ -84,20 +85,15 @@ struct ndi_output {
uint8_t *audio_conv_buffer;
size_t audio_conv_buffer_size;
+} ndi_output_t;
- os_performance_token_t *perf_token;
-};
-
-const char *ndi_output_getname(void *data)
+const char *ndi_output_getname(void *)
{
- UNUSED_PARAMETER(data);
return obs_module_text("NDIPlugin.OutputName");
}
-obs_properties_t *ndi_output_getproperties(void *data)
+obs_properties_t *ndi_output_getproperties(void *)
{
- UNUSED_PARAMETER(data);
-
obs_properties_t *props = obs_properties_create();
obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);
@@ -117,14 +113,26 @@ void ndi_output_getdefaults(obs_data_t *settings)
obs_data_set_default_bool(settings, "uses_audio", true);
}
-bool ndi_output_start(void *data)
-{
- blog(LOG_INFO, "+ndi_output_start(...)");
+void ndi_output_update(void *data, obs_data_t *settings);
- auto o = (struct ndi_output *)data;
+void *ndi_output_create(obs_data_t *settings, obs_output_t *output)
+{
+ auto name = obs_data_get_string(settings, "ndi_name");
+ blog(LOG_INFO, "[obs-ndi] +ndi_output_create('%s'...)", name);
+ auto o = (ndi_output_t *)bzalloc(sizeof(ndi_output_t));
+ o->output = output;
+ ndi_output_update(o, settings);
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_create('%s'...)", name);
+ return o;
+}
+bool ndi_output_start(void *data)
+{
+ auto o = (ndi_output_t *)data;
+ blog(LOG_INFO, "[obs-ndi] +ndi_output_start('%s'...)", o->ndi_name);
if (o->started) {
- blog(LOG_INFO, "-ndi_output_start()");
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_start('%s'...)",
+ o->ndi_name);
return false;
}
@@ -133,9 +141,10 @@ bool ndi_output_start(void *data)
audio_t *audio = obs_output_audio(o->output);
if (!video && !audio) {
- blog(LOG_ERROR, "'%s': no video and audio available",
+ blog(LOG_ERROR, "[obs-ndi] '%s': no video and audio available",
+ o->ndi_name);
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_start('%s'...)",
o->ndi_name);
- blog(LOG_INFO, "-ndi_output_start()");
return false;
}
@@ -175,9 +184,11 @@ bool ndi_output_start(void *data)
break;
default:
- blog(LOG_WARNING, "unsupported pixel format %d",
+ blog(LOG_WARNING,
+ "[obs-ndi] warning: unsupported pixel format %d",
format);
- blog(LOG_INFO, "-ndi_output_start()");
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_start('%s'...)",
+ o->ndi_name);
return false;
}
@@ -201,37 +212,41 @@ bool ndi_output_start(void *data)
o->ndi_sender = ndiLib->send_create(&send_desc);
if (o->ndi_sender) {
- if (o->perf_token) {
- os_end_high_performance(o->perf_token);
- }
- o->perf_token = os_request_high_performance("NDI Output");
-
o->started = obs_output_begin_data_capture(o->output, flags);
if (o->started) {
- blog(LOG_INFO, "'%s': ndi output started", o->ndi_name);
+ blog(LOG_INFO, "[obs-ndi] '%s': ndi output started",
+ o->ndi_name);
} else {
- blog(LOG_ERROR, "'%s': data capture start failed",
+ blog(LOG_ERROR,
+ "[obs-ndi] '%s': data capture start failed",
o->ndi_name);
}
} else {
- blog(LOG_ERROR, "'%s': ndi sender init failed", o->ndi_name);
+ blog(LOG_ERROR, "[obs-ndi] '%s': ndi sender init failed",
+ o->ndi_name);
}
- blog(LOG_INFO, "-ndi_output_start()");
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_start('%s'...)", o->ndi_name);
return o->started;
}
-void ndi_output_stop(void *data, uint64_t ts)
+void ndi_output_update(void *data, obs_data_t *settings)
{
- blog(LOG_INFO, "+ndi_output_stop(...)");
-
- UNUSED_PARAMETER(ts);
-
- auto o = (struct ndi_output *)data;
+ auto o = (ndi_output_t *)data;
+ o->ndi_name = obs_data_get_string(settings, "ndi_name");
+ blog(LOG_INFO, "[obs-ndi] ndi_output_update('%s'...)", o->ndi_name);
+ o->uses_video = obs_data_get_bool(settings, "uses_video");
+ o->uses_audio = obs_data_get_bool(settings, "uses_audio");
+}
+void ndi_output_stop(void *data, uint64_t)
+{
+ auto o = (ndi_output_t *)data;
+ blog(LOG_INFO, "[obs-ndi] +ndi_output_stop('%s'...)", o->ndi_name);
if (!o->started) {
- blog(LOG_INFO, "-ndi_output_stop(...)");
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_stop('%s'...)",
+ o->ndi_name);
return;
}
@@ -239,15 +254,12 @@ void ndi_output_stop(void *data, uint64_t ts)
obs_output_end_data_capture(o->output);
- if (o->perf_token) {
- os_end_high_performance(o->perf_token);
- o->perf_token = nullptr;
- }
-
if (o->ndi_sender) {
- blog(LOG_INFO, "+ndiLib->send_destroy(o->ndi_sender)");
+ blog(LOG_INFO,
+ "[obs-ndi] +ndiLib->send_destroy(o->ndi_sender)");
ndiLib->send_destroy(o->ndi_sender);
- blog(LOG_INFO, "-ndiLib->send_destroy(o->ndi_sender)");
+ blog(LOG_INFO,
+ "[obs-ndi] -ndiLib->send_destroy(o->ndi_sender)");
o->ndi_sender = nullptr;
}
@@ -264,45 +276,27 @@ void ndi_output_stop(void *data, uint64_t ts)
o->audio_channels = 0;
o->audio_samplerate = 0;
- blog(LOG_INFO, "-ndi_output_stop(...)");
-}
-
-void ndi_output_update(void *data, obs_data_t *settings)
-{
- auto o = (struct ndi_output *)data;
- o->ndi_name = obs_data_get_string(settings, "ndi_name");
- o->uses_video = obs_data_get_bool(settings, "uses_video");
- o->uses_audio = obs_data_get_bool(settings, "uses_audio");
-}
-
-void *ndi_output_create(obs_data_t *settings, obs_output_t *output)
-{
- auto o = (struct ndi_output *)bzalloc(sizeof(struct ndi_output));
- o->output = output;
- o->started = false;
- o->audio_conv_buffer = nullptr;
- o->audio_conv_buffer_size = 0;
- o->perf_token = NULL;
- ndi_output_update(o, settings);
- return o;
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_stop('%s'...)", o->ndi_name);
}
void ndi_output_destroy(void *data)
{
- blog(LOG_INFO, "+ndi_output_destroy(...)");
- auto o = (struct ndi_output *)data;
+ auto o = (ndi_output_t *)data;
+ blog(LOG_INFO, "[obs-ndi] +ndi_output_destroy('%s'...)", o->ndi_name);
if (o->audio_conv_buffer) {
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_output_destroy: freeing %zu bytes",
+ o->audio_conv_buffer_size);
bfree(o->audio_conv_buffer);
o->audio_conv_buffer = nullptr;
}
bfree(o);
- blog(LOG_INFO, "-ndi_output_destroy(...)");
+ blog(LOG_INFO, "[obs-ndi] -ndi_output_destroy('%s'...)", o->ndi_name);
}
-void ndi_output_rawvideo(void *data, struct video_data *frame)
+void ndi_output_rawvideo(void *data, video_data *frame)
{
- auto o = (struct ndi_output *)data;
-
+ auto o = (ndi_output_t *)data;
if (!o->started || !o->frame_width || !o->frame_height)
return;
@@ -313,10 +307,10 @@ void ndi_output_rawvideo(void *data, struct video_data *frame)
video_frame.xres = width;
video_frame.yres = height;
video_frame.frame_rate_N = (int)(o->video_framerate * 100);
- video_frame.frame_rate_D =
- 100; // TODO fixme: broken on fractional framerates
+ // TODO fixme: broken on fractional framerates
+ video_frame.frame_rate_D = 100;
video_frame.frame_format_type = NDIlib_frame_format_type_progressive;
- video_frame.timecode = (int64_t)(frame->timestamp / 100);
+ video_frame.timecode = frame->timestamp / 100;
video_frame.FourCC = o->frame_fourcc;
if (video_frame.FourCC == NDIlib_FourCC_type_UYVY) {
@@ -332,57 +326,71 @@ void ndi_output_rawvideo(void *data, struct video_data *frame)
ndiLib->send_send_video_async_v2(o->ndi_sender, &video_frame);
}
-void ndi_output_rawaudio(void *data, struct audio_data *frame)
+void ndi_output_rawaudio(void *data, audio_data *frame)
{
- auto o = (struct ndi_output *)data;
-
+ // NOTE: The logic in this function should be similar to
+ // obs-ndi-filter::ndi_filter_asyncaudio
+ auto o = (ndi_output_t *)data;
if (!o->started || !o->audio_samplerate || !o->audio_channels)
return;
NDIlib_audio_frame_v3_t audio_frame = {0};
audio_frame.sample_rate = o->audio_samplerate;
audio_frame.no_channels = (int)o->audio_channels;
+ audio_frame.timecode = NDIlib_send_timecode_synthesize;
audio_frame.no_samples = frame->frames;
audio_frame.channel_stride_in_bytes = frame->frames * 4;
audio_frame.FourCC = NDIlib_FourCC_audio_type_FLTP;
- const size_t data_size = (size_t)audio_frame.no_channels *
- (size_t)audio_frame.channel_stride_in_bytes;
+ const size_t data_size =
+ audio_frame.no_channels * audio_frame.channel_stride_in_bytes;
+
if (data_size > o->audio_conv_buffer_size) {
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_output_rawaudio: growing audio_conv_buffer from %zu to %zu bytes",
+ o->audio_conv_buffer_size, data_size);
if (o->audio_conv_buffer) {
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_output_rawaudio: freeing %zu bytes",
+ o->audio_conv_buffer_size);
bfree(o->audio_conv_buffer);
}
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_output_rawaudio: allocating %zu bytes",
+ data_size);
o->audio_conv_buffer = (uint8_t *)bmalloc(data_size);
o->audio_conv_buffer_size = data_size;
}
for (int i = 0; i < audio_frame.no_channels; ++i) {
memcpy(o->audio_conv_buffer +
- ((size_t)i *
- (size_t)audio_frame.channel_stride_in_bytes),
+ (i * audio_frame.channel_stride_in_bytes),
frame->data[i], audio_frame.channel_stride_in_bytes);
}
audio_frame.p_data = o->audio_conv_buffer;
- audio_frame.timecode = NDIlib_send_timecode_synthesize;
ndiLib->send_send_audio_v3(o->ndi_sender, &audio_frame);
}
-struct obs_output_info create_ndi_output_info()
+obs_output_info create_ndi_output_info()
{
- struct obs_output_info ndi_output_info = {};
+ obs_output_info ndi_output_info = {};
ndi_output_info.id = "ndi_output";
ndi_output_info.flags = OBS_OUTPUT_AV;
+
ndi_output_info.get_name = ndi_output_getname;
ndi_output_info.get_properties = ndi_output_getproperties;
ndi_output_info.get_defaults = ndi_output_getdefaults;
+
ndi_output_info.create = ndi_output_create;
- ndi_output_info.destroy = ndi_output_destroy;
- ndi_output_info.update = ndi_output_update;
ndi_output_info.start = ndi_output_start;
+ ndi_output_info.update = ndi_output_update;
ndi_output_info.stop = ndi_output_stop;
+ ndi_output_info.destroy = ndi_output_destroy;
+
ndi_output_info.raw_video = ndi_output_rawvideo;
ndi_output_info.raw_audio = ndi_output_rawaudio;
+
return ndi_output_info;
}
diff --git a/src/obs-ndi-source.cpp b/src/obs-ndi-source.cpp
index 4280a3df..b7e7bd72 100644
--- a/src/obs-ndi-source.cpp
+++ b/src/obs-ndi-source.cpp
@@ -26,14 +26,16 @@ with this program. If not, see
#include
#include
#include
+#include
#include "plugin-main.h"
#include "Config.h"
#define PROP_SOURCE "ndi_source_name"
#define PROP_BANDWIDTH "ndi_bw_mode"
-#define PROP_HW_ACCEL "ndi_recv_hw_accel"
#define PROP_SYNC "ndi_sync"
+#define PROP_FRAMESYNC "ndi_framesync"
+#define PROP_HW_ACCEL "ndi_recv_hw_accel"
#define PROP_FIX_ALPHA "ndi_fix_alpha_blending"
#define PROP_YUV_RANGE "yuv_range"
#define PROP_YUV_COLORSPACE "yuv_colorspace"
@@ -44,6 +46,7 @@ with this program. If not, see
#define PROP_TILT "ndi_tilt"
#define PROP_ZOOM "ndi_zoom"
+#define PROP_BW_UNDEFINED -1
#define PROP_BW_HIGHEST 0
#define PROP_BW_LOWEST 1
#define PROP_BW_AUDIO_ONLY 2
@@ -59,39 +62,54 @@ with this program. If not, see
#define PROP_YUV_SPACE_BT601 1
#define PROP_YUV_SPACE_BT709 2
+#define PROP_LATENCY_UNDEFINED -1
#define PROP_LATENCY_NORMAL 0
#define PROP_LATENCY_LOW 1
+#define PROP_LATENCY_LOWEST 2
extern NDIlib_find_instance_t ndi_finder;
-struct ndi_source {
- obs_source_t *source;
- NDIlib_recv_instance_t ndi_receiver;
+typedef struct {
+ bool enabled;
+ float pan;
+ float tilt;
+ float zoom;
+} ptz_t;
+
+typedef struct {
+ QByteArray ndi_receiver_name;
+ QByteArray ndi_source_name;
+ int bandwidth;
int sync_mode;
+ bool framesync_enabled;
+ bool hw_accel_enabled;
video_range_type yuv_range;
video_colorspace yuv_colorspace;
- pthread_t av_thread;
- bool running;
- NDIlib_tally_t tally;
- bool alpha_filter_enabled;
+ int latency;
bool audio_enabled;
- os_performance_token_t *perf_token;
- char *ndi_name;
- int bandwidth;
- bool hwAccelEnabled;
-};
+ ptz_t ptz;
+ NDIlib_tally_t tally;
+} ndi_source_config_t;
+
+typedef struct {
+ obs_source_t *obs_source;
+ ndi_source_config_t config;
+
+ bool running;
+ pthread_t av_thread;
+} ndi_source_t;
static obs_source_t *find_filter_by_id(obs_source_t *context, const char *id)
{
if (!context)
return nullptr;
- struct search_context {
+ typedef struct {
const char *query;
obs_source_t *result;
- };
+ } search_context_t;
- struct search_context filter_search = {};
+ search_context_t filter_search = {};
filter_search.query = id;
filter_search.result = nullptr;
@@ -102,8 +120,8 @@ static obs_source_t *find_filter_by_id(obs_source_t *context, const char *id)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#endif
- struct search_context *filter_search =
- static_cast(param);
+ search_context_t *filter_search =
+ static_cast(param);
const char *id = obs_source_get_id(filter);
#if defined(__linux__)
#pragma GCC diagnostic pop
@@ -174,27 +192,22 @@ static obs_source_frame *blank_video_frame()
return frame;
}
-const char *ndi_source_getname(void *data)
+const char *ndi_source_getname(void *)
{
- UNUSED_PARAMETER(data);
return obs_module_text("NDIPlugin.NDISourceName");
}
-obs_properties_t *ndi_source_getproperties(void *data)
+obs_properties_t *ndi_source_getproperties(void *)
{
- UNUSED_PARAMETER(data);
-
obs_properties_t *props = obs_properties_create();
obs_property_t *source_list = obs_properties_add_list(
props, PROP_SOURCE,
obs_module_text("NDIPlugin.SourceProps.SourceName"),
OBS_COMBO_TYPE_EDITABLE, OBS_COMBO_FORMAT_STRING);
-
uint32_t nbSources = 0;
const NDIlib_source_t *sources =
ndiLib->find_get_current_sources(ndi_finder, &nbSources);
-
for (uint32_t i = 0; i < nbSources; ++i) {
obs_property_list_add_string(source_list, sources[i].p_ndi_name,
sources[i].p_ndi_name);
@@ -204,7 +217,6 @@ obs_properties_t *ndi_source_getproperties(void *data)
props, PROP_BANDWIDTH,
obs_module_text("NDIPlugin.SourceProps.Bandwidth"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
-
obs_property_list_add_int(bw_modes,
obs_module_text("NDIPlugin.BWMode.Highest"),
PROP_BW_HIGHEST);
@@ -214,7 +226,6 @@ obs_properties_t *ndi_source_getproperties(void *data)
obs_property_list_add_int(bw_modes,
obs_module_text("NDIPlugin.BWMode.AudioOnly"),
PROP_BW_AUDIO_ONLY);
-
#if defined(__linux__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
@@ -243,7 +254,6 @@ obs_properties_t *ndi_source_getproperties(void *data)
obs_property_t *sync_modes = obs_properties_add_list(
props, PROP_SYNC, obs_module_text("NDIPlugin.SourceProps.Sync"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
-
obs_property_list_add_int(
sync_modes, obs_module_text("NDIPlugin.SyncMode.NDITimestamp"),
PROP_SYNC_NDI_TIMESTAMP);
@@ -252,6 +262,9 @@ obs_properties_t *ndi_source_getproperties(void *data)
obs_module_text("NDIPlugin.SyncMode.NDISourceTimecode"),
PROP_SYNC_NDI_SOURCE_TIMECODE);
+ obs_properties_add_bool(props, PROP_FRAMESYNC,
+ obs_module_text("NDIPlugin.NDIFrameSync"));
+
obs_properties_add_bool(
props, PROP_HW_ACCEL,
obs_module_text("NDIPlugin.SourceProps.HWAccel"));
@@ -264,7 +277,6 @@ obs_properties_t *ndi_source_getproperties(void *data)
props, PROP_YUV_RANGE,
obs_module_text("NDIPlugin.SourceProps.ColorRange"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
-
obs_property_list_add_int(
yuv_ranges,
obs_module_text("NDIPlugin.SourceProps.ColorRange.Partial"),
@@ -278,7 +290,6 @@ obs_properties_t *ndi_source_getproperties(void *data)
props, PROP_YUV_COLORSPACE,
obs_module_text("NDIPlugin.SourceProps.ColorSpace"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
-
obs_property_list_add_int(yuv_spaces, "BT.709", PROP_YUV_SPACE_BT709);
obs_property_list_add_int(yuv_spaces, "BT.601", PROP_YUV_SPACE_BT601);
@@ -286,7 +297,6 @@ obs_properties_t *ndi_source_getproperties(void *data)
props, PROP_LATENCY,
obs_module_text("NDIPlugin.SourceProps.Latency"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
-
obs_property_list_add_int(
latency_modes,
obs_module_text("NDIPlugin.SourceProps.Latency.Normal"),
@@ -295,42 +305,40 @@ obs_properties_t *ndi_source_getproperties(void *data)
latency_modes,
obs_module_text("NDIPlugin.SourceProps.Latency.Low"),
PROP_LATENCY_LOW);
+ obs_property_list_add_int(
+ latency_modes,
+ obs_module_text("NDIPlugin.SourceProps.Latency.Lowest"),
+ PROP_LATENCY_LOWEST);
obs_properties_add_bool(props, PROP_AUDIO,
obs_module_text("NDIPlugin.SourceProps.Audio"));
- obs_properties_t *group = obs_properties_create();
-
+ obs_properties_t *group_ptz = obs_properties_create();
obs_properties_add_float_slider(
- group, PROP_PAN, obs_module_text("NDIPlugin.SourceProps.Pan"),
- -1.0, 1.0, 0.001);
+ group_ptz, PROP_PAN,
+ obs_module_text("NDIPlugin.SourceProps.Pan"), -1.0, 1.0, 0.001);
obs_properties_add_float_slider(
- group, PROP_TILT, obs_module_text("NDIPlugin.SourceProps.Tilt"),
- -1.0, 1.0, 0.001);
+ group_ptz, PROP_TILT,
+ obs_module_text("NDIPlugin.SourceProps.Tilt"), -1.0, 1.0,
+ 0.001);
obs_properties_add_float_slider(
- group, PROP_ZOOM, obs_module_text("NDIPlugin.SourceProps.Zoom"),
- 0.0, 1.0, 0.001);
-
+ group_ptz, PROP_ZOOM,
+ obs_module_text("NDIPlugin.SourceProps.Zoom"), 0.0, 1.0, 0.001);
obs_properties_add_group(props, PROP_PTZ,
obs_module_text("NDIPlugin.SourceProps.PTZ"),
- OBS_GROUP_CHECKABLE, group);
-
- obs_properties_add_button(props, "ndi_website", "NDI.NewTek.com",
- [](obs_properties_t *pps,
- obs_property_t *prop, void *private_data) {
- UNUSED_PARAMETER(pps);
- UNUSED_PARAMETER(prop);
- UNUSED_PARAMETER(private_data);
+ OBS_GROUP_CHECKABLE, group_ptz);
+
+ obs_properties_add_button(
+ props, "ndi_website", "NDI.NewTek.com",
+ [](obs_properties_t *, obs_property_t *, void *) {
#if defined(_WIN32)
- ShellExecute(NULL, L"open",
- L"http://ndi.newtek.com",
- NULL, NULL,
- SW_SHOWNORMAL);
+ ShellExecute(NULL, L"open", L"http://ndi.newtek.com",
+ NULL, NULL, SW_SHOWNORMAL);
#elif defined(__linux__) || defined(__APPLE__)
- (void)!system("open http://ndi.newtek.com");
+ (void)!system("open http://ndi.newtek.com");
#endif
- return true;
- });
+ return true;
+ });
return props;
}
@@ -348,374 +356,712 @@ void ndi_source_getdefaults(obs_data_t *settings)
obs_data_set_default_bool(settings, PROP_AUDIO, true);
}
-void ndi_source_ptz(struct ndi_source *s, obs_data_t *settings)
-{
- if (!obs_data_get_bool(settings, PROP_PTZ))
- return;
- float pan = (float)obs_data_get_double(settings, PROP_PAN);
- float tilt = (float)obs_data_get_double(settings, PROP_TILT);
- ndiLib->recv_ptz_pan_tilt(s->ndi_receiver, pan, tilt);
- float zoom = (float)obs_data_get_double(settings, PROP_ZOOM);
- ndiLib->recv_ptz_zoom(s->ndi_receiver, zoom);
-}
+void ndi_source_thread_process_audio2(ndi_source_config_t *config,
+ NDIlib_audio_frame_v2_t *ndi_audio_frame2,
+ obs_source_t *obs_source,
+ obs_source_audio *obs_audio_frame);
-void *ndi_source_poll_audio_video(void *data)
-{
- auto s = (struct ndi_source *)data;
+void ndi_source_thread_process_audio3(ndi_source_config_t *config,
+ NDIlib_audio_frame_v3_t *ndi_audio_frame3,
+ obs_source_t *obs_source,
+ obs_source_audio *obs_audio_frame);
- blog(LOG_INFO, "A/V thread for '%s' started",
- obs_source_get_name(s->source));
+void ndi_source_thread_process_video2(ndi_source_config_t *config,
+ NDIlib_video_frame_v2_t *ndi_video_frame2,
+ obs_source *obs_source,
+ obs_source_frame *obs_video_frame);
+
+void *ndi_source_thread(void *data)
+{
+ auto s = (ndi_source_t *)data;
+ auto obs_source = s->obs_source;
+ QByteArray obs_source_ndi_receiver_name_utf8 =
+ QString(obs_source_get_name(obs_source)).toUtf8();
+ const char *obs_source_ndi_receiver_name =
+ obs_source_ndi_receiver_name_utf8.constData();
+ blog(LOG_INFO, "[obs-ndi] +ndi_source_thread('%s'...)",
+ obs_source_ndi_receiver_name);
+
+ ndi_source_config_t config_most_recent = {};
+ ndi_source_config_t config_last_used = {};
+ config_last_used.bandwidth = PROP_BW_UNDEFINED;
+ config_last_used.latency = PROP_LATENCY_UNDEFINED;
- NDIlib_audio_frame_v3_t audio_frame;
obs_source_audio obs_audio_frame = {};
- NDIlib_video_frame_v2_t video_frame;
obs_source_frame obs_video_frame = {};
- bool connected = false;
- if (s->perf_token) {
- os_end_high_performance(s->perf_token);
- }
- s->perf_token = os_request_high_performance("NDI Receiver Thread");
+ NDIlib_recv_create_v3_t recv_desc;
+ recv_desc.allow_video_fields = true;
+
+ NDIlib_recv_instance_t ndi_receiver = nullptr;
+ NDIlib_video_frame_v2_t video_frame2;
+ NDIlib_metadata_frame_t metadata_frame;
+ NDIlib_framesync_instance_t ndi_frame_sync = nullptr;
+ NDIlib_audio_frame_v2_t audio_frame2;
+ int64_t timestamp_audio = 0;
+ int64_t timestamp_video = 0;
+
+ NDIlib_audio_frame_v3_t audio_frame3;
NDIlib_frame_type_e frame_received = NDIlib_frame_type_none;
+
+ NDIlib_recv_create_v3_t *reset_recv_desc = &recv_desc;
+
while (s->running) {
- if (ndiLib->recv_get_no_connections(s->ndi_receiver) == 0) {
- if (connected)
- connected = false;
- std::this_thread::sleep_for(
- std::chrono::milliseconds(100));
- continue;
+ //
+ // Main NDI receiver loop
+ //
+
+ // semi-atomic not *TOO* heavy snapshot
+ config_most_recent = s->config;
+
+ if (config_most_recent.ndi_receiver_name !=
+ config_last_used.ndi_receiver_name) {
+ config_last_used.ndi_receiver_name =
+ config_most_recent.ndi_receiver_name;
+
+ reset_recv_desc = &recv_desc;
+ obs_source_ndi_receiver_name_utf8 =
+ config_most_recent.ndi_receiver_name;
+ obs_source_ndi_receiver_name =
+ obs_source_ndi_receiver_name_utf8.constData();
+ recv_desc.p_ndi_recv_name =
+ obs_source_ndi_receiver_name;
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' ndi_receiver_name changed; Setting recv_desc.p_ndi_recv_name='%s'",
+ obs_source_ndi_receiver_name,
+ recv_desc.p_ndi_recv_name);
}
- if (!connected) {
- connected = true;
- obs_data_t *settings =
- obs_source_get_settings(s->source);
- ndi_source_ptz(s, settings);
- obs_data_release(settings);
+ if (config_most_recent.ndi_source_name !=
+ config_last_used.ndi_source_name) {
+ config_last_used.ndi_source_name =
+ config_most_recent.ndi_source_name;
+
+ reset_recv_desc = &recv_desc;
+ recv_desc.source_to_connect_to.p_ndi_name =
+ config_most_recent.ndi_source_name;
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' ndi_source_name changed; Setting recv_desc.source_to_connect_to.p_ndi_name='%s'",
+ obs_source_ndi_receiver_name,
+ recv_desc.source_to_connect_to.p_ndi_name);
}
+ if (config_most_recent.bandwidth !=
+ config_last_used.bandwidth) {
+ config_last_used.bandwidth =
+ config_most_recent.bandwidth;
+
+ reset_recv_desc = &recv_desc;
+ switch (config_most_recent.bandwidth) {
+ case PROP_BW_HIGHEST:
+ default:
+ recv_desc.bandwidth =
+ NDIlib_recv_bandwidth_highest;
+ break;
+ case PROP_BW_LOWEST:
+ recv_desc.bandwidth =
+ NDIlib_recv_bandwidth_lowest;
+ break;
+ case PROP_BW_AUDIO_ONLY:
+ recv_desc.bandwidth =
+ NDIlib_recv_bandwidth_audio_only;
+ obs_source_output_video(obs_source,
+ blank_video_frame());
+ break;
+ }
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' bandwidth changed; Setting recv_desc.bandwidth='%d'",
+ obs_source_ndi_receiver_name, recv_desc.bandwidth);
+ }
+ if (config_most_recent.latency != config_last_used.latency) {
+ config_last_used.latency = config_most_recent.latency;
+
+ reset_recv_desc = &recv_desc;
+ if (config_most_recent.latency == PROP_LATENCY_NORMAL)
+ recv_desc.color_format =
+ NDIlib_recv_color_format_UYVY_BGRA;
+ else
+ recv_desc.color_format =
+ NDIlib_recv_color_format_fastest;
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' latency changed; Setting recv_desc.color_format='%d'",
+ obs_source_ndi_receiver_name,
+ recv_desc.color_format);
+ }
+ if (config_most_recent.framesync_enabled !=
+ config_last_used.framesync_enabled) {
+ config_last_used.framesync_enabled =
+ config_most_recent.framesync_enabled;
+
+ reset_recv_desc = &recv_desc;
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' framesync changed to %s",
+ obs_source_ndi_receiver_name,
+ config_most_recent.framesync_enabled ? "enabled"
+ : "disabled");
+ }
+ if (reset_recv_desc) {
+ reset_recv_desc = nullptr;
- frame_received =
- ndiLib->recv_capture_v3(s->ndi_receiver, &video_frame,
- &audio_frame, nullptr, 100);
-
- if (frame_received == NDIlib_frame_type_audio) {
- if (s->audio_enabled) {
- const int channelCount =
- audio_frame.no_channels > 8
- ? 8
- : audio_frame.no_channels;
-
- obs_audio_frame.speakers =
- channel_count_to_layout(channelCount);
-
- switch (s->sync_mode) {
- case PROP_SYNC_NDI_TIMESTAMP:
- obs_audio_frame.timestamp =
- (uint64_t)(audio_frame.timestamp *
- 100);
- break;
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' Resetting NDI receiver...",
+ obs_source_ndi_receiver_name);
- case PROP_SYNC_NDI_SOURCE_TIMECODE:
- obs_audio_frame.timestamp =
- (uint64_t)(audio_frame.timecode *
- 100);
+ if (ndi_frame_sync) {
+ ndiLib->framesync_destroy(ndi_frame_sync);
+ ndi_frame_sync = nullptr;
+ }
+
+ if (ndi_receiver) {
+#if 1
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' ndiLib->recv_destroy(ndi_receiver)",
+ obs_source_ndi_receiver_name);
+#endif
+ ndiLib->recv_destroy(ndi_receiver);
+ ndi_receiver = nullptr;
+ }
+#if 1
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' +ndi_receiver = ndiLib->recv_create_v3(&recv_desc)",
+ obs_source_ndi_receiver_name);
+#endif
+ ndi_receiver = ndiLib->recv_create_v3(&recv_desc);
+#if 1
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' -ndi_receiver = ndiLib->recv_create_v3(&recv_desc)",
+ obs_source_ndi_receiver_name);
+#endif
+ if (!ndi_receiver) {
+ blog(LOG_ERROR,
+ "[obs-ndi] ndi_source_thread: '%s' Cannot create ndi_receiver for NDI source '%s'",
+ obs_source_ndi_receiver_name,
+ recv_desc.source_to_connect_to.p_ndi_name);
+ break;
+ }
+
+ if (config_most_recent.framesync_enabled) {
+ timestamp_audio = 0;
+ timestamp_video = 0;
+
+#if 1
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' +ndi_frame_sync = ndiLib->framesync_create(ndi_receiver)",
+ obs_source_ndi_receiver_name);
+#endif
+ ndi_frame_sync =
+ ndiLib->framesync_create(ndi_receiver);
+#if 1
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' -ndi_frame_sync = ndiLib->framesync_create(ndi_receiver); ndi_frame_sync=%p",
+ obs_source_ndi_receiver_name,
+ ndi_frame_sync);
+#endif
+ if (!ndi_frame_sync) {
+ blog(LOG_ERROR,
+ "[obs-ndi] ndi_source_thread: '%s' Cannot create ndi_frame_sync for NDI source '%s'",
+ obs_source_ndi_receiver_name,
+ recv_desc.source_to_connect_to
+ .p_ndi_name);
break;
}
+ }
+ }
+
+ if (ndiLib->recv_get_no_connections(ndi_receiver) == 0) {
+#if 0
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' No connection",
+ obs_source_ndi_receiver_name);
+#endif
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(100));
+ continue;
+ }
- obs_audio_frame.samples_per_sec =
- audio_frame.sample_rate;
- obs_audio_frame.format =
- AUDIO_FORMAT_FLOAT_PLANAR;
- obs_audio_frame.frames = audio_frame.no_samples;
+ if (config_most_recent.hw_accel_enabled !=
+ config_last_used.hw_accel_enabled) {
+ config_last_used.hw_accel_enabled =
+ config_most_recent.hw_accel_enabled;
- for (int i = 0; i < channelCount; ++i) {
- obs_audio_frame.data[i] =
- (uint8_t *)audio_frame.p_data +
- i * audio_frame.channel_stride_in_bytes;
+ NDIlib_metadata_frame_t hwAccelMetadata;
+ hwAccelMetadata.p_data =
+ config_most_recent.hw_accel_enabled
+ ? (char *)""
+ : (char *)"";
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' hw_accel_enabled changed; Sending NDI metadata '%s'",
+ obs_source_ndi_receiver_name,
+ hwAccelMetadata.p_data);
+ ndiLib->recv_send_metadata(ndi_receiver,
+ &hwAccelMetadata);
+ }
+
+ if (config_most_recent.ptz.enabled) {
+ const static float tollerance = 0.001f;
+ if (fabs(config_most_recent.ptz.pan -
+ config_last_used.ptz.pan) > tollerance ||
+ fabs(config_most_recent.ptz.tilt -
+ config_last_used.ptz.tilt) > tollerance ||
+ fabs(config_most_recent.ptz.zoom -
+ config_last_used.ptz.zoom) > tollerance) {
+ if (ndiLib->recv_ptz_is_supported(
+ ndi_receiver)) {
+ config_last_used.ptz =
+ config_most_recent.ptz;
+
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' ptz changed; Sending PTZ pan=%f, tilt=%f, zoom=%f",
+ obs_source_ndi_receiver_name,
+ config_most_recent.ptz.pan,
+ config_most_recent.ptz.tilt,
+ config_most_recent.ptz.zoom);
+ ndiLib->recv_ptz_pan_tilt(
+ ndi_receiver,
+ config_most_recent.ptz.pan,
+ config_most_recent.ptz.tilt);
+ ndiLib->recv_ptz_zoom(
+ ndi_receiver,
+ config_most_recent.ptz.zoom);
}
+ }
+ }
+
+ if (config_most_recent.tally.on_preview !=
+ config_last_used.tally.on_preview ||
+ config_most_recent.tally.on_program !=
+ config_last_used.tally.on_program) {
+ config_last_used.tally = config_most_recent.tally;
+
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread: '%s' tally changed; Sending tally on_preview=%d, on_program=%d",
+ obs_source_ndi_receiver_name,
+ config_most_recent.tally.on_preview,
+ config_most_recent.tally.on_program);
+ ndiLib->recv_set_tally(ndi_receiver,
+ &config_most_recent.tally);
+ }
+
+ if (ndi_frame_sync) {
+ //
+ // AUDIO
+ //
+ audio_frame2 = {};
+ ndiLib->framesync_capture_audio(
+ ndi_frame_sync, &audio_frame2,
+ 0, // "Your desired sample rate. 0 for “use source”."
+ 0, // "Your desired channel count. 0 for “use source”."
+ 1024);
+ if (audio_frame2.p_data &&
+ (audio_frame2.timestamp > timestamp_audio)) {
+ //blog(LOG_INFO, "a");//udio_frame";
+ timestamp_audio = audio_frame2.timestamp;
+ ndi_source_thread_process_audio2(
+ &config_most_recent, &audio_frame2,
+ obs_source, &obs_audio_frame);
+ }
+ ndiLib->framesync_free_audio(ndi_frame_sync,
+ &audio_frame2);
+
+ //
+ // VIDEO
+ //
+ video_frame2 = {};
+ ndiLib->framesync_capture_video(
+ ndi_frame_sync, &video_frame2,
+ NDIlib_frame_format_type_progressive);
+ if (video_frame2.p_data &&
+ (video_frame2.timestamp > timestamp_video)) {
+ //blog(LOG_INFO, "v");//ideo_frame";
+ timestamp_video = video_frame2.timestamp;
+ ndi_source_thread_process_video2(
+ &config_most_recent, &video_frame2,
+ obs_source, &obs_video_frame);
+ }
+ ndiLib->framesync_free_video(ndi_frame_sync,
+ &video_frame2);
- obs_source_output_audio(s->source,
- &obs_audio_frame);
+ // TODO: More accurate sleep that subtracts the duration of this loop iteration?
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(5));
+ } else {
+ frame_received = ndiLib->recv_capture_v3(ndi_receiver,
+ &video_frame2,
+ &audio_frame3,
+ nullptr, 100);
+
+ if (frame_received == NDIlib_frame_type_audio) {
+ ndi_source_thread_process_audio3(
+ &config_most_recent, &audio_frame3,
+ obs_source, &obs_audio_frame);
+ ndiLib->recv_free_audio_v3(ndi_receiver,
+ &audio_frame3);
+ continue;
+ }
+
+ if (frame_received == NDIlib_frame_type_video) {
+ ndi_source_thread_process_video2(
+ &config_most_recent, &video_frame2,
+ obs_source, &obs_video_frame);
+ ndiLib->recv_free_video_v2(ndi_receiver,
+ &video_frame2);
+ continue;
}
- ndiLib->recv_free_audio_v3(s->ndi_receiver,
- &audio_frame);
}
+ }
- if (frame_received == NDIlib_frame_type_video) {
- switch (video_frame.FourCC) {
- case NDIlib_FourCC_type_BGRA:
- obs_video_frame.format = VIDEO_FORMAT_BGRA;
- break;
+ if (ndi_frame_sync) {
+ ndiLib->framesync_destroy(ndi_frame_sync);
+ ndi_frame_sync = nullptr;
+ }
- case NDIlib_FourCC_type_BGRX:
- obs_video_frame.format = VIDEO_FORMAT_BGRX;
- break;
+ if (ndi_receiver) {
+ ndiLib->recv_destroy(ndi_receiver);
+ ndi_receiver = nullptr;
+ }
- case NDIlib_FourCC_type_RGBA:
- case NDIlib_FourCC_type_RGBX:
- obs_video_frame.format = VIDEO_FORMAT_RGBA;
- break;
+ blog(LOG_INFO, "[obs-ndi] -ndi_source_thread('%s'...)",
+ obs_source_ndi_receiver_name);
- case NDIlib_FourCC_type_UYVY:
- case NDIlib_FourCC_type_UYVA:
- obs_video_frame.format = VIDEO_FORMAT_UYVY;
- break;
+ return nullptr;
+}
- case NDIlib_FourCC_type_I420:
- obs_video_frame.format = VIDEO_FORMAT_I420;
- break;
+void ndi_source_thread_process_audio2(ndi_source_config_t *config,
+ NDIlib_audio_frame_v2_t *ndi_audio_frame2,
+ obs_source_t *obs_source,
+ obs_source_audio *obs_audio_frame)
+{
+ if (!config->audio_enabled) {
+ return;
+ }
- case NDIlib_FourCC_type_NV12:
- obs_video_frame.format = VIDEO_FORMAT_NV12;
- break;
+ const int channelCount = ndi_audio_frame2->no_channels > 8
+ ? 8
+ : ndi_audio_frame2->no_channels;
- default:
- blog(LOG_INFO,
- "warning: unsupported video pixel format: %d",
- video_frame.FourCC);
- break;
- }
+ obs_audio_frame->speakers = channel_count_to_layout(channelCount);
- switch (s->sync_mode) {
- case PROP_SYNC_NDI_TIMESTAMP:
- obs_video_frame.timestamp =
- (uint64_t)(video_frame.timestamp * 100);
- break;
+ switch (config->sync_mode) {
+ case PROP_SYNC_NDI_TIMESTAMP:
+ obs_audio_frame->timestamp =
+ (uint64_t)(ndi_audio_frame2->timestamp * 100);
+ break;
- case PROP_SYNC_NDI_SOURCE_TIMECODE:
- obs_video_frame.timestamp =
- (uint64_t)(video_frame.timecode * 100);
- break;
- }
+ case PROP_SYNC_NDI_SOURCE_TIMECODE:
+ obs_audio_frame->timestamp =
+ (uint64_t)(ndi_audio_frame2->timecode * 100);
+ break;
+ }
- obs_video_frame.width = video_frame.xres;
- obs_video_frame.height = video_frame.yres;
- obs_video_frame.linesize[0] =
- video_frame.line_stride_in_bytes;
- obs_video_frame.data[0] = video_frame.p_data;
-
- video_format_get_parameters(
- s->yuv_colorspace, s->yuv_range,
- obs_video_frame.color_matrix,
- obs_video_frame.color_range_min,
- obs_video_frame.color_range_max);
-
- obs_source_output_video(s->source, &obs_video_frame);
- ndiLib->recv_free_video_v2(s->ndi_receiver,
- &video_frame);
- }
+ obs_audio_frame->samples_per_sec = ndi_audio_frame2->sample_rate;
+ obs_audio_frame->format = AUDIO_FORMAT_FLOAT_PLANAR;
+ obs_audio_frame->frames = ndi_audio_frame2->no_samples;
+ for (int i = 0; i < channelCount; ++i) {
+ obs_audio_frame->data[i] =
+ (uint8_t *)ndi_audio_frame2->p_data +
+ (i * ndi_audio_frame2->channel_stride_in_bytes);
}
- os_end_high_performance(s->perf_token);
- s->perf_token = NULL;
+ obs_source_output_audio(obs_source, obs_audio_frame);
+}
- blog(LOG_INFO, "audio thread for '%s' completed",
- obs_source_get_name(s->source));
- return nullptr;
+void ndi_source_thread_process_audio3(ndi_source_config_t *config,
+ NDIlib_audio_frame_v3_t *ndi_audio_frame3,
+ obs_source_t *obs_source,
+ obs_source_audio *obs_audio_frame)
+{
+ if (!config->audio_enabled) {
+ return;
+ }
+
+ const int channelCount = ndi_audio_frame3->no_channels > 8
+ ? 8
+ : ndi_audio_frame3->no_channels;
+
+ obs_audio_frame->speakers = channel_count_to_layout(channelCount);
+
+ switch (config->sync_mode) {
+ case PROP_SYNC_NDI_TIMESTAMP:
+ obs_audio_frame->timestamp =
+ (uint64_t)(ndi_audio_frame3->timestamp * 100);
+ break;
+
+ case PROP_SYNC_NDI_SOURCE_TIMECODE:
+ obs_audio_frame->timestamp =
+ (uint64_t)(ndi_audio_frame3->timecode * 100);
+ break;
+ }
+
+ obs_audio_frame->samples_per_sec = ndi_audio_frame3->sample_rate;
+ obs_audio_frame->format = AUDIO_FORMAT_FLOAT_PLANAR;
+ obs_audio_frame->frames = ndi_audio_frame3->no_samples;
+ for (int i = 0; i < channelCount; ++i) {
+ obs_audio_frame->data[i] =
+ (uint8_t *)ndi_audio_frame3->p_data +
+ (i * ndi_audio_frame3->channel_stride_in_bytes);
+ }
+
+ obs_source_output_audio(obs_source, obs_audio_frame);
}
-void ndi_source_update(void *data, obs_data_t *settings)
+void ndi_source_thread_process_video2(ndi_source_config_t *config,
+ NDIlib_video_frame_v2_t *ndi_video_frame,
+ obs_source *obs_source,
+ obs_source_frame *obs_video_frame)
{
- auto s = (struct ndi_source *)data;
-
- const char *ndi_name = obs_data_get_string(settings, PROP_SOURCE);
- long long bandwidth = obs_data_get_int(settings, PROP_BANDWIDTH);
- bool hwAccelEnabled = obs_data_get_bool(settings, PROP_HW_ACCEL);
- if (!s->ndi_name || strcmp(ndi_name, s->ndi_name) != 0 ||
- s->bandwidth != bandwidth) {
- s->bandwidth = bandwidth;
- bfree(s->ndi_name);
- s->ndi_name = bstrdup(ndi_name);
- if (s->running) {
- s->running = false;
- pthread_join(s->av_thread, NULL);
- }
- s->running = false;
- ndiLib->recv_destroy(s->ndi_receiver);
-
- NDIlib_recv_create_v3_t recv_desc;
- recv_desc.source_to_connect_to.p_ndi_name = ndi_name;
- recv_desc.allow_video_fields = true;
- recv_desc.color_format = NDIlib_recv_color_format_UYVY_BGRA;
-
- switch (obs_data_get_int(settings, PROP_BANDWIDTH)) {
- case PROP_BW_HIGHEST:
- default:
- recv_desc.bandwidth = NDIlib_recv_bandwidth_highest;
- break;
- case PROP_BW_LOWEST:
- recv_desc.bandwidth = NDIlib_recv_bandwidth_lowest;
- break;
- case PROP_BW_AUDIO_ONLY:
- recv_desc.bandwidth = NDIlib_recv_bandwidth_audio_only;
- obs_source_output_video(s->source, blank_video_frame());
- break;
- }
+ switch (ndi_video_frame->FourCC) {
+ case NDIlib_FourCC_type_BGRA:
+ obs_video_frame->format = VIDEO_FORMAT_BGRA;
+ break;
- s->ndi_receiver = ndiLib->recv_create_v3(&recv_desc);
- if (s->ndi_receiver) {
- if (hwAccelEnabled) {
- NDIlib_metadata_frame_t hwAccelMetadata;
- hwAccelMetadata.p_data =
- (char *)"";
- ndiLib->recv_send_metadata(s->ndi_receiver,
- &hwAccelMetadata);
- }
- s->hwAccelEnabled = hwAccelEnabled;
+ case NDIlib_FourCC_type_BGRX:
+ obs_video_frame->format = VIDEO_FORMAT_BGRX;
+ break;
- s->running = true;
- pthread_create(&s->av_thread, nullptr,
- ndi_source_poll_audio_video, data);
+ case NDIlib_FourCC_type_RGBA:
+ case NDIlib_FourCC_type_RGBX:
+ obs_video_frame->format = VIDEO_FORMAT_RGBA;
+ break;
- blog(LOG_INFO, "started A/V threads for source '%s'",
- recv_desc.source_to_connect_to.p_ndi_name);
+ case NDIlib_FourCC_type_UYVY:
+ case NDIlib_FourCC_type_UYVA:
+ obs_video_frame->format = VIDEO_FORMAT_UYVY;
+ break;
- // Update tally status
- Config *conf = Config::Current();
- s->tally.on_preview = conf->TallyPreviewEnabled &&
- obs_source_showing(s->source);
- s->tally.on_program = conf->TallyProgramEnabled &&
- obs_source_active(s->source);
- ndiLib->recv_set_tally(s->ndi_receiver, &s->tally);
- } else {
- blog(LOG_ERROR,
- "can't create a receiver for NDI source '%s'",
- ndi_name);
- }
+ case NDIlib_FourCC_type_I420:
+ obs_video_frame->format = VIDEO_FORMAT_I420;
+ break;
+
+ case NDIlib_FourCC_type_NV12:
+ obs_video_frame->format = VIDEO_FORMAT_NV12;
+ break;
+
+ default:
+ blog(LOG_INFO,
+ "[obs-ndi] warning: unsupported video pixel format: %d",
+ ndi_video_frame->FourCC);
+ break;
}
- if (s->ndi_receiver) {
- if (s->hwAccelEnabled != hwAccelEnabled) {
- NDIlib_metadata_frame_t hwAccelMetadata;
- hwAccelMetadata.p_data =
- hwAccelEnabled
- ? (char *)""
- : (char *)"";
- ndiLib->recv_send_metadata(s->ndi_receiver,
- &hwAccelMetadata);
- s->hwAccelEnabled = hwAccelEnabled;
- }
- ndi_source_ptz(s, settings);
+ switch (config->sync_mode) {
+ case PROP_SYNC_NDI_TIMESTAMP:
+ obs_video_frame->timestamp =
+ (uint64_t)(ndi_video_frame->timestamp * 100);
+ break;
+
+ case PROP_SYNC_NDI_SOURCE_TIMECODE:
+ obs_video_frame->timestamp =
+ (uint64_t)(ndi_video_frame->timecode * 100);
+ break;
}
- s->alpha_filter_enabled = obs_data_get_bool(settings, PROP_FIX_ALPHA);
- // Don't persist this value in settings
- obs_data_set_bool(settings, PROP_FIX_ALPHA, false);
+ obs_video_frame->width = ndi_video_frame->xres;
+ obs_video_frame->height = ndi_video_frame->yres;
+ obs_video_frame->linesize[0] = ndi_video_frame->line_stride_in_bytes;
+ obs_video_frame->data[0] = ndi_video_frame->p_data;
- if (s->alpha_filter_enabled) {
- obs_source_t *existing_filter =
- find_filter_by_id(s->source, OBS_NDI_ALPHA_FILTER_ID);
+ video_format_get_parameters(config->yuv_colorspace, config->yuv_range,
+ obs_video_frame->color_matrix,
+ obs_video_frame->color_range_min,
+ obs_video_frame->color_range_max);
+
+ obs_source_output_video(obs_source, obs_video_frame);
+}
+
+void ndi_source_thread_start(ndi_source_t *s)
+{
+ s->running = true;
+ pthread_create(&s->av_thread, nullptr, ndi_source_thread, s);
+ blog(LOG_INFO,
+ "[obs-ndi] ndi_source_thread_start: '%s' Started A/V ndi_source_thread for NDI source '%s'",
+ s->config.ndi_receiver_name.constData(),
+ s->config.ndi_source_name.constData());
+}
+
+void ndi_source_thread_stop(ndi_source_t *s)
+{
+ if (s->running) {
+ s->running = false;
+ pthread_join(s->av_thread, NULL);
+ }
+}
+
+void ndi_source_update(void *data, obs_data_t *settings)
+{
+ auto s = (ndi_source_t *)data;
+ auto obs_source = s->obs_source;
+ auto name = obs_source_get_name(obs_source);
+ blog(LOG_INFO, "[obs-ndi] +ndi_source_update('%s'...)", name);
+ ndi_source_config_t config = s->config;
+
+ config.ndi_source_name = obs_data_get_string(settings, PROP_SOURCE);
+ config.bandwidth = (int)obs_data_get_int(settings, PROP_BANDWIDTH);
+
+ config.sync_mode = (int)obs_data_get_int(settings, PROP_SYNC);
+ // if sync mode is set to the unsupported "Internal" mode, set it
+ // to "Source Timing" mode and apply that change to the settings data
+ if (config.sync_mode == PROP_SYNC_INTERNAL) {
+ config.sync_mode = PROP_SYNC_NDI_SOURCE_TIMECODE;
+ obs_data_set_int(settings, PROP_SYNC,
+ PROP_SYNC_NDI_SOURCE_TIMECODE);
+ }
+
+ config.framesync_enabled = obs_data_get_bool(settings, PROP_FRAMESYNC);
+
+ config.hw_accel_enabled = obs_data_get_bool(settings, PROP_HW_ACCEL);
+
+ bool alpha_filter_enabled = obs_data_get_bool(settings, PROP_FIX_ALPHA);
+ // Prevent duplicate filters by not persisting this value in settings
+ obs_data_set_bool(settings, PROP_FIX_ALPHA, false);
+ if (alpha_filter_enabled) {
+ obs_source_t *existing_filter =
+ find_filter_by_id(obs_source, OBS_NDI_ALPHA_FILTER_ID);
if (!existing_filter) {
obs_source_t *new_filter = obs_source_create(
OBS_NDI_ALPHA_FILTER_ID,
obs_module_text(
"NDIPlugin.PremultipliedAlphaFilterName"),
nullptr, nullptr);
- obs_source_filter_add(s->source, new_filter);
+ obs_source_filter_add(obs_source, new_filter);
obs_source_release(new_filter);
}
}
- s->sync_mode = (int)obs_data_get_int(settings, PROP_SYNC);
- // if sync mode is set to the unsupported "Internal" mode, set it
- // to "Source Timing" mode and apply that change to the settings data
- if (s->sync_mode == PROP_SYNC_INTERNAL) {
- s->sync_mode = PROP_SYNC_NDI_SOURCE_TIMECODE;
- obs_data_set_int(settings, PROP_SYNC,
- PROP_SYNC_NDI_SOURCE_TIMECODE);
- }
-
- s->yuv_range = prop_to_range_type(
+ config.yuv_range = prop_to_range_type(
(int)obs_data_get_int(settings, PROP_YUV_RANGE));
- s->yuv_colorspace = prop_to_colorspace(
+ config.yuv_colorspace = prop_to_colorspace(
(int)obs_data_get_int(settings, PROP_YUV_COLORSPACE));
- const bool is_unbuffered =
- (obs_data_get_int(settings, PROP_LATENCY) == PROP_LATENCY_LOW);
- obs_source_set_async_unbuffered(s->source, is_unbuffered);
+ config.latency = (int)obs_data_get_int(settings, PROP_LATENCY);
+ // Disable OBS buffering only for "Lowest" latency mode
+ const bool is_unbuffered = (config.latency == PROP_LATENCY_LOWEST);
+ obs_source_set_async_unbuffered(obs_source, is_unbuffered);
+
+ config.audio_enabled = obs_data_get_bool(settings, PROP_AUDIO);
+ obs_source_set_audio_active(obs_source, config.audio_enabled);
+
+ bool ptz_enabled = obs_data_get_bool(settings, PROP_PTZ);
+ float pan = (float)obs_data_get_double(settings, PROP_PAN);
+ float tilt = (float)obs_data_get_double(settings, PROP_TILT);
+ float zoom = (float)obs_data_get_double(settings, PROP_ZOOM);
+ config.ptz = {ptz_enabled, pan, tilt, zoom};
+
+ // Update tally status
+ Config *conf = Config::Current();
+ config.tally.on_preview = conf->TallyPreviewEnabled &&
+ obs_source_showing(obs_source);
+ config.tally.on_program = conf->TallyProgramEnabled &&
+ obs_source_active(obs_source);
+
+ s->config = config;
+
+ if (!config.ndi_source_name.isEmpty()) {
+ if (!s->running) {
+ ndi_source_thread_start(s);
+ }
+ } else {
+ ndi_source_thread_stop(s);
+ }
- s->audio_enabled = obs_data_get_bool(settings, PROP_AUDIO);
- obs_source_set_audio_active(s->source, s->audio_enabled);
+ blog(LOG_INFO, "[obs-ndi] -ndi_source_update('%s'...)", name);
}
void ndi_source_shown(void *data)
{
- auto s = (struct ndi_source *)data;
-
- if (s->ndi_receiver) {
- s->tally.on_preview = (Config::Current())->TallyPreviewEnabled;
- ndiLib->recv_set_tally(s->ndi_receiver, &s->tally);
- }
+ auto s = (ndi_source_t *)data;
+ auto name = obs_source_get_name(s->obs_source);
+ blog(LOG_INFO, "[obs-ndi] ndi_source_shown('%s'...)", name);
+ s->config.tally.on_preview = (Config::Current())->TallyPreviewEnabled;
}
void ndi_source_hidden(void *data)
{
- auto s = (struct ndi_source *)data;
-
- if (s->ndi_receiver) {
- s->tally.on_preview = false;
- ndiLib->recv_set_tally(s->ndi_receiver, &s->tally);
- }
+ auto s = (ndi_source_t *)data;
+ auto name = obs_source_get_name(s->obs_source);
+ blog(LOG_INFO, "[obs-ndi] ndi_source_hidden('%s'...)", name);
+ s->config.tally.on_preview = false;
}
void ndi_source_activated(void *data)
{
- auto s = (struct ndi_source *)data;
-
- if (s->ndi_receiver) {
- s->tally.on_program = (Config::Current())->TallyProgramEnabled;
- ndiLib->recv_set_tally(s->ndi_receiver, &s->tally);
- }
+ auto s = (ndi_source_t *)data;
+ auto name = obs_source_get_name(s->obs_source);
+ blog(LOG_INFO, "[obs-ndi] ndi_source_activated('%s'...)", name);
+ s->config.tally.on_program = (Config::Current())->TallyProgramEnabled;
}
void ndi_source_deactivated(void *data)
{
- auto s = (struct ndi_source *)data;
+ auto s = (ndi_source_t *)data;
+ auto name = obs_source_get_name(s->obs_source);
+ blog(LOG_INFO, "[obs-ndi] ndi_source_deactivated('%s'...)", name);
+ s->config.tally.on_program = false;
+}
- if (s->ndi_receiver) {
- s->tally.on_program = false;
- ndiLib->recv_set_tally(s->ndi_receiver, &s->tally);
- }
+void ndi_source_renamed(void *data, calldata_t *)
+{
+ auto s = (ndi_source_t *)data;
+ auto name = obs_source_get_name(s->obs_source);
+ blog(LOG_INFO, "[obs-ndi] ndi_source_renamed: name='%s'", name);
+ s->config.ndi_receiver_name =
+ QString("OBS-NDI '%1'").arg(name).toUtf8();
}
-void *ndi_source_create(obs_data_t *settings, obs_source_t *source)
+void *ndi_source_create(obs_data_t *settings, obs_source_t *obs_source)
{
- auto s = (struct ndi_source *)bzalloc(sizeof(struct ndi_source));
- s->source = source;
- s->running = false;
- s->perf_token = NULL;
+ auto name = obs_source_get_name(obs_source);
+ blog(LOG_INFO, "[obs-ndi] +ndi_source_create('%s'...)", name);
+
+ auto s = (ndi_source_t *)bzalloc(sizeof(ndi_source_t));
+ s->obs_source = obs_source;
+ s->config.ndi_receiver_name =
+ QString("OBS-NDI '%1'").arg(name).toUtf8();
+
+ auto sh = obs_source_get_signal_handler(s->obs_source);
+ signal_handler_connect(sh, "rename", ndi_source_renamed, s);
+
ndi_source_update(s, settings);
+
+ blog(LOG_INFO, "[obs-ndi] -ndi_source_create('%s'...)", name);
+
return s;
}
void ndi_source_destroy(void *data)
{
- auto s = (struct ndi_source *)data;
- s->running = false;
- pthread_join(s->av_thread, NULL);
- ndiLib->recv_destroy(s->ndi_receiver);
- bfree(s->ndi_name);
+ auto s = (ndi_source_t *)data;
+ auto name = obs_source_get_name(s->obs_source);
+ blog(LOG_INFO, "[obs-ndi] +ndi_source_destroy('%s'...)", name);
+
+ signal_handler_disconnect(obs_source_get_signal_handler(s->obs_source),
+ "rename", ndi_source_renamed, s);
+
+ ndi_source_thread_stop(s);
+
bfree(s);
+
+ blog(LOG_INFO, "[obs-ndi] -ndi_source_destroy('%s'...)", name);
}
-struct obs_source_info create_ndi_source_info()
+obs_source_info create_ndi_source_info()
{
- struct obs_source_info ndi_source_info = {};
+ obs_source_info ndi_source_info = {};
ndi_source_info.id = "ndi_source";
ndi_source_info.type = OBS_SOURCE_TYPE_INPUT;
ndi_source_info.output_flags = OBS_SOURCE_ASYNC_VIDEO |
OBS_SOURCE_AUDIO |
OBS_SOURCE_DO_NOT_DUPLICATE;
+
ndi_source_info.get_name = ndi_source_getname;
ndi_source_info.get_properties = ndi_source_getproperties;
ndi_source_info.get_defaults = ndi_source_getdefaults;
- ndi_source_info.update = ndi_source_update;
+
+ ndi_source_info.create = ndi_source_create;
+ ndi_source_info.activate = ndi_source_activated;
ndi_source_info.show = ndi_source_shown;
+ ndi_source_info.update = ndi_source_update;
ndi_source_info.hide = ndi_source_hidden;
- ndi_source_info.activate = ndi_source_activated;
ndi_source_info.deactivate = ndi_source_deactivated;
- ndi_source_info.create = ndi_source_create;
ndi_source_info.destroy = ndi_source_destroy;
return ndi_source_info;
diff --git a/src/plugin-main.cpp b/src/plugin-main.cpp
index 18e179ed..38c308e8 100644
--- a/src/plugin-main.cpp
+++ b/src/plugin-main.cpp
@@ -45,6 +45,16 @@ with this program. If not, see
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US")
+const char *obs_module_name()
+{
+ return "obs-ndi";
+}
+
+const char *obs_module_description()
+{
+ return "NDI input/output integration for OBS Studio";
+}
+
const NDIlib_v4 *ndiLib = nullptr;
extern struct obs_source_info create_ndi_source_info();
@@ -73,16 +83,19 @@ OutputSettings *output_settings = nullptr;
bool obs_module_load(void)
{
- obs_log(LOG_INFO, "obs_module_load: hello ! (version %s)",
- PLUGIN_VERSION);
+ blog(LOG_INFO,
+ "[obs-ndi] obs_module_load: you can haz obs-ndi (Version %s)",
+ PLUGIN_VERSION);
+ blog(LOG_INFO, "Qt Version: %s (runtime), %s (compiled)", qVersion(),
+ QT_VERSION_STR);
QMainWindow *main_window =
(QMainWindow *)obs_frontend_get_main_window();
ndiLib = load_ndilib();
if (!ndiLib) {
- obs_log(LOG_ERROR,
- "obs_module_load: load_ndilib() failed; Module won't load.");
+ blog(LOG_ERROR,
+ "[obs-ndi] obs_module_load: load_ndilib() failed; Module won't load.");
const char *msg_string_name = "";
#ifdef _MSC_VER
@@ -107,14 +120,14 @@ bool obs_module_load(void)
}
if (!ndiLib->initialize()) {
- obs_log(LOG_ERROR,
- "obs_module_load: ndiLib->initialize() failed; CPU unsupported by NDI library. Module won't load.");
+ blog(LOG_ERROR,
+ "[obs-ndi] obs_module_load: ndiLib->initialize() failed; CPU unsupported by NDI library. Module won't load.");
return false;
}
- obs_log(LOG_INFO,
- "obs_module_load: NDI library initialized successfully (%s)",
- ndiLib->version());
+ blog(LOG_INFO,
+ "[obs-ndi] obs_module_load: NDI library initialized successfully ('%s')",
+ ndiLib->version());
NDIlib_find_create_t find_desc = {0};
find_desc.show_local_sources = true;
@@ -194,9 +207,14 @@ bool obs_module_load(void)
return true;
}
-void obs_module_unload()
+void obs_module_post_load(void)
{
- obs_log(LOG_INFO, "+obs_module_unload()");
+ blog(LOG_INFO, "[obs-ndi] obs_module_post_load: ...");
+}
+
+void obs_module_unload(void)
+{
+ blog(LOG_INFO, "[obs-ndi] +obs_module_unload()");
if (ndiLib) {
if (ndi_finder) {
@@ -211,19 +229,9 @@ void obs_module_unload()
delete loaded_lib;
}
- obs_log(LOG_INFO, "obs_module_unload: goodbye !");
-
- obs_log(LOG_INFO, "-obs_module_unload()");
-}
+ blog(LOG_INFO, "[obs-ndi] obs_module_unload: goodbye !");
-const char *obs_module_name()
-{
- return "obs-ndi";
-}
-
-const char *obs_module_description()
-{
- return "NDI input/output integration for OBS Studio";
+ blog(LOG_INFO, "[obs-ndi] -obs_module_unload()");
}
const NDIlib_v4 *load_ndilib()
@@ -240,28 +248,28 @@ const NDIlib_v4 *load_ndilib()
for (QString location : locations) {
path = QDir::cleanPath(
QDir(location).absoluteFilePath(NDILIB_LIBRARY_NAME));
- obs_log(LOG_INFO, "load_ndilib: Trying '%s'",
- path.toUtf8().constData());
+ blog(LOG_INFO, "[obs-ndi] load_ndilib: Trying '%s'",
+ path.toUtf8().constData());
QFileInfo libPath(path);
if (libPath.exists() && libPath.isFile()) {
path = libPath.absoluteFilePath();
- obs_log(LOG_INFO,
- "load_ndilib: Found NDI library at '%s'",
- path.toUtf8().constData());
+ blog(LOG_INFO,
+ "[obs-ndi] load_ndilib: Found NDI library at '%s'",
+ path.toUtf8().constData());
loaded_lib = new QLibrary(path, nullptr);
if (loaded_lib->load()) {
- obs_log(LOG_INFO,
- "load_ndilib: NDI runtime loaded successfully");
+ blog(LOG_INFO,
+ "[obs-ndi] load_ndilib: NDI runtime loaded successfully");
NDIlib_v5_load_ lib_load =
(NDIlib_v5_load_)loaded_lib->resolve(
"NDIlib_v5_load");
if (lib_load != nullptr) {
- obs_log(LOG_INFO,
- "load_ndilib: NDIlib_v5_load found");
+ blog(LOG_INFO,
+ "[obs-ndi] load_ndilib: NDIlib_v5_load found");
return lib_load();
} else {
- obs_log(LOG_ERROR,
- "load_ndilib: ERROR: NDIlib_v5_load not found in loaded library");
+ blog(LOG_ERROR,
+ "[obs-ndi] load_ndilib: ERROR: NDIlib_v5_load not found in loaded library");
}
} else {
delete loaded_lib;
@@ -269,6 +277,7 @@ const NDIlib_v4 *load_ndilib()
}
}
}
- obs_log(LOG_ERROR, "load_ndilib: ERROR: Can't find the NDI library");
+ blog(LOG_ERROR,
+ "[obs-ndi] load_ndilib: ERROR: Can't find the NDI library");
return nullptr;
}
diff --git a/src/premultiplied-alpha-filter.cpp b/src/premultiplied-alpha-filter.cpp
index b536666a..4d6804a6 100644
--- a/src/premultiplied-alpha-filter.cpp
+++ b/src/premultiplied-alpha-filter.cpp
@@ -25,28 +25,21 @@ struct alpha_filter {
gs_effect_t *effect;
};
-const char *alpha_filter_getname(void *data)
+const char *alpha_filter_getname(void *)
{
- UNUSED_PARAMETER(data);
return obs_module_text("NDIPlugin.PremultipliedAlphaFilterName");
}
-obs_properties_t *alpha_filter_getproperties(void *data)
+obs_properties_t *alpha_filter_getproperties(void *)
{
- UNUSED_PARAMETER(data);
obs_properties_t *props = obs_properties_create();
return props;
}
-void alpha_filter_update(void *data, obs_data_t *settings)
-{
- UNUSED_PARAMETER(data);
- UNUSED_PARAMETER(settings);
-}
+void alpha_filter_update(void *, obs_data_t *) {}
-void *alpha_filter_create(obs_data_t *settings, obs_source_t *source)
+void *alpha_filter_create(obs_data_t *, obs_source_t *source)
{
- UNUSED_PARAMETER(settings);
struct alpha_filter *s =
(struct alpha_filter *)bzalloc(sizeof(struct alpha_filter));
s->context = source;
@@ -60,9 +53,8 @@ void alpha_filter_destroy(void *data)
bfree(s);
}
-void alpha_filter_videorender(void *data, gs_effect_t *effect)
+void alpha_filter_videorender(void *data, gs_effect_t *)
{
- UNUSED_PARAMETER(effect);
struct alpha_filter *s = (struct alpha_filter *)data;
if (!obs_source_process_filter_begin(s->context, GS_RGBA,
diff --git a/src/preview-output.cpp b/src/preview-output.cpp
index 5850cf3e..1c6fe35f 100644
--- a/src/preview-output.cpp
+++ b/src/preview-output.cpp
@@ -50,6 +50,8 @@ void preview_output_init(const char *default_name)
if (context.output)
return;
+ blog(LOG_INFO, "[obs-ndi] preview_output_init('%s')", default_name);
+
obs_data_t *output_settings = obs_data_create();
obs_data_set_string(output_settings, "ndi_name", default_name);
obs_data_set_bool(output_settings, "uses_audio", false);
@@ -64,7 +66,7 @@ void preview_output_start(const char *output_name)
return;
blog(LOG_INFO,
- "preview_output_start: starting NDI preview output with name '%s'",
+ "[obs-ndi] preview_output_start: starting NDI preview output with name '%s'",
output_name);
obs_get_video_info(&context.ovi);
@@ -82,38 +84,30 @@ void preview_output_start(const char *output_name)
const audio_output_info *mainAOI =
audio_output_get_info(obs_get_audio());
- video_output_info vi = {0};
- vi.name = output_name;
- vi.format = VIDEO_FORMAT_BGRA;
- vi.width = width;
- vi.height = height;
- vi.fps_den = context.ovi.fps_den;
- vi.fps_num = context.ovi.fps_num;
- vi.cache_size = 16;
- vi.colorspace = mainVOI->colorspace;
- vi.range = mainVOI->range;
-
- video_output_open(&context.video_queue, &vi);
-
- audio_output_info ai = {0};
- ai.name = output_name;
- ai.format = mainAOI->format;
- ai.samples_per_sec = mainAOI->samples_per_sec;
- ai.speakers = mainAOI->speakers;
- ai.input_callback = [](void *param, uint64_t start_ts, uint64_t end_ts,
- uint64_t *new_ts, uint32_t active_mixers,
- struct audio_output_data *mixes) {
- UNUSED_PARAMETER(param);
- UNUSED_PARAMETER(start_ts);
- UNUSED_PARAMETER(end_ts);
- UNUSED_PARAMETER(new_ts);
- UNUSED_PARAMETER(active_mixers);
- UNUSED_PARAMETER(mixes);
- return false;
- };
- ai.input_param = nullptr;
-
- audio_output_open(&context.dummy_audio_queue, &ai);
+ video_output_info voi = {0};
+ voi.name = output_name;
+ voi.format = VIDEO_FORMAT_BGRA;
+ voi.width = width;
+ voi.height = height;
+ voi.fps_den = context.ovi.fps_den;
+ voi.fps_num = context.ovi.fps_num;
+ voi.cache_size = 16;
+ voi.colorspace = mainVOI->colorspace;
+ voi.range = mainVOI->range;
+
+ video_output_open(&context.video_queue, &voi);
+
+ audio_output_info aoi = {0};
+ aoi.name = output_name;
+ aoi.format = mainAOI->format;
+ aoi.samples_per_sec = mainAOI->samples_per_sec;
+ aoi.speakers = mainAOI->speakers;
+ aoi.input_callback = [](void *, uint64_t, uint64_t, uint64_t *,
+ uint32_t,
+ struct audio_output_data *) { return false; };
+ aoi.input_param = nullptr;
+
+ audio_output_open(&context.dummy_audio_queue, &aoi);
obs_frontend_add_event_callback(on_preview_scene_changed, &context);
if (obs_frontend_preview_program_mode_active()) {
@@ -135,7 +129,8 @@ void preview_output_start(const char *output_name)
obs_output_start(context.output);
context.enabled = true;
- blog(LOG_INFO, "preview_output_start: started NDI preview output");
+ blog(LOG_INFO,
+ "[obs-ndi] preview_output_start: started NDI preview output");
}
void preview_output_stop()
@@ -143,7 +138,8 @@ void preview_output_stop()
if (!context.enabled)
return;
- blog(LOG_INFO, "preview_output_stop: stopping NDI preview output");
+ blog(LOG_INFO,
+ "[obs-ndi] preview_output_stop: stopping NDI preview output");
obs_output_stop(context.output);
@@ -163,11 +159,14 @@ void preview_output_stop()
context.enabled = false;
- blog(LOG_INFO, "preview_output_stop: stopped NDI preview output");
+ blog(LOG_INFO,
+ "[obs-ndi] preview_output_stop: stopped NDI preview output");
}
void preview_output_deinit()
{
+ blog(LOG_INFO, "[obs-ndi] preview_output_deinit()");
+
obs_output_release(context.output);
context.output = nullptr;
@@ -181,6 +180,7 @@ bool preview_output_is_enabled()
void on_preview_scene_changed(enum obs_frontend_event event, void *param)
{
+ blog(LOG_INFO, "[obs-ndi] on_preview_scene_changed(%d)", event);
auto ctx = (struct preview_output *)param;
switch (event) {
case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED:
@@ -203,13 +203,9 @@ void on_preview_scene_changed(enum obs_frontend_event event, void *param)
}
}
-void render_preview_source(void *param, uint32_t cx, uint32_t cy)
+void render_preview_source(void *param, uint32_t, uint32_t)
{
- UNUSED_PARAMETER(cx);
- UNUSED_PARAMETER(cy);
-
auto ctx = (struct preview_output *)param;
-
if (!ctx->current_source)
return;