Files
webrtc/rtptransceiver.go
boks1971 2af60a491d Filter unattached RTX when getting codecs
Recently added filtering out unattached RTX in SetCodecPrefrences.
Turns out it is needed in `getCodecs()` also in the following
scenario
- AddTrack from answer side adds a tracks and has media engine codecs
- An offer is received and negotiated codecs get updated in
  media engine. And this does not have one of the codecs added
  AddTrack above (default media engine codecs)
- Generate answer will do `getCodecs` which will filter out
  the codec missing from the `offer`, but would have let the
  corresponding RTX pass through and get added to the answer.
2025-08-27 01:34:35 +05:30

367 lines
10 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
import (
"fmt"
"sync"
"sync/atomic"
"github.com/pion/rtp"
)
// RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid.
type RTPTransceiver struct {
mid atomic.Value // string
sender atomic.Value // *RTPSender
receiver atomic.Value // *RTPReceiver
direction atomic.Value // RTPTransceiverDirection
currentDirection atomic.Value // RTPTransceiverDirection
currentRemoteDirection atomic.Value // RTPTransceiverDirection
codecs []RTPCodecParameters // User provided codecs via SetCodecPreferences
kind RTPCodecType
api *API
mu sync.RWMutex
}
func newRTPTransceiver(
receiver *RTPReceiver,
sender *RTPSender,
direction RTPTransceiverDirection,
kind RTPCodecType,
api *API,
) *RTPTransceiver {
t := &RTPTransceiver{kind: kind, api: api}
t.setReceiver(receiver)
t.setSender(sender)
t.setDirection(direction)
t.setCurrentDirection(RTPTransceiverDirectionUnknown)
return t
}
// SetCodecPreferences sets preferred list of supported codecs
// if codecs is empty or nil we reset to default from MediaEngine.
func (t *RTPTransceiver) SetCodecPreferences(codecs []RTPCodecParameters) error {
t.mu.Lock()
defer t.mu.Unlock()
for _, codec := range codecs {
if _, matchType := codecParametersFuzzySearch(
codec, t.api.mediaEngine.getCodecsByKind(t.kind),
); matchType == codecMatchNone {
return fmt.Errorf("%w %s", errRTPTransceiverCodecUnsupported, codec.MimeType)
}
}
t.codecs = filterUnattachedRTX(codecs)
return nil
}
// getCodecs returns list of supported codecs.
func (t *RTPTransceiver) getCodecs() []RTPCodecParameters {
t.mu.RLock()
defer t.mu.RUnlock()
mediaEngineCodecs := t.api.mediaEngine.getCodecsByKind(t.kind)
if len(t.codecs) == 0 {
return mediaEngineCodecs
}
filteredCodecs := []RTPCodecParameters{}
for _, codec := range t.codecs {
if c, matchType := codecParametersFuzzySearch(codec, mediaEngineCodecs); matchType != codecMatchNone {
if codec.PayloadType == 0 {
codec.PayloadType = c.PayloadType
}
codec.RTCPFeedback = rtcpFeedbackIntersection(codec.RTCPFeedback, c.RTCPFeedback)
filteredCodecs = append(filteredCodecs, codec)
}
}
return filterUnattachedRTX(filteredCodecs)
}
// Sender returns the RTPTransceiver's RTPSender if it has one.
func (t *RTPTransceiver) Sender() *RTPSender {
if v, ok := t.sender.Load().(*RTPSender); ok {
return v
}
return nil
}
// SetSender sets the RTPSender and Track to current transceiver.
func (t *RTPTransceiver) SetSender(s *RTPSender, track TrackLocal) error {
t.setSender(s)
return t.setSendingTrack(track)
}
func (t *RTPTransceiver) setSender(s *RTPSender) {
if s != nil {
s.setRTPTransceiver(t)
}
if prevSender := t.Sender(); prevSender != nil {
prevSender.setRTPTransceiver(nil)
}
t.sender.Store(s)
}
// Receiver returns the RTPTransceiver's RTPReceiver if it has one.
func (t *RTPTransceiver) Receiver() *RTPReceiver {
if v, ok := t.receiver.Load().(*RTPReceiver); ok {
return v
}
return nil
}
// SetMid sets the RTPTransceiver's mid. If it was already set, will return an error.
func (t *RTPTransceiver) SetMid(mid string) error {
if currentMid := t.Mid(); currentMid != "" {
return fmt.Errorf("%w: %s to %s", errRTPTransceiverCannotChangeMid, currentMid, mid)
}
t.mid.Store(mid)
return nil
}
// Mid gets the Transceiver's mid value. When not already set, this value will be set in CreateOffer or CreateAnswer.
func (t *RTPTransceiver) Mid() string {
if v, ok := t.mid.Load().(string); ok {
return v
}
return ""
}
// Kind returns RTPTransceiver's kind.
func (t *RTPTransceiver) Kind() RTPCodecType {
return t.kind
}
// Direction returns the RTPTransceiver's current direction.
func (t *RTPTransceiver) Direction() RTPTransceiverDirection {
if direction, ok := t.direction.Load().(RTPTransceiverDirection); ok {
return direction
}
return RTPTransceiverDirection(0)
}
// Stop irreversibly stops the RTPTransceiver.
func (t *RTPTransceiver) Stop() error {
if sender := t.Sender(); sender != nil {
if err := sender.Stop(); err != nil {
return err
}
}
if receiver := t.Receiver(); receiver != nil {
if err := receiver.Stop(); err != nil {
return err
}
}
t.setDirection(RTPTransceiverDirectionInactive)
t.setCurrentDirection(RTPTransceiverDirectionInactive)
return nil
}
func (t *RTPTransceiver) setReceiver(r *RTPReceiver) {
if r != nil {
r.setRTPTransceiver(t)
}
if prevReceiver := t.Receiver(); prevReceiver != nil {
prevReceiver.setRTPTransceiver(nil)
}
t.receiver.Store(r)
}
func (t *RTPTransceiver) setDirection(d RTPTransceiverDirection) {
t.direction.Store(d)
}
func (t *RTPTransceiver) setCurrentDirection(d RTPTransceiverDirection) {
t.currentDirection.Store(d)
}
func (t *RTPTransceiver) getCurrentDirection() RTPTransceiverDirection {
if v, ok := t.currentDirection.Load().(RTPTransceiverDirection); ok {
return v
}
return RTPTransceiverDirectionUnknown
}
func (t *RTPTransceiver) setCurrentRemoteDirection(d RTPTransceiverDirection) {
t.currentRemoteDirection.Store(d)
}
func (t *RTPTransceiver) getCurrentRemoteDirection() RTPTransceiverDirection {
if v, ok := t.currentRemoteDirection.Load().(RTPTransceiverDirection); ok {
return v
}
return RTPTransceiverDirectionUnknown
}
func (t *RTPTransceiver) setSendingTrack(track TrackLocal) error { //nolint:cyclop
if err := t.Sender().ReplaceTrack(track); err != nil {
return err
}
if track == nil {
t.setSender(nil)
}
switch {
case track != nil && t.Direction() == RTPTransceiverDirectionRecvonly:
t.setDirection(RTPTransceiverDirectionSendrecv)
case track != nil && t.Direction() == RTPTransceiverDirectionInactive:
t.setDirection(RTPTransceiverDirectionSendonly)
case track == nil && t.Direction() == RTPTransceiverDirectionSendrecv:
t.setDirection(RTPTransceiverDirectionRecvonly)
case track != nil && t.Direction() == RTPTransceiverDirectionSendonly:
// Handle the case where a sendonly transceiver was added by a negotiation
// initiated by remote peer. For example a remote peer added a transceiver
// with direction recvonly.
case track != nil && t.Direction() == RTPTransceiverDirectionSendrecv:
// Similar to above, but for sendrecv transceiver.
case track == nil && t.Direction() == RTPTransceiverDirectionSendonly:
t.setDirection(RTPTransceiverDirectionInactive)
default:
return errRTPTransceiverSetSendingInvalidState
}
return nil
}
func (t *RTPTransceiver) isSendAllowed(kind RTPCodecType) bool {
if t.kind != kind || t.Sender() != nil {
return false
}
// According to https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-addtrack, if the
// transceiver can be reused only if its currentDirection was never sendrecv or sendonly.
// But that will cause sdp to inflate. So we only check currentDirection's current value,
// that's worked for all browsers.
currentDirection := t.getCurrentDirection()
if currentDirection == RTPTransceiverDirectionSendrecv ||
currentDirection == RTPTransceiverDirectionSendonly {
return false
}
// `currentRemoteDirection` should be checked before using the transceiver for send.
// Remote directions could be
// - `sendrecv` or `recvonly` - can send, remote direction will transition from
// `sendrecv` -> `recvonly` if a remote track was removed.
// - `sendonly` or `inactive` - cannot send, remote direction will transitions from
// `sendonly` -> `inactive` if a remote track was removed.
// - `unknown` - can send - we are the offering side and remote direction is unknown
currentRemoteDirection := t.getCurrentRemoteDirection()
if currentRemoteDirection == RTPTransceiverDirectionSendonly ||
currentRemoteDirection == RTPTransceiverDirectionInactive {
return false
}
return true
}
func findByMid(mid string, localTransceivers []*RTPTransceiver) (*RTPTransceiver, []*RTPTransceiver) {
for i, t := range localTransceivers {
if t.Mid() == mid {
return t, append(localTransceivers[:i], localTransceivers[i+1:]...)
}
}
return nil, localTransceivers
}
// Given a direction+type pluck a transceiver from the passed list
// if no entry satisfies the requested type+direction return a inactive Transceiver.
func satisfyTypeAndDirection(
remoteKind RTPCodecType,
remoteDirection RTPTransceiverDirection,
localTransceivers []*RTPTransceiver,
) (*RTPTransceiver, []*RTPTransceiver) {
// Get direction order from most preferred to least
getPreferredDirections := func() []RTPTransceiverDirection {
switch remoteDirection {
case RTPTransceiverDirectionSendrecv:
return []RTPTransceiverDirection{
RTPTransceiverDirectionRecvonly,
RTPTransceiverDirectionSendrecv,
RTPTransceiverDirectionSendonly,
}
case RTPTransceiverDirectionSendonly:
return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}
case RTPTransceiverDirectionRecvonly:
return []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionSendrecv}
default:
return []RTPTransceiverDirection{}
}
}
for _, possibleDirection := range getPreferredDirections() {
for i := range localTransceivers {
t := localTransceivers[i]
if t.Mid() == "" && t.kind == remoteKind && possibleDirection == t.Direction() {
return t, append(localTransceivers[:i], localTransceivers[i+1:]...)
}
}
}
return nil, localTransceivers
}
// handleUnknownRTPPacket consumes a single RTP Packet and returns information that is helpful
// for demuxing and handling an unknown SSRC (usually for Simulcast).
func handleUnknownRTPPacket(
buf []byte,
midExtensionID,
streamIDExtensionID,
repairStreamIDExtensionID uint8,
mid, rid, rsid *string,
) (payloadType PayloadType, paddingOnly bool, err error) {
rp := &rtp.Packet{}
if err = rp.Unmarshal(buf); err != nil {
return 0, false, err
}
if rp.Padding && len(rp.Payload) == 0 {
paddingOnly = true
}
if !rp.Header.Extension {
return payloadType, paddingOnly, nil
}
payloadType = PayloadType(rp.PayloadType)
if payload := rp.GetExtension(midExtensionID); payload != nil {
*mid = string(payload)
}
if payload := rp.GetExtension(streamIDExtensionID); payload != nil {
*rid = string(payload)
}
if payload := rp.GetExtension(repairStreamIDExtensionID); payload != nil {
*rsid = string(payload)
}
return payloadType, paddingOnly, nil
}