Skip to content
Closed
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
3 changes: 3 additions & 0 deletions demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,7 @@ docker compose --env-file demos/dmabuf-browser/.env.example \
a bundled animated page, Electron, GPU capture, Janus, and browser preview.
- [CUDA overlay validation](cuda-overlay/README.md#run): run
`./demos/cuda-overlay/run.sh` to build, generate fixtures, and compare results.
- [MXL round-trip](mxl/README.md): publish into and read back from an MXL
shared-memory domain. Linux-only, uses the shared mixer image
(`avplumber-mixer:local`). GPU optional.

85 changes: 81 additions & 4 deletions demos/mixer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,113 @@ ARG DEBIAN_FRONTEND=noninteractive
ARG AVPLUMBER_REVISION=workspace
ARG FFMPEG_TAG=n7.1.5
ARG NV_CODEC_HEADERS_TAG=n12.1.14.0
ARG MXL_TAG=v1.1.0

# gcc-13 is required by the MXL SDK (C++20 features + newer libstdc++).
# Ubuntu 22.04 defaults to gcc-11, so pull gcc-13 from the ubuntu
# toolchain PPA and keep it alongside; FFmpeg and avplumber continue
# to use gcc-11.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
software-properties-common \
&& add-apt-repository -y ppa:ubuntu-toolchain-r/test \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
autoconf \
automake \
bison \
build-essential \
ca-certificates \
cmake \
curl \
flex \
g++-13 \
gcc-13 \
git \
libboost-all-dev \
libcurl4-openssl-dev \
libgnutls28-dev \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
libpcap-dev \
libssl-dev \
libtool \
libx11-dev \
make \
nasm \
ninja-build \
pkg-config \
python3 \
python3-dev \
python3-numpy \
python3-pil \
python3-pip \
tar \
unzip \
xxd \
zip \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*

# Ubuntu 22.04 ships cmake 3.22 but the MXL SDK requires >= 3.24.
# Upgrade via pip so a modern cmake is on PATH before FFmpeg configure.
RUN python3 -m pip install --no-cache-dir --upgrade 'cmake>=3.28'

RUN git clone --quiet --branch "${NV_CODEC_HEADERS_TAG}" --depth 1 \
https://github.com/FFmpeg/nv-codec-headers.git /tmp/nv-codec-headers \
&& make -C /tmp/nv-codec-headers install PREFIX=/usr/local \
&& rm -rf /tmp/nv-codec-headers

# --- Rust (MXL's ada-url dep pulled through vcpkg needs a rustc) --------
ENV RUSTUP_HOME=/opt/rustup CARGO_HOME=/opt/cargo
ENV PATH=/opt/cargo/bin:$PATH
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain 1.88.0 --profile minimal \
&& rustc --version

# --- vcpkg (used by the MXL SDK's CMake toolchain) ----------------------
ENV VCPKG_ROOT=/opt/vcpkg
ENV VCPKG_FORCE_SYSTEM_BINARIES=1
RUN git clone --quiet https://github.com/microsoft/vcpkg.git "$VCPKG_ROOT" \
&& "$VCPKG_ROOT/bootstrap-vcpkg.sh" -disableMetrics

# --- MXL SDK ------------------------------------------------------------
# Built with gcc-13 (required by the SDK). FFmpeg + avplumber below keep
# using the distro gcc-11 that the CUDA composition suite is validated
# with. libmxl statically links spdlog, so its pkg-config file is
# scrubbed of the private Requires before FFmpeg configure looks at it.
RUN git clone --quiet --branch "${MXL_TAG}" --depth 1 \
https://github.com/dmf-mxl/mxl.git /tmp/mxl \
&& CC=/usr/bin/gcc-13 CXX=/usr/bin/g++-13 \
cmake -S /tmp/mxl -B /tmp/mxl/build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DVCPKG_MANIFEST_MODE=ON \
-DBUILD_DOCS=OFF \
-DBUILD_TESTS=OFF \
&& cmake --build /tmp/mxl/build --parallel "$(nproc)" \
&& cmake --install /tmp/mxl/build \
&& ldconfig \
&& sed -i '/^Requires.private: spdlog$/d' /usr/local/lib/pkgconfig/libmxl.pc \
&& rm -rf /tmp/mxl

COPY deps/ffmpeg-patches /build/deps/ffmpeg-patches

RUN git clone --quiet --branch "${FFMPEG_TAG}" --depth 1 \
https://github.com/FFmpeg/FFmpeg.git /tmp/ffmpeg \
&& git -C /tmp/ffmpeg config user.name "mixer-demo builder" \
&& git -C /tmp/ffmpeg config user.email "mixer-demo@local" \
&& git -C /tmp/ffmpeg am /build/deps/ffmpeg-patches/*.patch
&& git -C /tmp/ffmpeg am /build/deps/ffmpeg-patches/*.patch \
# 0008 (MXL) was cherry-picked with -X theirs and pulled in a
# handful of test-scaffolding lines from the fork that reference
# files not present in n7.1.5. Strip them so `make` succeeds.
&& sed -i '/tests\/fate\/ogg-\(opus\|flac\|vorbis\)\.mak/d' /tmp/ffmpeg/tests/Makefile \
&& sed -i '/^# uri test if demuxer is enabled$/,+1d' /tmp/ffmpeg/tests/fate/mxl.mak

RUN cd /tmp/ffmpeg \
&& ./configure \
&& PKG_CONFIG_PATH=/usr/local/lib/pkgconfig \
./configure \
--prefix=/usr/local \
--libdir=/usr/local/lib \
--incdir=/usr/local/include \
Expand All @@ -61,6 +126,10 @@ RUN cd /tmp/ffmpeg \
--enable-nvdec \
--enable-nvenc \
--disable-libnpp \
--enable-libmxl \
--enable-demuxer=mxl \
--enable-muxer=mxl \
--enable-protocol=mxl \
--disable-doc \
--disable-htmlpages \
--disable-manpages \
Expand All @@ -78,7 +147,11 @@ RUN cd /tmp/ffmpeg \

RUN /usr/local/bin/ffmpeg -hide_banner -filters | grep -q ' overlay_many_cuda ' \
&& /usr/local/bin/ffmpeg -hide_banner -filters \
| awk '$2 == "transition_cuda" && $1 ~ /C/ { found = 1 } END { exit !found }'
| awk '$2 == "transition_cuda" && $1 ~ /C/ { found = 1 } END { exit !found }' \
&& /usr/local/bin/ffmpeg -hide_banner -demuxers 2>/dev/null \
| awk '$1 ~ /D/ && $2 == "mxl" { found = 1 } END { exit !found }' \
&& /usr/local/bin/ffmpeg -hide_banner -muxers 2>/dev/null \
| awk '$1 ~ /E/ && $2 == "mxl" { found = 1 } END { exit !found }'

WORKDIR /build

Expand Down Expand Up @@ -118,8 +191,12 @@ RUN test -f /build/_avplumber"$(python3-config --extension-suffix)" \
python3 -c 'import pyplumber'

COPY demos/mixer /build/demos/mixer
COPY demos/mxl /build/demos/mxl

RUN python3 -m pip install --no-cache-dir -r /build/demos/mixer/requirements.txt
RUN python3 -m pip install --no-cache-dir -r /build/demos/mixer/requirements.txt \
&& if [ -s /build/demos/mxl/requirements.txt ]; then \
python3 -m pip install --no-cache-dir -r /build/demos/mxl/requirements.txt; \
fi

ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/cuda/lib64
ENV AVPLUMBER_REVISION=${AVPLUMBER_REVISION}
Expand Down
127 changes: 127 additions & 0 deletions demos/mxl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# MXL demo

End-to-end proof that avplumber can publish and consume flows on the
[MXL](https://github.com/dmf-mxl/mxl) shared-memory transport, using
avplumber's generic `input` / `output` nodes with `format="mxl"` — no
MXL-specific node types are introduced.

Two graphs run in one process:

* **writer** — `lavfi testsrc` → decode → rescale to yuv422p10le →
encode as `v210` (10-bit 4:2:2 uncompressed, per SMPTE ST 2110) →
mux → `output(format="mxl", url="/dev/shm/mxl")` (the MXL FFmpeg
muxer takes a plain filesystem path).
* **reader** — `input(format="mxl", url="mxl:///dev/shm/mxl?id=<uuid>")`
→ demux → decode v210 → rescale to yuv420p → encode as mpeg4 → mux
→ fragmented mp4.

Verified end-to-end on Docker Desktop (aarch64) producing 28k mpeg4
packets of the testsrc pattern in a 10-second run.

## Requirements

* Linux x86_64 (MXL SDK is Linux-only — no macOS support).
* Docker with enough tmpfs at `/dev/shm` (default is fine for the demo).
* NVIDIA GPU + [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)
are optional for the MXL demo itself (CUDA init fails silently at
runtime without them), but the shared mixer image is built with
CUDA support and other demos need it.
* The 8-patch stack applied to `n7.1.5`. `deps/ffmpeg-patches/verify.sh`
confirms the tree hash; the MXL patch (`0008-...`) is generated
from `cbcrc/FFmpeg` via `deps/ffmpeg-patches/Dockerfile.mkpatch`
— see that directory's README.

## Build

This demo shares its runtime image with `demos/mixer/`. Build once
from the repository root (after `git submodule update --init --recursive`):

```sh
docker build -f demos/mixer/Dockerfile -t avplumber-mixer:local .
```

The image build:

1. Installs distro `gcc-11` plus `gcc-13` from
`ppa:ubuntu-toolchain-r/test` (the MXL SDK needs C++20; FFmpeg and
avplumber keep using gcc-11).
2. Bootstraps `microsoft/vcpkg` and Rust 1.88.0.
3. Builds and installs the MXL SDK (`dmf-mxl/mxl` @ `v1.1.0-beta-1`)
into `/usr/local` — libmxl is statically linked against spdlog, so
its `.pc` file is scrubbed of the private `Requires` before FFmpeg
configure.
4. Applies the full 8-patch stack to FFmpeg `n7.1.5`, strips a
handful of fork-side test scaffolding that references files not
present in `n7.1.5` (`tests/fate/ogg-*.mak`, duplicated
`fate-mxl-uri` rule), and configures with CUDA (`--enable-cuda
--enable-cuda-nvcc --enable-cuvid --enable-nvdec --enable-nvenc`)
plus MXL (`--enable-libmxl --enable-demuxer=mxl --enable-muxer=mxl
--enable-protocol=mxl`).
5. Builds `pyplumber` with `HAVE_CUDA=1 HAVE_NVCC=1`.

Verified inside the image:

```sh
ffmpeg -hide_banner -demuxers | grep mxl # D mxl Media eXchange Layer
ffmpeg -hide_banner -muxers | grep mxl # E mxl Media eXchange Layer
python3 -c 'import pyplumber' # OK
```

## Run

The demo needs a shared `/dev/shm/mxl` domain directory with an
`options.json` file. On a single host use `--ipc=host`:

```sh
mkdir -p /dev/shm/mxl
echo '{"urn:x-mxl:option:history_duration/v1.0": 100000000}' \
> /dev/shm/mxl/options.json

docker run --rm --ipc=host \
--entrypoint python3 \
-v /dev/shm/mxl:/dev/shm/mxl \
-v "$PWD/demos/mxl/test-media:/media" \
-e AVP_OUTPUT=/media/out.mp4 \
avplumber-mixer:local /build/demos/mxl/mxl_demo.py
```

Add `--gpus all` if you're on an NVIDIA host and want CUDA
initialization to succeed (the MXL demo itself doesn't need it).

Defaults to publishing an `lavfi testsrc` pattern; override with
`-e AVP_INPUT=/media/your-file.mp4` for a real file (played back
looped and realtime-paced through `InputRec`).

Runs both graphs in one process. Ctrl-C to stop — the fragmented mp4
stays playable even on interrupt because `movflags=frag_keyframe+
empty_moov+default_base_moof` is set on the reader-side output.

Split writer and reader across two containers by passing
`--writer-only` / `--reader-only` and sharing the flow UUID via
`AVP_MXL_VIDEO_ID`.

## Codec choices

MXL flows carry uncompressed frames. The muxer registered by
`0008-*.patch` insists on `v210` (10-bit 4:2:2 packed) for video —
`rawvideo` is rejected at header write. The reader re-encodes to
`mpeg4` (rather than H.264) because the NVIDIA-off image does not
link `libx264`.

## Known gaps

* Passing `blocking=1` to the MXL demuxer through avplumber's `options`
dict does not currently reach the demuxer's private AVOptions
— the reader survives EAGAIN via `auto_restart:"group"` instead.
Root-cause is in the order of format assignment vs `openInput` in
`src/nodes/input.cpp`; direct `ffmpeg -blocking 1` on the CLI works.
* The demo runs the reader at wall-clock max (~2800 fps into mpeg4
at 320×240) because we didn't wire in a realtime pacer on the
reader side. Adding a `RealtimeVideoFrame` node between decode and
encode would cap the reader to the source frame rate.

## References

* MXL SDK: <https://github.com/dmf-mxl/mxl>
* MXL FFmpeg fork (source for the 0008 patch): <https://github.com/cbcrc/FFmpeg>
* Reference build guidance: <https://github.com/cbcrc/guidance-for-building-ffmpeg-with-mxl>
Loading
Loading