1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-02-15 04:42:04 +00:00

Merge branch 'develop' into merge/develop

This commit is contained in:
winlin 2021-04-28 15:51:36 +08:00
commit aee20d7d65
27 changed files with 4336 additions and 1239 deletions

View file

@ -5,13 +5,13 @@ default: bench test
clean:
rm -f ./objs/srs_bench ./objs/srs_test
.format.txt: *.go srs/*.go vnet/*.go
.format.txt: *.go srs/*.go vnet/*.go janus/*.go
gofmt -w .
echo "done" > .format.txt
bench: ./objs/srs_bench
./objs/srs_bench: .format.txt *.go srs/*.go vnet/*.go Makefile
./objs/srs_bench: .format.txt *.go srs/*.go vnet/*.go janus/*.go Makefile
go build -mod=vendor -o objs/srs_bench .
test: ./objs/srs_test

View file

@ -184,4 +184,41 @@ yum install -y python2-pip &&
pip install lxml && pip install gcovr
```
## Janus
支持Janus的压测使用选项`-sfu janus`可以查看帮助:
```bash
./objs/srs_bench -sfu janus --help
```
首先需要启动Janus推荐使用[janus-docker](https://github.com/winlinvip/janus-docker#usage):
```bash
ip=$(ifconfig en0 inet|grep inet|awk '{print $2}') &&
sed -i '' "s/nat_1_1_mapping.*/nat_1_1_mapping=\"$ip\"/g" janus.jcfg &&
docker run --rm -it -p 8080:8080 -p 8443:8443 -p 20000-20010:20000-20010/udp \
-v $(pwd)/janus.jcfg:/usr/local/etc/janus/janus.jcfg \
-v $(pwd)/janus.plugin.videoroom.jcfg:/usr/local/etc/janus/janus.plugin.videoroom.jcfg \
registry.cn-hangzhou.aliyuncs.com/ossrs/janus:v1.0.7
```
> 若启动成功,打开页面,可以自动入会:[http://localhost:8080](http://localhost:8080)
模拟5个推流入会可以在页面看到入会的流
```bash
make -j10 && ./objs/srs_bench -sfu janus \
-pr webrtc://localhost:8080/2345/livestream \
-sa avatar.ogg -sv avatar.h264 -fps 25 -sn 5
```
模拟5个拉流入会只拉流不推流
```bash
make -j10 && ./objs/srs_bench -sfu janus \
-sr webrtc://localhost:8080/2345/livestream \
-nn 5
```
2021.01, Winlin

View file

@ -1,9 +1,9 @@
#!/bin/bash
FILES=(udpproxy.go udpproxy_test.go)
FILES=(udpproxy.go udpproxy_test.go udpproxy_direct.go udpproxy_direct_test.go)
for file in ${FILES[@]}; do
echo "cp vnet/udpproxy.go ~/git/transport/vnet/" &&
cp vnet/udpproxy.go ~/git/transport/vnet/
echo "cp vnet/$file ~/git/transport/vnet/" &&
cp vnet/$file ~/git/transport/vnet/
done
# https://github.com/pion/webrtc/wiki/Contributing#run-all-automated-tests-and-checks-before-submitting
@ -26,8 +26,8 @@ echo "go test -race ./..." &&
go test -race ./...
if [[ $? -ne 0 ]]; then echo "fail"; exit -1; fi
echo "golangci-lint run --skip-files conn_map_test.go" &&
golangci-lint run --skip-files conn_map_test.go
echo "golangci-lint run --skip-files conn_map_test.go,router_test.go" &&
golangci-lint run --skip-files conn_map_test.go,a_test.go
if [[ $? -ne 0 ]]; then echo "fail"; exit -1; fi
echo "OK"

1077
trunk/3rdparty/srs-bench/janus/api.go vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,301 @@
// 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 janus
import (
"context"
"io"
"os"
"strings"
"time"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/pion/interceptor"
"github.com/pion/rtp"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/media"
"github.com/pion/webrtc/v3/pkg/media/h264reader"
"github.com/pion/webrtc/v3/pkg/media/oggreader"
)
type videoIngester struct {
sourceVideo string
fps int
markerInterceptor *rtpInterceptor
sVideoTrack *webrtc.TrackLocalStaticSample
sVideoSender *webrtc.RTPSender
ready context.Context
readyCancel context.CancelFunc
}
func newVideoIngester(sourceVideo string) *videoIngester {
v := &videoIngester{markerInterceptor: &rtpInterceptor{}, sourceVideo: sourceVideo}
v.ready, v.readyCancel = context.WithCancel(context.Background())
return v
}
func (v *videoIngester) Close() error {
v.readyCancel()
if v.sVideoSender != nil {
_ = v.sVideoSender.Stop()
}
return nil
}
func (v *videoIngester) AddTrack(pc *webrtc.PeerConnection, fps int) error {
v.fps = fps
mimeType, trackID := "video/H264", "video"
if strings.HasSuffix(v.sourceVideo, ".ivf") {
mimeType = "video/VP8"
}
var err error
v.sVideoTrack, err = webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: mimeType, ClockRate: 90000}, trackID, "pion",
)
if err != nil {
return errors.Wrapf(err, "Create video track")
}
v.sVideoSender, err = pc.AddTrack(v.sVideoTrack)
if err != nil {
return errors.Wrapf(err, "Add video track")
}
return err
}
func (v *videoIngester) Ingest(ctx context.Context) error {
source, sender, track, fps := v.sourceVideo, v.sVideoSender, v.sVideoTrack, v.fps
f, err := os.Open(source)
if err != nil {
return errors.Wrapf(err, "Open file %v", source)
}
defer f.Close()
// TODO: FIXME: Support ivf for vp8.
h264, err := h264reader.NewReader(f)
if err != nil {
return errors.Wrapf(err, "Open h264 %v", source)
}
enc := sender.GetParameters().Encodings[0]
codec := sender.GetParameters().Codecs[0]
headers := sender.GetParameters().HeaderExtensions
logger.Tf(ctx, "Video %v, tbn=%v, fps=%v, ssrc=%v, pt=%v, header=%v",
codec.MimeType, codec.ClockRate, fps, enc.SSRC, codec.PayloadType, headers)
// OK, we are ready.
v.readyCancel()
clock := newWallClock()
sampleDuration := time.Duration(uint64(time.Millisecond) * 1000 / uint64(fps))
for ctx.Err() == nil {
var sps, pps *h264reader.NAL
var oFrames []*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")
}
oFrames = append(oFrames, 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
}
}
var frames []*h264reader.NAL
// Package SPS/PPS to STAP-A
if sps != nil && pps != nil {
stapA := packageAsSTAPA(sps, pps)
frames = append(frames, stapA)
}
// Append other original frames.
for _, frame := range oFrames {
if frame.UnitType != h264reader.NalUnitTypeSPS && frame.UnitType != h264reader.NalUnitTypePPS {
frames = append(frames, frame)
}
}
// Covert frames to sample(buffers).
for i, frame := range frames {
sample := media.Sample{Data: frame.Data, Duration: sampleDuration}
// Use the sample timestamp for frames.
if i != len(frames)-1 {
sample.Duration = 0
}
// For STAP-A, set marker to false, to make Chrome happy.
if ri := v.markerInterceptor; ri.rtpWriter == nil {
ri.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
// TODO: Should we decode to check whether SPS/PPS?
if len(payload) > 0 && payload[0]&0x1f == 24 {
header.Marker = false // 24, STAP-A
}
return ri.nextRTPWriter.Write(header, payload, attributes)
}
}
if err = track.WriteSample(sample); err != nil {
return errors.Wrapf(err, "Write sample")
}
}
if d := clock.Tick(sampleDuration); d > 0 {
time.Sleep(d)
}
}
return ctx.Err()
}
type audioIngester struct {
sourceAudio string
audioLevelInterceptor *rtpInterceptor
sAudioTrack *webrtc.TrackLocalStaticSample
sAudioSender *webrtc.RTPSender
ready context.Context
readyCancel context.CancelFunc
}
func newAudioIngester(sourceAudio string) *audioIngester {
v := &audioIngester{audioLevelInterceptor: &rtpInterceptor{}, sourceAudio: sourceAudio}
v.ready, v.readyCancel = context.WithCancel(context.Background())
return v
}
func (v *audioIngester) Close() error {
v.readyCancel() // OK we are closed, also ready.
if v.sAudioSender != nil {
_ = v.sAudioSender.Stop()
}
return nil
}
func (v *audioIngester) AddTrack(pc *webrtc.PeerConnection) error {
var err error
mimeType, trackID := "audio/opus", "audio"
v.sAudioTrack, err = webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: mimeType, ClockRate: 48000, Channels: 2}, trackID, "pion",
)
if err != nil {
return errors.Wrapf(err, "Create audio track")
}
v.sAudioSender, err = pc.AddTrack(v.sAudioTrack)
if err != nil {
return errors.Wrapf(err, "Add audio track")
}
return nil
}
func (v *audioIngester) Ingest(ctx context.Context) error {
source, sender, track := v.sourceAudio, v.sAudioSender, v.sAudioTrack
f, err := os.Open(source)
if err != nil {
return errors.Wrapf(err, "Open file %v", source)
}
defer f.Close()
ogg, _, err := oggreader.NewWith(f)
if err != nil {
return errors.Wrapf(err, "Open ogg %v", source)
}
enc := sender.GetParameters().Encodings[0]
codec := sender.GetParameters().Codecs[0]
headers := sender.GetParameters().HeaderExtensions
logger.Tf(ctx, "Audio %v, tbn=%v, channels=%v, ssrc=%v, pt=%v, header=%v",
codec.MimeType, codec.ClockRate, codec.Channels, enc.SSRC, codec.PayloadType, headers)
// Whether should encode the audio-level in RTP header.
var audioLevel *webrtc.RTPHeaderExtensionParameter
for _, h := range headers {
if h.URI == sdp.AudioLevelURI {
audioLevel = &h
}
}
// OK, we are ready.
v.readyCancel()
clock := newWallClock()
var lastGranule uint64
for ctx.Err() == nil {
pageData, pageHeader, err := ogg.ParseNextPage()
if err == io.EOF {
return io.EOF
}
if err != nil {
return errors.Wrapf(err, "Read ogg")
}
// The amount of samples is the difference between the last and current timestamp
sampleCount := pageHeader.GranulePosition - lastGranule
lastGranule = pageHeader.GranulePosition
sampleDuration := time.Duration(uint64(time.Millisecond) * 1000 * sampleCount / uint64(codec.ClockRate))
// For audio-level, set the extensions if negotiated.
if ri := v.audioLevelInterceptor; ri.rtpWriter == nil {
ri.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
if audioLevel != nil {
audioLevelPayload, err := new(rtp.AudioLevelExtension).Marshal()
if err != nil {
return 0, err
}
_ = header.SetExtension(uint8(audioLevel.ID), audioLevelPayload)
}
return ri.nextRTPWriter.Write(header, payload, attributes)
}
}
if err = track.WriteSample(media.Sample{Data: pageData, Duration: sampleDuration}); err != nil {
return errors.Wrapf(err, "Write sample")
}
if d := clock.Tick(sampleDuration); d > 0 {
time.Sleep(d)
}
}
return ctx.Err()
}

View file

@ -0,0 +1,158 @@
// 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 janus
import (
"github.com/pion/interceptor"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
type rtpInterceptorOptionFunc func(i *rtpInterceptor)
// Common RTP packet interceptor for benchmark.
// @remark Should never merge with rtcpInterceptor, because they has the same Write interface.
type rtpInterceptor struct {
// If rtpReader is nil, use the default next one to read.
rtpReader interceptor.RTPReaderFunc
nextRTPReader interceptor.RTPReader
// If rtpWriter is nil, use the default next one to write.
rtpWriter interceptor.RTPWriterFunc
nextRTPWriter interceptor.RTPWriter
// Other common fields.
bypassInterceptor
}
func newRTPInterceptor(options ...rtpInterceptorOptionFunc) *rtpInterceptor {
v := &rtpInterceptor{}
for _, opt := range options {
opt(v)
}
return v
}
func (v *rtpInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
v.nextRTPWriter = writer
return v // Handle all RTP
}
func (v *rtpInterceptor) Write(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
if v.rtpWriter != nil {
return v.rtpWriter(header, payload, attributes)
}
return v.nextRTPWriter.Write(header, payload, attributes)
}
func (v *rtpInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
}
func (v *rtpInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
v.nextRTPReader = reader
return v // Handle all RTP
}
func (v *rtpInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
if v.rtpReader != nil {
return v.rtpReader(b, a)
}
return v.nextRTPReader.Read(b, a)
}
func (v *rtpInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
}
type rtcpInterceptorOptionFunc func(i *rtcpInterceptor)
// Common RTCP packet interceptor for benchmark.
// @remark Should never merge with rtpInterceptor, because they has the same Write interface.
type rtcpInterceptor struct {
// If rtcpReader is nil, use the default next one to read.
rtcpReader interceptor.RTCPReaderFunc
nextRTCPReader interceptor.RTCPReader
// If rtcpWriter is nil, use the default next one to write.
rtcpWriter interceptor.RTCPWriterFunc
nextRTCPWriter interceptor.RTCPWriter
// Other common fields.
bypassInterceptor
}
func newRTCPInterceptor(options ...rtcpInterceptorOptionFunc) *rtcpInterceptor {
v := &rtcpInterceptor{}
for _, opt := range options {
opt(v)
}
return v
}
func (v *rtcpInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
v.nextRTCPReader = reader
return v // Handle all RTCP
}
func (v *rtcpInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
if v.rtcpReader != nil {
return v.rtcpReader(b, a)
}
return v.nextRTCPReader.Read(b, a)
}
func (v *rtcpInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
v.nextRTCPWriter = writer
return v // Handle all RTCP
}
func (v *rtcpInterceptor) Write(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
if v.rtcpWriter != nil {
return v.rtcpWriter(pkts, attributes)
}
return v.nextRTCPWriter.Write(pkts, attributes)
}
// Do nothing.
type bypassInterceptor struct {
interceptor.Interceptor
}
func (v *bypassInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
return reader
}
func (v *bypassInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
return writer
}
func (v *bypassInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
return writer
}
func (v *bypassInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
}
func (v *bypassInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
return reader
}
func (v *bypassInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
}
func (v *bypassInterceptor) Close() error {
return nil
}

198
trunk/3rdparty/srs-bench/janus/janus.go vendored Normal file
View file

@ -0,0 +1,198 @@
// 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 janus
import (
"context"
"flag"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"os"
"strings"
"sync"
"time"
)
var sr string
var pli int
var pr, sourceAudio, sourceVideo string
var fps int
var audioLevel, videoTWCC bool
var clients, streams, delay int
func Parse(ctx context.Context) {
fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
var sfu string
fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or janus")
fl.StringVar(&sr, "sr", "", "")
fl.IntVar(&pli, "pli", 10, "")
fl.StringVar(&pr, "pr", "", "")
fl.StringVar(&sourceAudio, "sa", "", "")
fl.StringVar(&sourceVideo, "sv", "", "")
fl.IntVar(&fps, "fps", 0, "")
fl.BoolVar(&audioLevel, "al", true, "")
fl.BoolVar(&videoTWCC, "twcc", true, "")
fl.IntVar(&clients, "nn", 1, "")
fl.IntVar(&streams, "sn", 1, "")
fl.IntVar(&delay, "delay", 50, "")
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 janus. Default: srs"))
fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1"))
fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1"))
fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50"))
fmt.Println(fmt.Sprintf(" -al [Optional] Whether enable audio-level. Default: true"))
fmt.Println(fmt.Sprintf(" -twcc [Optional] Whether enable vdieo-twcc. Default: true"))
fmt.Println(fmt.Sprintf("Player or Subscriber:"))
fmt.Println(fmt.Sprintf(" -sr The url to play/subscribe. If sn exceed 1, auto append variable %%d."))
fmt.Println(fmt.Sprintf(" -pli [Optional] PLI request interval in seconds. Default: 10"))
fmt.Println(fmt.Sprintf("Publisher:"))
fmt.Println(fmt.Sprintf(" -pr The url to publish. If sn exceed 1, auto append variable %%d."))
fmt.Println(fmt.Sprintf(" -fps 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个播放1个推流:"))
fmt.Println(fmt.Sprintf(" %v -sfu janus -sr webrtc://localhost:8080/2345/livestream", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -sfu janus -pr webrtc://localhost:8080/2345/livestream -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如1个流3个播放共3个客户端"))
fmt.Println(fmt.Sprintf(" %v -sfu janus -sr webrtc://localhost:8080/2345/livestream -nn 3", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -sfu janus -pr webrtc://localhost:8080/2345/livestream -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如2个流每个流3个播放共6个客户端"))
fmt.Println(fmt.Sprintf(" %v -sfu janus -sr webrtc://localhost:8080/2345/livestream_%%d -sn 2 -nn 3", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -sfu janus -pr webrtc://localhost:8080/2345/livestream_%%d -sn 2 -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如2个推流"))
fmt.Println(fmt.Sprintf(" %v -sfu janus -pr webrtc://localhost:8080/2345/livestream_%%d -sn 2 -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
}
if err := fl.Parse(os.Args[1:]); err == flag.ErrHelp {
os.Exit(0)
}
showHelp := (clients <= 0 || streams <= 0)
if sr == "" && pr == "" {
showHelp = true
}
if pr != "" && (sourceAudio == "" && sourceVideo == "") {
showHelp = true
}
if showHelp {
fl.Usage()
os.Exit(-1)
}
summaryDesc := fmt.Sprintf("delay=%v, al=%v, twcc=%v", delay, audioLevel, videoTWCC)
if sr != "" {
summaryDesc = fmt.Sprintf("%v, play(url=%v, pli=%v)", summaryDesc, sr, pli)
}
if pr != "" {
summaryDesc = fmt.Sprintf("%v, publish(url=%v, sa=%v, sv=%v, fps=%v)",
summaryDesc, pr, sourceAudio, sourceVideo, fps)
}
logger.Tf(ctx, "Run benchmark with %v", summaryDesc)
checkFlags := func() error {
if sourceVideo != "" && !strings.HasSuffix(sourceVideo, ".h264") {
return errors.Errorf("Should be .264, actual %v", sourceVideo)
}
if sourceVideo != "" && strings.HasSuffix(sourceVideo, ".h264") && fps <= 0 {
return errors.Errorf("Video fps should >0, actual %v", fps)
}
return nil
}
if err := checkFlags(); err != nil {
logger.Ef(ctx, "Check faile err %+v", err)
os.Exit(-1)
}
}
func Run(ctx context.Context) error {
// Run tasks.
var wg sync.WaitGroup
// Run all subscribers or players.
for i := 0; sr != "" && i < streams && ctx.Err() == nil; i++ {
r_auto := sr
if streams > 1 && !strings.Contains(r_auto, "%") {
r_auto += "%d"
}
r2 := r_auto
if strings.Contains(r2, "%") {
r2 = fmt.Sprintf(r2, i)
}
for j := 0; sr != "" && j < clients && ctx.Err() == nil; j++ {
wg.Add(1)
go func(sr string) {
defer wg.Done()
if err := startPlay(ctx, sr, audioLevel, videoTWCC, pli); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
}
}(r2)
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
// Run all publishers.
for i := 0; pr != "" && i < streams && ctx.Err() == nil; i++ {
r_auto := pr
if streams > 1 && !strings.Contains(r_auto, "%") {
r_auto += "%d"
}
r2 := r_auto
if strings.Contains(r2, "%") {
r2 = fmt.Sprintf(r2, i)
}
wg.Add(1)
go func(pr string) {
defer wg.Done()
if err := startPublish(ctx, pr, sourceAudio, sourceVideo, fps, audioLevel, videoTWCC); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
}
}(r2)
time.Sleep(time.Duration(delay) * time.Millisecond)
}
wg.Wait()
return nil
}

246
trunk/3rdparty/srs-bench/janus/player.go vendored Normal file
View file

@ -0,0 +1,246 @@
// 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 janus
import (
"context"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/pion/interceptor"
"github.com/pion/rtcp"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v3"
"net/url"
"strconv"
"strings"
"time"
)
func startPlay(ctx context.Context, r string, enableAudioLevel, enableTWCC bool, pli int) error {
ctx = logger.WithContext(ctx)
u, err := url.Parse(r)
if err != nil {
return errors.Wrapf(err, "Parse url %v", r)
}
var room int
var display string
if us := strings.SplitN(u.Path, "/", 3); len(us) >= 3 {
if iv, err := strconv.Atoi(us[1]); err != nil {
return errors.Wrapf(err, "parse %v", us[1])
} else {
room = iv
}
display = strings.Join(us[2:], "-")
}
logger.Tf(ctx, "Run play url=%v, room=%v, diplay=%v, audio-level=%v, twcc=%v",
r, room, display, enableAudioLevel, enableTWCC)
// For audio-level.
webrtcNewPeerConnection := func(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
m := &webrtc.MediaEngine{}
if err := m.RegisterDefaultCodecs(); err != nil {
return nil, err
}
for _, extension := range []string{sdp.SDESMidURI, sdp.SDESRTPStreamIDURI, sdp.TransportCCURI} {
if extension == sdp.TransportCCURI && !enableTWCC {
continue
}
if err := m.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeVideo); err != nil {
return nil, err
}
}
// https://github.com/pion/ion/issues/130
// https://github.com/pion/ion-sfu/pull/373/files#diff-6f42c5ac6f8192dd03e5a17e9d109e90cb76b1a4a7973be6ce44a89ffd1b5d18R73
for _, extension := range []string{sdp.SDESMidURI, sdp.SDESRTPStreamIDURI, sdp.AudioLevelURI} {
if extension == sdp.AudioLevelURI && !enableAudioLevel {
continue
}
if err := m.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeAudio); err != nil {
return nil, err
}
}
i := &interceptor.Registry{}
if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil {
return nil, err
}
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
return api.NewPeerConnection(configuration)
}
pc, err := webrtcNewPeerConnection(webrtc.Configuration{
SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,
})
if err != nil {
return errors.Wrapf(err, "Create PC")
}
var receivers []*webrtc.RTPReceiver
defer func() {
pc.Close()
for _, receiver := range receivers {
receiver.Stop()
}
}()
pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionRecvonly,
})
pc.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionRecvonly,
})
// Signaling API
api := newJanusAPI(fmt.Sprintf("http://%v/janus", u.Host))
if err := api.Create(ctx); err != nil {
return errors.Wrapf(err, "create")
}
defer api.Close()
// Discover the publisherInfo to subscribe.
publisherInfo, err := api.DiscoverPublisher(ctx, room, display, 5*time.Second)
if err != nil {
return err
}
logger.Tf(ctx, "Publisher found, room=%v, display=%v, %v", room, display, publisherInfo)
subscribeHandle, err := api.AttachPlugin(ctx)
if err != nil {
return errors.Wrap(err, "attach plugin")
}
offer, err := api.JoinAsSubscribe(ctx, subscribeHandle, room, publisherInfo)
if err != nil {
return errors.Wrapf(err, "subscribe")
}
// Exchange offer and generate answer.
if err := pc.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer, SDP: offer,
}); err != nil {
return errors.Wrapf(err, "Set offer %v", offer)
}
answer, err := pc.CreateAnswer(nil)
if err != nil {
return errors.Wrapf(err, "Create answer")
}
if err := pc.SetLocalDescription(answer); err != nil {
return errors.Wrapf(err, "Set answer %v", answer)
}
// Send answer to Janus.
if err := api.Subscribe(ctx, subscribeHandle, room, answer.SDP); err != nil {
return errors.Wrapf(err, "Subscribe with answer %v", answer)
}
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
}
if pli <= 0 {
return
}
for {
select {
case <-ctx.Done():
return
case <-time.After(time.Duration(pli) * time.Second):
_ = pc.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{
MediaSSRC: uint32(track.SSRC()),
}})
}
}
}()
receivers = append(receivers, receiver)
codec := track.Codec()
trackDesc := fmt.Sprintf("channels=%v", codec.Channels)
if track.Kind() == webrtc.RTPCodecTypeVideo {
trackDesc = fmt.Sprintf("fmtp=%v", codec.SDPFmtpLine)
}
if headers := receiver.GetParameters().HeaderExtensions; len(headers) > 0 {
trackDesc = fmt.Sprintf("%v, header=%v", trackDesc, headers)
}
logger.Tf(ctx, "Got track %v, pt=%v, tbn=%v, %v",
codec.MimeType, codec.PayloadType, codec.ClockRate, trackDesc)
return writeTrackToDisk(ctx, track)
}
ctx, cancel := context.WithCancel(ctx)
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.If(ctx, "ICE state %v", state)
if state == webrtc.ICEConnectionStateFailed || state == webrtc.ICEConnectionStateClosed {
if ctx.Err() != nil {
return
}
logger.Wf(ctx, "Close for ICE state %v", state)
cancel()
}
})
<-ctx.Done()
return nil
}
func writeTrackToDisk(ctx context.Context, track *webrtc.TrackRemote) error {
for ctx.Err() == nil {
pkt, _, err := track.ReadRTP()
if err != nil {
if ctx.Err() != nil {
return nil
}
return errors.Wrapf(err, "Read RTP")
}
logger.If(ctx, "Got packet ssrc=%v, pt=%v, seq=%v %vB",
pkt.SSRC, pkt.PayloadType, pkt.SequenceNumber, len(pkt.Payload))
}
return ctx.Err()
}

View file

@ -0,0 +1,352 @@
// 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 janus
import (
"context"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/pion/interceptor"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v3"
"io"
"net/url"
"strconv"
"strings"
"sync"
)
func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps int, enableAudioLevel, enableTWCC bool) error {
ctx = logger.WithContext(ctx)
u, err := url.Parse(r)
if err != nil {
return errors.Wrapf(err, "Parse url %v", r)
}
var room int
var display string
if us := strings.SplitN(u.Path, "/", 3); len(us) >= 3 {
if iv, err := strconv.Atoi(us[1]); err != nil {
return errors.Wrapf(err, "parse %v", us[1])
} else {
room = iv
}
display = strings.Join(us[2:], "-")
}
logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v, audio-level=%v, twcc=%v",
r, sourceAudio, sourceVideo, fps, enableAudioLevel, enableTWCC)
// Filter for SPS/PPS marker.
var aIngester *audioIngester
var vIngester *videoIngester
// For audio-level and sps/pps marker.
// TODO: FIXME: Should share with player.
webrtcNewPeerConnection := func(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
m := &webrtc.MediaEngine{}
if err := m.RegisterDefaultCodecs(); err != nil {
return nil, err
}
for _, extension := range []string{sdp.SDESMidURI, sdp.SDESRTPStreamIDURI, sdp.TransportCCURI} {
if extension == sdp.TransportCCURI && !enableTWCC {
continue
}
if err := m.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeVideo); err != nil {
return nil, err
}
}
// https://github.com/pion/ion/issues/130
// https://github.com/pion/ion-sfu/pull/373/files#diff-6f42c5ac6f8192dd03e5a17e9d109e90cb76b1a4a7973be6ce44a89ffd1b5d18R73
for _, extension := range []string{sdp.SDESMidURI, sdp.SDESRTPStreamIDURI, sdp.AudioLevelURI} {
if extension == sdp.AudioLevelURI && !enableAudioLevel {
continue
}
if err := m.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeAudio); err != nil {
return nil, err
}
}
registry := &interceptor.Registry{}
if err := webrtc.RegisterDefaultInterceptors(m, registry); err != nil {
return nil, err
}
if sourceAudio != "" {
aIngester = newAudioIngester(sourceAudio)
registry.Add(aIngester.audioLevelInterceptor)
}
if sourceVideo != "" {
vIngester = newVideoIngester(sourceVideo)
registry.Add(vIngester.markerInterceptor)
}
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(registry))
return api.NewPeerConnection(configuration)
}
pc, err := webrtcNewPeerConnection(webrtc.Configuration{})
if err != nil {
return errors.Wrapf(err, "Create PC")
}
doClose := func() {
if pc != nil {
pc.Close()
}
if vIngester != nil {
vIngester.Close()
}
if aIngester != nil {
aIngester.Close()
}
}
defer doClose()
if vIngester != nil {
if err := vIngester.AddTrack(pc, fps); err != nil {
return errors.Wrapf(err, "Add track")
}
}
if aIngester != nil {
if err := 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)
}
// Signaling API
api := newJanusAPI(fmt.Sprintf("http://%v/janus", u.Host))
webrtcUpCtx, webrtcUpCancel := context.WithCancel(ctx)
api.onWebrtcUp = func(sender, sessionID uint64) {
logger.Tf(ctx, "Event webrtcup: DTLS/SRTP done, from=(sender:%v,session:%v)", sender, sessionID)
webrtcUpCancel()
}
api.onMedia = func(sender, sessionID uint64, mtype string, receiving bool) {
logger.Tf(ctx, "Event media: %v receiving=%v, from=(sender:%v,session:%v)", mtype, receiving, sender, sessionID)
}
api.onSlowLink = func(sender, sessionID uint64, media string, lost uint64, uplink bool) {
logger.Tf(ctx, "Event slowlink: %v lost=%v, uplink=%v, from=(sender:%v,session:%v)", media, lost, uplink, sender, sessionID)
}
api.onPublisher = func(sender, sessionID uint64, publishers []publisherInfo) {
logger.Tf(ctx, "Event publisher: %v, from=(sender:%v,session:%v)", publishers, sender, sessionID)
}
api.onUnPublished = func(sender, sessionID, id uint64) {
logger.Tf(ctx, "Event unpublish: %v, from=(sender:%v,session:%v)", id, sender, sessionID)
}
api.onLeave = func(sender, sessionID, id uint64) {
logger.Tf(ctx, "Event leave: %v, from=(sender:%v,session:%v)", id, sender, sessionID)
}
if err := api.Create(ctx); err != nil {
return errors.Wrapf(err, "create")
}
defer api.Close()
publishHandleID, err := api.AttachPlugin(ctx)
if err != nil {
return errors.Wrapf(err, "attach plugin")
}
defer api.DetachPlugin(ctx, publishHandleID)
if err := api.JoinAsPublisher(ctx, publishHandleID, room, display); err != nil {
return errors.Wrapf(err, "join as publisher")
}
answer, err := api.Publish(ctx, publishHandleID, offer.SDP)
if err != nil {
return errors.Wrapf(err, "join as publisher")
}
defer api.UnPublish(ctx, publishHandleID)
// Setup the offer-answer
if err := pc.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeAnswer, SDP: answer,
}); err != nil {
return errors.Wrapf(err, "Set answer %v", answer)
}
logger.Tf(ctx, "State signaling=%v, ice=%v, conn=%v", pc.SignalingState(), pc.ICEConnectionState(), pc.ConnectionState())
// ICE state management.
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 aIngester != nil {
aIngester.sAudioSender.Transport().OnStateChange(func(state webrtc.DTLSTransportState) {
logger.Tf(ctx, "DTLS state %v", state)
})
}
ctx, cancel := context.WithCancel(ctx)
pcDoneCtx, pcDoneCancel := context.WithCancel(context.Background())
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
logger.Tf(ctx, "PC state %v", state)
if state == webrtc.PeerConnectionStateConnected {
pcDoneCancel()
}
if state == webrtc.PeerConnectionStateFailed || state == webrtc.PeerConnectionStateClosed {
if ctx.Err() != nil {
return
}
logger.Wf(ctx, "Close for PC state %v", state)
cancel()
}
})
// OK, DTLS/SRTP ok.
select {
case <-ctx.Done():
return nil
case <-webrtcUpCtx.Done():
}
// Wait for event from context or tracks.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
doClose() // Interrupt the RTCP read.
}()
wg.Add(1)
go func() {
defer wg.Done()
if aIngester == nil {
return
}
select {
case <-ctx.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start read audio packets")
}
buf := make([]byte, 1500)
for ctx.Err() == nil {
if _, _, err := aIngester.sAudioSender.Read(buf); err != nil {
return
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if aIngester == nil {
return
}
select {
case <-ctx.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest audio %v", sourceAudio)
}
// Read audio and send out.
for ctx.Err() == nil {
if err := aIngester.Ingest(ctx); err != nil {
if errors.Cause(err) == io.EOF {
logger.Tf(ctx, "EOF, restart ingest audio %v", sourceAudio)
continue
}
logger.Wf(ctx, "Ignore audio err %+v", err)
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if vIngester == nil {
return
}
select {
case <-ctx.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start read video packets")
}
buf := make([]byte, 1500)
for ctx.Err() == nil {
if _, _, err := vIngester.sVideoSender.Read(buf); err != nil {
return
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if vIngester == nil {
return
}
select {
case <-ctx.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest video %v", sourceVideo)
}
for ctx.Err() == nil {
if err := vIngester.Ingest(ctx); err != nil {
if errors.Cause(err) == io.EOF {
logger.Tf(ctx, "EOF, restart ingest video %v", sourceVideo)
continue
}
logger.Wf(ctx, "Ignore video err %+v", err)
}
}
}()
wg.Wait()
return nil
}

1176
trunk/3rdparty/srs-bench/janus/util.go vendored Normal file

File diff suppressed because it is too large Load diff

48
trunk/3rdparty/srs-bench/janus/util2.go vendored Normal file
View file

@ -0,0 +1,48 @@
// 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 janus
import (
"encoding/json"
"math/rand"
"strings"
)
func newTransactionID() string {
sb := strings.Builder{}
for i := 0; i < 12; i++ {
sb.WriteByte(byte('a') + byte(rand.Int()%26))
}
return sb.String()
}
func escapeJSON(s string) string {
var o map[string]interface{}
if err := json.Unmarshal([]byte(s), &o); err != nil {
return s
}
if b, err := json.Marshal(o); err != nil {
return s
} else {
return string(b)
}
}

View file

@ -24,133 +24,35 @@ import (
"context"
"flag"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/ossrs/srs-bench/janus"
"github.com/ossrs/srs-bench/srs"
"net"
"net/http"
"io/ioutil"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
func main() {
var sr, dumpAudio, dumpVideo string
var pli int
flag.StringVar(&sr, "sr", "", "")
flag.StringVar(&dumpAudio, "da", "", "")
flag.StringVar(&dumpVideo, "dv", "", "")
flag.IntVar(&pli, "pli", 10, "")
var pr, sourceAudio, sourceVideo string
var fps int
flag.StringVar(&pr, "pr", "", "")
flag.StringVar(&sourceAudio, "sa", "", "")
flag.StringVar(&sourceVideo, "sv", "", "")
flag.IntVar(&fps, "fps", 0, "")
var audioLevel, videoTWCC bool
flag.BoolVar(&audioLevel, "al", true, "")
flag.BoolVar(&videoTWCC, "twcc", true, "")
var clients, streams, delay int
flag.IntVar(&clients, "nn", 1, "")
flag.IntVar(&streams, "sn", 1, "")
flag.IntVar(&delay, "delay", 50, "")
var statListen string
flag.StringVar(&statListen, "stat", "", "")
flag.Usage = func() {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1"))
fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1"))
fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50"))
fmt.Println(fmt.Sprintf(" -al [Optional] Whether enable audio-level. Default: true"))
fmt.Println(fmt.Sprintf(" -twcc [Optional] Whether enable vdieo-twcc. Default: true"))
fmt.Println(fmt.Sprintf(" -stat [Optional] The stat server API listen port."))
fmt.Println(fmt.Sprintf("Player or Subscriber:"))
fmt.Println(fmt.Sprintf(" -sr The url to play/subscribe. If sn exceed 1, auto append variable %%d."))
fmt.Println(fmt.Sprintf(" -da [Optional] The file path to dump audio, ignore if empty."))
fmt.Println(fmt.Sprintf(" -dv [Optional] The file path to dump video, ignore if empty."))
fmt.Println(fmt.Sprintf(" -pli [Optional] PLI request interval in seconds. Default: 10"))
fmt.Println(fmt.Sprintf("Publisher:"))
fmt.Println(fmt.Sprintf(" -pr The url to publish. If sn exceed 1, auto append variable %%d."))
fmt.Println(fmt.Sprintf(" -fps 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个播放1个推流:"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如1个流3个播放共3个客户端"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -nn 3", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如2个流每个流3个播放共6个客户端"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream_%%d -sn 2 -nn 3", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如2个推流"))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如1个录制"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -da a.ogg -dv v.h264", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如1个明文播放"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream?encrypt=false", os.Args[0]))
fmt.Println()
}
flag.Parse()
showHelp := (clients <= 0 || streams <= 0)
if sr == "" && pr == "" {
showHelp = true
}
if pr != "" && (sourceAudio == "" && sourceVideo == "") {
showHelp = true
}
if showHelp {
flag.Usage()
os.Exit(-1)
}
if statListen != "" && !strings.Contains(statListen, ":") {
statListen = ":" + statListen
}
var sfu string
fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fl.SetOutput(ioutil.Discard)
fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or janus")
_ = fl.Parse(os.Args[1:])
ctx := context.Background()
summaryDesc := fmt.Sprintf("clients=%v, delay=%v, al=%v, twcc=%v, stat=%v", clients, delay, audioLevel, videoTWCC, statListen)
if sr != "" {
summaryDesc = fmt.Sprintf("%v, play(url=%v, da=%v, dv=%v, pli=%v)", summaryDesc, sr, dumpAudio, dumpVideo, pli)
}
if pr != "" {
summaryDesc = fmt.Sprintf("%v, publish(url=%v, sa=%v, sv=%v, fps=%v)",
summaryDesc, pr, sourceAudio, sourceVideo, fps)
}
logger.Tf(ctx, "Start benchmark with %v", summaryDesc)
checkFlag := func() error {
if dumpVideo != "" && !strings.HasSuffix(dumpVideo, ".h264") && !strings.HasSuffix(dumpVideo, ".ivf") {
return errors.Errorf("Should be .ivf or .264, actual %v", dumpVideo)
}
if sourceVideo != "" && !strings.HasSuffix(sourceVideo, ".h264") {
return errors.Errorf("Should be .264, actual %v", sourceVideo)
}
if sourceVideo != "" && strings.HasSuffix(sourceVideo, ".h264") && fps <= 0 {
return errors.Errorf("Video fps should >0, actual %v", fps)
}
return nil
}
if err := checkFlag(); err != nil {
logger.Ef(ctx, "Check faile err %+v", err)
if sfu == "srs" {
srs.Parse(ctx)
} else if sfu == "janus" {
janus.Parse(ctx)
} else {
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 janus. Default: srs"))
os.Exit(-1)
}
ctx, cancel := context.WithCancel(ctx)
// Process all signals.
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
@ -160,124 +62,17 @@ func main() {
}
}()
// Run tasks.
var wg sync.WaitGroup
// Start STAT API server.
wg.Add(1)
go func() {
defer wg.Done()
if statListen == "" {
return
}
var lc net.ListenConfig
ln, err := lc.Listen(ctx, "tcp", statListen)
if err != nil {
logger.Ef(ctx, "stat listen err+%v", err)
cancel()
return
}
mux := http.NewServeMux()
srs.HandleStat(ctx, mux, statListen)
srv := &http.Server{
Handler: mux,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
}
go func() {
<-ctx.Done()
srv.Shutdown(ctx)
}()
logger.Tf(ctx, "Stat listen at %v", statListen)
if err := srv.Serve(ln); err != nil {
if ctx.Err() == nil {
logger.Ef(ctx, "stat serve err+%v", err)
cancel()
}
return
}
}()
// Start all subscribers or players.
for i := 0; sr != "" && i < streams && ctx.Err() == nil; i++ {
r_auto := sr
if streams > 1 && !strings.Contains(r_auto, "%") {
r_auto += "%d"
}
r2 := r_auto
if strings.Contains(r2, "%") {
r2 = fmt.Sprintf(r2, i)
}
for j := 0; sr != "" && j < clients && ctx.Err() == nil; j++ {
// Dump audio or video only for the first client.
da, dv := dumpAudio, dumpVideo
if i > 0 {
da, dv = "", ""
}
srs.StatRTC.Subscribers.Expect++
srs.StatRTC.Subscribers.Alive++
wg.Add(1)
go func(sr, da, dv string) {
defer wg.Done()
defer func() {
srs.StatRTC.Subscribers.Alive--
}()
if err := srs.StartPlay(ctx, sr, da, dv, audioLevel, videoTWCC, pli); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
}
}(r2, da, dv)
time.Sleep(time.Duration(delay) * time.Millisecond)
}
var err error
if sfu == "srs" {
err = srs.Run(ctx)
} else if sfu == "janus" {
err = janus.Run(ctx)
}
// Start all publishers.
for i := 0; pr != "" && i < streams && ctx.Err() == nil; i++ {
r_auto := pr
if streams > 1 && !strings.Contains(r_auto, "%") {
r_auto += "%d"
}
r2 := r_auto
if strings.Contains(r2, "%") {
r2 = fmt.Sprintf(r2, i)
}
srs.StatRTC.Publishers.Expect++
srs.StatRTC.Publishers.Alive++
wg.Add(1)
go func(pr string) {
defer wg.Done()
defer func() {
srs.StatRTC.Publishers.Alive--
}()
if err := srs.StartPublish(ctx, pr, sourceAudio, sourceVideo, fps, audioLevel, videoTWCC); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
}
}(r2)
time.Sleep(time.Duration(delay) * time.Millisecond)
if err != nil {
logger.Wf(ctx, "srs err %+v", err)
return
}
wg.Wait()
logger.Tf(ctx, "Done")
}

View file

@ -41,15 +41,15 @@ import (
type videoIngester struct {
sourceVideo string
fps int
markerInterceptor *RTPInterceptor
markerInterceptor *rtpInterceptor
sVideoTrack *webrtc.TrackLocalStaticSample
sVideoSender *webrtc.RTPSender
ready context.Context
readyCancel context.CancelFunc
}
func NewVideoIngester(sourceVideo string) *videoIngester {
v := &videoIngester{markerInterceptor: &RTPInterceptor{}, sourceVideo: sourceVideo}
func newVideoIngester(sourceVideo string) *videoIngester {
v := &videoIngester{markerInterceptor: &rtpInterceptor{}, sourceVideo: sourceVideo}
v.ready, v.readyCancel = context.WithCancel(context.Background())
return v
}
@ -183,15 +183,15 @@ func (v *videoIngester) Ingest(ctx context.Context) error {
type audioIngester struct {
sourceAudio string
audioLevelInterceptor *RTPInterceptor
audioLevelInterceptor *rtpInterceptor
sAudioTrack *webrtc.TrackLocalStaticSample
sAudioSender *webrtc.RTPSender
ready context.Context
readyCancel context.CancelFunc
}
func NewAudioIngester(sourceAudio string) *audioIngester {
v := &audioIngester{audioLevelInterceptor: &RTPInterceptor{}, sourceAudio: sourceAudio}
func newAudioIngester(sourceAudio string) *audioIngester {
v := &audioIngester{audioLevelInterceptor: &rtpInterceptor{}, sourceAudio: sourceAudio}
v.ready, v.readyCancel = context.WithCancel(context.Background())
return v
}

View file

@ -26,11 +26,11 @@ import (
"github.com/pion/rtp"
)
type RTPInterceptorOptionFunc func(i *RTPInterceptor)
type rtpInterceptorOptionFunc func(i *rtpInterceptor)
// Common RTP packet interceptor for benchmark.
// @remark Should never merge with RTCPInterceptor, because they has the same Write interface.
type RTPInterceptor struct {
// @remark Should never merge with rtcpInterceptor, because they has the same Write interface.
type rtpInterceptor struct {
// If rtpReader is nil, use the default next one to read.
rtpReader interceptor.RTPReaderFunc
nextRTPReader interceptor.RTPReader
@ -38,52 +38,52 @@ type RTPInterceptor struct {
rtpWriter interceptor.RTPWriterFunc
nextRTPWriter interceptor.RTPWriter
// Other common fields.
BypassInterceptor
bypassInterceptor
}
func NewRTPInterceptor(options ...RTPInterceptorOptionFunc) *RTPInterceptor {
v := &RTPInterceptor{}
func newRTPInterceptor(options ...rtpInterceptorOptionFunc) *rtpInterceptor {
v := &rtpInterceptor{}
for _, opt := range options {
opt(v)
}
return v
}
func (v *RTPInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
func (v *rtpInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
v.nextRTPWriter = writer
return v // Handle all RTP
}
func (v *RTPInterceptor) Write(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
func (v *rtpInterceptor) Write(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
if v.rtpWriter != nil {
return v.rtpWriter(header, payload, attributes)
}
return v.nextRTPWriter.Write(header, payload, attributes)
}
func (v *RTPInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
func (v *rtpInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
}
func (v *RTPInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
func (v *rtpInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
v.nextRTPReader = reader
return v // Handle all RTP
}
func (v *RTPInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
func (v *rtpInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
if v.rtpReader != nil {
return v.rtpReader(b, a)
}
return v.nextRTPReader.Read(b, a)
}
func (v *RTPInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
func (v *rtpInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
}
type RTCPInterceptorOptionFunc func(i *RTCPInterceptor)
type rtcpInterceptorOptionFunc func(i *rtcpInterceptor)
// Common RTCP packet interceptor for benchmark.
// @remark Should never merge with RTPInterceptor, because they has the same Write interface.
type RTCPInterceptor struct {
// @remark Should never merge with rtpInterceptor, because they has the same Write interface.
type rtcpInterceptor struct {
// If rtcpReader is nil, use the default next one to read.
rtcpReader interceptor.RTCPReaderFunc
nextRTCPReader interceptor.RTCPReader
@ -91,35 +91,35 @@ type RTCPInterceptor struct {
rtcpWriter interceptor.RTCPWriterFunc
nextRTCPWriter interceptor.RTCPWriter
// Other common fields.
BypassInterceptor
bypassInterceptor
}
func NewRTCPInterceptor(options ...RTCPInterceptorOptionFunc) *RTCPInterceptor {
v := &RTCPInterceptor{}
func newRTCPInterceptor(options ...rtcpInterceptorOptionFunc) *rtcpInterceptor {
v := &rtcpInterceptor{}
for _, opt := range options {
opt(v)
}
return v
}
func (v *RTCPInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
func (v *rtcpInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
v.nextRTCPReader = reader
return v // Handle all RTCP
}
func (v *RTCPInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
func (v *rtcpInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
if v.rtcpReader != nil {
return v.rtcpReader(b, a)
}
return v.nextRTCPReader.Read(b, a)
}
func (v *RTCPInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
func (v *rtcpInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
v.nextRTCPWriter = writer
return v // Handle all RTCP
}
func (v *RTCPInterceptor) Write(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
func (v *rtcpInterceptor) Write(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
if v.rtcpWriter != nil {
return v.rtcpWriter(pkts, attributes)
}
@ -127,32 +127,32 @@ func (v *RTCPInterceptor) Write(pkts []rtcp.Packet, attributes interceptor.Attri
}
// Do nothing.
type BypassInterceptor struct {
type bypassInterceptor struct {
interceptor.Interceptor
}
func (v *BypassInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
func (v *bypassInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
return reader
}
func (v *BypassInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
func (v *bypassInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
return writer
}
func (v *BypassInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
func (v *bypassInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
return writer
}
func (v *BypassInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
func (v *bypassInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
}
func (v *BypassInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
func (v *bypassInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
return reader
}
func (v *BypassInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
func (v *bypassInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
}
func (v *BypassInterceptor) Close() error {
func (v *bypassInterceptor) Close() error {
return nil
}

View file

@ -40,10 +40,10 @@ import (
)
// @see https://github.com/pion/webrtc/blob/master/examples/save-to-disk/main.go
func StartPlay(ctx context.Context, r, dumpAudio, dumpVideo string, enableAudioLevel, enableTWCC bool, pli int) error {
func startPlay(ctx context.Context, r, dumpAudio, dumpVideo string, enableAudioLevel, enableTWCC bool, pli int) error {
ctx = logger.WithContext(ctx)
logger.Tf(ctx, "Start play url=%v, audio=%v, video=%v, audio-level=%v, twcc=%v",
logger.Tf(ctx, "Run play url=%v, audio=%v, video=%v, audio-level=%v, twcc=%v",
r, dumpAudio, dumpVideo, enableAudioLevel, enableTWCC)
// For audio-level.
@ -257,7 +257,7 @@ func StartPlay(ctx context.Context, r, dumpAudio, dumpVideo string, enableAudioL
case <-ctx.Done():
return
case <-time.After(5 * time.Second):
StatRTC.PeerConnection = pc.GetStats()
gStatRTC.PeerConnection = pc.GetStats()
}
}
}()

View file

@ -34,10 +34,10 @@ import (
)
// @see https://github.com/pion/webrtc/blob/master/examples/play-from-disk/main.go
func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps int, enableAudioLevel, enableTWCC bool) error {
func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps int, enableAudioLevel, enableTWCC bool) error {
ctx = logger.WithContext(ctx)
logger.Tf(ctx, "Start publish url=%v, audio=%v, video=%v, fps=%v, audio-level=%v, twcc=%v",
logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v, audio-level=%v, twcc=%v",
r, sourceAudio, sourceVideo, fps, enableAudioLevel, enableTWCC)
// Filter for SPS/PPS marker.
@ -78,11 +78,11 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
}
if sourceAudio != "" {
aIngester = NewAudioIngester(sourceAudio)
aIngester = newAudioIngester(sourceAudio)
registry.Add(aIngester.audioLevelInterceptor)
}
if sourceVideo != "" {
vIngester = NewVideoIngester(sourceVideo)
vIngester = newVideoIngester(sourceVideo)
registry.Add(vIngester.markerInterceptor)
}
@ -158,7 +158,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
}
ctx, cancel := context.WithCancel(ctx)
pcDone, pcDoneCancel := context.WithCancel(context.Background())
pcDoneCtx, pcDoneCancel := context.WithCancel(context.Background())
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
logger.Tf(ctx, "PC state %v", state)
@ -196,7 +196,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
case <-pcDone.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start read audio packets")
}
@ -218,7 +218,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
case <-pcDone.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest audio %v", sourceAudio)
}
@ -244,7 +244,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
case <-pcDone.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start read video packets")
}
@ -266,7 +266,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
case <-pcDone.Done():
case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest video %v", sourceVideo)
}
@ -290,7 +290,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
case <-ctx.Done():
return
case <-time.After(5 * time.Second):
StatRTC.PeerConnection = pc.GetStats()
gStatRTC.PeerConnection = pc.GetStats()
}
}
}()

View file

@ -58,6 +58,56 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
// Basic use scenario, publish a stream.
func TestRtcBasic_PublishOnly(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("publish-only-%v-%v", os.Getpid(), rand.Int())
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
return nil
})
if err != nil {
return err
}
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
}
logger.Tf(ctx, "publish write %v RTP read %v RTCP packets", nnRTP, nnRTCP)
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
logger.Tf(ctx, "Chunk %v, ok=%v %v bytes", chunk, ok, len(c.UserData()))
return true
})
}); err != nil {
return err
}
return p.Run(ctx, cancel)
}()); err != nil {
t.Errorf("err %+v", err)
}
}
// Basic use scenario, publish a stream, then play it.
func TestRtcBasic_PublishPlay(t *testing.T) {
ctx := logger.WithContext(context.Background())
@ -83,8 +133,8 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
defer wg.Wait()
// The event notify.
var thePublisher *TestPublisher
var thePlayer *TestPlayer
var thePublisher *testPublisher
var thePlayer *testPlayer
mainReady, mainReadyCancel := context.WithCancel(context.Background())
publishReady, publishReadyCancel := context.WithCancel(context.Background())
@ -99,13 +149,13 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
streamSuffix := fmt.Sprintf("basic-publish-play-%v-%v", os.Getpid(), rand.Int())
// Initialize player with private api.
if thePlayer, err = NewTestPlayer(CreateApiForPlayer, func(play *TestPlayer) error {
if thePlayer, err = newTestPlayer(createApiForPlayer, func(play *testPlayer) error {
play.streamSuffix = streamSuffix
resources = append(resources, play)
var nnPlayWriteRTCP, nnPlayReadRTCP, nnPlayWriteRTP, nnPlayReadRTP uint64
return play.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
return play.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpReader = func(payload []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnPlayReadRTP++; nnPlayReadRTP >= uint64(*srsPlayOKPackets) {
cancel() // Completed.
@ -115,7 +165,7 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
return i.nextRTPReader.Read(payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTCPReader.Read(buf, attributes)
nnPlayReadRTCP++
@ -133,14 +183,14 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
}
// Initialize publisher with private api.
if thePublisher, err = NewTestPublisher(CreateApiForPublisher, func(pub *TestPublisher) error {
if thePublisher, err = newTestPublisher(createApiForPublisher, func(pub *testPublisher) error {
pub.streamSuffix = streamSuffix
pub.iceReadyCancel = publishReadyCancel
resources = append(resources, pub)
var nnPubWriteRTCP, nnPubReadRTCP, nnPubWriteRTP, nnPubReadRTP uint64
return pub.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
return pub.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTPReader.Read(buf, attributes)
nnPubReadRTP++
@ -154,7 +204,7 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
return nn, err
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTCPReader.Read(buf, attributes)
nnPubReadRTCP++
@ -237,8 +287,8 @@ func TestRtcBasic_Republish(t *testing.T) {
defer wg.Wait()
// The event notify.
var thePublisher, theRepublisher *TestPublisher
var thePlayer *TestPlayer
var thePublisher, theRepublisher *testPublisher
var thePlayer *testPlayer
mainReady, mainReadyCancel := context.WithCancel(context.Background())
publishReady, publishReadyCancel := context.WithCancel(context.Background())
@ -254,13 +304,13 @@ func TestRtcBasic_Republish(t *testing.T) {
streamSuffix := fmt.Sprintf("basic-publish-play-%v-%v", os.Getpid(), rand.Int())
// Initialize player with private api.
if thePlayer, err = NewTestPlayer(CreateApiForPlayer, func(play *TestPlayer) error {
if thePlayer, err = newTestPlayer(createApiForPlayer, func(play *testPlayer) error {
play.streamSuffix = streamSuffix
resources = append(resources, play)
var nnPlayReadRTP uint64
return play.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
return play.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpReader = func(payload []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
select {
case <-republishReady.Done():
@ -280,14 +330,14 @@ func TestRtcBasic_Republish(t *testing.T) {
}
// Initialize publisher with private api.
if thePublisher, err = NewTestPublisher(CreateApiForPublisher, func(pub *TestPublisher) error {
if thePublisher, err = newTestPublisher(createApiForPublisher, func(pub *testPublisher) error {
pub.streamSuffix = streamSuffix
pub.iceReadyCancel = publishReadyCancel
resources = append(resources, pub)
var nnPubReadRTCP uint64
return pub.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
return pub.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTCPReader.Read(buf, attributes)
if nnPubReadRTCP++; nnPubReadRTCP > 0 && pub.cancel != nil {
@ -303,7 +353,7 @@ func TestRtcBasic_Republish(t *testing.T) {
}
// Initialize re-publisher with private api.
if theRepublisher, err = NewTestPublisher(CreateApiForPublisher, func(pub *TestPublisher) error {
if theRepublisher, err = newTestPublisher(createApiForPublisher, func(pub *testPublisher) error {
pub.streamSuffix = streamSuffix
pub.iceReadyCancel = republishReadyCancel
resources = append(resources, pub)
@ -369,7 +419,7 @@ func TestRtcBasic_Republish(t *testing.T) {
func TestRtcDTLS_ClientActive_Default(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@ -380,15 +430,15 @@ func TestRtcDTLS_ClientActive_Default(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -397,9 +447,9 @@ func TestRtcDTLS_ClientActive_Default(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@ -424,7 +474,7 @@ func TestRtcDTLS_ClientActive_Default(t *testing.T) {
func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-active-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -435,15 +485,15 @@ func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -452,9 +502,9 @@ func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@ -476,7 +526,7 @@ func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-active-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@ -487,15 +537,15 @@ func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -504,15 +554,15 @@ func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS {
return true
}
// Copy the alert to server, ignore error.
if chunk.content == DTLSContentTypeAlert {
if chunk.content == dtlsContentTypeAlert {
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
}
@ -535,7 +585,7 @@ func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
func TestRtcDTLS_ClientPassive_Duplicated_Alert(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-active-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -546,15 +596,15 @@ func TestRtcDTLS_ClientPassive_Duplicated_Alert(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -563,15 +613,15 @@ func TestRtcDTLS_ClientPassive_Duplicated_Alert(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS {
return true
}
// Copy the alert to server, ignore error.
if chunk.content == DTLSContentTypeAlert {
if chunk.content == dtlsContentTypeAlert {
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
}
@ -601,7 +651,7 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.T
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@ -612,15 +662,15 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.T
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -629,17 +679,17 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.T
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnClientHello, nnMaxDrop := 0, 1
var lastClientHello *DTLSRecord
var lastClientHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeClientHello {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeClientHello {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@ -679,7 +729,7 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -690,15 +740,15 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -707,17 +757,17 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnClientHello, nnMaxDrop := 0, 1
var lastClientHello *DTLSRecord
var lastClientHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeClientHello {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeClientHello {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@ -756,7 +806,7 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.T
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@ -767,15 +817,15 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.T
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -784,23 +834,23 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.T
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnServerHello, nnMaxDrop := 0, 1
var lastClientHello, lastServerHello *DTLSRecord
var lastClientHello, lastServerHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake ||
(chunk.handshake != DTLSHandshakeTypeClientHello && chunk.handshake != DTLSHandshakeTypeServerHello) {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake ||
(chunk.handshake != dtlsHandshakeTypeClientHello && chunk.handshake != dtlsHandshakeTypeServerHello) {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
if chunk.handshake == DTLSHandshakeTypeClientHello {
if chunk.handshake == dtlsHandshakeTypeClientHello {
if lastClientHello != nil && record.Equals(lastClientHello) {
r0 = errors.Errorf("dup record %v", record)
}
@ -844,7 +894,7 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -855,15 +905,15 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -872,23 +922,23 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnServerHello, nnMaxDrop := 0, 1
var lastClientHello, lastServerHello *DTLSRecord
var lastClientHello, lastServerHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake ||
(chunk.handshake != DTLSHandshakeTypeClientHello && chunk.handshake != DTLSHandshakeTypeServerHello) {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake ||
(chunk.handshake != dtlsHandshakeTypeClientHello && chunk.handshake != dtlsHandshakeTypeServerHello) {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
if chunk.handshake == DTLSHandshakeTypeClientHello {
if chunk.handshake == dtlsHandshakeTypeClientHello {
if lastClientHello != nil && record.Equals(lastClientHello) {
r0 = errors.Errorf("dup record %v", record)
}
@ -929,7 +979,7 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_Certificate(t *testing.T
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-certificate-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@ -940,15 +990,15 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_Certificate(t *testing.T
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -957,17 +1007,17 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_Certificate(t *testing.T
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
var lastCertificate *DTLSRecord
var lastCertificate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeCertificate {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeCertificate {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@ -1006,7 +1056,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_Certificate(t *testing.
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-certificate-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1017,15 +1067,15 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_Certificate(t *testing.
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -1034,17 +1084,17 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_Certificate(t *testing.
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
var lastCertificate *DTLSRecord
var lastCertificate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeCertificate {
chunk, parsed := newChunkMessageType(c)
if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeCertificate {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@ -1083,7 +1133,7 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *test
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-certificate-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@ -1094,15 +1144,15 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *test
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -1111,17 +1161,17 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *test
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
var lastChangeCipherSepc, lastCertifidate *DTLSRecord
var lastChangeCipherSepc, lastCertifidate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed || (!chunk.IsChangeCipherSpec() && !chunk.IsCertificate()) {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@ -1169,7 +1219,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-certificate-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1180,15 +1230,15 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -1197,17 +1247,17 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
var lastChangeCipherSepc, lastCertifidate *DTLSRecord
var lastChangeCipherSepc, lastCertifidate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed || (!chunk.IsChangeCipherSpec() && !chunk.IsCertificate()) {
return true
}
record, err := NewDTLSRecord(c.UserData())
record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@ -1246,7 +1296,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ClientHello(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1257,10 +1307,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ClientHello(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@ -1299,7 +1349,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ClientHello(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ServerHello(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1310,10 +1360,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ServerHello(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@ -1352,7 +1402,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ServerHello(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_Certificate(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1363,10 +1413,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_Certificate(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@ -1405,7 +1455,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_Certificate(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ChangeCipherSpec(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1416,10 +1466,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ChangeCipherSpec(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@ -1459,7 +1509,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ChangeCipherSpec(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_VeryBadNetwork(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1470,15 +1520,15 @@ func TestRtcDTLS_ClientPassive_ARQ_VeryBadNetwork(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -1487,10 +1537,10 @@ func TestRtcDTLS_ClientPassive_ARQ_VeryBadNetwork(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnDropClientHello, nnDropCertificate := 0, 0
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@ -1537,7 +1587,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_After_ClientHello(t *testing.T) {
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@ -1548,15 +1598,15 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_After_ClientHello(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@ -1565,11 +1615,11 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_After_ClientHello(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
}, func(api *TestWebRTCAPI) {
}, func(api *testWebRTCAPI) {
nnDropClientHello, nnDropCertificate := 0, 0
var firstCertificate time.Time
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
chunk, parsed := NewChunkMessageType(c)
chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}

282
trunk/3rdparty/srs-bench/srs/srs.go vendored Normal file
View file

@ -0,0 +1,282 @@
// 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 (
"context"
"flag"
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
)
var sr, dumpAudio, dumpVideo string
var pli int
var pr, sourceAudio, sourceVideo string
var fps int
var audioLevel, videoTWCC bool
var clients, streams, delay int
var statListen string
func Parse(ctx context.Context) {
fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
var sfu string
fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or janus")
fl.StringVar(&sr, "sr", "", "")
fl.StringVar(&dumpAudio, "da", "", "")
fl.StringVar(&dumpVideo, "dv", "", "")
fl.IntVar(&pli, "pli", 10, "")
fl.StringVar(&pr, "pr", "", "")
fl.StringVar(&sourceAudio, "sa", "", "")
fl.StringVar(&sourceVideo, "sv", "", "")
fl.IntVar(&fps, "fps", 0, "")
fl.BoolVar(&audioLevel, "al", true, "")
fl.BoolVar(&videoTWCC, "twcc", true, "")
fl.IntVar(&clients, "nn", 1, "")
fl.IntVar(&streams, "sn", 1, "")
fl.IntVar(&delay, "delay", 50, "")
fl.StringVar(&statListen, "stat", "", "")
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 janus. Default: srs"))
fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1"))
fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1"))
fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50"))
fmt.Println(fmt.Sprintf(" -al [Optional] Whether enable audio-level. Default: true"))
fmt.Println(fmt.Sprintf(" -twcc [Optional] Whether enable vdieo-twcc. Default: true"))
fmt.Println(fmt.Sprintf(" -stat [Optional] The stat server API listen port."))
fmt.Println(fmt.Sprintf("Player or Subscriber:"))
fmt.Println(fmt.Sprintf(" -sr The url to play/subscribe. If sn exceed 1, auto append variable %%d."))
fmt.Println(fmt.Sprintf(" -da [Optional] The file path to dump audio, ignore if empty."))
fmt.Println(fmt.Sprintf(" -dv [Optional] The file path to dump video, ignore if empty."))
fmt.Println(fmt.Sprintf(" -pli [Optional] PLI request interval in seconds. Default: 10"))
fmt.Println(fmt.Sprintf("Publisher:"))
fmt.Println(fmt.Sprintf(" -pr The url to publish. If sn exceed 1, auto append variable %%d."))
fmt.Println(fmt.Sprintf(" -fps 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个播放1个推流:"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如1个流3个播放共3个客户端"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -nn 3", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如2个流每个流3个播放共6个客户端"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream_%%d -sn 2 -nn 3", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如2个推流"))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如1个录制"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -da avatar.ogg -dv avatar.h264", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如1个明文播放"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream?encrypt=false", os.Args[0]))
fmt.Println()
}
_ = fl.Parse(os.Args[1:])
showHelp := (clients <= 0 || streams <= 0)
if sr == "" && pr == "" {
showHelp = true
}
if pr != "" && (sourceAudio == "" && sourceVideo == "") {
showHelp = true
}
if showHelp {
fl.Usage()
os.Exit(-1)
}
if statListen != "" && !strings.Contains(statListen, ":") {
statListen = ":" + statListen
}
summaryDesc := fmt.Sprintf("clients=%v, delay=%v, al=%v, twcc=%v, stat=%v", clients, delay, audioLevel, videoTWCC, statListen)
if sr != "" {
summaryDesc = fmt.Sprintf("%v, play(url=%v, da=%v, dv=%v, pli=%v)", summaryDesc, sr, dumpAudio, dumpVideo, pli)
}
if pr != "" {
summaryDesc = fmt.Sprintf("%v, publish(url=%v, sa=%v, sv=%v, fps=%v)",
summaryDesc, pr, sourceAudio, sourceVideo, fps)
}
logger.Tf(ctx, "Run benchmark with %v", summaryDesc)
checkFlags := func() error {
if dumpVideo != "" && !strings.HasSuffix(dumpVideo, ".h264") && !strings.HasSuffix(dumpVideo, ".ivf") {
return errors.Errorf("Should be .ivf or .264, actual %v", dumpVideo)
}
if sourceVideo != "" && !strings.HasSuffix(sourceVideo, ".h264") {
return errors.Errorf("Should be .264, actual %v", sourceVideo)
}
if sourceVideo != "" && strings.HasSuffix(sourceVideo, ".h264") && fps <= 0 {
return errors.Errorf("Video fps should >0, actual %v", fps)
}
return nil
}
if err := checkFlags(); err != nil {
logger.Ef(ctx, "Check faile err %+v", err)
os.Exit(-1)
}
}
func Run(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
// Run tasks.
var wg sync.WaitGroup
// Run STAT API server.
wg.Add(1)
go func() {
defer wg.Done()
if statListen == "" {
return
}
var lc net.ListenConfig
ln, err := lc.Listen(ctx, "tcp", statListen)
if err != nil {
logger.Ef(ctx, "stat listen err+%v", err)
cancel()
return
}
mux := http.NewServeMux()
handleStat(ctx, mux, statListen)
srv := &http.Server{
Handler: mux,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
}
go func() {
<-ctx.Done()
srv.Shutdown(ctx)
}()
logger.Tf(ctx, "Stat listen at %v", statListen)
if err := srv.Serve(ln); err != nil {
if ctx.Err() == nil {
logger.Ef(ctx, "stat serve err+%v", err)
cancel()
}
return
}
}()
// Run all subscribers or players.
for i := 0; sr != "" && i < streams && ctx.Err() == nil; i++ {
r_auto := sr
if streams > 1 && !strings.Contains(r_auto, "%") {
r_auto += "%d"
}
r2 := r_auto
if strings.Contains(r2, "%") {
r2 = fmt.Sprintf(r2, i)
}
for j := 0; sr != "" && j < clients && ctx.Err() == nil; j++ {
// Dump audio or video only for the first client.
da, dv := dumpAudio, dumpVideo
if i > 0 {
da, dv = "", ""
}
gStatRTC.Subscribers.Expect++
gStatRTC.Subscribers.Alive++
wg.Add(1)
go func(sr, da, dv string) {
defer wg.Done()
defer func() {
gStatRTC.Subscribers.Alive--
}()
if err := startPlay(ctx, sr, da, dv, audioLevel, videoTWCC, pli); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
}
}(r2, da, dv)
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
// Run all publishers.
for i := 0; pr != "" && i < streams && ctx.Err() == nil; i++ {
r_auto := pr
if streams > 1 && !strings.Contains(r_auto, "%") {
r_auto += "%d"
}
r2 := r_auto
if strings.Contains(r2, "%") {
r2 = fmt.Sprintf(r2, i)
}
gStatRTC.Publishers.Expect++
gStatRTC.Publishers.Alive++
wg.Add(1)
go func(pr string) {
defer wg.Done()
defer func() {
gStatRTC.Publishers.Alive--
}()
if err := startPublish(ctx, pr, sourceAudio, sourceVideo, fps, audioLevel, videoTWCC); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
}
}(r2)
time.Sleep(time.Duration(delay) * time.Millisecond)
}
wg.Wait()
return nil
}

View file

@ -41,9 +41,9 @@ type statRTC struct {
PeerConnection interface{} `json:"random-pc"`
}
var StatRTC statRTC
var gStatRTC statRTC
func HandleStat(ctx context.Context, mux *http.ServeMux, l string) {
func handleStat(ctx context.Context, mux *http.ServeMux, l string) {
if strings.HasPrefix(l, ":") {
l = "127.0.0.1" + l
}
@ -54,7 +54,7 @@ func HandleStat(ctx context.Context, mux *http.ServeMux, l string) {
Code int `json:"code"`
Data interface{} `json:"data"`
}{
0, &StatRTC,
0, &gStatRTC,
}
b, err := json.Marshal(res)

View file

@ -376,97 +376,97 @@ func srsIsRTCP(b []byte) bool {
return (len(b) >= 12) && (b[0]&0x80) != 0 && (b[1] >= 192 && b[1] <= 223)
}
type ChunkType int
type chunkType int
const (
ChunkTypeICE ChunkType = iota + 1
ChunkTypeDTLS
ChunkTypeRTP
ChunkTypeRTCP
chunkTypeICE chunkType = iota + 1
chunkTypeDTLS
chunkTypeRTP
chunkTypeRTCP
)
func (v ChunkType) String() string {
func (v chunkType) String() string {
switch v {
case ChunkTypeICE:
case chunkTypeICE:
return "ICE"
case ChunkTypeDTLS:
case chunkTypeDTLS:
return "DTLS"
case ChunkTypeRTP:
case chunkTypeRTP:
return "RTP"
case ChunkTypeRTCP:
case chunkTypeRTCP:
return "RTCP"
default:
return "Unknown"
}
}
type DTLSContentType int
type dtlsContentType int
const (
DTLSContentTypeHandshake DTLSContentType = 22
DTLSContentTypeChangeCipherSpec DTLSContentType = 20
DTLSContentTypeAlert DTLSContentType = 21
dtlsContentTypeHandshake dtlsContentType = 22
dtlsContentTypeChangeCipherSpec dtlsContentType = 20
dtlsContentTypeAlert dtlsContentType = 21
)
func (v DTLSContentType) String() string {
func (v dtlsContentType) String() string {
switch v {
case DTLSContentTypeHandshake:
case dtlsContentTypeHandshake:
return "Handshake"
case DTLSContentTypeChangeCipherSpec:
case dtlsContentTypeChangeCipherSpec:
return "ChangeCipherSpec"
default:
return "Unknown"
}
}
type DTLSHandshakeType int
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
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 {
func (v dtlsHandshakeType) String() string {
switch v {
case DTLSHandshakeTypeClientHello:
case dtlsHandshakeTypeClientHello:
return "ClientHello"
case DTLSHandshakeTypeServerHello:
case dtlsHandshakeTypeServerHello:
return "ServerHello"
case DTLSHandshakeTypeCertificate:
case dtlsHandshakeTypeCertificate:
return "Certificate"
case DTLSHandshakeTypeServerKeyExchange:
case dtlsHandshakeTypeServerKeyExchange:
return "ServerKeyExchange"
case DTLSHandshakeTypeCertificateRequest:
case dtlsHandshakeTypeCertificateRequest:
return "CertificateRequest"
case DTLSHandshakeTypeServerDone:
case dtlsHandshakeTypeServerDone:
return "ServerDone"
case DTLSHandshakeTypeCertificateVerify:
case dtlsHandshakeTypeCertificateVerify:
return "CertificateVerify"
case DTLSHandshakeTypeClientKeyExchange:
case dtlsHandshakeTypeClientKeyExchange:
return "ClientKeyExchange"
case DTLSHandshakeTypeFinished:
case dtlsHandshakeTypeFinished:
return "Finished"
default:
return "Unknown"
}
}
type ChunkMessageType struct {
chunk ChunkType
content DTLSContentType
handshake DTLSHandshakeType
type chunkMessageType struct {
chunk chunkType
content dtlsContentType
handshake dtlsHandshakeType
}
func (v *ChunkMessageType) String() string {
if v.chunk == ChunkTypeDTLS {
if v.content == DTLSContentTypeHandshake {
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)
@ -475,26 +475,26 @@ func (v *ChunkMessageType) String() string {
return fmt.Sprintf("%v", v.chunk)
}
func NewChunkMessageType(c vnet.Chunk) (*ChunkMessageType, bool) {
func newChunkMessageType(c vnet.Chunk) (*chunkMessageType, bool) {
b := c.UserData()
if len(b) == 0 {
return nil, false
}
v := &ChunkMessageType{}
v := &chunkMessageType{}
if srsIsRTPOrRTCP(b) {
if srsIsRTCP(b) {
v.chunk = ChunkTypeRTCP
v.chunk = chunkTypeRTCP
} else {
v.chunk = ChunkTypeRTP
v.chunk = chunkTypeRTP
}
return v, true
}
if srsIsStun(b) {
v.chunk = ChunkTypeICE
v.chunk = chunkTypeICE
return v, true
}
@ -502,40 +502,40 @@ func NewChunkMessageType(c vnet.Chunk) (*ChunkMessageType, bool) {
return nil, false
}
v.chunk, v.content = ChunkTypeDTLS, DTLSContentType(b[0])
if v.content != DTLSContentTypeHandshake {
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])
v.handshake = dtlsHandshakeType(b[13])
return v, true
}
func (v *ChunkMessageType) IsHandshake() bool {
return v.chunk == ChunkTypeDTLS && v.content == DTLSContentTypeHandshake
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) 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) 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) IsCertificate() bool {
return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeCertificate
}
func (v *ChunkMessageType) IsChangeCipherSpec() bool {
return v.chunk == ChunkTypeDTLS && v.content == DTLSContentTypeChangeCipherSpec
func (v *chunkMessageType) IsChangeCipherSpec() bool {
return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeChangeCipherSpec
}
type DTLSRecord struct {
ContentType DTLSContentType
type dtlsRecord struct {
ContentType dtlsContentType
Version uint16
Epoch uint16
SequenceNumber uint64
@ -543,25 +543,25 @@ type DTLSRecord struct {
Data []byte
}
func NewDTLSRecord(b []byte) (*DTLSRecord, error) {
v := &DTLSRecord{}
func newDTLSRecord(b []byte) (*dtlsRecord, error) {
v := &dtlsRecord{}
return v, v.Unmarshal(b)
}
func (v *DTLSRecord) String() string {
func (v *dtlsRecord) String() string {
return fmt.Sprintf("epoch=%v, sequence=%v", v.Epoch, v.SequenceNumber)
}
func (v *DTLSRecord) Equals(p *DTLSRecord) bool {
func (v *dtlsRecord) Equals(p *dtlsRecord) bool {
return v.Epoch == p.Epoch && v.SequenceNumber == p.SequenceNumber
}
func (v *DTLSRecord) Unmarshal(b []byte) error {
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.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])
@ -570,11 +570,11 @@ func (v *DTLSRecord) Unmarshal(b []byte) error {
return nil
}
type TestWebRTCAPIOptionFunc func(api *TestWebRTCAPI)
type testWebRTCAPIOptionFunc func(api *testWebRTCAPI)
type TestWebRTCAPI struct {
type testWebRTCAPI struct {
// The options to setup the api.
options []TestWebRTCAPIOptionFunc
options []testWebRTCAPIOptionFunc
// The api and settings.
api *webrtc.API
mediaEngine *webrtc.MediaEngine
@ -588,8 +588,8 @@ type TestWebRTCAPI struct {
proxy *vnet_proxy.UDPProxy
}
func NewTestWebRTCAPI(options ...TestWebRTCAPIOptionFunc) (*TestWebRTCAPI, error) {
v := &TestWebRTCAPI{}
func newTestWebRTCAPI(options ...testWebRTCAPIOptionFunc) (*testWebRTCAPI, error) {
v := &testWebRTCAPI{}
v.mediaEngine = &webrtc.MediaEngine{}
if err := v.mediaEngine.RegisterDefaultCodecs(); err != nil {
@ -610,7 +610,7 @@ func NewTestWebRTCAPI(options ...TestWebRTCAPIOptionFunc) (*TestWebRTCAPI, error
return v, nil
}
func (v *TestWebRTCAPI) Close() error {
func (v *testWebRTCAPI) Close() error {
if v.proxy != nil {
_ = v.proxy.Close()
}
@ -622,7 +622,7 @@ func (v *TestWebRTCAPI) Close() error {
return nil
}
func (v *TestWebRTCAPI) Setup(vnetClientIP string, options ...TestWebRTCAPIOptionFunc) error {
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
@ -674,23 +674,23 @@ func (v *TestWebRTCAPI) Setup(vnetClientIP string, options ...TestWebRTCAPIOptio
return nil
}
func (v *TestWebRTCAPI) NewPeerConnection(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
func (v *testWebRTCAPI) NewPeerConnection(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
return v.api.NewPeerConnection(configuration)
}
type TestPlayerOptionFunc func(p *TestPlayer) error
type testPlayerOptionFunc func(p *testPlayer) error
type TestPlayer struct {
type testPlayer struct {
pc *webrtc.PeerConnection
receivers []*webrtc.RTPReceiver
// We should dispose it.
api *TestWebRTCAPI
api *testWebRTCAPI
// Optional suffix for stream url.
streamSuffix string
}
func CreateApiForPlayer(play *TestPlayer) error {
api, err := NewTestWebRTCAPI()
func createApiForPlayer(play *testPlayer) error {
api, err := newTestWebRTCAPI()
if err != nil {
return err
}
@ -699,8 +699,8 @@ func CreateApiForPlayer(play *TestPlayer) error {
return nil
}
func NewTestPlayer(options ...TestPlayerOptionFunc) (*TestPlayer, error) {
v := &TestPlayer{}
func newTestPlayer(options ...testPlayerOptionFunc) (*testPlayer, error) {
v := &testPlayer{}
for _, opt := range options {
if err := opt(v); err != nil {
@ -711,11 +711,11 @@ func NewTestPlayer(options ...TestPlayerOptionFunc) (*TestPlayer, error) {
return v, nil
}
func (v *TestPlayer) Setup(vnetClientIP string, options ...TestWebRTCAPIOptionFunc) error {
func (v *testPlayer) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
return v.api.Setup(vnetClientIP, options...)
}
func (v *TestPlayer) Close() error {
func (v *testPlayer) Close() error {
if v.pc != nil {
_ = v.pc.Close()
}
@ -731,13 +731,13 @@ func (v *TestPlayer) Close() error {
return nil
}
func (v *TestPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
func (v *testPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
r := fmt.Sprintf("%v://%v%v", srsSchema, *srsServer, *srsStream)
if v.streamSuffix != "" {
r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
}
pli := time.Duration(*srsPlayPLI) * time.Millisecond
logger.Tf(ctx, "Start play url=%v", r)
logger.Tf(ctx, "Run play url=%v", r)
pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
if err != nil {
@ -770,7 +770,7 @@ func (v *TestPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
}
// Start a proxy for real server and vnet.
// Run a proxy for real server and vnet.
if address, err := parseAddressOfCandidate(answer); err != nil {
return errors.Wrapf(err, "parse address of %v", answer)
} else if err := v.api.proxy.Proxy(v.api.network, address); err != nil {
@ -834,9 +834,9 @@ func (v *TestPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
return err
}
type TestPublisherOptionFunc func(p *TestPublisher) error
type testPublisherOptionFunc func(p *testPublisher) error
type TestPublisher struct {
type testPublisher struct {
onOffer func(s *webrtc.SessionDescription) error
onAnswer func(s *webrtc.SessionDescription) error
iceReadyCancel context.CancelFunc
@ -845,15 +845,15 @@ type TestPublisher struct {
vIngester *videoIngester
pc *webrtc.PeerConnection
// We should dispose it.
api *TestWebRTCAPI
api *testWebRTCAPI
// Optional suffix for stream url.
streamSuffix string
// To cancel the publisher, pass by Run.
cancel context.CancelFunc
}
func CreateApiForPublisher(pub *TestPublisher) error {
api, err := NewTestWebRTCAPI()
func createApiForPublisher(pub *testPublisher) error {
api, err := newTestWebRTCAPI()
if err != nil {
return err
}
@ -862,10 +862,10 @@ func CreateApiForPublisher(pub *TestPublisher) error {
return nil
}
func NewTestPublisher(options ...TestPublisherOptionFunc) (*TestPublisher, error) {
func newTestPublisher(options ...testPublisherOptionFunc) (*testPublisher, error) {
sourceVideo, sourceAudio := *srsPublishVideo, *srsPublishAudio
v := &TestPublisher{}
v := &testPublisher{}
for _, opt := range options {
if err := opt(v); err != nil {
@ -875,17 +875,17 @@ func NewTestPublisher(options ...TestPublisherOptionFunc) (*TestPublisher, error
// Create ingesters.
if sourceAudio != "" {
v.aIngester = NewAudioIngester(sourceAudio)
v.aIngester = newAudioIngester(sourceAudio)
}
if sourceVideo != "" {
v.vIngester = NewVideoIngester(sourceVideo)
v.vIngester = newVideoIngester(sourceVideo)
}
// Setup the interceptors for packets.
api := v.api
api.options = append(api.options, func(api *TestWebRTCAPI) {
api.options = append(api.options, func(api *testWebRTCAPI) {
// Filter for RTCP packets.
rtcpInterceptor := &RTCPInterceptor{}
rtcpInterceptor := &rtcpInterceptor{}
rtcpInterceptor.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
return rtcpInterceptor.nextRTCPReader.Read(buf, attributes)
}
@ -906,11 +906,11 @@ func NewTestPublisher(options ...TestPublisherOptionFunc) (*TestPublisher, error
return v, nil
}
func (v *TestPublisher) Setup(vnetClientIP string, options ...TestWebRTCAPIOptionFunc) error {
func (v *testPublisher) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
return v.api.Setup(vnetClientIP, options...)
}
func (v *TestPublisher) Close() error {
func (v *testPublisher) Close() error {
if v.vIngester != nil {
_ = v.vIngester.Close()
}
@ -930,12 +930,12 @@ func (v *TestPublisher) Close() error {
return nil
}
func (v *TestPublisher) SetStreamSuffix(suffix string) *TestPublisher {
func (v *testPublisher) SetStreamSuffix(suffix string) *testPublisher {
v.streamSuffix = suffix
return v
}
func (v *TestPublisher) Run(ctx context.Context, cancel context.CancelFunc) error {
func (v *testPublisher) Run(ctx context.Context, cancel context.CancelFunc) error {
// Save the cancel.
v.cancel = cancel
@ -945,7 +945,7 @@ func (v *TestPublisher) Run(ctx context.Context, cancel context.CancelFunc) erro
}
sourceVideo, sourceAudio, fps := *srsPublishVideo, *srsPublishAudio, *srsPublishVideoFps
logger.Tf(ctx, "Start publish url=%v, audio=%v, video=%v, fps=%v",
logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v",
r, sourceAudio, sourceVideo, fps)
pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
@ -986,7 +986,7 @@ func (v *TestPublisher) Run(ctx context.Context, cancel context.CancelFunc) erro
return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
}
// Start a proxy for real server and vnet.
// 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 {

View file

@ -1,32 +1,14 @@
// 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 vnet
import (
"net"
)
// Deliver directly send packet to vnet or real-server.
// For example, we can use this API to simulate the REPLAY ATTACK.
func (v *UDPProxy) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err error) {
v.workers.Range(func(key, value interface{}) bool {
if nn, err := value.(*aUDPProxyWorker).Deliver(sourceAddr, destAddr, b); err != nil {
if nn, err = value.(*aUDPProxyWorker).Deliver(sourceAddr, destAddr, b); err != nil {
return false // Fail, abort.
} else if nn == len(b) {
return false // Done.
@ -43,12 +25,12 @@ func (v *aUDPProxyWorker) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn i
return 0, nil
}
// TODO: Support deliver packet from real server to vnet.
// If packet is from vent, proxy to real server.
// nolint:godox // TODO: Support deliver packet from real server to vnet.
// If packet is from vnet, proxy to real server.
var realSocket *net.UDPConn
if value, ok := v.endpoints.Load(addr.String()); !ok {
return 0, nil
} else {
} else { // nolint:golint
realSocket = value.(*net.UDPConn)
}

View file

@ -1,40 +1,22 @@
// 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 vnet
import (
"context"
"errors"
"fmt"
"github.com/pion/logging"
"github.com/pion/transport/vnet"
"net"
"sync"
"testing"
"time"
"github.com/pion/logging"
)
// vnet client:
// The vnet client:
// 10.0.0.11:5787
// proxy to real server:
// which proxy to real server:
// 192.168.1.10:8000
// We should get a reply if directly deliver to proxy.
func TestUDPProxyDirectDeliver(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
@ -57,7 +39,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
select {
case <-ctx.Done():
case <-time.After(time.Duration(*testTimeout) * time.Millisecond):
r2 = fmt.Errorf("timeout")
r2 = fmt.Errorf("timeout") // nolint:goerr113
}
}()
@ -86,7 +68,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
}
doVnetProxy := func() error {
router, err := vnet.NewRouter(&vnet.RouterConfig{
router, err := NewRouter(&RouterConfig{
CIDR: "0.0.0.0/0",
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
@ -94,23 +76,23 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
return err
}
clientNetwork := vnet.NewNet(&vnet.NetConfig{
clientNetwork := NewNet(&NetConfig{
StaticIP: "10.0.0.11",
})
if err = router.AddNet(clientNetwork); err != nil {
return err
}
if err := router.Start(); err != nil {
if err = router.Start(); err != nil {
return err
}
defer router.Stop()
defer router.Stop() // nolint:errcheck
proxy, err := NewProxy(router)
if err != nil {
return err
}
defer proxy.Close()
defer proxy.Close() // nolint:errcheck
// For utest, mock the target real server.
proxy.mockRealServerAddr = mockServer.realServerAddr
@ -122,7 +104,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
return err
}
if err := proxy.Proxy(clientNetwork, serverAddr); err != nil {
if err = proxy.Proxy(clientNetwork, serverAddr); err != nil {
return err
}
@ -137,41 +119,41 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
go func() {
<-ctx.Done()
selfKillCancel()
client.Close()
_ = client.Close()
}()
// Write by vnet client.
if _, err := client.WriteTo([]byte("Hello"), serverAddr); err != nil {
if _, err = client.WriteTo([]byte("Hello"), serverAddr); err != nil {
return err
}
buf := make([]byte, 1500)
if n, addr, err := client.ReadFrom(buf); err != nil {
if selfKill.Err() == context.Canceled {
if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet
if errors.Is(selfKill.Err(), context.Canceled) {
return nil
}
return err
} else if n != 5 || addr == nil {
return fmt.Errorf("n=%v, addr=%v", n, addr)
} else if string(buf[:n]) != "Hello" {
return fmt.Errorf("data %v", buf[:n])
return fmt.Errorf("n=%v, addr=%v", n, addr) // nolint:goerr113
} else if string(buf[:n]) != "Hello" { // nolint:goconst
return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113
}
// Directly write, simulate the ARQ packet.
// We should got the echo packet also.
if _, err := proxy.Deliver(client.LocalAddr(), serverAddr, []byte("Hello")); err != nil {
if _, err = proxy.Deliver(client.LocalAddr(), serverAddr, []byte("Hello")); err != nil {
return err
}
if n, addr, err := client.ReadFrom(buf); err != nil {
if selfKill.Err() == context.Canceled {
if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet
if errors.Is(selfKill.Err(), context.Canceled) {
return nil
}
return err
} else if n != 5 || addr == nil {
return fmt.Errorf("n=%v, addr=%v", n, addr)
return fmt.Errorf("n=%v, addr=%v", n, addr) // nolint:goerr113
} else if string(buf[:n]) != "Hello" {
return fmt.Errorf("data %v", buf[:n])
return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113
}
return err

13
trunk/configure vendored
View file

@ -462,7 +462,7 @@ mv ${SRS_WORKDIR}/${SRS_MAKEFILE} ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk
# generate phony header
cat << END > ${SRS_WORKDIR}/${SRS_MAKEFILE}
.PHONY: default _default install help clean destroy server srs_ingest_hls utest _prepare_dir $__mphonys
.PHONY: clean_srs clean_modules clean_openssl clean_nginx clean_cherrypy clean_srtp2 clean_opus clean_ffmpeg clean_st
.PHONY: clean_srs clean_modules clean_openssl clean_srtp2 clean_opus clean_ffmpeg clean_st
.PHONY: st ffmpeg
# install prefix.
@ -505,7 +505,6 @@ doclean:
(cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest $__mcleanups)
(cd ${SRS_OBJS_DIR} && rm -rf src/* include lib)
(mkdir -p ${SRS_OBJS_DIR}/utest && cd ${SRS_OBJS_DIR}/utest && rm -rf *.o *.a)
(cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players)
clean: clean_srs clean_modules
@ -541,12 +540,6 @@ clean_st:
(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf st-srs)
@echo "Please rebuild ST by: ./configure"
clean_nginx:
(cd ${SRS_OBJS_DIR} && rm -rf nginx)
clean_cherrypy:
(cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players)
st:
(cd ${SRS_OBJS_DIR} && rm -f srs srs_utest)
(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM}/st-srs && \$(MAKE) clean && \$(MAKE) ${_ST_MAKE} EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}")
@ -587,7 +580,11 @@ install:
@mkdir -p \$(__REAL_INSTALL)
@echo "Now make the http root dir"
@mkdir -p \$(__REAL_INSTALL)/objs/nginx/html
@cp -f research/api-server/static-dir/index.html \$(__REAL_INSTALL)/objs/nginx/html
@cp -f research/api-server/static-dir/crossdomain.xml \$(__REAL_INSTALL)/objs/nginx/html
@cp -f research/api-server/static-dir/favicon.ico \$(__REAL_INSTALL)/objs/nginx/html
@cp -Rf research/players \$(__REAL_INSTALL)/objs/nginx/html
@cp -Rf research/console \$(__REAL_INSTALL)/objs/nginx/html
@echo "Now copy binary files"
@mkdir -p \$(__REAL_INSTALL)/objs
@cp -f objs/srs \$(__REAL_INSTALL)/objs

View file

@ -153,18 +153,6 @@ ok_msg "start install srs"
ret=$?; if [[ 0 -ne ${ret} ]]; then failed_msg "install srs failed"; exit $ret; fi
ok_msg "install srs success"
# Copy srs-console
HTTP_HOME="${package_dir}/${INSTALL}/objs/nginx/html/"
(
cp $work_dir/research/api-server/static-dir/index.html ${HTTP_HOME} &&
cp $work_dir/research/api-server/static-dir/favicon.ico ${HTTP_HOME} &&
cp $work_dir/research/api-server/static-dir/crossdomain.xml ${HTTP_HOME} &&
cp -R $work_dir/research/players ${HTTP_HOME} &&
cp -R $work_dir/research/console ${HTTP_HOME}
) >> $log 2>&1
ret=$?; if [[ 0 -ne ${ret} ]]; then failed_msg "copy utilities failed"; exit $ret; fi
ok_msg "copy utilities success"
# copy extra files to package.
ok_msg "start copy extra files to package"
(

View file

@ -165,6 +165,33 @@ bool SrsErrorPithyPrint::can_print(int error_code, uint32_t* pnn)
return new_stage || stage->can_print();
}
SrsAlonePithyPrint::SrsAlonePithyPrint() : info_(0)
{
//stage work for one print
info_.nb_clients = 1;
previous_tick_ = srs_get_system_time();
}
SrsAlonePithyPrint::~SrsAlonePithyPrint()
{
}
void SrsAlonePithyPrint::elapse()
{
srs_utime_t diff = srs_get_system_time() - previous_tick_;
previous_tick_ = srs_get_system_time();
diff = srs_max(0, diff);
info_.elapse(diff);
}
bool SrsAlonePithyPrint::can_print()
{
return info_.can_print();
}
// The global stage manager for pithy print, multiple stages.
static SrsStageManager* _srs_stages = new SrsStageManager();

View file

@ -89,6 +89,20 @@ public:
bool can_print(int err, uint32_t* pnn = NULL);
};
// An standalone pithy print, without shared stages.
class SrsAlonePithyPrint
{
private:
SrsStageInfo info_;
srs_utime_t previous_tick_;
public:
SrsAlonePithyPrint();
virtual ~SrsAlonePithyPrint();
public:
virtual void elapse();
virtual bool can_print();
};
// The stage is used for a collection of object to do print,
// the print time in a stage is constant and not changed,
// that is, we always got one message to print every specified time.

View file

@ -563,619 +563,6 @@ VOID TEST(KernelRTCTest, StringDumpHexTest)
}
}
extern SSL_CTX* srs_build_dtls_ctx(SrsDtlsVersion version, std::string role);
class MockDtls
{
public:
SSL_CTX* dtls_ctx;
SSL* dtls;
BIO* bio_in;
BIO* bio_out;
ISrsDtlsCallback* callback_;
bool handshake_done_for_us;
SrsDtlsRole role_;
SrsDtlsVersion version_;
public:
MockDtls(ISrsDtlsCallback* callback);
virtual ~MockDtls();
srs_error_t initialize(std::string role, std::string version);
srs_error_t start_active_handshake();
srs_error_t on_dtls(char* data, int nb_data);
srs_error_t do_handshake();
};
MockDtls::MockDtls(ISrsDtlsCallback* callback)
{
dtls_ctx = NULL;
dtls = NULL;
callback_ = callback;
handshake_done_for_us = false;
role_ = SrsDtlsRoleServer;
version_ = SrsDtlsVersionAuto;
}
MockDtls::~MockDtls()
{
if (dtls_ctx) {
SSL_CTX_free(dtls_ctx);
dtls_ctx = NULL;
}
if (dtls) {
SSL_free(dtls);
dtls = NULL;
}
}
srs_error_t MockDtls::initialize(std::string role, std::string version)
{
role_ = SrsDtlsRoleServer;
if (role == "active") {
role_ = SrsDtlsRoleClient;
}
if (version == "dtls1.0") {
version_ = SrsDtlsVersion1_0;
} else if (version == "dtls1.2") {
version_ = SrsDtlsVersion1_2;
} else {
version_ = SrsDtlsVersionAuto;
}
dtls_ctx = srs_build_dtls_ctx(version_, role);
dtls = SSL_new(dtls_ctx);
srs_assert(dtls);
if (role_ == SrsDtlsRoleClient) {
SSL_set_connect_state(dtls);
SSL_set_max_send_fragment(dtls, kRtpPacketSize);
} else {
SSL_set_accept_state(dtls);
}
bio_in = BIO_new(BIO_s_mem());
srs_assert(bio_in);
bio_out = BIO_new(BIO_s_mem());
srs_assert(bio_out);
SSL_set_bio(dtls, bio_in, bio_out);
return srs_success;
}
srs_error_t MockDtls::start_active_handshake()
{
if (role_ == SrsDtlsRoleClient) {
return do_handshake();
}
return srs_success;
}
srs_error_t MockDtls::on_dtls(char* data, int nb_data)
{
srs_error_t err = srs_success;
srs_assert(BIO_reset(bio_in) == 1);
srs_assert(BIO_reset(bio_out) == 1);
srs_assert(BIO_write(bio_in, data, nb_data) > 0);
if ((err = do_handshake()) != srs_success) {
return srs_error_wrap(err, "do handshake");
}
while (BIO_ctrl_pending(bio_in) > 0) {
char buf[8092];
int nb = SSL_read(dtls, buf, sizeof(buf));
if (nb <= 0) {
continue;
}
if ((err = callback_->on_dtls_application_data(buf, nb)) != srs_success) {
return srs_error_wrap(err, "on DTLS data, size=%u", nb);
}
}
return err;
}
srs_error_t MockDtls::do_handshake()
{
srs_error_t err = srs_success;
int r0 = SSL_do_handshake(dtls);
int r1 = SSL_get_error(dtls, r0);
if (r0 < 0 && (r1 != SSL_ERROR_NONE && r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE)) {
return srs_error_new(ERROR_RTC_DTLS, "handshake r0=%d, r1=%d", r0, r1);
}
if (r1 == SSL_ERROR_NONE) {
handshake_done_for_us = true;
}
uint8_t* data = NULL;
int size = BIO_get_mem_data(bio_out, &data);
if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) {
return srs_error_wrap(err, "dtls send size=%u", size);
}
if (handshake_done_for_us) {
return callback_->on_dtls_handshake_done();
}
return err;
}
class MockDtlsCallback : virtual public ISrsDtlsCallback, virtual public ISrsCoroutineHandler
{
public:
SrsDtls* peer;
MockDtls* peer2;
SrsCoroutine* trd;
srs_error_t r0;
bool done;
std::vector<SrsSample> samples;
public:
int nn_client_hello_lost;
int nn_server_hello_lost;
int nn_certificate_lost;
int nn_new_session_lost;
int nn_change_cipher_lost;
public:
// client -> server
int nn_client_hello;
// server -> client
int nn_server_hello;
// client -> server
int nn_certificate;
// server -> client
int nn_new_session;
int nn_change_cipher;
public:
MockDtlsCallback();
virtual ~MockDtlsCallback();
virtual srs_error_t on_dtls_handshake_done();
virtual srs_error_t on_dtls_application_data(const char* data, const int len);
virtual srs_error_t write_dtls_data(void* data, int size);
virtual srs_error_t on_dtls_alert(std::string type, std::string desc);
virtual srs_error_t cycle();
};
MockDtlsCallback::MockDtlsCallback()
{
peer = NULL;
peer2 = NULL;
r0 = srs_success;
done = false;
trd = new SrsSTCoroutine("mock", this);
srs_assert(trd->start() == srs_success);
nn_client_hello_lost = 0;
nn_server_hello_lost = 0;
nn_certificate_lost = 0;
nn_new_session_lost = 0;
nn_change_cipher_lost = 0;
nn_client_hello = 0;
nn_server_hello = 0;
nn_certificate = 0;
nn_new_session = 0;
nn_change_cipher = 0;
}
MockDtlsCallback::~MockDtlsCallback()
{
srs_freep(trd);
srs_freep(r0);
for (vector<SrsSample>::iterator it = samples.begin(); it != samples.end(); ++it) {
delete[] it->bytes;
}
}
srs_error_t MockDtlsCallback::on_dtls_handshake_done()
{
done = true;
return srs_success;
}
srs_error_t MockDtlsCallback::on_dtls_application_data(const char* data, const int len)
{
return srs_success;
}
srs_error_t MockDtlsCallback::write_dtls_data(void* data, int size)
{
int nn_lost = 0;
if (true) {
uint8_t content_type = 0;
if (size >= 1) {
content_type = (uint8_t)((uint8_t*)data)[0];
}
uint8_t handshake_type = 0;
if (size >= 14) {
handshake_type = (uint8_t)((uint8_t*)data)[13];
}
if (content_type == 22) {
if (handshake_type == 1) {
nn_lost = nn_client_hello_lost--;
nn_client_hello++;
} else if (handshake_type == 2) {
nn_lost = nn_server_hello_lost--;
nn_server_hello++;
} else if (handshake_type == 11) {
nn_lost = nn_certificate_lost--;
nn_certificate++;
} else if (handshake_type == 4) {
nn_lost = nn_new_session_lost--;
nn_new_session++;
}
} else if (content_type == 20) {
nn_lost = nn_change_cipher_lost--;
nn_change_cipher++;
}
}
// Simulate to drop packet.
if (nn_lost > 0) {
return srs_success;
}
// Send out it.
char* cp = new char[size];
memcpy(cp, data, size);
samples.push_back(SrsSample((char*)cp, size));
return srs_success;
}
srs_error_t MockDtlsCallback::on_dtls_alert(std::string type, std::string desc)
{
return srs_success;
}
srs_error_t MockDtlsCallback::cycle()
{
srs_error_t err = srs_success;
while (err == srs_success) {
if ((err = trd->pull()) != srs_success) {
break;
}
if (samples.empty()) {
srs_usleep(0);
continue;
}
SrsSample p = samples.at(0);
samples.erase(samples.begin());
if (peer) {
err = peer->on_dtls((char*)p.bytes, p.size);
} else if (peer2) {
err = peer2->on_dtls((char*)p.bytes, p.size);
}
srs_freepa(p.bytes);
}
// Copy it for utest to check it.
r0 = srs_error_copy(err);
return err;
}
// Wait for mock io to done, try to switch to coroutine many times.
void mock_wait_dtls_io_done(SrsDtlsImpl* client_impl, int count = 100, int interval = 0)
{
for (int i = 0; i < count; i++) {
if (client_impl) {
dynamic_cast<SrsDtlsClientImpl*>(client_impl)->reset_timer_ = true;
}
srs_usleep(interval * SRS_UTIME_MILLISECONDS);
}
}
// To avoid the crash when peer or peer2 is freed before io.
class MockBridgeDtlsIO
{
private:
MockDtlsCallback* io_;
public:
MockBridgeDtlsIO(MockDtlsCallback* io, SrsDtls* peer, MockDtls* peer2) {
io_ = io;
io->peer = peer;
io->peer2 = peer2;
}
virtual ~MockBridgeDtlsIO() {
io_->peer = NULL;
io_->peer2 = NULL;
}
};
VOID TEST(KernelRTCTest, DTLSClientARQTest)
{
srs_error_t err = srs_success;
// No ARQ, check the number of packets.
if (true) {
MockDtlsCallback cio; SrsDtls client(&cio);
MockDtlsCallback sio; SrsDtls server(&sio);
cio.peer = &server; sio.peer = &client;
HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0"));
HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0"));
HELPER_EXPECT_SUCCESS(client.start_active_handshake());
mock_wait_dtls_io_done(client.impl, 15, 5);
EXPECT_TRUE(sio.r0 == srs_success);
EXPECT_TRUE(cio.r0 == srs_success);
EXPECT_TRUE(cio.done);
EXPECT_TRUE(sio.done);
EXPECT_EQ(1, cio.nn_client_hello);
EXPECT_EQ(1, sio.nn_server_hello);
EXPECT_EQ(1, cio.nn_certificate);
EXPECT_EQ(1, sio.nn_new_session);
EXPECT_EQ(0, sio.nn_change_cipher);
}
// ClientHello lost, client retransmit the ClientHello.
if (true) {
MockDtlsCallback cio; SrsDtls client(&cio);
MockDtlsCallback sio; SrsDtls server(&sio);
MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL);
HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0"));
HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0"));
// Lost 2 packets, total packets should be 3.
// Note that only one server hello.
cio.nn_client_hello_lost = 1;
HELPER_EXPECT_SUCCESS(client.start_active_handshake());
mock_wait_dtls_io_done(client.impl, 15, 5);
EXPECT_TRUE(sio.r0 == srs_success);
EXPECT_TRUE(cio.r0 == srs_success);
EXPECT_TRUE(cio.done);
EXPECT_TRUE(sio.done);
EXPECT_EQ(2, cio.nn_client_hello);
EXPECT_EQ(1, sio.nn_server_hello);
EXPECT_EQ(1, cio.nn_certificate);
EXPECT_EQ(1, sio.nn_new_session);
EXPECT_EQ(0, sio.nn_change_cipher);
}
// Certificate lost, client retransmit the Certificate.
if (true) {
MockDtlsCallback cio; SrsDtls client(&cio);
MockDtlsCallback sio; SrsDtls server(&sio);
MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL);
HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0"));
HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0"));
// Lost 2 packets, total packets should be 3.
// Note that only one server NewSessionTicket.
cio.nn_certificate_lost = 1;
HELPER_EXPECT_SUCCESS(client.start_active_handshake());
mock_wait_dtls_io_done(client.impl, 15, 5);
EXPECT_TRUE(sio.r0 == srs_success);
EXPECT_TRUE(cio.r0 == srs_success);
EXPECT_TRUE(cio.done);
EXPECT_TRUE(sio.done);
EXPECT_EQ(1, cio.nn_client_hello);
EXPECT_EQ(2, sio.nn_server_hello);
EXPECT_EQ(2, cio.nn_certificate);
EXPECT_EQ(0, sio.nn_new_session);
EXPECT_EQ(0, sio.nn_change_cipher);
}
}
VOID TEST(KernelRTCTest, DTLSServerARQTest)
{
srs_error_t err = srs_success;
// No ARQ, check the number of packets.
if (true) {
MockDtlsCallback cio; SrsDtls client(&cio);
MockDtlsCallback sio; SrsDtls server(&sio);
cio.peer = &server; sio.peer = &client;
HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0"));
HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0"));
HELPER_EXPECT_SUCCESS(client.start_active_handshake());
mock_wait_dtls_io_done(client.impl, 15, 5);
EXPECT_TRUE(sio.r0 == srs_success);
EXPECT_TRUE(cio.r0 == srs_success);
EXPECT_TRUE(cio.done);
EXPECT_TRUE(sio.done);
EXPECT_EQ(1, cio.nn_client_hello);
EXPECT_EQ(1, sio.nn_server_hello);
EXPECT_EQ(1, cio.nn_certificate);
EXPECT_EQ(1, sio.nn_new_session);
EXPECT_EQ(0, sio.nn_change_cipher);
}
// ServerHello lost, client retransmit the ClientHello.
if (true) {
MockDtlsCallback cio; SrsDtls client(&cio);
MockDtlsCallback sio; SrsDtls server(&sio);
MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL);
HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0"));
HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0"));
// Lost 2 packets, total packets should be 3.
sio.nn_server_hello_lost = 1;
HELPER_EXPECT_SUCCESS(client.start_active_handshake());
mock_wait_dtls_io_done(client.impl, 15, 5);
EXPECT_TRUE(sio.r0 == srs_success);
EXPECT_TRUE(cio.r0 == srs_success);
EXPECT_TRUE(cio.done);
EXPECT_TRUE(sio.done);
EXPECT_EQ(2, cio.nn_client_hello);
EXPECT_EQ(2, sio.nn_server_hello);
EXPECT_EQ(1, cio.nn_certificate);
EXPECT_EQ(1, sio.nn_new_session);
EXPECT_EQ(0, sio.nn_change_cipher);
}
// NewSessionTicket lost, client retransmit the Certificate.
if (true) {
MockDtlsCallback cio; SrsDtls client(&cio);
MockDtlsCallback sio; SrsDtls server(&sio);
MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL);
HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0"));
HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0"));
// Lost 2 packets, total packets should be 3.
sio.nn_new_session_lost = 1;
HELPER_EXPECT_SUCCESS(client.start_active_handshake());
mock_wait_dtls_io_done(client.impl, 15, 5);
EXPECT_TRUE(sio.r0 == srs_success);
EXPECT_TRUE(cio.r0 == srs_success);
EXPECT_TRUE(cio.done);
EXPECT_TRUE(sio.done);
EXPECT_EQ(1, cio.nn_client_hello);
EXPECT_EQ(1, sio.nn_server_hello);
EXPECT_EQ(2, cio.nn_certificate);
EXPECT_EQ(2, sio.nn_new_session);
EXPECT_EQ(0, sio.nn_change_cipher);
}
}
struct DTLSFlowCase
{
int id;
string ClientVersion;
string ServerVersion;
bool ClientDone;
bool ServerDone;
bool ClientError;
bool ServerError;
};
std::ostream& operator<< (std::ostream& stream, const DTLSFlowCase& c)
{
stream << "Case #" << c.id
<< ", client(" << c.ClientVersion << ",done=" << c.ClientDone << ",err=" << c.ClientError << ")"
<< ", server(" << c.ServerVersion << ",done=" << c.ServerDone << ",err=" << c.ServerError << ")";
return stream;
}
VOID TEST(KernelRTCTest, DTLSClientFlowTest)
{
srs_error_t err = srs_success;
DTLSFlowCase cases[] = {
// OK, Client, Server: DTLS v1.0
{0, "dtls1.0", "dtls1.0", true, true, false, false},
// OK, Client, Server: DTLS v1.2
{1, "dtls1.2", "dtls1.2", true, true, false, false},
// OK, Client: DTLS v1.0, Server: DTLS auto(v1.0 or v1.2).
{2, "dtls1.0", "auto", true, true, false, false},
// OK, Client: DTLS v1.2, Server: DTLS auto(v1.0 or v1.2).
{3, "dtls1.2", "auto", true, true, false, false},
// OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0
{4, "auto", "dtls1.0", true, true, false, false},
// OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0
{5, "auto", "dtls1.2", true, true, false, false},
// OK?, Client: DTLS v1.0, Server: DTLS v1.2
{6, "dtls1.0", "dtls1.2", true, true, false, false},
// OK?, Client: DTLS v1.2, Server: DTLS v1.0
{7, "dtls1.2", "dtls1.0", true, true, false, false},
};
for (int i = 0; i < (int)(sizeof(cases) / sizeof(DTLSFlowCase)); i++) {
DTLSFlowCase c = cases[i];
MockDtlsCallback cio; SrsDtls client(&cio);
MockDtlsCallback sio; MockDtls server(&sio);
MockBridgeDtlsIO b0(&cio, NULL, &server); MockBridgeDtlsIO b1(&sio, &client, NULL);
HELPER_EXPECT_SUCCESS(client.initialize("active", c.ClientVersion)) << c;
HELPER_EXPECT_SUCCESS(server.initialize("passive", c.ServerVersion)) << c;
HELPER_EXPECT_SUCCESS(client.start_active_handshake()) << c;
mock_wait_dtls_io_done(client.impl, 15, 5);
// Note that the cio error is generated from server, vice versa.
EXPECT_EQ(c.ClientDone, cio.done) << c;
EXPECT_EQ(c.ServerDone, sio.done) << c;
EXPECT_EQ(c.ClientError, sio.r0 != srs_success) << c;
EXPECT_EQ(c.ServerError, cio.r0 != srs_success) << c;
}
}
VOID TEST(KernelRTCTest, DTLSServerFlowTest)
{
srs_error_t err = srs_success;
DTLSFlowCase cases[] = {
// OK, Client, Server: DTLS v1.0
{0, "dtls1.0", "dtls1.0", true, true, false, false},
// OK, Client, Server: DTLS v1.2
{1, "dtls1.2", "dtls1.2", true, true, false, false},
// OK, Client: DTLS v1.0, Server: DTLS auto(v1.0 or v1.2).
{2, "dtls1.0", "auto", true, true, false, false},
// OK, Client: DTLS v1.2, Server: DTLS auto(v1.0 or v1.2).
{3, "dtls1.2", "auto", true, true, false, false},
// OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0
{4, "auto", "dtls1.0", true, true, false, false},
// OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0
{5, "auto", "dtls1.2", true, true, false, false},
// OK?, Client: DTLS v1.0, Server: DTLS v1.2
{6, "dtls1.0", "dtls1.2", true, true, false, false},
// OK?, Client: DTLS v1.2, Server: DTLS v1.0
{7, "dtls1.2", "dtls1.0", true, true, false, false},
};
for (int i = 0; i < (int)(sizeof(cases) / sizeof(DTLSFlowCase)); i++) {
DTLSFlowCase c = cases[i];
MockDtlsCallback cio; MockDtls client(&cio);
MockDtlsCallback sio; SrsDtls server(&sio);
MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, NULL, &client);
HELPER_EXPECT_SUCCESS(client.initialize("active", c.ClientVersion)) << c;
HELPER_EXPECT_SUCCESS(server.initialize("passive", c.ServerVersion)) << c;
HELPER_EXPECT_SUCCESS(client.start_active_handshake()) << c;
mock_wait_dtls_io_done(NULL, 15, 5);
// Note that the cio error is generated from server, vice versa.
EXPECT_EQ(c.ClientDone, cio.done) << c;
EXPECT_EQ(c.ServerDone, sio.done) << c;
EXPECT_EQ(c.ClientError, sio.r0 != srs_success) << c;
EXPECT_EQ(c.ServerError, cio.r0 != srs_success) << c;
}
}
VOID TEST(KernelRTCTest, SequenceCompare)
{
if (true) {