// Copyright 2020, Chef. All rights reserved. // https://github.com/q191201771/lal // // Use of this source code is governed by a MIT-style license // that can be found in the License file. // // Author: Chef (191201771@qq.com) package rtsp import ( "net" "strings" "sync/atomic" "time" "github.com/q191201771/naza/pkg/connection" "github.com/q191201771/lal/pkg/rtprtcp" "github.com/q191201771/lal/pkg/sdp" "github.com/q191201771/lal/pkg/base" "github.com/q191201771/naza/pkg/nazalog" "github.com/q191201771/naza/pkg/nazanet" ) // TODO chef: 主动发送SR type SubSession struct { UniqueKey string // const after ctor cmdSession *ServerCommandSession urlCtx base.URLContext //StreamName string // const after ctor rawSDP []byte // const after set sdpLogicCtx sdp.LogicContext // const after set audioRTPConn *nazanet.UDPConnection videoRTPConn *nazanet.UDPConnection audioRTCPConn *nazanet.UDPConnection videoRTCPConn *nazanet.UDPConnection audioRTPChannel int audioRTCPChannel int videoRTPChannel int videoRTCPChannel int stat base.StatSession currConnStat connection.Stat prevConnStat connection.Stat staleStat *connection.Stat } func NewSubSession(urlCtx base.URLContext, cmdSession *ServerCommandSession) *SubSession { uk := base.GenUniqueKey(base.UKPRTSPSubSession) ss := &SubSession{ UniqueKey: uk, urlCtx: urlCtx, cmdSession: cmdSession, stat: base.StatSession{ Protocol: base.ProtocolRTSP, SessionID: uk, StartTime: time.Now().Format("2006-01-02 15:04:05.999"), RemoteAddr: cmdSession.conn.RemoteAddr().String(), }, audioRTPChannel: -1, videoRTPChannel: -1, } nazalog.Infof("[%s] lifecycle new rtsp SubSession. session=%p, streamName=%s", uk, ss, urlCtx.LastItemOfPath) return ss } func (s *SubSession) InitWithSDP(rawSDP []byte, sdpLogicCtx sdp.LogicContext) { s.rawSDP = rawSDP s.sdpLogicCtx = sdpLogicCtx } func (s *SubSession) SetupWithConn(uri string, rtpConn, rtcpConn *nazanet.UDPConnection) error { if strings.HasSuffix(uri, s.sdpLogicCtx.AudioAControl) { s.audioRTPConn = rtpConn s.audioRTCPConn = rtcpConn } else if strings.HasSuffix(uri, s.sdpLogicCtx.VideoAControl) { s.videoRTPConn = rtpConn s.videoRTCPConn = rtcpConn } else { return ErrRTSP } go rtpConn.RunLoop(s.onReadUDPPacket) go rtcpConn.RunLoop(s.onReadUDPPacket) return nil } func (s *SubSession) SetupWithChannel(uri string, rtpChannel, rtcpChannel int, remoteAddr string) error { s.stat.RemoteAddr = remoteAddr if strings.HasSuffix(uri, s.sdpLogicCtx.AudioAControl) { s.audioRTPChannel = rtpChannel s.audioRTCPChannel = rtcpChannel return nil } if strings.HasSuffix(uri, s.sdpLogicCtx.VideoAControl) { s.videoRTPChannel = rtpChannel s.videoRTCPChannel = rtcpChannel return nil } return ErrRTSP } func (s *SubSession) Dispose() { nazalog.Infof("[%s] lifecycle dispose rtsp SubSession.", s.UniqueKey) if s.audioRTPConn != nil { _ = s.audioRTPConn.Dispose() } if s.audioRTCPConn != nil { _ = s.audioRTCPConn.Dispose() } if s.videoRTPConn != nil { _ = s.videoRTPConn.Dispose() } if s.videoRTCPConn != nil { _ = s.videoRTCPConn.Dispose() } _ = s.cmdSession.Dispose() } func (s *SubSession) GetStat() base.StatSession { s.stat.ReadBytesSum = atomic.LoadUint64(&s.currConnStat.ReadBytesSum) s.stat.WroteBytesSum = atomic.LoadUint64(&s.currConnStat.WroteBytesSum) return s.stat } func (s *SubSession) UpdateStat(interval uint32) { readBytesSum := atomic.LoadUint64(&s.currConnStat.ReadBytesSum) wroteBytesSum := atomic.LoadUint64(&s.currConnStat.WroteBytesSum) rDiff := readBytesSum - s.prevConnStat.ReadBytesSum s.stat.ReadBitrate = int(rDiff * 8 / 1024 / uint64(interval)) wDiff := wroteBytesSum - s.prevConnStat.WroteBytesSum s.stat.WriteBitrate = int(wDiff * 8 / 1024 / uint64(interval)) s.stat.Bitrate = s.stat.WriteBitrate s.prevConnStat.ReadBytesSum = readBytesSum s.prevConnStat.WroteBytesSum = wroteBytesSum } func (s *SubSession) IsAlive() (readAlive, writeAlive bool) { readBytesSum := atomic.LoadUint64(&s.currConnStat.ReadBytesSum) wroteBytesSum := atomic.LoadUint64(&s.currConnStat.WroteBytesSum) if s.staleStat == nil { s.staleStat = new(connection.Stat) s.staleStat.ReadBytesSum = readBytesSum s.staleStat.WroteBytesSum = wroteBytesSum return true, true } readAlive = !(readBytesSum-s.staleStat.ReadBytesSum == 0) writeAlive = !(wroteBytesSum-s.staleStat.WroteBytesSum == 0) s.staleStat.ReadBytesSum = readBytesSum s.staleStat.WroteBytesSum = wroteBytesSum return } func (s *SubSession) AppName() string { return s.urlCtx.PathWithoutLastItem } func (s *SubSession) StreamName() string { return s.urlCtx.LastItemOfPath } func (s *SubSession) RawQuery() string { return s.urlCtx.RawQuery } func (s *SubSession) WriteRTPPacket(packet rtprtcp.RTPPacket) { atomic.AddUint64(&s.currConnStat.WroteBytesSum, uint64(len(packet.Raw))) switch packet.Header.PacketType { case base.RTPPacketTypeAVCOrHEVC: if s.videoRTPConn != nil { _ = s.videoRTPConn.Write(packet.Raw) } if s.videoRTPChannel != -1 { _ = s.cmdSession.Write(s.videoRTPChannel, packet.Raw) } case base.RTPPacketTypeAAC: if s.audioRTPConn != nil { _ = s.audioRTPConn.Write(packet.Raw) } if s.audioRTPChannel != -1 { _ = s.cmdSession.Write(s.audioRTPChannel, packet.Raw) } default: nazalog.Errorf("[%s] write rtp packet but type invalid. type=%d", s.UniqueKey, packet.Header.PacketType) } } func (s *SubSession) onReadUDPPacket(b []byte, rAddr *net.UDPAddr, err error) bool { // TODO chef: impl me //nazalog.Errorf("[%s] SubSession::onReadUDPPacket. %s", s.UniqueKey, hex.Dump(b)) return true }