80 lines
2.0 KiB
Go
80 lines
2.0 KiB
Go
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
|
|
}
|