mediaengine_test.go 20 KB

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