Files
livekit/pkg/rtc/mediatrack.go
David Colburn 69b94e1142 error logging
2021-06-03 01:57:43 -05:00

350 lines
8.8 KiB
Go

package rtc
import (
"errors"
"sync"
"time"
"github.com/pion/ion-sfu/pkg/buffer"
"github.com/pion/ion-sfu/pkg/sfu"
"github.com/pion/ion-sfu/pkg/twcc"
"github.com/pion/rtcp"
"github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/rtcerr"
"github.com/livekit/protocol/utils"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/logger"
"github.com/livekit/livekit-server/pkg/rtc/types"
livekit "github.com/livekit/livekit-server/proto"
)
var (
feedbackTypes = []webrtc.RTCPFeedback{
{webrtc.TypeRTCPFBGoogREMB, ""},
{webrtc.TypeRTCPFBNACK, ""},
{webrtc.TypeRTCPFBNACK, "pli"}}
)
// MediaTrack represents a WebRTC track that needs to be forwarded
// Implements the PublishedTrack interface
type MediaTrack struct {
params MediaTrackParams
ssrc webrtc.SSRC
name string
streamID string
kind livekit.TrackType
codec webrtc.RTPCodecParameters
muted utils.AtomicFlag
// channel to send RTCP packets to the source
lock sync.RWMutex
// map of target participantId -> *SubscribedTrack
subscribedTracks map[string]*SubscribedTrack
twcc *twcc.Responder
audioLevel *AudioLevel
receiver sfu.Receiver
lastPLI time.Time
onClose func()
}
type MediaTrackParams struct {
TrackID string
ParticipantID string
RTCPChan chan []rtcp.Packet
BufferFactory *buffer.Factory
ReceiverConfig ReceiverConfig
AudioConfig config.AudioConfig
Stats *RoomStatsReporter
}
func NewMediaTrack(track *webrtc.TrackRemote, params MediaTrackParams) *MediaTrack {
t := &MediaTrack{
params: params,
ssrc: track.SSRC(),
streamID: track.StreamID(),
kind: ToProtoTrackKind(track.Kind()),
codec: track.Codec(),
lock: sync.RWMutex{},
subscribedTracks: make(map[string]*SubscribedTrack),
}
return t
}
func (t *MediaTrack) Start() {
}
func (t *MediaTrack) ID() string {
return t.params.TrackID
}
func (t *MediaTrack) Kind() livekit.TrackType {
return t.kind
}
func (t *MediaTrack) Name() string {
return t.name
}
func (t *MediaTrack) IsMuted() bool {
return t.muted.Get()
}
func (t *MediaTrack) SetMuted(muted bool) {
t.muted.TrySet(muted)
// mute all of the subscribedtracks
t.lock.RLock()
for _, st := range t.subscribedTracks {
st.SetPublisherMuted(muted)
}
t.lock.RUnlock()
}
func (t *MediaTrack) OnClose(f func()) {
t.onClose = f
}
func (t *MediaTrack) IsSubscriber(subId string) bool {
t.lock.RLock()
defer t.lock.RUnlock()
return t.subscribedTracks[subId] != nil
}
// AddSubscriber subscribes sub to current mediaTrack
func (t *MediaTrack) AddSubscriber(sub types.Participant) error {
if !sub.CanSubscribe() {
return ErrPermissionDenied
}
t.lock.Lock()
defer t.lock.Unlock()
existingSt := t.subscribedTracks[sub.ID()]
// don't subscribe to the same track multiple times
if existingSt != nil {
return nil
}
if t.receiver == nil {
// cannot add, no receiver
return errors.New("cannot subscribe without a receiver in place")
}
codec := t.receiver.Codec()
if err := sub.SubscriberMediaEngine().RegisterCodec(codec, t.receiver.Kind()); err != nil {
return err
}
// using DownTrack from ion-sfu
streamId := t.params.ParticipantID
if sub.ProtocolVersion().SupportsPackedStreamId() {
// when possible, pack both IDs in streamID to allow new streams to be generated
// react-native-webrtc still uses stream based APIs and require this
streamId = PackStreamID(t.params.ParticipantID, t.ID())
}
receiver := NewWrappedReceiver(t.receiver, t.ID(), streamId)
downTrack, err := sfu.NewDownTrack(webrtc.RTPCodecCapability{
MimeType: codec.MimeType,
ClockRate: codec.ClockRate,
Channels: codec.Channels,
SDPFmtpLine: codec.SDPFmtpLine,
RTCPFeedback: feedbackTypes,
}, receiver, t.params.BufferFactory, sub.ID(), t.params.ReceiverConfig.packetBufferSize)
if err != nil {
return err
}
subTrack := NewSubscribedTrack(downTrack)
transceiver, err := sub.SubscriberPC().AddTransceiverFromTrack(downTrack, webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly,
})
if err != nil {
return err
}
downTrack.SetTransceiver(transceiver)
// when outtrack is bound, start loop to send reports
downTrack.OnBind(func() {
subTrack.SetPublisherMuted(t.IsMuted())
go t.sendDownTrackBindingReports(sub.ID(), sub.RTCPChan())
})
downTrack.OnCloseHandler(func() {
t.lock.Lock()
delete(t.subscribedTracks, sub.ID())
t.lock.Unlock()
t.params.Stats.SubSubscribedTrack(t.kind.String())
// ignore if the subscribing sub is not connected
if sub.SubscriberPC().ConnectionState() == webrtc.PeerConnectionStateClosed {
return
}
// if the source has been terminated, we'll need to terminate all of the subscribedtracks
// however, if the dest sub has disconnected, then we can skip
sender := transceiver.Sender()
if sender == nil {
return
}
logger.Debugw("removing peerconnection track",
"track", t.params.TrackID,
"participantId", t.params.ParticipantID,
"destParticipant", sub.Identity())
if err := sub.SubscriberPC().RemoveTrack(sender); err != nil {
if err == webrtc.ErrConnectionClosed {
// sub closing, can skip removing subscribedtracks
return
}
if _, ok := err.(*rtcerr.InvalidStateError); !ok {
logger.Warnw("could not remove remoteTrack from forwarder", err,
"sub", sub.Identity())
}
}
sub.RemoveSubscribedTrack(t.params.ParticipantID, subTrack)
sub.Negotiate()
})
t.subscribedTracks[sub.ID()] = subTrack
t.receiver.AddDownTrack(downTrack, true)
// since sub will lock, run it in a gorountine to avoid deadlocks
go func() {
sub.AddSubscribedTrack(t.params.ParticipantID, subTrack)
sub.Negotiate()
}()
t.params.Stats.AddSubscribedTrack(t.kind.String())
return nil
}
// AddReceiver adds a new RTP receiver to the track
func (t *MediaTrack) AddReceiver(receiver *webrtc.RTPReceiver, track *webrtc.TrackRemote, twcc *twcc.Responder) {
t.lock.Lock()
defer t.lock.Unlock()
buff, rtcpReader := t.params.BufferFactory.GetBufferPair(uint32(track.SSRC()))
buff.OnFeedback(func(fb []rtcp.Packet) {
if t.params.Stats != nil {
t.params.Stats.incoming.HandleRTCP(fb)
}
// feedback for the source RTCP
t.params.RTCPChan <- fb
})
if t.Kind() == livekit.TrackType_AUDIO {
t.audioLevel = NewAudioLevel(t.params.AudioConfig.ActiveLevel, t.params.AudioConfig.MinPercentile)
buff.OnAudioLevel(func(level uint8) {
t.audioLevel.Observe(level)
})
} else if t.Kind() == livekit.TrackType_VIDEO {
if twcc != nil {
buff.OnTransportWideCC(func(sn uint16, timeNS int64, marker bool) {
twcc.Push(sn, timeNS, marker)
})
}
}
rtcpReader.OnPacket(func(bytes []byte) {
pkts, err := rtcp.Unmarshal(bytes)
if err != nil {
logger.Errorw("could not unmarshal RTCP", err)
return
}
for _, pkt := range pkts {
switch pkt := pkt.(type) {
case *rtcp.SourceDescription:
// do nothing for now
case *rtcp.SenderReport:
buff.SetSenderReportData(pkt.RTPTime, pkt.NTPTime)
}
}
})
if t.receiver == nil {
t.receiver = sfu.NewWebRTCReceiver(receiver, track, t.params.ParticipantID)
t.receiver.SetRTCPCh(t.params.RTCPChan)
t.receiver.OnCloseHandler(func() {
t.lock.Lock()
t.receiver = nil
onclose := t.onClose
t.lock.Unlock()
t.RemoveAllSubscribers()
t.params.Stats.SubPublishedTrack(t.kind.String())
if onclose != nil {
onclose()
}
})
t.params.Stats.AddPublishedTrack(t.kind.String())
}
t.receiver.AddUpTrack(track, buff, true)
buff.Bind(receiver.GetParameters(), buffer.Options{
MaxBitRate: t.params.ReceiverConfig.maxBitrate,
})
}
// RemoveSubscriber removes participant from subscription
// stop all forwarders to the client
func (t *MediaTrack) RemoveSubscriber(participantId string) {
t.lock.RLock()
defer t.lock.RUnlock()
if subTrack := t.subscribedTracks[participantId]; subTrack != nil {
go subTrack.DownTrack().Close()
}
}
func (t *MediaTrack) RemoveAllSubscribers() {
logger.Debugw("removing all subscribers", "track", t.params.TrackID)
t.lock.RLock()
defer t.lock.RUnlock()
for _, subTrack := range t.subscribedTracks {
go subTrack.DownTrack().Close()
}
t.subscribedTracks = make(map[string]*SubscribedTrack)
}
// TODO: send for all downtracks from the source participant
// https://tools.ietf.org/html/rfc7941
func (t *MediaTrack) sendDownTrackBindingReports(participantId string, rtcpCh chan []rtcp.Packet) {
var sd []rtcp.SourceDescriptionChunk
t.lock.RLock()
subTrack := t.subscribedTracks[participantId]
t.lock.RUnlock()
if subTrack == nil {
return
}
chunks := subTrack.DownTrack().CreateSourceDescriptionChunks()
if chunks == nil {
return
}
sd = append(sd, chunks...)
pkts := []rtcp.Packet{
&rtcp.SourceDescription{Chunks: sd},
}
go func() {
defer RecoverSilent()
batch := pkts
i := 0
for {
rtcpCh <- batch
if i > 5 {
return
}
i++
time.Sleep(20 * time.Millisecond)
}
}()
}