golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
7.7 KiB
339 lines
7.7 KiB
package core |
|
|
|
import ( |
|
"strconv" |
|
"sync" |
|
|
|
"github.com/pion/ice/v2" |
|
"github.com/pion/interceptor" |
|
"github.com/pion/webrtc/v3" |
|
|
|
"github.com/bluenviron/mediamtx/internal/logger" |
|
) |
|
|
|
type peerConnection struct { |
|
*webrtc.PeerConnection |
|
stateChangeMutex sync.Mutex |
|
localCandidateRecv chan *webrtc.ICECandidateInit |
|
connected chan struct{} |
|
disconnected chan struct{} |
|
closed chan struct{} |
|
gatheringDone chan struct{} |
|
} |
|
|
|
func newPeerConnection( |
|
videoCodec string, |
|
audioCodec string, |
|
iceServers []webrtc.ICEServer, |
|
iceHostNAT1To1IPs []string, |
|
iceUDPMux ice.UDPMux, |
|
iceTCPMux ice.TCPMux, |
|
log logger.Writer, |
|
) (*peerConnection, error) { |
|
configuration := webrtc.Configuration{ICEServers: iceServers} |
|
settingsEngine := webrtc.SettingEngine{} |
|
|
|
if len(iceHostNAT1To1IPs) != 0 { |
|
settingsEngine.SetNAT1To1IPs(iceHostNAT1To1IPs, webrtc.ICECandidateTypeHost) |
|
} |
|
|
|
if iceUDPMux != nil { |
|
settingsEngine.SetICEUDPMux(iceUDPMux) |
|
} |
|
|
|
if iceTCPMux != nil { |
|
settingsEngine.SetICETCPMux(iceTCPMux) |
|
settingsEngine.SetNetworkTypes([]webrtc.NetworkType{webrtc.NetworkTypeTCP4}) |
|
} |
|
|
|
mediaEngine := &webrtc.MediaEngine{} |
|
|
|
if videoCodec != "" || audioCodec != "" { |
|
switch videoCodec { |
|
case "av1": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeAV1, |
|
ClockRate: 90000, |
|
}, |
|
PayloadType: 96, |
|
}, |
|
webrtc.RTPCodecTypeVideo) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
case "vp9": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeVP9, |
|
ClockRate: 90000, |
|
SDPFmtpLine: "profile-id=0", |
|
}, |
|
PayloadType: 96, |
|
}, |
|
webrtc.RTPCodecTypeVideo) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
err = mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeVP9, |
|
ClockRate: 90000, |
|
SDPFmtpLine: "profile-id=1", |
|
}, |
|
PayloadType: 96, |
|
}, |
|
webrtc.RTPCodecTypeVideo) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
case "vp8": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeVP8, |
|
ClockRate: 90000, |
|
}, |
|
PayloadType: 96, |
|
}, |
|
webrtc.RTPCodecTypeVideo) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
case "h264": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeH264, |
|
ClockRate: 90000, |
|
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", |
|
}, |
|
PayloadType: 96, |
|
}, |
|
webrtc.RTPCodecTypeVideo) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
switch audioCodec { |
|
case "opus": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeOpus, |
|
ClockRate: 48000, |
|
Channels: 2, |
|
SDPFmtpLine: "minptime=10;useinbandfec=1", |
|
}, |
|
PayloadType: 111, |
|
}, |
|
webrtc.RTPCodecTypeAudio) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
case "g722": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeG722, |
|
ClockRate: 8000, |
|
}, |
|
PayloadType: 9, |
|
}, |
|
webrtc.RTPCodecTypeAudio) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
case "pcmu": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypePCMU, |
|
ClockRate: 8000, |
|
}, |
|
PayloadType: 0, |
|
}, |
|
webrtc.RTPCodecTypeAudio) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
case "pcma": |
|
err := mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypePCMA, |
|
ClockRate: 8000, |
|
}, |
|
PayloadType: 8, |
|
}, |
|
webrtc.RTPCodecTypeAudio) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
} else { |
|
// register all codecs |
|
err := mediaEngine.RegisterDefaultCodecs() |
|
if err != nil { |
|
return nil, err |
|
} |
|
err = mediaEngine.RegisterCodec( |
|
webrtc.RTPCodecParameters{ |
|
RTPCodecCapability: webrtc.RTPCodecCapability{ |
|
MimeType: webrtc.MimeTypeAV1, |
|
ClockRate: 90000, |
|
}, |
|
PayloadType: 105, |
|
}, |
|
webrtc.RTPCodecTypeVideo) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
interceptorRegistry := &interceptor.Registry{} |
|
if err := webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil { |
|
return nil, err |
|
} |
|
|
|
api := webrtc.NewAPI( |
|
webrtc.WithSettingEngine(settingsEngine), |
|
webrtc.WithMediaEngine(mediaEngine), |
|
webrtc.WithInterceptorRegistry(interceptorRegistry)) |
|
|
|
pc, err := api.NewPeerConnection(configuration) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
co := &peerConnection{ |
|
PeerConnection: pc, |
|
localCandidateRecv: make(chan *webrtc.ICECandidateInit), |
|
connected: make(chan struct{}), |
|
disconnected: make(chan struct{}), |
|
closed: make(chan struct{}), |
|
gatheringDone: make(chan struct{}), |
|
} |
|
|
|
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) { |
|
co.stateChangeMutex.Lock() |
|
defer co.stateChangeMutex.Unlock() |
|
|
|
select { |
|
case <-co.closed: |
|
return |
|
default: |
|
} |
|
|
|
log.Log(logger.Debug, "peer connection state: "+state.String()) |
|
|
|
switch state { |
|
case webrtc.PeerConnectionStateConnected: |
|
log.Log(logger.Info, "peer connection established, local candidate: %v, remote candidate: %v", |
|
co.localCandidate(), co.remoteCandidate()) |
|
|
|
close(co.connected) |
|
|
|
case webrtc.PeerConnectionStateDisconnected: |
|
close(co.disconnected) |
|
|
|
case webrtc.PeerConnectionStateClosed: |
|
close(co.closed) |
|
} |
|
}) |
|
|
|
pc.OnICECandidate(func(i *webrtc.ICECandidate) { |
|
if i != nil { |
|
v := i.ToJSON() |
|
select { |
|
case co.localCandidateRecv <- &v: |
|
case <-co.connected: |
|
case <-co.closed: |
|
} |
|
} else { |
|
close(co.gatheringDone) |
|
} |
|
}) |
|
|
|
return co, nil |
|
} |
|
|
|
func (co *peerConnection) close() { |
|
co.PeerConnection.Close() |
|
<-co.closed |
|
} |
|
|
|
func (co *peerConnection) localCandidate() string { |
|
var cid string |
|
for _, stats := range co.GetStats() { |
|
if tstats, ok := stats.(webrtc.ICECandidatePairStats); ok && tstats.Nominated { |
|
cid = tstats.LocalCandidateID |
|
break |
|
} |
|
} |
|
|
|
if cid != "" { |
|
for _, stats := range co.GetStats() { |
|
if tstats, ok := stats.(webrtc.ICECandidateStats); ok && tstats.ID == cid { |
|
return tstats.CandidateType.String() + "/" + tstats.Protocol + "/" + |
|
tstats.IP + "/" + strconv.FormatInt(int64(tstats.Port), 10) |
|
} |
|
} |
|
} |
|
|
|
return "" |
|
} |
|
|
|
func (co *peerConnection) remoteCandidate() string { |
|
var cid string |
|
for _, stats := range co.GetStats() { |
|
if tstats, ok := stats.(webrtc.ICECandidatePairStats); ok && tstats.Nominated { |
|
cid = tstats.RemoteCandidateID |
|
break |
|
} |
|
} |
|
|
|
if cid != "" { |
|
for _, stats := range co.GetStats() { |
|
if tstats, ok := stats.(webrtc.ICECandidateStats); ok && tstats.ID == cid { |
|
return tstats.CandidateType.String() + "/" + tstats.Protocol + "/" + |
|
tstats.IP + "/" + strconv.FormatInt(int64(tstats.Port), 10) |
|
} |
|
} |
|
} |
|
|
|
return "" |
|
} |
|
|
|
func (co *peerConnection) bytesReceived() uint64 { |
|
for _, stats := range co.GetStats() { |
|
if tstats, ok := stats.(webrtc.TransportStats); ok { |
|
if tstats.ID == "iceTransport" { |
|
return tstats.BytesReceived |
|
} |
|
} |
|
} |
|
return 0 |
|
} |
|
|
|
func (co *peerConnection) bytesSent() uint64 { |
|
for _, stats := range co.GetStats() { |
|
if tstats, ok := stats.(webrtc.TransportStats); ok { |
|
if tstats.ID == "iceTransport" { |
|
return tstats.BytesSent |
|
} |
|
} |
|
} |
|
return 0 |
|
}
|
|
|