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 }