From 03ccfa0e5fcfc68e05eb1b54d81fdce1af7c4947 Mon Sep 17 00:00:00 2001 From: q191201771 <191201771@qq.com> Date: Sat, 25 Dec 2021 18:13:22 +0800 Subject: [PATCH] =?UTF-8?q?1.=20rtmp.PushSession=E5=92=8CPullSession?= =?UTF-8?q?=E5=8F=AF=E9=85=8D=E7=BD=AEWriteBuf=E5=92=8CReadBuf=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=EF=BC=8C=E4=BB=A5=E5=8F=8AWriteChanSize=202.=20?= =?UTF-8?q?=E6=95=B4=E7=90=86=E5=AE=8C=E6=89=80=E6=9C=89error=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=80=BC=203.=20=E6=8F=90=E9=AB=98=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/demo/pullrtmp/pullrtmp.go | 1 + app/demo/pushrtmp/pushrtmp.go | 2 + pkg/base/error.go | 17 +- pkg/innertest/innertest.go | 109 +++-- pkg/logic/logic.go | 1 + pkg/logic/server_manager.go | 10 - pkg/rtmp/client_pull_session.go | 8 +- pkg/rtmp/client_push_session.go | 11 +- pkg/rtmp/client_session.go | 22 +- pkg/rtmp/server_session.go | 5 - pkg/rtmp/var.go | 8 +- pkg/rtprtcp/rtcp.go | 4 - pkg/rtprtcp/rtp.go | 6 - pkg/rtprtcp/rtp_packet.go | 2 +- pkg/rtprtcp/rtprtcp_test.go | 19 + pkg/rtsp/base_in_session.go | 22 +- pkg/rtsp/base_out_session.go | 31 +- pkg/rtsp/client_command_session.go | 5 +- pkg/rtsp/rtsp.go | 7 +- pkg/rtsp/rtsp_test.go | 19 + pkg/rtsp/server.go | 3 + pkg/rtsp/server_command_session.go | 37 +- pkg/sdp/avconfig.go | 23 +- pkg/sdp/pack.go | 4 +- pkg/sdp/{logic.go => parse_logic.go} | 0 pkg/sdp/{raw.go => parse_raw.go} | 19 +- pkg/sdp/parse_test.go | 580 +++++++++++++++++++++++++++ pkg/sdp/sdp.go | 6 - pkg/sdp/sdp_test.go | 571 +------------------------- 29 files changed, 864 insertions(+), 688 deletions(-) create mode 100644 pkg/rtprtcp/rtprtcp_test.go create mode 100644 pkg/rtsp/rtsp_test.go rename pkg/sdp/{logic.go => parse_logic.go} (100%) rename pkg/sdp/{raw.go => parse_raw.go} (92%) create mode 100644 pkg/sdp/parse_test.go diff --git a/app/demo/pullrtmp/pullrtmp.go b/app/demo/pullrtmp/pullrtmp.go index 90f9c7e..de579bb 100644 --- a/app/demo/pullrtmp/pullrtmp.go +++ b/app/demo/pullrtmp/pullrtmp.go @@ -95,6 +95,7 @@ func pull(url string, filename string) { session := rtmp.NewPullSession(func(option *rtmp.PullSessionOption) { option.PullTimeoutMs = 10000 option.ReadAvTimeoutMs = 10000 + option.ReadBufSize = 0 }) err = session.Pull(url, func(msg base.RtmpMsg) { diff --git a/app/demo/pushrtmp/pushrtmp.go b/app/demo/pushrtmp/pushrtmp.go index b2ff7be..0d8ced3 100644 --- a/app/demo/pushrtmp/pushrtmp.go +++ b/app/demo/pushrtmp/pushrtmp.go @@ -90,6 +90,8 @@ func push(tags []httpflv.Tag, url string, isRecursive bool) { ps := rtmp.NewPushSession(func(option *rtmp.PushSessionOption) { option.PushTimeoutMs = 5000 option.WriteAvTimeoutMs = 10000 + option.WriteBufSize = 0 + option.WriteChanSize = 0 }) if err := ps.Push(url); err != nil { diff --git a/pkg/base/error.go b/pkg/base/error.go index 4850560..aa19f28 100644 --- a/pkg/base/error.go +++ b/pkg/base/error.go @@ -58,7 +58,22 @@ var ( ErrRtmpUnexpectedMsg = errors.New("lal.rtmp: unexpected msg") ) -// TODO(chef): refactor 整理其他pkg的error +// ----- pkg/rtprtcp --------------------------------------------------------------------------------------------------- + +var ErrRtpRtcpShortBuffer = errors.New("lal.rtprtcp: buffer too short") + +// ----- pkg/rtsp ------------------------------------------------------------------------------------------------------ + +var ( + ErrRtsp = errors.New("lal.rtsp: fxxk") + ErrRtspClosedByObserver = errors.New("lal.rtsp: close by observer") +) + +// ----- pkg/sdp ------------------------------------------------------------------------------------------------------- + +var ErrSdp = errors.New("lal.sdp: fxxk") + +// --------------------------------------------------------------------------------------------------------------------- func NewErrAmfInvalidType(b byte) error { return fmt.Errorf("%w. b=%d", ErrAmfInvalidType, b) diff --git a/pkg/innertest/innertest.go b/pkg/innertest/innertest.go index a80ad27..11b476c 100644 --- a/pkg/innertest/innertest.go +++ b/pkg/innertest/innertest.go @@ -11,12 +11,15 @@ package innertest import ( "bytes" "fmt" - "io" "io/ioutil" "os" "testing" "time" + "github.com/q191201771/lal/pkg/rtprtcp" + "github.com/q191201771/lal/pkg/rtsp" + "github.com/q191201771/lal/pkg/sdp" + "github.com/q191201771/lal/pkg/remux" "github.com/q191201771/lal/pkg/base" @@ -33,15 +36,14 @@ import ( ) // 开启了一个lalserver -// 读取flv文件,使用rtmp协议推送至服务端 -// 分别用rtmp协议以及httpflv协议从服务端拉流,再将拉取的流保存为flv文件 +// rtmp pub 读取flv文件,使用rtmp协议推送至服务端 +// rtmp sub, httpflv sub 分别用rtmp协议以及httpflv协议从服务端拉流,再将拉取的流保存为flv文件 // 对比三份flv文件,看是否完全一致 -// 并检查hls生成的m3u8和ts文件,是否和之前的完全一致 +// hls 并检查hls生成的m3u8和ts文件,是否和之前的完全一致 // TODO chef: // - 加上relay push // - 加上relay pull -// - 加上rtspserver的测试 var ( tt *testing.T @@ -50,24 +52,42 @@ var ( rFlvFileName = "../../testdata/test.flv" wFlvPullFileName = "../../testdata/flvpull.flv" wRtmpPullFileName = "../../testdata/rtmppull.flv" + wRtspPullFileName = "../../testdata/rtsppull.flv" pushUrl string httpflvPullUrl string rtmpPullUrl string + rtspPullUrl string - fileReader httpflv.FlvFileReader httpFlvWriter httpflv.FlvFileWriter rtmpWriter httpflv.FlvFileWriter pushSession *rtmp.PushSession httpflvPullSession *httpflv.PullSession rtmpPullSession *rtmp.PullSession + rtspPullSession *rtsp.PullSession - fileTagCount nazaatomic.Uint32 - httpflvPullTagCount nazaatomic.Uint32 - rtmpPullTagCount nazaatomic.Uint32 + fileTagCount int + httpflvPullTagCount nazaatomic.Uint32 + rtmpPullTagCount nazaatomic.Uint32 + rtspSdpCtx sdp.LogicContext + rtspPullAvPacketCount nazaatomic.Uint32 ) +type RtspPullObserver struct { +} + +func (r RtspPullObserver) OnSdp(sdpCtx sdp.LogicContext) { + rtspSdpCtx = sdpCtx +} + +func (r RtspPullObserver) OnRtpPacket(pkt rtprtcp.RtpPacket) { +} + +func (r RtspPullObserver) OnAvPacket(pkt base.AvPacket) { + rtspPullAvPacketCount.Increment() +} + func Entry(t *testing.T) { if _, err := os.Lstat(confFile); err != nil { nazalog.Warnf("lstat %s error. err=%+v", confFile, err) @@ -93,9 +113,11 @@ func Entry(t *testing.T) { pushUrl = fmt.Sprintf("rtmp://127.0.0.1%s/live/innertest", config.RtmpConfig.Addr) httpflvPullUrl = fmt.Sprintf("http://127.0.0.1%s/live/innertest.flv", config.HttpflvConfig.HttpListenAddr) rtmpPullUrl = fmt.Sprintf("rtmp://127.0.0.1%s/live/innertest", config.RtmpConfig.Addr) + rtspPullUrl = fmt.Sprintf("rtsp://127.0.0.1%s/live/innertest", config.RtspConfig.Addr) - err = fileReader.Open(rFlvFileName) + tags, err := httpflv.ReadAllTagsFromFlvFile(rFlvFileName) assert.Equal(t, nil, err) + fileTagCount = len(tags) err = httpFlvWriter.Open(wFlvPullFileName) assert.Equal(t, nil, err) @@ -109,7 +131,8 @@ func Entry(t *testing.T) { go func() { rtmpPullSession = rtmp.NewPullSession(func(option *rtmp.PullSessionOption) { - option.ReadAvTimeoutMs = 500 + option.ReadAvTimeoutMs = 10000 + option.ReadBufSize = 0 }) err := rtmpPullSession.Pull( rtmpPullUrl, @@ -119,16 +142,14 @@ func Entry(t *testing.T) { assert.Equal(tt, nil, err) rtmpPullTagCount.Increment() }) - if err != nil { - nazalog.Error(err) - } + nazalog.Assert(nil, err) err = <-rtmpPullSession.WaitChan() nazalog.Debug(err) }() go func() { httpflvPullSession = httpflv.NewPullSession(func(option *httpflv.PullSessionOption) { - option.ReadTimeoutMs = 500 + option.ReadTimeoutMs = 10000 }) err := httpflvPullSession.Pull(httpflvPullUrl, func(tag httpflv.Tag) { err := httpFlvWriter.WriteTag(tag) @@ -136,43 +157,77 @@ func Entry(t *testing.T) { httpflvPullTagCount.Increment() }) nazalog.Assert(nil, err) + err = <-httpflvPullSession.WaitChan() + nazalog.Debug(err) }() time.Sleep(200 * time.Millisecond) - pushSession = rtmp.NewPushSession() + // TODO(chef): [test] [2021.12.25] rtsp sub测试 由于rtsp sub不支持没有pub时sub,只能sub失败后重试,所以没有验证收到的数据 + // TODO(chef): [perf] [2021.12.25] rtmp推rtsp拉的性能。开启rtsp pull后,rtmp pull的总时长增加了 + go func() { + for { + rtspPullAvPacketCount.Store(0) + var rtspPullObserver RtspPullObserver + rtspPullSession = rtsp.NewPullSession(&rtspPullObserver, func(option *rtsp.PullSessionOption) { + option.PullTimeoutMs = 500 + }) + err := rtspPullSession.Pull(rtspPullUrl) + nazalog.Debug(err) + if rtspSdpCtx.RawSdp != nil { + break + } + time.Sleep(100 * time.Millisecond) + } + }() + + pushSession = rtmp.NewPushSession(func(option *rtmp.PushSessionOption) { + option.WriteBufSize = 4096 + option.WriteChanSize = 1024 + }) err = pushSession.Push(pushUrl) assert.Equal(t, nil, err) - for { - tag, err := fileReader.ReadTag() - if err == io.EOF { - break - } + nazalog.Debugf("CHEFERASEME start push %+v", time.Now()) + for _, tag := range tags { assert.Equal(t, nil, err) - fileTagCount.Increment() chunks := remux.FlvTag2RtmpChunks(tag) + //nazalog.Debugf("rtmp push: %d", fileTagCount.Load()) err = pushSession.Write(chunks) assert.Equal(t, nil, err) } err = pushSession.Flush() assert.Equal(t, nil, err) - time.Sleep(1 * time.Second) + for { + if httpflvPullTagCount.Load() == uint32(fileTagCount) && + rtmpPullTagCount.Load() == uint32(fileTagCount) { + time.Sleep(100 * time.Millisecond) + break + } + time.Sleep(10 * time.Millisecond) + } + + nazalog.Debug("[innertest] start dispose.") - fileReader.Dispose() pushSession.Dispose() httpflvPullSession.Dispose() rtmpPullSession.Dispose() + //rtspPullSession.Dispose() + httpFlvWriter.Dispose() rtmpWriter.Dispose() + // 由于windows没有信号,会导致编译错误,所以直接调用Dispose //_ = syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) sm.Dispose() - nazalog.Debugf("count. %d %d %d", fileTagCount.Load(), httpflvPullTagCount.Load(), rtmpPullTagCount.Load()) + nazalog.Debugf("tag count. in=%d, out httpflv=%d, out rtmp=%d, out rtsp=%d", + fileTagCount, httpflvPullTagCount.Load(), rtmpPullTagCount.Load(), rtspPullAvPacketCount.Load()) + compareFile() + // 检查hls的ts文件 var allContent []byte var fileNum int err = filebatch.Walk( @@ -201,7 +256,7 @@ func compareFile() { nazalog.Debugf("%s filesize:%d", wFlvPullFileName, len(w)) res := bytes.Compare(r, w) assert.Equal(tt, 0, res) - err = os.Remove(wFlvPullFileName) + //err = os.Remove(wFlvPullFileName) assert.Equal(tt, nil, err) w2, err := ioutil.ReadFile(wRtmpPullFileName) @@ -209,6 +264,6 @@ func compareFile() { nazalog.Debugf("%s filesize:%d", wRtmpPullFileName, len(w2)) res = bytes.Compare(r, w2) assert.Equal(tt, 0, res) - err = os.Remove(wRtmpPullFileName) + //err = os.Remove(wRtmpPullFileName) assert.Equal(tt, nil, err) } diff --git a/pkg/logic/logic.go b/pkg/logic/logic.go index 754a595..ebda95c 100644 --- a/pkg/logic/logic.go +++ b/pkg/logic/logic.go @@ -15,6 +15,7 @@ type ILalServer interface { Dispose() // StatLalInfo StatXxx... CtrlXxx... + // // 一些获取状态、发送控制命令的API // 目的是方便业务方在不修改logic包内代码的前提下,在外层实现一些特定逻辑的定制化开发 // diff --git a/pkg/logic/server_manager.go b/pkg/logic/server_manager.go index 63d7343..c3878f4 100644 --- a/pkg/logic/server_manager.go +++ b/pkg/logic/server_manager.go @@ -380,7 +380,6 @@ func (sm *ServerManager) OnRtmpConnect(session *rtmp.ServerSession, opa rtmp.Obj } func (sm *ServerManager) OnNewRtmpPubSession(session *rtmp.ServerSession) bool { - nazalog.Debugf("CHEFERASEME [%s] OnNewRtmpPubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getOrCreateGroup(session.AppName(), session.StreamName()) @@ -404,7 +403,6 @@ func (sm *ServerManager) OnNewRtmpPubSession(session *rtmp.ServerSession) bool { } func (sm *ServerManager) OnDelRtmpPubSession(session *rtmp.ServerSession) { - nazalog.Debugf("CHEFERASEME [%s] OnDelRtmpPubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getGroup(session.AppName(), session.StreamName()) @@ -429,7 +427,6 @@ func (sm *ServerManager) OnDelRtmpPubSession(session *rtmp.ServerSession) { } func (sm *ServerManager) OnNewRtmpSubSession(session *rtmp.ServerSession) bool { - nazalog.Debugf("CHEFERASEME [%s] OnNewRtmpSubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getOrCreateGroup(session.AppName(), session.StreamName()) @@ -452,7 +449,6 @@ func (sm *ServerManager) OnNewRtmpSubSession(session *rtmp.ServerSession) bool { } func (sm *ServerManager) OnDelRtmpSubSession(session *rtmp.ServerSession) { - nazalog.Debugf("CHEFERASEME [%s] OnDelRtmpSubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getGroup(session.AppName(), session.StreamName()) @@ -478,7 +474,6 @@ func (sm *ServerManager) OnDelRtmpSubSession(session *rtmp.ServerSession) { // ----- implement HttpServerHandlerObserver interface ----------------------------------------------------------------- func (sm *ServerManager) OnNewHttpflvSubSession(session *httpflv.SubSession) bool { - nazalog.Debugf("CHEFERASEME [%s] OnNewHttpflvSubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getOrCreateGroup(session.AppName(), session.StreamName()) @@ -500,7 +495,6 @@ func (sm *ServerManager) OnNewHttpflvSubSession(session *httpflv.SubSession) boo } func (sm *ServerManager) OnDelHttpflvSubSession(session *httpflv.SubSession) { - nazalog.Debugf("CHEFERASEME [%s] OnDelHttpflvSubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getGroup(session.AppName(), session.StreamName()) @@ -525,7 +519,6 @@ func (sm *ServerManager) OnDelHttpflvSubSession(session *httpflv.SubSession) { } func (sm *ServerManager) OnNewHttptsSubSession(session *httpts.SubSession) bool { - nazalog.Debugf("CHEFERASEME [%s] OnNewHttptsSubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getOrCreateGroup(session.AppName(), session.StreamName()) @@ -547,7 +540,6 @@ func (sm *ServerManager) OnNewHttptsSubSession(session *httpts.SubSession) bool } func (sm *ServerManager) OnDelHttptsSubSession(session *httpts.SubSession) { - nazalog.Debugf("CHEFERASEME [%s] OnDelHttptsSubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getGroup(session.AppName(), session.StreamName()) @@ -582,7 +574,6 @@ func (sm *ServerManager) OnDelRtspSession(session *rtsp.ServerCommandSession) { } func (sm *ServerManager) OnNewRtspPubSession(session *rtsp.PubSession) bool { - nazalog.Debugf("CHEFERASEME [%s] OnNewRtspPubSession. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getOrCreateGroup(session.AppName(), session.StreamName()) @@ -630,7 +621,6 @@ func (sm *ServerManager) OnDelRtspPubSession(session *rtsp.PubSession) { } func (sm *ServerManager) OnNewRtspSubSessionDescribe(session *rtsp.SubSession) (ok bool, sdp []byte) { - nazalog.Debugf("CHEFERASEME [%s] OnNewRtspSubSessionDescribe. %s, %s, %s, %s", session.UniqueKey(), session.Url(), session.AppName(), session.StreamName(), session.RawQuery()) sm.mutex.Lock() defer sm.mutex.Unlock() group := sm.getOrCreateGroup(session.AppName(), session.StreamName()) diff --git a/pkg/rtmp/client_pull_session.go b/pkg/rtmp/client_pull_session.go index 81c237c..d52698c 100644 --- a/pkg/rtmp/client_pull_session.go +++ b/pkg/rtmp/client_pull_session.go @@ -24,12 +24,15 @@ type PullSessionOption struct { PullTimeoutMs int ReadAvTimeoutMs int + ReadBufSize int // io层读取音视频数据时的缓冲大小,如果为0,则没有缓冲 HandshakeComplexFlag bool } var defaultPullSessionOption = PullSessionOption{ - PullTimeoutMs: 10000, - ReadAvTimeoutMs: 0, + PullTimeoutMs: 10000, + ReadAvTimeoutMs: 0, + ReadBufSize: 0, + HandshakeComplexFlag: false, } type ModPullSessionOption func(option *PullSessionOption) @@ -44,6 +47,7 @@ func NewPullSession(modOptions ...ModPullSessionOption) *PullSession { core: NewClientSession(CstPullSession, func(option *ClientSessionOption) { option.DoTimeoutMs = opt.PullTimeoutMs option.ReadAvTimeoutMs = opt.ReadAvTimeoutMs + option.ReadBufSize = opt.ReadBufSize option.HandshakeComplexFlag = opt.HandshakeComplexFlag }), } diff --git a/pkg/rtmp/client_push_session.go b/pkg/rtmp/client_push_session.go index d13e2d9..c360a5d 100644 --- a/pkg/rtmp/client_push_session.go +++ b/pkg/rtmp/client_push_session.go @@ -22,12 +22,17 @@ type PushSessionOption struct { PushTimeoutMs int WriteAvTimeoutMs int + WriteBufSize int // io层发送音视频数据的缓冲大小,如果为0,则没有缓冲 + WriteChanSize int // io层发送音视频数据的异步队列大小,如果为0,则同步发送 HandshakeComplexFlag bool } var defaultPushSessionOption = PushSessionOption{ - PushTimeoutMs: 10000, - WriteAvTimeoutMs: 0, + PushTimeoutMs: 10000, + WriteAvTimeoutMs: 0, + WriteBufSize: 0, + WriteChanSize: 0, + HandshakeComplexFlag: false, } type ModPushSessionOption func(option *PushSessionOption) @@ -42,6 +47,8 @@ func NewPushSession(modOptions ...ModPushSessionOption) *PushSession { core: NewClientSession(CstPushSession, func(option *ClientSessionOption) { option.DoTimeoutMs = opt.PushTimeoutMs option.WriteAvTimeoutMs = opt.WriteAvTimeoutMs + option.WriteBufSize = opt.WriteBufSize + option.WriteChanSize = opt.WriteChanSize option.HandshakeComplexFlag = opt.HandshakeComplexFlag }), } diff --git a/pkg/rtmp/client_session.go b/pkg/rtmp/client_session.go index 28bbf1b..35dac46 100644 --- a/pkg/rtmp/client_session.go +++ b/pkg/rtmp/client_session.go @@ -62,16 +62,24 @@ const ( type ClientSessionOption struct { // 单位毫秒,如果为0,则没有超时 - DoTimeoutMs int // 从发起连接(包含了建立连接的时间)到收到publish或play信令结果的超时 - ReadAvTimeoutMs int // 读取音视频数据的超时 - WriteAvTimeoutMs int // 发送音视频数据的超时 + DoTimeoutMs int // 从发起连接(包含了建立连接的时间)到收到publish或play信令结果的超时 + ReadAvTimeoutMs int // 读取音视频数据的超时 + WriteAvTimeoutMs int // 发送音视频数据的超时 + + ReadBufSize int // io层读取音视频数据时的缓冲大小,如果为0,则没有缓冲 + WriteBufSize int // io层发送音视频数据的缓冲大小,如果为0,则没有缓冲 + WriteChanSize int // io层发送音视频数据的异步队列大小,如果为0,则同步发送 + HandshakeComplexFlag bool // 握手是否使用复杂模式 } var defaultClientSessOption = ClientSessionOption{ - DoTimeoutMs: 0, + DoTimeoutMs: 10000, ReadAvTimeoutMs: 0, WriteAvTimeoutMs: 0, + ReadBufSize: 0, + WriteBufSize: 0, + WriteChanSize: 0, HandshakeComplexFlag: false, } @@ -304,7 +312,7 @@ func (s *ClientSession) tcpConnect() error { } s.conn = connection.New(conn, func(option *connection.Option) { - option.ReadBufSize = readBufSize + option.ReadBufSize = s.option.ReadBufSize option.WriteChanFullBehavior = connection.WriteChanFullBehaviorBlock }) return nil @@ -533,8 +541,8 @@ func (s *ClientSession) notifyDoResultSucc() { } s.hasNotifyDoResultSucc = true - s.conn.ModWriteChanSize(wChanSize) - s.conn.ModWriteBufSize(writeBufSize) + s.conn.ModWriteChanSize(s.option.WriteChanSize) + s.conn.ModWriteBufSize(s.option.WriteBufSize) s.conn.ModReadTimeoutMs(s.option.ReadAvTimeoutMs) s.conn.ModWriteTimeoutMs(s.option.WriteAvTimeoutMs) diff --git a/pkg/rtmp/server_session.go b/pkg/rtmp/server_session.go index 67b7238..146eec2 100644 --- a/pkg/rtmp/server_session.go +++ b/pkg/rtmp/server_session.go @@ -464,11 +464,6 @@ func (s *ServerSession) doPlay(tid int, stream *Stream) (err error) { func (s *ServerSession) modConnProps() { s.conn.ModWriteChanSize(wChanSize) - // TODO chef: - // 使用合并发送 - // naza.connection 这种方式会导致最后一点数据发送不出去,我们应该使用更好的方式,比如合并发送模式下,Dispose时发送剩余数据 - // - //s.conn.ModWriteBufSize(writeBufSize) switch s.t { case ServerSessionTypePub: diff --git a/pkg/rtmp/var.go b/pkg/rtmp/var.go index ca51bc0..ee10db1 100644 --- a/pkg/rtmp/var.go +++ b/pkg/rtmp/var.go @@ -11,12 +11,14 @@ package rtmp // TODO chef // 一些更专业的配置项,暂时只在该源码文件中配置,不提供外部配置接口 var ( - readBufSize = 4096 // client/server session connection读缓冲的大小 - writeBufSize = 4096 // client/server session connection写缓冲的大小 - wChanSize = 1024 // client/server session 发送数据时,channel 的大小 + readBufSize = 4096 // server session connection读缓冲的大小 + wChanSize = 1024 // server session 发送数据时,channel 的大小 serverSessionReadAvTimeoutMs = 10000 // server pub session,读音视频数据超时 serverSessionWriteAvTimeoutMs = 10000 // server sub session,写音视频数据超时 + //writeBufSize = 4096 // 注意,因为lal server在group中使用writev做merge合并发送,这个废弃不需要了 + // LocalChunkSize + // // 本端(包括Server Session和Client Session)设置的chunk size,本端发送数据时切割chunk包时使用 // (对端发送数据时的chunk size由对端决定,和本变量没有关系) // diff --git a/pkg/rtprtcp/rtcp.go b/pkg/rtprtcp/rtcp.go index bd5e218..ad84d46 100644 --- a/pkg/rtprtcp/rtcp.go +++ b/pkg/rtprtcp/rtcp.go @@ -9,8 +9,6 @@ package rtprtcp import ( - "errors" - "github.com/q191201771/naza/pkg/bele" ) @@ -84,8 +82,6 @@ import ( // | profile-specific extensions | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -var ErrRtcp = errors.New("lal.rtcp: fxxk") - const ( RtcpPacketTypeSr = 200 // 0xc8 Sender Report RtcpPacketTypeRr = 201 // 0xc9 Receiver Report diff --git a/pkg/rtprtcp/rtp.go b/pkg/rtprtcp/rtp.go index d4cb20e..82b0f2b 100644 --- a/pkg/rtprtcp/rtp.go +++ b/pkg/rtprtcp/rtp.go @@ -8,12 +8,6 @@ package rtprtcp -import ( - "errors" -) - -var ErrRtp = errors.New("lal.rtp: fxxk") - // rfc3984 5.2. Common Structure of the RTP Payload Format // Table 1. Summary of NAL unit types and their payload structures // diff --git a/pkg/rtprtcp/rtp_packet.go b/pkg/rtprtcp/rtp_packet.go index 2d26962..8744b30 100644 --- a/pkg/rtprtcp/rtp_packet.go +++ b/pkg/rtprtcp/rtp_packet.go @@ -96,7 +96,7 @@ func MakeRtpPacket(h RtpHeader, payload []byte) (pkt RtpPacket) { func ParseRtpHeader(b []byte) (h RtpHeader, err error) { if len(b) < RtpFixedHeaderLength { - err = ErrRtp + err = base.ErrRtpRtcpShortBuffer return } diff --git a/pkg/rtprtcp/rtprtcp_test.go b/pkg/rtprtcp/rtprtcp_test.go new file mode 100644 index 0000000..bbb1c3c --- /dev/null +++ b/pkg/rtprtcp/rtprtcp_test.go @@ -0,0 +1,19 @@ +// 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 rtprtcp_test + +import ( + "testing" + + "github.com/q191201771/lal/pkg/innertest" +) + +func TestRtpRtcp(t *testing.T) { + innertest.Entry(t) +} diff --git a/pkg/rtsp/base_in_session.go b/pkg/rtsp/base_in_session.go index 3792921..2691d2d 100644 --- a/pkg/rtsp/base_in_session.go +++ b/pkg/rtsp/base_in_session.go @@ -29,6 +29,8 @@ import ( // 聚合PubSession和PullSession,也即流数据是输入类型的session +// BaseInSessionObserver +// // BaseInSession会向上层回调两种格式的数据(本质上是一份数据,业务方可自由选择使用): // 1. 原始的rtp packet // 2. rtp合并后的av packet @@ -36,10 +38,14 @@ import ( type BaseInSessionObserver interface { OnSdp(sdpCtx sdp.LogicContext) - // 回调收到的RTP包 + // OnRtpPacket 回调收到的RTP包 + // OnRtpPacket(pkt rtprtcp.RtpPacket) + // OnAvPacket + // // @param pkt: pkt结构体中字段含义见rtprtcp.OnAvPacket + // OnAvPacket(pkt base.AvPacket) } @@ -156,7 +162,7 @@ func (session *BaseInSession) SetupWithConn(uri string, rtpConn, rtcpConn *nazan session.videoRtpConn = rtpConn session.videoRtcpConn = rtcpConn } else { - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } go rtpConn.RunLoop(session.onReadRtpPacket) @@ -175,7 +181,7 @@ func (session *BaseInSession) SetupWithChannel(uri string, rtpChannel, rtcpChann session.videoRtcpChannel = rtcpChannel return nil } - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } // --------------------------------------------------------------------------------------------------------------------- @@ -322,7 +328,7 @@ func (session *BaseInSession) handleRtcpPacket(b []byte, rAddr *net.UDPAddr) err if len(b) <= 0 { nazalog.Errorf("[%s] handleRtcpPacket but length invalid. len=%d", session.uniqueKey, len(b)) - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } if session.loggedReadRtcpCount.Load() < session.debugLogMaxCount { @@ -369,11 +375,11 @@ func (session *BaseInSession) handleRtcpPacket(b []byte, rAddr *net.UDPAddr) err // 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 + return nazaerrors.Wrap(base.ErrRtsp) } default: nazalog.Warnf("[%s] handleRtcpPacket but type unknown. type=%d", session.uniqueKey, b[1]) - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } return nil @@ -384,13 +390,13 @@ func (session *BaseInSession) handleRtpPacket(b []byte) error { if len(b) < rtprtcp.RtpFixedHeaderLength { nazalog.Errorf("[%s] handleRtpPacket but length invalid. len=%d", session.uniqueKey, len(b)) - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } packetType := int(b[1] & 0x7F) if !session.sdpCtx.IsPayloadTypeOrigin(packetType) { nazalog.Errorf("[%s] handleRtpPacket but type invalid. type=%d", session.uniqueKey, packetType) - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } h, err := rtprtcp.ParseRtpHeader(b) diff --git a/pkg/rtsp/base_out_session.go b/pkg/rtsp/base_out_session.go index d03591e..4363e4b 100644 --- a/pkg/rtsp/base_out_session.go +++ b/pkg/rtsp/base_out_session.go @@ -14,6 +14,8 @@ import ( "sync" "time" + "github.com/q191201771/naza/pkg/nazaatomic" + "github.com/q191201771/lal/pkg/rtprtcp" "github.com/q191201771/naza/pkg/nazabytes" "github.com/q191201771/naza/pkg/nazaerrors" @@ -51,7 +53,8 @@ type BaseOutSession struct { debugLogMaxCount int loggedWriteAudioRtpCount int loggedWriteVideoRtpCount int - loggedReadUdpCount int + loggedReadRtpCount nazaatomic.Int32 // 因为音频和视频是两个连接,所以需要原子操作 + loggedReadRtcpCount nazaatomic.Int32 disposeOnce sync.Once waitChan chan error @@ -87,11 +90,11 @@ func (session *BaseOutSession) SetupWithConn(uri string, rtpConn, rtcpConn *naza session.videoRtpConn = rtpConn session.videoRtcpConn = rtcpConn } else { - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } - go rtpConn.RunLoop(session.onReadUdpPacket) - go rtcpConn.RunLoop(session.onReadUdpPacket) + go rtpConn.RunLoop(session.onReadRtpPacket) + go rtcpConn.RunLoop(session.onReadRtcpPacket) return nil } @@ -107,7 +110,7 @@ func (session *BaseOutSession) SetupWithChannel(uri string, rtpChannel, rtcpChan return nil } - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } // --------------------------------------------------------------------------------------------------------------------- @@ -176,7 +179,7 @@ func (session *BaseOutSession) WriteRtpPacket(packet rtprtcp.RtpPacket) error { } } else { nazalog.Errorf("[%s] write rtp packet but type invalid. type=%d", session.uniqueKey, t) - err = ErrRtsp + err = nazaerrors.Wrap(base.ErrRtsp) } if err == nil { @@ -224,12 +227,20 @@ func (session *BaseOutSession) UniqueKey() string { return session.uniqueKey } -func (session *BaseOutSession) onReadUdpPacket(b []byte, rAddr *net.UDPAddr, err error) bool { +func (session *BaseOutSession) onReadRtpPacket(b []byte, rAddr *net.UDPAddr, err error) bool { + if session.loggedReadRtpCount.Load() < int32(session.debugLogMaxCount) { + nazalog.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.loggedReadUdpCount < session.debugLogMaxCount { - nazalog.Debugf("[%s] LOGPACKET. read udp=%s", session.uniqueKey, hex.Dump(nazabytes.Prefix(b, 32))) - session.loggedReadUdpCount++ + if session.loggedReadRtcpCount.Load() < int32(session.debugLogMaxCount) { + nazalog.Debugf("[%s] LOGPACKET. read rtcp=%s", session.uniqueKey, hex.Dump(nazabytes.Prefix(b, 32))) + session.loggedReadRtcpCount.Increment() } return true } diff --git a/pkg/rtsp/client_command_session.go b/pkg/rtsp/client_command_session.go index d2e924c..a57aec3 100644 --- a/pkg/rtsp/client_command_session.go +++ b/pkg/rtsp/client_command_session.go @@ -17,6 +17,8 @@ import ( "sync" "time" + "github.com/q191201771/naza/pkg/nazaerrors" + "github.com/q191201771/lal/pkg/base" "github.com/q191201771/lal/pkg/rtprtcp" "github.com/q191201771/lal/pkg/sdp" @@ -564,7 +566,8 @@ func (session *ClientCommandSession) writeCmdReadResp(method, uri string, header session.auth.FeedWwwAuthenticate(ctx.Headers.Values(HeaderWwwAuthenticate), session.urlCtx.Username, session.urlCtx.Password) } - err = ErrRtsp + // TODO(chef): refactor never reach here? + err = nazaerrors.Wrap(base.ErrRtsp) return } diff --git a/pkg/rtsp/rtsp.go b/pkg/rtsp/rtsp.go index 999aaaa..cdc5126 100644 --- a/pkg/rtsp/rtsp.go +++ b/pkg/rtsp/rtsp.go @@ -9,12 +9,13 @@ package rtsp import ( - "errors" "fmt" "net" "strconv" "strings" + "github.com/q191201771/naza/pkg/nazaerrors" + "github.com/q191201771/lal/pkg/base" "github.com/q191201771/lal/pkg/rtprtcp" @@ -28,8 +29,6 @@ import ( // - [refactor] BaseInSession和BaseOutSession有不少重复内容 // - [refactor] PullSession和PushSession有不少重复内容 -var ErrRtsp = errors.New("lal.rtsp: fxxk") - const ( MethodOptions = "OPTIONS" MethodAnnounce = "ANNOUNCE" @@ -166,7 +165,7 @@ func parseTransport(setupTransport string, key string) (first, second uint16, er } items = strings.Split(clientPort, "-") if len(items) != 2 { - return 0, 0, ErrRtsp + return 0, 0, nazaerrors.Wrap(base.ErrRtsp) } iFirst, err := strconv.Atoi(items[0]) if err != nil { diff --git a/pkg/rtsp/rtsp_test.go b/pkg/rtsp/rtsp_test.go new file mode 100644 index 0000000..06b9834 --- /dev/null +++ b/pkg/rtsp/rtsp_test.go @@ -0,0 +1,19 @@ +// 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_test + +import ( + "testing" + + "github.com/q191201771/lal/pkg/innertest" +) + +func TestRtsp(t *testing.T) { + innertest.Entry(t) +} diff --git a/pkg/rtsp/server.go b/pkg/rtsp/server.go index 5499926..31e6a56 100644 --- a/pkg/rtsp/server.go +++ b/pkg/rtsp/server.go @@ -31,8 +31,11 @@ type ServerObserver interface { /////////////////////////////////////////////////////////////////////////// + // OnNewRtspSubSessionDescribe + // // @return 如果返回false,则表示上层要强制关闭这个拉流请求 // @return sdp + // OnNewRtspSubSessionDescribe(session *SubSession) (ok bool, sdp []byte) // @brief Describe阶段回调 diff --git a/pkg/rtsp/server_command_session.go b/pkg/rtsp/server_command_session.go index b7e0b09..7c17d00 100644 --- a/pkg/rtsp/server_command_session.go +++ b/pkg/rtsp/server_command_session.go @@ -14,6 +14,8 @@ import ( "net" "strings" + "github.com/q191201771/naza/pkg/nazaerrors" + "github.com/q191201771/naza/pkg/connection" "github.com/q191201771/lal/pkg/base" @@ -23,17 +25,26 @@ import ( ) type ServerCommandSessionObserver interface { + // OnNewRtspPubSession + // // @brief Announce阶段回调 // @return 如果返回false,则表示上层要强制关闭这个推流请求 + // OnNewRtspPubSession(session *PubSession) bool + // OnNewRtspSubSessionDescribe + // // @brief Describe阶段回调 // @return ok 如果返回false,则表示上层要强制关闭这个拉流请求 // @return sdp + // OnNewRtspSubSessionDescribe(session *SubSession) (ok bool, sdp []byte) + // OnNewRtspSubSessionPlay + // // @brief Describe阶段回调 // @return ok 如果返回false,则表示上层要强制关闭这个拉流请求 + // OnNewRtspSubSessionPlay(session *SubSession) bool } @@ -72,7 +83,14 @@ func (session *ServerCommandSession) Dispose() error { return session.conn.Close() } +func (session *ServerCommandSession) FeedSdp(b []byte) { + +} + +// WriteInterleavedPacket +// // 使用RTSP TCP命令连接,向对端发送RTP数据 +// func (session *ServerCommandSession) WriteInterleavedPacket(packet []byte, channel int) error { _, err := session.conn.Write(packInterleaved(channel, packet)) return err @@ -82,6 +100,8 @@ func (session *ServerCommandSession) RemoteAddr() string { return session.conn.RemoteAddr().String() } +// ----- ISessionStat -------------------------------------------------------------------------------------------------- + func (session *ServerCommandSession) UpdateStat(intervalSec uint32) { currStat := session.conn.GetStat() rDiff := currStat.ReadBytesSum - session.prevConnStat.ReadBytesSum @@ -112,10 +132,14 @@ func (session *ServerCommandSession) IsAlive() (readAlive, writeAlive bool) { return } +// ----- IObject ------------------------------------------------------------------------------------------------------- + func (session *ServerCommandSession) UniqueKey() string { return session.uniqueKey } +// --------------------------------------------------------------------------------------------------------------------- + func (session *ServerCommandSession) runCmdLoop() error { var r = bufio.NewReader(session.conn) @@ -176,7 +200,7 @@ Loop: nazalog.Errorf("[%s] unknown rtsp message. method=%s", session.uniqueKey, requestCtx.Method) } if handleMsgErr != nil { - nazalog.Errorf("[%s] handle rtsp message error. err=%+v", session.uniqueKey, handleMsgErr) + nazalog.Errorf("[%s] handle rtsp message error. err=%+v, ctx=%+v", session.uniqueKey, handleMsgErr, requestCtx) break } } @@ -214,8 +238,7 @@ func (session *ServerCommandSession) handleAnnounce(requestCtx nazahttp.HttpReqM session.pubSession.InitWithSdp(sdpCtx) if ok := session.observer.OnNewRtspPubSession(session.pubSession); !ok { - nazalog.Warnf("[%s] force close pubsession.", session.pubSession.uniqueKey) - return ErrRtsp + return base.ErrRtspClosedByObserver } resp := PackResponseAnnounce(requestCtx.Headers.Get(HeaderCSeq)) @@ -237,7 +260,7 @@ func (session *ServerCommandSession) handleDescribe(requestCtx nazahttp.HttpReqM ok, rawSdp := session.observer.OnNewRtspSubSessionDescribe(session.subSession) if !ok { nazalog.Warnf("[%s] force close subSession.", session.uniqueKey) - return ErrRtsp + return base.ErrRtspClosedByObserver } sdpCtx, _ := sdp.ParseSdp2LogicContext(rawSdp) @@ -275,7 +298,7 @@ func (session *ServerCommandSession) handleSetup(requestCtx nazahttp.HttpReqMsgC } } else { nazalog.Errorf("[%s] setup but session not exist.", session.uniqueKey) - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } resp := PackResponseSetup(requestCtx.Headers.Get(HeaderCSeq), htv) @@ -310,7 +333,7 @@ func (session *ServerCommandSession) handleSetup(requestCtx nazahttp.HttpReqMsgC htv = fmt.Sprintf(HeaderTransportServerPlayTmpl, rRtpPort, rRtcpPort, lRtpPort, lRtcpPort) } else { nazalog.Errorf("[%s] setup but session not exist.", session.uniqueKey) - return ErrRtsp + return nazaerrors.Wrap(base.ErrRtsp) } resp := PackResponseSetup(requestCtx.Headers.Get(HeaderCSeq), htv) @@ -328,7 +351,7 @@ func (session *ServerCommandSession) handleRecord(requestCtx nazahttp.HttpReqMsg func (session *ServerCommandSession) handlePlay(requestCtx nazahttp.HttpReqMsgCtx) error { nazalog.Infof("[%s] < R PLAY", session.uniqueKey) if ok := session.observer.OnNewRtspSubSessionPlay(session.subSession); !ok { - return ErrRtsp + return base.ErrRtspClosedByObserver } resp := PackResponsePlay(requestCtx.Headers.Get(HeaderCSeq)) _, err := session.conn.Write([]byte(resp)) diff --git a/pkg/sdp/avconfig.go b/pkg/sdp/avconfig.go index 517d691..e4e9b86 100644 --- a/pkg/sdp/avconfig.go +++ b/pkg/sdp/avconfig.go @@ -13,20 +13,22 @@ import ( "encoding/hex" "strings" + "github.com/q191201771/naza/pkg/nazaerrors" + "github.com/q191201771/lal/pkg/base" ) func ParseAsc(a *AFmtPBase) ([]byte, error) { if a.Format != base.RtpPacketTypeAac { - return nil, ErrSdp + return nil, nazaerrors.Wrap(base.ErrSdp) } v, ok := a.Parameters["config"] if !ok { - return nil, ErrSdp + return nil, nazaerrors.Wrap(base.ErrSdp) } if len(v) < 4 || (len(v)%2) != 0 { - return nil, ErrSdp + return nil, nazaerrors.Wrap(base.ErrSdp) } return hex.DecodeString(v) } @@ -34,7 +36,7 @@ func ParseAsc(a *AFmtPBase) ([]byte, error) { func ParseVpsSpsPps(a *AFmtPBase) (vps, sps, pps []byte, err error) { v, ok := a.Parameters["sprop-vps"] if !ok { - return nil, nil, nil, ErrSdp + return nil, nil, nil, nazaerrors.Wrap(base.ErrSdp) } if vps, err = base64.StdEncoding.DecodeString(v); err != nil { return nil, nil, nil, err @@ -42,7 +44,7 @@ func ParseVpsSpsPps(a *AFmtPBase) (vps, sps, pps []byte, err error) { v, ok = a.Parameters["sprop-sps"] if !ok { - return nil, nil, nil, ErrSdp + return nil, nil, nil, nazaerrors.Wrap(base.ErrSdp) } if sps, err = base64.StdEncoding.DecodeString(v); err != nil { return nil, nil, nil, err @@ -50,7 +52,7 @@ func ParseVpsSpsPps(a *AFmtPBase) (vps, sps, pps []byte, err error) { v, ok = a.Parameters["sprop-pps"] if !ok { - return nil, nil, nil, ErrSdp + return nil, nil, nil, nazaerrors.Wrap(base.ErrSdp) } if pps, err = base64.StdEncoding.DecodeString(v); err != nil { return nil, nil, nil, err @@ -59,22 +61,25 @@ func ParseVpsSpsPps(a *AFmtPBase) (vps, sps, pps []byte, err error) { return } +// ParseSpsPps +// // 解析AVC/H264的sps,pps // 例子见单元测试 +// func ParseSpsPps(a *AFmtPBase) (sps, pps []byte, err error) { v, ok := a.Parameters["sprop-parameter-sets"] if !ok { - return nil, nil, ErrSdp + return nil, nil, nazaerrors.Wrap(base.ErrSdp) } items := strings.SplitN(v, ",", 2) if len(items) != 2 { - return nil, nil, ErrSdp + return nil, nil, nazaerrors.Wrap(base.ErrSdp) } sps, err = base64.StdEncoding.DecodeString(items[0]) if err != nil { - return nil, nil, ErrSdp + return nil, nil, nazaerrors.Wrap(base.ErrSdp) } pps, err = base64.StdEncoding.DecodeString(items[1]) diff --git a/pkg/sdp/pack.go b/pkg/sdp/pack.go index 7e20283..9babe9a 100644 --- a/pkg/sdp/pack.go +++ b/pkg/sdp/pack.go @@ -14,6 +14,8 @@ import ( "fmt" "strings" + "github.com/q191201771/naza/pkg/nazaerrors" + "github.com/q191201771/lal/pkg/aac" "github.com/q191201771/lal/pkg/base" @@ -33,7 +35,7 @@ func Pack(vps, sps, pps, asc []byte) (ctx LogicContext, err error) { } if !hasAudio && !hasVideo { - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) return } diff --git a/pkg/sdp/logic.go b/pkg/sdp/parse_logic.go similarity index 100% rename from pkg/sdp/logic.go rename to pkg/sdp/parse_logic.go diff --git a/pkg/sdp/raw.go b/pkg/sdp/parse_raw.go similarity index 92% rename from pkg/sdp/raw.go rename to pkg/sdp/parse_raw.go index d99b9a8..65d0311 100644 --- a/pkg/sdp/raw.go +++ b/pkg/sdp/parse_raw.go @@ -11,6 +11,9 @@ package sdp import ( "strconv" "strings" + + "github.com/q191201771/lal/pkg/base" + "github.com/q191201771/naza/pkg/nazaerrors" ) type RawContext struct { @@ -83,7 +86,7 @@ func ParseM(s string) (ret M, err error) { ss := strings.TrimPrefix(s, "m=") items := strings.Split(ss, " ") if len(items) < 1 { - return ret, ErrSdp + return ret, nazaerrors.Wrap(base.ErrSdp) } ret.Media = items[0] return @@ -99,12 +102,12 @@ func ParseARtpMap(s string) (ret ARtpMap, err error) { items := strings.SplitN(s, ":", 2) if len(items) != 2 { - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) return } items = strings.SplitN(items[1], " ", 2) if len(items) != 2 { - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) return } ret.PayloadType, err = strconv.Atoi(items[0]) @@ -123,7 +126,7 @@ func ParseARtpMap(s string) (ret ARtpMap, err error) { return } default: - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) } return } @@ -139,13 +142,13 @@ func ParseAFmtPBase(s string) (ret AFmtPBase, err error) { items := strings.SplitN(s, ":", 2) if len(items) != 2 { - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) return } items = strings.SplitN(items[1], " ", 2) if len(items) != 2 { - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) return } @@ -164,7 +167,7 @@ func ParseAFmtPBase(s string) (ret AFmtPBase, err error) { pp = strings.TrimSpace(pp) kv := strings.SplitN(pp, "=", 2) if len(kv) != 2 { - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) return } ret.Parameters[kv[0]] = kv[1] @@ -175,7 +178,7 @@ func ParseAFmtPBase(s string) (ret AFmtPBase, err error) { func ParseAControl(s string) (ret AControl, err error) { if !strings.HasPrefix(s, "a=control:") { - err = ErrSdp + err = nazaerrors.Wrap(base.ErrSdp) return } ret.Value = strings.TrimPrefix(s, "a=control:") diff --git a/pkg/sdp/parse_test.go b/pkg/sdp/parse_test.go new file mode 100644 index 0000000..79866bb --- /dev/null +++ b/pkg/sdp/parse_test.go @@ -0,0 +1,580 @@ +// 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 sdp + +import ( + "encoding/hex" + "strings" + "testing" + + "github.com/q191201771/lal/pkg/base" + + "github.com/q191201771/naza/pkg/nazalog" + + "github.com/q191201771/naza/pkg/assert" +) + +var goldenSdp = "v=0" + "\r\n" + + "o=- 0 0 IN IP6 ::1" + "\r\n" + + "s=No Name" + "\r\n" + + "c=IN IP6 ::1" + "\r\n" + + "t=0 0" + "\r\n" + + "a=tool:libavformat 57.83.100" + "\r\n" + + "m=video 0 RTP/AVP 96" + "\r\n" + + "b=AS:212" + "\r\n" + + "a=rtpmap:96 H264/90000" + "\r\n" + + "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=; profile-level-id=640020" + "\r\n" + + "a=control:streamid=0" + "\r\n" + + "m=audio 0 RTP/AVP 97" + "\r\n" + + "b=AS:30" + "\r\n" + + "a=rtpmap:97 MPEG4-GENERIC/44100/2" + "\r\n" + + "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1210" + "\r\n" + + "a=control:streamid=1" + "\r\n" + +var goldenSps = []byte{ + 0x67, 0x64, 0x00, 0x20, 0xAC, 0xD9, 0x40, 0xC0, 0x29, 0xB0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x32, 0x0F, 0x18, 0x31, 0x96, +} + +var goldenPps = []byte{ + 0x68, 0xEB, 0xEC, 0xB2, 0x2C, +} + +func TestParseSdp2RawContext(t *testing.T) { + sdpCtx, err := ParseSdp2RawContext([]byte(goldenSdp)) + assert.Equal(t, nil, err) + nazalog.Debugf("sdp=%+v", sdpCtx) +} + +func TestParseARtpMap(t *testing.T) { + golden := map[string]ARtpMap{ + "rtpmap:96 H264/90000": { + PayloadType: 96, + EncodingName: "H264", + ClockRate: 90000, + EncodingParameters: "", + }, + "rtpmap:97 MPEG4-GENERIC/44100/2": { + PayloadType: 97, + EncodingName: "MPEG4-GENERIC", + ClockRate: 44100, + EncodingParameters: "2", + }, + "a=rtpmap:96 H265/90000": { + PayloadType: 96, + EncodingName: "H265", + ClockRate: 90000, + EncodingParameters: "", + }, + } + for in, out := range golden { + actual, err := ParseARtpMap(in) + assert.Equal(t, nil, err) + assert.Equal(t, out, actual) + } +} + +func TestParseFmtPBase(t *testing.T) { + golden := map[string]AFmtPBase{ + "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=; profile-level-id=640020": { + Format: 96, + Parameters: map[string]string{ + "packetization-mode": "1", + "sprop-parameter-sets": "Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=", + "profile-level-id": "640020", + }, + }, + "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1210": { + Format: 97, + Parameters: map[string]string{ + "profile-level-id": "1", + "mode": "AAC-hbr", + "sizelength": "13", + "indexlength": "3", + "indexdeltalength": "3", + "config": "1210", + }, + }, + "a=fmtp:96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwA/ugJA; sprop-sps=QgEBAWAAAAMAkAAAAwAAAwA/oAUCAXHy5bpKTC8BAQAAAwABAAADAA8I; sprop-pps=RAHAc8GJ": { + Format: 96, + Parameters: map[string]string{ + "sprop-vps": "QAEMAf//AWAAAAMAkAAAAwAAAwA/ugJA", + "sprop-sps": "QgEBAWAAAAMAkAAAAwAAAwA/oAUCAXHy5bpKTC8BAQAAAwABAAADAA8I", + "sprop-pps": "RAHAc8GJ", + }, + }, + } + for in, out := range golden { + actual, err := ParseAFmtPBase(in) + assert.Equal(t, nil, err) + assert.Equal(t, out, actual) + } +} + +func TestParseSpsPps(t *testing.T) { + s := "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=; profile-level-id=640020" + f, err := ParseAFmtPBase(s) + assert.Equal(t, nil, err) + sps, pps, err := ParseSpsPps(&f) + assert.Equal(t, nil, err) + assert.Equal(t, goldenSps, sps) + assert.Equal(t, goldenPps, pps) +} + +func TestParseAsc(t *testing.T) { + s := "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1210" + f, err := ParseAFmtPBase(s) + assert.Equal(t, nil, err) + asc, err := ParseAsc(&f) + assert.Equal(t, nil, err) + assert.Equal(t, []byte{0x12, 0x10}, asc) +} + +// TODO chef 补充assert判断 +//[]byte{0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0x95, 0x98, 0x09} +//[]byte{0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0xa0, 0x05, 0x02, 0x01, 0x69, 0x65, 0x95, 0x9a, 0x49, 0x32, 0xbc, 0x04, 0x04, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x3c, 0x20} +//[]byte{0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40} +func TestParseVpsSpsPps(t *testing.T) { + s := "a=fmtp:96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwA/ugJA; sprop-sps=QgEBAWAAAAMAkAAAAwAAAwA/oAUCAXHy5bpKTC8BAQAAAwABAAADAA8I; sprop-pps=RAHAc8GJ" + f, err := ParseAFmtPBase(s) + assert.Equal(t, nil, err) + vps, sps, pps, err := ParseVpsSpsPps(&f) + assert.Equal(t, nil, err) + nazalog.Debugf("%s", hex.Dump(vps)) + nazalog.Debugf("%s", hex.Dump(sps)) + nazalog.Debugf("%s", hex.Dump(pps)) +} + +func TestParseSdp2LogicContext(t *testing.T) { + ctx, err := ParseSdp2LogicContext([]byte(goldenSdp)) + assert.Equal(t, nil, err) + assert.Equal(t, true, ctx.hasAudio) + assert.Equal(t, true, ctx.hasVideo) + assert.Equal(t, 44100, ctx.AudioClockRate) + assert.Equal(t, 90000, ctx.VideoClockRate) + assert.Equal(t, true, ctx.IsAudioPayloadTypeOrigin(97)) + assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(96)) + assert.Equal(t, base.AvPacketPtAac, ctx.GetAudioPayloadTypeBase()) + assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) + assert.Equal(t, "streamid=1", ctx.audioAControl) + assert.Equal(t, "streamid=0", ctx.videoAControl) + assert.IsNotNil(t, ctx.Asc) + assert.Equal(t, nil, ctx.Vps) + assert.IsNotNil(t, ctx.Sps) + assert.IsNotNil(t, ctx.Pps) +} + +func TestCase2(t *testing.T) { + golden := `v=0 +o=- 2252316233 2252316233 IN IP4 0.0.0.0 +s=Media Server +c=IN IP4 0.0.0.0 +t=0 0 +a=control:* +a=packetization-supported:DH +a=rtppayload-supported:DH +a=range:npt=now- +m=video 0 RTP/AVP 98 +a=control:trackID=0 +a=framerate:25.000000 +a=rtpmap:98 H265/90000 +a=fmtp:98 profile-id=1;sprop-sps=QgEBAWAAAAMAsAAAAwAAAwBaoAWCAJBY2uSTL5A=;sprop-pps=RAHA8vA8kA==;sprop-vps=QAEMAf//AWAAAAMAsAAAAwAAAwBarAk= +a=recvonly` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + assert.Equal(t, false, ctx.hasAudio) + assert.Equal(t, true, ctx.hasVideo) + assert.Equal(t, 90000, ctx.VideoClockRate) + assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(98)) + assert.Equal(t, base.AvPacketPtHevc, ctx.GetVideoPayloadTypeBase()) + assert.Equal(t, "trackID=0", ctx.videoAControl) + assert.Equal(t, nil, ctx.Asc) + assert.IsNotNil(t, ctx.Vps) + assert.IsNotNil(t, ctx.Sps) + assert.IsNotNil(t, ctx.Pps) + nazalog.Debugf("%+v", ctx) +} + +func TestCase3(t *testing.T) { + golden := `v=0 +o=- 2252310609 2252310609 IN IP4 0.0.0.0 +s=Media Server +c=IN IP4 0.0.0.0 +t=0 0 +a=control:* +a=packetization-supported:DH +a=rtppayload-supported:DH +a=range:npt=now- +m=video 0 RTP/AVP 96 +a=control:trackID=0 +a=framerate:25.000000 +a=rtpmap:96 H264/90000 +a=fmtp:96 packetization-mode=1;profile-level-id=4D002A;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAoAAAfQAAGGoQgAA==,aO48gAA= +a=recvonly +m=audio 0 RTP/AVP 97 +a=control:trackID=1 +a=rtpmap:97 MPEG4-GENERIC/48000 +a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1188 +a=recvonly` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + assert.Equal(t, true, ctx.hasAudio) + assert.Equal(t, true, ctx.hasVideo) + assert.Equal(t, 48000, ctx.AudioClockRate) + assert.Equal(t, 90000, ctx.VideoClockRate) + assert.Equal(t, true, ctx.IsAudioPayloadTypeOrigin(97)) + assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(96)) + assert.Equal(t, base.AvPacketPtAac, ctx.GetAudioPayloadTypeBase()) + assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) + assert.Equal(t, "trackID=1", ctx.audioAControl) + assert.Equal(t, "trackID=0", ctx.videoAControl) + assert.IsNotNil(t, ctx.Asc) + assert.Equal(t, nil, ctx.Vps) + assert.IsNotNil(t, ctx.Sps) + assert.IsNotNil(t, ctx.Pps) + nazalog.Debugf("%+v", ctx) +} + +func TestCase4(t *testing.T) { + golden := `v=0 +o=- 1109162014219182 0 IN IP4 0.0.0.0 +s=HIK Media Server V3.4.103 +i=HIK Media Server Session Description : standard +e=NONE +c=IN IP4 0.0.0.0 +t=0 0 +a=control:* +b=AS:1034 +a=range:npt=now- +m=video 0 RTP/AVP 96 +i=Video Media +a=rtpmap:96 H264/90000 +a=fmtp:96 profile-level-id=4D0014;packetization-mode=0;sprop-parameter-sets=Z2QAIK2EAQwgCGEAQwgCGEAQwgCEO1AoA803AQEBQAAAAwBAAAAMoQ==,aO48sA== +a=control:trackID=video +b=AS:1024 +m=audio 0 RTP/AVP 8 +i=Audio Media +a=rtpmap:8 PCMA/8000 +a=control:trackID=audio +b=AS:10 +a=Media_header:MEDIAINFO=494D4B48020100000400000111710110401F000000FA000000000000000000000000000000000000; +a=appversion:1.0` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + assert.Equal(t, true, ctx.hasAudio) + assert.Equal(t, true, ctx.hasVideo) + assert.Equal(t, 8000, ctx.AudioClockRate) + assert.Equal(t, 90000, ctx.VideoClockRate) + assert.Equal(t, true, ctx.IsAudioPayloadTypeOrigin(8)) + assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(96)) + //assert.Equal(t, base.AvPacketPtAac, ctx.AudioPayloadType) + assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) + assert.Equal(t, "trackID=audio", ctx.audioAControl) + assert.Equal(t, "trackID=video", ctx.videoAControl) + assert.Equal(t, nil, ctx.Asc) + assert.Equal(t, nil, ctx.Vps) + assert.IsNotNil(t, ctx.Sps) + assert.IsNotNil(t, ctx.Pps) + nazalog.Debugf("%+v", ctx) +} + +func TestCase5(t *testing.T) { + golden := `v=0 +o=- 1001 1 IN IP4 192.168.0.221 +s=VCP IPC Realtime stream +m=video 0 RTP/AVP 105 +c=IN IP4 192.168.0.221 +a=control:rtsp://192.168.0.221/media/video1/video +a=rtpmap:105 H264/90000 +a=fmtp:105 profile-level-id=64002a; packetization-mode=1; sprop-parameter-sets=Z2QAKq2EAQwgCGEAQwgCGEAQwgCEO1A8ARPyzcBAQFAAAD6AAAnECEA=,aO4xshs= +a=recvonly +m=application 0 RTP/AVP 107 +c=IN IP4 192.168.0.221 +a=control:rtsp://192.168.0.221/media/video1/metadata +a=rtpmap:107 vnd.onvif.metadata/90000 +a=fmtp:107 DecoderTag=h3c-v3 RTCP=0 +a=recvonly` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + assert.Equal(t, false, ctx.hasAudio) + assert.Equal(t, true, ctx.hasVideo) + assert.Equal(t, 90000, ctx.VideoClockRate) + assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(105)) + assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) + assert.Equal(t, "rtsp://192.168.0.221/media/video1/video", ctx.videoAControl) + assert.Equal(t, nil, ctx.Vps) + assert.IsNotNil(t, ctx.Sps) + assert.IsNotNil(t, ctx.Pps) + nazalog.Debugf("%+v", ctx) +} + +func TestCase6(t *testing.T) { + golden := `v=0 +o=- 1109162014219182 0 IN IP4 0.0.0.0 +s=HIK Media Server V3.4.96 +i=HIK Media Server Session Description : standard +e=NONE +c=IN IP4 0.0.0.0 +t=0 0 +a=control:* +b=AS:2058 +a=range:npt=now- +m=video 0 RTP/AVP 96 +i=Video Media +a=rtpmap:96 H265/90000 +a=control:trackID=video +b=AS:2048 +m=audio 0 RTP/AVP 8 +i=Audio Media +a=rtpmap:8 PCMA/8000 +a=control:trackID=audio +b=AS:10 +a=Media_header:MEDIAINFO=494D4B48020100000400050011710110401F000000FA000000000000000000000000000000000000; +a=appversion:1.0` + + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + assert.Equal(t, 8000, ctx.AudioClockRate) + assert.Equal(t, 90000, ctx.VideoClockRate) + assert.Equal(t, base.AvPacketPtUnknown, ctx.audioPayloadTypeBase) + assert.Equal(t, base.AvPacketPtHevc, ctx.videoPayloadTypeBase) + assert.Equal(t, 8, ctx.audioPayloadTypeOrigin) + assert.Equal(t, 96, ctx.videoPayloadTypeOrigin) + assert.Equal(t, "trackID=audio", ctx.audioAControl) + assert.Equal(t, "trackID=video", ctx.videoAControl) + assert.Equal(t, nil, ctx.Asc) + assert.Equal(t, nil, ctx.Vps) + assert.Equal(t, nil, ctx.Sps) + assert.Equal(t, nil, ctx.Pps) + assert.Equal(t, true, ctx.hasAudio) + assert.Equal(t, true, ctx.hasVideo) + nazalog.Debugf("%+v", ctx) +} + +// #85 +func TestCase7(t *testing.T) { + golden := `v=0 +o=- 1109162014219182 0 IN IP4 0.0.0.0 +s=HIK Media Server V3.4.106 +i=HIK Media Server Session Description : standard +e=NONE +c=IN IP4 0.0.0.0 +t=0 0 +a=control:* +b=AS:4106 +a=range:clock=20210520T063812Z-20210520T064816Z +m=video 0 RTP/AVP 96 +i=Video Media +a=rtpmap:96 H264/90000 +a=fmtp:96 profile-level-id=4D0014;packetization-mode=0 +a=control:trackID=video +b=AS:4096 +m=audio 0 RTP/AVP 98 +i=Audio Media +a=rtpmap:98 G7221/16000 +a=control:trackID=audio +b=AS:10 +a=Media_header:MEDIAINFO=494D4B48020100000400000121720110803E0000803E000000000000000000000000000000000000; +a=appversion:1.0 +` + + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + _ = ctx +} + +func TestCase8(t *testing.T) { + golden := `v=0 +o=- 1622201479405259 1622201479405259 IN IP4 192.168.3.58 +s=Media Presentation +e=NONE +b=AS:5100 +t=0 0 +a=control:rtsp://192.168.3.58:554/Streaming/Channels/101/?transportmode=unicast +m=video 0 RTP/AVP 96 +c=IN IP4 0.0.0.0 +b=AS:5000 +a=recvonly +a=x-dimensions:1920,1080 +a=control:rtsp://192.168.3.58:554/Streaming/Channels/101/trackID=1?transportmode=unicast +a=rtpmap:96 H265/90000 +m=audio 0 RTP/AVP 8 +c=IN IP4 0.0.0.0 +b=AS:50 +a=recvonly +a=control:rtsp://192.168.3.58:554/Streaming/Channels/101/trackID=2?transportmode=unicast +a=rtpmap:8 PCMA/8000 +a=Media_header:MEDIAINFO=494D4B48010200000400050011710110401F000000FA000000000000000000000000000000000000; +a=appversion:1.0 +` + + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + _ = ctx +} + +func TestCase9(t *testing.T) { + golden := `v=0 +o=- 13362557 1 IN IP4 192.168.1.100 +s=RTSP/RTP stream from VZ Smart-IPC +i=h264 +t=0 0 +a=tool:LIVE555 Streaming Media v2016.07.19 +a=type:broadcast +a=control:* +a=range:npt=0- +a=x-qt-text-nam:RTSP/RTP stream from VZ Smart-IPC +a=x-qt-text-inf:h264 +m=video 0 RTP/AVP 96 +c=IN IP4 0.0.0.0 +b=AS:4000 +a=rtpmap:96 H264/90000 +a=fmtp:96 packetization-mode=1;profile-level-id=000042;sprop-parameter-sets= +a=control:track1 +m=audio 0 RTP/AVP 0 +c=IN IP4 0.0.0.0 +b=AS:64 +a=control:track2 +m=vzinfo 0 RTP/AVP 108 +c=IN IP4 0.0.0.0 +b=AS:5 +a=rtpmap:108 VND.ONVIF.METADATA/90000 +a=control:track3 +` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + _ = ctx +} + +// sdp aac中a=fmtp缺少config字段,这个case的实际情况是后续也没有aac的rtp包 +func TestCase10(t *testing.T) { + golden := `v=0 +o=- 0 0 IN IP4 0.0.0.0 +s=rtsp_demo +t=0 0 +a=control:rtsp://10.10.10.188:554/stream0 +a=range:npt=0- +m=video 0 RTP/AVP 96 +c=IN IP4 0.0.0.0 +a=rtpmap:96 H264/90000 +a=fmtp:96 packetization-mode=1;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA== +a=control:rtsp://10.10.10.188:554/stream0/track1 +m=audio 0 RTP/AVP 97 +c=IN IP4 0.0.0.0 +a=rtpmap:97 MPEG4-GENERIC/44100/2 +a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3 +a=control:rtsp://10.10.10.188:554/stream0/track2 +` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + _ = ctx +} + +// `fmtp:96 `后多了个`;` +func TestCase11(t *testing.T) { + golden := `v=0 +o=- 3331435948 1116907222000 IN IP4 192.168.2.109 +s=Session +c=IN IP4 192.168.2.109 +t=0 0 +a=range:npt=now- +a=control:* +m=video 0 RTP/AVP 96 +a=control:trackID=0 +a=rtpmap:96 H264/90000 +a=fmtp:96 ;packetization-mode=1;sprop-parameter-sets=Z00AKpY1QPAET8s3AQEBQAABwgAAV+Qh,aO4xsg== +b=AS:5000 +` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + _ = ctx +} + +// `a=fmtp:104 `这行的尾部多了个`;` +func TestCase12(t *testing.T) { + golden := `v=0 +o=- 2733083813174 2733083813174 IN IP4 192.168.0.101 +s=Media Presentation +e=NONE +b=AS:5100 +t=0 0 +a=control:rtsp://192.168.0.102:554/LiveMedia/ch1/Media1/ +m=video 0 RTP/AVP 96 +c=IN IP4 0.0.0.0 +b=AS:5000 +a=recvonly +a=x-dimensions:1280,960 +a=control:rtsp://192.168.0.102:554/LiveMedia/ch1/Media1/trackID=1 +a=rtpmap:96 H264/90000 +a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z01AII2NQCgDz/gLcBAQFAAAD6AAAw1DoYAHmCu8uNDAA8wV3lwo,aO44gA== +m=audio 0 RTP/AVP 104 +c=IN IP4 0.0.0.0 +b=AS:50 +a=recvonly +a=control:rtsp://192.168.0.102:554/LiveMedia/ch1/Media1/trackID=2 +a=rtpmap:104 mpeg4-generic/16000/1 +a=fmtp:104 profile-level-id=15; streamtype=5; mode=AAC-hbr; config=1408;SizeLength=13; IndexLength=3; IndexDeltaLength=3; Profile=1; +a=Media_header:MEDIAINFO=494D4B48010200000400000101200110803E0000007D000000000000000000000000000000000000; +a=appversion:1.0 +` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + _ = ctx +} + +// `a=fmtp:96`这行被切割成了多行 +func TestCase13(t *testing.T) { + golden := `v=0 +o=- 16513881941979867928 16513881941979867928 IN IP4 HF-SW-P1-ROC +s=Unnamed +i=N/A +c=IN IP4 0.0.0.0 +t=0 0 +a=tool:vlc 3.0.16 +a=recvonly +a=type:broadcast +a=charset:UTF-8 +a=control:rtsp://127.0.0.1:8066/demo +m=video 0 RTP/AVP 96 +b=RR:0 +a=rtpmap:96 H265/90000 +a=fmtp:96 tx-mode=SRST;profile-id=1;level-id=3;tier-flag=0;profile-space=0;sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwBdlZgJ;sprop-sps=QgEBAWAAAAMAkAAAAwAAAwBdoAKAgC0WWVmq8rgE +AAADAlwAAEakIA==;sprop-pps=RAHBc9CJ;sprop-sei=TgEF/////////2Qsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMTUxKSAtIDIuNzpbV2luZG93c11bR0NDIDYuNC4wXVs2NCBiaXRdIDhiaXQgLSBILjI2NS +9IRVZDIGNvZGVjIC0gQ29weXJpZ2h0IDIwMTMtMjAxOCAoYykgTXVsdGljb3Jld2FyZSwgSW5jIC0gaHR0cDovL3gyNjUub3JnIC0gb3B0aW9uczogY3B1aWQ9MTE3MzUwMyBmcmFtZS10aHJlYWRzPTggbnVtYS1wb29scz +04IG5vLXdwcCBuby1wbW9kZSBuby1wbWUgbm8tcHNuciBuby1zc2ltIGxvZy1sZXZlbD0yIGJpdGRlcHRoPTggaW5wdXQtY3NwPTEgZnBzPTQ1MjEvMTUxIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC +1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MyBuby1hbGxvdy1ub24tY29uZm9ybWFuY2Ugbm8tcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1ocmQgaW5mbyBoYXNoPTAgbm +8tdGVtcG9yYWwtbGF5ZXJzIG9wZW4tZ29wIG1pbi1rZXlpbnQ9MjUga2V5aW50PTI1MCBnb3AtbG9va2FoZWFkPTAgYmZyYW1lcz00IGItYWRhcHQ9MiBiLXB5cmFtaWQgYmZyYW1lLWJpYXM9MCByYy1sb29rYWhlYWQ9Mj +AgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTQwIHJhZGw9MCBuby1pbnRyYS1yZWZyZXNoIGN0dT0xNiBtaW4tY3Utc2l6ZT04IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTE2IHR1LWludGVyLWRlcHRoPTEgdH +UtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBzaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cm +Egc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTMgbm8tbGltaXQtbW9kZXMgbWU9MSBzdWJtZT0yIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIHdlaWdodHAgbm8td2VpZ2h0YiBuby1hbm +FseXplLXNyYy1waWNzIGRlYmxvY2s9MDowIHNhbyBuby1zYW8tbm9uLWRlYmxvY2sgcmQ9MyBuby1lYXJseS1za2lwIHJza2lwIG5vLWZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludH +JhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPT +AuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIHBicmF0aW89MS4zMCBhcS1tb2RlPTEgYXEtc3RyZW5ndGg9MS4wMCBjdXRyZWUgem9uZS1jb3VudD0wIG5vLXN0cmljdC1jYn +IgcWctc2l6ZT0xNiBuby1yYy1ncmFpbiBxcG1heD02OSBxcG1pbj0wIG5vLWNvbnN0LXZidiBzYXI9MCBvdmVyc2Nhbj0wIHZpZGVvZm9ybWF0PTUgcmFuZ2U9MCBjb2xvcnByaW09MiB0cmFuc2Zlcj0yIGNvbG9ybWF0cm +l4PTIgY2hyb21hbG9jPTAgZGlzcGxheS13aW5kb3c9MCBtYXgtY2xsPTAsMCBtaW4tbHVtYT0wIG1heC1sdW1hPTI1NSBsb2cyLW1heC1wb2MtbHNiPTggdnVpLXRpbWluZy1pbmZvIHZ1aS1ocmQtaW5mbyBzbGljZXM9MS +Buby1vcHQtcXAtcHBzIG5vLW9wdC1yZWYtbGlzdC1sZW5ndGgtcHBzIG5vLW11bHRpLXBhc3Mtb3B0LXJwcyBzY2VuZWN1dC1iaWFzPTAuMDUgbm8tb3B0LWN1LWRlbHRhLXFwIG5vLWFxLW1vdGlvbiBuby1oZHIgbm8taG +RyLW9wdCBuby1kaGRyMTAtb3B0IGFuYWx5c2lzLXJldXNlLWxldmVsPTUgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm +8tbG93cGFzcy1kY3QgcmVmaW5lLW12LXR5cGU9MCBjb3B5LXBpYz0xgA==; +a=control:rtsp://127.0.0.1:8066/demo/trackID=0 +` + golden = strings.ReplaceAll(golden, "\n", "\r\n") + ctx, err := ParseSdp2LogicContext([]byte(golden)) + assert.Equal(t, nil, err) + _ = ctx +} diff --git a/pkg/sdp/sdp.go b/pkg/sdp/sdp.go index 6c3b5e4..30e7c6f 100644 --- a/pkg/sdp/sdp.go +++ b/pkg/sdp/sdp.go @@ -8,14 +8,8 @@ package sdp -import ( - "errors" -) - // rfc4566 -var ErrSdp = errors.New("lal.sdp: fxxk") - const ( ARtpMapEncodingNameH265 = "H265" ARtpMapEncodingNameH264 = "H264" diff --git a/pkg/sdp/sdp_test.go b/pkg/sdp/sdp_test.go index 79866bb..5802bf1 100644 --- a/pkg/sdp/sdp_test.go +++ b/pkg/sdp/sdp_test.go @@ -1,4 +1,4 @@ -// Copyright 2020, Chef. All rights reserved. +// Copyright 2021, Chef. All rights reserved. // https://github.com/q191201771/lal // // Use of this source code is governed by a MIT-style license @@ -6,575 +6,14 @@ // // Author: Chef (191201771@qq.com) -package sdp +package sdp_test import ( - "encoding/hex" - "strings" "testing" - "github.com/q191201771/lal/pkg/base" - - "github.com/q191201771/naza/pkg/nazalog" - - "github.com/q191201771/naza/pkg/assert" + "github.com/q191201771/lal/pkg/innertest" ) -var goldenSdp = "v=0" + "\r\n" + - "o=- 0 0 IN IP6 ::1" + "\r\n" + - "s=No Name" + "\r\n" + - "c=IN IP6 ::1" + "\r\n" + - "t=0 0" + "\r\n" + - "a=tool:libavformat 57.83.100" + "\r\n" + - "m=video 0 RTP/AVP 96" + "\r\n" + - "b=AS:212" + "\r\n" + - "a=rtpmap:96 H264/90000" + "\r\n" + - "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=; profile-level-id=640020" + "\r\n" + - "a=control:streamid=0" + "\r\n" + - "m=audio 0 RTP/AVP 97" + "\r\n" + - "b=AS:30" + "\r\n" + - "a=rtpmap:97 MPEG4-GENERIC/44100/2" + "\r\n" + - "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1210" + "\r\n" + - "a=control:streamid=1" + "\r\n" - -var goldenSps = []byte{ - 0x67, 0x64, 0x00, 0x20, 0xAC, 0xD9, 0x40, 0xC0, 0x29, 0xB0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x32, 0x0F, 0x18, 0x31, 0x96, -} - -var goldenPps = []byte{ - 0x68, 0xEB, 0xEC, 0xB2, 0x2C, -} - -func TestParseSdp2RawContext(t *testing.T) { - sdpCtx, err := ParseSdp2RawContext([]byte(goldenSdp)) - assert.Equal(t, nil, err) - nazalog.Debugf("sdp=%+v", sdpCtx) -} - -func TestParseARtpMap(t *testing.T) { - golden := map[string]ARtpMap{ - "rtpmap:96 H264/90000": { - PayloadType: 96, - EncodingName: "H264", - ClockRate: 90000, - EncodingParameters: "", - }, - "rtpmap:97 MPEG4-GENERIC/44100/2": { - PayloadType: 97, - EncodingName: "MPEG4-GENERIC", - ClockRate: 44100, - EncodingParameters: "2", - }, - "a=rtpmap:96 H265/90000": { - PayloadType: 96, - EncodingName: "H265", - ClockRate: 90000, - EncodingParameters: "", - }, - } - for in, out := range golden { - actual, err := ParseARtpMap(in) - assert.Equal(t, nil, err) - assert.Equal(t, out, actual) - } -} - -func TestParseFmtPBase(t *testing.T) { - golden := map[string]AFmtPBase{ - "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=; profile-level-id=640020": { - Format: 96, - Parameters: map[string]string{ - "packetization-mode": "1", - "sprop-parameter-sets": "Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=", - "profile-level-id": "640020", - }, - }, - "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1210": { - Format: 97, - Parameters: map[string]string{ - "profile-level-id": "1", - "mode": "AAC-hbr", - "sizelength": "13", - "indexlength": "3", - "indexdeltalength": "3", - "config": "1210", - }, - }, - "a=fmtp:96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwA/ugJA; sprop-sps=QgEBAWAAAAMAkAAAAwAAAwA/oAUCAXHy5bpKTC8BAQAAAwABAAADAA8I; sprop-pps=RAHAc8GJ": { - Format: 96, - Parameters: map[string]string{ - "sprop-vps": "QAEMAf//AWAAAAMAkAAAAwAAAwA/ugJA", - "sprop-sps": "QgEBAWAAAAMAkAAAAwAAAwA/oAUCAXHy5bpKTC8BAQAAAwABAAADAA8I", - "sprop-pps": "RAHAc8GJ", - }, - }, - } - for in, out := range golden { - actual, err := ParseAFmtPBase(in) - assert.Equal(t, nil, err) - assert.Equal(t, out, actual) - } -} - -func TestParseSpsPps(t *testing.T) { - s := "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAIKzZQMApsBEAAAMAAQAAAwAyDxgxlg==,aOvssiw=; profile-level-id=640020" - f, err := ParseAFmtPBase(s) - assert.Equal(t, nil, err) - sps, pps, err := ParseSpsPps(&f) - assert.Equal(t, nil, err) - assert.Equal(t, goldenSps, sps) - assert.Equal(t, goldenPps, pps) -} - -func TestParseAsc(t *testing.T) { - s := "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1210" - f, err := ParseAFmtPBase(s) - assert.Equal(t, nil, err) - asc, err := ParseAsc(&f) - assert.Equal(t, nil, err) - assert.Equal(t, []byte{0x12, 0x10}, asc) -} - -// TODO chef 补充assert判断 -//[]byte{0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0x95, 0x98, 0x09} -//[]byte{0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0xa0, 0x05, 0x02, 0x01, 0x69, 0x65, 0x95, 0x9a, 0x49, 0x32, 0xbc, 0x04, 0x04, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x3c, 0x20} -//[]byte{0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40} -func TestParseVpsSpsPps(t *testing.T) { - s := "a=fmtp:96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwA/ugJA; sprop-sps=QgEBAWAAAAMAkAAAAwAAAwA/oAUCAXHy5bpKTC8BAQAAAwABAAADAA8I; sprop-pps=RAHAc8GJ" - f, err := ParseAFmtPBase(s) - assert.Equal(t, nil, err) - vps, sps, pps, err := ParseVpsSpsPps(&f) - assert.Equal(t, nil, err) - nazalog.Debugf("%s", hex.Dump(vps)) - nazalog.Debugf("%s", hex.Dump(sps)) - nazalog.Debugf("%s", hex.Dump(pps)) -} - -func TestParseSdp2LogicContext(t *testing.T) { - ctx, err := ParseSdp2LogicContext([]byte(goldenSdp)) - assert.Equal(t, nil, err) - assert.Equal(t, true, ctx.hasAudio) - assert.Equal(t, true, ctx.hasVideo) - assert.Equal(t, 44100, ctx.AudioClockRate) - assert.Equal(t, 90000, ctx.VideoClockRate) - assert.Equal(t, true, ctx.IsAudioPayloadTypeOrigin(97)) - assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(96)) - assert.Equal(t, base.AvPacketPtAac, ctx.GetAudioPayloadTypeBase()) - assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) - assert.Equal(t, "streamid=1", ctx.audioAControl) - assert.Equal(t, "streamid=0", ctx.videoAControl) - assert.IsNotNil(t, ctx.Asc) - assert.Equal(t, nil, ctx.Vps) - assert.IsNotNil(t, ctx.Sps) - assert.IsNotNil(t, ctx.Pps) -} - -func TestCase2(t *testing.T) { - golden := `v=0 -o=- 2252316233 2252316233 IN IP4 0.0.0.0 -s=Media Server -c=IN IP4 0.0.0.0 -t=0 0 -a=control:* -a=packetization-supported:DH -a=rtppayload-supported:DH -a=range:npt=now- -m=video 0 RTP/AVP 98 -a=control:trackID=0 -a=framerate:25.000000 -a=rtpmap:98 H265/90000 -a=fmtp:98 profile-id=1;sprop-sps=QgEBAWAAAAMAsAAAAwAAAwBaoAWCAJBY2uSTL5A=;sprop-pps=RAHA8vA8kA==;sprop-vps=QAEMAf//AWAAAAMAsAAAAwAAAwBarAk= -a=recvonly` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - assert.Equal(t, false, ctx.hasAudio) - assert.Equal(t, true, ctx.hasVideo) - assert.Equal(t, 90000, ctx.VideoClockRate) - assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(98)) - assert.Equal(t, base.AvPacketPtHevc, ctx.GetVideoPayloadTypeBase()) - assert.Equal(t, "trackID=0", ctx.videoAControl) - assert.Equal(t, nil, ctx.Asc) - assert.IsNotNil(t, ctx.Vps) - assert.IsNotNil(t, ctx.Sps) - assert.IsNotNil(t, ctx.Pps) - nazalog.Debugf("%+v", ctx) -} - -func TestCase3(t *testing.T) { - golden := `v=0 -o=- 2252310609 2252310609 IN IP4 0.0.0.0 -s=Media Server -c=IN IP4 0.0.0.0 -t=0 0 -a=control:* -a=packetization-supported:DH -a=rtppayload-supported:DH -a=range:npt=now- -m=video 0 RTP/AVP 96 -a=control:trackID=0 -a=framerate:25.000000 -a=rtpmap:96 H264/90000 -a=fmtp:96 packetization-mode=1;profile-level-id=4D002A;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAoAAAfQAAGGoQgAA==,aO48gAA= -a=recvonly -m=audio 0 RTP/AVP 97 -a=control:trackID=1 -a=rtpmap:97 MPEG4-GENERIC/48000 -a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1188 -a=recvonly` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - assert.Equal(t, true, ctx.hasAudio) - assert.Equal(t, true, ctx.hasVideo) - assert.Equal(t, 48000, ctx.AudioClockRate) - assert.Equal(t, 90000, ctx.VideoClockRate) - assert.Equal(t, true, ctx.IsAudioPayloadTypeOrigin(97)) - assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(96)) - assert.Equal(t, base.AvPacketPtAac, ctx.GetAudioPayloadTypeBase()) - assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) - assert.Equal(t, "trackID=1", ctx.audioAControl) - assert.Equal(t, "trackID=0", ctx.videoAControl) - assert.IsNotNil(t, ctx.Asc) - assert.Equal(t, nil, ctx.Vps) - assert.IsNotNil(t, ctx.Sps) - assert.IsNotNil(t, ctx.Pps) - nazalog.Debugf("%+v", ctx) -} - -func TestCase4(t *testing.T) { - golden := `v=0 -o=- 1109162014219182 0 IN IP4 0.0.0.0 -s=HIK Media Server V3.4.103 -i=HIK Media Server Session Description : standard -e=NONE -c=IN IP4 0.0.0.0 -t=0 0 -a=control:* -b=AS:1034 -a=range:npt=now- -m=video 0 RTP/AVP 96 -i=Video Media -a=rtpmap:96 H264/90000 -a=fmtp:96 profile-level-id=4D0014;packetization-mode=0;sprop-parameter-sets=Z2QAIK2EAQwgCGEAQwgCGEAQwgCEO1AoA803AQEBQAAAAwBAAAAMoQ==,aO48sA== -a=control:trackID=video -b=AS:1024 -m=audio 0 RTP/AVP 8 -i=Audio Media -a=rtpmap:8 PCMA/8000 -a=control:trackID=audio -b=AS:10 -a=Media_header:MEDIAINFO=494D4B48020100000400000111710110401F000000FA000000000000000000000000000000000000; -a=appversion:1.0` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - assert.Equal(t, true, ctx.hasAudio) - assert.Equal(t, true, ctx.hasVideo) - assert.Equal(t, 8000, ctx.AudioClockRate) - assert.Equal(t, 90000, ctx.VideoClockRate) - assert.Equal(t, true, ctx.IsAudioPayloadTypeOrigin(8)) - assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(96)) - //assert.Equal(t, base.AvPacketPtAac, ctx.AudioPayloadType) - assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) - assert.Equal(t, "trackID=audio", ctx.audioAControl) - assert.Equal(t, "trackID=video", ctx.videoAControl) - assert.Equal(t, nil, ctx.Asc) - assert.Equal(t, nil, ctx.Vps) - assert.IsNotNil(t, ctx.Sps) - assert.IsNotNil(t, ctx.Pps) - nazalog.Debugf("%+v", ctx) -} - -func TestCase5(t *testing.T) { - golden := `v=0 -o=- 1001 1 IN IP4 192.168.0.221 -s=VCP IPC Realtime stream -m=video 0 RTP/AVP 105 -c=IN IP4 192.168.0.221 -a=control:rtsp://192.168.0.221/media/video1/video -a=rtpmap:105 H264/90000 -a=fmtp:105 profile-level-id=64002a; packetization-mode=1; sprop-parameter-sets=Z2QAKq2EAQwgCGEAQwgCGEAQwgCEO1A8ARPyzcBAQFAAAD6AAAnECEA=,aO4xshs= -a=recvonly -m=application 0 RTP/AVP 107 -c=IN IP4 192.168.0.221 -a=control:rtsp://192.168.0.221/media/video1/metadata -a=rtpmap:107 vnd.onvif.metadata/90000 -a=fmtp:107 DecoderTag=h3c-v3 RTCP=0 -a=recvonly` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - assert.Equal(t, false, ctx.hasAudio) - assert.Equal(t, true, ctx.hasVideo) - assert.Equal(t, 90000, ctx.VideoClockRate) - assert.Equal(t, true, ctx.IsVideoPayloadTypeOrigin(105)) - assert.Equal(t, base.AvPacketPtAvc, ctx.GetVideoPayloadTypeBase()) - assert.Equal(t, "rtsp://192.168.0.221/media/video1/video", ctx.videoAControl) - assert.Equal(t, nil, ctx.Vps) - assert.IsNotNil(t, ctx.Sps) - assert.IsNotNil(t, ctx.Pps) - nazalog.Debugf("%+v", ctx) -} - -func TestCase6(t *testing.T) { - golden := `v=0 -o=- 1109162014219182 0 IN IP4 0.0.0.0 -s=HIK Media Server V3.4.96 -i=HIK Media Server Session Description : standard -e=NONE -c=IN IP4 0.0.0.0 -t=0 0 -a=control:* -b=AS:2058 -a=range:npt=now- -m=video 0 RTP/AVP 96 -i=Video Media -a=rtpmap:96 H265/90000 -a=control:trackID=video -b=AS:2048 -m=audio 0 RTP/AVP 8 -i=Audio Media -a=rtpmap:8 PCMA/8000 -a=control:trackID=audio -b=AS:10 -a=Media_header:MEDIAINFO=494D4B48020100000400050011710110401F000000FA000000000000000000000000000000000000; -a=appversion:1.0` - - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - assert.Equal(t, 8000, ctx.AudioClockRate) - assert.Equal(t, 90000, ctx.VideoClockRate) - assert.Equal(t, base.AvPacketPtUnknown, ctx.audioPayloadTypeBase) - assert.Equal(t, base.AvPacketPtHevc, ctx.videoPayloadTypeBase) - assert.Equal(t, 8, ctx.audioPayloadTypeOrigin) - assert.Equal(t, 96, ctx.videoPayloadTypeOrigin) - assert.Equal(t, "trackID=audio", ctx.audioAControl) - assert.Equal(t, "trackID=video", ctx.videoAControl) - assert.Equal(t, nil, ctx.Asc) - assert.Equal(t, nil, ctx.Vps) - assert.Equal(t, nil, ctx.Sps) - assert.Equal(t, nil, ctx.Pps) - assert.Equal(t, true, ctx.hasAudio) - assert.Equal(t, true, ctx.hasVideo) - nazalog.Debugf("%+v", ctx) -} - -// #85 -func TestCase7(t *testing.T) { - golden := `v=0 -o=- 1109162014219182 0 IN IP4 0.0.0.0 -s=HIK Media Server V3.4.106 -i=HIK Media Server Session Description : standard -e=NONE -c=IN IP4 0.0.0.0 -t=0 0 -a=control:* -b=AS:4106 -a=range:clock=20210520T063812Z-20210520T064816Z -m=video 0 RTP/AVP 96 -i=Video Media -a=rtpmap:96 H264/90000 -a=fmtp:96 profile-level-id=4D0014;packetization-mode=0 -a=control:trackID=video -b=AS:4096 -m=audio 0 RTP/AVP 98 -i=Audio Media -a=rtpmap:98 G7221/16000 -a=control:trackID=audio -b=AS:10 -a=Media_header:MEDIAINFO=494D4B48020100000400000121720110803E0000803E000000000000000000000000000000000000; -a=appversion:1.0 -` - - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - _ = ctx -} - -func TestCase8(t *testing.T) { - golden := `v=0 -o=- 1622201479405259 1622201479405259 IN IP4 192.168.3.58 -s=Media Presentation -e=NONE -b=AS:5100 -t=0 0 -a=control:rtsp://192.168.3.58:554/Streaming/Channels/101/?transportmode=unicast -m=video 0 RTP/AVP 96 -c=IN IP4 0.0.0.0 -b=AS:5000 -a=recvonly -a=x-dimensions:1920,1080 -a=control:rtsp://192.168.3.58:554/Streaming/Channels/101/trackID=1?transportmode=unicast -a=rtpmap:96 H265/90000 -m=audio 0 RTP/AVP 8 -c=IN IP4 0.0.0.0 -b=AS:50 -a=recvonly -a=control:rtsp://192.168.3.58:554/Streaming/Channels/101/trackID=2?transportmode=unicast -a=rtpmap:8 PCMA/8000 -a=Media_header:MEDIAINFO=494D4B48010200000400050011710110401F000000FA000000000000000000000000000000000000; -a=appversion:1.0 -` - - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - _ = ctx -} - -func TestCase9(t *testing.T) { - golden := `v=0 -o=- 13362557 1 IN IP4 192.168.1.100 -s=RTSP/RTP stream from VZ Smart-IPC -i=h264 -t=0 0 -a=tool:LIVE555 Streaming Media v2016.07.19 -a=type:broadcast -a=control:* -a=range:npt=0- -a=x-qt-text-nam:RTSP/RTP stream from VZ Smart-IPC -a=x-qt-text-inf:h264 -m=video 0 RTP/AVP 96 -c=IN IP4 0.0.0.0 -b=AS:4000 -a=rtpmap:96 H264/90000 -a=fmtp:96 packetization-mode=1;profile-level-id=000042;sprop-parameter-sets= -a=control:track1 -m=audio 0 RTP/AVP 0 -c=IN IP4 0.0.0.0 -b=AS:64 -a=control:track2 -m=vzinfo 0 RTP/AVP 108 -c=IN IP4 0.0.0.0 -b=AS:5 -a=rtpmap:108 VND.ONVIF.METADATA/90000 -a=control:track3 -` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - _ = ctx -} - -// sdp aac中a=fmtp缺少config字段,这个case的实际情况是后续也没有aac的rtp包 -func TestCase10(t *testing.T) { - golden := `v=0 -o=- 0 0 IN IP4 0.0.0.0 -s=rtsp_demo -t=0 0 -a=control:rtsp://10.10.10.188:554/stream0 -a=range:npt=0- -m=video 0 RTP/AVP 96 -c=IN IP4 0.0.0.0 -a=rtpmap:96 H264/90000 -a=fmtp:96 packetization-mode=1;sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA== -a=control:rtsp://10.10.10.188:554/stream0/track1 -m=audio 0 RTP/AVP 97 -c=IN IP4 0.0.0.0 -a=rtpmap:97 MPEG4-GENERIC/44100/2 -a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3 -a=control:rtsp://10.10.10.188:554/stream0/track2 -` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - _ = ctx -} - -// `fmtp:96 `后多了个`;` -func TestCase11(t *testing.T) { - golden := `v=0 -o=- 3331435948 1116907222000 IN IP4 192.168.2.109 -s=Session -c=IN IP4 192.168.2.109 -t=0 0 -a=range:npt=now- -a=control:* -m=video 0 RTP/AVP 96 -a=control:trackID=0 -a=rtpmap:96 H264/90000 -a=fmtp:96 ;packetization-mode=1;sprop-parameter-sets=Z00AKpY1QPAET8s3AQEBQAABwgAAV+Qh,aO4xsg== -b=AS:5000 -` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - _ = ctx -} - -// `a=fmtp:104 `这行的尾部多了个`;` -func TestCase12(t *testing.T) { - golden := `v=0 -o=- 2733083813174 2733083813174 IN IP4 192.168.0.101 -s=Media Presentation -e=NONE -b=AS:5100 -t=0 0 -a=control:rtsp://192.168.0.102:554/LiveMedia/ch1/Media1/ -m=video 0 RTP/AVP 96 -c=IN IP4 0.0.0.0 -b=AS:5000 -a=recvonly -a=x-dimensions:1280,960 -a=control:rtsp://192.168.0.102:554/LiveMedia/ch1/Media1/trackID=1 -a=rtpmap:96 H264/90000 -a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z01AII2NQCgDz/gLcBAQFAAAD6AAAw1DoYAHmCu8uNDAA8wV3lwo,aO44gA== -m=audio 0 RTP/AVP 104 -c=IN IP4 0.0.0.0 -b=AS:50 -a=recvonly -a=control:rtsp://192.168.0.102:554/LiveMedia/ch1/Media1/trackID=2 -a=rtpmap:104 mpeg4-generic/16000/1 -a=fmtp:104 profile-level-id=15; streamtype=5; mode=AAC-hbr; config=1408;SizeLength=13; IndexLength=3; IndexDeltaLength=3; Profile=1; -a=Media_header:MEDIAINFO=494D4B48010200000400000101200110803E0000007D000000000000000000000000000000000000; -a=appversion:1.0 -` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - _ = ctx -} - -// `a=fmtp:96`这行被切割成了多行 -func TestCase13(t *testing.T) { - golden := `v=0 -o=- 16513881941979867928 16513881941979867928 IN IP4 HF-SW-P1-ROC -s=Unnamed -i=N/A -c=IN IP4 0.0.0.0 -t=0 0 -a=tool:vlc 3.0.16 -a=recvonly -a=type:broadcast -a=charset:UTF-8 -a=control:rtsp://127.0.0.1:8066/demo -m=video 0 RTP/AVP 96 -b=RR:0 -a=rtpmap:96 H265/90000 -a=fmtp:96 tx-mode=SRST;profile-id=1;level-id=3;tier-flag=0;profile-space=0;sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwBdlZgJ;sprop-sps=QgEBAWAAAAMAkAAAAwAAAwBdoAKAgC0WWVmq8rgE -AAADAlwAAEakIA==;sprop-pps=RAHBc9CJ;sprop-sei=TgEF/////////2Qsot4JtRdH27tVpP5/wvxOeDI2NSAoYnVpbGQgMTUxKSAtIDIuNzpbV2luZG93c11bR0NDIDYuNC4wXVs2NCBiaXRdIDhiaXQgLSBILjI2NS -9IRVZDIGNvZGVjIC0gQ29weXJpZ2h0IDIwMTMtMjAxOCAoYykgTXVsdGljb3Jld2FyZSwgSW5jIC0gaHR0cDovL3gyNjUub3JnIC0gb3B0aW9uczogY3B1aWQ9MTE3MzUwMyBmcmFtZS10aHJlYWRzPTggbnVtYS1wb29scz -04IG5vLXdwcCBuby1wbW9kZSBuby1wbWUgbm8tcHNuciBuby1zc2ltIGxvZy1sZXZlbD0yIGJpdGRlcHRoPTggaW5wdXQtY3NwPTEgZnBzPTQ1MjEvMTUxIGlucHV0LXJlcz0xMjgweDcyMCBpbnRlcmxhY2U9MCB0b3RhbC -1mcmFtZXM9MCBsZXZlbC1pZGM9MCBoaWdoLXRpZXI9MSB1aGQtYmQ9MCByZWY9MyBuby1hbGxvdy1ub24tY29uZm9ybWFuY2Ugbm8tcmVwZWF0LWhlYWRlcnMgYW5uZXhiIG5vLWF1ZCBuby1ocmQgaW5mbyBoYXNoPTAgbm -8tdGVtcG9yYWwtbGF5ZXJzIG9wZW4tZ29wIG1pbi1rZXlpbnQ9MjUga2V5aW50PTI1MCBnb3AtbG9va2FoZWFkPTAgYmZyYW1lcz00IGItYWRhcHQ9MiBiLXB5cmFtaWQgYmZyYW1lLWJpYXM9MCByYy1sb29rYWhlYWQ9Mj -AgbG9va2FoZWFkLXNsaWNlcz00IHNjZW5lY3V0PTQwIHJhZGw9MCBuby1pbnRyYS1yZWZyZXNoIGN0dT0xNiBtaW4tY3Utc2l6ZT04IG5vLXJlY3Qgbm8tYW1wIG1heC10dS1zaXplPTE2IHR1LWludGVyLWRlcHRoPTEgdH -UtaW50cmEtZGVwdGg9MSBsaW1pdC10dT0wIHJkb3EtbGV2ZWw9MCBkeW5hbWljLXJkPTAuMDAgbm8tc3NpbS1yZCBzaWduaGlkZSBuby10c2tpcCBuci1pbnRyYT0wIG5yLWludGVyPTAgbm8tY29uc3RyYWluZWQtaW50cm -Egc3Ryb25nLWludHJhLXNtb290aGluZyBtYXgtbWVyZ2U9MiBsaW1pdC1yZWZzPTMgbm8tbGltaXQtbW9kZXMgbWU9MSBzdWJtZT0yIG1lcmFuZ2U9NTcgdGVtcG9yYWwtbXZwIHdlaWdodHAgbm8td2VpZ2h0YiBuby1hbm -FseXplLXNyYy1waWNzIGRlYmxvY2s9MDowIHNhbyBuby1zYW8tbm9uLWRlYmxvY2sgcmQ9MyBuby1lYXJseS1za2lwIHJza2lwIG5vLWZhc3QtaW50cmEgbm8tdHNraXAtZmFzdCBuby1jdS1sb3NzbGVzcyBuby1iLWludH -JhIG5vLXNwbGl0cmQtc2tpcCByZHBlbmFsdHk9MCBwc3ktcmQ9Mi4wMCBwc3ktcmRvcT0wLjAwIG5vLXJkLXJlZmluZSBuby1sb3NzbGVzcyBjYnFwb2Zmcz0wIGNycXBvZmZzPTAgcmM9Y3JmIGNyZj0yOC4wIHFjb21wPT -AuNjAgcXBzdGVwPTQgc3RhdHMtd3JpdGU9MCBzdGF0cy1yZWFkPTAgaXByYXRpbz0xLjQwIHBicmF0aW89MS4zMCBhcS1tb2RlPTEgYXEtc3RyZW5ndGg9MS4wMCBjdXRyZWUgem9uZS1jb3VudD0wIG5vLXN0cmljdC1jYn -IgcWctc2l6ZT0xNiBuby1yYy1ncmFpbiBxcG1heD02OSBxcG1pbj0wIG5vLWNvbnN0LXZidiBzYXI9MCBvdmVyc2Nhbj0wIHZpZGVvZm9ybWF0PTUgcmFuZ2U9MCBjb2xvcnByaW09MiB0cmFuc2Zlcj0yIGNvbG9ybWF0cm -l4PTIgY2hyb21hbG9jPTAgZGlzcGxheS13aW5kb3c9MCBtYXgtY2xsPTAsMCBtaW4tbHVtYT0wIG1heC1sdW1hPTI1NSBsb2cyLW1heC1wb2MtbHNiPTggdnVpLXRpbWluZy1pbmZvIHZ1aS1ocmQtaW5mbyBzbGljZXM9MS -Buby1vcHQtcXAtcHBzIG5vLW9wdC1yZWYtbGlzdC1sZW5ndGgtcHBzIG5vLW11bHRpLXBhc3Mtb3B0LXJwcyBzY2VuZWN1dC1iaWFzPTAuMDUgbm8tb3B0LWN1LWRlbHRhLXFwIG5vLWFxLW1vdGlvbiBuby1oZHIgbm8taG -RyLW9wdCBuby1kaGRyMTAtb3B0IGFuYWx5c2lzLXJldXNlLWxldmVsPTUgc2NhbGUtZmFjdG9yPTAgcmVmaW5lLWludHJhPTAgcmVmaW5lLWludGVyPTAgcmVmaW5lLW12PTAgbm8tbGltaXQtc2FvIGN0dS1pbmZvPTAgbm -8tbG93cGFzcy1kY3QgcmVmaW5lLW12LXR5cGU9MCBjb3B5LXBpYz0xgA==; -a=control:rtsp://127.0.0.1:8066/demo/trackID=0 -` - golden = strings.ReplaceAll(golden, "\n", "\r\n") - ctx, err := ParseSdp2LogicContext([]byte(golden)) - assert.Equal(t, nil, err) - _ = ctx +func TestSdp(t *testing.T) { + innertest.Entry(t) }