mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 02:34:42 +00:00
Refactor media (#401)
* Encapsulate media * Write audio by 4 bytes instead 2 * Update deps
This commit is contained in:
parent
2ed6e8724f
commit
7668ef7bd8
5 changed files with 120 additions and 128 deletions
9
go.mod
9
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
|
||||
|
|
|
|||
34
go.sum
34
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=
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue