install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/ice/v4"
|
||||
"github.com/pion/sdp/v3"
|
||||
"github.com/pion/stun/v3"
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
func UnmarshalMedias(descriptions []*sdp.MediaDescription) (medias []*core.Media) {
|
||||
// 1. Sort medias, so video will always be before audio
|
||||
// 2. Ignore application media from Hass default lovelace card
|
||||
// 3. Ignore media without direction (inactive media)
|
||||
// 4. Inverse media direction (because it is remote peer medias list)
|
||||
for _, kind := range []string{core.KindVideo, core.KindAudio} {
|
||||
for _, md := range descriptions {
|
||||
if md.MediaName.Media != kind {
|
||||
continue
|
||||
}
|
||||
|
||||
media := core.UnmarshalMedia(md)
|
||||
switch media.Direction {
|
||||
case core.DirectionSendRecv:
|
||||
media.Direction = core.DirectionRecvonly
|
||||
medias = append(medias, media)
|
||||
|
||||
media = media.Clone()
|
||||
media.Direction = core.DirectionSendonly
|
||||
|
||||
case core.DirectionRecvonly:
|
||||
media.Direction = core.DirectionSendonly
|
||||
|
||||
case core.DirectionSendonly:
|
||||
media.Direction = core.DirectionRecvonly
|
||||
|
||||
case "":
|
||||
continue
|
||||
}
|
||||
|
||||
// skip non-media codecs to avoid confusing users in info and logs
|
||||
media.Codecs = SkipNonMediaCodecs(media.Codecs)
|
||||
|
||||
medias = append(medias, media)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func SkipNonMediaCodecs(input []*core.Codec) (output []*core.Codec) {
|
||||
for _, codec := range input {
|
||||
switch codec.Name {
|
||||
case "RTX", "RED", "ULPFEC", "FLEXFEC-03":
|
||||
continue
|
||||
case "CN", "TELEPHONE-EVENT":
|
||||
continue // https://datatracker.ietf.org/doc/html/rfc7874
|
||||
}
|
||||
// VP8, VP9, H264, H265, AV1
|
||||
// OPUS, G722, PCMU, PCMA
|
||||
output = append(output, codec)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WithResampling - will add for consumer: PCMA/0, PCMU/0, PCM/0, PCML/0
|
||||
// so it can add resampling for PCMA/PCMU and repack for PCM/PCML
|
||||
func WithResampling(medias []*core.Media) []*core.Media {
|
||||
for _, media := range medias {
|
||||
if media.Kind != core.KindAudio || media.Direction != core.DirectionSendonly {
|
||||
continue
|
||||
}
|
||||
|
||||
var pcma, pcmu, pcm, pcml *core.Codec
|
||||
|
||||
for _, codec := range media.Codecs {
|
||||
switch codec.Name {
|
||||
case core.CodecPCMA:
|
||||
if codec.ClockRate != 0 {
|
||||
pcma = codec
|
||||
} else {
|
||||
pcma = nil
|
||||
}
|
||||
case core.CodecPCMU:
|
||||
if codec.ClockRate != 0 {
|
||||
pcmu = codec
|
||||
} else {
|
||||
pcmu = nil
|
||||
}
|
||||
case core.CodecPCM:
|
||||
pcm = codec
|
||||
case core.CodecPCML:
|
||||
pcml = codec
|
||||
}
|
||||
}
|
||||
|
||||
if pcma != nil {
|
||||
pcma = pcma.Clone()
|
||||
pcma.ClockRate = 0 // reset clock rate so will match any
|
||||
media.Codecs = append(media.Codecs, pcma)
|
||||
}
|
||||
if pcmu != nil {
|
||||
pcmu = pcmu.Clone()
|
||||
pcmu.ClockRate = 0
|
||||
media.Codecs = append(media.Codecs, pcmu)
|
||||
}
|
||||
if pcma != nil && pcm == nil {
|
||||
pcm = pcma.Clone()
|
||||
pcm.Name = core.CodecPCM
|
||||
media.Codecs = append(media.Codecs, pcm)
|
||||
}
|
||||
if pcma != nil && pcml == nil {
|
||||
pcml = pcma.Clone()
|
||||
pcml.Name = core.CodecPCML
|
||||
media.Codecs = append(media.Codecs, pcml)
|
||||
}
|
||||
}
|
||||
|
||||
return medias
|
||||
}
|
||||
|
||||
func NewCandidate(network, address string) (string, error) {
|
||||
i := strings.LastIndexByte(address, ':')
|
||||
if i < 0 {
|
||||
return "", errors.New("wrong candidate: " + address)
|
||||
}
|
||||
host, port := address[:i], address[i+1:]
|
||||
|
||||
i, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
config := &ice.CandidateHostConfig{
|
||||
Network: network,
|
||||
Address: host,
|
||||
Port: i,
|
||||
Component: ice.ComponentRTP,
|
||||
}
|
||||
|
||||
if network == "tcp" {
|
||||
config.TCPType = ice.TCPTypePassive
|
||||
}
|
||||
|
||||
cand, err := ice.NewCandidateHost(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "candidate:" + cand.Marshal(), nil
|
||||
}
|
||||
|
||||
func LookupIP(address string) (string, error) {
|
||||
if strings.HasPrefix(address, "stun:") {
|
||||
ip, err := GetCachedPublicIP()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ip.String() + address[4:], nil
|
||||
}
|
||||
|
||||
if IsIP(address) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
i := strings.IndexByte(address, ':')
|
||||
ips, err := net.LookupIP(address[:i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return "", fmt.Errorf("can't resolve: %s", address)
|
||||
}
|
||||
|
||||
return ips[0].String() + address[i:], nil
|
||||
}
|
||||
|
||||
// GetPublicIP example from https://github.com/pion/stun
|
||||
func GetPublicIP(address string) (net.IP, error) {
|
||||
conn, err := net.Dial("udp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := stun.NewClient(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = conn.SetDeadline(time.Now().Add(time.Second * 3)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res stun.Event
|
||||
|
||||
message := stun.MustBuild(stun.TransactionID, stun.BindingRequest)
|
||||
if err = c.Do(message, func(e stun.Event) { res = e }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = c.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
|
||||
var xorAddr stun.XORMappedAddress
|
||||
if err = xorAddr.GetFrom(res.Message); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return xorAddr.IP, nil
|
||||
}
|
||||
|
||||
var cachedIP net.IP
|
||||
var cachedTS time.Time
|
||||
|
||||
func GetCachedPublicIP(stuns ...string) (net.IP, error) {
|
||||
if now := time.Now(); now.After(cachedTS) {
|
||||
for _, addr := range stuns {
|
||||
if ip, _ := GetPublicIP(addr); ip != nil {
|
||||
cachedIP = ip
|
||||
cachedTS = now.Add(time.Minute * 5)
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if cachedIP == nil {
|
||||
return nil, errors.New("webrtc: can't get public IP")
|
||||
}
|
||||
return cachedIP, nil
|
||||
}
|
||||
|
||||
func IsIP(host string) bool {
|
||||
for _, i := range host {
|
||||
if i >= 'A' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func MimeType(codec *core.Codec) string {
|
||||
switch codec.Name {
|
||||
case core.CodecH264:
|
||||
return webrtc.MimeTypeH264
|
||||
case core.CodecH265:
|
||||
return webrtc.MimeTypeH265
|
||||
case core.CodecVP8:
|
||||
return webrtc.MimeTypeVP8
|
||||
case core.CodecVP9:
|
||||
return webrtc.MimeTypeVP9
|
||||
case core.CodecAV1:
|
||||
return webrtc.MimeTypeAV1
|
||||
case core.CodecPCMU:
|
||||
return webrtc.MimeTypePCMU
|
||||
case core.CodecPCMA:
|
||||
return webrtc.MimeTypePCMA
|
||||
case core.CodecOpus:
|
||||
return webrtc.MimeTypeOpus
|
||||
case core.CodecG722:
|
||||
return webrtc.MimeTypeG722
|
||||
}
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func CandidateICE(network, host, port string, priority uint32) string {
|
||||
// 1. Foundation
|
||||
// 2. Component, always 1 because RTP
|
||||
// 3. "udp" or "tcp"
|
||||
// 4. Priority
|
||||
// 5. Host - IP4 or IP6 or domain name
|
||||
// 6. Port
|
||||
// 7. "typ host"
|
||||
foundation := crc32.ChecksumIEEE([]byte("host" + host + network + "4"))
|
||||
s := fmt.Sprintf("candidate:%d 1 %s %d %s %s typ host", foundation, network, priority, host, port)
|
||||
if network == "tcp" {
|
||||
return s + " tcptype passive"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Priority = type << 24 + local << 8 + component
|
||||
// https://www.rfc-editor.org/rfc/rfc8445#section-5.1.2.1
|
||||
|
||||
const PriorityHostUDP uint32 = 0x001F_FFFF |
|
||||
126<<24 | // udp host
|
||||
7<<21 // udp
|
||||
const PriorityHostTCPPassive uint32 = 0x001F_FFFF |
|
||||
99<<24 | // tcp host
|
||||
4<<21 // tcp passive
|
||||
|
||||
// CandidateHostPriority (lower indexes has a higher priority)
|
||||
func CandidateHostPriority(network string, index int) uint32 {
|
||||
switch network {
|
||||
case "udp":
|
||||
return PriorityHostUDP - uint32(index)
|
||||
case "tcp":
|
||||
return PriorityHostTCPPassive - uint32(index)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func UnmarshalICEServers(b []byte) ([]webrtc.ICEServer, error) {
|
||||
type ICEServer struct {
|
||||
URLs any `json:"urls"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Credential string `json:"credential,omitempty"`
|
||||
}
|
||||
|
||||
var src []ICEServer
|
||||
if err := json.Unmarshal(b, &src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dst []webrtc.ICEServer
|
||||
for i := range src {
|
||||
srv := webrtc.ICEServer{
|
||||
Username: src[i].Username,
|
||||
Credential: src[i].Credential,
|
||||
}
|
||||
|
||||
switch v := src[i].URLs.(type) {
|
||||
case []any:
|
||||
for _, u := range v {
|
||||
if s, ok := u.(string); ok {
|
||||
srv.URLs = append(srv.URLs, s)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
srv.URLs = []string{v}
|
||||
}
|
||||
|
||||
dst = append(dst, srv)
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
Reference in New Issue
Block a user