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