MXVK Vulkan Framework 0.33.1
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxwrite.hpp
Go to the documentation of this file.
1/**
2 * @file mxwrite.hpp
3 * @brief FFmpeg-based video writer used by MXWrite.
4 */
5#ifndef FFWRITE_HPP
6#define FFWRITE_HPP
7extern "C" {
8#include <libavcodec/avcodec.h>
9#include <libavformat/avformat.h>
10#include <libavutil/hwcontext.h>
11#include <libavutil/imgutils.h>
12#include <libavutil/mathematics.h>
13#include <libavutil/opt.h>
14#include <libswscale/swscale.h>
15}
16#include <atomic>
17#include <chrono>
18#include <condition_variable>
19#include <cstdint>
20#include <mutex>
21#include <queue>
22#include <string>
23#include <string_view>
24#include <thread>
25#include <vector>
26#ifdef MXWRITE_HAS_CUDA_COPY
27#include <cuda_runtime.h>
28#endif
29
30/**
31 * @brief Queue entry that stores a frame pointer and its capture timestamp.
32 */
33struct Frame_Data {
34 void *data; ///< Pointer to RGBA frame data owned by the producer.
35 std::chrono::steady_clock::time_point capture_time; ///< Capture time for timestamp-based encoding.
36};
37
38/** @brief A video encoder reported by the linked FFmpeg installation. */
40 std::string name; ///< Exact libavcodec encoder name (for example, libx265).
41 std::string long_name; ///< Human-readable encoder description.
42 std::string codec_name; ///< Encoded format name (for example, hevc or av1).
43 std::string pixel_formats; ///< Comma-separated supported input pixel formats.
44 bool hardware = false; ///< True for hardware or hybrid encoders.
45 bool experimental = false; ///< True when FFmpeg marks the encoder experimental.
46};
47
48/** @brief One configurable AVOption exposed by a video encoder. */
50 std::string name; ///< Option name accepted by EncodeOptions::ffmpeg_options.
51 std::string type; ///< FFmpeg option type.
52 std::string default_value; ///< Encoder default, when it can be represented as text.
53 std::string minimum; ///< Minimum value for numeric options.
54 std::string maximum; ///< Maximum value for numeric options.
55 std::string choices; ///< Comma-separated named values for enum-like options.
56 std::string help; ///< Human-readable FFmpeg option description.
57};
58
59/** @return Video encoders registered by the linked FFmpeg libraries. */
60std::vector<EncoderInfo> available_video_encoders();
61
62/**
63 * @brief Return the options exposed by one registered video encoder.
64 * @param encoder_name Exact encoder name returned by available_video_encoders().
65 */
66std::vector<EncoderOptionInfo> video_encoder_options(std::string_view encoder_name);
67
68/**
69 * @brief User-configurable video encoder quality options.
70 *
71 * preset: x264 preset name — ultrafast, superfast, veryfast, faster, fast,
72 * medium, slow, slower, veryslow. Mapped to NVENC p1..p7.
73 * tune: Software tune, or NVENC hq, uhq, ll, ull, or lossless. Empty uses
74 * the encoder default (NVENC hq).
75 * crf: Constant Rate Factor, 0 (lossless) .. 51 (worst). 18 is visually
76 * near-lossless; 23 is default for x264; 28 is typical "small file".
77 * For NVENC this is forwarded as `cq`.
78 * codec: "auto" (NVENC if available, else software), "software" (force software),
79 * "nvenc" (force resolution-selected NVENC), or any exact video encoder
80 * name registered by FFmpeg, such as "libx264", "libx265", "libsvtav1",
81 * "h264_qsv", or "hevc_vaapi". NVENC policy requests fall back to the
82 * matching software codec.
83 * ffmpeg_options: Additional FFmpeg-style video encoder options, for example
84 * "-preset p6 -tune lossless -profile:v rext -pix_fmt yuv444p".
85 * These options override the corresponding built-in settings. MXWrite
86 * uses libavcodec directly, so input/output filenames are not accepted.
87 * realtime: when true, applies low-latency defaults (tune=zerolatency for x264,
88 * tune=ll + zerolatency=1 for NVENC). Extra options may override them.
89 * block_when_full: when true, producer threads block if the encoder queue is
90 * full instead of dropping frames.
91 */
93 std::string preset = "medium"; ///< Encoder preset name.
94 std::string tune = ""; ///< Optional tuning mode.
95 int crf = 18; ///< Constant Rate Factor.
96 std::string codec = "auto"; ///< Encoder selection policy or exact FFmpeg encoder name.
97 std::string ffmpeg_options; ///< Additional FFmpeg-style video encoder options.
98 bool realtime = false; ///< Enable low-latency settings.
99 bool block_when_full = false; ///< Pace producers to encoder throughput instead of dropping frames.
100
101 /**
102 * @brief HDR output options.
103 *
104 * When @ref HdrInfo::enabled is true, the writer switches to a dedicated
105 * HEVC Main10 + BT.2020 output path that:
106 * - Encodes with libx265 at 10-bit (AV_PIX_FMT_YUV420P10LE).
107 * - Tags the stream with BT.2020 primaries, BT.2020 non-constant luminance
108 * matrix, and SMPTE ST.2084 (PQ) transfer.
109 * - Converts incoming 8-bit sRGB RGBA shader output into PQ-encoded
110 * 10-bit YUV, placing SDR-range content at the 100-nit reference level
111 * inside the PQ signal (SDR-in-HDR-container).
112 * - Copies @ref mastering_display and @ref content_light side data from
113 * the input stream when provided, so player HDR metadata is preserved.
114 *
115 * This mode is intended for use when the *input* video is HDR; the 8-bit
116 * GL pipeline cannot reconstruct the original highlight precision, but the
117 * resulting file is a correctly-tagged HDR container.
118 */
119 struct HdrInfo {
120 bool enabled = false; ///< Enables the HDR output path.
121 int color_primaries = 0; ///< AVColorPrimaries value.
122 int color_trc = 0; ///< AVColorTransferCharacteristic value.
123 int color_space = 0; ///< AVColorSpace value.
124 int color_range = 0; ///< AVColorRange value.
125 /// Raw AVMasteringDisplayMetadata side-data bytes, or empty.
126 std::vector<uint8_t> mastering_display;
127 /// Raw AVContentLightMetadata side-data bytes, or empty.
128 std::vector<uint8_t> content_light;
130};
131
132/**
133 * @brief FFmpeg-backed RGBA video writer.
134 *
135 * The writer accepts host RGBA buffers, optional CUDA device buffers, and
136 * 16-bit HDR RGBA buffers. It can encode either frame-by-frame or from
137 * timestamped frames, depending on the open mode.
138 */
139class Writer {
140 public:
141 /** @brief Construct a closed writer. */
142 Writer() = default;
143
144 /**
145 * @brief Open an output file using the legacy CRF string interface.
146 * @param filename Output file path.
147 * @param width Output width in pixels.
148 * @param height Output height in pixels.
149 * @param fps Output frame rate.
150 * @param crf Constant Rate Factor as a string.
151 * @return true on success.
152 */
153 bool open(const std::string &filename, int width, int height, float fps, const char *crf);
154 /**
155 * @brief Open an output file using explicit encoder options.
156 * @param filename Output file path.
157 * @param width Output width in pixels.
158 * @param height Output height in pixels.
159 * @param fps Output frame rate.
160 * @param opts Encoder configuration.
161 * @return true on success.
162 */
163 bool open(const std::string &filename, int width, int height, float fps, const EncodeOptions &opts);
164 /**
165 * @brief Queue a host RGBA frame for immediate-mode encoding.
166 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
167 */
168 void write(void *rgba_buffer);
169 /**
170 * @brief Queue a host RGBA frame with an explicit presentation timestamp.
171 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
172 * @param pts Presentation timestamp in units of the configured frame time base.
173 */
174 void write_at_pts(void *rgba_buffer, int64_t pts);
175 /**
176 * @brief Write a 16-bit RGBA frame that is already PQ- or HLG-encoded in
177 * BT.2020 primaries (8 bytes/pixel: R16,G16,B16,A16, little-endian
178 * unsigned normalised).
179 *
180 * The data is expected to originate from the HDR GPU encode pass: it is
181 * BT.2020 primaries with a non-linear PQ (or HLG) transfer already
182 * applied, so this path skips colour-space conversion and only performs
183 * (a) the BT.2020 non-constant-luminance RGB'->YCbCr' matrix, and
184 * (b) 16-bit -> 10-bit limited-range scaling,
185 * producing AV_PIX_FMT_YUV420P10LE for libx265 Main10. Requires the
186 * writer to have been opened with @ref EncodeOptions::HdrInfo::enabled.
187 * @param rgba16_buffer Pointer to tightly packed RGBA16 pixels.
188 */
189 void write_hdr_rgba16(void *rgba16_buffer);
190 /**
191 * @brief Queue a 16-bit HDR RGBA frame with an explicit presentation timestamp.
192 * @param rgba16_buffer Pointer to tightly packed RGBA16 pixels.
193 * @param pts Presentation timestamp in units of the configured frame time base.
194 */
195 void write_hdr_rgba16_at_pts(void *rgba16_buffer, int64_t pts);
196 /**
197 * @brief Queue a CUDA RGBA frame for encoding.
198 * @param cuda_rgba_buffer CUDA device pointer.
199 * @param src_stride Source row pitch in bytes.
200 * @param bottom_up Whether the source is stored bottom-up.
201 * @return true if the frame was accepted.
202 */
203 bool write_cuda_rgba(void *cuda_rgba_buffer, int src_stride, bool bottom_up = false);
204 /**
205 * @brief Queue a CUDA RGBA frame with an explicit presentation timestamp.
206 * @param cuda_rgba_buffer CUDA device pointer.
207 * @param src_stride Source row pitch in bytes.
208 * @param pts Presentation timestamp in units of the configured frame time base.
209 * @param bottom_up Whether the source is stored bottom-up.
210 * @return true if the frame was accepted.
211 */
212 bool write_cuda_rgba_at_pts(void *cuda_rgba_buffer, int src_stride, int64_t pts,
213 bool bottom_up = false);
214 /**
215 * @brief Open a timestamp-based output stream using the legacy CRF string interface.
216 * @param filename Output file path.
217 * @param width Output width in pixels.
218 * @param height Output height in pixels.
219 * @param fps Nominal input frame rate.
220 * @param crf Constant Rate Factor as a string.
221 * @return true on success.
222 */
223 bool open_ts(const std::string &filename, int width, int height, float fps, const char *crf);
224 /**
225 * @brief Open a timestamp-based output stream using explicit encoder options.
226 * @param filename Output file path.
227 * @param width Output width in pixels.
228 * @param height Output height in pixels.
229 * @param fps Nominal input frame rate.
230 * @param opts Encoder configuration.
231 * @return true on success.
232 */
233 bool open_ts(const std::string &filename, int width, int height, float fps, const EncodeOptions &opts);
234 /**
235 * @brief Queue a host RGBA frame using capture timestamps.
236 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
237 */
238 void write_ts(void *rgba_buffer);
239 /** @brief Close the writer and flush pending packets. */
240 void close();
241 /** @brief Check whether the writer is currently open. */
242 bool is_open() const { return opened; }
243 /// @brief True when FFmpeg identifies the active encoder as hardware or hybrid.
244 bool is_hardware_encode() const { return active_encoder_hardware; }
245 /// @brief If true, keep one pending frame and pace producer threads to the
246 /// encoder instead of dropping frames. Intended for headless/batch
247 /// transcoding where every input frame must reach the output. Default: false.
248 void set_block_when_full(bool value) { block_when_full = value; }
249 /** @brief Check whether the encoder queue blocks instead of dropping frames. */
250 bool get_block_when_full() const { return block_when_full; }
251 /**
252 * @brief Return the output timeline length in nominal frame ticks.
253 *
254 * For sequential writes this equals the submitted frame count. Explicit
255 * PTS writes may leave gaps, in which case it is the highest accepted PTS
256 * plus one.
257 */
258 int64_t get_frame_count() const { return frame_count; }
259 /** @brief Return the current logical byte position of the output muxer. */
260 std::uint64_t get_bytes_written() const {
261 return bytes_written.load(std::memory_order_relaxed);
262 }
263 /** @brief Return the encoded duration in seconds. */
264 double get_duration() const;
265 /** @brief Close the writer on destruction if it is still open. */
267 if (is_open()) {
268 close();
269 opened = false;
270 }
271 }
272
273 private:
274 bool opened{false}; ///< Internal open-state flag.
275 int width = 0; ///< Output width in pixels.
276 int height = 0; ///< Output height in pixels.
277 int fps_num = 0; ///< Output FPS numerator.
278 int fps_den = 0; ///< Output FPS denominator.
279 int64_t frame_count = 0; ///< Next sequential PTS / explicit-PTS timeline length.
280 double last_duration = 0.0; ///< Cached duration from the last encode step.
281 AVFormatContext *format_ctx = nullptr; ///< Active container context.
282 AVCodecContext *codec_ctx = nullptr; ///< Active codec context.
283 AVStream *stream = nullptr; ///< Output video stream.
284 AVFrame *frameYUV = nullptr; ///< Software-converted YUV frame.
285 AVFrame *frameRGBA = nullptr; ///< Staging RGBA frame.
286 AVFrame *frame10 = nullptr; ///< YUV420P10LE frame used for HDR output.
287 AVFrame *upload_sw_frame = nullptr; ///< Software upload frame used by CUDA/hardware paths.
288 AVBufferRef *hw_device_ctx = nullptr; ///< Hardware device context, when available.
289 AVBufferRef *hw_frames_ctx = nullptr; ///< Hardware frames pool, when available.
290 bool use_hw_encode = false; ///< True when hardware encoding is active.
291 bool active_encoder_hardware = false; ///< True when the selected FFmpeg encoder is hardware-backed.
292 bool direct_cuda_upload = false; ///< True when CUDA RGBA frames can be copied without conversion.
293#ifdef MXWRITE_HAS_CUDA_COPY
294 // Dedicated stream so the producer's RGBA→hwframe copy does not serialise
295 // with the renderer's default-stream work or with the encoder thread.
296 cudaStream_t cuda_upload_stream = nullptr;
297#endif
298 bool hdr_output = false; ///< True when HDR (HEVC Main10/PQ) output is active.
299 EncodeOptions::HdrInfo hdr_info; ///< HDR metadata captured at open() time.
300 SwsContext *sws_ctx = nullptr; ///< Frame conversion context.
301 AVRational time_base; ///< Stream time base.
302 /** @brief Convert a frame rate into a rational numerator/denominator pair. */
303 void calculateFPSFraction(float fps, int &fps_num, int &fps_den);
304 std::chrono::steady_clock::time_point recordingStart; ///< Start time for timestamp mode.
305
306 std::queue<AVFrame *> encode_queue; ///< Pending encoded frames.
307 // Deep enough to absorb encoder hiccups (~4s at 30fps, ~2s at 60fps).
308 // Memory cost is bounded by the NVENC frame pool / sw RGBA frame buffer.
309 static constexpr size_t MAX_QUEUE_SIZE = 120;
310 // No-drop mode intentionally keeps only one pending frame. Together with
311 // the frame currently being encoded, this paces file processing to encoder
312 // throughput instead of producing 120-frame burst/stall cycles.
313 static constexpr size_t NO_DROP_QUEUE_SIZE = 1;
314 std::condition_variable queue_cv; ///< Signals queue availability.
315 std::jthread encode_thread; ///< Background encoder thread.
316
317 std::mutex queue_mutex{}; ///< Guards the frame queue.
318 std::mutex writer_mutex{}; ///< Guards writer state transitions.
319 bool stop_requested = false; ///< Signals encoder shutdown.
320 std::atomic<bool> block_when_full{false}; ///< Queue backpressure mode.
321 std::atomic<std::uint64_t> bytes_written{0}; ///< Logical output bytes accepted by FFmpeg.
322
323 /** @brief Shared implementation for open() and open_ts(). */
324 bool openInternal(const std::string &filename, int w, int h, float fps, const EncodeOptions &opts, bool ts_mode);
325 /** @brief Initialize an FFmpeg hardware device and frame pool for an encoder. */
326 bool initHardwareEncoding(const AVCodec *codec, AVPixelFormat requested_format,
327 bool prefer_cuda_rgba);
328 /** @brief Start the background encoder thread. */
329 void startEncoderThread();
330 /** @brief Stop the background encoder thread. */
331 void stopEncoderThread();
332 /** @brief Encoder thread main loop. */
333 void encodeLoop(std::stop_token stop_token);
334 /** @brief Encode and write one frame. */
335 void encodeAndWriteFrame(AVFrame *in_frame);
336 /** @brief Drain packets from the codec into the container. */
337 void drainEncoderPackets();
338 /** @brief Refresh the logical output byte count from FFmpeg. */
339 void updateBytesWritten() noexcept;
340 /** @brief Release a frame allocated for the encode queue. */
341 void releaseFrame(AVFrame *f);
342};
343
344/**
345 * @brief Copy audio from one video file to another.
346 * @param sourceAudioFile Input media file containing the audio stream.
347 * @param destVideoFile Output video file to receive the audio stream.
348 */
349extern void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile);
350/**
351 * @brief Free FFmpeg format contexts used during transfer operations.
352 * @param source_ctx Source format context.
353 * @param dest_ctx Destination format context.
354 * @param output_ctx Output format context.
355 */
356extern void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx);
357
358#endif
std::uint64_t get_bytes_written() const
Return the current logical byte position of the output muxer.
Definition mxwrite.hpp:260
bool is_hardware_encode() const
True when FFmpeg identifies the active encoder as hardware or hybrid.
Definition mxwrite.hpp:244
void write_hdr_rgba16_at_pts(void *rgba16_buffer, int64_t pts)
Queue a 16-bit HDR RGBA frame with an explicit presentation timestamp.
Definition mxwrite.cpp:1982
Writer()=default
Construct a closed writer.
bool get_block_when_full() const
Check whether the encoder queue blocks instead of dropping frames.
Definition mxwrite.hpp:250
bool write_cuda_rgba(void *cuda_rgba_buffer, int src_stride, bool bottom_up=false)
Queue a CUDA RGBA frame for encoding.
Definition mxwrite.cpp:2072
void set_block_when_full(bool value)
If true, keep one pending frame and pace producer threads to the encoder instead of dropping frames....
Definition mxwrite.hpp:248
void write_at_pts(void *rgba_buffer, int64_t pts)
Queue a host RGBA frame with an explicit presentation timestamp.
Definition mxwrite.cpp:1863
~Writer()
Close the writer on destruction if it is still open.
Definition mxwrite.hpp:266
bool open(const std::string &filename, int width, int height, float fps, const char *crf)
Open an output file using the legacy CRF string interface.
Definition mxwrite.cpp:1039
void write(void *rgba_buffer)
Queue a host RGBA frame for immediate-mode encoding.
Definition mxwrite.cpp:1859
bool open_ts(const std::string &filename, int width, int height, float fps, const char *crf)
Open a timestamp-based output stream using the legacy CRF string interface.
Definition mxwrite.cpp:1060
bool is_open() const
Check whether the writer is currently open.
Definition mxwrite.hpp:242
double get_duration() const
Return the encoded duration in seconds.
Definition mxwrite.cpp:2483
bool write_cuda_rgba_at_pts(void *cuda_rgba_buffer, int src_stride, int64_t pts, bool bottom_up=false)
Queue a CUDA RGBA frame with an explicit presentation timestamp.
Definition mxwrite.cpp:2077
void close()
Close the writer and flush pending packets.
Definition mxwrite.cpp:2425
int64_t get_frame_count() const
Return the output timeline length in nominal frame ticks.
Definition mxwrite.hpp:258
void write_hdr_rgba16(void *rgba16_buffer)
Write a 16-bit RGBA frame that is already PQ- or HLG-encoded in BT.2020 primaries (8 bytes/pixel: R16...
Definition mxwrite.cpp:1978
void write_ts(void *rgba_buffer)
Queue a host RGBA frame using capture timestamps.
Definition mxwrite.cpp:2211
std::vector< EncoderInfo > available_video_encoders()
Definition mxwrite.cpp:980
std::vector< EncoderOptionInfo > video_encoder_options(std::string_view encoder_name)
Return the options exposed by one registered video encoder.
Definition mxwrite.cpp:1018
void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile)
Copy audio from one video file to another.
Definition mxwrite.cpp:240
void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx)
Free FFmpeg format contexts used during transfer operations.
Definition mxwrite.cpp:226
HDR output options.
Definition mxwrite.hpp:119
std::vector< uint8_t > content_light
Raw AVContentLightMetadata side-data bytes, or empty.
Definition mxwrite.hpp:128
int color_range
AVColorRange value. Raw AVMasteringDisplayMetadata side-data bytes, or empty.
Definition mxwrite.hpp:124
std::vector< uint8_t > mastering_display
Definition mxwrite.hpp:126
int color_trc
AVColorTransferCharacteristic value.
Definition mxwrite.hpp:122
bool enabled
Enables the HDR output path.
Definition mxwrite.hpp:120
int color_primaries
AVColorPrimaries value.
Definition mxwrite.hpp:121
int color_space
AVColorSpace value.
Definition mxwrite.hpp:123
User-configurable video encoder quality options.
Definition mxwrite.hpp:92
std::string codec
Encoder selection policy or exact FFmpeg encoder name.
Definition mxwrite.hpp:96
std::string tune
Optional tuning mode.
Definition mxwrite.hpp:94
bool realtime
Enable low-latency settings.
Definition mxwrite.hpp:98
std::string ffmpeg_options
Additional FFmpeg-style video encoder options.
Definition mxwrite.hpp:97
struct EncodeOptions::HdrInfo hdr
int crf
Constant Rate Factor.
Definition mxwrite.hpp:95
bool block_when_full
Pace producers to encoder throughput instead of dropping frames.
Definition mxwrite.hpp:99
std::string preset
Encoder preset name.
Definition mxwrite.hpp:93
A video encoder reported by the linked FFmpeg installation.
Definition mxwrite.hpp:39
std::string long_name
Human-readable encoder description.
Definition mxwrite.hpp:41
std::string pixel_formats
Comma-separated supported input pixel formats.
Definition mxwrite.hpp:43
std::string name
Exact libavcodec encoder name (for example, libx265).
Definition mxwrite.hpp:40
std::string codec_name
Encoded format name (for example, hevc or av1).
Definition mxwrite.hpp:42
bool experimental
True when FFmpeg marks the encoder experimental.
Definition mxwrite.hpp:45
bool hardware
True for hardware or hybrid encoders.
Definition mxwrite.hpp:44
One configurable AVOption exposed by a video encoder.
Definition mxwrite.hpp:49
std::string minimum
Minimum value for numeric options.
Definition mxwrite.hpp:53
std::string name
Option name accepted by EncodeOptions::ffmpeg_options.
Definition mxwrite.hpp:50
std::string help
Human-readable FFmpeg option description.
Definition mxwrite.hpp:56
std::string choices
Comma-separated named values for enum-like options.
Definition mxwrite.hpp:55
std::string type
FFmpeg option type.
Definition mxwrite.hpp:51
std::string maximum
Maximum value for numeric options.
Definition mxwrite.hpp:54
std::string default_value
Encoder default, when it can be represented as text.
Definition mxwrite.hpp:52
Queue entry that stores a frame pointer and its capture timestamp.
Definition mxwrite.hpp:33
void * data
Pointer to RGBA frame data owned by the producer.
Definition mxwrite.hpp:34
std::chrono::steady_clock::time_point capture_time
Capture time for timestamp-based encoding.
Definition mxwrite.hpp:35