mediaengine_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  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 95 106 107 108 109 96 97
  258. a=rtpmap:94 VP8/90000
  259. a=rtpmap:95 rtx/90000
  260. a=fmtp:95 apt=94
  261. a=rtpmap:106 H264/90000
  262. a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
  263. a=rtpmap:107 rtx/90000
  264. a=fmtp:107 apt=106
  265. a=rtpmap:108 H264/90000
  266. a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
  267. a=rtpmap:109 rtx/90000
  268. a=fmtp:109 apt=108
  269. a=rtpmap:96 VP9/90000
  270. a=fmtp:96 profile-id=2
  271. a=rtpmap:97 rtx/90000
  272. a=fmtp:97 apt=96
  273. `
  274. m := MediaEngine{}
  275. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  276. RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
  277. PayloadType: 96,
  278. }, RTPCodecTypeVideo))
  279. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  280. RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
  281. PayloadType: 97,
  282. }, RTPCodecTypeVideo))
  283. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  284. RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", nil},
  285. PayloadType: 102,
  286. }, RTPCodecTypeVideo))
  287. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  288. RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil},
  289. PayloadType: 103,
  290. }, RTPCodecTypeVideo))
  291. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  292. RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", nil},
  293. PayloadType: 104,
  294. }, RTPCodecTypeVideo))
  295. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  296. RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=104", nil},
  297. PayloadType: 105,
  298. }, RTPCodecTypeVideo))
  299. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  300. RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=2", nil},
  301. PayloadType: 98,
  302. }, RTPCodecTypeVideo))
  303. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  304. RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil},
  305. PayloadType: 99,
  306. }, RTPCodecTypeVideo))
  307. assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
  308. assert.True(t, m.negotiatedVideo)
  309. vp9Codec, _, err := m.getCodecByPayload(96)
  310. assert.NoError(t, err)
  311. assert.Equal(t, vp9Codec.MimeType, MimeTypeVP9)
  312. vp9RTX, _, err := m.getCodecByPayload(97)
  313. assert.NoError(t, err)
  314. assert.Equal(t, vp9RTX.MimeType, "video/rtx")
  315. h264P1Codec, _, err := m.getCodecByPayload(106)
  316. assert.NoError(t, err)
  317. assert.Equal(t, h264P1Codec.MimeType, MimeTypeH264)
  318. assert.Equal(t, h264P1Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f")
  319. h264P1RTX, _, err := m.getCodecByPayload(107)
  320. assert.NoError(t, err)
  321. assert.Equal(t, h264P1RTX.MimeType, "video/rtx")
  322. assert.Equal(t, h264P1RTX.SDPFmtpLine, "apt=106")
  323. h264P0Codec, _, err := m.getCodecByPayload(108)
  324. assert.NoError(t, err)
  325. assert.Equal(t, h264P0Codec.MimeType, MimeTypeH264)
  326. assert.Equal(t, h264P0Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f")
  327. h264P0RTX, _, err := m.getCodecByPayload(109)
  328. assert.NoError(t, err)
  329. assert.Equal(t, h264P0RTX.MimeType, "video/rtx")
  330. assert.Equal(t, h264P0RTX.SDPFmtpLine, "apt=108")
  331. })
  332. t.Run("Matches when rtx apt for partial match codec", func(t *testing.T) {
  333. const profileLevels = `v=0
  334. o=- 4596489990601351948 2 IN IP4 127.0.0.1
  335. s=-
  336. t=0 0
  337. m=video 60323 UDP/TLS/RTP/SAVPF 94 96 97
  338. a=rtpmap:94 VP8/90000
  339. a=rtpmap:96 VP9/90000
  340. a=fmtp:96 profile-id=2
  341. a=rtpmap:97 rtx/90000
  342. a=fmtp:97 apt=96
  343. `
  344. m := MediaEngine{}
  345. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  346. RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
  347. PayloadType: 94,
  348. }, RTPCodecTypeVideo))
  349. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  350. RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=1", nil},
  351. PayloadType: 96,
  352. }, RTPCodecTypeVideo))
  353. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  354. RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil},
  355. PayloadType: 97,
  356. }, RTPCodecTypeVideo))
  357. assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
  358. assert.True(t, m.negotiatedVideo)
  359. _, _, err := m.getCodecByPayload(97)
  360. assert.ErrorIs(t, err, ErrCodecNotFound)
  361. })
  362. }
  363. func TestMediaEngineHeaderExtensionDirection(t *testing.T) {
  364. report := test.CheckRoutines(t)
  365. defer report()
  366. registerCodec := func(m *MediaEngine) {
  367. assert.NoError(t, m.RegisterCodec(
  368. RTPCodecParameters{
  369. RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil},
  370. PayloadType: 111,
  371. }, RTPCodecTypeAudio))
  372. }
  373. t.Run("No Direction", func(t *testing.T) {
  374. m := &MediaEngine{}
  375. registerCodec(m)
  376. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio))
  377. params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
  378. assert.Equal(t, 1, len(params.HeaderExtensions))
  379. })
  380. t.Run("Same Direction", func(t *testing.T) {
  381. m := &MediaEngine{}
  382. registerCodec(m)
  383. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionRecvonly))
  384. params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
  385. assert.Equal(t, 1, len(params.HeaderExtensions))
  386. })
  387. t.Run("Different Direction", func(t *testing.T) {
  388. m := &MediaEngine{}
  389. registerCodec(m)
  390. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly))
  391. params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
  392. assert.Equal(t, 0, len(params.HeaderExtensions))
  393. })
  394. t.Run("Invalid Direction", func(t *testing.T) {
  395. m := &MediaEngine{}
  396. registerCodec(m)
  397. assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv), ErrRegisterHeaderExtensionInvalidDirection)
  398. assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionInactive), ErrRegisterHeaderExtensionInvalidDirection)
  399. assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirection(0)), ErrRegisterHeaderExtensionInvalidDirection)
  400. })
  401. t.Run("Unique extmapid with different codec", func(t *testing.T) {
  402. m := &MediaEngine{}
  403. registerCodec(m)
  404. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio))
  405. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test2"}, RTPCodecTypeVideo))
  406. audio := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
  407. video := m.getRTPParametersByKind(RTPCodecTypeVideo, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
  408. assert.Equal(t, 1, len(audio.HeaderExtensions))
  409. assert.Equal(t, 1, len(video.HeaderExtensions))
  410. assert.NotEqual(t, audio.HeaderExtensions[0].ID, video.HeaderExtensions[0].ID)
  411. })
  412. }
  413. // If a user attempts to register a codec twice we should just discard duplicate calls
  414. func TestMediaEngineDoubleRegister(t *testing.T) {
  415. m := MediaEngine{}
  416. assert.NoError(t, m.RegisterCodec(
  417. RTPCodecParameters{
  418. RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil},
  419. PayloadType: 111,
  420. }, RTPCodecTypeAudio))
  421. assert.NoError(t, m.RegisterCodec(
  422. RTPCodecParameters{
  423. RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil},
  424. PayloadType: 111,
  425. }, RTPCodecTypeAudio))
  426. assert.Equal(t, len(m.audioCodecs), 1)
  427. }
  428. // The cloned MediaEngine instance should be able to update negotiated header extensions.
  429. func TestUpdateHeaderExtenstionToClonedMediaEngine(t *testing.T) {
  430. src := MediaEngine{}
  431. assert.NoError(t, src.RegisterCodec(
  432. RTPCodecParameters{
  433. RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil},
  434. PayloadType: 111,
  435. }, RTPCodecTypeAudio))
  436. assert.NoError(t, src.RegisterHeaderExtension(RTPHeaderExtensionCapability{"test-extension"}, RTPCodecTypeAudio))
  437. validate := func(m *MediaEngine) {
  438. assert.NoError(t, m.updateHeaderExtension(2, "test-extension", RTPCodecTypeAudio))
  439. id, audioNegotiated, videoNegotiated := m.getHeaderExtensionID(RTPHeaderExtensionCapability{URI: "test-extension"})
  440. assert.Equal(t, 2, id)
  441. assert.True(t, audioNegotiated)
  442. assert.False(t, videoNegotiated)
  443. }
  444. validate(&src)
  445. validate(src.copy())
  446. }
  447. func TestExtensionIdCollision(t *testing.T) {
  448. mustParse := func(raw string) sdp.SessionDescription {
  449. s := sdp.SessionDescription{}
  450. assert.NoError(t, s.Unmarshal([]byte(raw)))
  451. return s
  452. }
  453. sdpSnippet := `v=0
  454. o=- 4596489990601351948 2 IN IP4 127.0.0.1
  455. s=-
  456. t=0 0
  457. m=audio 9 UDP/TLS/RTP/SAVPF 111
  458. a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid
  459. a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
  460. a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
  461. a=rtpmap:111 opus/48000/2
  462. `
  463. m := MediaEngine{}
  464. assert.NoError(t, m.RegisterDefaultCodecs())
  465. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeVideo))
  466. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"urn:3gpp:video-orientation"}, RTPCodecTypeVideo))
  467. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeAudio))
  468. assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.AudioLevelURI}, RTPCodecTypeAudio))
  469. assert.NoError(t, m.updateFromRemoteDescription(mustParse(sdpSnippet)))
  470. assert.True(t, m.negotiatedAudio)
  471. assert.False(t, m.negotiatedVideo)
  472. id, audioNegotiated, videoNegotiated := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI})
  473. assert.Equal(t, id, 0)
  474. assert.False(t, audioNegotiated)
  475. assert.False(t, videoNegotiated)
  476. id, audioNegotiated, videoNegotiated = m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI})
  477. assert.Equal(t, id, 2)
  478. assert.True(t, audioNegotiated)
  479. assert.False(t, videoNegotiated)
  480. id, audioNegotiated, videoNegotiated = m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.AudioLevelURI})
  481. assert.Equal(t, id, 1)
  482. assert.True(t, audioNegotiated)
  483. assert.False(t, videoNegotiated)
  484. params := m.getRTPParametersByKind(RTPCodecTypeVideo, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly})
  485. extensions := params.HeaderExtensions
  486. assert.Equal(t, 2, len(extensions))
  487. midIndex := -1
  488. if extensions[0].URI == sdp.SDESMidURI {
  489. midIndex = 0
  490. } else if extensions[1].URI == sdp.SDESMidURI {
  491. midIndex = 1
  492. }
  493. voIndex := -1
  494. if extensions[0].URI == "urn:3gpp:video-orientation" {
  495. voIndex = 0
  496. } else if extensions[1].URI == "urn:3gpp:video-orientation" {
  497. voIndex = 1
  498. }
  499. assert.NotEqual(t, midIndex, -1)
  500. assert.NotEqual(t, voIndex, -1)
  501. assert.Equal(t, 2, extensions[midIndex].ID)
  502. assert.NotEqual(t, 1, extensions[voIndex].ID)
  503. assert.NotEqual(t, 2, extensions[voIndex].ID)
  504. assert.NotEqual(t, 5, extensions[voIndex].ID)
  505. }
  506. func TestCaseInsensitiveMimeType(t *testing.T) {
  507. const offerSdp = `
  508. v=0
  509. o=- 8448668841136641781 4 IN IP4 127.0.0.1
  510. s=-
  511. t=0 0
  512. a=group:BUNDLE 0 1 2
  513. a=extmap-allow-mixed
  514. a=msid-semantic: WMS 4beea6b0-cf95-449c-a1ec-78e16b247426
  515. m=video 9 UDP/TLS/RTP/SAVPF 96 127
  516. c=IN IP4 0.0.0.0
  517. a=rtcp:9 IN IP4 0.0.0.0
  518. a=ice-ufrag:1/MvHwjAyVf27aLu
  519. a=ice-pwd:3dBU7cFOBl120v33cynDvN1E
  520. a=ice-options:google-ice
  521. 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
  522. a=setup:actpass
  523. a=mid:1
  524. a=sendonly
  525. a=rtpmap:96 VP8/90000
  526. a=rtcp-fb:96 goog-remb
  527. a=rtcp-fb:96 transport-cc
  528. a=rtcp-fb:96 ccm fir
  529. a=rtcp-fb:96 nack
  530. a=rtcp-fb:96 nack pli
  531. a=rtpmap:127 H264/90000
  532. a=rtcp-fb:127 goog-remb
  533. a=rtcp-fb:127 transport-cc
  534. a=rtcp-fb:127 ccm fir
  535. a=rtcp-fb:127 nack
  536. a=rtcp-fb:127 nack pli
  537. a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
  538. `
  539. for _, mimeTypeVp8 := range []string{
  540. "video/vp8",
  541. "video/VP8",
  542. } {
  543. t.Run(fmt.Sprintf("MimeType: %s", mimeTypeVp8), func(t *testing.T) {
  544. me := &MediaEngine{}
  545. feedback := []RTCPFeedback{
  546. {Type: TypeRTCPFBTransportCC},
  547. {Type: TypeRTCPFBCCM, Parameter: "fir"},
  548. {Type: TypeRTCPFBNACK},
  549. {Type: TypeRTCPFBNACK, Parameter: "pli"},
  550. }
  551. for _, codec := range []RTPCodecParameters{
  552. {
  553. RTPCodecCapability: RTPCodecCapability{MimeType: mimeTypeVp8, ClockRate: 90000, RTCPFeedback: feedback},
  554. PayloadType: 96,
  555. },
  556. {
  557. RTPCodecCapability: RTPCodecCapability{MimeType: "video/h264", ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", RTCPFeedback: feedback},
  558. PayloadType: 127,
  559. },
  560. } {
  561. assert.NoError(t, me.RegisterCodec(codec, RTPCodecTypeVideo))
  562. }
  563. api := NewAPI(WithMediaEngine(me))
  564. pc, err := api.NewPeerConnection(Configuration{
  565. SDPSemantics: SDPSemanticsUnifiedPlan,
  566. })
  567. assert.NoError(t, err)
  568. offer := SessionDescription{
  569. Type: SDPTypeOffer,
  570. SDP: offerSdp,
  571. }
  572. assert.NoError(t, pc.SetRemoteDescription(offer))
  573. answer, err := pc.CreateAnswer(nil)
  574. assert.NoError(t, err)
  575. assert.NotNil(t, answer)
  576. assert.NoError(t, pc.SetLocalDescription(answer))
  577. assert.True(t, strings.Contains(answer.SDP, "VP8") || strings.Contains(answer.SDP, "vp8"))
  578. assert.NoError(t, pc.Close())
  579. })
  580. }
  581. }