1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

TEST: Upgrade pion to v3.2.9. (#3567)

------

Co-authored-by: chundonglinlin <chundonglinlin@163.com>
This commit is contained in:
Winlin 2023-06-05 11:25:04 +08:00 committed by GitHub
parent 104cf14d68
commit df854339ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1383 changed files with 118469 additions and 41421 deletions

View file

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
### JetBrains IDE ###
#####################
.idea/
@ -22,3 +25,4 @@ cover.out
*.wasm
examples/sfu-ws/cert.pem
examples/sfu-ws/key.pem
wasm_exec.js

View file

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
linters-settings:
govet:
check-shadowing: true
@ -10,19 +13,34 @@ linters-settings:
modules:
- github.com/pkg/errors:
recommendations:
- errors
- 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
- deadcode # Finds unused code
- contextcheck # check the function whether use a non-inherited context
- decorder # check declaration order and count of types, constants, variables and functions
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- 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
@ -35,40 +53,59 @@ linters:
- 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
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
- 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
- nakedret # Finds naked returns in functions greater than a specified function length
- 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
- scopelint # Scopelint checks for unpinned variables in go programs
- 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
- structcheck # Finds unused struct fields
- 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
- varcheck # Finds unused global variables and constants
- wastedassign # wastedassign finds wasted assignment statements
- whitespace # Tool for detection of leading and trailing whitespace
disable:
- 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
- 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:
@ -78,12 +115,23 @@ issues:
- path: _test\.go
linters:
- gocognit
- forbidigo
# Allow complex main function in examples
- path: examples
text: "of func `main` is high"
linters:
- gocognit
# Allow forbidden identifiers in examples
- path: examples
linters:
- forbidigo
# Allow forbidden identifiers in CLI commands
- path: cmd
linters:
- forbidigo
run:
skip-dirs-use-default: false

View file

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
builds:
- skip: true

View file

@ -0,0 +1,30 @@
# Thank you to everyone that made Pion possible. If you are interested in contributing
# we would love to have you https://github.com/pion/webrtc/wiki/Contributing
#
# This file is auto generated, using git to list all individuals contributors.
# see https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting
Aaron Boushley <boushley@gmail.com>
Adam Kiss <masterada@gmail.com>
adamroach <adam@nostrum.com>
Aditya Kumar <k.aditya00@gmail.com>
aler9 <46489434+aler9@users.noreply.github.com>
Antoine Baché <antoine@tenten.app>
Atsushi Watanabe <atsushi.w@ieee.org>
Bobby Peck <rpeck@mux.com>
boks1971 <raja.gobi@tutanota.com>
David Zhao <david@davidzhao.com>
Jonathan Müller <jonathan@fotokite.com>
Kevin Caffrey <kcaffrey@gmail.com>
Maksim Nesterov <msnesterov@avito.ru>
Mathis Engelbart <mathis.engelbart@gmail.com>
Miroslav <sedivy.miro@gmail.com>
Miroslav Šedivý <sedivy.miro@gmail.com>
Quentin Renard <contact@asticode.com>
Rayleigh Li <rayleigh.li@zoom.us>
Sean DuBois <sean@siobud.com>
Steffen Vogel <post@steffenvogel.de>
XLPolar <guangjin_pan@163.com>
ziminghua <565209960@qq.com>
# List of contributors not appearing in Git history

View file

@ -1,21 +1,9 @@
MIT License
Copyright (c) 2018
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:
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 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.
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.

View file

@ -8,7 +8,8 @@
<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>
<a href="https://pkg.go.dev/github.com/pion/interceptor"><img src="https://godoc.org/github.com/pion/interceptor?status.svg" alt="GoDoc"></a>
<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>
@ -27,18 +28,21 @@ by anyone. With the following tenets in mind.
* 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
### 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
* [Sender and Receiver Reports](https://tools.ietf.org/html/rfc3550#section-6.4)
- Bandwidth Estimation from Receiver Reports
* [Transport Wide Congestion Control Feedback](https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01)
### Planned Interceptors
* Bandwidth Estimation
- [NADA](https://tools.ietf.org/html/rfc8698)
- [Google Congestion Control](https://tools.ietf.org/html/draft-ietf-rmcat-gcc-02)
* JitterBuffer, re-order packets and wait for arrival
* [FlexFec](https://tools.ietf.org/html/draft-ietf-payload-flexible-fec-scheme-20)
* [webrtc-stats](https://www.w3.org/TR/webrtc-stats/) compliant statistics generation
* [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).
@ -62,20 +66,19 @@ sequentially as the packet moves through them.
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 [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion).
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:
* [Adam Kiss](https://github.com/masterada) - *Original Author*
* [Sean DuBois](https://github.com/sean-der) - *Original Author*
* [Atsushi Watanabe](https://github.com/at-wat)
* [Alessandro Ros](https://github.com/aler9)
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt)
### License
MIT License - see [LICENSE](LICENSE) for full text

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

View file

@ -1,3 +1,6 @@
// 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.

View file

@ -3,6 +3,8 @@
#
# 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:

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package interceptor
import (
@ -18,7 +21,7 @@ func flattenErrs(errs []error) error {
return multiError(errs2)
}
type multiError []error
type multiError []error //nolint
func (me multiError) Error() string {
var errstrings []string
@ -41,7 +44,7 @@ func (me multiError) Is(err error) bool {
if errors.Is(e, err) {
return true
}
if me2, ok := e.(multiError); ok {
if me2, ok := e.(multiError); ok { //nolint
if me2.Is(err) {
return true
}

View file

@ -1,10 +0,0 @@
module github.com/pion/interceptor
go 1.15
require (
github.com/pion/logging v0.2.2
github.com/pion/rtcp v1.2.6
github.com/pion/rtp v1.6.2
github.com/stretchr/testify v1.7.0
)

View file

@ -1,21 +0,0 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,3 +1,6 @@
// 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
@ -9,10 +12,14 @@ import (
"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
@ -62,9 +69,6 @@ type RTCPReader interface {
Read([]byte, Attributes) (int, Attributes, error)
}
// Attributes are a generic key/value store used by interceptors
type Attributes map[interface{}]interface{}
// RTPWriterFunc is an adapter for RTPWrite interface
type RTPWriterFunc func(header *rtp.Header, payload []byte, attributes Attributes) (int, error)
@ -96,13 +100,3 @@ func (f RTCPWriterFunc) Write(pkts []rtcp.Packet, attributes Attributes) (int, e
func (f RTCPReaderFunc) Read(b []byte, a Attributes) (int, Attributes, error) {
return f(b, a)
}
// 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
}

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

View file

@ -1,3 +1,6 @@
// 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

View file

@ -1,6 +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")
)

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package nack
import (
@ -8,9 +11,37 @@ import (
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
// 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,
interval: time.Millisecond * 100,
receiveLogs: map[uint32]*receiveLog{},
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
@ -26,28 +57,9 @@ type GeneratorInterceptor struct {
receiveLogsMu sync.Mutex
}
// NewGeneratorInterceptor returns a new GeneratorInterceptor interceptor
func NewGeneratorInterceptor(opts ...GeneratorOption) (*GeneratorInterceptor, error) {
r := &GeneratorInterceptor{
size: 8192,
skipLastN: 0,
interval: time.Millisecond * 100,
receiveLogs: map[uint32]*receiveLog{},
close: make(chan struct{}),
log: logging.NewDefaultLoggerFactory().NewLogger("nack_generator"),
}
for _, opt := range opts {
if err := opt(r); err != nil {
return nil, err
}
}
if _, err := newReceiveLog(r.size); err != nil {
return nil, err
}
return r, nil
// 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
@ -86,18 +98,21 @@ func (n *GeneratorInterceptor) BindRemoteStream(info *interceptor.StreamInfo, re
return 0, nil, err
}
pkt := rtp.Packet{}
if err = pkt.Unmarshal(b[:i]); err != nil {
if attr == nil {
attr = make(interceptor.Attributes)
}
header, err := attr.GetRTPHeader(b[:i])
if err != nil {
return 0, nil, err
}
receiveLog.add(pkt.Header.SequenceNumber)
receiveLog.add(header.SequenceNumber)
return i, attr, nil
})
}
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
func (n *GeneratorInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
// 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()
@ -122,6 +137,7 @@ func (n *GeneratorInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
senderSSRC := rand.Uint32() // #nosec
ticker := time.NewTicker(n.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package nack
import (

View file

@ -1,3 +1,6 @@
// 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

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package nack
import (
@ -127,8 +130,9 @@ func (s *receiveLog) getReceived(seq uint16) bool {
func (s *receiveLog) fixLastConsecutive() {
i := s.lastConsecutive + 1
for ; i != s.end+1 && s.getReceived(i); i++ {
for ; i != s.end+1 && s.getReceived(i); i++ { //nolint:revive
// find all consecutive packets
}
s.lastConsecutive = i - 1
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package nack
import (
@ -9,11 +12,46 @@ import (
"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
size uint16
log logging.LeveledLogger
packetFactory packetFactory
streams map[uint32]*localStream
streamsMu sync.Mutex
@ -24,25 +62,9 @@ type localStream struct {
rtpWriter interceptor.RTPWriter
}
// NewResponderInterceptor returns a new GeneratorInterceptor interceptor
func NewResponderInterceptor(opts ...ResponderOption) (*ResponderInterceptor, error) {
r := &ResponderInterceptor{
size: 8192,
log: logging.NewDefaultLoggerFactory().NewLogger("nack_responder"),
streams: map[uint32]*localStream{},
}
for _, opt := range opts {
if err := opt(r); err != nil {
return nil, err
}
}
if _, err := newSendBuffer(r.size); err != nil {
return nil, err
}
return r, nil
// 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
@ -54,7 +76,10 @@ func (n *ResponderInterceptor) BindRTCPReader(reader interceptor.RTCPReader) int
return 0, nil, err
}
pkts, err := rtcp.Unmarshal(b[:i])
if attr == nil {
attr = make(interceptor.Attributes)
}
pkts, err := attr.GetRTCPPackets(b[:i])
if err != nil {
return 0, nil, err
}
@ -85,7 +110,11 @@ func (n *ResponderInterceptor) BindLocalStream(info *interceptor.StreamInfo, wri
n.streamsMu.Unlock()
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
sendBuffer.add(&rtp.Packet{Header: *header, Payload: payload})
pkt, err := n.packetFactory.NewPacket(header, payload)
if err != nil {
return 0, err
}
sendBuffer.add(pkt)
return writer.Write(header, payload, attributes)
})
}
@ -108,9 +137,10 @@ func (n *ResponderInterceptor) resendPackets(nack *rtcp.TransportLayerNack) {
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 {
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

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package nack
import "github.com/pion/logging"
@ -21,3 +24,12 @@ func ResponderLog(log logging.LeveledLogger) ResponderOption {
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
}
}

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

View file

@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package nack
import (
"fmt"
"github.com/pion/rtp"
"sync"
)
const (
@ -11,10 +13,12 @@ const (
)
type sendBuffer struct {
packets []*rtp.Packet
packets []*retainablePacket
size uint16
lastAdded uint16
started bool
m sync.RWMutex
}
func newSendBuffer(size uint16) (*sendBuffer, error) {
@ -33,13 +37,16 @@ func newSendBuffer(size uint16) (*sendBuffer, error) {
}
return &sendBuffer{
packets: make([]*rtp.Packet, size),
packets: make([]*retainablePacket, size),
size: size,
}, nil
}
func (s *sendBuffer) add(packet *rtp.Packet) {
seq := packet.SequenceNumber
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
@ -52,15 +59,28 @@ func (s *sendBuffer) add(packet *rtp.Packet) {
return
} else if diff < uint16SizeHalf {
for i := s.lastAdded + 1; i != seq; i++ {
s.packets[i%s.size] = nil
idx := i % s.size
prevPacket := s.packets[idx]
if prevPacket != nil {
prevPacket.Release()
}
s.packets[idx] = nil
}
}
s.packets[seq%s.size] = packet
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) *rtp.Packet {
func (s *sendBuffer) get(seq uint16) *retainablePacket {
s.m.RLock()
defer s.m.RUnlock()
diff := s.lastAdded - seq
if diff >= uint16SizeHalf {
return nil
@ -70,5 +90,15 @@ func (s *sendBuffer) get(seq uint16) *rtp.Packet {
return nil
}
return s.packets[seq%s.size]
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
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package report
import (
@ -7,9 +10,36 @@ import (
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
// 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
@ -22,24 +52,6 @@ type ReceiverInterceptor struct {
close chan struct{}
}
// NewReceiverInterceptor returns a new ReceiverInterceptor interceptor.
func NewReceiverInterceptor(opts ...ReceiverOption) (*ReceiverInterceptor, error) {
r := &ReceiverInterceptor{
interval: 1 * time.Second,
now: time.Now,
log: logging.NewDefaultLoggerFactory().NewLogger("receiver_interceptor"),
close: make(chan struct{}),
}
for _, opt := range opts {
if err := opt(r); err != nil {
return nil, err
}
}
return r, nil
}
func (r *ReceiverInterceptor) isClosed() bool {
select {
case <-r.close:
@ -83,18 +95,15 @@ 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(key, value interface{}) bool {
stream := value.(*receiverStream)
var pkts []rtcp.Packet
pkts = append(pkts, stream.generateReport(now))
if _, err := rtcpWriter.Write(pkts, interceptor.Attributes{}); err != nil {
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)
}
@ -119,12 +128,15 @@ func (r *ReceiverInterceptor) BindRemoteStream(info *interceptor.StreamInfo, rea
return 0, nil, err
}
pkt := rtp.Packet{}
if err = pkt.Unmarshal(b[:i]); err != nil {
if attr == nil {
attr = make(interceptor.Attributes)
}
header, err := attr.GetRTPHeader(b[:i])
if err != nil {
return 0, nil, err
}
stream.processRTP(r.now(), &pkt)
stream.processRTP(r.now(), header)
return i, attr, nil
})
@ -144,7 +156,10 @@ func (r *ReceiverInterceptor) BindRTCPReader(reader interceptor.RTCPReader) inte
return 0, nil, err
}
pkts, err := rtcp.Unmarshal(b[:i])
if attr == nil {
attr = make(interceptor.Attributes)
}
pkts, err := attr.GetRTCPPackets(b[:i])
if err != nil {
return 0, nil, err
}
@ -156,8 +171,11 @@ func (r *ReceiverInterceptor) BindRTCPReader(reader interceptor.RTCPReader) inte
continue
}
stream := value.(*receiverStream)
stream.processSenderReport(r.now(), sr)
if stream, ok := value.(*receiverStream); !ok {
r.log.Warnf("failed to cast ReceiverInterceptor stream")
} else {
stream.processSenderReport(r.now(), sr)
}
}
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package report
import (

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package report
import (
@ -40,21 +43,21 @@ func newReceiverStream(ssrc uint32, clockRate uint32) *receiverStream {
}
}
func (stream *receiverStream) processRTP(now time.Time, pkt *rtp.Packet) {
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(pkt.SequenceNumber)
stream.lastSeqnum = pkt.SequenceNumber
stream.lastReportSeqnum = pkt.SequenceNumber - 1
stream.lastRTPTimeRTP = pkt.Timestamp
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(pkt.SequenceNumber)
stream.setReceived(pktHeader.SequenceNumber)
diff := int32(pkt.SequenceNumber) - int32(stream.lastSeqnum)
diff := int32(pktHeader.SequenceNumber) - int32(stream.lastSeqnum)
if diff > 0 || diff < -0x0FFF {
// overflow
if diff < -0x0FFF {
@ -62,22 +65,22 @@ func (stream *receiverStream) processRTP(now time.Time, pkt *rtp.Packet) {
}
// set missing packets as missing
for i := stream.lastSeqnum + 1; i != pkt.SequenceNumber; i++ {
for i := stream.lastSeqnum + 1; i != pktHeader.SequenceNumber; i++ {
stream.delReceived(i)
}
stream.lastSeqnum = pkt.SequenceNumber
stream.lastSeqnum = pktHeader.SequenceNumber
}
// compute jitter
// https://tools.ietf.org/html/rfc3550#page-39
D := now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate -
(float64(pkt.Timestamp) - float64(stream.lastRTPTimeRTP))
(float64(pktHeader.Timestamp) - float64(stream.lastRTPTimeRTP))
if D < 0 {
D = -D
}
stream.jitter += (D - stream.jitter) / 16
stream.lastRTPTimeRTP = pkt.Timestamp
stream.lastRTPTimeRTP = pktHeader.Timestamp
stream.lastRTPTimeTime = now
}
}

View file

@ -1,2 +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

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package report
import (
@ -10,14 +13,32 @@ import (
"github.com/pion/rtp"
)
func ntpTime(t time.Time) uint64 {
// seconds since 1st January 1900
s := (float64(t.UnixNano()) / 1000000000) + 2208988800
// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor
type SenderInterceptorFactory struct {
opts []SenderOption
}
// 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)
// NewInterceptor constructs a new SenderInterceptor
func (s *SenderInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
i := &SenderInterceptor{
interval: 1 * time.Second,
now: time.Now,
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.
@ -32,24 +53,6 @@ type SenderInterceptor struct {
close chan struct{}
}
// NewSenderInterceptor returns a new SenderInterceptor interceptor.
func NewSenderInterceptor(opts ...SenderOption) (*SenderInterceptor, error) {
s := &SenderInterceptor{
interval: 1 * time.Second,
now: time.Now,
log: logging.NewDefaultLoggerFactory().NewLogger("sender_interceptor"),
close: make(chan struct{}),
}
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, err
}
}
return s, nil
}
func (s *SenderInterceptor) isClosed() bool {
select {
case <-s.close:
@ -93,26 +96,15 @@ func (s *SenderInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
defer s.wg.Done()
ticker := time.NewTicker(s.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
now := s.now()
s.streams.Range(func(key, value interface{}) bool {
ssrc := key.(uint32)
stream := value.(*senderStream)
stream.m.Lock()
defer stream.m.Unlock()
sr := &rtcp.SenderReport{
SSRC: ssrc,
NTPTime: ntpTime(now),
RTPTime: stream.lastRTPTimeRTP + uint32(now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate),
PacketCount: stream.packetCount,
OctetCount: stream.octetCount,
}
if _, err := rtcpWriter.Write([]rtcp.Packet{sr}, interceptor.Attributes{}); err != nil {
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)
}
@ -128,7 +120,7 @@ func (s *SenderInterceptor) loop(rtcpWriter interceptor.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.
func (s *SenderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
stream := newSenderStream(info.ClockRate)
stream := newSenderStream(info.SSRC, info.ClockRate)
s.streams.Store(info.SSRC, stream)
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, a interceptor.Attributes) (int, error) {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package report
import (

View file

@ -1,13 +1,19 @@
// 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
@ -18,8 +24,9 @@ type senderStream struct {
octetCount uint32
}
func newSenderStream(clockRate uint32) *senderStream {
func newSenderStream(ssrc uint32, clockRate uint32) *senderStream {
return &senderStream{
ssrc: ssrc,
clockRate: float64(clockRate),
}
}
@ -35,3 +42,16 @@ func (stream *senderStream) processRTP(now time.Time, header *rtp.Header, payloa
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,
}
}

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

View 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 pkts == nil {
continue
}
if _, err := w.Write(pkts, nil); err != nil {
s.log.Error(err.Error())
}
}
}
}

View file

@ -0,0 +1,276 @@
// 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/rtcp"
)
type pktInfo struct {
sequenceNumber uint32
arrivalTime int64
}
// 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 {
receivedPackets []pktInfo
cycles uint32
lastSequenceNumber uint16
senderSSRC uint32
mediaSSRC uint32
fbPktCnt uint8
}
// NewRecorder creates a new Recorder which uses the given senderSSRC in the created
// feedback packets.
func NewRecorder(senderSSRC uint32) *Recorder {
return &Recorder{
receivedPackets: []pktInfo{},
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
if sequenceNumber < 0x0fff && (r.lastSequenceNumber&0xffff) > 0xf000 {
r.cycles += 1 << 16
}
r.receivedPackets = insertSorted(r.receivedPackets, pktInfo{
sequenceNumber: r.cycles | uint32(sequenceNumber),
arrivalTime: arrivalTime,
})
r.lastSequenceNumber = sequenceNumber
}
func insertSorted(list []pktInfo, element pktInfo) []pktInfo {
if len(list) == 0 {
return append(list, element)
}
for i := len(list) - 1; i >= 0; i-- {
if list[i].sequenceNumber < element.sequenceNumber {
list = append(list, pktInfo{})
copy(list[i+2:], list[i+1:])
list[i+1] = element
return list
}
if list[i].sequenceNumber == element.sequenceNumber {
list[i] = element
return list
}
}
// element.sequenceNumber is between 0 and first ever received sequenceNumber
return append([]pktInfo{element}, list...)
}
// BuildFeedbackPacket creates a new RTCP packet containing a TWCC feedback report.
func (r *Recorder) BuildFeedbackPacket() []rtcp.Packet {
if len(r.receivedPackets) < 2 {
return nil
}
feedback := newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)
r.fbPktCnt++
feedback.setBase(uint16(r.receivedPackets[0].sequenceNumber&0xffff), r.receivedPackets[0].arrivalTime)
var pkts []rtcp.Packet
for _, pkt := range r.receivedPackets {
ok := feedback.addReceived(uint16(pkt.sequenceNumber&0xffff), pkt.arrivalTime)
if !ok {
pkts = append(pkts, feedback.getRTCP())
feedback = newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)
r.fbPktCnt++
feedback.addReceived(uint16(pkt.sequenceNumber&0xffff), pkt.arrivalTime)
}
}
r.receivedPackets = []pktInfo{}
pkts = append(pkts, feedback.getRTCP())
return pkts
}
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
delta250US := deltaUS / 250
if delta250US < math.MinInt16 || delta250US > math.MaxInt16 { // delta doesn't fit into 16 bit, need to create new packet
return false
}
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: deltaUS,
})
f.lastTimestampUS = timestampUS
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 min(a, b int) int {
if a < b {
return a
}
return b
}

View file

@ -1,20 +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 {
interceptors []Interceptor
factories []Factory
}
// Add adds a new Interceptor to the registry.
func (i *Registry) Add(icpr Interceptor) {
i.interceptors = append(i.interceptors, icpr)
func (r *Registry) Add(f Factory) {
r.factories = append(r.factories, f)
}
// Build constructs a single Interceptor from a InterceptorRegistry
func (i *Registry) Build() Interceptor {
if len(i.interceptors) == 0 {
return &NoOp{}
func (r *Registry) Build(id string) (Interceptor, error) {
if len(r.factories) == 0 {
return &NoOp{}, nil
}
return NewChain(i.interceptors)
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
}

View file

@ -1,15 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"postUpdateOptions": [
"gomodTidy"
],
"commitBody": "Generated by renovateBot",
"packageRules": [
{
"packagePatterns": ["^golang.org/x/"],
"schedule": ["on the first day of the month"]
}
"github>pion/renovate-config"
]
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package interceptor
// RTPHeaderExtension represents a negotiated RFC5285 RTP header extension.