// Copyright 2021, 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 ( "encoding/hex" "net" "sync" "github.com/q191201771/naza/pkg/nazaatomic" "github.com/q191201771/lal/pkg/rtprtcp" "github.com/q191201771/naza/pkg/nazabytes" "github.com/q191201771/naza/pkg/nazaerrors" "github.com/q191201771/lal/pkg/base" "github.com/q191201771/lal/pkg/sdp" "github.com/q191201771/naza/pkg/nazanet" ) // BaseOutSession out的含义是音视频由本端发送至对端 type BaseOutSession struct { cmdSession IInterleavedPacketWriter sdpCtx sdp.LogicContext audioRtpConn *nazanet.UdpConnection videoRtpConn *nazanet.UdpConnection audioRtcpConn *nazanet.UdpConnection videoRtcpConn *nazanet.UdpConnection audioRtpChannel int audioRtcpChannel int videoRtpChannel int videoRtcpChannel int sessionStat base.BasicSessionStat // only for debug log debugLogMaxCount int loggedWriteAudioRtpCount int loggedWriteVideoRtpCount int loggedReadRtpCount nazaatomic.Int32 // 因为音频和视频是两个连接,所以需要原子操作 loggedReadRtcpCount nazaatomic.Int32 disposeOnce sync.Once waitChan chan error } func NewBaseOutSession(sessionType base.SessionType, cmdSession IInterleavedPacketWriter) *BaseOutSession { s := &BaseOutSession{ cmdSession: cmdSession, sessionStat: base.NewBasicSessionStat(sessionType, ""), audioRtpChannel: -1, videoRtpChannel: -1, debugLogMaxCount: 3, waitChan: make(chan error, 1), } Log.Infof("[%s] lifecycle new rtsp BaseOutSession. session=%p", s.UniqueKey(), s) return s } func (session *BaseOutSession) InitWithSdp(sdpCtx sdp.LogicContext) { session.sdpCtx = sdpCtx } func (session *BaseOutSession) SetupWithConn(uri string, rtpConn, rtcpConn *nazanet.UdpConnection) error { if session.sdpCtx.IsAudioUri(uri) { session.audioRtpConn = rtpConn session.audioRtcpConn = rtcpConn } else if session.sdpCtx.IsVideoUri(uri) { session.videoRtpConn = rtpConn session.videoRtcpConn = rtcpConn } else { return nazaerrors.Wrap(base.ErrRtsp) } go rtpConn.RunLoop(session.onReadRtpPacket) go rtcpConn.RunLoop(session.onReadRtcpPacket) return nil } func (session *BaseOutSession) SetupWithChannel(uri string, rtpChannel, rtcpChannel int) error { if session.sdpCtx.IsAudioUri(uri) { session.audioRtpChannel = rtpChannel session.audioRtcpChannel = rtcpChannel return nil } else if session.sdpCtx.IsVideoUri(uri) { session.videoRtpChannel = rtpChannel session.videoRtcpChannel = rtcpChannel return nil } return nazaerrors.Wrap(base.ErrRtsp) } // --------------------------------------------------------------------------------------------------------------------- // IClientSessionLifecycle interface // --------------------------------------------------------------------------------------------------------------------- // Dispose 文档请参考: IClientSessionLifecycle interface func (session *BaseOutSession) Dispose() error { return session.dispose(nil) } // WaitChan 文档请参考: IClientSessionLifecycle interface // // 注意,目前只有一种情况,即上层主动调用Dispose函数,此时error为nil func (session *BaseOutSession) WaitChan() <-chan error { return session.waitChan } // --------------------------------------------------------------------------------------------------------------------- func (session *BaseOutSession) HandleInterleavedPacket(b []byte, channel int) { switch channel { case session.audioRtpChannel: fallthrough case session.videoRtpChannel: Log.Warnf("[%s] not supposed to read packet in rtp channel of BaseOutSession. channel=%d, len=%d", session.UniqueKey(), channel, len(b)) case session.audioRtcpChannel: fallthrough case session.videoRtcpChannel: Log.Debugf("[%s] read interleaved rtcp packet. b=%s", session.UniqueKey(), hex.Dump(nazabytes.Prefix(b, 32))) default: Log.Errorf("[%s] read interleaved packet but channel invalid. channel=%d", session.UniqueKey(), channel) } } func (session *BaseOutSession) WriteRtpPacket(packet rtprtcp.RtpPacket) error { var err error // 发送数据时,保证和sdp的原始类型对应 t := int(packet.Header.PacketType) if session.sdpCtx.IsAudioPayloadTypeOrigin(t) { if session.loggedWriteAudioRtpCount < session.debugLogMaxCount { Log.Debugf("[%s] LOGPACKET. write audio rtp=%+v", session.UniqueKey(), packet.Header) session.loggedWriteAudioRtpCount++ } if session.audioRtpConn != nil { err = session.audioRtpConn.Write(packet.Raw) } if session.audioRtpChannel != -1 { err = session.cmdSession.WriteInterleavedPacket(packet.Raw, session.audioRtpChannel) } } else if session.sdpCtx.IsVideoPayloadTypeOrigin(t) { if session.loggedWriteVideoRtpCount < session.debugLogMaxCount { Log.Debugf("[%s] LOGPACKET. write video rtp=%+v", session.UniqueKey(), packet.Header) session.loggedWriteVideoRtpCount++ } if session.videoRtpConn != nil { err = session.videoRtpConn.Write(packet.Raw) } if session.videoRtpChannel != -1 { err = session.cmdSession.WriteInterleavedPacket(packet.Raw, session.videoRtpChannel) } } else { Log.Errorf("[%s] write rtp packet but type invalid. type=%d", session.UniqueKey(), t) err = nazaerrors.Wrap(base.ErrRtsp) } if err == nil { session.sessionStat.AddWriteBytes(len(packet.Raw)) } return err } // ----- ISessionStat -------------------------------------------------------------------------------------------------- func (session *BaseOutSession) GetStat() base.StatSession { return session.sessionStat.GetStat() } func (session *BaseOutSession) UpdateStat(intervalSec uint32) { session.sessionStat.UpdateStat(intervalSec) } func (session *BaseOutSession) IsAlive() (readAlive, writeAlive bool) { return session.sessionStat.IsAlive() } // --------------------------------------------------------------------------------------------------------------------- func (session *BaseOutSession) UniqueKey() string { return session.sessionStat.UniqueKey() } func (session *BaseOutSession) onReadRtpPacket(b []byte, rAddr *net.UDPAddr, err error) bool { // TODO(chef): [fix] 在收到rtp和rtcp的地方,加入stat统计 202205 if session.loggedReadRtpCount.Load() < int32(session.debugLogMaxCount) { Log.Debugf("[%s] LOGPACKET. read rtp=%s", session.UniqueKey(), hex.Dump(nazabytes.Prefix(b, 32))) session.loggedReadRtpCount.Increment() } return true } func (session *BaseOutSession) onReadRtcpPacket(b []byte, rAddr *net.UDPAddr, err error) bool { // TODO chef: impl me if session.loggedReadRtcpCount.Load() < int32(session.debugLogMaxCount) { Log.Debugf("[%s] LOGPACKET. read rtcp=%s", session.UniqueKey(), hex.Dump(nazabytes.Prefix(b, 32))) session.loggedReadRtcpCount.Increment() } return true } func (session *BaseOutSession) dispose(err error) error { var retErr error session.disposeOnce.Do(func() { Log.Infof("[%s] lifecycle dispose rtsp BaseOutSession. session=%p", session.UniqueKey(), session) var e1, e2, e3, e4 error if session.audioRtpConn != nil { e1 = session.audioRtpConn.Dispose() } if session.audioRtcpConn != nil { e2 = session.audioRtcpConn.Dispose() } if session.videoRtpConn != nil { e3 = session.videoRtpConn.Dispose() } if session.videoRtcpConn != nil { e4 = session.videoRtcpConn.Dispose() } session.waitChan <- nil retErr = nazaerrors.CombineErrors(e1, e2, e3, e4) }) return retErr }