Files

83 lines
2.3 KiB
Go

package nativeaudio
import (
"encoding/binary"
"errors"
gopus "github.com/thesyncim/gopus"
)
const (
SampleRate = 48_000
Channels = 2
FrameSamples = 960
FrameBytes = FrameSamples * Channels * 2
MaxPacketBytes = 4_000
DefaultBitrate = 128_000
defaultComplexity = 5
)
// Encoder adapts the capture process's fixed 48 kHz PCM frames to WebRTC
// Opus packets. The buffers are owned by the encoder and reused after each
// Encode call; the returned packet must be consumed before the next call.
type Encoder struct {
codec *gopus.Encoder
pcm [FrameSamples * Channels]int16
packet [MaxPacketBytes]byte
}
func (encoder *Encoder) Bitrate() int { return encoder.codec.Bitrate() }
func (encoder *Encoder) SetBitrate(bitrate int) error {
if encoder == nil || encoder.codec == nil || bitrate < 6_000 || bitrate > 510_000 {
return errors.New("native audio bitrate is outside the Opus range")
}
return encoder.codec.SetBitrate(bitrate)
}
func NewEncoder(bitrate int) (*Encoder, error) {
if bitrate < 6_000 || bitrate > 510_000 {
return nil, errors.New("native audio bitrate is outside the Opus range")
}
codec, err := gopus.NewEncoder(gopus.EncoderConfig{
SampleRate: SampleRate,
Channels: Channels,
Application: gopus.ApplicationAudio,
})
if err != nil {
return nil, err
}
if err = codec.SetBitrate(bitrate); err != nil {
return nil, err
}
if err = codec.SetComplexity(defaultComplexity); err != nil {
return nil, err
}
codec.SetDTX(false)
if err = codec.SetInBandFEC(gopus.InBandFECDisabled); err != nil {
return nil, err
}
return &Encoder{codec: codec}, nil
}
// Encode accepts one interleaved little-endian PCM16 frame and returns an
// Opus packet backed by the encoder's reusable output buffer.
func (encoder *Encoder) Encode(pcm []byte) ([]byte, error) {
if encoder == nil || encoder.codec == nil || len(pcm) != FrameBytes {
return nil, errors.New("native audio PCM frame is invalid")
}
for index := range encoder.pcm {
encoder.pcm[index] = int16(binary.LittleEndian.Uint16(
pcm[index*2 : index*2+2],
))
}
count, err := encoder.codec.EncodeInt16(encoder.pcm[:], encoder.packet[:])
if err != nil {
return nil, err
}
if count <= 0 || count > len(encoder.packet) {
return nil, errors.New("native audio encoder returned an invalid packet")
}
return encoder.packet[:count], nil
}