From e5845e2d3b270148e37dae292e3cfbdeafe5ed37 Mon Sep 17 00:00:00 2001 From: q191201771 <191201771@qq.com> Date: Sat, 19 Sep 2020 22:14:12 +0800 Subject: [PATCH] =?UTF-8?q?1.=20[fix]=20=E4=BF=AE=E5=A4=8Drtsp=20pub?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=8E=A5=E6=94=B6IPv6=20RTP=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84=E9=97=AE=E9=A2=98=202.=20[feat]=20=E9=83=A8?= =?UTF-8?q?=E5=88=86rtsp=20pub=E6=94=AF=E6=8C=81h265=E7=9A=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/demo/analyseflv/analyseflv.go | 7 +- app/demo/learnrtsp/learnrtsp.go | 14 +- go.mod | 2 +- go.sum | 4 +- pkg/alpha/stun/client.go | 4 +- pkg/alpha/stun/server.go | 4 +- pkg/base/rtprtcp.go | 2 +- pkg/hevc/hevc.go | 357 +++++++++++++++++++++++++++++- pkg/hevc/hevc_test.go | 89 +++++--- pkg/logic/group.go | 70 ++++-- pkg/rtmp/metadata.go | 22 +- pkg/rtsp/server_pub_session.go | 50 +++-- pkg/sdp/sdp.go | 45 +++- pkg/sdp/sdp_test.go | 30 +++ 14 files changed, 596 insertions(+), 104 deletions(-) diff --git a/app/demo/analyseflv/analyseflv.go b/app/demo/analyseflv/analyseflv.go index a2be2a3..076acc6 100644 --- a/app/demo/analyseflv/analyseflv.go +++ b/app/demo/analyseflv/analyseflv.go @@ -173,7 +173,12 @@ func analysisVideoTag(tag httpflv.Tag) { } } else if tag.IsHEVCKeySeqHeader() { t = typeHEVC - nazalog.Debugf("%s", nazastring.DumpSliceByte(tag.Raw[11:])) + //nazalog.Debugf("%s", nazastring.DumpSliceByte(tag.Raw[11:])) + vps, sps, pps, _ := hevc.ParseVPSSPSPPSFromSeqHeader(tag.Raw[11:]) + nazalog.Debugf("%s", nazastring.DumpSliceByte(vps)) + nazalog.Debugf("%s", nazastring.DumpSliceByte(sps)) + nazalog.Debugf("%s", nazastring.DumpSliceByte(pps)) + //nazalog.Debugf("%s %s %s %+v", hex.Dump(vps), hex.Dump(sps), hex.Dump(pps), err) buf.WriteString(" [HEVC SeqHeader] ") } } else { diff --git a/app/demo/learnrtsp/learnrtsp.go b/app/demo/learnrtsp/learnrtsp.go index f5aa86b..f9cb266 100644 --- a/app/demo/learnrtsp/learnrtsp.go +++ b/app/demo/learnrtsp/learnrtsp.go @@ -36,15 +36,23 @@ func (obs *Obs) OnSPSPPS(sps, pps []byte) { _, _ = avcFp.Write([]byte{0, 0, 0, 1}) _, _ = avcFp.Write(pps) } +func (obs *Obs) OnVPSSPSPPS(vps, sps, pps []byte) { + _, _ = avcFp.Write([]byte{0, 0, 0, 1}) + _, _ = avcFp.Write(vps) + _, _ = avcFp.Write([]byte{0, 0, 0, 1}) + _, _ = avcFp.Write(sps) + _, _ = avcFp.Write([]byte{0, 0, 0, 1}) + _, _ = avcFp.Write(pps) +} func (obs *Obs) OnAVPacket(pkt base.AVPacket) { nazalog.Debugf("type=%d, ts=%d, len=%d", pkt.PayloadType, pkt.Timestamp, len(pkt.Payload)) switch pkt.PayloadType { case base.RTPPacketTypeAVC: // TODO chef: 由于存在多nalu情况,需要进行拆分 - //_, _ = avcFp.Write([]byte{0, 0, 0, 1}) - //_, _ = avcFp.Write(pkt.Payload) - //_ = avcFp.Sync() + _, _ = avcFp.Write([]byte{0, 0, 0, 1}) + _, _ = avcFp.Write(pkt.Payload) + _ = avcFp.Sync() case base.RTPPacketTypeAAC: h, _ := a.CalcADTSHeader(uint16(len(pkt.Payload))) _, _ = aacFp.Write(h) diff --git a/go.mod b/go.mod index 6b55370..20fed7c 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/q191201771/lal go 1.12 -require github.com/q191201771/naza v0.14.0 +require github.com/q191201771/naza v0.15.0 diff --git a/go.sum b/go.sum index 6a9f2aa..c108dbf 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/q191201771/naza v0.14.0 h1:bgjiNRmaSxoYwuyjNvCOtvJgwFYmGlBj6tQpaaK2zhE= -github.com/q191201771/naza v0.14.0/go.mod h1:SE14GBGO9mAn6JZl3NlfWGtNOT7xQjxOG7f3YOdBThM= +github.com/q191201771/naza v0.15.0 h1:HFyRrluqZhpnBu6YQ1soIk6cR9P8G/9sDMFLBhTTBRc= +github.com/q191201771/naza v0.15.0/go.mod h1:SE14GBGO9mAn6JZl3NlfWGtNOT7xQjxOG7f3YOdBThM= diff --git a/pkg/alpha/stun/client.go b/pkg/alpha/stun/client.go index 3f3bb79..89b54fe 100644 --- a/pkg/alpha/stun/client.go +++ b/pkg/alpha/stun/client.go @@ -27,7 +27,9 @@ func (c *Client) Query(addr string, timeoutMS int) (ip string, port int, err err addr = fmt.Sprintf("%s:%d", addr, DefaultPort) } - uc, err := nazanet.NewUDPConnection("", addr) + uc, err := nazanet.NewUDPConnection(func(option *nazanet.UDPConnectionOption) { + option.LAddr = addr + }) if err != nil { return "", 0, err } diff --git a/pkg/alpha/stun/server.go b/pkg/alpha/stun/server.go index 288a891..47abc98 100644 --- a/pkg/alpha/stun/server.go +++ b/pkg/alpha/stun/server.go @@ -20,7 +20,9 @@ type Server struct { } func NewServer(addr string) (*Server, error) { - conn, err := nazanet.NewUDPConnection(addr, "") + conn, err := nazanet.NewUDPConnection(func(option *nazanet.UDPConnectionOption) { + option.LAddr = addr + }) if err != nil { return nil, err } diff --git a/pkg/base/rtprtcp.go b/pkg/base/rtprtcp.go index 784d1e9..6571184 100644 --- a/pkg/base/rtprtcp.go +++ b/pkg/base/rtprtcp.go @@ -9,6 +9,6 @@ package base const ( - RTPPacketTypeAVC = 96 + RTPPacketTypeAVC = 96 // 注意,RTSP SDP中,HEVC也使用96 TODO chef: 是否需要重命名 RTPPacketTypeAAC = 97 ) diff --git a/pkg/hevc/hevc.go b/pkg/hevc/hevc.go index 012b4aa..a89aeed 100644 --- a/pkg/hevc/hevc.go +++ b/pkg/hevc/hevc.go @@ -9,11 +9,11 @@ package hevc import ( - "encoding/hex" "errors" + "github.com/q191201771/naza/pkg/nazabits" + "github.com/q191201771/naza/pkg/bele" - "github.com/q191201771/naza/pkg/nazalog" ) // AnnexB @@ -44,6 +44,28 @@ var ( NALUTypeSEISuffix uint8 = 40 // 0x28 ) +type Context struct { + // unsigned int(8) configurationVersion = 1; + configurationVersion uint8 + // unsigned int(2) general_profile_space; + // unsigned int(1) general_tier_flag; + // unsigned int(5) general_profile_idc; + generalProfileSpace uint8 + generalTierFlag uint8 + generalProfileIDC uint8 + // unsigned int(32) general_profile_compatibility_flags; + generalProfileCompatibilityFlags uint32 + // unsigned int(48) general_constraint_indicator_flags; + generalConstraintIndicatorFlags uint64 + generalLevelIDC uint8 + + numTemporalLayers uint8 + + chromaFormat uint32 + bitDepthLumaMinus8 uint32 + bitDepthChromaMinus8 uint32 +} + func ParseNALUTypeReadable(v uint8) string { b, ok := NALUTypeMapping[ParseNALUType(v)] if !ok { @@ -92,14 +114,14 @@ func ParseVPSSPSPPSFromSeqHeader(payload []byte) (vps, sps, pps []byte, err erro if payload[0] != 0x1c || payload[1] != 0x00 || payload[2] != 0 || payload[3] != 0 || payload[4] != 0 { return nil, nil, nil, ErrHEVC } - nazalog.Debugf("%s", hex.Dump(payload)) + //nazalog.Debugf("%s", hex.Dump(payload)) if len(payload) < 33 { return nil, nil, nil, ErrHEVC } index := 27 - if numOfArrays := payload[index]; numOfArrays != 3 { + if numOfArrays := payload[index]; numOfArrays != 3 && numOfArrays != 4 { return nil, nil, nil, ErrHEVC } index++ @@ -152,3 +174,330 @@ func ParseVPSSPSPPSFromSeqHeader(payload []byte) (vps, sps, pps []byte, err erro return } + +func BuildSeqHeaderFromVPSSPSPPS(vps, sps, pps []byte) ([]byte, error) { + var sh []byte + sh = make([]byte, 1024) + sh[0] = 0x1c + sh[1] = 0x0 + sh[2] = 0x0 + sh[3] = 0x0 + sh[4] = 0x0 + + // unsigned int(8) configurationVersion = 1; + sh[5] = 0x1 + + ctx := newContext() + if err := ParseVPS(vps, ctx); err != nil { + return nil, err + } + if err := ParseSPS(sps, ctx); err != nil { + return nil, err + } + + // unsigned int(2) general_profile_space; + // unsigned int(1) general_tier_flag; + // unsigned int(5) general_profile_idc; + sh[6] = ctx.generalProfileSpace<<6 | ctx.generalTierFlag<<5 | ctx.generalProfileIDC + // unsigned int(32) general_profile_compatibility_flags + bele.BEPutUint32(sh[7:], ctx.generalProfileCompatibilityFlags) + // unsigned int(48) general_constraint_indicator_flags + bele.BEPutUint32(sh[11:], uint32(ctx.generalConstraintIndicatorFlags>>16)) + bele.BEPutUint16(sh[15:], uint16(ctx.generalConstraintIndicatorFlags)) + sh[17] = ctx.generalLevelIDC + + return sh, nil +} + +func ParseVPS(vps []byte, ctx *Context) error { + br := nazabits.NewBitReader(vps) + + // type + if _, err := br.ReadBits8(8); err != nil { + return err + } + + // skip + // vps_video_parameter_set_id u(4) + // vps_reserved_three_2bits u(2) + // vps_max_layers_minus1 u(6) + if _, err := br.ReadBits16(12); err != nil { + return ErrHEVC + } + + vpsMaxSubLayersMinus1, err := br.ReadBits8(3) + if err != nil { + return ErrHEVC + } + if vpsMaxSubLayersMinus1+1 > ctx.numTemporalLayers { + ctx.numTemporalLayers = vpsMaxSubLayersMinus1 + 1 + } + + // skip + // vps_temporal_id_nesting_flag u(1) + // vps_reserved_0xffff_16bits u(16) + if _, err := br.ReadBits32(17); err != nil { + return ErrHEVC + } + + return parsePTL(br, ctx, vpsMaxSubLayersMinus1) +} + +func ParseSPS(sps []byte, ctx *Context) error { + var err error + br := nazabits.NewBitReader(sps) + + // type + if _, err = br.ReadBits8(8); err != nil { + return err + } + + // sps_video_parameter_set_id + if _, err = br.ReadBits8(4); err != nil { + return err + } + + spsMaxSubLayersMinus1, err := br.ReadBits8(3) + if err != nil { + return err + } + + if _, err := br.ReadBit(); err != nil { + return err + } + + if err = parsePTL(br, ctx, spsMaxSubLayersMinus1); err != nil { + return err + } + + if _, err = br.ReadGolomb(); err != nil { + return err + } + + if ctx.chromaFormat, err = br.ReadGolomb(); err != nil { + return err + } + if ctx.chromaFormat == 3 { + if _, err = br.ReadBit(); err != nil { + return err + } + } + + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + + conformanceWindowFlag, err := br.ReadBit() + if err != nil { + return err + } + if conformanceWindowFlag != 0 { + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + } + + if ctx.bitDepthLumaMinus8, err = br.ReadGolomb(); err != nil { + return err + } + if ctx.bitDepthChromaMinus8, err = br.ReadGolomb(); err != nil { + return err + } + _, err = br.ReadGolomb() + if err != nil { + return err + } + spsSubLayerOrderingInfoPresentFlag, err := br.ReadBit() + if err != nil { + return err + } + var i uint8 + if spsSubLayerOrderingInfoPresentFlag != 0 { + i = 0 + } else { + i = spsMaxSubLayersMinus1 + } + for ; i <= spsMaxSubLayersMinus1; i++ { + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + } + + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + if _, err = br.ReadGolomb(); err != nil { + return err + } + + return nil +} + +func parsePTL(br nazabits.BitReader, ctx *Context, maxSubLayersMinus1 uint8) error { + var err error + var ptl Context + if ptl.generalProfileSpace, err = br.ReadBits8(2); err != nil { + return err + } + if ptl.generalTierFlag, err = br.ReadBit(); err != nil { + return err + } + if ptl.generalProfileIDC, err = br.ReadBits8(5); err != nil { + return err + } + if ptl.generalProfileCompatibilityFlags, err = br.ReadBits32(32); err != nil { + return err + } + if ptl.generalConstraintIndicatorFlags, err = br.ReadBits64(48); err != nil { + return err + } + if ptl.generalLevelIDC, err = br.ReadBits8(8); err != nil { + return err + } + updatePTL(ctx, &ptl) + + /* + subLayerProfilePresentFlag := make([]uint8, maxSubLayersMinus1) + subLayerLevelPresentFlag := make([]uint8, maxSubLayersMinus1) + for i := uint8(0); i < maxSubLayersMinus1; i++ { + if subLayerProfilePresentFlag[i], err = br.ReadBit(); err != nil { + return err + } + if subLayerLevelPresentFlag[i], err = br.ReadBit(); err != nil { + return err + } + } + if maxSubLayersMinus1 > 0 { + for i := maxSubLayersMinus1; i < 8; i++ { + if _, err = br.ReadBits8(2); err != nil { + return err + } + } + } + + for i := uint8(0); i < maxSubLayersMinus1; i++ { + if subLayerProfilePresentFlag[i] != 0 { + if _, err = br.ReadBits32(32); err != nil { + return err + } + if _, err = br.ReadBits32(32); err != nil { + return err + } + if _, err = br.ReadBits32(24); err != nil { + return err + } + } + + if subLayerLevelPresentFlag[i] != 0 { + if _, err = br.ReadBits8(8); err != nil { + return err + } + } + } + */ + + return nil +} + +func updatePTL(ctx, ptl *Context) { + ctx.generalProfileSpace = ptl.generalProfileSpace + + if ctx.generalTierFlag < ptl.generalTierFlag { + ctx.generalLevelIDC = ptl.generalLevelIDC + } else { + if ptl.generalLevelIDC > ctx.generalLevelIDC { + ctx.generalLevelIDC = ptl.generalLevelIDC + } + + ctx.generalTierFlag = ptl.generalTierFlag + } + + if ptl.generalProfileIDC > ctx.generalProfileIDC { + ctx.generalProfileIDC = ptl.generalProfileIDC + } + + ctx.generalProfileCompatibilityFlags &= ptl.generalProfileCompatibilityFlags + + ctx.generalConstraintIndicatorFlags &= ptl.generalConstraintIndicatorFlags +} + +func newContext() *Context { + return &Context{ + configurationVersion: 1, + //hvcc->lengthSizeMinusOne = 3; // 4 bytes + generalProfileCompatibilityFlags: 0xffffffff, + generalConstraintIndicatorFlags: 0xffffffffffff, + //hvcc->min_spatial_segmentation_idc = MAX_SPATIAL_SEGMENTATION + 1; + } +} + +//func skipScalingListData(br nazabits.BitReader) error { +// var numCoeffs int +// var i int +// +// for i = 0; i < 4; i++ { +// k := 6 +// if i == 3 { +// k = 2 +// } +// for j := 0; j < k; j++ { +// f, err := br.ReadBit() +// if err != nil { +// return err +// } +// if f != 0 { +// if _, err := br.ReadGolomb(); err != nil { +// return err +// } +// } else { +// numCoeffs = 1 << (4+(uint32(i)<<1)) +// if numCoeffs > 64 { +// numCoeffs = 64 +// } +// } +// } +// } +// +// if i > 1 { +// if _, err := br.ReadGolomb(); err != nil { +// return err +// } +// } +// for i := 0; i < numCoeffs; i++ { +// if _, err := br.ReadGolomb(); err != nil { +// return err +// } +// } +// +// return nil +//} diff --git a/pkg/hevc/hevc_test.go b/pkg/hevc/hevc_test.go index 502e8fd..e987db7 100644 --- a/pkg/hevc/hevc_test.go +++ b/pkg/hevc/hevc_test.go @@ -9,67 +9,84 @@ package hevc_test import ( + "encoding/hex" "testing" + "github.com/q191201771/naza/pkg/nazalog" + "github.com/q191201771/lal/pkg/hevc" "github.com/q191201771/naza/pkg/assert" ) -// https://github.com/ksvc/FFmpeg/blob/release/3.3/libavformat/hevc.c#L936 -var goldenSH = []byte{ +// http://ffmpeg.org/doxygen/trunk/hevc_8c_source.html + +var goldenSH2 = []byte{ 0x1c, 0x00, 0x00, 0x00, 0x00, - 0x01, // configurationVersion - 0x01, - 0x60, 0x00, 0x00, 0x00, - 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x5d, + 0x01, // configurationVersion + 0x01, // general_profile_space, general_tier_flag, general_profile_idc + 0x60, 0x00, 0x00, 0x00, // general_profile_compatibility_flags + 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, // general_constraint_indicator_flags + 0x3f, // general_level_idc 0xf0, 0x00, 0xfc, 0xfd, 0xf8, 0xf8, - 0x00, 0x00, // favgFrameRate + 0x00, 0x00, 0x0f, - 0x03, // numOfArrarys - 0x20, 0x00, 0x01, 0x00, 0x17, - 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xac, 0x09, - 0x21, 0x00, 0x01, 0x00, 0x25, - 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x04, 0x82, 0x00, 0x40, 0x16, 0x5a, 0xee, 0x4c, 0x92, 0xea, 0x52, 0x0a, 0x0c, 0x0c, 0x05, 0xda, 0x14, 0x25, - 0x22, 0x00, 0x01, 0x00, 0x08, - 0x44, 0x01, 0xc0, 0xe3, 0x0f, 0x03, 0xb0, 0x84, -} - -var goldenVPS = []byte{ - 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xac, 0x09, + 0x04, + 0x20, 0x00, 0x01, 0x00, 0x18, + 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0xba, 0x02, 0x40, + 0x21, 0x00, 0x01, 0x00, 0x2a, + 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3f, 0xa0, 0x05, 0x02, 0x01, 0x71, 0xf2, 0xe5, 0xba, 0x4a, 0x4c, 0x2f, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x0f, 0x08, + 0x22, 0x00, 0x01, 0x00, 0x06, + 0x44, 0x01, 0xc0, 0x73, 0xc1, 0x89, + 0x27, 0x00, 0x01, 0x08, 0x1b, 0x4e, 0x01, 0x05, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0x2c, 0xa2, 0xde, 0x09, 0xb5, 0x17, 0x47, 0xdb, 0xbb, 0x55, 0xa4, 0xfe, 0x7f, 0xc2, 0xfc, 0x4e, 0x78, 0x32, 0x36, 0x35, 0x20, 0x28, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x31, 0x37, 0x39, 0x29, 0x20, 0x2d, 0x20, 0x33, 0x2e, 0x32, 0x2e, 0x31, 0x2b, 0x31, 0x2d, 0x62, 0x35, 0x63, 0x38, 0x36, 0x61, 0x36, 0x34, 0x62, 0x62, 0x62, 0x65, 0x3a, 0x5b, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58, 0x5d, 0x5b, 0x63, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x31, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x5d, 0x5b, 0x36, 0x34, 0x20, 0x62, 0x69, 0x74, 0x5d, 0x20, 0x38, 0x62, 0x69, 0x74, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x35, 0x2f, 0x48, 0x45, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2d, 0x32, 0x30, 0x31, 0x38, 0x20, 0x28, 0x63, 0x29, 0x20, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x6f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x78, 0x32, 0x36, 0x35, 0x2e, 0x6f, 0x72, 0x67, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x70, 0x75, 0x69, 0x64, 0x3d, 0x31, 0x31, 0x31, 0x31, 0x30, 0x33, 0x39, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x32, 0x20, 0x77, 0x70, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x73, 0x6e, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x20, 0x6c, 0x6f, 0x67, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x32, 0x20, 0x62, 0x69, 0x74, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x38, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x63, 0x73, 0x70, 0x3d, 0x31, 0x20, 0x66, 0x70, 0x73, 0x3d, 0x31, 0x35, 0x2f, 0x31, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x72, 0x65, 0x73, 0x3d, 0x36, 0x34, 0x30, 0x78, 0x33, 0x36, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x3d, 0x30, 0x20, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x2d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x63, 0x3d, 0x30, 0x20, 0x68, 0x69, 0x67, 0x68, 0x2d, 0x74, 0x69, 0x65, 0x72, 0x3d, 0x31, 0x20, 0x75, 0x68, 0x64, 0x2d, 0x62, 0x64, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x3d, 0x31, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x6e, 0x65, 0x78, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x75, 0x64, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x72, 0x64, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x68, 0x61, 0x73, 0x68, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x70, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x31, 0x35, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, 0x20, 0x67, 0x6f, 0x70, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x62, 0x2d, 0x61, 0x64, 0x61, 0x70, 0x74, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x62, 0x2d, 0x70, 0x79, 0x72, 0x61, 0x6d, 0x69, 0x64, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x35, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x2d, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, 0x30, 0x20, 0x72, 0x61, 0x64, 0x6c, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x70, 0x6c, 0x69, 0x63, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x20, 0x63, 0x74, 0x75, 0x3d, 0x33, 0x32, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x63, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x65, 0x63, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6d, 0x70, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x74, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x74, 0x75, 0x3d, 0x30, 0x20, 0x72, 0x64, 0x6f, 0x71, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x30, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x2d, 0x72, 0x64, 0x3d, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x2d, 0x72, 0x64, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x69, 0x67, 0x6e, 0x68, 0x69, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x3d, 0x32, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x3d, 0x30, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x30, 0x20, 0x6d, 0x65, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x35, 0x37, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6d, 0x76, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x6d, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x2d, 0x73, 0x72, 0x63, 0x2d, 0x70, 0x69, 0x63, 0x73, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, 0x30, 0x3a, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x61, 0x6f, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x61, 0x6f, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x72, 0x64, 0x3d, 0x32, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x2d, 0x73, 0x61, 0x6f, 0x3d, 0x30, 0x20, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x66, 0x61, 0x73, 0x74, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x2d, 0x66, 0x61, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x75, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x62, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x72, 0x64, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x64, 0x70, 0x65, 0x6e, 0x61, 0x6c, 0x74, 0x79, 0x3d, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x3d, 0x32, 0x2e, 0x30, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x6f, 0x71, 0x3d, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x64, 0x2d, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x63, 0x62, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x63, 0x72, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x63, 0x72, 0x66, 0x20, 0x63, 0x72, 0x66, 0x3d, 0x32, 0x38, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3d, 0x30, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x69, 0x70, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, 0x20, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x20, 0x61, 0x71, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3d, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x63, 0x75, 0x74, 0x72, 0x65, 0x65, 0x20, 0x7a, 0x6f, 0x6e, 0x65, 0x2d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x2d, 0x63, 0x62, 0x72, 0x20, 0x71, 0x67, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x63, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x2d, 0x76, 0x62, 0x76, 0x20, 0x73, 0x61, 0x72, 0x3d, 0x31, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x63, 0x61, 0x6e, 0x3d, 0x30, 0x20, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x3d, 0x35, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x70, 0x72, 0x69, 0x6d, 0x3d, 0x32, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x3d, 0x32, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x6c, 0x6f, 0x63, 0x3d, 0x30, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x2d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x3d, 0x30, 0x20, 0x63, 0x6c, 0x6c, 0x3d, 0x30, 0x2c, 0x30, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x30, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x32, 0x35, 0x35, 0x20, 0x6c, 0x6f, 0x67, 0x32, 0x2d, 0x6d, 0x61, 0x78, 0x2d, 0x70, 0x6f, 0x63, 0x2d, 0x6c, 0x73, 0x62, 0x3d, 0x38, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x68, 0x72, 0x64, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x71, 0x70, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x70, 0x73, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x2e, 0x30, 0x35, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x63, 0x75, 0x2d, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2d, 0x71, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x64, 0x68, 0x64, 0x72, 0x31, 0x30, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x69, 0x64, 0x72, 0x2d, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2d, 0x73, 0x65, 0x69, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x2d, 0x72, 0x65, 0x75, 0x73, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x35, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2d, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x6d, 0x76, 0x3d, 0x31, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x63, 0x74, 0x75, 0x2d, 0x64, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x73, 0x61, 0x6f, 0x20, 0x63, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x77, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x64, 0x63, 0x74, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2d, 0x70, 0x69, 0x63, 0x3d, 0x31, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x75, 0x73, 0x69, 0x7a, 0x65, 0x2d, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x2d, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x2d, 0x73, 0x65, 0x69, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x65, 0x76, 0x63, 0x2d, 0x61, 0x71, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x76, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x71, 0x70, 0x2d, 0x61, 0x64, 0x61, 0x70, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x80, } -var goldenSPS = []byte{ - 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x04, 0x82, 0x00, 0x40, 0x16, 0x5a, 0xee, 0x4c, 0x92, 0xea, 0x52, 0x0a, 0x0c, 0x0c, 0x05, 0xda, 0x14, 0x25, +var goldenVPS2 = []byte{ + 0x40, + 0x01, 0x0c, 0x01, 0xff, + // ptl + 0xff, + 0x01, 0x60, 0x00, 0x00, + 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, + 0x00, + 0x00, 0x03, 0x00, 0x3f, 0xba, 0x02, 0x40, } -var goldenPPS = []byte{ - 0x44, 0x01, 0xc0, 0xe3, 0x0f, 0x03, 0xb0, 0x84, +var goldenSPS2 = []byte{ + 0x42, + 0x01, + // ptl + 0x01, + 0x01, 0x60, 0x00, 0x00, + 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, + 0x00, + 0x00, 0x03, 0x00, 0x3f, 0xa0, 0x05, 0x02, 0x01, 0x71, 0xf2, 0xe5, 0xba, 0x4a, 0x4c, 0x2f, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x0f, 0x08, } -var goldenVPSSPSPPSAnnexB = []byte{ - 0x00, 0x00, 0x00, 0x01, - 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xac, 0x09, - 0x00, 0x00, 0x00, 0x01, - 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x04, 0x82, 0x00, 0x40, 0x16, 0x5a, 0xee, 0x4c, 0x92, 0xea, 0x52, 0x0a, 0x0c, 0x0c, 0x05, 0xda, 0x14, 0x25, - 0x00, 0x00, 0x00, 0x01, - 0x44, 0x01, 0xc0, 0xe3, 0x0f, 0x03, 0xb0, 0x84, +var goldenPPS2 = []byte{ + 0x44, 0x01, 0xc0, 0x73, 0xc1, 0x89, } func TestParseVPSSPSPPSFromSeqHeader(t *testing.T) { - vps, sps, pps, err := hevc.ParseVPSSPSPPSFromSeqHeader(goldenSH) + vps, sps, pps, err := hevc.ParseVPSSPSPPSFromSeqHeader(goldenSH2) assert.Equal(t, nil, err) - assert.Equal(t, goldenVPS, vps) - assert.Equal(t, goldenSPS, sps) - assert.Equal(t, goldenPPS, pps) + assert.Equal(t, goldenVPS2, vps) + assert.Equal(t, goldenSPS2, sps) + assert.Equal(t, goldenPPS2, pps) } func TestVPSSPSPPSSeqHeader2AnnexB(t *testing.T) { - out, err := hevc.VPSSPSPPSSeqHeader2AnnexB(goldenSH) + _, err := hevc.VPSSPSPPSSeqHeader2AnnexB(goldenSH2) + assert.Equal(t, nil, err) +} + +func TestBuildSeqHeaderFromVPSSPSPPS(t *testing.T) { + out, err := hevc.BuildSeqHeaderFromVPSSPSPPS(goldenVPS2, goldenSPS2, goldenPPS2) + _ = out assert.Equal(t, nil, err) - assert.Equal(t, goldenVPSSPSPPSAnnexB, out) + nazalog.Debugf("%s", hex.Dump(goldenSH2[5:18])) + nazalog.Debugf("%s", hex.Dump(out[5:18])) } diff --git a/pkg/logic/group.go b/pkg/logic/group.go index 3af8b89..b4b23cb 100644 --- a/pkg/logic/group.go +++ b/pkg/logic/group.go @@ -12,6 +12,8 @@ import ( "fmt" "sync" + "github.com/q191201771/naza/pkg/nazastring" + "github.com/q191201771/lal/pkg/httpts" "github.com/q191201771/naza/pkg/bele" @@ -57,10 +59,11 @@ type Group struct { gopCache *GOPCache httpflvGopCache *GOPCache - adts aac.ADTS - asc []byte - sps []byte - pps []byte + // rtsp pub使用 + asc []byte + vps []byte + sps []byte + pps []byte } type pushProxy struct { @@ -360,10 +363,6 @@ func (group *Group) OnReadRTMPAVMsg(msg base.RTMPMsg) { // rtsp.PubSession func (group *Group) OnASC(asc []byte) { - if err := group.adts.InitWithAACAudioSpecificConfig(asc); err != nil { - nazalog.Errorf("init with aac asc failed. err=%+v", err) - return - } group.asc = asc group.broadcastMetadataAndSeqHeader() } @@ -375,6 +374,14 @@ func (group *Group) OnSPSPPS(sps, pps []byte) { group.broadcastMetadataAndSeqHeader() } +// rtsp.PubSession +func (group *Group) OnVPSSPSPPS(vps, sps, pps []byte) { + group.vps = vps + group.sps = sps + group.pps = pps + group.broadcastMetadataAndSeqHeader() +} + // rtsp.PubSession func (group *Group) OnAVPacket(pkt base.AVPacket) { var h base.RTMPHeader @@ -465,21 +472,32 @@ func (group *Group) broadcastMetadataAndSeqHeader() { return } - ctx, err := avc.ParseSPS(group.sps) - if err != nil { - nazalog.Errorf("parse sps failed. err=%+v", err) - return - } + var metadata []byte + var vsh []byte + var err error + if group.isHEVC() { + metadata, err = rtmp.BuildMetadata(-1, -1, int(base.RTMPSoundFormatAAC), int(base.RTMPCodecIDHEVC)) + if err != nil { + nazalog.Errorf("build metadata failed. err=%+v", err) + return + } + } else { + ctx, err := avc.ParseSPS(group.sps) + if err != nil { + nazalog.Errorf("parse sps failed. err=%+v", err) + return + } - metadata, err := rtmp.BuildMetadata(int(ctx.Width), int(ctx.Height), int(base.RTMPSoundFormatAAC), int(base.RTMPCodecIDAVC)) - if err != nil { - nazalog.Errorf("build metadata failed. err=%+v", err) - return - } - vsh, err := avc.BuildSeqHeaderFromSPSPPS(group.sps, group.pps) - if err != nil { - nazalog.Errorf("build avc seq header failed. err=%+v", err) - return + metadata, err = rtmp.BuildMetadata(int(ctx.Width), int(ctx.Height), int(base.RTMPSoundFormatAAC), int(base.RTMPCodecIDAVC)) + if err != nil { + nazalog.Errorf("build metadata failed. err=%+v", err) + return + } + vsh, err = avc.BuildSeqHeaderFromSPSPPS(group.sps, group.pps) + if err != nil { + nazalog.Errorf("build avc seq header failed. err=%+v", err) + return + } } ash, err := aac.BuildAACSeqHeader(group.asc) if err != nil { @@ -522,6 +540,9 @@ func (group *Group) broadcastMetadataAndSeqHeader() { // TODO chef: 目前相当于其他类型往rtmp.AVMsg转了,考虑统一往一个通用类型转 // @param msg 调用结束后,内部不持有msg.Payload内存块 func (group *Group) broadcastRTMP(msg base.RTMPMsg) { + if msg.IsHEVCKeySeqHeader() { + nazalog.Debugf("%s", nazastring.DumpSliceByte(msg.Payload)) + } var ( lcd LazyChunkDivider lrm2ft LazyRTMPMsg2FLVTag @@ -765,3 +786,8 @@ func (group *Group) delIn() { group.gopCache.Clear() group.httpflvGopCache.Clear() } + +// TODO chef: 后续看是否有更合适的方法判断 +func (group *Group) isHEVC() bool { + return group.vps != nil +} diff --git a/pkg/rtmp/metadata.go b/pkg/rtmp/metadata.go index ad9c932..be94929 100644 --- a/pkg/rtmp/metadata.go +++ b/pkg/rtmp/metadata.go @@ -43,6 +43,8 @@ func ParseMetadata(b []byte) (ObjectPairArray, error) { // - author // - version // +// @param width 如果为-1,则metadata中不写入width +// @param height 如果为-1,则metadata中不写入height // @param audiocodecid AAC 10 // @param videocodecid AVC 7 // @@ -53,14 +55,18 @@ func BuildMetadata(width int, height int, audiocodecid int, videocodecid int) ([ } var opa ObjectPairArray - opa = append(opa, ObjectPair{ - Key: "width", - Value: width, - }) - opa = append(opa, ObjectPair{ - Key: "height", - Value: height, - }) + if width != -1 { + opa = append(opa, ObjectPair{ + Key: "width", + Value: width, + }) + } + if height != -1 { + opa = append(opa, ObjectPair{ + Key: "height", + Value: height, + }) + } opa = append(opa, ObjectPair{ Key: "audiocodecid", Value: audiocodecid, diff --git a/pkg/rtsp/server_pub_session.go b/pkg/rtsp/server_pub_session.go index 0b25f9d..844bf82 100644 --- a/pkg/rtsp/server_pub_session.go +++ b/pkg/rtsp/server_pub_session.go @@ -24,7 +24,8 @@ import ( type PubSessionObserver interface { OnASC(asc []byte) - OnSPSPPS(sps, pps []byte) + OnSPSPPS(sps, pps []byte) // 如果是H264 + OnVPSSPSPPS(vps, sps, pps []byte) // 如果是H265 // @param pkt: pkt结构体中字段含义见rtprtcp.OnAVPacket OnAVPacket(pkt base.AVPacket) @@ -45,6 +46,7 @@ type PubSession struct { audioSsrc uint32 videoSsrc uint32 + vps []byte // 如果是H265的话 sps []byte pps []byte asc []byte @@ -65,7 +67,11 @@ func (p *PubSession) SetObserver(obs PubSessionObserver) { p.observer = obs if p.sps != nil && p.pps != nil { - p.observer.OnSPSPPS(p.sps, p.pps) + if p.vps != nil { + p.observer.OnVPSSPSPPS(p.vps, p.sps, p.pps) + } else { + p.observer.OnSPSPPS(p.sps, p.pps) + } } if p.asc != nil { p.observer.OnASC(p.asc) @@ -79,12 +85,31 @@ func (p *PubSession) InitWithSDP(sdpCtx sdp.SDPContext) { var videoPayloadType int var audioClockRate int var videoClockRate int + + var isHEVC bool + + for _, item := range sdpCtx.ARTPMapList { + switch item.PayloadType { + case base.RTPPacketTypeAVC: + videoClockRate = item.ClockRate + isHEVC = item.EncodingName == "H265" + case base.RTPPacketTypeAAC: + audioClockRate = item.ClockRate + default: + nazalog.Errorf("unknown payloadType. type=%d", item.PayloadType) + } + } + for _, item := range sdpCtx.AFmtPBaseList { switch item.Format { case base.RTPPacketTypeAVC: videoPayloadType = item.Format - p.sps, p.pps, err = sdp.ParseSPSPPS(item) + if isHEVC { + p.vps, p.sps, p.pps, err = sdp.ParseVPSSPSPPS(item) + } else { + p.sps, p.pps, err = sdp.ParseSPSPPS(item) + } if err != nil { nazalog.Errorf("parse sps pps from sdp failed.") } @@ -100,17 +125,6 @@ func (p *PubSession) InitWithSDP(sdpCtx sdp.SDPContext) { } } - for _, item := range sdpCtx.ARTPMapList { - switch item.PayloadType { - case base.RTPPacketTypeAVC: - videoClockRate = item.ClockRate - case base.RTPPacketTypeAAC: - audioClockRate = item.ClockRate - default: - nazalog.Errorf("unknown payloadType. type=%d", item.PayloadType) - } - } - p.audioUnpacker = rtprtcp.NewRTPUnpacker(audioPayloadType, audioClockRate, unpackerItemMaxSize, p.onAVPacketUnpacked) p.videoUnpacker = rtprtcp.NewRTPUnpacker(videoPayloadType, videoClockRate, unpackerItemMaxSize, p.onAVPacketUnpacked) @@ -119,13 +133,17 @@ func (p *PubSession) InitWithSDP(sdpCtx sdp.SDPContext) { } func (p *PubSession) SetRTPConn(conn *net.UDPConn) { - server := nazanet.NewUDPConnectionWithConn(conn) + server, _ := nazanet.NewUDPConnection(func(option *nazanet.UDPConnectionOption) { + option.Conn = conn + }) go server.RunLoop(p.onReadUDPPacket) p.rtpConn = server } func (p *PubSession) SetRTCPConn(conn *net.UDPConn) { - server := nazanet.NewUDPConnectionWithConn(conn) + server, _ := nazanet.NewUDPConnection(func(option *nazanet.UDPConnectionOption) { + option.Conn = conn + }) go server.RunLoop(p.onReadUDPPacket) p.rtcpConn = server } diff --git a/pkg/sdp/sdp.go b/pkg/sdp/sdp.go index 8aef8fa..4c615a4 100644 --- a/pkg/sdp/sdp.go +++ b/pkg/sdp/sdp.go @@ -168,31 +168,60 @@ func ParseASC(a AFmtPBase) ([]byte, error) { return r, nil } +func ParseVPSSPSPPS(a AFmtPBase) (vps, sps, pps []byte, err error) { + if a.Format != base.RTPPacketTypeAVC { + return nil, nil, nil, ErrSDP + } + + v, ok := a.Parameters["sprop-vps"] + if !ok { + return nil, nil, nil, ErrSDP + } + if vps, err = base64.StdEncoding.DecodeString(v); err != nil { + return nil, nil, nil, err + } + + v, ok = a.Parameters["sprop-sps"] + if !ok { + return nil, nil, nil, ErrSDP + } + if sps, err = base64.StdEncoding.DecodeString(v); err != nil { + return nil, nil, nil, err + } + + v, ok = a.Parameters["sprop-pps"] + if !ok { + return nil, nil, nil, ErrSDP + } + if pps, err = base64.StdEncoding.DecodeString(v); err != nil { + return nil, nil, nil, err + } + + return +} + +// 解析AVC/H264的sps,pps // 例子见单元测试 func ParseSPSPPS(a AFmtPBase) (sps, pps []byte, err error) { if a.Format != base.RTPPacketTypeAVC { - err = ErrSDP - return + return nil, nil, ErrSDP } v, ok := a.Parameters["sprop-parameter-sets"] if !ok { - err = ErrSDP - return + return nil, nil, ErrSDP } items := strings.SplitN(v, ",", 2) if len(items) != 2 { - err = ErrSDP - return + return nil, nil, ErrSDP } sps, err = base64.StdEncoding.DecodeString(items[0]) if err != nil { - return + return nil, nil, ErrSDP } pps, err = base64.StdEncoding.DecodeString(items[1]) - return } diff --git a/pkg/sdp/sdp_test.go b/pkg/sdp/sdp_test.go index 11d08a5..62c8e8c 100644 --- a/pkg/sdp/sdp_test.go +++ b/pkg/sdp/sdp_test.go @@ -9,6 +9,7 @@ package sdp_test import ( + "encoding/hex" "testing" "github.com/q191201771/lal/pkg/sdp" @@ -63,6 +64,12 @@ func TestParseARTPMap(t *testing.T) { ClockRate: 44100, EncodingParameters: "2", }, + "a=rtpmap:96 H265/90000": { + PayloadType: 96, + EncodingName: "H265", + ClockRate: 90000, + EncodingParameters: "", + }, } for in, out := range golden { actual, err := sdp.ParseARTPMap(in) @@ -92,6 +99,14 @@ func TestParseFmtPBase(t *testing.T) { "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 := sdp.ParseAFmtPBase(in) @@ -118,3 +133,18 @@ func TestParseASC(t *testing.T) { 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 := sdp.ParseAFmtPBase(s) + assert.Equal(t, nil, err) + vps, sps, pps, err := sdp.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)) +}