package aoiprxkit import ( "fmt" "net" "strconv" "strings" "time" ) type SDPInfo struct { SessionName string Origin string MulticastGroup string Port int PayloadType uint8 Encoding string SampleRateHz int Channels int PacketTimeMS int } // ParseMinimalSDP extracts the multicast address, port and one rtpmap line. // It is deliberately small and not a full SDP parser. func ParseMinimalSDP(s string) (SDPInfo, error) { var out SDPInfo lines := strings.Split(strings.ReplaceAll(s, "\r\n", "\n"), "\n") for _, raw := range lines { line := strings.TrimSpace(raw) switch { case strings.HasPrefix(line, "s="): out.SessionName = strings.TrimPrefix(line, "s=") case strings.HasPrefix(line, "o="): out.Origin = strings.TrimPrefix(line, "o=") case strings.HasPrefix(line, "c=IN IP4 "): rest := strings.TrimPrefix(line, "c=IN IP4 ") host := strings.Split(rest, "/")[0] if net.ParseIP(host) == nil { return out, fmt.Errorf("invalid multicast host in c=: %q", host) } out.MulticastGroup = host case strings.HasPrefix(line, "m=audio "): fields := strings.Fields(line) if len(fields) < 4 { return out, fmt.Errorf("invalid m=audio line") } port, err := strconv.Atoi(fields[1]) if err != nil { return out, fmt.Errorf("invalid audio port: %w", err) } pt, err := strconv.Atoi(fields[3]) if err != nil { return out, fmt.Errorf("invalid payload type: %w", err) } out.Port = port out.PayloadType = uint8(pt) case strings.HasPrefix(line, "a=rtpmap:"): rest := strings.TrimPrefix(line, "a=rtpmap:") parts := strings.Fields(rest) if len(parts) != 2 { return out, fmt.Errorf("invalid rtpmap line") } pt, err := strconv.Atoi(parts[0]) if err != nil { return out, fmt.Errorf("invalid rtpmap payload type: %w", err) } codecParts := strings.Split(parts[1], "/") if len(codecParts) < 2 { return out, fmt.Errorf("invalid rtpmap codec tuple") } sr, err := strconv.Atoi(codecParts[1]) if err != nil { return out, fmt.Errorf("invalid rtpmap sample rate: %w", err) } ch := 1 if len(codecParts) >= 3 { ch, err = strconv.Atoi(codecParts[2]) if err != nil { return out, fmt.Errorf("invalid rtpmap channel count: %w", err) } } out.PayloadType = uint8(pt) out.Encoding = codecParts[0] out.SampleRateHz = sr out.Channels = ch case strings.HasPrefix(line, "a=ptime:"): ms, err := strconv.Atoi(strings.TrimPrefix(line, "a=ptime:")) if err == nil { out.PacketTimeMS = ms } } } if out.MulticastGroup == "" || out.Port == 0 || out.Encoding == "" || out.SampleRateHz == 0 { return out, fmt.Errorf("incomplete SDP: %+v", out) } return out, nil } func ConfigFromSDP(base Config, info SDPInfo) (Config, error) { cfg := base cfg.MulticastGroup = info.MulticastGroup cfg.Port = info.Port cfg.PayloadType = info.PayloadType cfg.SampleRateHz = info.SampleRateHz cfg.Channels = info.Channels cfg.Encoding = info.Encoding if info.PacketTimeMS > 0 { cfg.PacketTime = time.Duration(info.PacketTimeMS) * time.Millisecond } return cfg, cfg.Validate() }