mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 12:17:06 +00:00
127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
// bridges PCM frames between an ntgcalls P2P call and pion's opus-over-RTP
|
|
package audio
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gopkg.in/hraban/opus.v2"
|
|
)
|
|
|
|
// hardcoded for 48kHz / stereo / 20ms / VoIP
|
|
const (
|
|
SampleRate = 48000
|
|
Channels = 2
|
|
|
|
OpusFrameMs = 20
|
|
NtgFrameMs = 10
|
|
|
|
// samples per channel
|
|
SamplesPerOpusFrame = SampleRate * OpusFrameMs / 1000 // 960
|
|
SamplesPerNtgFrame = SampleRate * NtgFrameMs / 1000 // 480
|
|
|
|
// libwebrtc may emit up to 120ms opus frames under load
|
|
MaxDecodeSamplesPerChannel = SampleRate * 120 / 1000 // 5760
|
|
|
|
MaxOpusPacketBytes = 1500
|
|
NtgFrameBytes = SamplesPerNtgFrame * Channels * 2 // 1920 (s16 stereo)
|
|
OpusInputBytes = SamplesPerOpusFrame * Channels * 2 // 3840 (s16 stereo)
|
|
|
|
// fallback when remote SDP omits maxaveragebitrate
|
|
DefaultBitrate = 128000
|
|
)
|
|
|
|
// thin opus.v2 wrapper fixed to the 48k/stereo/20ms/VoIP shape; fmtp.go
|
|
// layers SDP tuning on top. Keeps the CGO opus dep confined to this file.
|
|
type Encoder struct {
|
|
enc *opus.Encoder
|
|
}
|
|
|
|
func NewEncoder() (*Encoder, error) {
|
|
e, err := opus.NewEncoder(SampleRate, Channels, opus.AppVoIP)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opus.NewEncoder: %w", err)
|
|
}
|
|
if err := e.SetBitrate(DefaultBitrate); err != nil {
|
|
return nil, fmt.Errorf("opus SetBitrate: %w", err)
|
|
}
|
|
return &Encoder{enc: e}, nil
|
|
}
|
|
|
|
// one 20ms stereo frame into out; len(pcm) must be SamplesPerOpusFrame*Channels
|
|
func (e *Encoder) Encode(pcm []int16, out []byte) (int, error) {
|
|
if len(pcm) != SamplesPerOpusFrame*Channels {
|
|
return 0, fmt.Errorf("opus encode: pcm len %d, want %d", len(pcm), SamplesPerOpusFrame*Channels)
|
|
}
|
|
return e.enc.Encode(pcm, out)
|
|
}
|
|
|
|
func (e *Encoder) SetInBandFEC(on bool) error { return e.enc.SetInBandFEC(on) }
|
|
func (e *Encoder) SetDTX(on bool) error { return e.enc.SetDTX(on) }
|
|
func (e *Encoder) SetBitrate(bps int) error { return e.enc.SetBitrate(bps) }
|
|
func (e *Encoder) SetPacketLossPerc(p int) error { return e.enc.SetPacketLossPerc(p) }
|
|
func (e *Encoder) SetMaxBandwidth(bw opus.Bandwidth) error {
|
|
return e.enc.SetMaxBandwidth(bw)
|
|
}
|
|
|
|
// getters for tests asserting fmtp took effect
|
|
func (e *Encoder) InBandFEC() (bool, error) { return e.enc.InBandFEC() }
|
|
func (e *Encoder) DTX() (bool, error) { return e.enc.DTX() }
|
|
func (e *Encoder) Bitrate() (int, error) { return e.enc.Bitrate() }
|
|
func (e *Encoder) PacketLossPerc() (int, error) { return e.enc.PacketLossPerc() }
|
|
func (e *Encoder) MaxBandwidth() (opus.Bandwidth, error) { return e.enc.MaxBandwidth() }
|
|
|
|
type Decoder struct {
|
|
dec *opus.Decoder
|
|
}
|
|
|
|
func NewDecoder() (*Decoder, error) {
|
|
d, err := opus.NewDecoder(SampleRate, Channels)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opus.NewDecoder: %w", err)
|
|
}
|
|
return &Decoder{dec: d}, nil
|
|
}
|
|
|
|
// one opus packet -> interleaved s16 stereo PCM; out sized for max frame
|
|
func (d *Decoder) Decode(data []byte, out []int16) (int, error) {
|
|
if len(data) == 0 {
|
|
return 0, errors.New("opus decode: empty packet")
|
|
}
|
|
return d.dec.Decode(data, out)
|
|
}
|
|
|
|
// one PLC frame; out sized to the last successful Decode's frame
|
|
func (d *Decoder) DecodePLC(out []int16) error {
|
|
return d.dec.DecodePLC(out)
|
|
}
|
|
|
|
// SDP maxplaybackrate -> opus.Bandwidth (RFC 7587 §6.1)
|
|
func BandwidthForPlaybackRate(hz int) opus.Bandwidth {
|
|
switch {
|
|
case hz <= 8000:
|
|
return opus.Narrowband
|
|
case hz <= 12000:
|
|
return opus.Mediumband
|
|
case hz <= 16000:
|
|
return opus.Wideband
|
|
case hz <= 24000:
|
|
return opus.SuperWideband
|
|
default:
|
|
return opus.Fullband
|
|
}
|
|
}
|
|
|
|
// interleaved s16-LE bytes <-> int16 samples; dst must be sized correctly
|
|
func PCMBytesToInt16(src []byte, dst []int16) {
|
|
for i := range dst {
|
|
dst[i] = int16(binary.LittleEndian.Uint16(src[i*2:]))
|
|
}
|
|
}
|
|
|
|
func PCMInt16ToBytes(src []int16, dst []byte) {
|
|
for i, s := range src {
|
|
binary.LittleEndian.PutUint16(dst[i*2:], uint16(s))
|
|
}
|
|
}
|