mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-26 20:31:11 +08:00
212 lines
4.4 KiB
Go
212 lines
4.4 KiB
Go
package core
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pion/sdp/v3"
|
|
)
|
|
|
|
// Media take best from:
|
|
// - deepch/vdk/format/rtsp/sdp.Media
|
|
// - pion/sdp.MediaDescription
|
|
type Media struct {
|
|
Kind string `json:"kind,omitempty"` // video or audio
|
|
Direction string `json:"direction,omitempty"` // sendonly, recvonly
|
|
Codecs []*Codec `json:"codecs,omitempty"`
|
|
|
|
ID string `json:"id,omitempty"` // MID for WebRTC, Control for RTSP
|
|
}
|
|
|
|
func (m *Media) String() string {
|
|
s := fmt.Sprintf("%s, %s", m.Kind, m.Direction)
|
|
for _, codec := range m.Codecs {
|
|
name := codec.String()
|
|
|
|
if strings.Contains(s, name) {
|
|
continue
|
|
}
|
|
|
|
s += ", " + name
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (m *Media) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(m.String())
|
|
}
|
|
|
|
func (m *Media) Clone() *Media {
|
|
clone := *m
|
|
clone.Codecs = make([]*Codec, len(m.Codecs))
|
|
for i, codec := range m.Codecs {
|
|
clone.Codecs[i] = codec.Clone()
|
|
}
|
|
return &clone
|
|
}
|
|
|
|
func (m *Media) MatchMedia(remote *Media) (codec, remoteCodec *Codec) {
|
|
// check same kind and opposite dirrection
|
|
if m.Kind != remote.Kind ||
|
|
m.Direction == DirectionSendonly && remote.Direction != DirectionRecvonly ||
|
|
m.Direction == DirectionRecvonly && remote.Direction != DirectionSendonly {
|
|
return nil, nil
|
|
}
|
|
|
|
for _, codec = range m.Codecs {
|
|
for _, remoteCodec = range remote.Codecs {
|
|
if codec.Match(remoteCodec) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *Media) MatchCodec(remote *Codec) *Codec {
|
|
for _, codec := range m.Codecs {
|
|
if codec.Match(remote) {
|
|
return codec
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Media) MatchAll() bool {
|
|
for _, codec := range m.Codecs {
|
|
if codec.Name == CodecAll {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (m *Media) Equal(media *Media) bool {
|
|
if media.ID != "" {
|
|
return m.ID == media.ID
|
|
}
|
|
return m.String() == media.String()
|
|
}
|
|
|
|
func GetKind(name string) string {
|
|
switch name {
|
|
case CodecH264, CodecH265, CodecVP8, CodecVP9, CodecAV1, CodecJPEG, CodecRAW:
|
|
return KindVideo
|
|
case CodecPCMU, CodecPCMA, CodecAAC, CodecOpus, CodecG722, CodecMP3, CodecPCM, CodecPCML, CodecELD, CodecFLAC:
|
|
return KindAudio
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func MarshalSDP(name string, medias []*Media) ([]byte, error) {
|
|
sd := &sdp.SessionDescription{
|
|
Origin: sdp.Origin{
|
|
Username: "-", SessionID: 1, SessionVersion: 1,
|
|
NetworkType: "IN", AddressType: "IP4", UnicastAddress: "0.0.0.0",
|
|
},
|
|
SessionName: sdp.SessionName(name),
|
|
ConnectionInformation: &sdp.ConnectionInformation{
|
|
NetworkType: "IN", AddressType: "IP4", Address: &sdp.Address{
|
|
Address: "0.0.0.0",
|
|
},
|
|
},
|
|
TimeDescriptions: []sdp.TimeDescription{
|
|
{Timing: sdp.Timing{}},
|
|
},
|
|
}
|
|
|
|
for _, media := range medias {
|
|
if media.Codecs == nil {
|
|
continue
|
|
}
|
|
|
|
codec := media.Codecs[0]
|
|
|
|
switch codec.Name {
|
|
case CodecELD:
|
|
name = CodecAAC
|
|
case CodecPCML:
|
|
name = CodecPCM // beacuse we using pcm.LittleToBig for RTSP server
|
|
default:
|
|
name = codec.Name
|
|
}
|
|
|
|
md := &sdp.MediaDescription{
|
|
MediaName: sdp.MediaName{
|
|
Media: media.Kind,
|
|
Protos: []string{"RTP", "AVP"},
|
|
},
|
|
}
|
|
md.WithCodec(codec.PayloadType, name, codec.ClockRate, uint16(codec.Channels), codec.FmtpLine)
|
|
|
|
if media.Direction != "" {
|
|
md.WithPropertyAttribute(media.Direction)
|
|
}
|
|
|
|
if media.ID != "" {
|
|
md.WithValueAttribute("control", media.ID)
|
|
}
|
|
|
|
sd.MediaDescriptions = append(sd.MediaDescriptions, md)
|
|
}
|
|
|
|
return sd.Marshal()
|
|
}
|
|
|
|
func UnmarshalMedia(md *sdp.MediaDescription) *Media {
|
|
m := &Media{
|
|
Kind: md.MediaName.Media,
|
|
}
|
|
|
|
for _, attr := range md.Attributes {
|
|
switch attr.Key {
|
|
case DirectionSendonly, DirectionRecvonly, DirectionSendRecv:
|
|
m.Direction = attr.Key
|
|
case "control", "mid":
|
|
m.ID = attr.Value
|
|
}
|
|
}
|
|
|
|
for _, format := range md.MediaName.Formats {
|
|
m.Codecs = append(m.Codecs, UnmarshalCodec(md, format))
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func ParseQuery(query map[string][]string) (medias []*Media) {
|
|
// set media candidates from query list
|
|
for key, values := range query {
|
|
switch key {
|
|
case KindVideo, KindAudio:
|
|
for _, value := range values {
|
|
media := &Media{Kind: key, Direction: DirectionSendonly}
|
|
|
|
for _, name := range strings.Split(value, ",") {
|
|
name = strings.ToUpper(name)
|
|
|
|
// check aliases
|
|
switch name {
|
|
case "", "COPY":
|
|
name = CodecAny
|
|
case "MJPEG":
|
|
name = CodecJPEG
|
|
case "AAC":
|
|
name = CodecAAC
|
|
case "MP3":
|
|
name = CodecMP3
|
|
}
|
|
|
|
media.Codecs = append(media.Codecs, &Codec{Name: name})
|
|
}
|
|
|
|
medias = append(medias, media)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|