From 9c2483ef48906f5e756309ffee81a98e9832de79 Mon Sep 17 00:00:00 2001 From: CaIon Date: Sat, 13 Dec 2025 23:57:32 +0800 Subject: [PATCH] fix(audio): improve WAV duration calculation with enhanced PCM size handling --- common/audio.go | 59 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/common/audio.go b/common/audio.go index 1eb1079de..466cd2c79 100644 --- a/common/audio.go +++ b/common/audio.go @@ -71,15 +71,66 @@ func getMP3Duration(r io.Reader) (float64, error) { // getWAVDuration 解析 WAV 文件头以获取时长。 func getWAVDuration(r io.ReadSeeker) (float64, error) { + // 1. 强制复位指针 + r.Seek(0, io.SeekStart) + dec := wav.NewDecoder(r) + + // IsValidFile 会读取 fmt 块 if !dec.IsValidFile() { return 0, errors.New("invalid wav file") } - d, err := dec.Duration() - if err != nil { - return 0, errors.Wrap(err, "failed to get wav duration") + + // 尝试寻找 data 块 + if err := dec.FwdToPCM(); err != nil { + return 0, errors.Wrap(err, "failed to find PCM data chunk") } - return d.Seconds(), nil + + pcmSize := int64(dec.PCMSize) + + // 如果读出来的 Size 是 0,尝试用文件大小反推 + if pcmSize == 0 { + // 获取文件总大小 + currentPos, _ := r.Seek(0, io.SeekCurrent) // 当前通常在 data chunk header 之后 + endPos, _ := r.Seek(0, io.SeekEnd) + fileSize := endPos + + // 恢复位置(虽然如果不继续读也没关系) + r.Seek(currentPos, io.SeekStart) + + // 数据区大小 ≈ 文件总大小 - 当前指针位置(即Header大小) + // 注意:FwdToPCM 成功后,CurrentPos 应该刚好指向 Data 区数据的开始 + // 或者是 Data Chunk ID + Size 之后。 + // WAV Header 一般 44 字节。 + if fileSize > 44 { + // 如果 FwdToPCM 成功,Reader 应该位于 data 块的数据起始处 + // 所以剩余的所有字节理论上都是音频数据 + pcmSize = fileSize - currentPos + + // 简单的兜底:如果算出来还是负数或0,强制按文件大小-44计算 + if pcmSize <= 0 { + pcmSize = fileSize - 44 + } + } + } + + numChans := int64(dec.NumChans) + bitDepth := int64(dec.BitDepth) + sampleRate := float64(dec.SampleRate) + + if sampleRate == 0 || numChans == 0 || bitDepth == 0 { + return 0, errors.New("invalid wav header metadata") + } + + bytesPerFrame := numChans * (bitDepth / 8) + if bytesPerFrame == 0 { + return 0, errors.New("invalid byte depth calculation") + } + + totalFrames := pcmSize / bytesPerFrame + + durationSeconds := float64(totalFrames) / sampleRate + return durationSeconds, nil } // getFLACDuration 解析 FLAC 文件的 STREAMINFO 块。