MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_ff_capture.cpp
Go to the documentation of this file.
1/**
2 * @file mxvk_ff_capture.cpp
3 * @brief Implementation of mxvk::VK_FF_Capture.
4 */
7
8#include <algorithm>
9#include <cstring>
10#include <format>
11#include <iostream>
12#ifdef MXVK_CUDA
13#include <cuda_runtime_api.h>
14#include <opencv2/cudaarithm.hpp>
15#ifdef MXVK_CUDA_NPP
16#include <nppi_color_conversion.h>
17#include <nppi_data_exchange_and_initialization.h>
18#endif
19#endif
20
21namespace mxvk {
22
23 namespace {
24 [[nodiscard]] double rationalToDouble(AVRational rational) {
25 if (rational.num <= 0 || rational.den <= 0) {
26 return 0.0;
27 }
28 return av_q2d(rational);
29 }
30
31 [[nodiscard]] bool convertBt2020Yuv10LimitedToRgba16(const AVFrame *source, std::vector<uint16_t> &rgba, int &width, int &height, int &pitch, bool flipY) {
32 if (source == nullptr || source->data[0] == nullptr || source->data[1] == nullptr) {
33 return false;
34 }
35 const bool p010 = source->format == AV_PIX_FMT_P010LE;
36 if (!p010 && (source->format != AV_PIX_FMT_YUV420P10LE || source->data[2] == nullptr)) {
37 return false;
38 }
39
40 width = source->width;
41 height = source->height;
42 pitch = width * 8;
43 if (width <= 0 || height <= 0) {
44 return false;
45 }
46 rgba.resize(static_cast<size_t>(width) * static_cast<size_t>(height) * 4U);
47
48 const int sampleShift = p010 ? 6 : 0;
49 const auto sample10 = [sampleShift](const uint8_t *plane, int stride, int x, int y) {
50 uint16_t raw = 0;
51 std::memcpy(&raw, plane + static_cast<size_t>(y) * stride + static_cast<size_t>(x) * 2U, sizeof(raw));
52 return static_cast<int>(raw >> sampleShift);
53 };
54
55 constexpr float CR_R = 1.4746F;
56 constexpr float CB_G = -0.16455312684366F;
57 constexpr float CR_G = -0.57135313725490F;
58 constexpr float CB_B = 1.8814F;
59 constexpr float INV_Y = 1.0F / 876.0F;
60 constexpr float INV_C = 1.0F / 896.0F;
61
62 for (int y = 0; y < height; ++y) {
63 uint16_t *destination = rgba.data() + static_cast<size_t>(y) * width * 4U;
64 const int chromaY = y >> 1;
65 for (int x = 0; x < width; ++x) {
66 const int chromaX = x >> 1;
67 const int ySample = sample10(source->data[0], source->linesize[0], x, y);
68 int cbSample = 0;
69 int crSample = 0;
70 if (p010) {
71 cbSample = sample10(source->data[1], source->linesize[1], chromaX * 2, chromaY);
72 crSample = sample10(source->data[1], source->linesize[1], chromaX * 2 + 1, chromaY);
73 } else {
74 cbSample = sample10(source->data[1], source->linesize[1], chromaX, chromaY);
75 crSample = sample10(source->data[2], source->linesize[2], chromaX, chromaY);
76 }
77
78 const float yValue = (ySample - 64) * INV_Y;
79 const float cb = (cbSample - 512) * INV_C;
80 const float cr = (crSample - 512) * INV_C;
81 const float red = std::clamp(yValue + CR_R * cr, 0.0F, 1.0F);
82 const float green = std::clamp(yValue + CB_G * cb + CR_G * cr, 0.0F, 1.0F);
83 const float blue = std::clamp(yValue + CB_B * cb, 0.0F, 1.0F);
84
85 destination[x * 4 + 0] = static_cast<uint16_t>(red * 65535.0F + 0.5F);
86 destination[x * 4 + 1] = static_cast<uint16_t>(green * 65535.0F + 0.5F);
87 destination[x * 4 + 2] = static_cast<uint16_t>(blue * 65535.0F + 0.5F);
88 destination[x * 4 + 3] = UINT16_MAX;
89 }
90 }
91
92 if (flipY) {
93 std::vector<uint16_t> row(static_cast<size_t>(width) * 4U);
94 for (int y = 0; y < height / 2; ++y) {
95 uint16_t *top = rgba.data() + static_cast<size_t>(y) * width * 4U;
96 uint16_t *bottom = rgba.data() + static_cast<size_t>(height - 1 - y) * width * 4U;
97 std::copy_n(top, row.size(), row.data());
98 std::copy_n(bottom, row.size(), top);
99 std::copy_n(row.data(), row.size(), bottom);
100 }
101 }
102 return true;
103 }
104
105#if defined(MXVK_CUDA) && defined(MXVK_CUDA_NPP)
106 [[nodiscard]] NppStreamContext makeNppStreamContext(cudaStream_t stream) {
107 NppStreamContext context{};
108 context.hStream = stream;
109 cudaGetDevice(&context.nCudaDeviceId);
110
111 cudaDeviceProp properties{};
112 cudaGetDeviceProperties(&properties, context.nCudaDeviceId);
113 context.nMultiProcessorCount = properties.multiProcessorCount;
114 context.nMaxThreadsPerMultiProcessor = properties.maxThreadsPerMultiProcessor;
115 context.nMaxThreadsPerBlock = properties.maxThreadsPerBlock;
116 context.nSharedMemPerBlock = properties.sharedMemPerBlock;
117
118 cudaDeviceGetAttribute(&context.nCudaDevAttrComputeCapabilityMajor, cudaDevAttrComputeCapabilityMajor, context.nCudaDeviceId);
119 cudaDeviceGetAttribute(&context.nCudaDevAttrComputeCapabilityMinor, cudaDevAttrComputeCapabilityMinor, context.nCudaDeviceId);
120 cudaStreamGetFlags(stream, &context.nStreamFlags);
121 return context;
122 }
123#endif
124 } // namespace
125
127
128 bool VK_FF_Capture::open(const std::string &filename) { return open(filename, -1); }
129
130 bool VK_FF_Capture::open(const std::string &filename, int cuda_device) {
131 close();
132
133 if (avformat_open_input(&formatCtx, filename.c_str(), nullptr, nullptr) < 0) {
134 std::cout << std::format("mxvk_ff_capture: failed to open file: {}\n", filename);
135 close();
136 return false;
137 }
138
139 if (avformat_find_stream_info(formatCtx, nullptr) < 0) {
140 std::cout << std::format("mxvk_ff_capture: failed to read stream info: {}\n", filename);
141 close();
142 return false;
143 }
144
145 const int streamIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
146 if (streamIndex < 0) {
147 std::cout << std::format("mxvk_ff_capture: no video stream found: {}\n", filename);
148 close();
149 return false;
150 }
151 videoStream = streamIndex;
152
153 AVStream *stream = formatCtx->streams[videoStream];
154 const AVCodecParameters *codecParams = stream->codecpar;
155 const AVCodec *decoder = avcodec_find_decoder(codecParams->codec_id);
156 if (decoder == nullptr) {
157 std::cout << "mxvk_ff_capture: no decoder found for video stream\n";
158 close();
159 return false;
160 }
161
162 codecCtx = avcodec_alloc_context3(decoder);
163 if (codecCtx == nullptr || avcodec_parameters_to_context(codecCtx, codecParams) < 0) {
164 std::cout << "mxvk_ff_capture: failed to create decoder context\n";
165 close();
166 return false;
167 }
168
169 codecCtx->opaque = this;
170 if (initHardwareDevice(decoder, cuda_device)) {
171 codecCtx->get_format = &VK_FF_Capture::chooseHwFormat;
172 codecCtx->hw_device_ctx = av_buffer_ref(hwDeviceCtx);
173 hardwareDecode = codecCtx->hw_device_ctx != nullptr;
174 }
175
176 if (avcodec_open2(codecCtx, decoder, nullptr) < 0) {
177 std::cout << "mxvk_ff_capture: failed to open decoder\n";
178 close();
179 return false;
180 }
181
182 packet = av_packet_alloc();
183 frame = av_frame_alloc();
184 swFrame = av_frame_alloc();
185 if (packet == nullptr || frame == nullptr || swFrame == nullptr) {
186 std::cout << "mxvk_ff_capture: failed to allocate decoder frames\n";
187 close();
188 return false;
189 }
190
191 frameWidth = codecCtx->width;
192 frameHeight = codecCtx->height;
193 frameFps = rationalToDouble(stream->avg_frame_rate);
194 if (frameFps <= 0.0) {
195 frameFps = rationalToDouble(stream->r_frame_rate);
196 }
197 if (frameFps <= 0.0) {
198 frameFps = 30.0;
199 }
200
201 const std::string decodeMode = hardwareDecode && hardwareDecodeDevice >= 0 ? std::format("cuda:{}", hardwareDecodeDevice) : hardwareDecode ? "cuda" : "software";
202 std::cout << std::format("mxvk_ff_capture: opened {} ({}x{}, {:.3f} fps, decode={})\n", filename, frameWidth, frameHeight, frameFps, decodeMode);
203 return true;
204 }
205
207 if (!is_open() || videoStream < 0) {
208 return false;
209 }
210
211 AVStream *stream = formatCtx->streams[videoStream];
212 const int64_t timestamp = stream->start_time == AV_NOPTS_VALUE ? 0 : stream->start_time;
213 if (av_seek_frame(formatCtx, videoStream, timestamp, AVSEEK_FLAG_BACKWARD) < 0) {
214 std::cout << "mxvk_ff_capture: failed to seek to start\n";
215 return false;
216 }
217
218 avcodec_flush_buffers(codecCtx);
219 av_packet_unref(packet);
220 av_frame_unref(frame);
221 av_frame_unref(swFrame);
222 return true;
223 }
224
226 if (!is_open() || !decodeNextFrame()) {
227 return false;
228 }
229 av_frame_unref(frame);
230 return true;
231 }
232
234#ifdef MXVK_CUDA
235 if (decoder_surface_copy_event != nullptr) {
236 cudaEventDestroy(decoder_surface_copy_event);
237 decoder_surface_copy_event = nullptr;
238 }
239 decoder_surface_barrier_logged = false;
240#endif
241 if (swsCtx != nullptr) {
242 sws_freeContext(swsCtx);
243 swsCtx = nullptr;
244 }
245 rgba16_conversion_logged = false;
246 if (hwDeviceCtx != nullptr) {
247 av_buffer_unref(&hwDeviceCtx);
248 }
249 if (swFrame != nullptr) {
250 av_frame_free(&swFrame);
251 }
252 if (frame != nullptr) {
253 av_frame_free(&frame);
254 }
255 if (packet != nullptr) {
256 av_packet_free(&packet);
257 }
258 if (codecCtx != nullptr) {
259 avcodec_free_context(&codecCtx);
260 }
261 if (formatCtx != nullptr) {
262 avformat_close_input(&formatCtx);
263 }
264 videoStream = -1;
265 frameWidth = 0;
266 frameHeight = 0;
267 frameFps = 30.0;
268 hwPixFmt = AV_PIX_FMT_NONE;
269 hardwareDecode = false;
270 hardwareDecodeDevice = -1;
271 }
272
273 bool VK_FF_Capture::readRgba(std::vector<uint8_t> &rgba, int &width, int &height, int &pitch, bool flipY) {
274 if (!is_open()) {
275 return false;
276 }
277
278 if (!decodeNextFrame()) {
279 return false;
280 }
281 const bool converted = convertFrameToRgba(frame, rgba, width, height, pitch, flipY);
282 av_frame_unref(frame);
283 return converted;
284 }
285
286 bool VK_FF_Capture::readRgba16(std::vector<uint16_t> &rgba, int &width, int &height, int &pitch, bool flipY) {
287 if (!is_open()) {
288 return false;
289 }
290
291 if (!decodeNextFrame()) {
292 return false;
293 }
294 const bool converted = convertFrameToRgba16(frame, rgba, width, height, pitch, flipY);
295 av_frame_unref(frame);
296 return converted;
297 }
298
299#ifdef MXVK_CUDA
300 bool VK_FF_Capture::readGpuRgba(cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream, bool flipY) {
301 if (!is_open()) {
302 return false;
303 }
304
305 if (!decodeNextFrame()) {
306 return false;
307 }
308 const bool converted = convertFrameToGpuRgba(frame, rgba, stream, flipY);
309 av_frame_unref(frame);
310 return converted;
311 }
312#endif
313
314 bool VK_FF_Capture::decodeNextFrame() {
315 bool draining = false;
316 while (true) {
317 const int receiveResult = avcodec_receive_frame(codecCtx, frame);
318 if (receiveResult == 0) {
319 return true;
320 }
321 if (receiveResult == AVERROR_EOF) {
322 return false;
323 }
324 if (receiveResult != AVERROR(EAGAIN)) {
325 return false;
326 }
327 if (draining) {
328 return false;
329 }
330
331 while (true) {
332 const int readResult = av_read_frame(formatCtx, packet);
333 if (readResult < 0) {
334 draining = true;
335 avcodec_send_packet(codecCtx, nullptr);
336 break;
337 }
338
339 if (packet->stream_index == videoStream) {
340 const int sendResult = avcodec_send_packet(codecCtx, packet);
341 av_packet_unref(packet);
342 if (sendResult == 0 || sendResult == AVERROR(EAGAIN)) {
343 break;
344 }
345 return false;
346 }
347 av_packet_unref(packet);
348 }
349 }
350 }
351
352 AVPixelFormat VK_FF_Capture::chooseHwFormat(AVCodecContext *ctx, const AVPixelFormat *formats) {
353 const auto *capture = static_cast<const VK_FF_Capture *>(ctx->opaque);
354 if (capture == nullptr) {
355 return formats[0];
356 }
357 return capture->selectHwFormat(formats);
358 }
359
360 AVPixelFormat VK_FF_Capture::selectHwFormat(const AVPixelFormat *formats) const {
361 for (const AVPixelFormat *format = formats; *format != AV_PIX_FMT_NONE; ++format) {
362 if (*format == hwPixFmt) {
363 return *format;
364 }
365 }
366 std::cout << "mxvk_ff_capture: requested CUDA pixel format is unavailable; decoder will use software frames\n";
367 return formats[0];
368 }
369
370 bool VK_FF_Capture::initHardwareDevice(const AVCodec *decoder, int cuda_device) {
371 const AVHWDeviceType deviceType = av_hwdevice_find_type_by_name("cuda");
372 if (deviceType == AV_HWDEVICE_TYPE_NONE) {
373 return false;
374 }
375
376 for (int index = 0;; ++index) {
377 const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, index);
378 if (config == nullptr) {
379 return false;
380 }
381 const bool hasDeviceCtx = (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0;
382 if (hasDeviceCtx && config->device_type == deviceType) {
383 hwPixFmt = config->pix_fmt;
384 break;
385 }
386 }
387
388 const std::string deviceName = cuda_device >= 0 ? std::to_string(cuda_device) : std::string{};
389 const char *device = deviceName.empty() ? nullptr : deviceName.c_str();
390 if (av_hwdevice_ctx_create(&hwDeviceCtx, deviceType, device, nullptr, 0) < 0) {
391 hwPixFmt = AV_PIX_FMT_NONE;
392 return false;
393 }
394
395 hardwareDecodeDevice = cuda_device;
396 return true;
397 }
398
399 bool VK_FF_Capture::convertFrameToRgba(const AVFrame *decodedFrame, std::vector<uint8_t> &rgba, int &width, int &height, int &pitch, bool flipY) {
400 const AVFrame *sourceFrame = decodedFrame;
401 if (decodedFrame->format == hwPixFmt && hwPixFmt != AV_PIX_FMT_NONE) {
402 av_frame_unref(swFrame);
403 if (av_hwframe_transfer_data(swFrame, decodedFrame, 0) < 0) {
404 std::cout << "mxvk_ff_capture: failed to transfer CUDA decoded frame to host memory\n";
405 return false;
406 }
407 sourceFrame = swFrame;
408 }
409
410 width = sourceFrame->width;
411 height = sourceFrame->height;
412 pitch = width * 4;
413 if (width <= 0 || height <= 0) {
414 return false;
415 }
416
417 rgba.resize(static_cast<size_t>(pitch) * static_cast<size_t>(height));
418 uint8_t *dstData[4] = {rgba.data(), nullptr, nullptr, nullptr};
419 int dstLinesize[4] = {pitch, 0, 0, 0};
420
421 swsCtx = sws_getCachedContext(swsCtx, sourceFrame->width, sourceFrame->height, static_cast<AVPixelFormat>(sourceFrame->format), width, height, AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr);
422 if (swsCtx == nullptr) {
423 return false;
424 }
425
426 const int scaledRows = sws_scale(swsCtx, sourceFrame->data, sourceFrame->linesize, 0, sourceFrame->height, dstData, dstLinesize);
427 if (scaledRows != height) {
428 return false;
429 }
430 if (flipY) {
431 flipRows(rgba, pitch);
432 }
433 return true;
434 }
435
436 bool VK_FF_Capture::convertFrameToRgba16(const AVFrame *decodedFrame, std::vector<uint16_t> &rgba, int &width, int &height, int &pitch, bool flipY) {
437 const AVFrame *sourceFrame = decodedFrame;
438 if (decodedFrame->format == hwPixFmt && hwPixFmt != AV_PIX_FMT_NONE) {
439 av_frame_unref(swFrame);
440 if (av_hwframe_transfer_data(swFrame, decodedFrame, 0) < 0) {
441 std::cout << "mxvk_ff_capture: failed to transfer CUDA decoded "
442 "HDR frame to host memory\n";
443 return false;
444 }
445 sourceFrame = swFrame;
446 }
447
448 const AVColorSpace colorSpace = sourceFrame->colorspace != AVCOL_SPC_UNSPECIFIED ? sourceFrame->colorspace : codecCtx->colorspace;
449 const AVColorRange colorRange = sourceFrame->color_range != AVCOL_RANGE_UNSPECIFIED ? sourceFrame->color_range : codecCtx->color_range;
450 if (colorSpace == AVCOL_SPC_BT2020_NCL && colorRange != AVCOL_RANGE_JPEG && convertBt2020Yuv10LimitedToRgba16(sourceFrame, rgba, width, height, pitch, flipY)) {
451 if (!rgba16_conversion_logged) {
452 std::cout << "mxvk_ff_capture: preserving 10-bit BT.2020 NCL input "
453 "through native RGBA16 conversion\n";
454 rgba16_conversion_logged = true;
455 }
456 return true;
457 }
458
459 width = sourceFrame->width;
460 height = sourceFrame->height;
461 pitch = width * 8;
462 if (width <= 0 || height <= 0) {
463 return false;
464 }
465
466 rgba.resize(static_cast<size_t>(width) * static_cast<size_t>(height) * 4U);
467 auto *bytes = reinterpret_cast<uint8_t *>(rgba.data());
468 uint8_t *dstData[4] = {bytes, nullptr, nullptr, nullptr};
469 int dstLinesize[4] = {pitch, 0, 0, 0};
470
471 swsCtx = sws_getCachedContext(swsCtx, sourceFrame->width, sourceFrame->height, static_cast<AVPixelFormat>(sourceFrame->format), width, height, AV_PIX_FMT_RGBA64, SWS_BILINEAR, nullptr, nullptr, nullptr);
472 if (swsCtx == nullptr) {
473 return false;
474 }
475
476 int sourceColorSpace = SWS_CS_DEFAULT;
477 switch (colorSpace) {
478 case AVCOL_SPC_BT2020_NCL:
479 case AVCOL_SPC_BT2020_CL:
480 sourceColorSpace = SWS_CS_BT2020;
481 break;
482 case AVCOL_SPC_BT709:
483 sourceColorSpace = SWS_CS_ITU709;
484 break;
485 case AVCOL_SPC_SMPTE170M:
486 case AVCOL_SPC_BT470BG:
487 sourceColorSpace = SWS_CS_ITU601;
488 break;
489 default:
490 break;
491 }
492 const int *sourceCoefficients = sws_getCoefficients(sourceColorSpace);
493 const int *destinationCoefficients = sws_getCoefficients(SWS_CS_BT2020);
494 sws_setColorspaceDetails(swsCtx, sourceCoefficients, colorRange == AVCOL_RANGE_JPEG ? 1 : 0, destinationCoefficients, 1, 0, 1 << 16, 1 << 16);
495 if (!rgba16_conversion_logged) {
496 std::cout << "mxvk_ff_capture: preserving high-bit-depth input through "
497 "FFmpeg RGBA64 conversion\n";
498 rgba16_conversion_logged = true;
499 }
500
501 const int scaledRows = sws_scale(swsCtx, sourceFrame->data, sourceFrame->linesize, 0, sourceFrame->height, dstData, dstLinesize);
502 if (scaledRows != height) {
503 return false;
504 }
505 if (flipY) {
506 std::vector<uint8_t> row(static_cast<size_t>(pitch));
507 for (int y = 0; y < height / 2; ++y) {
508 uint8_t *top = bytes + static_cast<size_t>(y) * pitch;
509 uint8_t *bottom = bytes + static_cast<size_t>(height - 1 - y) * pitch;
510 std::memcpy(row.data(), top, static_cast<size_t>(pitch));
511 std::memcpy(top, bottom, static_cast<size_t>(pitch));
512 std::memcpy(bottom, row.data(), static_cast<size_t>(pitch));
513 }
514 }
515 return true;
516 }
517
518#ifdef MXVK_CUDA
519 bool VK_FF_Capture::convertFrameToGpuRgba(const AVFrame *decodedFrame, cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream, bool flipY) {
520 if (decodedFrame->format == hwPixFmt && hwPixFmt != AV_PIX_FMT_NONE && decodedFrame->hw_frames_ctx != nullptr) {
521 const auto *framesContext = reinterpret_cast<const AVHWFramesContext *>(decodedFrame->hw_frames_ctx->data);
522 if (framesContext != nullptr && framesContext->sw_format == AV_PIX_FMT_NV12) {
523 const int width = decodedFrame->width;
524 const int height = decodedFrame->height;
525 if (width <= 0 || height <= 0 || decodedFrame->data[0] == nullptr || decodedFrame->data[1] == nullptr) {
526 return false;
527 }
528
529 gpuNv12.create(height + (height / 2), width, CV_8UC1);
530 cudaStream_t cudaStream = mxvk::cuda_stream_handle(stream);
531 if (decoder_surface_copy_event == nullptr) {
532 const cudaError_t event_result = cudaEventCreateWithFlags(&decoder_surface_copy_event, cudaEventDisableTiming);
533 if (event_result != cudaSuccess) {
534 std::cout << "mxvk_ff_capture: CUDA decoder-surface event creation failed: " << cudaGetErrorString(event_result) << "\n";
535 return false;
536 }
537 }
538
539 const auto synchronize_decoder_surface_copy = [&]() {
540 cudaError_t result = cudaEventSynchronize(decoder_surface_copy_event);
541 if (result != cudaSuccess) {
542 std::cout << "mxvk_ff_capture: CUDA decoder-surface copy barrier failed: " << cudaGetErrorString(result) << "\n";
543 result = cudaStreamSynchronize(cudaStream);
544 if (result != cudaSuccess) {
545 std::cout << "mxvk_ff_capture: CUDA capture-stream synchronization failed: " << cudaGetErrorString(result) << "\n";
546 return false;
547 }
548 }
549 if (!decoder_surface_barrier_logged) {
550 std::cout << "mxvk_ff_capture: asynchronous NVDEC surface-copy barrier active\n";
551 decoder_surface_barrier_logged = true;
552 }
553 return true;
554 };
555
556 cudaError_t result = cudaMemcpy2DAsync(gpuNv12.ptr(), gpuNv12.step, decodedFrame->data[0], static_cast<size_t>(decodedFrame->linesize[0]), static_cast<size_t>(width), static_cast<size_t>(height), cudaMemcpyDeviceToDevice, cudaStream);
557 if (result != cudaSuccess) {
558 std::cout << "mxvk_ff_capture: CUDA NV12 luma copy failed: " << cudaGetErrorString(result) << "\n";
559 cudaStreamSynchronize(cudaStream);
560 return false;
561 }
562
563 result = cudaMemcpy2DAsync(gpuNv12.ptr(height), gpuNv12.step, decodedFrame->data[1], static_cast<size_t>(decodedFrame->linesize[1]), static_cast<size_t>(width), static_cast<size_t>(height / 2), cudaMemcpyDeviceToDevice, cudaStream);
564 if (result != cudaSuccess) {
565 std::cout << "mxvk_ff_capture: CUDA NV12 chroma copy failed: " << cudaGetErrorString(result) << "\n";
566 cudaStreamSynchronize(cudaStream);
567 return false;
568 }
569
570 result = cudaEventRecord(decoder_surface_copy_event, cudaStream);
571 if (result != cudaSuccess) {
572 std::cout << "mxvk_ff_capture: CUDA decoder-surface event record failed: " << cudaGetErrorString(result) << "\n";
573 cudaStreamSynchronize(cudaStream);
574 return false;
575 }
576
577#ifdef MXVK_CUDA_NPP
578 gpuRgb.create(height, width, CV_8UC3);
579 gpuRgba.create(height, width, CV_8UC4);
580
581 const Npp8u *srcPlanes[2] = {
582 static_cast<const Npp8u *>(gpuNv12.ptr()),
583 static_cast<const Npp8u *>(gpuNv12.ptr(height)),
584 };
585 const NppiSize roi{width, height};
586 const NppStreamContext nppContext = makeNppStreamContext(cudaStream);
587 NppStatus nppStatus = nppiNV12ToRGB_8u_P2C3R_Ctx(srcPlanes, static_cast<int>(gpuNv12.step), static_cast<Npp8u *>(gpuRgb.ptr()), static_cast<int>(gpuRgb.step), roi, nppContext);
588 if (nppStatus != NPP_SUCCESS) {
589 std::cout << "mxvk_ff_capture: NPP NV12 to RGB conversion failed: " << static_cast<int>(nppStatus) << "\n";
590 synchronize_decoder_surface_copy();
591 return false;
592 }
593
594 const int rgbaOrder[4] = {0, 1, 2, 3};
595 nppStatus = nppiSwapChannels_8u_C3C4R_Ctx(static_cast<const Npp8u *>(gpuRgb.ptr()), static_cast<int>(gpuRgb.step), static_cast<Npp8u *>(gpuRgba.ptr()), static_cast<int>(gpuRgba.step), roi, rgbaOrder, 255, nppContext);
596 if (nppStatus != NPP_SUCCESS) {
597 std::cout << "mxvk_ff_capture: NPP RGB to RGBA conversion failed: " << static_cast<int>(nppStatus) << "\n";
598 synchronize_decoder_surface_copy();
599 return false;
600 }
601
602 if (flipY) {
603 cv::cuda::flip(gpuRgba, gpuFlippedRgba, 0, stream);
604 rgba = gpuFlippedRgba;
605 } else {
606 rgba = gpuRgba;
607 }
608 if (!synchronize_decoder_surface_copy()) {
609 return false;
610 }
611 return true;
612#else
613 try {
614 cv::cuda::cvtColor(gpuNv12, gpuRgba, cv::COLOR_YUV2RGBA_NV12, 0, stream);
615 if (flipY) {
616 cv::cuda::flip(gpuRgba, gpuFlippedRgba, 0, stream);
617 rgba = gpuFlippedRgba;
618 } else {
619 rgba = gpuRgba;
620 }
621 if (!synchronize_decoder_surface_copy()) {
622 return false;
623 }
624 return true;
625 } catch (const cv::Exception &e) {
626 if (!synchronize_decoder_surface_copy()) {
627 return false;
628 }
629 std::cout << "mxvk_ff_capture: CUDA NV12 to RGBA conversion failed; falling back to host conversion: " << e.what() << "\n";
630 }
631#endif
632 }
633 }
634
635 std::vector<uint8_t> hostRgba;
636 int width = 0;
637 int height = 0;
638 int pitch = 0;
639 if (!convertFrameToRgba(decodedFrame, hostRgba, width, height, pitch, flipY) || pitch != width * 4) {
640 return false;
641 }
642 cv::Mat hostFrame(height, width, CV_8UC4, hostRgba.data(), static_cast<size_t>(pitch));
643 gpuRgba.upload(hostFrame, stream);
644 stream.waitForCompletion();
645 rgba = gpuRgba;
646 return true;
647 }
648#endif
649
650 void VK_FF_Capture::flipRows(std::vector<uint8_t> &rgba, int pitch) const {
651 std::vector<uint8_t> row(static_cast<size_t>(pitch));
652 for (int top = 0, bottom = frameHeight - 1; top < bottom; ++top, --bottom) {
653 auto *topPtr = rgba.data() + static_cast<size_t>(top) * static_cast<size_t>(pitch);
654 auto *bottomPtr = rgba.data() + static_cast<size_t>(bottom) * static_cast<size_t>(pitch);
655 std::memcpy(row.data(), topPtr, static_cast<size_t>(pitch));
656 std::memcpy(topPtr, bottomPtr, static_cast<size_t>(pitch));
657 std::memcpy(bottomPtr, row.data(), static_cast<size_t>(pitch));
658 }
659 }
660
661} // namespace mxvk
void close()
Close the active file and release decoder resources.
bool is_open() const
Check whether a decoder is open.
int width() const
Source width in pixels.
int height() const
Source height in pixels.
bool open(const std::string &filename)
Open a video file.
bool readRgba16(std::vector< uint16_t > &rgba, int &width, int &height, int &pitch, bool flipY=false)
Decode the next frame as native-endian RGBA16.
VK_FF_Capture()=default
Construct a closed capture source.
bool readRgba(std::vector< uint8_t > &rgba, int &width, int &height, int &pitch, bool flipY=false)
Decode the next frame as tightly packed RGBA8.
bool skip()
Decode and discard the next video frame.
bool seek_start()
Seek the active video stream back to its beginning.
~VK_FF_Capture()
Close and release FFmpeg resources.
FFmpeg video-file capture with optional CUDA hardware decoding.
Small compatibility wrappers around OpenCV CUDA APIs.
bool convertBt2020Yuv10LimitedToRgba16(const AVFrame *source, std::vector< uint16_t > &rgba, int &width, int &height, int &pitch, bool flipY)
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31