From 8417dc2c98dbc24bda03783831bbcb9836c002df Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 1 Dec 2025 15:18:33 +0900 Subject: [PATCH] audio/internal/convert: bug fix: a returned stream's Length and actual read buffer length didn't match Closes #3352 --- audio/audio.go | 8 +++--- audio/internal/convert/resampling.go | 31 +++++++++++++++-------- audio/internal/convert/resampling_test.go | 17 +++++++++++++ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/audio/audio.go b/audio/audio.go index a4a878277..4c494a92c 100644 --- a/audio/audio.go +++ b/audio/audio.go @@ -556,7 +556,7 @@ func (h *hookerImpl) AppendHookOnBeforeUpdate(f func() error) { } // ResampleReader converts the sample rate of the given singed 16bit integer, little-endian, 2 channels (stereo) stream. -// size is the length of the source stream in bytes. +// size is the length of the source stream in bytes. 0 indicates the length is unknown. // from is the original sample rate. // to is the target sample rate. // @@ -573,7 +573,7 @@ func ResampleReader(source io.Reader, size int64, from, to int) io.Reader { } // ResampleReaderF32 converts the sample rate of the given 32bit float, little-endian, 2 channels (stereo) stream. -// size is the length of the source stream in bytes. +// size is the length of the source stream in bytes. 0 indicates the length is unknown. // from is the original sample rate. // to is the target sample rate. // @@ -590,7 +590,7 @@ func ResampleReaderF32(source io.Reader, size int64, from, to int) io.Reader { } // Resample converts the sample rate of the given singed 16bit integer, little-endian, 2 channels (stereo) stream. -// size is the length of the source stream in bytes. +// size is the length of the source stream in bytes. 0 indicates the length is unknown. // from is the original sample rate. // to is the target sample rate. // @@ -605,7 +605,7 @@ func Resample(source io.ReadSeeker, size int64, from, to int) io.ReadSeeker { } // ResampleF32 converts the sample rate of the given 32bit float, little-endian, 2 channels (stereo) stream. -// size is the length of the source stream in bytes. +// size is the length of the source stream in bytes. 0 indicates the length is unknown. // from is the original sample rate. // to is the target sample rate. // diff --git a/audio/internal/convert/resampling.go b/audio/internal/convert/resampling.go index ba96c02cb..274eced60 100644 --- a/audio/internal/convert/resampling.go +++ b/audio/internal/convert/resampling.go @@ -80,8 +80,11 @@ func sinc01(x float64) float64 { } type Resampling struct { - source io.Reader - size int64 + source io.Reader + + // size is the length of the source stream in bytes. 0 indicates the length is unknown. + size int64 + from int to int bitDepthInBytes int @@ -199,10 +202,8 @@ func (r *Resampling) src(i int64) (float64, float64, error) { if r.eofBufIndex == r.srcBlock && ii >= int64(len(r.srcBufL[r.srcBlock])-1) { err = io.EOF } - if _, ok := r.source.(io.Seeker); ok { - if r.size/sizePerSample <= i { - err = io.EOF - } + if r.size > 0 && r.size/sizePerSample <= i { + err = io.EOF } return r.srcBufL[r.srcBlock][ii], r.srcBufR[r.srcBlock][ii], err } @@ -215,8 +216,7 @@ func (r *Resampling) at(t int64) (float64, float64, error) { startN = 0 } endN := int64(tInSrc + windowSize) - lv := 0.0 - rv := 0.0 + var lv, rv float64 var eof bool for n := startN; n <= endN; n++ { srcL, srcR, err := r.src(n) @@ -259,11 +259,13 @@ func (r *Resampling) Read(b []byte) (int, error) { n := len(b) / size * size switch r.bitDepthInBytes { case 2: - for i := 0; i < n/size; i++ { + for i := range n / size { ldata, rdata, err := r.at(r.pos/int64(size) + int64(i)) if err != nil && err != io.EOF { return 0, err } + // EOF from the at method indicates that the source reaches the end, and doesn't indicate the resampled data ends. + // Continue the loop even if EOF is returned. if err == io.EOF { r.eof = true } @@ -273,9 +275,14 @@ func (r *Resampling) Read(b []byte) (int, error) { b[4*i+1] = byte(l16 >> 8) b[4*i+2] = byte(r16) b[4*i+3] = byte(r16 >> 8) + // If the source is an io.Seeker and the length is known, check whether the resampled data ends (#3352). + if r.size > 0 && r.pos+int64(size*i) >= r.Length() { + n = size * i + break + } } case 4: - for i := 0; i < n/size; i++ { + for i := range n / size { ldata, rdata, err := r.at(r.pos/int64(size) + int64(i)) if err != nil && err != io.EOF { return 0, err @@ -295,6 +302,10 @@ func (r *Resampling) Read(b []byte) (int, error) { b[8*i+5] = byte(r32b >> 8) b[8*i+6] = byte(r32b >> 16) b[8*i+7] = byte(r32b >> 24) + if r.size > 0 && r.pos+int64(size*i) >= r.Length() { + n = size * i + break + } } default: panic("not reached") diff --git a/audio/internal/convert/resampling_test.go b/audio/internal/convert/resampling_test.go index f42c35357..cbb1dab86 100644 --- a/audio/internal/convert/resampling_test.go +++ b/audio/internal/convert/resampling_test.go @@ -164,3 +164,20 @@ func TestResampling(t *testing.T) { }) } } + +// Issue #3352 +func TestResamplingLen(t *testing.T) { + buf := make([]byte, 8*48000) + src := bytes.NewReader(buf) + resampled := convert.NewResampling(src, int64(len(buf)), 48000, 96000, 4) + if got, want := resampled.Length(), int64(len(buf)*2); got != want { + t.Errorf("got: %d, want: %d", got, want) + } + decodedBuf, err := io.ReadAll(resampled) + if err != nil { + t.Fatal(err) + } + if got, want := len(decodedBuf), int(len(buf)*2); got != want { + t.Errorf("got: %d, want: %d", got, want) + } +}