MXVK Vulkan Framework 0.33.1
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxwrite.hpp File Reference

FFmpeg-based video writer used by MXWrite. More...

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <queue>
#include <string>
#include <string_view>
#include <thread>
#include <vector>

Go to the source code of this file.

Classes

struct  EncodeOptions
 User-configurable video encoder quality options. More...
struct  EncoderInfo
 A video encoder reported by the linked FFmpeg installation. More...
struct  EncoderOptionInfo
 One configurable AVOption exposed by a video encoder. More...
struct  Frame_Data
 Queue entry that stores a frame pointer and its capture timestamp. More...
struct  EncodeOptions::HdrInfo
 HDR output options. More...
class  Writer
 FFmpeg-backed RGBA video writer. More...

Functions

std::vector< EncoderInfoavailable_video_encoders ()
void cleanup_contexts (AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx)
 Free FFmpeg format contexts used during transfer operations.
void transfer_audio (std::string_view sourceAudioFile, std::string_view destVideoFile)
 Copy audio from one video file to another.
std::vector< EncoderOptionInfovideo_encoder_options (std::string_view encoder_name)
 Return the options exposed by one registered video encoder.

Detailed Description

FFmpeg-based video writer used by MXWrite.

Definition in file mxwrite.hpp.

Function Documentation

◆ available_video_encoders()

std::vector< EncoderInfo > available_video_encoders ( )
Returns
Video encoders registered by the linked FFmpeg libraries.

Definition at line 980 of file mxwrite.cpp.

980 {
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) {
985 continue;
986 }
987 EncoderInfo info;
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);
991 info.hardware = codec_uses_hardware(codec);
992 info.experimental = (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) != 0;
993 const std::vector<AVPixelFormat> formats = supported_pixel_formats(codec);
994 for (AVPixelFormat format : formats) {
995 const char *name = av_get_pix_fmt_name(format);
996 if (!name) {
997 continue;
998 }
999 if (!info.pixel_formats.empty()) {
1000 info.pixel_formats += ", ";
1001 }
1002 info.pixel_formats += name;
1003 }
1004 result.push_back(std::move(info));
1005 }
1006 std::sort(result.begin(), result.end(), [](const EncoderInfo &left, const EncoderInfo &right) {
1007 if (left.codec_name != right.codec_name) {
1008 return left.codec_name < right.codec_name;
1009 }
1010 if (left.hardware != right.hardware) {
1011 return left.hardware > right.hardware;
1012 }
1013 return left.name < right.name;
1014 });
1015 return result;
1016}
std::vector< AVPixelFormat > supported_pixel_formats(const AVCodec *codec)
Definition mxwrite.cpp:770
bool codec_uses_hardware(const AVCodec *codec)
Definition mxwrite.cpp:840
A video encoder reported by the linked FFmpeg installation.
Definition mxwrite.hpp:39
std::string long_name
Human-readable encoder description.
Definition mxwrite.hpp:41
std::string pixel_formats
Comma-separated supported input pixel formats.
Definition mxwrite.hpp:43
std::string name
Exact libavcodec encoder name (for example, libx265).
Definition mxwrite.hpp:40
std::string codec_name
Encoded format name (for example, hevc or av1).
Definition mxwrite.hpp:42
bool experimental
True when FFmpeg marks the encoder experimental.
Definition mxwrite.hpp:45
bool hardware
True for hardware or hybrid encoders.
Definition mxwrite.hpp:44

◆ cleanup_contexts()

void cleanup_contexts ( AVFormatContext * source_ctx,
AVFormatContext * dest_ctx,
AVFormatContext * output_ctx )
extern

Free FFmpeg format contexts used during transfer operations.

Parameters
source_ctxSource format context.
dest_ctxDestination format context.
output_ctxOutput format context.

Definition at line 226 of file mxwrite.cpp.

228 {
229 if (source_ctx)
230 avformat_close_input(&source_ctx);
231 if (dest_ctx)
232 avformat_close_input(&dest_ctx);
233 if (output_ctx) {
234 if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
235 avio_closep(&output_ctx->pb);
236 avformat_free_context(output_ctx);
237 }
238}

◆ transfer_audio()

void transfer_audio ( std::string_view sourceAudioFile,
std::string_view destVideoFile )
extern

Copy audio from one video file to another.

Parameters
sourceAudioFileInput media file containing the audio stream.
destVideoFileOutput video file to receive the audio stream.

Definition at line 240 of file mxwrite.cpp.

