PR #25771 opened by Qiaobin Lin (qblin)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25771
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25771.patch

## Summary

`cuvidDecodePicture()` only reports errors that prevent a picture from being 
submitted. Errors the hardware detects later, including concealed ones, were 
never reflected in `AVFrame.decode_error_flags`, so callers could not 
distinguish a corrupted frame from a clean one the way they can with software 
decoding.

Query `cuvidGetDecodeStatus()` after `cuvidMapVideoFrame()`, which blocks until 
decoding completes per the NVDEC API guide, and map `cuvidDecodeStatus_Error` / 
`cuvidDecodeStatus_Error_Concealed` to `FF_DECODE_ERROR_INVALID_BITSTREAM` / 
`FF_DECODE_ERROR_CONCEALMENT_ACTIVE`. `CUDA_ERROR_NOT_SUPPORTED` is cached; 
`InProgress` is logged once and leaves the frame unflagged.

Same patch as the ML submission, rebased onto current master; no functional 
change (`range-diff` identical).

<https://patchwork.ffmpeg.org/project/ffmpeg/patch/[email protected]/>

## Testing

RTX 4070, driver 570.124.04:

- Clean 1920x1080 H.264, 30 s over RTSP/TCP: 894 frames, no flags, 0 decode 
errors.
- Corrupt H.264: NVDEC and software flag the same PTS with 
`CONCEALMENT_ACTIVE`; software also sets `DECODE_SLICES` on the multi-slice 
sample.
- Corrupt HEVC: NVDEC flags `CONCEALMENT_ACTIVE`; software did not flag the 
corrupted frame.
- Low-delay H.264/HEVC, one thread: no `InProgress`, corrupt frames still 
flagged; same for H.264 with B-frames.
- UDP/RTP packet loss: two concealed frames flagged; `AV_FRAME_FLAG_CORRUPT` 
unset.
- MPEG-1/2/4, VP8, VP9: `CUDA_ERROR_NOT_SUPPORTED`, one log per decoder.
- AV1: clean frames return `Success`; corrupt frames return status 9 
(`Error_Concealed`), mapped to `CONCEALMENT_ACTIVE`.
- AV1 + film grain (`--film-grain-test=1`, 50 frames, all `apply_grain=1`, 
`cf->idx != cf->ref_idx`): clean frames return `Success`; a corrupt frame 
returns status 9 via `cf->idx`.
- JPEG: `InProgress` on clean samples; a corrupt frame is flagged only when a 
final status is available; best-effort, logged once.
- `-xerror`: `AVERROR_INVALIDDATA` on corrupt, 0 on clean.

Samples: H.264/HEVC generated with libx264/libx265; corruption injected into 
`mdat` or by removing a slice NAL. AV1 Y4M generated with ffmpeg; aomenc 
produced the film-grain bitstream.

## Known limits

- CUARRAY output has no equivalent blocking call, so it is not covered.
- Legacy cuvid is not covered; reference-picture errors are not propagated, and 
`FF_DECODE_ERROR_DECODE_SLICES` is not set.
- No FATE test is possible without NVIDIA hardware; CI cannot exercise this 
path.
- `cuvidGetDecodeStatus()` is documented for H.264, HEVC and JPEG only. The 
tested driver also returns a final status for AV1, but that is outside the 
documented contract.
- Status 8 (`Error`, not concealed) was not observed in testing; the mapping 
follows the SDK enum.

## Notes

- `cuvidGetDecodeStatus` is already a required `LOAD_SYMBOL` in the ffnvcodec 
loader; no new runtime dependency.
- The query is not gated on `err_recognition`; hardware and software should set 
these flags under the same conditions.
- Flagged frames now produce the same `corrupt decoded frame` warning as 
software decoding, and `-xerror` fails on them.
- One driver call per output picture, in the existing map context; no extra 
synchronization.



>From 94cf187af3e43605756e98ee8b7ba33c0297a020 Mon Sep 17 00:00:00 2001
From: Qiaobin Lin <[email protected]>
Date: Wed, 21 Oct 2026 20:16:51 +0800
Subject: [PATCH] avcodec/nvdec: set decode_error_flags for hardware decode
 errors

cuvidDecodePicture() only reports errors that prevent a picture from
being submitted. Errors detected by the hardware afterwards, including
concealed ones, went unreported, so callers could not distinguish a
corrupted frame from a clean one the way they can with software
decoding.

cuvidMapVideoFrame() blocks until decoding of the picture is complete,
per the NVDEC Video Decoder API Programming Guide. Query
cuvidGetDecodeStatus() after it returns and map
cuvidDecodeStatus_Error to FF_DECODE_ERROR_INVALID_BITSTREAM and
cuvidDecodeStatus_Error_Concealed to
FF_DECODE_ERROR_CONCEALMENT_ACTIVE. On the tested driver the status is
final for H.264 and HEVC. JPEG can still report InProgress after the
map; those frames are left unflagged. CUarray output has no equivalent
blocking call, so it is not covered.

