13#include <cuda_runtime_api.h>
14#include <opencv2/cudaarithm.hpp>
16#include <nppi_color_conversion.h>
17#include <nppi_data_exchange_and_initialization.h>
25 if (rational.num <= 0 || rational.den <= 0) {
28 return av_q2d(rational);
31#if defined(MXVK_CUDA) && defined(MXVK_CUDA_NPP)
32 [[nodiscard]] NppStreamContext makeNppStreamContext(cudaStream_t stream) {
33 NppStreamContext context{};
34 context.hStream = stream;
35 cudaGetDevice(&context.nCudaDeviceId);
37 cudaDeviceProp properties{};
38 cudaGetDeviceProperties(&properties, context.nCudaDeviceId);
39 context.nMultiProcessorCount = properties.multiProcessorCount;
40 context.nMaxThreadsPerMultiProcessor = properties.maxThreadsPerMultiProcessor;
41 context.nMaxThreadsPerBlock = properties.maxThreadsPerBlock;
42 context.nSharedMemPerBlock = properties.sharedMemPerBlock;
44 cudaDeviceGetAttribute(
45 &context.nCudaDevAttrComputeCapabilityMajor,
46 cudaDevAttrComputeCapabilityMajor,
47 context.nCudaDeviceId);
48 cudaDeviceGetAttribute(
49 &context.nCudaDevAttrComputeCapabilityMinor,
50 cudaDevAttrComputeCapabilityMinor,
51 context.nCudaDeviceId);
52 cudaStreamGetFlags(stream, &context.nStreamFlags);
65 if (avformat_open_input(&formatCtx, filename.c_str(),
nullptr,
nullptr) < 0) {
66 std::cout << std::format(
"mxvk_ff_capture: failed to open file: {}\n", filename);
71 if (avformat_find_stream_info(formatCtx,
nullptr) < 0) {
72 std::cout << std::format(
"mxvk_ff_capture: failed to read stream info: {}\n", filename);
77 const int streamIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1,
nullptr, 0);
78 if (streamIndex < 0) {
79 std::cout << std::format(
"mxvk_ff_capture: no video stream found: {}\n", filename);
83 videoStream = streamIndex;
85 AVStream *stream = formatCtx->streams[videoStream];
86 const AVCodecParameters *codecParams = stream->codecpar;
87 const AVCodec *decoder = avcodec_find_decoder(codecParams->codec_id);
88 if (decoder ==
nullptr) {
89 std::cout <<
"mxvk_ff_capture: no decoder found for video stream\n";
94 codecCtx = avcodec_alloc_context3(decoder);
95 if (codecCtx ==
nullptr || avcodec_parameters_to_context(codecCtx, codecParams) < 0) {
96 std::cout <<
"mxvk_ff_capture: failed to create decoder context\n";
101 codecCtx->opaque =
this;
102 if (initHardwareDevice(decoder)) {
103 codecCtx->get_format = &VK_FF_Capture::chooseHwFormat;
104 codecCtx->hw_device_ctx = av_buffer_ref(hwDeviceCtx);
105 hardwareDecode = codecCtx->hw_device_ctx !=
nullptr;
108 if (avcodec_open2(codecCtx, decoder,
nullptr) < 0) {
109 std::cout <<
"mxvk_ff_capture: failed to open decoder\n";
114 packet = av_packet_alloc();
115 frame = av_frame_alloc();
116 swFrame = av_frame_alloc();
117 if (packet ==
nullptr || frame ==
nullptr || swFrame ==
nullptr) {
118 std::cout <<
"mxvk_ff_capture: failed to allocate decoder frames\n";
123 frameWidth = codecCtx->width;
124 frameHeight = codecCtx->height;
125 frameFps = rationalToDouble(stream->avg_frame_rate);
126 if (frameFps <= 0.0) {
127 frameFps = rationalToDouble(stream->r_frame_rate);
129 if (frameFps <= 0.0) {
133 std::cout << std::format(
134 "mxvk_ff_capture: opened {} ({}x{}, {:.3f} fps, decode={})\n",
139 hardwareDecode ?
"cuda" :
"software");
144 if (swsCtx !=
nullptr) {
145 sws_freeContext(swsCtx);
148 if (hwDeviceCtx !=
nullptr) {
149 av_buffer_unref(&hwDeviceCtx);
151 if (swFrame !=
nullptr) {
152 av_frame_free(&swFrame);
154 if (frame !=
nullptr) {
155 av_frame_free(&frame);
157 if (packet !=
nullptr) {
158 av_packet_free(&packet);
160 if (codecCtx !=
nullptr) {
161 avcodec_free_context(&codecCtx);
163 if (formatCtx !=
nullptr) {
164 avformat_close_input(&formatCtx);
170 hwPixFmt = AV_PIX_FMT_NONE;
171 hardwareDecode =
false;
179 if (!decodeNextFrame()) {
182 const bool converted = convertFrameToRgba(frame, rgba,
width,
height, pitch, flipY);
183 av_frame_unref(frame);
188 bool VK_FF_Capture::readGpuRgba(cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream,
bool flipY) {
193 if (!decodeNextFrame()) {
196 const bool converted = convertFrameToGpuRgba(frame, rgba, stream, flipY);
197 av_frame_unref(frame);
202 bool VK_FF_Capture::decodeNextFrame() {
203 bool draining =
false;
205 const int receiveResult = avcodec_receive_frame(codecCtx, frame);
206 if (receiveResult == 0) {
209 if (receiveResult == AVERROR_EOF) {
212 if (receiveResult != AVERROR(EAGAIN)) {
220 const int readResult = av_read_frame(formatCtx, packet);
221 if (readResult < 0) {
223 avcodec_send_packet(codecCtx,
nullptr);
227 if (packet->stream_index == videoStream) {
228 const int sendResult = avcodec_send_packet(codecCtx, packet);
229 av_packet_unref(packet);
230 if (sendResult == 0 || sendResult == AVERROR(EAGAIN)) {
235 av_packet_unref(packet);
240 AVPixelFormat VK_FF_Capture::chooseHwFormat(AVCodecContext *ctx,
const AVPixelFormat *formats) {
241 const auto *capture =
static_cast<const VK_FF_Capture *
>(ctx->opaque);
242 if (capture ==
nullptr) {
245 return capture->selectHwFormat(formats);
248 AVPixelFormat VK_FF_Capture::selectHwFormat(
const AVPixelFormat *formats)
const {
249 for (
const AVPixelFormat *format = formats; *format != AV_PIX_FMT_NONE; ++format) {
250 if (*format == hwPixFmt) {
254 std::cout <<
"mxvk_ff_capture: requested CUDA pixel format is unavailable; decoder will use software frames\n";
258 bool VK_FF_Capture::initHardwareDevice(
const AVCodec *decoder) {
259 const AVHWDeviceType deviceType = av_hwdevice_find_type_by_name(
"cuda");
260 if (deviceType == AV_HWDEVICE_TYPE_NONE) {
264 for (
int index = 0;; ++index) {
265 const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, index);
266 if (config ==
nullptr) {
269 const bool hasDeviceCtx = (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0;
270 if (hasDeviceCtx && config->device_type == deviceType) {
271 hwPixFmt = config->pix_fmt;
276 if (av_hwdevice_ctx_create(&hwDeviceCtx, deviceType,
nullptr,
nullptr, 0) < 0) {
277 hwPixFmt = AV_PIX_FMT_NONE;
284 bool VK_FF_Capture::convertFrameToRgba(
const AVFrame *decodedFrame, std::vector<uint8_t> &rgba,
int &width,
int &height,
int &pitch,
bool flipY) {
285 const AVFrame *sourceFrame = decodedFrame;
286 if (decodedFrame->format == hwPixFmt && hwPixFmt != AV_PIX_FMT_NONE) {
287 av_frame_unref(swFrame);
288 if (av_hwframe_transfer_data(swFrame, decodedFrame, 0) < 0) {
289 std::cout <<
"mxvk_ff_capture: failed to transfer CUDA decoded frame to host memory\n";
292 sourceFrame = swFrame;
295 width = sourceFrame->width;
296 height = sourceFrame->height;
302 rgba.resize(
static_cast<size_t>(pitch) *
static_cast<size_t>(
height));
303 uint8_t *dstData[4] = {rgba.data(),
nullptr,
nullptr,
nullptr};
304 int dstLinesize[4] = {pitch, 0, 0, 0};
306 swsCtx = sws_getCachedContext(
310 static_cast<AVPixelFormat
>(sourceFrame->format),
318 if (swsCtx ==
nullptr) {
322 const int scaledRows = sws_scale(swsCtx, sourceFrame->data, sourceFrame->linesize, 0, sourceFrame->height, dstData, dstLinesize);
323 if (scaledRows !=
height) {
327 flipRows(rgba, pitch);
333 bool VK_FF_Capture::convertFrameToGpuRgba(
const AVFrame *decodedFrame, cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream,
bool flipY) {
334 if (decodedFrame->format == hwPixFmt && hwPixFmt != AV_PIX_FMT_NONE && decodedFrame->hw_frames_ctx !=
nullptr) {
335 const auto *framesContext =
reinterpret_cast<const AVHWFramesContext *
>(decodedFrame->hw_frames_ctx->data);
336 if (framesContext !=
nullptr && framesContext->sw_format == AV_PIX_FMT_NV12) {
337 const int width = decodedFrame->width;
338 const int height = decodedFrame->height;
344 cudaStream_t cudaStream = mxvk::cuda_stream_handle(stream);
345 cudaError_t result = cudaMemcpy2DAsync(
348 decodedFrame->data[0],
349 static_cast<size_t>(decodedFrame->linesize[0]),
350 static_cast<size_t>(
width),
351 static_cast<size_t>(
height),
352 cudaMemcpyDeviceToDevice,
354 if (result != cudaSuccess) {
355 std::cout <<
"mxvk_ff_capture: CUDA NV12 luma copy failed: " << cudaGetErrorString(result) <<
"\n";
359 result = cudaMemcpy2DAsync(
362 decodedFrame->data[1],
363 static_cast<size_t>(decodedFrame->linesize[1]),
364 static_cast<size_t>(
width),
365 static_cast<size_t>(
height / 2),
366 cudaMemcpyDeviceToDevice,
368 if (result != cudaSuccess) {
369 std::cout <<
"mxvk_ff_capture: CUDA NV12 chroma copy failed: " << cudaGetErrorString(result) <<
"\n";
377 const Npp8u *srcPlanes[2] = {
378 static_cast<const Npp8u *
>(gpuNv12.ptr()),
379 static_cast<const Npp8u *
>(gpuNv12.ptr(
height)),
382 const NppStreamContext nppContext = makeNppStreamContext(cudaStream);
383 NppStatus nppStatus = nppiNV12ToRGB_8u_P2C3R_Ctx(
385 static_cast<int>(gpuNv12.step),
386 static_cast<Npp8u *
>(gpuRgb.ptr()),
387 static_cast<int>(gpuRgb.step),
390 if (nppStatus != NPP_SUCCESS) {
391 std::cout <<
"mxvk_ff_capture: NPP NV12 to RGB conversion failed: " <<
static_cast<int>(nppStatus) <<
"\n";
395 const int rgbaOrder[4] = {0, 1, 2, 3};
396 nppStatus = nppiSwapChannels_8u_C3C4R_Ctx(
397 static_cast<const Npp8u *
>(gpuRgb.ptr()),
398 static_cast<int>(gpuRgb.step),
399 static_cast<Npp8u *
>(gpuRgba.ptr()),
400 static_cast<int>(gpuRgba.step),
405 if (nppStatus != NPP_SUCCESS) {
406 std::cout <<
"mxvk_ff_capture: NPP RGB to RGBA conversion failed: " <<
static_cast<int>(nppStatus) <<
"\n";
411 cv::cuda::flip(gpuRgba, gpuFlippedRgba, 0, stream);
412 rgba = gpuFlippedRgba;
419 cv::cuda::cvtColor(gpuNv12, gpuRgba, cv::COLOR_YUV2RGBA_NV12, 0, stream);
421 cv::cuda::flip(gpuRgba, gpuFlippedRgba, 0, stream);
422 rgba = gpuFlippedRgba;
427 }
catch (
const cv::Exception &e) {
428 std::cout <<
"mxvk_ff_capture: CUDA NV12 to RGBA conversion failed; falling back to host conversion: " << e.what() <<
"\n";
434 std::vector<uint8_t> hostRgba;
438 if (!convertFrameToRgba(decodedFrame, hostRgba,
width,
height, pitch, flipY) || pitch !=
width * 4) {
441 cv::Mat hostFrame(
height,
width, CV_8UC4, hostRgba.data(),
static_cast<size_t>(pitch));
442 gpuRgba.upload(hostFrame, stream);
443 stream.waitForCompletion();
449 void VK_FF_Capture::flipRows(std::vector<uint8_t> &rgba,
int pitch)
const {
450 std::vector<uint8_t> row(
static_cast<size_t>(pitch));
451 for (
int top = 0, bottom = frameHeight - 1; top < bottom; ++top, --bottom) {
452 auto *topPtr = rgba.data() +
static_cast<size_t>(top) *
static_cast<size_t>(pitch);
453 auto *bottomPtr = rgba.data() +
static_cast<size_t>(bottom) *
static_cast<size_t>(pitch);
454 std::memcpy(row.data(), topPtr,
static_cast<size_t>(pitch));
455 std::memcpy(topPtr, bottomPtr,
static_cast<size_t>(pitch));
456 std::memcpy(bottomPtr, row.data(),
static_cast<size_t>(pitch));
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.
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.
~VK_FF_Capture()
Close and release FFmpeg resources.
FFmpeg video-file capture with optional CUDA hardware decoding.
Small compatibility wrappers around OpenCV CUDA APIs.
double rationalToDouble(AVRational rational)
Utilities for loading and saving PNG images.