From 7668ef7bd826e2a67b71e83e6b65085494c124e3 Mon Sep 17 00:00:00 2001 From: sergystepanov Date: Sat, 27 May 2023 17:34:35 +0300 Subject: [PATCH] Refactor media (#401) * Encapsulate media * Write audio by 4 bytes instead 2 * Update deps --- go.mod | 9 +- go.sum | 34 ++------ pkg/worker/coordinatorhandlers.go | 2 +- pkg/worker/media.go | 133 ++++++++++++++++-------------- pkg/worker/media_test.go | 70 +++++++++------- 5 files changed, 120 insertions(+), 128 deletions(-) diff --git a/go.mod b/go.mod index aeff81a3..611385d4 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/knadh/koanf/v2 v2.0.1 github.com/pion/interceptor v0.1.17 github.com/pion/logging v0.2.2 - github.com/pion/webrtc/v3 v3.2.5 + github.com/pion/webrtc/v3 v3.2.8 github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.29.1 github.com/veandco/go-sdl2 v0.4.35 @@ -26,13 +26,13 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pion/datachannel v1.5.5 // indirect github.com/pion/dtls/v2 v2.2.7 // indirect - github.com/pion/ice/v2 v2.3.4 // indirect + github.com/pion/ice/v2 v2.3.6 // indirect github.com/pion/mdns v0.0.7 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.10 // indirect @@ -40,10 +40,9 @@ require ( github.com/pion/sctp v1.8.7 // indirect github.com/pion/sdp/v3 v3.0.6 // indirect github.com/pion/srtp/v2 v2.0.15 // indirect - github.com/pion/stun v0.5.2 // indirect + github.com/pion/stun v0.6.0 // indirect github.com/pion/transport/v2 v2.2.1 // indirect github.com/pion/turn/v2 v2.1.0 // indirect - github.com/pion/udp/v2 v2.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.8.3 // indirect github.com/valyala/fastrand v1.1.0 // indirect diff --git a/go.sum b/go.sum index b3c7999e..e6ba3a6a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/VictoriaMetrics/metrics v1.23.1 h1:/j8DzeJBxSpL2qSIdqnRFLvQQhbJyJbbEi22yMm7oL0= -github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc= github.com/VictoriaMetrics/metrics v1.24.0 h1:ILavebReOjYctAGY5QU2F9X0MYvkcrG3aEn2RKa1Zkw= github.com/VictoriaMetrics/metrics v1.24.0/go.mod h1:eFT25kvsTidQFHb6U0oa0rTrDRdz4xTYjpL8+UPohys= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= @@ -50,8 +48,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -69,16 +67,10 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= -github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4= -github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= -github.com/pion/ice/v2 v2.3.2 h1:vh+fi4RkZ8H5fB4brZ/jm3j4BqFgMmNs+aB3X52Hu7M= -github.com/pion/ice/v2 v2.3.2/go.mod h1:AMIpuJqcpe+UwloocNebmTSWhCZM1TUCo9v7nW50jX0= -github.com/pion/ice/v2 v2.3.4 h1:tjYjTLpWyZzUjpDnzk6T1y3oQyhyY2DiM2t095iDhyQ= -github.com/pion/ice/v2 v2.3.4/go.mod h1:jVbxqPWQDK5+/V/YqpinUcP0YtDGYqd24n2lusVdX80= -github.com/pion/interceptor v0.1.16 h1:0GDZrfNO+BmVNWymS31fMlVtPO2IJVBzy2Qq5XCYMIg= -github.com/pion/interceptor v0.1.16/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI= +github.com/pion/ice/v2 v2.3.6 h1:Jgqw36cAud47iD+N6rNX225uHvrgWtAlHfVyOQc3Heg= +github.com/pion/ice/v2 v2.3.6/go.mod h1:9/TzKDRwBVAPsC+YOrKH/e3xDrubeTRACU9/sHQarsU= github.com/pion/interceptor v0.1.17 h1:prJtgwFh/gB8zMqGZoOgJPHivOwVAp61i2aG61Du/1w= github.com/pion/interceptor v0.1.17/go.mod h1:SY8kpmfVBvrbUzvj2bsXz7OJt5JvmVNZ+4Kjq7FcwrI= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -96,31 +88,24 @@ github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw= github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU= github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= -github.com/pion/srtp/v2 v2.0.12/go.mod h1:C3Ep44hlOo2qEYaq4ddsmK5dL63eLehXFbHaZ9F5V9Y= -github.com/pion/srtp/v2 v2.0.13 h1:GJQNMCqbYrNIBt1f3maX+E+woREVh2ilhAafBh0vqmk= -github.com/pion/srtp/v2 v2.0.13/go.mod h1:FA7u5fWpVITMYNL70TA3csQuMQJA5/+6ZMajGxveHgM= github.com/pion/srtp/v2 v2.0.15 h1:+tqRtXGsGwHC0G0IUIAzRmdkHvriF79IHVfZGfHrQoA= github.com/pion/srtp/v2 v2.0.15/go.mod h1:b/pQOlDrbB0HEH5EUAQXzSYxikFbNcNuKmF8tM0hCtw= github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw= -github.com/pion/stun v0.5.2 h1:J/8glQnDV91dfk2+ZnGN0o9bUJgABhTNljwfQWByoXE= -github.com/pion/stun v0.5.2/go.mod h1:TNo1HjyjaFVpMZsvowqPeV8TfwRytympQC0//neaksA= +github.com/pion/stun v0.6.0 h1:JHT/2iyGDPrFWE8NNC15wnddBN8KifsEDw8swQmrEmU= +github.com/pion/stun v0.6.0/go.mod h1:HPqcfoeqQn9cuaet7AOmB5e5xkObu9DwBdurwLKO9oA= github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0= github.com/pion/transport/v2 v2.1.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ= -github.com/pion/transport/v2 v2.2.0 h1:u5lFqFHkXLMXMzai8tixZDfVjb8eOjH35yCunhPeb1c= github.com/pion/transport/v2 v2.2.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ= github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI= github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs= -github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54= github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8= -github.com/pion/webrtc/v3 v3.2.1 h1:eehbYzkM6xWoH3LXoIBnZTb4TOrjwmVzI78JO1+5kgQ= -github.com/pion/webrtc/v3 v3.2.1/go.mod h1:sQVqop5YhZezvKyyz6Nywvf15LhlXUWiXWdN5DV4zHs= -github.com/pion/webrtc/v3 v3.2.5 h1:WA38+a1T3/EP55k5IYQFLH3ORaNpRTcElW6UL1CwNeA= -github.com/pion/webrtc/v3 v3.2.5/go.mod h1:8+GhDtUqfKmnZkj+aT2kjvV9B2/nhSTqINEXbVQEGSo= +github.com/pion/webrtc/v3 v3.2.8 h1:RmDEz7wjK3k0sAuCSMptfxp095pBYSkSSm5ySiJYIHI= +github.com/pion/webrtc/v3 v3.2.8/go.mod h1:6/7wF1P86AQAw4iTmKIgdzaevaQ8qh9SfrFyypqmN6w= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -137,7 +122,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -153,8 +137,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= diff --git a/pkg/worker/coordinatorhandlers.go b/pkg/worker/coordinatorhandlers.go index 1f827faa..5dab2c9c 100644 --- a/pkg/worker/coordinatorhandlers.go +++ b/pkg/worker/coordinatorhandlers.go @@ -27,7 +27,7 @@ func buildConnQuery(id com.Uid, conf config.Worker, address string) (string, err func (c *coordinator) HandleWebrtcInit(rq api.WebrtcInitRequest[com.Uid], w *Worker, connApi *webrtc.ApiFactory) api.Out { peer := webrtc.New(c.log, connApi) - localSDP, err := peer.NewCall(w.conf.Encoder.Video.Codec, audioCodec, func(data any) { + localSDP, err := peer.NewCall(w.conf.Encoder.Video.Codec, "opus", func(data any) { candidate, err := toBase64Json(data) if err != nil { c.log.Error().Err(err).Msgf("ICE candidate encode fail for [%v]", data) diff --git a/pkg/worker/media.go b/pkg/worker/media.go index 6754d66d..14191a7a 100644 --- a/pkg/worker/media.go +++ b/pkg/worker/media.go @@ -3,6 +3,7 @@ package worker import ( "sync" "time" + "unsafe" "github.com/giongto35/cloud-game/v3/pkg/config" "github.com/giongto35/cloud-game/v3/pkg/worker/emulator" @@ -13,51 +14,55 @@ import ( webrtc "github.com/pion/webrtc/v3/pkg/media" ) +const ( + dstHz = 48000 + sampleBufLen = 1024 * 4 +) + +// buffer is a simple non-concurrent safe ring buffer for audio samples. +type ( + buffer struct { + s samples + wi int + dst int + stretch bool + } + samples []int16 +) + var ( encoderOnce = sync.Once{} opusCoder *opus.Encoder samplePool sync.Pool - audioPool = sync.Pool{New: func() any { b := make([]int16, 3000); return &b }} + audioPool = sync.Pool{New: func() any { b := make([]int16, sampleBufLen); return &b }} ) -const ( - audioChannels = 2 - audioCodec = "opus" - audioFrequency = 48000 -) +func newBuffer(srcLen int) buffer { return buffer{s: make(samples, srcLen)} } -// Buffer is a simple non-thread safe ring buffer for audio samples. -// It should be used for 16bit PCM (LE interleaved) data. -type ( - Buffer struct { - s Samples - wi int - } - OnFull func(s Samples) - Samples []int16 -) +// enableStretch adds a simple stretching of buffer to a desired size before +// the onFull callback call. +func (b *buffer) enableStretch(l int) { b.stretch = true; b.dst = l } -func NewBuffer(numSamples int) Buffer { return Buffer{s: make(Samples, numSamples)} } - -// Write fills the buffer with data calling a callback function when -// the internal buffer fills out. +// write fills the buffer until it's full and then passes the gathered data into a callback. // -// Consider two cases: -// -// 1. Underflow, when the length of written data is less than the buffer's available space. +// There are two cases to consider: +// 1. Underflow, when the length of the written data is less than the buffer's available space. // 2. Overflow, when the length exceeds the current available buffer space. -// In the both cases we overwrite any previous values in the buffer and move the internal -// write pointer on the length of written data. -// In the first case we won't call the callback, but it will be called every time +// +// We overwrite any previous values in the buffer and move the internal write pointer +// by the length of the written data. +// In the first case, we won't call the callback, but it will be called every time // when the internal buffer overflows until all samples are read. -func (b *Buffer) Write(s Samples, onFull OnFull) (r int) { +func (b *buffer) write(s samples, onFull func(samples)) (r int) { for r < len(s) { w := copy(b.s[b.wi:], s[r:]) r += w b.wi += w if b.wi == len(b.s) { b.wi = 0 - if onFull != nil { + if b.stretch { + onFull(b.s.stretch(b.dst)) + } else { onFull(b.s) } } @@ -65,18 +70,29 @@ func (b *Buffer) Write(s Samples, onFull OnFull) (r int) { return } -// GetFrameSizeFor calculates audio frame size, i.e. 48k*frame/1000*2 -func GetFrameSizeFor(hz int, frame int) int { return hz * frame / 1000 * audioChannels } +// frame calculates an audio stereo frame size, i.e. 48k*frame/1000*2 +func frame(hz int, frame int) int { return hz * frame / 1000 * 2 } -func (r *Room) initAudio(frequency int, conf config.Audio) { - buf := NewBuffer(GetFrameSizeFor(frequency, conf.Frame)) - resample, frameLen := frequency != audioFrequency, 0 - if resample { - frameLen = GetFrameSizeFor(audioFrequency, conf.Frame) +// stretch does a simple stretching of audio samples. +// something like: [1,2,3,4,5,6] -> [1,2,x,x,3,4,x,x,5,6,x,x] -> [1,2,1,2,3,4,3,4,5,6,5,6] +func (s samples) stretch(size int) []int16 { + out := (*audioPool.Get().(*[]int16))[:size] + n := len(s) + ratio := float32(size) / float32(n) + sPtr := unsafe.Pointer(&s[0]) + for i, l, r := 0, 0, 0; i < n; i += 2 { + l, r = r, int(float32((i+2)>>1)*ratio)<<1 // index in src * ratio -> approximated index in dst *2 due to int16 + for j := l; j < r; j += 2 { + *(*int32)(unsafe.Pointer(&out[j])) = *(*int32)(sPtr) // out[j] = s[i]; out[j+1] = s[i+1] + } + sPtr = unsafe.Add(sPtr, uintptr(4)) } + return out +} +func (r *Room) initAudio(srcHz int, conf config.Audio) { encoderOnce.Do(func() { - enc, err := opus.NewEncoder(audioFrequency) + enc, err := opus.NewEncoder(dstHz) if err != nil { r.log.Fatal().Err(err).Msg("couldn't create audio encoder") } @@ -87,23 +103,28 @@ func (r *Room) initAudio(frequency int, conf config.Audio) { } r.log.Debug().Msgf("Opus: %v", opusCoder.GetInfo()) - dur := time.Duration(conf.Frame) * time.Millisecond + buf := newBuffer(frame(srcHz, conf.Frame)) + if srcHz != dstHz { + buf.enableStretch(frame(dstHz, conf.Frame)) + r.log.Debug().Msgf("Resample %vHz -> %vHz", srcHz, dstHz) + } + frameDur := time.Duration(conf.Frame) * time.Millisecond - fn := func(s Samples) { - if resample { - s = ResampleStretchNew(s, frameLen) - } - f, err := opusCoder.Encode(s) - audioPool.Put((*[]int16)(&s)) - if err == nil { - r.handleSample(f, dur, func(u *Session, s *webrtc.Sample) { + r.emulator.SetAudio(func(raw *emulator.GameAudio) { + buf.write(*raw.Data, func(pcm samples) { + data, err := opusCoder.Encode(pcm) + audioPool.Put((*[]int16)(&pcm)) + if err != nil { + r.log.Error().Err(err).Msgf("opus encode fail") + return + } + r.handleSample(data, frameDur, func(u *Session, s *webrtc.Sample) { if err := u.SendAudio(s); err != nil { r.log.Error().Err(err).Send() } }) - } - } - r.emulator.SetAudio(func(samples *emulator.GameAudio) { buf.Write(*samples.Data, fn) }) + }) + }) } // initVideo processes videoFrames images with an encoder (codec) then pushes the result to WebRTC. @@ -160,19 +181,3 @@ func (r *Room) handleSample(b []byte, d time.Duration, fn func(*Session, *webrtc }) samplePool.Put(sample) } - -// ResampleStretchNew does a simple stretching of audio samples. -// something like: [1,2,3,4,5,6] -> [1,2,x,x,3,4,x,x,5,6,x,x] -> [1,2,1,2,3,4,3,4,5,6,5,6] -func ResampleStretchNew(pcm []int16, size int) []int16 { - out := (*audioPool.Get().(*[]int16))[:size] - n := len(pcm) - ratio := float32(size) / float32(n) - for i, l, r := 0, 0, 0; i < n; i += 2 { - l, r = r, int(float32((i+2)>>1)*ratio)<<1 - for j := l; j < r-1; j += 2 { - out[j] = pcm[i] - out[j+1] = pcm[i+1] - } - } - return out -} diff --git a/pkg/worker/media_test.go b/pkg/worker/media_test.go index 7086a51f..4e5a03f6 100644 --- a/pkg/worker/media_test.go +++ b/pkg/worker/media_test.go @@ -1,7 +1,6 @@ package worker import ( - "fmt" "image" "math/rand" "reflect" @@ -86,7 +85,7 @@ func genTestImage(w, h int, seed float32) *image.RGBA { func TestResampleStretch(t *testing.T) { type args struct { - pcm []int16 + pcm samples size int } tests := []struct { @@ -106,7 +105,7 @@ func TestResampleStretch(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rez2 := ResampleStretchNew(tt.args.pcm, tt.args.size) + rez2 := tt.args.pcm.stretch(tt.args.size) if rez2[0] != tt.args.pcm[0] || rez2[1] != tt.args.pcm[1] || rez2[len(rez2)-1] != tt.args.pcm[len(tt.args.pcm)-1] || @@ -119,20 +118,10 @@ func TestResampleStretch(t *testing.T) { } func BenchmarkResampler(b *testing.B) { - tests := []struct { - name string - fn func(pcm []int16, size int) []int16 - }{ - {name: "new", fn: ResampleStretchNew}, - } - pcm := gen(1764) + pcm := samples(gen(1764)) size := 1920 - for _, bn := range tests { - b.Run(fmt.Sprintf("%v", bn.name), func(b *testing.B) { - for i := 0; i < b.N; i++ { - bn.fn(pcm, size) - } - }) + for i := 0; i < b.N; i++ { + pcm.stretch(size) } } @@ -141,10 +130,6 @@ func gen(l int) []int16 { for i := range nums { nums[i] = int16(rand.Intn(10)) } - //for i := len(nums) / 2; i < len(nums)/2+42; i++ { - // nums[i] = 0 - //} - return nums } @@ -157,7 +142,7 @@ func TestBufferWrite(t *testing.T) { tests := []struct { bufLen int writes []bufWrite - expect Samples + expect samples }{ { bufLen: 20, @@ -166,7 +151,7 @@ func TestBufferWrite(t *testing.T) { {sample: 2, len: 20}, {sample: 3, len: 30}, }, - expect: Samples{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + expect: samples{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, }, { bufLen: 11, @@ -175,15 +160,15 @@ func TestBufferWrite(t *testing.T) { {sample: 2, len: 18}, {sample: 3, len: 2}, }, - expect: Samples{3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3}, + expect: samples{3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3}, }, } for _, test := range tests { - var lastResult Samples - buf := NewBuffer(test.bufLen) + var lastResult samples + buf := newBuffer(test.bufLen) for _, w := range test.writes { - buf.Write(samplesOf(w.sample, w.len), func(s Samples) { lastResult = s }) + buf.write(samplesOf(w.sample, w.len), func(s samples) { lastResult = s }) } if !reflect.DeepEqual(test.expect, lastResult) { t.Errorf("not expted buffer, %v != %v", lastResult, test.expect) @@ -192,21 +177,42 @@ func TestBufferWrite(t *testing.T) { } func BenchmarkBufferWrite(b *testing.B) { - fn := func(_ Samples) {} + fn := func(_ samples) {} l := 1920 - buf := NewBuffer(l) + buf := newBuffer(l) samples1 := samplesOf(1, l/2) samples2 := samplesOf(2, l*2) for i := 0; i < b.N; i++ { - buf.Write(samples1, fn) - buf.Write(samples2, fn) + buf.write(samples1, fn) + buf.write(samples2, fn) } } -func samplesOf(v int16, len int) (s Samples) { - s = make(Samples, len) +func samplesOf(v int16, len int) (s samples) { + s = make(samples, len) for i := range s { s[i] = v } return } + +func Test_frame(t *testing.T) { + type args struct { + hz int + frame int + } + tests := []struct { + name string + args args + want int + }{ + {name: "mGBA", args: args{hz: 32768, frame: 10}, want: 654}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := frame(tt.args.hz, tt.args.frame); got != tt.want { + t.Errorf("frame() = %v, want %v", got, tt.want) + } + }) + } +}