Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
name: Release binaries

# Builds the portable Linux binary, checks that it runs on several distributions, and attaches it
# to the GitHub release. Pull requests that touch the packaging build and test it without publishing.

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to attach the binary to (leave empty to only build)"
required: false
pull_request:
paths:
- "packaging/**"
- ".github/workflows/release.yml"
- "CMakeLists.txt"

permissions:
contents: write

jobs:
build:
name: Build portable x86_64 binary
runs-on: ubuntu-24.04
# An old glibc (2.31) as the baseline: the binary runs on Ubuntu 20.04, Debian 11, RHEL 9 and newer.
container: ubuntu:20.04
env:
DEBIAN_FRONTEND: noninteractive
DEPS: /opt/ndistreamer-deps
steps:
- name: Install build tools
run: |
apt-get update
apt-get install -y --no-install-recommends software-properties-common gpg-agent ca-certificates \
curl git xz-utils bzip2 make ninja-build pkg-config python3-pip binutils file
add-apt-repository -y ppa:ubuntu-toolchain-r/test
apt-get update
apt-get install -y --no-install-recommends gcc-13 g++-13
update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-13 100
update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-13 100
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 100
pip3 install --no-cache-dir meson cmake

- uses: actions/checkout@v4

- name: Cache static dependencies
id: cache
uses: actions/cache@v4
with:
path: /opt/ndistreamer-deps
key: static-deps-focal-x86_64-${{ hashFiles('packaging/build-static-deps.sh') }}

- name: Build static dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: packaging/build-static-deps.sh "$DEPS"

- name: Build GoogleTest
run: |
git clone --quiet --depth 1 --branch v1.15.2 https://github.com/google/googletest /tmp/googletest
cmake -S /tmp/googletest -B /tmp/googletest/build -G Ninja -DCMAKE_INSTALL_PREFIX=/opt/googletest
cmake --build /tmp/googletest/build
cmake --install /tmp/googletest/build

- name: Configure
run: >
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
-DNDISTREAMER_STATIC=ON -DNDISTREAMER_WERROR=ON
"-DCMAKE_PREFIX_PATH=$DEPS;/opt/googletest"

- name: Build
run: cmake --build build

- name: Test
run: ctest --test-dir build --output-on-failure --timeout 120

- name: Package
run: packaging/package.sh build/ndistreamer "$DEPS" dist

- uses: actions/upload-artifact@v4
with:
name: release
path: dist/

# The fake NDI runtime and a clip let the smoke tests stream without the real NDI runtime.
- name: Collect smoke test files
run: |
mkdir -p smoke
cp build/tests/fake_ndi/libndi.so.6 build/tests/media/av.mp4 smoke/

- uses: actions/upload-artifact@v4
with:
name: smoke-test-files
path: smoke/

smoke-test:
name: Run on ${{ matrix.image }}
needs: build
runs-on: ubuntu-24.04
container: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
include:
- image: ubuntu:20.04
setup: apt-get update && apt-get install -y --no-install-recommends ca-certificates
- image: debian:12
setup: apt-get update && apt-get install -y --no-install-recommends ca-certificates
- image: rockylinux:9
setup: "true"
- image: fedora:latest
setup: "true"
- image: archlinux:latest
setup: "true"
steps:
- name: Prepare
run: ${{ matrix.setup }}

- uses: actions/download-artifact@v4
with:
name: release
path: dist

- uses: actions/download-artifact@v4
with:
name: smoke-test-files
path: smoke

- name: Run
shell: bash
run: |
set -euxo pipefail
tar -xzf dist/ndistreamer-*-linux-x86_64.tar.gz
bin=$(echo ndistreamer-*-linux-x86_64/ndistreamer)
"$bin" --version

# Without the NDI runtime it must fail cleanly and say why.
if "$bin" -i smoke/av.mp4 --ndi-lib /nonexistent/libndi.so.6 2> err.txt; then exit 1; fi
grep "cannot load the NDI runtime" err.txt

# Decode and send a local file, then a file over HTTPS (system CA bundle detection).
export FAKE_NDI_LOG=$PWD/sent.log
"$bin" -i smoke/av.mp4 --ndi-lib smoke/libndi.so.6 --no-pacing
test "$(grep -c '^video' sent.log)" -eq 50
"$bin" -i https://castus-public-resources.s3.us-west-2.amazonaws.com/content/videos/mp4/rickroll.mp4 \
--ndi-lib smoke/libndi.so.6 --no-pacing
test "$(grep -c '^video' sent.log)" -gt 100

publish:
name: Attach to release
needs: smoke-test
if: github.event_name == 'release' || inputs.tag != ''
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v4
with:
name: release
path: dist

