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_in_session.go

436 lines
14 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 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 (
"encoding/hex"
"net"
"sync"
"time"
"github.com/q191201771/naza/pkg/nazaatomic"
"github.com/q191201771/naza/pkg/nazaerrors"
"github.com/q191201771/naza/pkg/nazastring"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/lal/pkg/rtprtcp"
"github.com/q191201771/lal/pkg/sdp"
"github.com/q191201771/naza/pkg/connection"
"github.com/q191201771/naza/pkg/nazalog"
"github.com/q191201771/naza/pkg/nazanet"
)
// 聚合PubSession和PullSession也即流数据是输入类型的session
// BaseInSession会向上层回调两种格式的数据(本质上是一份数据,业务方可自由选择使用)
// 1. 原始的rtp packet
// 2. rtp合并后的av packet
//
type BaseInSessionObserver interface {
OnSDP(sdpCtx sdp.LogicContext)
// 回调收到的RTP包
OnRTPPacket(pkt rtprtcp.RTPPacket)
// @param pkt: pkt结构体中字段含义见rtprtcp.OnAVPacket
OnAVPacket(pkt base.AVPacket)
}
type BaseInSession struct {
uniqueKey string // 使用上层Session的值
cmdSession IInterleavedPacketWriter
observer BaseInSessionObserver
audioRTPConn *nazanet.UDPConnection
videoRTPConn *nazanet.UDPConnection
audioRTCPConn *nazanet.UDPConnection
videoRTCPConn *nazanet.UDPConnection
audioRTPChannel int
audioRTCPChannel int
videoRTPChannel int
videoRTCPChannel int
currConnStat connection.StatAtomic
prevConnStat connection.Stat
staleStat *connection.Stat
stat base.StatSession
mu sync.Mutex
rawSDP []byte // const after set
sdpLogicCtx sdp.LogicContext // const after set
avPacketQueue *AVPacketQueue
audioRRProducer *rtprtcp.RRProducer
videoRRProducer *rtprtcp.RRProducer
audioUnpacker rtprtcp.IRTPUnpacker
videoUnpacker rtprtcp.IRTPUnpacker
audioSSRC nazaatomic.Uint32
videoSSRC nazaatomic.Uint32
// only for debug log
debugLogMaxCount uint32
loggedReadAudioRTPCount nazaatomic.Uint32
loggedReadVideoRTPCount nazaatomic.Uint32
loggedReadRTCPCount nazaatomic.Uint32
loggedReadSRCount nazaatomic.Uint32
}
func NewBaseInSession(uniqueKey string, cmdSession IInterleavedPacketWriter) *BaseInSession {
s := &BaseInSession{
uniqueKey: uniqueKey,
stat: base.StatSession{
Protocol: base.ProtocolRTSP,
SessionID: uniqueKey,
StartTime: time.Now().Format("2006-01-02 15:04:05.999"),
},
cmdSession: cmdSession,
debugLogMaxCount: 3,
}
nazalog.Infof("[%s] lifecycle new rtsp BaseInSession. session=%p", uniqueKey, s)
return s
}
func NewBaseInSessionWithObserver(uniqueKey string, cmdSession IInterleavedPacketWriter, observer BaseInSessionObserver) *BaseInSession {
s := NewBaseInSession(uniqueKey, cmdSession)
s.observer = observer
return s
}
func (session *BaseInSession) InitWithSDP(rawSDP []byte, sdpLogicCtx sdp.LogicContext) {
session.mu.Lock()
session.rawSDP = rawSDP
session.sdpLogicCtx = sdpLogicCtx
session.mu.Unlock()
if session.sdpLogicCtx.IsAudioUnpackable() {
session.audioUnpacker = rtprtcp.DefaultRTPUnpackerFactory(session.sdpLogicCtx.GetAudioPayloadTypeBase(), session.sdpLogicCtx.AudioClockRate, unpackerItemMaxSize, session.onAVPacketUnpacked)
} else {
nazalog.Warnf("[%s] audio unpacker not support for this type yet.", session.uniqueKey)
}
if session.sdpLogicCtx.IsVideoUnpackable() {
session.videoUnpacker = rtprtcp.DefaultRTPUnpackerFactory(session.sdpLogicCtx.GetVideoPayloadTypeBase(), session.sdpLogicCtx.VideoClockRate, unpackerItemMaxSize, session.onAVPacketUnpacked)
} else {
nazalog.Warnf("[%s] video unpacker not support this type yet.", session.uniqueKey)
}
session.audioRRProducer = rtprtcp.NewRRProducer(session.sdpLogicCtx.AudioClockRate)
session.videoRRProducer = rtprtcp.NewRRProducer(session.sdpLogicCtx.VideoClockRate)
if session.sdpLogicCtx.IsAudioUnpackable() && session.sdpLogicCtx.IsVideoUnpackable() {
session.mu.Lock()
session.avPacketQueue = NewAVPacketQueue(session.onAVPacket)
session.mu.Unlock()
}
if session.observer != nil {
session.observer.OnSDP(session.sdpLogicCtx)
}
}
// 如果没有设置回调监听对象可以通过该函数设置调用方保证调用该函数发生在调用InitWithSDP之后
func (session *BaseInSession) SetObserver(observer BaseInSessionObserver) {
session.observer = observer
// 避免在当前协程回调,降低业务方使用负担,不必担心设置监听对象和回调函数中锁重入
go func() {
session.observer.OnSDP(session.sdpLogicCtx)
}()
}
func (session *BaseInSession) SetupWithConn(uri string, rtpConn, rtcpConn *nazanet.UDPConnection) error {
if session.sdpLogicCtx.IsAudioURI(uri) {
session.audioRTPConn = rtpConn
session.audioRTCPConn = rtcpConn
} else if session.sdpLogicCtx.IsVideoURI(uri) {
session.videoRTPConn = rtpConn
session.videoRTCPConn = rtcpConn
} else {
return ErrRTSP
}
go rtpConn.RunLoop(session.onReadRTPPacket)
go rtcpConn.RunLoop(session.onReadRTCPPacket)
return nil
}
func (session *BaseInSession) SetupWithChannel(uri string, rtpChannel, rtcpChannel int) error {
if session.sdpLogicCtx.IsAudioURI(uri) {
session.audioRTPChannel = rtpChannel
session.audioRTCPChannel = rtcpChannel
return nil
} else if session.sdpLogicCtx.IsVideoURI(uri) {
session.videoRTPChannel = rtpChannel
session.videoRTCPChannel = rtcpChannel
return nil
}
return ErrRTSP
}
func (session *BaseInSession) Dispose() error {
nazalog.Infof("[%s] lifecycle dispose rtsp BaseInSession. 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()
}
return nazaerrors.CombineErrors(e1, e2, e3, e4)
}
func (session *BaseInSession) GetSDP() ([]byte, sdp.LogicContext) {
session.mu.Lock()
defer session.mu.Unlock()
return session.rawSDP, session.sdpLogicCtx
}
func (session *BaseInSession) HandleInterleavedPacket(b []byte, channel int) {
switch channel {
case session.audioRTPChannel:
fallthrough
case session.videoRTPChannel:
_ = session.handleRTPPacket(b)
case session.audioRTCPChannel:
fallthrough
case session.videoRTCPChannel:
_ = session.handleRTCPPacket(b, nil)
default:
nazalog.Errorf("[%s] read interleaved packet but channel invalid. channel=%d", session.uniqueKey, channel)
}
}
// 发现pull时需要先给对端发送数据才能收到数据
func (session *BaseInSession) WriteRTPRTCPDummy() {
if session.videoRTPConn != nil {
_ = session.videoRTPConn.Write(dummyRTPPacket)
}
if session.videoRTCPConn != nil {
_ = session.videoRTCPConn.Write(dummyRTCPPacket)
}
if session.audioRTPConn != nil {
_ = session.audioRTPConn.Write(dummyRTPPacket)
}
if session.audioRTCPConn != nil {
_ = session.audioRTCPConn.Write(dummyRTCPPacket)
}
}
func (session *BaseInSession) GetStat() base.StatSession {
session.stat.ReadBytesSum = session.currConnStat.ReadBytesSum.Load()
session.stat.WroteBytesSum = session.currConnStat.WroteBytesSum.Load()
return session.stat
}
func (session *BaseInSession) UpdateStat(intervalSec uint32) {
readBytesSum := session.currConnStat.ReadBytesSum.Load()
wroteBytesSum := session.currConnStat.WroteBytesSum.Load()
rDiff := readBytesSum - session.prevConnStat.ReadBytesSum
session.stat.ReadBitrate = int(rDiff * 8 / 1024 / uint64(intervalSec))
wDiff := wroteBytesSum - session.prevConnStat.WroteBytesSum
session.stat.WriteBitrate = int(wDiff * 8 / 1024 / uint64(intervalSec))
session.stat.Bitrate = session.stat.ReadBitrate
session.prevConnStat.ReadBytesSum = readBytesSum
session.prevConnStat.WroteBytesSum = wroteBytesSum
}
func (session *BaseInSession) IsAlive() (readAlive, writeAlive bool) {
readBytesSum := session.currConnStat.ReadBytesSum.Load()
wroteBytesSum := session.currConnStat.WroteBytesSum.Load()
if session.staleStat == nil {
session.staleStat = new(connection.Stat)
session.staleStat.ReadBytesSum = readBytesSum
session.staleStat.WroteBytesSum = wroteBytesSum
return true, true
}
readAlive = !(readBytesSum-session.staleStat.ReadBytesSum == 0)
writeAlive = !(wroteBytesSum-session.staleStat.WroteBytesSum == 0)
session.staleStat.ReadBytesSum = readBytesSum
session.staleStat.WroteBytesSum = wroteBytesSum
return
}
func (session *BaseInSession) UniqueKey() string {
return session.uniqueKey
}
// callback by RTPUnpacker
func (session *BaseInSession) onAVPacketUnpacked(pkt base.AVPacket) {
session.mu.Lock()
defer session.mu.Unlock()
if session.avPacketQueue != nil {
session.avPacketQueue.Feed(pkt)
} else {
session.observer.OnAVPacket(pkt)
}
}
// callback by avpacket queue
func (session *BaseInSession) onAVPacket(pkt base.AVPacket) {
session.observer.OnAVPacket(pkt)
}
// callback by UDPConnection
func (session *BaseInSession) onReadRTPPacket(b []byte, rAddr *net.UDPAddr, err error) bool {
if err != nil {
nazalog.Errorf("[%s] read udp packet failed. err=%+v", session.uniqueKey, err)
return true
}
_ = session.handleRTPPacket(b)
return true
}
// callback by UDPConnection
func (session *BaseInSession) onReadRTCPPacket(b []byte, rAddr *net.UDPAddr, err error) bool {
if err != nil {
nazalog.Errorf("[%s] read udp packet failed. err=%+v", session.uniqueKey, err)
return true
}
_ = session.handleRTCPPacket(b, rAddr)
return true
}
// @param rAddr 对端地址往对端发送数据时使用注意如果nil则表示是interleaved模式我们直接往TCP连接发数据
func (session *BaseInSession) handleRTCPPacket(b []byte, rAddr *net.UDPAddr) error {
session.currConnStat.ReadBytesSum.Add(uint64(len(b)))
if len(b) <= 0 {
nazalog.Errorf("[%s] handleRTCPPacket but length invalid. len=%d", session.uniqueKey, len(b))
return ErrRTSP
}
if session.loggedReadRTCPCount.Load() < session.debugLogMaxCount {
nazalog.Debugf("[%s] LOGPACKET. read rtcp=%s", session.uniqueKey, hex.Dump(nazastring.SubSliceSafety(b, 32)))
session.loggedReadRTCPCount.Increment()
}
packetType := b[1]
switch packetType {
case rtprtcp.RTCPPacketTypeSR:
sr := rtprtcp.ParseSR(b)
if session.loggedReadSRCount.Load() < session.debugLogMaxCount {
nazalog.Debugf("[%s] LOGPACKET. %+v", session.uniqueKey, sr)
session.loggedReadSRCount.Increment()
}
var rrBuf []byte
switch sr.SenderSSRC {
case session.audioSSRC.Load():
session.mu.Lock()
rrBuf = session.audioRRProducer.Produce(sr.GetMiddleNTP())
session.mu.Unlock()
if rrBuf != nil {
if rAddr != nil {
_ = session.audioRTCPConn.Write2Addr(rrBuf, rAddr)
} else {
_ = session.cmdSession.WriteInterleavedPacket(rrBuf, session.audioRTCPChannel)
}
session.currConnStat.WroteBytesSum.Add(uint64(len(b)))
}
case session.videoSSRC.Load():
session.mu.Lock()
rrBuf = session.videoRRProducer.Produce(sr.GetMiddleNTP())
session.mu.Unlock()
if rrBuf != nil {
if rAddr != nil {
_ = session.videoRTCPConn.Write2Addr(rrBuf, rAddr)
} else {
_ = session.cmdSession.WriteInterleavedPacket(rrBuf, session.videoRTCPChannel)
}
session.currConnStat.WroteBytesSum.Add(uint64(len(b)))
}
default:
// ffmpeg推流时会在发送第一个RTP包之前就发送一个SR所以关闭这个警告日志
//nazalog.Warnf("[%s] read rtcp sr but senderSSRC invalid. senderSSRC=%d, audio=%d, video=%d",
// p.uniqueKey, sr.SenderSSRC, p.audioSSRC, p.videoSSRC)
return ErrRTSP
}
default:
nazalog.Warnf("[%s] handleRTCPPacket but type unknown. type=%d", session.uniqueKey, b[1])
return ErrRTSP
}
return nil
}
func (session *BaseInSession) handleRTPPacket(b []byte) error {
session.currConnStat.ReadBytesSum.Add(uint64(len(b)))
if len(b) < rtprtcp.RTPFixedHeaderLength {
nazalog.Errorf("[%s] handleRTPPacket but length invalid. len=%d", session.uniqueKey, len(b))
return ErrRTSP
}
packetType := int(b[1] & 0x7F)
if !session.sdpLogicCtx.IsPayloadTypeOrigin(packetType) {
nazalog.Errorf("[%s] handleRTPPacket but type invalid. type=%d", session.uniqueKey, packetType)
return ErrRTSP
}
h, err := rtprtcp.ParseRTPHeader(b)
if err != nil {
nazalog.Errorf("[%s] handleRTPPacket invalid rtp packet. err=%+v", session.uniqueKey, err)
return err
}
var pkt rtprtcp.RTPPacket
pkt.Header = h
pkt.Raw = b
// 接收数据时保证了sdp的原始类型对应
if session.sdpLogicCtx.IsAudioPayloadTypeOrigin(packetType) {
if session.loggedReadAudioRTPCount.Load() < session.debugLogMaxCount {
nazalog.Debugf("[%s] LOGPACKET. read audio rtp=%+v, len=%d", session.uniqueKey, h, len(b))
session.loggedReadAudioRTPCount.Increment()
}
session.audioSSRC.Store(h.SSRC)
session.observer.OnRTPPacket(pkt)
session.mu.Lock()
session.audioRRProducer.FeedRTPPacket(h.Seq)
session.mu.Unlock()
if session.audioUnpacker != nil {
session.audioUnpacker.Feed(pkt)
}
} else if session.sdpLogicCtx.IsVideoPayloadTypeOrigin(packetType) {
if session.loggedReadVideoRTPCount.Load() < session.debugLogMaxCount {
nazalog.Debugf("[%s] LOGPACKET. read video rtp=%+v, len=%d", session.uniqueKey, h, len(b))
session.loggedReadVideoRTPCount.Increment()
}
session.videoSSRC.Store(h.SSRC)
session.observer.OnRTPPacket(pkt)
session.mu.Lock()
session.videoRRProducer.FeedRTPPacket(h.Seq)
session.mu.Unlock()
if session.videoUnpacker != nil {
session.videoUnpacker.Feed(pkt)
}
} else {
// noop 因为前面已经判断过type了所以永远不会走到这
}
return nil
}