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:
parent
9c81a0e1bd
commit
5a420ece3b
298 changed files with 43343 additions and 763 deletions
145
trunk/3rdparty/srs-bench/gb28181/gb28181.go
vendored
Normal file
145
trunk/3rdparty/srs-bench/gb28181/gb28181.go
vendored
Normal 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
|
||||
}
|
45
trunk/3rdparty/srs-bench/gb28181/gb28181_test.go
vendored
Normal file
45
trunk/3rdparty/srs-bench/gb28181/gb28181_test.go
vendored
Normal 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())
|
||||
}
|
495
trunk/3rdparty/srs-bench/gb28181/gb_test.go
vendored
Normal file
495
trunk/3rdparty/srs-bench/gb28181/gb_test.go
vendored
Normal 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)
|
||||
}
|
||||
}
|
418
trunk/3rdparty/srs-bench/gb28181/ingester.go
vendored
Normal file
418
trunk/3rdparty/srs-bench/gb28181/ingester.go
vendored
Normal 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
281
trunk/3rdparty/srs-bench/gb28181/ps.go
vendored
Normal 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
561
trunk/3rdparty/srs-bench/gb28181/sip.go
vendored
Normal 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
361
trunk/3rdparty/srs-bench/gb28181/util.go
vendored
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue