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  }