mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 12:17:06 +00:00
142 lines
3.4 KiB
Go
142 lines
3.4 KiB
Go
package audio
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// opus fmtp params from peer SDP.
|
|
// ApplyTo honors UseInBandFEC, UseDTX, MaxAverageBitrate, MaxPlaybackRate.
|
|
// CBR/Stereo/Ptime are parsed but not applied (pipeline is fixed shape and
|
|
// hraban/opus.v2 doesn't wrap OPUS_SET_VBR).
|
|
type Fmtp struct {
|
|
UseInBandFEC bool // useinbandfec=1
|
|
UseDTX bool // usedtx=1
|
|
CBR bool // cbr=1 (not applied; see type doc)
|
|
MaxAverageBitrate int // maxaveragebitrate=<bps>, 0 = unspecified
|
|
MaxPlaybackRate int // maxplaybackrate=<Hz>, 0 = unspecified
|
|
SpropStereo bool // sprop-stereo=1 (not applied)
|
|
Stereo bool // stereo=1 (not applied)
|
|
MinPtime int // ms (not applied)
|
|
MaxPtime int // ms (not applied)
|
|
}
|
|
|
|
// params half of the opus a=fmtp line; empty if no opus fmtp.
|
|
// Two-pass because pion may emit rtpmap and fmtp in any order.
|
|
func ExtractOpusFmtp(sdp string) string {
|
|
opusPTs := make(map[string]bool)
|
|
type fmtpEntry struct {
|
|
pt string
|
|
params string
|
|
}
|
|
var fmtps []fmtpEntry
|
|
for _, raw := range strings.Split(sdp, "\n") {
|
|
line := strings.TrimRight(raw, "\r")
|
|
if !strings.HasPrefix(line, "a=") {
|
|
continue
|
|
}
|
|
body := line[2:]
|
|
if rest, ok := strings.CutPrefix(body, "rtpmap:"); ok {
|
|
// "<pt> opus/48000/2"
|
|
sp := strings.IndexByte(rest, ' ')
|
|
if sp < 0 {
|
|
continue
|
|
}
|
|
pt, codec := rest[:sp], rest[sp+1:]
|
|
if strings.HasPrefix(strings.ToLower(codec), "opus/") {
|
|
opusPTs[pt] = true
|
|
}
|
|
continue
|
|
}
|
|
if rest, ok := strings.CutPrefix(body, "fmtp:"); ok {
|
|
sp := strings.IndexByte(rest, ' ')
|
|
if sp < 0 {
|
|
continue
|
|
}
|
|
fmtps = append(fmtps, fmtpEntry{pt: rest[:sp], params: rest[sp+1:]})
|
|
}
|
|
}
|
|
for _, f := range fmtps {
|
|
if opusPTs[f.pt] {
|
|
return f.params
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// unknown keys and malformed numerics are silently ignored; weird SDP
|
|
// shouldn't kill the call
|
|
func ParseFmtp(s string) Fmtp {
|
|
var f Fmtp
|
|
for _, kv := range strings.Split(s, ";") {
|
|
kv = strings.TrimSpace(kv)
|
|
if kv == "" {
|
|
continue
|
|
}
|
|
eq := strings.IndexByte(kv, '=')
|
|
if eq < 0 {
|
|
continue
|
|
}
|
|
key := strings.ToLower(strings.TrimSpace(kv[:eq]))
|
|
val := strings.TrimSpace(kv[eq+1:])
|
|
switch key {
|
|
case "useinbandfec":
|
|
f.UseInBandFEC = val == "1"
|
|
case "usedtx":
|
|
f.UseDTX = val == "1"
|
|
case "cbr":
|
|
f.CBR = val == "1"
|
|
case "maxaveragebitrate":
|
|
if n, err := strconv.Atoi(val); err == nil {
|
|
f.MaxAverageBitrate = n
|
|
}
|
|
case "maxplaybackrate":
|
|
if n, err := strconv.Atoi(val); err == nil {
|
|
f.MaxPlaybackRate = n
|
|
}
|
|
case "sprop-stereo":
|
|
f.SpropStereo = val == "1"
|
|
case "stereo":
|
|
f.Stereo = val == "1"
|
|
case "minptime":
|
|
if n, err := strconv.Atoi(val); err == nil {
|
|
f.MinPtime = n
|
|
}
|
|
case "maxptime":
|
|
if n, err := strconv.Atoi(val); err == nil {
|
|
f.MaxPtime = n
|
|
}
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (f Fmtp) ApplyTo(e *Encoder) error {
|
|
bitrate := f.MaxAverageBitrate
|
|
if bitrate <= 0 {
|
|
bitrate = DefaultBitrate
|
|
}
|
|
if err := e.SetBitrate(bitrate); err != nil {
|
|
return err
|
|
}
|
|
if f.MaxPlaybackRate > 0 {
|
|
if err := e.SetMaxBandwidth(BandwidthForPlaybackRate(f.MaxPlaybackRate)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if f.UseInBandFEC {
|
|
if err := e.SetInBandFEC(true); err != nil {
|
|
return err
|
|
}
|
|
// hint to opus, not a guarantee
|
|
if err := e.SetPacketLossPerc(5); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if f.UseDTX {
|
|
if err := e.SetDTX(true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|