github.com/cnotch/ipchub@v1.1.0/media/stream.go (about) 1 // Copyright (c) 2019,CAOHONGJU All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 package media 6 7 import ( 8 "errors" 9 "strings" 10 "sync/atomic" 11 "time" 12 13 "github.com/cnotch/ipchub/av/codec" 14 "github.com/cnotch/ipchub/av/format/flv" 15 "github.com/cnotch/ipchub/av/format/hls" 16 "github.com/cnotch/ipchub/av/format/mpegts" 17 "github.com/cnotch/ipchub/av/format/rtp" 18 "github.com/cnotch/ipchub/av/format/sdp" 19 "github.com/cnotch/ipchub/config" 20 "github.com/cnotch/ipchub/media/cache" 21 "github.com/cnotch/ipchub/stats" 22 "github.com/cnotch/ipchub/utils" 23 "github.com/cnotch/queue" 24 "github.com/cnotch/xlog" 25 ) 26 27 // 流状态 28 const ( 29 StreamOK int32 = iota 30 StreamClosed // 源关闭 31 StreamReplaced // 流被替换 32 StreamNoConsumer 33 ) 34 35 // 错误定义 36 var ( 37 // ErrStreamClosed 流被关闭 38 ErrStreamClosed = errors.New("stream is closed") 39 // ErrStreamReplaced 流被替换 40 ErrStreamReplaced = errors.New("stream is replaced") 41 statusErrors = []error{nil, ErrStreamClosed, ErrStreamReplaced} 42 ) 43 44 // Stream 媒体流 45 type Stream struct { 46 startOn time.Time // 启动时间 47 path string // 流路径 48 rawsdp string 49 size uint64 // 流已经接收到的输入(字节) 50 status int32 // 流状态 51 consumerSequenceSeed uint32 52 consumptions consumptions // 消费者列表 53 cache packCache // 媒体包缓存 54 rtpDemuxer rtpDemuxer 55 flvMuxer flvMuxer 56 flvConsumptions consumptions 57 flvCache packCache 58 tsMuxer *mpegts.Muxer 59 hlsSG *hls.SegmentGenerator 60 hlsPlaylist *hls.Playlist 61 attrs map[string]string // 流属性 62 multicast Multicastable 63 hls Hlsable 64 logger *xlog.Logger // 日志对象 65 Video codec.VideoMeta 66 Audio codec.AudioMeta 67 } 68 69 // NewStream 创建新的流 70 func NewStream(path string, rawsdp string, options ...Option) *Stream { 71 s := &Stream{ 72 startOn: time.Now(), 73 path: utils.CanonicalPath(path), 74 rawsdp: rawsdp, 75 status: StreamOK, 76 consumerSequenceSeed: 0, 77 attrs: make(map[string]string, 2), 78 logger: xlog.L().With(xlog.Fields(xlog.F("path", path))), 79 } 80 81 // parseMeta 82 sdp.ParseMetadata(rawsdp, &s.Video, &s.Audio) 83 84 // init Cache 85 switch s.Video.Codec { 86 case "H264": 87 s.cache = cache.NewH264Cache(config.CacheGop()) 88 case "H265": 89 s.cache = cache.NewHevcCache(config.CacheGop()) 90 default: 91 s.cache = emptyCache{} 92 } 93 94 for _, option := range options { 95 option.apply(s) 96 } 97 98 // prepareOtherStream 99 s.prepareOtherStream() 100 return s 101 } 102 103 func (s *Stream) prepareOtherStream() { 104 // steam(rtp)->rtpdemuxer->stream(frame)->flvmuxer->stream(tag) 105 106 s.flvCache = emptyCache{} 107 s.flvMuxer = emptyFlvMuxer{} 108 109 // prepare rtp.Packet -> codec.Frame 110 var err error 111 if s.rtpDemuxer, err = rtp.NewDemuxer(&s.Video, &s.Audio, 112 s, s.logger.With(xlog.Fields(xlog.F("extra", "rtp2frame")))); err != nil { 113 s.rtpDemuxer = emptyRtpDemuxer{} 114 return 115 } 116 117 // prepare codec.Frame -> flv.Tag 118 var flvMuxer *flv.Muxer 119 if flvMuxer, err = flv.NewMuxer(&s.Video, &s.Audio, 120 s, s.logger.With(xlog.Fields(xlog.F("extra", "frame2flv")))); err == nil { 121 s.flvCache = cache.NewFlvCache(config.CacheGop()) 122 s.flvMuxer = flvMuxer 123 } 124 125 // prepare codec.Frame -> mpegts.Frame 126 if s.Video.Codec == "H264" { 127 hlsPlaylist := hls.NewPlaylist() 128 sg, err := hls.NewSegmentGenerator(hlsPlaylist, s.path, 129 config.HlsFragment(), 130 config.HlsPath(), s.Audio.SampleRate, 131 s.logger.With(xlog.Fields(xlog.F("extra", "hls.Muxer")))) 132 if err != nil { 133 return 134 } 135 tsMuxer, err2 := mpegts.NewMuxer(&s.Video, &s.Audio, sg, 136 s.logger.With(xlog.Fields(xlog.F("extra", "ts.Muxer")))) 137 if err2 != nil { 138 return 139 } 140 s.tsMuxer = tsMuxer 141 s.hlsSG = sg 142 s.hlsPlaylist = hlsPlaylist 143 } 144 } 145 146 // Path 流路径 147 func (s *Stream) Path() string { 148 return s.path 149 } 150 151 // Sdp sdp 字串 152 func (s *Stream) Sdp() string { 153 return s.rawsdp 154 } 155 156 // FlvTypeFlags 支持的 flv TypeFlags 157 func (s *Stream) FlvTypeFlags() byte { 158 return s.flvMuxer.TypeFlags() 159 } 160 161 // Attr 流属性 162 func (s *Stream) Attr(key string) string { 163 return s.attrs[strings.ToLower(strings.TrimSpace(key))] 164 } 165 166 // Close 关闭流 167 func (s *Stream) Close() error { 168 return s.close(StreamClosed) 169 } 170 func (s *Stream) close(status int32) error { 171 if atomic.LoadInt32(&s.status) != StreamOK { 172 return nil 173 } 174 175 // 修改流状态 176 if status != StreamReplaced { 177 status = StreamClosed 178 } 179 atomic.StoreInt32(&s.status, status) 180 181 // 关闭 hls 182 if s.tsMuxer != nil { 183 s.tsMuxer.Close() 184 s.hlsSG.Close() 185 s.hlsPlaylist.Close() 186 } 187 188 // 关闭 flv 消费者和 Muxer 189 s.flvConsumptions.RemoveAndCloseAll() 190 s.flvCache.Reset() 191 s.flvMuxer.Close() 192 193 // 关闭 av.Frame 转换器 194 s.rtpDemuxer.Close() 195 196 s.consumptions.RemoveAndCloseAll() 197 s.cache.Reset() 198 return nil 199 } 200 201 // WriteRtpPacket 向流写入一个媒体包 202 func (s *Stream) WriteRtpPacket(packet *rtp.Packet) error { 203 status := atomic.LoadInt32(&s.status) 204 if status != StreamOK { 205 return statusErrors[status] 206 } 207 208 atomic.AddUint64(&s.size, uint64(packet.Size())) 209 210 keyframe := s.cache.CachePack(packet) 211 s.consumptions.SendToAll(packet, keyframe) 212 213 s.rtpDemuxer.WriteRtpPacket(packet) 214 return nil 215 } 216 217 // WriteFrame . 218 func (s *Stream) WriteFrame(frame *codec.Frame) error { 219 if err := s.flvMuxer.WriteFrame(frame); err != nil { 220 s.logger.Error(err.Error()) 221 } 222 if s.tsMuxer != nil { 223 if err := s.tsMuxer.WriteFrame(frame); err != nil { 224 s.logger.Error(err.Error()) 225 } 226 } 227 return nil 228 } 229 230 // WriteTag . 231 func (s *Stream) WriteFlvTag(tag *flv.Tag) error { 232 status := atomic.LoadInt32(&s.status) 233 if status != StreamOK { 234 return statusErrors[status] 235 } 236 237 keyframe := s.flvCache.CachePack(tag) 238 s.flvConsumptions.SendToAll(tag, keyframe) 239 return nil 240 } 241 242 // Multicastable 返回组播支持能力,不支持返回nil 243 func (s *Stream) Multicastable() Multicastable { 244 return s.multicast 245 } 246 247 // Hlsable 返回支持hls能力,不支持返回nil 248 func (s *Stream) Hlsable() Hlsable { 249 return s.hlsPlaylist 250 } 251 252 func (s *Stream) startConsume(consumer Consumer, packetType PacketType, extra string, useGopCache bool) CID { 253 if packetType == FLVPacket && s.flvMuxer == nil { 254 return CID(0) // 不支持 255 } 256 257 c := &consumption{ 258 startOn: time.Now(), 259 stream: s, 260 cid: NewCID(packetType, &s.consumerSequenceSeed), 261 recvQueue: queue.NewSyncQueue(), 262 consumer: consumer, 263 packetType: packetType, 264 extra: extra, 265 Flow: stats.NewFlow(), 266 maxQLen: 1000, 267 } 268 269 c.logger = s.logger.With(xlog.Fields( 270 xlog.F("cid", uint32(c.cid)), 271 xlog.F("packettype", c.packetType.String()), 272 xlog.F("extra", c.extra))) 273 274 cs := &s.consumptions 275 cache := s.cache 276 if packetType == FLVPacket { 277 cs = &s.flvConsumptions 278 cache = s.flvCache 279 } 280 281 if useGopCache { 282 c.sendGop(cache) // 新消费者,先发送gop缓存 283 } 284 cs.Add(c) 285 286 go c.consume() 287 return c.cid 288 } 289 290 // StartConsume 开始消费 291 func (s *Stream) StartConsume(consumer Consumer, packetType PacketType, extra string) CID { 292 return s.startConsume(consumer, packetType, extra, true) 293 } 294 295 // StartConsumeNoGopCache 开始消费,不使用GopCahce 296 func (s *Stream) StartConsumeNoGopCache(consumer Consumer, packetType PacketType, extra string) CID { 297 return s.startConsume(consumer, packetType, extra, false) 298 } 299 300 // StopConsume 开始消费 301 func (s *Stream) StopConsume(cid CID) { 302 cs := &s.consumptions 303 if cid.Type() == FLVPacket { 304 cs = &s.flvConsumptions 305 } 306 307 c := cs.Remove(cid) 308 if c != nil { 309 c.Close() 310 } 311 } 312 313 // ConsumerCount 流消费者计数 314 func (s *Stream) ConsumerCount() int { 315 return s.consumptions.Count() + s.flvConsumptions.Count() 316 } 317 318 // StreamInfo 流信息 319 type StreamInfo struct { 320 StartOn string `json:"start_on"` 321 Path string `json:"path"` 322 Addr string `json:"addr"` 323 Size int `json:"size"` 324 Video *codec.VideoMeta `json:"video,omitempty"` 325 Audio *codec.AudioMeta `json:"audio,omitempty"` 326 ConsumptionCount int `json:"cc"` 327 Consumptions []ConsumptionInfo `json:"cs,omitempty"` 328 } 329 330 // Info 获取流信息 331 func (s *Stream) Info(includeCS bool) *StreamInfo { 332 si := &StreamInfo{ 333 StartOn: s.startOn.Format(time.RFC3339Nano), 334 Path: s.path, 335 Addr: s.Attr("addr"), 336 Size: int(atomic.LoadUint64(&s.size) / 1024), 337 ConsumptionCount: s.ConsumerCount(), 338 } 339 340 if len(s.Video.Codec) != 0 { 341 si.Video = &s.Video 342 } 343 if len(s.Audio.Codec) != 0 { 344 si.Audio = &s.Audio 345 } 346 if includeCS { 347 si.Consumptions = s.consumptions.Infos() 348 si.Consumptions = append(si.Consumptions, s.flvConsumptions.Infos()...) 349 } 350 return si 351 } 352 353 // GetConsumption 获取指定消费信息 354 func (s *Stream) GetConsumption(cid CID) (ConsumptionInfo, bool) { 355 cs := &s.consumptions 356 if cid.Type() == FLVPacket { 357 cs = &s.flvConsumptions 358 } 359 360 c, ok := cs.Load(cid) 361 if ok { 362 return c.(*consumption).Info(), ok 363 } 364 return ConsumptionInfo{}, false 365 }