- name: Upload release assets
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.event.release.tag_name || inputs.tag }}
run: gh release upload "$TAG" dist/* --clobber --repo "$GITHUB_REPOSITORY"
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 2.0.1

- **Prebuilt portable binary.** Releases now include `ndistreamer-VERSION-linux-x86_64.tar.gz`. It
runs on any x86-64 Linux with glibc 2.31 or newer (Ubuntu 20.04, Debian 11, RHEL 9, Fedora, Arch
and later). FFmpeg 9.0 and its libraries are linked in statically, with software decoding of all
common codecs including AV1, VAAPI and NVDEC hardware decoding, HTTPS, and V4L2 capture. CI
checks that it runs on Ubuntu, Debian, Rocky Linux, Fedora and Arch.
- New `-DNDISTREAMER_STATIC=ON` build option and `packaging/` scripts to produce the portable
build yourself.
- HTTPS inputs work with FFmpeg builds that use Mbed TLS: the system CA bundle is found
automatically.

## 2.0.0

A ground-up rewrite. The goal is unchanged (stream media as an NDI source from Linux), but the
Expand Down
21 changes: 19 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.20)

project(ndistreamer
VERSION 2.0.0
VERSION 2.0.1
DESCRIPTION "Stream any media file, network stream or capture device as an NDI source"
HOMEPAGE_URL "https://github.com/lowlevelmetal/ndi-streamer"
LANGUAGES CXX)
Expand All @@ -20,6 +20,8 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
option(NDISTREAMER_BUILD_TESTS "Build the test suite (requires GoogleTest)" ${PROJECT_IS_TOP_LEVEL})
option(NDISTREAMER_SANITIZE "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
option(NDISTREAMER_WERROR "Treat compiler warnings as errors" OFF)
option(NDISTREAMER_STATIC
"Link FFmpeg and the C++ runtime statically for a portable binary (see packaging/build-static-deps.sh)" OFF)
set(NDI_SDK_DIR "" CACHE PATH
"Optional NDI SDK root. Not needed to build; enables ABI verification and end-to-end tests")

Expand All @@ -37,6 +39,21 @@ pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET
libswresample>=4)
find_package(Threads REQUIRED)

if(NDISTREAMER_STATIC)
# Static FFmpeg, e.g. from packaging/build-static-deps.sh. Only glibc stays dynamic: the NDI
# runtime is loaded with dlopen, which a fully static executable cannot do.
# libatomic is a GCC runtime library that not every system has, so link its archive too.
list(TRANSFORM FFMPEG_STATIC_LDFLAGS REPLACE "^-latomic$" "-l:libatomic.a")
add_library(ndistreamer_ffmpeg INTERFACE)
target_include_directories(ndistreamer_ffmpeg SYSTEM INTERFACE ${FFMPEG_STATIC_INCLUDE_DIRS})
target_compile_options(ndistreamer_ffmpeg INTERFACE ${FFMPEG_STATIC_CFLAGS_OTHER})
target_link_libraries(ndistreamer_ffmpeg INTERFACE ${FFMPEG_STATIC_LDFLAGS})
set(NDISTREAMER_FFMPEG ndistreamer_ffmpeg)
add_link_options(-static-libstdc++ -static-libgcc)
else()
set(NDISTREAMER_FFMPEG PkgConfig::FFMPEG)
endif()

# Newer FFmpeg lets filter sources carry color properties.
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_INCLUDE_DIRS})
Expand Down Expand Up @@ -83,7 +100,7 @@ target_include_directories(ndistreamer_core PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR}/generated)
target_link_libraries(ndistreamer_core
PUBLIC PkgConfig::FFMPEG Threads::Threads ${CMAKE_DL_LIBS}
PUBLIC ${NDISTREAMER_FFMPEG} Threads::Threads ${CMAKE_DL_LIBS}
PRIVATE ndistreamer_warnings)
target_compile_definitions(ndistreamer_core PRIVATE
$<$<BOOL:${NDISTREAMER_HAVE_BUFFERSRC_COLOR}>:NDISTREAMER_HAVE_BUFFERSRC_COLOR>)
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ built for servers, signage players and test rigs, with no GUI and no X server ne
- **Well behaved.** Ctrl+C or SIGTERM stops cleanly, errors are reported clearly, exit codes are
meaningful, and `--stats` gives periodic statistics.

## Download

Each [release](https://github.com/lowlevelmetal/ndi-streamer/releases) has a prebuilt binary for
x86-64 Linux that runs on any distribution with glibc 2.31 or newer (Ubuntu 20.04, Debian 11,
RHEL 9, Fedora, Arch and later):

```sh
tar -xzf ndistreamer-*-linux-x86_64.tar.gz
./ndistreamer-*-linux-x86_64/ndistreamer --version
```

FFmpeg is built in, so all you need is the NDI runtime (see [Installing the NDI
runtime](#installing-the-ndi-runtime)). The binary decodes all common codecs (AV1 included), supports
VAAPI and NVDEC hardware decoding, and reads files, network streams (including HTTPS) and V4L2
devices. X11 screen capture and ALSA/PulseAudio input need the system's libraries; for those, build
from source.

## Requirements

- Linux on x86-64 or ARM
Expand Down Expand Up @@ -64,6 +81,22 @@ cmake --build build
sudo cmake --install build # optional: installs to /usr/local/bin/ndistreamer
```

### Portable static build

To build the same kind of binary as the releases, compile the static dependencies once, then point
CMake at them:

```sh
packaging/build-static-deps.sh ~/ndistreamer-deps # needs meson, ninja, nasm and cmake
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNDISTREAMER_STATIC=ON \
-DCMAKE_PREFIX_PATH=~/ndistreamer-deps
cmake --build build
packaging/package.sh build/ndistreamer ~/ndistreamer-deps dist # optional: release archive
```

The binary links only against glibc, so build on the oldest distribution you want to support. The
releases are built on Ubuntu 20.04.

### Installing the NDI runtime

Download the NDI SDK for Linux and run its installer. It unpacks into `NDI SDK for Linux/`. Then do
Expand Down
Loading
Loading