Skip to content

Commit c9e019d

Browse files
committed
Don't let device interfaces take ownership of the frame they convert
convert_av_frame_to_frame_output() took a non-const UniqueAVFrame&, which let implementations steal the caller's frame. Both CUDA interfaces did: BetaCudaDeviceInterface moved out of it, and CudaDeviceInterface reassigned it with the result of maybe_convert_av_frame_to_nv12_or_rgb24(). That was invisible under SingleStreamDecoder, whose frame is a loop local that dies right after conversion. It is not invisible under the building-block ops, where the frame is owned by a handle that outlives the call: the frame ends up freed twice, corrupting the heap. Take the frame by const reference all the way down the conversion path, so a callee physically cannot move from it or reset it. Where an implementation needs a different frame, it now owns that one separately and binds a const reference to whichever of the two applies -- the idiom convert_audio_av_frame_to_frame_output() already used. maybe_convert_av_frame_to_nv12_or_rgb24() returns a null frame to mean "no conversion needed" instead of handing back the input.
1 parent 3f7692b commit c9e019d

14 files changed

Lines changed: 41 additions & 31 deletions

src/torchcodec/_core/BetaCudaDeviceInterface.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ void BetaCudaDeviceInterface::flush() {
824824
}
825825

826826
UniqueAVFrame BetaCudaDeviceInterface::transfer_cpu_frame_to_gpu(
827-
UniqueAVFrame& cpu_frame,
827+
const UniqueAVFrame& cpu_frame,
828828
AVPixelFormat target_pix_fmt) {
829829
// This is called in the context of the CPU fallback: the frame was decoded on
830830
// the CPU, and in this function we convert that frame into NV12 or P016
@@ -967,7 +967,7 @@ UniqueAVFrame BetaCudaDeviceInterface::transfer_cpu_frame_to_gpu(
967967
}
968968

969969
void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
970-
UniqueAVFrame& av_frame,
970+
const UniqueAVFrame& av_frame,
971971
FrameOutput& frame_output,
972972
std::optional<torch::stable::Tensor> pre_allocated_output_tensor) {
973973
if (cpu_fallback_) {
@@ -1003,15 +1003,16 @@ void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
10031003
// may round them up to even.
10041004
FrameDims original_dims(av_frame->height, av_frame->width);
10051005

1006-
UniqueAVFrame gpu_frame;
1006+
// On the CPU fallback we own the GPU frame we just created; otherwise the
1007+
// input frame is already what we need, and it's only borrowed.
1008+
UniqueAVFrame transferred_frame;
10071009
if (cpu_fallback_) {
10081010
AVPixelFormat target_pix_fmt = (output_dtype_ == OutputDtype::FLOAT32)
10091011
? AV_PIX_FMT_P016LE
10101012
: AV_PIX_FMT_NV12;
1011-
gpu_frame = transfer_cpu_frame_to_gpu(av_frame, target_pix_fmt);
1012-
} else {
1013-
gpu_frame = std::move(av_frame);
1013+
transferred_frame = transfer_cpu_frame_to_gpu(av_frame, target_pix_fmt);
10141014
}
1015+
const UniqueAVFrame& gpu_frame = cpu_fallback_ ? transferred_frame : av_frame;
10151016

10161017
STD_TORCH_CHECK(
10171018
gpu_frame->format == AV_PIX_FMT_NV12 ||

src/torchcodec/_core/BetaCudaDeviceInterface.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class BetaCudaDeviceInterface : public DeviceInterface {
5252
OutputDtype requested_dtype) const override;
5353

5454
void convert_av_frame_to_frame_output(
55-
UniqueAVFrame& av_frame,
55+
const UniqueAVFrame& av_frame,
5656
FrameOutput& frame_output,
5757
std::optional<torch::stable::Tensor> pre_allocated_output_tensor)
5858
override;
@@ -92,7 +92,7 @@ class BetaCudaDeviceInterface : public DeviceInterface {
9292
const CUVIDPARSERDISPINFO& disp_info);
9393

9494
UniqueAVFrame transfer_cpu_frame_to_gpu(
95-
UniqueAVFrame& cpu_frame,
95+
const UniqueAVFrame& cpu_frame,
9696
AVPixelFormat target_pix_fmt);
9797

9898
void apply_rotation(

src/torchcodec/_core/ColorConverter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ ColorConverter::ColorConverter(
4545
/*resized_output_dims=*/std::nullopt);
4646
}
4747

48-
torch::stable::Tensor ColorConverter::convert(UniqueAVFrame& av_frame) {
48+
torch::stable::Tensor ColorConverter::convert(const UniqueAVFrame& av_frame) {
4949
FrameOutput frame_output;
5050
device_interface_->convert_av_frame_to_frame_output(
5151
av_frame, frame_output, std::nullopt);

src/torchcodec/_core/ColorConverter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class FORCE_PUBLIC_VISIBILITY ColorConverter {
2121
const StableDevice& device = StableDevice(kStableCPU),
2222
std::string_view device_variant = "default");
2323

24-
torch::stable::Tensor convert(UniqueAVFrame& av_frame);
24+
torch::stable::Tensor convert(const UniqueAVFrame& av_frame);
2525

2626
private:
2727
std::unique_ptr<DeviceInterface> device_interface_;

src/torchcodec/_core/CpuDeviceInterface.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ ColorConversionLibrary CpuDeviceInterface::get_color_conversion_library(
200200
}
201201

202202
void CpuDeviceInterface::convert_av_frame_to_frame_output(
203-
UniqueAVFrame& av_frame,
203+
const UniqueAVFrame& av_frame,
204204
FrameOutput& frame_output,
205205
std::optional<torch::stable::Tensor> pre_allocated_output_tensor) {
206206
STD_TORCH_CHECK(initialized_, "CpuDeviceInterface was not initialized.");
@@ -223,7 +223,7 @@ void CpuDeviceInterface::convert_av_frame_to_frame_output(
223223
// Dimension order of the preAllocatedOutputTensor must be HWC, regardless of
224224
// `dimension_order` parameter. It's up to callers to re-shape it if needed.
225225
void CpuDeviceInterface::convert_video_av_frame_to_frame_output(
226-
UniqueAVFrame& av_frame,
226+
const UniqueAVFrame& av_frame,
227227
FrameOutput& frame_output,
228228
std::optional<torch::stable::Tensor> pre_allocated_output_tensor) {
229229
// Note that we ignore the dimensions from the metadata; we don't even bother
@@ -350,7 +350,7 @@ CpuDeviceInterface::convert_av_frame_to_tensor_using_filter_graph(
350350
}
351351

352352
void CpuDeviceInterface::convert_audio_av_frame_to_frame_output(
353-
UniqueAVFrame& src_av_frame,
353+
const UniqueAVFrame& src_av_frame,
354354
FrameOutput& frame_output) {
355355
AVSampleFormat src_sample_format =
356356
static_cast<AVSampleFormat>(src_av_frame->format);

src/torchcodec/_core/CpuDeviceInterface.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class CpuDeviceInterface : public DeviceInterface {
4141
override;
4242

4343
void convert_av_frame_to_frame_output(
44-
UniqueAVFrame& av_frame,
44+
const UniqueAVFrame& av_frame,
4545
FrameOutput& frame_output,
4646
std::optional<torch::stable::Tensor> pre_allocated_output_tensor)
4747
override;
@@ -59,11 +59,11 @@ class CpuDeviceInterface : public DeviceInterface {
5959

6060
private:
6161
void convert_audio_av_frame_to_frame_output(
62-
UniqueAVFrame& src_av_frame,
62+
const UniqueAVFrame& src_av_frame,
6363
FrameOutput& frame_output);
6464

6565
void convert_video_av_frame_to_frame_output(
66-
UniqueAVFrame& av_frame,
66+
const UniqueAVFrame& av_frame,
6767
FrameOutput& frame_output,
6868
std::optional<torch::stable::Tensor> pre_allocated_output_tensor);
6969

src/torchcodec/_core/CudaDeviceInterface.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ void CudaDeviceInterface::register_hardware_device_with_codec(
146146
}
147147

148148
UniqueAVFrame CudaDeviceInterface::maybe_convert_av_frame_to_nv12_or_rgb24(
149-
UniqueAVFrame& av_frame) {
149+
const UniqueAVFrame& av_frame) {
150150
// We need FFmpeg filters to handle those conversion cases which are not
151151
// directly implemented in CUDA or CPU device interface (in case of a
152152
// fallback).
@@ -155,7 +155,7 @@ UniqueAVFrame CudaDeviceInterface::maybe_convert_av_frame_to_nv12_or_rgb24(
155155
// skipping filters context as CPU device interface will handle everything for
156156
// us.
157157
if (av_frame->format != AV_PIX_FMT_CUDA) {
158-
return std::move(av_frame);
158+
return UniqueAVFrame{};
159159
}
160160

161161
auto hw_frames_ctx =
@@ -169,7 +169,7 @@ UniqueAVFrame CudaDeviceInterface::maybe_convert_av_frame_to_nv12_or_rgb24(
169169

170170
// If the frame is already in NV12 format, we don't need to do anything.
171171
if (actual_format == AV_PIX_FMT_NV12) {
172-
return std::move(av_frame);
172+
return UniqueAVFrame{};
173173
}
174174

175175
AVPixelFormat output_format;
@@ -237,18 +237,21 @@ UniqueAVFrame CudaDeviceInterface::maybe_convert_av_frame_to_nv12_or_rgb24(
237237
}
238238

239239
void CudaDeviceInterface::convert_av_frame_to_frame_output(
240-
UniqueAVFrame& av_frame,
240+
const UniqueAVFrame& input_av_frame,
241241
FrameOutput& frame_output,
242242
std::optional<torch::stable::Tensor> pre_allocated_output_tensor) {
243243
validate_pre_allocated_tensor_shape(
244244
pre_allocated_output_tensor,
245-
FrameDims(av_frame->height, av_frame->width));
245+
FrameDims(input_av_frame->height, input_av_frame->width));
246246

247247
has_decoded_frame_ = true;
248248

249249
// All of our CUDA decoding assumes NV12 format. We handle non-NV12 formats by
250250
// converting them to NV12.
251-
av_frame = maybe_convert_av_frame_to_nv12_or_rgb24(av_frame);
251+
UniqueAVFrame converted_av_frame =
252+
maybe_convert_av_frame_to_nv12_or_rgb24(input_av_frame);
253+
const UniqueAVFrame& av_frame =
254+
converted_av_frame ? converted_av_frame : input_av_frame;
252255

253256
if (av_frame->format != AV_PIX_FMT_CUDA) {
254257
// The frame's format is AV_PIX_FMT_CUDA if and only if its content is on

src/torchcodec/_core/CudaDeviceInterface.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class CudaDeviceInterface : public DeviceInterface {
4141
AVCodecContext* codec_context) override;
4242

4343
void convert_av_frame_to_frame_output(
44-
UniqueAVFrame& av_frame,
44+
const UniqueAVFrame& av_frame,
4545
FrameOutput& frame_output,
4646
std::optional<torch::stable::Tensor> pre_allocated_output_tensor)
4747
override;
@@ -63,9 +63,10 @@ class CudaDeviceInterface : public DeviceInterface {
6363
private:
6464
// Our CUDA decoding code assumes NV12 format. In order to handle other
6565
// kinds of input, we need to convert them to NV12. Our current implementation
66-
// does this using filtergraph.
66+
// does this using filtergraph. Returns a null frame when no conversion is
67+
// needed, i.e. when the input frame can be used as-is.
6768
UniqueAVFrame maybe_convert_av_frame_to_nv12_or_rgb24(
68-
UniqueAVFrame& av_frame);
69+
const UniqueAVFrame& av_frame);
6970

7071
// We sometimes encounter frames that cannot be decoded on the CUDA device.
7172
// Rather than erroring out, we decode them on the CPU.

src/torchcodec/_core/DeviceInterface.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,13 @@ class DeviceInterface {
9898
return requested_dtype;
9999
}
100100

101+
// The caller retains ownership of the frame, and keeps using it after this
102+
// call returns (the building-block ops hand out frames that live in Python).
103+
// Implementations must not take ownership of it, and must not free it.
104+
// Implementations needing a different frame, e.g. after a format conversion,
105+
// must allocate their own and leave this one alone.
101106
virtual void convert_av_frame_to_frame_output(
102-
UniqueAVFrame& av_frame,
107+
const UniqueAVFrame& av_frame,
103108
FrameOutput& frame_output,
104109
std::optional<torch::stable::Tensor> pre_allocated_output_tensor =
105110
std::nullopt) = 0;

src/torchcodec/_core/SingleStreamDecoder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1573,7 +1573,7 @@ UniqueAVFrame SingleStreamDecoder::decode_av_frame(
15731573
// --------------------------------------------------------------------------
15741574

15751575
FrameOutput SingleStreamDecoder::convert_av_frame_to_frame_output(
1576-
UniqueAVFrame& av_frame,
1576+
const UniqueAVFrame& av_frame,
15771577
std::optional<torch::stable::Tensor> pre_allocated_output_tensor) {
15781578
// Convert the frame to tensor.
15791579
FrameOutput frame_output;

0 commit comments

Comments
 (0)