mirror of
https://github.com/livekit/livekit.git
synced 2026-04-13 11:05:43 +00:00
* Participant traffic load. Capturing information about participant traffic - Upstream/Downstream - Audio/Video/Data - Packets/Bytes This captures a notion of how much traffic load a participant is generating. Can be used to make allocation decisions. * Clean up * SIP patches * reporter goroutine * unlock * move traffic stats from protocol * check type
848 lines
22 KiB
Go
848 lines
22 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package rtc
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pion/rtcp"
|
|
"github.com/pion/webrtc/v3"
|
|
"go.uber.org/atomic"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/protocol/logger"
|
|
|
|
"github.com/livekit/livekit-server/pkg/config"
|
|
"github.com/livekit/livekit-server/pkg/rtc/types"
|
|
"github.com/livekit/livekit-server/pkg/sfu"
|
|
"github.com/livekit/livekit-server/pkg/sfu/buffer"
|
|
"github.com/livekit/livekit-server/pkg/sfu/dependencydescriptor"
|
|
"github.com/livekit/livekit-server/pkg/telemetry"
|
|
)
|
|
|
|
const (
|
|
layerSelectionTolerance = 0.9
|
|
)
|
|
|
|
var (
|
|
ErrNotOpen = errors.New("track is not open")
|
|
ErrNoReceiver = errors.New("cannot subscribe without a receiver in place")
|
|
)
|
|
|
|
// ------------------------------------------------------
|
|
|
|
type mediaTrackReceiverState int
|
|
|
|
const (
|
|
mediaTrackReceiverStateOpen mediaTrackReceiverState = iota
|
|
mediaTrackReceiverStateClosing
|
|
mediaTrackReceiverStateClosed
|
|
)
|
|
|
|
func (m mediaTrackReceiverState) String() string {
|
|
switch m {
|
|
case mediaTrackReceiverStateOpen:
|
|
return "OPEN"
|
|
case mediaTrackReceiverStateClosing:
|
|
return "CLOSING"
|
|
case mediaTrackReceiverStateClosed:
|
|
return "CLOSED"
|
|
default:
|
|
return fmt.Sprintf("%d", int(m))
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------
|
|
|
|
type simulcastReceiver struct {
|
|
sfu.TrackReceiver
|
|
priority int
|
|
layerSSRCs [livekit.VideoQuality_HIGH + 1]uint32
|
|
}
|
|
|
|
func (r *simulcastReceiver) Priority() int {
|
|
return r.priority
|
|
}
|
|
|
|
type MediaTrackReceiverParams struct {
|
|
TrackInfo *livekit.TrackInfo
|
|
MediaTrack types.MediaTrack
|
|
IsRelayed bool
|
|
ParticipantID livekit.ParticipantID
|
|
ParticipantIdentity livekit.ParticipantIdentity
|
|
ParticipantVersion uint32
|
|
ReceiverConfig ReceiverConfig
|
|
SubscriberConfig DirectionConfig
|
|
AudioConfig config.AudioConfig
|
|
Telemetry telemetry.TelemetryService
|
|
Logger logger.Logger
|
|
}
|
|
|
|
type MediaTrackReceiver struct {
|
|
params MediaTrackReceiverParams
|
|
muted atomic.Bool
|
|
simulcasted atomic.Bool
|
|
|
|
lock sync.RWMutex
|
|
receivers []*simulcastReceiver
|
|
receiversShadow []*simulcastReceiver
|
|
trackInfo *livekit.TrackInfo
|
|
layerDimensions map[livekit.VideoQuality]*livekit.VideoLayer
|
|
potentialCodecs []webrtc.RTPCodecParameters
|
|
state mediaTrackReceiverState
|
|
|
|
onSetupReceiver func(mime string)
|
|
onMediaLossFeedback func(dt *sfu.DownTrack, report *rtcp.ReceiverReport)
|
|
onVideoLayerUpdate func(layers []*livekit.VideoLayer)
|
|
onClose []func()
|
|
|
|
*MediaTrackSubscriptions
|
|
}
|
|
|
|
func NewMediaTrackReceiver(params MediaTrackReceiverParams) *MediaTrackReceiver {
|
|
t := &MediaTrackReceiver{
|
|
params: params,
|
|
trackInfo: proto.Clone(params.TrackInfo).(*livekit.TrackInfo),
|
|
layerDimensions: make(map[livekit.VideoQuality]*livekit.VideoLayer),
|
|
state: mediaTrackReceiverStateOpen,
|
|
}
|
|
|
|
t.MediaTrackSubscriptions = NewMediaTrackSubscriptions(MediaTrackSubscriptionsParams{
|
|
MediaTrack: params.MediaTrack,
|
|
IsRelayed: params.IsRelayed,
|
|
ReceiverConfig: params.ReceiverConfig,
|
|
SubscriberConfig: params.SubscriberConfig,
|
|
Telemetry: params.Telemetry,
|
|
Logger: params.Logger,
|
|
})
|
|
t.MediaTrackSubscriptions.OnDownTrackCreated(t.onDownTrackCreated)
|
|
|
|
if t.trackInfo.Muted {
|
|
t.SetMuted(true)
|
|
}
|
|
|
|
if t.trackInfo != nil && t.Kind() == livekit.TrackType_VIDEO {
|
|
t.UpdateVideoLayers(t.trackInfo.Layers)
|
|
// LK-TODO: maybe use this or simulcast flag in TrackInfo to set simulcasted here
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Restart() {
|
|
t.lock.Lock()
|
|
receivers := t.receiversShadow
|
|
t.lock.Unlock()
|
|
|
|
for _, receiver := range receivers {
|
|
receiver.SetMaxExpectedSpatialLayer(buffer.VideoQualityToSpatialLayer(livekit.VideoQuality_HIGH, t.params.TrackInfo))
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) OnSetupReceiver(f func(mime string)) {
|
|
t.lock.Lock()
|
|
t.onSetupReceiver = f
|
|
t.lock.Unlock()
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) SetupReceiver(receiver sfu.TrackReceiver, priority int, mid string) {
|
|
t.lock.Lock()
|
|
if t.state != mediaTrackReceiverStateOpen {
|
|
t.params.Logger.Warnw("cannot set up receiver on a track not open", nil)
|
|
t.lock.Unlock()
|
|
return
|
|
}
|
|
|
|
// codec position maybe taken by DummyReceiver, check and upgrade to WebRTCReceiver
|
|
var upgradeReceiver bool
|
|
for _, r := range t.receivers {
|
|
if strings.EqualFold(r.Codec().MimeType, receiver.Codec().MimeType) {
|
|
if d, ok := r.TrackReceiver.(*DummyReceiver); ok {
|
|
d.Upgrade(receiver)
|
|
upgradeReceiver = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !upgradeReceiver {
|
|
t.receivers = append(t.receivers, &simulcastReceiver{TrackReceiver: receiver, priority: priority})
|
|
}
|
|
|
|
sort.Slice(t.receivers, func(i, j int) bool {
|
|
return t.receivers[i].Priority() < t.receivers[j].Priority()
|
|
})
|
|
|
|
if mid != "" {
|
|
if priority == 0 {
|
|
t.trackInfo.MimeType = receiver.Codec().MimeType
|
|
t.trackInfo.Mid = mid
|
|
|
|
// for clients don't have simulcast codecs (old version or single codec), add the primary codec
|
|
if len(t.trackInfo.Codecs) == 0 && t.trackInfo.Type == livekit.TrackType_VIDEO {
|
|
t.trackInfo.Codecs = append(t.trackInfo.Codecs, &livekit.SimulcastCodecInfo{})
|
|
}
|
|
}
|
|
|
|
for i, ci := range t.trackInfo.Codecs {
|
|
if i == priority {
|
|
ci.Mid = mid
|
|
ci.MimeType = receiver.Codec().MimeType
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
t.shadowReceiversLocked()
|
|
|
|
onSetupReceiver := t.onSetupReceiver
|
|
t.params.Logger.Debugw("setup receiver", "mime", receiver.Codec().MimeType, "priority", priority, "receivers", t.receiversShadow)
|
|
t.lock.Unlock()
|
|
|
|
if onSetupReceiver != nil {
|
|
onSetupReceiver(receiver.Codec().MimeType)
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) SetPotentialCodecs(codecs []webrtc.RTPCodecParameters, headers []webrtc.RTPHeaderExtensionParameter) {
|
|
// The potential codecs have not published yet, so we can't get the actual Extensions, the client/browser uses same extensions
|
|
// for all video codecs so we assume they will have same extensions as the primary codec except for the dependency descriptor
|
|
// that is munged in svc codec.
|
|
headersWithoutDD := make([]webrtc.RTPHeaderExtensionParameter, 0, len(headers))
|
|
for _, h := range headers {
|
|
if h.URI != dependencydescriptor.ExtensionURI {
|
|
headersWithoutDD = append(headersWithoutDD, h)
|
|
}
|
|
}
|
|
t.lock.Lock()
|
|
t.potentialCodecs = codecs
|
|
for i, c := range codecs {
|
|
var exist bool
|
|
for _, r := range t.receivers {
|
|
if strings.EqualFold(c.MimeType, r.Codec().MimeType) {
|
|
exist = true
|
|
break
|
|
}
|
|
}
|
|
if !exist {
|
|
extHeaders := headers
|
|
if !sfu.IsSvcCodec(c.MimeType) {
|
|
extHeaders = headersWithoutDD
|
|
}
|
|
t.receivers = append(t.receivers, &simulcastReceiver{
|
|
TrackReceiver: NewDummyReceiver(livekit.TrackID(t.trackInfo.Sid), string(t.PublisherID()), c, extHeaders),
|
|
priority: i,
|
|
})
|
|
}
|
|
}
|
|
sort.Slice(t.receivers, func(i, j int) bool {
|
|
return t.receivers[i].Priority() < t.receivers[j].Priority()
|
|
})
|
|
t.shadowReceiversLocked()
|
|
t.lock.Unlock()
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) shadowReceiversLocked() {
|
|
t.receiversShadow = make([]*simulcastReceiver, len(t.receivers))
|
|
copy(t.receiversShadow, t.receivers)
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) SetLayerSsrc(mime string, rid string, ssrc uint32) {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
|
|
layer := buffer.RidToSpatialLayer(rid, t.params.TrackInfo)
|
|
if layer == buffer.InvalidLayerSpatial {
|
|
// non-simulcast case will not have `rid`
|
|
layer = 0
|
|
}
|
|
for _, receiver := range t.receiversShadow {
|
|
if strings.EqualFold(receiver.Codec().MimeType, mime) && int(layer) < len(receiver.layerSSRCs) {
|
|
receiver.layerSSRCs[layer] = ssrc
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) ClearReceiver(mime string, willBeResumed bool) {
|
|
t.params.Logger.Debugw("clearing receiver", "mime", mime)
|
|
t.lock.Lock()
|
|
for idx, receiver := range t.receivers {
|
|
if strings.EqualFold(receiver.Codec().MimeType, mime) {
|
|
t.receivers[idx] = t.receivers[len(t.receivers)-1]
|
|
t.receivers = t.receivers[:len(t.receivers)-1]
|
|
break
|
|
}
|
|
}
|
|
|
|
t.shadowReceiversLocked()
|
|
t.lock.Unlock()
|
|
|
|
t.removeAllSubscribersForMime(mime, willBeResumed)
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) ClearAllReceivers(willBeResumed bool) {
|
|
t.params.Logger.Debugw("clearing all receivers")
|
|
t.lock.Lock()
|
|
var mimes []string
|
|
for _, receiver := range t.receivers {
|
|
mimes = append(mimes, receiver.Codec().MimeType)
|
|
}
|
|
|
|
t.receivers = t.receivers[:0]
|
|
t.receiversShadow = nil
|
|
t.lock.Unlock()
|
|
|
|
for _, mime := range mimes {
|
|
t.ClearReceiver(mime, willBeResumed)
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) OnMediaLossFeedback(f func(dt *sfu.DownTrack, rr *rtcp.ReceiverReport)) {
|
|
t.onMediaLossFeedback = f
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) OnVideoLayerUpdate(f func(layers []*livekit.VideoLayer)) {
|
|
t.onVideoLayerUpdate = f
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) IsOpen() bool {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
if t.state != mediaTrackReceiverStateOpen {
|
|
return false
|
|
}
|
|
// If any one of the receivers has entered closed state, we would not consider the track open
|
|
for _, receiver := range t.receivers {
|
|
if receiver.IsClosed() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) SetClosing() {
|
|
t.lock.Lock()
|
|
defer t.lock.Unlock()
|
|
if t.state == mediaTrackReceiverStateOpen {
|
|
t.state = mediaTrackReceiverStateClosing
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) TryClose() bool {
|
|
t.lock.RLock()
|
|
if t.state == mediaTrackReceiverStateClosed {
|
|
t.lock.RUnlock()
|
|
return true
|
|
}
|
|
|
|
for _, receiver := range t.receiversShadow {
|
|
if dr, _ := receiver.TrackReceiver.(*DummyReceiver); dr != nil && dr.Receiver() != nil {
|
|
t.lock.RUnlock()
|
|
return false
|
|
}
|
|
}
|
|
t.lock.RUnlock()
|
|
t.Close()
|
|
|
|
return true
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Close() {
|
|
t.lock.Lock()
|
|
if t.state == mediaTrackReceiverStateClosed {
|
|
t.lock.Unlock()
|
|
return
|
|
}
|
|
|
|
t.state = mediaTrackReceiverStateClosed
|
|
onclose := t.onClose
|
|
t.lock.Unlock()
|
|
|
|
for _, f := range onclose {
|
|
f()
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) ID() livekit.TrackID {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return livekit.TrackID(t.trackInfo.Sid)
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Kind() livekit.TrackType {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return t.trackInfo.Type
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Source() livekit.TrackSource {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return t.trackInfo.Source
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Stream() string {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return t.trackInfo.Stream
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) PublisherID() livekit.ParticipantID {
|
|
return t.params.ParticipantID
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) PublisherIdentity() livekit.ParticipantIdentity {
|
|
return t.params.ParticipantIdentity
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) PublisherVersion() uint32 {
|
|
return t.params.ParticipantVersion
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) IsSimulcast() bool {
|
|
return t.simulcasted.Load()
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) SetSimulcast(simulcast bool) {
|
|
t.simulcasted.Store(simulcast)
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Name() string {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return t.trackInfo.Name
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) IsMuted() bool {
|
|
return t.muted.Load()
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) SetMuted(muted bool) {
|
|
t.muted.Store(muted)
|
|
|
|
t.lock.RLock()
|
|
receivers := t.receiversShadow
|
|
t.lock.RUnlock()
|
|
for _, receiver := range receivers {
|
|
receiver.SetUpTrackPaused(muted)
|
|
}
|
|
|
|
t.MediaTrackSubscriptions.SetMuted(muted)
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) AddOnClose(f func()) {
|
|
if f == nil {
|
|
return
|
|
}
|
|
|
|
t.lock.Lock()
|
|
t.onClose = append(t.onClose, f)
|
|
t.lock.Unlock()
|
|
}
|
|
|
|
// AddSubscriber subscribes sub to current mediaTrack
|
|
func (t *MediaTrackReceiver) AddSubscriber(sub types.LocalParticipant) (types.SubscribedTrack, error) {
|
|
t.lock.RLock()
|
|
if t.state != mediaTrackReceiverStateOpen {
|
|
t.lock.RUnlock()
|
|
return nil, ErrNotOpen
|
|
}
|
|
|
|
receivers := t.receiversShadow
|
|
potentialCodecs := make([]webrtc.RTPCodecParameters, len(t.potentialCodecs))
|
|
copy(potentialCodecs, t.potentialCodecs)
|
|
t.lock.RUnlock()
|
|
|
|
if len(receivers) == 0 {
|
|
// cannot add, no receiver
|
|
return nil, ErrNoReceiver
|
|
}
|
|
|
|
for _, receiver := range receivers {
|
|
codec := receiver.Codec()
|
|
var found bool
|
|
for _, pc := range potentialCodecs {
|
|
if codec.MimeType == pc.MimeType {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
potentialCodecs = append(potentialCodecs, codec)
|
|
}
|
|
}
|
|
|
|
streamId := string(t.PublisherID())
|
|
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.PublisherID(), t.ID())
|
|
}
|
|
|
|
tLogger := LoggerWithTrack(sub.GetLogger(), t.ID(), t.params.IsRelayed)
|
|
wr := NewWrappedReceiver(WrappedReceiverParams{
|
|
Receivers: receivers,
|
|
TrackID: t.ID(),
|
|
StreamId: streamId,
|
|
UpstreamCodecs: potentialCodecs,
|
|
Logger: tLogger,
|
|
DisableRed: t.trackInfo.GetDisableRed() || !t.params.AudioConfig.ActiveREDEncoding,
|
|
})
|
|
return t.MediaTrackSubscriptions.AddSubscriber(sub, wr)
|
|
}
|
|
|
|
// RemoveSubscriber removes participant from subscription
|
|
// stop all forwarders to the client
|
|
func (t *MediaTrackReceiver) RemoveSubscriber(subscriberID livekit.ParticipantID, willBeResumed bool) {
|
|
_ = t.MediaTrackSubscriptions.RemoveSubscriber(subscriberID, willBeResumed)
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) removeAllSubscribersForMime(mime string, willBeResumed bool) {
|
|
t.params.Logger.Debugw("removing all subscribers for mime", "mime", mime)
|
|
for _, subscriberID := range t.MediaTrackSubscriptions.GetAllSubscribersForMime(mime) {
|
|
t.RemoveSubscriber(subscriberID, willBeResumed)
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) RevokeDisallowedSubscribers(allowedSubscriberIdentities []livekit.ParticipantIdentity) []livekit.ParticipantIdentity {
|
|
var revokedSubscriberIdentities []livekit.ParticipantIdentity
|
|
|
|
// LK-TODO: large number of subscribers needs to be solved for this loop
|
|
for _, subTrack := range t.MediaTrackSubscriptions.getAllSubscribedTracks() {
|
|
found := false
|
|
for _, allowedIdentity := range allowedSubscriberIdentities {
|
|
if subTrack.SubscriberIdentity() == allowedIdentity {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.params.Logger.Infow("revoking subscription",
|
|
"subscriber", subTrack.SubscriberIdentity(),
|
|
"subscriberID", subTrack.SubscriberID(),
|
|
)
|
|
t.RemoveSubscriber(subTrack.SubscriberID(), false)
|
|
revokedSubscriberIdentities = append(revokedSubscriberIdentities, subTrack.SubscriberIdentity())
|
|
}
|
|
}
|
|
|
|
return revokedSubscriberIdentities
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) UpdateTrackInfo(ti *livekit.TrackInfo) {
|
|
t.lock.Lock()
|
|
t.trackInfo = proto.Clone(ti).(*livekit.TrackInfo)
|
|
t.lock.Unlock()
|
|
|
|
if ti != nil && t.Kind() == livekit.TrackType_VIDEO {
|
|
t.UpdateVideoLayers(ti.Layers)
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) TrackInfo(generateLayer bool) *livekit.TrackInfo {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
ti := proto.Clone(t.trackInfo).(*livekit.TrackInfo)
|
|
if !generateLayer {
|
|
return ti
|
|
}
|
|
|
|
layers := t.getVideoLayersLocked()
|
|
|
|
// set video layer ssrc info
|
|
for i, ci := range ti.Codecs {
|
|
for _, receiver := range t.receiversShadow {
|
|
if receiver.priority == i {
|
|
originLayers := ci.Layers
|
|
ci.Layers = []*livekit.VideoLayer{}
|
|
for layerIdx, layer := range layers {
|
|
ci.Layers = append(ci.Layers, proto.Clone(layer).(*livekit.VideoLayer))
|
|
|
|
// if origin layer has ssrc, don't override it
|
|
ssrcFound := false
|
|
for _, l := range originLayers {
|
|
if l.Quality == ci.Layers[layerIdx].Quality {
|
|
if l.Ssrc != 0 {
|
|
ci.Layers[layerIdx].Ssrc = l.Ssrc
|
|
ssrcFound = true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if !ssrcFound && int(layer.Quality) < len(receiver.layerSSRCs) {
|
|
ci.Layers[layerIdx].Ssrc = receiver.layerSSRCs[layer.Quality]
|
|
}
|
|
}
|
|
|
|
if i == 0 {
|
|
ti.Layers = ci.Layers
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// for client don't use simulcast codecs (old client version or single codec)
|
|
if len(ti.Codecs) == 0 && len(t.receiversShadow) > 0 {
|
|
receiver := t.receiversShadow[0]
|
|
originLayers := ti.Layers
|
|
ti.Layers = []*livekit.VideoLayer{}
|
|
for layerIdx, layer := range layers {
|
|
ti.Layers = append(ti.Layers, proto.Clone(layer).(*livekit.VideoLayer))
|
|
|
|
// if origin layer has ssrc, don't override it
|
|
ssrcFound := false
|
|
for _, l := range originLayers {
|
|
if l.Quality == ti.Layers[layerIdx].Quality {
|
|
if l.Ssrc != 0 {
|
|
ti.Layers[layerIdx].Ssrc = l.Ssrc
|
|
ssrcFound = true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if !ssrcFound && int(layer.Quality) < len(receiver.layerSSRCs) {
|
|
ti.Layers[layerIdx].Ssrc = receiver.layerSSRCs[layer.Quality]
|
|
}
|
|
}
|
|
}
|
|
|
|
return ti
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) UpdateVideoLayers(layers []*livekit.VideoLayer) {
|
|
t.lock.Lock()
|
|
for _, layer := range layers {
|
|
t.layerDimensions[layer.Quality] = layer
|
|
}
|
|
t.lock.Unlock()
|
|
|
|
t.MediaTrackSubscriptions.UpdateVideoLayers()
|
|
if t.onVideoLayerUpdate != nil {
|
|
t.onVideoLayerUpdate(layers)
|
|
}
|
|
|
|
// TODO: this might need to trigger a participant update for clients to pick up dimension change
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) GetVideoLayers() []*livekit.VideoLayer {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return t.getVideoLayersLocked()
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) getVideoLayersLocked() []*livekit.VideoLayer {
|
|
layers := make([]*livekit.VideoLayer, 0)
|
|
for _, layer := range t.layerDimensions {
|
|
layers = append(layers, proto.Clone(layer).(*livekit.VideoLayer))
|
|
}
|
|
|
|
return layers
|
|
}
|
|
|
|
// GetQualityForDimension finds the closest quality to use for desired dimensions
|
|
// affords a 20% tolerance on dimension
|
|
func (t *MediaTrackReceiver) GetQualityForDimension(width, height uint32) livekit.VideoQuality {
|
|
quality := livekit.VideoQuality_HIGH
|
|
if t.Kind() == livekit.TrackType_AUDIO {
|
|
return quality
|
|
}
|
|
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
if t.trackInfo.Height == 0 {
|
|
return quality
|
|
}
|
|
origSize := t.trackInfo.Height
|
|
requestedSize := height
|
|
if t.trackInfo.Width < t.trackInfo.Height {
|
|
// for portrait videos
|
|
origSize = t.trackInfo.Width
|
|
requestedSize = width
|
|
}
|
|
|
|
// default sizes representing qualities low - high
|
|
layerSizes := []uint32{180, 360, origSize}
|
|
var providedSizes []uint32
|
|
for _, layer := range t.layerDimensions {
|
|
providedSizes = append(providedSizes, layer.Height)
|
|
}
|
|
if len(providedSizes) > 0 {
|
|
layerSizes = providedSizes
|
|
// comparing height always
|
|
requestedSize = height
|
|
sort.Slice(layerSizes, func(i, j int) bool {
|
|
return layerSizes[i] < layerSizes[j]
|
|
})
|
|
}
|
|
|
|
// finds the highest layer with smallest dimensions that still satisfy client demands
|
|
requestedSize = uint32(float32(requestedSize) * layerSelectionTolerance)
|
|
for i, s := range layerSizes {
|
|
quality = livekit.VideoQuality(i)
|
|
if i == len(layerSizes)-1 {
|
|
break
|
|
} else if s >= requestedSize && s != layerSizes[i+1] {
|
|
break
|
|
}
|
|
}
|
|
|
|
return quality
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) GetAudioLevel() (float64, bool) {
|
|
receiver := t.PrimaryReceiver()
|
|
if receiver == nil {
|
|
return 0, false
|
|
}
|
|
|
|
return receiver.GetAudioLevel()
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) onDownTrackCreated(downTrack *sfu.DownTrack) {
|
|
if t.Kind() == livekit.TrackType_AUDIO {
|
|
downTrack.AddReceiverReportListener(func(dt *sfu.DownTrack, rr *rtcp.ReceiverReport) {
|
|
if t.onMediaLossFeedback != nil {
|
|
t.onMediaLossFeedback(dt, rr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) DebugInfo() map[string]interface{} {
|
|
info := map[string]interface{}{
|
|
"ID": t.ID(),
|
|
"Kind": t.Kind().String(),
|
|
"PubMuted": t.muted.Load(),
|
|
}
|
|
|
|
info["DownTracks"] = t.MediaTrackSubscriptions.DebugInfo()
|
|
|
|
t.lock.RLock()
|
|
receivers := t.receiversShadow
|
|
t.lock.RUnlock()
|
|
for _, receiver := range receivers {
|
|
info[receiver.Codec().MimeType] = receiver.DebugInfo()
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) PrimaryReceiver() sfu.TrackReceiver {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
if len(t.receiversShadow) == 0 {
|
|
return nil
|
|
}
|
|
if dr, ok := t.receiversShadow[0].TrackReceiver.(*DummyReceiver); ok {
|
|
return dr.Receiver()
|
|
}
|
|
return t.receiversShadow[0].TrackReceiver
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Receiver(mime string) sfu.TrackReceiver {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
for _, r := range t.receiversShadow {
|
|
if strings.EqualFold(r.Codec().MimeType, mime) {
|
|
if dr, ok := r.TrackReceiver.(*DummyReceiver); ok {
|
|
return dr.Receiver()
|
|
}
|
|
return r.TrackReceiver
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) Receivers() []sfu.TrackReceiver {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
receivers := make([]sfu.TrackReceiver, 0, len(t.receiversShadow))
|
|
for _, r := range t.receiversShadow {
|
|
receivers = append(receivers, r.TrackReceiver)
|
|
}
|
|
return receivers
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) SetRTT(rtt uint32) {
|
|
t.lock.Lock()
|
|
receivers := t.receiversShadow
|
|
t.lock.Unlock()
|
|
|
|
for _, r := range receivers {
|
|
if wr, ok := r.TrackReceiver.(*sfu.WebRTCReceiver); ok {
|
|
wr.SetRTT(rtt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) GetTemporalLayerForSpatialFps(spatial int32, fps uint32, mime string) int32 {
|
|
receiver := t.Receiver(mime)
|
|
if receiver == nil {
|
|
return buffer.DefaultMaxLayerTemporal
|
|
}
|
|
|
|
layerFps := receiver.GetTemporalLayerFpsForSpatial(spatial)
|
|
requestFps := float32(fps) * layerSelectionTolerance
|
|
for i, f := range layerFps {
|
|
if requestFps <= f {
|
|
return int32(i)
|
|
}
|
|
}
|
|
return buffer.DefaultMaxLayerTemporal
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) IsEncrypted() bool {
|
|
t.lock.RLock()
|
|
defer t.lock.RUnlock()
|
|
|
|
return t.trackInfo.Encryption != livekit.Encryption_NONE
|
|
}
|
|
|
|
func (t *MediaTrackReceiver) GetTrackStats() *livekit.RTPStats {
|
|
t.lock.Lock()
|
|
receivers := t.receiversShadow
|
|
t.lock.Unlock()
|
|
|
|
stats := make([]*livekit.RTPStats, 0, len(receivers))
|
|
for _, receiver := range receivers {
|
|
receiverStats := receiver.GetTrackStats()
|
|
if receiverStats != nil {
|
|
stats = append(stats, receiverStats)
|
|
}
|
|
}
|
|
|
|
return buffer.AggregateRTPStats(stats)
|
|
}
|
|
|
|
// ---------------------------
|