track_local_static_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. "context"
  8. "errors"
  9. "testing"
  10. "time"
  11. "github.com/pion/rtp"
  12. "github.com/pion/transport/v2/test"
  13. "github.com/stretchr/testify/assert"
  14. )
  15. // If a remote doesn't support a Codec used by a `TrackLocalStatic`
  16. // an error should be returned to the user
  17. func Test_TrackLocalStatic_NoCodecIntersection(t *testing.T) {
  18. lim := test.TimeOut(time.Second * 30)
  19. defer lim.Stop()
  20. report := test.CheckRoutines(t)
  21. defer report()
  22. track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
  23. assert.NoError(t, err)
  24. t.Run("Offerer", func(t *testing.T) {
  25. pc, err := NewPeerConnection(Configuration{})
  26. assert.NoError(t, err)
  27. noCodecPC, err := NewAPI().NewPeerConnection(Configuration{})
  28. assert.NoError(t, err)
  29. _, err = pc.AddTrack(track)
  30. assert.NoError(t, err)
  31. assert.ErrorIs(t, signalPair(pc, noCodecPC), ErrUnsupportedCodec)
  32. closePairNow(t, noCodecPC, pc)
  33. })
  34. t.Run("Answerer", func(t *testing.T) {
  35. pc, err := NewPeerConnection(Configuration{})
  36. assert.NoError(t, err)
  37. m := &MediaEngine{}
  38. assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
  39. RTPCodecCapability: RTPCodecCapability{MimeType: "video/VP9", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
  40. PayloadType: 96,
  41. }, RTPCodecTypeVideo))
  42. vp9OnlyPC, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{})
  43. assert.NoError(t, err)
  44. _, err = vp9OnlyPC.AddTransceiverFromKind(RTPCodecTypeVideo)
  45. assert.NoError(t, err)
  46. _, err = pc.AddTrack(track)
  47. assert.NoError(t, err)
  48. assert.True(t, errors.Is(signalPair(vp9OnlyPC, pc), ErrUnsupportedCodec))
  49. closePairNow(t, vp9OnlyPC, pc)
  50. })
  51. t.Run("Local", func(t *testing.T) {
  52. offerer, answerer, err := newPair()
  53. assert.NoError(t, err)
  54. invalidCodecTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: "video/invalid-codec"}, "video", "pion")
  55. assert.NoError(t, err)
  56. _, err = offerer.AddTrack(invalidCodecTrack)
  57. assert.NoError(t, err)
  58. assert.True(t, errors.Is(signalPair(offerer, answerer), ErrUnsupportedCodec))
  59. closePairNow(t, offerer, answerer)
  60. })
  61. }
  62. // Assert that Bind/Unbind happens when expected
  63. func Test_TrackLocalStatic_Closed(t *testing.T) {
  64. lim := test.TimeOut(time.Second * 30)
  65. defer lim.Stop()
  66. report := test.CheckRoutines(t)
  67. defer report()
  68. pcOffer, pcAnswer, err := newPair()
  69. assert.NoError(t, err)
  70. _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
  71. assert.NoError(t, err)
  72. vp8Writer, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
  73. assert.NoError(t, err)
  74. _, err = pcOffer.AddTrack(vp8Writer)
  75. assert.NoError(t, err)
  76. assert.Equal(t, len(vp8Writer.bindings), 0, "No binding should exist before signaling")
  77. assert.NoError(t, signalPair(pcOffer, pcAnswer))
  78. assert.Equal(t, len(vp8Writer.bindings), 1, "binding should exist after signaling")
  79. closePairNow(t, pcOffer, pcAnswer)
  80. assert.Equal(t, len(vp8Writer.bindings), 0, "No binding should exist after close")
  81. }
  82. func Test_TrackLocalStatic_PayloadType(t *testing.T) {
  83. lim := test.TimeOut(time.Second * 30)
  84. defer lim.Stop()
  85. report := test.CheckRoutines(t)
  86. defer report()
  87. mediaEngineOne := &MediaEngine{}
  88. assert.NoError(t, mediaEngineOne.RegisterCodec(RTPCodecParameters{
  89. RTPCodecCapability: RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
  90. PayloadType: 100,
  91. }, RTPCodecTypeVideo))
  92. mediaEngineTwo := &MediaEngine{}
  93. assert.NoError(t, mediaEngineTwo.RegisterCodec(RTPCodecParameters{
  94. RTPCodecCapability: RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
  95. PayloadType: 200,
  96. }, RTPCodecTypeVideo))
  97. offerer, err := NewAPI(WithMediaEngine(mediaEngineOne)).NewPeerConnection(Configuration{})
  98. assert.NoError(t, err)
  99. answerer, err := NewAPI(WithMediaEngine(mediaEngineTwo)).NewPeerConnection(Configuration{})
  100. assert.NoError(t, err)
  101. track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
  102. assert.NoError(t, err)
  103. _, err = offerer.AddTransceiverFromKind(RTPCodecTypeVideo)
  104. assert.NoError(t, err)
  105. _, err = answerer.AddTrack(track)
  106. assert.NoError(t, err)
  107. onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
  108. offerer.OnTrack(func(track *TrackRemote, r *RTPReceiver) {
  109. assert.Equal(t, track.PayloadType(), PayloadType(100))
  110. assert.Equal(t, track.Codec().RTPCodecCapability.MimeType, "video/VP8")
  111. onTrackFiredFunc()
  112. })
  113. assert.NoError(t, signalPair(offerer, answerer))
  114. sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{track})
  115. closePairNow(t, offerer, answerer)
  116. }
  117. // Assert that writing to a Track doesn't modify the input
  118. // Even though we can pass a pointer we shouldn't modify the incoming value
  119. func Test_TrackLocalStatic_Mutate_Input(t *testing.T) {
  120. lim := test.TimeOut(time.Second * 30)
  121. defer lim.Stop()
  122. report := test.CheckRoutines(t)
  123. defer report()
  124. pcOffer, pcAnswer, err := newPair()
  125. assert.NoError(t, err)
  126. vp8Writer, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
  127. assert.NoError(t, err)
  128. _, err = pcOffer.AddTrack(vp8Writer)
  129. assert.NoError(t, err)
  130. assert.NoError(t, signalPair(pcOffer, pcAnswer))
  131. pkt := &rtp.Packet{Header: rtp.Header{SSRC: 1, PayloadType: 1}}
  132. assert.NoError(t, vp8Writer.WriteRTP(pkt))
  133. assert.Equal(t, pkt.Header.SSRC, uint32(1))
  134. assert.Equal(t, pkt.Header.PayloadType, uint8(1))
  135. closePairNow(t, pcOffer, pcAnswer)
  136. }
  137. // Assert that writing to a Track that has Binded (but not connected)
  138. // does not block
  139. func Test_TrackLocalStatic_Binding_NonBlocking(t *testing.T) {
  140. lim := test.TimeOut(time.Second * 5)
  141. defer lim.Stop()
  142. report := test.CheckRoutines(t)
  143. defer report()
  144. pcOffer, pcAnswer, err := newPair()
  145. assert.NoError(t, err)
  146. _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo)
  147. assert.NoError(t, err)
  148. vp8Writer, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
  149. assert.NoError(t, err)
  150. _, err = pcAnswer.AddTrack(vp8Writer)
  151. assert.NoError(t, err)
  152. offer, err := pcOffer.CreateOffer(nil)
  153. assert.NoError(t, err)
  154. assert.NoError(t, pcAnswer.SetRemoteDescription(offer))
  155. answer, err := pcAnswer.CreateAnswer(nil)
  156. assert.NoError(t, err)
  157. assert.NoError(t, pcAnswer.SetLocalDescription(answer))
  158. _, err = vp8Writer.Write(make([]byte, 20))
  159. assert.NoError(t, err)
  160. closePairNow(t, pcOffer, pcAnswer)
  161. }
  162. func BenchmarkTrackLocalWrite(b *testing.B) {
  163. offerPC, answerPC, err := newPair()
  164. defer closePairNow(b, offerPC, answerPC)
  165. if err != nil {
  166. b.Fatalf("Failed to create a PC pair for testing")
  167. }
  168. track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
  169. assert.NoError(b, err)
  170. _, err = offerPC.AddTrack(track)
  171. assert.NoError(b, err)
  172. _, err = answerPC.AddTransceiverFromKind(RTPCodecTypeVideo)
  173. assert.NoError(b, err)
  174. b.SetBytes(1024)
  175. buf := make([]byte, 1024)
  176. for i := 0; i < b.N; i++ {
  177. _, err := track.Write(buf)
  178. assert.NoError(b, err)
  179. }
  180. }
  181. func Test_TrackLocalStatic_Padding(t *testing.T) {
  182. mediaEngineOne := &MediaEngine{}
  183. assert.NoError(t, mediaEngineOne.RegisterCodec(RTPCodecParameters{
  184. RTPCodecCapability: RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
  185. PayloadType: 100,
  186. }, RTPCodecTypeVideo))
  187. mediaEngineTwo := &MediaEngine{}
  188. assert.NoError(t, mediaEngineTwo.RegisterCodec(RTPCodecParameters{
  189. RTPCodecCapability: RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
  190. PayloadType: 200,
  191. }, RTPCodecTypeVideo))
  192. offerer, err := NewAPI(WithMediaEngine(mediaEngineOne)).NewPeerConnection(Configuration{})
  193. assert.NoError(t, err)
  194. answerer, err := NewAPI(WithMediaEngine(mediaEngineTwo)).NewPeerConnection(Configuration{})
  195. assert.NoError(t, err)
  196. track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
  197. assert.NoError(t, err)
  198. _, err = offerer.AddTransceiverFromKind(RTPCodecTypeVideo)
  199. assert.NoError(t, err)
  200. _, err = answerer.AddTrack(track)
  201. assert.NoError(t, err)
  202. onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
  203. offerer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) {
  204. assert.Equal(t, track.PayloadType(), PayloadType(100))
  205. assert.Equal(t, track.Codec().RTPCodecCapability.MimeType, "video/VP8")
  206. for i := 0; i < 20; i++ {
  207. // Padding payload
  208. p, _, e := track.ReadRTP()
  209. assert.NoError(t, e)
  210. assert.True(t, p.Padding)
  211. assert.Equal(t, p.PaddingSize, byte(255))
  212. }
  213. onTrackFiredFunc()
  214. })
  215. assert.NoError(t, signalPair(offerer, answerer))
  216. exit := false
  217. for !exit {
  218. select {
  219. case <-time.After(1 * time.Millisecond):
  220. assert.NoError(t, track.GeneratePadding(1))
  221. case <-onTrackFired.Done():
  222. exit = true
  223. }
  224. }
  225. closePairNow(t, offerer, answerer)
  226. }