package audio import ( "io" "github.com/zeozeozeo/gomplerate" ) // resamplingReader 包装 io.Reader 并提供音频重采样 type resamplingReader struct { source io.Reader resampler *gomplerate.Resampler buffer []byte // 原始数据缓冲区 eof bool } // newResamplingReader 创建重采样 reader // sourceRate: 源采样率(如 16000) // targetRate: 目标采样率(如 44100) // channels: 声道数(1=单声道, 2=立体声) func newResamplingReader(src io.Reader, sourceRate, targetRate, channels int) (io.Reader, error) { resampler, err := gomplerate.NewResampler(channels, sourceRate, targetRate) if err != nil { return nil, err } return &resamplingReader{ source: src, resampler: resampler, buffer: make([]byte, 0, 8192), }, nil } func (r *resamplingReader) Read(p []byte) (n int, err error) { const chunkSize = 4096 // 读取原始数据 if !r.eof && len(r.buffer) < chunkSize { buf := make([]byte, chunkSize) rn, readErr := r.source.Read(buf) if readErr != nil { if readErr == io.EOF { r.eof = true } else { return 0, readErr } } if rn > 0 { r.buffer = append(r.buffer, buf[:rn]...) } } // 没有数据了 if len(r.buffer) == 0 { return 0, io.EOF } // 将字节转换为 int16 int16Data := bytesToInt16(r.buffer) // 重采样 resampled := r.resampler.ResampleInt16(int16Data) // 转回字节 output := int16ToBytes(resampled) // 如果输出太小,继续读取 if len(output) < len(p) && !r.eof { return r.Read(p) } // 复制到输出 n = copy(p, output) // 更新缓冲区 remainingSamples := (len(r.buffer) / 2) - len(int16Data) if remainingSamples > 0 { r.buffer = r.buffer[len(int16Data)*2:] } else { r.buffer = r.buffer[:0] } return n, nil } // bytesToInt16 将字节切片转换为 int16 切片 func bytesToInt16(b []byte) []int16 { result := make([]int16, len(b)/2) for i := 0; i < len(result); i++ { result[i] = int16(b[i*2]) | int16(b[i*2+1])<<8 } return result } // int16ToBytes 将 int16 切片转换为字节切片 func int16ToBytes(i []int16) []byte { result := make([]byte, len(i)*2) for n, v := range i { result[n*2] = byte(v) result[n*2+1] = byte(v >> 8) } return result } // needsResample 检查是否需要重采样 func needsResample(sourceRate, targetRate int) bool { return sourceRate != targetRate }