MXVK Vulkan Framework 0.24.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 <chrono>
17#include <condition_variable>
18#include <atomic>
19#include <mutex>
20#include <queue>
21#include <string>
22#include <string_view>
23#include <thread>
24#include <vector>
25#include <cstdint>
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/**
39 * @brief User-configurable video encoder quality options.
40 *
41 * preset: x264 preset name — ultrafast, superfast, veryfast, faster, fast,
42 * medium, slow, slower, veryslow. Mapped to NVENC p1..p7.
43 * tune: x264 tune — empty string (none), film, animation, grain, stillimage,
44 * psnr, ssim, fastdecode, zerolatency.
45 * crf: Constant Rate Factor, 0 (lossless) .. 51 (worst). 18 is visually
46 * near-lossless; 23 is default for x264; 28 is typical "small file".
47 * For NVENC this is forwarded as `cq`.
48 * codec: "auto" (NVENC if available, else software), "software" (force software),
49 * "nvenc" (force resolution-selected NVENC), "h264_nvenc", or
50 * "hevc_nvenc". NVENC requests fall back to the matching software codec.
51 * realtime: when true, applies low-latency settings (tune=zerolatency for x264,
52 * tune=ll + zerolatency=1 for NVENC). Overrides tune value.
53 * block_when_full: when true, producer threads block if the encoder queue is
54 * full instead of dropping frames.
55 */
57 std::string preset = "medium"; ///< Encoder preset name.
58 std::string tune = ""; ///< Optional tuning mode.
59 int crf = 18; ///< Constant Rate Factor.
60 std::string codec = "auto"; ///< Encoder selection policy or concrete NVENC codec.
61 bool realtime = false; ///< Enable low-latency settings.
62 bool block_when_full = false; ///< Block producer threads instead of dropping when the encoder queue is full.
63
64 /**
65 * @brief HDR output options.
66 *
67 * When @ref HdrInfo::enabled is true, the writer switches to a dedicated
68 * HEVC Main10 + BT.2020 output path that:
69 * - Encodes with libx265 at 10-bit (AV_PIX_FMT_YUV420P10LE).
70 * - Tags the stream with BT.2020 primaries, BT.2020 non-constant luminance
71 * matrix, and SMPTE ST.2084 (PQ) transfer.
72 * - Converts incoming 8-bit sRGB RGBA shader output into PQ-encoded
73 * 10-bit YUV, placing SDR-range content at the 100-nit reference level
74 * inside the PQ signal (SDR-in-HDR-container).
75 * - Copies @ref mastering_display and @ref content_light side data from
76 * the input stream when provided, so player HDR metadata is preserved.
77 *
78 * This mode is intended for use when the *input* video is HDR; the 8-bit
79 * GL pipeline cannot reconstruct the original highlight precision, but the
80 * resulting file is a correctly-tagged HDR container.
81 */
82 struct HdrInfo {
83 bool enabled = false; ///< Enables the HDR output path.
84 int color_primaries = 0; ///< AVColorPrimaries value.
85 int color_trc = 0; ///< AVColorTransferCharacteristic value.
86 int color_space = 0; ///< AVColorSpace value.
87 int color_range = 0; ///< AVColorRange value.
88 /// Raw AVMasteringDisplayMetadata side-data bytes, or empty.
89 std::vector<uint8_t> mastering_display;
90 /// Raw AVContentLightMetadata side-data bytes, or empty.
91 std::vector<uint8_t> content_light;
92 } hdr;
93};
94
95/**
96 * @brief FFmpeg-backed RGBA video writer.
97 *
98 * The writer accepts host RGBA buffers, optional CUDA device buffers, and
99 * 16-bit HDR RGBA buffers. It can encode either frame-by-frame or from
100 * timestamped frames, depending on the open mode.
101 */
102class Writer {
103 public:
104 /** @brief Construct a closed writer. */
105 Writer() = default;
106
107 /**
108 * @brief Open an output file using the legacy CRF string interface.
109 * @param filename Output file path.
110 * @param width Output width in pixels.
111 * @param height Output height in pixels.
112 * @param fps Output frame rate.
113 * @param crf Constant Rate Factor as a string.
114 * @return true on success.
115 */
116 bool open(const std::string &filename, int width, int height, float fps, const char *crf);
117 /**
118 * @brief Open an output file using explicit encoder options.
119 * @param filename Output file path.
120 * @param width Output width in pixels.
121 * @param height Output height in pixels.
122 * @param fps Output frame rate.
123 * @param opts Encoder configuration.
124 * @return true on success.
125 */
126 bool open(const std::string &filename, int width, int height, float fps, const EncodeOptions &opts);
127 /**
128 * @brief Queue a host RGBA frame for immediate-mode encoding.
129 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
130 */
131 void write(void *rgba_buffer);
132 /**
133 * @brief Write a 16-bit RGBA frame that is already PQ- or HLG-encoded in
134 * BT.2020 primaries (8 bytes/pixel: R16,G16,B16,A16, little-endian
135 * unsigned normalised).
136 *
137 * The data is expected to originate from the HDR GPU encode pass: it is
138 * BT.2020 primaries with a non-linear PQ (or HLG) transfer already
139 * applied, so this path skips colour-space conversion and only performs
140 * (a) the BT.2020 non-constant-luminance RGB'->YCbCr' matrix, and
141 * (b) 16-bit -> 10-bit limited-range scaling,
142 * producing AV_PIX_FMT_YUV420P10LE for libx265 Main10. Requires the
143 * writer to have been opened with @ref EncodeOptions::HdrInfo::enabled.
144 * @param rgba16_buffer Pointer to tightly packed RGBA16 pixels.
145 */
146 void write_hdr_rgba16(void *rgba16_buffer);
147 /**
148 * @brief Queue a CUDA RGBA frame for encoding.
149 * @param cuda_rgba_buffer CUDA device pointer.
150 * @param src_stride Source row pitch in bytes.
151 * @param bottom_up Whether the source is stored bottom-up.
152 * @return true if the frame was accepted.
153 */
154 bool write_cuda_rgba(void *cuda_rgba_buffer, int src_stride, bool bottom_up = false);
155 /**
156 * @brief Open a timestamp-based output stream using the legacy CRF string interface.
157 * @param filename Output file path.
158 * @param width Output width in pixels.
159 * @param height Output height in pixels.
160 * @param fps Nominal input frame rate.
161 * @param crf Constant Rate Factor as a string.
162 * @return true on success.
163 */
164 bool open_ts(const std::string &filename, int width, int height, float fps, const char *crf);
165 /**
166 * @brief Open a timestamp-based output stream using explicit encoder options.
167 * @param filename Output file path.
168 * @param width Output width in pixels.
169 * @param height Output height in pixels.
170 * @param fps Nominal input frame rate.
171 * @param opts Encoder configuration.
172 * @return true on success.
173 */
174 bool open_ts(const std::string &filename, int width, int height, float fps, const EncodeOptions &opts);
175 /**
176 * @brief Queue a host RGBA frame using capture timestamps.
177 * @param rgba_buffer Pointer to tightly packed RGBA8 pixels.
178 */
179 void write_ts(void *rgba_buffer);
180 /** @brief Close the writer and flush pending packets. */
181 void close();
182 /** @brief Check whether the writer is currently open. */
183 bool is_open() const { return opened; }
184 /// @brief True when the active encoder backend is hardware (NVENC).
185 bool is_hardware_encode() const { return use_hw_encode; }
186 /// @brief If true, producer threads block when the encoder queue is full
187 /// instead of dropping frames. Intended for headless/batch transcoding
188 /// where every input frame must reach the output. Default: false (drop).
189 void set_block_when_full(bool value) { block_when_full = value; }
190 /** @brief Check whether the encoder queue blocks instead of dropping frames. */
191 bool get_block_when_full() const { return block_when_full; }
192 /** @brief Return the number of frames submitted to the writer. */
193 int64_t get_frame_count() const { return frame_count; }
194 /** @brief Return the encoded duration in seconds. */
195 double get_duration() const;
196 /** @brief Close the writer on destruction if it is still open. */
198 if (is_open()) {
199 close();
200 opened = false;
201 }
202 }
203
204 private:
205 bool opened{false}; ///< Internal open-state flag.
206 int width = 0; ///< Output width in pixels.
207 int height = 0; ///< Output height in pixels.
208 int fps_num = 0; ///< Output FPS numerator.
209 int fps_den = 0; ///< Output FPS denominator.
210 int64_t frame_count = 0; ///< Frames submitted so far.
211 double last_duration = 0.0; ///< Cached duration from the last encode step.
212 AVFormatContext *format_ctx = nullptr; ///< Active container context.
213 AVCodecContext *codec_ctx = nullptr; ///< Active codec context.
214 AVStream *stream = nullptr; ///< Output video stream.
215 AVFrame *frameYUV = nullptr; ///< Software-converted YUV frame.
216 AVFrame *frameRGBA = nullptr; ///< Staging RGBA frame.
217 AVFrame *frame10 = nullptr; ///< YUV420P10LE frame used for HDR output.
218 AVFrame *upload_sw_frame = nullptr; ///< Software upload frame used by CUDA/hardware paths.
219 AVBufferRef *hw_device_ctx = nullptr; ///< Hardware device context, when available.
220 AVBufferRef *hw_frames_ctx = nullptr; ///< Hardware frames pool, when available.
221 bool use_hw_encode = false; ///< True when hardware encoding is active.
222#ifdef MXWRITE_HAS_CUDA_COPY
223 // Dedicated stream so the producer's RGBA→hwframe copy does not serialise
224 // with the renderer's default-stream work or with the encoder thread.
225 cudaStream_t cuda_upload_stream = nullptr;
226#endif
227 bool hdr_output = false; ///< True when HDR (HEVC Main10/PQ) output is active.
228 EncodeOptions::HdrInfo hdr_info; ///< HDR metadata captured at open() time.
229 SwsContext *sws_ctx = nullptr; ///< Frame conversion context.
230 AVRational time_base; ///< Stream time base.
231 /** @brief Convert a frame rate into a rational numerator/denominator pair. */
232 void calculateFPSFraction(float fps, int &fps_num, int &fps_den);
233 std::chrono::steady_clock::time_point recordingStart; ///< Start time for timestamp mode.
234
235 std::queue<AVFrame *> encode_queue; ///< Pending encoded frames.
236 // Deep enough to absorb encoder hiccups (~4s at 30fps, ~2s at 60fps).
237 // Memory cost is bounded by the NVENC frame pool / sw RGBA frame buffer.
238 static constexpr size_t MAX_QUEUE_SIZE = 120;
239 std::condition_variable queue_cv; ///< Signals queue availability.
240 std::jthread encode_thread; ///< Background encoder thread.
241
242 std::mutex queue_mutex{}; ///< Guards the frame queue.
243 std::mutex writer_mutex{}; ///< Guards writer state transitions.
244 bool stop_requested = false; ///< Signals encoder shutdown.
245 std::atomic<bool> block_when_full{false}; ///< Queue backpressure mode.
246
247 /** @brief Shared implementation for open() and open_ts(). */
248 bool openInternal(const std::string &filename, int w, int h, float fps, const EncodeOptions &opts, bool ts_mode);
249 /** @brief Initialize CUDA/NVENC resources when hardware encoding is selected. */
250 bool initHardwareEncoding();
251 /** @brief Start the background encoder thread. */
252 void startEncoderThread();
253 /** @brief Stop the background encoder thread. */
254 void stopEncoderThread();
255 /** @brief Encoder thread main loop. */
256 void encodeLoop(std::stop_token stop_token);
257 /** @brief Encode and write one frame. */
258 void encodeAndWriteFrame(AVFrame *in_frame);
259 /** @brief Drain packets from the codec into the container. */
260 void drainEncoderPackets();
261 /** @brief Release a frame allocated for the encode queue. */
262 void releaseFrame(AVFrame *f);
263};
264
265/**
266 * @brief Copy audio from one video file to another.
267 * @param sourceAudioFile Input media file containing the audio stream.
268 * @param destVideoFile Output video file to receive the audio stream.
269 */
270extern void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile);
271/**
272 * @brief Free FFmpeg format contexts used during transfer operations.
273 * @param source_ctx Source format context.
274 * @param dest_ctx Destination format context.
275 * @param output_ctx Output format context.
276 */
277extern void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx);
278
279#endif
bool is_hardware_encode() const
True when the active encoder backend is hardware (NVENC).
Definition mxwrite.hpp:185
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:191
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:1185
void set_block_when_full(bool value)
If true, producer threads block when the encoder queue is full instead of dropping frames....
Definition mxwrite.hpp:189
~Writer()
Close the writer on destruction if it is still open.
Definition mxwrite.hpp:197
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:473
void write(void *rgba_buffer)
Queue a host RGBA frame for immediate-mode encoding.
Definition mxwrite.cpp:1002
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:491
bool is_open() const
Check whether the writer is currently open.
Definition mxwrite.hpp:183
double get_duration() const
Return the encoded duration in seconds.
Definition mxwrite.cpp:1521
void close()
Close the writer and flush pending packets.
Definition mxwrite.cpp:1465
int64_t get_frame_count() const
Return the number of frames submitted to the writer.
Definition mxwrite.hpp:193
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:1100
void write_ts(void *rgba_buffer)
Queue a host RGBA frame using capture timestamps.
Definition mxwrite.cpp:1311
void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile)
Copy audio from one video file to another.
Definition mxwrite.cpp:234
void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx)
Free FFmpeg format contexts used during transfer operations.
Definition mxwrite.cpp:220
HDR output options.
Definition mxwrite.hpp:82
std::vector< uint8_t > content_light
Raw AVContentLightMetadata side-data bytes, or empty.
Definition mxwrite.hpp:91
int color_range
AVColorRange value. Raw AVMasteringDisplayMetadata side-data bytes, or empty.
Definition mxwrite.hpp:87
std::vector< uint8_t > mastering_display
Definition mxwrite.hpp:89
int color_trc
AVColorTransferCharacteristic value.
Definition mxwrite.hpp:85
bool enabled
Enables the HDR output path.
Definition mxwrite.hpp:83
int color_primaries
AVColorPrimaries value.
Definition mxwrite.hpp:84
int color_space
AVColorSpace value.
Definition mxwrite.hpp:86
User-configurable video encoder quality options.
Definition mxwrite.hpp:56
std::string codec
Encoder selection policy or concrete NVENC codec.
Definition mxwrite.hpp:60
std::string tune
Optional tuning mode.
Definition mxwrite.hpp:58
bool realtime
Enable low-latency settings.
Definition mxwrite.hpp:61
struct EncodeOptions::HdrInfo hdr
int crf
Constant Rate Factor.
Definition mxwrite.hpp:59
bool block_when_full
Block producer threads instead of dropping when the encoder queue is full.
Definition mxwrite.hpp:62
std::string preset
Encoder preset name.
Definition mxwrite.hpp:57
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