Join one FM-Funknetz talk group from a headset window.

The station reads ~/.funknetz.json, speaks protocol 2.0, and builds an unsigned funknetz.app.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Konrad Neitzel
2026-10-03 16:21:41 +02:00
co-authored by Cursor
parent 8e75515460
commit 9c4f6621fc
23 changed files with 2933 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
// Package opus encodes and decodes the reflector's 16 kHz mono frames with libopus.
package opus
/*
#cgo pkg-config: opus
#include <opus.h>
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"
)
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
}
// Close releases the decoder.
func (d *Decoder) Close() {
if d == nil || d.dec == nil {
return
}
C.opus_decoder_destroy(d.dec)
d.dec = nil
}