package audio import ( "encoding/base64" "encoding/binary" "errors" "math" "time" ) func IsPCM16Aligned(data []byte) bool { return len(data)%2 == 0 } func Base64Encode(data []byte) string { return base64.StdEncoding.EncodeToString(data) } func Base64Decode(s string) ([]byte, error) { return base64.StdEncoding.DecodeString(s) } func ChunkBytes(data []byte, max int) [][]byte { if max <= 0 || len(data) == 0 { return nil } out := [][]byte{} for len(data) > 0 { n := max if len(data) < n { n = len(data) } cp := append([]byte(nil), data[:n]...) out = append(out, cp) data = data[n:] } return out } func ChunkPCM16ByDuration(data []byte, rate int, dur time.Duration) [][]byte { if rate <= 0 || dur <= 0 { return nil } bytes := int(dur.Seconds()*float64(rate)) * 2 if bytes < 2 { bytes = 2 } return ChunkBytes(data, bytes) } func ResamplePCM16MonoLinear(data []byte, fromRate, toRate int) ([]byte, error) { if len(data) == 0 { return nil, nil } if len(data)%2 != 0 { return nil, errors.New("pcm16 data is not 2-byte aligned") } if fromRate <= 0 || toRate <= 0 { return nil, errors.New("sample rates must be positive") } if fromRate == toRate { return append([]byte(nil), data...), nil } inN := len(data) / 2 outN := int(math.Round(float64(inN) * float64(toRate) / float64(fromRate))) if outN < 1 { outN = 1 } out := make([]byte, outN*2) for i := 0; i < outN; i++ { pos := float64(i) * float64(fromRate) / float64(toRate) idx := int(math.Floor(pos)) frac := pos - float64(idx) if idx >= inN-1 { binary.LittleEndian.PutUint16(out[i*2:], binary.LittleEndian.Uint16(data[(inN-1)*2:])) continue } a := int16(binary.LittleEndian.Uint16(data[idx*2:])) b := int16(binary.LittleEndian.Uint16(data[(idx+1)*2:])) v := float64(a) + (float64(b)-float64(a))*frac if v > math.MaxInt16 { v = math.MaxInt16 } if v < math.MinInt16 { v = math.MinInt16 } binary.LittleEndian.PutUint16(out[i*2:], uint16(int16(math.Round(v)))) } return out, nil }