Skip to content

Commit d3b759c

Browse files
authored
Programmatically enforce resolution of OuputDtype (#1444)
1 parent 1a2ee9a commit d3b759c

5 files changed

Lines changed: 47 additions & 25 deletions

File tree

src/torchcodec/_core/SingleStreamDecoder.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -578,17 +578,25 @@ void SingleStreamDecoder::addVideoStream(
578578
activeStreamIndex_, customFrameMappings.value());
579579
}
580580

581-
// Resolve AUTO once based on the source bit depth, so downstream code
582-
// only has to handle UINT8 / FLOAT32.
583-
//
584-
// TODO: programmatically enforce that OutputDtype is "resolved" (i.e. not
585-
// AUTO) past this point.
586-
if (streamInfo.videoStreamOptions.outputDtype == OutputDtype::AUTO) {
587-
const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(
588-
static_cast<AVPixelFormat>(streamInfo.stream->codecpar->format));
589-
streamInfo.videoStreamOptions.outputDtype =
590-
(desc != nullptr && desc->comp[0].depth > 8) ? OutputDtype::FLOAT32
591-
: OutputDtype::UINT8;
581+
// Resolve the user-facing OutputDtypeConfig (which may be AUTO) into an
582+
// OutputDtype that downstream code can use directly.
583+
// TODO_HDR: This is basically our heuristic that defines how we identify HDR
584+
// videos, we might want to refine it.
585+
switch (streamInfo.videoStreamOptions.outputDtypeConfig) {
586+
case OutputDtypeConfig::UINT8:
587+
streamInfo.videoStreamOptions.outputDtype = OutputDtype::UINT8;
588+
break;
589+
case OutputDtypeConfig::FLOAT32:
590+
streamInfo.videoStreamOptions.outputDtype = OutputDtype::FLOAT32;
591+
break;
592+
case OutputDtypeConfig::AUTO: {
593+
const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(
594+
static_cast<AVPixelFormat>(streamInfo.stream->codecpar->format));
595+
streamInfo.videoStreamOptions.outputDtype =
596+
(desc != nullptr && desc->comp[0].depth > 8) ? OutputDtype::FLOAT32
597+
: OutputDtype::UINT8;
598+
break;
599+
}
592600
}
593601

594602
// Set preRotationDims_ for the active stream. These are the raw encoded
@@ -1515,10 +1523,9 @@ torch::stable::Tensor SingleStreamDecoder::maybePermuteHWC2CHW(
15151523
}
15161524
}
15171525

