srs/trunk/3rdparty/srs-bench/srs/util.go
Winlin 5a420ece3b
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.
2022-10-06 17:40:58 +08:00

1699 lines
42 KiB
Go

// The MIT License (MIT)
//
// Copyright (c) 2021 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 srs
import (
"bytes"
"context"
"flag"
"fmt"
"github.com/ossrs/go-oryx-lib/amf0"
"github.com/ossrs/go-oryx-lib/avc"
"github.com/ossrs/go-oryx-lib/flv"
"github.com/ossrs/go-oryx-lib/rtmp"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"io"
"math/rand"
"net"
"net/url"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
vnet_proxy "github.com/ossrs/srs-bench/vnet"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/transport/vnet"
"github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/media/h264reader"
)
var srsHttps *bool
var srsLog *bool
var srsTimeout *int
var srsPlayPLI *int
var srsPlayOKPackets *int
var srsPublishOKPackets *int
var srsPublishVideoFps *int
var srsDTLSDropPackets *int
var srsSchema string
var srsServer *string
var srsStream *string
var srsLiveStream *string
var srsPublishAudio *string
var srsPublishVideo *string
var srsPublishAvatar *string
var srsPublishBBB *string
var srsVnetClientIP *string
func prepareTest() (err error) {
srsHttps = flag.Bool("srs-https", false, "Whther connect to HTTPS-API")
srsServer = flag.String("srs-server", "127.0.0.1", "The RTC server to connect to")
srsStream = flag.String("srs-stream", "/rtc/regression", "The RTC app/stream to play")
srsLiveStream = flag.String("srs-live-stream", "/live/livestream", "The LIVE app/stream to play")
srsLog = flag.Bool("srs-log", false, "Whether enable the detail log")
srsTimeout = flag.Int("srs-timeout", 5000, "For each case, the timeout in ms")
srsPlayPLI = flag.Int("srs-play-pli", 5000, "The PLI interval in seconds for player.")
srsPlayOKPackets = flag.Int("srs-play-ok-packets", 10, "If recv N RTP packets, it's ok, or fail")
srsPublishOKPackets = flag.Int("srs-publish-ok-packets", 3, "If send N RTP, recv N RTCP packets, it's ok, or fail")
srsPublishAudio = flag.String("srs-publish-audio", "avatar.ogg", "The audio file for publisher.")
srsPublishVideo = flag.String("srs-publish-video", "avatar.h264", "The video file for publisher.")
srsPublishAvatar = flag.String("srs-publish-avatar", "avatar.flv", "The avatar file for publisher.")
srsPublishBBB = flag.String("srs-publish-bbb", "bbb.flv", "The bbb file for publisher.")
srsPublishVideoFps = flag.Int("srs-publish-video-fps", 25, "The video fps for publisher.")
srsVnetClientIP = flag.String("srs-vnet-client-ip", "192.168.168.168", "The client ip in pion/vnet.")
srsDTLSDropPackets = flag.Int("srs-dtls-drop-packets", 5, "If dropped N packets, it's ok, or fail")
// Should parse it first.
flag.Parse()
// The stream should starts with /, for example, /rtc/regression
if !strings.HasPrefix(*srsStream, "/") {
*srsStream = "/" + *srsStream
}
// Generate srs protocol from whether use HTTPS.
srsSchema = "http"
if *srsHttps {
srsSchema = "https"
}
// 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 *srsPublishAvatar, err = tryOpenFile(*srsPublishAvatar); err != nil {
return err
}
if *srsPublishBBB, err = tryOpenFile(*srsPublishBBB); err != nil {
return err
}
if *srsPublishAudio, err = tryOpenFile(*srsPublishAudio); err != nil {
return err
}
return nil
}
// Request SRS RTC API, the apiPath like "/rtc/v1/play", the r is WebRTC url like
// "webrtc://localhost/live/livestream", and the offer is SDP in string.
//
// Return the response of answer SDP in string.
func apiRtcRequest(ctx context.Context, apiPath, r, offer string) (string, error) {
u, err := url.Parse(r)
if err != nil {
return "", errors.Wrapf(err, "Parse url %v", r)
}
// Build api url.
host := u.Host
if !strings.Contains(host, ":") {
host += ":1985"
}
api := fmt.Sprintf("http://%v", host)
if !strings.HasPrefix(apiPath, "/") {
api += "/"
}
api += apiPath
if !strings.HasSuffix(apiPath, "/") {
api += "/"
}
if u.RawQuery != "" {
api += "?" + u.RawQuery
}
// Build JSON body.
reqBody := struct {
Api string `json:"api"`
ClientIP string `json:"clientip"`
SDP string `json:"sdp"`
StreamURL string `json:"streamurl"`
}{
api, "", offer, r,
}
resBody := struct {
Code int `json:"code"`
Session string `json:"sessionid"`
SDP string `json:"sdp"`
}{}
if err := apiRequest(ctx, api, reqBody, &resBody); err != nil {
return "", errors.Wrapf(err, "request api=%v", api)
}
if resBody.Code != 0 {
return "", errors.Errorf("Server fail code=%v", resBody.Code)
}
logger.If(ctx, "Parse response to code=%v, session=%v, sdp=%v",
resBody.Code, resBody.Session, escapeSDP(resBody.SDP))
logger.Tf(ctx, "Parse response to code=%v, session=%v, sdp=%v bytes",
resBody.Code, resBody.Session, len(resBody.SDP))
return resBody.SDP, nil
}
func escapeSDP(sdp string) string {
return strings.ReplaceAll(strings.ReplaceAll(sdp, "\r", "\\r"), "\n", "\\n")
}
func packageAsSTAPA(frames ...*h264reader.NAL) *h264reader.NAL {
first := frames[0]
buf := bytes.Buffer{}
buf.WriteByte(
first.RefIdc<<5&0x60 | byte(24), // STAP-A
)
for _, frame := range frames {
buf.WriteByte(byte(len(frame.Data) >> 8))
buf.WriteByte(byte(len(frame.Data)))
buf.Write(frame.Data)
}
return &h264reader.NAL{
PictureOrderCount: first.PictureOrderCount,
ForbiddenZeroBit: false,
RefIdc: first.RefIdc,
UnitType: h264reader.NalUnitType(24), // STAP-A
Data: buf.Bytes(),
}
}
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
}
// Do nothing for SDP.
func testUtilPassBy(s *webrtc.SessionDescription) error {
return nil
}
// Set to active, as DTLS client, to start ClientHello.
func testUtilSetupActive(s *webrtc.SessionDescription) error {
if strings.Contains(s.SDP, "setup:passive") {
return errors.New("set to active")
}
s.SDP = strings.ReplaceAll(s.SDP, "setup:actpass", "setup:active")
return nil
}
// Set to passive, as DTLS client, to start ClientHello.
func testUtilSetupPassive(s *webrtc.SessionDescription) error {
if strings.Contains(s.SDP, "setup:active") {
return errors.New("set to passive")
}
s.SDP = strings.ReplaceAll(s.SDP, "setup:actpass", "setup:passive")
return nil
}
// Parse address from SDP.
// candidate:0 1 udp 2130706431 192.168.3.8 8000 typ host generation 0
func parseAddressOfCandidate(answerSDP string) (*net.UDPAddr, error) {
answer := webrtc.SessionDescription{Type: webrtc.SDPTypeAnswer, SDP: answerSDP}
answerObject, err := answer.Unmarshal()
if err != nil {
return nil, errors.Wrapf(err, "unmarshal answer %v", answerSDP)
}
if len(answerObject.MediaDescriptions) == 0 {
return nil, errors.New("no media")
}
candidate, ok := answerObject.MediaDescriptions[0].Attribute("candidate")
if !ok {
return nil, errors.New("no candidate")
}
// candidate:0 1 udp 2130706431 192.168.3.8 8000 typ host generation 0
attrs := strings.Split(candidate, " ")
if len(attrs) <= 6 {
return nil, errors.Errorf("no address in %v", candidate)
}
// Parse ip and port from answer.
ip := attrs[4]
port, err := strconv.Atoi(attrs[5])
if err != nil {
return nil, errors.Wrapf(err, "invalid port %v", candidate)
}
address := fmt.Sprintf("%v:%v", ip, port)
addr, err := net.ResolveUDPAddr("udp4", address)
if err != nil {
return nil, errors.Wrapf(err, "parse %v", address)
}
return addr, 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, ","))
}
// For STUN packet, 0x00 is binding request, 0x01 is binding success response.
// @see srs_is_stun of https://github.com/ossrs/srs
func srsIsStun(b []byte) bool {
return len(b) > 0 && (b[0] == 0 || b[0] == 1)
}
// change_cipher_spec(20), alert(21), handshake(22), application_data(23)
// @see https://tools.ietf.org/html/rfc2246#section-6.2.1
// @see srs_is_dtls of https://github.com/ossrs/srs
func srsIsDTLS(b []byte) bool {
return len(b) >= 13 && (b[0] > 19 && b[0] < 64)
}
// For RTP or RTCP, the V=2 which is in the high 2bits, 0xC0 (1100 0000)
// @see srs_is_rtp_or_rtcp of https://github.com/ossrs/srs
func srsIsRTPOrRTCP(b []byte) bool {
return len(b) >= 12 && (b[0]&0xC0) == 0x80
}
// For RTCP, PT is [128, 223] (or without marker [0, 95]).
// Literally, RTCP starts from 64 not 0, so PT is [192, 223] (or without marker [64, 95]).
// @note For RTP, the PT is [96, 127], or [224, 255] with marker.
// @see srs_is_rtcp of https://github.com/ossrs/srs
func srsIsRTCP(b []byte) bool {
return (len(b) >= 12) && (b[0]&0x80) != 0 && (b[1] >= 192 && b[1] <= 223)
}
type chunkType int
const (
chunkTypeICE chunkType = iota + 1
chunkTypeDTLS
chunkTypeRTP
chunkTypeRTCP
)
func (v chunkType) String() string {
switch v {
case chunkTypeICE:
return "ICE"
case chunkTypeDTLS:
return "DTLS"
case chunkTypeRTP:
return "RTP"
case chunkTypeRTCP:
return "RTCP"
default:
return "Unknown"
}
}
type dtlsContentType int
const (
dtlsContentTypeHandshake dtlsContentType = 22
dtlsContentTypeChangeCipherSpec dtlsContentType = 20
dtlsContentTypeAlert dtlsContentType = 21
)
func (v dtlsContentType) String() string {
switch v {
case dtlsContentTypeHandshake:
return "Handshake"
case dtlsContentTypeChangeCipherSpec:
return "ChangeCipherSpec"
default:
return "Unknown"
}
}
type dtlsHandshakeType int
const (
dtlsHandshakeTypeClientHello dtlsHandshakeType = 1
dtlsHandshakeTypeServerHello dtlsHandshakeType = 2
dtlsHandshakeTypeCertificate dtlsHandshakeType = 11
dtlsHandshakeTypeServerKeyExchange dtlsHandshakeType = 12
dtlsHandshakeTypeCertificateRequest dtlsHandshakeType = 13
dtlsHandshakeTypeServerDone dtlsHandshakeType = 14
dtlsHandshakeTypeCertificateVerify dtlsHandshakeType = 15
dtlsHandshakeTypeClientKeyExchange dtlsHandshakeType = 16
dtlsHandshakeTypeFinished dtlsHandshakeType = 20
)
func (v dtlsHandshakeType) String() string {
switch v {
case dtlsHandshakeTypeClientHello:
return "ClientHello"
case dtlsHandshakeTypeServerHello:
return "ServerHello"
case dtlsHandshakeTypeCertificate:
return "Certificate"
case dtlsHandshakeTypeServerKeyExchange:
return "ServerKeyExchange"
case dtlsHandshakeTypeCertificateRequest:
return "CertificateRequest"
case dtlsHandshakeTypeServerDone:
return "ServerDone"
case dtlsHandshakeTypeCertificateVerify:
return "CertificateVerify"
case dtlsHandshakeTypeClientKeyExchange:
return "ClientKeyExchange"
case dtlsHandshakeTypeFinished:
return "Finished"
default:
return "Unknown"
}
}
type chunkMessageType struct {
chunk chunkType
content dtlsContentType
handshake dtlsHandshakeType
}
func (v *chunkMessageType) String() string {
if v.chunk == chunkTypeDTLS {
if v.content == dtlsContentTypeHandshake {
return fmt.Sprintf("%v-%v-%v", v.chunk, v.content, v.handshake)
} else {
return fmt.Sprintf("%v-%v", v.chunk, v.content)
}
}
return fmt.Sprintf("%v", v.chunk)
}
func newChunkMessageType(c vnet.Chunk) (*chunkMessageType, bool) {
b := c.UserData()
if len(b) == 0 {
return nil, false
}
v := &chunkMessageType{}
if srsIsRTPOrRTCP(b) {
if srsIsRTCP(b) {
v.chunk = chunkTypeRTCP
} else {
v.chunk = chunkTypeRTP
}
return v, true
}
if srsIsStun(b) {
v.chunk = chunkTypeICE
return v, true
}
if !srsIsDTLS(b) {
return nil, false
}
v.chunk, v.content = chunkTypeDTLS, dtlsContentType(b[0])
if v.content != dtlsContentTypeHandshake {
return v, true
}
if len(b) < 14 {
return v, false
}
v.handshake = dtlsHandshakeType(b[13])
return v, true
}
func (v *chunkMessageType) IsHandshake() bool {
return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake
}
func (v *chunkMessageType) IsClientHello() bool {
return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeClientHello
}
func (v *chunkMessageType) IsServerHello() bool {
return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeServerHello
}
func (v *chunkMessageType) IsCertificate() bool {
return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeCertificate
}
func (v *chunkMessageType) IsChangeCipherSpec() bool {
return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeChangeCipherSpec
}
type dtlsRecord struct {
ContentType dtlsContentType
Version uint16
Epoch uint16
SequenceNumber uint64
Length uint16
Data []byte
}
func newDTLSRecord(b []byte) (*dtlsRecord, error) {
v := &dtlsRecord{}
return v, v.Unmarshal(b)
}
func (v *dtlsRecord) String() string {
return fmt.Sprintf("epoch=%v, sequence=%v", v.Epoch, v.SequenceNumber)
}
func (v *dtlsRecord) Equals(p *dtlsRecord) bool {
return v.Epoch == p.Epoch && v.SequenceNumber == p.SequenceNumber
}
func (v *dtlsRecord) Unmarshal(b []byte) error {
if len(b) < 13 {
return errors.Errorf("requires 13B only %v", len(b))
}
v.ContentType = dtlsContentType(b[0])
v.Version = uint16(b[1])<<8 | uint16(b[2])
v.Epoch = uint16(b[3])<<8 | uint16(b[4])
v.SequenceNumber = uint64(b[5])<<40 | uint64(b[6])<<32 | uint64(b[7])<<24 | uint64(b[8])<<16 | uint64(b[9])<<8 | uint64(b[10])
v.Length = uint16(b[11])<<8 | uint16(b[12])
v.Data = b[13:]
return nil
}
// The func to setup testWebRTCAPI
type testWebRTCAPIOptionFunc func(api *testWebRTCAPI)
type testWebRTCAPI struct {
// The options to setup the api.
options []testWebRTCAPIOptionFunc
// The api and settings.
api *webrtc.API
mediaEngine *webrtc.MediaEngine
registry *interceptor.Registry
settingEngine *webrtc.SettingEngine
// The vnet router, can be shared by different apis, but we do not share it.
router *vnet.Router
// The network for api.
network *vnet.Net
// The vnet UDP proxy bind to the router.
proxy *vnet_proxy.UDPProxy
}
// The func to initialize testWebRTCAPI
type testWebRTCAPIInitFunc func(api *testWebRTCAPI) error
// Implements interface testWebRTCAPIInitFunc to init testWebRTCAPI
func registerDefaultCodecs(api *testWebRTCAPI) error {
v := api
if err := v.mediaEngine.RegisterDefaultCodecs(); err != nil {
return err
}
if err := webrtc.RegisterDefaultInterceptors(v.mediaEngine, v.registry); err != nil {
return err
}
return nil
}
// Implements interface testWebRTCAPIInitFunc to init testWebRTCAPI
func registerMiniCodecs(api *testWebRTCAPI) error {
v := api
if err := v.mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{webrtc.MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
PayloadType: 111,
}, webrtc.RTPCodecTypeAudio); err != nil {
return err
}
videoRTCPFeedback := []webrtc.RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
if err := v.mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{webrtc.MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", videoRTCPFeedback},
PayloadType: 108,
}, webrtc.RTPCodecTypeVideo); err != nil {
return err
}
// Interceptors for NACK??? @see webrtc.ConfigureNack(v.mediaEngine, v.registry)
return nil
}
// Implements interface testWebRTCAPIInitFunc to init testWebRTCAPI
func registerMiniCodecsWithoutNack(api *testWebRTCAPI) error {
v := api
if err := v.mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{webrtc.MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil},
PayloadType: 111,
}, webrtc.RTPCodecTypeAudio); err != nil {
return err
}
videoRTCPFeedback := []webrtc.RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}}
if err := v.mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{webrtc.MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", videoRTCPFeedback},
PayloadType: 108,
}, webrtc.RTPCodecTypeVideo); err != nil {
return err
}
// Interceptors for NACK??? @see webrtc.ConfigureNack(v.mediaEngine, v.registry)
return nil
}
func newTestWebRTCAPI(inits ...testWebRTCAPIInitFunc) (*testWebRTCAPI, error) {
v := &testWebRTCAPI{}
v.mediaEngine = &webrtc.MediaEngine{}
v.registry = &interceptor.Registry{}
v.settingEngine = &webrtc.SettingEngine{}
// Apply initialize filter, for example, register default codecs when create publisher/player.
for _, setup := range inits {
if setup == nil {
continue
}
if err := setup(v); err != nil {
return nil, err
}
}
return v, nil
}
func (v *testWebRTCAPI) Close() error {
if v.proxy != nil {
_ = v.proxy.Close()
}
if v.router != nil {
_ = v.router.Stop()
}
return nil
}
func (v *testWebRTCAPI) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
// Setting engine for https://github.com/pion/transport/tree/master/vnet
setupVnet := func(vnetClientIP string) (err error) {
// We create a private router for a api, however, it's possible to share the
// same router between apis.
if v.router, err = vnet.NewRouter(&vnet.RouterConfig{
CIDR: "0.0.0.0/0", // Accept all ip, no sub router.
LoggerFactory: logging.NewDefaultLoggerFactory(),
}); err != nil {
return errors.Wrapf(err, "create router for api")
}
// Each api should bind to a network, however, it's possible to share it
// for different apis.
v.network = vnet.NewNet(&vnet.NetConfig{
StaticIP: vnetClientIP,
})
if err = v.router.AddNet(v.network); err != nil {
return errors.Wrapf(err, "create network for api")
}
v.settingEngine.SetVNet(v.network)
// Create a proxy bind to the router.
if v.proxy, err = vnet_proxy.NewProxy(v.router); err != nil {
return errors.Wrapf(err, "create proxy for router")
}
return v.router.Start()
}
if err := setupVnet(vnetClientIP); err != nil {
return err
}
// Apply options from params, for example, tester to register vnet filter.
for _, setup := range options {
setup(v)
}
// Apply options in api, for example, publisher register audio-level interceptor.
for _, setup := range v.options {
setup(v)
}
v.api = webrtc.NewAPI(
webrtc.WithMediaEngine(v.mediaEngine),
webrtc.WithInterceptorRegistry(v.registry),
webrtc.WithSettingEngine(*v.settingEngine),
)
return nil
}
func (v *testWebRTCAPI) NewPeerConnection(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
return v.api.NewPeerConnection(configuration)
}
type testPlayerOptionFunc func(p *testPlayer) error
type testPlayer struct {
onOffer func(s *webrtc.SessionDescription) error
onAnswer func(s *webrtc.SessionDescription) error
iceReadyCancel context.CancelFunc
pc *webrtc.PeerConnection
receivers []*webrtc.RTPReceiver
// We should dispose it.
api *testWebRTCAPI
// Optional suffix for stream url.
streamSuffix string
// Optional app/stream to play, use srsStream by default.
defaultStream string
}
// Create test player, the init is used to initialize api which maybe nil,
// and the options is used to setup the player itself.
func newTestPlayer(init testWebRTCAPIInitFunc, options ...testPlayerOptionFunc) (*testPlayer, error) {
v := &testPlayer{}
api, err := newTestWebRTCAPI(init)
if err != nil {
return nil, err
}
v.api = api
for _, opt := range options {
if err := opt(v); err != nil {
return nil, err
}
}
return v, nil
}
func (v *testPlayer) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
return v.api.Setup(vnetClientIP, options...)
}
func (v *testPlayer) Close() error {
if v.pc != nil {
_ = v.pc.Close()
}
for _, receiver := range v.receivers {
_ = receiver.Stop()
}
if v.api != nil {
_ = v.api.Close()
}
return nil
}
func (v *testPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
r := fmt.Sprintf("%v://%v%v", srsSchema, *srsServer, *srsStream)
if v.defaultStream != "" {
r = fmt.Sprintf("%v://%v%v", srsSchema, *srsServer, v.defaultStream)
}
if v.streamSuffix != "" {
r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
}
pli := time.Duration(*srsPlayPLI) * time.Millisecond
logger.Tf(ctx, "Run play url=%v", r)
pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
if err != nil {
return errors.Wrapf(err, "Create PC")
}
v.pc = pc
if _, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionRecvonly,
}); err != nil {
return errors.Wrapf(err, "add track")
}
if _, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionRecvonly,
}); err != nil {
return errors.Wrapf(err, "add track")
}
offer, err := pc.CreateOffer(nil)
if err != nil {
return errors.Wrapf(err, "Create Offer")
}
if err := pc.SetLocalDescription(offer); err != nil {
return errors.Wrapf(err, "Set offer %v", offer)
}
if v.onOffer != nil {
if err := v.onOffer(&offer); err != nil {
return errors.Wrapf(err, "sdp %v %v", offer.Type, offer.SDP)
}
}
answerSDP, err := apiRtcRequest(ctx, "/rtc/v1/play", r, offer.SDP)
if err != nil {
return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
}
// Run a proxy for real server and vnet.
if address, err := parseAddressOfCandidate(answerSDP); err != nil {
return errors.Wrapf(err, "parse address of %v", answerSDP)
} else if err := v.api.proxy.Proxy(v.api.network, address); err != nil {
return errors.Wrapf(err, "proxy %v to %v", v.api.network, address)
}
answer := &webrtc.SessionDescription{
Type: webrtc.SDPTypeAnswer, SDP: answerSDP,
}
if v.onAnswer != nil {
if err := v.onAnswer(answer); err != nil {
return errors.Wrapf(err, "on answerSDP")
}
}
if err := pc.SetRemoteDescription(*answer); err != nil {
return errors.Wrapf(err, "Set answerSDP %v", answerSDP)
}
handleTrack := func(ctx context.Context, track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) error {
// Send a PLI on an interval so that the publisher is pushing a keyframe
go func() {
if track.Kind() == webrtc.RTPCodecTypeAudio {
return
}
for {
select {
case <-ctx.Done():
return
case <-time.After(pli):
_ = pc.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{
MediaSSRC: uint32(track.SSRC()),
}})
}
}
}()
v.receivers = append(v.receivers, receiver)
for ctx.Err() == nil {
_, _, err := track.ReadRTP()
if err != nil {
return errors.Wrapf(err, "Read RTP")
}
}
return nil
}
pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
err = handleTrack(ctx, track, receiver)
if err != nil {
codec := track.Codec()
err = errors.Wrapf(err, "Handle track %v, pt=%v", codec.MimeType, codec.PayloadType)
cancel()
}
})
pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
logger.Tf(ctx, "ICE state %v", state)
})
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
logger.Tf(ctx, "PC state %v", state)
if state == webrtc.PeerConnectionStateConnected {
if v.iceReadyCancel != nil {
v.iceReadyCancel()
}
}
if state == webrtc.PeerConnectionStateFailed || state == webrtc.PeerConnectionStateClosed {
err = errors.Errorf("Close for PC state %v", state)
cancel()
}
})
<-ctx.Done()
return err
}
type testPublisherOptionFunc func(p *testPublisher) error
type testPublisher struct {
onOffer func(s *webrtc.SessionDescription) error
onAnswer func(s *webrtc.SessionDescription) error
iceReadyCancel context.CancelFunc
// internal objects
aIngester *audioIngester
vIngester *videoIngester
pc *webrtc.PeerConnection
// We should dispose it.
api *testWebRTCAPI
// Optional suffix for stream url.
streamSuffix string
// To cancel the publisher, pass by Run.
cancel context.CancelFunc
}
// Create test publisher, the init is used to initialize api which maybe nil,
// and the options is used to setup the publisher itself.
func newTestPublisher(init testWebRTCAPIInitFunc, options ...testPublisherOptionFunc) (*testPublisher, error) {
sourceVideo, sourceAudio := *srsPublishVideo, *srsPublishAudio
v := &testPublisher{}
api, err := newTestWebRTCAPI(init)
if err != nil {
return nil, err
}
v.api = api
for _, opt := range options {
if err := opt(v); err != nil {
return nil, err
}
}
// Create ingesters.
if sourceAudio != "" {
v.aIngester = newAudioIngester(sourceAudio)
}
if sourceVideo != "" {
v.vIngester = newVideoIngester(sourceVideo)
}
// Setup the interceptors for packets.
api.options = append(api.options, func(api *testWebRTCAPI) {
// Filter for RTCP packets.
rtcpInterceptor := &rtcpInterceptor{}
rtcpInterceptor.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
return rtcpInterceptor.nextRTCPReader.Read(buf, attributes)
}
rtcpInterceptor.rtcpWriter = func(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
return rtcpInterceptor.nextRTCPWriter.Write(pkts, attributes)
}
api.registry.Add(rtcpInterceptor)
// Filter for ingesters.
if sourceAudio != "" {
api.registry.Add(v.aIngester.audioLevelInterceptor)
}
if sourceVideo != "" {
api.registry.Add(v.vIngester.markerInterceptor)
}
})
return v, nil
}
func (v *testPublisher) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
return v.api.Setup(vnetClientIP, options...)
}
func (v *testPublisher) Close() error {
if v.vIngester != nil {
_ = v.vIngester.Close()
}
if v.aIngester != nil {
_ = v.aIngester.Close()
}
if v.pc != nil {
_ = v.pc.Close()
}
if v.api != nil {
_ = v.api.Close()
}
return nil
}
func (v *testPublisher) SetStreamSuffix(suffix string) *testPublisher {
v.streamSuffix = suffix
return v
}
func (v *testPublisher) Run(ctx context.Context, cancel context.CancelFunc) error {
// Save the cancel.
v.cancel = cancel
r := fmt.Sprintf("%v://%v%v", srsSchema, *srsServer, *srsStream)
if v.streamSuffix != "" {
r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
}
sourceVideo, sourceAudio, fps := *srsPublishVideo, *srsPublishAudio, *srsPublishVideoFps
logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v",
r, sourceAudio, sourceVideo, fps)
pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
if err != nil {
return errors.Wrapf(err, "Create PC")
}
v.pc = pc
if v.vIngester != nil {
if err := v.vIngester.AddTrack(pc, fps); err != nil {
return errors.Wrapf(err, "Add track")
}
}
if v.aIngester != nil {
if err := v.aIngester.AddTrack(pc); err != nil {
return errors.Wrapf(err, "Add track")
}
}
offer, err := pc.CreateOffer(nil)
if err != nil {
return errors.Wrapf(err, "Create Offer")
}
if err := pc.SetLocalDescription(offer); err != nil {
return errors.Wrapf(err, "Set offer %v", offer)
}
if v.onOffer != nil {
if err := v.onOffer(&offer); err != nil {
return errors.Wrapf(err, "sdp %v %v", offer.Type, offer.SDP)
}
}
answerSDP, err := apiRtcRequest(ctx, "/rtc/v1/publish", r, offer.SDP)
if err != nil {
return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
}
// Run a proxy for real server and vnet.
if address, err := parseAddressOfCandidate(answerSDP); err != nil {
return errors.Wrapf(err, "parse address of %v", answerSDP)
} else if err := v.api.proxy.Proxy(v.api.network, address); err != nil {
return errors.Wrapf(err, "proxy %v to %v", v.api.network, address)
}
answer := &webrtc.SessionDescription{
Type: webrtc.SDPTypeAnswer, SDP: answerSDP,
}
if v.onAnswer != nil {
if err := v.onAnswer(answer); err != nil {
return errors.Wrapf(err, "on answerSDP")
}
}
if err := pc.SetRemoteDescription(*answer); err != nil {
return errors.Wrapf(err, "Set answerSDP %v", answerSDP)
}
logger.Tf(ctx, "State signaling=%v, ice=%v, conn=%v", pc.SignalingState(), pc.ICEConnectionState(), pc.ConnectionState())
// ICE state management.
pc.OnICEGatheringStateChange(func(state webrtc.ICEGathererState) {
logger.Tf(ctx, "ICE gather state %v", state)
})
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
logger.Tf(ctx, "ICE candidate %v %v:%v", candidate.Protocol, candidate.Address, candidate.Port)
})
pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
logger.Tf(ctx, "ICE state %v", state)
})
pc.OnSignalingStateChange(func(state webrtc.SignalingState) {
logger.Tf(ctx, "Signaling state %v", state)
})
if v.aIngester != nil {
v.aIngester.sAudioSender.Transport().OnStateChange(func(state webrtc.DTLSTransportState) {
logger.Tf(ctx, "DTLS state %v", state)
})
}
pcDone, pcDoneCancel := context.WithCancel(context.Background())
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
logger.Tf(ctx, "PC state %v", state)
if state == webrtc.PeerConnectionStateConnected {
pcDoneCancel()
if v.iceReadyCancel != nil {
v.iceReadyCancel()
}
}
if state == webrtc.PeerConnectionStateFailed || state == webrtc.PeerConnectionStateClosed {
err = errors.Errorf("Close for PC state %v", state)
cancel()
}
})
// Wait for event from context or tracks.
var wg sync.WaitGroup
var finalErr error
wg.Add(1)
go func() {
defer wg.Done()
defer logger.Tf(ctx, "ingest notify done")
<-ctx.Done()
if v.aIngester != nil && v.aIngester.sAudioSender != nil {
// We MUST wait for the ingester ready(or closed), because it might crash if sender is disposed.
<-v.aIngester.ready.Done()
_ = v.aIngester.Close()
}
if v.vIngester != nil && v.vIngester.sVideoSender != nil {
// We MUST wait for the ingester ready(or closed), because it might crash if sender is disposed.
<-v.vIngester.ready.Done()
_ = v.vIngester.Close()
}
}()
wg.Add(1)
go func() {
defer wg.Done()
defer cancel()
if v.aIngester == nil {
return
}
defer v.aIngester.readyCancel()
select {
case <-ctx.Done():
return
case <-pcDone.Done():
}
wg.Add(1)
go func() {
defer wg.Done()
defer logger.Tf(ctx, "aingester sender read done")
buf := make([]byte, 1500)
for ctx.Err() == nil {
if _, _, err := v.aIngester.sAudioSender.Read(buf); err != nil {
return
}
}
}()
for {
if err := v.aIngester.Ingest(ctx); err != nil {
if err == io.EOF {
logger.Tf(ctx, "aingester retry for %v", err)
continue
}
if err != context.Canceled {
finalErr = errors.Wrapf(err, "audio")
}
logger.Tf(ctx, "aingester err=%v, final=%v", err, finalErr)
return
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
defer cancel()
if v.vIngester == nil {
return
}
defer v.vIngester.readyCancel()
select {
case <-ctx.Done():
return
case <-pcDone.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest video %v", sourceVideo)
}
wg.Add(1)
go func() {
defer wg.Done()
defer logger.Tf(ctx, "vingester sender read done")
buf := make([]byte, 1500)
for ctx.Err() == nil {
// The Read() might block in r.rtcpInterceptor.Read(b, a),
// so that the Stop() can not stop it.
if _, _, err := v.vIngester.sVideoSender.Read(buf); err != nil {
return
}
}
}()
for {
if err := v.vIngester.Ingest(ctx); err != nil {
if err == io.EOF {
logger.Tf(ctx, "vingester retry for %v", err)
continue
}
if err != context.Canceled {
finalErr = errors.Wrapf(err, "video")
}
logger.Tf(ctx, "vingester err=%v, final=%v", err, finalErr)
return
}
}
}()
wg.Wait()
logger.Tf(ctx, "ingester done ctx=%v, final=%v", ctx.Err(), finalErr)
if finalErr != nil {
return finalErr
}
return ctx.Err()
}
type RTMPClient struct {
rtmpUrl string
rtmpTcUrl string
rtmpStream string
rtmpUrlObject *url.URL
streamID int
conn *net.TCPConn
proto *rtmp.Protocol
}
func (v *RTMPClient) Close() error {
if v.conn != nil {
v.conn.Close()
}
return nil
}
func (v *RTMPClient) connect(rtmpUrl string) error {
v.rtmpUrl = rtmpUrl
if index := strings.LastIndex(rtmpUrl, "/"); index <= 0 {
return fmt.Errorf("invalid url %v, index=%v", rtmpUrl, index)
} else {
v.rtmpTcUrl = rtmpUrl[0:index]
v.rtmpStream = rtmpUrl[index+1:]
}
// Parse RTMP url.
rtmpUrlObject, err := url.Parse(rtmpUrl)
if err != nil {
return err
}
v.rtmpUrlObject = rtmpUrlObject
port := rtmpUrlObject.Port()
if port == "" {
port = "1935"
}
// Connect to TCP server.
rtmpAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%v:%v", rtmpUrlObject.Hostname(), port))
if err != nil {
return err
}
c, err := net.DialTCP("tcp4", nil, rtmpAddr)
if err != nil {
return err
}
v.conn = c
// RTMP Handshake with server.
hs := rtmp.NewHandshake(rand.New(rand.NewSource(time.Now().UnixNano())))
if err := hs.WriteC0S0(c); err != nil {
return err
}
if err := hs.WriteC1S1(c); err != nil {
return err
}
if _, err := hs.ReadC0S0(c); err != nil {
return err
}
s1, err := hs.ReadC1S1(c)
if err != nil {
return err
}
if _, err := hs.ReadC2S2(c); err != nil {
return err
}
if err := hs.WriteC2S2(c, s1); err != nil {
return err
}
// Connect to RTMP tcUrl.
p := rtmp.NewProtocol(v.conn)
pkt := rtmp.NewConnectAppPacket()
pkt.CommandObject.Set("tcUrl", amf0.NewString(v.rtmpTcUrl))
if err = p.WritePacket(pkt, 0); err != nil {
return err
}
res := rtmp.NewConnectAppResPacket(pkt.TransactionID)
if _, err := p.ExpectPacket(&res); err != nil {
return err
}
v.proto = p
return nil
}
func (v *RTMPClient) Publish(ctx context.Context, rtmpUrl string) error {
if err := v.connect(rtmpUrl); err != nil {
return err
}
p := v.proto
// Create RTMP stream.
if true {
pkt := rtmp.NewCreateStreamPacket()
if err := p.WritePacket(pkt, 0); err != nil {
return err
}
res := rtmp.NewCreateStreamResPacket(pkt.TransactionID)
if _, err := p.ExpectPacket(&res); err != nil {
return err
}
v.streamID = int(res.StreamID)
}
// Publish RTMP stream.
if true {
pkt := rtmp.NewPublishPacket()
pkt.StreamName = *amf0.NewString(v.rtmpStream)
if err := p.WritePacket(pkt, v.streamID); err != nil {
return err
}
res := rtmp.NewCallPacket()
if _, err := p.ExpectPacket(&res); err != nil {
return err
}
}
return nil
}
func (v *RTMPClient) Play(ctx context.Context, rtmpUrl string) error {
if err := v.connect(rtmpUrl); err != nil {
return err
}
p := v.proto
// Create RTMP stream.
if true {
pkt := rtmp.NewCreateStreamPacket()
if err := p.WritePacket(pkt, 0); err != nil {
return err
}
res := rtmp.NewCreateStreamResPacket(pkt.TransactionID)
if _, err := p.ExpectPacket(&res); err != nil {
return err
}
v.streamID = int(res.StreamID)
}
// Play RTMP stream.
if true {
pkt := rtmp.NewPlayPacket()
pkt.StreamName = *amf0.NewString(v.rtmpStream)
if err := p.WritePacket(pkt, v.streamID); err != nil {
return err
}
res := rtmp.NewCallPacket()
if _, err := p.ExpectPacket(&res); err != nil {
return err
}
}
return nil
}
type RTMPPublisher struct {
client *RTMPClient
// Whether auto close transport when ingest done.
closeTransportWhenIngestDone bool
onSendPacket func(m *rtmp.Message) error
}
func NewRTMPPublisher() *RTMPPublisher {
v := &RTMPPublisher{
client: &RTMPClient{},
}
// By default, set to on.
v.closeTransportWhenIngestDone = true
return v
}
func (v *RTMPPublisher) Close() error {
return v.client.Close()
}
func (v *RTMPPublisher) Publish(ctx context.Context, rtmpUrl string) error {
return v.client.Publish(ctx, rtmpUrl)
}
func (v *RTMPPublisher) Ingest(ctx context.Context, flvInput string) error {
// If ctx is cancelled, close the RTMP transport.
var wg sync.WaitGroup
defer wg.Wait()
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
if v.closeTransportWhenIngestDone {
v.Close()
}
}()
// Consume all packets.
err := v.ingest(flvInput)
if err == io.EOF {
return nil
}
if ctx.Err() == context.Canceled {
return nil
}
return err
}
func (v *RTMPPublisher) ingest(flvInput string) error {
p := v.client
fs, err := os.Open(flvInput)
if err != nil {
return err
}
defer fs.Close()
demuxer, err := flv.NewDemuxer(fs)
if err != nil {
return err
}
if _, _, _, err = demuxer.ReadHeader(); err != nil {
return err
}
for {
tagType, tagSize, timestamp, err := demuxer.ReadTagHeader()
if err != nil {
return err
}
tag, err := demuxer.ReadTag(tagSize)
if err != nil {
return err
}
if tagType != flv.TagTypeVideo && tagType != flv.TagTypeAudio {
continue
}
m := rtmp.NewStreamMessage(p.streamID)
m.MessageType = rtmp.MessageType(tagType)
m.Timestamp = uint64(timestamp)
m.Payload = tag
if err = p.proto.WriteMessage(m); err != nil {
return err
}
if v.onSendPacket != nil {
if err = v.onSendPacket(m); err != nil {
return err
}
}
}
return nil
}
type RTMPPlayer struct {
// Transport.
client *RTMPClient
// FLV packager.
videoPackager flv.VideoPackager
onRecvPacket func(m *rtmp.Message, a *flv.AudioFrame, v *flv.VideoFrame) error
}
func NewRTMPPlayer() *RTMPPlayer {
return &RTMPPlayer{
client: &RTMPClient{},
}
}
func (v *RTMPPlayer) Close() error {
return v.client.Close()
}
func (v *RTMPPlayer) Play(ctx context.Context, rtmpUrl string) error {
var err error
if v.videoPackager, err = flv.NewVideoPackager(); err != nil {
return err
}
return v.client.Play(ctx, rtmpUrl)
}
func (v *RTMPPlayer) Consume(ctx context.Context) error {
// If ctx is cancelled, close the RTMP transport.
var wg sync.WaitGroup
defer wg.Wait()
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
v.Close()
}()
// Consume all packets.
err := v.consume()
if err == io.EOF {
return nil
}
if ctx.Err() == context.Canceled {
return nil
}
return err
}
func (v *RTMPPlayer) consume() error {
for {
res, err := v.client.proto.ExpectMessage(rtmp.MessageTypeVideo, rtmp.MessageTypeAudio)
if err != nil {
return err
}
if v.onRecvPacket != nil {
var audioFrame *flv.AudioFrame
var videoFrame *flv.VideoFrame
if res.MessageType == rtmp.MessageTypeVideo {
if videoFrame, err = v.videoPackager.Decode(res.Payload); err != nil {
return err
}
}
if err := v.onRecvPacket(res, audioFrame, videoFrame); err != nil {
return err
}
}
}
}
func IsAvccrEquals(a, b *avc.AVCDecoderConfigurationRecord) bool {
if a == nil || b == nil {
return false
}
if a.AVCLevelIndication != b.AVCLevelIndication ||
a.AVCProfileIndication != b.AVCProfileIndication ||
a.LengthSizeMinusOne != b.LengthSizeMinusOne ||
len(a.SequenceParameterSetNALUnits) != len(b.SequenceParameterSetNALUnits) ||
len(a.PictureParameterSetNALUnits) != len(b.PictureParameterSetNALUnits) {
return false
}
for i := 0; i < len(a.SequenceParameterSetNALUnits); i++ {
if !IsNALUEquals(a.SequenceParameterSetNALUnits[i], b.SequenceParameterSetNALUnits[i]) {
return false
}
}
for i := 0; i < len(a.PictureParameterSetNALUnits); i++ {
if !IsNALUEquals(a.PictureParameterSetNALUnits[i], b.PictureParameterSetNALUnits[i]) {
return false
}
}
return true
}
func IsNALUEquals(a, b *avc.NALU) bool {
if a == nil || b == nil {
return false
}
if a.NALUType != b.NALUType || a.NALRefIDC != b.NALRefIDC {
return false
}
return bytes.Equal(a.Data, b.Data)
}
func DemuxRtpSpsPps(payload []byte) ([]byte, []*avc.NALU, error) {
// Parse RTP packet.
pkt := rtp.Packet{}
if err := pkt.Unmarshal(payload); err != nil {
return nil, nil, err
}
// Decode H264 packet.
h264Packet := codecs.H264Packet{}
annexb, err := h264Packet.Unmarshal(pkt.Payload)
if err != nil {
return annexb, nil, err
}
// Ignore if not STAP-A
if !bytes.HasPrefix(annexb, []byte{0x00, 0x00, 0x00, 0x01}) {
return annexb, nil, err
}
// Parse to NALUs
rawNalus := bytes.Split(annexb, []byte{0x00, 0x00, 0x00, 0x01})
nalus := []*avc.NALU{}
for _, rawNalu := range rawNalus {
if len(rawNalu) == 0 {
continue
}
nalu := avc.NewNALU()
if err := nalu.UnmarshalBinary(rawNalu); err != nil {
return annexb, nil, err
}
nalus = append(nalus, nalu)
}
return annexb, nalus, nil
}