1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

GB28181: Support GB28181-2016 protocol. v5.0.74 (#3201)

01. Support GB config as StreamCaster.
02. Support disable GB by --gb28181=off.
03. Add utests for SIP examples.
04. Wireshark plugin to decode TCP/9000 as rtp.rfc4571
05. Support MPEGPS program stream codec.
06. Add utest for PS stream codec.
07. Decode MPEGPS packet stream.
08. Carry RTP and PS packet as helper in PS message.
09. Support recover from error mode.
10. Support process by a pack of PS/TS messages.
11. Add statistic for recovered and msgs dropped.
12. Recover from err position fastly.
13. Define state machine for GB session.
14. Bind context to GB session.
15. Re-invite when media disconnected.
16. Update GitHub actions with GB28181.
17. Support parse CANDIDATE by env or pip.
18. Support mux GB28181 to RTMP.
19. Support regression test by srs-bench.
This commit is contained in:
Winlin 2022-10-06 17:40:58 +08:00 committed by GitHub
parent 9c81a0e1bd
commit 5a420ece3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
298 changed files with 43343 additions and 763 deletions

View file

@ -0,0 +1,145 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package gb28181
import (
"context"
"flag"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"io"
"os"
"strings"
"time"
)
type gbMainConfig struct {
sipConfig SIPConfig
psConfig PSConfig
}
func Parse(ctx context.Context) interface{} {
fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
var sfu string
fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or gb28181 or janus")
c := &gbMainConfig{}
fl.StringVar(&c.sipConfig.addr, "pr", "", "")
fl.StringVar(&c.sipConfig.user, "user", "", "")
fl.StringVar(&c.sipConfig.server, "server", "", "")
fl.StringVar(&c.sipConfig.domain, "domain", "", "")
fl.IntVar(&c.sipConfig.random, "random", 0, "")
fl.StringVar(&c.psConfig.video, "sv", "", "")
fl.StringVar(&c.psConfig.audio, "sa", "", "")
fl.IntVar(&c.psConfig.fps, "fps", 0, "")
fl.Usage = func() {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or gb28181 or janus. Default: srs"))
fmt.Println(fmt.Sprintf("SIP:"))
fmt.Println(fmt.Sprintf(" -user The SIP username, ID of device."))
fmt.Println(fmt.Sprintf(" -random Append N number to user as random device ID, like 1320000001."))
fmt.Println(fmt.Sprintf(" -server The SIP server ID, ID of server."))
fmt.Println(fmt.Sprintf(" -domain The SIP domain, domain of server and device."))
fmt.Println(fmt.Sprintf("Publisher:"))
fmt.Println(fmt.Sprintf(" -pr The SIP server address, format is tcp://ip:port over TCP."))
fmt.Println(fmt.Sprintf(" -fps [Optional] The fps of .h264 source file."))
fmt.Println(fmt.Sprintf(" -sa [Optional] The file path to read audio, ignore if empty."))
fmt.Println(fmt.Sprintf(" -sv [Optional] The file path to read video, ignore if empty."))
fmt.Println(fmt.Sprintf("\n例如1个推流"))
fmt.Println(fmt.Sprintf(" %v -sfu gb28181 -pr tcp://127.0.0.1:5060 -user 34020000001320000001 -server 34020000002000000001 -domain 3402000000", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -sfu gb28181 -pr tcp://127.0.0.1:5060 -user 3402000000 -random 10 -server 34020000002000000001 -domain 3402000000", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -sfu gb28181 -pr tcp://127.0.0.1:5060 -user 3402000000 -random 10 -server 34020000002000000001 -domain 3402000000 -sa avatar.aac -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -sfu gb28181 -pr tcp://127.0.0.1:5060 -user livestream -server srs -domain ossrs.io -sa avatar.aac -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println()
}
if err := fl.Parse(os.Args[1:]); err == flag.ErrHelp {
os.Exit(0)
}
showHelp := c.sipConfig.String() == ""
if showHelp {
fl.Usage()
os.Exit(-1)
}
summaryDesc := ""
if c.sipConfig.addr != "" {
pubString := strings.Join([]string{c.sipConfig.String(), c.psConfig.String()}, ",")
summaryDesc = fmt.Sprintf("%v, publish(%v)", summaryDesc, pubString)
}
logger.Tf(ctx, "Run benchmark with %v", summaryDesc)
return c
}
func Run(ctx context.Context, r0 interface{}) (err error) {
conf := r0.(*gbMainConfig)
ctx, cancel := context.WithCancel(ctx)
session := NewGBSession(&GBSessionConfig{
regTimeout: 3 * time.Hour, inviteTimeout: 3 * time.Hour,
}, &conf.sipConfig)
defer session.Close()
if err := session.Connect(ctx); err != nil {
return errors.Wrapf(err, "connect %v", conf.sipConfig)
}
if err := session.Register(ctx); err != nil {
return errors.Wrapf(err, "register %v", conf.sipConfig)
}
if err := session.Invite(ctx); err != nil {
return errors.Wrapf(err, "invite %v", conf.sipConfig)
}
if conf.psConfig.video == "" || conf.psConfig.audio == "" {
cancel()
return nil
}
ingester := NewPSIngester(&IngesterConfig{
psConfig: conf.psConfig,
ssrc: uint32(session.out.ssrc),
clockRate: session.out.clockRate,
payloadType: uint8(session.out.payloadType),
})
defer ingester.Close()
if ingester.conf.serverAddr, err = utilBuildMediaAddr(session.sip.conf.addr, session.out.mediaPort); err != nil {
return err
}
if err := ingester.Ingest(ctx); err != nil {
if errors.Cause(err) == io.EOF {
logger.Tf(ctx, "EOF, video=%v, audio=%v", conf.psConfig.video, conf.psConfig.audio)
return nil
}
return errors.Wrap(err, "ingest")
}
return nil
}

View file

@ -0,0 +1,45 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package gb28181
import (
"github.com/ossrs/go-oryx-lib/logger"
"io/ioutil"
"os"
"testing"
)
func TestMain(m *testing.M) {
if err := prepareTest(); err != nil {
logger.Ef(nil, "Prepare test fail, err %+v", err)
os.Exit(-1)
}
// Disable the logger during all tests.
if *srsLog == false {
olw := logger.Switch(ioutil.Discard)
defer func() {
logger.Switch(olw)
}()
}
os.Exit(m.Run())
}

View file

@ -0,0 +1,495 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package gb28181
import (
"context"
"fmt"
"github.com/ghettovoice/gosip/sip"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/ossrs/go-oryx-lib/errors"
"testing"
"time"
)
func TestGbPublishRegularly(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
err := func() error {
t := NewGBTestPublisher()
defer t.Close()
var nnPackets int
t.ingester.onSendPacket = func(pack *PSPackStream) error {
if nnPackets += 1; nnPackets > 10 {
cancel()
}
return nil
}
if err := t.Run(ctx); err != nil {
return err
}
return nil
}()
if err := filterTestError(ctx.Err(), err); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbSessionHandshake(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
err := func() error {
t := NewGBTestSession()
defer t.Close()
// Use fast heartbeat for utest.
t.session.heartbeatInterval = 100 * time.Millisecond
if err := t.Run(ctx); err != nil {
return err
}
var nn int
t.session.onMessageHeartbeat = func(req, res sip.Message) error {
if nn++; nn >= 3 {
t.session.cancel()
}
return nil
}
<-t.session.heartbeatCtx.Done()
return t.session.heartbeatCtx.Err()
}()
if err := filterTestError(ctx.Err(), err); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbSessionHandshakeDropRegisterOk(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
var conf *SIPConfig
r0 := func() error {
t := NewGBTestSession()
defer t.Close()
conf = t.session.sip.conf
ctx, cancel2 := context.WithCancel(ctx)
t.session.onRegisterDone = func(req, res sip.Message) error {
cancel2()
return nil
}
return t.Run(ctx)
}()
// Use the same session for SIP.
r1 := func() error {
session := NewGBTestSession()
session.session.sip.conf = conf
defer session.Close()
return session.Run(ctx)
}()
if err := filterTestError(ctx.Err(), r0, r1); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbSessionHandshakeDropInviteRequest(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
var conf *SIPConfig
r0 := func() error {
t := NewGBTestSession()
defer t.Close()
conf = t.session.sip.conf
// Drop the invite request, to simulate the device crash or disconnect when got this message.
ctx2, cancel2 := context.WithCancel(ctx)
t.session.onInviteRequest = func(req sip.Message) error {
cancel2()
return nil
}
return t.Run(ctx2)
}()
// When device restart session when inviting, server should re-invite when got register message.
r1 := func() error {
t := NewGBTestSession()
t.session.sip.conf = conf
defer t.Close()
return t.Run(ctx)
}()
if err := filterTestError(ctx.Err(), r0, r1); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbSessionHandshakeDropInvite200Ack(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
var conf *SIPConfig
r0 := func() error {
t := NewGBTestSession()
defer t.Close()
conf = t.session.sip.conf
// Drop the invite ok ACK, to simulate the device crash or disconnect when got this message.
ctx2, cancel2 := context.WithCancel(ctx)
t.session.onInviteOkAck = func(req, res sip.Message) error {
cancel2()
return nil
}
return t.Run(ctx2)
}()
// When device restart session when 200 ack of invite, server should be stable state and waiting for media, then
//there should be a media timeout and re-invite.
r1 := func() error {
t := NewGBTestSession()
t.session.sip.conf = conf
defer t.Close()
return t.Run(ctx)
}()
if err := filterTestError(ctx.Err(), r0, r1); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbPublishMediaDisconnect(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
var conf *SIPConfig
r0 := func() error {
t := NewGBTestPublisher()
defer t.Close()
conf = t.session.sip.conf
var nnPackets int
ctx2, cancel2 := context.WithCancel(ctx)
t.ingester.onSendPacket = func(pack *PSPackStream) error {
if nnPackets += 1; nnPackets > 200 {
cancel2()
}
return nil
}
if err := t.Run(ctx2); err != nil {
return err
}
return nil
}()
r1 := func() error {
t := NewGBTestSession()
t.session.sip.conf = conf
defer t.Close()
return t.Run(ctx)
}()
if err := filterTestError(ctx.Err(), r0, r1); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbSessionBye(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
err := func() error {
t := NewGBTestSession()
defer t.Close()
// Use fast heartbeat for utest.
t.session.heartbeatInterval = 100 * time.Millisecond
if err := t.Run(ctx); err != nil {
return err
}
var nn int
t.session.onMessageHeartbeat = func(req, res sip.Message) error {
if nn++; nn == 3 {
return t.session.Bye(ctx)
}
return nil
}
reconnectTimeout := time.Duration(*srsMediaTimeout+*srsReinviteTimeout+1000) * time.Millisecond
ctx2, cancel2 := context.WithTimeout(ctx, reconnectTimeout)
defer cancel2()
req, err := t.session.sip.Wait(ctx2, sip.INVITE)
if req != nil {
return fmt.Errorf("should not invite after bye")
}
if errors.Cause(err) == context.DeadlineExceeded {
return nil
}
return err
}()
if err := filterTestError(ctx.Err(), err); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbSessionUnregister(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
err := func() error {
t := NewGBTestSession()
defer t.Close()
// Use fast heartbeat for utest.
t.session.heartbeatInterval = 100 * time.Millisecond
if err := t.Run(ctx); err != nil {
return err
}
var nn int
t.session.onMessageHeartbeat = func(req, res sip.Message) error {
if nn++; nn == 3 {
return t.session.UnRegister(ctx)
}
return nil
}
reconnectTimeout := time.Duration(*srsMediaTimeout+*srsReinviteTimeout+1000) * time.Millisecond
ctx2, cancel2 := context.WithTimeout(ctx, reconnectTimeout)
defer cancel2()
req, err := t.session.sip.Wait(ctx2, sip.INVITE)
if req != nil {
return fmt.Errorf("should not invite after bye")
}
if errors.Cause(err) == context.DeadlineExceeded {
return nil
}
return err
}()
if err := filterTestError(ctx.Err(), err); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbPublishReinvite(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
var conf *SIPConfig
err := func() error {
t := NewGBTestPublisher()
defer t.Close()
conf = t.session.sip.conf
var nnPackets int
ctx2, cancel2 := context.WithCancel(ctx)
t.ingester.onSendPacket = func(pack *PSPackStream) error {
if nnPackets += 1; nnPackets == 3 {
cancel2()
}
return nil
}
if err := t.Run(ctx2); err != nil {
return err
}
return nil
}()
r1 := func() error {
t := NewGBTestSession()
defer t.Close()
t.session.sip.conf = conf
// Only register the device, bind to session.
if err := t.session.Connect(ctx); err != nil {
return err
}
if err := t.session.Register(ctx); err != nil {
return err
}
// We should get reinvite when reconnect to SRS.
req, err := t.session.sip.Wait(ctx, sip.INVITE)
if req == nil {
return fmt.Errorf("should reinvite after disconnect")
}
return err
}()
if err := filterTestError(ctx.Err(), err, r1); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbPublishBye(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
var conf *SIPConfig
err := func() error {
t := NewGBTestPublisher()
defer t.Close()
conf = t.session.sip.conf
var nnPackets int
ctx2, cancel2 := context.WithCancel(ctx)
t.ingester.onSendPacket = func(pack *PSPackStream) error {
if nnPackets += 1; nnPackets == 10 {
if err := t.session.Bye(ctx2); err != nil {
return err
}
cancel2()
}
return nil
}
if err := t.Run(ctx2); err != nil {
return err
}
return nil
}()
r1 := func() error {
t := NewGBTestSession()
defer t.Close()
t.session.sip.conf = conf
// Only register the device, bind to session.
if err := t.session.Connect(ctx); err != nil {
return err
}
if err := t.session.Register(ctx); err != nil {
return err
}
// We should not get reinvite when reconnect to SRS.
reconnectTimeout := time.Duration(*srsMediaTimeout+*srsReinviteTimeout+1000) * time.Millisecond
ctx2, cancel2 := context.WithTimeout(ctx, reconnectTimeout)
defer cancel2()
req, err := t.session.sip.Wait(ctx2, sip.INVITE)
if req != nil {
return fmt.Errorf("should not invite after bye")
}
if errors.Cause(err) == context.DeadlineExceeded {
return nil
}
return err
}()
if err := filterTestError(ctx.Err(), err, r1); err != nil {
t.Errorf("err %+v", err)
}
}
func TestGbPublishUnregister(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
defer cancel()
var conf *SIPConfig
err := func() error {
t := NewGBTestPublisher()
defer t.Close()
conf = t.session.sip.conf
var nnPackets int
ctx2, cancel2 := context.WithCancel(ctx)
t.ingester.onSendPacket = func(pack *PSPackStream) error {
if nnPackets += 1; nnPackets == 10 {
if err := t.session.UnRegister(ctx2); err != nil {
return err
}
cancel2()
}
return nil
}
if err := t.Run(ctx2); err != nil {
return err
}
return nil
}()
r1 := func() error {
t := NewGBTestSession()
defer t.Close()
t.session.sip.conf = conf
// Only register the device, bind to session.
if err := t.session.Connect(ctx); err != nil {
return err
}
if err := t.session.Register(ctx); err != nil {
return err
}
// We should not get reinvite when reconnect to SRS.
reconnectTimeout := time.Duration(*srsMediaTimeout+*srsReinviteTimeout+1000) * time.Millisecond
ctx2, cancel2 := context.WithTimeout(ctx, reconnectTimeout)
defer cancel2()
req, err := t.session.sip.Wait(ctx2, sip.INVITE)
if req != nil {
return fmt.Errorf("should not invite after bye")
}
if errors.Cause(err) == context.DeadlineExceeded {
return nil
}
return err
}()
if err := filterTestError(ctx.Err(), err, r1); err != nil {
t.Errorf("err %+v", err)
}
}

View file

@ -0,0 +1,418 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package gb28181
import (
"context"
"github.com/ghettovoice/gosip/sip"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/pion/webrtc/v3/pkg/media/h264reader"
"io"
"os"
"strconv"
"strings"
"sync"
"time"
)
type GBSessionConfig struct {
regTimeout time.Duration
inviteTimeout time.Duration
}
type GBSessionOutput struct {
ssrc int64
mediaPort int64
clockRate uint64
payloadType uint8
}
type GBSession struct {
// GB config.
conf *GBSessionConfig
// The output of session.
out *GBSessionOutput
// The SIP session object.
sip *SIPSession
// Callback when REGISTER done.
onRegisterDone func(req, res sip.Message) error
// Callback when got INVITE request.
onInviteRequest func(req sip.Message) error
// Callback when got INVITE 200 OK ACK request.
onInviteOkAck func(req, res sip.Message) error
// Callback when got MESSAGE response.
onMessageHeartbeat func(req, res sip.Message) error
// For heartbeat coroutines.
heartbeatInterval time.Duration
heartbeatCtx context.Context
cancel context.CancelFunc
// WaitGroup for coroutines.
wg sync.WaitGroup
}
func NewGBSession(c *GBSessionConfig, sc *SIPConfig) *GBSession {
return &GBSession{
sip: NewSIPSession(sc),
conf: c,
out: &GBSessionOutput{
clockRate: uint64(90000),
payloadType: uint8(96),
},
heartbeatInterval: 1 * time.Second,
}
}
func (v *GBSession) Close() error {
if v.cancel != nil {
v.cancel()
}
v.sip.Close()
v.wg.Wait()
return nil
}
func (v *GBSession) Connect(ctx context.Context) error {
client := v.sip
if err := client.Connect(ctx); err != nil {
return errors.Wrap(err, "connect")
}
return ctx.Err()
}
func (v *GBSession) Register(ctx context.Context) error {
client := v.sip
for ctx.Err() == nil {
ctx, regCancel := context.WithTimeout(ctx, v.conf.regTimeout)
defer regCancel()
regReq, regRes, err := client.Register(ctx)
if err != nil {
return errors.Wrap(err, "register")
}
logger.Tf(ctx, "Register id=%v, response=%v", regReq.MessageID(), regRes.MessageID())
if v.onRegisterDone != nil {
if err = v.onRegisterDone(regReq, regRes); err != nil {
return errors.Wrap(err, "callback")
}
}
break
}
return ctx.Err()
}
func (v *GBSession) Invite(ctx context.Context) error {
client := v.sip
for ctx.Err() == nil {
ctx, inviteCancel := context.WithTimeout(ctx, v.conf.inviteTimeout)
defer inviteCancel()
inviteReq, err := client.Wait(ctx, sip.INVITE)
if err != nil {
return errors.Wrap(err, "wait")
}
logger.Tf(ctx, "Got INVITE request, Call-ID=%v", sipGetCallID(inviteReq))
if v.onInviteRequest != nil {
if err = v.onInviteRequest(inviteReq); err != nil {
return errors.Wrap(err, "callback")
}
}
if err = client.Trying(ctx, inviteReq); err != nil {
return errors.Wrapf(err, "trying invite is %v", inviteReq.String())
}
time.Sleep(100 * time.Millisecond)
inviteRes, err := client.InviteResponse(ctx, inviteReq)
if err != nil {
return errors.Wrapf(err, "response invite is %v", inviteReq.String())
}
offer := inviteReq.Body()
ssrcStr := strings.Split(strings.Split(offer, "y=")[1], "\r\n")[0]
if v.out.ssrc, err = strconv.ParseInt(ssrcStr, 10, 64); err != nil {
return errors.Wrapf(err, "parse ssrc=%v, sdp %v", ssrcStr, offer)
}
mediaPortStr := strings.Split(strings.Split(offer, "m=video")[1], " ")[1]
if v.out.mediaPort, err = strconv.ParseInt(mediaPortStr, 10, 64); err != nil {
return errors.Wrapf(err, "parse media port=%v, sdp %v", mediaPortStr, offer)
}
logger.Tf(ctx, "Invite id=%v, response=%v, y=%v, ssrc=%v, mediaPort=%v",
inviteReq.MessageID(), inviteRes.MessageID(), ssrcStr, v.out.ssrc, v.out.mediaPort,
)
if v.onInviteOkAck != nil {
if err = v.onInviteOkAck(inviteReq, inviteRes); err != nil {
return errors.Wrap(err, "callback")
}
}
break
}
// Start goroutine for heartbeat every 1s.
v.heartbeatCtx, v.cancel = context.WithCancel(ctx)
go func(ctx context.Context) {
v.wg.Add(1)
defer v.wg.Done()
for ctx.Err() == nil {
req, res, err := client.Message(ctx)
if err != nil {
v.cancel()
logger.Ef(ctx, "heartbeat err %+v", err)
return
}
if v.onMessageHeartbeat != nil {
if err = v.onMessageHeartbeat(req, res); err != nil {
v.cancel()
logger.Ef(ctx, "callback err %+v", err)
return
}
}
select {
case <-ctx.Done():
return
case <-time.After(v.heartbeatInterval):
}
}
}(v.heartbeatCtx)
return ctx.Err()
}
func (v *GBSession) Bye(ctx context.Context) error {
client := v.sip
for ctx.Err() == nil {
ctx, regCancel := context.WithTimeout(ctx, v.conf.regTimeout)
defer regCancel()
regReq, regRes, err := client.Bye(ctx)
if err != nil {
return errors.Wrap(err, "bye")
}
logger.Tf(ctx, "Bye id=%v, response=%v", regReq.MessageID(), regRes.MessageID())
break
}
return ctx.Err()
}
func (v *GBSession) UnRegister(ctx context.Context) error {
client := v.sip
for ctx.Err() == nil {
ctx, regCancel := context.WithTimeout(ctx, v.conf.regTimeout)
defer regCancel()
regReq, regRes, err := client.UnRegister(ctx)
if err != nil {
return errors.Wrap(err, "UnRegister")
}
logger.Tf(ctx, "UnRegister id=%v, response=%v", regReq.MessageID(), regRes.MessageID())
break
}
return ctx.Err()
}
type IngesterConfig struct {
psConfig PSConfig
ssrc uint32
serverAddr string
clockRate uint64
payloadType uint8
}
type PSIngester struct {
conf *IngesterConfig
onSendPacket func(pack *PSPackStream) error
cancel context.CancelFunc
}
func NewPSIngester(c *IngesterConfig) *PSIngester {
return &PSIngester{conf: c}
}
func (v *PSIngester) Close() error {
if v.cancel != nil {
v.cancel()
}
return nil
}
func (v *PSIngester) Ingest(ctx context.Context) error {
ctx, v.cancel = context.WithCancel(ctx)
ps := NewPSClient(uint32(v.conf.ssrc), v.conf.serverAddr)
if err := ps.Connect(ctx); err != nil {
return errors.Wrapf(err, "connect media=%v", v.conf.serverAddr)
}
defer ps.Close()
videoFile, err := os.Open(v.conf.psConfig.video)
if err != nil {
return errors.Wrapf(err, "Open file %v", v.conf.psConfig.video)
}
defer videoFile.Close()
f, err := os.Open(v.conf.psConfig.audio)
if err != nil {
return errors.Wrapf(err, "Open file %v", v.conf.psConfig.audio)
}
defer f.Close()
h264, err := h264reader.NewReader(videoFile)
if err != nil {
return errors.Wrapf(err, "Open h264 %v", v.conf.psConfig.video)
}
audio, err := NewAACReader(f)
if err != nil {
return errors.Wrapf(err, "Open ogg %v", v.conf.psConfig.audio)
}
// Scale the video samples to 1024 according to AAC, that is 1 video frame means 1024 samples.
audioSampleRate := audio.codec.ASC().SampleRate.ToHz()
videoSampleRate := 1024 * 1000 / v.conf.psConfig.fps
logger.Tf(ctx, "PS: Media stream, tbn=%v, ssrc=%v, pt=%v, Video(%v, fps=%v, rate=%v), Audio(%v, rate=%v, channels=%v)",
v.conf.clockRate, v.conf.ssrc, v.conf.payloadType, v.conf.psConfig.video, v.conf.psConfig.fps, videoSampleRate,
v.conf.psConfig.audio, audioSampleRate, audio.codec.ASC().Channels)
lastPrint := time.Now()
var aacSamples, avcSamples uint64
var audioDTS, videoDTS uint64
defer func() {
logger.Tf(ctx, "Consume Video(samples=%v, dts=%v, ts=%.2f) and Audio(samples=%v, dts=%v, ts=%.2f)",
avcSamples, videoDTS, float64(videoDTS)/90.0, aacSamples, audioDTS, float64(audioDTS)/90.0,
)
}()
clock := newWallClock()
var pack *PSPackStream
for ctx.Err() == nil {
if pack == nil {
pack = NewPSPackStream(v.conf.payloadType)
}
// One pack should only contains one video frame.
if !pack.hasVideo {
var sps, pps *h264reader.NAL
var videoFrames []*h264reader.NAL
for ctx.Err() == nil {
frame, err := h264.NextNAL()
if err == io.EOF {
return io.EOF
}
if err != nil {
return errors.Wrapf(err, "Read h264")
}
videoFrames = append(videoFrames, frame)
logger.If(ctx, "NALU %v PictureOrderCount=%v, ForbiddenZeroBit=%v, RefIdc=%v, %v bytes",
frame.UnitType.String(), frame.PictureOrderCount, frame.ForbiddenZeroBit, frame.RefIdc, len(frame.Data))
if frame.UnitType == h264reader.NalUnitTypeSPS {
sps = frame
} else if frame.UnitType == h264reader.NalUnitTypePPS {
pps = frame
} else {
break
}
}
// We convert the video sample rate to be based over 1024, that is 1024 samples means one video frame.
avcSamples += 1024
videoDTS = uint64(v.conf.clockRate*avcSamples) / uint64(videoSampleRate)
if sps != nil || pps != nil {
err = pack.WriteHeader(videoDTS)
} else {
err = pack.WritePackHeader(videoDTS)
}
if err != nil {
return errors.Wrap(err, "pack header")
}
for _, frame := range videoFrames {
if err = pack.WriteVideo(frame.Data, videoDTS); err != nil {
return errors.Wrapf(err, "write video %v", len(frame.Data))
}
}
}
// Always read and consume one audio frame each time.
if true {
audioFrame, err := audio.NextADTSFrame()
if err != nil {
return errors.Wrap(err, "Read AAC")
}
// Each AAC frame contains 1024 samples, DTS = total-samples / sample-rate
aacSamples += 1024
audioDTS = uint64(v.conf.clockRate*aacSamples) / uint64(audioSampleRate)
if time.Now().Sub(lastPrint) > 3*time.Second {
lastPrint = time.Now()
logger.Tf(ctx, "Consume Video(samples=%v, dts=%v, ts=%.2f) and Audio(samples=%v, dts=%v, ts=%.2f)",
avcSamples, videoDTS, float64(videoDTS)/90.0, aacSamples, audioDTS, float64(audioDTS)/90.0,
)
}
if err = pack.WriteAudio(audioFrame, audioDTS); err != nil {
return errors.Wrapf(err, "write audio %v", len(audioFrame))
}
}
// Send pack when got video and enough audio frames.
if pack.hasVideo && videoDTS < audioDTS {
if err := ps.WritePacksOverRTP(pack.packets); err != nil {
return errors.Wrap(err, "write")
}
if v.onSendPacket != nil {
if err := v.onSendPacket(pack); err != nil {
return errors.Wrap(err, "callback")
}
}
pack = nil // Reset pack.
}
// One audio frame(1024 samples), the duration is 1024/audioSampleRate in seconds.
sampleDuration := time.Duration(uint64(time.Second) * 1024 / uint64(audioSampleRate))
if d := clock.Tick(sampleDuration); d > 0 {
time.Sleep(d)
}
}
return nil
}

281
trunk/3rdparty/srs-bench/gb28181/ps.go vendored Normal file
View file

@ -0,0 +1,281 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package gb28181
import (
"context"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/pion/rtp"
"github.com/yapingcat/gomedia/codec"
"github.com/yapingcat/gomedia/mpeg2"
"math"
"net"
"net/url"
"strings"
)
type PSConfig struct {
// The video source file.
video string
// The fps for h264 file.
fps int
// The audio source file.
audio string
}
func (v *PSConfig) String() string {
sb := []string{}
if v.video != "" {
sb = append(sb, fmt.Sprintf("video=%v", v.video))
}
if v.fps > 0 {
sb = append(sb, fmt.Sprintf("fps=%v", v.fps))
}
if v.audio != "" {
sb = append(sb, fmt.Sprintf("audio=%v", v.audio))
}
return strings.Join(sb, ",")
}
type PSClient struct {
// SSRC from SDP.
ssrc uint32
// The server IP address and port to connect to.
serverAddr string
// Inner state, sequence number.
seq uint16
// Inner state, media TCP connection
conn *net.TCPConn
}
func NewPSClient(ssrc uint32, serverAddr string) *PSClient {
return &PSClient{ssrc: ssrc, serverAddr: serverAddr}
}
func (v *PSClient) Close() error {
if v.conn != nil {
v.conn.Close()
}
return nil
}
func (v *PSClient) Connect(ctx context.Context) error {
if u, err := url.Parse(v.serverAddr); err != nil {
return errors.Wrapf(err, "parse addr=%v", v.serverAddr)
} else if addr, err := net.ResolveTCPAddr(u.Scheme, u.Host); err != nil {
return errors.Wrapf(err, "parse addr=%v, scheme=%v, host=%v", v.serverAddr, u.Scheme, u.Host)
} else if v.conn, err = net.DialTCP(u.Scheme, nil, addr); err != nil {
return errors.Wrapf(err, "connect addr=%v as %v", v.serverAddr, addr.String())
}
return nil
}
func (v *PSClient) WritePacksOverRTP(packs []*PSPacket) error {
for _, pack := range packs {
for _, payload := range pack.ps {
v.seq++
p := rtp.Packet{Header: rtp.Header{
Version: 2, PayloadType: uint8(pack.pt), SequenceNumber: v.seq,
Timestamp: uint32(pack.ts), SSRC: uint32(v.ssrc),
}, Payload: payload}
b, err := p.Marshal()
if err != nil {
return errors.Wrapf(err, "rtp marshal")
}
if _, err = v.conn.Write([]byte{uint8(len(b) >> 8), uint8(len(b))}); err != nil {
return errors.Wrapf(err, "write length=%v", len(b))
}
if _, err = v.conn.Write(b); err != nil {
return errors.Wrapf(err, "write payload %v bytes", len(b))
}
}
}
return nil
}
type PSPacketType int
const (
PSPacketTypePackHeader PSPacketType = iota
PSPacketTypeSystemHeader
PSPacketTypeProgramStramMap
PSPacketTypeVideo
PSPacketTypeAudio
)
type PSPacket struct {
t PSPacketType
ts uint64
pt uint8
ps [][]byte
}
func NewPSPacket(t PSPacketType, p []byte, ts uint64, pt uint8) *PSPacket {
v := &PSPacket{t: t, ts: ts, pt: pt}
if p != nil {
v.ps = append(v.ps, p)
}
return v
}
func (v *PSPacket) Append(p []byte) *PSPacket {
v.ps = append(v.ps, p)
return v
}
type PSPackStream struct {
// The RTP paload type.
pt uint8
// Split a big media frame to small PES packets.
ideaPesLength int
// The generated bytes of PS stream data.
packets []*PSPacket
// Whether has video packet.
hasVideo bool
}
func NewPSPackStream(pt uint8) *PSPackStream {
return &PSPackStream{ideaPesLength: 1400, pt: pt}
}
func (v *PSPackStream) WriteHeader(dts uint64) error {
if err := v.WritePackHeader(dts); err != nil {
return err
}
if err := v.WriteSystemHeader(dts); err != nil {
return err
}
if err := v.WriteProgramStreamMap(dts); err != nil {
return err
}
return nil
}
func (v *PSPackStream) WritePackHeader(dts uint64) error {
w := codec.NewBitStreamWriter(1500)
pack := &mpeg2.PSPackHeader{
System_clock_reference_base: dts,
Program_mux_rate: 159953,
Pack_stuffing_length: 6,
}
pack.Encode(w)
v.packets = append(v.packets, NewPSPacket(PSPacketTypePackHeader, w.Bits(), dts, v.pt))
return nil
}
func (v *PSPackStream) WriteSystemHeader(dts uint64) error {
w := codec.NewBitStreamWriter(1500)
system := &mpeg2.System_header{
Rate_bound: 159953,
Video_bound: 1,
Audio_bound: 1,
Streams: []*mpeg2.Elementary_Stream{
// SrsTsPESStreamIdVideoCommon = 0xe0
&mpeg2.Elementary_Stream{Stream_id: uint8(0xe0), P_STD_buffer_bound_scale: 1, P_STD_buffer_size_bound: 128},
// SrsTsPESStreamIdAudioCommon = 0xc0
&mpeg2.Elementary_Stream{Stream_id: uint8(0xc0), P_STD_buffer_bound_scale: 0, P_STD_buffer_size_bound: 8},
// SrsTsPESStreamIdPrivateStream1 = 0xbd
&mpeg2.Elementary_Stream{Stream_id: uint8(0xbd), P_STD_buffer_bound_scale: 1, P_STD_buffer_size_bound: 128},
// SrsTsPESStreamIdPrivateStream2 = 0xbf
&mpeg2.Elementary_Stream{Stream_id: uint8(0xbf), P_STD_buffer_bound_scale: 1, P_STD_buffer_size_bound: 128},
},
}
system.Encode(w)
v.packets = append(v.packets, NewPSPacket(PSPacketTypeSystemHeader, w.Bits(), dts, v.pt))
return nil
}
func (v *PSPackStream) WriteProgramStreamMap(dts uint64) error {
w := codec.NewBitStreamWriter(1500)
psm := &mpeg2.Program_stream_map{
Stream_map: []*mpeg2.Elementary_stream_elem{
// SrsTsPESStreamIdVideoCommon = 0xe0
mpeg2.NewElementary_stream_elem(uint8(mpeg2.PS_STREAM_H264), 0xe0),
// SrsTsPESStreamIdAudioCommon = 0xc0
mpeg2.NewElementary_stream_elem(uint8(mpeg2.PS_STREAM_AAC), 0xc0),
},
}
psm.Encode(w)
v.packets = append(v.packets, NewPSPacket(PSPacketTypeProgramStramMap, w.Bits(), dts, v.pt))
return nil
}
// The nalu is raw data without ANNEXB header.
func (v *PSPackStream) WriteVideo(nalu []byte, dts uint64) error {
// Mux frame payload in AnnexB format. Always fresh NALU header for frame, see srs_avc_insert_aud.
annexb := append([]byte{0, 0, 0, 1}, nalu...)
video := NewPSPacket(PSPacketTypeVideo, nil, dts, v.pt)
for i := 0; i < len(annexb); i += v.ideaPesLength {
payloadLength := int(math.Min(float64(v.ideaPesLength), float64(len(annexb)-i)))
bb := annexb[i : i+payloadLength]
w := codec.NewBitStreamWriter(65535)
pes := &mpeg2.PesPacket{
Stream_id: uint8(0xe0), // SrsTsPESStreamIdVideoCommon = 0xe0
PTS_DTS_flags: uint8(0x03), Dts: dts, Pts: dts, // Both DTS and PTS.
Pes_payload: bb,
}
utilUpdatePesPacketLength(pes)
pes.Encode(w)
video.Append(w.Bits())
}
v.hasVideo = true
v.packets = append(v.packets, video)
return nil
}
// Write AAC ADTS frame.
func (v *PSPackStream) WriteAudio(adts []byte, dts uint64) error {
w := codec.NewBitStreamWriter(65535)
pes := &mpeg2.PesPacket{
Stream_id: uint8(0xc0), // SrsTsPESStreamIdAudioCommon = 0xc0
PTS_DTS_flags: uint8(0x03), Dts: dts, Pts: dts, // Both DTS and PTS.
Pes_payload: adts,
}
utilUpdatePesPacketLength(pes)
pes.Encode(w)
v.packets = append(v.packets, NewPSPacket(PSPacketTypeAudio, w.Bits(), dts, v.pt))
return nil
}

561
trunk/3rdparty/srs-bench/gb28181/sip.go vendored Normal file
View file

@ -0,0 +1,561 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package gb28181
import (
"context"
"fmt"
"github.com/ghettovoice/gosip/log"
"github.com/ghettovoice/gosip/sip"
"github.com/ghettovoice/gosip/transport"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"math/rand"
"net/url"
"strings"
"sync"
"time"
)
type SIPConfig struct {
// The server address, for example: tcp://127.0.0.1:5060k
addr string
// The SIP domain, for example: ossrs.io or 3402000000
domain string
// The SIP device ID, for example: camera or 34020000001320000001
user string
// The N number of random device ID, for example, 10 means 1320000001
random int
// The SIP server ID, for example: srs or 34020000002000000001
server string
// The cached device id.
deviceID string
}
// The global cache to avoid conflict of deviceID.
// Note that it's not coroutine safe, but it should be OK for utest.
var deviceIDCache map[string]bool
func init() {
deviceIDCache = make(map[string]bool)
}
func (v *SIPConfig) DeviceID() string {
for v.deviceID == "" {
// Generate a random ID.
var rid string
for len(rid) < v.random {
rid += fmt.Sprintf("%v", rand.Uint64())
}
deviceID := fmt.Sprintf("%v%v", v.user, rid[:v.random])
// Ignore if exists.
if _, ok := deviceIDCache[deviceID]; !ok {
v.deviceID = deviceID
deviceIDCache[deviceID] = true
}
}
return v.deviceID
}
func (v *SIPConfig) String() string {
sb := []string{}
if v.addr != "" {
sb = append(sb, fmt.Sprintf("addr=%v", v.addr))
}
if v.domain != "" {
sb = append(sb, fmt.Sprintf("domain=%v", v.domain))
}
if v.user != "" {
sb = append(sb, fmt.Sprintf("user=%v", v.user))
sb = append(sb, fmt.Sprintf("deviceID=%v", v.DeviceID()))
}
if v.random > 0 {
sb = append(sb, fmt.Sprintf("random=%v", v.random))
}
if v.server != "" {
sb = append(sb, fmt.Sprintf("server=%v", v.server))
}
return strings.Join(sb, ",")
}
type SIPSession struct {
conf *SIPConfig
rb *sip.RequestBuilder
requests chan sip.Request
responses chan sip.Response
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
client *SIPClient
seq uint
}
func NewSIPSession(c *SIPConfig) *SIPSession {
return &SIPSession{
conf: c, client: NewSIPClient(), rb: sip.NewRequestBuilder(),
requests: make(chan sip.Request, 1024), responses: make(chan sip.Response, 1024),
seq: 100,
}
}
func (v *SIPSession) Close() error {
if v.cancel != nil {
v.cancel()
}
v.client.Close()
v.wg.Wait()
return nil
}
func (v *SIPSession) Connect(ctx context.Context) error {
if ctx.Err() != nil {
return ctx.Err()
}
ctx, cancel := context.WithCancel(ctx)
v.ctx, v.cancel = ctx, cancel
if err := v.client.Connect(ctx, v.conf.addr); err != nil {
return errors.Wrapf(err, "connect with sipConfig %v", v.conf.String())
}
// Dispatch requests and responses.
go func() {
v.wg.Add(1)
defer v.wg.Done()
for {
select {
case <-v.ctx.Done():
return
case msg := <-v.client.incoming:
if req, ok := msg.(sip.Request); ok {
select {
case v.requests <- req:
case <-v.ctx.Done():
return
}
} else if res, ok := msg.(sip.Response); ok {
select {
case v.responses <- res:
case <-v.ctx.Done():
return
}
} else {
logger.Wf(ctx, "Drop message %v", msg.String())
}
}
}
}()
return nil
}
func (v *SIPSession) Register(ctx context.Context) (sip.Message, sip.Message, error) {
return v.doRegister(ctx, 3600)
}
func (v *SIPSession) UnRegister(ctx context.Context) (sip.Message, sip.Message, error) {
return v.doRegister(ctx, 0)
}
func (v *SIPSession) doRegister(ctx context.Context, expires int) (sip.Message, sip.Message, error) {
if ctx.Err() != nil {
return nil, nil, ctx.Err()
}
sipPort := sip.Port(5060)
sipCallID := sip.CallID(fmt.Sprintf("%v", rand.Uint64()))
sipBranch := fmt.Sprintf("z9hG4bK_%v", rand.Uint32())
sipTag := fmt.Sprintf("%v", rand.Uint32())
sipMaxForwards := sip.MaxForwards(70)
sipExpires := sip.Expires(uint32(expires))
sipPIP := "192.168.3.99"
v.seq++
rb := v.rb
rb.SetTransport("TCP")
rb.SetMethod(sip.REGISTER)
rb.AddVia(&sip.ViaHop{
ProtocolName: "SIP", ProtocolVersion: "2.0", Transport: "TCP", Host: sipPIP, Port: &sipPort,
Params: sip.NewParams().Add("branch", sip.String{Str: sipBranch}),
})
rb.SetFrom(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.DeviceID()}, FHost: v.conf.domain},
Params: sip.NewParams().Add("tag", sip.String{Str: sipTag}),
})
rb.SetTo(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.DeviceID()}, FHost: v.conf.domain},
})
rb.SetCallID(&sipCallID)
rb.SetSeqNo(v.seq)
rb.SetRecipient(&sip.SipUri{FUser: sip.String{v.conf.server}, FHost: v.conf.domain})
rb.SetContact(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.DeviceID()}, FHost: sipPIP, FPort: &sipPort},
})
rb.SetMaxForwards(&sipMaxForwards)
rb.SetExpires(&sipExpires)
req, err := rb.Build()
if err != nil {
return req, nil, errors.Wrap(err, "build request")
}
if err = v.client.Send(req); err != nil {
return req, nil, errors.Wrapf(err, "send request %v", req.String())
}
callID := sipGetCallID(req)
if callID == "" {
return req, nil, errors.Errorf("Invalid SIP Call-ID register %v", req.String())
}
logger.Tf(ctx, "Send REGISTER request, Call-ID=%v, Expires=%v", callID, expires)
for {
select {
case <-ctx.Done():
return nil, nil, ctx.Err()
case <-v.ctx.Done():
return nil, nil, v.ctx.Err()
case msg := <-v.responses:
if tv := sipGetCallID(msg); tv == callID {
return req, msg, nil
} else {
logger.Wf(v.ctx, "Not callID=%v, msg=%v, drop message %v", callID, tv, msg.String())
}
}
}
}
func (v *SIPSession) Trying(ctx context.Context, invite sip.Message) error {
if ctx.Err() != nil {
return ctx.Err()
}
req, ok := invite.(sip.Request)
if !ok {
return errors.Errorf("Invalid SIP request invite %v", invite.String())
}
res := sip.NewResponseFromRequest("", req, sip.StatusCode(100), "Trying", "")
if err := v.client.Send(res); err != nil {
return errors.Wrapf(err, "send response %v", res.String())
}
return nil
}
func (v *SIPSession) InviteResponse(ctx context.Context, invite sip.Message) (sip.Message, error) {
if ctx.Err() != nil {
return nil, ctx.Err()
}
req, ok := invite.(sip.Request)
if !ok {
return nil, errors.Errorf("Invalid SIP request invite %v", invite.String())
}
callID := sipGetCallID(invite)
if callID == "" {
return nil, errors.Errorf("Invalid SIP Call-ID invite %v", invite.String())
}
res := sip.NewResponseFromRequest("", req, sip.StatusCode(200), "OK", "")
if err := v.client.Send(res); err != nil {
return nil, errors.Wrapf(err, "send response %v", res.String())
}
logger.Tf(ctx, "Send INVITE response, Call-ID=%v", callID)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-v.ctx.Done():
return nil, v.ctx.Err()
case msg := <-v.requests:
// Must be an ACK message.
if !msg.IsAck() {
return msg, errors.Errorf("invalid ACK message %v", msg.String())
}
// Check CALL-ID of ACK, should be equal to 200 OK.
if tv := sipGetCallID(msg); tv == callID {
return msg, nil
} else {
logger.Wf(v.ctx, "Not callID=%v, msg=%v, drop message %v", callID, tv, msg.String())
}
}
}
}
func (v *SIPSession) Message(ctx context.Context) (sip.Message, sip.Message, error) {
if ctx.Err() != nil {
return nil, nil, ctx.Err()
}
sipPort := sip.Port(5060)
sipCallID := sip.CallID(fmt.Sprintf("%v", rand.Uint64()))
sipBranch := fmt.Sprintf("z9hG4bK_%v", rand.Uint32())
sipTag := fmt.Sprintf("%v", rand.Uint32())
sipMaxForwards := sip.MaxForwards(70)
sipExpires := sip.Expires(3600)
sipPIP := "192.168.3.99"
v.seq++
rb := v.rb
rb.SetTransport("TCP")
rb.SetMethod(sip.MESSAGE)
rb.AddVia(&sip.ViaHop{
ProtocolName: "SIP", ProtocolVersion: "2.0", Transport: "TCP", Host: sipPIP, Port: &sipPort,
Params: sip.NewParams().Add("branch", sip.String{Str: sipBranch}),
})
rb.SetFrom(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.DeviceID()}, FHost: v.conf.domain},
Params: sip.NewParams().Add("tag", sip.String{Str: sipTag}),
})
rb.SetTo(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.server}, FHost: v.conf.domain},
})
rb.SetCallID(&sipCallID)
rb.SetSeqNo(v.seq)
rb.SetRecipient(&sip.SipUri{FUser: sip.String{v.conf.server}, FHost: v.conf.domain})
rb.SetContact(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.DeviceID()}, FHost: sipPIP, FPort: &sipPort},
})
rb.SetMaxForwards(&sipMaxForwards)
rb.SetExpires(&sipExpires)
v.seq++
rb.SetBody(strings.Join([]string{
`<?xml version="1.0" encoding="GB2312"?>`,
"<Notify>",
"<CmdType>Keepalive</CmdType>",
fmt.Sprintf("<SN>%v</SN>", v.seq),
fmt.Sprintf("<DeviceID>%v</DeviceID>", v.conf.DeviceID()),
"<Status>OK</Status>",
"</Notify>\n",
}, "\n"))
req, err := rb.Build()
if err != nil {
return req, nil, errors.Wrap(err, "build request")
}
if err = v.client.Send(req); err != nil {
return req, nil, errors.Wrapf(err, "send request %v", req.String())
}
callID := sipGetCallID(req)
if callID == "" {
return req, nil, errors.Errorf("Invalid SIP Call-ID message %v", req.String())
}
logger.Tf(ctx, "Send MESSAGE request, Call-ID=%v", callID)
for {
select {
case <-ctx.Done():
return nil, nil, ctx.Err()
case <-v.ctx.Done():
return nil, nil, v.ctx.Err()
case msg := <-v.responses:
if tv := sipGetCallID(msg); tv == callID {
return req, msg, nil
} else {
logger.Wf(v.ctx, "Not callID=%v, msg=%v, drop message %v", callID, tv, msg.String())
}
}
}
}
func (v *SIPSession) Bye(ctx context.Context) (sip.Message, sip.Message, error) {
if ctx.Err() != nil {
return nil, nil, ctx.Err()
}
sipPort := sip.Port(5060)
sipCallID := sip.CallID(fmt.Sprintf("%v", rand.Uint64()))
sipBranch := fmt.Sprintf("z9hG4bK_%v", rand.Uint32())
sipTag := fmt.Sprintf("%v", rand.Uint32())
sipMaxForwards := sip.MaxForwards(70)
sipExpires := sip.Expires(3600)
sipPIP := "192.168.3.99"
v.seq++
rb := v.rb
rb.SetTransport("TCP")
rb.SetMethod(sip.BYE)
rb.AddVia(&sip.ViaHop{
ProtocolName: "SIP", ProtocolVersion: "2.0", Transport: "TCP", Host: sipPIP, Port: &sipPort,
Params: sip.NewParams().Add("branch", sip.String{Str: sipBranch}),
})
rb.SetFrom(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.DeviceID()}, FHost: v.conf.domain},
Params: sip.NewParams().Add("tag", sip.String{Str: sipTag}),
})
rb.SetTo(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.server}, FHost: v.conf.domain},
})
rb.SetCallID(&sipCallID)
rb.SetSeqNo(v.seq)
rb.SetRecipient(&sip.SipUri{FUser: sip.String{v.conf.server}, FHost: v.conf.domain})
rb.SetContact(&sip.Address{
Uri: &sip.SipUri{FUser: sip.String{v.conf.DeviceID()}, FHost: sipPIP, FPort: &sipPort},
})
rb.SetMaxForwards(&sipMaxForwards)
rb.SetExpires(&sipExpires)
req, err := rb.Build()
if err != nil {
return req, nil, errors.Wrap(err, "build request")
}
if err = v.client.Send(req); err != nil {
return req, nil, errors.Wrapf(err, "send request %v", req.String())
}
callID := sipGetCallID(req)
if callID == "" {
return req, nil, errors.Errorf("Invalid SIP Call-ID bye %v", req.String())
}
logger.Tf(ctx, "Send BYE request, Call-ID=%v", callID)
for {
select {
case <-ctx.Done():
return nil, nil, ctx.Err()
case <-v.ctx.Done():
return nil, nil, v.ctx.Err()
case msg := <-v.responses:
if tv := sipGetCallID(msg); tv == callID {
return req, msg, nil
} else {
logger.Wf(v.ctx, "Not callID=%v, msg=%v, drop message %v", callID, tv, msg.String())
}
}
}
}
func (v *SIPSession) Wait(ctx context.Context, method sip.RequestMethod) (sip.Message, error) {
if ctx.Err() != nil {
return nil, ctx.Err()
}
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-v.ctx.Done():
return nil, v.ctx.Err()
case msg := <-v.requests:
if r, ok := msg.(sip.Request); ok && r.Method() == method {
return msg, nil
} else {
logger.Wf(v.ctx, "Not method=%v, drop message %v", method, msg.String())
}
}
}
}
type SIPClient struct {
ctx context.Context
cancel context.CancelFunc
incoming chan sip.Message
target *transport.Target
protocol transport.Protocol
cleanupTimeout time.Duration
}
func NewSIPClient() *SIPClient {
return &SIPClient{
cleanupTimeout: 5 * time.Second,
}
}
func (v *SIPClient) Close() error {
if v.cancel != nil {
v.cancel()
}
// Wait for protocol stack to cleanup.
if v.protocol != nil {
select {
case <-time.After(v.cleanupTimeout):
logger.E(v.ctx, "Wait for protocol cleanup timeout")
case <-v.protocol.Done():
logger.T(v.ctx, "SIP protocol stack done")
}
}
return nil
}
func (v *SIPClient) Connect(ctx context.Context, addr string) error {
prURL, err := url.Parse(addr)
if err != nil {
return errors.Wrapf(err, "parse addr=%v", addr)
}
if prURL.Scheme != "tcp" && prURL.Scheme != "tcp4" {
return errors.Errorf("invalid scheme=%v of addr=%v", prURL.Scheme, addr)
}
target, err := transport.NewTargetFromAddr(prURL.Host)
if err != nil {
return errors.Wrapf(err, "create target to %v", prURL.Host)
}
v.target = target
incoming := make(chan sip.Message, 1024)
errs := make(chan error, 1)
cancels := make(chan struct{}, 1)
protocol := transport.NewTcpProtocol(incoming, errs, cancels, nil, log.NewDefaultLogrusLogger())
v.protocol = protocol
v.incoming = incoming
// Convert protocol stack errs to context signal.
ctx, cancel := context.WithCancel(ctx)
v.cancel = cancel
v.ctx = ctx
go func() {
select {
case <-ctx.Done():
return
case r0 := <-errs:
logger.Ef(ctx, "SIP stack err %+v", r0)
cancel()
}
}()
// Covert context signal to cancels for protocol stack.
go func() {
<-ctx.Done()
close(cancels)
logger.Tf(ctx, "Notify SIP stack to cancel")
}()
return nil
}
func (v *SIPClient) Send(msg sip.Message) error {
logger.Tf(v.ctx, "Send msg %v", msg.String())
return v.protocol.Send(v.target, msg)
}