1526+
// TODO_HDR: should this be a single call along with maybePermuteHWC2CHW?
15181527
torch::stable::Tensor SingleStreamDecoder::maybeConvertToFloat32(
15191528
torch::stable::Tensor& tensor) {
1520-
// AUTO has been resolved to UINT8 or FLOAT32 in addVideoStream, so we only
1521-
// need to handle FLOAT32 here.
15221529
OutputDtype outputDtype =
15231530
streamInfos_[activeStreamIndex_].videoStreamOptions.outputDtype;
15241531
if (outputDtype != OutputDtype::FLOAT32) {

src/torchcodec/_core/SingleStreamDecoder.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,7 @@ class FORCE_PUBLIC_VISIBILITY SingleStreamDecoder {
277277
torch::stable::Tensor maybePermuteHWC2CHW(torch::stable::Tensor& hwcTensor);
278278

279279
// Converts the tensor to float32 and normalizes to [0, 1] when the active
280-
// stream's outputDtype calls for it (FLOAT32 always, or AUTO when the tensor
281-
// is uint16). Otherwise returns the input unchanged.
280+
// stream's outputDtype is FLOAT32. Otherwise returns the input unchanged.
282281
torch::stable::Tensor maybeConvertToFloat32(torch::stable::Tensor& tensor);
283282

284283
FrameOutput convertAVFrameToFrameOutput(

src/torchcodec/_core/StreamOptions.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,20 @@ enum ColorConversionLibrary {
2121
SWSCALE
2222
};
2323

24-
// Controls the dtype of decoded frame tensors.
24+
// The resolved output dtype. Only UINT8 or FLOAT32 — no AUTO.
25+
// All code downstream of addVideoStream() should use this.
26+
enum class OutputDtype { UINT8, FLOAT32 };
27+
28+
// The user-facing output dtype config, which may include AUTO.
29+
// AUTO is resolved in addVideoStream() into an OutputDtype.
2530
// UINT8: Always output uint8 tensors (default, backward compatible). Uses an
2631
// 8-bit / RGB24 intermediate.
2732
// FLOAT32: Always output float32 tensors normalized to [0, 1]. Uses a 16-bit /
2833
// RGB48 intermediate so the YUV->RGB matrix output is preserved at
2934
// full precision through the float cast, regardless of source bit
3035
// depth.
3136
// AUTO: Output uint8 for SDR (<=8-bit) sources, float32 for HDR (>8-bit).
32-
// Resolved upstream in addVideoStream so downstream code only ever sees
33-
// UINT8 / FLOAT32.
34-
enum class OutputDtype { UINT8, FLOAT32, AUTO };
37+
enum class OutputDtypeConfig { UINT8, FLOAT32, AUTO };
3538

3639
struct VideoStreamOptions {
3740
VideoStreamOptions() {}
@@ -59,9 +62,12 @@ struct VideoStreamOptions {
5962
// Device variant (e.g., "nvdec", "ffmpeg")
6063
std::string_view deviceVariant = "default";
6164

62-
// Controls the dtype of decoded frame tensors. Default UINT8 preserves
63-
// existing behavior; FLOAT32 and AUTO are used for high-bit-depth output
64-
// (e.g. HDR).
65+
// The user-specified output dtype config. May be AUTO, which gets resolved
66+
// in addVideoStream() into outputDtype below.
67+
OutputDtypeConfig outputDtypeConfig = OutputDtypeConfig::UINT8;
68+
69+
// Set by addVideoStream() after resolving AUTO. All downstream code should
70+
// read this field.
6571
OutputDtype outputDtype = OutputDtype::UINT8;
6672

6773
// Encoding options

src/torchcodec/_core/custom_ops.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -516,18 +516,27 @@ void _add_video_stream(
516516
std::optional<torch::stable::Tensor>
517517
custom_frame_mappings_keyframe_indices = std::nullopt,
518518
std::optional<std::string> color_conversion_library = std::nullopt,
519+
// TODO_HDR: see other TODO, should default be uint8 instead? Maybe it
520+
// shoudn't even be optional?
521+
// videoStreamOptions.outputDtype defaults to UINT8 so if
522+
// output_dtype is nullopt, the videoStreamOptions will default to UINT8.
523+
// But that's pretty implicit and suggests maybe this shouldn't be optional
524+
// at all.
525+
// TODO_UINT8 Also this currently takes strings but surely the public API
526+
// will want to support torch.dtype object, we should figure out when to do
527+
// the conversion.
519528
std::optional<std::string> output_dtype = std::nullopt) {
520529
VideoStreamOptions videoStreamOptions;
521530
videoStreamOptions.ffmpegThreadCount = num_threads;
522531

523532
if (output_dtype.has_value()) {
524533
const std::string& val = *output_dtype;
525534
if (val == "uint8") {
526-
videoStreamOptions.outputDtype = OutputDtype::UINT8;
535+
videoStreamOptions.outputDtypeConfig = OutputDtypeConfig::UINT8;
527536
} else if (val == "float32") {
528-
videoStreamOptions.outputDtype = OutputDtype::FLOAT32;
537+
videoStreamOptions.outputDtypeConfig = OutputDtypeConfig::FLOAT32;
529538
} else if (val == "auto") {
530-
videoStreamOptions.outputDtype = OutputDtype::AUTO;
539+
videoStreamOptions.outputDtypeConfig = OutputDtypeConfig::AUTO;
531540
} else {
532541
STD_TORCH_CHECK(
533542
false,

src/torchcodec/_core/ops.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def add_video_stream(
8484
custom_frame_mappings: (
8585
tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None
8686
) = None,
87+
# TODO_HDR: should default be None or uint8??
8788
output_dtype: str | None = None,
8889
) -> None:
8990
custom_frame_mappings_pts: torch.Tensor | None = None

0 commit comments

Comments
 (0)