Skip to content

Commit 1e0f188

Browse files
authored
Reset resampler on seek to fix off-by-one audio sample count (#1604)
1 parent bb65fcb commit 1e0f188

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
@@ -476,6 +476,11 @@ CpuDeviceInterface::maybe_flush_audio_buffers() {
476476
/*length=*/actual_num_remaining_samples);
477477
}
478478

479+
void CpuDeviceInterface::flush() {
480+
DeviceInterface::flush();
481+
swr_context_.reset();
482+
}
483+
479484
std::string CpuDeviceInterface::get_details() {
480485
return std::string("CPU Device Interface.");
481486
}

src/torchcodec/_core/CpuDeviceInterface.h

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

46+
void flush() override;
47+
4648
void convert_av_frame_to_frame_output(
4749
const AVFrame& av_frame,
4850
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)