MXVK Vulkan Framework 0.35.0
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 * bit_rate: Target video bitrate in bits per second. A positive value selects
79 * bitrate-based VBR and disables the built-in CRF/CQ setting.
80 * codec: "auto" (NVENC if available, else software), "software" (force software),
81 * "nvenc" (force resolution-selected NVENC), or any exact video encoder
82 * name registered by FFmpeg, such as "libx264", "libx265", "libsvtav1",
83 * "h264_qsv", or "hevc_vaapi". NVENC policy requests fall back to the
84 * matching software codec.
85 * ffmpeg_options: Additional FFmpeg-style video encoder options, for example
86 * "-preset p6 -tune lossless -profile:v rext -pix_fmt yuv444p".
87 * These options override the corresponding built-in settings. MXWrite
88 * uses libavcodec directly, so input/output filenames are not accepted.
89 * realtime: when true, applies low-latency defaults (tune=zerolatency for x264,
90 * tune=ll + zerolatency=1 for NVENC). Extra options may override them.
91 * block_when_full: when true, producer threads block if the encoder queue is
92 * full instead of dropping frames.
93 */
95 std::string preset = "medium"; ///< Encoder preset name.
96 std::string tune = ""; ///< Optional tuning mode.
97 int crf = 18; ///< Constant Rate Factor.
98 std::int64_t bit_rate = 0; ///< Target bits/second; 0 selects CRF/CQ.
99 std::string codec = "auto"; ///< Encoder selection policy or exact FFmpeg encoder name.
100 std::string ffmpeg_options; ///< Additional FFmpeg-style video encoder options.
101 bool realtime = false; ///< Enable low-latency settings.
102 bool block_when_full = false; ///< Pace producers to encoder throughput instead of dropping frames.
103
104 /**
105 * @brief HDR output options.
106 *
107 * When @ref HdrInfo::enabled is true, the writer switches to a dedicated
108 * HEVC Main10 + BT.2020 output path that:
109 * - Encodes with libx265 at 10-bit (AV_PIX_FMT_YUV420P10LE).
110 * - Tags the stream with BT.2020 primaries, BT.2020 non-constant luminance
111 * matrix, and SMPTE ST.2084 (PQ) transfer.
112 * - Accepts already-PQ/HLG-encoded BT.2020 RGBA16 through the dedicated
113 * HDR write methods and converts it directly to 10-bit YUV.
114 * - Retains an RGBA8 compatibility path that places SDR-range content at
115 * the 100-nit reference level inside a PQ signal.
116 * - Copies @ref mastering_display and @ref content_light side data from
117 * the input stream when provided, so player HDR metadata is preserved.
118 *
119 * This mode is intended for HDR render pipelines and preserves their
120 * 16-bit encoded signal through the RGB-to-YUV conversion.
121 */
122 struct HdrInfo {
123 bool enabled = false; ///< Enables the HDR output path.
124 int color_primaries = 0; ///< AVColorPrimaries value.
125 int color_trc = 0; ///< AVColorTransferCharacteristic value.
126 int color_space = 0; ///< AVColorSpace value.
127 int color_range = 0; ///< AVColorRange value.
128 /// Raw AVMasteringDisplayMetadata side-data bytes, or empty.
129 std::vector<uint8_t> mastering_display;
130 /// Raw AVContentLightMetadata side-data bytes, or empty.
131 std::vector<uint8_t> content_light;
133};
134
135/**
136 * @brief FFmpeg-backed RGBA video writer.
137 *
138 * The writer accepts host RGBA buffers, optional CUDA device buffers, and
139 * 16-bit HDR RGBA buffers. It can encode either frame-by-frame or from
140 * timestamped frames, depending on the open mode.
141 */
142class Writer {
143 public:
144 /** @brief Construct a closed writer. */
145 Writer() = default;
146
147 /**
148 * @brief Open an output file using the legacy CRF string interface.
149 * @param filename Output file path.
150 * @param width Output width in pixels.
151 * @param height Output height in pixels.
152 * @param fps Output frame rate.
153 * @param crf Constant Rate Factor as a string.
154 * @return true on success.
155 */
156 bool open(const std::string &filename, int width, int height, float fps, const char *crf);
157 /**
158 * @brief Open an output file using explicit encoder options.
159 * @param filename Output file path.
160 * @param width Output width in pixels.
161 * @param height Output height in pixels.
162 * @param fps Output frame rate.
163 * @param opts Encoder configuration.
164 * @return true on success.
165 */
166 bool open(const std::string &filename, int width, int height, float fps, const EncodeOptions &opts);
167 /**
168 * @brief Queue a host RGBA frame for immediate-mode encoding.
169 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
170 */
171 void write(void *rgba_buffer);
172 /**
173 * @brief Queue a host RGBA frame with an explicit presentation timestamp.
174 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
175 * @param pts Presentation timestamp in units of the configured frame time base.
176 */
177 void write_at_pts(void *rgba_buffer, int64_t pts);
178 /**
179 * @brief Write a 16-bit RGBA frame that is already PQ- or HLG-encoded in
180 * BT.2020 primaries (8 bytes/pixel: R16,G16,B16,A16, little-endian
181 * unsigned normalised).
182 *
183 * The data is expected to originate from the HDR GPU encode pass: it is
184 * BT.2020 primaries with a non-linear PQ (or HLG) transfer already
185 * applied, so this path skips colour-space conversion and only performs
186 * (a) the BT.2020 non-constant-luminance RGB'->YCbCr' matrix, and
187 * (b) 16-bit -> 10-bit limited-range scaling,
188 * producing AV_PIX_FMT_YUV420P10LE for libx265 Main10. Requires the
189 * writer to have been opened with @ref EncodeOptions::HdrInfo::enabled.
190 * @param rgba16_buffer Pointer to tightly packed RGBA16 pixels.
191 */
192 void write_hdr_rgba16(void *rgba16_buffer);
193 /**
194 * @brief Queue a 16-bit HDR RGBA frame with an explicit presentation timestamp.
195 * @param rgba16_buffer Pointer to tightly packed RGBA16 pixels.
196 * @param pts Presentation timestamp in units of the configured frame time base.
197 */
198 void write_hdr_rgba16_at_pts(void *rgba16_buffer, int64_t pts);
199 /**
200 * @brief Queue a CUDA RGBA frame for encoding.
201 * @param cuda_rgba_buffer CUDA device pointer.
202 * @param src_stride Source row pitch in bytes.
203 * @param bottom_up Whether the source is stored bottom-up.
204 * @return true if the frame was accepted.
205 */
206 bool write_cuda_rgba(void *cuda_rgba_buffer, int src_stride, bool bottom_up = false);
207 /**
208 * @brief Queue a CUDA RGBA frame with an explicit presentation timestamp.
209 * @param cuda_rgba_buffer CUDA device pointer.
210 * @param src_stride Source row pitch in bytes.
211 * @param pts Presentation timestamp in units of the configured frame time base.
212 * @param bottom_up Whether the source is stored bottom-up.
213 * @return true if the frame was accepted.
214 */
215 bool write_cuda_rgba_at_pts(void *cuda_rgba_buffer, int src_stride, int64_t pts, bool bottom_up = false);
216 /**
217 * @brief Open a timestamp-based output stream using the legacy CRF string interface.
218 * @param filename Output file path.
219 * @param width Output width in pixels.
220 * @param height Output height in pixels.
221 * @param fps Nominal input frame rate.
222 * @param crf Constant Rate Factor as a string.
223 * @return true on success.
224 */
225 bool open_ts(const std::string &filename, int width, int height, float fps, const char *crf);
226 /**
227 * @brief Open a timestamp-based output stream using explicit encoder options.
228 * @param filename Output file path.
229 * @param width Output width in pixels.
230 * @param height Output height in pixels.
231 * @param fps Nominal input frame rate.
232 * @param opts Encoder configuration.
233 * @return true on success.
234 */
235 bool open_ts(const std::string &filename, int width, int height, float fps, const EncodeOptions &opts);
236 /**
237 * @brief Queue a host RGBA frame using capture timestamps.
238 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
239 */
240 void write_ts(void *rgba_buffer);
241 /** @brief Close the writer and flush pending packets. */
242 void close();
243 /** @brief Check whether the writer is currently open. */
244 bool is_open() const { return opened; }
245 /// @brief True when FFmpeg identifies the active encoder as hardware or hybrid.
246 bool is_hardware_encode() const { return active_encoder_hardware; }
247 /// @brief If true, keep one pending frame and pace producer threads to the
248 /// encoder instead of dropping frames. Intended for headless/batch
249 /// transcoding where every input frame must reach the output. Default: false.
250 void set_block_when_full(bool value) { block_when_full = value; }
251 /** @brief Check whether the encoder queue blocks instead of dropping frames. */
252 bool get_block_when_full() const { return block_when_full; }
253 /**
254 * @brief Return the output timeline length in nominal frame ticks.
255 *
256 * For sequential writes this equals the submitted frame count. Explicit
257 * PTS writes may leave gaps, in which case it is the highest accepted PTS
258 * plus one.
259 */
260 int64_t get_frame_count() const { return frame_count; }
261 /** @brief Return the current logical byte position of the output muxer. */
262 std::uint64_t get_bytes_written() const { return bytes_written.load(std::memory_order_relaxed); }
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, bool prefer_cuda_rgba);
327 /** @brief Start the background encoder thread. */
328 void startEncoderThread();
329 /** @brief Stop the background encoder thread. */
330 void stopEncoderThread();
331 /** @brief Encoder thread main loop. */
332 void encodeLoop(std::stop_token stop_token);
333 /** @brief Encode and write one frame. */
334 void encodeAndWriteFrame(AVFrame *in_frame);
335 /** @brief Drain packets from the codec into the container. */
336 void drainEncoderPackets();
337 /** @brief Refresh the logical output byte count from FFmpeg. */
338 void updateBytesWritten() noexcept;
339 /** @brief Release a frame allocated for the encode queue. */
340 void releaseFrame(AVFrame *f);
341};
342
343/**
344 * @brief Copy audio from one video file to another.
345 * @param sourceAudioFile Input media file containing the audio stream.
346 * @param destVideoFile Output video file to receive the audio stream.
347 */
348extern void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile);
349/**
350 * @brief Free FFmpeg format contexts used during transfer operations.
351 * @param source_ctx Source format context.
352 * @param dest_ctx Destination format context.
353 * @param output_ctx Output format context.
354 */
355extern void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx);
356
357#endif
std::uint64_t get_bytes_written() const
Return the current logical byte position of the output muxer.
Definition mxwrite.hpp:262
bool is_hardware_encode() const
True when FFmpeg identifies the active encoder as hardware or hybrid.
Definition mxwrite.hpp:246
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:1862
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:252
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:1936
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:250
void write_at_pts(void *rgba_buffer, int64_t pts)
Queue a host RGBA frame with an explicit presentation timestamp.
Definition mxwrite.cpp:1753
~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:977
void write(void *rgba_buffer)
Queue a host RGBA frame for immediate-mode encoding.
Definition mxwrite.cpp:1751
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:998
bool is_open() const
Check whether the writer is currently open.
Definition mxwrite.hpp:244
double get_duration() const
Return the encoded duration in seconds.
Definition mxwrite.cpp:2300
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:1938
void close()
Close the writer and flush pending packets.
Definition mxwrite.cpp:2239
int64_t get_frame_count() const
Return the output timeline length in nominal frame ticks.
Definition mxwrite.hpp:260
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:1860
void write_ts(void *rgba_buffer)
Queue a host RGBA frame using capture timestamps.
Definition mxwrite.cpp:2049
std::vector< EncoderInfo > available_video_encoders()
Definition mxwrite.cpp:920
std::vector< EncoderOptionInfo > video_encoder_options(std::string_view encoder_name)
Return the options exposed by one registered video encoder.
Definition mxwrite.cpp:958
void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile)
Copy audio from one video file to another.
Definition mxwrite.cpp:221
void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx)
Free FFmpeg format contexts used during transfer operations.
Definition mxwrite.cpp:209
HDR output options.
Definition mxwrite.hpp:122
std::vector< uint8_t > content_light
Raw AVContentLightMetadata side-data bytes, or empty.
Definition mxwrite.hpp:131
int color_range
AVColorRange value. Raw AVMasteringDisplayMetadata side-data bytes, or empty.
Definition mxwrite.hpp:127
std::vector< uint8_t > mastering_display
Definition mxwrite.hpp:129
int color_trc
AVColorTransferCharacteristic value.
Definition mxwrite.hpp:125
bool enabled
Enables the HDR output path.
Definition mxwrite.hpp:123
int color_primaries
AVColorPrimaries value.
Definition mxwrite.hpp:124
int color_space
AVColorSpace value.
Definition mxwrite.hpp:126
User-configurable video encoder quality options.
Definition mxwrite.hpp:94
std::string codec
Encoder selection policy or exact FFmpeg encoder name.
Definition mxwrite.hpp:99
std::int64_t bit_rate
Target bits/second; 0 selects CRF/CQ.
Definition mxwrite.hpp:98
std::string tune
Optional tuning mode.
Definition mxwrite.hpp:96
bool realtime
Enable low-latency settings.
Definition mxwrite.hpp:101
std::string ffmpeg_options
Additional FFmpeg-style video encoder options.
Definition mxwrite.hpp:100
struct EncodeOptions::HdrInfo hdr
int crf
Constant Rate Factor.
Definition mxwrite.hpp:97
bool block_when_full
Pace producers to encoder throughput instead of dropping frames.
Definition mxwrite.hpp:102
std::string preset
Encoder preset name.
Definition mxwrite.hpp:95
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