github.com/pion/webrtc/v4@v4.0.1/mediaengine.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  //go:build !js
     5  // +build !js
     6  
     7  package webrtc
     8  
     9  import (
    10  	"fmt"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/pion/rtp"
    17  	"github.com/pion/rtp/codecs"
    18  	"github.com/pion/sdp/v3"
    19  	"github.com/pion/webrtc/v4/internal/fmtp"
    20  )
    21  
    22  const (
    23  	// MimeTypeH264 H264 MIME type.
    24  	// Note: Matching should be case insensitive.
    25  	MimeTypeH264 = "video/H264"
    26  	// MimeTypeH265 H265 MIME type
    27  	// Note: Matching should be case insensitive.
    28  	MimeTypeH265 = "video/H265"
    29  	// MimeTypeOpus Opus MIME type
    30  	// Note: Matching should be case insensitive.
    31  	MimeTypeOpus = "audio/opus"
    32  	// MimeTypeVP8 VP8 MIME type
    33  	// Note: Matching should be case insensitive.
    34  	MimeTypeVP8 = "video/VP8"
    35  	// MimeTypeVP9 VP9 MIME type
    36  	// Note: Matching should be case insensitive.
    37  	MimeTypeVP9 = "video/VP9"
    38  	// MimeTypeAV1 AV1 MIME type
    39  	// Note: Matching should be case insensitive.
    40  	MimeTypeAV1 = "video/AV1"
    41  	// MimeTypeG722 G722 MIME type
    42  	// Note: Matching should be case insensitive.
    43  	MimeTypeG722 = "audio/G722"
    44  	// MimeTypePCMU PCMU MIME type
    45  	// Note: Matching should be case insensitive.
    46  	MimeTypePCMU = "audio/PCMU"
    47  	// MimeTypePCMA PCMA MIME type
    48  	// Note: Matching should be case insensitive.
    49  	MimeTypePCMA = "audio/PCMA"
    50  	// MimeTypeRTX RTX MIME type
    51  	// Note: Matching should be case insensitive.
    52  	MimeTypeRTX = "video/rtx"
    53  	// MimeTypeFlexFEC FEC MIME Type
    54  	// Note: Matching should be case insensitive.
    55  	MimeTypeFlexFEC = "video/flexfec"
    56  )
    57  
    58  type mediaEngineHeaderExtension struct {
    59  	uri              string
    60  	isAudio, isVideo bool
    61  
    62  	// If set only Transceivers of this direction are allowed
    63  	allowedDirections []RTPTransceiverDirection
    64  }
    65  
    66  // A MediaEngine defines the codecs supported by a PeerConnection, and the
    67  // configuration of those codecs.
    68  type MediaEngine struct {
    69  	// If we have attempted to negotiate a codec type yet.
    70  	negotiatedVideo, negotiatedAudio bool
    71  
    72  	videoCodecs, audioCodecs                     []RTPCodecParameters
    73  	negotiatedVideoCodecs, negotiatedAudioCodecs []RTPCodecParameters
    74  
    75  	headerExtensions           []mediaEngineHeaderExtension
    76  	negotiatedHeaderExtensions map[int]mediaEngineHeaderExtension
    77  
    78  	mu sync.RWMutex
    79  }
    80  
    81  // RegisterDefaultCodecs registers the default codecs supported by Pion WebRTC.
    82  // RegisterDefaultCodecs is not safe for concurrent use.
    83  func (m *MediaEngine) RegisterDefaultCodecs() error {
    84  	// Default Pion Audio Codecs
    85  	for _, codec := range []RTPCodecParameters{
    86  		{
    87  			RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
    88  			PayloadType:        111,
    89  		},
    90  		{
    91  			RTPCodecCapability: RTPCodecCapability{MimeTypeG722, 8000, 0, "", nil},
    92  			PayloadType:        rtp.PayloadTypeG722,
    93  		},
    94  		{
    95  			RTPCodecCapability: RTPCodecCapability{MimeTypePCMU, 8000, 0, "", nil},
    96  			PayloadType:        rtp.PayloadTypePCMU,
    97  		},
    98  		{
    99  			RTPCodecCapability: RTPCodecCapability{MimeTypePCMA, 8000, 0, "", nil},
   100  			PayloadType:        rtp.PayloadTypePCMA,
   101  		},
   102  	} {
   103  		if err := m.RegisterCodec(codec, RTPCodecTypeAudio); err != nil {
   104  			return err
   105  		}
   106  	}
   107  
   108  	videoRTCPFeedback := []RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
   109  	for _, codec := range []RTPCodecParameters{
   110  		{
   111  			RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", videoRTCPFeedback},
   112  			PayloadType:        96,
   113  		},
   114  		{
   115  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
   116  			PayloadType:        97,
   117  		},
   118  
   119  		{
   120  			RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", videoRTCPFeedback},
   121  			PayloadType:        102,
   122  		},
   123  		{
   124  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=102", nil},
   125  			PayloadType:        103,
   126  		},
   127  
   128  		{
   129  			RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback},
   130  			PayloadType:        104,
   131  		},
   132  		{
   133  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=104", nil},
   134  			PayloadType:        105,
   135  		},
   136  
   137  		{
   138  			RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", videoRTCPFeedback},
   139  			PayloadType:        106,
   140  		},
   141  		{
   142  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=106", nil},
   143  			PayloadType:        107,
   144  		},
   145  
   146  		{
   147  			RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", videoRTCPFeedback},
   148  			PayloadType:        108,
   149  		},
   150  		{
   151  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=108", nil},
   152  			PayloadType:        109,
   153  		},
   154  
   155  		{
   156  			RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f", videoRTCPFeedback},
   157  			PayloadType:        127,
   158  		},
   159  		{
   160  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=127", nil},
   161  			PayloadType:        125,
   162  		},
   163  
   164  		{
   165  			RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f", videoRTCPFeedback},
   166  			PayloadType:        39,
   167  		},
   168  		{
   169  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=39", nil},
   170  			PayloadType:        40,
   171  		},
   172  
   173  		{
   174  			RTPCodecCapability: RTPCodecCapability{MimeTypeAV1, 90000, 0, "", videoRTCPFeedback},
   175  			PayloadType:        45,
   176  		},
   177  		{
   178  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=45", nil},
   179  			PayloadType:        46,
   180  		},
   181  
   182  		{
   183  			RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=0", videoRTCPFeedback},
   184  			PayloadType:        98,
   185  		},
   186  		{
   187  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=98", nil},
   188  			PayloadType:        99,
   189  		},
   190  
   191  		{
   192  			RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=2", videoRTCPFeedback},
   193  			PayloadType:        100,
   194  		},
   195  		{
   196  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=100", nil},
   197  			PayloadType:        101,
   198  		},
   199  
   200  		{
   201  			RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f", videoRTCPFeedback},
   202  			PayloadType:        112,
   203  		},
   204  		{
   205  			RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=112", nil},
   206  			PayloadType:        113,
   207  		},
   208  	} {
   209  		if err := m.RegisterCodec(codec, RTPCodecTypeVideo); err != nil {
   210  			return err
   211  		}
   212  	}
   213  
   214  	return nil
   215  }
   216  
   217  // addCodec will append codec if it not exists
   218  func (m *MediaEngine) addCodec(codecs []RTPCodecParameters, codec RTPCodecParameters) []RTPCodecParameters {
   219  	for _, c := range codecs {
   220  		if c.MimeType == codec.MimeType && c.PayloadType == codec.PayloadType {
   221  			return codecs
   222  		}
   223  	}
   224  	return append(codecs, codec)
   225  }
   226  
   227  // RegisterCodec adds codec to the MediaEngine
   228  // These are the list of codecs supported by this PeerConnection.
   229  // RegisterCodec is not safe for concurrent use.
   230  func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType) error {
   231  	m.mu.Lock()
   232  	defer m.mu.Unlock()
   233  
   234  	codec.statsID = fmt.Sprintf("RTPCodec-%d", time.Now().UnixNano())
   235  	switch typ {
   236  	case RTPCodecTypeAudio:
   237  		m.audioCodecs = m.addCodec(m.audioCodecs, codec)
   238  	case RTPCodecTypeVideo:
   239  		m.videoCodecs = m.addCodec(m.videoCodecs, codec)
   240  	default:
   241  		return ErrUnknownType
   242  	}
   243  	return nil
   244  }
   245  
   246  // RegisterHeaderExtension adds a header extension to the MediaEngine
   247  // To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete
   248  func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapability, typ RTPCodecType, allowedDirections ...RTPTransceiverDirection) error {
   249  	m.mu.Lock()
   250  	defer m.mu.Unlock()
   251  
   252  	if m.negotiatedHeaderExtensions == nil {
   253  		m.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{}
   254  	}
   255  
   256  	if len(allowedDirections) == 0 {
   257  		allowedDirections = []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendonly}
   258  	}
   259  
   260  	for _, direction := range allowedDirections {
   261  		if direction != RTPTransceiverDirectionRecvonly && direction != RTPTransceiverDirectionSendonly {
   262  			return ErrRegisterHeaderExtensionInvalidDirection
   263  		}
   264  	}
   265  
   266  	extensionIndex := -1
   267  	for i := range m.headerExtensions {
   268  		if extension.URI == m.headerExtensions[i].uri {
   269  			extensionIndex = i
   270  		}
   271  	}
   272  
   273  	if extensionIndex == -1 {
   274  		m.headerExtensions = append(m.headerExtensions, mediaEngineHeaderExtension{})
   275  		extensionIndex = len(m.headerExtensions) - 1
   276  	}
   277  
   278  	if typ == RTPCodecTypeAudio {
   279  		m.headerExtensions[extensionIndex].isAudio = true
   280  	} else if typ == RTPCodecTypeVideo {
   281  		m.headerExtensions[extensionIndex].isVideo = true
   282  	}
   283  
   284  	m.headerExtensions[extensionIndex].uri = extension.URI
   285  	m.headerExtensions[extensionIndex].allowedDirections = allowedDirections
   286  
   287  	return nil
   288  }
   289  
   290  // RegisterFeedback adds feedback mechanism to already registered codecs.
   291  func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType) {
   292  	m.mu.Lock()
   293  	defer m.mu.Unlock()
   294  
   295  	if typ == RTPCodecTypeVideo {
   296  		for i, v := range m.videoCodecs {
   297  			v.RTCPFeedback = append(v.RTCPFeedback, feedback)
   298  			m.videoCodecs[i] = v
   299  		}
   300  	} else if typ == RTPCodecTypeAudio {
   301  		for i, v := range m.audioCodecs {
   302  			v.RTCPFeedback = append(v.RTCPFeedback, feedback)
   303  			m.audioCodecs[i] = v
   304  		}
   305  	}
   306  }
   307  
   308  // getHeaderExtensionID returns the negotiated ID for a header extension.
   309  // If the Header Extension isn't enabled ok will be false
   310  func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) {
   311  	m.mu.RLock()
   312  	defer m.mu.RUnlock()
   313  
   314  	if m.negotiatedHeaderExtensions == nil {
   315  		return 0, false, false
   316  	}
   317  
   318  	for id, h := range m.negotiatedHeaderExtensions {
   319  		if extension.URI == h.uri {
   320  			return id, h.isAudio, h.isVideo
   321  		}
   322  	}
   323  
   324  	return
   325  }
   326  
   327  // copy copies any user modifiable state of the MediaEngine
   328  // all internal state is reset
   329  func (m *MediaEngine) copy() *MediaEngine {
   330  	m.mu.Lock()
   331  	defer m.mu.Unlock()
   332  	cloned := &MediaEngine{
   333  		videoCodecs:      append([]RTPCodecParameters{}, m.videoCodecs...),
   334  		audioCodecs:      append([]RTPCodecParameters{}, m.audioCodecs...),
   335  		headerExtensions: append([]mediaEngineHeaderExtension{}, m.headerExtensions...),
   336  	}
   337  	if len(m.headerExtensions) > 0 {
   338  		cloned.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{}
   339  	}
   340  	return cloned
   341  }
   342  
   343  func findCodecByPayload(codecs []RTPCodecParameters, payloadType PayloadType) *RTPCodecParameters {
   344  	for _, codec := range codecs {
   345  		if codec.PayloadType == payloadType {
   346  			return &codec
   347  		}
   348  	}
   349  	return nil
   350  }
   351  
   352  func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, RTPCodecType, error) {
   353  	m.mu.RLock()
   354  	defer m.mu.RUnlock()
   355  
   356  	// if we've negotiated audio or video, check the negotiated types before our
   357  	// built-in payload types, to ensure we pick the codec the other side wants.
   358  	if m.negotiatedVideo {
   359  		if codec := findCodecByPayload(m.negotiatedVideoCodecs, payloadType); codec != nil {
   360  			return *codec, RTPCodecTypeVideo, nil
   361  		}
   362  	}
   363  	if m.negotiatedAudio {
   364  		if codec := findCodecByPayload(m.negotiatedAudioCodecs, payloadType); codec != nil {
   365  			return *codec, RTPCodecTypeAudio, nil
   366  		}
   367  	}
   368  	if !m.negotiatedVideo {
   369  		if codec := findCodecByPayload(m.videoCodecs, payloadType); codec != nil {
   370  			return *codec, RTPCodecTypeVideo, nil
   371  		}
   372  	}
   373  	if !m.negotiatedAudio {
   374  		if codec := findCodecByPayload(m.audioCodecs, payloadType); codec != nil {
   375  			return *codec, RTPCodecTypeAudio, nil
   376  		}
   377  	}
   378  
   379  	return RTPCodecParameters{}, 0, ErrCodecNotFound
   380  }
   381  
   382  func (m *MediaEngine) collectStats(collector *statsReportCollector) {
   383  	m.mu.RLock()
   384  	defer m.mu.RUnlock()
   385  
   386  	statsLoop := func(codecs []RTPCodecParameters) {
   387  		for _, codec := range codecs {
   388  			collector.Collecting()
   389  			stats := CodecStats{
   390  				Timestamp:   statsTimestampFrom(time.Now()),
   391  				Type:        StatsTypeCodec,
   392  				ID:          codec.statsID,
   393  				PayloadType: codec.PayloadType,
   394  				MimeType:    codec.MimeType,
   395  				ClockRate:   codec.ClockRate,
   396  				Channels:    uint8(codec.Channels),
   397  				SDPFmtpLine: codec.SDPFmtpLine,
   398  			}
   399  
   400  			collector.Collect(stats.ID, stats)
   401  		}
   402  	}
   403  
   404  	statsLoop(m.videoCodecs)
   405  	statsLoop(m.audioCodecs)
   406  }
   407  
   408  // Look up a codec and enable if it exists
   409  func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCodecType, exactMatches, partialMatches []RTPCodecParameters) (codecMatchType, error) {
   410  	codecs := m.videoCodecs
   411  	if typ == RTPCodecTypeAudio {
   412  		codecs = m.audioCodecs
   413  	}
   414  
   415  	remoteFmtp := fmtp.Parse(remoteCodec.RTPCodecCapability.MimeType, remoteCodec.RTPCodecCapability.SDPFmtpLine)
   416  	if apt, hasApt := remoteFmtp.Parameter("apt"); hasApt {
   417  		payloadType, err := strconv.ParseUint(apt, 10, 8)
   418  		if err != nil {
   419  			return codecMatchNone, err
   420  		}
   421  
   422  		aptMatch := codecMatchNone
   423  		var aptCodec RTPCodecParameters
   424  		for _, codec := range exactMatches {
   425  			if codec.PayloadType == PayloadType(payloadType) {
   426  				aptMatch = codecMatchExact
   427  				aptCodec = codec
   428  				break
   429  			}
   430  		}
   431  
   432  		if aptMatch == codecMatchNone {
   433  			for _, codec := range partialMatches {
   434  				if codec.PayloadType == PayloadType(payloadType) {
   435  					aptMatch = codecMatchPartial
   436  					aptCodec = codec
   437  					break
   438  				}
   439  			}
   440  		}
   441  
   442  		if aptMatch == codecMatchNone {
   443  			return codecMatchNone, nil // not an error, we just ignore this codec we don't support
   444  		}
   445  
   446  		// replace the apt value with the original codec's payload type
   447  		toMatchCodec := remoteCodec
   448  		if aptMatched, mt := codecParametersFuzzySearch(aptCodec, codecs); mt == aptMatch {
   449  			toMatchCodec.SDPFmtpLine = strings.Replace(toMatchCodec.SDPFmtpLine, fmt.Sprintf("apt=%d", payloadType), fmt.Sprintf("apt=%d", aptMatched.PayloadType), 1)
   450  		}
   451  
   452  		// if apt's media codec is partial match, then apt codec must be partial match too
   453  		_, matchType := codecParametersFuzzySearch(toMatchCodec, codecs)
   454  		if matchType == codecMatchExact && aptMatch == codecMatchPartial {
   455  			matchType = codecMatchPartial
   456  		}
   457  		return matchType, nil
   458  	}
   459  
   460  	_, matchType := codecParametersFuzzySearch(remoteCodec, codecs)
   461  	return matchType, nil
   462  }
   463  
   464  // Update header extensions from a remote media section
   465  func (m *MediaEngine) updateHeaderExtensionFromMediaSection(media *sdp.MediaDescription) error {
   466  	var typ RTPCodecType
   467  	switch {
   468  	case strings.EqualFold(media.MediaName.Media, "audio"):
   469  		typ = RTPCodecTypeAudio
   470  	case strings.EqualFold(media.MediaName.Media, "video"):
   471  		typ = RTPCodecTypeVideo
   472  	default:
   473  		return nil
   474  	}
   475  	extensions, err := rtpExtensionsFromMediaDescription(media)
   476  	if err != nil {
   477  		return err
   478  	}
   479  
   480  	for extension, id := range extensions {
   481  		if err = m.updateHeaderExtension(id, extension, typ); err != nil {
   482  			return err
   483  		}
   484  	}
   485  	return nil
   486  }
   487  
   488  // Look up a header extension and enable if it exists
   489  func (m *MediaEngine) updateHeaderExtension(id int, extension string, typ RTPCodecType) error {
   490  	if m.negotiatedHeaderExtensions == nil {
   491  		return nil
   492  	}
   493  
   494  	for _, localExtension := range m.headerExtensions {
   495  		if localExtension.uri == extension {
   496  			h := mediaEngineHeaderExtension{uri: extension, allowedDirections: localExtension.allowedDirections}
   497  			if existingValue, ok := m.negotiatedHeaderExtensions[id]; ok {
   498  				h = existingValue
   499  			}
   500  
   501  			switch {
   502  			case localExtension.isAudio && typ == RTPCodecTypeAudio:
   503  				h.isAudio = true
   504  			case localExtension.isVideo && typ == RTPCodecTypeVideo:
   505  				h.isVideo = true
   506  			}
   507  
   508  			m.negotiatedHeaderExtensions[id] = h
   509  		}
   510  	}
   511  	return nil
   512  }
   513  
   514  func (m *MediaEngine) pushCodecs(codecs []RTPCodecParameters, typ RTPCodecType) {
   515  	for _, codec := range codecs {
   516  		if typ == RTPCodecTypeAudio {
   517  			m.negotiatedAudioCodecs = m.addCodec(m.negotiatedAudioCodecs, codec)
   518  		} else if typ == RTPCodecTypeVideo {
   519  			m.negotiatedVideoCodecs = m.addCodec(m.negotiatedVideoCodecs, codec)
   520  		}
   521  	}
   522  }
   523  
   524  // Update the MediaEngine from a remote description
   525  func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) error {
   526  	m.mu.Lock()
   527  	defer m.mu.Unlock()
   528  
   529  	for _, media := range desc.MediaDescriptions {
   530  		var typ RTPCodecType
   531  
   532  		switch {
   533  		case strings.EqualFold(media.MediaName.Media, "audio"):
   534  			typ = RTPCodecTypeAudio
   535  		case strings.EqualFold(media.MediaName.Media, "video"):
   536  			typ = RTPCodecTypeVideo
   537  		}
   538  
   539  		switch {
   540  		case !m.negotiatedAudio && typ == RTPCodecTypeAudio:
   541  			m.negotiatedAudio = true
   542  		case !m.negotiatedVideo && typ == RTPCodecTypeVideo:
   543  			m.negotiatedVideo = true
   544  		default:
   545  			// update header extesions from remote sdp if codec is negotiated, Firefox
   546  			// would send updated header extension in renegotiation.
   547  			// e.g. publish first track without simucalst ->negotiated-> publish second track with simucalst
   548  			// then the two media secontions have different rtp header extensions in offer
   549  			if err := m.updateHeaderExtensionFromMediaSection(media); err != nil {
   550  				return err
   551  			}
   552  			continue
   553  		}
   554  
   555  		codecs, err := codecsFromMediaDescription(media)
   556  		if err != nil {
   557  			return err
   558  		}
   559  
   560  		exactMatches := make([]RTPCodecParameters, 0, len(codecs))
   561  		partialMatches := make([]RTPCodecParameters, 0, len(codecs))
   562  
   563  		for _, codec := range codecs {
   564  			matchType, mErr := m.matchRemoteCodec(codec, typ, exactMatches, partialMatches)
   565  			if mErr != nil {
   566  				return mErr
   567  			}
   568  
   569  			if matchType == codecMatchExact {
   570  				exactMatches = append(exactMatches, codec)
   571  			} else if matchType == codecMatchPartial {
   572  				partialMatches = append(partialMatches, codec)
   573  			}
   574  		}
   575  
   576  		// use exact matches when they exist, otherwise fall back to partial
   577  		switch {
   578  		case len(exactMatches) > 0:
   579  			m.pushCodecs(exactMatches, typ)
   580  		case len(partialMatches) > 0:
   581  			m.pushCodecs(partialMatches, typ)
   582  		default:
   583  			// no match, not negotiated
   584  			continue
   585  		}
   586  
   587  		if err := m.updateHeaderExtensionFromMediaSection(media); err != nil {
   588  			return err
   589  		}
   590  	}
   591  	return nil
   592  }
   593  
   594  func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters {
   595  	m.mu.RLock()
   596  	defer m.mu.RUnlock()
   597  
   598  	if typ == RTPCodecTypeVideo {
   599  		if m.negotiatedVideo {
   600  			return m.negotiatedVideoCodecs
   601  		}
   602  
   603  		return m.videoCodecs
   604  	} else if typ == RTPCodecTypeAudio {
   605  		if m.negotiatedAudio {
   606  			return m.negotiatedAudioCodecs
   607  		}
   608  
   609  		return m.audioCodecs
   610  	}
   611  
   612  	return nil
   613  }
   614  
   615  func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPTransceiverDirection) RTPParameters { //nolint:gocognit
   616  	headerExtensions := make([]RTPHeaderExtensionParameter, 0)
   617  
   618  	// perform before locking to prevent recursive RLocks
   619  	foundCodecs := m.getCodecsByKind(typ)
   620  
   621  	m.mu.RLock()
   622  	defer m.mu.RUnlock()
   623  	if m.negotiatedVideo && typ == RTPCodecTypeVideo ||
   624  		m.negotiatedAudio && typ == RTPCodecTypeAudio {
   625  		for id, e := range m.negotiatedHeaderExtensions {
   626  			if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
   627  				headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
   628  			}
   629  		}
   630  	} else {
   631  		mediaHeaderExtensions := make(map[int]mediaEngineHeaderExtension)
   632  		for _, e := range m.headerExtensions {
   633  			usingNegotiatedID := false
   634  			for id := range m.negotiatedHeaderExtensions {
   635  				if m.negotiatedHeaderExtensions[id].uri == e.uri {
   636  					usingNegotiatedID = true
   637  					mediaHeaderExtensions[id] = e
   638  					break
   639  				}
   640  			}
   641  			if !usingNegotiatedID {
   642  				for id := 1; id < 15; id++ {
   643  					idAvailable := true
   644  					if _, ok := mediaHeaderExtensions[id]; ok {
   645  						idAvailable = false
   646  					}
   647  					if _, taken := m.negotiatedHeaderExtensions[id]; idAvailable && !taken {
   648  						mediaHeaderExtensions[id] = e
   649  						break
   650  					}
   651  				}
   652  			}
   653  		}
   654  
   655  		for id, e := range mediaHeaderExtensions {
   656  			if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
   657  				headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
   658  			}
   659  		}
   660  	}
   661  
   662  	return RTPParameters{
   663  		HeaderExtensions: headerExtensions,
   664  		Codecs:           foundCodecs,
   665  	}
   666  }
   667  
   668  func (m *MediaEngine) getRTPParametersByPayloadType(payloadType PayloadType) (RTPParameters, error) {
   669  	codec, typ, err := m.getCodecByPayload(payloadType)
   670  	if err != nil {
   671  		return RTPParameters{}, err
   672  	}
   673  
   674  	m.mu.RLock()
   675  	defer m.mu.RUnlock()
   676  	headerExtensions := make([]RTPHeaderExtensionParameter, 0)
   677  	for id, e := range m.negotiatedHeaderExtensions {
   678  		if e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo {
   679  			headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
   680  		}
   681  	}
   682  
   683  	return RTPParameters{
   684  		HeaderExtensions: headerExtensions,
   685  		Codecs:           []RTPCodecParameters{codec},
   686  	}, nil
   687  }
   688  
   689  func payloaderForCodec(codec RTPCodecCapability) (rtp.Payloader, error) {
   690  	switch strings.ToLower(codec.MimeType) {
   691  	case strings.ToLower(MimeTypeH264):
   692  		return &codecs.H264Payloader{}, nil
   693  	case strings.ToLower(MimeTypeOpus):
   694  		return &codecs.OpusPayloader{}, nil
   695  	case strings.ToLower(MimeTypeVP8):
   696  		return &codecs.VP8Payloader{
   697  			EnablePictureID: true,
   698  		}, nil
   699  	case strings.ToLower(MimeTypeVP9):
   700  		return &codecs.VP9Payloader{}, nil
   701  	case strings.ToLower(MimeTypeAV1):
   702  		return &codecs.AV1Payloader{}, nil
   703  	case strings.ToLower(MimeTypeG722):
   704  		return &codecs.G722Payloader{}, nil
   705  	case strings.ToLower(MimeTypePCMU), strings.ToLower(MimeTypePCMA):
   706  		return &codecs.G711Payloader{}, nil
   707  	default:
   708  		return nil, ErrNoPayloaderForCodec
   709  	}
   710  }
   711  
   712  func (m *MediaEngine) isRTXEnabled(typ RTPCodecType, directions []RTPTransceiverDirection) bool {
   713  	for _, p := range m.getRTPParametersByKind(typ, directions).Codecs {
   714  		if p.MimeType == MimeTypeRTX {
   715  			return true
   716  		}
   717  	}
   718  
   719  	return false
   720  }
   721  
   722  func (m *MediaEngine) isFECEnabled(typ RTPCodecType, directions []RTPTransceiverDirection) bool {
   723  	for _, p := range m.getRTPParametersByKind(typ, directions).Codecs {
   724  		if strings.Contains(p.MimeType, MimeTypeFlexFEC) {
   725  			return true
   726  		}
   727  	}
   728  
   729  	return false
   730  }