mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 18:46:11 +00:00
143 lines
2.6 KiB
Go
143 lines
2.6 KiB
Go
package media
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/giongto35/cloud-game/v3/pkg/resampler"
|
|
)
|
|
|
|
type ResampleAlgo uint8
|
|
|
|
const (
|
|
ResampleNearest ResampleAlgo = iota
|
|
ResampleLinear
|
|
ResampleSpeex
|
|
)
|
|
|
|
type buffer struct {
|
|
raw samples
|
|
scratch samples
|
|
buckets []bucket
|
|
srcHz int
|
|
dstHz int
|
|
bi int
|
|
algo ResampleAlgo
|
|
|
|
resampler *resampler.Resampler
|
|
}
|
|
|
|
type bucket struct {
|
|
mem samples
|
|
ms float32
|
|
p int
|
|
dst int
|
|
}
|
|
|
|
func newBuffer(frames []float32, hz int) (*buffer, error) {
|
|
if hz < 2000 || len(frames) == 0 {
|
|
return nil, errors.New("invalid params")
|
|
}
|
|
|
|
buckets := make([]bucket, len(frames))
|
|
var total int
|
|
for i, ms := range frames {
|
|
n := stereoSamples(hz, ms)
|
|
buckets[i] = bucket{ms: ms, dst: n}
|
|
total += n
|
|
}
|
|
if total == 0 {
|
|
return nil, errors.New("zero buffer size")
|
|
}
|
|
|
|
raw := make(samples, total)
|
|
for i, off := 0, 0; i < len(buckets); i++ {
|
|
buckets[i].mem = raw[off : off+buckets[i].dst]
|
|
off += buckets[i].dst
|
|
}
|
|
|
|
return &buffer{
|
|
raw: raw,
|
|
scratch: make(samples, 5760),
|
|
buckets: buckets,
|
|
srcHz: hz,
|
|
dstHz: hz,
|
|
bi: len(buckets) - 1,
|
|
}, nil
|
|
}
|
|
|
|
func (b *buffer) close() {
|
|
if b.resampler != nil {
|
|
b.resampler.Destroy()
|
|
b.resampler = nil
|
|
}
|
|
}
|
|
|
|
func (b *buffer) resample(hz int, algo ResampleAlgo) error {
|
|
b.algo, b.dstHz = algo, hz
|
|
for i := range b.buckets {
|
|
b.buckets[i].dst = stereoSamples(hz, b.buckets[i].ms)
|
|
}
|
|
if algo == ResampleSpeex {
|
|
var err error
|
|
b.resampler, err = resampler.Init(2, b.srcHz, hz, resampler.QualityMax)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *buffer) write(s samples, onFull func(samples, float32)) int {
|
|
n := len(s)
|
|
for i := 0; i < n; {
|
|
cur := &b.buckets[b.bi]
|
|
c := copy(cur.mem[cur.p:], s[i:])
|
|
i += c
|
|
cur.p += c
|
|
if cur.p == len(cur.mem) {
|
|
onFull(b.stretch(cur.mem, cur.dst), cur.ms)
|
|
b.choose(n - i)
|
|
b.buckets[b.bi].p = 0
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (b *buffer) choose(rem int) {
|
|
for i := len(b.buckets) - 1; i >= 0; i-- {
|
|
if rem >= len(b.buckets[i].mem) {
|
|
b.bi = i
|
|
return
|
|
}
|
|
}
|
|
b.bi = 0
|
|
}
|
|
|
|
func (b *buffer) stretch(src samples, size int) samples {
|
|
if len(src) == size {
|
|
return src
|
|
}
|
|
|
|
if cap(b.scratch) < size {
|
|
b.scratch = make(samples, size)
|
|
}
|
|
out := b.scratch[:size]
|
|
|
|
if b.algo == ResampleSpeex && b.resampler != nil {
|
|
if n, _ := b.resampler.Process(out, src); n > 0 {
|
|
for i := n; i < size; i += 2 {
|
|
out[i], out[i+1] = out[n-2], out[n-1]
|
|
}
|
|
return out
|
|
}
|
|
}
|
|
|
|
if b.algo == ResampleNearest {
|
|
resampler.Nearest(out, src)
|
|
} else {
|
|
resampler.Linear(out, src)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func stereoSamples(hz int, ms float32) int {
|
|
return int(float32(hz)*ms/1000+0.5) * 2
|
|
}
|