Skip to content

Commit 2fbf280

Browse files
committed
Reset resampler on seek to fix off-by-one audio sample count
1 parent e8f2347 commit 2fbf280

3 files changed

Lines changed: 30 additions & 0 deletions

File tree

src/torchcodec/_core/CpuDeviceInterface.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,11 @@ CpuDeviceInterface::maybe_flush_audio_buffers() {
478478
/*length=*/actual_num_remaining_samples);
479479
}
480480

481+
void CpuDeviceInterface::flush() {
482+
DeviceInterface::flush();
483+
swr_context_.reset();
484+
}
485+
481486
std::string CpuDeviceInterface::get_details() {
482487
return std::string("CPU Device Interface.");
483488
}

src/torchcodec/_core/CpuDeviceInterface.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class CpuDeviceInterface : public DeviceInterface {
4040
virtual std::optional<torch::stable::Tensor> maybe_flush_audio_buffers()
4141
override;
4242

43+
void flush() override;
44+
4345
void convert_av_frame_to_frame_output(
4446
const AVFrame& av_frame,
4547
FrameOutput& frame_output,

test/test_decoders.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3082,6 +3082,29 @@ def test_downsample_empty_frame(self):
30823082
frames_44100_to_8000.data, frames_8000.data, atol=0.03, rtol=0
30833083
)
30843084

3085+
def test_resample_seek_sample_count(self):
3086+
# Non-regression test for https://github.com/meta-pytorch/torchcodec/issues/1601
3087+
# When resampling, the swresample context buffers samples and tracks a
3088+
# fractional sample position across calls. If it isn't reset on a
3089+
# mid-stream seek, stale state leaks into the next range decode and the
3090+
# output sample count can be off by one.
3091+
# The exact condition in which this happens is unclear to me but claude
3092+
# managed to find this test that reproduces consistently - and the fix
3093+
# was to reset the swresample context on a mid-stream seek, which seems
3094+
# like a very normal thing to do.
3095+
asset = SINE_MONO_S32_44100
3096+
assert asset.sample_rate == 44_100
3097+
assert asset.duration_seconds == 4
3098+
3099+
out_sample_rate = 16_000
3100+
decoder = AudioDecoder(asset.path, sample_rate=out_sample_rate)
3101+
3102+
decoder.get_samples_played_in_range(start_seconds=2.0, stop_seconds=4.8)
3103+
tail = decoder.get_samples_played_in_range(start_seconds=3.6, stop_seconds=4.8)
3104+
3105+
# [3.6, 4.0) of audio at out_sample_rate.
3106+
assert tail.data.shape[1] == round(0.4 * out_sample_rate)
3107+
30853108
def test_decode_s16_ffmpeg4(self):
30863109
# Non-regression test for https://github.com/pytorch/torchcodec/issues/843
30873110
# Ensures that decoding s16 on FFmpeg4 handles

0 commit comments

Comments
 (0)