cloud-game/pkg/encoder/opus/encoder.go
sergystepanov 602b9ea47c
Make executables ready for static linking (#307)
* Disable CGO (C libs) for coordinator

Needed for static linking.

* Fix "error strings should not be capitalized (ST1005)"

* Fix SA1015

Using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here (SA1015)

* Fix SA9004

Only the first constant in this group has an explicit type (SA9004).

* Remove unused code in the webrtc package

* Fix S1000

Should use a simple channel send/receive instead of select with a single case (S1000).

* Force dir creation/check for core downloader

* Update Makefile release script

* Disable ASLR for worker builds

* Remove WORKER_BUILD_PARAMS flag from the CI

* Don't force recompilation in release

* Add Debian packages installer script

* Build worker app without libopusfile

* Test worker app without libopusfile

* Add opus wrapper without opusfile

* Make install.sh executable

* Add opus lib in Win builds

* Make insecure HTTPS requests when downloading libs

* Add ca-certificates for Docker build stage

go mod download works only with SSL certificates installed.

* Move libx264 wrapper into the repo

That way we can get rid of a lot of not needed external Go dependencies.

* Update the dependencies
2021-04-06 18:16:52 +03:00

136 lines
3.4 KiB
Go

package opus
import (
"fmt"
"log"
)
type Encoder struct {
*LibOpusEncoder
channels int
inFrequency int
outFrequency int
// OPUS output buffer, 1K should be enough
outBufferSize int
buffer Buffer
onFullBuffer func(data []byte)
resampleBufSize int
}
func NewEncoder(inputSampleRate, outputSampleRate, channels int, options ...func(*Encoder) error) (Encoder, error) {
encoder, err := NewOpusEncoder(
outputSampleRate,
channels,
// be aware that low delay option is not optimized for voice
AppRestrictedLowdelay,
)
if err != nil {
return Encoder{}, err
}
enc := &Encoder{
LibOpusEncoder: encoder,
buffer: Buffer{Data: make([]int16, inputSampleRate*20/1000*channels)},
channels: channels,
inFrequency: inputSampleRate,
outFrequency: outputSampleRate,
outBufferSize: 1024,
onFullBuffer: func(data []byte) {},
}
_ = enc.SetMaxBandwidth(FullBand)
_ = enc.SetBitrate(192000)
_ = enc.SetComplexity(10)
for _, option := range options {
err := option(enc)
if err != nil {
return Encoder{}, err
}
}
return *enc, nil
}
func SampleBuffer(ms int, resampling bool) func(*Encoder) error {
return func(e *Encoder) (err error) {
e.buffer = Buffer{Data: make([]int16, e.inFrequency*ms/1000*e.channels)}
if resampling {
e.resampleBufSize = e.outFrequency * ms / 1000 * e.channels
}
return
}
}
func CallbackOnFullBuffer(fn func(_ []byte)) func(*Encoder) error {
return func(e *Encoder) (err error) {
e.onFullBuffer = fn
return
}
}
func (e *Encoder) BufferWrite(samples []int16) (written int) {
n := len(samples)
for k := n / len(e.buffer.Data); written < n || k >= 0; k-- {
written += e.buffer.Write(samples[written:])
if e.buffer.Full() {
data, err := e.Encode(e.buffer.Data)
if err != nil {
log.Println("[!] Failed to encode", err)
continue
}
e.onFullBuffer(data)
}
}
return
}
func (e *Encoder) Encode(pcm []int16) ([]byte, error) {
if e.resampleBufSize > 0 {
pcm = resampleFn(pcm, e.resampleBufSize)
}
data := make([]byte, e.outBufferSize)
n, err := e.LibOpusEncoder.Encode(pcm, data)
if err != nil {
return []byte{}, err
}
return data[:n], nil
}
func (e *Encoder) GetInfo() string {
bitrate, _ := e.LibOpusEncoder.Bitrate()
complexity, _ := e.LibOpusEncoder.Complexity()
dtx, _ := e.LibOpusEncoder.DTX()
fec, _ := e.LibOpusEncoder.FEC()
maxBandwidth, _ := e.LibOpusEncoder.MaxBandwidth()
lossPercent, _ := e.LibOpusEncoder.PacketLossPerc()
sampleRate, _ := e.LibOpusEncoder.SampleRate()
return fmt.Sprintf(
"%v, Bitrate: %v bps, Complexity: %v, DTX: %v, FEC: %v, Max bandwidth: *%v, Loss%%: %v, Rate: %v Hz",
CodecVersion(), bitrate, complexity, dtx, fec, maxBandwidth, lossPercent, sampleRate,
)
}
// resampleFn does a simple linear interpolation of audio samples.
func resampleFn(pcm []int16, size int) []int16 {
r, l, audio := make([]int16, size/2), make([]int16, size/2), make([]int16, size)
// ratio is basically the destination sample rate
// divided by the origin sample rate (i.e. 48000/44100)
ratio := float32(size) / float32(len(pcm))
for i, n := 0, len(pcm)-1; i < n; i += 2 {
idx := int(float32(i/2) * ratio)
r[idx], l[idx] = pcm[i], pcm[i+1]
}
for i, n := 1, len(r); i < n; i++ {
if r[i] == 0 {
r[i] = r[i-1]
}
if l[i] == 0 {
l[i] = l[i-1]
}
}
for i := 0; i < size-1; i += 2 {
audio[i], audio[i+1] = r[i/2], l[i/2]
}
return audio
}