1. rtmp.PushSession和PullSession可配置WriteBuf和ReadBuf大小,以及WriteChanSize 2. 整理完所有error返回值 3. 提高测试覆盖率

pull/114/head
q191201771 3 years ago
parent 83aa44eebe
commit 03ccfa0e5f

@ -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) {

@ -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 {

@ -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)

@ -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)
}

@ -15,6 +15,7 @@ type ILalServer interface {
Dispose()
// StatLalInfo StatXxx... CtrlXxx...
//
// 一些获取状态、发送控制命令的API
// 目的是方便业务方在不修改logic包内代码的前提下在外层实现一些特定逻辑的定制化开发
//

@ -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())

@ -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
}),
}

@ -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
}),
}

@ -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)

@ -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:

@ -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由对端决定和本变量没有关系
//

@ -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

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

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

@ -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)
}

@ -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)

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

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

@ -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 {

@ -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)
}

@ -31,8 +31,11 @@ type ServerObserver interface {
///////////////////////////////////////////////////////////////////////////
// OnNewRtspSubSessionDescribe
//
// @return 如果返回false则表示上层要强制关闭这个拉流请求
// @return sdp
//
OnNewRtspSubSessionDescribe(session *SubSession) (ok bool, sdp []byte)
// @brief Describe阶段回调

@ -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))

@ -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的spspps
// 例子见单元测试
//
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])

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

@ -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:")

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

@ -8,14 +8,8 @@
package sdp
import (
"errors"
)
// rfc4566
var ErrSdp = errors.New("lal.sdp: fxxk")
const (
ARtpMapEncodingNameH265 = "H265"
ARtpMapEncodingNameH264 = "H264"

@ -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)
}

Loading…
Cancel
Save