11#ifdef MXWRITE_HAS_CUDA_COPY
12#include <cuda_runtime.h>
15#include <libavcodec/avcodec.h>
16#include <libavformat/avformat.h>
17#include <libavutil/imgutils.h>
18#include <libavutil/mathematics.h>
19#include <libavutil/opt.h>
20#include <libavutil/mastering_display_metadata.h>
21#include <libswscale/swscale.h>
28constexpr float kPqM1 = 2610.0f / 16384.0f;
29constexpr float kPqM2 = (2523.0f / 4096.0f) * 128.0f;
30constexpr float kPqC1 = 3424.0f / 4096.0f;
31constexpr float kPqC2 = (2413.0f / 4096.0f) * 32.0f;
32constexpr float kPqC3 = (2392.0f / 4096.0f) * 32.0f;
38 return (v <= 0.04045f) ? (v / 12.92f)
39 : std::pow((v + 0.055f) / 1.055f, 2.4f);
44 const float Lm = std::pow(std::max(0.0f, L),
kPqM1);
46 const float den = 1.0f +
kPqC3 * Lm;
47 return std::pow(num / den,
kPqM2);
51 if (v < 0.0f) v = 0.0f;
52 if (v > 1023.0f) v = 1023.0f;
53 return static_cast<uint16_t
>(v + 0.5f);
62 uint16_t *y_plane,
int y_stride_shorts,
63 uint16_t *u_plane,
int u_stride_shorts,
64 uint16_t *v_plane,
int v_stride_shorts,
65 int width,
int height) {
71 constexpr float kKr = 0.2627f;
72 constexpr float kKg = 0.6780f;
73 constexpr float kKb = 0.0593f;
74 constexpr float kPbDiv = 1.0f / 1.8814f;
75 constexpr float kPrDiv = 1.0f / 1.4746f;
77 for (
int y = 0; y < height; y += 2) {
78 const int y1 = std::min(y + 1, height - 1);
79 const uint8_t *row0 = rgba + y * src_stride_bytes;
80 const uint8_t *row1 = rgba + y1 * src_stride_bytes;
81 uint16_t *yr0 = y_plane + y * y_stride_shorts;
82 uint16_t *yr1 = y_plane + y1 * y_stride_shorts;
83 uint16_t *ur = u_plane + (y / 2) * u_stride_shorts;
84 uint16_t *vr = v_plane + (y / 2) * v_stride_shorts;
86 for (
int x = 0; x < width; x += 2) {
87 const int x1 = std::min(x + 1, width - 1);
90 auto loadPq = [](
const uint8_t *px,
91 float &Y,
float &U,
float &V) {
93 const float r =
srgbEotf(px[0] * (1.0f / 255.0f));
94 const float g =
srgbEotf(px[1] * (1.0f / 255.0f));
95 const float b =
srgbEotf(px[2] * (1.0f / 255.0f));
101 const float rp =
pqOetf(rL);
102 const float gp =
pqOetf(gL);
103 const float bp =
pqOetf(bL);
105 Y = kKr * rp + kKg * gp + kKb * bp;
106 U = (bp - Y) * kPbDiv;
107 V = (rp - Y) * kPrDiv;
114 loadPq(row0 + x * 4, Y00, U00, V00);
115 loadPq(row0 + x1 * 4, Y01, U01, V01);
116 loadPq(row1 + x * 4, Y10, U10, V10);
117 loadPq(row1 + x1 * 4, Y11, U11, V11);
120 yr0[x] =
clamp10(Y00 * 876.0f + 64.0f);
121 yr0[x1] =
clamp10(Y01 * 876.0f + 64.0f);
122 yr1[x] =
clamp10(Y10 * 876.0f + 64.0f);
123 yr1[x1] =
clamp10(Y11 * 876.0f + 64.0f);
126 const float Uavg = 0.25f * (U00 + U01 + U10 + U11);
127 const float Vavg = 0.25f * (V00 + V01 + V10 + V11);
128 ur[x / 2] =
clamp10(Uavg * 896.0f + 512.0f);
129 vr[x / 2] =
clamp10(Vavg * 896.0f + 512.0f);
140 int src_stride_shorts,
141 uint16_t *y_plane,
int y_stride_shorts,
142 uint16_t *u_plane,
int u_stride_shorts,
143 uint16_t *v_plane,
int v_stride_shorts,
144 int width,
int height) {
145 constexpr float kKr = 0.2627f;
146 constexpr float kKg = 0.6780f;
147 constexpr float kKb = 0.0593f;
148 constexpr float kPbDiv = 1.0f / 1.8814f;
149 constexpr float kPrDiv = 1.0f / 1.4746f;
150 constexpr float kInv65535 = 1.0f / 65535.0f;
152 for (
int y = 0; y < height; y += 2) {
153 const int y1 = std::min(y + 1, height - 1);
154 const uint16_t *row0 = rgba + y * src_stride_shorts;
155 const uint16_t *row1 = rgba + y1 * src_stride_shorts;
156 uint16_t *yr0 = y_plane + y * y_stride_shorts;
157 uint16_t *yr1 = y_plane + y1 * y_stride_shorts;
158 uint16_t *ur = u_plane + (y / 2) * u_stride_shorts;
159 uint16_t *vr = v_plane + (y / 2) * v_stride_shorts;
161 for (
int x = 0; x < width; x += 2) {
162 const int x1 = std::min(x + 1, width - 1);
164 auto load = [&](
const uint16_t *px,
float &Y,
float &U,
float &V) {
165 const float rp = px[0] * kInv65535;
166 const float gp = px[1] * kInv65535;
167 const float bp = px[2] * kInv65535;
168 Y = kKr * rp + kKg * gp + kKb * bp;
169 U = (bp - Y) * kPbDiv;
170 V = (rp - Y) * kPrDiv;
177 load(row0 + x * 4, Y00, U00, V00);
178 load(row0 + x1 * 4, Y01, U01, V01);
179 load(row1 + x * 4, Y10, U10, V10);
180 load(row1 + x1 * 4, Y11, U11, V11);
182 yr0[x] =
clamp10(Y00 * 876.0f + 64.0f);
183 yr0[x1] =
clamp10(Y01 * 876.0f + 64.0f);
184 yr1[x] =
clamp10(Y10 * 876.0f + 64.0f);
185 yr1[x1] =
clamp10(Y11 * 876.0f + 64.0f);
187 const float Uavg = 0.25f * (U00 + U01 + U10 + U11);
188 const float Vavg = 0.25f * (V00 + V01 + V10 + V11);
189 ur[x / 2] =
clamp10(Uavg * 896.0f + 512.0f);
190 vr[x / 2] =
clamp10(Vavg * 896.0f + 512.0f);
201 const char *ext = strrchr(filename,
'.');
205 std::string lower_ext(ext);
206 std::transform(lower_ext.begin(), lower_ext.end(), lower_ext.begin(),
207 [](
unsigned char c) { return std::tolower(c); });
208 static const char *kSupported[] = {
209 ".mp4",
".mkv",
".mov",
".avi",
".m4v",
210 ".ts",
".mts",
".m2ts",
".mpg",
".mpeg",
211 ".flv",
".f4v",
".3gp",
".3g2",
".wmv",
214 for (
const char *s : kSupported) {
215 if (lower_ext == s)
return true;
221 AVFormatContext *dest_ctx,
222 AVFormatContext *output_ctx) {
224 avformat_close_input(&source_ctx);
226 avformat_close_input(&dest_ctx);
228 if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
229 avio_closep(&output_ctx->pb);
230 avformat_free_context(output_ctx);
234void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile) {
237 std::cerr <<
"Unsupported output format. Supported formats: .mp4, .mkv, .avi, .mov\n";
241 AVFormatContext *source_ctx =
nullptr, *dest_ctx =
nullptr, *output_ctx =
nullptr;
242 int source_audio_idx = -1, dest_video_idx = -1, dest_audio_idx = -1;
243 std::string temp_output = std::string(destVideoFile) +
".tmp";
245 if (avformat_open_input(&source_ctx, sourceAudioFile.data(),
nullptr,
nullptr) != 0 ||
246 avformat_open_input(&dest_ctx, destVideoFile.data(),
nullptr,
nullptr) != 0) {
247 std::cerr <<
"Failed to open input files\n";
252 if (avformat_find_stream_info(source_ctx,
nullptr) < 0 ||
253 avformat_find_stream_info(dest_ctx,
nullptr) < 0) {
254 std::cerr <<
"Failed to find stream info\n";
259 for (
unsigned i = 0; i < source_ctx->nb_streams; ++i) {
260 if (source_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
261 source_audio_idx = i;
266 for (
unsigned i = 0; i < dest_ctx->nb_streams; ++i) {
267 AVMediaType type = dest_ctx->streams[i]->codecpar->codec_type;
268 if (type == AVMEDIA_TYPE_VIDEO)
270 else if (type == AVMEDIA_TYPE_AUDIO)
274 if (source_audio_idx == -1 || dest_video_idx == -1) {
275 std::cerr <<
"Required streams not found\n";
280 const AVOutputFormat *output_fmt = av_guess_format(
nullptr, destVideoFile.data(),
nullptr);
282 output_fmt = av_guess_format(
"mp4",
nullptr,
nullptr);
284 std::cerr <<
"Failed to determine output format\n";
290 if (avformat_alloc_output_context2(&output_ctx, output_fmt,
nullptr, temp_output.c_str()) < 0) {
291 std::cerr <<
"Failed to create output context\n";
296 const AVCodec *audio_codec = avcodec_find_decoder(source_ctx->streams[source_audio_idx]->codecpar->codec_id);
298 std::cerr <<
"Failed to find audio decoder\n";
303 for (
unsigned i = 0; i < dest_ctx->nb_streams; ++i) {
304 if (dest_ctx->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
308 AVStream *dest_stream = dest_ctx->streams[i];
309 AVStream *out_stream = avformat_new_stream(output_ctx,
nullptr);
311 std::cerr <<
"Failed to create output stream\n";
316 if (avcodec_parameters_copy(out_stream->codecpar, dest_stream->codecpar) < 0) {
317 std::cerr <<
"Failed to copy video parameters\n";
322 out_stream->time_base = dest_stream->time_base;
323 out_stream->codecpar->codec_tag = 0;
326 AVStream *out_stream = avformat_new_stream(output_ctx, audio_codec);
328 std::cerr <<
"Failed to create audio stream\n";
333 AVCodecParameters *source_params = source_ctx->streams[source_audio_idx]->codecpar;
334 if (avcodec_parameters_copy(out_stream->codecpar, source_params) < 0) {
335 std::cerr <<
"Failed to copy audio parameters\n";
340 if (source_params->frame_size == 0) {
341 out_stream->codecpar->frame_size = 1024;
343 out_stream->codecpar->frame_size = source_params->frame_size;
346 out_stream->time_base = source_ctx->streams[source_audio_idx]->time_base;
347 out_stream->codecpar->codec_tag = 0;
348 dest_audio_idx = out_stream->index;
350 if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {
351 if (avio_open(&output_ctx->pb, temp_output.c_str(), AVIO_FLAG_WRITE) < 0) {
352 std::cerr <<
"Failed to open output file\n";
357 if (avformat_write_header(output_ctx,
nullptr) < 0) {
358 std::cerr <<
"Failed to write header\n";
363 while (av_read_frame(dest_ctx, &packet) >= 0) {
364 if (packet.stream_index == dest_audio_idx) {
365 av_packet_unref(&packet);
369 AVStream *in_stream = dest_ctx->streams[packet.stream_index];
370 AVStream *out_stream = output_ctx->streams[packet.stream_index];
371 av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
373 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
374 std::cerr <<
"Failed to write packet\n";
375 av_packet_unref(&packet);
379 av_packet_unref(&packet);
382 int64_t video_duration_ts = 0;
384 AVStream *vid_stream = dest_ctx->streams[0];
385 if (vid_stream->duration > 0) {
386 video_duration_ts = av_rescale_q(vid_stream->duration, vid_stream->time_base, source_ctx->streams[source_audio_idx]->time_base);
387 }
else if (dest_ctx->duration > 0) {
388 AVRational av_tb = {1, AV_TIME_BASE};
389 video_duration_ts = av_rescale_q(dest_ctx->duration, av_tb, source_ctx->streams[source_audio_idx]->time_base);
393 av_seek_frame(source_ctx, source_audio_idx, 0, AVSEEK_FLAG_BACKWARD);
394 while (av_read_frame(source_ctx, &packet) >= 0) {
395 if (packet.stream_index == source_audio_idx) {
396 if (video_duration_ts > 0 && packet.pts != AV_NOPTS_VALUE && packet.pts > video_duration_ts) {
397 av_packet_unref(&packet);
400 AVStream *in_stream = source_ctx->streams[packet.stream_index];
401 AVStream *out_stream = output_ctx->streams[dest_audio_idx];
402 av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
403 packet.stream_index = dest_audio_idx;
405 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
406 std::cerr <<
"Failed to write audio packet\n";
407 av_packet_unref(&packet);
412 av_packet_unref(&packet);
414 av_write_trailer(output_ctx);
416 std::remove(destVideoFile.data());
417 std::rename(temp_output.c_str(), destVideoFile.data());
420void Writer::calculateFPSFraction(
float fps,
int &fps_num,
int &fps_den) {
421 const float epsilon = 0.001f;
423 if (std::fabs(fps - 29.97f) < epsilon) {
426 }
else if (std::fabs(fps - 59.94f) < epsilon) {
430 float precision = 1000.0f;
431 fps_num =
static_cast<int>(std::round(fps * precision));
432 fps_den =
static_cast<int>(precision);
433 int gcd = std::gcd(fps_num, fps_den);
444 if (p ==
"ultrafast")
return "p1";
445 if (p ==
"superfast")
return "p2";
446 if (p ==
"veryfast" || p ==
"faster")
return "p3";
447 if (p ==
"fast")
return "p4";
448 if (p ==
"medium" || p.empty())
return "p5";
449 if (p ==
"slow" || p ==
"slower")
return "p6";
450 if (p ==
"veryslow")
return "p7";
456 static const char *presets[] = {
457 "ultrafast",
"superfast",
"veryfast",
"faster",
"fast",
458 "medium",
"slow",
"slower",
"veryslow",
"placebo"
460 for (
const char *n : presets)
if (p == n)
return true;
465 std::transform(text.begin(), text.end(), text.begin(), [](
unsigned char ch) {
466 return static_cast<char>(std::tolower(ch));
473bool Writer::open(
const std::string &filename,
int w,
int h,
float fps,
const char *crf) {
474 std::lock_guard<std::mutex> lock(writer_mutex);
477 try { opts.
crf = std::stoi(crf); }
catch (...) {}
480 opts.
preset =
"ultrafast";
481 opts.
tune =
"zerolatency";
483 return openInternal(filename, w, h, fps, opts,
false);
487 std::lock_guard<std::mutex> lock(writer_mutex);
488 return openInternal(filename, w, h, fps, opts,
false);
491bool Writer::open_ts(
const std::string &filename,
int w,
int h,
float fps,
const char *crf) {
492 std::lock_guard<std::mutex> lock(writer_mutex);
495 try { opts.
crf = std::stoi(crf); }
catch (...) {}
497 opts.
preset =
"ultrafast";
498 opts.
tune =
"zerolatency";
500 return openInternal(filename, w, h, fps, opts,
true);
504 std::lock_guard<std::mutex> lock(writer_mutex);
505 return openInternal(filename, w, h, fps, opts,
true);
508bool Writer::initHardwareEncoding() {
509 if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA,
nullptr,
nullptr, 0) < 0) {
513 codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
514 codec_ctx->pix_fmt = AV_PIX_FMT_CUDA;
515 codec_ctx->sw_pix_fmt = AV_PIX_FMT_RGBA;
517 hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx);
518 if (!hw_frames_ctx) {
522 auto *frames_ctx =
reinterpret_cast<AVHWFramesContext *
>(hw_frames_ctx->data);
523 frames_ctx->format = AV_PIX_FMT_CUDA;
524 frames_ctx->sw_format = AV_PIX_FMT_RGBA;
525 frames_ctx->width = width;
526 frames_ctx->height = height;
530 frames_ctx->initial_pool_size =
static_cast<int>(MAX_QUEUE_SIZE) + 8;
532 if (av_hwframe_ctx_init(hw_frames_ctx) < 0) {
536 codec_ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ctx);
538 upload_sw_frame = av_frame_alloc();
539 if (!upload_sw_frame) {
542 upload_sw_frame->format = AV_PIX_FMT_RGBA;
543 upload_sw_frame->width = width;
544 upload_sw_frame->height = height;
545 if (av_frame_get_buffer(upload_sw_frame, 32) < 0) {
549#ifdef MXWRITE_HAS_CUDA_COPY
552 if (!cuda_upload_stream) {
553 if (cudaStreamCreateWithFlags(&cuda_upload_stream, cudaStreamNonBlocking) != cudaSuccess) {
554 cuda_upload_stream =
nullptr;
562bool Writer::openInternal(
const std::string &filename,
int w,
int h,
float fps,
const EncodeOptions &opts,
bool ts_mode) {
563 avformat_network_init();
564 av_log_set_level(AV_LOG_ERROR);
566 stop_requested =
false;
569 block_when_full.store(opts.
block_when_full, std::memory_order_relaxed);
571 while (!encode_queue.empty()) {
572 releaseFrame(encode_queue.front());
578 if (avformat_alloc_output_context2(&format_ctx,
nullptr,
nullptr, filename.c_str()) < 0) {
579 std::cerr <<
"Could not allocate output context.\n";
593 const AVCodec *hdr_codec = avcodec_find_encoder_by_name(
"libx265");
595 std::cerr <<
"MXWrite: HDR output requested but libx265 encoder not available.\n";
596 avformat_free_context(format_ctx);
597 format_ctx =
nullptr;
601 stream = avformat_new_stream(format_ctx, hdr_codec);
603 std::cerr <<
"MXWrite: could not create HDR stream.\n";
604 avformat_free_context(format_ctx);
605 format_ctx =
nullptr;
609 calculateFPSFraction(fps, fps_num, fps_den);
610 AVRational tb_hdr = {fps_den, fps_num};
611 stream->time_base = tb_hdr;
613 codec_ctx = avcodec_alloc_context3(hdr_codec);
615 std::cerr <<
"MXWrite: could not allocate HDR codec context.\n";
616 avformat_free_context(format_ctx);
617 format_ctx =
nullptr;
621 codec_ctx->width = width;
622 codec_ctx->height = height;
623 codec_ctx->time_base = stream->time_base;
624 codec_ctx->framerate = AVRational{fps_num, fps_den};
625 codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P10LE;
626 codec_ctx->profile = AV_PROFILE_HEVC_MAIN_10;
627 codec_ctx->bits_per_raw_sample = 10;
628 codec_ctx->gop_size = 30;
629 codec_ctx->max_b_frames = 0;
630 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
631 codec_ctx->thread_type = FF_THREAD_SLICE;
632 codec_ctx->delay = 0;
635 codec_ctx->color_primaries =
static_cast<AVColorPrimaries
>(
636 hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
637 codec_ctx->color_trc =
static_cast<AVColorTransferCharacteristic
>(
638 hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
639 codec_ctx->colorspace =
static_cast<AVColorSpace
>(
640 hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
641 codec_ctx->color_range =
static_cast<AVColorRange
>(
642 hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
643 codec_ctx->chroma_sample_location = AVCHROMA_LOC_LEFT;
646 std::string preset_hdr = opts.
preset.empty() ? std::string(
"medium") : opts.preset;
647 av_opt_set(codec_ctx->priv_data,
"preset", preset_hdr.c_str(), 0);
648 int crf_val_hdr = opts.
crf;
649 if (crf_val_hdr < 0) crf_val_hdr = 0;
650 if (crf_val_hdr > 51) crf_val_hdr = 51;
651 const std::string crf_hdr = std::to_string(crf_val_hdr);
652 av_opt_set(codec_ctx->priv_data,
"crf", crf_hdr.c_str(), 0);
656 std::string x265_params =
"profile=main10:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:range=limited:repeat-headers=1";
658 if (codec_ctx->color_trc == AVCOL_TRC_ARIB_STD_B67) {
659 x265_params =
"profile=main10:colorprim=bt2020:transfer=arib-std-b67:colormatrix=bt2020nc:range=limited:repeat-headers=1";
661 av_opt_set(codec_ctx->priv_data,
"x265-params", x265_params.c_str(), 0);
664 if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
665 codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
667 if (avcodec_open2(codec_ctx, hdr_codec,
nullptr) < 0) {
668 std::cerr <<
"MXWrite: could not open libx265 for HDR output.\n";
669 avcodec_free_context(&codec_ctx);
670 avformat_free_context(format_ctx);
671 format_ctx =
nullptr;
674 if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
675 std::cerr <<
"MXWrite: could not copy HDR codec parameters.\n";
676 avcodec_free_context(&codec_ctx);
677 avformat_free_context(format_ctx);
678 format_ctx =
nullptr;
685 auto attach_side = [&](AVPacketSideDataType type,
686 const std::vector<uint8_t> &payload) {
687 if (payload.empty())
return;
688 uint8_t *buf =
static_cast<uint8_t *
>(av_malloc(payload.size()));
690 std::memcpy(buf, payload.data(), payload.size());
691 const AVPacketSideData *added = av_packet_side_data_add(
692 &stream->codecpar->coded_side_data,
693 &stream->codecpar->nb_coded_side_data,
700 std::cerr <<
"MXWrite: failed to attach HDR side data (type " << (int)type <<
").\n";
703 attach_side(AV_PKT_DATA_MASTERING_DISPLAY_METADATA, hdr_info.mastering_display);
704 attach_side(AV_PKT_DATA_CONTENT_LIGHT_LEVEL, hdr_info.content_light);
706 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
707 if (avio_open(&format_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE) < 0) {
708 std::cerr <<
"MXWrite: could not open HDR output file: " << filename <<
"\n";
709 avcodec_free_context(&codec_ctx);
710 avformat_free_context(format_ctx);
711 format_ctx =
nullptr;
715 if (avformat_write_header(format_ctx,
nullptr) < 0) {
716 std::cerr <<
"MXWrite: error writing HDR MP4 header.\n";
717 avio_closep(&format_ctx->pb);
718 avcodec_free_context(&codec_ctx);
719 avformat_free_context(format_ctx);
720 format_ctx =
nullptr;
725 frame10 = av_frame_alloc();
727 std::cerr <<
"MXWrite: could not allocate YUV420P10LE frame.\n";
728 avio_closep(&format_ctx->pb);
729 avcodec_free_context(&codec_ctx);
730 avformat_free_context(format_ctx);
731 format_ctx =
nullptr;
734 frame10->format = AV_PIX_FMT_YUV420P10LE;
735 frame10->width = width;
736 frame10->height = height;
737 if (av_frame_get_buffer(frame10, 32) < 0) {
738 std::cerr <<
"MXWrite: could not allocate YUV420P10LE buffer.\n";
739 av_frame_free(&frame10);
740 avio_closep(&format_ctx->pb);
741 avcodec_free_context(&codec_ctx);
742 avformat_free_context(format_ctx);
743 format_ctx =
nullptr;
748 use_hw_encode =
false;
749 recordingStart = std::chrono::steady_clock::now();
750 startEncoderThread();
751 std::cout <<
"MXWrite: HDR output active (libx265 Main10, BT.2020, "
752 << (codec_ctx->color_trc == AVCOL_TRC_ARIB_STD_B67 ?
"HLG" :
"PQ")
758 const bool is_high_res = (width > 3840 || height > 2160);
760 const bool explicit_hevc_nvenc = (codec_pref ==
"hevc_nvenc" || codec_pref ==
"h265_nvenc");
761 const bool explicit_h264_nvenc = (codec_pref ==
"h264_nvenc");
762 const bool use_hevc_codec = explicit_hevc_nvenc || (!explicit_h264_nvenc && is_high_res);
763 const char *hw_codec_name = use_hevc_codec ?
"hevc_nvenc" :
"h264_nvenc";
764 const AVCodecID sw_codec_id = use_hevc_codec ? AV_CODEC_ID_HEVC : AV_CODEC_ID_H264;
767 const AVCodec *codec =
nullptr;
768 bool wants_hw =
false;
769 if (codec_pref ==
"software" || codec_pref ==
"x264" || codec_pref ==
"cpu") {
770 codec = avcodec_find_encoder(sw_codec_id);
775 codec = avcodec_find_encoder_by_name(hw_codec_name);
776 wants_hw = (codec !=
nullptr);
778 if (codec_pref ==
"nvenc" || explicit_hevc_nvenc || explicit_h264_nvenc) {
779 std::cerr <<
"MXWrite: NVENC requested but " << hw_codec_name
780 <<
" not available; falling back to software.\n";
782 codec = avcodec_find_encoder(sw_codec_id);
787 std::cerr <<
"Could not find " << (use_hevc_codec ?
"H.265" :
"H.264") <<
" encoder.\n";
788 avformat_free_context(format_ctx);
789 format_ctx =
nullptr;
794 std::string preset = opts.
preset.empty() ? std::string(
"medium") : opts.preset;
798 int crf_val = opts.
crf;
799 if (crf_val < 0) crf_val = 0;
800 if (crf_val > 51) crf_val = 51;
801 const std::string crf_str = std::to_string(crf_val);
803 stream = avformat_new_stream(format_ctx, codec);
805 std::cerr <<
"Could not create new stream.\n";
806 avformat_free_context(format_ctx);
807 format_ctx =
nullptr;
811 calculateFPSFraction(fps, fps_num, fps_den);
813 AVRational tb = {fps_den, fps_num};
814 stream->time_base = tb;
816 codec_ctx = avcodec_alloc_context3(codec);
818 std::cerr <<
"Could not allocate codec context.\n";
819 avformat_free_context(format_ctx);
820 format_ctx =
nullptr;
824 codec_ctx->width = width;
825 codec_ctx->height = height;
826 codec_ctx->time_base = stream->time_base;
827 codec_ctx->framerate = AVRational{fps_num, fps_den};
828 codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
829 codec_ctx->gop_size = 30;
830 codec_ctx->max_b_frames = 0;
831 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
835 codec_ctx->thread_type = FF_THREAD_SLICE;
836 codec_ctx->slices = 4;
838 codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
840 codec_ctx->delay = 0;
843 codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
848 av_opt_set(codec_ctx->priv_data,
"preset", nv_preset, 0);
850 const char *nv_tune = opts.
realtime ?
"ll" :
"hq";
851 av_opt_set(codec_ctx->priv_data,
"tune", nv_tune, 0);
852 av_opt_set(codec_ctx->priv_data,
"rc",
"vbr", 0);
853 av_opt_set(codec_ctx->priv_data,
"cq", crf_str.c_str(), 0);
855 av_opt_set(codec_ctx->priv_data,
"zerolatency",
"1", 0);
857 if (use_hevc_codec) {
858 av_opt_set(codec_ctx->priv_data,
"tier",
"high", 0);
861 if (initHardwareEncoding()) {
862 use_hw_encode =
true;
863 std::cout <<
"MXWrite: hardware encoder selected (" << hw_codec_name <<
")\n";
865 std::cerr <<
"MXWrite: " << hw_codec_name <<
" present but CUDA context failed, falling back to software encoder\n";
866 av_buffer_unref(&hw_frames_ctx);
867 av_buffer_unref(&hw_device_ctx);
868 avcodec_free_context(&codec_ctx);
870 codec = avcodec_find_encoder(sw_codec_id);
872 std::cerr <<
"Could not find software fallback encoder.\n";
873 avformat_free_context(format_ctx);
874 format_ctx =
nullptr;
878 codec_ctx = avcodec_alloc_context3(codec);
880 std::cerr <<
"Could not allocate fallback codec context.\n";
881 avformat_free_context(format_ctx);
882 format_ctx =
nullptr;
886 codec_ctx->width = width;
887 codec_ctx->height = height;
888 codec_ctx->time_base = stream->time_base;
889 codec_ctx->framerate = AVRational{fps_num, fps_den};
890 codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
891 codec_ctx->gop_size = 30;
892 codec_ctx->max_b_frames = 0;
893 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
895 codec_ctx->thread_type = FF_THREAD_SLICE;
896 codec_ctx->slices = 4;
898 codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
900 codec_ctx->delay = 0;
903 codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
908 if (!use_hw_encode) {
909 av_opt_set(codec_ctx->priv_data,
"preset", preset.c_str(), 0);
911 std::string tune = opts.
realtime ? std::string(
"zerolatency") : opts.tune;
912 if (!tune.empty() && tune !=
"none") {
913 av_opt_set(codec_ctx->priv_data,
"tune", tune.c_str(), 0);
915 av_opt_set(codec_ctx->priv_data,
"crf", crf_str.c_str(), 0);
916 if (opts.
realtime && sw_codec_id == AV_CODEC_ID_H264) {
919 av_opt_set(codec_ctx->priv_data,
"x264-params",
"bframes=0:ref=1:me=dia:subme=0", 0);
920 av_opt_set(codec_ctx->priv_data,
"force_cfr",
"1", 0);
926 if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
927 codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
929 if (avcodec_open2(codec_ctx, codec,
nullptr) < 0) {
930 std::cerr <<
"Could not open codec.\n";
931 avcodec_free_context(&codec_ctx);
932 avformat_free_context(format_ctx);
933 format_ctx =
nullptr;
936 if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
937 std::cerr <<
"Could not copy codec parameters.\n";
938 avcodec_free_context(&codec_ctx);
939 avformat_free_context(format_ctx);
940 format_ctx =
nullptr;
943 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
944 if (avio_open(&format_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE) < 0) {
945 std::cerr <<
"Could not open output file: " << filename <<
"\n";
946 avcodec_free_context(&codec_ctx);
947 avformat_free_context(format_ctx);
948 format_ctx =
nullptr;
952 if (avformat_write_header(format_ctx,
nullptr) < 0) {
953 std::cerr <<
"Error writing MP4 header.\n";
954 avio_closep(&format_ctx->pb);
955 avcodec_free_context(&codec_ctx);
956 avformat_free_context(format_ctx);
957 format_ctx =
nullptr;
961 if (!use_hw_encode) {
962 frameYUV = av_frame_alloc();
964 std::cerr <<
"Could not allocate YUV frame.\n";
965 avio_closep(&format_ctx->pb);
966 avcodec_free_context(&codec_ctx);
967 avformat_free_context(format_ctx);
968 format_ctx =
nullptr;
971 frameYUV->format = AV_PIX_FMT_YUV420P;
972 frameYUV->width = width;
973 frameYUV->height = height;
974 if (av_frame_get_buffer(frameYUV, 32) < 0) {
975 std::cerr <<
"Could not allocate frame buffer for YUV frame.\n";
976 av_frame_free(&frameYUV);
977 avio_closep(&format_ctx->pb);
978 avcodec_free_context(&codec_ctx);
979 avformat_free_context(format_ctx);
980 format_ctx =
nullptr;
984 sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA, width, height, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
986 std::cerr <<
"Could not initialize conversion context.\n";
987 av_frame_free(&frameYUV);
988 avio_closep(&format_ctx->pb);
989 avcodec_free_context(&codec_ctx);
990 avformat_free_context(format_ctx);
991 format_ctx =
nullptr;
997 recordingStart = std::chrono::steady_clock::now();
998 startEncoderThread();
1008 std::lock_guard<std::mutex> lock(writer_mutex);
1014 AVFrame *queued_frame = av_frame_alloc();
1015 if (!queued_frame) {
1016 std::cerr <<
"Writer: failed to allocate queued frame\n";
1020 if (use_hw_encode) {
1021 queued_frame->format = AV_PIX_FMT_CUDA;
1022 queued_frame->width = width;
1023 queued_frame->height = height;
1024 if (av_hwframe_get_buffer(hw_frames_ctx, queued_frame, 0) < 0) {
1025 std::cerr <<
"Writer: failed to allocate CUDA frame from hardware pool\n";
1026 releaseFrame(queued_frame);
1030 if (av_frame_make_writable(upload_sw_frame) < 0) {
1031 std::cerr <<
"Writer: software upload frame not writable\n";
1032 releaseFrame(queued_frame);
1036 const auto *src =
static_cast<const uint8_t *
>(rgba_buffer);
1037 for (
int y = 0; y < height; ++y) {
1038 std::memcpy(upload_sw_frame->data[0] +
static_cast<size_t>(y) * upload_sw_frame->linesize[0],
1039 src +
static_cast<size_t>(y) *
static_cast<size_t>(width) * 4,
1040 static_cast<size_t>(width) * 4);
1043 if (av_hwframe_transfer_data(queued_frame, upload_sw_frame, 0) < 0) {
1044 std::cerr <<
"Writer: failed to transfer RGBA system frame to CUDA frame\n";
1045 releaseFrame(queued_frame);
1049 queued_frame->format = AV_PIX_FMT_RGBA;
1050 queued_frame->width = width;
1051 queued_frame->height = height;
1052 if (av_frame_get_buffer(queued_frame, 32) < 0) {
1053 std::cerr <<
"Writer: failed to allocate queued RGBA frame buffer\n";
1054 releaseFrame(queued_frame);
1057 if (av_frame_make_writable(queued_frame) < 0) {
1058 std::cerr <<
"Writer: queued RGBA frame not writable\n";
1059 releaseFrame(queued_frame);
1063 const auto *src =
static_cast<const uint8_t *
>(rgba_buffer);
1064 for (
int y = 0; y < height; ++y) {
1065 std::memcpy(queued_frame->data[0] +
static_cast<size_t>(y) * queued_frame->linesize[0],
1066 src +
static_cast<size_t>(y) *
static_cast<size_t>(width) * 4,
1067 static_cast<size_t>(width) * 4);
1072 std::unique_lock<std::mutex> lock(queue_mutex);
1073 if (block_when_full.load(std::memory_order_relaxed)) {
1074 if (encode_queue.size() >= MAX_QUEUE_SIZE) {
1077 queue_cv.wait(lock, [
this] {
1078 return stop_requested || encode_queue.empty();
1081 if (stop_requested) {
1082 releaseFrame(queued_frame);
1085 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
1086 static int drop_counter = 0;
1087 if (++drop_counter % 30 == 0) {
1088 std::cerr <<
"Writer: dropped " << drop_counter <<
" SDR frames (encoder queue full)\n";
1090 releaseFrame(queued_frame);
1093 queued_frame->pts = frame_count++;
1094 encode_queue.push(queued_frame);
1097 queue_cv.notify_one();
1101 if (!rgba16_buffer) {
1106 std::lock_guard<std::mutex> lock(writer_mutex);
1111 std::cerr <<
"Writer: write_hdr_rgba16 called but writer not in HDR mode\n";
1116 AVFrame *queued_frame = av_frame_alloc();
1117 if (!queued_frame) {
1118 std::cerr <<
"Writer: failed to allocate queued HDR frame\n";
1121 queued_frame->format = AV_PIX_FMT_YUV420P10LE;
1122 queued_frame->width = width;
1123 queued_frame->height = height;
1124 if (av_frame_get_buffer(queued_frame, 32) < 0) {
1125 std::cerr <<
"Writer: failed to allocate YUV420P10 buffer\n";
1126 releaseFrame(queued_frame);
1129 if (av_frame_make_writable(queued_frame) < 0) {
1130 std::cerr <<
"Writer: queued HDR frame not writable\n";
1131 releaseFrame(queued_frame);
1137 convertBt2020Rgba16EncodedToYuv420p10(
1138 reinterpret_cast<const uint16_t *
>(rgba16_buffer),
1140 reinterpret_cast<uint16_t *
>(queued_frame->data[0]),
1141 queued_frame->linesize[0] / 2,
1142 reinterpret_cast<uint16_t *
>(queued_frame->data[1]),
1143 queued_frame->linesize[1] / 2,
1144 reinterpret_cast<uint16_t *
>(queued_frame->data[2]),
1145 queued_frame->linesize[2] / 2,
1149 queued_frame->color_primaries =
static_cast<AVColorPrimaries
>(
1150 hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
1151 queued_frame->color_trc =
static_cast<AVColorTransferCharacteristic
>(
1152 hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
1153 queued_frame->colorspace =
static_cast<AVColorSpace
>(
1154 hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
1155 queued_frame->color_range =
static_cast<AVColorRange
>(
1156 hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
1159 std::unique_lock<std::mutex> lock(queue_mutex);
1160 if (block_when_full.load(std::memory_order_relaxed)) {
1161 if (encode_queue.size() >= MAX_QUEUE_SIZE) {
1162 queue_cv.wait(lock, [
this] {
1163 return stop_requested || encode_queue.empty();
1166 if (stop_requested) {
1167 releaseFrame(queued_frame);
1170 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
1171 static int drop_counter = 0;
1172 if (++drop_counter % 30 == 0) {
1173 std::cerr <<
"Writer: dropped " << drop_counter <<
" HDR frames (encoder queue full)\n";
1175 releaseFrame(queued_frame);
1178 queued_frame->pts = frame_count++;
1179 encode_queue.push(queued_frame);
1182 queue_cv.notify_one();
1186 if (!cuda_rgba_buffer || src_stride <= 0) {
1191 std::lock_guard<std::mutex> lock(writer_mutex);
1192 if (!opened || !use_hw_encode) {
1197 AVFrame *queued_frame = av_frame_alloc();
1198 if (!queued_frame) {
1199 std::cerr <<
"Writer: failed to allocate queued CUDA frame\n";
1203 queued_frame->format = AV_PIX_FMT_CUDA;
1204 queued_frame->width = width;
1205 queued_frame->height = height;
1207 if (av_hwframe_get_buffer(hw_frames_ctx, queued_frame, 0) < 0) {
1208 std::cerr <<
"Writer: failed to allocate CUDA frame from hardware pool\n";
1209 releaseFrame(queued_frame);
1213#ifdef MXWRITE_HAS_CUDA_COPY
1214 cudaStream_t stream = cuda_upload_stream;
1215 const bool use_async_stream = (stream !=
nullptr);
1217 const auto copy_err = use_async_stream
1218 ? cudaMemcpy2DAsync(
1219 queued_frame->data[0],
1220 static_cast<size_t>(queued_frame->linesize[0]),
1222 static_cast<size_t>(src_stride),
1223 static_cast<size_t>(width) * 4,
1224 static_cast<size_t>(height),
1225 cudaMemcpyDeviceToDevice,
1228 queued_frame->data[0],
1229 static_cast<size_t>(queued_frame->linesize[0]),
1231 static_cast<size_t>(src_stride),
1232 static_cast<size_t>(width) * 4,
1233 static_cast<size_t>(height),
1234 cudaMemcpyDeviceToDevice);
1236 if (copy_err != cudaSuccess) {
1237 std::cerr <<
"Writer: cudaMemcpy2D device upload failed: " << cudaGetErrorString(copy_err) <<
"\n";
1238 releaseFrame(queued_frame);
1245 auto *src_base =
static_cast<unsigned char *
>(cuda_rgba_buffer);
1246 auto *dst_base = queued_frame->data[0];
1247 const size_t row_bytes =
static_cast<size_t>(width) * 4;
1249 for (
int y = 0; y < height; ++y) {
1250 auto *src_row = src_base +
static_cast<size_t>(height - 1 - y) *
static_cast<size_t>(src_stride);
1251 auto *dst_row = dst_base +
static_cast<size_t>(y) *
static_cast<size_t>(queued_frame->linesize[0]);
1252 const auto row_copy_err = use_async_stream
1253 ? cudaMemcpyAsync(dst_row, src_row, row_bytes, cudaMemcpyDeviceToDevice, stream)
1254 : cudaMemcpy(dst_row, src_row, row_bytes, cudaMemcpyDeviceToDevice);
1255 if (row_copy_err != cudaSuccess) {
1256 std::cerr <<
"Writer: cudaMemcpy row upload failed: " << cudaGetErrorString(row_copy_err) <<
"\n";
1257 releaseFrame(queued_frame);
1265 if (use_async_stream) {
1266 const auto sync_err = cudaStreamSynchronize(stream);
1267 if (sync_err != cudaSuccess) {
1268 std::cerr <<
"Writer: cudaStreamSynchronize failed: " << cudaGetErrorString(sync_err) <<
"\n";
1269 releaseFrame(queued_frame);
1274 std::cerr <<
"Writer: CUDA copy support disabled at build time\n";
1275 releaseFrame(queued_frame);
1280 std::unique_lock<std::mutex> lock(queue_mutex);
1281 if (block_when_full.load(std::memory_order_relaxed)) {
1282 if (encode_queue.size() >= MAX_QUEUE_SIZE) {
1283 queue_cv.wait(lock, [
this] {
1284 return stop_requested || encode_queue.empty();
1287 if (stop_requested) {
1288 releaseFrame(queued_frame);
1291 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
1292 static int drop_counter = 0;
1293 if (++drop_counter % 30 == 0) {
1294 std::cerr <<
"Writer: dropped " << drop_counter <<
" frames (encoder queue full)\n";
1296 releaseFrame(queued_frame);
1300 queue_cv.notify_one();
1303 queued_frame->pts = frame_count++;
1304 encode_queue.push(queued_frame);
1307 queue_cv.notify_one();
1315void Writer::startEncoderThread() {
1316 stop_requested =
false;
1317 encode_thread = std::jthread([
this](std::stop_token st) {
1322void Writer::stopEncoderThread() {
1324 std::lock_guard<std::mutex> lock(queue_mutex);
1325 stop_requested =
true;
1327 queue_cv.notify_all();
1329 if (encode_thread.joinable()) {
1330 encode_thread.request_stop();
1331 encode_thread.join();
1335void Writer::releaseFrame(AVFrame *f) {
1342void Writer::drainEncoderPackets() {
1343 AVPacket *pkt = av_packet_alloc();
1345 std::cerr <<
"Writer: failed to allocate packet\n";
1350 int ret = avcodec_receive_packet(codec_ctx, pkt);
1351 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
1355 std::cerr <<
"Writer: error receiving packet: " << ret <<
"\n";
1359 av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
1360 pkt->stream_index = stream->index;
1362 if (av_interleaved_write_frame(format_ctx, pkt) < 0) {
1363 std::cerr <<
"Writer: error writing frame\n";
1364 av_packet_unref(pkt);
1367 av_packet_unref(pkt);
1370 av_packet_free(&pkt);
1373void Writer::encodeAndWriteFrame(AVFrame *in_frame) {
1378 AVFrame *encode_frame = in_frame;
1380 if (in_frame->format == AV_PIX_FMT_YUV420P10LE) {
1383 encode_frame = in_frame;
1387 if (av_frame_make_writable(frame10) < 0) {
1388 std::cerr <<
"Writer: HDR frame not writable\n";
1393 in_frame->linesize[0],
1394 reinterpret_cast<uint16_t *
>(frame10->data[0]),
1395 frame10->linesize[0] / 2,
1396 reinterpret_cast<uint16_t *
>(frame10->data[1]),
1397 frame10->linesize[1] / 2,
1398 reinterpret_cast<uint16_t *
>(frame10->data[2]),
1399 frame10->linesize[2] / 2,
1402 frame10->pts = in_frame->pts;
1403 frame10->color_primaries =
static_cast<AVColorPrimaries
>(
1404 hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
1405 frame10->color_trc =
static_cast<AVColorTransferCharacteristic
>(
1406 hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
1407 frame10->colorspace =
static_cast<AVColorSpace
>(
1408 hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
1409 frame10->color_range =
static_cast<AVColorRange
>(
1410 hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
1411 encode_frame = frame10;
1413 }
else if (!use_hw_encode) {
1414 const uint8_t *src_data[1] = {in_frame->data[0]};
1415 int src_linesize[1] = {in_frame->linesize[0]};
1416 sws_scale(sws_ctx, src_data, src_linesize, 0, height, frameYUV->data, frameYUV->linesize);
1417 frameYUV->pts = in_frame->pts;
1418 encode_frame = frameYUV;
1421 int ret = avcodec_send_frame(codec_ctx, encode_frame);
1422 if (ret == AVERROR(EAGAIN)) {
1424 drainEncoderPackets();
1425 ret = avcodec_send_frame(codec_ctx, encode_frame);
1428 std::cerr <<
"Writer: error sending frame to encoder: " << ret <<
"\n";
1432 drainEncoderPackets();
1435void Writer::encodeLoop(std::stop_token stop_token) {
1437 AVFrame *frame =
nullptr;
1439 std::unique_lock<std::mutex> lock(queue_mutex);
1440 queue_cv.wait(lock, [
this, &stop_token]() {
1441 return stop_requested || stop_token.stop_requested() || !encode_queue.empty();
1444 if ((stop_requested || stop_token.stop_requested()) && encode_queue.empty()) {
1448 frame = encode_queue.front();
1452 queue_cv.notify_one();
1454 encodeAndWriteFrame(frame);
1455 releaseFrame(frame);
1459 const int flush_ret = avcodec_send_frame(codec_ctx,
nullptr);
1460 if (flush_ret >= 0) {
1461 drainEncoderPackets();
1466 std::lock_guard<std::mutex> lock(writer_mutex);
1471 stopEncoderThread();
1473 if (stream && stream->duration > 0) {
1474 last_duration =
static_cast<double>(stream->duration) * av_q2d(stream->time_base);
1475 }
else if (fps_num > 0 && fps_den > 0) {
1476 last_duration =
static_cast<double>(frame_count) *
static_cast<double>(fps_den) /
static_cast<double>(fps_num);
1479 av_write_trailer(format_ctx);
1481 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
1482 avio_closep(&format_ctx->pb);
1485 av_frame_free(&frameRGBA);
1486 av_frame_free(&frameYUV);
1487 av_frame_free(&frame10);
1488 sws_freeContext(sws_ctx);
1489 av_frame_free(&upload_sw_frame);
1490#ifdef MXWRITE_HAS_CUDA_COPY
1492 if (cuda_upload_stream) {
1493 cudaStreamSynchronize(cuda_upload_stream);
1494 cudaStreamDestroy(cuda_upload_stream);
1495 cuda_upload_stream =
nullptr;
1498 avcodec_free_context(&codec_ctx);
1499 av_buffer_unref(&hw_frames_ctx);
1500 av_buffer_unref(&hw_device_ctx);
1501 avformat_free_context(format_ctx);
1504 while (!encode_queue.empty()) {
1505 releaseFrame(encode_queue.front());
1509 format_ctx =
nullptr;
1510 codec_ctx =
nullptr;
1512 frameRGBA =
nullptr;
1515 upload_sw_frame =
nullptr;
1516 use_hw_encode =
false;
1518 stop_requested =
false;
1522 if (!opened && last_duration > 0.0) {
1523 return last_duration;
1525 if (stream && stream->duration > 0) {
1526 return static_cast<double>(stream->duration) * av_q2d(stream->time_base);
1528 if (fps_num > 0 && fps_den > 0) {
1529 return static_cast<double>(frame_count) *
static_cast<double>(fps_den) /
static_cast<double>(fps_num);
bool write_cuda_rgba(void *cuda_rgba_buffer, int src_stride, bool bottom_up=false)
Queue a CUDA RGBA frame for encoding.
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.
void write(void *rgba_buffer)
Queue a host RGBA frame for immediate-mode encoding.
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.
double get_duration() const
Return the encoded duration in seconds.
void close()
Close the writer and flush pending packets.
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...
void write_ts(void *rgba_buffer)
Queue a host RGBA frame using capture timestamps.
std::mutex transfer_audio_mutex
void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile)
Copy audio from one video file to another.
bool is_format_supported(const char *filename)
void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx)
Free FFmpeg format contexts used during transfer operations.
FFmpeg-based video writer used by MXWrite.
std::string lowercase_ascii(std::string text)
uint16_t clamp10(float v)
void convertRgbaToBt2020PqYuv420p10(const uint8_t *rgba, int src_stride_bytes, uint16_t *y_plane, int y_stride_shorts, uint16_t *u_plane, int u_stride_shorts, uint16_t *v_plane, int v_stride_shorts, int width, int height)
bool is_valid_x264_preset(const std::string &p)
void convertBt2020Rgba16EncodedToYuv420p10(const uint16_t *rgba, int src_stride_shorts, uint16_t *y_plane, int y_stride_shorts, uint16_t *u_plane, int u_stride_shorts, uint16_t *v_plane, int v_stride_shorts, int width, int height)
constexpr float kSdrRefFraction
const char * x264_preset_to_nvenc(const std::string &p)
bool enabled
Enables the HDR output path.
User-configurable video encoder quality options.
std::string codec
Encoder selection policy or concrete NVENC codec.
std::string tune
Optional tuning mode.
bool realtime
Enable low-latency settings.
struct EncodeOptions::HdrInfo hdr
int crf
Constant Rate Factor.
bool block_when_full
Block producer threads instead of dropping when the encoder queue is full.
std::string preset
Encoder preset name.