mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 10:35:44 +00:00
318 lines
6.7 KiB
Go
318 lines
6.7 KiB
Go
package media
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/giongto35/cloud-game/v3/pkg/resampler"
|
|
)
|
|
|
|
func mustBuffer(t *testing.T, frames []float32, hz int) *buffer {
|
|
t.Helper()
|
|
buf, err := newBuffer(frames, hz)
|
|
if err != nil {
|
|
t.Fatalf("failed to create buffer: %v", err)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func samplesOf(v int16, n int) samples {
|
|
s := make(samples, n)
|
|
for i := range s {
|
|
s[i] = v
|
|
}
|
|
return s
|
|
}
|
|
|
|
func ramp(pairs int) samples {
|
|
s := make(samples, pairs*2)
|
|
for i := range pairs {
|
|
s[i*2], s[i*2+1] = int16(i), int16(i)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func TestNewBuffer(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
frames []float32
|
|
hz int
|
|
wantErr bool
|
|
}{
|
|
{"valid single", []float32{10}, 48000, false},
|
|
{"valid multi", []float32{10, 20}, 48000, false},
|
|
{"hz too low", []float32{10}, 1999, true},
|
|
{"empty frames", []float32{}, 48000, true},
|
|
{"nil frames", nil, 48000, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf, err := newBuffer(tt.frames, tt.hz)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("err = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
if buf != nil {
|
|
buf.close()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBufferBucketSizes(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{10, 20}, 48000)
|
|
defer buf.close()
|
|
|
|
if len(buf.buckets) != 2 {
|
|
t.Fatalf("got %d buckets, want 2", len(buf.buckets))
|
|
}
|
|
if n := len(buf.buckets[0].mem); n != 960 {
|
|
t.Errorf("bucket[0] = %d, want 960", n)
|
|
}
|
|
if n := len(buf.buckets[1].mem); n != 1920 {
|
|
t.Errorf("bucket[1] = %d, want 1920", n)
|
|
}
|
|
}
|
|
|
|
func TestBufferClose(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{10}, 48000)
|
|
buf.close()
|
|
buf.close() // idempotent
|
|
if buf.resampler != nil {
|
|
t.Error("resampler should be nil after close")
|
|
}
|
|
}
|
|
|
|
func TestBufferWrite(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
writes []struct {
|
|
v int16
|
|
n int
|
|
}
|
|
want samples
|
|
}{
|
|
{
|
|
name: "overflow triggers callback",
|
|
writes: []struct {
|
|
v int16
|
|
n int
|
|
}{{1, 10}, {2, 20}, {3, 30}},
|
|
want: samples{
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
},
|
|
},
|
|
{
|
|
name: "partial fill",
|
|
writes: []struct {
|
|
v int16
|
|
n int
|
|
}{{1, 3}, {2, 18}, {3, 2}},
|
|
want: samples{1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{10, 5}, 2000)
|
|
defer buf.close()
|
|
|
|
var got samples
|
|
for _, w := range tt.writes {
|
|
buf.write(samplesOf(w.v, w.n), func(s samples, _ float32) { got = s })
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("\ngot: %v\nwant: %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBufferWriteExact(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{10}, 2000) // 40 samples
|
|
defer buf.close()
|
|
|
|
calls := 0
|
|
buf.write(samplesOf(1, 40), func(_ samples, ms float32) {
|
|
calls++
|
|
if ms != 10 {
|
|
t.Errorf("ms = %v, want 10", ms)
|
|
}
|
|
})
|
|
if calls != 1 {
|
|
t.Errorf("calls = %d, want 1", calls)
|
|
}
|
|
}
|
|
|
|
func TestBufferWriteReturn(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{10}, 2000)
|
|
defer buf.close()
|
|
|
|
if n := buf.write(samplesOf(1, 100), func(samples, float32) {}); n != 100 {
|
|
t.Errorf("return = %d, want 100", n)
|
|
}
|
|
}
|
|
|
|
func TestBufferChoose(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{20, 10, 5}, 48000) // 1920, 960, 480
|
|
defer buf.close()
|
|
|
|
tests := []struct{ rem, want int }{
|
|
{10000, 2}, {500, 2}, {479, 0}, {0, 0},
|
|
}
|
|
for _, tt := range tests {
|
|
buf.choose(tt.rem)
|
|
if buf.bi != tt.want {
|
|
t.Errorf("choose(%d) = %d, want %d", tt.rem, buf.bi, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStereoSamples(t *testing.T) {
|
|
tests := []struct {
|
|
hz int
|
|
ms float32
|
|
want int
|
|
}{
|
|
{16000, 5, 160},
|
|
{32768, 10, 656},
|
|
{32768, 2.5, 164},
|
|
{32768, 5, 328},
|
|
{44100, 10, 882},
|
|
{48000, 10, 960},
|
|
{48000, 2.5, 240},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := stereoSamples(tt.hz, tt.ms); got != tt.want {
|
|
t.Errorf("stereoSamples(%d, %.0f) = %d, want %d", tt.hz, tt.ms, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStretchPassthrough(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{10}, 48000)
|
|
defer buf.close()
|
|
|
|
src := samples{1, 2, 3, 4}
|
|
if res := buf.stretch(src, 4); &res[0] != &src[0] {
|
|
t.Error("expected zero-copy when sizes match")
|
|
}
|
|
}
|
|
|
|
func TestLinear(t *testing.T) {
|
|
t.Run("interpolation", func(t *testing.T) {
|
|
out := make(samples, 8)
|
|
resampler.Linear(out, samples{0, 0, 100, 100})
|
|
if out[2] <= 0 || out[2] >= 100 {
|
|
t.Errorf("middle value %d not interpolated", out[2])
|
|
}
|
|
})
|
|
|
|
t.Run("sizes", func(t *testing.T) {
|
|
cases := []struct{ srcPairs, dstSize int }{
|
|
{4, 16}, {8, 8}, {4, 8},
|
|
}
|
|
for _, tc := range cases {
|
|
out := make(samples, tc.dstSize)
|
|
resampler.Linear(out, ramp(tc.srcPairs))
|
|
if len(out) != tc.dstSize {
|
|
t.Errorf("len = %d, want %d", len(out), tc.dstSize)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNearest(t *testing.T) {
|
|
tests := []struct {
|
|
src samples
|
|
want samples
|
|
}{
|
|
{samples{10, 20, 30, 40}, samples{10, 20, 10, 20, 30, 40, 30, 40}},
|
|
{samples{10, 20, 30, 40, 50, 60, 70, 80}, samples{10, 20, 50, 60}},
|
|
}
|
|
for _, tt := range tests {
|
|
out := make(samples, len(tt.want))
|
|
resampler.Nearest(out, tt.src)
|
|
if !reflect.DeepEqual(out, tt.want) {
|
|
t.Errorf("nearest(%v) = %v, want %v", tt.src, out, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSpeex(t *testing.T) {
|
|
buf := mustBuffer(t, []float32{10}, 48000)
|
|
defer buf.close()
|
|
|
|
if err := buf.resample(24000, ResampleSpeex); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Run("stretch", func(t *testing.T) {
|
|
res := buf.stretch(samplesOf(1000, 960), 480)
|
|
if len(res) != 480 {
|
|
t.Errorf("len = %d, want 480", len(res))
|
|
}
|
|
for _, s := range res {
|
|
if s != 0 {
|
|
return
|
|
}
|
|
}
|
|
t.Error("output is silent")
|
|
})
|
|
|
|
t.Run("write", func(t *testing.T) {
|
|
calls := 0
|
|
buf.write(samplesOf(5000, 960), func(s samples, ms float32) {
|
|
calls++
|
|
if len(s) != 480 {
|
|
t.Errorf("len = %d, want 480", len(s))
|
|
}
|
|
if ms != 10 {
|
|
t.Errorf("ms = %v, want 10", ms)
|
|
}
|
|
})
|
|
if calls != 1 {
|
|
t.Errorf("calls = %d, want 1", calls)
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkStretch(b *testing.B) {
|
|
src := samplesOf(1000, 1920) // 20ms @ 48kHz
|
|
|
|
b.Run("speex", func(b *testing.B) {
|
|
buf, _ := newBuffer([]float32{20}, 48000)
|
|
defer buf.close()
|
|
_ = buf.resample(24000, ResampleSpeex)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
buf.stretch(src, 960)
|
|
}
|
|
})
|
|
|
|
b.Run("linear", func(b *testing.B) {
|
|
buf, _ := newBuffer([]float32{20}, 48000)
|
|
defer buf.close()
|
|
_ = buf.resample(24000, ResampleLinear)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
buf.stretch(src, 960)
|
|
}
|
|
})
|
|
|
|
b.Run("nearest", func(b *testing.B) {
|
|
buf, _ := newBuffer([]float32{20}, 48000)
|
|
defer buf.close()
|
|
_ = buf.resample(24000, ResampleNearest)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
buf.stretch(src, 960)
|
|
}
|
|
})
|
|
}
|