mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 18:46:11 +00:00
138 lines
3.3 KiB
Go
138 lines
3.3 KiB
Go
package opus
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"gopkg.in/hraban/opus.v2"
|
|
)
|
|
|
|
type Encoder struct {
|
|
*opus.Encoder
|
|
|
|
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 := opus.NewEncoder(
|
|
outputSampleRate,
|
|
channels,
|
|
// be aware that low delay option is not optimized for voice
|
|
opus.AppRestrictedLowdelay,
|
|
)
|
|
if err != nil {
|
|
return Encoder{}, err
|
|
}
|
|
enc := &Encoder{
|
|
Encoder: encoder,
|
|
buffer: Buffer{Data: make([]int16, inputSampleRate*20/1000*channels)},
|
|
channels: channels,
|
|
inFrequency: inputSampleRate,
|
|
outFrequency: outputSampleRate,
|
|
outBufferSize: 1024,
|
|
onFullBuffer: func(data []byte) {},
|
|
}
|
|
|
|
_ = enc.SetMaxBandwidth(opus.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.Encoder.Encode(pcm, data)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
return data[:n], nil
|
|
}
|
|
|
|
func (e *Encoder) GetInfo() string {
|
|
bitrate, _ := e.Encoder.Bitrate()
|
|
complexity, _ := e.Encoder.Complexity()
|
|
dtx, _ := e.Encoder.DTX()
|
|
fec, _ := e.Encoder.InBandFEC()
|
|
maxBandwidth, _ := e.Encoder.MaxBandwidth()
|
|
lossPercent, _ := e.Encoder.PacketLossPerc()
|
|
sampleRate, _ := e.Encoder.SampleRate()
|
|
return fmt.Sprintf(
|
|
"Bitrate: %v bps, Complexity: %v, DTX: %v, FEC: %v, Max bandwidth: *%v, Loss%%: %v, Rate: %v Hz",
|
|
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
|
|
}
|