track_local_static.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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. "strings"
  8. "sync"
  9. "github.com/pion/rtp"
  10. "github.com/pion/webrtc/v3/internal/util"
  11. "github.com/pion/webrtc/v3/pkg/media"
  12. )
  13. // trackBinding is a single bind for a Track
  14. // Bind can be called multiple times, this stores the
  15. // result for a single bind call so that it can be used when writing
  16. type trackBinding struct {
  17. id string
  18. ssrc SSRC
  19. payloadType PayloadType
  20. writeStream TrackLocalWriter
  21. }
  22. // TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets.
  23. // If you wish to send a media.Sample use TrackLocalStaticSample
  24. type TrackLocalStaticRTP struct {
  25. mu sync.RWMutex
  26. bindings []trackBinding
  27. codec RTPCodecCapability
  28. id, rid, streamID string
  29. }
  30. // NewTrackLocalStaticRTP returns a TrackLocalStaticRTP.
  31. func NewTrackLocalStaticRTP(c RTPCodecCapability, id, streamID string, options ...func(*TrackLocalStaticRTP)) (*TrackLocalStaticRTP, error) {
  32. t := &TrackLocalStaticRTP{
  33. codec: c,
  34. bindings: []trackBinding{},
  35. id: id,
  36. streamID: streamID,
  37. }
  38. for _, option := range options {
  39. option(t)
  40. }
  41. return t, nil
  42. }
  43. // WithRTPStreamID sets the RTP stream ID for this TrackLocalStaticRTP.
  44. func WithRTPStreamID(rid string) func(*TrackLocalStaticRTP) {
  45. return func(t *TrackLocalStaticRTP) {
  46. t.rid = rid
  47. }
  48. }
  49. // Bind is called by the PeerConnection after negotiation is complete
  50. // This asserts that the code requested is supported by the remote peer.
  51. // If so it setups all the state (SSRC and PayloadType) to have a call
  52. func (s *TrackLocalStaticRTP) Bind(t TrackLocalContext) (RTPCodecParameters, error) {
  53. s.mu.Lock()
  54. defer s.mu.Unlock()
  55. parameters := RTPCodecParameters{RTPCodecCapability: s.codec}
  56. if codec, matchType := codecParametersFuzzySearch(parameters, t.CodecParameters()); matchType != codecMatchNone {
  57. s.bindings = append(s.bindings, trackBinding{
  58. ssrc: t.SSRC(),
  59. payloadType: codec.PayloadType,
  60. writeStream: t.WriteStream(),
  61. id: t.ID(),
  62. })
  63. return codec, nil
  64. }
  65. return RTPCodecParameters{}, ErrUnsupportedCodec
  66. }
  67. // Unbind implements the teardown logic when the track is no longer needed. This happens
  68. // because a track has been stopped.
  69. func (s *TrackLocalStaticRTP) Unbind(t TrackLocalContext) error {
  70. s.mu.Lock()
  71. defer s.mu.Unlock()
  72. for i := range s.bindings {
  73. if s.bindings[i].id == t.ID() {
  74. s.bindings[i] = s.bindings[len(s.bindings)-1]
  75. s.bindings = s.bindings[:len(s.bindings)-1]
  76. return nil
  77. }
  78. }
  79. return ErrUnbindFailed
  80. }
  81. // ID is the unique identifier for this Track. This should be unique for the
  82. // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
  83. // and StreamID would be 'desktop' or 'webcam'
  84. func (s *TrackLocalStaticRTP) ID() string { return s.id }
  85. // StreamID is the group this track belongs too. This must be unique
  86. func (s *TrackLocalStaticRTP) StreamID() string { return s.streamID }
  87. // RID is the RTP stream identifier.
  88. func (s *TrackLocalStaticRTP) RID() string { return s.rid }
  89. // Kind controls if this TrackLocal is audio or video
  90. func (s *TrackLocalStaticRTP) Kind() RTPCodecType {
  91. switch {
  92. case strings.HasPrefix(s.codec.MimeType, "audio/"):
  93. return RTPCodecTypeAudio
  94. case strings.HasPrefix(s.codec.MimeType, "video/"):
  95. return RTPCodecTypeVideo
  96. default:
  97. return RTPCodecType(0)
  98. }
  99. }
  100. // Codec gets the Codec of the track
  101. func (s *TrackLocalStaticRTP) Codec() RTPCodecCapability {
  102. return s.codec
  103. }
  104. // packetPool is a pool of packets used by WriteRTP and Write below
  105. // nolint:gochecknoglobals
  106. var rtpPacketPool = sync.Pool{
  107. New: func() interface{} {
  108. return &rtp.Packet{}
  109. },
  110. }
  111. func resetPacketPoolAllocation(localPacket *rtp.Packet) {
  112. *localPacket = rtp.Packet{}
  113. rtpPacketPool.Put(localPacket)
  114. }
  115. func getPacketAllocationFromPool() *rtp.Packet {
  116. ipacket := rtpPacketPool.Get()
  117. return ipacket.(*rtp.Packet) //nolint:forcetypeassert
  118. }
  119. // WriteRTP writes a RTP Packet to the TrackLocalStaticRTP
  120. // If one PeerConnection fails the packets will still be sent to
  121. // all PeerConnections. The error message will contain the ID of the failed
  122. // PeerConnections so you can remove them
  123. func (s *TrackLocalStaticRTP) WriteRTP(p *rtp.Packet) error {
  124. packet := getPacketAllocationFromPool()
  125. defer resetPacketPoolAllocation(packet)
  126. *packet = *p
  127. return s.writeRTP(packet)
  128. }
  129. // writeRTP is like WriteRTP, except that it may modify the packet p
  130. func (s *TrackLocalStaticRTP) writeRTP(p *rtp.Packet) error {
  131. s.mu.RLock()
  132. defer s.mu.RUnlock()
  133. writeErrs := []error{}
  134. for _, b := range s.bindings {
  135. p.Header.SSRC = uint32(b.ssrc)
  136. p.Header.PayloadType = uint8(b.payloadType)
  137. if _, err := b.writeStream.WriteRTP(&p.Header, p.Payload); err != nil {
  138. writeErrs = append(writeErrs, err)
  139. }
  140. }
  141. return util.FlattenErrs(writeErrs)
  142. }
  143. // Write writes a RTP Packet as a buffer to the TrackLocalStaticRTP
  144. // If one PeerConnection fails the packets will still be sent to
  145. // all PeerConnections. The error message will contain the ID of the failed
  146. // PeerConnections so you can remove them
  147. func (s *TrackLocalStaticRTP) Write(b []byte) (n int, err error) {
  148. packet := getPacketAllocationFromPool()
  149. defer resetPacketPoolAllocation(packet)
  150. if err = packet.Unmarshal(b); err != nil {
  151. return 0, err
  152. }
  153. return len(b), s.writeRTP(packet)
  154. }
  155. // TrackLocalStaticSample is a TrackLocal that has a pre-set codec and accepts Samples.
  156. // If you wish to send a RTP Packet use TrackLocalStaticRTP
  157. type TrackLocalStaticSample struct {
  158. packetizer rtp.Packetizer
  159. sequencer rtp.Sequencer
  160. rtpTrack *TrackLocalStaticRTP
  161. clockRate float64
  162. }
  163. // NewTrackLocalStaticSample returns a TrackLocalStaticSample
  164. func NewTrackLocalStaticSample(c RTPCodecCapability, id, streamID string, options ...func(*TrackLocalStaticRTP)) (*TrackLocalStaticSample, error) {
  165. rtpTrack, err := NewTrackLocalStaticRTP(c, id, streamID, options...)
  166. if err != nil {
  167. return nil, err
  168. }
  169. return &TrackLocalStaticSample{
  170. rtpTrack: rtpTrack,
  171. }, nil
  172. }
  173. // ID is the unique identifier for this Track. This should be unique for the
  174. // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video'
  175. // and StreamID would be 'desktop' or 'webcam'
  176. func (s *TrackLocalStaticSample) ID() string { return s.rtpTrack.ID() }
  177. // StreamID is the group this track belongs too. This must be unique
  178. func (s *TrackLocalStaticSample) StreamID() string { return s.rtpTrack.StreamID() }
  179. // RID is the RTP stream identifier.
  180. func (s *TrackLocalStaticSample) RID() string { return s.rtpTrack.RID() }
  181. // Kind controls if this TrackLocal is audio or video
  182. func (s *TrackLocalStaticSample) Kind() RTPCodecType { return s.rtpTrack.Kind() }
  183. // Codec gets the Codec of the track
  184. func (s *TrackLocalStaticSample) Codec() RTPCodecCapability {
  185. return s.rtpTrack.Codec()
  186. }
  187. // Bind is called by the PeerConnection after negotiation is complete
  188. // This asserts that the code requested is supported by the remote peer.
  189. // If so it setups all the state (SSRC and PayloadType) to have a call
  190. func (s *TrackLocalStaticSample) Bind(t TrackLocalContext) (RTPCodecParameters, error) {
  191. codec, err := s.rtpTrack.Bind(t)
  192. if err != nil {
  193. return codec, err
  194. }
  195. s.rtpTrack.mu.Lock()
  196. defer s.rtpTrack.mu.Unlock()
  197. // We only need one packetizer
  198. if s.packetizer != nil {
  199. return codec, nil
  200. }
  201. payloader, err := payloaderForCodec(codec.RTPCodecCapability)
  202. if err != nil {
  203. return codec, err
  204. }
  205. s.sequencer = rtp.NewRandomSequencer()
  206. s.packetizer = rtp.NewPacketizer(
  207. rtpOutboundMTU,
  208. 0, // Value is handled when writing
  209. 0, // Value is handled when writing
  210. payloader,
  211. s.sequencer,
  212. codec.ClockRate,
  213. )
  214. s.clockRate = float64(codec.RTPCodecCapability.ClockRate)
  215. return codec, nil
  216. }
  217. // Unbind implements the teardown logic when the track is no longer needed. This happens
  218. // because a track has been stopped.
  219. func (s *TrackLocalStaticSample) Unbind(t TrackLocalContext) error {
  220. return s.rtpTrack.Unbind(t)
  221. }
  222. // WriteSample writes a Sample to the TrackLocalStaticSample
  223. // If one PeerConnection fails the packets will still be sent to
  224. // all PeerConnections. The error message will contain the ID of the failed
  225. // PeerConnections so you can remove them
  226. func (s *TrackLocalStaticSample) WriteSample(sample media.Sample) error {
  227. s.rtpTrack.mu.RLock()
  228. p := s.packetizer
  229. clockRate := s.clockRate
  230. s.rtpTrack.mu.RUnlock()
  231. if p == nil {
  232. return nil
  233. }
  234. // skip packets by the number of previously dropped packets
  235. for i := uint16(0); i < sample.PrevDroppedPackets; i++ {
  236. s.sequencer.NextSequenceNumber()
  237. }
  238. samples := uint32(sample.Duration.Seconds() * clockRate)
  239. if sample.PrevDroppedPackets > 0 {
  240. p.SkipSamples(samples * uint32(sample.PrevDroppedPackets))
  241. }
  242. packets := p.Packetize(sample.Data, samples)
  243. writeErrs := []error{}
  244. for _, p := range packets {
  245. if err := s.rtpTrack.WriteRTP(p); err != nil {
  246. writeErrs = append(writeErrs, err)
  247. }
  248. }
  249. return util.FlattenErrs(writeErrs)
  250. }
  251. // GeneratePadding writes padding-only samples to the TrackLocalStaticSample
  252. // If one PeerConnection fails the packets will still be sent to
  253. // all PeerConnections. The error message will contain the ID of the failed
  254. // PeerConnections so you can remove them
  255. func (s *TrackLocalStaticSample) GeneratePadding(samples uint32) error {
  256. s.rtpTrack.mu.RLock()
  257. p := s.packetizer
  258. s.rtpTrack.mu.RUnlock()
  259. if p == nil {
  260. return nil
  261. }
  262. packets := p.GeneratePadding(samples)
  263. writeErrs := []error{}
  264. for _, p := range packets {
  265. if err := s.rtpTrack.WriteRTP(p); err != nil {
  266. writeErrs = append(writeErrs, err)
  267. }
  268. }
  269. return util.FlattenErrs(writeErrs)
  270. }