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

Signaling works!

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