Directories | |
| examples | |
| opencv_example | |
Files | |
| mxwrite-nanobind.hpp | |
| mxwrite.cpp | |
| mxwrite.hpp | |
| FFmpeg-based video writer used by MXWrite. | |
| mxwrite_nanobind.cpp | |
MXWrite is a small C++20 video-writing library built directly on FFmpeg. It is the recording backend used by ACMX2 and ACMXVK. Applications submit RGBA video frames while MXWrite performs color conversion, encoding, packet writing, and container finalization on a background thread.
MXWrite supports software and hardware encoders, sequential or explicit frame timestamps, queue backpressure, CUDA-resident input, 10-bit HDR output, encoder introspection, and audio remuxing. The available codecs ultimately depend on the FFmpeg build installed on the system.
Typical packages are:
On Windows, MXWrite can be built in an MSYS2 UCRT64 or MinGW64 environment with the corresponding FFmpeg and CMake packages installed.
From the root of the acidcam-gpu repository:
MXWrite builds as a static library by default. To build a shared library instead, configure with -DSHARED=ON:
Use -DSHARED=OFF, or omit the option, for the default static library. Shared builds install the .so or .dylib into the library directory. On Windows, the DLL is installed into the binary directory and its import library into the library directory.
The nanobind extension is disabled by default. Enable it with -DPYTHON_MODULE=ON:
This builds the mxwrite_ext module and links it with MXWrite. CMake first looks for an installed nanobind package and then asks the selected Python interpreter for nanobind's CMake directory. By default, installation places the extension in lib/python<major>.<minor>/site-packages beneath CMAKE_INSTALL_PREFIX. Override that location with -DMXWRITE_PYTHON_INSTALL_DIR=<directory>. The default static MXWrite build is recommended for a self-contained Python extension. -DPYTHON_MODULE=OFF, or omitting the option, leaves Python and nanobind out of configuration entirely. When combined with -DSHARED=ON, the installed extension uses a relative loader path to the MXWrite shared library on Linux and macOS; Windows installations also place the MXWrite DLL beside the extension.
The Python example generates an animated RGBA test pattern, queries the available FFmpeg encoders and their options, writes a video, and validates the reported frame count, duration, and output size. NumPy is required to supply frames to the module:
Pass --explicit-pts to exercise open_ts() and write_at_pts(). Use --help to see the resolution, frame-rate, encoder, preset, and CRF options.
MXWrite can also be built and installed directly through pip from the repository checkout. The FFmpeg development packages listed above must already be installed because pip does not provide those native system libraries:
The pip build uses scikit-build-core to invoke CMake, enables the nanobind module automatically, and installs NumPy as its runtime dependency.
To install MXWrite into a new virtual environment from this combined MXVK checkout, use:
Pip installs NumPy plus the isolated build requirements (scikit-build-core and nanobind) automatically. FFmpeg development packages must still be installed through the system package manager; see the Debian/Ubuntu and Arch commands in the root Python wheel installation guide. MXWrite has no OpenCV setting. Its wheel defaults to a static, self-contained extension; build a shared variant with:
CMake automatically enables MXWRITE_HAS_CUDA_COPY when it finds the CUDA Toolkit. This definition changes the layout of Writer, so every translation unit using mxwrite.hpp must receive the same definition as the library. The exported MXWrite::mxwrite CMake target propagates it automatically. Consumers of a CUDA-enabled installation must also make CUDA::cudart available, normally with find_package(CUDAToolkit REQUIRED).
To use the installed CMake package:
The repository's top-level ACMX Pcons build compiles the same MXWrite source directly and propagates the matching CUDA definition when CUDA is enabled.
Calling close() drains queued frames, flushes the encoder, writes the container trailer, and releases FFmpeg resources. The destructor closes an open writer automatically, but explicit close() makes output lifetime clear.
Host input passed to write() or write_at_pts() must be tightly packed RGBA8 with exactly four bytes per pixel and dimensions matching open(). MXWrite copies the data before the call returns, so the caller may reuse its buffer.
There are two timestamp styles:
| Method | Behavior |
|---|---|
| write(frame) | Assigns sequential PTS values 0, 1, 2, ... at the configured frame rate. |
| write_at_pts(frame, pts) | Uses an explicit PTS measured in output-frame ticks. Values must move forward; stale or duplicate slots are discarded. Gaps are preserved. |
| write_hdr_rgba16_at_pts(frame, pts) | Explicit-PTS equivalent for encoded HDR RGBA16 input. |
| write_cuda_rgba_at_pts(frame, stride, pts) | Explicit-PTS equivalent for CUDA input. |
open_ts() remains as a compatibility entry point. It enables the low-delay threading path, but write_ts() currently aliases write() and therefore uses sequential timestamps. Applications that already have source timestamps should use write_at_pts() directly.
Common fractional rates are represented exactly, including 23.976, 29.97, 47.952, 59.94, and 119.88 FPS. Other rates are converted to a reduced rational with milliframe precision.
EncodeOptions provides the primary controls:
| Field | Default | Purpose |
|---|---|---|
| codec | auto | Encoder policy or exact FFmpeg encoder name. |
| preset | medium | Speed/quality preset. x264-style names are mapped to NVENC p1 through p7. |
| tune | empty | Software or hardware tuning mode. |
| crf | 18 | CRF for software encoders or CQ for NVENC, clamped to 0–51. |
| bit_rate | 0 | Target bits per second. A positive value selects bitrate-based operation instead of built-in CRF/CQ. |
| ffmpeg_options | empty | Additional FFmpeg-style encoder and muxer options. |
| realtime | false | Enables low-latency encoder settings. |
| block_when_full | false | Blocks the producer rather than dropping frames when encoding falls behind. |
For example, 10 Mbit/s target bitrate encoding is:
This is equivalent in intent to FFmpeg's -b:v 10M. Leave bit_rate at zero to use crf or NVENC cq instead.
Registration does not guarantee that a hardware encoder can run. Its driver, device, FFmpeg hardware support, and accepted pixel formats must also be available. Exact hardware-frame-only selections fail with a diagnostic if their device context cannot be created.
ffmpeg_options accepts familiar option spelling and supports quoted values:
Options in this string override matching built-in choices. MXWrite uses libavcodec and libavformat directly; the string must contain only video encoder or muxer options, not an FFmpeg command, input filename, or output filename. Unsupported options cause open() to fail with a diagnostic.
The legacy overload below is retained for existing applications:
It parses the string as CRF and preserves the original ultrafast, zero-latency, realtime behavior. New code should prefer EncodeOptions.
Encoding runs on a background std::jthread. In normal mode the pending queue holds up to 120 frames. If the producer outruns the encoder after the queue is full, new frames are dropped and MXWrite periodically reports the count.
For file conversion, headless rendering, or any workflow where every submitted frame must be encoded, enable backpressure before opening or at runtime:
Backpressure keeps one frame pending and blocks the producer until the encoder has room. This prevents frame loss and naturally paces offline processing to the encoder's throughput.
When MXWrite is compiled with CUDA support and a compatible CUDA hardware encoder is active, a CUDA RGBA8 image can be submitted without downloading it to host memory:
The source must be an RGBA8 CUDA device allocation. src_stride is its row pitch in bytes, and bottom_up requests a vertical flip during the device copy. The method returns false when the writer is closed or direct CUDA ingestion is unavailable. Explicit timestamps are supported by write_cuda_rgba_at_pts().
Set options.hdr.enabled to select the dedicated HDR path. It currently uses software libx265, HEVC Main10, and yuv420p10le. The output is tagged with BT.2020 primaries, BT.2020 non-constant-luminance matrix coefficients, and PQ by default. Setting color_trc to FFmpeg's HLG value produces HLG signaling.
write_hdr_rgba16() expects tightly packed, little-endian, unsigned-normalized RGBA16 whose RGB channels are already encoded as BT.2020 PQ or HLG. MXWrite converts that signal to limited-range YUV420P10 without applying another transfer function. RGBA8 submitted through write() is supported as an SDR compatibility input and is placed at the 100-nit reference level in a PQ signal.
Raw FFmpeg mastering-display and content-light side-data payloads can be placed in options.hdr.mastering_display and options.hdr.content_light. MXWrite copies them to the output stream when possible.
Applications can build encoder controls from the linked FFmpeg installation:
EncoderInfo reports the exact encoder name, codec family, human-readable name, supported pixel formats, hardware classification, and experimental status. EncoderOptionInfo exposes FFmpeg option types, defaults, ranges, named choices, and help text.
While a writer is open, applications may query:
For explicit PTS writes, get_frame_count() is the highest accepted PTS plus one, so it includes intentional timeline gaps rather than only counting calls.
transfer_audio(source, destination) remuxes the first audio stream from a source media file into an already encoded destination video. Video packets and audio packets are copied without re-encoding, audio is clipped to the video duration, and the destination is replaced through a temporary file.
The helper supports common containers including MP4, MKV, MOV, AVI, MPEG-TS, MPEG, FLV, 3GP, WMV, ASF, and VOB. The source must contain an audio stream and the destination must contain a video stream. Calls are serialized internally.
The output container is selected from the filename extension by libavformat. The selected encoder must be valid for that container. For software encoding, MXWrite chooses a compatible system-memory pixel format and converts from RGBA with libswscale. Additional -pix_fmt requests are honored only when the encoder and the implemented upload path support them.
opencv_example/ contains a small camera program demonstrating the legacy open() and open_ts() entry points. Build it after installing MXWrite and OpenCV:
The first argument is the camera index. The second is 0 for normal mode or 1 for the low-delay compatibility mode.
MXWrite is part of the Acid Cam project and is distributed under the repository's License.