diff --git a/trunk/3rdparty/copy_from_gits.sh b/trunk/3rdparty/copy_from_gits.sh
new file mode 100755
index 000000000..822b01b63
--- /dev/null
+++ b/trunk/3rdparty/copy_from_gits.sh
@@ -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/
diff --git a/trunk/3rdparty/httpx-static/README.md b/trunk/3rdparty/httpx-static/README.md
index 3b6259475..d82b7b7a6 100644
--- a/trunk/3rdparty/httpx-static/README.md
+++ b/trunk/3rdparty/httpx-static/README.md
@@ -29,7 +29,7 @@ $GOPATH/bin/httpx-static -https 8443 -root `pwd`/html
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
@@ -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.
+## 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
* v0.0.3, 2017-11-03, Support multiple proxy HTTP to HTTPS.
diff --git a/trunk/3rdparty/signaling/Dockerfile b/trunk/3rdparty/signaling/Dockerfile
new file mode 100644
index 000000000..393e57004
--- /dev/null
+++ b/trunk/3rdparty/signaling/Dockerfile
@@ -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"]
diff --git a/trunk/3rdparty/signaling/README.md b/trunk/3rdparty/signaling/README.md
index d786896a9..f20c724f9 100644
--- a/trunk/3rdparty/signaling/README.md
+++ b/trunk/3rdparty/signaling/README.md
@@ -4,21 +4,59 @@ WebRTC signaling for https://github.com/ossrs/srs
## 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
-git clone -b 4.0release https://gitee.com/ossrs/srs.git srs &&
-cd srs/trunk && ./configure && make && ./objs/srs -c conf/rtc.conf
+docker run --rm --env CANDIDATE=$(ifconfig en0 inet| grep 'inet '|awk '{print $2}') \
+ -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
-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:
* [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
diff --git a/trunk/3rdparty/signaling/auto/release.sh b/trunk/3rdparty/signaling/auto/release.sh
new file mode 100755
index 000000000..c63c9e7b7
--- /dev/null
+++ b/trunk/3rdparty/signaling/auto/release.sh
@@ -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"
diff --git a/trunk/3rdparty/signaling/www/demos/index.html b/trunk/3rdparty/signaling/www/demos/index.html
index ed3b6e03a..0cb04ccc2 100644
--- a/trunk/3rdparty/signaling/www/demos/index.html
+++ b/trunk/3rdparty/signaling/www/demos/index.html
@@ -7,7 +7,7 @@
- Run demo for WebRTC: One to One over SFU(SRS)
- 点击进入SRS一对一通话演示
+ Run demo for WebRTC: One to One over SFU(SRS)
+ 点击进入SRS一对一通话演示
diff --git a/trunk/3rdparty/srs-bench/Makefile b/trunk/3rdparty/srs-bench/Makefile
index dedfc5fe5..813d1409a 100644
--- a/trunk/3rdparty/srs-bench/Makefile
+++ b/trunk/3rdparty/srs-bench/Makefile
@@ -5,13 +5,13 @@ default: bench test
clean:
rm -f ./objs/srs_bench ./objs/srs_test
-.format.txt: *.go srs/*.go vnet/*.go
+.format.txt: *.go srs/*.go vnet/*.go janus/*.go
gofmt -w .
echo "done" > .format.txt
bench: ./objs/srs_bench
-./objs/srs_bench: .format.txt *.go srs/*.go vnet/*.go Makefile
+./objs/srs_bench: .format.txt *.go srs/*.go vnet/*.go janus/*.go Makefile
go build -mod=vendor -o objs/srs_bench .
test: ./objs/srs_test
diff --git a/trunk/3rdparty/srs-bench/README.md b/trunk/3rdparty/srs-bench/README.md
index 6b903692c..d45820cc8 100644
--- a/trunk/3rdparty/srs-bench/README.md
+++ b/trunk/3rdparty/srs-bench/README.md
@@ -184,4 +184,41 @@ yum install -y python2-pip &&
pip install lxml && pip install gcovr
```
+## Janus
+
+支持Janus的压测,使用选项`-sfu janus`可以查看帮助:
+
+```bash
+./objs/srs_bench -sfu janus --help
+```
+
+首先需要启动Janus,推荐使用[janus-docker](https://github.com/winlinvip/janus-docker#usage):
+
+```bash
+ip=$(ifconfig en0 inet|grep inet|awk '{print $2}') &&
+sed -i '' "s/nat_1_1_mapping.*/nat_1_1_mapping=\"$ip\"/g" janus.jcfg &&
+docker run --rm -it -p 8080:8080 -p 8443:8443 -p 20000-20010:20000-20010/udp \
+ -v $(pwd)/janus.jcfg:/usr/local/etc/janus/janus.jcfg \
+ -v $(pwd)/janus.plugin.videoroom.jcfg:/usr/local/etc/janus/janus.plugin.videoroom.jcfg \
+ registry.cn-hangzhou.aliyuncs.com/ossrs/janus:v1.0.7
+```
+
+> 若启动成功,打开页面,可以自动入会:[http://localhost:8080](http://localhost:8080)
+
+模拟5个推流入会,可以在页面看到入会的流:
+
+```bash
+make -j10 && ./objs/srs_bench -sfu janus \
+ -pr webrtc://localhost:8080/2345/livestream \
+ -sa avatar.ogg -sv avatar.h264 -fps 25 -sn 5
+```
+
+模拟5个拉流入会,只拉流不推流:
+
+```bash
+make -j10 && ./objs/srs_bench -sfu janus \
+ -sr webrtc://localhost:8080/2345/livestream \
+ -nn 5
+```
+
2021.01, Winlin
diff --git a/trunk/3rdparty/srs-bench/auto/sync_vnet.sh b/trunk/3rdparty/srs-bench/auto/sync_vnet.sh
index 55ef15f1a..ad00d3993 100755
--- a/trunk/3rdparty/srs-bench/auto/sync_vnet.sh
+++ b/trunk/3rdparty/srs-bench/auto/sync_vnet.sh
@@ -1,9 +1,9 @@
#!/bin/bash
-FILES=(udpproxy.go udpproxy_test.go)
+FILES=(udpproxy.go udpproxy_test.go udpproxy_direct.go udpproxy_direct_test.go)
for file in ${FILES[@]}; do
- echo "cp vnet/udpproxy.go ~/git/transport/vnet/" &&
- cp vnet/udpproxy.go ~/git/transport/vnet/
+ echo "cp vnet/$file ~/git/transport/vnet/" &&
+ cp vnet/$file ~/git/transport/vnet/
done
# https://github.com/pion/webrtc/wiki/Contributing#run-all-automated-tests-and-checks-before-submitting
@@ -26,8 +26,8 @@ echo "go test -race ./..." &&
go test -race ./...
if [[ $? -ne 0 ]]; then echo "fail"; exit -1; fi
-echo "golangci-lint run --skip-files conn_map_test.go" &&
-golangci-lint run --skip-files conn_map_test.go
+echo "golangci-lint run --skip-files conn_map_test.go,router_test.go" &&
+golangci-lint run --skip-files conn_map_test.go,router_test.go
if [[ $? -ne 0 ]]; then echo "fail"; exit -1; fi
echo "OK"
diff --git a/trunk/3rdparty/srs-bench/janus/api.go b/trunk/3rdparty/srs-bench/janus/api.go
new file mode 100644
index 000000000..152a0995e
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/api.go
@@ -0,0 +1,1077 @@
+// 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"
+ "encoding/json"
+ "fmt"
+ "github.com/ossrs/go-oryx-lib/errors"
+ "github.com/ossrs/go-oryx-lib/logger"
+ "io/ioutil"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+)
+
+type publisherInfo struct {
+ AudioCodec string `json:"audio_codec"`
+ Display string `json:"display"`
+ ID uint64 `json:"id"`
+ Talking bool `json:"talking"`
+ VideoCodec string `json:"video_codec"`
+}
+
+func (v publisherInfo) String() string {
+ return fmt.Sprintf("%v(codec:%v/%v,id:%v,talk:%v)",
+ v.Display, v.VideoCodec, v.AudioCodec, v.ID, v.Talking)
+}
+
+type janusReply struct {
+ transactionID string
+ replies chan []byte
+}
+
+func newJanusReply(tid string) *janusReply {
+ return &janusReply{
+ transactionID: tid,
+ replies: make(chan []byte, 1),
+ }
+}
+
+type janusHandle struct {
+ api *janusAPI
+ // The ID created by API.
+ handleID uint64
+ publisherID uint64
+}
+
+type janusAPI struct {
+ // For example, http://localhost:8088/janus
+ r string
+ // The ID created by API.
+ sessionID uint64 // By Create().
+ privateID uint64 // By JoinAsPublisher().
+ // The handles, key is handleID, value is *janusHandle
+ handles sync.Map
+ // The callbacks.
+ onDetached func(sender, sessionID uint64)
+ onWebrtcUp func(sender, sessionID uint64)
+ onMedia func(sender, sessionID uint64, mtype string, receiving bool)
+ onSlowLink func(sender, sessionID uint64, media string, lost uint64, uplink bool)
+ onPublisher func(sender, sessionID uint64, publishers []publisherInfo)
+ onUnPublished func(sender, sessionID, id uint64)
+ onLeave func(sender, sessionID, id uint64)
+ // The context for polling.
+ pollingCtx context.Context
+ pollingCancel context.CancelFunc
+ wg sync.WaitGroup
+ // The replies of polling key is transactionID, value is janusReply.
+ replies sync.Map
+}
+
+func newJanusAPI(r string) *janusAPI {
+ v := &janusAPI{r: r}
+ if !strings.HasSuffix(r, "/") {
+ v.r += "/"
+ }
+ v.onDetached = func(sender, sessionID uint64) {
+ }
+ v.onWebrtcUp = func(sender, sessionID uint64) {
+ }
+ v.onMedia = func(sender, sessionID uint64, mtype string, receiving bool) {
+ }
+ v.onSlowLink = func(sender, sessionID uint64, media string, lost uint64, uplink bool) {
+ }
+ v.onPublisher = func(sender, sessionID uint64, publishers []publisherInfo) {
+ }
+ v.onUnPublished = func(sender, sessionID, id uint64) {
+ }
+ v.onLeave = func(sender, sessionID, id uint64) {
+ }
+ return v
+}
+
+func (v *janusAPI) Close() error {
+ v.pollingCancel()
+ v.wg.Wait()
+ return nil
+}
+
+func (v *janusAPI) Create(ctx context.Context) error {
+ v.pollingCtx, v.pollingCancel = context.WithCancel(ctx)
+
+ api := v.r
+
+ reqBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ }{
+ "create", newTransactionID(),
+ }
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ resBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ Data struct {
+ ID uint64 `json:"id"`
+ } `json:"data"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &resBody); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if resBody.Janus != "success" {
+ return errors.Errorf("Server fail code=%v %v", resBody.Janus, s2)
+ }
+
+ v.sessionID = resBody.Data.ID
+ logger.Tf(ctx, "Parse create sessionID=%v", v.sessionID)
+
+ v.wg.Add(1)
+ go func() {
+ defer v.wg.Done()
+ defer v.pollingCancel()
+
+ for v.pollingCtx.Err() == nil {
+ if err := v.polling(v.pollingCtx); err != nil {
+ if v.pollingCtx.Err() != context.Canceled {
+ logger.Wf(ctx, "polling err %+v", err)
+ }
+ break
+ }
+ }
+ }()
+
+ return nil
+}
+
+func (v *janusAPI) AttachPlugin(ctx context.Context) (handleID uint64, err error) {
+ api := fmt.Sprintf("%v%v", v.r, v.sessionID)
+
+ reqBody := struct {
+ Janus string `json:"janus"`
+ OpaqueID string `json:"opaque_id"`
+ Plugin string `json:"plugin"`
+ Transaction string `json:"transaction"`
+ }{
+ "attach", newTransactionID(),
+ "janus.plugin.videoroom", newTransactionID(),
+ }
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return 0, errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return 0, errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return 0, errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return 0, errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ resBody := struct {
+ Janus string `json:"janus"`
+ SessionID uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ Data struct {
+ ID uint64 `json:"id"`
+ } `json:"data"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &resBody); err != nil {
+ return 0, errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if resBody.Janus != "success" {
+ return 0, errors.Errorf("Server fail code=%v %v", resBody.Janus, s2)
+ }
+
+ h := &janusHandle{}
+ h.handleID = resBody.Data.ID
+ h.api = v
+ v.handles.Store(h.handleID, h)
+ logger.Tf(ctx, "Parse create handleID=%v", h.handleID)
+
+ return h.handleID, nil
+}
+
+func (v *janusAPI) DetachPlugin(ctx context.Context, handleID uint64) error {
+ handler := v.loadHandler(handleID)
+ api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
+
+ reqBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ }{
+ "detach", newTransactionID(),
+ }
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ ackBody := struct {
+ Janus string `json:"janus"`
+ SessionID uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if ackBody.Janus != "success" {
+ return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
+ }
+ logger.Tf(ctx, "Detach tid=%v done", reqBody.Transaction)
+
+ return nil
+}
+
+func (v *janusAPI) loadHandler(handleID uint64) *janusHandle {
+ if h, ok := v.handles.Load(handleID); !ok {
+ return nil
+ } else {
+ return h.(*janusHandle)
+ }
+}
+
+func (v *janusAPI) JoinAsPublisher(ctx context.Context, handleID uint64, room int, display string) error {
+ handler := v.loadHandler(handleID)
+ api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
+
+ reqBodyBody := struct {
+ Request string `json:"request"`
+ PType string `json:"ptype"`
+ Room int `json:"room"`
+ Display string `json:"display"`
+ }{
+ "join", "publisher", room, display,
+ }
+ reqBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ Body interface{} `json:"body"`
+ }{
+ "message", newTransactionID(), reqBodyBody,
+ }
+
+ reply := newJanusReply(reqBody.Transaction)
+ v.replies.Store(reqBody.Transaction, reply)
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ ackBody := struct {
+ Janus string `json:"janus"`
+ SessionID uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if ackBody.Janus != "ack" {
+ return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
+ }
+ logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
+
+ // Reply from polling.
+ var s3 string
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case b3 := <-reply.replies:
+ s3 = escapeJSON(string(b3))
+ logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
+ }
+ resBody := struct {
+ Janus string `json:"janus"`
+ Session uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ Sender uint64 `json:"sender"`
+ PluginData struct {
+ Plugin string `json:"plugin"`
+ Data struct {
+ VideoRoom string `json:"videoroom"`
+ Room int `json:"room"`
+ Description string `json:"description"`
+ ID uint64 `json:"id"`
+ PrivateID uint64 `json:"private_id"`
+ Publishers []publisherInfo `json:"publishers"`
+ } `json:"data"`
+ } `json:"plugindata"`
+ }{}
+ if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s3)
+ }
+
+ plugin := resBody.PluginData.Data
+ if resBody.Janus != "event" || plugin.VideoRoom != "joined" {
+ return errors.Errorf("Server fail janus=%v, plugin=%v %v", resBody.Janus, plugin.VideoRoom, s3)
+ }
+
+ handler.publisherID = plugin.ID
+ v.privateID = plugin.PrivateID
+ logger.Tf(ctx, "Join as publisher room=%v, display=%v, tid=%v ok, event=%v, plugin=%v, id=%v, private=%v, publishers=%v",
+ room, display, reply.transactionID, resBody.Janus, plugin.VideoRoom, handler.publisherID, plugin.PrivateID, len(plugin.Publishers))
+
+ if len(plugin.Publishers) > 0 {
+ v.onPublisher(resBody.Sender, resBody.Session, plugin.Publishers)
+ }
+
+ return nil
+}
+
+func (v *janusAPI) UnPublish(ctx context.Context, handleID uint64) error {
+ handler := v.loadHandler(handleID)
+ api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
+
+ reqBodyBody := struct {
+ Request string `json:"request"`
+ }{
+ "unpublish",
+ }
+ reqBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ Body interface{} `json:"body"`
+ }{
+ "message", newTransactionID(), reqBodyBody,
+ }
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ ackBody := struct {
+ Janus string `json:"janus"`
+ SessionID uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if ackBody.Janus != "ack" {
+ return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
+ }
+ logger.Tf(ctx, "UnPublish tid=%v done", reqBody.Transaction)
+
+ return nil
+}
+
+func (v *janusAPI) Publish(ctx context.Context, handleID uint64, offer string) (answer string, err error) {
+ handler := v.loadHandler(handleID)
+ api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
+
+ reqBodyBody := struct {
+ Request string `json:"request"`
+ Video bool `json:"video"`
+ Audio bool `json:"audio"`
+ }{
+ "configure", true, true,
+ }
+ jsepBody := struct {
+ Type string `json:"type"`
+ SDP string `json:"sdp"`
+ }{
+ "offer", offer,
+ }
+ reqBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ Body interface{} `json:"body"`
+ JSEP interface{} `json:"jsep"`
+ }{
+ "message", newTransactionID(), reqBodyBody, jsepBody,
+ }
+
+ reply := newJanusReply(reqBody.Transaction)
+ v.replies.Store(reqBody.Transaction, reply)
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return "", errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return "", errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return "", errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return "", errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ ackBody := struct {
+ Janus string `json:"janus"`
+ SessionID uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
+ return "", errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if ackBody.Janus != "ack" {
+ return "", errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
+ }
+ logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
+
+ // Reply from polling.
+ var s3 string
+ select {
+ case <-ctx.Done():
+ return "", ctx.Err()
+ case b3 := <-reply.replies:
+ s3 = escapeJSON(string(b3))
+ logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
+ }
+ resBody := struct {
+ Janus string `json:"janus"`
+ Session uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ Sender uint64 `json:"sender"`
+ PluginData struct {
+ Plugin string `json:"plugin"`
+ Data struct {
+ VideoRoom string `json:"videoroom"`
+ Room int `json:"room"`
+ Configured string `json:"configured"`
+ AudioCodec string `json:"audio_codec"`
+ VideoCodec string `json:"video_codec"`
+ } `json:"data"`
+ } `json:"plugindata"`
+ JSEP struct {
+ Type string `json:"type"`
+ SDP string `json:"sdp"`
+ } `json:"jsep"`
+ }{}
+ if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
+ return "", errors.Wrapf(err, "Marshal %v", s3)
+ }
+
+ plugin := resBody.PluginData.Data
+ jsep := resBody.JSEP
+ if resBody.Janus != "event" || plugin.VideoRoom != "event" {
+ return "", errors.Errorf("Server fail janus=%v, plugin=%v %v", resBody.Janus, plugin.VideoRoom, s3)
+ }
+ logger.Tf(ctx, "Configure publisher offer=%vB, tid=%v ok, event=%v, plugin=%v, answer=%vB",
+ len(offer), reply.transactionID, resBody.Janus, plugin.VideoRoom, len(jsep.SDP))
+
+ return jsep.SDP, nil
+}
+
+func (v *janusAPI) JoinAsSubscribe(ctx context.Context, handleID uint64, room int, publisher *publisherInfo) (offer string, err error) {
+ handler := v.loadHandler(handleID)
+ api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
+
+ reqBodyBody := struct {
+ Request string `json:"request"`
+ PType string `json:"ptype"`
+ Room int `json:"room"`
+ Feed uint64 `json:"feed"`
+ PrivateID uint64 `json:"private_id"`
+ }{
+ "join", "subscriber", room, publisher.ID, v.privateID,
+ }
+ reqBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ Body interface{} `json:"body"`
+ }{
+ "message", newTransactionID(), reqBodyBody,
+ }
+
+ reply := newJanusReply(reqBody.Transaction)
+ v.replies.Store(reqBody.Transaction, reply)
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return "", errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return "", errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return "", errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return "", errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ ackBody := struct {
+ Janus string `json:"janus"`
+ SessionID uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
+ return "", errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if ackBody.Janus != "ack" {
+ return "", errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
+ }
+ logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
+
+ // Reply from polling.
+ var s3 string
+ select {
+ case <-ctx.Done():
+ return "", ctx.Err()
+ case b3 := <-reply.replies:
+ s3 = escapeJSON(string(b3))
+ logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
+ }
+ resBody := struct {
+ Janus string `json:"janus"`
+ Session uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ Sender uint64 `json:"sender"`
+ PluginData struct {
+ Plugin string `json:"plugin"`
+ Data struct {
+ VideoRoom string `json:"videoroom"`
+ Room int `json:"room"`
+ ID uint64 `json:"id"`
+ Display string `json:"display"`
+ } `json:"data"`
+ } `json:"plugindata"`
+ JSEP struct {
+ Type string `json:"type"`
+ SDP string `json:"sdp"`
+ } `json:"jsep"`
+ }{}
+ if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
+ return "", errors.Wrapf(err, "Marshal %v", s3)
+ }
+
+ plugin := resBody.PluginData.Data
+ jsep := resBody.JSEP
+ if resBody.Janus != "event" || plugin.VideoRoom != "attached" {
+ return "", errors.Errorf("Server fail janus=%v, plugin=%v %v", resBody.Janus, plugin.VideoRoom, s3)
+ }
+ logger.Tf(ctx, "Join as subscriber room=%v, tid=%v ok, event=%v, plugin=%v, offer=%vB",
+ room, reply.transactionID, resBody.Janus, plugin.VideoRoom, len(jsep.SDP))
+
+ return jsep.SDP, nil
+}
+
+func (v *janusAPI) Subscribe(ctx context.Context, handleID uint64, room int, answer string) error {
+ handler := v.loadHandler(handleID)
+ api := fmt.Sprintf("%v%v/%v", v.r, v.sessionID, handler.handleID)
+
+ reqBodyBody := struct {
+ Request string `json:"request"`
+ Room int `json:"room"`
+ }{
+ "start", room,
+ }
+ jsepBody := struct {
+ Type string `json:"type"`
+ SDP string `json:"sdp"`
+ }{
+ "answer", answer,
+ }
+ reqBody := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ Body interface{} `json:"body"`
+ JSEP interface{} `json:"jsep"`
+ }{
+ "message", newTransactionID(), reqBodyBody, jsepBody,
+ }
+
+ reply := newJanusReply(reqBody.Transaction)
+ v.replies.Store(reqBody.Transaction, reply)
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.Tf(ctx, "Request url api=%v with %v", api, string(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return errors.Wrapf(err, "Read response for %v", string(b))
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Response from %v is %v", api, s2)
+
+ ackBody := struct {
+ Janus string `json:"janus"`
+ SessionID uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &ackBody); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s2)
+ }
+ if ackBody.Janus != "ack" {
+ return errors.Errorf("Server fail code=%v %v", ackBody.Janus, s2)
+ }
+ logger.Tf(ctx, "Response tid=%v ack", reply.transactionID)
+
+ // Reply from polling.
+ var s3 string
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case b3 := <-reply.replies:
+ s3 = escapeJSON(string(b3))
+ logger.Tf(ctx, "Async response tid=%v, reply=%v", reply.transactionID, s3)
+ }
+ resBody := struct {
+ Janus string `json:"janus"`
+ Session uint64 `json:"session_id"`
+ Transaction string `json:"transaction"`
+ Sender uint64 `json:"sender"`
+ PluginData struct {
+ Plugin string `json:"plugin"`
+ Data struct {
+ VideoRoom string `json:"videoroom"`
+ Room int `json:"room"`
+ Started string `json:"started"`
+ } `json:"data"`
+ } `json:"plugindata"`
+ }{}
+ if err := json.Unmarshal([]byte(s3), &resBody); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s3)
+ }
+
+ plugin := resBody.PluginData.Data
+ if resBody.Janus != "event" || plugin.VideoRoom != "event" || plugin.Started != "ok" {
+ return errors.Errorf("Server fail janus=%v, plugin=%v, started=%v %v", resBody.Janus, plugin.VideoRoom, plugin.Started, s3)
+ }
+ logger.Tf(ctx, "Start subscribe answer=%vB, tid=%v ok, event=%v, plugin=%v, started=%v",
+ len(answer), reply.transactionID, resBody.Janus, plugin.VideoRoom, plugin.Started)
+
+ return nil
+}
+
+func (v *janusAPI) polling(ctx context.Context) error {
+ api := fmt.Sprintf("%v%v?rid=%v&maxev=1", v.r, v.sessionID,
+ uint64(time.Duration(time.Now().UnixNano())/time.Millisecond))
+ logger.Tf(ctx, "Polling: Request url api=%v", api)
+
+ req, err := http.NewRequest("GET", api, nil)
+ if err != nil {
+ return errors.Wrapf(err, "HTTP request %v", api)
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return errors.Wrapf(err, "Do HTTP request %v", api)
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return errors.Wrapf(err, "Read response for %v", api)
+ }
+
+ s2 := escapeJSON(string(b2))
+ logger.Tf(ctx, "Polling: Response from %v is %v", api, s2)
+
+ if len(b2) == 0 {
+ return nil
+ }
+
+ replyID := struct {
+ Janus string `json:"janus"`
+ Transaction string `json:"transaction"`
+ }{}
+ if err := json.Unmarshal([]byte(s2), &replyID); err != nil {
+ return errors.Wrapf(err, "Marshal %v", s2)
+ }
+
+ switch replyID.Janus {
+ case "event":
+ if r, ok := v.replies.Load(replyID.Transaction); !ok {
+ if err := v.handleCall(replyID.Janus, s2); err != nil {
+ logger.Wf(ctx, "Polling: Handle call %v fail %v, err %+v", replyID.Janus, s2, err)
+ }
+ } else if r2, ok := r.(*janusReply); !ok {
+ logger.Wf(ctx, "Polling: Ignore tid=%v reply %v", replyID.Transaction, s2)
+ } else {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case r2.replies <- b2:
+ logger.Tf(ctx, "Polling: Reply tid=%v ok, %v", replyID.Transaction, s2)
+ }
+ }
+ case "keepalive":
+ return nil
+ case "webrtcup", "media", "slowlink", "detached":
+ if err := v.handleCall(replyID.Janus, s2); err != nil {
+ logger.Wf(ctx, "Polling: Handle call %v fail %v, err %+v", replyID.Janus, s2, err)
+ }
+ default:
+ logger.Wf(ctx, "Polling: Unknown janus=%v %v", replyID.Janus, s2)
+ }
+
+ return nil
+}
+
+func (v *janusAPI) handleCall(janus string, s string) error {
+ type callHeader struct {
+ Sender uint64 `json:"sender"`
+ SessionID uint64 `json:"session_id"`
+ }
+
+ switch janus {
+ case "detached":
+ /*{
+ "janus": "detached",
+ "sender": 4201795482244652,
+ "session_id": 373403124722380
+ }*/
+ r := callHeader{}
+ if err := json.Unmarshal([]byte(s), &r); err != nil {
+ return err
+ }
+
+ v.onDetached(r.Sender, r.SessionID)
+ case "webrtcup":
+ /*{
+ "janus": "webrtcup",
+ "sender": 7698695982180732,
+ "session_id": 2403223275773854
+ }*/
+ r := callHeader{}
+ if err := json.Unmarshal([]byte(s), &r); err != nil {
+ return err
+ }
+
+ v.onWebrtcUp(r.Sender, r.SessionID)
+ case "media":
+ /*{
+ "janus": "media",
+ "receiving": true,
+ "sender": 7698695982180732,
+ "session_id": 2403223275773854,
+ "type": "audio"
+ }*/
+ r := struct {
+ callHeader
+ Type string `json:"type"`
+ Receiving bool `json:"receiving"`
+ }{}
+ if err := json.Unmarshal([]byte(s), &r); err != nil {
+ return err
+ }
+
+ v.onMedia(r.Sender, r.SessionID, r.Type, r.Receiving)
+ case "slowlink":
+ /*{
+ "janus": "slowlink",
+ "lost": 4294902988,
+ "media": "video",
+ "sender": 562229074390269,
+ "session_id": 156116325213625,
+ "uplink": false
+ }*/
+ r := struct {
+ callHeader
+ Lost uint64 `json:"lost"`
+ Media string `json:"media"`
+ Uplink bool `json:"uplink"`
+ }{}
+ if err := json.Unmarshal([]byte(s), &r); err != nil {
+ return err
+ }
+
+ v.onSlowLink(r.Sender, r.SessionID, r.Media, r.Lost, r.Uplink)
+ case "event":
+ if strings.Contains(s, "publishers") {
+ /*{
+ "janus": "event",
+ "plugindata": {
+ "data": {
+ "publishers": [{
+ "audio_codec": "opus",
+ "display": "test",
+ "id": 2805536617160145,
+ "talking": false,
+ "video_codec": "h264"
+ }],
+ "room": 2345,
+ "videoroom": "event"
+ },
+ "plugin": "janus.plugin.videoroom"
+ },
+ "sender": 2156044968631669,
+ "session_id": 6696376606446844
+ }*/
+ r := struct {
+ callHeader
+ PluginData struct {
+ Data struct {
+ Publishers []publisherInfo `json:"publishers"`
+ Room int `json:"room"`
+ VideoRoom string `json:"videoroom"`
+ } `json:"data"`
+ Plugin string `json:"plugin"`
+ } `json:"plugindata"`
+ }{}
+ if err := json.Unmarshal([]byte(s), &r); err != nil {
+ return err
+ }
+
+ v.onPublisher(r.Sender, r.SessionID, r.PluginData.Data.Publishers)
+ } else if strings.Contains(s, "unpublished") {
+ /*{
+ "janus": "event",
+ "plugindata": {
+ "data": {
+ "room": 2345,
+ "unpublished": 2805536617160145,
+ "videoroom": "event"
+ },
+ "plugin": "janus.plugin.videoroom"
+ },
+ "sender": 2156044968631669,
+ "session_id": 6696376606446844
+ }*/
+ r := struct {
+ callHeader
+ PluginData struct {
+ Data struct {
+ Room int `json:"room"`
+ UnPublished uint64 `json:"unpublished"`
+ VideoRoom string `json:"videoroom"`
+ } `json:"data"`
+ Plugin string `json:"plugin"`
+ } `json:"plugindata"`
+ }{}
+ if err := json.Unmarshal([]byte(s), &r); err != nil {
+ return err
+ }
+
+ v.onUnPublished(r.Sender, r.SessionID, r.PluginData.Data.UnPublished)
+ } else if strings.Contains(s, "leaving") {
+ /*{
+ "janus": "event",
+ "plugindata": {
+ "data": {
+ "leaving": 2805536617160145,
+ "room": 2345,
+ "videoroom": "event"
+ },
+ "plugin": "janus.plugin.videoroom"
+ },
+ "sender": 2156044968631669,
+ "session_id": 6696376606446844
+ }*/
+ r := struct {
+ callHeader
+ PluginData struct {
+ Data struct {
+ Leaving uint64 `json:"leaving"`
+ Room int `json:"room"`
+ VideoRoom string `json:"videoroom"`
+ } `json:"data"`
+ Plugin string `json:"plugin"`
+ } `json:"plugindata"`
+ }{}
+ if err := json.Unmarshal([]byte(s), &r); err != nil {
+ return err
+ }
+
+ v.onLeave(r.Sender, r.SessionID, r.PluginData.Data.Leaving)
+ }
+ }
+
+ return nil
+}
+
+func (v *janusAPI) DiscoverPublisher(ctx context.Context, room int, display string, timeout time.Duration) (*publisherInfo, error) {
+ var publisher *publisherInfo
+ discoverCtx, discoverCancel := context.WithCancel(context.Background())
+
+ ov := v.onPublisher
+ defer func() {
+ v.onPublisher = ov
+ }()
+ v.onPublisher = func(sender, sessionID uint64, publishers []publisherInfo) {
+ for _, p := range publishers {
+ if p.Display == display {
+ publisher = &p
+ discoverCancel()
+ logger.Tf(ctx, "Publisher discovered %v", p)
+ return
+ }
+ }
+ }
+ go func() {
+ if err := func() error {
+ publishHandleID, err := v.AttachPlugin(ctx)
+ if err != nil {
+ return err
+ }
+ defer v.DetachPlugin(ctx, publishHandleID)
+
+ if err := v.JoinAsPublisher(ctx, publishHandleID, room, fmt.Sprintf("sub-%v", display)); err != nil {
+ return err
+ }
+
+ <-discoverCtx.Done()
+ return nil
+ }(); err != nil {
+ logger.Ef(ctx, "join err %+v", err)
+ }
+ }()
+
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-discoverCtx.Done():
+ case <-time.After(timeout):
+ discoverCancel()
+ }
+ if publisher == nil {
+ return nil, errors.Errorf("no publisher for room=%v, display=%v, session=%v",
+ room, display, v.sessionID)
+ }
+
+ return publisher, nil
+}
diff --git a/trunk/3rdparty/srs-bench/janus/ingester.go b/trunk/3rdparty/srs-bench/janus/ingester.go
new file mode 100644
index 000000000..78d058ed2
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/ingester.go
@@ -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()
+}
diff --git a/trunk/3rdparty/srs-bench/janus/interceptor.go b/trunk/3rdparty/srs-bench/janus/interceptor.go
new file mode 100644
index 000000000..56c151fbb
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/interceptor.go
@@ -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
+}
diff --git a/trunk/3rdparty/srs-bench/janus/janus.go b/trunk/3rdparty/srs-bench/janus/janus.go
new file mode 100644
index 000000000..ee98e573b
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/janus.go
@@ -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
+}
diff --git a/trunk/3rdparty/srs-bench/janus/player.go b/trunk/3rdparty/srs-bench/janus/player.go
new file mode 100644
index 000000000..e2844a462
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/player.go
@@ -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()
+}
diff --git a/trunk/3rdparty/srs-bench/janus/publisher.go b/trunk/3rdparty/srs-bench/janus/publisher.go
new file mode 100644
index 000000000..361619a86
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/publisher.go
@@ -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
+}
diff --git a/trunk/3rdparty/srs-bench/janus/util.go b/trunk/3rdparty/srs-bench/janus/util.go
new file mode 100644
index 000000000..ec30de187
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/util.go
@@ -0,0 +1,1176 @@
+// 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 (
+ "bytes"
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/ossrs/go-oryx-lib/errors"
+ "github.com/ossrs/go-oryx-lib/logger"
+ vnet_proxy "github.com/ossrs/srs-bench/vnet"
+ "github.com/pion/interceptor"
+ "github.com/pion/logging"
+ "github.com/pion/rtcp"
+ "github.com/pion/transport/vnet"
+ "github.com/pion/webrtc/v3"
+ "github.com/pion/webrtc/v3/pkg/media/h264reader"
+)
+
+var srsHttps *bool
+var srsLog *bool
+
+var srsTimeout *int
+var srsPlayPLI *int
+var srsPlayOKPackets *int
+var srsPublishOKPackets *int
+var srsPublishVideoFps *int
+var srsDTLSDropPackets *int
+
+var srsSchema string
+var srsServer *string
+var srsStream *string
+var srsPublishAudio *string
+var srsPublishVideo *string
+var srsVnetClientIP *string
+
+func prepareTest() error {
+ var err error
+
+ srsHttps = flag.Bool("srs-https", false, "Whther connect to HTTPS-API")
+ srsServer = flag.String("srs-server", "127.0.0.1", "The RTC server to connect to")
+ srsStream = flag.String("srs-stream", "/rtc/regression", "The RTC stream to play")
+ srsLog = flag.Bool("srs-log", false, "Whether enable the detail log")
+ srsTimeout = flag.Int("srs-timeout", 5000, "For each case, the timeout in ms")
+ srsPlayPLI = flag.Int("srs-play-pli", 5000, "The PLI interval in seconds for player.")
+ srsPlayOKPackets = flag.Int("srs-play-ok-packets", 10, "If recv N RTP packets, it's ok, or fail")
+ srsPublishOKPackets = flag.Int("srs-publish-ok-packets", 3, "If send N RTP, recv N RTCP packets, it's ok, or fail")
+ srsPublishAudio = flag.String("srs-publish-audio", "avatar.ogg", "The audio file for publisher.")
+ srsPublishVideo = flag.String("srs-publish-video", "avatar.h264", "The video file for publisher.")
+ srsPublishVideoFps = flag.Int("srs-publish-video-fps", 25, "The video fps for publisher.")
+ srsVnetClientIP = flag.String("srs-vnet-client-ip", "192.168.168.168", "The client ip in pion/vnet.")
+ srsDTLSDropPackets = flag.Int("srs-dtls-drop-packets", 5, "If dropped N packets, it's ok, or fail")
+
+ // Should parse it first.
+ flag.Parse()
+
+ // The stream should starts with /, for example, /rtc/regression
+ if !strings.HasPrefix(*srsStream, "/") {
+ *srsStream = "/" + *srsStream
+ }
+
+ // Generate srs protocol from whether use HTTPS.
+ srsSchema = "http"
+ if *srsHttps {
+ srsSchema = "https"
+ }
+
+ // Check file.
+ tryOpenFile := func(filename string) (string, error) {
+ if filename == "" {
+ return filename, nil
+ }
+
+ f, err := os.Open(filename)
+ if err != nil {
+ nfilename := path.Join("../", filename)
+ f2, err := os.Open(nfilename)
+ if err != nil {
+ return filename, errors.Wrapf(err, "No video file at %v or %v", filename, nfilename)
+ }
+ defer f2.Close()
+
+ return nfilename, nil
+ }
+ defer f.Close()
+
+ return filename, nil
+ }
+
+ if *srsPublishVideo, err = tryOpenFile(*srsPublishVideo); err != nil {
+ return err
+ }
+
+ if *srsPublishAudio, err = tryOpenFile(*srsPublishAudio); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func apiRtcRequest(ctx context.Context, apiPath, r, offer string) (string, error) {
+ u, err := url.Parse(r)
+ if err != nil {
+ return "", errors.Wrapf(err, "Parse url %v", r)
+ }
+
+ // Build api url.
+ host := u.Host
+ if !strings.Contains(host, ":") {
+ host += ":1985"
+ }
+
+ api := fmt.Sprintf("http://%v", host)
+ if !strings.HasPrefix(apiPath, "/") {
+ api += "/"
+ }
+ api += apiPath
+
+ if !strings.HasSuffix(apiPath, "/") {
+ api += "/"
+ }
+ if u.RawQuery != "" {
+ api += "?" + u.RawQuery
+ }
+
+ // Build JSON body.
+ reqBody := struct {
+ Api string `json:"api"`
+ ClientIP string `json:"clientip"`
+ SDP string `json:"sdp"`
+ StreamURL string `json:"streamurl"`
+ }{
+ api, "", offer, r,
+ }
+
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return "", errors.Wrapf(err, "Marshal body %v", reqBody)
+ }
+ logger.If(ctx, "Request url api=%v with %v", api, string(b))
+ logger.Tf(ctx, "Request url api=%v with %v bytes", api, len(b))
+
+ req, err := http.NewRequest("POST", api, strings.NewReader(string(b)))
+ if err != nil {
+ return "", errors.Wrapf(err, "HTTP request %v", string(b))
+ }
+
+ res, err := http.DefaultClient.Do(req.WithContext(ctx))
+ if err != nil {
+ return "", errors.Wrapf(err, "Do HTTP request %v", string(b))
+ }
+
+ b2, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return "", errors.Wrapf(err, "Read response for %v", string(b))
+ }
+ logger.If(ctx, "Response from %v is %v", api, string(b2))
+ logger.Tf(ctx, "Response from %v is %v bytes", api, len(b2))
+
+ resBody := struct {
+ Code int `json:"code"`
+ Session string `json:"sessionid"`
+ SDP string `json:"sdp"`
+ }{}
+ if err := json.Unmarshal(b2, &resBody); err != nil {
+ return "", errors.Wrapf(err, "Marshal %v", string(b2))
+ }
+
+ if resBody.Code != 0 {
+ return "", errors.Errorf("Server fail code=%v %v", resBody.Code, string(b2))
+ }
+ logger.If(ctx, "Parse response to code=%v, session=%v, sdp=%v",
+ resBody.Code, resBody.Session, escapeSDP(resBody.SDP))
+ logger.Tf(ctx, "Parse response to code=%v, session=%v, sdp=%v bytes",
+ resBody.Code, resBody.Session, len(resBody.SDP))
+
+ return resBody.SDP, nil
+}
+
+func escapeSDP(sdp string) string {
+ return strings.ReplaceAll(strings.ReplaceAll(sdp, "\r", "\\r"), "\n", "\\n")
+}
+
+func packageAsSTAPA(frames ...*h264reader.NAL) *h264reader.NAL {
+ first := frames[0]
+
+ buf := bytes.Buffer{}
+ buf.WriteByte(
+ first.RefIdc<<5&0x60 | byte(24), // STAP-A
+ )
+
+ for _, frame := range frames {
+ buf.WriteByte(byte(len(frame.Data) >> 8))
+ buf.WriteByte(byte(len(frame.Data)))
+ buf.Write(frame.Data)
+ }
+
+ return &h264reader.NAL{
+ PictureOrderCount: first.PictureOrderCount,
+ ForbiddenZeroBit: false,
+ RefIdc: first.RefIdc,
+ UnitType: h264reader.NalUnitType(24), // STAP-A
+ Data: buf.Bytes(),
+ }
+}
+
+type wallClock struct {
+ start time.Time
+ duration time.Duration
+}
+
+func newWallClock() *wallClock {
+ return &wallClock{start: time.Now()}
+}
+
+func (v *wallClock) Tick(d time.Duration) time.Duration {
+ v.duration += d
+
+ wc := time.Now().Sub(v.start)
+ re := v.duration - wc
+ if re > 30*time.Millisecond {
+ return re
+ }
+ return 0
+}
+
+// Set to active, as DTLS client, to start ClientHello.
+func testUtilSetupActive(s *webrtc.SessionDescription) error {
+ if strings.Contains(s.SDP, "setup:passive") {
+ return errors.New("set to active")
+ }
+
+ s.SDP = strings.ReplaceAll(s.SDP, "setup:actpass", "setup:active")
+ return nil
+}
+
+// Set to passive, as DTLS client, to start ClientHello.
+func testUtilSetupPassive(s *webrtc.SessionDescription) error {
+ if strings.Contains(s.SDP, "setup:active") {
+ return errors.New("set to passive")
+ }
+
+ s.SDP = strings.ReplaceAll(s.SDP, "setup:actpass", "setup:passive")
+ return nil
+}
+
+// Parse address from SDP.
+// candidate:0 1 udp 2130706431 192.168.3.8 8000 typ host generation 0
+func parseAddressOfCandidate(answerSDP string) (*net.UDPAddr, error) {
+ answer := webrtc.SessionDescription{Type: webrtc.SDPTypeAnswer, SDP: answerSDP}
+ answerObject, err := answer.Unmarshal()
+ if err != nil {
+ return nil, errors.Wrapf(err, "unmarshal answer %v", answerSDP)
+ }
+
+ if len(answerObject.MediaDescriptions) == 0 {
+ return nil, errors.New("no media")
+ }
+
+ candidate, ok := answerObject.MediaDescriptions[0].Attribute("candidate")
+ if !ok {
+ return nil, errors.New("no candidate")
+ }
+
+ // candidate:0 1 udp 2130706431 192.168.3.8 8000 typ host generation 0
+ attrs := strings.Split(candidate, " ")
+ if len(attrs) <= 6 {
+ return nil, errors.Errorf("no address in %v", candidate)
+ }
+
+ // Parse ip and port from answer.
+ ip := attrs[4]
+ port, err := strconv.Atoi(attrs[5])
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid port %v", candidate)
+ }
+
+ address := fmt.Sprintf("%v:%v", ip, port)
+ addr, err := net.ResolveUDPAddr("udp4", address)
+ if err != nil {
+ return nil, errors.Wrapf(err, "parse %v", address)
+ }
+
+ return addr, nil
+}
+
+// Filter the test error, ignore context.Canceled
+func filterTestError(errs ...error) error {
+ var filteredErrors []error
+
+ for _, err := range errs {
+ if err == nil || errors.Cause(err) == context.Canceled {
+ continue
+ }
+
+ // If url error, server maybe error, do not print the detail log.
+ if r0 := errors.Cause(err); r0 != nil {
+ if r1, ok := r0.(*url.Error); ok {
+ err = r1
+ }
+ }
+
+ filteredErrors = append(filteredErrors, err)
+ }
+
+ if len(filteredErrors) == 0 {
+ return nil
+ }
+ if len(filteredErrors) == 1 {
+ return filteredErrors[0]
+ }
+
+ var descs []string
+ for i, err := range filteredErrors[1:] {
+ descs = append(descs, fmt.Sprintf("err #%d, %+v", i, err))
+ }
+ return errors.Wrapf(filteredErrors[0], "with %v", strings.Join(descs, ","))
+}
+
+// For STUN packet, 0x00 is binding request, 0x01 is binding success response.
+// @see srs_is_stun of https://github.com/ossrs/srs
+func srsIsStun(b []byte) bool {
+ return len(b) > 0 && (b[0] == 0 || b[0] == 1)
+}
+
+// change_cipher_spec(20), alert(21), handshake(22), application_data(23)
+// @see https://tools.ietf.org/html/rfc2246#section-6.2.1
+// @see srs_is_dtls of https://github.com/ossrs/srs
+func srsIsDTLS(b []byte) bool {
+ return len(b) >= 13 && (b[0] > 19 && b[0] < 64)
+}
+
+// For RTP or RTCP, the V=2 which is in the high 2bits, 0xC0 (1100 0000)
+// @see srs_is_rtp_or_rtcp of https://github.com/ossrs/srs
+func srsIsRTPOrRTCP(b []byte) bool {
+ return len(b) >= 12 && (b[0]&0xC0) == 0x80
+}
+
+// For RTCP, PT is [128, 223] (or without marker [0, 95]).
+// Literally, RTCP starts from 64 not 0, so PT is [192, 223] (or without marker [64, 95]).
+// @note For RTP, the PT is [96, 127], or [224, 255] with marker.
+// @see srs_is_rtcp of https://github.com/ossrs/srs
+func srsIsRTCP(b []byte) bool {
+ return (len(b) >= 12) && (b[0]&0x80) != 0 && (b[1] >= 192 && b[1] <= 223)
+}
+
+type chunkType int
+
+const (
+ chunkTypeICE chunkType = iota + 1
+ chunkTypeDTLS
+ chunkTypeRTP
+ chunkTypeRTCP
+)
+
+func (v chunkType) String() string {
+ switch v {
+ case chunkTypeICE:
+ return "ICE"
+ case chunkTypeDTLS:
+ return "DTLS"
+ case chunkTypeRTP:
+ return "RTP"
+ case chunkTypeRTCP:
+ return "RTCP"
+ default:
+ return "Unknown"
+ }
+}
+
+type dtlsContentType int
+
+const (
+ dtlsContentTypeHandshake dtlsContentType = 22
+ dtlsContentTypeChangeCipherSpec dtlsContentType = 20
+ dtlsContentTypeAlert dtlsContentType = 21
+)
+
+func (v dtlsContentType) String() string {
+ switch v {
+ case dtlsContentTypeHandshake:
+ return "Handshake"
+ case dtlsContentTypeChangeCipherSpec:
+ return "ChangeCipherSpec"
+ default:
+ return "Unknown"
+ }
+}
+
+type dtlsHandshakeType int
+
+const (
+ dtlsHandshakeTypeClientHello dtlsHandshakeType = 1
+ dtlsHandshakeTypeServerHello dtlsHandshakeType = 2
+ dtlsHandshakeTypeCertificate dtlsHandshakeType = 11
+ dtlsHandshakeTypeServerKeyExchange dtlsHandshakeType = 12
+ dtlsHandshakeTypeCertificateRequest dtlsHandshakeType = 13
+ dtlsHandshakeTypeServerDone dtlsHandshakeType = 14
+ dtlsHandshakeTypeCertificateVerify dtlsHandshakeType = 15
+ dtlsHandshakeTypeClientKeyExchange dtlsHandshakeType = 16
+ dtlsHandshakeTypeFinished dtlsHandshakeType = 20
+)
+
+func (v dtlsHandshakeType) String() string {
+ switch v {
+ case dtlsHandshakeTypeClientHello:
+ return "ClientHello"
+ case dtlsHandshakeTypeServerHello:
+ return "ServerHello"
+ case dtlsHandshakeTypeCertificate:
+ return "Certificate"
+ case dtlsHandshakeTypeServerKeyExchange:
+ return "ServerKeyExchange"
+ case dtlsHandshakeTypeCertificateRequest:
+ return "CertificateRequest"
+ case dtlsHandshakeTypeServerDone:
+ return "ServerDone"
+ case dtlsHandshakeTypeCertificateVerify:
+ return "CertificateVerify"
+ case dtlsHandshakeTypeClientKeyExchange:
+ return "ClientKeyExchange"
+ case dtlsHandshakeTypeFinished:
+ return "Finished"
+ default:
+ return "Unknown"
+ }
+}
+
+type chunkMessageType struct {
+ chunk chunkType
+ content dtlsContentType
+ handshake dtlsHandshakeType
+}
+
+func (v *chunkMessageType) String() string {
+ if v.chunk == chunkTypeDTLS {
+ if v.content == dtlsContentTypeHandshake {
+ return fmt.Sprintf("%v-%v-%v", v.chunk, v.content, v.handshake)
+ } else {
+ return fmt.Sprintf("%v-%v", v.chunk, v.content)
+ }
+ }
+ return fmt.Sprintf("%v", v.chunk)
+}
+
+func newChunkMessageType(c vnet.Chunk) (*chunkMessageType, bool) {
+ b := c.UserData()
+
+ if len(b) == 0 {
+ return nil, false
+ }
+
+ v := &chunkMessageType{}
+
+ if srsIsRTPOrRTCP(b) {
+ if srsIsRTCP(b) {
+ v.chunk = chunkTypeRTCP
+ } else {
+ v.chunk = chunkTypeRTP
+ }
+ return v, true
+ }
+
+ if srsIsStun(b) {
+ v.chunk = chunkTypeICE
+ return v, true
+ }
+
+ if !srsIsDTLS(b) {
+ return nil, false
+ }
+
+ v.chunk, v.content = chunkTypeDTLS, dtlsContentType(b[0])
+ if v.content != dtlsContentTypeHandshake {
+ return v, true
+ }
+
+ if len(b) < 14 {
+ return v, false
+ }
+ v.handshake = dtlsHandshakeType(b[13])
+ return v, true
+}
+
+func (v *chunkMessageType) IsHandshake() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake
+}
+
+func (v *chunkMessageType) IsClientHello() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeClientHello
+}
+
+func (v *chunkMessageType) IsServerHello() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeServerHello
+}
+
+func (v *chunkMessageType) IsCertificate() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeCertificate
+}
+
+func (v *chunkMessageType) IsChangeCipherSpec() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeChangeCipherSpec
+}
+
+type dtlsRecord struct {
+ ContentType dtlsContentType
+ Version uint16
+ Epoch uint16
+ SequenceNumber uint64
+ Length uint16
+ Data []byte
+}
+
+func newDTLSRecord(b []byte) (*dtlsRecord, error) {
+ v := &dtlsRecord{}
+ return v, v.Unmarshal(b)
+}
+
+func (v *dtlsRecord) String() string {
+ return fmt.Sprintf("epoch=%v, sequence=%v", v.Epoch, v.SequenceNumber)
+}
+
+func (v *dtlsRecord) Equals(p *dtlsRecord) bool {
+ return v.Epoch == p.Epoch && v.SequenceNumber == p.SequenceNumber
+}
+
+func (v *dtlsRecord) Unmarshal(b []byte) error {
+ if len(b) < 13 {
+ return errors.Errorf("requires 13B only %v", len(b))
+ }
+
+ v.ContentType = dtlsContentType(b[0])
+ v.Version = uint16(b[1])<<8 | uint16(b[2])
+ v.Epoch = uint16(b[3])<<8 | uint16(b[4])
+ v.SequenceNumber = uint64(b[5])<<40 | uint64(b[6])<<32 | uint64(b[7])<<24 | uint64(b[8])<<16 | uint64(b[9])<<8 | uint64(b[10])
+ v.Length = uint16(b[11])<<8 | uint16(b[12])
+ v.Data = b[13:]
+ return nil
+}
+
+type testWebRTCAPIOptionFunc func(api *testWebRTCAPI)
+
+type testWebRTCAPI struct {
+ // The options to setup the api.
+ options []testWebRTCAPIOptionFunc
+ // The api and settings.
+ api *webrtc.API
+ mediaEngine *webrtc.MediaEngine
+ registry *interceptor.Registry
+ settingEngine *webrtc.SettingEngine
+ // The vnet router, can be shared by different apis, but we do not share it.
+ router *vnet.Router
+ // The network for api.
+ network *vnet.Net
+ // The vnet UDP proxy bind to the router.
+ proxy *vnet_proxy.UDPProxy
+}
+
+func newTestWebRTCAPI(options ...testWebRTCAPIOptionFunc) (*testWebRTCAPI, error) {
+ v := &testWebRTCAPI{}
+
+ v.mediaEngine = &webrtc.MediaEngine{}
+ if err := v.mediaEngine.RegisterDefaultCodecs(); err != nil {
+ return nil, err
+ }
+
+ v.registry = &interceptor.Registry{}
+ if err := webrtc.RegisterDefaultInterceptors(v.mediaEngine, v.registry); err != nil {
+ return nil, err
+ }
+
+ for _, setup := range options {
+ setup(v)
+ }
+
+ v.settingEngine = &webrtc.SettingEngine{}
+
+ return v, nil
+}
+
+func (v *testWebRTCAPI) Close() error {
+ if v.proxy != nil {
+ _ = v.proxy.Close()
+ }
+
+ if v.router != nil {
+ _ = v.router.Stop()
+ }
+
+ return nil
+}
+
+func (v *testWebRTCAPI) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
+ // Setting engine for https://github.com/pion/transport/tree/master/vnet
+ setupVnet := func(vnetClientIP string) (err error) {
+ // We create a private router for a api, however, it's possible to share the
+ // same router between apis.
+ if v.router, err = vnet.NewRouter(&vnet.RouterConfig{
+ CIDR: "0.0.0.0/0", // Accept all ip, no sub router.
+ LoggerFactory: logging.NewDefaultLoggerFactory(),
+ }); err != nil {
+ return errors.Wrapf(err, "create router for api")
+ }
+
+ // Each api should bind to a network, however, it's possible to share it
+ // for different apis.
+ v.network = vnet.NewNet(&vnet.NetConfig{
+ StaticIP: vnetClientIP,
+ })
+
+ if err = v.router.AddNet(v.network); err != nil {
+ return errors.Wrapf(err, "create network for api")
+ }
+
+ v.settingEngine.SetVNet(v.network)
+
+ // Create a proxy bind to the router.
+ if v.proxy, err = vnet_proxy.NewProxy(v.router); err != nil {
+ return errors.Wrapf(err, "create proxy for router")
+ }
+
+ return v.router.Start()
+ }
+ if err := setupVnet(vnetClientIP); err != nil {
+ return err
+ }
+
+ for _, setup := range options {
+ setup(v)
+ }
+
+ for _, setup := range v.options {
+ setup(v)
+ }
+
+ v.api = webrtc.NewAPI(
+ webrtc.WithMediaEngine(v.mediaEngine),
+ webrtc.WithInterceptorRegistry(v.registry),
+ webrtc.WithSettingEngine(*v.settingEngine),
+ )
+
+ return nil
+}
+
+func (v *testWebRTCAPI) NewPeerConnection(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
+ return v.api.NewPeerConnection(configuration)
+}
+
+type testPlayerOptionFunc func(p *testPlayer) error
+
+type testPlayer struct {
+ pc *webrtc.PeerConnection
+ receivers []*webrtc.RTPReceiver
+ // We should dispose it.
+ api *testWebRTCAPI
+ // Optional suffix for stream url.
+ streamSuffix string
+}
+
+func createApiForPlayer(play *testPlayer) error {
+ api, err := newTestWebRTCAPI()
+ if err != nil {
+ return err
+ }
+
+ play.api = api
+ return nil
+}
+
+func newTestPlayer(options ...testPlayerOptionFunc) (*testPlayer, error) {
+ v := &testPlayer{}
+
+ for _, opt := range options {
+ if err := opt(v); err != nil {
+ return nil, err
+ }
+ }
+
+ return v, nil
+}
+
+func (v *testPlayer) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
+ return v.api.Setup(vnetClientIP, options...)
+}
+
+func (v *testPlayer) Close() error {
+ if v.pc != nil {
+ _ = v.pc.Close()
+ }
+
+ for _, receiver := range v.receivers {
+ _ = receiver.Stop()
+ }
+
+ if v.api != nil {
+ _ = v.api.Close()
+ }
+
+ return nil
+}
+
+func (v *testPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
+ r := fmt.Sprintf("%v://%v%v", srsSchema, *srsServer, *srsStream)
+ if v.streamSuffix != "" {
+ r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
+ }
+ pli := time.Duration(*srsPlayPLI) * time.Millisecond
+ logger.Tf(ctx, "Run play url=%v", r)
+
+ pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
+ if err != nil {
+ return errors.Wrapf(err, "Create PC")
+ }
+ v.pc = pc
+
+ if _, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RTPTransceiverInit{
+ Direction: webrtc.RTPTransceiverDirectionRecvonly,
+ }); err != nil {
+ return errors.Wrapf(err, "add track")
+ }
+ if _, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{
+ Direction: webrtc.RTPTransceiverDirectionRecvonly,
+ }); err != nil {
+ return errors.Wrapf(err, "add track")
+ }
+
+ offer, err := pc.CreateOffer(nil)
+ if err != nil {
+ return errors.Wrapf(err, "Create Offer")
+ }
+
+ if err := pc.SetLocalDescription(offer); err != nil {
+ return errors.Wrapf(err, "Set offer %v", offer)
+ }
+
+ answer, err := apiRtcRequest(ctx, "/rtc/v1/play", r, offer.SDP)
+ if err != nil {
+ return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
+ }
+
+ // Run a proxy for real server and vnet.
+ if address, err := parseAddressOfCandidate(answer); err != nil {
+ return errors.Wrapf(err, "parse address of %v", answer)
+ } else if err := v.api.proxy.Proxy(v.api.network, address); err != nil {
+ return errors.Wrapf(err, "proxy %v to %v", v.api.network, address)
+ }
+
+ if err := pc.SetRemoteDescription(webrtc.SessionDescription{
+ Type: webrtc.SDPTypeAnswer, SDP: answer,
+ }); err != nil {
+ return errors.Wrapf(err, "Set 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
+ }
+
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(pli):
+ _ = pc.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{
+ MediaSSRC: uint32(track.SSRC()),
+ }})
+ }
+ }
+ }()
+
+ v.receivers = append(v.receivers, receiver)
+
+ for ctx.Err() == nil {
+ _, _, err := track.ReadRTP()
+ if err != nil {
+ return errors.Wrapf(err, "Read RTP")
+ }
+ }
+
+ return nil
+ }
+
+ pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
+ err = handleTrack(ctx, track, receiver)
+ if err != nil {
+ codec := track.Codec()
+ err = errors.Wrapf(err, "Handle track %v, pt=%v", codec.MimeType, codec.PayloadType)
+ cancel()
+ }
+ })
+
+ pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
+ if state == webrtc.ICEConnectionStateFailed || state == webrtc.ICEConnectionStateClosed {
+ err = errors.Errorf("Close for ICE state %v", state)
+ cancel()
+ }
+ })
+
+ <-ctx.Done()
+ return err
+}
+
+type testPublisherOptionFunc func(p *testPublisher) error
+
+type testPublisher struct {
+ onOffer func(s *webrtc.SessionDescription) error
+ onAnswer func(s *webrtc.SessionDescription) error
+ iceReadyCancel context.CancelFunc
+ // internal objects
+ aIngester *audioIngester
+ vIngester *videoIngester
+ pc *webrtc.PeerConnection
+ // We should dispose it.
+ api *testWebRTCAPI
+ // Optional suffix for stream url.
+ streamSuffix string
+ // To cancel the publisher, pass by Run.
+ cancel context.CancelFunc
+}
+
+func createApiForPublisher(pub *testPublisher) error {
+ api, err := newTestWebRTCAPI()
+ if err != nil {
+ return err
+ }
+
+ pub.api = api
+ return nil
+}
+
+func newTestPublisher(options ...testPublisherOptionFunc) (*testPublisher, error) {
+ sourceVideo, sourceAudio := *srsPublishVideo, *srsPublishAudio
+
+ v := &testPublisher{}
+
+ for _, opt := range options {
+ if err := opt(v); err != nil {
+ return nil, err
+ }
+ }
+
+ // Create ingesters.
+ if sourceAudio != "" {
+ v.aIngester = newAudioIngester(sourceAudio)
+ }
+ if sourceVideo != "" {
+ v.vIngester = newVideoIngester(sourceVideo)
+ }
+
+ // Setup the interceptors for packets.
+ api := v.api
+ api.options = append(api.options, func(api *testWebRTCAPI) {
+ // Filter for RTCP packets.
+ rtcpInterceptor := &rtcpInterceptor{}
+ rtcpInterceptor.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
+ return rtcpInterceptor.nextRTCPReader.Read(buf, attributes)
+ }
+ rtcpInterceptor.rtcpWriter = func(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
+ return rtcpInterceptor.nextRTCPWriter.Write(pkts, attributes)
+ }
+ api.registry.Add(rtcpInterceptor)
+
+ // Filter for ingesters.
+ if sourceAudio != "" {
+ api.registry.Add(v.aIngester.audioLevelInterceptor)
+ }
+ if sourceVideo != "" {
+ api.registry.Add(v.vIngester.markerInterceptor)
+ }
+ })
+
+ return v, nil
+}
+
+func (v *testPublisher) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
+ return v.api.Setup(vnetClientIP, options...)
+}
+
+func (v *testPublisher) Close() error {
+ if v.vIngester != nil {
+ _ = v.vIngester.Close()
+ }
+
+ if v.aIngester != nil {
+ _ = v.aIngester.Close()
+ }
+
+ if v.pc != nil {
+ _ = v.pc.Close()
+ }
+
+ if v.api != nil {
+ _ = v.api.Close()
+ }
+
+ return nil
+}
+
+func (v *testPublisher) SetStreamSuffix(suffix string) *testPublisher {
+ v.streamSuffix = suffix
+ return v
+}
+
+func (v *testPublisher) Run(ctx context.Context, cancel context.CancelFunc) error {
+ // Save the cancel.
+ v.cancel = cancel
+
+ r := fmt.Sprintf("%v://%v%v", srsSchema, *srsServer, *srsStream)
+ if v.streamSuffix != "" {
+ r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
+ }
+ sourceVideo, sourceAudio, fps := *srsPublishVideo, *srsPublishAudio, *srsPublishVideoFps
+
+ logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v",
+ r, sourceAudio, sourceVideo, fps)
+
+ pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
+ if err != nil {
+ return errors.Wrapf(err, "Create PC")
+ }
+ v.pc = pc
+
+ if v.vIngester != nil {
+ if err := v.vIngester.AddTrack(pc, fps); err != nil {
+ return errors.Wrapf(err, "Add track")
+ }
+ }
+
+ if v.aIngester != nil {
+ if err := v.aIngester.AddTrack(pc); err != nil {
+ return errors.Wrapf(err, "Add track")
+ }
+ }
+
+ offer, err := pc.CreateOffer(nil)
+ if err != nil {
+ return errors.Wrapf(err, "Create Offer")
+ }
+
+ if err := pc.SetLocalDescription(offer); err != nil {
+ return errors.Wrapf(err, "Set offer %v", offer)
+ }
+
+ if v.onOffer != nil {
+ if err := v.onOffer(&offer); err != nil {
+ return errors.Wrapf(err, "sdp %v %v", offer.Type, offer.SDP)
+ }
+ }
+
+ answerSDP, err := apiRtcRequest(ctx, "/rtc/v1/publish", r, offer.SDP)
+ if err != nil {
+ return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
+ }
+
+ // Run a proxy for real server and vnet.
+ if address, err := parseAddressOfCandidate(answerSDP); err != nil {
+ return errors.Wrapf(err, "parse address of %v", answerSDP)
+ } else if err := v.api.proxy.Proxy(v.api.network, address); err != nil {
+ return errors.Wrapf(err, "proxy %v to %v", v.api.network, address)
+ }
+
+ answer := &webrtc.SessionDescription{
+ Type: webrtc.SDPTypeAnswer, SDP: answerSDP,
+ }
+ if v.onAnswer != nil {
+ if err := v.onAnswer(answer); err != nil {
+ return errors.Wrapf(err, "on answerSDP")
+ }
+ }
+
+ if err := pc.SetRemoteDescription(*answer); err != nil {
+ return errors.Wrapf(err, "Set answerSDP %v", answerSDP)
+ }
+
+ logger.Tf(ctx, "State signaling=%v, ice=%v, conn=%v", pc.SignalingState(), pc.ICEConnectionState(), pc.ConnectionState())
+
+ // ICE state management.
+ pc.OnICEGatheringStateChange(func(state webrtc.ICEGathererState) {
+ logger.Tf(ctx, "ICE gather state %v", state)
+ })
+ pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
+ logger.Tf(ctx, "ICE candidate %v %v:%v", candidate.Protocol, candidate.Address, candidate.Port)
+
+ })
+ pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
+ logger.Tf(ctx, "ICE state %v", state)
+ })
+
+ pc.OnSignalingStateChange(func(state webrtc.SignalingState) {
+ logger.Tf(ctx, "Signaling state %v", state)
+ })
+
+ if v.aIngester != nil {
+ v.aIngester.sAudioSender.Transport().OnStateChange(func(state webrtc.DTLSTransportState) {
+ logger.Tf(ctx, "DTLS state %v", state)
+ })
+ }
+
+ pcDone, pcDoneCancel := context.WithCancel(context.Background())
+ pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
+ logger.Tf(ctx, "PC state %v", state)
+
+ if state == webrtc.PeerConnectionStateConnected {
+ pcDoneCancel()
+ if v.iceReadyCancel != nil {
+ v.iceReadyCancel()
+ }
+ }
+
+ if state == webrtc.PeerConnectionStateFailed || state == webrtc.PeerConnectionStateClosed {
+ err = errors.Errorf("Close for PC state %v", state)
+ cancel()
+ }
+ })
+
+ // Wait for event from context or tracks.
+ var wg sync.WaitGroup
+ var finalErr error
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer logger.Tf(ctx, "ingest notify done")
+
+ <-ctx.Done()
+
+ if v.aIngester != nil && v.aIngester.sAudioSender != nil {
+ // We MUST wait for the ingester ready(or closed), because it might crash if sender is disposed.
+ <-v.aIngester.ready.Done()
+
+ _ = v.aIngester.Close()
+ }
+
+ if v.vIngester != nil && v.vIngester.sVideoSender != nil {
+ // We MUST wait for the ingester ready(or closed), because it might crash if sender is disposed.
+ <-v.vIngester.ready.Done()
+
+ _ = v.vIngester.Close()
+ }
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer cancel()
+
+ if v.aIngester == nil {
+ return
+ }
+ defer v.aIngester.readyCancel()
+
+ select {
+ case <-ctx.Done():
+ return
+ case <-pcDone.Done():
+ }
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer logger.Tf(ctx, "aingester sender read done")
+
+ buf := make([]byte, 1500)
+ for ctx.Err() == nil {
+ if _, _, err := v.aIngester.sAudioSender.Read(buf); err != nil {
+ return
+ }
+ }
+ }()
+
+ for {
+ if err := v.aIngester.Ingest(ctx); err != nil {
+ if err == io.EOF {
+ logger.Tf(ctx, "aingester retry for %v", err)
+ continue
+ }
+ if err != context.Canceled {
+ finalErr = errors.Wrapf(err, "audio")
+ }
+
+ logger.Tf(ctx, "aingester err=%v, final=%v", err, finalErr)
+ return
+ }
+ }
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer cancel()
+
+ if v.vIngester == nil {
+ return
+ }
+ defer v.vIngester.readyCancel()
+
+ select {
+ case <-ctx.Done():
+ return
+ case <-pcDone.Done():
+ logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest video %v", sourceVideo)
+ }
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer logger.Tf(ctx, "vingester sender read done")
+
+ buf := make([]byte, 1500)
+ for ctx.Err() == nil {
+ // The Read() might block in r.rtcpInterceptor.Read(b, a),
+ // so that the Stop() can not stop it.
+ if _, _, err := v.vIngester.sVideoSender.Read(buf); err != nil {
+ return
+ }
+ }
+ }()
+
+ for {
+ if err := v.vIngester.Ingest(ctx); err != nil {
+ if err == io.EOF {
+ logger.Tf(ctx, "vingester retry for %v", err)
+ continue
+ }
+ if err != context.Canceled {
+ finalErr = errors.Wrapf(err, "video")
+ }
+
+ logger.Tf(ctx, "vingester err=%v, final=%v", err, finalErr)
+ return
+ }
+ }
+ }()
+
+ wg.Wait()
+
+ logger.Tf(ctx, "ingester done ctx=%v, final=%v", ctx.Err(), finalErr)
+ if finalErr != nil {
+ return finalErr
+ }
+ return ctx.Err()
+}
diff --git a/trunk/3rdparty/srs-bench/janus/util2.go b/trunk/3rdparty/srs-bench/janus/util2.go
new file mode 100644
index 000000000..1b1d236a9
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/janus/util2.go
@@ -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)
+ }
+}
diff --git a/trunk/3rdparty/srs-bench/main.go b/trunk/3rdparty/srs-bench/main.go
index 8b83c4dcb..8d9978c57 100644
--- a/trunk/3rdparty/srs-bench/main.go
+++ b/trunk/3rdparty/srs-bench/main.go
@@ -24,133 +24,35 @@ import (
"context"
"flag"
"fmt"
- "github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
+ "github.com/ossrs/srs-bench/janus"
"github.com/ossrs/srs-bench/srs"
- "net"
- "net/http"
+ "io/ioutil"
"os"
"os/signal"
- "strings"
- "sync"
"syscall"
- "time"
)
func main() {
- var sr, dumpAudio, dumpVideo string
- var pli int
- flag.StringVar(&sr, "sr", "", "")
- flag.StringVar(&dumpAudio, "da", "", "")
- flag.StringVar(&dumpVideo, "dv", "", "")
- flag.IntVar(&pli, "pli", 10, "")
+ var sfu string
+ fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
+ fl.SetOutput(ioutil.Discard)
+ fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or janus")
+ _ = fl.Parse(os.Args[1:])
- var pr, sourceAudio, sourceVideo string
- var fps int
- flag.StringVar(&pr, "pr", "", "")
- flag.StringVar(&sourceAudio, "sa", "", "")
- flag.StringVar(&sourceVideo, "sv", "", "")
- flag.IntVar(&fps, "fps", 0, "")
-
- var audioLevel, videoTWCC bool
- flag.BoolVar(&audioLevel, "al", true, "")
- flag.BoolVar(&videoTWCC, "twcc", true, "")
-
- var clients, streams, delay int
- flag.IntVar(&clients, "nn", 1, "")
- flag.IntVar(&streams, "sn", 1, "")
- flag.IntVar(&delay, "delay", 50, "")
-
- var statListen string
- flag.StringVar(&statListen, "stat", "", "")
-
- flag.Usage = func() {
+ ctx := context.Background()
+ if sfu == "srs" {
+ srs.Parse(ctx)
+ } else if sfu == "janus" {
+ janus.Parse(ctx)
+ } else {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
- fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1"))
- fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1"))
- fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50"))
- fmt.Println(fmt.Sprintf(" -al [Optional] Whether enable audio-level. Default: true"))
- fmt.Println(fmt.Sprintf(" -twcc [Optional] Whether enable vdieo-twcc. Default: true"))
- fmt.Println(fmt.Sprintf(" -stat [Optional] The stat server API listen port."))
- fmt.Println(fmt.Sprintf("Player or Subscriber:"))
- fmt.Println(fmt.Sprintf(" -sr The url to play/subscribe. If sn exceed 1, auto append variable %%d."))
- fmt.Println(fmt.Sprintf(" -da [Optional] The file path to dump audio, ignore if empty."))
- fmt.Println(fmt.Sprintf(" -dv [Optional] The file path to dump video, ignore if empty."))
- fmt.Println(fmt.Sprintf(" -pli [Optional] PLI request interval in seconds. Default: 10"))
- fmt.Println(fmt.Sprintf("Publisher:"))
- fmt.Println(fmt.Sprintf(" -pr The url to publish. If sn exceed 1, auto append variable %%d."))
- fmt.Println(fmt.Sprintf(" -fps The fps of .h264 source file."))
- fmt.Println(fmt.Sprintf(" -sa [Optional] The file path to read audio, ignore if empty."))
- fmt.Println(fmt.Sprintf(" -sv [Optional] The file path to read video, ignore if empty."))
- fmt.Println(fmt.Sprintf("\n例如,1个播放,1个推流:"))
- fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream", os.Args[0]))
- fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
- fmt.Println(fmt.Sprintf("\n例如,1个流,3个播放,共3个客户端:"))
- fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -nn 3", os.Args[0]))
- fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
- fmt.Println(fmt.Sprintf("\n例如,2个流,每个流3个播放,共6个客户端:"))
- fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream_%%d -sn 2 -nn 3", os.Args[0]))
- fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
- fmt.Println(fmt.Sprintf("\n例如,2个推流:"))
- fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa a.ogg -sv v.h264 -fps 25", os.Args[0]))
- fmt.Println(fmt.Sprintf("\n例如,1个录制:"))
- fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -da a.ogg -dv v.h264", os.Args[0]))
- fmt.Println(fmt.Sprintf("\n例如,1个明文播放:"))
- fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream?encrypt=false", os.Args[0]))
- fmt.Println()
- }
- flag.Parse()
-
- showHelp := (clients <= 0 || streams <= 0)
- if sr == "" && pr == "" {
- showHelp = true
- }
- if pr != "" && (sourceAudio == "" && sourceVideo == "") {
- showHelp = true
- }
- if showHelp {
- flag.Usage()
- os.Exit(-1)
- }
-
- if statListen != "" && !strings.Contains(statListen, ":") {
- statListen = ":" + statListen
- }
-
- 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)
+ fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or janus. Default: srs"))
os.Exit(-1)
}
ctx, cancel := context.WithCancel(ctx)
-
- // Process all signals.
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
@@ -160,124 +62,17 @@ func main() {
}
}()
- // Run tasks.
- var wg sync.WaitGroup
-
- // Start STAT API server.
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- if statListen == "" {
- return
- }
-
- var lc net.ListenConfig
- ln, err := lc.Listen(ctx, "tcp", statListen)
- if err != nil {
- logger.Ef(ctx, "stat listen err+%v", err)
- cancel()
- return
- }
-
- mux := http.NewServeMux()
- srs.HandleStat(ctx, mux, statListen)
-
- srv := &http.Server{
- Handler: mux,
- BaseContext: func(listener net.Listener) context.Context {
- return ctx
- },
- }
-
- go func() {
- <-ctx.Done()
- srv.Shutdown(ctx)
- }()
-
- logger.Tf(ctx, "Stat listen at %v", statListen)
- if err := srv.Serve(ln); err != nil {
- if ctx.Err() == nil {
- logger.Ef(ctx, "stat serve err+%v", err)
- cancel()
- }
- return
- }
- }()
-
- // Start all subscribers or players.
- for i := 0; sr != "" && i < streams && ctx.Err() == nil; i++ {
- r_auto := sr
- if streams > 1 && !strings.Contains(r_auto, "%") {
- r_auto += "%d"
- }
-
- r2 := r_auto
- if strings.Contains(r2, "%") {
- r2 = fmt.Sprintf(r2, i)
- }
-
- for j := 0; sr != "" && j < clients && ctx.Err() == nil; j++ {
- // Dump audio or video only for the first client.
- da, dv := dumpAudio, dumpVideo
- if i > 0 {
- da, dv = "", ""
- }
-
- srs.StatRTC.Subscribers.Expect++
- srs.StatRTC.Subscribers.Alive++
-
- wg.Add(1)
- go func(sr, da, dv string) {
- defer wg.Done()
- defer func() {
- srs.StatRTC.Subscribers.Alive--
- }()
-
- if err := srs.StartPlay(ctx, sr, da, dv, audioLevel, videoTWCC, pli); err != nil {
- if errors.Cause(err) != context.Canceled {
- logger.Wf(ctx, "Run err %+v", err)
- }
- }
- }(r2, da, dv)
-
- time.Sleep(time.Duration(delay) * time.Millisecond)
- }
+ var err error
+ if sfu == "srs" {
+ err = srs.Run(ctx)
+ } else if sfu == "janus" {
+ err = janus.Run(ctx)
}
- // Start all publishers.
- for i := 0; pr != "" && i < streams && ctx.Err() == nil; i++ {
- r_auto := pr
- if streams > 1 && !strings.Contains(r_auto, "%") {
- r_auto += "%d"
- }
-
- r2 := r_auto
- if strings.Contains(r2, "%") {
- r2 = fmt.Sprintf(r2, i)
- }
-
- srs.StatRTC.Publishers.Expect++
- srs.StatRTC.Publishers.Alive++
-
- wg.Add(1)
- go func(pr string) {
- defer wg.Done()
- defer func() {
- srs.StatRTC.Publishers.Alive--
- }()
-
- if err := srs.StartPublish(ctx, pr, sourceAudio, sourceVideo, fps, audioLevel, videoTWCC); err != nil {
- if errors.Cause(err) != context.Canceled {
- logger.Wf(ctx, "Run err %+v", err)
- }
- }
- }(r2)
-
- time.Sleep(time.Duration(delay) * time.Millisecond)
+ if err != nil {
+ logger.Wf(ctx, "srs err %+v", err)
+ return
}
- wg.Wait()
-
logger.Tf(ctx, "Done")
}
diff --git a/trunk/3rdparty/srs-bench/srs/ingester.go b/trunk/3rdparty/srs-bench/srs/ingester.go
index bcedebbb6..fe26552f1 100644
--- a/trunk/3rdparty/srs-bench/srs/ingester.go
+++ b/trunk/3rdparty/srs-bench/srs/ingester.go
@@ -41,15 +41,15 @@ import (
type videoIngester struct {
sourceVideo string
fps int
- markerInterceptor *RTPInterceptor
+ markerInterceptor *rtpInterceptor
sVideoTrack *webrtc.TrackLocalStaticSample
sVideoSender *webrtc.RTPSender
ready context.Context
readyCancel context.CancelFunc
}
-func NewVideoIngester(sourceVideo string) *videoIngester {
- v := &videoIngester{markerInterceptor: &RTPInterceptor{}, sourceVideo: sourceVideo}
+func newVideoIngester(sourceVideo string) *videoIngester {
+ v := &videoIngester{markerInterceptor: &rtpInterceptor{}, sourceVideo: sourceVideo}
v.ready, v.readyCancel = context.WithCancel(context.Background())
return v
}
@@ -183,15 +183,15 @@ func (v *videoIngester) Ingest(ctx context.Context) error {
type audioIngester struct {
sourceAudio string
- audioLevelInterceptor *RTPInterceptor
+ audioLevelInterceptor *rtpInterceptor
sAudioTrack *webrtc.TrackLocalStaticSample
sAudioSender *webrtc.RTPSender
ready context.Context
readyCancel context.CancelFunc
}
-func NewAudioIngester(sourceAudio string) *audioIngester {
- v := &audioIngester{audioLevelInterceptor: &RTPInterceptor{}, sourceAudio: sourceAudio}
+func newAudioIngester(sourceAudio string) *audioIngester {
+ v := &audioIngester{audioLevelInterceptor: &rtpInterceptor{}, sourceAudio: sourceAudio}
v.ready, v.readyCancel = context.WithCancel(context.Background())
return v
}
diff --git a/trunk/3rdparty/srs-bench/srs/interceptor.go b/trunk/3rdparty/srs-bench/srs/interceptor.go
index 9757b705c..e86354b08 100644
--- a/trunk/3rdparty/srs-bench/srs/interceptor.go
+++ b/trunk/3rdparty/srs-bench/srs/interceptor.go
@@ -26,11 +26,11 @@ import (
"github.com/pion/rtp"
)
-type RTPInterceptorOptionFunc func(i *RTPInterceptor)
+type rtpInterceptorOptionFunc func(i *rtpInterceptor)
// Common RTP packet interceptor for benchmark.
-// @remark Should never merge with RTCPInterceptor, because they has the same Write interface.
-type RTPInterceptor struct {
+// @remark Should never merge with rtcpInterceptor, because they has the same Write interface.
+type rtpInterceptor struct {
// If rtpReader is nil, use the default next one to read.
rtpReader interceptor.RTPReaderFunc
nextRTPReader interceptor.RTPReader
@@ -38,52 +38,52 @@ type RTPInterceptor struct {
rtpWriter interceptor.RTPWriterFunc
nextRTPWriter interceptor.RTPWriter
// Other common fields.
- BypassInterceptor
+ bypassInterceptor
}
-func NewRTPInterceptor(options ...RTPInterceptorOptionFunc) *RTPInterceptor {
- v := &RTPInterceptor{}
+func newRTPInterceptor(options ...rtpInterceptorOptionFunc) *rtpInterceptor {
+ v := &rtpInterceptor{}
for _, opt := range options {
opt(v)
}
return v
}
-func (v *RTPInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
+func (v *rtpInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
v.nextRTPWriter = writer
return v // Handle all RTP
}
-func (v *RTPInterceptor) Write(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
+func (v *rtpInterceptor) Write(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
if v.rtpWriter != nil {
return v.rtpWriter(header, payload, attributes)
}
return v.nextRTPWriter.Write(header, payload, attributes)
}
-func (v *RTPInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
+func (v *rtpInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
}
-func (v *RTPInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
+func (v *rtpInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
v.nextRTPReader = reader
return v // Handle all RTP
}
-func (v *RTPInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
+func (v *rtpInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
if v.rtpReader != nil {
return v.rtpReader(b, a)
}
return v.nextRTPReader.Read(b, a)
}
-func (v *RTPInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
+func (v *rtpInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
}
-type RTCPInterceptorOptionFunc func(i *RTCPInterceptor)
+type rtcpInterceptorOptionFunc func(i *rtcpInterceptor)
// Common RTCP packet interceptor for benchmark.
-// @remark Should never merge with RTPInterceptor, because they has the same Write interface.
-type RTCPInterceptor struct {
+// @remark Should never merge with rtpInterceptor, because they has the same Write interface.
+type rtcpInterceptor struct {
// If rtcpReader is nil, use the default next one to read.
rtcpReader interceptor.RTCPReaderFunc
nextRTCPReader interceptor.RTCPReader
@@ -91,35 +91,35 @@ type RTCPInterceptor struct {
rtcpWriter interceptor.RTCPWriterFunc
nextRTCPWriter interceptor.RTCPWriter
// Other common fields.
- BypassInterceptor
+ bypassInterceptor
}
-func NewRTCPInterceptor(options ...RTCPInterceptorOptionFunc) *RTCPInterceptor {
- v := &RTCPInterceptor{}
+func newRTCPInterceptor(options ...rtcpInterceptorOptionFunc) *rtcpInterceptor {
+ v := &rtcpInterceptor{}
for _, opt := range options {
opt(v)
}
return v
}
-func (v *RTCPInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
+func (v *rtcpInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
v.nextRTCPReader = reader
return v // Handle all RTCP
}
-func (v *RTCPInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
+func (v *rtcpInterceptor) Read(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
if v.rtcpReader != nil {
return v.rtcpReader(b, a)
}
return v.nextRTCPReader.Read(b, a)
}
-func (v *RTCPInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
+func (v *rtcpInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
v.nextRTCPWriter = writer
return v // Handle all RTCP
}
-func (v *RTCPInterceptor) Write(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
+func (v *rtcpInterceptor) Write(pkts []rtcp.Packet, attributes interceptor.Attributes) (int, error) {
if v.rtcpWriter != nil {
return v.rtcpWriter(pkts, attributes)
}
@@ -127,32 +127,32 @@ func (v *RTCPInterceptor) Write(pkts []rtcp.Packet, attributes interceptor.Attri
}
// Do nothing.
-type BypassInterceptor struct {
+type bypassInterceptor struct {
interceptor.Interceptor
}
-func (v *BypassInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
+func (v *bypassInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
return reader
}
-func (v *BypassInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
+func (v *bypassInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
return writer
}
-func (v *BypassInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
+func (v *bypassInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
return writer
}
-func (v *BypassInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
+func (v *bypassInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
}
-func (v *BypassInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
+func (v *bypassInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
return reader
}
-func (v *BypassInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
+func (v *bypassInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
}
-func (v *BypassInterceptor) Close() error {
+func (v *bypassInterceptor) Close() error {
return nil
}
diff --git a/trunk/3rdparty/srs-bench/srs/player.go b/trunk/3rdparty/srs-bench/srs/player.go
index 8dc91d030..fafcd1a6b 100644
--- a/trunk/3rdparty/srs-bench/srs/player.go
+++ b/trunk/3rdparty/srs-bench/srs/player.go
@@ -40,10 +40,10 @@ import (
)
// @see https://github.com/pion/webrtc/blob/master/examples/save-to-disk/main.go
-func StartPlay(ctx context.Context, r, dumpAudio, dumpVideo string, enableAudioLevel, enableTWCC bool, pli int) error {
+func startPlay(ctx context.Context, r, dumpAudio, dumpVideo string, enableAudioLevel, enableTWCC bool, pli int) error {
ctx = logger.WithContext(ctx)
- logger.Tf(ctx, "Start play url=%v, audio=%v, video=%v, audio-level=%v, twcc=%v",
+ logger.Tf(ctx, "Run play url=%v, audio=%v, video=%v, audio-level=%v, twcc=%v",
r, dumpAudio, dumpVideo, enableAudioLevel, enableTWCC)
// For audio-level.
@@ -257,7 +257,7 @@ func StartPlay(ctx context.Context, r, dumpAudio, dumpVideo string, enableAudioL
case <-ctx.Done():
return
case <-time.After(5 * time.Second):
- StatRTC.PeerConnection = pc.GetStats()
+ gStatRTC.PeerConnection = pc.GetStats()
}
}
}()
diff --git a/trunk/3rdparty/srs-bench/srs/publisher.go b/trunk/3rdparty/srs-bench/srs/publisher.go
index 49abab72a..2c78207c7 100644
--- a/trunk/3rdparty/srs-bench/srs/publisher.go
+++ b/trunk/3rdparty/srs-bench/srs/publisher.go
@@ -34,10 +34,10 @@ import (
)
// @see https://github.com/pion/webrtc/blob/master/examples/play-from-disk/main.go
-func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps int, enableAudioLevel, enableTWCC bool) error {
+func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps int, enableAudioLevel, enableTWCC bool) error {
ctx = logger.WithContext(ctx)
- logger.Tf(ctx, "Start publish url=%v, audio=%v, video=%v, fps=%v, audio-level=%v, twcc=%v",
+ logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v, audio-level=%v, twcc=%v",
r, sourceAudio, sourceVideo, fps, enableAudioLevel, enableTWCC)
// Filter for SPS/PPS marker.
@@ -78,11 +78,11 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
}
if sourceAudio != "" {
- aIngester = NewAudioIngester(sourceAudio)
+ aIngester = newAudioIngester(sourceAudio)
registry.Add(aIngester.audioLevelInterceptor)
}
if sourceVideo != "" {
- vIngester = NewVideoIngester(sourceVideo)
+ vIngester = newVideoIngester(sourceVideo)
registry.Add(vIngester.markerInterceptor)
}
@@ -158,7 +158,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
}
ctx, cancel := context.WithCancel(ctx)
- pcDone, pcDoneCancel := context.WithCancel(context.Background())
+ pcDoneCtx, pcDoneCancel := context.WithCancel(context.Background())
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
logger.Tf(ctx, "PC state %v", state)
@@ -196,7 +196,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
- case <-pcDone.Done():
+ case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start read audio packets")
}
@@ -218,7 +218,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
- case <-pcDone.Done():
+ case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest audio %v", sourceAudio)
}
@@ -244,7 +244,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
- case <-pcDone.Done():
+ case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start read video packets")
}
@@ -266,7 +266,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
select {
case <-ctx.Done():
- case <-pcDone.Done():
+ case <-pcDoneCtx.Done():
logger.Tf(ctx, "PC(ICE+DTLS+SRTP) done, start ingest video %v", sourceVideo)
}
@@ -290,7 +290,7 @@ func StartPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
case <-ctx.Done():
return
case <-time.After(5 * time.Second):
- StatRTC.PeerConnection = pc.GetStats()
+ gStatRTC.PeerConnection = pc.GetStats()
}
}
}()
diff --git a/trunk/3rdparty/srs-bench/srs/rtc_test.go b/trunk/3rdparty/srs-bench/srs/rtc_test.go
index de5ac0ff0..686e54819 100644
--- a/trunk/3rdparty/srs-bench/srs/rtc_test.go
+++ b/trunk/3rdparty/srs-bench/srs/rtc_test.go
@@ -58,6 +58,56 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
+// Basic use scenario, publish a stream.
+func TestRtcBasic_PublishOnly(t *testing.T) {
+ if err := filterTestError(func() error {
+ streamSuffix := fmt.Sprintf("publish-only-%v-%v", os.Getpid(), rand.Int())
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
+ p.streamSuffix = streamSuffix
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ defer p.Close()
+
+ ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
+ var nnRTCP, nnRTP int64
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
+ i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
+ nnRTP++
+ return i.nextRTPWriter.Write(header, payload, attributes)
+ }
+ }))
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
+ i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
+ if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
+ cancel() // Send enough packets, done.
+ }
+ logger.Tf(ctx, "publish write %v RTP read %v RTCP packets", nnRTP, nnRTCP)
+ return i.nextRTCPReader.Read(buf, attributes)
+ }
+ }))
+ }, func(api *testWebRTCAPI) {
+ api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed {
+ return true
+ }
+ logger.Tf(ctx, "Chunk %v, ok=%v %v bytes", chunk, ok, len(c.UserData()))
+ return true
+ })
+ }); err != nil {
+ return err
+ }
+
+ return p.Run(ctx, cancel)
+ }()); err != nil {
+ t.Errorf("err %+v", err)
+ }
+}
+
// Basic use scenario, publish a stream, then play it.
func TestRtcBasic_PublishPlay(t *testing.T) {
ctx := logger.WithContext(context.Background())
@@ -83,8 +133,8 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
defer wg.Wait()
// The event notify.
- var thePublisher *TestPublisher
- var thePlayer *TestPlayer
+ var thePublisher *testPublisher
+ var thePlayer *testPlayer
mainReady, mainReadyCancel := context.WithCancel(context.Background())
publishReady, publishReadyCancel := context.WithCancel(context.Background())
@@ -99,13 +149,13 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
streamSuffix := fmt.Sprintf("basic-publish-play-%v-%v", os.Getpid(), rand.Int())
// Initialize player with private api.
- if thePlayer, err = NewTestPlayer(CreateApiForPlayer, func(play *TestPlayer) error {
+ if thePlayer, err = newTestPlayer(createApiForPlayer, func(play *testPlayer) error {
play.streamSuffix = streamSuffix
resources = append(resources, play)
var nnPlayWriteRTCP, nnPlayReadRTCP, nnPlayWriteRTP, nnPlayReadRTP uint64
- return play.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ return play.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpReader = func(payload []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnPlayReadRTP++; nnPlayReadRTP >= uint64(*srsPlayOKPackets) {
cancel() // Completed.
@@ -115,7 +165,7 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
return i.nextRTPReader.Read(payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTCPReader.Read(buf, attributes)
nnPlayReadRTCP++
@@ -133,14 +183,14 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
}
// Initialize publisher with private api.
- if thePublisher, err = NewTestPublisher(CreateApiForPublisher, func(pub *TestPublisher) error {
+ if thePublisher, err = newTestPublisher(createApiForPublisher, func(pub *testPublisher) error {
pub.streamSuffix = streamSuffix
pub.iceReadyCancel = publishReadyCancel
resources = append(resources, pub)
var nnPubWriteRTCP, nnPubReadRTCP, nnPubWriteRTP, nnPubReadRTP uint64
- return pub.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ return pub.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTPReader.Read(buf, attributes)
nnPubReadRTP++
@@ -154,7 +204,7 @@ func TestRtcBasic_PublishPlay(t *testing.T) {
return nn, err
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTCPReader.Read(buf, attributes)
nnPubReadRTCP++
@@ -237,8 +287,8 @@ func TestRtcBasic_Republish(t *testing.T) {
defer wg.Wait()
// The event notify.
- var thePublisher, theRepublisher *TestPublisher
- var thePlayer *TestPlayer
+ var thePublisher, theRepublisher *testPublisher
+ var thePlayer *testPlayer
mainReady, mainReadyCancel := context.WithCancel(context.Background())
publishReady, publishReadyCancel := context.WithCancel(context.Background())
@@ -254,13 +304,13 @@ func TestRtcBasic_Republish(t *testing.T) {
streamSuffix := fmt.Sprintf("basic-publish-play-%v-%v", os.Getpid(), rand.Int())
// Initialize player with private api.
- if thePlayer, err = NewTestPlayer(CreateApiForPlayer, func(play *TestPlayer) error {
+ if thePlayer, err = newTestPlayer(createApiForPlayer, func(play *testPlayer) error {
play.streamSuffix = streamSuffix
resources = append(resources, play)
var nnPlayReadRTP uint64
- return play.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ return play.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpReader = func(payload []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
select {
case <-republishReady.Done():
@@ -280,14 +330,14 @@ func TestRtcBasic_Republish(t *testing.T) {
}
// Initialize publisher with private api.
- if thePublisher, err = NewTestPublisher(CreateApiForPublisher, func(pub *TestPublisher) error {
+ if thePublisher, err = newTestPublisher(createApiForPublisher, func(pub *testPublisher) error {
pub.streamSuffix = streamSuffix
pub.iceReadyCancel = publishReadyCancel
resources = append(resources, pub)
var nnPubReadRTCP uint64
- return pub.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ return pub.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
nn, attr, err := i.nextRTCPReader.Read(buf, attributes)
if nnPubReadRTCP++; nnPubReadRTCP > 0 && pub.cancel != nil {
@@ -303,7 +353,7 @@ func TestRtcBasic_Republish(t *testing.T) {
}
// Initialize re-publisher with private api.
- if theRepublisher, err = NewTestPublisher(CreateApiForPublisher, func(pub *TestPublisher) error {
+ if theRepublisher, err = newTestPublisher(createApiForPublisher, func(pub *testPublisher) error {
pub.streamSuffix = streamSuffix
pub.iceReadyCancel = republishReadyCancel
resources = append(resources, pub)
@@ -369,7 +419,7 @@ func TestRtcBasic_Republish(t *testing.T) {
func TestRtcDTLS_ClientActive_Default(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@@ -380,15 +430,15 @@ func TestRtcDTLS_ClientActive_Default(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -397,9 +447,9 @@ func TestRtcDTLS_ClientActive_Default(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@@ -424,7 +474,7 @@ func TestRtcDTLS_ClientActive_Default(t *testing.T) {
func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-active-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -435,15 +485,15 @@ func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -452,9 +502,9 @@ func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@@ -476,7 +526,7 @@ func TestRtcDTLS_ClientPassive_Default(t *testing.T) {
func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-active-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@@ -487,15 +537,15 @@ func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -504,15 +554,15 @@ func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS {
return true
}
// Copy the alert to server, ignore error.
- if chunk.content == DTLSContentTypeAlert {
+ if chunk.content == dtlsContentTypeAlert {
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
}
@@ -535,7 +585,7 @@ func TestRtcDTLS_ClientActive_Duplicated_Alert(t *testing.T) {
func TestRtcDTLS_ClientPassive_Duplicated_Alert(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-active-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -546,15 +596,15 @@ func TestRtcDTLS_ClientPassive_Duplicated_Alert(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -563,15 +613,15 @@ func TestRtcDTLS_ClientPassive_Duplicated_Alert(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS {
return true
}
// Copy the alert to server, ignore error.
- if chunk.content == DTLSContentTypeAlert {
+ if chunk.content == dtlsContentTypeAlert {
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
_, _ = api.proxy.Deliver(c.SourceAddr(), c.DestinationAddr(), c.UserData())
}
@@ -601,7 +651,7 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.T
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@@ -612,15 +662,15 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.T
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -629,17 +679,17 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.T
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnClientHello, nnMaxDrop := 0, 1
- var lastClientHello *DTLSRecord
+ var lastClientHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeClientHello {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeClientHello {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@@ -679,7 +729,7 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -690,15 +740,15 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -707,17 +757,17 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ClientHello(t *testing.
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnClientHello, nnMaxDrop := 0, 1
- var lastClientHello *DTLSRecord
+ var lastClientHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeClientHello {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeClientHello {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@@ -756,7 +806,7 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.T
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@@ -767,15 +817,15 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.T
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -784,23 +834,23 @@ func TestRtcDTLS_ClientActive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.T
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnServerHello, nnMaxDrop := 0, 1
- var lastClientHello, lastServerHello *DTLSRecord
+ var lastClientHello, lastServerHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake ||
- (chunk.handshake != DTLSHandshakeTypeClientHello && chunk.handshake != DTLSHandshakeTypeServerHello) {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake ||
+ (chunk.handshake != dtlsHandshakeTypeClientHello && chunk.handshake != dtlsHandshakeTypeServerHello) {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
- if chunk.handshake == DTLSHandshakeTypeClientHello {
+ if chunk.handshake == dtlsHandshakeTypeClientHello {
if lastClientHello != nil && record.Equals(lastClientHello) {
r0 = errors.Errorf("dup record %v", record)
}
@@ -844,7 +894,7 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-client-hello-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -855,15 +905,15 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -872,23 +922,23 @@ func TestRtcDTLS_ClientPassive_ARQ_ClientHello_ByDropped_ServerHello(t *testing.
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnServerHello, nnMaxDrop := 0, 1
- var lastClientHello, lastServerHello *DTLSRecord
+ var lastClientHello, lastServerHello *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake ||
- (chunk.handshake != DTLSHandshakeTypeClientHello && chunk.handshake != DTLSHandshakeTypeServerHello) {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake ||
+ (chunk.handshake != dtlsHandshakeTypeClientHello && chunk.handshake != dtlsHandshakeTypeServerHello) {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
- if chunk.handshake == DTLSHandshakeTypeClientHello {
+ if chunk.handshake == dtlsHandshakeTypeClientHello {
if lastClientHello != nil && record.Equals(lastClientHello) {
r0 = errors.Errorf("dup record %v", record)
}
@@ -929,7 +979,7 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_Certificate(t *testing.T
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-certificate-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@@ -940,15 +990,15 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_Certificate(t *testing.T
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -957,17 +1007,17 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_Certificate(t *testing.T
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
- var lastCertificate *DTLSRecord
+ var lastCertificate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeCertificate {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeCertificate {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@@ -1006,7 +1056,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_Certificate(t *testing.
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-certificate-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1017,15 +1067,15 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_Certificate(t *testing.
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -1034,17 +1084,17 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_Certificate(t *testing.
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
- var lastCertificate *DTLSRecord
+ var lastCertificate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
- if !parsed || chunk.chunk != ChunkTypeDTLS || chunk.content != DTLSContentTypeHandshake || chunk.handshake != DTLSHandshakeTypeCertificate {
+ chunk, parsed := newChunkMessageType(c)
+ if !parsed || chunk.chunk != chunkTypeDTLS || chunk.content != dtlsContentTypeHandshake || chunk.handshake != dtlsHandshakeTypeCertificate {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@@ -1083,7 +1133,7 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *test
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-active-arq-certificate-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupActive
return nil
@@ -1094,15 +1144,15 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *test
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -1111,17 +1161,17 @@ func TestRtcDTLS_ClientActive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *test
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
- var lastChangeCipherSepc, lastCertifidate *DTLSRecord
+ var lastChangeCipherSepc, lastCertifidate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed || (!chunk.IsChangeCipherSpec() && !chunk.IsCertificate()) {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@@ -1169,7 +1219,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
var r0, r1 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-arq-certificate-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1180,15 +1230,15 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -1197,17 +1247,17 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnCertificate, nnMaxDrop := 0, 1
- var lastChangeCipherSepc, lastCertifidate *DTLSRecord
+ var lastChangeCipherSepc, lastCertifidate *dtlsRecord
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed || (!chunk.IsChangeCipherSpec() && !chunk.IsCertificate()) {
return true
}
- record, err := NewDTLSRecord(c.UserData())
+ record, err := newDTLSRecord(c.UserData())
if err != nil {
return true
}
@@ -1246,7 +1296,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_ByDropped_ChangeCipherSpec(t *tes
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ClientHello(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1257,10 +1307,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ClientHello(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@@ -1299,7 +1349,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ClientHello(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ServerHello(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1310,10 +1360,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ServerHello(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@@ -1352,7 +1402,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ServerHello(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_Certificate(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1363,10 +1413,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_Certificate(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@@ -1405,7 +1455,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_Certificate(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ChangeCipherSpec(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1416,10 +1466,10 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ChangeCipherSpec(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
nnDrop, dropAll := 0, false
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@@ -1459,7 +1509,7 @@ func TestRtcDTLS_ClientPassive_ARQ_DropAllAfter_ChangeCipherSpec(t *testing.T) {
func TestRtcDTLS_ClientPassive_ARQ_VeryBadNetwork(t *testing.T) {
if err := filterTestError(func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1470,15 +1520,15 @@ func TestRtcDTLS_ClientPassive_ARQ_VeryBadNetwork(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -1487,10 +1537,10 @@ func TestRtcDTLS_ClientPassive_ARQ_VeryBadNetwork(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnDropClientHello, nnDropCertificate := 0, 0
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
@@ -1537,7 +1587,7 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_After_ClientHello(t *testing.T) {
var r0 error
err := func() error {
streamSuffix := fmt.Sprintf("dtls-passive-no-arq-%v-%v", os.Getpid(), rand.Int())
- p, err := NewTestPublisher(CreateApiForPublisher, func(p *TestPublisher) error {
+ p, err := newTestPublisher(createApiForPublisher, func(p *testPublisher) error {
p.streamSuffix = streamSuffix
p.onOffer = testUtilSetupPassive
return nil
@@ -1548,15 +1598,15 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_After_ClientHello(t *testing.T) {
defer p.Close()
ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond)
- if err := p.Setup(*srsVnetClientIP, func(api *TestWebRTCAPI) {
+ if err := p.Setup(*srsVnetClientIP, func(api *testWebRTCAPI) {
var nnRTCP, nnRTP int64
- api.registry.Add(NewRTPInterceptor(func(i *RTPInterceptor) {
+ api.registry.Add(newRTPInterceptor(func(i *rtpInterceptor) {
i.rtpWriter = func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
nnRTP++
return i.nextRTPWriter.Write(header, payload, attributes)
}
}))
- api.registry.Add(NewRTCPInterceptor(func(i *RTCPInterceptor) {
+ api.registry.Add(newRTCPInterceptor(func(i *rtcpInterceptor) {
i.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
if nnRTCP++; nnRTCP >= int64(*srsPublishOKPackets) && nnRTP >= int64(*srsPublishOKPackets) {
cancel() // Send enough packets, done.
@@ -1565,11 +1615,11 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_After_ClientHello(t *testing.T) {
return i.nextRTCPReader.Read(buf, attributes)
}
}))
- }, func(api *TestWebRTCAPI) {
+ }, func(api *testWebRTCAPI) {
nnDropClientHello, nnDropCertificate := 0, 0
var firstCertificate time.Time
api.router.AddChunkFilter(func(c vnet.Chunk) (ok bool) {
- chunk, parsed := NewChunkMessageType(c)
+ chunk, parsed := newChunkMessageType(c)
if !parsed {
return true
}
diff --git a/trunk/3rdparty/srs-bench/srs/srs.go b/trunk/3rdparty/srs-bench/srs/srs.go
new file mode 100644
index 000000000..91597259a
--- /dev/null
+++ b/trunk/3rdparty/srs-bench/srs/srs.go
@@ -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
+}
diff --git a/trunk/3rdparty/srs-bench/srs/stat.go b/trunk/3rdparty/srs-bench/srs/stat.go
index ef83fe78d..93918acdd 100644
--- a/trunk/3rdparty/srs-bench/srs/stat.go
+++ b/trunk/3rdparty/srs-bench/srs/stat.go
@@ -41,9 +41,9 @@ type statRTC struct {
PeerConnection interface{} `json:"random-pc"`
}
-var StatRTC statRTC
+var gStatRTC statRTC
-func HandleStat(ctx context.Context, mux *http.ServeMux, l string) {
+func handleStat(ctx context.Context, mux *http.ServeMux, l string) {
if strings.HasPrefix(l, ":") {
l = "127.0.0.1" + l
}
@@ -54,7 +54,7 @@ func HandleStat(ctx context.Context, mux *http.ServeMux, l string) {
Code int `json:"code"`
Data interface{} `json:"data"`
}{
- 0, &StatRTC,
+ 0, &gStatRTC,
}
b, err := json.Marshal(res)
diff --git a/trunk/3rdparty/srs-bench/srs/util.go b/trunk/3rdparty/srs-bench/srs/util.go
index e8cb47dad..54516e1f0 100644
--- a/trunk/3rdparty/srs-bench/srs/util.go
+++ b/trunk/3rdparty/srs-bench/srs/util.go
@@ -376,97 +376,97 @@ func srsIsRTCP(b []byte) bool {
return (len(b) >= 12) && (b[0]&0x80) != 0 && (b[1] >= 192 && b[1] <= 223)
}
-type ChunkType int
+type chunkType int
const (
- ChunkTypeICE ChunkType = iota + 1
- ChunkTypeDTLS
- ChunkTypeRTP
- ChunkTypeRTCP
+ chunkTypeICE chunkType = iota + 1
+ chunkTypeDTLS
+ chunkTypeRTP
+ chunkTypeRTCP
)
-func (v ChunkType) String() string {
+func (v chunkType) String() string {
switch v {
- case ChunkTypeICE:
+ case chunkTypeICE:
return "ICE"
- case ChunkTypeDTLS:
+ case chunkTypeDTLS:
return "DTLS"
- case ChunkTypeRTP:
+ case chunkTypeRTP:
return "RTP"
- case ChunkTypeRTCP:
+ case chunkTypeRTCP:
return "RTCP"
default:
return "Unknown"
}
}
-type DTLSContentType int
+type dtlsContentType int
const (
- DTLSContentTypeHandshake DTLSContentType = 22
- DTLSContentTypeChangeCipherSpec DTLSContentType = 20
- DTLSContentTypeAlert DTLSContentType = 21
+ dtlsContentTypeHandshake dtlsContentType = 22
+ dtlsContentTypeChangeCipherSpec dtlsContentType = 20
+ dtlsContentTypeAlert dtlsContentType = 21
)
-func (v DTLSContentType) String() string {
+func (v dtlsContentType) String() string {
switch v {
- case DTLSContentTypeHandshake:
+ case dtlsContentTypeHandshake:
return "Handshake"
- case DTLSContentTypeChangeCipherSpec:
+ case dtlsContentTypeChangeCipherSpec:
return "ChangeCipherSpec"
default:
return "Unknown"
}
}
-type DTLSHandshakeType int
+type dtlsHandshakeType int
const (
- DTLSHandshakeTypeClientHello DTLSHandshakeType = 1
- DTLSHandshakeTypeServerHello DTLSHandshakeType = 2
- DTLSHandshakeTypeCertificate DTLSHandshakeType = 11
- DTLSHandshakeTypeServerKeyExchange DTLSHandshakeType = 12
- DTLSHandshakeTypeCertificateRequest DTLSHandshakeType = 13
- DTLSHandshakeTypeServerDone DTLSHandshakeType = 14
- DTLSHandshakeTypeCertificateVerify DTLSHandshakeType = 15
- DTLSHandshakeTypeClientKeyExchange DTLSHandshakeType = 16
- DTLSHandshakeTypeFinished DTLSHandshakeType = 20
+ dtlsHandshakeTypeClientHello dtlsHandshakeType = 1
+ dtlsHandshakeTypeServerHello dtlsHandshakeType = 2
+ dtlsHandshakeTypeCertificate dtlsHandshakeType = 11
+ dtlsHandshakeTypeServerKeyExchange dtlsHandshakeType = 12
+ dtlsHandshakeTypeCertificateRequest dtlsHandshakeType = 13
+ dtlsHandshakeTypeServerDone dtlsHandshakeType = 14
+ dtlsHandshakeTypeCertificateVerify dtlsHandshakeType = 15
+ dtlsHandshakeTypeClientKeyExchange dtlsHandshakeType = 16
+ dtlsHandshakeTypeFinished dtlsHandshakeType = 20
)
-func (v DTLSHandshakeType) String() string {
+func (v dtlsHandshakeType) String() string {
switch v {
- case DTLSHandshakeTypeClientHello:
+ case dtlsHandshakeTypeClientHello:
return "ClientHello"
- case DTLSHandshakeTypeServerHello:
+ case dtlsHandshakeTypeServerHello:
return "ServerHello"
- case DTLSHandshakeTypeCertificate:
+ case dtlsHandshakeTypeCertificate:
return "Certificate"
- case DTLSHandshakeTypeServerKeyExchange:
+ case dtlsHandshakeTypeServerKeyExchange:
return "ServerKeyExchange"
- case DTLSHandshakeTypeCertificateRequest:
+ case dtlsHandshakeTypeCertificateRequest:
return "CertificateRequest"
- case DTLSHandshakeTypeServerDone:
+ case dtlsHandshakeTypeServerDone:
return "ServerDone"
- case DTLSHandshakeTypeCertificateVerify:
+ case dtlsHandshakeTypeCertificateVerify:
return "CertificateVerify"
- case DTLSHandshakeTypeClientKeyExchange:
+ case dtlsHandshakeTypeClientKeyExchange:
return "ClientKeyExchange"
- case DTLSHandshakeTypeFinished:
+ case dtlsHandshakeTypeFinished:
return "Finished"
default:
return "Unknown"
}
}
-type ChunkMessageType struct {
- chunk ChunkType
- content DTLSContentType
- handshake DTLSHandshakeType
+type chunkMessageType struct {
+ chunk chunkType
+ content dtlsContentType
+ handshake dtlsHandshakeType
}
-func (v *ChunkMessageType) String() string {
- if v.chunk == ChunkTypeDTLS {
- if v.content == DTLSContentTypeHandshake {
+func (v *chunkMessageType) String() string {
+ if v.chunk == chunkTypeDTLS {
+ if v.content == dtlsContentTypeHandshake {
return fmt.Sprintf("%v-%v-%v", v.chunk, v.content, v.handshake)
} else {
return fmt.Sprintf("%v-%v", v.chunk, v.content)
@@ -475,26 +475,26 @@ func (v *ChunkMessageType) String() string {
return fmt.Sprintf("%v", v.chunk)
}
-func NewChunkMessageType(c vnet.Chunk) (*ChunkMessageType, bool) {
+func newChunkMessageType(c vnet.Chunk) (*chunkMessageType, bool) {
b := c.UserData()
if len(b) == 0 {
return nil, false
}
- v := &ChunkMessageType{}
+ v := &chunkMessageType{}
if srsIsRTPOrRTCP(b) {
if srsIsRTCP(b) {
- v.chunk = ChunkTypeRTCP
+ v.chunk = chunkTypeRTCP
} else {
- v.chunk = ChunkTypeRTP
+ v.chunk = chunkTypeRTP
}
return v, true
}
if srsIsStun(b) {
- v.chunk = ChunkTypeICE
+ v.chunk = chunkTypeICE
return v, true
}
@@ -502,40 +502,40 @@ func NewChunkMessageType(c vnet.Chunk) (*ChunkMessageType, bool) {
return nil, false
}
- v.chunk, v.content = ChunkTypeDTLS, DTLSContentType(b[0])
- if v.content != DTLSContentTypeHandshake {
+ v.chunk, v.content = chunkTypeDTLS, dtlsContentType(b[0])
+ if v.content != dtlsContentTypeHandshake {
return v, true
}
if len(b) < 14 {
return v, false
}
- v.handshake = DTLSHandshakeType(b[13])
+ v.handshake = dtlsHandshakeType(b[13])
return v, true
}
-func (v *ChunkMessageType) IsHandshake() bool {
- return v.chunk == ChunkTypeDTLS && v.content == DTLSContentTypeHandshake
+func (v *chunkMessageType) IsHandshake() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake
}
-func (v *ChunkMessageType) IsClientHello() bool {
- return v.chunk == ChunkTypeDTLS && v.content == DTLSContentTypeHandshake && v.handshake == DTLSHandshakeTypeClientHello
+func (v *chunkMessageType) IsClientHello() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeClientHello
}
-func (v *ChunkMessageType) IsServerHello() bool {
- return v.chunk == ChunkTypeDTLS && v.content == DTLSContentTypeHandshake && v.handshake == DTLSHandshakeTypeServerHello
+func (v *chunkMessageType) IsServerHello() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeServerHello
}
-func (v *ChunkMessageType) IsCertificate() bool {
- return v.chunk == ChunkTypeDTLS && v.content == DTLSContentTypeHandshake && v.handshake == DTLSHandshakeTypeCertificate
+func (v *chunkMessageType) IsCertificate() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeHandshake && v.handshake == dtlsHandshakeTypeCertificate
}
-func (v *ChunkMessageType) IsChangeCipherSpec() bool {
- return v.chunk == ChunkTypeDTLS && v.content == DTLSContentTypeChangeCipherSpec
+func (v *chunkMessageType) IsChangeCipherSpec() bool {
+ return v.chunk == chunkTypeDTLS && v.content == dtlsContentTypeChangeCipherSpec
}
-type DTLSRecord struct {
- ContentType DTLSContentType
+type dtlsRecord struct {
+ ContentType dtlsContentType
Version uint16
Epoch uint16
SequenceNumber uint64
@@ -543,25 +543,25 @@ type DTLSRecord struct {
Data []byte
}
-func NewDTLSRecord(b []byte) (*DTLSRecord, error) {
- v := &DTLSRecord{}
+func newDTLSRecord(b []byte) (*dtlsRecord, error) {
+ v := &dtlsRecord{}
return v, v.Unmarshal(b)
}
-func (v *DTLSRecord) String() string {
+func (v *dtlsRecord) String() string {
return fmt.Sprintf("epoch=%v, sequence=%v", v.Epoch, v.SequenceNumber)
}
-func (v *DTLSRecord) Equals(p *DTLSRecord) bool {
+func (v *dtlsRecord) Equals(p *dtlsRecord) bool {
return v.Epoch == p.Epoch && v.SequenceNumber == p.SequenceNumber
}
-func (v *DTLSRecord) Unmarshal(b []byte) error {
+func (v *dtlsRecord) Unmarshal(b []byte) error {
if len(b) < 13 {
return errors.Errorf("requires 13B only %v", len(b))
}
- v.ContentType = DTLSContentType(b[0])
+ v.ContentType = dtlsContentType(b[0])
v.Version = uint16(b[1])<<8 | uint16(b[2])
v.Epoch = uint16(b[3])<<8 | uint16(b[4])
v.SequenceNumber = uint64(b[5])<<40 | uint64(b[6])<<32 | uint64(b[7])<<24 | uint64(b[8])<<16 | uint64(b[9])<<8 | uint64(b[10])
@@ -570,11 +570,11 @@ func (v *DTLSRecord) Unmarshal(b []byte) error {
return nil
}
-type TestWebRTCAPIOptionFunc func(api *TestWebRTCAPI)
+type testWebRTCAPIOptionFunc func(api *testWebRTCAPI)
-type TestWebRTCAPI struct {
+type testWebRTCAPI struct {
// The options to setup the api.
- options []TestWebRTCAPIOptionFunc
+ options []testWebRTCAPIOptionFunc
// The api and settings.
api *webrtc.API
mediaEngine *webrtc.MediaEngine
@@ -588,8 +588,8 @@ type TestWebRTCAPI struct {
proxy *vnet_proxy.UDPProxy
}
-func NewTestWebRTCAPI(options ...TestWebRTCAPIOptionFunc) (*TestWebRTCAPI, error) {
- v := &TestWebRTCAPI{}
+func newTestWebRTCAPI(options ...testWebRTCAPIOptionFunc) (*testWebRTCAPI, error) {
+ v := &testWebRTCAPI{}
v.mediaEngine = &webrtc.MediaEngine{}
if err := v.mediaEngine.RegisterDefaultCodecs(); err != nil {
@@ -610,7 +610,7 @@ func NewTestWebRTCAPI(options ...TestWebRTCAPIOptionFunc) (*TestWebRTCAPI, error
return v, nil
}
-func (v *TestWebRTCAPI) Close() error {
+func (v *testWebRTCAPI) Close() error {
if v.proxy != nil {
_ = v.proxy.Close()
}
@@ -622,7 +622,7 @@ func (v *TestWebRTCAPI) Close() error {
return nil
}
-func (v *TestWebRTCAPI) Setup(vnetClientIP string, options ...TestWebRTCAPIOptionFunc) error {
+func (v *testWebRTCAPI) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
// Setting engine for https://github.com/pion/transport/tree/master/vnet
setupVnet := func(vnetClientIP string) (err error) {
// We create a private router for a api, however, it's possible to share the
@@ -674,23 +674,23 @@ func (v *TestWebRTCAPI) Setup(vnetClientIP string, options ...TestWebRTCAPIOptio
return nil
}
-func (v *TestWebRTCAPI) NewPeerConnection(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
+func (v *testWebRTCAPI) NewPeerConnection(configuration webrtc.Configuration) (*webrtc.PeerConnection, error) {
return v.api.NewPeerConnection(configuration)
}
-type TestPlayerOptionFunc func(p *TestPlayer) error
+type testPlayerOptionFunc func(p *testPlayer) error
-type TestPlayer struct {
+type testPlayer struct {
pc *webrtc.PeerConnection
receivers []*webrtc.RTPReceiver
// We should dispose it.
- api *TestWebRTCAPI
+ api *testWebRTCAPI
// Optional suffix for stream url.
streamSuffix string
}
-func CreateApiForPlayer(play *TestPlayer) error {
- api, err := NewTestWebRTCAPI()
+func createApiForPlayer(play *testPlayer) error {
+ api, err := newTestWebRTCAPI()
if err != nil {
return err
}
@@ -699,8 +699,8 @@ func CreateApiForPlayer(play *TestPlayer) error {
return nil
}
-func NewTestPlayer(options ...TestPlayerOptionFunc) (*TestPlayer, error) {
- v := &TestPlayer{}
+func newTestPlayer(options ...testPlayerOptionFunc) (*testPlayer, error) {
+ v := &testPlayer{}
for _, opt := range options {
if err := opt(v); err != nil {
@@ -711,11 +711,11 @@ func NewTestPlayer(options ...TestPlayerOptionFunc) (*TestPlayer, error) {
return v, nil
}
-func (v *TestPlayer) Setup(vnetClientIP string, options ...TestWebRTCAPIOptionFunc) error {
+func (v *testPlayer) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
return v.api.Setup(vnetClientIP, options...)
}
-func (v *TestPlayer) Close() error {
+func (v *testPlayer) Close() error {
if v.pc != nil {
_ = v.pc.Close()
}
@@ -731,13 +731,13 @@ func (v *TestPlayer) Close() error {
return nil
}
-func (v *TestPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
+func (v *testPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
r := fmt.Sprintf("%v://%v%v", srsSchema, *srsServer, *srsStream)
if v.streamSuffix != "" {
r = fmt.Sprintf("%v-%v", r, v.streamSuffix)
}
pli := time.Duration(*srsPlayPLI) * time.Millisecond
- logger.Tf(ctx, "Start play url=%v", r)
+ logger.Tf(ctx, "Run play url=%v", r)
pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
if err != nil {
@@ -770,7 +770,7 @@ func (v *TestPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
}
- // Start a proxy for real server and vnet.
+ // Run a proxy for real server and vnet.
if address, err := parseAddressOfCandidate(answer); err != nil {
return errors.Wrapf(err, "parse address of %v", answer)
} else if err := v.api.proxy.Proxy(v.api.network, address); err != nil {
@@ -834,9 +834,9 @@ func (v *TestPlayer) Run(ctx context.Context, cancel context.CancelFunc) error {
return err
}
-type TestPublisherOptionFunc func(p *TestPublisher) error
+type testPublisherOptionFunc func(p *testPublisher) error
-type TestPublisher struct {
+type testPublisher struct {
onOffer func(s *webrtc.SessionDescription) error
onAnswer func(s *webrtc.SessionDescription) error
iceReadyCancel context.CancelFunc
@@ -845,15 +845,15 @@ type TestPublisher struct {
vIngester *videoIngester
pc *webrtc.PeerConnection
// We should dispose it.
- api *TestWebRTCAPI
+ api *testWebRTCAPI
// Optional suffix for stream url.
streamSuffix string
// To cancel the publisher, pass by Run.
cancel context.CancelFunc
}
-func CreateApiForPublisher(pub *TestPublisher) error {
- api, err := NewTestWebRTCAPI()
+func createApiForPublisher(pub *testPublisher) error {
+ api, err := newTestWebRTCAPI()
if err != nil {
return err
}
@@ -862,10 +862,10 @@ func CreateApiForPublisher(pub *TestPublisher) error {
return nil
}
-func NewTestPublisher(options ...TestPublisherOptionFunc) (*TestPublisher, error) {
+func newTestPublisher(options ...testPublisherOptionFunc) (*testPublisher, error) {
sourceVideo, sourceAudio := *srsPublishVideo, *srsPublishAudio
- v := &TestPublisher{}
+ v := &testPublisher{}
for _, opt := range options {
if err := opt(v); err != nil {
@@ -875,17 +875,17 @@ func NewTestPublisher(options ...TestPublisherOptionFunc) (*TestPublisher, error
// Create ingesters.
if sourceAudio != "" {
- v.aIngester = NewAudioIngester(sourceAudio)
+ v.aIngester = newAudioIngester(sourceAudio)
}
if sourceVideo != "" {
- v.vIngester = NewVideoIngester(sourceVideo)
+ v.vIngester = newVideoIngester(sourceVideo)
}
// Setup the interceptors for packets.
api := v.api
- api.options = append(api.options, func(api *TestWebRTCAPI) {
+ api.options = append(api.options, func(api *testWebRTCAPI) {
// Filter for RTCP packets.
- rtcpInterceptor := &RTCPInterceptor{}
+ rtcpInterceptor := &rtcpInterceptor{}
rtcpInterceptor.rtcpReader = func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
return rtcpInterceptor.nextRTCPReader.Read(buf, attributes)
}
@@ -906,11 +906,11 @@ func NewTestPublisher(options ...TestPublisherOptionFunc) (*TestPublisher, error
return v, nil
}
-func (v *TestPublisher) Setup(vnetClientIP string, options ...TestWebRTCAPIOptionFunc) error {
+func (v *testPublisher) Setup(vnetClientIP string, options ...testWebRTCAPIOptionFunc) error {
return v.api.Setup(vnetClientIP, options...)
}
-func (v *TestPublisher) Close() error {
+func (v *testPublisher) Close() error {
if v.vIngester != nil {
_ = v.vIngester.Close()
}
@@ -930,12 +930,12 @@ func (v *TestPublisher) Close() error {
return nil
}
-func (v *TestPublisher) SetStreamSuffix(suffix string) *TestPublisher {
+func (v *testPublisher) SetStreamSuffix(suffix string) *testPublisher {
v.streamSuffix = suffix
return v
}
-func (v *TestPublisher) Run(ctx context.Context, cancel context.CancelFunc) error {
+func (v *testPublisher) Run(ctx context.Context, cancel context.CancelFunc) error {
// Save the cancel.
v.cancel = cancel
@@ -945,7 +945,7 @@ func (v *TestPublisher) Run(ctx context.Context, cancel context.CancelFunc) erro
}
sourceVideo, sourceAudio, fps := *srsPublishVideo, *srsPublishAudio, *srsPublishVideoFps
- logger.Tf(ctx, "Start publish url=%v, audio=%v, video=%v, fps=%v",
+ logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v",
r, sourceAudio, sourceVideo, fps)
pc, err := v.api.NewPeerConnection(webrtc.Configuration{})
@@ -986,7 +986,7 @@ func (v *TestPublisher) Run(ctx context.Context, cancel context.CancelFunc) erro
return errors.Wrapf(err, "Api request offer=%v", offer.SDP)
}
- // Start a proxy for real server and vnet.
+ // Run a proxy for real server and vnet.
if address, err := parseAddressOfCandidate(answerSDP); err != nil {
return errors.Wrapf(err, "parse address of %v", answerSDP)
} else if err := v.api.proxy.Proxy(v.api.network, address); err != nil {
diff --git a/trunk/3rdparty/srs-bench/vnet/udpproxy_direct.go b/trunk/3rdparty/srs-bench/vnet/udpproxy_direct.go
index 35e4618d7..9ab460e71 100644
--- a/trunk/3rdparty/srs-bench/vnet/udpproxy_direct.go
+++ b/trunk/3rdparty/srs-bench/vnet/udpproxy_direct.go
@@ -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
import (
+ "fmt"
"net"
)
+// Deliver directly send packet to vnet or real-server.
+// For example, we can use this API to simulate the REPLAY ATTACK.
func (v *UDPProxy) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err error) {
v.workers.Range(func(key, value interface{}) bool {
- if nn, err := value.(*aUDPProxyWorker).Deliver(sourceAddr, destAddr, b); err != nil {
+ if nn, err = value.(*aUDPProxyWorker).Deliver(sourceAddr, destAddr, b); err != nil {
return false // Fail, abort.
} else if nn == len(b) {
return false // Done.
@@ -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) {
addr, ok := sourceAddr.(*net.UDPAddr)
if !ok {
- return 0, nil
+ return 0, fmt.Errorf("invalid addr %v", sourceAddr) // nolint:goerr113
}
- // TODO: Support deliver packet from real server to vnet.
- // If packet is from vent, proxy to real server.
+ // nolint:godox // TODO: Support deliver packet from real server to vnet.
+ // If packet is from vnet, proxy to real server.
var realSocket *net.UDPConn
if value, ok := v.endpoints.Load(addr.String()); !ok {
return 0, nil
- } else {
+ } else { // nolint:golint
realSocket = value.(*net.UDPConn)
}
diff --git a/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go b/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go
index 48b776957..18e43c191 100644
--- a/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go
+++ b/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go
@@ -1,41 +1,25 @@
-// 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.
+// +build !wasm
+
package vnet
import (
"context"
+ "errors"
"fmt"
- "github.com/pion/logging"
- "github.com/pion/transport/vnet"
"net"
"sync"
"testing"
"time"
+
+ "github.com/pion/logging"
)
-// vnet client:
+// The vnet client:
// 10.0.0.11:5787
-// proxy to real server:
+// which proxy to real server:
// 192.168.1.10:8000
-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())
var r0, r1, r2 error
@@ -57,7 +41,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
select {
case <-ctx.Done():
case <-time.After(time.Duration(*testTimeout) * time.Millisecond):
- r2 = fmt.Errorf("timeout")
+ r2 = fmt.Errorf("timeout") // nolint:goerr113
}
}()
@@ -86,7 +70,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
}
doVnetProxy := func() error {
- router, err := vnet.NewRouter(&vnet.RouterConfig{
+ router, err := NewRouter(&RouterConfig{
CIDR: "0.0.0.0/0",
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
@@ -94,23 +78,23 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
return err
}
- clientNetwork := vnet.NewNet(&vnet.NetConfig{
+ clientNetwork := NewNet(&NetConfig{
StaticIP: "10.0.0.11",
})
if err = router.AddNet(clientNetwork); err != nil {
return err
}
- if err := router.Start(); err != nil {
+ if err = router.Start(); err != nil {
return err
}
- defer router.Stop()
+ defer router.Stop() // nolint:errcheck
proxy, err := NewProxy(router)
if err != nil {
return err
}
- defer proxy.Close()
+ defer proxy.Close() // nolint:errcheck
// For utest, mock the target real server.
proxy.mockRealServerAddr = mockServer.realServerAddr
@@ -122,7 +106,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
return err
}
- if err := proxy.Proxy(clientNetwork, serverAddr); err != nil {
+ if err = proxy.Proxy(clientNetwork, serverAddr); err != nil {
return err
}
@@ -137,41 +121,41 @@ func TestUDPProxyDirectDeliver(t *testing.T) {
go func() {
<-ctx.Done()
selfKillCancel()
- client.Close()
+ _ = client.Close()
}()
// Write by vnet client.
- if _, err := client.WriteTo([]byte("Hello"), serverAddr); err != nil {
+ if _, err = client.WriteTo([]byte("Hello"), serverAddr); err != nil {
return err
}
buf := make([]byte, 1500)
- if n, addr, err := client.ReadFrom(buf); err != nil {
- if selfKill.Err() == context.Canceled {
+ if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet
+ if errors.Is(selfKill.Err(), context.Canceled) {
return nil
}
return err
} else if n != 5 || addr == nil {
- return fmt.Errorf("n=%v, addr=%v", n, addr)
- } else if string(buf[:n]) != "Hello" {
- return fmt.Errorf("data %v", buf[:n])
+ return fmt.Errorf("n=%v, addr=%v", n, addr) // nolint:goerr113
+ } else if string(buf[:n]) != "Hello" { // nolint:goconst
+ return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113
}
// Directly write, simulate the ARQ packet.
// We should got the echo packet also.
- if _, err := proxy.Deliver(client.LocalAddr(), serverAddr, []byte("Hello")); err != nil {
+ if _, err = proxy.Deliver(client.LocalAddr(), serverAddr, []byte("Hello")); err != nil {
return err
}
- if n, addr, err := client.ReadFrom(buf); err != nil {
- if selfKill.Err() == context.Canceled {
+ if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet
+ if errors.Is(selfKill.Err(), context.Canceled) {
return nil
}
return err
} else if n != 5 || addr == nil {
- return fmt.Errorf("n=%v, addr=%v", n, addr)
+ return fmt.Errorf("n=%v, addr=%v", n, addr) // nolint:goerr113
} else if string(buf[:n]) != "Hello" {
- return fmt.Errorf("data %v", buf[:n])
+ return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113
}
return err
@@ -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
+ }
+ }()
+}