15#ifdef MXWRITE_HAS_CUDA_COPY
16#include <cuda_runtime.h>
19#include <libavcodec/avcodec.h>
20#include <libavformat/avformat.h>
21#include <libavutil/imgutils.h>
22#include <libavutil/mastering_display_metadata.h>
23#include <libavutil/mathematics.h>
24#include <libavutil/opt.h>
25#include <libavutil/pixdesc.h>
26#include <libswscale/swscale.h>
33 constexpr float kPqM1 = 2610.0f / 16384.0f;
34 constexpr float kPqM2 = (2523.0f / 4096.0f) * 128.0f;
35 constexpr float kPqC1 = 3424.0f / 4096.0f;
36 constexpr float kPqC2 = (2413.0f / 4096.0f) * 32.0f;
37 constexpr float kPqC3 = (2392.0f / 4096.0f) * 32.0f;
43 return (v <= 0.04045f) ? (v / 12.92f) : std::pow((v + 0.055f) / 1.055f, 2.4f);
48 const float Lm = std::pow(std::max(0.0f, L),
kPqM1);
50 const float den = 1.0f +
kPqC3 * Lm;
51 return std::pow(num / den,
kPqM2);
59 return static_cast<uint16_t
>(v + 0.5f);
66 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) {
72 constexpr float kKr = 0.2627f;
73 constexpr float kKg = 0.6780f;
74 constexpr float kKb = 0.0593f;
75 constexpr float kPbDiv = 1.0f / 1.8814f;
76 constexpr float kPrDiv = 1.0f / 1.4746f;
78 for (
int y = 0; y < height; y += 2) {
79 const int y1 = std::min(y + 1, height - 1);
80 const uint8_t *row0 = rgba + y * src_stride_bytes;
81 const uint8_t *row1 = rgba + y1 * src_stride_bytes;
82 uint16_t *yr0 = y_plane + y * y_stride_shorts;
83 uint16_t *yr1 = y_plane + y1 * y_stride_shorts;
84 uint16_t *ur = u_plane + (y / 2) * u_stride_shorts;
85 uint16_t *vr = v_plane + (y / 2) * v_stride_shorts;
87 for (
int x = 0; x < width; x += 2) {
88 const int x1 = std::min(x + 1, width - 1);
91 auto loadPq = [](
const uint8_t *px,
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);
139 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) {
140 constexpr float kKr = 0.2627f;
141 constexpr float kKg = 0.6780f;
142 constexpr float kKb = 0.0593f;
143 constexpr float kPbDiv = 1.0f / 1.8814f;
144 constexpr float kPrDiv = 1.0f / 1.4746f;
145 constexpr float kInv65535 = 1.0f / 65535.0f;
147 for (
int y = 0; y < height; y += 2) {
148 const int y1 = std::min(y + 1, height - 1);
149 const uint16_t *row0 = rgba + y * src_stride_shorts;
150 const uint16_t *row1 = rgba + y1 * src_stride_shorts;
151 uint16_t *yr0 = y_plane + y * y_stride_shorts;
152 uint16_t *yr1 = y_plane + y1 * y_stride_shorts;
153 uint16_t *ur = u_plane + (y / 2) * u_stride_shorts;
154 uint16_t *vr = v_plane + (y / 2) * v_stride_shorts;
156 for (
int x = 0; x < width; x += 2) {
157 const int x1 = std::min(x + 1, width - 1);
159 auto load = [&](
const uint16_t *px,
float &Y,
float &U,
float &V) {
160 const float rp = px[0] * kInv65535;
161 const float gp = px[1] * kInv65535;
162 const float bp = px[2] * kInv65535;
163 Y = kKr * rp + kKg * gp + kKb * bp;
164 U = (bp - Y) * kPbDiv;
165 V = (rp - Y) * kPrDiv;
172 load(row0 + x * 4, Y00, U00, V00);
173 load(row0 + x1 * 4, Y01, U01, V01);
174 load(row1 + x * 4, Y10, U10, V10);
175 load(row1 + x1 * 4, Y11, U11, V11);
177 yr0[x] =
clamp10(Y00 * 876.0f + 64.0f);
178 yr0[x1] =
clamp10(Y01 * 876.0f + 64.0f);
179 yr1[x] =
clamp10(Y10 * 876.0f + 64.0f);
180 yr1[x1] =
clamp10(Y11 * 876.0f + 64.0f);
182 const float Uavg = 0.25f * (U00 + U01 + U10 + U11);
183 const float Vavg = 0.25f * (V00 + V01 + V10 + V11);
184 ur[x / 2] =
clamp10(Uavg * 896.0f + 512.0f);
185 vr[x / 2] =
clamp10(Vavg * 896.0f + 512.0f);
195 const char *ext = strrchr(filename,
'.');
199 std::string lower_ext(ext);
200 std::transform(lower_ext.begin(), lower_ext.end(), lower_ext.begin(), [](
unsigned char c) { return std::tolower(c); });
201 static const char *kSupported[] = {
".mp4",
".mkv",
".mov",
".avi",
".m4v",
".ts",
".mts",
".m2ts",
".mpg",
".mpeg",
".flv",
".f4v",
".3gp",
".3g2",
".wmv",
".asf",
".vob"};
202 for (
const char *s : kSupported) {
209void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx) {
211 avformat_close_input(&source_ctx);
213 avformat_close_input(&dest_ctx);
215 if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
216 avio_closep(&output_ctx->pb);
217 avformat_free_context(output_ctx);
221void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile) {
224 std::cerr <<
"Unsupported output format. Supported formats: .mp4, .mkv, .avi, .mov\n";
228 AVFormatContext *source_ctx =
nullptr, *dest_ctx =
nullptr, *output_ctx =
nullptr;
229 int source_audio_idx = -1, dest_video_idx = -1, dest_audio_idx = -1;
230 std::string temp_output = std::string(destVideoFile) +
".tmp";
232 if (avformat_open_input(&source_ctx, sourceAudioFile.data(),
nullptr,
nullptr) != 0 || avformat_open_input(&dest_ctx, destVideoFile.data(),
nullptr,
nullptr) != 0) {
233 std::cerr <<
"Failed to open input files\n";
238 if (avformat_find_stream_info(source_ctx,
nullptr) < 0 || avformat_find_stream_info(dest_ctx,
nullptr) < 0) {
239 std::cerr <<
"Failed to find stream info\n";
244 for (
unsigned i = 0; i < source_ctx->nb_streams; ++i) {
245 if (source_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
246 source_audio_idx = i;
251 for (
unsigned i = 0; i < dest_ctx->nb_streams; ++i) {
252 AVMediaType type = dest_ctx->streams[i]->codecpar->codec_type;
253 if (type == AVMEDIA_TYPE_VIDEO)
255 else if (type == AVMEDIA_TYPE_AUDIO)
259 if (source_audio_idx == -1 || dest_video_idx == -1) {
260 std::cerr <<
"Required streams not found\n";
265 const AVOutputFormat *output_fmt = av_guess_format(
nullptr, destVideoFile.data(),
nullptr);
267 output_fmt = av_guess_format(
"mp4",
nullptr,
nullptr);
269 std::cerr <<
"Failed to determine output format\n";
275 if (avformat_alloc_output_context2(&output_ctx, output_fmt,
nullptr, temp_output.c_str()) < 0) {
276 std::cerr <<
"Failed to create output context\n";
281 const AVCodec *audio_codec = avcodec_find_decoder(source_ctx->streams[source_audio_idx]->codecpar->codec_id);
283 std::cerr <<
"Failed to find audio decoder\n";
288 for (
unsigned i = 0; i < dest_ctx->nb_streams; ++i) {
289 if (dest_ctx->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
293 AVStream *dest_stream = dest_ctx->streams[i];
294 AVStream *out_stream = avformat_new_stream(output_ctx,
nullptr);
296 std::cerr <<
"Failed to create output stream\n";
301 if (avcodec_parameters_copy(out_stream->codecpar, dest_stream->codecpar) < 0) {
302 std::cerr <<
"Failed to copy video parameters\n";
307 out_stream->time_base = dest_stream->time_base;
308 out_stream->codecpar->codec_tag = 0;
311 AVStream *out_stream = avformat_new_stream(output_ctx, audio_codec);
313 std::cerr <<
"Failed to create audio stream\n";
318 AVCodecParameters *source_params = source_ctx->streams[source_audio_idx]->codecpar;
319 if (avcodec_parameters_copy(out_stream->codecpar, source_params) < 0) {
320 std::cerr <<
"Failed to copy audio parameters\n";
325 if (source_params->frame_size == 0) {
326 out_stream->codecpar->frame_size = 1024;
328 out_stream->codecpar->frame_size = source_params->frame_size;
331 out_stream->time_base = source_ctx->streams[source_audio_idx]->time_base;
332 out_stream->codecpar->codec_tag = 0;
333 dest_audio_idx = out_stream->index;
335 if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {
336 if (avio_open(&output_ctx->pb, temp_output.c_str(), AVIO_FLAG_WRITE) < 0) {
337 std::cerr <<
"Failed to open output file\n";
342 if (avformat_write_header(output_ctx,
nullptr) < 0) {
343 std::cerr <<
"Failed to write header\n";
348 while (av_read_frame(dest_ctx, &packet) >= 0) {
349 if (packet.stream_index == dest_audio_idx) {
350 av_packet_unref(&packet);
354 AVStream *in_stream = dest_ctx->streams[packet.stream_index];
355 AVStream *out_stream = output_ctx->streams[packet.stream_index];
356 av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
358 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
359 std::cerr <<
"Failed to write packet\n";
360 av_packet_unref(&packet);
364 av_packet_unref(&packet);
367 int64_t video_duration_ts = 0;
369 AVStream *vid_stream = dest_ctx->streams[0];
370 if (vid_stream->duration > 0) {
371 video_duration_ts = av_rescale_q(vid_stream->duration, vid_stream->time_base, source_ctx->streams[source_audio_idx]->time_base);
372 }
else if (dest_ctx->duration > 0) {
373 AVRational av_tb = {1, AV_TIME_BASE};
374 video_duration_ts = av_rescale_q(dest_ctx->duration, av_tb, source_ctx->streams[source_audio_idx]->time_base);
378 av_seek_frame(source_ctx, source_audio_idx, 0, AVSEEK_FLAG_BACKWARD);
379 while (av_read_frame(source_ctx, &packet) >= 0) {
380 if (packet.stream_index == source_audio_idx) {
381 if (video_duration_ts > 0 && packet.pts != AV_NOPTS_VALUE && packet.pts > video_duration_ts) {
382 av_packet_unref(&packet);
385 AVStream *in_stream = source_ctx->streams[packet.stream_index];
386 AVStream *out_stream = output_ctx->streams[dest_audio_idx];
387 av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
388 packet.stream_index = dest_audio_idx;
390 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
391 std::cerr <<
"Failed to write audio packet\n";
392 av_packet_unref(&packet);
397 av_packet_unref(&packet);
399 av_write_trailer(output_ctx);
401 std::remove(destVideoFile.data());
402 std::rename(temp_output.c_str(), destVideoFile.data());
405void Writer::calculateFPSFraction(
float fps,
int &fps_num,
int &fps_den) {
406 const float epsilon = 0.001f;
407 if (std::fabs(fps - 23.976f) < epsilon) {
410 }
else if (std::fabs(fps - 29.97f) < epsilon) {
413 }
else if (std::fabs(fps - 47.952f) < epsilon) {
416 }
else if (std::fabs(fps - 59.94f) < epsilon) {
419 }
else if (std::fabs(fps - 119.88f) < epsilon) {
423 float precision = 1000.0f;
424 fps_num =
static_cast<int>(std::round(fps * precision));
425 fps_den =
static_cast<int>(precision);
426 int gcd = std::gcd(fps_num, fps_den);
437 if (p ==
"ultrafast")
439 if (p ==
"superfast")
441 if (p ==
"veryfast" || p ==
"faster")
445 if (p ==
"medium" || p.empty())
447 if (p ==
"slow" || p ==
"slower")
456 static const char *presets[] = {
"ultrafast",
"superfast",
"veryfast",
"faster",
"fast",
"medium",
"slow",
"slower",
"veryslow",
"placebo"};
457 for (
const char *n : presets)
464 std::transform(text.begin(), text.end(), text.begin(), [](
unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
474#if LIBAVUTIL_VERSION_MAJOR >= 60
475 return static_cast<int>(type) & ~AV_OPT_TYPE_FLAG_ARRAY;
477 return static_cast<int>(type);
482 if (token.size() < 2 || token.front() !=
'-') {
485 const unsigned char next =
static_cast<unsigned char>(token[1]);
486 return !std::isdigit(next) && token[1] !=
'.';
491 bool token_started =
false;
492 bool escaped =
false;
495 for (
char ch : text) {
498 token_started =
true;
502 if (ch ==
'\\' && quote !=
'\'') {
504 token_started =
true;
513 token_started =
true;
516 if (ch ==
'\'' || ch ==
'"') {
518 token_started =
true;
519 }
else if (std::isspace(
static_cast<unsigned char>(ch))) {
521 tokens.push_back(token);
523 token_started =
false;
527 token_started =
true;
532 error =
"trailing escape character";
536 error =
"unterminated quote";
540 tokens.push_back(token);
546 while (!name.empty() && name.front() ==
'-') {
547 name.erase(name.begin());
550 const size_t stream_specifier = name.find(
':');
551 if (stream_specifier != std::string::npos) {
552 const std::string suffix = name.substr(stream_specifier + 1);
553 if (suffix ==
"v" || suffix.starts_with(
"v:")) {
554 name.erase(stream_specifier);
562 return codec ==
"h265_nvenc" || codec ==
"h264" || codec ==
"h265" || codec ==
"hevc" || codec ==
"nvenc" || codec ==
"software" || codec ==
"auto" || avcodec_find_encoder_by_name(codec.c_str()) !=
nullptr;
566 std::vector<std::string> tokens;
569 std::cerr <<
"MXWrite: invalid extra FFmpeg parameters: " << error <<
".\n";
573 for (
size_t index = 0; index < tokens.size(); ++index) {
574 std::string token = tokens[index];
577 options.push_back({
"codec", token});
579 std::cerr <<
"MXWrite: ignoring non-option extra parameter '" << token <<
"' (filenames are selected by Writer::open).\n";
584 while (!token.empty() && token.front() ==
'-') {
585 token.erase(token.begin());
587 const size_t equals = token.find(
'=');
590 if (equals != std::string::npos) {
591 value = token.substr(equals + 1);
593 value = tokens[++index];
599 std::cerr <<
"MXWrite: invalid empty FFmpeg option name.\n";
602 options.push_back({std::move(name), std::move(value)});
607 const std::string *
find_ffmpeg_option(
const std::vector<FfmpegOption> &options,
const std::string &name) {
608 for (
auto option = options.rbegin(); option != options.rend(); ++option) {
609 if (option->name == name) {
610 return &option->value;
616 bool is_reserved_ffmpeg_option(
const std::string &name) {
return name ==
"c" || name ==
"codec" || name ==
"vcodec" || name ==
"pix_fmt" || name ==
"pixel_format"; }
618 bool apply_ffmpeg_options(
const std::vector<FfmpegOption> &options, AVCodecContext *context, AVFormatContext *output_context) {
624 int result = AVERROR_OPTION_NOT_FOUND;
625 if (context->priv_data) {
626 result = av_opt_set(context->priv_data, option.name.c_str(), option.value.c_str(), 0);
628 if (result == AVERROR_OPTION_NOT_FOUND) {
629 result = av_opt_set(context, option.name.c_str(), option.value.c_str(), 0);
631 if (result == AVERROR_OPTION_NOT_FOUND && output_context->priv_data) {
632 result = av_opt_set(output_context->priv_data, option.name.c_str(), option.value.c_str(), 0);
635 char error_text[AV_ERROR_MAX_STRING_SIZE] = {};
636 av_strerror(result, error_text,
sizeof(error_text));
637 std::cerr <<
"MXWrite: FFmpeg option '-" << option.name <<
" " << option.value <<
"' was rejected: " << error_text <<
".\n";
645 if (!
object || value.empty()) {
648 const AVOption *option = av_opt_find(
object, name,
nullptr, 0, 0);
653 const bool textual = base_type == AV_OPT_TYPE_STRING || base_type == AV_OPT_TYPE_DICT || base_type == AV_OPT_TYPE_BINARY;
654 const bool numeric_text = std::isdigit(
static_cast<unsigned char>(value.front())) || value.front() ==
'-' || value.front() ==
'+' || value.front() ==
'.';
655 if (!textual && !numeric_text) {
656 const AVOption *named_value = av_opt_find(
object, value.c_str(), option->unit, 0, 0);
657 if (!named_value || named_value->type != AV_OPT_TYPE_CONST) {
661 return av_opt_set(
object, name, value.c_str(), 0) >= 0;
664 bool is_nvenc_tune(
const std::string &tune) {
return tune ==
"hq" || tune ==
"uhq" || tune ==
"ll" || tune ==
"ull" || tune ==
"lossless"; }
685 std::vector<FfmpegOption> translated;
686 bool enable_lossless =
false;
690 if (value.
name ==
"preset") {
692 }
else if (value.
name ==
"tune") {
694 if (tune ==
"lossless") {
695 enable_lossless =
true;
698 if (tune ==
"hq" || tune ==
"uhq") {
701 if (tune ==
"ll" || tune ==
"ull") {
702 value.
value =
"zerolatency";
705 value.
value = use_hevc_codec ?
"main444-8" :
"high444";
706 }
else if (value.
name ==
"rgb_mode" || value.
name ==
"rc") {
708 }
else if (value.
name ==
"cq") {
711 translated.push_back(std::move(value));
714 if (enable_lossless) {
715 if (use_hevc_codec) {
716 bool appended =
false;
718 if (option.name ==
"x265-params") {
719 if (!option.value.empty())
721 option.value +=
"lossless=1";
726 translated.push_back({
"x265-params",
"lossless=1"});
729 translated.push_back({
"qp",
"0"});
736 std::vector<AVPixelFormat> formats;
741#if LIBAVCODEC_VERSION_MAJOR >= 61
742 const void *configurations =
nullptr;
743 int configuration_count = 0;
744 if (avcodec_get_supported_config(
nullptr, codec, AV_CODEC_CONFIG_PIX_FORMAT, 0, &configurations, &configuration_count) >= 0 && configurations) {
745 const auto *pixel_formats =
static_cast<const AVPixelFormat *
>(configurations);
746 formats.assign(pixel_formats, pixel_formats + configuration_count);
749 if (codec->pix_fmts) {
750 for (
const AVPixelFormat *format = codec->pix_fmts; *format != AV_PIX_FMT_NONE; ++format) {
751 formats.push_back(*format);
759 const AVPixFmtDescriptor *descriptor = av_pix_fmt_desc_get(format);
760 return descriptor && (descriptor->flags & AV_PIX_FMT_FLAG_HWACCEL) != 0;
765 auto is_usable = [](AVPixelFormat format) {
return !
is_hardware_pixel_format(format) && sws_isSupportedOutput(format); };
767 if (requested_format != AV_PIX_FMT_NONE) {
768 if (!is_usable(requested_format)) {
769 return AV_PIX_FMT_NONE;
771 if (formats.empty() || std::find(formats.begin(), formats.end(), requested_format) != formats.end()) {
772 return requested_format;
774 return AV_PIX_FMT_NONE;
777 static constexpr AVPixelFormat preferred_formats[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV422P10LE, AV_PIX_FMT_YUV444P10LE, AV_PIX_FMT_BGRA, AV_PIX_FMT_RGBA};
778 if (formats.empty()) {
779 return AV_PIX_FMT_YUV420P;
781 for (AVPixelFormat preferred : preferred_formats) {
782 if (std::find(formats.begin(), formats.end(), preferred) != formats.end() && is_usable(preferred)) {
786 for (AVPixelFormat format : formats) {
787 if (is_usable(format)) {
791 return AV_PIX_FMT_NONE;
798 if ((codec->capabilities & (AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_HYBRID)) != 0) {
801 for (
int index = 0; avcodec_get_hw_config(codec, index); ++index) {
804 const std::string name = codec->name ?
lowercase_ascii(codec->name) : std::string{};
805 static constexpr std::string_view hardware_markers[] = {
"_nvenc",
"_qsv",
"_vaapi",
"_amf",
"_v4l2m2m",
"_videotoolbox",
"_mediafoundation",
"_vulkan"};
806 return std::any_of(std::begin(hardware_markers), std::end(hardware_markers), [&name](std::string_view marker) {
return name.find(marker) != std::string::npos; });
812 case AV_OPT_TYPE_FLAGS:
814 case AV_OPT_TYPE_INT:
816 case AV_OPT_TYPE_INT64:
818 case AV_OPT_TYPE_DOUBLE:
820 case AV_OPT_TYPE_FLOAT:
822 case AV_OPT_TYPE_STRING:
824 case AV_OPT_TYPE_RATIONAL:
826 case AV_OPT_TYPE_BINARY:
828 case AV_OPT_TYPE_DICT:
830 case AV_OPT_TYPE_UINT64:
832 case AV_OPT_TYPE_IMAGE_SIZE:
834 case AV_OPT_TYPE_PIXEL_FMT:
835 return "pixel-format";
836 case AV_OPT_TYPE_SAMPLE_FMT:
837 return "sample-format";
838 case AV_OPT_TYPE_VIDEO_RATE:
840 case AV_OPT_TYPE_DURATION:
842 case AV_OPT_TYPE_COLOR:
844 case AV_OPT_TYPE_BOOL:
846#if LIBAVUTIL_VERSION_MAJOR >= 57
847 case AV_OPT_TYPE_CHLAYOUT:
849 case AV_OPT_TYPE_CHANNEL_LAYOUT:
851 return "channel-layout";
852#if LIBAVUTIL_VERSION_MAJOR >= 60
853 case AV_OPT_TYPE_UINT:
863 return base_type == AV_OPT_TYPE_FLAGS || base_type == AV_OPT_TYPE_INT || base_type == AV_OPT_TYPE_INT64 || base_type == AV_OPT_TYPE_DOUBLE || base_type == AV_OPT_TYPE_FLOAT || base_type == AV_OPT_TYPE_UINT64 || base_type == AV_OPT_TYPE_BOOL
864#if LIBAVUTIL_VERSION_MAJOR >= 60
865 || base_type == AV_OPT_TYPE_UINT
871 std::ostringstream stream;
872 stream << std::setprecision(12) << value;
880 for (
const AVOption *option =
nullptr; (option = av_opt_next(
object, option));) {
881 if (option->type == AV_OPT_TYPE_CONST || (option->flags & AV_OPT_FLAG_ENCODING_PARAM) == 0 || (option->flags & AV_OPT_FLAG_VIDEO_PARAM) == 0 || (option->flags & (AV_OPT_FLAG_READONLY | AV_OPT_FLAG_DEPRECATED)) != 0 || !option->name || !seen.insert(option->name).second) {
886 info.
name = option->name;
888 info.
help = option->help ? option->help :
"";
894 uint8_t *value =
nullptr;
895 if (av_opt_get(
object, option->name, 0, &value) >= 0 && value) {
901 std::vector<std::string> choices;
902 for (
const AVOption *choice =
nullptr; (choice = av_opt_next(
object, choice));) {
903 if (choice->type == AV_OPT_TYPE_CONST && choice->unit && choice->name && std::string_view(choice->unit) == option->unit) {
904 choices.emplace_back(choice->name);
907 for (
size_t index = 0; index < choices.size(); ++index) {
911 info.
choices += choices[index];
914 result.push_back(std::move(info));
921 std::vector<EncoderInfo> result;
922 void *iterator =
nullptr;
923 while (
const AVCodec *codec = av_codec_iterate(&iterator)) {
924 if (!av_codec_is_encoder(codec) || codec->type != AVMEDIA_TYPE_VIDEO || !codec->name) {
928 info.
name = codec->name;
929 info.
long_name = codec->long_name ? codec->long_name : codec->name;
930 info.
codec_name = avcodec_get_name(codec->id);
932 info.
experimental = (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) != 0;
934 for (AVPixelFormat format : formats) {
935 const char *name = av_get_pix_fmt_name(format);
944 result.push_back(std::move(info));
947 if (left.codec_name != right.codec_name) {
948 return left.codec_name < right.codec_name;
951 return left.hardware > right.hardware;
959 const std::string name(encoder_name);
960 const AVCodec *codec = avcodec_find_encoder_by_name(name.c_str());
961 if (!codec || codec->type != AVMEDIA_TYPE_VIDEO) {
964 AVCodecContext *context = avcodec_alloc_context3(codec);
968 std::vector<EncoderOptionInfo> result;
969 std::set<std::string> seen;
972 avcodec_free_context(&context);
977bool Writer::open(
const std::string &filename,
int w,
int h,
float fps,
const char *crf) {
978 std::lock_guard<std::mutex> lock(writer_mutex);
982 opts.
crf = std::stoi(crf);
987 opts.
preset =
"ultrafast";
988 opts.
tune =
"zerolatency";
990 return openInternal(filename, w, h, fps, opts,
false);
994 std::lock_guard<std::mutex> lock(writer_mutex);
995 return openInternal(filename, w, h, fps, opts,
false);
998bool Writer::open_ts(
const std::string &filename,
int w,
int h,
float fps,
const char *crf) {
999 std::lock_guard<std::mutex> lock(writer_mutex);
1003 opts.
crf = std::stoi(crf);
1007 opts.
preset =
"ultrafast";
1008 opts.
tune =
"zerolatency";
1010 return openInternal(filename, w, h, fps, opts,
true);
1014 std::lock_guard<std::mutex> lock(writer_mutex);
1015 return openInternal(filename, w, h, fps, opts,
true);
1018bool Writer::initHardwareEncoding(
const AVCodec *codec, AVPixelFormat requested_format,
bool prefer_cuda_rgba) {
1019 const AVCodecHWConfig *hardware_config =
nullptr;
1020 for (
int index = 0;
const AVCodecHWConfig *config = avcodec_get_hw_config(codec, index); ++index) {
1021 if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) == 0 || config->device_type == AV_HWDEVICE_TYPE_NONE || config->pix_fmt == AV_PIX_FMT_NONE) {
1024 if (!hardware_config || (prefer_cuda_rgba && config->device_type == AV_HWDEVICE_TYPE_CUDA)) {
1025 hardware_config = config;
1027 if (prefer_cuda_rgba && config->device_type == AV_HWDEVICE_TYPE_CUDA) {
1031 if (!hardware_config || av_hwdevice_ctx_create(&hw_device_ctx, hardware_config->device_type,
nullptr,
nullptr, 0) < 0) {
1035 codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
1036 codec_ctx->pix_fmt = hardware_config->pix_fmt;
1038 hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx);
1039 if (!hw_frames_ctx) {
1043 auto *frames_ctx =
reinterpret_cast<AVHWFramesContext *
>(hw_frames_ctx->data);
1044 frames_ctx->format = hardware_config->pix_fmt;
1046 AVHWFramesConstraints *constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,
nullptr);
1047 auto valid_software_format = [constraints](AVPixelFormat format) {
1051 if (!constraints || !constraints->valid_sw_formats) {
1054 for (
const AVPixelFormat *valid = constraints->valid_sw_formats; *valid != AV_PIX_FMT_NONE; ++valid) {
1055 if (*valid == format) {
1062 AVPixelFormat upload_format = AV_PIX_FMT_NONE;
1063 if (prefer_cuda_rgba && valid_software_format(AV_PIX_FMT_RGBA)) {
1064 upload_format = AV_PIX_FMT_RGBA;
1065 }
else if (valid_software_format(requested_format)) {
1066 upload_format = requested_format;
1068 static constexpr AVPixelFormat preferred_upload_formats[] = {AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, AV_PIX_FMT_P010LE, AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV422P, AV_PIX_FMT_BGRA, AV_PIX_FMT_RGBA};
1069 for (AVPixelFormat format : preferred_upload_formats) {
1070 if (valid_software_format(format)) {
1071 upload_format = format;
1075 if (upload_format == AV_PIX_FMT_NONE && constraints && constraints->valid_sw_formats) {
1076 for (
const AVPixelFormat *valid = constraints->valid_sw_formats; *valid != AV_PIX_FMT_NONE; ++valid) {
1077 if (valid_software_format(*valid)) {
1078 upload_format = *valid;
1084 av_hwframe_constraints_free(&constraints);
1085 if (upload_format == AV_PIX_FMT_NONE) {
1089 frames_ctx->sw_format = upload_format;
1090 frames_ctx->width = width;
1091 frames_ctx->height = height;
1095 frames_ctx->initial_pool_size =
static_cast<int>(MAX_QUEUE_SIZE) + 8;
1097 if (av_hwframe_ctx_init(hw_frames_ctx) < 0) {
1101 codec_ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ctx);
1102 codec_ctx->sw_pix_fmt = upload_format;
1104 upload_sw_frame = av_frame_alloc();
1105 if (!upload_sw_frame) {
1108 upload_sw_frame->format = upload_format;
1109 upload_sw_frame->width = width;
1110 upload_sw_frame->height = height;
1111 if (av_frame_get_buffer(upload_sw_frame, 32) < 0) {
1115 if (upload_format != AV_PIX_FMT_RGBA) {
1116 sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA, width, height, upload_format, SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
1122 direct_cuda_upload = hardware_config->device_type == AV_HWDEVICE_TYPE_CUDA && upload_format == AV_PIX_FMT_RGBA;
1124#ifdef MXWRITE_HAS_CUDA_COPY
1127 if (direct_cuda_upload && !cuda_upload_stream) {
1128 if (cudaStreamCreateWithFlags(&cuda_upload_stream, cudaStreamNonBlocking) != cudaSuccess) {
1129 cuda_upload_stream =
nullptr;
1137bool Writer::openInternal(
const std::string &filename,
int w,
int h,
float fps,
const EncodeOptions &opts,
bool ts_mode) {
1138 avformat_network_init();
1139 av_log_set_level(AV_LOG_ERROR);
1141 active_encoder_hardware =
false;
1142 stop_requested =
false;
1144 last_duration = 0.0;
1145 bytes_written.store(0, std::memory_order_relaxed);
1146 block_when_full.store(opts.
block_when_full, std::memory_order_relaxed);
1148 while (!encode_queue.empty()) {
1149 releaseFrame(encode_queue.front());
1153 std::vector<FfmpegOption> extra_options;
1159 if (!codec_override) {
1162 if (!codec_override) {
1166 const std::string *pixel_format_option =
find_ffmpeg_option(extra_options,
"pix_fmt");
1167 if (!pixel_format_option) {
1170 AVPixelFormat requested_pixel_format = AV_PIX_FMT_NONE;
1171 if (pixel_format_option) {
1172 requested_pixel_format = av_get_pix_fmt(pixel_format_option->c_str());
1173 if (requested_pixel_format == AV_PIX_FMT_NONE) {
1174 std::cerr <<
"MXWrite: unknown pixel format '" << *pixel_format_option <<
"'.\n";
1181 if (avformat_alloc_output_context2(&format_ctx,
nullptr,
nullptr, filename.c_str()) < 0) {
1182 std::cerr <<
"Could not allocate output context.\n";
1189 hdr_info = opts.
hdr;
1196 const AVCodec *hdr_codec = avcodec_find_encoder_by_name(
"libx265");
1198 std::cerr <<
"MXWrite: HDR output requested but libx265 encoder not available.\n";
1199 avformat_free_context(format_ctx);
1200 format_ctx =
nullptr;
1204 stream = avformat_new_stream(format_ctx, hdr_codec);
1206 std::cerr <<
"MXWrite: could not create HDR stream.\n";
1207 avformat_free_context(format_ctx);
1208 format_ctx =
nullptr;
1212 calculateFPSFraction(fps, fps_num, fps_den);
1213 AVRational tb_hdr = {fps_den, fps_num};
1214 stream->time_base = tb_hdr;
1216 codec_ctx = avcodec_alloc_context3(hdr_codec);
1218 std::cerr <<
"MXWrite: could not allocate HDR codec context.\n";
1219 avformat_free_context(format_ctx);
1220 format_ctx =
nullptr;
1224 codec_ctx->width = width;
1225 codec_ctx->height = height;
1226 codec_ctx->time_base = stream->time_base;
1227 codec_ctx->framerate = AVRational{fps_num, fps_den};
1228 codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P10LE;
1229 codec_ctx->profile = AV_PROFILE_HEVC_MAIN_10;
1230 codec_ctx->bits_per_raw_sample = 10;
1231 codec_ctx->gop_size = 30;
1232 codec_ctx->max_b_frames = 0;
1233 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
1234 codec_ctx->thread_type = FF_THREAD_SLICE;
1235 codec_ctx->delay = 0;
1236 codec_ctx->bit_rate = std::max<std::int64_t>(0, opts.
bit_rate);
1239 codec_ctx->color_primaries =
static_cast<AVColorPrimaries
>(hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
1240 codec_ctx->color_trc =
static_cast<AVColorTransferCharacteristic
>(hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
1241 codec_ctx->colorspace =
static_cast<AVColorSpace
>(hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
1242 codec_ctx->color_range =
static_cast<AVColorRange
>(hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
1243 codec_ctx->chroma_sample_location = AVCHROMA_LOC_LEFT;
1246 std::string preset_hdr = opts.
preset.empty() ? std::string(
"medium") : opts.preset;
1247 av_opt_set(codec_ctx->priv_data,
"preset", preset_hdr.c_str(), 0);
1249 int crf_val_hdr = opts.
crf;
1250 if (crf_val_hdr < 0)
1252 if (crf_val_hdr > 51)
1254 const std::string crf_hdr = std::to_string(crf_val_hdr);
1255 av_opt_set(codec_ctx->priv_data,
"crf", crf_hdr.c_str(), 0);
1260 std::string x265_params =
"profile=main10:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:range=limited:repeat-headers=1";
1262 if (codec_ctx->color_trc == AVCOL_TRC_ARIB_STD_B67) {
1263 x265_params =
"profile=main10:colorprim=bt2020:transfer=arib-std-b67:colormatrix=bt2020nc:range=limited:repeat-headers=1";
1265 av_opt_set(codec_ctx->priv_data,
"x265-params", x265_params.c_str(), 0);
1267 if (pixel_format_option && requested_pixel_format != AV_PIX_FMT_YUV420P10LE) {
1268 std::cerr <<
"MXWrite: HDR output forces yuv420p10le; ignoring requested pixel format '" << *pixel_format_option <<
"'.\n";
1271 avcodec_free_context(&codec_ctx);
1272 avformat_free_context(format_ctx);
1273 format_ctx =
nullptr;
1278 if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
1279 codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
1281 if (avcodec_open2(codec_ctx, hdr_codec,
nullptr) < 0) {
1282 std::cerr <<
"MXWrite: could not open libx265 for HDR output.\n";
1283 avcodec_free_context(&codec_ctx);
1284 avformat_free_context(format_ctx);
1285 format_ctx =
nullptr;
1288 if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
1289 std::cerr <<
"MXWrite: could not copy HDR codec parameters.\n";
1290 avcodec_free_context(&codec_ctx);
1291 avformat_free_context(format_ctx);
1292 format_ctx =
nullptr;
1299 auto attach_side = [&](AVPacketSideDataType type,
const std::vector<uint8_t> &payload) {
1300 if (payload.empty())
1302 uint8_t *buf =
static_cast<uint8_t *
>(av_malloc(payload.size()));
1305 std::memcpy(buf, payload.data(), payload.size());
1306 const AVPacketSideData *added = av_packet_side_data_add(&stream->codecpar->coded_side_data, &stream->codecpar->nb_coded_side_data, type, buf, payload.size(), 0);
1309 std::cerr <<
"MXWrite: failed to attach HDR side data (type " << (int)type <<
").\n";
1312 attach_side(AV_PKT_DATA_MASTERING_DISPLAY_METADATA, hdr_info.mastering_display);
1313 attach_side(AV_PKT_DATA_CONTENT_LIGHT_LEVEL, hdr_info.content_light);
1315 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
1316 if (avio_open(&format_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE) < 0) {
1317 std::cerr <<
"MXWrite: could not open HDR output file: " << filename <<
"\n";
1318 avcodec_free_context(&codec_ctx);
1319 avformat_free_context(format_ctx);
1320 format_ctx =
nullptr;
1324 if (avformat_write_header(format_ctx,
nullptr) < 0) {
1325 std::cerr <<
"MXWrite: error writing HDR MP4 header.\n";
1326 avio_closep(&format_ctx->pb);
1327 avcodec_free_context(&codec_ctx);
1328 avformat_free_context(format_ctx);
1329 format_ctx =
nullptr;
1332 updateBytesWritten();
1335 frame10 = av_frame_alloc();
1337 std::cerr <<
"MXWrite: could not allocate YUV420P10LE frame.\n";
1338 avio_closep(&format_ctx->pb);
1339 avcodec_free_context(&codec_ctx);
1340 avformat_free_context(format_ctx);
1341 format_ctx =
nullptr;
1344 frame10->format = AV_PIX_FMT_YUV420P10LE;
1345 frame10->width = width;
1346 frame10->height = height;
1347 if (av_frame_get_buffer(frame10, 32) < 0) {
1348 std::cerr <<
"MXWrite: could not allocate YUV420P10LE buffer.\n";
1349 av_frame_free(&frame10);
1350 avio_closep(&format_ctx->pb);
1351 avcodec_free_context(&codec_ctx);
1352 avformat_free_context(format_ctx);
1353 format_ctx =
nullptr;
1358 use_hw_encode =
false;
1359 recordingStart = std::chrono::steady_clock::now();
1360 startEncoderThread();
1361 std::cout <<
"MXWrite: HDR output active (libx265 Main10, BT.2020, " << (codec_ctx->color_trc == AVCOL_TRC_ARIB_STD_B67 ?
"HLG" :
"PQ") <<
")\n";
1366 const bool is_high_res = (width > 3840 || height > 2160);
1368 if (codec_pref.empty()) {
1369 codec_pref =
"auto";
1371 const bool explicit_hevc_nvenc = (codec_pref ==
"hevc_nvenc" || codec_pref ==
"h265_nvenc");
1372 const bool explicit_h264_nvenc = (codec_pref ==
"h264_nvenc");
1373 const bool explicit_hevc_software = (codec_pref ==
"hevc" || codec_pref ==
"h265" || codec_pref ==
"libx265");
1374 const bool explicit_h264_software = (codec_pref ==
"h264" || codec_pref ==
"libx264");
1375 const bool use_hevc_codec = explicit_hevc_nvenc || explicit_hevc_software || (!explicit_h264_nvenc && !explicit_h264_software && is_high_res);
1376 std::string hw_codec_name = use_hevc_codec ?
"hevc_nvenc" :
"h264_nvenc";
1377 AVCodecID sw_codec_id = use_hevc_codec ? AV_CODEC_ID_HEVC : AV_CODEC_ID_H264;
1380 const AVCodec *codec =
nullptr;
1381 bool wants_hw =
false;
1382 if (codec_pref ==
"software" || codec_pref ==
"x264" || codec_pref ==
"cpu" || explicit_hevc_software || explicit_h264_software) {
1383 if (codec_pref ==
"libx264" || codec_pref ==
"libx265") {
1384 codec = avcodec_find_encoder_by_name(codec_pref.c_str());
1386 codec = avcodec_find_encoder(sw_codec_id);
1389 }
else if (codec_pref ==
"auto" || codec_pref ==
"nvenc" || explicit_hevc_nvenc || explicit_h264_nvenc) {
1392 codec = avcodec_find_encoder_by_name(hw_codec_name.c_str());
1393 wants_hw = (codec !=
nullptr);
1395 if (codec_pref ==
"nvenc" || explicit_hevc_nvenc || explicit_h264_nvenc) {
1396 std::cerr <<
"MXWrite: NVENC requested but " << hw_codec_name <<
" not available; falling back to software.\n";
1398 codec = avcodec_find_encoder(sw_codec_id);
1405 codec = avcodec_find_encoder_by_name(codec_pref.c_str());
1406 wants_hw = codec && (codec_pref.ends_with(
"_nvenc"));
1408 hw_codec_name = codec_pref;
1409 sw_codec_id = codec->id;
1414 std::cerr <<
"MXWrite: could not find requested video encoder '" << codec_pref <<
"'. Use acmx2 --list-encoders to see this FFmpeg build.\n";
1415 avformat_free_context(format_ctx);
1416 format_ctx =
nullptr;
1421 std::string preset = opts.
preset.empty() ? std::string(
"medium") : opts.preset;
1425 int crf_val = opts.
crf;
1430 const std::string crf_str = std::to_string(crf_val);
1432 stream = avformat_new_stream(format_ctx, codec);
1434 std::cerr <<
"Could not create new stream.\n";
1435 avformat_free_context(format_ctx);
1436 format_ctx =
nullptr;
1440 calculateFPSFraction(fps, fps_num, fps_den);
1442 AVRational tb = {fps_den, fps_num};
1443 stream->time_base = tb;
1445 codec_ctx = avcodec_alloc_context3(codec);
1447 std::cerr <<
"Could not allocate codec context.\n";
1448 avformat_free_context(format_ctx);
1449 format_ctx =
nullptr;
1453 codec_ctx->width = width;
1454 codec_ctx->height = height;
1455 codec_ctx->time_base = stream->time_base;
1456 codec_ctx->framerate = AVRational{fps_num, fps_den};
1457 AVPixelFormat software_pixel_format =
choose_software_pixel_format(wants_hw ? avcodec_find_encoder(sw_codec_id) : codec, requested_pixel_format);
1458 const bool requires_hardware_frames = !wants_hw && software_pixel_format == AV_PIX_FMT_NONE &&
codec_uses_hardware(codec);
1459 if (software_pixel_format == AV_PIX_FMT_NONE && !requires_hardware_frames) {
1460 const char *requested_name = requested_pixel_format == AV_PIX_FMT_NONE ?
"an automatic system-memory format" : av_get_pix_fmt_name(requested_pixel_format);
1461 std::cerr <<
"MXWrite: encoder '" << codec->name <<
"' does not accept " << (requested_name ? requested_name :
"the requested pixel format")
1462 <<
" that MXWrite can convert from RGBA. Choose a supported -pix_fmt; "
1463 "hardware-frame-only encoders require a device-specific upload path.\n";
1464 avcodec_free_context(&codec_ctx);
1465 avformat_free_context(format_ctx);
1466 format_ctx =
nullptr;
1469 codec_ctx->pix_fmt = wants_hw ? AV_PIX_FMT_CUDA : software_pixel_format;
1470 codec_ctx->gop_size = 30;
1471 codec_ctx->max_b_frames = 0;
1472 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
1476 codec_ctx->thread_type = FF_THREAD_SLICE;
1477 codec_ctx->slices = 4;
1479 codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
1481 codec_ctx->delay = 0;
1482 codec_ctx->bit_rate = std::max<std::int64_t>(0, opts.
bit_rate);
1485 codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
1488 bool extra_options_applied =
false;
1489 bool fell_back_from_hardware =
false;
1490 if (requires_hardware_frames) {
1492 if (!opts.
tune.empty() && opts.
tune !=
"none") {
1499 avcodec_free_context(&codec_ctx);
1500 avformat_free_context(format_ctx);
1501 format_ctx =
nullptr;
1504 extra_options_applied =
true;
1505 if (!initHardwareEncoding(codec, requested_pixel_format,
false)) {
1506 std::cerr <<
"MXWrite: encoder '" << codec->name
1507 <<
"' requires hardware frames, but its FFmpeg hardware device "
1508 "could not be initialized.\n";
1509 av_buffer_unref(&hw_frames_ctx);
1510 av_buffer_unref(&hw_device_ctx);
1511 av_frame_free(&upload_sw_frame);
1512 sws_freeContext(sws_ctx);
1514 avcodec_free_context(&codec_ctx);
1515 avformat_free_context(format_ctx);
1516 format_ctx =
nullptr;
1519 use_hw_encode =
true;
1520 std::cout <<
"MXWrite: hardware encoder selected (" << codec->name <<
")\n";
1524 av_opt_set(codec_ctx->priv_data,
"preset", nv_preset, 0);
1527 const std::string nv_tune = opts.
realtime ? std::string(
"ll") : (
is_nvenc_tune(requested_tune) ? requested_tune : std::string(
"hq"));
1528 av_opt_set(codec_ctx->priv_data,
"tune", nv_tune.c_str(), 0);
1530 const bool lossless_tune = nv_tune ==
"lossless" || (custom_tune &&
lowercase_ascii(*custom_tune) ==
"lossless");
1531 if (!lossless_tune) {
1532 av_opt_set(codec_ctx->priv_data,
"rc",
"vbr", 0);
1534 av_opt_set(codec_ctx->priv_data,
"cq", crf_str.c_str(), 0);
1538 av_opt_set(codec_ctx->priv_data,
"zerolatency",
"1", 0);
1540 if (use_hevc_codec) {
1541 av_opt_set(codec_ctx->priv_data,
"tier",
"high", 0);
1544 if (requested_pixel_format == AV_PIX_FMT_YUV444P) {
1545 if (av_opt_set(codec_ctx->priv_data,
"rgb_mode",
"yuv444", 0) < 0) {
1546 std::cerr <<
"MXWrite: this NVENC build cannot convert RGBA input to yuv444p.\n";
1547 avcodec_free_context(&codec_ctx);
1548 avformat_free_context(format_ctx);
1549 format_ctx =
nullptr;
1553 const char *profile = use_hevc_codec ?
"rext" :
"high444p";
1554 av_opt_set(codec_ctx->priv_data,
"profile", profile, 0);
1556 }
else if (requested_pixel_format != AV_PIX_FMT_NONE && requested_pixel_format != AV_PIX_FMT_YUV420P && requested_pixel_format != AV_PIX_FMT_RGBA) {
1557 std::cerr <<
"MXWrite: NVENC RGBA ingestion supports custom -pix_fmt yuv420p, "
1558 "yuv444p, or rgba; requested '"
1559 << av_get_pix_fmt_name(requested_pixel_format) <<
"'.\n";
1560 avcodec_free_context(&codec_ctx);
1561 avformat_free_context(format_ctx);
1562 format_ctx =
nullptr;
1567 avcodec_free_context(&codec_ctx);
1568 avformat_free_context(format_ctx);
1569 format_ctx =
nullptr;
1572 extra_options_applied =
true;
1574 if (initHardwareEncoding(codec, requested_pixel_format,
true)) {
1575 use_hw_encode =
true;
1576 std::cout <<
"MXWrite: hardware encoder selected (" << hw_codec_name <<
")\n";
1578 std::cerr <<
"MXWrite: " << hw_codec_name <<
" present but CUDA context failed, falling back to software encoder\n";
1579 fell_back_from_hardware =
true;
1580 av_buffer_unref(&hw_frames_ctx);
1581 av_buffer_unref(&hw_device_ctx);
1582 av_frame_free(&upload_sw_frame);
1583 sws_freeContext(sws_ctx);
1585 direct_cuda_upload =
false;
1586 avcodec_free_context(&codec_ctx);
1588 codec = avcodec_find_encoder(sw_codec_id);
1590 std::cerr <<
"Could not find software fallback encoder.\n";
1591 avformat_free_context(format_ctx);
1592 format_ctx =
nullptr;
1596 codec_ctx = avcodec_alloc_context3(codec);
1598 std::cerr <<
"Could not allocate fallback codec context.\n";
1599 avformat_free_context(format_ctx);
1600 format_ctx =
nullptr;
1604 codec_ctx->width = width;
1605 codec_ctx->height = height;
1606 codec_ctx->time_base = stream->time_base;
1607 codec_ctx->framerate = AVRational{fps_num, fps_den};
1608 codec_ctx->pix_fmt = software_pixel_format;
1609 codec_ctx->gop_size = 30;
1610 codec_ctx->max_b_frames = 0;
1611 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
1613 codec_ctx->thread_type = FF_THREAD_SLICE;
1614 codec_ctx->slices = 4;
1616 codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
1618 codec_ctx->delay = 0;
1619 codec_ctx->bit_rate = std::max<std::int64_t>(0, opts.
bit_rate);
1622 codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
1624 extra_options_applied =
false;
1628 if (!use_hw_encode) {
1632 std::string tune = opts.
realtime ? std::string(
"zerolatency") : opts.tune;
1633 if (fell_back_from_hardware) {
1635 if (lowered_tune ==
"hq" || lowered_tune ==
"uhq") {
1637 }
else if (lowered_tune ==
"ll" || lowered_tune ==
"ull") {
1638 tune =
"zerolatency";
1639 }
else if (lowered_tune ==
"lossless") {
1641 if (use_hevc_codec) {
1642 av_opt_set(codec_ctx->priv_data,
"x265-params",
"lossless=1", 0);
1644 av_opt_set(codec_ctx->priv_data,
"qp",
"0", 0);
1648 if (!tune.empty() && tune !=
"none") {
1654 if (opts.
realtime && codec->id == AV_CODEC_ID_H264) {
1657 av_opt_set(codec_ctx->priv_data,
"x264-params",
"bframes=0:ref=1:me=dia:subme=0", 0);
1658 av_opt_set(codec_ctx->priv_data,
"force_cfr",
"1", 0);
1662 const std::vector<FfmpegOption> translated_fallback_options = fell_back_from_hardware ?
software_fallback_options(extra_options, use_hevc_codec) : std::vector<FfmpegOption>{};
1663 const std::vector<FfmpegOption> &options_to_apply = fell_back_from_hardware ? translated_fallback_options : extra_options;
1664 if (!extra_options_applied && !
apply_ffmpeg_options(options_to_apply, codec_ctx, format_ctx)) {
1665 avcodec_free_context(&codec_ctx);
1666 avformat_free_context(format_ctx);
1667 format_ctx =
nullptr;
1673 if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
1674 codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
1676 if (avcodec_open2(codec_ctx, codec,
nullptr) < 0) {
1677 std::cerr <<
"MXWrite: could not open encoder '" << codec->name <<
"'. The encoder may require unavailable hardware or options.\n";
1678 avcodec_free_context(&codec_ctx);
1679 avformat_free_context(format_ctx);
1680 format_ctx =
nullptr;
1684 if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
1685 std::cerr <<
"Could not copy codec parameters.\n";
1686 avcodec_free_context(&codec_ctx);
1687 avformat_free_context(format_ctx);
1688 format_ctx =
nullptr;
1691 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
1692 if (avio_open(&format_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE) < 0) {
1693 std::cerr <<
"Could not open output file: " << filename <<
"\n";
1694 avcodec_free_context(&codec_ctx);
1695 avformat_free_context(format_ctx);
1696 format_ctx =
nullptr;
1700 if (avformat_write_header(format_ctx,
nullptr) < 0) {
1701 std::cerr <<
"Error writing MP4 header.\n";
1702 avio_closep(&format_ctx->pb);
1703 avcodec_free_context(&codec_ctx);
1704 avformat_free_context(format_ctx);
1705 format_ctx =
nullptr;
1708 updateBytesWritten();
1710 if (!use_hw_encode) {
1711 frameYUV = av_frame_alloc();
1713 std::cerr <<
"Could not allocate YUV frame.\n";
1714 avio_closep(&format_ctx->pb);
1715 avcodec_free_context(&codec_ctx);
1716 avformat_free_context(format_ctx);
1717 format_ctx =
nullptr;
1720 frameYUV->format = software_pixel_format;
1721 frameYUV->width = width;
1722 frameYUV->height = height;
1723 if (av_frame_get_buffer(frameYUV, 32) < 0) {
1724 std::cerr <<
"Could not allocate frame buffer for YUV frame.\n";
1725 av_frame_free(&frameYUV);
1726 avio_closep(&format_ctx->pb);
1727 avcodec_free_context(&codec_ctx);
1728 avformat_free_context(format_ctx);
1729 format_ctx =
nullptr;
1733 sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA, width, height, software_pixel_format, SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
1735 std::cerr <<
"Could not initialize conversion context.\n";
1736 av_frame_free(&frameYUV);
1737 avio_closep(&format_ctx->pb);
1738 avcodec_free_context(&codec_ctx);
1739 avformat_free_context(format_ctx);
1740 format_ctx =
nullptr;
1746 recordingStart = std::chrono::steady_clock::now();
1747 startEncoderThread();
1759 std::lock_guard<std::mutex> lock(writer_mutex);
1765 AVFrame *queued_frame = av_frame_alloc();
1766 if (!queued_frame) {
1767 std::cerr <<
"Writer: failed to allocate queued frame\n";
1771 if (use_hw_encode && hdr_output) {
1772 queued_frame->format = codec_ctx->pix_fmt;
1773 queued_frame->width = width;
1774 queued_frame->height = height;
1775 if (av_hwframe_get_buffer(hw_frames_ctx, queued_frame, 0) < 0) {
1776 std::cerr <<
"Writer: failed to allocate frame from hardware pool\n";
1777 releaseFrame(queued_frame);
1781 if (av_frame_make_writable(upload_sw_frame) < 0) {
1782 std::cerr <<
"Writer: software upload frame not writable\n";
1783 releaseFrame(queued_frame);
1787 const auto *src =
static_cast<const uint8_t *
>(rgba_buffer);
1788 if (upload_sw_frame->format == AV_PIX_FMT_RGBA) {
1789 for (
int y = 0; y < height; ++y) {
1790 std::memcpy(upload_sw_frame->data[0] +
static_cast<size_t>(y) * upload_sw_frame->linesize[0], src +
static_cast<size_t>(y) *
static_cast<size_t>(width) * 4,
static_cast<size_t>(width) * 4);
1793 const uint8_t *source_data[1] = {src};
1794 const int source_linesize[1] = {width * 4};
1795 sws_scale(sws_ctx, source_data, source_linesize, 0, height, upload_sw_frame->data, upload_sw_frame->linesize);
1798 if (av_hwframe_transfer_data(queued_frame, upload_sw_frame, 0) < 0) {
1799 std::cerr <<
"Writer: failed to transfer system frame to hardware frame\n";
1800 releaseFrame(queued_frame);
1804 queued_frame->format = AV_PIX_FMT_RGBA;
1805 queued_frame->width = width;
1806 queued_frame->height = height;
1807 if (av_frame_get_buffer(queued_frame, 32) < 0) {
1808 std::cerr <<
"Writer: failed to allocate queued RGBA frame buffer\n";
1809 releaseFrame(queued_frame);
1812 if (av_frame_make_writable(queued_frame) < 0) {
1813 std::cerr <<
"Writer: queued RGBA frame not writable\n";
1814 releaseFrame(queued_frame);
1818 const auto *src =
static_cast<const uint8_t *
>(rgba_buffer);
1819 for (
int y = 0; y < height; ++y) {
1820 std::memcpy(queued_frame->data[0] +
static_cast<size_t>(y) * queued_frame->linesize[0], src +
static_cast<size_t>(y) *
static_cast<size_t>(width) * 4,
static_cast<size_t>(width) * 4);
1825 std::unique_lock<std::mutex> lock(queue_mutex);
1826 if (block_when_full.load(std::memory_order_relaxed)) {
1827 if (encode_queue.size() >= NO_DROP_QUEUE_SIZE) {
1831 queue_cv.wait(lock, [
this] {
return stop_requested || encode_queue.size() < NO_DROP_QUEUE_SIZE; });
1833 if (stop_requested) {
1834 releaseFrame(queued_frame);
1837 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
1838 static int drop_counter = 0;
1839 if (++drop_counter % 30 == 0) {
1840 std::cerr <<
"Writer: dropped " << drop_counter <<
" SDR frames (encoder queue full)\n";
1842 releaseFrame(queued_frame);
1845 if (pts != AV_NOPTS_VALUE && pts < frame_count) {
1849 releaseFrame(queued_frame);
1852 queued_frame->pts = pts == AV_NOPTS_VALUE ? frame_count : pts;
1853 frame_count = queued_frame->pts + 1;
1854 encode_queue.push(queued_frame);
1857 queue_cv.notify_one();
1863 if (!rgba16_buffer) {
1868 std::lock_guard<std::mutex> lock(writer_mutex);
1873 std::cerr <<
"Writer: write_hdr_rgba16 called but writer not in HDR mode\n";
1878 AVFrame *queued_frame = av_frame_alloc();
1879 if (!queued_frame) {
1880 std::cerr <<
"Writer: failed to allocate queued HDR frame\n";
1883 queued_frame->format = AV_PIX_FMT_YUV420P10LE;
1884 queued_frame->width = width;
1885 queued_frame->height = height;
1886 if (av_frame_get_buffer(queued_frame, 32) < 0) {
1887 std::cerr <<
"Writer: failed to allocate YUV420P10 buffer\n";
1888 releaseFrame(queued_frame);
1891 if (av_frame_make_writable(queued_frame) < 0) {
1892 std::cerr <<
"Writer: queued HDR frame not writable\n";
1893 releaseFrame(queued_frame);
1899 convertBt2020Rgba16EncodedToYuv420p10(
reinterpret_cast<const uint16_t *
>(rgba16_buffer), width * 4,
reinterpret_cast<uint16_t *
>(queued_frame->data[0]), queued_frame->linesize[0] / 2,
reinterpret_cast<uint16_t *
>(queued_frame->data[1]), queued_frame->linesize[1] / 2,
reinterpret_cast<uint16_t *
>(queued_frame->data[2]), queued_frame->linesize[2] / 2, width, height);
1901 queued_frame->color_primaries =
static_cast<AVColorPrimaries
>(hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
1902 queued_frame->color_trc =
static_cast<AVColorTransferCharacteristic
>(hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
1903 queued_frame->colorspace =
static_cast<AVColorSpace
>(hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
1904 queued_frame->color_range =
static_cast<AVColorRange
>(hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
1907 std::unique_lock<std::mutex> lock(queue_mutex);
1908 if (block_when_full.load(std::memory_order_relaxed)) {
1909 if (encode_queue.size() >= NO_DROP_QUEUE_SIZE) {
1910 queue_cv.wait(lock, [
this] {
return stop_requested || encode_queue.size() < NO_DROP_QUEUE_SIZE; });
1912 if (stop_requested) {
1913 releaseFrame(queued_frame);
1916 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
1917 static int drop_counter = 0;
1918 if (++drop_counter % 30 == 0) {
1919 std::cerr <<
"Writer: dropped " << drop_counter <<
" HDR frames (encoder queue full)\n";
1921 releaseFrame(queued_frame);
1924 if (pts != AV_NOPTS_VALUE && pts < frame_count) {
1925 releaseFrame(queued_frame);
1928 queued_frame->pts = pts == AV_NOPTS_VALUE ? frame_count : pts;
1929 frame_count = queued_frame->pts + 1;
1930 encode_queue.push(queued_frame);
1933 queue_cv.notify_one();
1939 if (!cuda_rgba_buffer || src_stride <= 0) {
1944 std::lock_guard<std::mutex> lock(writer_mutex);
1945 if (!opened || !use_hw_encode || !direct_cuda_upload) {
1950 AVFrame *queued_frame = av_frame_alloc();
1951 if (!queued_frame) {
1952 std::cerr <<
"Writer: failed to allocate queued CUDA frame\n";
1956 queued_frame->format = AV_PIX_FMT_CUDA;
1957 queued_frame->width = width;
1958 queued_frame->height = height;
1960 if (av_hwframe_get_buffer(hw_frames_ctx, queued_frame, 0) < 0) {
1961 std::cerr <<
"Writer: failed to allocate CUDA frame from hardware pool\n";
1962 releaseFrame(queued_frame);
1966#ifdef MXWRITE_HAS_CUDA_COPY
1967 cudaStream_t stream = cuda_upload_stream;
1968 const bool use_async_stream = (stream !=
nullptr);
1970 const auto copy_err = use_async_stream ? cudaMemcpy2DAsync(queued_frame->data[0],
static_cast<size_t>(queued_frame->linesize[0]), cuda_rgba_buffer,
static_cast<size_t>(src_stride),
static_cast<size_t>(width) * 4,
static_cast<size_t>(height), cudaMemcpyDeviceToDevice, stream) : cudaMemcpy2D(queued_frame->data[0],
static_cast<size_t>(queued_frame->linesize[0]), cuda_rgba_buffer,
static_cast<size_t>(src_stride),
static_cast<size_t>(width) * 4,
static_cast<size_t>(height), cudaMemcpyDeviceToDevice);
1972 if (copy_err != cudaSuccess) {
1973 std::cerr <<
"Writer: cudaMemcpy2D device upload failed: " << cudaGetErrorString(copy_err) <<
"\n";
1974 releaseFrame(queued_frame);
1981 auto *src_base =
static_cast<unsigned char *
>(cuda_rgba_buffer);
1982 auto *dst_base = queued_frame->data[0];
1983 const size_t row_bytes =
static_cast<size_t>(width) * 4;
1985 for (
int y = 0; y < height; ++y) {
1986 auto *src_row = src_base +
static_cast<size_t>(height - 1 - y) *
static_cast<size_t>(src_stride);
1987 auto *dst_row = dst_base +
static_cast<size_t>(y) *
static_cast<size_t>(queued_frame->linesize[0]);
1988 const auto row_copy_err = use_async_stream ? cudaMemcpyAsync(dst_row, src_row, row_bytes, cudaMemcpyDeviceToDevice, stream) : cudaMemcpy(dst_row, src_row, row_bytes, cudaMemcpyDeviceToDevice);
1989 if (row_copy_err != cudaSuccess) {
1990 std::cerr <<
"Writer: cudaMemcpy row upload failed: " << cudaGetErrorString(row_copy_err) <<
"\n";
1991 releaseFrame(queued_frame);
1999 if (use_async_stream) {
2000 const auto sync_err = cudaStreamSynchronize(stream);
2001 if (sync_err != cudaSuccess) {
2002 std::cerr <<
"Writer: cudaStreamSynchronize failed: " << cudaGetErrorString(sync_err) <<
"\n";
2003 releaseFrame(queued_frame);
2008 std::cerr <<
"Writer: CUDA copy support disabled at build time\n";
2009 releaseFrame(queued_frame);
2014 std::unique_lock<std::mutex> lock(queue_mutex);
2015 if (block_when_full.load(std::memory_order_relaxed)) {
2016 if (encode_queue.size() >= NO_DROP_QUEUE_SIZE) {
2017 queue_cv.wait(lock, [
this] {
return stop_requested || encode_queue.size() < NO_DROP_QUEUE_SIZE; });
2019 if (stop_requested) {
2020 releaseFrame(queued_frame);
2023 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
2024 static int drop_counter = 0;
2025 if (++drop_counter % 30 == 0) {
2026 std::cerr <<
"Writer: dropped " << drop_counter <<
" frames (encoder queue full)\n";
2028 releaseFrame(queued_frame);
2032 queue_cv.notify_one();
2035 if (pts != AV_NOPTS_VALUE && pts < frame_count) {
2036 releaseFrame(queued_frame);
2037 queue_cv.notify_one();
2040 queued_frame->pts = pts == AV_NOPTS_VALUE ? frame_count : pts;
2041 frame_count = queued_frame->pts + 1;
2042 encode_queue.push(queued_frame);
2045 queue_cv.notify_one();
2051void Writer::startEncoderThread() {
2052 stop_requested =
false;
2053 encode_thread = std::jthread([
this](std::stop_token st) { encodeLoop(st); });
2056void Writer::stopEncoderThread() {
2058 std::lock_guard<std::mutex> lock(queue_mutex);
2059 stop_requested =
true;
2061 queue_cv.notify_all();
2063 if (encode_thread.joinable()) {
2064 encode_thread.request_stop();
2065 encode_thread.join();
2069void Writer::releaseFrame(AVFrame *f) {
2076void Writer::drainEncoderPackets() {
2077 AVPacket *pkt = av_packet_alloc();
2079 std::cerr <<
"Writer: failed to allocate packet\n";
2084 int ret = avcodec_receive_packet(codec_ctx, pkt);
2085 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
2089 std::cerr <<
"Writer: error receiving packet: " << ret <<
"\n";
2093 if (pkt->duration <= 0) {
2096 av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
2097 pkt->stream_index = stream->index;
2099 if (av_interleaved_write_frame(format_ctx, pkt) < 0) {
2100 std::cerr <<
"Writer: error writing frame\n";
2101 av_packet_unref(pkt);
2104 updateBytesWritten();
2105 av_packet_unref(pkt);
2108 av_packet_free(&pkt);
2111void Writer::encodeAndWriteFrame(AVFrame *in_frame) {
2116 AVFrame *encode_frame = in_frame;
2117 AVFrame *uploaded_frame =
nullptr;
2119 if (in_frame->format == AV_PIX_FMT_YUV420P10LE) {
2122 encode_frame = in_frame;
2126 if (av_frame_make_writable(frame10) < 0) {
2127 std::cerr <<
"Writer: HDR frame not writable\n";
2130 convertRgbaToBt2020PqYuv420p10(in_frame->data[0], in_frame->linesize[0],
reinterpret_cast<uint16_t *
>(frame10->data[0]), frame10->linesize[0] / 2,
reinterpret_cast<uint16_t *
>(frame10->data[1]), frame10->linesize[1] / 2,
reinterpret_cast<uint16_t *
>(frame10->data[2]), frame10->linesize[2] / 2, width, height);
2131 frame10->pts = in_frame->pts;
2132 frame10->color_primaries =
static_cast<AVColorPrimaries
>(hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
2133 frame10->color_trc =
static_cast<AVColorTransferCharacteristic
>(hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
2134 frame10->colorspace =
static_cast<AVColorSpace
>(hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
2135 frame10->color_range =
static_cast<AVColorRange
>(hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
2136 encode_frame = frame10;
2138 }
else if (use_hw_encode && in_frame->format == AV_PIX_FMT_RGBA) {
2139 uploaded_frame = av_frame_alloc();
2140 if (!uploaded_frame) {
2141 std::cerr <<
"Writer: failed to allocate hardware upload frame\n";
2144 uploaded_frame->format = codec_ctx->pix_fmt;
2145 uploaded_frame->width = width;
2146 uploaded_frame->height = height;
2147 if (av_hwframe_get_buffer(hw_frames_ctx, uploaded_frame, 0) < 0) {
2148 std::cerr <<
"Writer: failed to allocate frame from hardware pool\n";
2149 releaseFrame(uploaded_frame);
2152 if (av_frame_make_writable(upload_sw_frame) < 0) {
2153 std::cerr <<
"Writer: software upload frame not writable\n";
2154 releaseFrame(uploaded_frame);
2158 if (upload_sw_frame->format == AV_PIX_FMT_RGBA) {
2159 for (
int y = 0; y < height; ++y) {
2160 std::memcpy(upload_sw_frame->data[0] +
static_cast<size_t>(y) * upload_sw_frame->linesize[0], in_frame->data[0] +
static_cast<size_t>(y) * in_frame->linesize[0],
static_cast<size_t>(width) * 4);
2163 const uint8_t *source_data[1] = {in_frame->data[0]};
2164 const int source_linesize[1] = {in_frame->linesize[0]};
2165 sws_scale(sws_ctx, source_data, source_linesize, 0, height, upload_sw_frame->data, upload_sw_frame->linesize);
2168 if (av_hwframe_transfer_data(uploaded_frame, upload_sw_frame, 0) < 0) {
2169 std::cerr <<
"Writer: failed to transfer system frame to hardware frame\n";
2170 releaseFrame(uploaded_frame);
2173 uploaded_frame->pts = in_frame->pts;
2174 encode_frame = uploaded_frame;
2175 }
else if (!use_hw_encode) {
2176 const uint8_t *src_data[1] = {in_frame->data[0]};
2177 int src_linesize[1] = {in_frame->linesize[0]};
2178 sws_scale(sws_ctx, src_data, src_linesize, 0, height, frameYUV->data, frameYUV->linesize);
2179 frameYUV->pts = in_frame->pts;
2180 encode_frame = frameYUV;
2183 int ret = avcodec_send_frame(codec_ctx, encode_frame);
2184 if (ret == AVERROR(EAGAIN)) {
2186 drainEncoderPackets();
2187 ret = avcodec_send_frame(codec_ctx, encode_frame);
2190 std::cerr <<
"Writer: error sending frame to encoder: " << ret <<
"\n";
2191 releaseFrame(uploaded_frame);
2195 drainEncoderPackets();
2196 releaseFrame(uploaded_frame);
2199void Writer::encodeLoop(std::stop_token stop_token) {
2201 AVFrame *frame =
nullptr;
2203 std::unique_lock<std::mutex> lock(queue_mutex);
2204 queue_cv.wait(lock, [
this, &stop_token]() {
return stop_requested || stop_token.stop_requested() || !encode_queue.empty(); });
2206 if ((stop_requested || stop_token.stop_requested()) && encode_queue.empty()) {
2210 frame = encode_queue.front();
2214 queue_cv.notify_one();
2216 encodeAndWriteFrame(frame);
2217 releaseFrame(frame);
2221 const int flush_ret = avcodec_send_frame(codec_ctx,
nullptr);
2222 if (flush_ret >= 0) {
2223 drainEncoderPackets();
2228void Writer::updateBytesWritten() noexcept {
2229 if (!format_ctx || !format_ctx->pb) {
2233 const int64_t position = avio_tell(format_ctx->pb);
2234 if (position >= 0) {
2235 bytes_written.store(
static_cast<std::uint64_t
>(position), std::memory_order_relaxed);
2240 std::lock_guard<std::mutex> lock(writer_mutex);
2245 stopEncoderThread();
2247 if (stream && codec_ctx && frame_count > 0) {
2248 stream->duration = av_rescale_q(frame_count, codec_ctx->time_base, stream->time_base);
2250 if (fps_num > 0 && fps_den > 0) {
2251 last_duration =
static_cast<double>(frame_count) *
static_cast<double>(fps_den) /
static_cast<double>(fps_num);
2252 }
else if (stream && stream->duration > 0) {
2253 last_duration =
static_cast<double>(stream->duration) * av_q2d(stream->time_base);
2256 av_write_trailer(format_ctx);
2257 updateBytesWritten();
2259 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
2260 avio_closep(&format_ctx->pb);
2263 av_frame_free(&frameRGBA);
2264 av_frame_free(&frameYUV);
2265 av_frame_free(&frame10);
2266 sws_freeContext(sws_ctx);
2267 av_frame_free(&upload_sw_frame);
2268#ifdef MXWRITE_HAS_CUDA_COPY
2270 if (cuda_upload_stream) {
2271 cudaStreamSynchronize(cuda_upload_stream);
2272 cudaStreamDestroy(cuda_upload_stream);
2273 cuda_upload_stream =
nullptr;
2276 avcodec_free_context(&codec_ctx);
2277 av_buffer_unref(&hw_frames_ctx);
2278 av_buffer_unref(&hw_device_ctx);
2279 avformat_free_context(format_ctx);
2281 while (!encode_queue.empty()) {
2282 releaseFrame(encode_queue.front());
2286 format_ctx =
nullptr;
2287 codec_ctx =
nullptr;
2289 frameRGBA =
nullptr;
2292 upload_sw_frame =
nullptr;
2293 use_hw_encode =
false;
2294 active_encoder_hardware =
false;
2295 direct_cuda_upload =
false;
2297 stop_requested =
false;
2301 if (!opened && last_duration > 0.0) {
2302 return last_duration;
2304 if (stream && stream->duration > 0) {
2305 return static_cast<double>(stream->duration) * av_q2d(stream->time_base);
2307 if (fps_num > 0 && fps_den > 0) {
2308 return static_cast<double>(frame_count) *
static_cast<double>(fps_den) /
static_cast<double>(fps_num);
void write_hdr_rgba16_at_pts(void *rgba16_buffer, int64_t pts)
Queue a 16-bit HDR RGBA frame with an explicit presentation timestamp.
bool write_cuda_rgba(void *cuda_rgba_buffer, int src_stride, bool bottom_up=false)
Queue a CUDA RGBA frame for encoding.
void write_at_pts(void *rgba_buffer, int64_t pts)
Queue a host RGBA frame with an explicit presentation timestamp.
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.
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.
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.
std::vector< EncoderInfo > available_video_encoders()
std::vector< EncoderOptionInfo > video_encoder_options(std::string_view encoder_name)
Return the options exposed by one registered video encoder.
FFmpeg-based video writer used by MXWrite.
const std::string * find_ffmpeg_option(const std::vector< FfmpegOption > &options, const std::string &name)
std::string nvenc_preset_to_software(const std::string &preset)
std::string lowercase_ascii(std::string text)
uint16_t clamp10(float v)
bool looks_like_option(const std::string &token)
bool parse_ffmpeg_options(const std::string &text, std::vector< FfmpegOption > &options)
std::vector< AVPixelFormat > supported_pixel_formats(const AVCodec *codec)
bool is_codec_name(const std::string &value)
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_nvenc_tune(const std::string &tune)
bool option_has_numeric_range(AVOptionType type)
std::string normalize_ffmpeg_option_name(std::string name)
std::string number_text(double value)
bool is_valid_x264_preset(const std::string &p)
int option_base_type(AVOptionType type)
bool tokenize_ffmpeg_options(const std::string &text, std::vector< std::string > &tokens, std::string &error)
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)
bool set_named_encoder_option(void *object, const char *name, const std::string &value)
constexpr float kSdrRefFraction
bool is_reserved_ffmpeg_option(const std::string &name)
void append_encoder_options(void *object, std::vector< EncoderOptionInfo > &result, std::set< std::string > &seen)
const char * x264_preset_to_nvenc(const std::string &p)
std::vector< FfmpegOption > software_fallback_options(const std::vector< FfmpegOption > &options, bool use_hevc_codec)
bool apply_ffmpeg_options(const std::vector< FfmpegOption > &options, AVCodecContext *context, AVFormatContext *output_context)
bool codec_uses_hardware(const AVCodec *codec)
std::string option_type_name(AVOptionType type)
AVPixelFormat choose_software_pixel_format(const AVCodec *codec, AVPixelFormat requested_format)
bool is_hardware_pixel_format(AVPixelFormat format)
bool enabled
Enables the HDR output path.
User-configurable video encoder quality options.
std::string codec
Encoder selection policy or exact FFmpeg encoder name.
std::int64_t bit_rate
Target bits/second; 0 selects CRF/CQ.
std::string tune
Optional tuning mode.
bool realtime
Enable low-latency settings.
std::string ffmpeg_options
Additional FFmpeg-style video encoder options.
struct EncodeOptions::HdrInfo hdr
int crf
Constant Rate Factor.
bool block_when_full
Pace producers to encoder throughput instead of dropping frames.
std::string preset
Encoder preset name.
A video encoder reported by the linked FFmpeg installation.
std::string long_name
Human-readable encoder description.
std::string pixel_formats
Comma-separated supported input pixel formats.
std::string name
Exact libavcodec encoder name (for example, libx265).
std::string codec_name
Encoded format name (for example, hevc or av1).
bool experimental
True when FFmpeg marks the encoder experimental.
bool hardware
True for hardware or hybrid encoders.
One configurable AVOption exposed by a video encoder.
std::string minimum
Minimum value for numeric options.
std::string name
Option name accepted by EncodeOptions::ffmpeg_options.
std::string help
Human-readable FFmpeg option description.
std::string choices
Comma-separated named values for enum-like options.
std::string type
FFmpeg option type.
std::string maximum
Maximum value for numeric options.
std::string default_value
Encoder default, when it can be represented as text.