ACMX 2.136.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 =
29 512U * 1024U * 1024U;
30 constexpr std::int64_t MAX_IMAGE_PIXELS = 67108864;
31 input::validate_file_size(filename, "graphic input",
32 MAX_IMAGE_FILE_BYTES);
33 const cv::Mat source = cv::imread(filename, cv::IMREAD_UNCHANGED);
34 if (source.empty()) {
35 throw std::runtime_error("unable to load image: " + filename);
36 }
37 if (source.cols <= 0 || source.rows <= 0 ||
38 static_cast<std::int64_t>(source.cols) * source.rows >
39 MAX_IMAGE_PIXELS) {
40 throw std::runtime_error(
41 "graphic input dimensions exceed the supported limit");
42 }
43
44 cv::Mat rgba;
45 switch (source.channels()) {
46 case 1:
47 cv::cvtColor(source, rgba, cv::COLOR_GRAY2RGBA);
48 break;
49 case 3:
50 cv::cvtColor(source, rgba, cv::COLOR_BGR2RGBA);
51 break;
52 case 4:
53 cv::cvtColor(source, rgba, cv::COLOR_BGRA2RGBA);
54 break;
55 default:
56 throw std::runtime_error("unsupported image channel count: " +
57 std::to_string(source.channels()));
58 }
59 return rgba;
60 }
61
62 [[nodiscard]] double probeVideoDuration(const std::string &filename) {
63 if (filename.empty() || filename.find("://") != std::string::npos) {
64 return 0.0;
65 }
66
67 AVFormatContext *format = nullptr;
68 if (avformat_open_input(&format, filename.c_str(), nullptr, nullptr) < 0) {
69 return 0.0;
70 }
71 const auto close_format = [&format] {
72 if (format != nullptr) {
73 avformat_close_input(&format);
74 }
75 };
76 if (avformat_find_stream_info(format, nullptr) < 0) {
77 close_format();
78 return 0.0;
79 }
80
81 double duration = 0.0;
82 if (format->duration != AV_NOPTS_VALUE && format->duration > 0) {
83 duration = static_cast<double>(format->duration) /
84 static_cast<double>(AV_TIME_BASE);
85 } else {
86 const int stream_index = av_find_best_stream(
87 format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
88 if (stream_index >= 0) {
89 const AVStream *stream = format->streams[stream_index];
90 if (stream->duration != AV_NOPTS_VALUE &&
91 stream->duration > 0 && stream->time_base.num > 0 &&
92 stream->time_base.den > 0) {
93 duration = static_cast<double>(stream->duration) *
94 static_cast<double>(stream->time_base.num) /
95 static_cast<double>(stream->time_base.den);
96 }
97 }
98 }
99 close_format();
100 return std::isfinite(duration) && duration > 0.0 ? duration : 0.0;
101 }
102
104 const std::string &filename) {
105 VideoHdrInfo info;
106 if (filename.empty() || filename.find("://") != std::string::npos) {
107 return info;
108 }
109
110 AVFormatContext *format = nullptr;
111 if (avformat_open_input(&format, filename.c_str(), nullptr, nullptr) < 0) {
112 return info;
113 }
114 const auto close_format = [&format] {
115 if (format != nullptr) {
116 avformat_close_input(&format);
117 }
118 };
119 if (avformat_find_stream_info(format, nullptr) < 0) {
120 close_format();
121 return info;
122 }
123
124 const int stream_index = av_find_best_stream(
125 format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
126 if (stream_index < 0) {
127 close_format();
128 return info;
129 }
130
131 const AVStream *stream = format->streams[stream_index];
132 const AVCodecParameters *parameters = stream->codecpar;
133 info.valid = true;
134 info.color_primaries = parameters->color_primaries;
135 info.color_transfer = parameters->color_trc;
136 info.color_space = parameters->color_space;
137 info.color_range = parameters->color_range;
138 info.bit_depth = parameters->bits_per_raw_sample;
139 if (info.bit_depth <= 0 && parameters->format != AV_PIX_FMT_NONE) {
140 const auto pixel_format =
141 static_cast<AVPixelFormat>(parameters->format);
142 const AVPixFmtDescriptor *descriptor =
143 av_pix_fmt_desc_get(pixel_format);
144 if (descriptor != nullptr && descriptor->nb_components > 0) {
145 info.bit_depth = descriptor->comp[0].depth;
146 }
147 }
148
149 const bool bt2020_primaries =
150 parameters->color_primaries == AVCOL_PRI_BT2020;
151 const bool hdr_transfer =
152 parameters->color_trc == AVCOL_TRC_SMPTE2084 ||
153 parameters->color_trc == AVCOL_TRC_ARIB_STD_B67 ||
154 parameters->color_trc == AVCOL_TRC_BT2020_10 ||
155 parameters->color_trc == AVCOL_TRC_BT2020_12;
156 const bool bt2020_space =
157 parameters->color_space == AVCOL_SPC_BT2020_NCL ||
158 parameters->color_space == AVCOL_SPC_BT2020_CL;
159 info.hdr = info.bit_depth >= 10 &&
160 (bt2020_primaries || hdr_transfer || bt2020_space);
161
162 const auto copy_side_data = [&info](const AVPacketSideData *side_data,
163 int side_data_count) {
164 for (int index = 0; index < side_data_count; ++index) {
165 const AVPacketSideData &entry = side_data[index];
166 if (entry.type == AV_PKT_DATA_MASTERING_DISPLAY_METADATA &&
167 entry.size == sizeof(AVMasteringDisplayMetadata)) {
168 info.mastering_display.assign(entry.data,
169 entry.data + entry.size);
170 } else if (entry.type ==
171 AV_PKT_DATA_CONTENT_LIGHT_LEVEL &&
172 entry.size == sizeof(AVContentLightMetadata)) {
173 info.content_light.assign(entry.data,
174 entry.data + entry.size);
175 }
176 }
177 };
178#if LIBAVCODEC_VERSION_MAJOR >= 60
179 copy_side_data(parameters->coded_side_data,
180 parameters->nb_coded_side_data);
181#else
182 copy_side_data(stream->side_data, stream->nb_side_data);
183#endif
184
185 if (info.mastering_display.empty() || info.content_light.empty()) {
186 const AVCodec *decoder =
187 avcodec_find_decoder(parameters->codec_id);
188 AVCodecContext *decoder_context =
189 decoder != nullptr ? avcodec_alloc_context3(decoder)
190 : nullptr;
191 AVPacket *packet = av_packet_alloc();
192 AVFrame *frame = av_frame_alloc();
193 const auto copy_frame_side_data = [&info](const AVFrame *source) {
194 if (info.mastering_display.empty()) {
195 const AVFrameSideData *side_data = av_frame_get_side_data(
196 source, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
197 if (side_data != nullptr &&
198 side_data->size ==
199 sizeof(AVMasteringDisplayMetadata)) {
200 info.mastering_display.assign(
201 side_data->data,
202 side_data->data + side_data->size);
203 }
204 }
205 if (info.content_light.empty()) {
206 const AVFrameSideData *side_data = av_frame_get_side_data(
207 source, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
208 if (side_data != nullptr &&
209 side_data->size == sizeof(AVContentLightMetadata)) {
210 info.content_light.assign(
211 side_data->data,
212 side_data->data + side_data->size);
213 }
214 }
215 };
216 if (decoder_context != nullptr && packet != nullptr &&
217 frame != nullptr &&
218 avcodec_parameters_to_context(decoder_context, parameters) >=
219 0 &&
220 avcodec_open2(decoder_context, decoder, nullptr) >= 0) {
221 constexpr int MAX_METADATA_PACKETS = 256;
222 bool decoded_frame = false;
223 for (int packet_count = 0;
224 packet_count < MAX_METADATA_PACKETS && !decoded_frame &&
225 av_read_frame(format, packet) >= 0;
226 ++packet_count) {
227 if (packet->stream_index == stream_index &&
228 avcodec_send_packet(decoder_context, packet) >= 0) {
229 while (avcodec_receive_frame(decoder_context, frame) >=
230 0) {
231 copy_frame_side_data(frame);
232 decoded_frame = true;
233 av_frame_unref(frame);
234 }
235 }
236 av_packet_unref(packet);
237 }
238 if (!decoded_frame) {
239 avcodec_send_packet(decoder_context, nullptr);
240 while (avcodec_receive_frame(decoder_context, frame) >=
241 0) {
242 copy_frame_side_data(frame);
243 decoded_frame = true;
244 av_frame_unref(frame);
245 }
246 }
247 }
248 av_frame_free(&frame);
249 av_packet_free(&packet);
250 avcodec_free_context(&decoder_context);
251 }
252
253 close_format();
254 return info;
255 }
256
257 void printVideoHdrInfo(const VideoHdrInfo &info, std::ostream &output) {
258 const auto label = [](const char *name) -> std::string_view {
259 return name != nullptr ? std::string_view(name)
260 : std::string_view("unknown");
261 };
262 output << "HDR: " << (info.hdr ? "yes" : "no") << '\n'
263 << "Bit depth: " << info.bit_depth << '\n'
264 << "Color primaries: "
265 << label(av_color_primaries_name(
266 static_cast<AVColorPrimaries>(info.color_primaries)))
267 << " (" << info.color_primaries << ")\n"
268 << "Transfer: "
269 << label(av_color_transfer_name(
270 static_cast<AVColorTransferCharacteristic>(
271 info.color_transfer)))
272 << " (" << info.color_transfer << ")\n"
273 << "Matrix: "
274 << label(av_color_space_name(
275 static_cast<AVColorSpace>(info.color_space)))
276 << " (" << info.color_space << ")\n"
277 << "Range: "
278 << label(av_color_range_name(
279 static_cast<AVColorRange>(info.color_range)))
280 << " (" << info.color_range << ")\n"
281 << "Mastering-display metadata: "
282 << info.mastering_display.size() << " bytes\n"
283 << "Content-light metadata: " << info.content_light.size()
284 << " bytes\n";
285 }
286 // Frame transforms, CUDA device helpers, and asynchronous camera capture.
287 void rotateFrame(cv::Mat &frame, FrameRotation rotation) {
288 switch (rotation) {
290 break;
292 cv::rotate(frame, frame, cv::ROTATE_90_CLOCKWISE);
293 break;
295 cv::rotate(frame, frame, cv::ROTATE_180);
296 break;
298 cv::rotate(frame, frame, cv::ROTATE_90_COUNTERCLOCKWISE);
299 break;
300 }
301 }
302
303 [[nodiscard]] bool rotationSwapsDimensions(FrameRotation rotation) {
304 return rotation == FrameRotation::Clockwise90 ||
306 }
307
308#ifdef ACMXVK_WITH_MXVK_CUDA
309 void select_cuda_device(int device_index) {
310 const int device_count = cv::cuda::getCudaEnabledDeviceCount();
311 if (device_count <= 0) {
312 throw std::runtime_error("no CUDA-capable devices are available");
313 }
314 if (device_index < 0 || device_index >= device_count) {
315 throw std::runtime_error(
316 "CUDA device index must be between 0 and " +
317 std::to_string(device_count - 1));
318 }
319 cv::cuda::setDevice(device_index);
320 const cv::cuda::DeviceInfo device(device_index);
321 std::cout << "acmxvk: CUDA device " << device_index << ": "
322 << device.name() << '\n';
323 }
324
325 void list_cuda_devices(std::ostream &output) {
326 const int device_count = cv::cuda::getCudaEnabledDeviceCount();
327 if (device_count < 0) {
328 throw std::runtime_error(
329 "OpenCV could not query CUDA devices (error " +
330 std::to_string(device_count) + ")");
331 }
332 output << "acmxvk: found " << device_count << " CUDA device(s)\n";
333 for (int index = 0; index < device_count; ++index) {
334 const cv::cuda::DeviceInfo device(index);
335 output << " " << index << ": " << device.name() << " ("
336 << (device.totalMemory() / (1024U * 1024U)) << " MiB)\n";
337 }
338 }
339#endif
340} // 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:35
std::vector< std::uint8_t > content_light
std::vector< std::uint8_t > mastering_display