ACMX 2.139.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
media_utils.cpp
Go to the documentation of this file.
1#include "media_utils.hpp"
2
4
5extern "C" {
6#include <libavcodec/avcodec.h>
7#include <libavcodec/version.h>
8#include <libavformat/avformat.h>
9#include <libavutil/avutil.h>
10#include <libavutil/mastering_display_metadata.h>
11#include <libavutil/pixdesc.h>
12}
13
14#include <opencv2/imgcodecs.hpp>
15#include <opencv2/imgproc.hpp>
16#ifdef ACMXVK_WITH_MXVK_CUDA
17#include <opencv2/core/cuda.hpp>
18#endif
19
20#include <cmath>
21#include <cstdint>
22#include <iostream>
23#include <stdexcept>
24#include <string_view>
25
26namespace acmxvk {
27 [[nodiscard]] cv::Mat loadRgbaImage(const std::string &filename) {
28 constexpr std::uintmax_t MAX_IMAGE_FILE_BYTES = 512U * 1024U * 1024U;
29 constexpr std::int64_t MAX_IMAGE_PIXELS = 67108864;
30 input::validate_file_size(filename, "graphic input", MAX_IMAGE_FILE_BYTES);
31 const cv::Mat source = cv::imread(filename, cv::IMREAD_UNCHANGED);
32 if (source.empty()) {
33 throw std::runtime_error("unable to load image: " + filename);
34 }
35 if (source.cols <= 0 || source.rows <= 0 || static_cast<std::int64_t>(source.cols) * source.rows > MAX_IMAGE_PIXELS) {
36 throw std::runtime_error("graphic input dimensions exceed the supported limit");
37 }
38
39 cv::Mat rgba;
40 switch (source.channels()) {
41 case 1:
42 cv::cvtColor(source, rgba, cv::COLOR_GRAY2RGBA);
43 break;
44 case 3:
45 cv::cvtColor(source, rgba, cv::COLOR_BGR2RGBA);
46 break;
47 case 4:
48 cv::cvtColor(source, rgba, cv::COLOR_BGRA2RGBA);
49 break;
50 default:
51 throw std::runtime_error("unsupported image channel count: " + std::to_string(source.channels()));
52 }
53 return rgba;
54 }
55
56 [[nodiscard]] double probeVideoDuration(const std::string &filename) {
57 if (filename.empty() || filename.find("://") != std::string::npos) {
58 return 0.0;
59 }
60
61 AVFormatContext *format = nullptr;
62 if (avformat_open_input(&format, filename.c_str(), nullptr, nullptr) < 0) {
63 return 0.0;
64 }
65 const auto close_format = [&format] {
66 if (format != nullptr) {
67 avformat_close_input(&format);
68 }
69 };
70 if (avformat_find_stream_info(format, nullptr) < 0) {
71 close_format();
72 return 0.0;
73 }
74
75 double duration = 0.0;
76 if (format->duration != AV_NOPTS_VALUE && format->duration > 0) {
77 duration = static_cast<double>(format->duration) / static_cast<double>(AV_TIME_BASE);
78 } else {
79 const int stream_index = av_find_best_stream(format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
80 if (stream_index >= 0) {
81 const AVStream *stream = format->streams[stream_index];
82 if (stream->duration != AV_NOPTS_VALUE && stream->duration > 0 && stream->time_base.num > 0 && stream->time_base.den > 0) {
83 duration = static_cast<double>(stream->duration) * static_cast<double>(stream->time_base.num) / static_cast<double>(stream->time_base.den);
84 }
85 }
86 }
87 close_format();
88 return std::isfinite(duration) && duration > 0.0 ? duration : 0.0;
89 }
90
91 [[nodiscard]] VideoHdrInfo probeVideoHdrInfo(const std::string &filename) {
92 VideoHdrInfo info;
93 if (filename.empty() || filename.find("://") != std::string::npos) {
94 return info;
95 }
96
97 AVFormatContext *format = nullptr;
98 if (avformat_open_input(&format, filename.c_str(), nullptr, nullptr) < 0) {
99 return info;
100 }
101 const auto close_format = [&format] {
102 if (format != nullptr) {
103 avformat_close_input(&format);
104 }
105 };
106 if (avformat_find_stream_info(format, nullptr) < 0) {
107 close_format();
108 return info;
109 }
110
111 const int stream_index = av_find_best_stream(format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
112 if (stream_index < 0) {
113 close_format();
114 return info;
115 }
116
117 const AVStream *stream = format->streams[stream_index];
118 const AVCodecParameters *parameters = stream->codecpar;
119 info.valid = true;
120 info.color_primaries = parameters->color_primaries;
121 info.color_transfer = parameters->color_trc;
122 info.color_space = parameters->color_space;
123 info.color_range = parameters->color_range;
124 info.bit_depth = parameters->bits_per_raw_sample;
125 if (info.bit_depth <= 0 && parameters->format != AV_PIX_FMT_NONE) {
126 const auto pixel_format = static_cast<AVPixelFormat>(parameters->format);
127 const AVPixFmtDescriptor *descriptor = av_pix_fmt_desc_get(pixel_format);
128 if (descriptor != nullptr && descriptor->nb_components > 0) {
129 info.bit_depth = descriptor->comp[0].depth;
130 }
131 }
132
133 const bool bt2020_primaries = parameters->color_primaries == AVCOL_PRI_BT2020;
134 const bool hdr_transfer = parameters->color_trc == AVCOL_TRC_SMPTE2084 || parameters->color_trc == AVCOL_TRC_ARIB_STD_B67 || parameters->color_trc == AVCOL_TRC_BT2020_10 || parameters->color_trc == AVCOL_TRC_BT2020_12;
135 const bool bt2020_space = parameters->color_space == AVCOL_SPC_BT2020_NCL || parameters->color_space == AVCOL_SPC_BT2020_CL;
136 info.hdr = info.bit_depth >= 10 && (bt2020_primaries || hdr_transfer || bt2020_space);
137
138 const auto copy_side_data = [&info](const AVPacketSideData *side_data, int side_data_count) {
139 for (int index = 0; index < side_data_count; ++index) {
140 const AVPacketSideData &entry = side_data[index];
141 if (entry.type == AV_PKT_DATA_MASTERING_DISPLAY_METADATA && entry.size == sizeof(AVMasteringDisplayMetadata)) {
142 info.mastering_display.assign(entry.data, entry.data + entry.size);
143 } else if (entry.type == AV_PKT_DATA_CONTENT_LIGHT_LEVEL && entry.size == sizeof(AVContentLightMetadata)) {
144 info.content_light.assign(entry.data, entry.data + entry.size);
145 }
146 }
147 };
148#if LIBAVCODEC_VERSION_MAJOR >= 60
149 copy_side_data(parameters->coded_side_data, parameters->nb_coded_side_data);
150#else
151 copy_side_data(stream->side_data, stream->nb_side_data);
152#endif
153
154 if (info.mastering_display.empty() || info.content_light.empty()) {
155 const AVCodec *decoder = avcodec_find_decoder(parameters->codec_id);
156 AVCodecContext *decoder_context = decoder != nullptr ? avcodec_alloc_context3(decoder) : nullptr;
157 AVPacket *packet = av_packet_alloc();
158 AVFrame *frame = av_frame_alloc();
159 const auto copy_frame_side_data = [&info](const AVFrame *source) {
160 if (info.mastering_display.empty()) {
161 const AVFrameSideData *side_data = av_frame_get_side_data(source, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
162 if (side_data != nullptr && side_data->size == sizeof(AVMasteringDisplayMetadata)) {
163 info.mastering_display.assign(side_data->data, side_data->data + side_data->size);
164 }
165 }
166 if (info.content_light.empty()) {
167 const AVFrameSideData *side_data = av_frame_get_side_data(source, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
168 if (side_data != nullptr && side_data->size == sizeof(AVContentLightMetadata)) {
169 info.content_light.assign(side_data->data, side_data->data + side_data->size);
170 }
171 }
172 };
173 if (decoder_context != nullptr && packet != nullptr && frame != nullptr && avcodec_parameters_to_context(decoder_context, parameters) >= 0 && avcodec_open2(decoder_context, decoder, nullptr) >= 0) {
174 constexpr int MAX_METADATA_PACKETS = 256;
175 bool decoded_frame = false;
176 for (int packet_count = 0; packet_count < MAX_METADATA_PACKETS && !decoded_frame && av_read_frame(format, packet) >= 0; ++packet_count) {
177 if (packet->stream_index == stream_index && avcodec_send_packet(decoder_context, packet) >= 0) {
178 while (avcodec_receive_frame(decoder_context, frame) >= 0) {
179 copy_frame_side_data(frame);
180 decoded_frame = true;
181 av_frame_unref(frame);
182 }
183 }
184 av_packet_unref(packet);
185 }
186 if (!decoded_frame) {
187 avcodec_send_packet(decoder_context, nullptr);
188 while (avcodec_receive_frame(decoder_context, frame) >= 0) {
189 copy_frame_side_data(frame);
190 decoded_frame = true;
191 av_frame_unref(frame);
192 }
193 }
194 }
195 av_frame_free(&frame);
196 av_packet_free(&packet);
197 avcodec_free_context(&decoder_context);
198 }
199
200 close_format();
201 return info;
202 }
203
204 void printVideoHdrInfo(const VideoHdrInfo &info, std::ostream &output) {
205 const auto label = [](const char *name) -> std::string_view { return name != nullptr ? std::string_view(name) : std::string_view("unknown"); };
206 output << "HDR: " << (info.hdr ? "yes" : "no") << '\n'
207 << "Bit depth: " << info.bit_depth << '\n'
208 << "Color primaries: " << label(av_color_primaries_name(static_cast<AVColorPrimaries>(info.color_primaries))) << " (" << info.color_primaries << ")\n"
209 << "Transfer: " << label(av_color_transfer_name(static_cast<AVColorTransferCharacteristic>(info.color_transfer))) << " (" << info.color_transfer << ")\n"
210 << "Matrix: " << label(av_color_space_name(static_cast<AVColorSpace>(info.color_space))) << " (" << info.color_space << ")\n"
211 << "Range: " << label(av_color_range_name(static_cast<AVColorRange>(info.color_range))) << " (" << info.color_range << ")\n"
212 << "Mastering-display metadata: " << info.mastering_display.size() << " bytes\n"
213 << "Content-light metadata: " << info.content_light.size() << " bytes\n";
214 }
215 // Frame transforms, CUDA device helpers, and asynchronous camera capture.
216 void rotateFrame(cv::Mat &frame, FrameRotation rotation) {
217 switch (rotation) {
219 break;
221 cv::rotate(frame, frame, cv::ROTATE_90_CLOCKWISE);
222 break;
224 cv::rotate(frame, frame, cv::ROTATE_180);
225 break;
227 cv::rotate(frame, frame, cv::ROTATE_90_COUNTERCLOCKWISE);
228 break;
229 }
230 }
231
232 [[nodiscard]] bool rotationSwapsDimensions(FrameRotation rotation) { return rotation == FrameRotation::Clockwise90 || rotation == FrameRotation::Counterclockwise90; }
233
234#ifdef ACMXVK_WITH_MXVK_CUDA
235 void select_cuda_device(int device_index) {
236 const int device_count = cv::cuda::getCudaEnabledDeviceCount();
237 if (device_count <= 0) {
238 throw std::runtime_error("no CUDA-capable devices are available");
239 }
240 if (device_index < 0 || device_index >= device_count) {
241 throw std::runtime_error("CUDA device index must be between 0 and " + std::to_string(device_count - 1));
242 }
243 cv::cuda::setDevice(device_index);
244 const cv::cuda::DeviceInfo device(device_index);
245 std::cout << "acmxvk: CUDA device " << device_index << ": " << device.name() << '\n';
246 }
247
248 void list_cuda_devices(std::ostream &output) {
249 const int device_count = cv::cuda::getCudaEnabledDeviceCount();
250 if (device_count < 0) {
251 throw std::runtime_error("OpenCV could not query CUDA devices (error " + std::to_string(device_count) + ")");
252 }
253 output << "acmxvk: found " << device_count << " CUDA device(s)\n";
254 for (int index = 0; index < device_count; ++index) {
255 const cv::cuda::DeviceInfo device(index);
256 output << " " << index << ": " << device.name() << " (" << (device.totalMemory() / (1024U * 1024U)) << " MiB)\n";
257 }
258 }
259#endif
260} // namespace acmxvk
void validate_file_size(const std::filesystem::path &path, std::string_view context, std::uintmax_t maximum_bytes)
cv::Mat loadRgbaImage(const std::string &filename)
bool rotationSwapsDimensions(FrameRotation rotation)
double probeVideoDuration(const std::string &filename)
void printVideoHdrInfo(const VideoHdrInfo &info, std::ostream &output)
void rotateFrame(cv::Mat &frame, FrameRotation rotation)
VideoHdrInfo probeVideoHdrInfo(const std::string &filename)
FrameRotation
Definition options.hpp:17
std::vector< std::uint8_t > content_light
std::vector< std::uint8_t > mastering_display