361
trunk/3rdparty/srs-bench/gb28181/util.go vendored Normal file
View file

@ -0,0 +1,361 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package gb28181
import (
"bufio"
"context"
"flag"
"fmt"
"github.com/ghettovoice/gosip/sip"
"github.com/ossrs/go-oryx-lib/aac"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/yapingcat/gomedia/mpeg2"
"io"
"net"
"net/url"
"os"
"path"
"strings"
"time"
)
var srsLog *bool
var srsTimeout *int
var srsPublishVideoFps *int
var srsSipAddr *string
var srsSipUser *string
var srsSipRandomID *int
var srsSipDomain *string
var srsSipSvrID *string
var srsMediaTimeout *int
var srsReinviteTimeout *int
var srsPublishAudio *string
var srsPublishVideo *string
func prepareTest() (err error) {
srsSipAddr = flag.String("srs-sip", "tcp://127.0.0.1:5060", "The SRS GB server to connect to")
srsSipUser = flag.String("srs-stream", "3402000000", "The GB user/stream to publish")
srsSipRandomID = flag.Int("srs-random", 10, "The GB user/stream random suffix to publish")
srsSipDomain = flag.String("srs-domain", "3402000000", "The GB SIP domain")
srsSipSvrID = flag.String("srs-server", "34020000002000000001", "The GB server ID for SIP")
srsLog = flag.Bool("srs-log", false, "Whether enable the detail log")
srsTimeout = flag.Int("srs-timeout", 11000, "For each case, the timeout in ms")
srsMediaTimeout = flag.Int("srs-media-timeout", 2100, "PS media disconnect timeout in ms")
srsReinviteTimeout = flag.Int("srs-reinvite-timeout", 1200, "When disconnect, SIP re-invite timeout in ms")
srsPublishAudio = flag.String("srs-publish-audio", "avatar.aac", "The audio file for publisher.")
srsPublishVideo = flag.String("srs-publish-video", "avatar.h264", "The video file for publisher.")
srsPublishVideoFps = flag.Int("srs-publish-video-fps", 25, "The video fps for publisher.")
// Should parse it first.
flag.Parse()
// Check file.
tryOpenFile := func(filename string) (string, error) {
if filename == "" {
return filename, nil
}
f, err := os.Open(filename)
if err != nil {
nfilename := path.Join("../", filename)
f2, err := os.Open(nfilename)
if err != nil {
return filename, errors.Wrapf(err, "No video file at %v or %v", filename, nfilename)
}
defer f2.Close()
return nfilename, nil
}
defer f.Close()
return filename, nil
}
if *srsPublishVideo, err = tryOpenFile(*srsPublishVideo); err != nil {
return err
}
if *srsPublishAudio, err = tryOpenFile(*srsPublishAudio); err != nil {
return err
}
return nil
}
type GBTestSession struct {
session *GBSession
}
func NewGBTestSession() *GBTestSession {
sipConfig := SIPConfig{
addr: *srsSipAddr,
domain: *srsSipDomain,
user: *srsSipUser,
random: *srsSipRandomID,
server: *srsSipSvrID,
}
return &GBTestSession{
session: NewGBSession(&GBSessionConfig{
regTimeout: time.Duration(*srsTimeout) * 5 * time.Minute,
inviteTimeout: time.Duration(*srsTimeout) * 5 * time.Minute,
}, &sipConfig),
}
}
func (v *GBTestSession) Close() error {
v.session.Close()
return nil
}
func (v *GBTestSession) Run(ctx context.Context) (err error) {
if err = v.session.Connect(ctx); err != nil {
return errors.Wrap(err, "connect")
}
if err = v.session.Register(ctx); err != nil {
return errors.Wrap(err, "register")
}
if err = v.session.Invite(ctx); err != nil {
return errors.Wrap(err, "invite")
}
return nil
}
type GBTestPublisher struct {
session *GBSession
ingester *PSIngester
}
func NewGBTestPublisher() *GBTestPublisher {
sipConfig := SIPConfig{
addr: *srsSipAddr,
domain: *srsSipDomain,
user: *srsSipUser,
random: *srsSipRandomID,
server: *srsSipSvrID,
}
psConfig := PSConfig{
video: *srsPublishVideo,
fps: *srsPublishVideoFps,
audio: *srsPublishAudio,
}
return &GBTestPublisher{
session: NewGBSession(&GBSessionConfig{
regTimeout: time.Duration(*srsTimeout) * 5 * time.Minute,
inviteTimeout: time.Duration(*srsTimeout) * 5 * time.Minute,
}, &sipConfig),
ingester: NewPSIngester(&IngesterConfig{
psConfig: psConfig,
}),
}
}
func (v *GBTestPublisher) Close() error {
v.ingester.Close()
v.session.Close()
return nil
}
func (v *GBTestPublisher) Run(ctx context.Context) (err error) {
if err = v.session.Connect(ctx); err != nil {
return errors.Wrap(err, "connect")
}
if err = v.session.Register(ctx); err != nil {
return errors.Wrap(err, "register")
}
if err = v.session.Invite(ctx); err != nil {
return errors.Wrap(err, "invite")
}
serverAddr, err := utilBuildMediaAddr(v.session.sip.conf.addr, v.session.out.mediaPort)
if err != nil {
return errors.Wrap(err, "parse")
}
v.ingester.conf.serverAddr = serverAddr
v.ingester.conf.ssrc = uint32(v.session.out.ssrc)
v.ingester.conf.clockRate = v.session.out.clockRate
v.ingester.conf.payloadType = uint8(v.session.out.payloadType)
if err := v.ingester.Ingest(ctx); err != nil {
return errors.Wrap(err, "ingest")
}
return nil
}
// Filter the test error, ignore context.Canceled
func filterTestError(errs ...error) error {
var filteredErrors []error
for _, err := range errs {
if err == nil || errors.Cause(err) == context.Canceled {
continue
}
// If url error, server maybe error, do not print the detail log.
if r0 := errors.Cause(err); r0 != nil {
if r1, ok := r0.(*url.Error); ok {
err = r1
}
}
filteredErrors = append(filteredErrors, err)
}
if len(filteredErrors) == 0 {
return nil
}
if len(filteredErrors) == 1 {
return filteredErrors[0]
}
var descs []string
for i, err := range filteredErrors[1:] {
descs = append(descs, fmt.Sprintf("err #%d, %+v", i, err))
}
return errors.Wrapf(filteredErrors[0], "with %v", strings.Join(descs, ","))
}
type wallClock struct {
start time.Time
duration time.Duration
}
func newWallClock() *wallClock {
return &wallClock{start: time.Now()}
}
func (v *wallClock) Tick(d time.Duration) time.Duration {
v.duration += d
wc := time.Now().Sub(v.start)
re := v.duration - wc
if re > 30*time.Millisecond {
return re
}
return 0
}
func sipGetCallID(m sip.Message) string {
if v, ok := m.CallID(); !ok {
return ""
} else {
return v.Value()
}
}
func utilBuildMediaAddr(addr string, mediaPort int64) (string, error) {
if u, err := url.Parse(addr); err != nil {
return "", errors.Wrapf(err, "parse %v", addr)
} else if addr, err := net.ResolveTCPAddr(u.Scheme, u.Host); err != nil {
return "", errors.Wrapf(err, "parse %v scheme=%v, host=%v", addr, u.Scheme, u.Host)
} else {
return fmt.Sprintf("%v://%v:%v",
u.Scheme, addr.IP.String(), mediaPort,
), nil
}
}
// See SrsMpegPES::decode
func utilUpdatePesPacketLength(pes *mpeg2.PesPacket) {
var nb_required int
if pes.PTS_DTS_flags == 0x2 {
nb_required += 5
}
if pes.PTS_DTS_flags == 0x3 {
nb_required += 10
}
if pes.ESCR_flag > 0 {
nb_required += 6
}
if pes.ES_rate_flag > 0 {
nb_required += 3
}
if pes.DSM_trick_mode_flag > 0 {
nb_required += 1
}
if pes.Additional_copy_info_flag > 0 {
nb_required += 1
}
if pes.PES_CRC_flag > 0 {
nb_required += 2
}
if pes.PES_extension_flag > 0 {
nb_required += 1
}
// Size before PES_header_data_length.
const fixed = uint16(3)
// Size after PES_header_data_length.
pes.PES_header_data_length = uint8(nb_required)
// Size after PES_packet_length
pes.PES_packet_length = uint16(len(pes.Pes_payload)) + fixed + uint16(pes.PES_header_data_length)
}
type AACReader struct {
codec aac.ADTS
r *bufio.Reader
}
func NewAACReader(f io.Reader) (*AACReader, error) {
v := &AACReader{}
var err error
if v.codec, err = aac.NewADTS(); err != nil {
return nil, err
}
v.r = bufio.NewReaderSize(f, 4096)
b, err := v.r.Peek(7 + 1024)
if err != nil {
return nil, err
}
if _, _, err = v.codec.Decode(b); err != nil {
return nil, err
}
return v, nil
}
func (v *AACReader) NextADTSFrame() ([]byte, error) {
b, err := v.r.Peek(7 + 1024)
if err != nil {
return nil, err
}
_, left, err := v.codec.Decode(b)
if err != nil {
return nil, err
}
adts := b[:len(b)-len(left)]
if _, err = v.r.Discard(len(adts)); err != nil {
return nil, err
}
return adts, nil
}