You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lal/pkg/rtsp/base_out_session.go

240 lines
7.5 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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
}