Tools: Sync 3rdparty tools

pull/2357/head
winlin 4 years ago
parent dea6136238
commit 2783ac7c92

@ -0,0 +1,10 @@
#!/bin/bash
echo "Copy signaling"
cp -R ~/signaling/* signaling/
echo "Copy httpx-static"
cp -R ~/httpx-static/* httpx-static/
echo "Copy srs-bench"
cp -R ~/srs-bench/* srs-bench/

@ -29,7 +29,7 @@ $GOPATH/bin/httpx-static -https 8443 -root `pwd`/html
Open https://localhost:8443/ in browser. Open https://localhost:8443/ in browser.
> Remark: Click `ADVANCED` => `Proceed to localhost (unsafe)`. > Remark: Click `ADVANCED` => `Proceed to localhost (unsafe)`, or type `thisisunsafe` in page.
*HTTPS proxy*: Proxy http as https *HTTPS proxy*: Proxy http as https
@ -44,6 +44,25 @@ $GOPATH/bin/httpx-static -https 8443 -root `pwd`/html -proxy http://ossrs.net:19
Open https://localhost:8443/api/v1/summaries in browser. Open https://localhost:8443/api/v1/summaries in browser.
## Docker
Run httpx-static in docker:
```bash
docker run --rm -p 80:80 -p 443:443 registry.cn-hangzhou.aliyuncs.com/ossrs/httpx:v1.0.2
```
> Note: More images and version is [here](https://cr.console.aliyun.com/repository/cn-hangzhou/ossrs/httpx/images).
To proxy to other dockers, in macOS:
```bash
CANDIDATE=$(ifconfig en0 inet| grep 'inet '|awk '{print $2}') &&
docker run --rm -p 80:80 -p 443:443 registry.cn-hangzhou.aliyuncs.com/ossrs/httpx:v1.0.5 \
./bin/httpx-static -http 80 -https 443 -ssk ./etc/server.key -ssc ./etc/server.crt \
-proxy http://$CANDIDATE:8080/
```
## History ## History
* v0.0.3, 2017-11-03, Support multiple proxy HTTP to HTTPS. * v0.0.3, 2017-11-03, Support multiple proxy HTTP to HTTPS.

@ -0,0 +1,24 @@
############################################################
# build
############################################################
FROM registry.cn-hangzhou.aliyuncs.com/ossrs/srs:dev AS build
COPY . /tmp/signaling
RUN cd /tmp/signaling && make
RUN cp /tmp/signaling/objs/signaling /usr/local/bin/signaling
RUN cp -R /tmp/signaling/www /usr/local/
############################################################
# dist
############################################################
FROM centos:7 AS dist
# HTTP/1989
EXPOSE 1989
# SRS binary, config files and srs-console.
COPY --from=build /usr/local/bin/signaling /usr/local/bin/
COPY --from=build /usr/local/www /usr/local/www
# Default workdir and command.
WORKDIR /usr/local
CMD ["./bin/signaling"]

@ -4,21 +4,59 @@ WebRTC signaling for https://github.com/ossrs/srs
## Usage ## Usage
Build and [run SRS](https://github.com/ossrs/srs/tree/4.0release#usage): [Run SRS](https://github.com/ossrs/srs/tree/4.0release#usage) in docker:
```bash ```bash
git clone -b 4.0release https://gitee.com/ossrs/srs.git srs && docker run --rm --env CANDIDATE=$(ifconfig en0 inet| grep 'inet '|awk '{print $2}') \
cd srs/trunk && ./configure && make && ./objs/srs -c conf/rtc.conf -p 1935:1935 -p 8080:8080 -p 1985:1985 -p 8000:8000/udp \
registry.cn-hangzhou.aliyuncs.com/ossrs/srs:v4.0.95 \
objs/srs -c conf/rtc.conf
``` ```
Build and run signaling: > Note: More images and version is [here](https://cr.console.aliyun.com/repository/cn-hangzhou/ossrs/srs/images).
Run signaling in docker:
```bash ```bash
cd srs/trunk/3rdparty/signaling && make && ./objs/signaling docker run --rm -p 1989:1989 registry.cn-hangzhou.aliyuncs.com/ossrs/signaling:v1.0.4
``` ```
> Note: More images and version is [here](https://cr.console.aliyun.com/repository/cn-hangzhou/ossrs/signaling/images).
Open the H5 demos: Open the H5 demos:
* [WebRTC: One to One over SFU(SRS)](http://localhost:1989/demos/one2one.html?autostart=true) * [WebRTC: One to One over SFU(SRS)](http://localhost:1989/demos/one2one.html?autostart=true)
## Build from source
Build and [run SRS](https://github.com/ossrs/srs/tree/4.0release#usage):
```bash
cd ~/git && git clone -b 4.0release https://gitee.com/ossrs/srs.git srs &&
cd ~/git/srs/trunk && ./configure && make && ./objs/srs -c conf/rtc.conf
```
Build and run signaling:
```bash
cd ~/git/srs/trunk/3rdparty/signaling && make && ./objs/signaling
```
Open demos by localhost: http://localhost:1989/demos
Build and run httpx-static for HTTPS/WSS:
```bash
cd ~/git/srs/trunk/3rdparty/httpx-static && make &&
./objs/httpx-static -http 80 -https 443 -ssk server.key -ssc server.crt \
-proxy http://127.0.0.1:1989/sig -proxy http://127.0.0.1:1985/rtc \
-proxy http://127.0.0.1:8080/
```
Open demos by HTTPS or IP:
* http://localhost/demos/
* https://localhost/demos/
* https://192.168.3.6/demos/
Winlin 2021.05 Winlin 2021.05

@ -0,0 +1,70 @@
#!/bin/bash
SRS_GIT=$HOME/git/signaling
SRS_TAG=
# linux shell color support.
RED="\\033[31m"
GREEN="\\033[32m"
YELLOW="\\033[33m"
BLACK="\\033[0m"
function NICE() {
echo -e "${GREEN}$@${BLACK}"
}
function TRACE() {
echo -e "${BLACK}$@${BLACK}"
}
function WARN() {
echo -e "${YELLOW}$@${BLACK}"
}
function ERROR() {
echo -e "${RED}$@${BLACK}"
}
##################################################################################
##################################################################################
##################################################################################
if [[ -z $SRS_TAG ]]; then
SRS_TAG=`(cd $SRS_GIT && git describe --tags --abbrev=0 --exclude release-* 2>/dev/null)`
if [[ $? -ne 0 ]]; then
echo "Invalid tag $SRS_TAG of $SRS_FILTER in $SRS_GIT"
exit -1
fi
fi
NICE "Build docker for $SRS_GIT, tag is $SRS_TAG"
git ci -am "Release $SRS_TAG"
# For aliyun hub.
NICE "aliyun hub release-v$SRS_TAG"
echo "git push aliyun"
git push aliyun
git tag -d release-v$SRS_TAG 2>/dev/null
echo "Cleanup tag $SRS_TAG for aliyun"
git tag release-v$SRS_TAG; git push -f aliyun release-v$SRS_TAG
echo "Create new tag $SRS_TAG for aliyun"
echo ""
NICE "aliyun hub release-vlatest"
git tag -d release-vlatest 2>/dev/null
echo "Cleanup tag latest for aliyun"
git tag release-vlatest; git push -f aliyun release-vlatest
echo "Create new tag latest for aliyun"
# For github.com
echo "git push origin"
git push origin
echo "git push origin $SRS_TAG"
git push origin $SRS_TAG
NICE "Update github ok"

@ -7,7 +7,7 @@
<body> <body>
<h3><a href="https://github.com/ossrs/signaling">Signaling</a> works!</h3> <h3><a href="https://github.com/ossrs/signaling">Signaling</a> works!</h3>
<p> <p>
Run demo for <a href="one2one.html">WebRTC: One to One over SFU(SRS)</a><br/> Run demo for <a href="one2one.html?autostart=true">WebRTC: One to One over SFU(SRS)</a><br/>
点击进入<a href="one2one.html">SRS一对一通话演示</a> 点击进入<a href="one2one.html?autostart=true">SRS一对一通话演示</a>
</p> </p>
</body> </body>

@ -5,13 +5,13 @@ default: bench test
clean: clean:
rm -f ./objs/srs_bench ./objs/srs_test 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 . gofmt -w .
echo "done" > .format.txt echo "done" > .format.txt
bench: ./objs/srs_bench 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 . go build -mod=vendor -o objs/srs_bench .
test: ./objs/srs_test test: ./objs/srs_test

@ -184,4 +184,41 @@ yum install -y python2-pip &&
pip install lxml && pip install gcovr 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 2021.01, Winlin

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

File diff suppressed because it is too large Load Diff

@ -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()
}

@ -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
}

@ -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
}

@ -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()
}

@ -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
}

File diff suppressed because it is too large Load Diff

@ -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)
}
}

@ -24,133 +24,35 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger" "github.com/ossrs/go-oryx-lib/logger"
"github.com/ossrs/srs-bench/janus"
"github.com/ossrs/srs-bench/srs" "github.com/ossrs/srs-bench/srs"
"net" "io/ioutil"
"net/http"
"os" "os"
"os/signal" "os/signal"
"strings"
"sync"
"syscall" "syscall"
"time"
) )
func main() { func main() {
var sr, dumpAudio, dumpVideo string var sfu string
var pli int fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
flag.StringVar(&sr, "sr", "", "") fl.SetOutput(ioutil.Discard)
flag.StringVar(&dumpAudio, "da", "", "") fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or janus")
flag.StringVar(&dumpVideo, "dv", "", "") _ = fl.Parse(os.Args[1:])
flag.IntVar(&pli, "pli", 10, "")
var pr, sourceAudio, sourceVideo string ctx := context.Background()
var fps int if sfu == "srs" {
flag.StringVar(&pr, "pr", "", "") srs.Parse(ctx)
flag.StringVar(&sourceAudio, "sa", "", "") } else if sfu == "janus" {
flag.StringVar(&sourceVideo, "sv", "", "") janus.Parse(ctx)
flag.IntVar(&fps, "fps", 0, "") } else {
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("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:")) fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1")) fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or janus. Default: srs"))
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
}
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)
os.Exit(-1) os.Exit(-1)
} }
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
// Process all signals.
go func() { go func() {
sigs := make(chan os.Signal, 1) sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
@ -160,124 +62,17 @@ func main() {
} }
}() }()
// Run tasks. var err error
var wg sync.WaitGroup if sfu == "srs" {
err = srs.Run(ctx)
// Start STAT API server. } else if sfu == "janus" {
wg.Add(1) err = janus.Run(ctx)
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)
}
} }
// Start all publishers. if err != nil {
for i := 0; pr != "" && i < streams && ctx.Err() == nil; i++ { logger.Wf(ctx, "srs err %+v", err)
r_auto := pr return
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)
} }
wg.Wait()
logger.Tf(ctx, "Done") logger.Tf(ctx, "Done")
} }

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

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

@ -40,10 +40,10 @@ import (
) )
// @see https://github.com/pion/webrtc/blob/master/examples/save-to-disk/main.go // @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) 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) r, dumpAudio, dumpVideo, enableAudioLevel, enableTWCC)
// For audio-level. // For audio-level.
@ -257,7 +257,7 @@ func StartPlay(ctx context.Context, r, dumpAudio, dumpVideo string, enableAudioL
case <-ctx.Done(): case <-ctx.Done():
return return
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
StatRTC.PeerConnection = pc.GetStats() gStatRTC.PeerConnection = pc.GetStats()
} }
} }
}() }()

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

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

@ -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
}

@ -41,9 +41,9 @@ type statRTC struct {
PeerConnection interface{} `json:"random-pc"` 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, ":") { if strings.HasPrefix(l, ":") {
l = "127.0.0.1" + 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"` Code int `json:"code"`
Data interface{} `json:"data"` Data interface{} `json:"data"`
}{ }{
0, &StatRTC, 0, &gStatRTC,
} }
b, err := json.Marshal(res) b, err := json.Marshal(res)

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

@ -1,32 +1,15 @@
// 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 package vnet
import ( import (
"fmt"
"net" "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) { func (v *UDPProxy) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err error) {
v.workers.Range(func(key, value interface{}) bool { 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. return false // Fail, abort.
} else if nn == len(b) { } else if nn == len(b) {
return false // Done. return false // Done.
@ -40,15 +23,15 @@ func (v *UDPProxy) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err
func (v *aUDPProxyWorker) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err error) { func (v *aUDPProxyWorker) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err error) {
addr, ok := sourceAddr.(*net.UDPAddr) addr, ok := sourceAddr.(*net.UDPAddr)
if !ok { if !ok {
return 0, nil return 0, fmt.Errorf("invalid addr %v", sourceAddr) // nolint:goerr113
} }
// TODO: Support deliver packet from real server to vnet. // nolint:godox // TODO: Support deliver packet from real server to vnet.
// If packet is from vent, proxy to real server. // If packet is from vnet, proxy to real server.
var realSocket *net.UDPConn var realSocket *net.UDPConn
if value, ok := v.endpoints.Load(addr.String()); !ok { if value, ok := v.endpoints.Load(addr.String()); !ok {
return 0, nil return 0, nil
} else { } else { // nolint:golint
realSocket = value.(*net.UDPConn) realSocket = value.(*net.UDPConn)
} }

@ -1,41 +1,25 @@
// The MIT License (MIT) // +build !wasm
//
// 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 package vnet
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/pion/logging"
"github.com/pion/transport/vnet"
"net" "net"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/pion/logging"
) )
// vnet client: // The vnet client:
// 10.0.0.11:5787 // 10.0.0.11:5787
// proxy to real server: // which proxy to real server:
// 192.168.1.10:8000 // 192.168.1.10:8000
func TestUDPProxyDirectDeliver(t *testing.T) { // We should get a reply if directly deliver to proxy.
func TestUDPProxyDirectDeliverTypical(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
var r0, r1, r2 error var r0, r1, r2 error
@ -57,7 +41,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
case <-time.After(time.Duration(*testTimeout) * time.Millisecond): case <-time.After(time.Duration(*testTimeout) * time.Millisecond):
r2 = fmt.Errorf("timeout") r2 = fmt.Errorf("timeout") // nolint:goerr113
} }
}() }()
@ -86,7 +70,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
} }
doVnetProxy := func() error { doVnetProxy := func() error {
router, err := vnet.NewRouter(&vnet.RouterConfig{ router, err := NewRouter(&RouterConfig{
CIDR: "0.0.0.0/0", CIDR: "0.0.0.0/0",
LoggerFactory: logging.NewDefaultLoggerFactory(), LoggerFactory: logging.NewDefaultLoggerFactory(),
}) })
@ -94,23 +78,23 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
return err return err
} }
clientNetwork := vnet.NewNet(&vnet.NetConfig{ clientNetwork := NewNet(&NetConfig{
StaticIP: "10.0.0.11", StaticIP: "10.0.0.11",
}) })
if err = router.AddNet(clientNetwork); err != nil { if err = router.AddNet(clientNetwork); err != nil {
return err return err
} }
if err := router.Start(); err != nil { if err = router.Start(); err != nil {
return err return err
} }
defer router.Stop() defer router.Stop() // nolint:errcheck
proxy, err := NewProxy(router) proxy, err := NewProxy(router)
if err != nil { if err != nil {
return err return err
} }
defer proxy.Close() defer proxy.Close() // nolint:errcheck
// For utest, mock the target real server. // For utest, mock the target real server.
proxy.mockRealServerAddr = mockServer.realServerAddr proxy.mockRealServerAddr = mockServer.realServerAddr
@ -122,7 +106,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
return err return err
} }
if err := proxy.Proxy(clientNetwork, serverAddr); err != nil { if err = proxy.Proxy(clientNetwork, serverAddr); err != nil {
return err return err
} }
@ -137,41 +121,41 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
go func() { go func() {
<-ctx.Done() <-ctx.Done()
selfKillCancel() selfKillCancel()
client.Close() _ = client.Close()
}() }()
// Write by vnet client. // Write by vnet client.
if _, err := client.WriteTo([]byte("Hello"), serverAddr); err != nil { if _, err = client.WriteTo([]byte("Hello"), serverAddr); err != nil {
return err return err
} }
buf := make([]byte, 1500) buf := make([]byte, 1500)
if n, addr, err := client.ReadFrom(buf); err != nil { if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet
if selfKill.Err() == context.Canceled { if errors.Is(selfKill.Err(), context.Canceled) {
return nil return nil
} }
return err return err
} else if n != 5 || addr == nil { } 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" { } else if string(buf[:n]) != "Hello" { // nolint:goconst
return fmt.Errorf("data %v", buf[:n]) return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113
} }
// Directly write, simulate the ARQ packet. // Directly write, simulate the ARQ packet.
// We should got the echo packet also. // 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 return err
} }
if n, addr, err := client.ReadFrom(buf); err != nil { if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet
if selfKill.Err() == context.Canceled { if errors.Is(selfKill.Err(), context.Canceled) {
return nil return nil
} }
return err return err
} else if n != 5 || addr == nil { } 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" { } else if string(buf[:n]) != "Hello" {
return fmt.Errorf("data %v", buf[:n]) return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113
} }
return err return err
@ -182,3 +166,170 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
} }
}() }()
} }
// Error if deliver to invalid address.
func TestUDPProxyDirectDeliverBadcase(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
var r0, r1, r2 error
defer func() {
if r0 != nil || r1 != nil || r2 != nil {
t.Errorf("fail for ctx=%v, r0=%v, r1=%v, r2=%v", ctx.Err(), r0, r1, r2)
}
}()
var wg sync.WaitGroup
defer wg.Wait()
// Timeout, fail
wg.Add(1)
go func() {
defer wg.Done()
defer cancel()
select {
case <-ctx.Done():
case <-time.After(time.Duration(*testTimeout) * time.Millisecond):
r2 = fmt.Errorf("timeout") // nolint:goerr113
}
}()
// For utest, we always proxy vnet packets to the random port we listen to.
mockServer := NewMockUDPEchoServer()
wg.Add(1)
go func() {
defer wg.Done()
defer cancel()
if err := mockServer.doMockUDPServer(ctx); err != nil {
r0 = err
}
}()
// Create a vent and proxy.
wg.Add(1)
go func() {
defer wg.Done()
defer cancel()
// When real server is ready, start the vnet test.
select {
case <-ctx.Done():
return
case <-mockServer.realServerReady.Done():
}
doVnetProxy := func() error {
router, err := NewRouter(&RouterConfig{
CIDR: "0.0.0.0/0",
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
if err != nil {
return err
}
clientNetwork := NewNet(&NetConfig{
StaticIP: "10.0.0.11",
})
if err = router.AddNet(clientNetwork); err != nil {
return err
}
if err = router.Start(); err != nil {
return err
}
defer router.Stop() // nolint:errcheck
proxy, err := NewProxy(router)
if err != nil {
return err
}
defer proxy.Close() // nolint:errcheck
// For utest, mock the target real server.
proxy.mockRealServerAddr = mockServer.realServerAddr
// The real server address to proxy to.
// Note that for utest, we will proxy to a local address.
serverAddr, err := net.ResolveUDPAddr("udp4", "192.168.1.10:8000")
if err != nil {
return err
}
if err = proxy.Proxy(clientNetwork, serverAddr); err != nil {
return err
}
// Now, all packets from client, will be proxy to real server, vice versa.
client, err := clientNetwork.ListenPacket("udp4", "10.0.0.11:5787")
if err != nil {
return err
}
// When system quit, interrupt client.
selfKill, selfKillCancel := context.WithCancel(context.Background())
go func() {
<-ctx.Done()
selfKillCancel()
_ = client.Close()
}()
// Write by vnet client.
if _, err = client.WriteTo([]byte("Hello"), serverAddr); err != nil {
return err
}
buf := make([]byte, 1500)
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) // nolint:goerr113
} else if string(buf[:n]) != "Hello" { // nolint:goconst
return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113
}
// BadCase: Invalid address, error and ignore.
tcpAddr, err := net.ResolveTCPAddr("tcp4", "192.168.1.10:8000")
if err != nil {
return err
}
if _, err = proxy.Deliver(tcpAddr, serverAddr, []byte("Hello")); err == nil {
return fmt.Errorf("should err") // nolint:goerr113
}
// BadCase: Invalid target address, ignore.
udpAddr, err := net.ResolveUDPAddr("udp4", "10.0.0.12:5788")
if err != nil {
return err
}
if nn, err := proxy.Deliver(udpAddr, serverAddr, []byte("Hello")); err != nil { // nolint:govet
return err
} else if nn != 0 {
return fmt.Errorf("invalid %v", nn) // nolint:goerr113
}
// BadCase: Write on closed socket, error and ignore.
proxy.workers.Range(func(key, value interface{}) bool {
value.(*aUDPProxyWorker).endpoints.Range(func(key, value interface{}) bool {
_ = value.(*net.UDPConn).Close()
return true
})
return true
})
if _, err = proxy.Deliver(client.LocalAddr(), serverAddr, []byte("Hello")); err == nil {
return fmt.Errorf("should error") // nolint:goerr113
}
return nil
}
if err := doVnetProxy(); err != nil {
r1 = err
}
}()
}

Loading…
Cancel
Save