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)
44 : std::pow((v + 0.055f) / 1.055f, 2.4f);
49 const float Lm = std::pow(std::max(0.0f, L),
kPqM1);
51 const float den = 1.0f +
kPqC3 * Lm;
52 return std::pow(num / den,
kPqM2);
60 return static_cast<uint16_t
>(v + 0.5f);
69 uint16_t *y_plane,
int y_stride_shorts,
70 uint16_t *u_plane,
int u_stride_shorts,
71 uint16_t *v_plane,
int v_stride_shorts,
72 int width,
int height) {
78 constexpr float kKr = 0.2627f;
79 constexpr float kKg = 0.6780f;
80 constexpr float kKb = 0.0593f;
81 constexpr float kPbDiv = 1.0f / 1.8814f;
82 constexpr float kPrDiv = 1.0f / 1.4746f;
84 for (
int y = 0; y < height; y += 2) {
85 const int y1 = std::min(y + 1, height - 1);
86 const uint8_t *row0 = rgba + y * src_stride_bytes;
87 const uint8_t *row1 = rgba + y1 * src_stride_bytes;
88 uint16_t *yr0 = y_plane + y * y_stride_shorts;
89 uint16_t *yr1 = y_plane + y1 * y_stride_shorts;
90 uint16_t *ur = u_plane + (y / 2) * u_stride_shorts;
91 uint16_t *vr = v_plane + (y / 2) * v_stride_shorts;
93 for (
int x = 0; x < width; x += 2) {
94 const int x1 = std::min(x + 1, width - 1);
97 auto loadPq = [](
const uint8_t *px,
98 float &Y,
float &U,
float &V) {
100 const float r =
srgbEotf(px[0] * (1.0f / 255.0f));
101 const float g =
srgbEotf(px[1] * (1.0f / 255.0f));
102 const float b =
srgbEotf(px[2] * (1.0f / 255.0f));
108 const float rp =
pqOetf(rL);
109 const float gp =
pqOetf(gL);
110 const float bp =
pqOetf(bL);
112 Y = kKr * rp + kKg * gp + kKb * bp;
113 U = (bp - Y) * kPbDiv;
114 V = (rp - Y) * kPrDiv;
121 loadPq(row0 + x * 4, Y00, U00, V00);
122 loadPq(row0 + x1 * 4, Y01, U01, V01);
123 loadPq(row1 + x * 4, Y10, U10, V10);
124 loadPq(row1 + x1 * 4, Y11, U11, V11);
127 yr0[x] =
clamp10(Y00 * 876.0f + 64.0f);
128 yr0[x1] =
clamp10(Y01 * 876.0f + 64.0f);
129 yr1[x] =
clamp10(Y10 * 876.0f + 64.0f);
130 yr1[x1] =
clamp10(Y11 * 876.0f + 64.0f);
133 const float Uavg = 0.25f * (U00 + U01 + U10 + U11);
134 const float Vavg = 0.25f * (V00 + V01 + V10 + V11);
135 ur[x / 2] =
clamp10(Uavg * 896.0f + 512.0f);
136 vr[x / 2] =
clamp10(Vavg * 896.0f + 512.0f);
147 int src_stride_shorts,
148 uint16_t *y_plane,
int y_stride_shorts,
149 uint16_t *u_plane,
int u_stride_shorts,
150 uint16_t *v_plane,
int v_stride_shorts,
151 int width,
int height) {
152 constexpr float kKr = 0.2627f;
153 constexpr float kKg = 0.6780f;
154 constexpr float kKb = 0.0593f;
155 constexpr float kPbDiv = 1.0f / 1.8814f;
156 constexpr float kPrDiv = 1.0f / 1.4746f;
157 constexpr float kInv65535 = 1.0f / 65535.0f;
159 for (
int y = 0; y < height; y += 2) {
160 const int y1 = std::min(y + 1, height - 1);
161 const uint16_t *row0 = rgba + y * src_stride_shorts;
162 const uint16_t *row1 = rgba + y1 * src_stride_shorts;
163 uint16_t *yr0 = y_plane + y * y_stride_shorts;
164 uint16_t *yr1 = y_plane + y1 * y_stride_shorts;
165 uint16_t *ur = u_plane + (y / 2) * u_stride_shorts;
166 uint16_t *vr = v_plane + (y / 2) * v_stride_shorts;
168 for (
int x = 0; x < width; x += 2) {
169 const int x1 = std::min(x + 1, width - 1);
171 auto load = [&](
const uint16_t *px,
float &Y,
float &U,
float &V) {
172 const float rp = px[0] * kInv65535;
173 const float gp = px[1] * kInv65535;
174 const float bp = px[2] * kInv65535;
175 Y = kKr * rp + kKg * gp + kKb * bp;
176 U = (bp - Y) * kPbDiv;
177 V = (rp - Y) * kPrDiv;
184 load(row0 + x * 4, Y00, U00, V00);
185 load(row0 + x1 * 4, Y01, U01, V01);
186 load(row1 + x * 4, Y10, U10, V10);
187 load(row1 + x1 * 4, Y11, U11, V11);
189 yr0[x] =
clamp10(Y00 * 876.0f + 64.0f);
190 yr0[x1] =
clamp10(Y01 * 876.0f + 64.0f);
191 yr1[x] =
clamp10(Y10 * 876.0f + 64.0f);
192 yr1[x1] =
clamp10(Y11 * 876.0f + 64.0f);
194 const float Uavg = 0.25f * (U00 + U01 + U10 + U11);
195 const float Vavg = 0.25f * (V00 + V01 + V10 + V11);
196 ur[x / 2] =
clamp10(Uavg * 896.0f + 512.0f);
197 vr[x / 2] =
clamp10(Vavg * 896.0f + 512.0f);
207 const char *ext = strrchr(filename,
'.');
211 std::string lower_ext(ext);
212 std::transform(lower_ext.begin(), lower_ext.end(), lower_ext.begin(),
213 [](
unsigned char c) { return std::tolower(c); });
214 static const char *kSupported[] = {
215 ".mp4",
".mkv",
".mov",
".avi",
".m4v",
216 ".ts",
".mts",
".m2ts",
".mpg",
".mpeg",
217 ".flv",
".f4v",
".3gp",
".3g2",
".wmv",
219 for (
const char *s : kSupported) {
227 AVFormatContext *dest_ctx,
228 AVFormatContext *output_ctx) {
230 avformat_close_input(&source_ctx);
232 avformat_close_input(&dest_ctx);
234 if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
235 avio_closep(&output_ctx->pb);
236 avformat_free_context(output_ctx);
240void transfer_audio(std::string_view sourceAudioFile, std::string_view destVideoFile) {
243 std::cerr <<
"Unsupported output format. Supported formats: .mp4, .mkv, .avi, .mov\n";
247 AVFormatContext *source_ctx =
nullptr, *dest_ctx =
nullptr, *output_ctx =
nullptr;
248 int source_audio_idx = -1, dest_video_idx = -1, dest_audio_idx = -1;
249 std::string temp_output = std::string(destVideoFile) +
".tmp";
251 if (avformat_open_input(&source_ctx, sourceAudioFile.data(),
nullptr,
nullptr) != 0 ||
252 avformat_open_input(&dest_ctx, destVideoFile.data(),
nullptr,
nullptr) != 0) {
253 std::cerr <<
"Failed to open input files\n";
258 if (avformat_find_stream_info(source_ctx,
nullptr) < 0 ||
259 avformat_find_stream_info(dest_ctx,
nullptr) < 0) {
260 std::cerr <<
"Failed to find stream info\n";
265 for (
unsigned i = 0; i < source_ctx->nb_streams; ++i) {
266 if (source_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
267 source_audio_idx = i;
272 for (
unsigned i = 0; i < dest_ctx->nb_streams; ++i) {
273 AVMediaType type = dest_ctx->streams[i]->codecpar->codec_type;
274 if (type == AVMEDIA_TYPE_VIDEO)
276 else if (type == AVMEDIA_TYPE_AUDIO)
280 if (source_audio_idx == -1 || dest_video_idx == -1) {
281 std::cerr <<
"Required streams not found\n";
286 const AVOutputFormat *output_fmt = av_guess_format(
nullptr, destVideoFile.data(),
nullptr);
288 output_fmt = av_guess_format(
"mp4",
nullptr,
nullptr);
290 std::cerr <<
"Failed to determine output format\n";
296 if (avformat_alloc_output_context2(&output_ctx, output_fmt,
nullptr, temp_output.c_str()) < 0) {
297 std::cerr <<
"Failed to create output context\n";
302 const AVCodec *audio_codec = avcodec_find_decoder(source_ctx->streams[source_audio_idx]->codecpar->codec_id);
304 std::cerr <<
"Failed to find audio decoder\n";
309 for (
unsigned i = 0; i < dest_ctx->nb_streams; ++i) {
310 if (dest_ctx->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
314 AVStream *dest_stream = dest_ctx->streams[i];
315 AVStream *out_stream = avformat_new_stream(output_ctx,
nullptr);
317 std::cerr <<
"Failed to create output stream\n";
322 if (avcodec_parameters_copy(out_stream->codecpar, dest_stream->codecpar) < 0) {
323 std::cerr <<
"Failed to copy video parameters\n";
328 out_stream->time_base = dest_stream->time_base;
329 out_stream->codecpar->codec_tag = 0;
332 AVStream *out_stream = avformat_new_stream(output_ctx, audio_codec);
334 std::cerr <<
"Failed to create audio stream\n";
339 AVCodecParameters *source_params = source_ctx->streams[source_audio_idx]->codecpar;
340 if (avcodec_parameters_copy(out_stream->codecpar, source_params) < 0) {
341 std::cerr <<
"Failed to copy audio parameters\n";
346 if (source_params->frame_size == 0) {
347 out_stream->codecpar->frame_size = 1024;
349 out_stream->codecpar->frame_size = source_params->frame_size;
352 out_stream->time_base = source_ctx->streams[source_audio_idx]->time_base;
353 out_stream->codecpar->codec_tag = 0;
354 dest_audio_idx = out_stream->index;
356 if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {
357 if (avio_open(&output_ctx->pb, temp_output.c_str(), AVIO_FLAG_WRITE) < 0) {
358 std::cerr <<
"Failed to open output file\n";
363 if (avformat_write_header(output_ctx,
nullptr) < 0) {
364 std::cerr <<
"Failed to write header\n";
369 while (av_read_frame(dest_ctx, &packet) >= 0) {
370 if (packet.stream_index == dest_audio_idx) {
371 av_packet_unref(&packet);
375 AVStream *in_stream = dest_ctx->streams[packet.stream_index];
376 AVStream *out_stream = output_ctx->streams[packet.stream_index];
377 av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
379 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
380 std::cerr <<
"Failed to write packet\n";
381 av_packet_unref(&packet);
385 av_packet_unref(&packet);
388 int64_t video_duration_ts = 0;
390 AVStream *vid_stream = dest_ctx->streams[0];
391 if (vid_stream->duration > 0) {
392 video_duration_ts = av_rescale_q(vid_stream->duration, vid_stream->time_base, source_ctx->streams[source_audio_idx]->time_base);
393 }
else if (dest_ctx->duration > 0) {
394 AVRational av_tb = {1, AV_TIME_BASE};
395 video_duration_ts = av_rescale_q(dest_ctx->duration, av_tb, source_ctx->streams[source_audio_idx]->time_base);
399 av_seek_frame(source_ctx, source_audio_idx, 0, AVSEEK_FLAG_BACKWARD);
400 while (av_read_frame(source_ctx, &packet) >= 0) {
401 if (packet.stream_index == source_audio_idx) {
402 if (video_duration_ts > 0 && packet.pts != AV_NOPTS_VALUE && packet.pts > video_duration_ts) {
403 av_packet_unref(&packet);
406 AVStream *in_stream = source_ctx->streams[packet.stream_index];
407 AVStream *out_stream = output_ctx->streams[dest_audio_idx];
408 av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
409 packet.stream_index = dest_audio_idx;
411 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
412 std::cerr <<
"Failed to write audio packet\n";
413 av_packet_unref(&packet);
418 av_packet_unref(&packet);
420 av_write_trailer(output_ctx);
422 std::remove(destVideoFile.data());
423 std::rename(temp_output.c_str(), destVideoFile.data());
426void Writer::calculateFPSFraction(
float fps,
int &fps_num,
int &fps_den) {
427 const float epsilon = 0.001f;
429 if (std::fabs(fps - 29.97f) < epsilon) {
432 }
else if (std::fabs(fps - 59.94f) < epsilon) {
436 float precision = 1000.0f;
437 fps_num =
static_cast<int>(std::round(fps * precision));
438 fps_den =
static_cast<int>(precision);
439 int gcd = std::gcd(fps_num, fps_den);
450 if (p ==
"ultrafast")
452 if (p ==
"superfast")
454 if (p ==
"veryfast" || p ==
"faster")
458 if (p ==
"medium" || p.empty())
460 if (p ==
"slow" || p ==
"slower")
469 static const char *presets[] = {
470 "ultrafast",
"superfast",
"veryfast",
"faster",
"fast",
471 "medium",
"slow",
"slower",
"veryslow",
"placebo"};
472 for (
const char *n : presets)
479 std::transform(text.begin(), text.end(), text.begin(), [](
unsigned char ch) {
480 return static_cast<char>(std::tolower(ch));
491#if LIBAVUTIL_VERSION_MAJOR >= 60
492 return static_cast<int>(type) & ~AV_OPT_TYPE_FLAG_ARRAY;
494 return static_cast<int>(type);
499 if (token.size() < 2 || token.front() !=
'-') {
502 const unsigned char next =
static_cast<unsigned char>(token[1]);
503 return !std::isdigit(next) && token[1] !=
'.';
507 std::string &error) {
509 bool token_started =
false;
510 bool escaped =
false;
513 for (
char ch : text) {
516 token_started =
true;
520 if (ch ==
'\\' && quote !=
'\'') {
522 token_started =
true;
531 token_started =
true;
534 if (ch ==
'\'' || ch ==
'"') {
536 token_started =
true;
537 }
else if (std::isspace(
static_cast<unsigned char>(ch))) {
539 tokens.push_back(token);
541 token_started =
false;
545 token_started =
true;
550 error =
"trailing escape character";
554 error =
"unterminated quote";
558 tokens.push_back(token);
564 while (!name.empty() && name.front() ==
'-') {
565 name.erase(name.begin());
568 const size_t stream_specifier = name.find(
':');
569 if (stream_specifier != std::string::npos) {
570 const std::string suffix = name.substr(stream_specifier + 1);
571 if (suffix ==
"v" || suffix.starts_with(
"v:")) {
572 name.erase(stream_specifier);
580 return codec ==
"h265_nvenc" || codec ==
"h264" || codec ==
"h265" ||
581 codec ==
"hevc" || codec ==
"nvenc" || codec ==
"software" || codec ==
"auto" ||
582 avcodec_find_encoder_by_name(codec.c_str()) !=
nullptr;
586 std::vector<std::string> tokens;
589 std::cerr <<
"MXWrite: invalid extra FFmpeg parameters: " << error <<
".\n";
593 for (
size_t index = 0; index < tokens.size(); ++index) {
594 std::string token = tokens[index];
597 options.push_back({
"codec", token});
599 std::cerr <<
"MXWrite: ignoring non-option extra parameter '" << token
600 <<
"' (filenames are selected by Writer::open).\n";
605 while (!token.empty() && token.front() ==
'-') {
606 token.erase(token.begin());
608 const size_t equals = token.find(
'=');
611 if (equals != std::string::npos) {
612 value = token.substr(equals + 1);
614 value = tokens[++index];
620 std::cerr <<
"MXWrite: invalid empty FFmpeg option name.\n";
623 options.push_back({std::move(name), std::move(value)});
629 const std::string &name) {
630 for (
auto option = options.rbegin(); option != options.rend(); ++option) {
631 if (option->name == name) {
632 return &option->value;
639 return name ==
"c" || name ==
"codec" || name ==
"vcodec" || name ==
"pix_fmt" ||
640 name ==
"pixel_format";
644 AVFormatContext *output_context) {
650 int result = AVERROR_OPTION_NOT_FOUND;
651 if (context->priv_data) {
652 result = av_opt_set(context->priv_data, option.name.c_str(), option.value.c_str(), 0);
654 if (result == AVERROR_OPTION_NOT_FOUND) {
655 result = av_opt_set(context, option.name.c_str(), option.value.c_str(), 0);
657 if (result == AVERROR_OPTION_NOT_FOUND && output_context->priv_data) {
658 result = av_opt_set(output_context->priv_data, option.name.c_str(), option.value.c_str(), 0);
661 char error_text[AV_ERROR_MAX_STRING_SIZE] = {};
662 av_strerror(result, error_text,
sizeof(error_text));
663 std::cerr <<
"MXWrite: FFmpeg option '-" << option.name <<
" " << option.value
664 <<
"' was rejected: " << error_text <<
".\n";
672 if (!
object || value.empty()) {
675 const AVOption *option = av_opt_find(
object, name,
nullptr, 0, 0);
680 const bool textual = base_type == AV_OPT_TYPE_STRING ||
681 base_type == AV_OPT_TYPE_DICT ||
682 base_type == AV_OPT_TYPE_BINARY;
683 const bool numeric_text = std::isdigit(
static_cast<unsigned char>(value.front())) ||
684 value.front() ==
'-' || value.front() ==
'+' ||
685 value.front() ==
'.';
686 if (!textual && !numeric_text) {
687 const AVOption *named_value = av_opt_find(
object, value.c_str(), option->unit, 0, 0);
688 if (!named_value || named_value->type != AV_OPT_TYPE_CONST) {
692 return av_opt_set(
object, name, value.c_str(), 0) >= 0;
696 return tune ==
"hq" || tune ==
"uhq" || tune ==
"ll" || tune ==
"ull" ||
719 const std::vector<FfmpegOption> &options,
bool use_hevc_codec) {
720 std::vector<FfmpegOption> translated;
721 bool enable_lossless =
false;
725 if (value.
name ==
"preset") {
727 }
else if (value.
name ==
"tune") {
729 if (tune ==
"lossless") {
730 enable_lossless =
true;
733 if (tune ==
"hq" || tune ==
"uhq") {
736 if (tune ==
"ll" || tune ==
"ull") {
737 value.
value =
"zerolatency";
740 value.
value = use_hevc_codec ?
"main444-8" :
"high444";
741 }
else if (value.
name ==
"rgb_mode" || value.
name ==
"rc") {
743 }
else if (value.
name ==
"cq") {
746 translated.push_back(std::move(value));
749 if (enable_lossless) {
750 if (use_hevc_codec) {
751 bool appended =
false;
753 if (option.name ==
"x265-params") {
754 if (!option.value.empty())
756 option.value +=
"lossless=1";
761 translated.push_back({
"x265-params",
"lossless=1"});
764 translated.push_back({
"qp",
"0"});
771 std::vector<AVPixelFormat> formats;
776#if LIBAVCODEC_VERSION_MAJOR >= 61
777 const void *configurations =
nullptr;
778 int configuration_count = 0;
779 if (avcodec_get_supported_config(
nullptr, codec, AV_CODEC_CONFIG_PIX_FORMAT, 0,
780 &configurations, &configuration_count) >= 0 &&
782 const auto *pixel_formats =
static_cast<const AVPixelFormat *
>(configurations);
783 formats.assign(pixel_formats, pixel_formats + configuration_count);
786 if (codec->pix_fmts) {
787 for (
const AVPixelFormat *format = codec->pix_fmts;
788 *format != AV_PIX_FMT_NONE; ++format) {
789 formats.push_back(*format);
797 const AVPixFmtDescriptor *descriptor = av_pix_fmt_desc_get(format);
798 return descriptor && (descriptor->flags & AV_PIX_FMT_FLAG_HWACCEL) != 0;
802 AVPixelFormat requested_format) {
804 auto is_usable = [](AVPixelFormat format) {
808 if (requested_format != AV_PIX_FMT_NONE) {
809 if (!is_usable(requested_format)) {
810 return AV_PIX_FMT_NONE;
812 if (formats.empty() ||
813 std::find(formats.begin(), formats.end(), requested_format) != formats.end()) {
814 return requested_format;
816 return AV_PIX_FMT_NONE;
819 static constexpr AVPixelFormat preferred_formats[] = {
820 AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12, AV_PIX_FMT_YUV422P,
821 AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV422P10LE,
822 AV_PIX_FMT_YUV444P10LE, AV_PIX_FMT_BGRA, AV_PIX_FMT_RGBA};
823 if (formats.empty()) {
824 return AV_PIX_FMT_YUV420P;
826 for (AVPixelFormat preferred : preferred_formats) {
827 if (std::find(formats.begin(), formats.end(), preferred) != formats.end() &&
828 is_usable(preferred)) {
832 for (AVPixelFormat format : formats) {
833 if (is_usable(format)) {
837 return AV_PIX_FMT_NONE;
844 if ((codec->capabilities & (AV_CODEC_CAP_HARDWARE | AV_CODEC_CAP_HYBRID)) != 0) {
847 for (
int index = 0; avcodec_get_hw_config(codec, index); ++index) {
850 const std::string name = codec->name ?
lowercase_ascii(codec->name) : std::string{};
851 static constexpr std::string_view hardware_markers[] = {
852 "_nvenc",
"_qsv",
"_vaapi",
"_amf",
"_v4l2m2m",
"_videotoolbox",
853 "_mediafoundation",
"_vulkan"};
854 return std::any_of(std::begin(hardware_markers), std::end(hardware_markers),
855 [&name](std::string_view marker) {
856 return name.find(marker) != std::string::npos;
863 case AV_OPT_TYPE_FLAGS:
865 case AV_OPT_TYPE_INT:
867 case AV_OPT_TYPE_INT64:
869 case AV_OPT_TYPE_DOUBLE:
871 case AV_OPT_TYPE_FLOAT:
873 case AV_OPT_TYPE_STRING:
875 case AV_OPT_TYPE_RATIONAL:
877 case AV_OPT_TYPE_BINARY:
879 case AV_OPT_TYPE_DICT:
881 case AV_OPT_TYPE_UINT64:
883 case AV_OPT_TYPE_IMAGE_SIZE:
885 case AV_OPT_TYPE_PIXEL_FMT:
886 return "pixel-format";
887 case AV_OPT_TYPE_SAMPLE_FMT:
888 return "sample-format";
889 case AV_OPT_TYPE_VIDEO_RATE:
891 case AV_OPT_TYPE_DURATION:
893 case AV_OPT_TYPE_COLOR:
895 case AV_OPT_TYPE_BOOL:
897#if LIBAVUTIL_VERSION_MAJOR >= 57
898 case AV_OPT_TYPE_CHLAYOUT:
900 case AV_OPT_TYPE_CHANNEL_LAYOUT:
902 return "channel-layout";
903#if LIBAVUTIL_VERSION_MAJOR >= 60
904 case AV_OPT_TYPE_UINT:
914 return base_type == AV_OPT_TYPE_FLAGS || base_type == AV_OPT_TYPE_INT ||
915 base_type == AV_OPT_TYPE_INT64 || base_type == AV_OPT_TYPE_DOUBLE ||
916 base_type == AV_OPT_TYPE_FLOAT || base_type == AV_OPT_TYPE_UINT64 ||
917 base_type == AV_OPT_TYPE_BOOL
918#if LIBAVUTIL_VERSION_MAJOR >= 60
919 || base_type == AV_OPT_TYPE_UINT
925 std::ostringstream stream;
926 stream << std::setprecision(12) << value;
931 std::set<std::string> &seen) {
935 for (
const AVOption *option =
nullptr; (option = av_opt_next(
object, option));) {
936 if (option->type == AV_OPT_TYPE_CONST ||
937 (option->flags & AV_OPT_FLAG_ENCODING_PARAM) == 0 ||
938 (option->flags & AV_OPT_FLAG_VIDEO_PARAM) == 0 ||
939 (option->flags & (AV_OPT_FLAG_READONLY | AV_OPT_FLAG_DEPRECATED)) != 0 ||
940 !option->name || !seen.insert(option->name).second) {
945 info.
name = option->name;
947 info.
help = option->help ? option->help :
"";
953 uint8_t *value =
nullptr;
954 if (av_opt_get(
object, option->name, 0, &value) >= 0 && value) {
960 std::vector<std::string> choices;
961 for (
const AVOption *choice =
nullptr; (choice = av_opt_next(
object, choice));) {
962 if (choice->type == AV_OPT_TYPE_CONST && choice->unit && choice->name &&
963 std::string_view(choice->unit) == option->unit) {
964 choices.emplace_back(choice->name);
967 for (
size_t index = 0; index < choices.size(); ++index) {
971 info.
choices += choices[index];
974 result.push_back(std::move(info));
981 std::vector<EncoderInfo> result;
982 void *iterator =
nullptr;
983 while (
const AVCodec *codec = av_codec_iterate(&iterator)) {
984 if (!av_codec_is_encoder(codec) || codec->type != AVMEDIA_TYPE_VIDEO || !codec->name) {
988 info.
name = codec->name;
989 info.
long_name = codec->long_name ? codec->long_name : codec->name;
990 info.
codec_name = avcodec_get_name(codec->id);
992 info.
experimental = (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) != 0;
994 for (AVPixelFormat format : formats) {
995 const char *name = av_get_pix_fmt_name(format);
1004 result.push_back(std::move(info));
1007 if (left.codec_name != right.codec_name) {
1008 return left.codec_name < right.codec_name;
1011 return left.hardware > right.hardware;
1019 const std::string name(encoder_name);
1020 const AVCodec *codec = avcodec_find_encoder_by_name(name.c_str());
1021 if (!codec || codec->type != AVMEDIA_TYPE_VIDEO) {
1024 AVCodecContext *context = avcodec_alloc_context3(codec);
1028 std::vector<EncoderOptionInfo> result;
1029 std::set<std::string> seen;
1032 avcodec_free_context(&context);
1034 return left.name < right.name;
1039bool Writer::open(
const std::string &filename,
int w,
int h,
float fps,
const char *crf) {
1040 std::lock_guard<std::mutex> lock(writer_mutex);
1044 opts.
crf = std::stoi(crf);
1049 opts.
preset =
"ultrafast";
1050 opts.
tune =
"zerolatency";
1052 return openInternal(filename, w, h, fps, opts,
false);
1056 std::lock_guard<std::mutex> lock(writer_mutex);
1057 return openInternal(filename, w, h, fps, opts,
false);
1060bool Writer::open_ts(
const std::string &filename,
int w,
int h,
float fps,
const char *crf) {
1061 std::lock_guard<std::mutex> lock(writer_mutex);
1065 opts.
crf = std::stoi(crf);
1069 opts.
preset =
"ultrafast";
1070 opts.
tune =
"zerolatency";
1072 return openInternal(filename, w, h, fps, opts,
true);
1076 std::lock_guard<std::mutex> lock(writer_mutex);
1077 return openInternal(filename, w, h, fps, opts,
true);
1080bool Writer::initHardwareEncoding(
const AVCodec *codec, AVPixelFormat requested_format,
1081 bool prefer_cuda_rgba) {
1082 const AVCodecHWConfig *hardware_config =
nullptr;
1083 for (
int index = 0;
const AVCodecHWConfig *config = avcodec_get_hw_config(codec, index);
1085 if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) == 0 ||
1086 config->device_type == AV_HWDEVICE_TYPE_NONE || config->pix_fmt == AV_PIX_FMT_NONE) {
1089 if (!hardware_config ||
1090 (prefer_cuda_rgba && config->device_type == AV_HWDEVICE_TYPE_CUDA)) {
1091 hardware_config = config;
1093 if (prefer_cuda_rgba && config->device_type == AV_HWDEVICE_TYPE_CUDA) {
1097 if (!hardware_config ||
1098 av_hwdevice_ctx_create(&hw_device_ctx, hardware_config->device_type,
1099 nullptr,
nullptr, 0) < 0) {
1103 codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
1104 codec_ctx->pix_fmt = hardware_config->pix_fmt;
1106 hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx);
1107 if (!hw_frames_ctx) {
1111 auto *frames_ctx =
reinterpret_cast<AVHWFramesContext *
>(hw_frames_ctx->data);
1112 frames_ctx->format = hardware_config->pix_fmt;
1114 AVHWFramesConstraints *constraints =
1115 av_hwdevice_get_hwframe_constraints(hw_device_ctx,
nullptr);
1116 auto valid_software_format = [constraints](AVPixelFormat format) {
1118 !sws_isSupportedOutput(format)) {
1121 if (!constraints || !constraints->valid_sw_formats) {
1124 for (
const AVPixelFormat *valid = constraints->valid_sw_formats;
1125 *valid != AV_PIX_FMT_NONE; ++valid) {
1126 if (*valid == format) {
1133 AVPixelFormat upload_format = AV_PIX_FMT_NONE;
1134 if (prefer_cuda_rgba && valid_software_format(AV_PIX_FMT_RGBA)) {
1135 upload_format = AV_PIX_FMT_RGBA;
1136 }
else if (valid_software_format(requested_format)) {
1137 upload_format = requested_format;
1139 static constexpr AVPixelFormat preferred_upload_formats[] = {
1140 AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, AV_PIX_FMT_P010LE,
1141 AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV422P, AV_PIX_FMT_BGRA,
1143 for (AVPixelFormat format : preferred_upload_formats) {
1144 if (valid_software_format(format)) {
1145 upload_format = format;
1149 if (upload_format == AV_PIX_FMT_NONE && constraints &&
1150 constraints->valid_sw_formats) {
1151 for (
const AVPixelFormat *valid = constraints->valid_sw_formats;
1152 *valid != AV_PIX_FMT_NONE; ++valid) {
1153 if (valid_software_format(*valid)) {
1154 upload_format = *valid;
1160 av_hwframe_constraints_free(&constraints);
1161 if (upload_format == AV_PIX_FMT_NONE) {
1165 frames_ctx->sw_format = upload_format;
1166 frames_ctx->width = width;
1167 frames_ctx->height = height;
1171 frames_ctx->initial_pool_size =
static_cast<int>(MAX_QUEUE_SIZE) + 8;
1173 if (av_hwframe_ctx_init(hw_frames_ctx) < 0) {
1177 codec_ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ctx);
1178 codec_ctx->sw_pix_fmt = upload_format;
1180 upload_sw_frame = av_frame_alloc();
1181 if (!upload_sw_frame) {
1184 upload_sw_frame->format = upload_format;
1185 upload_sw_frame->width = width;
1186 upload_sw_frame->height = height;
1187 if (av_frame_get_buffer(upload_sw_frame, 32) < 0) {
1191 if (upload_format != AV_PIX_FMT_RGBA) {
1192 sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA, width, height,
1193 upload_format, SWS_FAST_BILINEAR,
nullptr,
1200 direct_cuda_upload = hardware_config->device_type == AV_HWDEVICE_TYPE_CUDA &&
1201 upload_format == AV_PIX_FMT_RGBA;
1203#ifdef MXWRITE_HAS_CUDA_COPY
1206 if (direct_cuda_upload && !cuda_upload_stream) {
1207 if (cudaStreamCreateWithFlags(&cuda_upload_stream, cudaStreamNonBlocking) != cudaSuccess) {
1208 cuda_upload_stream =
nullptr;
1216bool Writer::openInternal(
const std::string &filename,
int w,
int h,
float fps,
const EncodeOptions &opts,
bool ts_mode) {
1217 avformat_network_init();
1218 av_log_set_level(AV_LOG_ERROR);
1220 active_encoder_hardware =
false;
1221 stop_requested =
false;
1223 last_duration = 0.0;
1224 bytes_written.store(0, std::memory_order_relaxed);
1225 block_when_full.store(opts.
block_when_full, std::memory_order_relaxed);
1227 while (!encode_queue.empty()) {
1228 releaseFrame(encode_queue.front());
1232 std::vector<FfmpegOption> extra_options;
1239 if (!codec_override) {
1242 if (!codec_override) {
1246 const std::string *pixel_format_option =
find_ffmpeg_option(extra_options,
"pix_fmt");
1247 if (!pixel_format_option) {
1250 AVPixelFormat requested_pixel_format = AV_PIX_FMT_NONE;
1251 if (pixel_format_option) {
1252 requested_pixel_format = av_get_pix_fmt(pixel_format_option->c_str());
1253 if (requested_pixel_format == AV_PIX_FMT_NONE) {
1254 std::cerr <<
"MXWrite: unknown pixel format '" << *pixel_format_option <<
"'.\n";
1261 if (avformat_alloc_output_context2(&format_ctx,
nullptr,
nullptr, filename.c_str()) < 0) {
1262 std::cerr <<
"Could not allocate output context.\n";
1269 hdr_info = opts.
hdr;
1276 const AVCodec *hdr_codec = avcodec_find_encoder_by_name(
"libx265");
1278 std::cerr <<
"MXWrite: HDR output requested but libx265 encoder not available.\n";
1279 avformat_free_context(format_ctx);
1280 format_ctx =
nullptr;
1284 stream = avformat_new_stream(format_ctx, hdr_codec);
1286 std::cerr <<
"MXWrite: could not create HDR stream.\n";
1287 avformat_free_context(format_ctx);
1288 format_ctx =
nullptr;
1292 calculateFPSFraction(fps, fps_num, fps_den);
1293 AVRational tb_hdr = {fps_den, fps_num};
1294 stream->time_base = tb_hdr;
1296 codec_ctx = avcodec_alloc_context3(hdr_codec);
1298 std::cerr <<
"MXWrite: could not allocate HDR codec context.\n";
1299 avformat_free_context(format_ctx);
1300 format_ctx =
nullptr;
1304 codec_ctx->width = width;
1305 codec_ctx->height = height;
1306 codec_ctx->time_base = stream->time_base;
1307 codec_ctx->framerate = AVRational{fps_num, fps_den};
1308 codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P10LE;
1309 codec_ctx->profile = AV_PROFILE_HEVC_MAIN_10;
1310 codec_ctx->bits_per_raw_sample = 10;
1311 codec_ctx->gop_size = 30;
1312 codec_ctx->max_b_frames = 0;
1313 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
1314 codec_ctx->thread_type = FF_THREAD_SLICE;
1315 codec_ctx->delay = 0;
1318 codec_ctx->color_primaries =
static_cast<AVColorPrimaries
>(
1319 hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
1320 codec_ctx->color_trc =
static_cast<AVColorTransferCharacteristic
>(
1321 hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
1322 codec_ctx->colorspace =
static_cast<AVColorSpace
>(
1323 hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
1324 codec_ctx->color_range =
static_cast<AVColorRange
>(
1325 hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
1326 codec_ctx->chroma_sample_location = AVCHROMA_LOC_LEFT;
1329 std::string preset_hdr = opts.
preset.empty() ? std::string(
"medium") : opts.preset;
1330 av_opt_set(codec_ctx->priv_data,
"preset", preset_hdr.c_str(), 0);
1331 int crf_val_hdr = opts.
crf;
1332 if (crf_val_hdr < 0)
1334 if (crf_val_hdr > 51)
1336 const std::string crf_hdr = std::to_string(crf_val_hdr);
1337 av_opt_set(codec_ctx->priv_data,
"crf", crf_hdr.c_str(), 0);
1341 std::string x265_params =
"profile=main10:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:range=limited:repeat-headers=1";
1343 if (codec_ctx->color_trc == AVCOL_TRC_ARIB_STD_B67) {
1344 x265_params =
"profile=main10:colorprim=bt2020:transfer=arib-std-b67:colormatrix=bt2020nc:range=limited:repeat-headers=1";
1346 av_opt_set(codec_ctx->priv_data,
"x265-params", x265_params.c_str(), 0);
1348 if (pixel_format_option && requested_pixel_format != AV_PIX_FMT_YUV420P10LE) {
1349 std::cerr <<
"MXWrite: HDR output forces yuv420p10le; ignoring requested pixel format '"
1350 << *pixel_format_option <<
"'.\n";
1353 avcodec_free_context(&codec_ctx);
1354 avformat_free_context(format_ctx);
1355 format_ctx =
nullptr;
1360 if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
1361 codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
1363 if (avcodec_open2(codec_ctx, hdr_codec,
nullptr) < 0) {
1364 std::cerr <<
"MXWrite: could not open libx265 for HDR output.\n";
1365 avcodec_free_context(&codec_ctx);
1366 avformat_free_context(format_ctx);
1367 format_ctx =
nullptr;
1370 if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
1371 std::cerr <<
"MXWrite: could not copy HDR codec parameters.\n";
1372 avcodec_free_context(&codec_ctx);
1373 avformat_free_context(format_ctx);
1374 format_ctx =
nullptr;
1381 auto attach_side = [&](AVPacketSideDataType type,
1382 const std::vector<uint8_t> &payload) {
1383 if (payload.empty())
1385 uint8_t *buf =
static_cast<uint8_t *
>(av_malloc(payload.size()));
1388 std::memcpy(buf, payload.data(), payload.size());
1389 const AVPacketSideData *added = av_packet_side_data_add(
1390 &stream->codecpar->coded_side_data,
1391 &stream->codecpar->nb_coded_side_data,
1398 std::cerr <<
"MXWrite: failed to attach HDR side data (type " << (int)type <<
").\n";
1401 attach_side(AV_PKT_DATA_MASTERING_DISPLAY_METADATA, hdr_info.mastering_display);
1402 attach_side(AV_PKT_DATA_CONTENT_LIGHT_LEVEL, hdr_info.content_light);
1404 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
1405 if (avio_open(&format_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE) < 0) {
1406 std::cerr <<
"MXWrite: could not open HDR output file: " << filename <<
"\n";
1407 avcodec_free_context(&codec_ctx);
1408 avformat_free_context(format_ctx);
1409 format_ctx =
nullptr;
1413 if (avformat_write_header(format_ctx,
nullptr) < 0) {
1414 std::cerr <<
"MXWrite: error writing HDR MP4 header.\n";
1415 avio_closep(&format_ctx->pb);
1416 avcodec_free_context(&codec_ctx);
1417 avformat_free_context(format_ctx);
1418 format_ctx =
nullptr;
1421 updateBytesWritten();
1424 frame10 = av_frame_alloc();
1426 std::cerr <<
"MXWrite: could not allocate YUV420P10LE frame.\n";
1427 avio_closep(&format_ctx->pb);
1428 avcodec_free_context(&codec_ctx);
1429 avformat_free_context(format_ctx);
1430 format_ctx =
nullptr;
1433 frame10->format = AV_PIX_FMT_YUV420P10LE;
1434 frame10->width = width;
1435 frame10->height = height;
1436 if (av_frame_get_buffer(frame10, 32) < 0) {
1437 std::cerr <<
"MXWrite: could not allocate YUV420P10LE buffer.\n";
1438 av_frame_free(&frame10);
1439 avio_closep(&format_ctx->pb);
1440 avcodec_free_context(&codec_ctx);
1441 avformat_free_context(format_ctx);
1442 format_ctx =
nullptr;
1447 use_hw_encode =
false;
1448 recordingStart = std::chrono::steady_clock::now();
1449 startEncoderThread();
1450 std::cout <<
"MXWrite: HDR output active (libx265 Main10, BT.2020, "
1451 << (codec_ctx->color_trc == AVCOL_TRC_ARIB_STD_B67 ?
"HLG" :
"PQ")
1457 const bool is_high_res = (width > 3840 || height > 2160);
1459 if (codec_pref.empty()) {
1460 codec_pref =
"auto";
1462 const bool explicit_hevc_nvenc = (codec_pref ==
"hevc_nvenc" || codec_pref ==
"h265_nvenc");
1463 const bool explicit_h264_nvenc = (codec_pref ==
"h264_nvenc");
1464 const bool explicit_hevc_software =
1465 (codec_pref ==
"hevc" || codec_pref ==
"h265" || codec_pref ==
"libx265");
1466 const bool explicit_h264_software = (codec_pref ==
"h264" || codec_pref ==
"libx264");
1467 const bool use_hevc_codec = explicit_hevc_nvenc || explicit_hevc_software ||
1468 (!explicit_h264_nvenc && !explicit_h264_software && is_high_res);
1469 std::string hw_codec_name = use_hevc_codec ?
"hevc_nvenc" :
"h264_nvenc";
1470 AVCodecID sw_codec_id = use_hevc_codec ? AV_CODEC_ID_HEVC : AV_CODEC_ID_H264;
1473 const AVCodec *codec =
nullptr;
1474 bool wants_hw =
false;
1475 if (codec_pref ==
"software" || codec_pref ==
"x264" || codec_pref ==
"cpu" ||
1476 explicit_hevc_software || explicit_h264_software) {
1477 if (codec_pref ==
"libx264" || codec_pref ==
"libx265") {
1478 codec = avcodec_find_encoder_by_name(codec_pref.c_str());
1480 codec = avcodec_find_encoder(sw_codec_id);
1483 }
else if (codec_pref ==
"auto" || codec_pref ==
"nvenc" ||
1484 explicit_hevc_nvenc || explicit_h264_nvenc) {
1487 codec = avcodec_find_encoder_by_name(hw_codec_name.c_str());
1488 wants_hw = (codec !=
nullptr);
1490 if (codec_pref ==
"nvenc" || explicit_hevc_nvenc || explicit_h264_nvenc) {
1491 std::cerr <<
"MXWrite: NVENC requested but " << hw_codec_name
1492 <<
" not available; falling back to software.\n";
1494 codec = avcodec_find_encoder(sw_codec_id);
1501 codec = avcodec_find_encoder_by_name(codec_pref.c_str());
1502 wants_hw = codec && (codec_pref.ends_with(
"_nvenc"));
1504 hw_codec_name = codec_pref;
1505 sw_codec_id = codec->id;
1510 std::cerr <<
"MXWrite: could not find requested video encoder '"
1511 << codec_pref <<
"'. Use acmx2 --list-encoders to see this FFmpeg build.\n";
1512 avformat_free_context(format_ctx);
1513 format_ctx =
nullptr;
1518 std::string preset = opts.
preset.empty() ? std::string(
"medium") : opts.preset;
1522 int crf_val = opts.
crf;
1527 const std::string crf_str = std::to_string(crf_val);
1529 stream = avformat_new_stream(format_ctx, codec);
1531 std::cerr <<
"Could not create new stream.\n";
1532 avformat_free_context(format_ctx);
1533 format_ctx =
nullptr;
1537 calculateFPSFraction(fps, fps_num, fps_den);
1539 AVRational tb = {fps_den, fps_num};
1540 stream->time_base = tb;
1542 codec_ctx = avcodec_alloc_context3(codec);
1544 std::cerr <<
"Could not allocate codec context.\n";
1545 avformat_free_context(format_ctx);
1546 format_ctx =
nullptr;
1550 codec_ctx->width = width;
1551 codec_ctx->height = height;
1552 codec_ctx->time_base = stream->time_base;
1553 codec_ctx->framerate = AVRational{fps_num, fps_den};
1555 wants_hw ? avcodec_find_encoder(sw_codec_id) : codec, requested_pixel_format);
1556 const bool requires_hardware_frames = !wants_hw &&
1557 software_pixel_format == AV_PIX_FMT_NONE &&
1559 if (software_pixel_format == AV_PIX_FMT_NONE && !requires_hardware_frames) {
1560 const char *requested_name = requested_pixel_format == AV_PIX_FMT_NONE
1561 ?
"an automatic system-memory format"
1562 : av_get_pix_fmt_name(requested_pixel_format);
1563 std::cerr <<
"MXWrite: encoder '" << codec->name <<
"' does not accept "
1564 << (requested_name ? requested_name :
"the requested pixel format")
1565 <<
" that MXWrite can convert from RGBA. Choose a supported -pix_fmt; "
1566 "hardware-frame-only encoders require a device-specific upload path.\n";
1567 avcodec_free_context(&codec_ctx);
1568 avformat_free_context(format_ctx);
1569 format_ctx =
nullptr;
1572 codec_ctx->pix_fmt = wants_hw ? AV_PIX_FMT_CUDA : software_pixel_format;
1573 codec_ctx->gop_size = 30;
1574 codec_ctx->max_b_frames = 0;
1575 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
1579 codec_ctx->thread_type = FF_THREAD_SLICE;
1580 codec_ctx->slices = 4;
1582 codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
1584 codec_ctx->delay = 0;
1587 codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
1590 bool extra_options_applied =
false;
1591 bool fell_back_from_hardware =
false;
1592 if (requires_hardware_frames) {
1594 if (!opts.
tune.empty() && opts.
tune !=
"none") {
1599 avcodec_free_context(&codec_ctx);
1600 avformat_free_context(format_ctx);
1601 format_ctx =
nullptr;
1604 extra_options_applied =
true;
1605 if (!initHardwareEncoding(codec, requested_pixel_format,
false)) {
1606 std::cerr <<
"MXWrite: encoder '" << codec->name
1607 <<
"' requires hardware frames, but its FFmpeg hardware device "
1608 "could not be initialized.\n";
1609 av_buffer_unref(&hw_frames_ctx);
1610 av_buffer_unref(&hw_device_ctx);
1611 av_frame_free(&upload_sw_frame);
1612 sws_freeContext(sws_ctx);
1614 avcodec_free_context(&codec_ctx);
1615 avformat_free_context(format_ctx);
1616 format_ctx =
nullptr;
1619 use_hw_encode =
true;
1620 std::cout <<
"MXWrite: hardware encoder selected (" << codec->name <<
")\n";
1624 av_opt_set(codec_ctx->priv_data,
"preset", nv_preset, 0);
1627 const std::string nv_tune =
1629 : (
is_nvenc_tune(requested_tune) ? requested_tune : std::string(
"hq"));
1630 av_opt_set(codec_ctx->priv_data,
"tune", nv_tune.c_str(), 0);
1632 const bool lossless_tune = nv_tune ==
"lossless" ||
1634 if (!lossless_tune) {
1635 av_opt_set(codec_ctx->priv_data,
"rc",
"vbr", 0);
1636 av_opt_set(codec_ctx->priv_data,
"cq", crf_str.c_str(), 0);
1639 av_opt_set(codec_ctx->priv_data,
"zerolatency",
"1", 0);
1641 if (use_hevc_codec) {
1642 av_opt_set(codec_ctx->priv_data,
"tier",
"high", 0);
1645 if (requested_pixel_format == AV_PIX_FMT_YUV444P) {
1646 if (av_opt_set(codec_ctx->priv_data,
"rgb_mode",
"yuv444", 0) < 0) {
1647 std::cerr <<
"MXWrite: this NVENC build cannot convert RGBA input to yuv444p.\n";
1648 avcodec_free_context(&codec_ctx);
1649 avformat_free_context(format_ctx);
1650 format_ctx =
nullptr;
1654 const char *profile = use_hevc_codec ?
"rext" :
"high444p";
1655 av_opt_set(codec_ctx->priv_data,
"profile", profile, 0);
1657 }
else if (requested_pixel_format != AV_PIX_FMT_NONE &&
1658 requested_pixel_format != AV_PIX_FMT_YUV420P &&
1659 requested_pixel_format != AV_PIX_FMT_RGBA) {
1660 std::cerr <<
"MXWrite: NVENC RGBA ingestion supports custom -pix_fmt yuv420p, "
1661 "yuv444p, or rgba; requested '"
1662 << av_get_pix_fmt_name(requested_pixel_format) <<
"'.\n";
1663 avcodec_free_context(&codec_ctx);
1664 avformat_free_context(format_ctx);
1665 format_ctx =
nullptr;
1670 avcodec_free_context(&codec_ctx);
1671 avformat_free_context(format_ctx);
1672 format_ctx =
nullptr;
1675 extra_options_applied =
true;
1677 if (initHardwareEncoding(codec, requested_pixel_format,
true)) {
1678 use_hw_encode =
true;
1679 std::cout <<
"MXWrite: hardware encoder selected (" << hw_codec_name <<
")\n";
1681 std::cerr <<
"MXWrite: " << hw_codec_name <<
" present but CUDA context failed, falling back to software encoder\n";
1682 fell_back_from_hardware =
true;
1683 av_buffer_unref(&hw_frames_ctx);
1684 av_buffer_unref(&hw_device_ctx);
1685 av_frame_free(&upload_sw_frame);
1686 sws_freeContext(sws_ctx);
1688 direct_cuda_upload =
false;
1689 avcodec_free_context(&codec_ctx);
1691 codec = avcodec_find_encoder(sw_codec_id);
1693 std::cerr <<
"Could not find software fallback encoder.\n";
1694 avformat_free_context(format_ctx);
1695 format_ctx =
nullptr;
1699 codec_ctx = avcodec_alloc_context3(codec);
1701 std::cerr <<
"Could not allocate fallback codec context.\n";
1702 avformat_free_context(format_ctx);
1703 format_ctx =
nullptr;
1707 codec_ctx->width = width;
1708 codec_ctx->height = height;
1709 codec_ctx->time_base = stream->time_base;
1710 codec_ctx->framerate = AVRational{fps_num, fps_den};
1711 codec_ctx->pix_fmt = software_pixel_format;
1712 codec_ctx->gop_size = 30;
1713 codec_ctx->max_b_frames = 0;
1714 codec_ctx->thread_count = std::max(1u, std::thread::hardware_concurrency());
1716 codec_ctx->thread_type = FF_THREAD_SLICE;
1717 codec_ctx->slices = 4;
1719 codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
1721 codec_ctx->delay = 0;
1724 codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
1726 extra_options_applied =
false;
1730 if (!use_hw_encode) {
1731 const std::string software_preset =
1735 std::string tune = opts.
realtime ? std::string(
"zerolatency") : opts.tune;
1736 if (fell_back_from_hardware) {
1738 if (lowered_tune ==
"hq" || lowered_tune ==
"uhq") {
1740 }
else if (lowered_tune ==
"ll" || lowered_tune ==
"ull") {
1741 tune =
"zerolatency";
1742 }
else if (lowered_tune ==
"lossless") {
1744 if (use_hevc_codec) {
1745 av_opt_set(codec_ctx->priv_data,
"x265-params",
"lossless=1", 0);
1747 av_opt_set(codec_ctx->priv_data,
"qp",
"0", 0);
1751 if (!tune.empty() && tune !=
"none") {
1755 if (opts.
realtime && codec->id == AV_CODEC_ID_H264) {
1758 av_opt_set(codec_ctx->priv_data,
"x264-params",
"bframes=0:ref=1:me=dia:subme=0", 0);
1759 av_opt_set(codec_ctx->priv_data,
"force_cfr",
"1", 0);
1763 const std::vector<FfmpegOption> translated_fallback_options =
1765 : std::vector<FfmpegOption>{};
1766 const std::vector<FfmpegOption> &options_to_apply =
1767 fell_back_from_hardware ? translated_fallback_options : extra_options;
1768 if (!extra_options_applied &&
1770 avcodec_free_context(&codec_ctx);
1771 avformat_free_context(format_ctx);
1772 format_ctx =
nullptr;
1778 if (format_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
1779 codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
1781 if (avcodec_open2(codec_ctx, codec,
nullptr) < 0) {
1782 std::cerr <<
"MXWrite: could not open encoder '" << codec->name
1783 <<
"'. The encoder may require unavailable hardware or options.\n";
1784 avcodec_free_context(&codec_ctx);
1785 avformat_free_context(format_ctx);
1786 format_ctx =
nullptr;
1790 if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
1791 std::cerr <<
"Could not copy codec parameters.\n";
1792 avcodec_free_context(&codec_ctx);
1793 avformat_free_context(format_ctx);
1794 format_ctx =
nullptr;
1797 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
1798 if (avio_open(&format_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE) < 0) {
1799 std::cerr <<
"Could not open output file: " << filename <<
"\n";
1800 avcodec_free_context(&codec_ctx);
1801 avformat_free_context(format_ctx);
1802 format_ctx =
nullptr;
1806 if (avformat_write_header(format_ctx,
nullptr) < 0) {
1807 std::cerr <<
"Error writing MP4 header.\n";
1808 avio_closep(&format_ctx->pb);
1809 avcodec_free_context(&codec_ctx);
1810 avformat_free_context(format_ctx);
1811 format_ctx =
nullptr;
1814 updateBytesWritten();
1816 if (!use_hw_encode) {
1817 frameYUV = av_frame_alloc();
1819 std::cerr <<
"Could not allocate YUV frame.\n";
1820 avio_closep(&format_ctx->pb);
1821 avcodec_free_context(&codec_ctx);
1822 avformat_free_context(format_ctx);
1823 format_ctx =
nullptr;
1826 frameYUV->format = software_pixel_format;
1827 frameYUV->width = width;
1828 frameYUV->height = height;
1829 if (av_frame_get_buffer(frameYUV, 32) < 0) {
1830 std::cerr <<
"Could not allocate frame buffer for YUV frame.\n";
1831 av_frame_free(&frameYUV);
1832 avio_closep(&format_ctx->pb);
1833 avcodec_free_context(&codec_ctx);
1834 avformat_free_context(format_ctx);
1835 format_ctx =
nullptr;
1839 sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA, width, height,
1840 software_pixel_format, SWS_FAST_BILINEAR,
nullptr,
1843 std::cerr <<
"Could not initialize conversion context.\n";
1844 av_frame_free(&frameYUV);
1845 avio_closep(&format_ctx->pb);
1846 avcodec_free_context(&codec_ctx);
1847 avformat_free_context(format_ctx);
1848 format_ctx =
nullptr;
1854 recordingStart = std::chrono::steady_clock::now();
1855 startEncoderThread();
1869 std::lock_guard<std::mutex> lock(writer_mutex);
1875 AVFrame *queued_frame = av_frame_alloc();
1876 if (!queued_frame) {
1877 std::cerr <<
"Writer: failed to allocate queued frame\n";
1881 if (use_hw_encode && hdr_output) {
1882 queued_frame->format = codec_ctx->pix_fmt;
1883 queued_frame->width = width;
1884 queued_frame->height = height;
1885 if (av_hwframe_get_buffer(hw_frames_ctx, queued_frame, 0) < 0) {
1886 std::cerr <<
"Writer: failed to allocate frame from hardware pool\n";
1887 releaseFrame(queued_frame);
1891 if (av_frame_make_writable(upload_sw_frame) < 0) {
1892 std::cerr <<
"Writer: software upload frame not writable\n";
1893 releaseFrame(queued_frame);
1897 const auto *src =
static_cast<const uint8_t *
>(rgba_buffer);
1898 if (upload_sw_frame->format == AV_PIX_FMT_RGBA) {
1899 for (
int y = 0; y < height; ++y) {
1900 std::memcpy(upload_sw_frame->data[0] +
1901 static_cast<size_t>(y) * upload_sw_frame->linesize[0],
1902 src +
static_cast<size_t>(y) *
static_cast<size_t>(width) * 4,
1903 static_cast<size_t>(width) * 4);
1906 const uint8_t *source_data[1] = {src};
1907 const int source_linesize[1] = {width * 4};
1908 sws_scale(sws_ctx, source_data, source_linesize, 0, height,
1909 upload_sw_frame->data, upload_sw_frame->linesize);
1912 if (av_hwframe_transfer_data(queued_frame, upload_sw_frame, 0) < 0) {
1913 std::cerr <<
"Writer: failed to transfer system frame to hardware frame\n";
1914 releaseFrame(queued_frame);
1918 queued_frame->format = AV_PIX_FMT_RGBA;
1919 queued_frame->width = width;
1920 queued_frame->height = height;
1921 if (av_frame_get_buffer(queued_frame, 32) < 0) {
1922 std::cerr <<
"Writer: failed to allocate queued RGBA frame buffer\n";
1923 releaseFrame(queued_frame);
1926 if (av_frame_make_writable(queued_frame) < 0) {
1927 std::cerr <<
"Writer: queued RGBA frame not writable\n";
1928 releaseFrame(queued_frame);
1932 const auto *src =
static_cast<const uint8_t *
>(rgba_buffer);
1933 for (
int y = 0; y < height; ++y) {
1934 std::memcpy(queued_frame->data[0] +
static_cast<size_t>(y) * queued_frame->linesize[0],
1935 src +
static_cast<size_t>(y) *
static_cast<size_t>(width) * 4,
1936 static_cast<size_t>(width) * 4);
1941 std::unique_lock<std::mutex> lock(queue_mutex);
1942 if (block_when_full.load(std::memory_order_relaxed)) {
1943 if (encode_queue.size() >= NO_DROP_QUEUE_SIZE) {
1947 queue_cv.wait(lock, [
this] {
1948 return stop_requested || encode_queue.size() < NO_DROP_QUEUE_SIZE;
1951 if (stop_requested) {
1952 releaseFrame(queued_frame);
1955 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
1956 static int drop_counter = 0;
1957 if (++drop_counter % 30 == 0) {
1958 std::cerr <<
"Writer: dropped " << drop_counter <<
" SDR frames (encoder queue full)\n";
1960 releaseFrame(queued_frame);
1963 if (pts != AV_NOPTS_VALUE && pts < frame_count) {
1967 releaseFrame(queued_frame);
1970 queued_frame->pts = pts == AV_NOPTS_VALUE ? frame_count : pts;
1971 frame_count = queued_frame->pts + 1;
1972 encode_queue.push(queued_frame);
1975 queue_cv.notify_one();
1983 if (!rgba16_buffer) {
1988 std::lock_guard<std::mutex> lock(writer_mutex);
1993 std::cerr <<
"Writer: write_hdr_rgba16 called but writer not in HDR mode\n";
1998 AVFrame *queued_frame = av_frame_alloc();
1999 if (!queued_frame) {
2000 std::cerr <<
"Writer: failed to allocate queued HDR frame\n";
2003 queued_frame->format = AV_PIX_FMT_YUV420P10LE;
2004 queued_frame->width = width;
2005 queued_frame->height = height;
2006 if (av_frame_get_buffer(queued_frame, 32) < 0) {
2007 std::cerr <<
"Writer: failed to allocate YUV420P10 buffer\n";
2008 releaseFrame(queued_frame);
2011 if (av_frame_make_writable(queued_frame) < 0) {
2012 std::cerr <<
"Writer: queued HDR frame not writable\n";
2013 releaseFrame(queued_frame);
2019 convertBt2020Rgba16EncodedToYuv420p10(
2020 reinterpret_cast<const uint16_t *
>(rgba16_buffer),
2022 reinterpret_cast<uint16_t *
>(queued_frame->data[0]),
2023 queued_frame->linesize[0] / 2,
2024 reinterpret_cast<uint16_t *
>(queued_frame->data[1]),
2025 queued_frame->linesize[1] / 2,
2026 reinterpret_cast<uint16_t *
>(queued_frame->data[2]),
2027 queued_frame->linesize[2] / 2,
2031 queued_frame->color_primaries =
static_cast<AVColorPrimaries
>(
2032 hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
2033 queued_frame->color_trc =
static_cast<AVColorTransferCharacteristic
>(
2034 hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
2035 queued_frame->colorspace =
static_cast<AVColorSpace
>(
2036 hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
2037 queued_frame->color_range =
static_cast<AVColorRange
>(
2038 hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
2041 std::unique_lock<std::mutex> lock(queue_mutex);
2042 if (block_when_full.load(std::memory_order_relaxed)) {
2043 if (encode_queue.size() >= NO_DROP_QUEUE_SIZE) {
2044 queue_cv.wait(lock, [
this] {
2045 return stop_requested || encode_queue.size() < NO_DROP_QUEUE_SIZE;
2048 if (stop_requested) {
2049 releaseFrame(queued_frame);
2052 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
2053 static int drop_counter = 0;
2054 if (++drop_counter % 30 == 0) {
2055 std::cerr <<
"Writer: dropped " << drop_counter <<
" HDR frames (encoder queue full)\n";
2057 releaseFrame(queued_frame);
2060 if (pts != AV_NOPTS_VALUE && pts < frame_count) {
2061 releaseFrame(queued_frame);
2064 queued_frame->pts = pts == AV_NOPTS_VALUE ? frame_count : pts;
2065 frame_count = queued_frame->pts + 1;
2066 encode_queue.push(queued_frame);
2069 queue_cv.notify_one();
2079 [[maybe_unused]]
bool bottom_up) {
2080 if (!cuda_rgba_buffer || src_stride <= 0) {
2085 std::lock_guard<std::mutex> lock(writer_mutex);
2086 if (!opened || !use_hw_encode || !direct_cuda_upload) {
2091 AVFrame *queued_frame = av_frame_alloc();
2092 if (!queued_frame) {
2093 std::cerr <<
"Writer: failed to allocate queued CUDA frame\n";
2097 queued_frame->format = AV_PIX_FMT_CUDA;
2098 queued_frame->width = width;
2099 queued_frame->height = height;
2101 if (av_hwframe_get_buffer(hw_frames_ctx, queued_frame, 0) < 0) {
2102 std::cerr <<
"Writer: failed to allocate CUDA frame from hardware pool\n";
2103 releaseFrame(queued_frame);
2107#ifdef MXWRITE_HAS_CUDA_COPY
2108 cudaStream_t stream = cuda_upload_stream;
2109 const bool use_async_stream = (stream !=
nullptr);
2111 const auto copy_err = use_async_stream
2112 ? cudaMemcpy2DAsync(
2113 queued_frame->data[0],
2114 static_cast<size_t>(queued_frame->linesize[0]),
2116 static_cast<size_t>(src_stride),
2117 static_cast<size_t>(width) * 4,
2118 static_cast<size_t>(height),
2119 cudaMemcpyDeviceToDevice,
2122 queued_frame->data[0],
2123 static_cast<size_t>(queued_frame->linesize[0]),
2125 static_cast<size_t>(src_stride),
2126 static_cast<size_t>(width) * 4,
2127 static_cast<size_t>(height),
2128 cudaMemcpyDeviceToDevice);
2130 if (copy_err != cudaSuccess) {
2131 std::cerr <<
"Writer: cudaMemcpy2D device upload failed: " << cudaGetErrorString(copy_err) <<
"\n";
2132 releaseFrame(queued_frame);
2139 auto *src_base =
static_cast<unsigned char *
>(cuda_rgba_buffer);
2140 auto *dst_base = queued_frame->data[0];
2141 const size_t row_bytes =
static_cast<size_t>(width) * 4;
2143 for (
int y = 0; y < height; ++y) {
2144 auto *src_row = src_base +
static_cast<size_t>(height - 1 - y) *
static_cast<size_t>(src_stride);
2145 auto *dst_row = dst_base +
static_cast<size_t>(y) *
static_cast<size_t>(queued_frame->linesize[0]);
2146 const auto row_copy_err = use_async_stream
2147 ? cudaMemcpyAsync(dst_row, src_row, row_bytes, cudaMemcpyDeviceToDevice, stream)
2148 : cudaMemcpy(dst_row, src_row, row_bytes, cudaMemcpyDeviceToDevice);
2149 if (row_copy_err != cudaSuccess) {
2150 std::cerr <<
"Writer: cudaMemcpy row upload failed: " << cudaGetErrorString(row_copy_err) <<
"\n";
2151 releaseFrame(queued_frame);
2159 if (use_async_stream) {
2160 const auto sync_err = cudaStreamSynchronize(stream);
2161 if (sync_err != cudaSuccess) {
2162 std::cerr <<
"Writer: cudaStreamSynchronize failed: " << cudaGetErrorString(sync_err) <<
"\n";
2163 releaseFrame(queued_frame);
2168 std::cerr <<
"Writer: CUDA copy support disabled at build time\n";
2169 releaseFrame(queued_frame);
2174 std::unique_lock<std::mutex> lock(queue_mutex);
2175 if (block_when_full.load(std::memory_order_relaxed)) {
2176 if (encode_queue.size() >= NO_DROP_QUEUE_SIZE) {
2177 queue_cv.wait(lock, [
this] {
2178 return stop_requested || encode_queue.size() < NO_DROP_QUEUE_SIZE;
2181 if (stop_requested) {
2182 releaseFrame(queued_frame);
2185 }
else if (stop_requested || encode_queue.size() >= MAX_QUEUE_SIZE) {
2186 static int drop_counter = 0;
2187 if (++drop_counter % 30 == 0) {
2188 std::cerr <<
"Writer: dropped " << drop_counter <<
" frames (encoder queue full)\n";
2190 releaseFrame(queued_frame);
2194 queue_cv.notify_one();
2197 if (pts != AV_NOPTS_VALUE && pts < frame_count) {
2198 releaseFrame(queued_frame);
2199 queue_cv.notify_one();
2202 queued_frame->pts = pts == AV_NOPTS_VALUE ? frame_count : pts;
2203 frame_count = queued_frame->pts + 1;
2204 encode_queue.push(queued_frame);
2207 queue_cv.notify_one();
2215void Writer::startEncoderThread() {
2216 stop_requested =
false;
2217 encode_thread = std::jthread([
this](std::stop_token st) {
2222void Writer::stopEncoderThread() {
2224 std::lock_guard<std::mutex> lock(queue_mutex);
2225 stop_requested =
true;
2227 queue_cv.notify_all();
2229 if (encode_thread.joinable()) {
2230 encode_thread.request_stop();
2231 encode_thread.join();
2235void Writer::releaseFrame(AVFrame *f) {
2242void Writer::drainEncoderPackets() {
2243 AVPacket *pkt = av_packet_alloc();
2245 std::cerr <<
"Writer: failed to allocate packet\n";
2250 int ret = avcodec_receive_packet(codec_ctx, pkt);
2251 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
2255 std::cerr <<
"Writer: error receiving packet: " << ret <<
"\n";
2259 av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
2260 pkt->stream_index = stream->index;
2262 if (av_interleaved_write_frame(format_ctx, pkt) < 0) {
2263 std::cerr <<
"Writer: error writing frame\n";
2264 av_packet_unref(pkt);
2267 updateBytesWritten();
2268 av_packet_unref(pkt);
2271 av_packet_free(&pkt);
2274void Writer::encodeAndWriteFrame(AVFrame *in_frame) {
2279 AVFrame *encode_frame = in_frame;
2280 AVFrame *uploaded_frame =
nullptr;
2282 if (in_frame->format == AV_PIX_FMT_YUV420P10LE) {
2285 encode_frame = in_frame;
2289 if (av_frame_make_writable(frame10) < 0) {
2290 std::cerr <<
"Writer: HDR frame not writable\n";
2295 in_frame->linesize[0],
2296 reinterpret_cast<uint16_t *
>(frame10->data[0]),
2297 frame10->linesize[0] / 2,
2298 reinterpret_cast<uint16_t *
>(frame10->data[1]),
2299 frame10->linesize[1] / 2,
2300 reinterpret_cast<uint16_t *
>(frame10->data[2]),
2301 frame10->linesize[2] / 2,
2304 frame10->pts = in_frame->pts;
2305 frame10->color_primaries =
static_cast<AVColorPrimaries
>(
2306 hdr_info.color_primaries ? hdr_info.color_primaries : AVCOL_PRI_BT2020);
2307 frame10->color_trc =
static_cast<AVColorTransferCharacteristic
>(
2308 hdr_info.color_trc ? hdr_info.color_trc : AVCOL_TRC_SMPTE2084);
2309 frame10->colorspace =
static_cast<AVColorSpace
>(
2310 hdr_info.color_space ? hdr_info.color_space : AVCOL_SPC_BT2020_NCL);
2311 frame10->color_range =
static_cast<AVColorRange
>(
2312 hdr_info.color_range ? hdr_info.color_range : AVCOL_RANGE_MPEG);
2313 encode_frame = frame10;
2315 }
else if (use_hw_encode && in_frame->format == AV_PIX_FMT_RGBA) {
2316 uploaded_frame = av_frame_alloc();
2317 if (!uploaded_frame) {
2318 std::cerr <<
"Writer: failed to allocate hardware upload frame\n";
2321 uploaded_frame->format = codec_ctx->pix_fmt;
2322 uploaded_frame->width = width;
2323 uploaded_frame->height = height;
2324 if (av_hwframe_get_buffer(hw_frames_ctx, uploaded_frame, 0) < 0) {
2325 std::cerr <<
"Writer: failed to allocate frame from hardware pool\n";
2326 releaseFrame(uploaded_frame);
2329 if (av_frame_make_writable(upload_sw_frame) < 0) {
2330 std::cerr <<
"Writer: software upload frame not writable\n";
2331 releaseFrame(uploaded_frame);
2335 if (upload_sw_frame->format == AV_PIX_FMT_RGBA) {
2336 for (
int y = 0; y < height; ++y) {
2338 upload_sw_frame->data[0] +
2339 static_cast<size_t>(y) * upload_sw_frame->linesize[0],
2341 static_cast<size_t>(y) * in_frame->linesize[0],
2342 static_cast<size_t>(width) * 4);
2345 const uint8_t *source_data[1] = {in_frame->data[0]};
2346 const int source_linesize[1] = {in_frame->linesize[0]};
2347 sws_scale(sws_ctx, source_data, source_linesize, 0, height,
2348 upload_sw_frame->data, upload_sw_frame->linesize);
2351 if (av_hwframe_transfer_data(uploaded_frame, upload_sw_frame, 0) < 0) {
2352 std::cerr <<
"Writer: failed to transfer system frame to hardware frame\n";
2353 releaseFrame(uploaded_frame);
2356 uploaded_frame->pts = in_frame->pts;
2357 encode_frame = uploaded_frame;
2358 }
else if (!use_hw_encode) {
2359 const uint8_t *src_data[1] = {in_frame->data[0]};
2360 int src_linesize[1] = {in_frame->linesize[0]};
2361 sws_scale(sws_ctx, src_data, src_linesize, 0, height, frameYUV->data, frameYUV->linesize);
2362 frameYUV->pts = in_frame->pts;
2363 encode_frame = frameYUV;
2366 int ret = avcodec_send_frame(codec_ctx, encode_frame);
2367 if (ret == AVERROR(EAGAIN)) {
2369 drainEncoderPackets();
2370 ret = avcodec_send_frame(codec_ctx, encode_frame);
2373 std::cerr <<
"Writer: error sending frame to encoder: " << ret <<
"\n";
2374 releaseFrame(uploaded_frame);
2378 drainEncoderPackets();
2379 releaseFrame(uploaded_frame);
2382void Writer::encodeLoop(std::stop_token stop_token) {
2384 AVFrame *frame =
nullptr;
2386 std::unique_lock<std::mutex> lock(queue_mutex);
2387 queue_cv.wait(lock, [
this, &stop_token]() {
2388 return stop_requested || stop_token.stop_requested() || !encode_queue.empty();
2391 if ((stop_requested || stop_token.stop_requested()) && encode_queue.empty()) {
2395 frame = encode_queue.front();
2399 queue_cv.notify_one();
2401 encodeAndWriteFrame(frame);
2402 releaseFrame(frame);
2406 const int flush_ret = avcodec_send_frame(codec_ctx,
nullptr);
2407 if (flush_ret >= 0) {
2408 drainEncoderPackets();
2413void Writer::updateBytesWritten() noexcept {
2414 if (!format_ctx || !format_ctx->pb) {
2418 const int64_t position = avio_tell(format_ctx->pb);
2419 if (position >= 0) {
2420 bytes_written.store(
static_cast<std::uint64_t
>(position),
2421 std::memory_order_relaxed);
2426 std::lock_guard<std::mutex> lock(writer_mutex);
2431 stopEncoderThread();
2433 if (stream && stream->duration > 0) {
2434 last_duration =
static_cast<double>(stream->duration) * av_q2d(stream->time_base);
2435 }
else if (fps_num > 0 && fps_den > 0) {
2436 last_duration =
static_cast<double>(frame_count) *
static_cast<double>(fps_den) /
static_cast<double>(fps_num);
2439 av_write_trailer(format_ctx);
2440 updateBytesWritten();
2442 if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
2443 avio_closep(&format_ctx->pb);
2446 av_frame_free(&frameRGBA);
2447 av_frame_free(&frameYUV);
2448 av_frame_free(&frame10);
2449 sws_freeContext(sws_ctx);
2450 av_frame_free(&upload_sw_frame);
2451#ifdef MXWRITE_HAS_CUDA_COPY
2453 if (cuda_upload_stream) {
2454 cudaStreamSynchronize(cuda_upload_stream);
2455 cudaStreamDestroy(cuda_upload_stream);
2456 cuda_upload_stream =
nullptr;
2459 avcodec_free_context(&codec_ctx);
2460 av_buffer_unref(&hw_frames_ctx);
2461 av_buffer_unref(&hw_device_ctx);
2462 avformat_free_context(format_ctx);
2464 while (!encode_queue.empty()) {
2465 releaseFrame(encode_queue.front());
2469 format_ctx =
nullptr;
2470 codec_ctx =
nullptr;
2472 frameRGBA =
nullptr;
2475 upload_sw_frame =
nullptr;
2476 use_hw_encode =
false;
2477 active_encoder_hardware =
false;
2478 direct_cuda_upload =
false;
2480 stop_requested =
false;
2484 if (!opened && last_duration > 0.0) {
2485 return last_duration;
2487 if (stream && stream->duration > 0) {
2488 return static_cast<double>(stream->duration) * av_q2d(stream->time_base);
2490 if (fps_num > 0 && fps_den > 0) {
2491 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::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.