Skip to content

Commit 3a32d2f

Browse files
authored
Tag NVDEC surfaces with their real pixel format (#1624)
1 parent fccfb9b commit 3a32d2f

7 files changed

Lines changed: 68 additions & 25 deletions

File tree

src/torchcodec/_core/BetaCudaDeviceInterface.cpp

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ cudaVideoSurfaceFormat get_preferred_surface_format(OutputDtype output_dtype) {
8484
// when decoding happens, but this interface can be used in
8585
// color-conversion-only mode.
8686
bool is_cpu_fallback(int format) {
87-
return format != AV_PIX_FMT_NV12 && format != AV_PIX_FMT_P016LE;
87+
return format != AV_PIX_FMT_NV12 && !is_nvdec_16bit_surface(format);
8888
}
8989

9090
static bool g_cuda_nvdec = register_device_interface(
@@ -825,9 +825,9 @@ UniqueAVFrame BetaCudaDeviceInterface::convert_cuda_frame_to_av_frame(
825825

826826
av_frame->width = width;
827827
av_frame->height = height;
828-
av_frame->format = (surface_format_ == cudaVideoSurfaceFormat_P016)
829-
? AV_PIX_FMT_P016LE
830-
: AV_PIX_FMT_NV12;
828+
av_frame->format = nvdec_pix_fmt(
829+
surface_format_ == cudaVideoSurfaceFormat_P016,
830+
static_cast<int>(video_format_.bit_depth_luma_minus8) + 8);
831831
av_frame->pts = disp_info.timestamp;
832832

833833
// TODONVDEC P2: We compute the duration based on average frame rate info, so
@@ -1177,6 +1177,8 @@ void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
11771177

11781178
UniqueAVFrame transferred_frame;
11791179
if (cpu_fallback) {
1180+
// TODO: uploaded fallback frames stay tagged P016 even for 10-/12-bit
1181+
// sources, so they report 16 bits where an NVDEC frame reports the truth.
11801182
AVPixelFormat target_pix_fmt = (output_dtype_ == OutputDtype::FLOAT32)
11811183
? AV_PIX_FMT_P016LE
11821184
: AV_PIX_FMT_NV12;
@@ -1190,8 +1192,8 @@ void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
11901192

11911193
STD_TORCH_CHECK(
11921194
gpu_frame.format == AV_PIX_FMT_NV12 ||
1193-
gpu_frame.format == AV_PIX_FMT_P016LE,
1194-
"Expected NV12 or P016LE format frame");
1195+
is_nvdec_16bit_surface(gpu_frame.format),
1196+
"Expected NV12 or 16-bit semi-planar format frame");
11951197

11961198
cudaStream_t producer_stream;
11971199
if (mode() == Mode::ColorConverterOnly) {
@@ -1210,21 +1212,13 @@ void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
12101212
// execrcized.
12111213
auto convert_frame = [&](std::optional<torch::stable::Tensor> pre_alloc)
12121214
-> torch::stable::Tensor {
1213-
bool is_p016 = (gpu_frame.format == AV_PIX_FMT_P016LE);
1214-
int bit_depth = 8;
1215-
if (is_p016) {
1216-
bit_depth = cpu_fallback
1217-
? codec_context_->bits_per_raw_sample
1218-
: static_cast<int>(video_format_.bit_depth_luma_minus8) + 8;
1219-
}
12201215
return convert_yuv_frame_to_rgb(
12211216
gpu_frame,
12221217
device_,
12231218
producer_stream,
12241219
pre_alloc,
12251220
original_dims,
1226-
is_p016,
1227-
bit_depth,
1221+
static_cast<AVPixelFormat>(gpu_frame.format),
12281222
cached_color_matrix_);
12291223
};
12301224

src/torchcodec/_core/CudaDeviceInterface.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,7 @@ void CudaDeviceInterface::convert_av_frame_to_frame_output(
345345
nvdec_stream,
346346
pre_allocated_output_tensor,
347347
FrameDims(av_frame.height, av_frame.width),
348-
/*isP016=*/false,
349-
/*bitDepth=*/8,
348+
actual_format,
350349
cached_color_matrix_);
351350
}
352351

src/torchcodec/_core/FFMPEGCommon.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,45 @@ extern "C" {
1616

1717
namespace facebook::torchcodec {
1818

19+
#if FFMPEG_HAS_P012
20+
// takes is_p016_surface as input instead of the actual NVDEC surface type so we
21+
// don't have to include the NVDEC headers here
22+
AVPixelFormat nvdec_pix_fmt(bool is_p016_surface, int bit_depth) {
23+
if (!is_p016_surface) {
24+
return AV_PIX_FMT_NV12;
25+
}
26+
switch (bit_depth) {
27+
case 10:
28+
return AV_PIX_FMT_P010LE;
29+
case 12:
30+
return AV_PIX_FMT_P012LE;
31+
default:
32+
return AV_PIX_FMT_P016LE;
33+
}
34+
}
35+
#else
36+
AVPixelFormat nvdec_pix_fmt(bool is_p016_surface, int bit_depth) {
37+
// TODO_API_BREAKDOWN P2: needs a comment about P012 missing and why it's
38+
// still OK to return P016LE.
39+
if (!is_p016_surface) {
40+
return AV_PIX_FMT_NV12;
41+
}
42+
return bit_depth == 10 ? AV_PIX_FMT_P010LE : AV_PIX_FMT_P016LE;
43+
}
44+
#endif // FFMPEG_HAS_P012
45+
46+
#if FFMPEG_HAS_P012
47+
bool is_nvdec_16bit_surface(int format) {
48+
return format == AV_PIX_FMT_P010LE || format == AV_PIX_FMT_P012LE ||
49+
format == AV_PIX_FMT_P016LE;
50+
}
51+
#else
52+
53+
bool is_nvdec_16bit_surface(int format) {
54+
return format == AV_PIX_FMT_P010LE || format == AV_PIX_FMT_P016LE;
55+
}
56+
#endif
57+
1958
AutoAVPacket::AutoAVPacket() : av_packet_(av_packet_alloc()) {
2059
STD_TORCH_CHECK(av_packet_ != nullptr, "Couldn't allocate avPacket.");
2160
}

src/torchcodec/_core/FFMPEGCommon.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ extern "C" {
4444
#define FFMPEG_HAS_CH_LAYOUT 0
4545
#endif
4646

47+
// FFmpeg 6 added AV_PIX_FMT_P012, the 12-bit sibling of P010.
48+
#if LIBAVUTIL_VERSION_MAJOR >= 58
49+
#define FFMPEG_HAS_P012 1
50+
#else
51+
#define FFMPEG_HAS_P012 0
52+
#endif
53+
4754
// FFmpeg 7.1 added avcodec_get_supported_config(), replacing the codec's
4855
// pix_fmts / sample_fmts / supported_samplerates / ch_layouts arrays.
4956
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100)
@@ -61,6 +68,9 @@ extern "C" {
6168

6269
namespace facebook::torchcodec {
6370

71+
AVPixelFormat nvdec_pix_fmt(bool is_p016_surface, int bit_depth);
72+
bool is_nvdec_16bit_surface(int format);
73+
6474
// FFMPEG uses special delete functions for some structures. These template
6575
// functions are used to pass into unique_ptr as custom deleters so we can
6676
// wrap FFMPEG structs with unique_ptrs for ease of use.

src/torchcodec/_core/PacketDecoder.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@ FramePlanes frame_to_planes(
140140
int64_t bytes_per_sample = (comp.depth > 8) ? 2 : 1;
141141
int64_t linesize = av_frame.linesize[comp.plane];
142142
STD_TORCH_CHECK(
143-
comp.shift == 0 && comp.depth <= 16 && linesize > 0 &&
144-
comp.step % bytes_per_sample == 0 &&
143+
comp.depth <= 16 && linesize > 0 && comp.step % bytes_per_sample == 0 &&
145144
linesize % bytes_per_sample == 0,
146145
"Cannot expose component ",
147146
c,

src/torchcodec/_core/color_conversion.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,13 @@ torch::stable::Tensor convert_yuv_frame_to_rgb(
194194
cudaStream_t producer_stream,
195195
std::optional<torch::stable::Tensor> pre_allocated_output_tensor,
196196
const FrameDims& output_dims,
197-
bool is_p016,
198-
int bit_depth,
197+
AVPixelFormat pix_fmt,
199198
CachedColorMatrix& cached_color_matrix) {
199+
bool is_p016 = is_nvdec_16bit_surface(pix_fmt);
200+
const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(pix_fmt);
201+
STD_TORCH_CHECK(desc != nullptr, "Unknown pixel format on decoded frame");
202+
int bit_depth = desc->comp[0].depth;
203+
200204
float out_scale = is_p016 ? 65535.0f : 255.0f;
201205
OutputDtype out_dtype = is_p016 ? OutputDtype::FLOAT32 : OutputDtype::UINT8;
202206

src/torchcodec/_core/color_conversion.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,16 @@ void launch_p016_to_rgb16_kernel(
7979

8080
// Convert a YUV frame (NV12 or P016) on GPU to an interleaved RGB tensor.
8181
//
82-
// isP016: true for P016 (uint16 I/O), false for NV12 (uint8 I/O).
83-
// bitDepth: 8 for NV12, actual bit depth (10 or 12) for P016.
8482
// outputDims: desired output size; if the frame was rounded up to even
8583
// dimensions, the result is cropped back to outputDims.
84+
// pixFmt: the format the samples are actually in (NV12, P010, P012, P016).
8685
torch::stable::Tensor convert_yuv_frame_to_rgb(
8786
const AVFrame& av_frame,
8887
const StableDevice& device,
8988
cudaStream_t producer_stream,
9089
std::optional<torch::stable::Tensor> pre_allocated_output_tensor,
9190
const FrameDims& output_dims,
92-
bool is_p016,
93-
int bit_depth,
91+
AVPixelFormat pix_fmt,
9492
CachedColorMatrix& cached_color_matrix);
9593

9694
// Compute the RGB -> YUV color conversion matrix (for encoding).

0 commit comments

Comments
 (0)