mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-01 13:42:20 -04:00
* 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>
257 lines
6.4 KiB
Go
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
|
|
}
|