240 {
241 std::lock_guard<std::mutex> lock(transfer_audio_mutex);
242 if (!is_format_supported(destVideoFile.data())) {
243 std::cerr << "Unsupported output format. Supported formats: .mp4, .mkv, .avi, .mov\n";
244 return;
245 }
246
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";
250
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";
254 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
255 return;
256 }
257
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";
261 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
262 return;
263 }
264
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;
268 break;
269 }
270 }
271
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)
275 dest_video_idx = i;
276 else if (type == AVMEDIA_TYPE_AUDIO)
277 dest_audio_idx = i;
278 }
279
280 if (source_audio_idx == -1 || dest_video_idx == -1) {
281 std::cerr << "Required streams not found\n";
282 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
283 return;
284 }
285
286 const AVOutputFormat *output_fmt = av_guess_format(nullptr, destVideoFile.data(), nullptr);
287 if (!output_fmt) {
288 output_fmt = av_guess_format("mp4", nullptr, nullptr);
289 if (!output_fmt) {
290 std::cerr << "Failed to determine output format\n";
291 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
292 return;
293 }
294 }
295
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";
298 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
299 return;
300 }
301
302 const AVCodec *audio_codec = avcodec_find_decoder(source_ctx->streams[source_audio_idx]->codecpar->codec_id);
303 if (!audio_codec) {
304 std::cerr << "Failed to find audio decoder\n";
305 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
306 return;
307 }
308
309 for (unsigned i = 0; i < dest_ctx->nb_streams; ++i) {
310 if (dest_ctx->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
311 continue;
312 }
313
314 AVStream *dest_stream = dest_ctx->streams[i];
315 AVStream *out_stream = avformat_new_stream(output_ctx, nullptr);
316 if (!out_stream) {
317 std::cerr << "Failed to create output stream\n";
318 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
319 return;
320 }
321
322 if (avcodec_parameters_copy(out_stream->codecpar, dest_stream->codecpar) < 0) {
323 std::cerr << "Failed to copy video parameters\n";
324 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
325 return;
326 }
327
328 out_stream->time_base = dest_stream->time_base;
329 out_stream->codecpar->codec_tag = 0;
330 }
331
332 AVStream *out_stream = avformat_new_stream(output_ctx, audio_codec);
333 if (!out_stream) {
334 std::cerr << "Failed to create audio stream\n";
335 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
336 return;
337 }
338
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";
342 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
343 return;
344 }
345
346 if (source_params->frame_size == 0) {
347 out_stream->codecpar->frame_size = 1024;
348 } else {
349 out_stream->codecpar->frame_size = source_params->frame_size;
350 }
351
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;
355
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";
359 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
360 return;
361 }
362 }
363 if (avformat_write_header(output_ctx, nullptr) < 0) {
364 std::cerr << "Failed to write header\n";
365 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
366 return;
367 }
368 AVPacket packet;
369 while (av_read_frame(dest_ctx, &packet) >= 0) {
370 if (packet.stream_index == dest_audio_idx) {
371 av_packet_unref(&packet);
372 continue;
373 }
374
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);
378
379 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
380 std::cerr << "Failed to write packet\n";
381 av_packet_unref(&packet);
382 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
383 return;
384 }
385 av_packet_unref(&packet);
386 }
387
388 int64_t video_duration_ts = 0;
389 {
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);
396 }
397 }
398
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);
404 break;
405 }
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;
410
411 if (av_interleaved_write_frame(output_ctx, &packet) < 0) {
412 std::cerr << "Failed to write audio packet\n";
413 av_packet_unref(&packet);
414 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
415 return;
416 }
417 }
418 av_packet_unref(&packet);
419 }
420 av_write_trailer(output_ctx);
421 cleanup_contexts(source_ctx, dest_ctx, output_ctx);
422 std::remove(destVideoFile.data());
423 std::rename(temp_output.c_str(), destVideoFile.data());
424}
std::mutex transfer_audio_mutex
Definition mxwrite.cpp:204
bool is_format_supported(const char *filename)
Definition mxwrite.cpp:206
void cleanup_contexts(AVFormatContext *source_ctx, AVFormatContext *dest_ctx, AVFormatContext *output_ctx)
Free FFmpeg format contexts used during transfer operations.
Definition mxwrite.cpp:226

◆ video_encoder_options()

std::vector< EncoderOptionInfo > video_encoder_options ( std::string_view encoder_name)

Return the options exposed by one registered video encoder.

Parameters
encoder_nameExact encoder name returned by available_video_encoders().

Definition at line 1018 of file mxwrite.cpp.

1018 {
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) {
1022 return {};
1023 }
1024 AVCodecContext *context = avcodec_alloc_context3(codec);
1025 if (!context) {
1026 return {};
1027 }
1028 std::vector<EncoderOptionInfo> result;
1029 std::set<std::string> seen;
1030 append_encoder_options(context->priv_data, result, seen);
1031 append_encoder_options(context, result, seen);
1032 avcodec_free_context(&context);
1033 std::sort(result.begin(), result.end(), [](const EncoderOptionInfo &left, const EncoderOptionInfo &right) {
1034 return left.name < right.name;
1035 });
1036 return result;
1037}
void append_encoder_options(void *object, std::vector< EncoderOptionInfo > &result, std::set< std::string > &seen)
Definition mxwrite.cpp:930
One configurable AVOption exposed by a video encoder.
Definition mxwrite.hpp:49