Files
LocalAI/backend/go/opus/codec.go
Richard Palethorpe f9a850c02a feat(realtime): WebRTC support (#8790)
* feat(realtime): WebRTC support

Signed-off-by: Richard Palethorpe <io@richiejp.com>

* fix(tracing): Show full LLM opts and deltas

Signed-off-by: Richard Palethorpe <io@richiejp.com>

---------

Signed-off-by: Richard Palethorpe <io@richiejp.com>
2026-03-13 21:37:15 +01:00

257 lines
6.4 KiB
Go

package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"github.com/ebitengine/purego"
)
const (
ApplicationVoIP = 2048
ApplicationAudio = 2049
ApplicationRestrictedLowDelay = 2051
)
var (
initOnce sync.Once
initErr error
opusLib uintptr
shimLib uintptr
// libopus functions
cEncoderCreate func(fs int32, channels int32, application int32, errPtr *int32) uintptr
cEncode func(st uintptr, pcm *int16, frameSize int32, data *byte, maxBytes int32) int32
cEncoderDestroy func(st uintptr)
cDecoderCreate func(fs int32, channels int32, errPtr *int32) uintptr
cDecode func(st uintptr, data *byte, dataLen int32, pcm *int16, frameSize int32, decodeFec int32) int32
cDecoderDestroy func(st uintptr)
// shim functions (non-variadic wrappers for opus_encoder_ctl)
cSetBitrate func(st uintptr, bitrate int32) int32
cSetComplexity func(st uintptr, complexity int32) int32
)
func loadLib(names []string) (uintptr, error) {
var firstErr error
for _, name := range names {
h, err := purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL)
if err == nil {
return h, nil
}
if firstErr == nil {
firstErr = err
}
}
return 0, firstErr
}
func ensureInit() error {
initOnce.Do(func() {
initErr = doInit()
})
return initErr
}
const shimHint = "ensure libopus-dev is installed and rebuild, or set OPUS_LIBRARY / OPUS_SHIM_LIBRARY env vars"
func doInit() error {
opusNames := opusSearchPaths()
var err error
opusLib, err = loadLib(opusNames)
if err != nil {
return fmt.Errorf("opus: failed to load libopus (%s): %w", shimHint, err)
}
purego.RegisterLibFunc(&cEncoderCreate, opusLib, "opus_encoder_create")
purego.RegisterLibFunc(&cEncode, opusLib, "opus_encode")
purego.RegisterLibFunc(&cEncoderDestroy, opusLib, "opus_encoder_destroy")
purego.RegisterLibFunc(&cDecoderCreate, opusLib, "opus_decoder_create")
purego.RegisterLibFunc(&cDecode, opusLib, "opus_decode")
purego.RegisterLibFunc(&cDecoderDestroy, opusLib, "opus_decoder_destroy")
shimNames := shimSearchPaths()
shimLib, err = loadLib(shimNames)
if err != nil {
return fmt.Errorf("opus: failed to load libopusshim (%s): %w", shimHint, err)
}
purego.RegisterLibFunc(&cSetBitrate, shimLib, "opus_shim_encoder_set_bitrate")
purego.RegisterLibFunc(&cSetComplexity, shimLib, "opus_shim_encoder_set_complexity")
return nil
}
func opusSearchPaths() []string {
var paths []string
if env := os.Getenv("OPUS_LIBRARY"); env != "" {
paths = append(paths, env)
}
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
paths = append(paths, filepath.Join(dir, "libopus.so.0"), filepath.Join(dir, "libopus.so"))
if runtime.GOOS == "darwin" {
paths = append(paths, filepath.Join(dir, "libopus.dylib"))
}
}
paths = append(paths, "libopus.so.0", "libopus.so", "libopus.dylib", "opus.dll")
if runtime.GOOS == "darwin" {
paths = append(paths,
"/opt/homebrew/lib/libopus.dylib",
"/usr/local/lib/libopus.dylib",
)
}
return paths
}
func shimSearchPaths() []string {
var paths []string
if env := os.Getenv("OPUS_SHIM_LIBRARY"); env != "" {
paths = append(paths, env)
}
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
paths = append(paths, filepath.Join(dir, "libopusshim.so"))
if runtime.GOOS == "darwin" {
paths = append(paths, filepath.Join(dir, "libopusshim.dylib"))
}
}
paths = append(paths, "./libopusshim.so", "libopusshim.so")
if runtime.GOOS == "darwin" {
paths = append(paths, "./libopusshim.dylib", "libopusshim.dylib")
}
return paths
}
// Encoder wraps a libopus OpusEncoder via purego.
type Encoder struct {
st uintptr
}
func NewEncoder(sampleRate, channels, application int) (*Encoder, error) {
if err := ensureInit(); err != nil {
return nil, err
}
var opusErr int32
st := cEncoderCreate(int32(sampleRate), int32(channels), int32(application), &opusErr)
if opusErr != 0 || st == 0 {
return nil, fmt.Errorf("opus_encoder_create failed: error %d", opusErr)
}
return &Encoder{st: st}, nil
}
// Encode encodes a frame of PCM int16 samples. It returns the number of bytes
// written to out, or a negative error code.
func (e *Encoder) Encode(pcm []int16, frameSize int, out []byte) (int, error) {
if len(pcm) == 0 || len(out) == 0 {
return 0, errors.New("opus encode: empty input or output buffer")
}
n := cEncode(e.st, &pcm[0], int32(frameSize), &out[0], int32(len(out)))
if n < 0 {
return 0, fmt.Errorf("opus_encode failed: error %d", n)
}
return int(n), nil
}
func (e *Encoder) SetBitrate(bitrate int) error {
if ret := cSetBitrate(e.st, int32(bitrate)); ret != 0 {
return fmt.Errorf("opus set bitrate: error %d", ret)
}
return nil
}
func (e *Encoder) SetComplexity(complexity int) error {
if ret := cSetComplexity(e.st, int32(complexity)); ret != 0 {
return fmt.Errorf("opus set complexity: error %d", ret)
}
return nil
}
func (e *Encoder) Close() {
if e.st != 0 {
cEncoderDestroy(e.st)
e.st = 0
}
}
// Decoder wraps a libopus OpusDecoder via purego.
type Decoder struct {
st uintptr
}
func NewDecoder(sampleRate, channels int) (*Decoder, error) {
if err := ensureInit(); err != nil {
return nil, err
}
var opusErr int32
st := cDecoderCreate(int32(sampleRate), int32(channels), &opusErr)
if opusErr != 0 || st == 0 {
return nil, fmt.Errorf("opus_decoder_create failed: error %d", opusErr)
}
return &Decoder{st: st}, nil
}
// Decode decodes an Opus packet into pcm. frameSize is the max number of
// samples per channel that pcm can hold. Returns the number of decoded samples
// per channel.
func (d *Decoder) Decode(data []byte, pcm []int16, frameSize int, fec bool) (int, error) {
if len(pcm) == 0 {
return 0, errors.New("opus decode: empty output buffer")
}
var dataPtr *byte
var dataLen int32
if len(data) > 0 {
dataPtr = &data[0]
dataLen = int32(len(data))
}
decodeFec := int32(0)
if fec {
decodeFec = 1
}
n := cDecode(d.st, dataPtr, dataLen, &pcm[0], int32(frameSize), decodeFec)
if n < 0 {
return 0, fmt.Errorf("opus_decode failed: error %d", n)
}
return int(n), nil
}
func (d *Decoder) Close() {
if d.st != 0 {
cDecoderDestroy(d.st)
d.st = 0
}
}
// Init eagerly loads the opus libraries, returning any error.
// Calling this is optional; the libraries are loaded lazily on first use.
func Init() error {
return ensureInit()
}
// Reset allows re-initialization (for testing).
func Reset() {
initOnce = sync.Once{}
initErr = nil
opusLib = 0
shimLib = 0
}