直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理
Made-with: Cursor
This commit is contained in:
28
server/vendor/github.com/pion/interceptor/.gitignore
generated
vendored
Normal file
28
server/vendor/github.com/pion/interceptor/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
### JetBrains IDE ###
|
||||
#####################
|
||||
.idea/
|
||||
|
||||
### Emacs Temporary Files ###
|
||||
#############################
|
||||
*~
|
||||
|
||||
### Folders ###
|
||||
###############
|
||||
bin/
|
||||
vendor/
|
||||
node_modules/
|
||||
|
||||
### Files ###
|
||||
#############
|
||||
*.ivf
|
||||
*.ogg
|
||||
tags
|
||||
cover.out
|
||||
*.sw[poe]
|
||||
*.wasm
|
||||
examples/sfu-ws/cert.pem
|
||||
examples/sfu-ws/key.pem
|
||||
wasm_exec.js
|
||||
125
server/vendor/github.com/pion/interceptor/.golangci.yml
generated
vendored
Normal file
125
server/vendor/github.com/pion/interceptor/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
enable:
|
||||
- shadow
|
||||
misspell:
|
||||
locale: US
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
forbidigo:
|
||||
forbid:
|
||||
- ^fmt.Print(f|ln)?$
|
||||
- ^log.(Panic|Fatal|Print)(f|ln)?$
|
||||
- ^os.Exit$
|
||||
- ^panic$
|
||||
- ^print(ln)?$
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
|
||||
- bidichk # Checks for dangerous unicode character sequences
|
||||
- bodyclose # checks whether HTTP response body is closed successfully
|
||||
- contextcheck # check the function whether use a non-inherited context
|
||||
- decorder # check declaration order and count of types, constants, variables and functions
|
||||
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
|
||||
- dupl # Tool for code clone detection
|
||||
- durationcheck # check for two durations multiplied together
|
||||
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
|
||||
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.
|
||||
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
|
||||
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
|
||||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- exportloopref # checks for pointers to enclosing loop variables
|
||||
- forbidigo # Forbids identifiers
|
||||
- forcetypeassert # finds forced type assertions
|
||||
- gci # Gci control golang package import order and make it always deterministic.
|
||||
- gochecknoglobals # Checks that no globals are present in Go code
|
||||
- gochecknoinits # Checks that no init functions are present in Go code
|
||||
- gocognit # Computes and checks the cognitive complexity of functions
|
||||
- goconst # Finds repeated strings that could be replaced by a constant
|
||||
- gocritic # The most opinionated Go source code linter
|
||||
- godox # Tool for detection of FIXME, TODO and other comment keywords
|
||||
- goerr113 # Golang linter to check the errors handling expressions
|
||||
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
|
||||
- gofumpt # Gofumpt checks whether code was gofumpt-ed.
|
||||
- goheader # Checks is file header matches to pattern
|
||||
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
|
||||
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
|
||||
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
|
||||
- gosec # Inspects source code for security problems
|
||||
- gosimple # Linter for Go source code that specializes in simplifying a code
|
||||
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
|
||||
- grouper # An analyzer to analyze expression groups.
|
||||
- importas # Enforces consistent import aliases
|
||||
- ineffassign # Detects when assignments to existing variables are not used
|
||||
- misspell # Finds commonly misspelled English words in comments
|
||||
- nilerr # Finds the code that returns nil even if it checks that the error is not nil.
|
||||
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value.
|
||||
- noctx # noctx finds sending http request without context.Context
|
||||
- predeclared # find code that shadows one of Go's predeclared identifiers
|
||||
- revive # golint replacement, finds style mistakes
|
||||
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
|
||||
- stylecheck # Stylecheck is a replacement for golint
|
||||
- tagliatelle # Checks the struct tags.
|
||||
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
|
||||
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
|
||||
- unconvert # Remove unnecessary type conversions
|
||||
- unparam # Reports unused function parameters
|
||||
- unused # Checks Go code for unused constants, variables, functions and types
|
||||
- wastedassign # wastedassign finds wasted assignment statements
|
||||
- whitespace # Tool for detection of leading and trailing whitespace
|
||||
disable:
|
||||
- depguard # Go linter that checks if package imports are in a list of acceptable packages
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field
|
||||
- cyclop # checks function and package cyclomatic complexity
|
||||
- exhaustivestruct # Checks if all struct's fields are initialized
|
||||
- funlen # Tool for detection of long functions
|
||||
- gocyclo # Computes and checks the cyclomatic complexity of functions
|
||||
- godot # Check if comments end in a period
|
||||
- gomnd # An analyzer to detect magic numbers.
|
||||
- ifshort # Checks that your code uses short syntax for if-statements whenever possible
|
||||
- ireturn # Accept Interfaces, Return Concrete Types
|
||||
- lll # Reports long lines
|
||||
- maintidx # maintidx measures the maintainability index of each function.
|
||||
- makezero # Finds slice declarations with non-zero initial length
|
||||
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length
|
||||
- nestif # Reports deeply nested if statements
|
||||
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
|
||||
- nolintlint # Reports ill-formed or insufficient nolint directives
|
||||
- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test
|
||||
- prealloc # Finds slice declarations that could potentially be preallocated
|
||||
- promlinter # Check Prometheus metrics naming via promlint
|
||||
- rowserrcheck # checks whether Err of rows is checked successfully
|
||||
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
|
||||
- testpackage # linter that makes you use a separate _test package
|
||||
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
|
||||
- varnamelen # checks that the length of a variable's name matches its scope
|
||||
- wrapcheck # Checks that errors returned from external packages are wrapped
|
||||
- wsl # Whitespace Linter - Forces you to use empty lines!
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude-dirs-use-default: false
|
||||
exclude-rules:
|
||||
# Allow complex tests and examples, better to be self contained
|
||||
- path: (examples|main\.go|_test\.go)
|
||||
linters:
|
||||
- forbidigo
|
||||
- gocognit
|
||||
|
||||
# Allow forbidden identifiers in CLI commands
|
||||
- path: cmd
|
||||
linters:
|
||||
- forbidigo
|
||||
5
server/vendor/github.com/pion/interceptor/.goreleaser.yml
generated
vendored
Normal file
5
server/vendor/github.com/pion/interceptor/.goreleaser.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
builds:
|
||||
- skip: true
|
||||
9
server/vendor/github.com/pion/interceptor/LICENSE
generated
vendored
Normal file
9
server/vendor/github.com/pion/interceptor/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 The Pion community <https://pion.ly>
|
||||
|
||||
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.
|
||||
84
server/vendor/github.com/pion/interceptor/README.md
generated
vendored
Normal file
84
server/vendor/github.com/pion/interceptor/README.md
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
Pion Interceptor
|
||||
<br>
|
||||
</h1>
|
||||
<h4 align="center">RTCP and RTCP processors for building real time communications</h4>
|
||||
<p align="center">
|
||||
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-interceptor-gray.svg?longCache=true&colorB=brightgreen" alt="Pion Interceptor"></a>
|
||||
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||
<br>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/interceptor/test.yaml">
|
||||
<a href="https://pkg.go.dev/github.com/pion/interceptor"><img src="https://pkg.go.dev/badge/github.com/pion/interceptor.svg" alt="Go Reference"></a>
|
||||
<a href="https://codecov.io/gh/pion/interceptor"><img src="https://codecov.io/gh/pion/interceptor/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/pion/interceptor"><img src="https://goreportcard.com/badge/github.com/pion/interceptor" alt="Go Report Card"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||
</p>
|
||||
<br>
|
||||
|
||||
Interceptor is a framework for building RTP/RTCP communication software. This framework defines
|
||||
a interface that each interceptor must satisfy. These interceptors are then run sequentially. We
|
||||
also then provide common interceptors that will be useful for building RTC software.
|
||||
|
||||
This package was built for [pion/webrtc](https://github.com/pion/webrtc), but we designed it to be consumable
|
||||
by anyone. With the following tenets in mind.
|
||||
|
||||
* Useful defaults. Each interceptor will be configured to give you a good default experience.
|
||||
* Unblock unique use cases. New use cases are what is driving WebRTC, we want to empower them.
|
||||
* Encourage modification. Add your own interceptors without forking. Mixing with the ones we provide.
|
||||
* Empower learning. This code base should be useful to read and learn even if you aren't using Pion.
|
||||
|
||||
### Current Interceptors
|
||||
* [NACK Generator/Responder](https://github.com/pion/interceptor/tree/master/pkg/nack)
|
||||
* [Sender and Receiver Reports](https://github.com/pion/interceptor/tree/master/pkg/report)
|
||||
* [Transport Wide Congestion Control Feedback](https://github.com/pion/interceptor/tree/master/pkg/twcc)
|
||||
* [Packet Dump](https://github.com/pion/interceptor/tree/master/pkg/packetdump)
|
||||
* [Google Congestion Control](https://github.com/pion/interceptor/tree/master/pkg/gcc)
|
||||
* [Stats](https://github.com/pion/interceptor/tree/master/pkg/stats) A [webrtc-stats](https://www.w3.org/TR/webrtc-stats/) compliant statistics generation
|
||||
* [Interval PLI](https://github.com/pion/interceptor/tree/master/pkg/intervalpli) Generate PLI on a interval. Useful when no decoder is available.
|
||||
|
||||
### Planned Interceptors
|
||||
* Bandwidth Estimation
|
||||
- [NADA](https://tools.ietf.org/html/rfc8698)
|
||||
* JitterBuffer, re-order packets and wait for arrival
|
||||
* [FlexFec](https://tools.ietf.org/html/draft-ietf-payload-flexible-fec-scheme-20)
|
||||
* [RTCP Feedback for Congestion Control](https://datatracker.ietf.org/doc/html/rfc8888) the standardized alternative to TWCC.
|
||||
|
||||
### Interceptor Public API
|
||||
The public interface is defined in [interceptor.go](https://github.com/pion/interceptor/blob/master/interceptor.go).
|
||||
The methods you need to satisy are broken up into 4 groups.
|
||||
|
||||
* `BindRTCPWriter` and `BindRTCPReader` allow you to inspect/modify RTCP traffic.
|
||||
* `BindLocalStream` and `BindRemoteStream` notify you of a new SSRC stream and allow you to inspect/modify.
|
||||
* `UnbindLocalStream` and `UnbindRemoteStream` notify you when a SSRC stream has been removed
|
||||
* `Close` called when the interceptor is closed.
|
||||
|
||||
Interceptors also pass Attributes between each other. These are a collection of key/value pairs and are useful for storing metadata
|
||||
or caching.
|
||||
|
||||
[noop.go](https://github.com/pion/interceptor/blob/master/noop.go) is an interceptor that satisfies this interface, but does nothing.
|
||||
You can embed this interceptor as a starting point so you only need to define exactly what you need.
|
||||
|
||||
[chain.go]( https://github.com/pion/interceptor/blob/master/chain.go) is used to combine multiple interceptors into one. They are called
|
||||
sequentially as the packet moves through them.
|
||||
|
||||
### Examples
|
||||
The [examples](https://github.com/pion/interceptor/blob/master/examples) directory provides some basic examples. If you need more please file an issue!
|
||||
You should also look in [pion/webrtc](https://github.com/pion/webrtc) for real world examples.
|
||||
|
||||
### Roadmap
|
||||
The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
|
||||
|
||||
### Community
|
||||
Pion has an active community on the [Slack](https://pion.ly/slack).
|
||||
|
||||
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
|
||||
|
||||
We are always looking to support **your projects**. Please reach out if you have something to build!
|
||||
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
|
||||
|
||||
### Contributing
|
||||
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible
|
||||
|
||||
### License
|
||||
MIT License - see [LICENSE](LICENSE) for full text
|
||||
68
server/vendor/github.com/pion/interceptor/attributes.go
generated
vendored
Normal file
68
server/vendor/github.com/pion/interceptor/attributes.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interceptor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type unmarshaledDataKeyType int
|
||||
|
||||
const (
|
||||
rtpHeaderKey unmarshaledDataKeyType = iota
|
||||
rtcpPacketsKey
|
||||
)
|
||||
|
||||
var errInvalidType = errors.New("found value of invalid type in attributes map")
|
||||
|
||||
// Attributes are a generic key/value store used by interceptors
|
||||
type Attributes map[interface{}]interface{}
|
||||
|
||||
// Get returns the attribute associated with key.
|
||||
func (a Attributes) Get(key interface{}) interface{} {
|
||||
return a[key]
|
||||
}
|
||||
|
||||
// Set sets the attribute associated with key to the given value.
|
||||
func (a Attributes) Set(key interface{}, val interface{}) {
|
||||
a[key] = val
|
||||
}
|
||||
|
||||
// GetRTPHeader gets the RTP header if present. If it is not present, it will be
|
||||
// unmarshalled from the raw byte slice and stored in the attribtues.
|
||||
func (a Attributes) GetRTPHeader(raw []byte) (*rtp.Header, error) {
|
||||
if val, ok := a[rtpHeaderKey]; ok {
|
||||
if header, ok := val.(*rtp.Header); ok {
|
||||
return header, nil
|
||||
}
|
||||
return nil, errInvalidType
|
||||
}
|
||||
header := &rtp.Header{}
|
||||
if _, err := header.Unmarshal(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a[rtpHeaderKey] = header
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// GetRTCPPackets gets the RTCP packets if present. If the packet slice is not
|
||||
// present, it will be unmarshaled from the raw byte slice and stored in the
|
||||
// attributes.
|
||||
func (a Attributes) GetRTCPPackets(raw []byte) ([]rtcp.Packet, error) {
|
||||
if val, ok := a[rtcpPacketsKey]; ok {
|
||||
if packets, ok := val.([]rtcp.Packet); ok {
|
||||
return packets, nil
|
||||
}
|
||||
return nil, errInvalidType
|
||||
}
|
||||
pkts, err := rtcp.Unmarshal(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a[rtcpPacketsKey] = pkts
|
||||
return pkts, nil
|
||||
}
|
||||
78
server/vendor/github.com/pion/interceptor/chain.go
generated
vendored
Normal file
78
server/vendor/github.com/pion/interceptor/chain.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interceptor
|
||||
|
||||
// Chain is an interceptor that runs all child interceptors in order.
|
||||
type Chain struct {
|
||||
interceptors []Interceptor
|
||||
}
|
||||
|
||||
// NewChain returns a new Chain interceptor.
|
||||
func NewChain(interceptors []Interceptor) *Chain {
|
||||
return &Chain{interceptors: interceptors}
|
||||
}
|
||||
|
||||
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
|
||||
// change in the future. The returned method will be called once per packet batch.
|
||||
func (i *Chain) BindRTCPReader(reader RTCPReader) RTCPReader {
|
||||
for _, interceptor := range i.interceptors {
|
||||
reader = interceptor.BindRTCPReader(reader)
|
||||
}
|
||||
|
||||
return reader
|
||||
}
|
||||
|
||||
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
|
||||
// will be called once per packet batch.
|
||||
func (i *Chain) BindRTCPWriter(writer RTCPWriter) RTCPWriter {
|
||||
for _, interceptor := range i.interceptors {
|
||||
writer = interceptor.BindRTCPWriter(writer)
|
||||
}
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (i *Chain) BindLocalStream(ctx *StreamInfo, writer RTPWriter) RTPWriter {
|
||||
for _, interceptor := range i.interceptors {
|
||||
writer = interceptor.BindLocalStream(ctx, writer)
|
||||
}
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (i *Chain) UnbindLocalStream(ctx *StreamInfo) {
|
||||
for _, interceptor := range i.interceptors {
|
||||
interceptor.UnbindLocalStream(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (i *Chain) BindRemoteStream(ctx *StreamInfo, reader RTPReader) RTPReader {
|
||||
for _, interceptor := range i.interceptors {
|
||||
reader = interceptor.BindRemoteStream(ctx, reader)
|
||||
}
|
||||
|
||||
return reader
|
||||
}
|
||||
|
||||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (i *Chain) UnbindRemoteStream(ctx *StreamInfo) {
|
||||
for _, interceptor := range i.interceptors {
|
||||
interceptor.UnbindRemoteStream(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the Interceptor, cleaning up any data if necessary.
|
||||
func (i *Chain) Close() error {
|
||||
var errs []error
|
||||
for _, interceptor := range i.interceptors {
|
||||
errs = append(errs, interceptor.Close())
|
||||
}
|
||||
|
||||
return flattenErrs(errs)
|
||||
}
|
||||
22
server/vendor/github.com/pion/interceptor/codecov.yml
generated
vendored
Normal file
22
server/vendor/github.com/pion/interceptor/codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# DO NOT EDIT THIS FILE
|
||||
#
|
||||
# It is automatically copied from https://github.com/pion/.goassets repository.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# Allow decreasing 2% of total coverage to avoid noise.
|
||||
threshold: 2%
|
||||
patch:
|
||||
default:
|
||||
target: 70%
|
||||
only_pulls: true
|
||||
|
||||
ignore:
|
||||
- "examples/*"
|
||||
- "examples/**/*"
|
||||
54
server/vendor/github.com/pion/interceptor/errors.go
generated
vendored
Normal file
54
server/vendor/github.com/pion/interceptor/errors.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interceptor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func flattenErrs(errs []error) error {
|
||||
errs2 := []error{}
|
||||
for _, e := range errs {
|
||||
if e != nil {
|
||||
errs2 = append(errs2, e)
|
||||
}
|
||||
}
|
||||
if len(errs2) == 0 {
|
||||
return nil
|
||||
}
|
||||
return multiError(errs2)
|
||||
}
|
||||
|
||||
type multiError []error //nolint
|
||||
|
||||
func (me multiError) Error() string {
|
||||
var errstrings []string
|
||||
|
||||
for _, err := range me {
|
||||
if err != nil {
|
||||
errstrings = append(errstrings, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errstrings) == 0 {
|
||||
return "multiError must contain multiple error but is empty"
|
||||
}
|
||||
|
||||
return strings.Join(errstrings, "\n")
|
||||
}
|
||||
|
||||
func (me multiError) Is(err error) bool {
|
||||
for _, e := range me {
|
||||
if errors.Is(e, err) {
|
||||
return true
|
||||
}
|
||||
if me2, ok := e.(multiError); ok { //nolint
|
||||
if me2.Is(err) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
102
server/vendor/github.com/pion/interceptor/interceptor.go
generated
vendored
Normal file
102
server/vendor/github.com/pion/interceptor/interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package interceptor contains the Interceptor interface, with some useful interceptors that should be safe to use
|
||||
// in most cases.
|
||||
package interceptor
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// Factory provides an interface for constructing interceptors
|
||||
type Factory interface {
|
||||
NewInterceptor(id string) (Interceptor, error)
|
||||
}
|
||||
|
||||
// Interceptor can be used to add functionality to you PeerConnections by modifying any incoming/outgoing rtp/rtcp
|
||||
// packets, or sending your own packets as needed.
|
||||
type Interceptor interface {
|
||||
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
|
||||
// change in the future. The returned method will be called once per packet batch.
|
||||
BindRTCPReader(reader RTCPReader) RTCPReader
|
||||
|
||||
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
|
||||
// will be called once per packet batch.
|
||||
BindRTCPWriter(writer RTCPWriter) RTCPWriter
|
||||
|
||||
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
BindLocalStream(info *StreamInfo, writer RTPWriter) RTPWriter
|
||||
|
||||
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
UnbindLocalStream(info *StreamInfo)
|
||||
|
||||
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
BindRemoteStream(info *StreamInfo, reader RTPReader) RTPReader
|
||||
|
||||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
UnbindRemoteStream(info *StreamInfo)
|
||||
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// RTPWriter is used by Interceptor.BindLocalStream.
|
||||
type RTPWriter interface {
|
||||
// Write a rtp packet
|
||||
Write(header *rtp.Header, payload []byte, attributes Attributes) (int, error)
|
||||
}
|
||||
|
||||
// RTPReader is used by Interceptor.BindRemoteStream.
|
||||
type RTPReader interface {
|
||||
// Read a rtp packet
|
||||
Read([]byte, Attributes) (int, Attributes, error)
|
||||
}
|
||||
|
||||
// RTCPWriter is used by Interceptor.BindRTCPWriter.
|
||||
type RTCPWriter interface {
|
||||
// Write a batch of rtcp packets
|
||||
Write(pkts []rtcp.Packet, attributes Attributes) (int, error)
|
||||
}
|
||||
|
||||
// RTCPReader is used by Interceptor.BindRTCPReader.
|
||||
type RTCPReader interface {
|
||||
// Read a batch of rtcp packets
|
||||
Read([]byte, Attributes) (int, Attributes, error)
|
||||
}
|
||||
|
||||
// RTPWriterFunc is an adapter for RTPWrite interface
|
||||
type RTPWriterFunc func(header *rtp.Header, payload []byte, attributes Attributes) (int, error)
|
||||
|
||||
// RTPReaderFunc is an adapter for RTPReader interface
|
||||
type RTPReaderFunc func([]byte, Attributes) (int, Attributes, error)
|
||||
|
||||
// RTCPWriterFunc is an adapter for RTCPWriter interface
|
||||
type RTCPWriterFunc func(pkts []rtcp.Packet, attributes Attributes) (int, error)
|
||||
|
||||
// RTCPReaderFunc is an adapter for RTCPReader interface
|
||||
type RTCPReaderFunc func([]byte, Attributes) (int, Attributes, error)
|
||||
|
||||
// Write a rtp packet
|
||||
func (f RTPWriterFunc) Write(header *rtp.Header, payload []byte, attributes Attributes) (int, error) {
|
||||
return f(header, payload, attributes)
|
||||
}
|
||||
|
||||
// Read a rtp packet
|
||||
func (f RTPReaderFunc) Read(b []byte, a Attributes) (int, Attributes, error) {
|
||||
return f(b, a)
|
||||
}
|
||||
|
||||
// Write a batch of rtcp packets
|
||||
func (f RTCPWriterFunc) Write(pkts []rtcp.Packet, attributes Attributes) (int, error) {
|
||||
return f(pkts, attributes)
|
||||
}
|
||||
|
||||
// Read a batch of rtcp packets
|
||||
func (f RTCPReaderFunc) Read(b []byte, a Attributes) (int, Attributes, error) {
|
||||
return f(b, a)
|
||||
}
|
||||
30
server/vendor/github.com/pion/interceptor/internal/ntp/ntp.go
generated
vendored
Normal file
30
server/vendor/github.com/pion/interceptor/internal/ntp/ntp.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package ntp provides conversion methods between time.Time and NTP timestamps
|
||||
// stored in uint64
|
||||
package ntp
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ToNTP converts a time.Time oboject to an uint64 NTP timestamp
|
||||
func ToNTP(t time.Time) uint64 {
|
||||
// seconds since 1st January 1900
|
||||
s := (float64(t.UnixNano()) / 1000000000) + 2208988800
|
||||
|
||||
// higher 32 bits are the integer part, lower 32 bits are the fractional part
|
||||
integerPart := uint32(s)
|
||||
fractionalPart := uint32((s - float64(integerPart)) * 0xFFFFFFFF)
|
||||
return uint64(integerPart)<<32 | uint64(fractionalPart)
|
||||
}
|
||||
|
||||
// ToTime converts a uint64 NTP timestamps to a time.Time object
|
||||
func ToTime(t uint64) time.Time {
|
||||
seconds := (t & 0xFFFFFFFF00000000) >> 32
|
||||
fractional := float64(t&0x00000000FFFFFFFF) / float64(0xFFFFFFFF)
|
||||
d := time.Duration(seconds)*time.Second + time.Duration(fractional*1e9)*time.Nanosecond
|
||||
|
||||
return time.Unix(0, 0).Add(-2208988800 * time.Second).Add(d)
|
||||
}
|
||||
45
server/vendor/github.com/pion/interceptor/internal/sequencenumber/unwrapper.go
generated
vendored
Normal file
45
server/vendor/github.com/pion/interceptor/internal/sequencenumber/unwrapper.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package sequencenumber provides a sequence number unwrapper
|
||||
package sequencenumber
|
||||
|
||||
const (
|
||||
maxSequenceNumberPlusOne = int64(65536)
|
||||
breakpoint = 32768 // half of max uint16
|
||||
)
|
||||
|
||||
// Unwrapper stores an unwrapped sequence number
|
||||
type Unwrapper struct {
|
||||
init bool
|
||||
lastUnwrapped int64
|
||||
}
|
||||
|
||||
func isNewer(value, previous uint16) bool {
|
||||
if value-previous == breakpoint {
|
||||
return value > previous
|
||||
}
|
||||
return value != previous && (value-previous) < breakpoint
|
||||
}
|
||||
|
||||
// Unwrap unwraps the next sequencenumber
|
||||
func (u *Unwrapper) Unwrap(i uint16) int64 {
|
||||
if !u.init {
|
||||
u.init = true
|
||||
u.lastUnwrapped = int64(i)
|
||||
return u.lastUnwrapped
|
||||
}
|
||||
|
||||
lastWrapped := uint16(u.lastUnwrapped)
|
||||
delta := int64(i - lastWrapped)
|
||||
if isNewer(i, lastWrapped) {
|
||||
if delta < 0 {
|
||||
delta += maxSequenceNumberPlusOne
|
||||
}
|
||||
} else if delta > 0 && u.lastUnwrapped+delta-maxSequenceNumberPlusOne >= 0 {
|
||||
delta -= maxSequenceNumberPlusOne
|
||||
}
|
||||
|
||||
u.lastUnwrapped += delta
|
||||
return u.lastUnwrapped
|
||||
}
|
||||
43
server/vendor/github.com/pion/interceptor/noop.go
generated
vendored
Normal file
43
server/vendor/github.com/pion/interceptor/noop.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interceptor
|
||||
|
||||
// NoOp is an Interceptor that does not modify any packets. It can embedded in other interceptors, so it's
|
||||
// possible to implement only a subset of the methods.
|
||||
type NoOp struct{}
|
||||
|
||||
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
|
||||
// change in the future. The returned method will be called once per packet batch.
|
||||
func (i *NoOp) BindRTCPReader(reader RTCPReader) RTCPReader {
|
||||
return reader
|
||||
}
|
||||
|
||||
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
|
||||
// will be called once per packet batch.
|
||||
func (i *NoOp) BindRTCPWriter(writer RTCPWriter) RTCPWriter {
|
||||
return writer
|
||||
}
|
||||
|
||||
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (i *NoOp) BindLocalStream(_ *StreamInfo, writer RTPWriter) RTPWriter {
|
||||
return writer
|
||||
}
|
||||
|
||||
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (i *NoOp) UnbindLocalStream(_ *StreamInfo) {}
|
||||
|
||||
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (i *NoOp) BindRemoteStream(_ *StreamInfo, reader RTPReader) RTPReader {
|
||||
return reader
|
||||
}
|
||||
|
||||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (i *NoOp) UnbindRemoteStream(_ *StreamInfo) {}
|
||||
|
||||
// Close closes the Interceptor, cleaning up any data if necessary.
|
||||
func (i *NoOp) Close() error {
|
||||
return nil
|
||||
}
|
||||
15
server/vendor/github.com/pion/interceptor/pkg/nack/errors.go
generated
vendored
Normal file
15
server/vendor/github.com/pion/interceptor/pkg/nack/errors.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrInvalidSize is returned by newReceiveLog/newSendBuffer, when an incorrect buffer size is supplied.
|
||||
var ErrInvalidSize = errors.New("invalid buffer size")
|
||||
|
||||
var (
|
||||
errPacketReleased = errors.New("could not retain packet, already released")
|
||||
errFailedToCastHeaderPool = errors.New("could not access header pool, failed cast")
|
||||
errFailedToCastPayloadPool = errors.New("could not access payload pool, failed cast")
|
||||
)
|
||||
216
server/vendor/github.com/pion/interceptor/pkg/nack/generator_interceptor.go
generated
vendored
Normal file
216
server/vendor/github.com/pion/interceptor/pkg/nack/generator_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
)
|
||||
|
||||
// GeneratorInterceptorFactory is a interceptor.Factory for a GeneratorInterceptor
|
||||
type GeneratorInterceptorFactory struct {
|
||||
opts []GeneratorOption
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new ReceiverInterceptor
|
||||
func (g *GeneratorInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
i := &GeneratorInterceptor{
|
||||
size: 512,
|
||||
skipLastN: 0,
|
||||
maxNacksPerPacket: 0,
|
||||
interval: time.Millisecond * 100,
|
||||
receiveLogs: map[uint32]*receiveLog{},
|
||||
nackCountLogs: map[uint32]map[uint16]uint16{},
|
||||
close: make(chan struct{}),
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("nack_generator"),
|
||||
}
|
||||
|
||||
for _, opt := range g.opts {
|
||||
if err := opt(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := newReceiveLog(i.size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// GeneratorInterceptor interceptor generates nack feedback messages.
|
||||
type GeneratorInterceptor struct {
|
||||
interceptor.NoOp
|
||||
size uint16
|
||||
skipLastN uint16
|
||||
maxNacksPerPacket uint16
|
||||
interval time.Duration
|
||||
m sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
close chan struct{}
|
||||
log logging.LeveledLogger
|
||||
nackCountLogs map[uint32]map[uint16]uint16
|
||||
|
||||
receiveLogs map[uint32]*receiveLog
|
||||
receiveLogsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewGeneratorInterceptor returns a new GeneratorInterceptorFactory
|
||||
func NewGeneratorInterceptor(opts ...GeneratorOption) (*GeneratorInterceptorFactory, error) {
|
||||
return &GeneratorInterceptorFactory{opts}, nil
|
||||
}
|
||||
|
||||
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
|
||||
// will be called once per packet batch.
|
||||
func (n *GeneratorInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
|
||||
n.m.Lock()
|
||||
defer n.m.Unlock()
|
||||
|
||||
if n.isClosed() {
|
||||
return writer
|
||||
}
|
||||
|
||||
n.wg.Add(1)
|
||||
|
||||
go n.loop(writer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (n *GeneratorInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
|
||||
if !streamSupportNack(info) {
|
||||
return reader
|
||||
}
|
||||
|
||||
// error is already checked in NewGeneratorInterceptor
|
||||
receiveLog, _ := newReceiveLog(n.size)
|
||||
n.receiveLogsMu.Lock()
|
||||
n.receiveLogs[info.SSRC] = receiveLog
|
||||
n.receiveLogsMu.Unlock()
|
||||
|
||||
return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
|
||||
i, attr, err := reader.Read(b, a)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
attr = make(interceptor.Attributes)
|
||||
}
|
||||
header, err := attr.GetRTPHeader(b[:i])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
receiveLog.add(header.SequenceNumber)
|
||||
|
||||
return i, attr, nil
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (n *GeneratorInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
|
||||
n.receiveLogsMu.Lock()
|
||||
delete(n.receiveLogs, info.SSRC)
|
||||
n.receiveLogsMu.Unlock()
|
||||
}
|
||||
|
||||
// Close closes the interceptor
|
||||
func (n *GeneratorInterceptor) Close() error {
|
||||
defer n.wg.Wait()
|
||||
n.m.Lock()
|
||||
defer n.m.Unlock()
|
||||
|
||||
if !n.isClosed() {
|
||||
close(n.close)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint:gocognit
|
||||
func (n *GeneratorInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
|
||||
defer n.wg.Done()
|
||||
|
||||
senderSSRC := rand.Uint32() // #nosec
|
||||
|
||||
ticker := time.NewTicker(n.interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
func() {
|
||||
n.receiveLogsMu.Lock()
|
||||
defer n.receiveLogsMu.Unlock()
|
||||
|
||||
for ssrc, receiveLog := range n.receiveLogs {
|
||||
missing := receiveLog.missingSeqNumbers(n.skipLastN)
|
||||
|
||||
if len(missing) == 0 || n.nackCountLogs[ssrc] == nil {
|
||||
n.nackCountLogs[ssrc] = map[uint16]uint16{}
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredMissing := []uint16{}
|
||||
if n.maxNacksPerPacket > 0 {
|
||||
for _, missingSeq := range missing {
|
||||
if n.nackCountLogs[ssrc][missingSeq] < n.maxNacksPerPacket {
|
||||
filteredMissing = append(filteredMissing, missingSeq)
|
||||
}
|
||||
n.nackCountLogs[ssrc][missingSeq]++
|
||||
}
|
||||
} else {
|
||||
filteredMissing = missing
|
||||
}
|
||||
|
||||
nack := &rtcp.TransportLayerNack{
|
||||
SenderSSRC: senderSSRC,
|
||||
MediaSSRC: ssrc,
|
||||
Nacks: rtcp.NackPairsFromSequenceNumbers(filteredMissing),
|
||||
}
|
||||
|
||||
for nackSeq := range n.nackCountLogs[ssrc] {
|
||||
isMissing := false
|
||||
for _, missingSeq := range missing {
|
||||
if missingSeq == nackSeq {
|
||||
isMissing = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isMissing {
|
||||
delete(n.nackCountLogs[ssrc], nackSeq)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filteredMissing) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := rtcpWriter.Write([]rtcp.Packet{nack}, interceptor.Attributes{}); err != nil {
|
||||
n.log.Warnf("failed sending nack: %+v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
case <-n.close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *GeneratorInterceptor) isClosed() bool {
|
||||
select {
|
||||
case <-n.close:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
56
server/vendor/github.com/pion/interceptor/pkg/nack/generator_option.go
generated
vendored
Normal file
56
server/vendor/github.com/pion/interceptor/pkg/nack/generator_option.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
// GeneratorOption can be used to configure GeneratorInterceptor
|
||||
type GeneratorOption func(r *GeneratorInterceptor) error
|
||||
|
||||
// GeneratorSize sets the size of the interceptor.
|
||||
// Size must be one of: 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
|
||||
func GeneratorSize(size uint16) GeneratorOption {
|
||||
return func(r *GeneratorInterceptor) error {
|
||||
r.size = size
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratorSkipLastN sets the number of packets (n-1 packets before the last received packets) to ignore when generating
|
||||
// nack requests.
|
||||
func GeneratorSkipLastN(skipLastN uint16) GeneratorOption {
|
||||
return func(r *GeneratorInterceptor) error {
|
||||
r.skipLastN = skipLastN
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratorMaxNacksPerPacket sets the maximum number of NACKs sent per missing packet, e.g. if set to 2, a missing
|
||||
// packet will only be NACKed at most twice. If set to 0 (default), max number of NACKs is unlimited
|
||||
func GeneratorMaxNacksPerPacket(maxNacks uint16) GeneratorOption {
|
||||
return func(r *GeneratorInterceptor) error {
|
||||
r.maxNacksPerPacket = maxNacks
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratorLog sets a logger for the interceptor
|
||||
func GeneratorLog(log logging.LeveledLogger) GeneratorOption {
|
||||
return func(r *GeneratorInterceptor) error {
|
||||
r.log = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratorInterval sets the nack send interval for the interceptor
|
||||
func GeneratorInterval(interval time.Duration) GeneratorOption {
|
||||
return func(r *GeneratorInterceptor) error {
|
||||
r.interval = interval
|
||||
return nil
|
||||
}
|
||||
}
|
||||
17
server/vendor/github.com/pion/interceptor/pkg/nack/nack.go
generated
vendored
Normal file
17
server/vendor/github.com/pion/interceptor/pkg/nack/nack.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package nack provides interceptors to implement sending and receiving negative acknowledgements
|
||||
package nack
|
||||
|
||||
import "github.com/pion/interceptor"
|
||||
|
||||
func streamSupportNack(info *interceptor.StreamInfo) bool {
|
||||
for _, fb := range info.RTCPFeedback {
|
||||
if fb.Type == "nack" && fb.Parameter == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
138
server/vendor/github.com/pion/interceptor/pkg/nack/receive_log.go
generated
vendored
Normal file
138
server/vendor/github.com/pion/interceptor/pkg/nack/receive_log.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type receiveLog struct {
|
||||
packets []uint64
|
||||
size uint16
|
||||
end uint16
|
||||
started bool
|
||||
lastConsecutive uint16
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func newReceiveLog(size uint16) (*receiveLog, error) {
|
||||
allowedSizes := make([]uint16, 0)
|
||||
correctSize := false
|
||||
for i := 6; i < 16; i++ {
|
||||
if size == 1<<i {
|
||||
correctSize = true
|
||||
break
|
||||
}
|
||||
allowedSizes = append(allowedSizes, 1<<i)
|
||||
}
|
||||
|
||||
if !correctSize {
|
||||
return nil, fmt.Errorf("%w: %d is not a valid size, allowed sizes: %v", ErrInvalidSize, size, allowedSizes)
|
||||
}
|
||||
|
||||
return &receiveLog{
|
||||
packets: make([]uint64, size/64),
|
||||
size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *receiveLog) add(seq uint16) {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
if !s.started {
|
||||
s.setReceived(seq)
|
||||
s.end = seq
|
||||
s.started = true
|
||||
s.lastConsecutive = seq
|
||||
return
|
||||
}
|
||||
|
||||
diff := seq - s.end
|
||||
switch {
|
||||
case diff == 0:
|
||||
return
|
||||
case diff < uint16SizeHalf:
|
||||
// this means a positive diff, in other words seq > end (with counting for rollovers)
|
||||
for i := s.end + 1; i != seq; i++ {
|
||||
// clear packets between end and seq (these may contain packets from a "size" ago)
|
||||
s.delReceived(i)
|
||||
}
|
||||
s.end = seq
|
||||
|
||||
if s.lastConsecutive+1 == seq {
|
||||
s.lastConsecutive = seq
|
||||
} else if seq-s.lastConsecutive > s.size {
|
||||
s.lastConsecutive = seq - s.size
|
||||
s.fixLastConsecutive() // there might be valid packets at the beginning of the buffer now
|
||||
}
|
||||
case s.lastConsecutive+1 == seq:
|
||||
// negative diff, seq < end (with counting for rollovers)
|
||||
s.lastConsecutive = seq
|
||||
s.fixLastConsecutive() // there might be other valid packets after seq
|
||||
}
|
||||
|
||||
s.setReceived(seq)
|
||||
}
|
||||
|
||||
func (s *receiveLog) get(seq uint16) bool {
|
||||
s.m.RLock()
|
||||
defer s.m.RUnlock()
|
||||
|
||||
diff := s.end - seq
|
||||
if diff >= uint16SizeHalf {
|
||||
return false
|
||||
}
|
||||
|
||||
if diff >= s.size {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.getReceived(seq)
|
||||
}
|
||||
|
||||
func (s *receiveLog) missingSeqNumbers(skipLastN uint16) []uint16 {
|
||||
s.m.RLock()
|
||||
defer s.m.RUnlock()
|
||||
|
||||
until := s.end - skipLastN
|
||||
if until-s.lastConsecutive >= uint16SizeHalf {
|
||||
// until < s.lastConsecutive (counting for rollover)
|
||||
return nil
|
||||
}
|
||||
|
||||
missingPacketSeqNums := make([]uint16, 0)
|
||||
for i := s.lastConsecutive + 1; i != until+1; i++ {
|
||||
if !s.getReceived(i) {
|
||||
missingPacketSeqNums = append(missingPacketSeqNums, i)
|
||||
}
|
||||
}
|
||||
|
||||
return missingPacketSeqNums
|
||||
}
|
||||
|
||||
func (s *receiveLog) setReceived(seq uint16) {
|
||||
pos := seq % s.size
|
||||
s.packets[pos/64] |= 1 << (pos % 64)
|
||||
}
|
||||
|
||||
func (s *receiveLog) delReceived(seq uint16) {
|
||||
pos := seq % s.size
|
||||
s.packets[pos/64] &^= 1 << (pos % 64)
|
||||
}
|
||||
|
||||
func (s *receiveLog) getReceived(seq uint16) bool {
|
||||
pos := seq % s.size
|
||||
return (s.packets[pos/64] & (1 << (pos % 64))) != 0
|
||||
}
|
||||
|
||||
func (s *receiveLog) fixLastConsecutive() {
|
||||
i := s.lastConsecutive + 1
|
||||
for ; i != s.end+1 && s.getReceived(i); i++ { //nolint:revive
|
||||
// find all consecutive packets
|
||||
}
|
||||
|
||||
s.lastConsecutive = i - 1
|
||||
}
|
||||
149
server/vendor/github.com/pion/interceptor/pkg/nack/responder_interceptor.go
generated
vendored
Normal file
149
server/vendor/github.com/pion/interceptor/pkg/nack/responder_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// ResponderInterceptorFactory is a interceptor.Factory for a ResponderInterceptor
|
||||
type ResponderInterceptorFactory struct {
|
||||
opts []ResponderOption
|
||||
}
|
||||
|
||||
type packetFactory interface {
|
||||
NewPacket(header *rtp.Header, payload []byte) (*retainablePacket, error)
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new ResponderInterceptor
|
||||
func (r *ResponderInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
i := &ResponderInterceptor{
|
||||
size: 1024,
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("nack_responder"),
|
||||
streams: map[uint32]*localStream{},
|
||||
}
|
||||
|
||||
for _, opt := range r.opts {
|
||||
if err := opt(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i.packetFactory == nil {
|
||||
i.packetFactory = newPacketManager()
|
||||
}
|
||||
|
||||
if _, err := newSendBuffer(i.size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// ResponderInterceptor responds to nack feedback messages
|
||||
type ResponderInterceptor struct {
|
||||
interceptor.NoOp
|
||||
size uint16
|
||||
log logging.LeveledLogger
|
||||
packetFactory packetFactory
|
||||
|
||||
streams map[uint32]*localStream
|
||||
streamsMu sync.Mutex
|
||||
}
|
||||
|
||||
type localStream struct {
|
||||
sendBuffer *sendBuffer
|
||||
rtpWriter interceptor.RTPWriter
|
||||
}
|
||||
|
||||
// NewResponderInterceptor returns a new ResponderInterceptorFactor
|
||||
func NewResponderInterceptor(opts ...ResponderOption) (*ResponderInterceptorFactory, error) {
|
||||
return &ResponderInterceptorFactory{opts}, nil
|
||||
}
|
||||
|
||||
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
|
||||
// change in the future. The returned method will be called once per packet batch.
|
||||
func (n *ResponderInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
|
||||
return interceptor.RTCPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
|
||||
i, attr, err := reader.Read(b, a)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
attr = make(interceptor.Attributes)
|
||||
}
|
||||
pkts, err := attr.GetRTCPPackets(b[:i])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, rtcpPacket := range pkts {
|
||||
nack, ok := rtcpPacket.(*rtcp.TransportLayerNack)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
go n.resendPackets(nack)
|
||||
}
|
||||
|
||||
return i, attr, err
|
||||
})
|
||||
}
|
||||
|
||||
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (n *ResponderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
|
||||
if !streamSupportNack(info) {
|
||||
return writer
|
||||
}
|
||||
|
||||
// error is already checked in NewGeneratorInterceptor
|
||||
sendBuffer, _ := newSendBuffer(n.size)
|
||||
n.streamsMu.Lock()
|
||||
n.streams[info.SSRC] = &localStream{sendBuffer: sendBuffer, rtpWriter: writer}
|
||||
n.streamsMu.Unlock()
|
||||
|
||||
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
|
||||
pkt, err := n.packetFactory.NewPacket(header, payload)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sendBuffer.add(pkt)
|
||||
return writer.Write(header, payload, attributes)
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (n *ResponderInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
|
||||
n.streamsMu.Lock()
|
||||
delete(n.streams, info.SSRC)
|
||||
n.streamsMu.Unlock()
|
||||
}
|
||||
|
||||
func (n *ResponderInterceptor) resendPackets(nack *rtcp.TransportLayerNack) {
|
||||
n.streamsMu.Lock()
|
||||
stream, ok := n.streams[nack.MediaSSRC]
|
||||
n.streamsMu.Unlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range nack.Nacks {
|
||||
nack.Nacks[i].Range(func(seq uint16) bool {
|
||||
if p := stream.sendBuffer.get(seq); p != nil {
|
||||
if _, err := stream.rtpWriter.Write(p.Header(), p.Payload(), interceptor.Attributes{}); err != nil {
|
||||
n.log.Warnf("failed resending nacked packet: %+v", err)
|
||||
}
|
||||
p.Release()
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
35
server/vendor/github.com/pion/interceptor/pkg/nack/responder_option.go
generated
vendored
Normal file
35
server/vendor/github.com/pion/interceptor/pkg/nack/responder_option.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import "github.com/pion/logging"
|
||||
|
||||
// ResponderOption can be used to configure ResponderInterceptor
|
||||
type ResponderOption func(s *ResponderInterceptor) error
|
||||
|
||||
// ResponderSize sets the size of the interceptor.
|
||||
// Size must be one of: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
|
||||
func ResponderSize(size uint16) ResponderOption {
|
||||
return func(r *ResponderInterceptor) error {
|
||||
r.size = size
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ResponderLog sets a logger for the interceptor
|
||||
func ResponderLog(log logging.LeveledLogger) ResponderOption {
|
||||
return func(r *ResponderInterceptor) error {
|
||||
r.log = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DisableCopy bypasses copy of underlying packets. It should be used when
|
||||
// you are not re-using underlying buffers of packets that have been written
|
||||
func DisableCopy() ResponderOption {
|
||||
return func(s *ResponderInterceptor) error {
|
||||
s.packetFactory = &noOpPacketFactory{}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
132
server/vendor/github.com/pion/interceptor/pkg/nack/retainable_packet.go
generated
vendored
Normal file
132
server/vendor/github.com/pion/interceptor/pkg/nack/retainable_packet.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
const maxPayloadLen = 1460
|
||||
|
||||
type packetManager struct {
|
||||
headerPool *sync.Pool
|
||||
payloadPool *sync.Pool
|
||||
}
|
||||
|
||||
func newPacketManager() *packetManager {
|
||||
return &packetManager{
|
||||
headerPool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &rtp.Header{}
|
||||
},
|
||||
},
|
||||
payloadPool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
buf := make([]byte, maxPayloadLen)
|
||||
return &buf
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *packetManager) NewPacket(header *rtp.Header, payload []byte) (*retainablePacket, error) {
|
||||
if len(payload) > maxPayloadLen {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
p := &retainablePacket{
|
||||
onRelease: m.releasePacket,
|
||||
// new packets have retain count of 1
|
||||
count: 1,
|
||||
}
|
||||
|
||||
var ok bool
|
||||
p.header, ok = m.headerPool.Get().(*rtp.Header)
|
||||
if !ok {
|
||||
return nil, errFailedToCastHeaderPool
|
||||
}
|
||||
|
||||
*p.header = header.Clone()
|
||||
|
||||
if payload != nil {
|
||||
p.buffer, ok = m.payloadPool.Get().(*[]byte)
|
||||
if !ok {
|
||||
return nil, errFailedToCastPayloadPool
|
||||
}
|
||||
|
||||
size := copy(*p.buffer, payload)
|
||||
p.payload = (*p.buffer)[:size]
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *packetManager) releasePacket(header *rtp.Header, payload *[]byte) {
|
||||
m.headerPool.Put(header)
|
||||
if payload != nil {
|
||||
m.payloadPool.Put(payload)
|
||||
}
|
||||
}
|
||||
|
||||
type noOpPacketFactory struct{}
|
||||
|
||||
func (f *noOpPacketFactory) NewPacket(header *rtp.Header, payload []byte) (*retainablePacket, error) {
|
||||
return &retainablePacket{
|
||||
onRelease: f.releasePacket,
|
||||
count: 1,
|
||||
header: header,
|
||||
payload: payload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *noOpPacketFactory) releasePacket(_ *rtp.Header, _ *[]byte) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
type retainablePacket struct {
|
||||
onRelease func(*rtp.Header, *[]byte)
|
||||
|
||||
countMu sync.Mutex
|
||||
count int
|
||||
|
||||
header *rtp.Header
|
||||
buffer *[]byte
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (p *retainablePacket) Header() *rtp.Header {
|
||||
return p.header
|
||||
}
|
||||
|
||||
func (p *retainablePacket) Payload() []byte {
|
||||
return p.payload
|
||||
}
|
||||
|
||||
func (p *retainablePacket) Retain() error {
|
||||
p.countMu.Lock()
|
||||
defer p.countMu.Unlock()
|
||||
if p.count == 0 {
|
||||
// already released
|
||||
return errPacketReleased
|
||||
}
|
||||
p.count++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *retainablePacket) Release() {
|
||||
p.countMu.Lock()
|
||||
defer p.countMu.Unlock()
|
||||
p.count--
|
||||
|
||||
if p.count == 0 {
|
||||
// release back to pool
|
||||
p.onRelease(p.header, p.buffer)
|
||||
p.header = nil
|
||||
p.buffer = nil
|
||||
p.payload = nil
|
||||
}
|
||||
}
|
||||
104
server/vendor/github.com/pion/interceptor/pkg/nack/send_buffer.go
generated
vendored
Normal file
104
server/vendor/github.com/pion/interceptor/pkg/nack/send_buffer.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
uint16SizeHalf = 1 << 15
|
||||
)
|
||||
|
||||
type sendBuffer struct {
|
||||
packets []*retainablePacket
|
||||
size uint16
|
||||
lastAdded uint16
|
||||
started bool
|
||||
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func newSendBuffer(size uint16) (*sendBuffer, error) {
|
||||
allowedSizes := make([]uint16, 0)
|
||||
correctSize := false
|
||||
for i := 0; i < 16; i++ {
|
||||
if size == 1<<i {
|
||||
correctSize = true
|
||||
break
|
||||
}
|
||||
allowedSizes = append(allowedSizes, 1<<i)
|
||||
}
|
||||
|
||||
if !correctSize {
|
||||
return nil, fmt.Errorf("%w: %d is not a valid size, allowed sizes: %v", ErrInvalidSize, size, allowedSizes)
|
||||
}
|
||||
|
||||
return &sendBuffer{
|
||||
packets: make([]*retainablePacket, size),
|
||||
size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sendBuffer) add(packet *retainablePacket) {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
seq := packet.Header().SequenceNumber
|
||||
if !s.started {
|
||||
s.packets[seq%s.size] = packet
|
||||
s.lastAdded = seq
|
||||
s.started = true
|
||||
return
|
||||
}
|
||||
|
||||
diff := seq - s.lastAdded
|
||||
if diff == 0 {
|
||||
return
|
||||
} else if diff < uint16SizeHalf {
|
||||
for i := s.lastAdded + 1; i != seq; i++ {
|
||||
idx := i % s.size
|
||||
prevPacket := s.packets[idx]
|
||||
if prevPacket != nil {
|
||||
prevPacket.Release()
|
||||
}
|
||||
s.packets[idx] = nil
|
||||
}
|
||||
}
|
||||
|
||||
idx := seq % s.size
|
||||
prevPacket := s.packets[idx]
|
||||
if prevPacket != nil {
|
||||
prevPacket.Release()
|
||||
}
|
||||
s.packets[idx] = packet
|
||||
s.lastAdded = seq
|
||||
}
|
||||
|
||||
func (s *sendBuffer) get(seq uint16) *retainablePacket {
|
||||
s.m.RLock()
|
||||
defer s.m.RUnlock()
|
||||
|
||||
diff := s.lastAdded - seq
|
||||
if diff >= uint16SizeHalf {
|
||||
return nil
|
||||
}
|
||||
|
||||
if diff >= s.size {
|
||||
return nil
|
||||
}
|
||||
|
||||
pkt := s.packets[seq%s.size]
|
||||
if pkt != nil {
|
||||
if pkt.Header().SequenceNumber != seq {
|
||||
return nil
|
||||
}
|
||||
// already released
|
||||
if err := pkt.Retain(); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return pkt
|
||||
}
|
||||
184
server/vendor/github.com/pion/interceptor/pkg/report/receiver_interceptor.go
generated
vendored
Normal file
184
server/vendor/github.com/pion/interceptor/pkg/report/receiver_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
)
|
||||
|
||||
// ReceiverInterceptorFactory is a interceptor.Factory for a ReceiverInterceptor
|
||||
type ReceiverInterceptorFactory struct {
|
||||
opts []ReceiverOption
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new ReceiverInterceptor
|
||||
func (r *ReceiverInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
i := &ReceiverInterceptor{
|
||||
interval: 1 * time.Second,
|
||||
now: time.Now,
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("receiver_interceptor"),
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
|
||||
for _, opt := range r.opts {
|
||||
if err := opt(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// NewReceiverInterceptor returns a new ReceiverInterceptorFactory
|
||||
func NewReceiverInterceptor(opts ...ReceiverOption) (*ReceiverInterceptorFactory, error) {
|
||||
return &ReceiverInterceptorFactory{opts}, nil
|
||||
}
|
||||
|
||||
// ReceiverInterceptor interceptor generates receiver reports.
|
||||
type ReceiverInterceptor struct {
|
||||
interceptor.NoOp
|
||||
interval time.Duration
|
||||
now func() time.Time
|
||||
streams sync.Map
|
||||
log logging.LeveledLogger
|
||||
m sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
close chan struct{}
|
||||
}
|
||||
|
||||
func (r *ReceiverInterceptor) isClosed() bool {
|
||||
select {
|
||||
case <-r.close:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the interceptor.
|
||||
func (r *ReceiverInterceptor) Close() error {
|
||||
defer r.wg.Wait()
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
if !r.isClosed() {
|
||||
close(r.close)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
|
||||
// will be called once per packet batch.
|
||||
func (r *ReceiverInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
if r.isClosed() {
|
||||
return writer
|
||||
}
|
||||
|
||||
r.wg.Add(1)
|
||||
|
||||
go r.loop(writer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (r *ReceiverInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
|
||||
defer r.wg.Done()
|
||||
|
||||
ticker := time.NewTicker(r.interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
now := r.now()
|
||||
r.streams.Range(func(_, value interface{}) bool {
|
||||
if stream, ok := value.(*receiverStream); !ok {
|
||||
r.log.Warnf("failed to cast ReceiverInterceptor stream")
|
||||
} else if _, err := rtcpWriter.Write([]rtcp.Packet{stream.generateReport(now)}, interceptor.Attributes{}); err != nil {
|
||||
r.log.Warnf("failed sending: %+v", err)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
case <-r.close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (r *ReceiverInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
|
||||
stream := newReceiverStream(info.SSRC, info.ClockRate)
|
||||
r.streams.Store(info.SSRC, stream)
|
||||
|
||||
return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
|
||||
i, attr, err := reader.Read(b, a)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
attr = make(interceptor.Attributes)
|
||||
}
|
||||
header, err := attr.GetRTPHeader(b[:i])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
stream.processRTP(r.now(), header)
|
||||
|
||||
return i, attr, nil
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (r *ReceiverInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
|
||||
r.streams.Delete(info.SSRC)
|
||||
}
|
||||
|
||||
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
|
||||
// change in the future. The returned method will be called once per packet batch.
|
||||
func (r *ReceiverInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
|
||||
return interceptor.RTCPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
|
||||
i, attr, err := reader.Read(b, a)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
attr = make(interceptor.Attributes)
|
||||
}
|
||||
pkts, err := attr.GetRTCPPackets(b[:i])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
for _, pkt := range pkts {
|
||||
if sr, ok := (pkt).(*rtcp.SenderReport); ok {
|
||||
value, ok := r.streams.Load(sr.SSRC)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if stream, ok := value.(*receiverStream); !ok {
|
||||
r.log.Warnf("failed to cast ReceiverInterceptor stream")
|
||||
} else {
|
||||
stream.processSenderReport(r.now(), sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i, attr, nil
|
||||
})
|
||||
}
|
||||
37
server/vendor/github.com/pion/interceptor/pkg/report/receiver_option.go
generated
vendored
Normal file
37
server/vendor/github.com/pion/interceptor/pkg/report/receiver_option.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
// ReceiverOption can be used to configure ReceiverInterceptor.
|
||||
type ReceiverOption func(r *ReceiverInterceptor) error
|
||||
|
||||
// ReceiverLog sets a logger for the interceptor.
|
||||
func ReceiverLog(log logging.LeveledLogger) ReceiverOption {
|
||||
return func(r *ReceiverInterceptor) error {
|
||||
r.log = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReceiverInterval sets send interval for the interceptor.
|
||||
func ReceiverInterval(interval time.Duration) ReceiverOption {
|
||||
return func(r *ReceiverInterceptor) error {
|
||||
r.interval = interval
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReceiverNow sets an alternative for the time.Now function.
|
||||
func ReceiverNow(f func() time.Time) ReceiverOption {
|
||||
return func(r *ReceiverInterceptor) error {
|
||||
r.now = f
|
||||
return nil
|
||||
}
|
||||
}
|
||||
169
server/vendor/github.com/pion/interceptor/pkg/report/receiver_stream.go
generated
vendored
Normal file
169
server/vendor/github.com/pion/interceptor/pkg/report/receiver_stream.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
const (
|
||||
// packetsPerHistoryEntry represents how many packets are in the bitmask for
|
||||
// each entry in the `packets` slice in the receiver stream. Because we use
|
||||
// a uint64, we can keep track of 64 packets per entry.
|
||||
packetsPerHistoryEntry = 64
|
||||
)
|
||||
|
||||
type receiverStream struct {
|
||||
ssrc uint32
|
||||
receiverSSRC uint32
|
||||
clockRate float64
|
||||
|
||||
m sync.Mutex
|
||||
size uint16
|
||||
packets []uint64
|
||||
started bool
|
||||
seqnumCycles uint16
|
||||
lastSeqnum uint16
|
||||
lastReportSeqnum uint16
|
||||
lastRTPTimeRTP uint32
|
||||
lastRTPTimeTime time.Time
|
||||
jitter float64
|
||||
lastSenderReport uint32
|
||||
lastSenderReportTime time.Time
|
||||
totalLost uint32
|
||||
}
|
||||
|
||||
func newReceiverStream(ssrc uint32, clockRate uint32) *receiverStream {
|
||||
receiverSSRC := rand.Uint32() // #nosec
|
||||
return &receiverStream{
|
||||
ssrc: ssrc,
|
||||
receiverSSRC: receiverSSRC,
|
||||
clockRate: float64(clockRate),
|
||||
size: 128,
|
||||
packets: make([]uint64, 128),
|
||||
}
|
||||
}
|
||||
|
||||
func (stream *receiverStream) processRTP(now time.Time, pktHeader *rtp.Header) {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
if !stream.started { // first frame
|
||||
stream.started = true
|
||||
stream.setReceived(pktHeader.SequenceNumber)
|
||||
stream.lastSeqnum = pktHeader.SequenceNumber
|
||||
stream.lastReportSeqnum = pktHeader.SequenceNumber - 1
|
||||
stream.lastRTPTimeRTP = pktHeader.Timestamp
|
||||
stream.lastRTPTimeTime = now
|
||||
} else { // following frames
|
||||
stream.setReceived(pktHeader.SequenceNumber)
|
||||
|
||||
diff := pktHeader.SequenceNumber - stream.lastSeqnum
|
||||
if diff > 0 && diff < (1<<15) {
|
||||
// wrap around
|
||||
if pktHeader.SequenceNumber < stream.lastSeqnum {
|
||||
stream.seqnumCycles++
|
||||
}
|
||||
|
||||
// set missing packets as missing
|
||||
for i := stream.lastSeqnum + 1; i != pktHeader.SequenceNumber; i++ {
|
||||
stream.delReceived(i)
|
||||
}
|
||||
|
||||
stream.lastSeqnum = pktHeader.SequenceNumber
|
||||
}
|
||||
|
||||
// compute jitter
|
||||
// https://tools.ietf.org/html/rfc3550#page-39
|
||||
D := now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate -
|
||||
(float64(pktHeader.Timestamp) - float64(stream.lastRTPTimeRTP))
|
||||
if D < 0 {
|
||||
D = -D
|
||||
}
|
||||
stream.jitter += (D - stream.jitter) / 16
|
||||
stream.lastRTPTimeRTP = pktHeader.Timestamp
|
||||
stream.lastRTPTimeTime = now
|
||||
}
|
||||
}
|
||||
|
||||
func (stream *receiverStream) setReceived(seq uint16) {
|
||||
pos := seq % (stream.size * packetsPerHistoryEntry)
|
||||
stream.packets[pos/packetsPerHistoryEntry] |= 1 << (pos % packetsPerHistoryEntry)
|
||||
}
|
||||
|
||||
func (stream *receiverStream) delReceived(seq uint16) {
|
||||
pos := seq % (stream.size * packetsPerHistoryEntry)
|
||||
stream.packets[pos/packetsPerHistoryEntry] &^= 1 << (pos % packetsPerHistoryEntry)
|
||||
}
|
||||
|
||||
func (stream *receiverStream) getReceived(seq uint16) bool {
|
||||
pos := seq % (stream.size * packetsPerHistoryEntry)
|
||||
return (stream.packets[pos/packetsPerHistoryEntry] & (1 << (pos % packetsPerHistoryEntry))) != 0
|
||||
}
|
||||
|
||||
func (stream *receiverStream) processSenderReport(now time.Time, sr *rtcp.SenderReport) {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
stream.lastSenderReport = uint32(sr.NTPTime >> 16)
|
||||
stream.lastSenderReportTime = now
|
||||
}
|
||||
|
||||
func (stream *receiverStream) generateReport(now time.Time) *rtcp.ReceiverReport {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
totalSinceReport := stream.lastSeqnum - stream.lastReportSeqnum
|
||||
totalLostSinceReport := func() uint32 {
|
||||
if stream.lastSeqnum == stream.lastReportSeqnum {
|
||||
return 0
|
||||
}
|
||||
|
||||
ret := uint32(0)
|
||||
for i := stream.lastReportSeqnum + 1; i != stream.lastSeqnum; i++ {
|
||||
if !stream.getReceived(i) {
|
||||
ret++
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}()
|
||||
stream.totalLost += totalLostSinceReport
|
||||
|
||||
// allow up to 24 bits
|
||||
if totalLostSinceReport > 0xFFFFFF {
|
||||
totalLostSinceReport = 0xFFFFFF
|
||||
}
|
||||
if stream.totalLost > 0xFFFFFF {
|
||||
stream.totalLost = 0xFFFFFF
|
||||
}
|
||||
|
||||
r := &rtcp.ReceiverReport{
|
||||
SSRC: stream.receiverSSRC,
|
||||
Reports: []rtcp.ReceptionReport{
|
||||
{
|
||||
SSRC: stream.ssrc,
|
||||
LastSequenceNumber: uint32(stream.seqnumCycles)<<16 | uint32(stream.lastSeqnum),
|
||||
LastSenderReport: stream.lastSenderReport,
|
||||
FractionLost: uint8(float64(totalLostSinceReport*256) / float64(totalSinceReport)),
|
||||
TotalLost: stream.totalLost,
|
||||
Delay: func() uint32 {
|
||||
if stream.lastSenderReportTime.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return uint32(now.Sub(stream.lastSenderReportTime).Seconds() * 65536)
|
||||
}(),
|
||||
Jitter: uint32(stream.jitter),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
stream.lastReportSeqnum = stream.lastSeqnum
|
||||
|
||||
return r
|
||||
}
|
||||
5
server/vendor/github.com/pion/interceptor/pkg/report/report.go
generated
vendored
Normal file
5
server/vendor/github.com/pion/interceptor/pkg/report/report.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package report provides interceptors to implement sending sender and receiver reports.
|
||||
package report
|
||||
151
server/vendor/github.com/pion/interceptor/pkg/report/sender_interceptor.go
generated
vendored
Normal file
151
server/vendor/github.com/pion/interceptor/pkg/report/sender_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// TickerFactory is a factory to create new tickers
|
||||
type TickerFactory func(d time.Duration) Ticker
|
||||
|
||||
// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor
|
||||
type SenderInterceptorFactory struct {
|
||||
opts []SenderOption
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new SenderInterceptor
|
||||
func (s *SenderInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
i := &SenderInterceptor{
|
||||
interval: 1 * time.Second,
|
||||
now: time.Now,
|
||||
newTicker: func(d time.Duration) Ticker {
|
||||
return &timeTicker{time.NewTicker(d)}
|
||||
},
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("sender_interceptor"),
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
|
||||
for _, opt := range s.opts {
|
||||
if err := opt(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// NewSenderInterceptor returns a new SenderInterceptorFactory
|
||||
func NewSenderInterceptor(opts ...SenderOption) (*SenderInterceptorFactory, error) {
|
||||
return &SenderInterceptorFactory{opts}, nil
|
||||
}
|
||||
|
||||
// SenderInterceptor interceptor generates sender reports.
|
||||
type SenderInterceptor struct {
|
||||
interceptor.NoOp
|
||||
interval time.Duration
|
||||
now func() time.Time
|
||||
newTicker TickerFactory
|
||||
streams sync.Map
|
||||
log logging.LeveledLogger
|
||||
m sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
close chan struct{}
|
||||
started chan struct{}
|
||||
|
||||
useLatestPacket bool
|
||||
}
|
||||
|
||||
func (s *SenderInterceptor) isClosed() bool {
|
||||
select {
|
||||
case <-s.close:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the interceptor.
|
||||
func (s *SenderInterceptor) Close() error {
|
||||
defer s.wg.Wait()
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
if !s.isClosed() {
|
||||
close(s.close)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
|
||||
// will be called once per packet batch.
|
||||
func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
if s.isClosed() {
|
||||
return writer
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
|
||||
go s.loop(writer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (s *SenderInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
|
||||
defer s.wg.Done()
|
||||
|
||||
ticker := s.newTicker(s.interval)
|
||||
defer ticker.Stop()
|
||||
if s.started != nil {
|
||||
// This lets us synchronize in tests to know whether the loop has begun or not.
|
||||
// It only happens if started was initialized, which should not occur in non-tests.
|
||||
close(s.started)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ticker.Ch():
|
||||
now := s.now()
|
||||
s.streams.Range(func(_, value interface{}) bool {
|
||||
if stream, ok := value.(*senderStream); !ok {
|
||||
s.log.Warnf("failed to cast SenderInterceptor stream")
|
||||
} else if _, err := rtcpWriter.Write([]rtcp.Packet{stream.generateReport(now)}, interceptor.Attributes{}); err != nil {
|
||||
s.log.Warnf("failed sending: %+v", err)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
case <-s.close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (s *SenderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
|
||||
stream := newSenderStream(info.SSRC, info.ClockRate, s.useLatestPacket)
|
||||
s.streams.Store(info.SSRC, stream)
|
||||
|
||||
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, a interceptor.Attributes) (int, error) {
|
||||
stream.processRTP(s.now(), header, payload)
|
||||
|
||||
return writer.Write(header, payload, a)
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (s *SenderInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
|
||||
s.streams.Delete(info.SSRC)
|
||||
}
|
||||
63
server/vendor/github.com/pion/interceptor/pkg/report/sender_option.go
generated
vendored
Normal file
63
server/vendor/github.com/pion/interceptor/pkg/report/sender_option.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
// SenderOption can be used to configure SenderInterceptor.
|
||||
type SenderOption func(r *SenderInterceptor) error
|
||||
|
||||
// SenderLog sets a logger for the interceptor.
|
||||
func SenderLog(log logging.LeveledLogger) SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.log = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SenderInterval sets send interval for the interceptor.
|
||||
func SenderInterval(interval time.Duration) SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.interval = interval
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SenderNow sets an alternative for the time.Now function.
|
||||
func SenderNow(f func() time.Time) SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.now = f
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SenderTicker sets an alternative for the time.NewTicker function.
|
||||
func SenderTicker(f TickerFactory) SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.newTicker = f
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SenderUseLatestPacket sets the interceptor to always use the latest packet, even
|
||||
// if it appears to be out-of-order.
|
||||
func SenderUseLatestPacket() SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.useLatestPacket = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// enableStartTracking is used by tests to synchronize whether the loop() has begun
|
||||
// and it's safe to start sending ticks to the ticker.
|
||||
func enableStartTracking(startedCh chan struct{}) SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.started = startedCh
|
||||
return nil
|
||||
}
|
||||
}
|
||||
65
server/vendor/github.com/pion/interceptor/pkg/report/sender_stream.go
generated
vendored
Normal file
65
server/vendor/github.com/pion/interceptor/pkg/report/sender_stream.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor/internal/ntp"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type senderStream struct {
|
||||
ssrc uint32
|
||||
clockRate float64
|
||||
m sync.Mutex
|
||||
|
||||
useLatestPacket bool
|
||||
|
||||
// data from rtp packets
|
||||
lastRTPTimeRTP uint32
|
||||
lastRTPTimeTime time.Time
|
||||
lastRTPSN uint16
|
||||
packetCount uint32
|
||||
octetCount uint32
|
||||
}
|
||||
|
||||
func newSenderStream(ssrc uint32, clockRate uint32, useLatestPacket bool) *senderStream {
|
||||
return &senderStream{
|
||||
ssrc: ssrc,
|
||||
clockRate: float64(clockRate),
|
||||
useLatestPacket: useLatestPacket,
|
||||
}
|
||||
}
|
||||
|
||||
func (stream *senderStream) processRTP(now time.Time, header *rtp.Header, payload []byte) {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
diff := header.SequenceNumber - stream.lastRTPSN
|
||||
if stream.useLatestPacket || stream.packetCount == 0 || (diff > 0 && diff < (1<<15)) {
|
||||
// Told to consider every packet, or this was the first packet, or it's in-order
|
||||
stream.lastRTPSN = header.SequenceNumber
|
||||
stream.lastRTPTimeRTP = header.Timestamp
|
||||
stream.lastRTPTimeTime = now
|
||||
}
|
||||
|
||||
stream.packetCount++
|
||||
stream.octetCount += uint32(len(payload))
|
||||
}
|
||||
|
||||
func (stream *senderStream) generateReport(now time.Time) *rtcp.SenderReport {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
return &rtcp.SenderReport{
|
||||
SSRC: stream.ssrc,
|
||||
NTPTime: ntp.ToNTP(now),
|
||||
RTPTime: stream.lastRTPTimeRTP + uint32(now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate),
|
||||
PacketCount: stream.packetCount,
|
||||
OctetCount: stream.octetCount,
|
||||
}
|
||||
}
|
||||
20
server/vendor/github.com/pion/interceptor/pkg/report/ticker.go
generated
vendored
Normal file
20
server/vendor/github.com/pion/interceptor/pkg/report/ticker.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import "time"
|
||||
|
||||
// Ticker is an interface for *time.Ticker for use with the SenderTicker option.
|
||||
type Ticker interface {
|
||||
Ch() <-chan time.Time
|
||||
Stop()
|
||||
}
|
||||
|
||||
type timeTicker struct {
|
||||
*time.Ticker
|
||||
}
|
||||
|
||||
func (t *timeTicker) Ch() <-chan time.Time {
|
||||
return t.C
|
||||
}
|
||||
192
server/vendor/github.com/pion/interceptor/pkg/twcc/arrival_time_map.go
generated
vendored
Normal file
192
server/vendor/github.com/pion/interceptor/pkg/twcc/arrival_time_map.go
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package twcc
|
||||
|
||||
const (
|
||||
minCapacity = 128
|
||||
maxNumberOfPackets = 1 << 15
|
||||
)
|
||||
|
||||
// packetArrivalTimeMap is adapted from Chrome's implementation of TWCC, and keeps track
|
||||
// of the arrival times of packets. It is used by the TWCC interceptor to build feedback
|
||||
// packets.
|
||||
// See https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/webrtc/modules/remote_bitrate_estimator/packet_arrival_map.h;drc=b5cd13bb6d5d157a5fbe3628b2dd1c1e106203c6
|
||||
type packetArrivalTimeMap struct {
|
||||
// arrivalTimes is a circular buffer, where the packet with sequence number sn is stored
|
||||
// in slot sn % len(arrivalTimes)
|
||||
arrivalTimes []int64
|
||||
|
||||
// The unwrapped sequence numbers for the range of valid sequence numbers in arrivalTimes.
|
||||
// beginSequenceNumber is inclusive, and endSequenceNumber is exclusive.
|
||||
beginSequenceNumber, endSequenceNumber int64
|
||||
}
|
||||
|
||||
// AddPacket records the fact that the packet with sequence number sequenceNumber arrived
|
||||
// at arrivalTime.
|
||||
func (m *packetArrivalTimeMap) AddPacket(sequenceNumber int64, arrivalTime int64) {
|
||||
if m.arrivalTimes == nil {
|
||||
// First packet
|
||||
m.reallocate(minCapacity)
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
m.endSequenceNumber = sequenceNumber + 1
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
return
|
||||
}
|
||||
|
||||
if sequenceNumber >= m.beginSequenceNumber && sequenceNumber < m.endSequenceNumber {
|
||||
// The packet is within the buffer, no need to resize.
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
return
|
||||
}
|
||||
|
||||
if sequenceNumber < m.beginSequenceNumber {
|
||||
// The packet goes before the current buffer. Expand to add packet,
|
||||
// but only if it fits within the maximum number of packets.
|
||||
newSize := int(m.endSequenceNumber - sequenceNumber)
|
||||
if newSize > maxNumberOfPackets {
|
||||
// Don't expand the buffer back for this packet, as it would remove newer received
|
||||
// packets.
|
||||
return
|
||||
}
|
||||
m.adjustToSize(newSize)
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
m.setNotReceived(sequenceNumber+1, m.beginSequenceNumber)
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
return
|
||||
}
|
||||
|
||||
// The packet goes after the buffer.
|
||||
newEndSequenceNumber := sequenceNumber + 1
|
||||
|
||||
if newEndSequenceNumber >= m.endSequenceNumber+maxNumberOfPackets {
|
||||
// All old packets have to be removed.
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
m.endSequenceNumber = newEndSequenceNumber
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
return
|
||||
}
|
||||
|
||||
if m.beginSequenceNumber < newEndSequenceNumber-maxNumberOfPackets {
|
||||
// Remove oldest entries.
|
||||
m.beginSequenceNumber = newEndSequenceNumber - maxNumberOfPackets
|
||||
}
|
||||
|
||||
m.adjustToSize(int(newEndSequenceNumber - m.beginSequenceNumber))
|
||||
|
||||
// Packets can be received out of order. If this isn't the next expected packet,
|
||||
// add enough placeholders to fill the gap.
|
||||
m.setNotReceived(m.endSequenceNumber, sequenceNumber)
|
||||
m.endSequenceNumber = newEndSequenceNumber
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) setNotReceived(startInclusive, endExclusive int64) {
|
||||
for sn := startInclusive; sn < endExclusive; sn++ {
|
||||
m.arrivalTimes[m.index(sn)] = -1
|
||||
}
|
||||
}
|
||||
|
||||
// BeginSequenceNumber returns the first valid sequence number in the map.
|
||||
func (m *packetArrivalTimeMap) BeginSequenceNumber() int64 {
|
||||
return m.beginSequenceNumber
|
||||
}
|
||||
|
||||
// EndSequenceNumber returns the first sequence number after the last valid sequence number in the map.
|
||||
func (m *packetArrivalTimeMap) EndSequenceNumber() int64 {
|
||||
return m.endSequenceNumber
|
||||
}
|
||||
|
||||
// FindNextAtOrAfter returns the sequence number and timestamp of the first received packet that has a sequence number
|
||||
// greator or equal to sequenceNumber.
|
||||
func (m *packetArrivalTimeMap) FindNextAtOrAfter(sequenceNumber int64) (foundSequenceNumber int64, arrivalTime int64, ok bool) {
|
||||
for sequenceNumber = m.Clamp(sequenceNumber); sequenceNumber < m.endSequenceNumber; sequenceNumber++ {
|
||||
if t := m.get(sequenceNumber); t >= 0 {
|
||||
return sequenceNumber, t, true
|
||||
}
|
||||
}
|
||||
return -1, -1, false
|
||||
}
|
||||
|
||||
// EraseTo erases all elements from the beginning of the map until sequenceNumber.
|
||||
func (m *packetArrivalTimeMap) EraseTo(sequenceNumber int64) {
|
||||
if sequenceNumber < m.beginSequenceNumber {
|
||||
return
|
||||
}
|
||||
if sequenceNumber >= m.endSequenceNumber {
|
||||
// Erase all.
|
||||
m.beginSequenceNumber = m.endSequenceNumber
|
||||
return
|
||||
}
|
||||
// Remove some
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
m.adjustToSize(int(m.endSequenceNumber - m.beginSequenceNumber))
|
||||
}
|
||||
|
||||
// RemoveOldPackets removes packets from the beginning of the map as long as they are before
|
||||
// sequenceNumber and with an age older than arrivalTimeLimit.
|
||||
func (m *packetArrivalTimeMap) RemoveOldPackets(sequenceNumber int64, arrivalTimeLimit int64) {
|
||||
checkTo := min64(sequenceNumber, m.endSequenceNumber)
|
||||
for m.beginSequenceNumber < checkTo && m.get(m.beginSequenceNumber) <= arrivalTimeLimit {
|
||||
m.beginSequenceNumber++
|
||||
}
|
||||
m.adjustToSize(int(m.endSequenceNumber - m.beginSequenceNumber))
|
||||
}
|
||||
|
||||
// HasReceived returns whether a packet with the sequence number has been received.
|
||||
func (m *packetArrivalTimeMap) HasReceived(sequenceNumber int64) bool {
|
||||
return m.get(sequenceNumber) >= 0
|
||||
}
|
||||
|
||||
// Clamp returns sequenceNumber clamped to [beginSequenceNumber, endSequenceNumber]
|
||||
func (m *packetArrivalTimeMap) Clamp(sequenceNumber int64) int64 {
|
||||
if sequenceNumber < m.beginSequenceNumber {
|
||||
return m.beginSequenceNumber
|
||||
}
|
||||
if m.endSequenceNumber < sequenceNumber {
|
||||
return m.endSequenceNumber
|
||||
}
|
||||
return sequenceNumber
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) get(sequenceNumber int64) int64 {
|
||||
if sequenceNumber < m.beginSequenceNumber || sequenceNumber >= m.endSequenceNumber {
|
||||
return -1
|
||||
}
|
||||
return m.arrivalTimes[m.index(sequenceNumber)]
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) index(sequenceNumber int64) int {
|
||||
// Sequence number might be negative, and we always guarantee that arrivalTimes
|
||||
// length is a power of 2, so it's easier to use "&" instead of "%"
|
||||
return int(sequenceNumber & int64(m.capacity()-1))
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) adjustToSize(newSize int) {
|
||||
if newSize > m.capacity() {
|
||||
newCapacity := m.capacity()
|
||||
for newCapacity < newSize {
|
||||
newCapacity *= 2
|
||||
}
|
||||
m.reallocate(newCapacity)
|
||||
}
|
||||
if m.capacity() > max(minCapacity, newSize*4) {
|
||||
newCapacity := m.capacity()
|
||||
for newCapacity >= 2*max(newSize, minCapacity) {
|
||||
newCapacity /= 2
|
||||
}
|
||||
m.reallocate(newCapacity)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) capacity() int {
|
||||
return len(m.arrivalTimes)
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) reallocate(newCapacity int) {
|
||||
newBuffer := make([]int64, newCapacity)
|
||||
for sn := m.beginSequenceNumber; sn < m.endSequenceNumber; sn++ {
|
||||
newBuffer[int(sn&(int64(newCapacity-1)))] = m.get(sn)
|
||||
}
|
||||
m.arrivalTimes = newBuffer
|
||||
}
|
||||
66
server/vendor/github.com/pion/interceptor/pkg/twcc/header_extension_interceptor.go
generated
vendored
Normal file
66
server/vendor/github.com/pion/interceptor/pkg/twcc/header_extension_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package twcc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
var errHeaderIsNil = errors.New("header is nil")
|
||||
|
||||
// HeaderExtensionInterceptorFactory is a interceptor.Factory for a HeaderExtensionInterceptor
|
||||
type HeaderExtensionInterceptorFactory struct{}
|
||||
|
||||
// NewInterceptor constructs a new HeaderExtensionInterceptor
|
||||
func (h *HeaderExtensionInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
return &HeaderExtensionInterceptor{}, nil
|
||||
}
|
||||
|
||||
// NewHeaderExtensionInterceptor returns a HeaderExtensionInterceptorFactory
|
||||
func NewHeaderExtensionInterceptor() (*HeaderExtensionInterceptorFactory, error) {
|
||||
return &HeaderExtensionInterceptorFactory{}, nil
|
||||
}
|
||||
|
||||
// HeaderExtensionInterceptor adds transport wide sequence numbers as header extension to each RTP packet
|
||||
type HeaderExtensionInterceptor struct {
|
||||
interceptor.NoOp
|
||||
nextSequenceNr uint32
|
||||
}
|
||||
|
||||
const transportCCURI = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
|
||||
|
||||
// BindLocalStream returns a writer that adds a rtp.TransportCCExtension
|
||||
// header with increasing sequence numbers to each outgoing packet.
|
||||
func (h *HeaderExtensionInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
|
||||
var hdrExtID uint8
|
||||
for _, e := range info.RTPHeaderExtensions {
|
||||
if e.URI == transportCCURI {
|
||||
hdrExtID = uint8(e.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
if hdrExtID == 0 { // Don't add header extension if ID is 0, because 0 is an invalid extension ID
|
||||
return writer
|
||||
}
|
||||
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
|
||||
sequenceNumber := atomic.AddUint32(&h.nextSequenceNr, 1) - 1
|
||||
|
||||
tcc, err := (&rtp.TransportCCExtension{TransportSequence: uint16(sequenceNumber)}).Marshal()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if header == nil {
|
||||
return 0, errHeaderIsNil
|
||||
}
|
||||
err = header.SetExtension(hdrExtID, tcc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return writer.Write(header, payload, attributes)
|
||||
})
|
||||
}
|
||||
207
server/vendor/github.com/pion/interceptor/pkg/twcc/sender_interceptor.go
generated
vendored
Normal file
207
server/vendor/github.com/pion/interceptor/pkg/twcc/sender_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package twcc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor
|
||||
type SenderInterceptorFactory struct {
|
||||
opts []Option
|
||||
}
|
||||
|
||||
var errClosed = errors.New("interceptor is closed")
|
||||
|
||||
// NewInterceptor constructs a new SenderInterceptor
|
||||
func (s *SenderInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
i := &SenderInterceptor{
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("twcc_sender_interceptor"),
|
||||
packetChan: make(chan packet),
|
||||
close: make(chan struct{}),
|
||||
interval: 100 * time.Millisecond,
|
||||
startTime: time.Now(),
|
||||
}
|
||||
|
||||
for _, opt := range s.opts {
|
||||
err := opt(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// NewSenderInterceptor returns a new SenderInterceptorFactory configured with the given options.
|
||||
func NewSenderInterceptor(opts ...Option) (*SenderInterceptorFactory, error) {
|
||||
return &SenderInterceptorFactory{opts: opts}, nil
|
||||
}
|
||||
|
||||
// SenderInterceptor sends transport wide congestion control reports as specified in:
|
||||
// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
type SenderInterceptor struct {
|
||||
interceptor.NoOp
|
||||
|
||||
log logging.LeveledLogger
|
||||
|
||||
m sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
close chan struct{}
|
||||
|
||||
interval time.Duration
|
||||
startTime time.Time
|
||||
|
||||
recorder *Recorder
|
||||
packetChan chan packet
|
||||
}
|
||||
|
||||
// An Option is a function that can be used to configure a SenderInterceptor
|
||||
type Option func(*SenderInterceptor) error
|
||||
|
||||
// SendInterval sets the interval at which the interceptor
|
||||
// will send new feedback reports.
|
||||
func SendInterval(interval time.Duration) Option {
|
||||
return func(s *SenderInterceptor) error {
|
||||
s.interval = interval
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
|
||||
// will be called once per packet batch.
|
||||
func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
s.recorder = NewRecorder(rand.Uint32()) // #nosec
|
||||
|
||||
if s.isClosed() {
|
||||
return writer
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
|
||||
go s.loop(writer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
type packet struct {
|
||||
hdr *rtp.Header
|
||||
sequenceNumber uint16
|
||||
arrivalTime int64
|
||||
ssrc uint32
|
||||
}
|
||||
|
||||
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
|
||||
// will be called once per rtp packet.
|
||||
func (s *SenderInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
|
||||
var hdrExtID uint8
|
||||
for _, e := range info.RTPHeaderExtensions {
|
||||
if e.URI == transportCCURI {
|
||||
hdrExtID = uint8(e.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
if hdrExtID == 0 { // Don't try to read header extension if ID is 0, because 0 is an invalid extension ID
|
||||
return reader
|
||||
}
|
||||
return interceptor.RTPReaderFunc(func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
|
||||
i, attr, err := reader.Read(buf, attributes)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
attr = make(interceptor.Attributes)
|
||||
}
|
||||
header, err := attr.GetRTPHeader(buf[:i])
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var tccExt rtp.TransportCCExtension
|
||||
if ext := header.GetExtension(hdrExtID); ext != nil {
|
||||
err = tccExt.Unmarshal(ext)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
p := packet{
|
||||
hdr: header,
|
||||
sequenceNumber: tccExt.TransportSequence,
|
||||
arrivalTime: time.Since(s.startTime).Microseconds(),
|
||||
ssrc: info.SSRC,
|
||||
}
|
||||
select {
|
||||
case <-s.close:
|
||||
return 0, nil, errClosed
|
||||
case s.packetChan <- p:
|
||||
}
|
||||
}
|
||||
|
||||
return i, attr, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Close closes the interceptor.
|
||||
func (s *SenderInterceptor) Close() error {
|
||||
defer s.wg.Wait()
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
if !s.isClosed() {
|
||||
close(s.close)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SenderInterceptor) isClosed() bool {
|
||||
select {
|
||||
case <-s.close:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SenderInterceptor) loop(w interceptor.RTCPWriter) {
|
||||
defer s.wg.Done()
|
||||
|
||||
select {
|
||||
case <-s.close:
|
||||
return
|
||||
case p := <-s.packetChan:
|
||||
s.recorder.Record(p.ssrc, p.sequenceNumber, p.arrivalTime)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(s.interval)
|
||||
for {
|
||||
select {
|
||||
case <-s.close:
|
||||
ticker.Stop()
|
||||
return
|
||||
case p := <-s.packetChan:
|
||||
s.recorder.Record(p.ssrc, p.sequenceNumber, p.arrivalTime)
|
||||
|
||||
case <-ticker.C:
|
||||
// build and send twcc
|
||||
pkts := s.recorder.BuildFeedbackPacket()
|
||||
if len(pkts) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := w.Write(pkts, nil); err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
366
server/vendor/github.com/pion/interceptor/pkg/twcc/twcc.go
generated
vendored
Normal file
366
server/vendor/github.com/pion/interceptor/pkg/twcc/twcc.go
generated
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package twcc provides interceptors to implement transport wide congestion control.
|
||||
package twcc
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/pion/interceptor/internal/sequencenumber"
|
||||
"github.com/pion/rtcp"
|
||||
)
|
||||
|
||||
const (
|
||||
packetWindowMicroseconds = 500_000
|
||||
maxMissingSequenceNumbers = 0x7FFE
|
||||
)
|
||||
|
||||
// Recorder records incoming RTP packets and their delays and creates
|
||||
// transport wide congestion control feedback reports as specified in
|
||||
// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
type Recorder struct {
|
||||
arrivalTimeMap packetArrivalTimeMap
|
||||
|
||||
sequenceUnwrapper sequencenumber.Unwrapper
|
||||
|
||||
// startSequenceNumber is the first sequence number that will be included in the the
|
||||
// next feedback packet.
|
||||
startSequenceNumber *int64
|
||||
|
||||
senderSSRC uint32
|
||||
mediaSSRC uint32
|
||||
fbPktCnt uint8
|
||||
|
||||
packetsHeld int
|
||||
}
|
||||
|
||||
// NewRecorder creates a new Recorder which uses the given senderSSRC in the created
|
||||
// feedback packets.
|
||||
func NewRecorder(senderSSRC uint32) *Recorder {
|
||||
return &Recorder{
|
||||
senderSSRC: senderSSRC,
|
||||
}
|
||||
}
|
||||
|
||||
// Record marks a packet with mediaSSRC and a transport wide sequence number sequenceNumber as received at arrivalTime.
|
||||
func (r *Recorder) Record(mediaSSRC uint32, sequenceNumber uint16, arrivalTime int64) {
|
||||
r.mediaSSRC = mediaSSRC
|
||||
|
||||
// "Unwrap" the sequence number to get a monotonically increasing sequence number that
|
||||
// won't wrap around after math.MaxUint16.
|
||||
unwrappedSN := r.sequenceUnwrapper.Unwrap(sequenceNumber)
|
||||
r.maybeCullOldPackets(unwrappedSN, arrivalTime)
|
||||
if r.startSequenceNumber == nil || unwrappedSN < *r.startSequenceNumber {
|
||||
r.startSequenceNumber = &unwrappedSN
|
||||
}
|
||||
|
||||
// We are only interested in the first time a packet is received.
|
||||
if r.arrivalTimeMap.HasReceived(unwrappedSN) {
|
||||
return
|
||||
}
|
||||
|
||||
r.arrivalTimeMap.AddPacket(unwrappedSN, arrivalTime)
|
||||
r.packetsHeld++
|
||||
|
||||
// Limit the range of sequence numbers to send feedback for.
|
||||
if *r.startSequenceNumber < r.arrivalTimeMap.BeginSequenceNumber() {
|
||||
sn := r.arrivalTimeMap.BeginSequenceNumber()
|
||||
r.startSequenceNumber = &sn
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) maybeCullOldPackets(sequenceNumber int64, arrivalTime int64) {
|
||||
if r.startSequenceNumber != nil && *r.startSequenceNumber >= r.arrivalTimeMap.EndSequenceNumber() && arrivalTime >= packetWindowMicroseconds {
|
||||
r.arrivalTimeMap.RemoveOldPackets(sequenceNumber, arrivalTime-packetWindowMicroseconds)
|
||||
}
|
||||
}
|
||||
|
||||
// PacketsHeld returns the number of received packets currently held by the recorder
|
||||
func (r *Recorder) PacketsHeld() int {
|
||||
return r.packetsHeld
|
||||
}
|
||||
|
||||
// BuildFeedbackPacket creates a new RTCP packet containing a TWCC feedback report.
|
||||
func (r *Recorder) BuildFeedbackPacket() []rtcp.Packet {
|
||||
if r.startSequenceNumber == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
endSN := r.arrivalTimeMap.EndSequenceNumber()
|
||||
var feedbacks []rtcp.Packet
|
||||
for *r.startSequenceNumber < endSN {
|
||||
feedback := r.maybeBuildFeedbackPacket(*r.startSequenceNumber, endSN)
|
||||
if feedback == nil {
|
||||
break
|
||||
}
|
||||
feedbacks = append(feedbacks, feedback.getRTCP())
|
||||
|
||||
// NOTE: we don't erase packets from the history in case they need to be resent
|
||||
// after a reordering. They will be removed instead in Record when they get too
|
||||
// old.
|
||||
}
|
||||
r.packetsHeld = 0
|
||||
return feedbacks
|
||||
}
|
||||
|
||||
// maybeBuildFeedbackPacket builds a feedback packet starting from startSN (inclusive) until
|
||||
// endSN (exclusive).
|
||||
func (r *Recorder) maybeBuildFeedbackPacket(beginSeqNumInclusive, endSeqNumExclusive int64) *feedback {
|
||||
// NOTE: The logic of this method is inspired by the implementation in Chrome.
|
||||
// See https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc;l=276;drc=b5cd13bb6d5d157a5fbe3628b2dd1c1e106203c6
|
||||
startSNInclusive, endSNExclusive := r.arrivalTimeMap.Clamp(beginSeqNumInclusive), r.arrivalTimeMap.Clamp(endSeqNumExclusive)
|
||||
|
||||
// Create feedback on demand, as we don't yet know if there are packets in the range that have been
|
||||
// received.
|
||||
var fb *feedback
|
||||
|
||||
nextSequenceNumber := beginSeqNumInclusive
|
||||
|
||||
for seq := startSNInclusive; seq < endSNExclusive; seq++ {
|
||||
foundSeq, arrivalTime, ok := r.arrivalTimeMap.FindNextAtOrAfter(seq)
|
||||
seq = foundSeq
|
||||
if !ok || seq >= endSNExclusive {
|
||||
break
|
||||
}
|
||||
|
||||
if fb == nil {
|
||||
fb = newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)
|
||||
r.fbPktCnt++
|
||||
|
||||
// It should be possible to add seq to this new packet.
|
||||
// If the difference between seq and beginSeqNumInclusive is too large, discard
|
||||
// reporting too old missing packets.
|
||||
baseSequenceNumber := max64(beginSeqNumInclusive, seq-maxMissingSequenceNumbers)
|
||||
|
||||
// baseSequenceNumber is the expected first sequence number. This is known,
|
||||
// but we may not have actually received it, so the base time should be the time
|
||||
// of the first received packet in the feedback.
|
||||
fb.setBase(uint16(baseSequenceNumber), arrivalTime)
|
||||
|
||||
if !fb.addReceived(uint16(seq), arrivalTime) {
|
||||
// Could not add a single received packet to the feedback.
|
||||
// This is unexpected to actually occur, but if it does, we'll
|
||||
// try again after skipping any missing packets.
|
||||
// NOTE: It's fine that we already incremented fbPktCnt, as in essence
|
||||
// we did actually "skip" a feedback (and this matches Chrome's behavior).
|
||||
r.startSequenceNumber = &seq
|
||||
return nil
|
||||
}
|
||||
} else if !fb.addReceived(uint16(seq), arrivalTime) {
|
||||
// Could not add timestamp. Packet may be full. Return
|
||||
// and try again with a fresh packet.
|
||||
break
|
||||
}
|
||||
|
||||
nextSequenceNumber = seq + 1
|
||||
}
|
||||
|
||||
r.startSequenceNumber = &nextSequenceNumber
|
||||
return fb
|
||||
}
|
||||
|
||||
type feedback struct {
|
||||
rtcp *rtcp.TransportLayerCC
|
||||
baseSequenceNumber uint16
|
||||
refTimestamp64MS int64
|
||||
lastTimestampUS int64
|
||||
nextSequenceNumber uint16
|
||||
sequenceNumberCount uint16
|
||||
len int
|
||||
lastChunk chunk
|
||||
chunks []rtcp.PacketStatusChunk
|
||||
deltas []*rtcp.RecvDelta
|
||||
}
|
||||
|
||||
func newFeedback(senderSSRC, mediaSSRC uint32, count uint8) *feedback {
|
||||
return &feedback{
|
||||
rtcp: &rtcp.TransportLayerCC{
|
||||
SenderSSRC: senderSSRC,
|
||||
MediaSSRC: mediaSSRC,
|
||||
FbPktCount: count,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *feedback) setBase(sequenceNumber uint16, timeUS int64) {
|
||||
f.baseSequenceNumber = sequenceNumber
|
||||
f.nextSequenceNumber = f.baseSequenceNumber
|
||||
f.refTimestamp64MS = timeUS / 64e3
|
||||
f.lastTimestampUS = f.refTimestamp64MS * 64e3
|
||||
}
|
||||
|
||||
func (f *feedback) getRTCP() *rtcp.TransportLayerCC {
|
||||
f.rtcp.PacketStatusCount = f.sequenceNumberCount
|
||||
f.rtcp.ReferenceTime = uint32(f.refTimestamp64MS)
|
||||
f.rtcp.BaseSequenceNumber = f.baseSequenceNumber
|
||||
for len(f.lastChunk.deltas) > 0 {
|
||||
f.chunks = append(f.chunks, f.lastChunk.encode())
|
||||
}
|
||||
f.rtcp.PacketChunks = append(f.rtcp.PacketChunks, f.chunks...)
|
||||
f.rtcp.RecvDeltas = f.deltas
|
||||
|
||||
padLen := 20 + len(f.rtcp.PacketChunks)*2 + f.len // 4 bytes header + 16 bytes twcc header + 2 bytes for each chunk + length of deltas
|
||||
padding := padLen%4 != 0
|
||||
for padLen%4 != 0 {
|
||||
padLen++
|
||||
}
|
||||
f.rtcp.Header = rtcp.Header{
|
||||
Count: rtcp.FormatTCC,
|
||||
Type: rtcp.TypeTransportSpecificFeedback,
|
||||
Padding: padding,
|
||||
Length: uint16((padLen / 4) - 1),
|
||||
}
|
||||
|
||||
return f.rtcp
|
||||
}
|
||||
|
||||
func (f *feedback) addReceived(sequenceNumber uint16, timestampUS int64) bool {
|
||||
deltaUS := timestampUS - f.lastTimestampUS
|
||||
var delta250US int64
|
||||
if deltaUS >= 0 {
|
||||
delta250US = (deltaUS + rtcp.TypeTCCDeltaScaleFactor/2) / rtcp.TypeTCCDeltaScaleFactor
|
||||
} else {
|
||||
delta250US = (deltaUS - rtcp.TypeTCCDeltaScaleFactor/2) / rtcp.TypeTCCDeltaScaleFactor
|
||||
}
|
||||
if delta250US < math.MinInt16 || delta250US > math.MaxInt16 { // delta doesn't fit into 16 bit, need to create new packet
|
||||
return false
|
||||
}
|
||||
deltaUSRounded := delta250US * rtcp.TypeTCCDeltaScaleFactor
|
||||
|
||||
for ; f.nextSequenceNumber != sequenceNumber; f.nextSequenceNumber++ {
|
||||
if !f.lastChunk.canAdd(rtcp.TypeTCCPacketNotReceived) {
|
||||
f.chunks = append(f.chunks, f.lastChunk.encode())
|
||||
}
|
||||
f.lastChunk.add(rtcp.TypeTCCPacketNotReceived)
|
||||
f.sequenceNumberCount++
|
||||
}
|
||||
|
||||
var recvDelta uint16
|
||||
switch {
|
||||
case delta250US >= 0 && delta250US <= 0xff:
|
||||
f.len++
|
||||
recvDelta = rtcp.TypeTCCPacketReceivedSmallDelta
|
||||
default:
|
||||
f.len += 2
|
||||
recvDelta = rtcp.TypeTCCPacketReceivedLargeDelta
|
||||
}
|
||||
|
||||
if !f.lastChunk.canAdd(recvDelta) {
|
||||
f.chunks = append(f.chunks, f.lastChunk.encode())
|
||||
}
|
||||
f.lastChunk.add(recvDelta)
|
||||
f.deltas = append(f.deltas, &rtcp.RecvDelta{
|
||||
Type: recvDelta,
|
||||
Delta: deltaUSRounded,
|
||||
})
|
||||
f.lastTimestampUS += deltaUSRounded
|
||||
f.sequenceNumberCount++
|
||||
f.nextSequenceNumber++
|
||||
return true
|
||||
}
|
||||
|
||||
const (
|
||||
maxRunLengthCap = 0x1fff // 13 bits
|
||||
maxOneBitCap = 14 // bits
|
||||
maxTwoBitCap = 7 // bits
|
||||
)
|
||||
|
||||
type chunk struct {
|
||||
hasLargeDelta bool
|
||||
hasDifferentTypes bool
|
||||
deltas []uint16
|
||||
}
|
||||
|
||||
func (c *chunk) canAdd(delta uint16) bool {
|
||||
if len(c.deltas) < maxTwoBitCap {
|
||||
return true
|
||||
}
|
||||
if len(c.deltas) < maxOneBitCap && !c.hasLargeDelta && delta != rtcp.TypeTCCPacketReceivedLargeDelta {
|
||||
return true
|
||||
}
|
||||
if len(c.deltas) < maxRunLengthCap && !c.hasDifferentTypes && delta == c.deltas[0] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *chunk) add(delta uint16) {
|
||||
c.deltas = append(c.deltas, delta)
|
||||
c.hasLargeDelta = c.hasLargeDelta || delta == rtcp.TypeTCCPacketReceivedLargeDelta
|
||||
c.hasDifferentTypes = c.hasDifferentTypes || delta != c.deltas[0]
|
||||
}
|
||||
|
||||
func (c *chunk) encode() rtcp.PacketStatusChunk {
|
||||
if !c.hasDifferentTypes {
|
||||
defer c.reset()
|
||||
return &rtcp.RunLengthChunk{
|
||||
PacketStatusSymbol: c.deltas[0],
|
||||
RunLength: uint16(len(c.deltas)),
|
||||
}
|
||||
}
|
||||
if len(c.deltas) == maxOneBitCap {
|
||||
defer c.reset()
|
||||
return &rtcp.StatusVectorChunk{
|
||||
SymbolSize: rtcp.TypeTCCSymbolSizeOneBit,
|
||||
SymbolList: c.deltas,
|
||||
}
|
||||
}
|
||||
|
||||
minCap := min(maxTwoBitCap, len(c.deltas))
|
||||
svc := &rtcp.StatusVectorChunk{
|
||||
SymbolSize: rtcp.TypeTCCSymbolSizeTwoBit,
|
||||
SymbolList: c.deltas[:minCap],
|
||||
}
|
||||
c.deltas = c.deltas[minCap:]
|
||||
c.hasDifferentTypes = false
|
||||
c.hasLargeDelta = false
|
||||
|
||||
if len(c.deltas) > 0 {
|
||||
tmp := c.deltas[0]
|
||||
for _, d := range c.deltas {
|
||||
if tmp != d {
|
||||
c.hasDifferentTypes = true
|
||||
}
|
||||
if d == rtcp.TypeTCCPacketReceivedLargeDelta {
|
||||
c.hasLargeDelta = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func (c *chunk) reset() {
|
||||
c.deltas = []uint16{}
|
||||
c.hasLargeDelta = false
|
||||
c.hasDifferentTypes = false
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max64(a, b int64) int64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min64(a, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
33
server/vendor/github.com/pion/interceptor/registry.go
generated
vendored
Normal file
33
server/vendor/github.com/pion/interceptor/registry.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interceptor
|
||||
|
||||
// Registry is a collector for interceptors.
|
||||
type Registry struct {
|
||||
factories []Factory
|
||||
}
|
||||
|
||||
// Add adds a new Interceptor to the registry.
|
||||
func (r *Registry) Add(f Factory) {
|
||||
r.factories = append(r.factories, f)
|
||||
}
|
||||
|
||||
// Build constructs a single Interceptor from a InterceptorRegistry
|
||||
func (r *Registry) Build(id string) (Interceptor, error) {
|
||||
if len(r.factories) == 0 {
|
||||
return &NoOp{}, nil
|
||||
}
|
||||
|
||||
interceptors := []Interceptor{}
|
||||
for _, f := range r.factories {
|
||||
i, err := f.NewInterceptor(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interceptors = append(interceptors, i)
|
||||
}
|
||||
|
||||
return NewChain(interceptors), nil
|
||||
}
|
||||
6
server/vendor/github.com/pion/interceptor/renovate.json
generated
vendored
Normal file
6
server/vendor/github.com/pion/interceptor/renovate.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"github>pion/renovate-config"
|
||||
]
|
||||
}
|
||||
37
server/vendor/github.com/pion/interceptor/streaminfo.go
generated
vendored
Normal file
37
server/vendor/github.com/pion/interceptor/streaminfo.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package interceptor
|
||||
|
||||
// RTPHeaderExtension represents a negotiated RFC5285 RTP header extension.
|
||||
type RTPHeaderExtension struct {
|
||||
URI string
|
||||
ID int
|
||||
}
|
||||
|
||||
// StreamInfo is the Context passed when a StreamLocal or StreamRemote has been Binded or Unbinded
|
||||
type StreamInfo struct {
|
||||
ID string
|
||||
Attributes Attributes
|
||||
SSRC uint32
|
||||
PayloadType uint8
|
||||
RTPHeaderExtensions []RTPHeaderExtension
|
||||
MimeType string
|
||||
ClockRate uint32
|
||||
Channels uint16
|
||||
SDPFmtpLine string
|
||||
RTCPFeedback []RTCPFeedback
|
||||
}
|
||||
|
||||
// RTCPFeedback signals the connection to use additional RTCP packet types.
|
||||
// https://draft.ortc.org/#dom-rtcrtcpfeedback
|
||||
type RTCPFeedback struct {
|
||||
// Type is the type of feedback.
|
||||
// see: https://draft.ortc.org/#dom-rtcrtcpfeedback
|
||||
// valid: ack, ccm, nack, goog-remb, transport-cc
|
||||
Type string
|
||||
|
||||
// The parameter value depends on the type.
|
||||
// For example, type="nack" parameter="pli" will send Picture Loss Indicator packets.
|
||||
Parameter string
|
||||
}
|
||||
Reference in New Issue
Block a user