github.com/pion/webrtc/v3@v3.2.24/mediaengine_test.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 "regexp" 12 "strings" 13 "testing" 14 15 "github.com/pion/sdp/v3" 16 "github.com/pion/transport/v2/test" 17 "github.com/stretchr/testify/assert" 18 ) 19 20 // pion/webrtc#1078 21 func TestOpusCase(t *testing.T) { 22 pc, err := NewPeerConnection(Configuration{}) 23 assert.NoError(t, err) 24 25 _, err = pc.AddTransceiverFromKind(RTPCodecTypeAudio) 26 assert.NoError(t, err) 27 28 offer, err := pc.CreateOffer(nil) 29 assert.NoError(t, err) 30 31 assert.True(t, regexp.MustCompile(`(?m)^a=rtpmap:\d+ opus/48000/2`).MatchString(offer.SDP)) 32 assert.NoError(t, pc.Close()) 33 } 34 35 // pion/example-webrtc-applications#89 36 func TestVideoCase(t *testing.T) { 37 pc, err := NewPeerConnection(Configuration{}) 38 assert.NoError(t, err) 39 40 _, err = pc.AddTransceiverFromKind(RTPCodecTypeVideo) 41 assert.NoError(t, err) 42 43 offer, err := pc.CreateOffer(nil) 44 assert.NoError(t, err) 45 46 assert.True(t, regexp.MustCompile(`(?m)^a=rtpmap:\d+ H264/90000`).MatchString(offer.SDP)) 47 assert.True(t, regexp.MustCompile(`(?m)^a=rtpmap:\d+ VP8/90000`).MatchString(offer.SDP)) 48 assert.True(t, regexp.MustCompile(`(?m)^a=rtpmap:\d+ VP9/90000`).MatchString(offer.SDP)) 49 assert.NoError(t, pc.Close()) 50 } 51 52 func TestMediaEngineRemoteDescription(t *testing.T) { 53 mustParse := func(raw string) sdp.SessionDescription { 54 s := sdp.SessionDescription{} 55 assert.NoError(t, s.Unmarshal([]byte(raw))) 56 return s 57 } 58 59 t.Run("No Media", func(t *testing.T) { 60 const noMedia = `v=0 61 o=- 4596489990601351948 2 IN IP4 127.0.0.1 62 s=- 63 t=0 0 64 ` 65 m := MediaEngine{} 66 assert.NoError(t, m.RegisterDefaultCodecs()) 67 assert.NoError(t, m.updateFromRemoteDescription(mustParse(noMedia))) 68 69 assert.False(t, m.negotiatedVideo) 70 assert.False(t, m.negotiatedAudio) 71 }) 72 73 t.Run("Enable Opus", func(t *testing.T) { 74 const opusSamePayload = `v=0 75 o=- 4596489990601351948 2 IN IP4 127.0.0.1 76 s=- 77 t=0 0 78 m=audio 9 UDP/TLS/RTP/SAVPF 111 79 a=rtpmap:111 opus/48000/2 80 a=fmtp:111 minptime=10; useinbandfec=1 81 ` 82 83 m := MediaEngine{} 84 assert.NoError(t, m.RegisterDefaultCodecs()) 85 assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload))) 86 87 assert.False(t, m.negotiatedVideo) 88 assert.True(t, m.negotiatedAudio) 89 90 opusCodec, _, err := m.getCodecByPayload(111) 91 assert.NoError(t, err) 92 assert.Equal(t, opusCodec.MimeType, MimeTypeOpus) 93 }) 94 95 t.Run("Change Payload Type", func(t *testing.T) { 96 const opusSamePayload = `v=0 97 o=- 4596489990601351948 2 IN IP4 127.0.0.1 98 s=- 99 t=0 0 100 m=audio 9 UDP/TLS/RTP/SAVPF 112 101 a=rtpmap:112 opus/48000/2 102 a=fmtp:112 minptime=10; useinbandfec=1 103 ` 104 105 m := MediaEngine{} 106 assert.NoError(t, m.RegisterDefaultCodecs()) 107 assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload))) 108 109 assert.False(t, m.negotiatedVideo) 110 assert.True(t, m.negotiatedAudio) 111 112 _, _, err := m.getCodecByPayload(111) 113 assert.Error(t, err) 114 115 opusCodec, _, err := m.getCodecByPayload(112) 116 assert.NoError(t, err) 117 assert.Equal(t, opusCodec.MimeType, MimeTypeOpus) 118 }) 119 120 t.Run("Ambiguous Payload Type", func(t *testing.T) { 121 const opusSamePayload = `v=0 122 o=- 4596489990601351948 2 IN IP4 127.0.0.1 123 s=- 124 t=0 0 125 m=audio 9 UDP/TLS/RTP/SAVPF 96 126 a=rtpmap:96 opus/48000/2 127 a=fmtp:96 minptime=10; useinbandfec=1 128 ` 129 130 m := MediaEngine{} 131 assert.NoError(t, m.RegisterDefaultCodecs()) 132 assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload))) 133 134 assert.False(t, m.negotiatedVideo) 135 assert.True(t, m.negotiatedAudio) 136 137 opusCodec, _, err := m.getCodecByPayload(96) 138 assert.NoError(t, err) 139 assert.Equal(t, opusCodec.MimeType, MimeTypeOpus) 140 }) 141 142 t.Run("Case Insensitive", func(t *testing.T) { 143 const opusUpcase = `v=0 144 o=- 4596489990601351948 2 IN IP4 127.0.0.1 145 s=- 146 t=0 0 147 m=audio 9 UDP/TLS/RTP/SAVPF 111 148 a=rtpmap:111 OPUS/48000/2 149 a=fmtp:111 minptime=10; useinbandfec=1 150 ` 151 152 m := MediaEngine{} 153 assert.NoError(t, m.RegisterDefaultCodecs()) 154 assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusUpcase))) 155 156 assert.False(t, m.negotiatedVideo) 157 assert.True(t, m.negotiatedAudio) 158 159 opusCodec, _, err := m.getCodecByPayload(111) 160 assert.NoError(t, err) 161 assert.Equal(t, opusCodec.MimeType, "audio/OPUS") 162 }) 163 164 t.Run("Handle different fmtp", func(t *testing.T) { 165 const opusNoFmtp = `v=0 166 o=- 4596489990601351948 2 IN IP4 127.0.0.1 167 s=- 168 t=0 0 169 m=audio 9 UDP/TLS/RTP/SAVPF 111 170 a=rtpmap:111 opus/48000/2 171 ` 172 173 m := MediaEngine{} 174 assert.NoError(t, m.RegisterDefaultCodecs()) 175 assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusNoFmtp))) 176 177 assert.False(t, m.negotiatedVideo) 178 assert.True(t, m.negotiatedAudio) 179 180 opusCodec, _, err := m.getCodecByPayload(111) 181 assert.NoError(t, err) 182 assert.Equal(t, opusCodec.MimeType, MimeTypeOpus) 183 }) 184 185 t.Run("Header Extensions", func(t *testing.T) { 186 const headerExtensions = `v=0 187 o=- 4596489990601351948 2 IN IP4 127.0.0.1 188 s=- 189 t=0 0 190 m=audio 9 UDP/TLS/RTP/SAVPF 111 191 a=extmap:7 urn:ietf:params:rtp-hdrext:sdes:mid 192 a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id 193 a=rtpmap:111 opus/48000/2 194 ` 195 196 m := MediaEngine{} 197 assert.NoError(t, m.RegisterDefaultCodecs()) 198 registerSimulcastHeaderExtensions(&m, RTPCodecTypeAudio) 199 assert.NoError(t, m.updateFromRemoteDescription(mustParse(headerExtensions))) 200 201 assert.False(t, m.negotiatedVideo) 202 assert.True(t, m.negotiatedAudio) 203 204 absID, absAudioEnabled, absVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI}) 205 assert.Equal(t, absID, 0) 206 assert.False(t, absAudioEnabled) 207 assert.False(t, absVideoEnabled) 208 209 midID, midAudioEnabled, midVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI}) 210 assert.Equal(t, midID, 7) 211 assert.True(t, midAudioEnabled) 212 assert.False(t, midVideoEnabled) 213 }) 214 215 t.Run("Prefers exact codec matches", func(t *testing.T) { 216 const profileLevels = `v=0 217 o=- 4596489990601351948 2 IN IP4 127.0.0.1 218 s=- 219 t=0 0 220 m=video 60323 UDP/TLS/RTP/SAVPF 96 98 221 a=rtpmap:96 H264/90000 222 a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f 223 a=rtpmap:98 H264/90000 224 a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f 225 ` 226 m := MediaEngine{} 227 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 228 RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil}, 229 PayloadType: 127, 230 }, RTPCodecTypeVideo)) 231 assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels))) 232 233 assert.True(t, m.negotiatedVideo) 234 assert.False(t, m.negotiatedAudio) 235 236 supportedH264, _, err := m.getCodecByPayload(98) 237 assert.NoError(t, err) 238 assert.Equal(t, supportedH264.MimeType, MimeTypeH264) 239 240 _, _, err = m.getCodecByPayload(96) 241 assert.Error(t, err) 242 }) 243 244 t.Run("Does not match when fmtpline is set and does not match", func(t *testing.T) { 245 const profileLevels = `v=0 246 o=- 4596489990601351948 2 IN IP4 127.0.0.1 247 s=- 248 t=0 0 249 m=video 60323 UDP/TLS/RTP/SAVPF 96 98 250 a=rtpmap:96 H264/90000 251 a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f 252 ` 253 m := MediaEngine{} 254 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 255 RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil}, 256 PayloadType: 127, 257 }, RTPCodecTypeVideo)) 258 assert.Error(t, m.updateFromRemoteDescription(mustParse(profileLevels))) 259 260 _, _, err := m.getCodecByPayload(96) 261 assert.Error(t, err) 262 }) 263 264 t.Run("Matches when fmtpline is not set in offer, but exists in mediaengine", func(t *testing.T) { 265 const profileLevels = `v=0 266 o=- 4596489990601351948 2 IN IP4 127.0.0.1 267 s=- 268 t=0 0 269 m=video 60323 UDP/TLS/RTP/SAVPF 96 270 a=rtpmap:96 VP9/90000 271 ` 272 m := MediaEngine{} 273 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 274 RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=0", nil}, 275 PayloadType: 98, 276 }, RTPCodecTypeVideo)) 277 assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels))) 278 279 assert.True(t, m.negotiatedVideo) 280 281 _, _, err := m.getCodecByPayload(96) 282 assert.NoError(t, err) 283 }) 284 285 t.Run("Matches when fmtpline exists in neither", func(t *testing.T) { 286 const profileLevels = `v=0 287 o=- 4596489990601351948 2 IN IP4 127.0.0.1 288 s=- 289 t=0 0 290 m=video 60323 UDP/TLS/RTP/SAVPF 96 291 a=rtpmap:96 VP8/90000 292 ` 293 m := MediaEngine{} 294 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 295 RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, 296 PayloadType: 96, 297 }, RTPCodecTypeVideo)) 298 assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels))) 299 300 assert.True(t, m.negotiatedVideo) 301 302 _, _, err := m.getCodecByPayload(96) 303 assert.NoError(t, err) 304 }) 305 306 t.Run("Matches when rtx apt for exact match codec", func(t *testing.T) { 307 const profileLevels = `v=0 308 o=- 4596489990601351948 2 IN IP4 127.0.0.1 309 s=- 310 t=0 0 311 m=video 60323 UDP/TLS/RTP/SAVPF 94 96 97 312 a=rtpmap:94 VP8/90000 313 a=rtpmap:96 VP9/90000 314 a=fmtp:96 profile-id=2 315 a=rtpmap:97 rtx/90000 316 a=fmtp:97 apt=96 317 ` 318 m := MediaEngine{} 319 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 320 RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, 321 PayloadType: 94, 322 }, RTPCodecTypeVideo)) 323 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 324 RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=2", nil}, 325 PayloadType: 96, 326 }, RTPCodecTypeVideo)) 327 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 328 RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil}, 329 PayloadType: 97, 330 }, RTPCodecTypeVideo)) 331 assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels))) 332 333 assert.True(t, m.negotiatedVideo) 334 335 _, _, err := m.getCodecByPayload(97) 336 assert.NoError(t, err) 337 }) 338 339 t.Run("Matches when rtx apt for partial match codec", func(t *testing.T) { 340 const profileLevels = `v=0 341 o=- 4596489990601351948 2 IN IP4 127.0.0.1 342 s=- 343 t=0 0 344 m=video 60323 UDP/TLS/RTP/SAVPF 94 96 97 345 a=rtpmap:94 VP8/90000 346 a=rtpmap:96 VP9/90000 347 a=fmtp:96 profile-id=2 348 a=rtpmap:97 rtx/90000 349 a=fmtp:97 apt=96 350 ` 351 m := MediaEngine{} 352 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 353 RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, 354 PayloadType: 94, 355 }, RTPCodecTypeVideo)) 356 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 357 RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=1", nil}, 358 PayloadType: 96, 359 }, RTPCodecTypeVideo)) 360 assert.NoError(t, m.RegisterCodec(RTPCodecParameters{ 361 RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil}, 362 PayloadType: 97, 363 }, RTPCodecTypeVideo)) 364 assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels))) 365 366 assert.True(t, m.negotiatedVideo) 367 368 _, _, err := m.getCodecByPayload(97) 369 assert.ErrorIs(t, err, ErrCodecNotFound) 370 }) 371 } 372 373 func TestMediaEngineHeaderExtensionDirection(t *testing.T) { 374 report := test.CheckRoutines(t) 375 defer report() 376 377 registerCodec := func(m *MediaEngine) { 378 assert.NoError(t, m.RegisterCodec( 379 RTPCodecParameters{ 380 RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil}, 381 PayloadType: 111, 382 }, RTPCodecTypeAudio)) 383 } 384 385 t.Run("No Direction", func(t *testing.T) { 386 m := &MediaEngine{} 387 registerCodec(m) 388 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio)) 389 390 params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) 391 392 assert.Equal(t, 1, len(params.HeaderExtensions)) 393 }) 394 395 t.Run("Same Direction", func(t *testing.T) { 396 m := &MediaEngine{} 397 registerCodec(m) 398 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionRecvonly)) 399 400 params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) 401 402 assert.Equal(t, 1, len(params.HeaderExtensions)) 403 }) 404 405 t.Run("Different Direction", func(t *testing.T) { 406 m := &MediaEngine{} 407 registerCodec(m) 408 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly)) 409 410 params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) 411 412 assert.Equal(t, 0, len(params.HeaderExtensions)) 413 }) 414 415 t.Run("Invalid Direction", func(t *testing.T) { 416 m := &MediaEngine{} 417 registerCodec(m) 418 419 assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv), ErrRegisterHeaderExtensionInvalidDirection) 420 assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionInactive), ErrRegisterHeaderExtensionInvalidDirection) 421 assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirection(0)), ErrRegisterHeaderExtensionInvalidDirection) 422 }) 423 424 t.Run("Unique extmapid with different codec", func(t *testing.T) { 425 m := &MediaEngine{} 426 registerCodec(m) 427 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio)) 428 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test2"}, RTPCodecTypeVideo)) 429 430 audio := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) 431 video := m.getRTPParametersByKind(RTPCodecTypeVideo, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) 432 433 assert.Equal(t, 1, len(audio.HeaderExtensions)) 434 assert.Equal(t, 1, len(video.HeaderExtensions)) 435 assert.NotEqual(t, audio.HeaderExtensions[0].ID, video.HeaderExtensions[0].ID) 436 }) 437 } 438 439 // If a user attempts to register a codec twice we should just discard duplicate calls 440 func TestMediaEngineDoubleRegister(t *testing.T) { 441 m := MediaEngine{} 442 443 assert.NoError(t, m.RegisterCodec( 444 RTPCodecParameters{ 445 RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil}, 446 PayloadType: 111, 447 }, RTPCodecTypeAudio)) 448 449 assert.NoError(t, m.RegisterCodec( 450 RTPCodecParameters{ 451 RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil}, 452 PayloadType: 111, 453 }, RTPCodecTypeAudio)) 454 455 assert.Equal(t, len(m.audioCodecs), 1) 456 } 457 458 // The cloned MediaEngine instance should be able to update negotiated header extensions. 459 func TestUpdateHeaderExtenstionToClonedMediaEngine(t *testing.T) { 460 src := MediaEngine{} 461 462 assert.NoError(t, src.RegisterCodec( 463 RTPCodecParameters{ 464 RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil}, 465 PayloadType: 111, 466 }, RTPCodecTypeAudio)) 467 468 assert.NoError(t, src.RegisterHeaderExtension(RTPHeaderExtensionCapability{"test-extension"}, RTPCodecTypeAudio)) 469 470 validate := func(m *MediaEngine) { 471 assert.NoError(t, m.updateHeaderExtension(2, "test-extension", RTPCodecTypeAudio)) 472 473 id, audioNegotiated, videoNegotiated := m.getHeaderExtensionID(RTPHeaderExtensionCapability{URI: "test-extension"}) 474 assert.Equal(t, 2, id) 475 assert.True(t, audioNegotiated) 476 assert.False(t, videoNegotiated) 477 } 478 479 validate(&src) 480 validate(src.copy()) 481 } 482 483 func TestExtensionIdCollision(t *testing.T) { 484 mustParse := func(raw string) sdp.SessionDescription { 485 s := sdp.SessionDescription{} 486 assert.NoError(t, s.Unmarshal([]byte(raw))) 487 return s 488 } 489 sdpSnippet := `v=0 490 o=- 4596489990601351948 2 IN IP4 127.0.0.1 491 s=- 492 t=0 0 493 m=audio 9 UDP/TLS/RTP/SAVPF 111 494 a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid 495 a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level 496 a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id 497 a=rtpmap:111 opus/48000/2 498 ` 499 500 m := MediaEngine{} 501 assert.NoError(t, m.RegisterDefaultCodecs()) 502 503 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeVideo)) 504 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"urn:3gpp:video-orientation"}, RTPCodecTypeVideo)) 505 506 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeAudio)) 507 assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.AudioLevelURI}, RTPCodecTypeAudio)) 508 509 assert.NoError(t, m.updateFromRemoteDescription(mustParse(sdpSnippet))) 510 511 assert.True(t, m.negotiatedAudio) 512 assert.False(t, m.negotiatedVideo) 513 514 id, audioNegotiated, videoNegotiated := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI}) 515 assert.Equal(t, id, 0) 516 assert.False(t, audioNegotiated) 517 assert.False(t, videoNegotiated) 518 519 id, audioNegotiated, videoNegotiated = m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI}) 520 assert.Equal(t, id, 2) 521 assert.True(t, audioNegotiated) 522 assert.False(t, videoNegotiated) 523 524 id, audioNegotiated, videoNegotiated = m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.AudioLevelURI}) 525 assert.Equal(t, id, 1) 526 assert.True(t, audioNegotiated) 527 assert.False(t, videoNegotiated) 528 529 params := m.getRTPParametersByKind(RTPCodecTypeVideo, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}) 530 extensions := params.HeaderExtensions 531 532 assert.Equal(t, 2, len(extensions)) 533 534 midIndex := -1 535 if extensions[0].URI == sdp.SDESMidURI { 536 midIndex = 0 537 } else if extensions[1].URI == sdp.SDESMidURI { 538 midIndex = 1 539 } 540 541 voIndex := -1 542 if extensions[0].URI == "urn:3gpp:video-orientation" { 543 voIndex = 0 544 } else if extensions[1].URI == "urn:3gpp:video-orientation" { 545 voIndex = 1 546 } 547 548 assert.NotEqual(t, midIndex, -1) 549 assert.NotEqual(t, voIndex, -1) 550 551 assert.Equal(t, 2, extensions[midIndex].ID) 552 assert.NotEqual(t, 1, extensions[voIndex].ID) 553 assert.NotEqual(t, 2, extensions[voIndex].ID) 554 assert.NotEqual(t, 5, extensions[voIndex].ID) 555 } 556 557 func TestCaseInsensitiveMimeType(t *testing.T) { 558 const offerSdp = ` 559 v=0 560 o=- 8448668841136641781 4 IN IP4 127.0.0.1 561 s=- 562 t=0 0 563 a=group:BUNDLE 0 1 2 564 a=extmap-allow-mixed 565 a=msid-semantic: WMS 4beea6b0-cf95-449c-a1ec-78e16b247426 566 m=video 9 UDP/TLS/RTP/SAVPF 96 127 567 c=IN IP4 0.0.0.0 568 a=rtcp:9 IN IP4 0.0.0.0 569 a=ice-ufrag:1/MvHwjAyVf27aLu 570 a=ice-pwd:3dBU7cFOBl120v33cynDvN1E 571 a=ice-options:google-ice 572 a=fingerprint:sha-256 75:74:5A:A6:A4:E5:52:F4:A7:67:4C:01:C7:EE:91:3F:21:3D:A2:E3:53:7B:6F:30:86:F2:30:AA:65:FB:04:24 573 a=setup:actpass 574 a=mid:1 575 a=sendonly 576 a=rtpmap:96 VP8/90000 577 a=rtcp-fb:96 goog-remb 578 a=rtcp-fb:96 transport-cc 579 a=rtcp-fb:96 ccm fir 580 a=rtcp-fb:96 nack 581 a=rtcp-fb:96 nack pli 582 a=rtpmap:127 H264/90000 583 a=rtcp-fb:127 goog-remb 584 a=rtcp-fb:127 transport-cc 585 a=rtcp-fb:127 ccm fir 586 a=rtcp-fb:127 nack 587 a=rtcp-fb:127 nack pli 588 a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f 589 590 ` 591 592 for _, mimeTypeVp8 := range []string{ 593 "video/vp8", 594 "video/VP8", 595 } { 596 t.Run(fmt.Sprintf("MimeType: %s", mimeTypeVp8), func(t *testing.T) { 597 me := &MediaEngine{} 598 feedback := []RTCPFeedback{ 599 {Type: TypeRTCPFBTransportCC}, 600 {Type: TypeRTCPFBCCM, Parameter: "fir"}, 601 {Type: TypeRTCPFBNACK}, 602 {Type: TypeRTCPFBNACK, Parameter: "pli"}, 603 } 604 605 for _, codec := range []RTPCodecParameters{ 606 { 607 RTPCodecCapability: RTPCodecCapability{MimeType: mimeTypeVp8, ClockRate: 90000, RTCPFeedback: feedback}, 608 PayloadType: 96, 609 }, 610 { 611 RTPCodecCapability: RTPCodecCapability{MimeType: "video/h264", ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", RTCPFeedback: feedback}, 612 PayloadType: 127, 613 }, 614 } { 615 assert.NoError(t, me.RegisterCodec(codec, RTPCodecTypeVideo)) 616 } 617 618 api := NewAPI(WithMediaEngine(me)) 619 pc, err := api.NewPeerConnection(Configuration{ 620 SDPSemantics: SDPSemanticsUnifiedPlan, 621 }) 622 assert.NoError(t, err) 623 624 offer := SessionDescription{ 625 Type: SDPTypeOffer, 626 SDP: offerSdp, 627 } 628 629 assert.NoError(t, pc.SetRemoteDescription(offer)) 630 answer, err := pc.CreateAnswer(nil) 631 assert.NoError(t, err) 632 assert.NotNil(t, answer) 633 assert.NoError(t, pc.SetLocalDescription(answer)) 634 assert.True(t, strings.Contains(answer.SDP, "VP8") || strings.Contains(answer.SDP, "vp8")) 635 636 assert.NoError(t, pc.Close()) 637 }) 638 } 639 }