cuvidGetDecodeStatus() is documented for H.264, HEVC and JPEG only,
but the tested driver also returns a final status for AV1, including
film grain. For unsupported codecs or GPUs, cuvidGetDecodeStatus()
returns CUDA_ERROR_NOT_SUPPORTED; the result is cached so that an
unsupported decoder only pays for one failed call.

Only errors for the output picture are reported: reference-picture
errors are not propagated, and FF_DECODE_ERROR_DECODE_SLICES is not
set. ffmpeg now logs "corrupt decoded frame" and -xerror fails on such
frames, as with software decoding.

Signed-off-by: Qiaobin Lin <[email protected]>
---
 libavcodec/nvdec.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/libavcodec/nvdec.c b/libavcodec/nvdec.c
index d928e3c7a1a..0b22e4d4c9b 100644
--- a/libavcodec/nvdec.c
+++ b/libavcodec/nvdec.c
@@ -46,6 +46,9 @@
 #define cudaVideoSurfaceFormat_YUV444_16Bit 3
 #endif
 
+/* The CUresult enum in nv-codec-headers lacks this value. */
+#define NVDEC_CUDA_ERROR_NOT_SUPPORTED 801
+
 typedef struct NVDECDecoder {
     CUvideodecoder decoder;
 
@@ -58,8 +61,16 @@ typedef struct NVDECDecoder {
     CudaFunctions *cudl;
     CuvidFunctions *cvdl;
 
+    /* Cleared if cuvidGetDecodeStatus reports the codec/GPU as
+     * unsupported. */
+    int decode_status_supported;
+
+    /* Log InProgress once; JPEG can keep reporting it. */
+    int in_progress_logged;
+
     int unsafe_output;
     int opaque_output;
+
 #ifdef NVDEC_HAVE_OPAQUE_OUTPUT_SUPPORT
     atomic_int *surface_in_use;
     int          num_surfaces;
@@ -235,6 +246,8 @@ static int nvdec_decoder_create(NVDECDecoder **out, 
AVBufferRef *hw_device_ref,
     if (!decoder)
         return AVERROR(ENOMEM);
 
+    decoder->decode_status_supported = 1;
+
     decoder->hw_device_ref = av_buffer_ref(hw_device_ref);
     if (!decoder->hw_device_ref) {
         ret = AVERROR(ENOMEM);
@@ -794,6 +807,53 @@ static void nvdec_opaque_release_slot(void *opaque, 
uint8_t *data)
 }
 #endif
 
+/*
+ * The decoder's CUDA context must be current and decoding must have
+ * completed. idx is the surface index for cuvidMapVideoFrame and the
+ * picture index for cuvidDecodePicture.
+ */
+static void nvdec_set_decode_error_flags(void *logctx, NVDECDecoder *decoder,
+                                         int idx, AVFrame *frame)
+{
+    CUVIDGETDECODESTATUS status = { 0 };
+    CUresult res;
+
+    if (!decoder->decode_status_supported)
+        return;
+
+    res = decoder->cvdl->cuvidGetDecodeStatus(decoder->decoder, idx, &status);
+    if (res == NVDEC_CUDA_ERROR_NOT_SUPPORTED) {
+        decoder->decode_status_supported = 0;
+        av_log(logctx, AV_LOG_VERBOSE,
+               "Decode status reporting is not supported for this codec on "
+               "this GPU, decode errors will not be reported\n");
+        return;
+    }
+    if (res != CUDA_SUCCESS)
+        return;
+
+    switch (status.decodeStatus) {
+    case cuvidDecodeStatus_Error:
+        frame->decode_error_flags |= FF_DECODE_ERROR_INVALID_BITSTREAM;
+        break;
+    case cuvidDecodeStatus_Error_Concealed:
+        frame->decode_error_flags |= FF_DECODE_ERROR_CONCEALMENT_ACTIVE;
+        break;
+    case cuvidDecodeStatus_InProgress:
+        /* H.264/HEVC report a final status after the blocking map. JPEG
+         * can still report InProgress here, even for clean pictures. */
+        if (!decoder->in_progress_logged) {
+            av_log(logctx, AV_LOG_DEBUG,
+                   "Decode status still InProgress after a blocking map, "
+                   "errors not reported for such frames\n");
+            decoder->in_progress_logged = 1;
+        }
+        break;
+    default:
+        break;
+    }
+}
+
 static int nvdec_retrieve_data(void *logctx, AVFrame *frame)
 {
     FrameDecodeData  *fdd = frame->private_ref;
@@ -922,6 +982,10 @@ static int nvdec_retrieve_data(void *logctx, AVFrame 
*frame)
     if (ret < 0)
         goto finish;
 
+    /* cuvidMapVideoFrame() blocks until decoding completes (NVDEC API
+     * guide). */
+    nvdec_set_decode_error_flags(logctx, decoder, cf->idx, frame);
+
     ret = av_buffer_replace(&frame->hw_frames_ctx, 
decoder->real_hw_frames_ref);
     if (ret < 0)
         goto copy_fail;
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to