mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 02:34:42 +00:00
Add and use Speex audio resampler
This commit is contained in:
parent
671e875f12
commit
9d54ea4c49
6 changed files with 155 additions and 154 deletions
|
|
@ -54,6 +54,7 @@ RUN apt-get -q update && apt-get -q install --no-install-recommends -y \
|
|||
libyuv-dev \
|
||||
libjpeg-turbo8-dev \
|
||||
libx264-dev \
|
||||
libspeexdsp-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
|
|||
|
|
@ -60,13 +60,13 @@ a better sense of performance.
|
|||
|
||||
```
|
||||
# Ubuntu / Windows (WSL2)
|
||||
apt-get install -y make gcc pkg-config libvpx-dev libx264-dev libopus-dev libsdl2-dev libyuv-dev libjpeg-turbo8-dev
|
||||
apt-get install -y make gcc pkg-config libvpx-dev libx264-dev libopus-dev libsdl2-dev libyuv-dev libjpeg-turbo8-dev libspeexdsp-dev
|
||||
|
||||
# MacOS
|
||||
brew install pkg-config libvpx x264 opus sdl2 jpeg-turbo
|
||||
brew install pkg-config libvpx x264 opus sdl2 jpeg-turbo speexdsp
|
||||
|
||||
# Windows (MSYS2)
|
||||
pacman -Sy --noconfirm --needed git make mingw-w64-ucrt-x86_64-{gcc,pkgconf,dlfcn,libvpx,opus,libx264,SDL2,libyuv,libjpeg-turbo}
|
||||
pacman -Sy --noconfirm --needed git make mingw-w64-ucrt-x86_64-{gcc,pkgconf,dlfcn,libvpx,opus,libx264,SDL2,libyuv,libjpeg-turbo,speexdsp}
|
||||
```
|
||||
|
||||
(You don't need to download libyuv on macOS)
|
||||
|
|
|
|||
29
go.mod
29
go.mod
|
|
@ -4,6 +4,7 @@ go 1.25
|
|||
|
||||
require (
|
||||
github.com/VictoriaMetrics/metrics v1.40.2
|
||||
github.com/aam335/speexdsp v0.0.0-20190116080032-198c2d2ba225
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/goccy/go-json v0.10.5
|
||||
|
|
@ -12,15 +13,15 @@ require (
|
|||
github.com/knadh/koanf/maps v0.1.2
|
||||
github.com/knadh/koanf/v2 v2.3.0
|
||||
github.com/minio/minio-go/v7 v7.0.97
|
||||
github.com/pion/ice/v4 v4.0.10
|
||||
github.com/pion/ice/v4 v4.1.0
|
||||
github.com/pion/interceptor v0.1.42
|
||||
github.com/pion/logging v0.2.4
|
||||
github.com/pion/webrtc/v4 v4.1.6
|
||||
github.com/pion/webrtc/v4 v4.1.8
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/veandco/go-sdl2 v0.4.40
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/image v0.30.0
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/image v0.34.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ require (
|
|||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
|
|
@ -40,23 +41,23 @@ require (
|
|||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pion/datachannel v1.5.10 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.7 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.9 // indirect
|
||||
github.com/pion/mdns/v2 v2.1.0 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.16 // indirect
|
||||
github.com/pion/rtp v1.8.25 // indirect
|
||||
github.com/pion/sctp v1.8.40 // indirect
|
||||
github.com/pion/rtp v1.8.26 // indirect
|
||||
github.com/pion/sctp v1.8.41 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.16 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.8 // indirect
|
||||
github.com/pion/stun/v3 v3.0.1 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.9 // indirect
|
||||
github.com/pion/stun/v3 v3.0.2 // indirect
|
||||
github.com/pion/transport/v3 v3.1.1 // indirect
|
||||
github.com/pion/turn/v4 v4.1.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/tinylib/msgp v1.5.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
github.com/valyala/fastrand v1.1.0 // indirect
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
)
|
||||
|
|
|
|||
30
go.sum
30
go.sum
|
|
@ -1,5 +1,7 @@
|
|||
github.com/VictoriaMetrics/metrics v1.40.2 h1:OVSjKcQEx6JAwGeu8/KQm9Su5qJ72TMEW4xYn5vw3Ac=
|
||||
github.com/VictoriaMetrics/metrics v1.40.2/go.mod h1:XE4uudAAIRaJE614Tl5HMrtoEU6+GDZO4QTnNSsZRuA=
|
||||
github.com/aam335/speexdsp v0.0.0-20190116080032-198c2d2ba225 h1:qZ9Sv2nmB9oFAZLdhsvjpBW4NyKexrSnCzjQJPfcaTU=
|
||||
github.com/aam335/speexdsp v0.0.0-20190116080032-198c2d2ba225/go.mod h1:gui3wdg1cup88TpLbUDkl88CPrD+b9ICs886eDh2hOQ=
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
|
|
@ -24,6 +26,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
|||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
|
|
@ -60,8 +64,12 @@ github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk
|
|||
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
|
||||
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
|
||||
github.com/pion/dtls/v3 v3.0.9 h1:4AijfFRm8mAjd1gfdlB1wzJF3fjjR/VPIpJgkEtvYmM=
|
||||
github.com/pion/dtls/v3 v3.0.9/go.mod h1:abApPjgadS/ra1wvUzHLc3o2HvoxppAh+NZkyApL4Os=
|
||||
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||
github.com/pion/ice/v4 v4.1.0 h1:YlxIii2bTPWyC08/4hdmtYq4srbrY0T9xcTsTjldGqU=
|
||||
github.com/pion/ice/v4 v4.1.0/go.mod h1:5gPbzYxqenvn05k7zKPIZFuSAufolygiy6P1U9HzvZ4=
|
||||
github.com/pion/interceptor v0.1.42 h1:0/4tvNtruXflBxLfApMVoMubUMik57VZ+94U0J7cmkQ=
|
||||
github.com/pion/interceptor v0.1.42/go.mod h1:g6XYTChs9XyolIQFhRHOOUS+bGVGLRfgTCUzH29EfVU=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
|
|
@ -74,20 +82,30 @@ github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
|||
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
||||
github.com/pion/rtp v1.8.25 h1:b8+y44GNbwOJTYWuVan7SglX/hMlicVCAtL50ztyZHw=
|
||||
github.com/pion/rtp v1.8.25/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc=
|
||||
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/sctp v1.8.40 h1:bqbgWYOrUhsYItEnRObUYZuzvOMsVplS3oNgzedBlG8=
|
||||
github.com/pion/sctp v1.8.40/go.mod h1:SPBBUENXE6ThkEksN5ZavfAhFYll+h+66ZiG6IZQuzo=
|
||||
github.com/pion/sctp v1.8.41 h1:20R4OHAno4Vky3/iE4xccInAScAa83X6nWUfyc65MIs=
|
||||
github.com/pion/sctp v1.8.41/go.mod h1:2wO6HBycUH7iCssuGyc2e9+0giXVW0pyCv3ZuL8LiyY=
|
||||
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
|
||||
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
||||
github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM=
|
||||
github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg=
|
||||
github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY=
|
||||
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8=
|
||||
github.com/pion/stun/v3 v3.0.1 h1:jx1uUq6BdPihF0yF33Jj2mh+C9p0atY94IkdnW174kA=
|
||||
github.com/pion/stun/v3 v3.0.1/go.mod h1:RHnvlKFg+qHgoKIqtQWMOJF52wsImCAf/Jh5GjX+4Tw=
|
||||
github.com/pion/stun/v3 v3.0.2 h1:BJuGEN2oLrJisiNEJtUTJC4BGbzbfp37LizfqswblFU=
|
||||
github.com/pion/stun/v3 v3.0.2/go.mod h1:JFJKfIWvt178MCF5H/YIgZ4VX3LYE77vca4b9HP60SA=
|
||||
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
|
||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pion/turn/v4 v4.1.3 h1:jVNW0iR05AS94ysEtvzsrk3gKs9Zqxf6HmnsLfRvlzA=
|
||||
github.com/pion/turn/v4 v4.1.3/go.mod h1:TD/eiBUf5f5LwXbCJa35T7dPtTpCHRJ9oJWmyPLVT3A=
|
||||
github.com/pion/webrtc/v4 v4.1.6 h1:srHH2HwvCGwPba25EYJgUzgLqCQoXl1VCUnrGQMSzUw=
|
||||
github.com/pion/webrtc/v4 v4.1.6/go.mod h1:wKecGRlkl3ox/As/MYghJL+b/cVXMEhoPMJWPuGQFhU=
|
||||
github.com/pion/webrtc/v4 v4.1.8 h1:ynkjfiURDQ1+8EcJsoa60yumHAmyeYjz08AaOuor+sk=
|
||||
github.com/pion/webrtc/v4 v4.1.8/go.mod h1:KVaARG2RN0lZx0jc7AWTe38JpPv+1/KicOZ9jN52J/s=
|
||||
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=
|
||||
|
|
@ -101,6 +119,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tinylib/msgp v1.5.0 h1:GWnqAE54wmnlFazjq2+vgr736Akg58iiHImh+kPY2pc=
|
||||
github.com/tinylib/msgp v1.5.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
|
||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
|
|
@ -111,17 +131,27 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
|||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
|
||||
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
|
||||
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
||||
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
|||
|
|
@ -341,9 +341,9 @@ encoder:
|
|||
frames:
|
||||
- 10
|
||||
- 5
|
||||
# linear (1) or nearest neighbour (0) audio resampler
|
||||
# linear should sound slightly better
|
||||
resampler: 1
|
||||
# speex (2), linear (1) or nearest neighbour (0) audio resampler
|
||||
# linear should sound slightly better than 0
|
||||
resampler: 2
|
||||
video:
|
||||
# h264, vpx (vp8) or vp9
|
||||
codec: h264
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
package media
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aam335/speexdsp"
|
||||
)
|
||||
|
||||
type ResampleAlgo uint8
|
||||
|
||||
const (
|
||||
ResampleNearest ResampleAlgo = iota
|
||||
ResampleLinear
|
||||
ResampleSpeex
|
||||
)
|
||||
|
||||
// preallocated scratch buffer for resampling output
|
||||
// size for max Opus frame: 60ms at 48kHz stereo = 48000 * 0.06 * 2 = 5760 samples
|
||||
var stretchBuf = make(samples, 5760)
|
||||
|
||||
// buffer is a simple non-concurrent safe buffer for audio samples.
|
||||
type buffer struct {
|
||||
useResample bool
|
||||
algo ResampleAlgo
|
||||
srcHz int
|
||||
|
||||
raw samples
|
||||
|
||||
buckets []bucket
|
||||
bi int
|
||||
raw samples
|
||||
scratch samples
|
||||
buckets []bucket
|
||||
resampler *speexdsp.Resampler
|
||||
srcHz int
|
||||
dstHz int
|
||||
bi int
|
||||
algo ResampleAlgo
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
|
|
@ -33,181 +33,150 @@ type bucket struct {
|
|||
}
|
||||
|
||||
func newBuffer(frames []float32, hz int) (*buffer, error) {
|
||||
if hz < 2000 {
|
||||
return nil, errors.New("hz should be > 2000")
|
||||
}
|
||||
if len(frames) == 0 {
|
||||
return nil, errors.New("frames list is empty")
|
||||
if hz < 2000 || len(frames) == 0 {
|
||||
return nil, errors.New("invalid params")
|
||||
}
|
||||
|
||||
buf := buffer{srcHz: hz}
|
||||
|
||||
totalSize := 0
|
||||
var totalSize int
|
||||
for _, f := range frames {
|
||||
totalSize += frameStereoSamples(hz, f)
|
||||
totalSize += stereoSamples(hz, f)
|
||||
}
|
||||
|
||||
if totalSize == 0 {
|
||||
return nil, errors.New("calculated buffer size is 0, check params")
|
||||
return nil, errors.New("zero buffer size")
|
||||
}
|
||||
|
||||
buf.raw = make(samples, totalSize)
|
||||
buf := &buffer{
|
||||
raw: make(samples, totalSize),
|
||||
scratch: make(samples, 5760),
|
||||
srcHz: hz,
|
||||
dstHz: hz,
|
||||
}
|
||||
|
||||
// map buckets to the raw continuous array
|
||||
offset := 0
|
||||
for _, f := range frames {
|
||||
size := frameStereoSamples(hz, f)
|
||||
buf.buckets = append(buf.buckets, bucket{
|
||||
mem: buf.raw[offset : offset+size],
|
||||
ms: f,
|
||||
})
|
||||
size := stereoSamples(hz, f)
|
||||
buf.buckets = append(buf.buckets, bucket{mem: buf.raw[offset : offset+size], ms: f, dst: size})
|
||||
offset += size
|
||||
}
|
||||
|
||||
// start with the largest bucket (last one, assuming frames are sorted ascending)
|
||||
buf.bi = len(buf.buckets) - 1
|
||||
|
||||
return &buf, nil
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// cur returns the current bucket pointer
|
||||
func (b *buffer) cur() *bucket { return &b.buckets[b.bi] }
|
||||
func (b *buffer) close() {
|
||||
if b.resampler != nil {
|
||||
b.resampler.Destroy()
|
||||
b.resampler = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *buffer) resample(targetHz int, algo ResampleAlgo) error {
|
||||
b.algo = algo
|
||||
b.dstHz = targetHz
|
||||
|
||||
for i := range b.buckets {
|
||||
b.buckets[i].dst = stereoSamples(targetHz, b.buckets[i].ms)
|
||||
}
|
||||
|
||||
if algo == ResampleSpeex {
|
||||
var err error
|
||||
if b.resampler, err = speexdsp.ResamplerInit(2, b.srcHz, targetHz, speexdsp.QualityDesktop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buffer) write(s samples, onFull func(samples, float32)) int {
|
||||
read := 0
|
||||
for read < len(s) {
|
||||
cur := &b.buckets[b.bi]
|
||||
n := copy(cur.mem[cur.p:], s[read:])
|
||||
read += n
|
||||
cur.p += n
|
||||
|
||||
if cur.p == len(cur.mem) {
|
||||
onFull(b.stretch(cur.mem, cur.dst), cur.ms)
|
||||
b.choose(len(s) - read)
|
||||
b.buckets[b.bi].p = 0
|
||||
}
|
||||
}
|
||||
return read
|
||||
}
|
||||
|
||||
// choose selects the best bucket for the remaining samples.
|
||||
// It picks the largest bucket that can be completely filled.
|
||||
// Buckets should be sorted by size ascending for this to work optimally.
|
||||
func (b *buffer) choose(remaining int) {
|
||||
// search from largest to smallest
|
||||
for i := len(b.buckets) - 1; i >= 0; i-- {
|
||||
if remaining >= len(b.buckets[i].mem) {
|
||||
b.bi = i
|
||||
return
|
||||
}
|
||||
}
|
||||
// fall back to smallest bucket if remaining < all bucket sizes
|
||||
b.bi = 0
|
||||
}
|
||||
|
||||
// resample enables resampling to target Hz with specified algorithm
|
||||
func (b *buffer) resample(targetHz int, algo ResampleAlgo) {
|
||||
b.useResample = true
|
||||
b.algo = algo
|
||||
for i := range b.buckets {
|
||||
b.buckets[i].dst = frameStereoSamples(targetHz, b.buckets[i].ms)
|
||||
}
|
||||
}
|
||||
|
||||
// stretch applies the selected resampling algorithm
|
||||
func (b *buffer) stretch(src samples, dstSize int) samples {
|
||||
switch b.algo {
|
||||
case ResampleNearest:
|
||||
return stretchNearest(src, dstSize)
|
||||
case ResampleLinear:
|
||||
return stretchLinear(src, dstSize)
|
||||
default:
|
||||
return stretchLinear(src, dstSize)
|
||||
}
|
||||
}
|
||||
|
||||
// write fills the buffer and calls onFull when a complete frame is ready.
|
||||
// returns the number of samples consumed.
|
||||
func (b *buffer) write(s samples, onFull func(samples, float32)) int {
|
||||
read := 0
|
||||
for read < len(s) {
|
||||
cur := b.cur()
|
||||
|
||||
// copy all samples into current bucket
|
||||
n := copy(cur.mem[cur.p:], s[read:])
|
||||
read += n
|
||||
cur.p += n
|
||||
|
||||
// bucket is full - emit frame
|
||||
if cur.p == len(cur.mem) {
|
||||
if b.useResample {
|
||||
onFull(b.stretch(cur.mem, cur.dst), cur.ms)
|
||||
} else {
|
||||
onFull(cur.mem, cur.ms)
|
||||
case ResampleSpeex:
|
||||
if b.resampler != nil {
|
||||
if _, out, err := b.resampler.PocessIntInterleaved(src); err == nil {
|
||||
if len(out) == dstSize {
|
||||
return out
|
||||
}
|
||||
src = out // use speex output for linear correction
|
||||
}
|
||||
|
||||
// select next bucket and reset write position
|
||||
b.choose(len(s) - read)
|
||||
b.cur().p = 0
|
||||
}
|
||||
fallthrough
|
||||
case ResampleLinear:
|
||||
return b.linear(src, dstSize)
|
||||
case ResampleNearest:
|
||||
return b.nearest(src, dstSize)
|
||||
default:
|
||||
return b.linear(src, dstSize)
|
||||
}
|
||||
return read
|
||||
}
|
||||
|
||||
// frameStereoSamples calculates stereo frame size in samples.
|
||||
// e.g., 48000 Hz * 20ms = 960 samples/channel * 2 channels = 1920 total samples
|
||||
func frameStereoSamples(hz int, ms float32) int {
|
||||
samplesPerChannel := int(float32(hz)*ms/1000 + 0.5) // round to nearest
|
||||
return samplesPerChannel * 2 // stereo
|
||||
}
|
||||
|
||||
// stretchLinear resamples stereo audio using linear interpolation.
|
||||
func stretchLinear(src samples, dstSize int) samples {
|
||||
func (b *buffer) linear(src samples, dstSize int) samples {
|
||||
srcLen := len(src)
|
||||
if srcLen < 2 || dstSize < 2 {
|
||||
return stretchBuf[:dstSize]
|
||||
return b.scratch[:dstSize]
|
||||
}
|
||||
|
||||
out := stretchBuf[:dstSize]
|
||||
|
||||
srcPairs := srcLen / 2
|
||||
dstPairs := dstSize / 2
|
||||
|
||||
// Fixed-point ratio for precision (16.16 fixed point)
|
||||
out := b.scratch[:dstSize]
|
||||
srcPairs, dstPairs := srcLen/2, dstSize/2
|
||||
ratio := ((srcPairs - 1) << 16) / (dstPairs - 1)
|
||||
|
||||
for i := 0; i < dstPairs; i++ {
|
||||
// Calculate source position in fixed-point
|
||||
pos := i * ratio
|
||||
srcIdx := pos >> 16
|
||||
frac := pos & 0xFFFF
|
||||
idx, frac := (pos>>16)*2, pos&0xFFFF
|
||||
di := i * 2
|
||||
|
||||
dstIdx := i * 2
|
||||
|
||||
if srcIdx >= srcPairs-1 {
|
||||
// Last sample - no interpolation
|
||||
out[dstIdx] = src[srcLen-2]
|
||||
out[dstIdx+1] = src[srcLen-1]
|
||||
if idx >= srcLen-2 {
|
||||
out[di], out[di+1] = src[srcLen-2], src[srcLen-1]
|
||||
} else {
|
||||
// Linear interpolation for both channels
|
||||
srcBase := srcIdx * 2
|
||||
|
||||
// Left channel
|
||||
l0 := int32(src[srcBase])
|
||||
l1 := int32(src[srcBase+2])
|
||||
out[dstIdx] = int16(l0 + ((l1-l0)*int32(frac))>>16)
|
||||
|
||||
// Right channel
|
||||
r0 := int32(src[srcBase+1])
|
||||
r1 := int32(src[srcBase+3])
|
||||
out[dstIdx+1] = int16(r0 + ((r1-r0)*int32(frac))>>16)
|
||||
out[di] = int16(int32(src[idx]) + ((int32(src[idx+2])-int32(src[idx]))*int32(frac))>>16)
|
||||
out[di+1] = int16(int32(src[idx+1]) + ((int32(src[idx+3])-int32(src[idx+1]))*int32(frac))>>16)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// stretchNearest is a faster nearest-neighbor version if quality isn't critical
|
||||
func stretchNearest(src samples, dstSize int) samples {
|
||||
func (b *buffer) nearest(src samples, dstSize int) samples {
|
||||
srcLen := len(src)
|
||||
if srcLen < 2 || dstSize < 2 {
|
||||
return stretchBuf[:dstSize]
|
||||
return b.scratch[:dstSize]
|
||||
}
|
||||
|
||||
out := stretchBuf[:dstSize]
|
||||
|
||||
srcPairs := srcLen / 2
|
||||
dstPairs := dstSize / 2
|
||||
out := b.scratch[:dstSize]
|
||||
srcPairs, dstPairs := srcLen/2, dstSize/2
|
||||
|
||||
for i := 0; i < dstPairs; i++ {
|
||||
srcIdx := (i * srcPairs / dstPairs) * 2
|
||||
dstIdx := i * 2
|
||||
out[dstIdx] = src[srcIdx]
|
||||
out[dstIdx+1] = src[srcIdx+1]
|
||||
si := (i * srcPairs / dstPairs) * 2
|
||||
di := i * 2
|
||||
out[di], out[di+1] = src[si], src[si+1]
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func stereoSamples(hz int, ms float32) int {
|
||||
return int(float32(hz)*ms/1000+0.5) * 2
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue