Files
funknetz/internal/opus/opus.go
T
konradandCursor 3d1a2a1669 Speak protocol 3.0 and fall back to 2.0 only when that is the reflector's maximum.
A reflector that has moved to 3.0 will not accept a station that always announces 2.0, and a 2.x reflector still needs one explicit plaintext session.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 14:24:00 +02:00

137 lines
3.2 KiB
Go

// Package opus encodes and decodes the reflector's 16 kHz mono frames with libopus.
package opus
/*
#cgo pkg-config: opus
#include <opus.h>
static void reset_decoder(OpusDecoder *dec) {
opus_decoder_ctl(dec, OPUS_RESET_STATE);
}
static int configure_encoder(OpusEncoder *enc) {
int err;
err = opus_encoder_ctl(enc, OPUS_SET_BITRATE(20000));
if (err < 0) return err;
err = opus_encoder_ctl(enc, OPUS_SET_VBR(1));
if (err < 0) return err;
err = opus_encoder_ctl(enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
if (err < 0) return err;
err = opus_encoder_ctl(enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_MEDIUMBAND));
if (err < 0) return err;
err = opus_encoder_ctl(enc, OPUS_SET_DTX(0));
return err;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
// FrameSamples is the 20 ms frame at 16 kHz, which is what the reflector carries.
const FrameSamples = 320
// Encoder produces one raw Opus packet per 20 ms frame.
type Encoder struct {
enc *C.OpusEncoder
}
// Decoder turns one raw Opus packet into 320 mono samples.
type Decoder struct {
dec *C.OpusDecoder
}
// NewEncoder returns a mono 16 kHz voice encoder at 20 kbit/s.
func NewEncoder() (*Encoder, error) {
var err C.int
enc := C.opus_encoder_create(16000, 1, C.OPUS_APPLICATION_AUDIO, &err)
if err < 0 || enc == nil {
return nil, fmt.Errorf("opus encoder %d", int(err))
}
if c := C.configure_encoder(enc); c < 0 {
C.opus_encoder_destroy(enc)
return nil, fmt.Errorf("opus encoder setup %d", int(c))
}
return &Encoder{enc: enc}, nil
}
// Encode encodes exactly 320 samples.
func (e *Encoder) Encode(pcm []int16) ([]byte, error) {
if len(pcm) != FrameSamples {
return nil, fmt.Errorf("opus frame is %d samples", len(pcm))
}
out := make([]byte, 4000)
n := C.opus_encode(
e.enc,
(*C.opus_int16)(unsafe.Pointer(&pcm[0])),
FrameSamples,
(*C.uchar)(unsafe.Pointer(&out[0])),
C.opus_int32(len(out)),
)
if n < 0 {
return nil, fmt.Errorf("opus encode %d", int(n))
}
return append([]byte(nil), out[:n]...), nil
}
// Close releases the encoder.
func (e *Encoder) Close() {
if e == nil || e.enc == nil {
return
}
C.opus_encoder_destroy(e.enc)
e.enc = nil
}
// NewDecoder returns a mono 16 kHz decoder.
func NewDecoder() (*Decoder, error) {
var err C.int
dec := C.opus_decoder_create(16000, 1, &err)
if err < 0 || dec == nil {
return nil, fmt.Errorf("opus decoder %d", int(err))
}
return &Decoder{dec: dec}, nil
}
// Decode decodes one packet into 320 samples.
func (d *Decoder) Decode(packet []byte) ([]int16, error) {
if len(packet) == 0 {
return nil, fmt.Errorf("empty opus packet")
}
pcm := make([]int16, FrameSamples)
n := C.opus_decode(
d.dec,
(*C.uchar)(unsafe.Pointer(&packet[0])),
C.opus_int32(len(packet)),
(*C.opus_int16)(unsafe.Pointer(&pcm[0])),
FrameSamples,
0,
)
if n < 0 {
return nil, fmt.Errorf("opus decode %d", int(n))
}
if int(n) != FrameSamples {
return nil, fmt.Errorf("opus decode returned %d samples", int(n))
}
return pcm, nil
}
// Reset clears decoder state after a gap in the received audio.
func (d *Decoder) Reset() {
if d == nil || d.dec == nil {
return
}
C.reset_decoder(d.dec)
}
// Close releases the decoder.
func (d *Decoder) Close() {
if d == nil || d.dec == nil {
return
}
C.opus_decoder_destroy(d.dec)
d.dec = nil
}