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..487bf09ba 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,a_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 pr, sourceAudio, sourceVideo string - var fps int - flag.StringVar(&pr, "pr", "", "") - flag.StringVar(&sourceAudio, "sa", "", "") - flag.StringVar(&sourceVideo, "sv", "", "") - flag.IntVar(&fps, "fps", 0, "") - - var audioLevel, videoTWCC bool - flag.BoolVar(&audioLevel, "al", true, "") - flag.BoolVar(&videoTWCC, "twcc", true, "") - - var clients, streams, delay int - flag.IntVar(&clients, "nn", 1, "") - flag.IntVar(&streams, "sn", 1, "") - flag.IntVar(&delay, "delay", 50, "") - - var statListen string - flag.StringVar(&statListen, "stat", "", "") - - flag.Usage = func() { - fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0])) - fmt.Println(fmt.Sprintf("Options:")) - fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1")) - fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1")) - fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50")) - fmt.Println(fmt.Sprintf(" -al [Optional] Whether enable audio-level. Default: true")) - fmt.Println(fmt.Sprintf(" -twcc [Optional] Whether enable vdieo-twcc. Default: true")) - fmt.Println(fmt.Sprintf(" -stat [Optional] The stat server API listen port.")) - fmt.Println(fmt.Sprintf("Player or Subscriber:")) - fmt.Println(fmt.Sprintf(" -sr The url to play/subscribe. If sn exceed 1, auto append variable %%d.")) - fmt.Println(fmt.Sprintf(" -da [Optional] The file path to dump audio, ignore if empty.")) - fmt.Println(fmt.Sprintf(" -dv [Optional] The file path to dump video, ignore if empty.")) - fmt.Println(fmt.Sprintf(" -pli [Optional] PLI request interval in seconds. Default: 10")) - fmt.Println(fmt.Sprintf("Publisher:")) - fmt.Println(fmt.Sprintf(" -pr The url to publish. If sn exceed 1, auto append variable %%d.")) - fmt.Println(fmt.Sprintf(" -fps The fps of .h264 source file.")) - fmt.Println(fmt.Sprintf(" -sa [Optional] The file path to read audio, ignore if empty.")) - fmt.Println(fmt.Sprintf(" -sv [Optional] The file path to read video, ignore if empty.")) - fmt.Println(fmt.Sprintf("\n例如,1个播放,1个推流:")) - fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream", os.Args[0])) - fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa a.ogg -sv v.h264 -fps 25", os.Args[0])) - fmt.Println(fmt.Sprintf("\n例如,1个流,3个播放,共3个客户端:")) - fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -nn 3", os.Args[0])) - fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa a.ogg -sv v.h264 -fps 25", os.Args[0])) - fmt.Println(fmt.Sprintf("\n例如,2个流,每个流3个播放,共6个客户端:")) - fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream_%%d -sn 2 -nn 3", os.Args[0])) - fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa a.ogg -sv v.h264 -fps 25", os.Args[0])) - fmt.Println(fmt.Sprintf("\n例如,2个推流:")) - fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream_%%d -sn 2 -sa a.ogg -sv v.h264 -fps 25", os.Args[0])) - fmt.Println(fmt.Sprintf("\n例如,1个录制:")) - fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream -da a.ogg -dv v.h264", os.Args[0])) - fmt.Println(fmt.Sprintf("\n例如,1个明文播放:")) - fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream?encrypt=false", os.Args[0])) - fmt.Println() - } - flag.Parse() - - showHelp := (clients <= 0 || streams <= 0) - if sr == "" && pr == "" { - showHelp = true - } - if pr != "" && (sourceAudio == "" && sourceVideo == "") { - showHelp = true - } - if showHelp { - flag.Usage() - os.Exit(-1) - } - - if statListen != "" && !strings.Contains(statListen, ":") { - statListen = ":" + statListen - } + var sfu string + fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + fl.SetOutput(ioutil.Discard) + fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or janus") + _ = fl.Parse(os.Args[1:]) ctx := context.Background() - summaryDesc := fmt.Sprintf("clients=%v, delay=%v, al=%v, twcc=%v, stat=%v", clients, delay, audioLevel, videoTWCC, statListen) - if sr != "" { - summaryDesc = fmt.Sprintf("%v, play(url=%v, da=%v, dv=%v, pli=%v)", summaryDesc, sr, dumpAudio, dumpVideo, pli) - } - if pr != "" { - summaryDesc = fmt.Sprintf("%v, publish(url=%v, sa=%v, sv=%v, fps=%v)", - summaryDesc, pr, sourceAudio, sourceVideo, fps) - } - logger.Tf(ctx, "Start benchmark with %v", summaryDesc) - - checkFlag := func() error { - if dumpVideo != "" && !strings.HasSuffix(dumpVideo, ".h264") && !strings.HasSuffix(dumpVideo, ".ivf") { - return errors.Errorf("Should be .ivf or .264, actual %v", dumpVideo) - } - - if sourceVideo != "" && !strings.HasSuffix(sourceVideo, ".h264") { - return errors.Errorf("Should be .264, actual %v", sourceVideo) - } - - if sourceVideo != "" && strings.HasSuffix(sourceVideo, ".h264") && fps <= 0 { - return errors.Errorf("Video fps should >0, actual %v", fps) - } - return nil - } - if err := checkFlag(); err != nil { - logger.Ef(ctx, "Check faile err %+v", err) + if sfu == "srs" { + srs.Parse(ctx) + } else if sfu == "janus" { + janus.Parse(ctx) + } else { + fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0])) + fmt.Println(fmt.Sprintf("Options:")) + fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or janus. Default: srs")) os.Exit(-1) } ctx, cancel := context.WithCancel(ctx) - - // Process all signals. go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) @@ -160,124 +62,17 @@ func main() { } }() - // Run tasks. - var wg sync.WaitGroup - - // Start STAT API server. - wg.Add(1) - go func() { - defer wg.Done() - - if statListen == "" { - return - } - - var lc net.ListenConfig - ln, err := lc.Listen(ctx, "tcp", statListen) - if err != nil { - logger.Ef(ctx, "stat listen err+%v", err) - cancel() - return - } - - mux := http.NewServeMux() - srs.HandleStat(ctx, mux, statListen) - - srv := &http.Server{ - Handler: mux, - BaseContext: func(listener net.Listener) context.Context { - return ctx - }, - } - - go func() { - <-ctx.Done() - srv.Shutdown(ctx) - }() - - logger.Tf(ctx, "Stat listen at %v", statListen) - if err := srv.Serve(ln); err != nil { - if ctx.Err() == nil { - logger.Ef(ctx, "stat serve err+%v", err) - cancel() - } - return - } - }() - - // Start all subscribers or players. - for i := 0; sr != "" && i < streams && ctx.Err() == nil; i++ { - r_auto := sr - if streams > 1 && !strings.Contains(r_auto, "%") { - r_auto += "%d" - } - - r2 := r_auto - if strings.Contains(r2, "%") { - r2 = fmt.Sprintf(r2, i) - } - - for j := 0; sr != "" && j < clients && ctx.Err() == nil; j++ { - // Dump audio or video only for the first client. - da, dv := dumpAudio, dumpVideo - if i > 0 { - da, dv = "", "" - } - - srs.StatRTC.Subscribers.Expect++ - srs.StatRTC.Subscribers.Alive++ - - wg.Add(1) - go func(sr, da, dv string) { - defer wg.Done() - defer func() { - srs.StatRTC.Subscribers.Alive-- - }() - - if err := srs.StartPlay(ctx, sr, da, dv, audioLevel, videoTWCC, pli); err != nil { - if errors.Cause(err) != context.Canceled { - logger.Wf(ctx, "Run err %+v", err) - } - } - }(r2, da, dv) - - time.Sleep(time.Duration(delay) * time.Millisecond) - } + var err error + if sfu == "srs" { + err = srs.Run(ctx) + } else if sfu == "janus" { + err = janus.Run(ctx) } - // Start all publishers. - for i := 0; pr != "" && i < streams && ctx.Err() == nil; i++ { - r_auto := pr - if streams > 1 && !strings.Contains(r_auto, "%") { - r_auto += "%d" - } - - r2 := r_auto - if strings.Contains(r2, "%") { - r2 = fmt.Sprintf(r2, i) - } - - srs.StatRTC.Publishers.Expect++ - srs.StatRTC.Publishers.Alive++ - - wg.Add(1) - go func(pr string) { - defer wg.Done() - defer func() { - srs.StatRTC.Publishers.Alive-- - }() - - if err := srs.StartPublish(ctx, pr, sourceAudio, sourceVideo, fps, audioLevel, videoTWCC); err != nil { - if errors.Cause(err) != context.Canceled { - logger.Wf(ctx, "Run err %+v", err) - } - } - }(r2) - - time.Sleep(time.Duration(delay) * time.Millisecond) + if err != nil { + logger.Wf(ctx, "srs err %+v", err) + return } - wg.Wait() - logger.Tf(ctx, "Done") } 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..ff902d56f 100644 --- a/trunk/3rdparty/srs-bench/vnet/udpproxy_direct.go +++ b/trunk/3rdparty/srs-bench/vnet/udpproxy_direct.go @@ -1,32 +1,14 @@ -// The MIT License (MIT) -// -// Copyright (c) 2021 Winlin -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package vnet import ( "net" ) +// Deliver directly send packet to vnet or real-server. +// For example, we can use this API to simulate the REPLAY ATTACK. func (v *UDPProxy) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn int, err error) { v.workers.Range(func(key, value interface{}) bool { - if nn, err := value.(*aUDPProxyWorker).Deliver(sourceAddr, destAddr, b); err != nil { + if nn, err = value.(*aUDPProxyWorker).Deliver(sourceAddr, destAddr, b); err != nil { return false // Fail, abort. } else if nn == len(b) { return false // Done. @@ -43,12 +25,12 @@ func (v *aUDPProxyWorker) Deliver(sourceAddr, destAddr net.Addr, b []byte) (nn i return 0, nil } - // TODO: Support deliver packet from real server to vnet. - // If packet is from vent, proxy to real server. + // nolint:godox // TODO: Support deliver packet from real server to vnet. + // If packet is from vnet, proxy to real server. var realSocket *net.UDPConn if value, ok := v.endpoints.Load(addr.String()); !ok { return 0, nil - } else { + } else { // nolint:golint realSocket = value.(*net.UDPConn) } diff --git a/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go b/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go index 48b776957..7ad367b82 100644 --- a/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go +++ b/trunk/3rdparty/srs-bench/vnet/udpproxy_direct_test.go @@ -1,40 +1,22 @@ -// The MIT License (MIT) -// -// Copyright (c) 2021 Winlin -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package vnet import ( "context" + "errors" "fmt" - "github.com/pion/logging" - "github.com/pion/transport/vnet" "net" "sync" "testing" "time" + + "github.com/pion/logging" ) -// vnet client: +// The vnet client: // 10.0.0.11:5787 -// proxy to real server: +// which proxy to real server: // 192.168.1.10:8000 +// We should get a reply if directly deliver to proxy. func TestUDPProxyDirectDeliver(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -57,7 +39,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) { select { case <-ctx.Done(): case <-time.After(time.Duration(*testTimeout) * time.Millisecond): - r2 = fmt.Errorf("timeout") + r2 = fmt.Errorf("timeout") // nolint:goerr113 } }() @@ -86,7 +68,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) { } doVnetProxy := func() error { - router, err := vnet.NewRouter(&vnet.RouterConfig{ + router, err := NewRouter(&RouterConfig{ CIDR: "0.0.0.0/0", LoggerFactory: logging.NewDefaultLoggerFactory(), }) @@ -94,23 +76,23 @@ func TestUDPProxyDirectDeliver(t *testing.T) { return err } - clientNetwork := vnet.NewNet(&vnet.NetConfig{ + clientNetwork := NewNet(&NetConfig{ StaticIP: "10.0.0.11", }) if err = router.AddNet(clientNetwork); err != nil { return err } - if err := router.Start(); err != nil { + if err = router.Start(); err != nil { return err } - defer router.Stop() + defer router.Stop() // nolint:errcheck proxy, err := NewProxy(router) if err != nil { return err } - defer proxy.Close() + defer proxy.Close() // nolint:errcheck // For utest, mock the target real server. proxy.mockRealServerAddr = mockServer.realServerAddr @@ -122,7 +104,7 @@ func TestUDPProxyDirectDeliver(t *testing.T) { return err } - if err := proxy.Proxy(clientNetwork, serverAddr); err != nil { + if err = proxy.Proxy(clientNetwork, serverAddr); err != nil { return err } @@ -137,41 +119,41 @@ func TestUDPProxyDirectDeliver(t *testing.T) { go func() { <-ctx.Done() selfKillCancel() - client.Close() + _ = client.Close() }() // Write by vnet client. - if _, err := client.WriteTo([]byte("Hello"), serverAddr); err != nil { + if _, err = client.WriteTo([]byte("Hello"), serverAddr); err != nil { return err } buf := make([]byte, 1500) - if n, addr, err := client.ReadFrom(buf); err != nil { - if selfKill.Err() == context.Canceled { + if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet + if errors.Is(selfKill.Err(), context.Canceled) { return nil } return err } else if n != 5 || addr == nil { - return fmt.Errorf("n=%v, addr=%v", n, addr) - } else if string(buf[:n]) != "Hello" { - return fmt.Errorf("data %v", buf[:n]) + return fmt.Errorf("n=%v, addr=%v", n, addr) // nolint:goerr113 + } else if string(buf[:n]) != "Hello" { // nolint:goconst + return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113 } // Directly write, simulate the ARQ packet. // We should got the echo packet also. - if _, err := proxy.Deliver(client.LocalAddr(), serverAddr, []byte("Hello")); err != nil { + if _, err = proxy.Deliver(client.LocalAddr(), serverAddr, []byte("Hello")); err != nil { return err } - if n, addr, err := client.ReadFrom(buf); err != nil { - if selfKill.Err() == context.Canceled { + if n, addr, err := client.ReadFrom(buf); err != nil { // nolint:gocritic,govet + if errors.Is(selfKill.Err(), context.Canceled) { return nil } return err } else if n != 5 || addr == nil { - return fmt.Errorf("n=%v, addr=%v", n, addr) + return fmt.Errorf("n=%v, addr=%v", n, addr) // nolint:goerr113 } else if string(buf[:n]) != "Hello" { - return fmt.Errorf("data %v", buf[:n]) + return fmt.Errorf("data %v", buf[:n]) // nolint:goerr113 } return err diff --git a/trunk/configure b/trunk/configure index b77baca7b..b57700e2d 100755 --- a/trunk/configure +++ b/trunk/configure @@ -462,7 +462,7 @@ mv ${SRS_WORKDIR}/${SRS_MAKEFILE} ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk # generate phony header cat << END > ${SRS_WORKDIR}/${SRS_MAKEFILE} .PHONY: default _default install help clean destroy server srs_ingest_hls utest _prepare_dir $__mphonys -.PHONY: clean_srs clean_modules clean_openssl clean_nginx clean_cherrypy clean_srtp2 clean_opus clean_ffmpeg clean_st +.PHONY: clean_srs clean_modules clean_openssl clean_srtp2 clean_opus clean_ffmpeg clean_st .PHONY: st ffmpeg # install prefix. @@ -505,7 +505,6 @@ doclean: (cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest $__mcleanups) (cd ${SRS_OBJS_DIR} && rm -rf src/* include lib) (mkdir -p ${SRS_OBJS_DIR}/utest && cd ${SRS_OBJS_DIR}/utest && rm -rf *.o *.a) - (cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players) clean: clean_srs clean_modules @@ -541,12 +540,6 @@ clean_st: (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf st-srs) @echo "Please rebuild ST by: ./configure" -clean_nginx: - (cd ${SRS_OBJS_DIR} && rm -rf nginx) - -clean_cherrypy: - (cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players) - st: (cd ${SRS_OBJS_DIR} && rm -f srs srs_utest) (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM}/st-srs && \$(MAKE) clean && \$(MAKE) ${_ST_MAKE} EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}") @@ -587,7 +580,11 @@ install: @mkdir -p \$(__REAL_INSTALL) @echo "Now make the http root dir" @mkdir -p \$(__REAL_INSTALL)/objs/nginx/html + @cp -f research/api-server/static-dir/index.html \$(__REAL_INSTALL)/objs/nginx/html @cp -f research/api-server/static-dir/crossdomain.xml \$(__REAL_INSTALL)/objs/nginx/html + @cp -f research/api-server/static-dir/favicon.ico \$(__REAL_INSTALL)/objs/nginx/html + @cp -Rf research/players \$(__REAL_INSTALL)/objs/nginx/html + @cp -Rf research/console \$(__REAL_INSTALL)/objs/nginx/html @echo "Now copy binary files" @mkdir -p \$(__REAL_INSTALL)/objs @cp -f objs/srs \$(__REAL_INSTALL)/objs diff --git a/trunk/scripts/package.sh b/trunk/scripts/package.sh index 8627989af..ee4b6d739 100755 --- a/trunk/scripts/package.sh +++ b/trunk/scripts/package.sh @@ -153,18 +153,6 @@ ok_msg "start install srs" ret=$?; if [[ 0 -ne ${ret} ]]; then failed_msg "install srs failed"; exit $ret; fi ok_msg "install srs success" -# Copy srs-console -HTTP_HOME="${package_dir}/${INSTALL}/objs/nginx/html/" -( - cp $work_dir/research/api-server/static-dir/index.html ${HTTP_HOME} && - cp $work_dir/research/api-server/static-dir/favicon.ico ${HTTP_HOME} && - cp $work_dir/research/api-server/static-dir/crossdomain.xml ${HTTP_HOME} && - cp -R $work_dir/research/players ${HTTP_HOME} && - cp -R $work_dir/research/console ${HTTP_HOME} -) >> $log 2>&1 -ret=$?; if [[ 0 -ne ${ret} ]]; then failed_msg "copy utilities failed"; exit $ret; fi -ok_msg "copy utilities success" - # copy extra files to package. ok_msg "start copy extra files to package" ( diff --git a/trunk/src/app/srs_app_pithy_print.cpp b/trunk/src/app/srs_app_pithy_print.cpp index 40c1a9b4b..98137326b 100644 --- a/trunk/src/app/srs_app_pithy_print.cpp +++ b/trunk/src/app/srs_app_pithy_print.cpp @@ -165,6 +165,33 @@ bool SrsErrorPithyPrint::can_print(int error_code, uint32_t* pnn) return new_stage || stage->can_print(); } +SrsAlonePithyPrint::SrsAlonePithyPrint() : info_(0) +{ + //stage work for one print + info_.nb_clients = 1; + + previous_tick_ = srs_get_system_time(); +} + +SrsAlonePithyPrint::~SrsAlonePithyPrint() +{ +} + +void SrsAlonePithyPrint::elapse() +{ + srs_utime_t diff = srs_get_system_time() - previous_tick_; + previous_tick_ = srs_get_system_time(); + + diff = srs_max(0, diff); + + info_.elapse(diff); +} + +bool SrsAlonePithyPrint::can_print() +{ + return info_.can_print(); +} + // The global stage manager for pithy print, multiple stages. static SrsStageManager* _srs_stages = new SrsStageManager(); diff --git a/trunk/src/app/srs_app_pithy_print.hpp b/trunk/src/app/srs_app_pithy_print.hpp index d1b044553..e9c51fe0f 100644 --- a/trunk/src/app/srs_app_pithy_print.hpp +++ b/trunk/src/app/srs_app_pithy_print.hpp @@ -89,6 +89,20 @@ public: bool can_print(int err, uint32_t* pnn = NULL); }; +// An standalone pithy print, without shared stages. +class SrsAlonePithyPrint +{ +private: + SrsStageInfo info_; + srs_utime_t previous_tick_; +public: + SrsAlonePithyPrint(); + virtual ~SrsAlonePithyPrint(); +public: + virtual void elapse(); + virtual bool can_print(); +}; + // The stage is used for a collection of object to do print, // the print time in a stage is constant and not changed, // that is, we always got one message to print every specified time. diff --git a/trunk/src/utest/srs_utest_rtc.cpp b/trunk/src/utest/srs_utest_rtc.cpp index 125705870..fb56237f1 100644 --- a/trunk/src/utest/srs_utest_rtc.cpp +++ b/trunk/src/utest/srs_utest_rtc.cpp @@ -563,619 +563,6 @@ VOID TEST(KernelRTCTest, StringDumpHexTest) } } -extern SSL_CTX* srs_build_dtls_ctx(SrsDtlsVersion version, std::string role); - -class MockDtls -{ -public: - SSL_CTX* dtls_ctx; - SSL* dtls; - BIO* bio_in; - BIO* bio_out; - ISrsDtlsCallback* callback_; - bool handshake_done_for_us; - SrsDtlsRole role_; - SrsDtlsVersion version_; -public: - MockDtls(ISrsDtlsCallback* callback); - virtual ~MockDtls(); - srs_error_t initialize(std::string role, std::string version); - srs_error_t start_active_handshake(); - srs_error_t on_dtls(char* data, int nb_data); - srs_error_t do_handshake(); -}; - -MockDtls::MockDtls(ISrsDtlsCallback* callback) -{ - dtls_ctx = NULL; - dtls = NULL; - - callback_ = callback; - handshake_done_for_us = false; - - role_ = SrsDtlsRoleServer; - version_ = SrsDtlsVersionAuto; -} - -MockDtls::~MockDtls() -{ - if (dtls_ctx) { - SSL_CTX_free(dtls_ctx); - dtls_ctx = NULL; - } - - if (dtls) { - SSL_free(dtls); - dtls = NULL; - } -} - -srs_error_t MockDtls::initialize(std::string role, std::string version) -{ - role_ = SrsDtlsRoleServer; - if (role == "active") { - role_ = SrsDtlsRoleClient; - } - - if (version == "dtls1.0") { - version_ = SrsDtlsVersion1_0; - } else if (version == "dtls1.2") { - version_ = SrsDtlsVersion1_2; - } else { - version_ = SrsDtlsVersionAuto; - } - - dtls_ctx = srs_build_dtls_ctx(version_, role); - dtls = SSL_new(dtls_ctx); - srs_assert(dtls); - - if (role_ == SrsDtlsRoleClient) { - SSL_set_connect_state(dtls); - SSL_set_max_send_fragment(dtls, kRtpPacketSize); - } else { - SSL_set_accept_state(dtls); - } - - bio_in = BIO_new(BIO_s_mem()); - srs_assert(bio_in); - - bio_out = BIO_new(BIO_s_mem()); - srs_assert(bio_out); - - SSL_set_bio(dtls, bio_in, bio_out); - return srs_success; -} - -srs_error_t MockDtls::start_active_handshake() -{ - if (role_ == SrsDtlsRoleClient) { - return do_handshake(); - } - return srs_success; -} - -srs_error_t MockDtls::on_dtls(char* data, int nb_data) -{ - srs_error_t err = srs_success; - - srs_assert(BIO_reset(bio_in) == 1); - srs_assert(BIO_reset(bio_out) == 1); - srs_assert(BIO_write(bio_in, data, nb_data) > 0); - - if ((err = do_handshake()) != srs_success) { - return srs_error_wrap(err, "do handshake"); - } - - while (BIO_ctrl_pending(bio_in) > 0) { - char buf[8092]; - int nb = SSL_read(dtls, buf, sizeof(buf)); - if (nb <= 0) { - continue; - } - - if ((err = callback_->on_dtls_application_data(buf, nb)) != srs_success) { - return srs_error_wrap(err, "on DTLS data, size=%u", nb); - } - } - - return err; -} - -srs_error_t MockDtls::do_handshake() -{ - srs_error_t err = srs_success; - - int r0 = SSL_do_handshake(dtls); - int r1 = SSL_get_error(dtls, r0); - if (r0 < 0 && (r1 != SSL_ERROR_NONE && r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE)) { - return srs_error_new(ERROR_RTC_DTLS, "handshake r0=%d, r1=%d", r0, r1); - } - if (r1 == SSL_ERROR_NONE) { - handshake_done_for_us = true; - } - - uint8_t* data = NULL; - int size = BIO_get_mem_data(bio_out, &data); - - if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) { - return srs_error_wrap(err, "dtls send size=%u", size); - } - - if (handshake_done_for_us) { - return callback_->on_dtls_handshake_done(); - } - - return err; -} - -class MockDtlsCallback : virtual public ISrsDtlsCallback, virtual public ISrsCoroutineHandler -{ -public: - SrsDtls* peer; - MockDtls* peer2; - SrsCoroutine* trd; - srs_error_t r0; - bool done; - std::vector samples; -public: - int nn_client_hello_lost; - int nn_server_hello_lost; - int nn_certificate_lost; - int nn_new_session_lost; - int nn_change_cipher_lost; -public: - // client -> server - int nn_client_hello; - // server -> client - int nn_server_hello; - // client -> server - int nn_certificate; - // server -> client - int nn_new_session; - int nn_change_cipher; -public: - MockDtlsCallback(); - virtual ~MockDtlsCallback(); - virtual srs_error_t on_dtls_handshake_done(); - virtual srs_error_t on_dtls_application_data(const char* data, const int len); - virtual srs_error_t write_dtls_data(void* data, int size); - virtual srs_error_t on_dtls_alert(std::string type, std::string desc); - virtual srs_error_t cycle(); -}; - -MockDtlsCallback::MockDtlsCallback() -{ - peer = NULL; - peer2 = NULL; - r0 = srs_success; - done = false; - trd = new SrsSTCoroutine("mock", this); - srs_assert(trd->start() == srs_success); - - nn_client_hello_lost = 0; - nn_server_hello_lost = 0; - nn_certificate_lost = 0; - nn_new_session_lost = 0; - nn_change_cipher_lost = 0; - - nn_client_hello = 0; - nn_server_hello = 0; - nn_certificate = 0; - nn_new_session = 0; - nn_change_cipher = 0; -} - -MockDtlsCallback::~MockDtlsCallback() -{ - srs_freep(trd); - srs_freep(r0); - for (vector::iterator it = samples.begin(); it != samples.end(); ++it) { - delete[] it->bytes; - } -} - -srs_error_t MockDtlsCallback::on_dtls_handshake_done() -{ - done = true; - return srs_success; -} - -srs_error_t MockDtlsCallback::on_dtls_application_data(const char* data, const int len) -{ - return srs_success; -} - -srs_error_t MockDtlsCallback::write_dtls_data(void* data, int size) -{ - int nn_lost = 0; - if (true) { - uint8_t content_type = 0; - if (size >= 1) { - content_type = (uint8_t)((uint8_t*)data)[0]; - } - - uint8_t handshake_type = 0; - if (size >= 14) { - handshake_type = (uint8_t)((uint8_t*)data)[13]; - } - - if (content_type == 22) { - if (handshake_type == 1) { - nn_lost = nn_client_hello_lost--; - nn_client_hello++; - } else if (handshake_type == 2) { - nn_lost = nn_server_hello_lost--; - nn_server_hello++; - } else if (handshake_type == 11) { - nn_lost = nn_certificate_lost--; - nn_certificate++; - } else if (handshake_type == 4) { - nn_lost = nn_new_session_lost--; - nn_new_session++; - } - } else if (content_type == 20) { - nn_lost = nn_change_cipher_lost--; - nn_change_cipher++; - } - } - - // Simulate to drop packet. - if (nn_lost > 0) { - return srs_success; - } - - // Send out it. - char* cp = new char[size]; - memcpy(cp, data, size); - - samples.push_back(SrsSample((char*)cp, size)); - - return srs_success; -} - -srs_error_t MockDtlsCallback::on_dtls_alert(std::string type, std::string desc) -{ - return srs_success; -} - -srs_error_t MockDtlsCallback::cycle() -{ - srs_error_t err = srs_success; - - while (err == srs_success) { - if ((err = trd->pull()) != srs_success) { - break; - } - - if (samples.empty()) { - srs_usleep(0); - continue; - } - - SrsSample p = samples.at(0); - samples.erase(samples.begin()); - - if (peer) { - err = peer->on_dtls((char*)p.bytes, p.size); - } else if (peer2) { - err = peer2->on_dtls((char*)p.bytes, p.size); - } - - srs_freepa(p.bytes); - } - - // Copy it for utest to check it. - r0 = srs_error_copy(err); - - return err; -} - -// Wait for mock io to done, try to switch to coroutine many times. -void mock_wait_dtls_io_done(SrsDtlsImpl* client_impl, int count = 100, int interval = 0) -{ - for (int i = 0; i < count; i++) { - if (client_impl) { - dynamic_cast(client_impl)->reset_timer_ = true; - } - srs_usleep(interval * SRS_UTIME_MILLISECONDS); - } -} - -// To avoid the crash when peer or peer2 is freed before io. -class MockBridgeDtlsIO -{ -private: - MockDtlsCallback* io_; -public: - MockBridgeDtlsIO(MockDtlsCallback* io, SrsDtls* peer, MockDtls* peer2) { - io_ = io; - io->peer = peer; - io->peer2 = peer2; - } - virtual ~MockBridgeDtlsIO() { - io_->peer = NULL; - io_->peer2 = NULL; - } -}; - -VOID TEST(KernelRTCTest, DTLSClientARQTest) -{ - srs_error_t err = srs_success; - - // No ARQ, check the number of packets. - if (true) { - MockDtlsCallback cio; SrsDtls client(&cio); - MockDtlsCallback sio; SrsDtls server(&sio); - cio.peer = &server; sio.peer = &client; - HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); - HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()); - mock_wait_dtls_io_done(client.impl, 15, 5); - - EXPECT_TRUE(sio.r0 == srs_success); - EXPECT_TRUE(cio.r0 == srs_success); - - EXPECT_TRUE(cio.done); - EXPECT_TRUE(sio.done); - - EXPECT_EQ(1, cio.nn_client_hello); - EXPECT_EQ(1, sio.nn_server_hello); - EXPECT_EQ(1, cio.nn_certificate); - EXPECT_EQ(1, sio.nn_new_session); - EXPECT_EQ(0, sio.nn_change_cipher); - } - - // ClientHello lost, client retransmit the ClientHello. - if (true) { - MockDtlsCallback cio; SrsDtls client(&cio); - MockDtlsCallback sio; SrsDtls server(&sio); - MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); - HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); - HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); - - // Lost 2 packets, total packets should be 3. - // Note that only one server hello. - cio.nn_client_hello_lost = 1; - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()); - mock_wait_dtls_io_done(client.impl, 15, 5); - - EXPECT_TRUE(sio.r0 == srs_success); - EXPECT_TRUE(cio.r0 == srs_success); - - EXPECT_TRUE(cio.done); - EXPECT_TRUE(sio.done); - - EXPECT_EQ(2, cio.nn_client_hello); - EXPECT_EQ(1, sio.nn_server_hello); - EXPECT_EQ(1, cio.nn_certificate); - EXPECT_EQ(1, sio.nn_new_session); - EXPECT_EQ(0, sio.nn_change_cipher); - } - - // Certificate lost, client retransmit the Certificate. - if (true) { - MockDtlsCallback cio; SrsDtls client(&cio); - MockDtlsCallback sio; SrsDtls server(&sio); - MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); - HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); - HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); - - // Lost 2 packets, total packets should be 3. - // Note that only one server NewSessionTicket. - cio.nn_certificate_lost = 1; - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()); - mock_wait_dtls_io_done(client.impl, 15, 5); - - EXPECT_TRUE(sio.r0 == srs_success); - EXPECT_TRUE(cio.r0 == srs_success); - - EXPECT_TRUE(cio.done); - EXPECT_TRUE(sio.done); - - EXPECT_EQ(1, cio.nn_client_hello); - EXPECT_EQ(2, sio.nn_server_hello); - EXPECT_EQ(2, cio.nn_certificate); - EXPECT_EQ(0, sio.nn_new_session); - EXPECT_EQ(0, sio.nn_change_cipher); - } -} - -VOID TEST(KernelRTCTest, DTLSServerARQTest) -{ - srs_error_t err = srs_success; - - // No ARQ, check the number of packets. - if (true) { - MockDtlsCallback cio; SrsDtls client(&cio); - MockDtlsCallback sio; SrsDtls server(&sio); - cio.peer = &server; sio.peer = &client; - HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); - HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()); - mock_wait_dtls_io_done(client.impl, 15, 5); - - EXPECT_TRUE(sio.r0 == srs_success); - EXPECT_TRUE(cio.r0 == srs_success); - - EXPECT_TRUE(cio.done); - EXPECT_TRUE(sio.done); - - EXPECT_EQ(1, cio.nn_client_hello); - EXPECT_EQ(1, sio.nn_server_hello); - EXPECT_EQ(1, cio.nn_certificate); - EXPECT_EQ(1, sio.nn_new_session); - EXPECT_EQ(0, sio.nn_change_cipher); - } - - // ServerHello lost, client retransmit the ClientHello. - if (true) { - MockDtlsCallback cio; SrsDtls client(&cio); - MockDtlsCallback sio; SrsDtls server(&sio); - MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); - HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); - HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); - - // Lost 2 packets, total packets should be 3. - sio.nn_server_hello_lost = 1; - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()); - mock_wait_dtls_io_done(client.impl, 15, 5); - - EXPECT_TRUE(sio.r0 == srs_success); - EXPECT_TRUE(cio.r0 == srs_success); - - EXPECT_TRUE(cio.done); - EXPECT_TRUE(sio.done); - - EXPECT_EQ(2, cio.nn_client_hello); - EXPECT_EQ(2, sio.nn_server_hello); - EXPECT_EQ(1, cio.nn_certificate); - EXPECT_EQ(1, sio.nn_new_session); - EXPECT_EQ(0, sio.nn_change_cipher); - } - - // NewSessionTicket lost, client retransmit the Certificate. - if (true) { - MockDtlsCallback cio; SrsDtls client(&cio); - MockDtlsCallback sio; SrsDtls server(&sio); - MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); - HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); - HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); - - // Lost 2 packets, total packets should be 3. - sio.nn_new_session_lost = 1; - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()); - mock_wait_dtls_io_done(client.impl, 15, 5); - - EXPECT_TRUE(sio.r0 == srs_success); - EXPECT_TRUE(cio.r0 == srs_success); - - EXPECT_TRUE(cio.done); - EXPECT_TRUE(sio.done); - - EXPECT_EQ(1, cio.nn_client_hello); - EXPECT_EQ(1, sio.nn_server_hello); - EXPECT_EQ(2, cio.nn_certificate); - EXPECT_EQ(2, sio.nn_new_session); - EXPECT_EQ(0, sio.nn_change_cipher); - } -} - -struct DTLSFlowCase -{ - int id; - - string ClientVersion; - string ServerVersion; - - bool ClientDone; - bool ServerDone; - - bool ClientError; - bool ServerError; -}; - -std::ostream& operator<< (std::ostream& stream, const DTLSFlowCase& c) -{ - stream << "Case #" << c.id - << ", client(" << c.ClientVersion << ",done=" << c.ClientDone << ",err=" << c.ClientError << ")" - << ", server(" << c.ServerVersion << ",done=" << c.ServerDone << ",err=" << c.ServerError << ")"; - return stream; -} - -VOID TEST(KernelRTCTest, DTLSClientFlowTest) -{ - srs_error_t err = srs_success; - - DTLSFlowCase cases[] = { - // OK, Client, Server: DTLS v1.0 - {0, "dtls1.0", "dtls1.0", true, true, false, false}, - // OK, Client, Server: DTLS v1.2 - {1, "dtls1.2", "dtls1.2", true, true, false, false}, - // OK, Client: DTLS v1.0, Server: DTLS auto(v1.0 or v1.2). - {2, "dtls1.0", "auto", true, true, false, false}, - // OK, Client: DTLS v1.2, Server: DTLS auto(v1.0 or v1.2). - {3, "dtls1.2", "auto", true, true, false, false}, - // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 - {4, "auto", "dtls1.0", true, true, false, false}, - // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 - {5, "auto", "dtls1.2", true, true, false, false}, - // OK?, Client: DTLS v1.0, Server: DTLS v1.2 - {6, "dtls1.0", "dtls1.2", true, true, false, false}, - // OK?, Client: DTLS v1.2, Server: DTLS v1.0 - {7, "dtls1.2", "dtls1.0", true, true, false, false}, - }; - - for (int i = 0; i < (int)(sizeof(cases) / sizeof(DTLSFlowCase)); i++) { - DTLSFlowCase c = cases[i]; - - MockDtlsCallback cio; SrsDtls client(&cio); - MockDtlsCallback sio; MockDtls server(&sio); - MockBridgeDtlsIO b0(&cio, NULL, &server); MockBridgeDtlsIO b1(&sio, &client, NULL); - HELPER_EXPECT_SUCCESS(client.initialize("active", c.ClientVersion)) << c; - HELPER_EXPECT_SUCCESS(server.initialize("passive", c.ServerVersion)) << c; - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()) << c; - mock_wait_dtls_io_done(client.impl, 15, 5); - - // Note that the cio error is generated from server, vice versa. - EXPECT_EQ(c.ClientDone, cio.done) << c; - EXPECT_EQ(c.ServerDone, sio.done) << c; - - EXPECT_EQ(c.ClientError, sio.r0 != srs_success) << c; - EXPECT_EQ(c.ServerError, cio.r0 != srs_success) << c; - } -} - -VOID TEST(KernelRTCTest, DTLSServerFlowTest) -{ - srs_error_t err = srs_success; - - DTLSFlowCase cases[] = { - // OK, Client, Server: DTLS v1.0 - {0, "dtls1.0", "dtls1.0", true, true, false, false}, - // OK, Client, Server: DTLS v1.2 - {1, "dtls1.2", "dtls1.2", true, true, false, false}, - // OK, Client: DTLS v1.0, Server: DTLS auto(v1.0 or v1.2). - {2, "dtls1.0", "auto", true, true, false, false}, - // OK, Client: DTLS v1.2, Server: DTLS auto(v1.0 or v1.2). - {3, "dtls1.2", "auto", true, true, false, false}, - // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 - {4, "auto", "dtls1.0", true, true, false, false}, - // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 - {5, "auto", "dtls1.2", true, true, false, false}, - // OK?, Client: DTLS v1.0, Server: DTLS v1.2 - {6, "dtls1.0", "dtls1.2", true, true, false, false}, - // OK?, Client: DTLS v1.2, Server: DTLS v1.0 - {7, "dtls1.2", "dtls1.0", true, true, false, false}, - }; - - for (int i = 0; i < (int)(sizeof(cases) / sizeof(DTLSFlowCase)); i++) { - DTLSFlowCase c = cases[i]; - - MockDtlsCallback cio; MockDtls client(&cio); - MockDtlsCallback sio; SrsDtls server(&sio); - MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, NULL, &client); - HELPER_EXPECT_SUCCESS(client.initialize("active", c.ClientVersion)) << c; - HELPER_EXPECT_SUCCESS(server.initialize("passive", c.ServerVersion)) << c; - - HELPER_EXPECT_SUCCESS(client.start_active_handshake()) << c; - mock_wait_dtls_io_done(NULL, 15, 5); - - // Note that the cio error is generated from server, vice versa. - EXPECT_EQ(c.ClientDone, cio.done) << c; - EXPECT_EQ(c.ServerDone, sio.done) << c; - - EXPECT_EQ(c.ClientError, sio.r0 != srs_success) << c; - EXPECT_EQ(c.ServerError, cio.r0 != srs_success) << c; - } -} - VOID TEST(KernelRTCTest, SequenceCompare) { if (true) {