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

SmartPtr: Support load test for source by srs-bench. v6.0.130 (#4097)

1. Add live benchmark support in srs-bench, which only connects and
disconnects without any media transport, to test source creation and
disposal and verify source memory leaks.
2. SmartPtr: Support cleanup of HTTP-FLV stream. Unregister the HTTP-FLV
handler for the pattern and clean up the objects and resources.
3. Support benchmarking RTMP/SRT with srs-bench by integrating the gosrt
and oryx RTMP libraries.
4. Refine SRT and RTC sources by using a timer to clean up the sources,
following the same strategy as the Live source.

---------

Co-authored-by: Haibo Chen <495810242@qq.com>
Co-authored-by: Jacob Su <suzp1984@gmail.com>
This commit is contained in:
Winlin 2024-06-21 07:13:12 +08:00 committed by GitHub
parent e3d74fb045
commit 1f9309ae25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
508 changed files with 6805 additions and 3299 deletions

View file

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View file

@ -0,0 +1,63 @@
[![PkgGoDev](https://pkg.go.dev/badge/github.com/haivision/srtgo)](https://pkg.go.dev/github.com/haivision/srtgo)
# srtgo
Go bindings for [SRT](https://github.com/Haivision/srt) (Secure Reliable Transport), the open source transport technology that optimizes streaming performance across unpredictable networks.
## Why srtgo?
The purpose of srtgo is easing the adoption of SRT transport technology. Using Go, with just a few lines of code you can implement an application that sends/receives data with all the benefits of SRT technology: security and reliability, while keeping latency low.
## Is this a new implementation of SRT?
No! We are just exposing the great work done by the community in the [SRT project](https://github.com/Haivision/srt) as a golang library. All the functionality and implementation still resides in the official SRT project.
# Features supported
* Basic API exposed to easy develop SRT sender/receiver apps
* Caller and Listener mode
* Live transport type
* File transport type
* Message/Buffer API
* SRT transport options up to SRT 1.4.1
* SRT Stats retrieval
# Usage
Example of a SRT receiver application:
``` go
package main
import (
"github.com/haivision/srtgo"
"fmt"
)
func main() {
options := make(map[string]string)
options["transtype"] = "file"
sck := srtgo.NewSrtSocket("0.0.0.0", 8090, options)
defer sck.Close()
sck.Listen(1)
s, _ := sck.Accept()
defer s.Close()
buf := make([]byte, 2048)
for {
n, _ := s.Read(buf)
if n == 0 {
break
}
fmt.Println("Received %d bytes", n)
}
//....
}
```
# Dependencies
* srtlib
You can find detailed instructions about how to install srtlib in its [README file](https://github.com/Haivision/srt#requirements)
gosrt has been developed with srt 1.4.1 as its main target and has been successfully tested in srt 1.3.4 and above.

View file

@ -0,0 +1,69 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
SRTSOCKET srt_accept_wrapped(SRTSOCKET lsn, struct sockaddr* addr, int* addrlen, int *srterror, int *syserror)
{
int ret = srt_accept(lsn, addr, addrlen);
if (ret < 0) {
*srterror = srt_getlasterror(syserror);
}
return ret;
}
*/
import "C"
import (
"fmt"
"net"
"syscall"
"unsafe"
)
func srtAcceptImpl(lsn C.SRTSOCKET, addr *C.struct_sockaddr, addrlen *C.int) (C.SRTSOCKET, error) {
srterr := C.int(0)
syserr := C.int(0)
socket := C.srt_accept_wrapped(lsn, addr, addrlen, &srterr, &syserr)
if srterr != 0 {
srterror := SRTErrno(srterr)
if syserr < 0 {
srterror.wrapSysErr(syscall.Errno(syserr))
}
return socket, srterror
}
return socket, nil
}
// Accept an incoming connection
func (s SrtSocket) Accept() (*SrtSocket, *net.UDPAddr, error) {
var err error
if !s.blocking {
err = s.pd.wait(ModeRead)
if err != nil {
return nil, nil, err
}
}
var addr syscall.RawSockaddrAny
sclen := C.int(syscall.SizeofSockaddrAny)
socket, err := srtAcceptImpl(s.socket, (*C.struct_sockaddr)(unsafe.Pointer(&addr)), &sclen)
if err != nil {
return nil, nil, err
}
if socket == SRT_INVALID_SOCK {
return nil, nil, fmt.Errorf("srt accept, error accepting the connection: %w", srtGetAndClearError())
}
newSocket, err := newFromSocket(&s, socket)
if err != nil {
return nil, nil, fmt.Errorf("new socket could not be created: %w", err)
}
udpAddr, err := udpAddrFromSockaddr(&addr)
if err != nil {
return nil, nil, err
}
return newSocket, udpAddr, nil
}

View file

@ -0,0 +1,7 @@
#include <srt/srt.h>
int srtListenCBWrapper(void* opaque, SRTSOCKET ns, int hs_version, struct sockaddr* peeraddr, char* streamid);
void srtConnectCBWrapper(void* opaque, SRTSOCKET ns, int errorcode, struct sockaddr* peeraddr, int token);
int srtListenCB(void* opaque, SRTSOCKET ns, int hs_version, const struct sockaddr* peeraddr, const char* streamid);
void srtConnectCB(void* opaque, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token);

View file

@ -0,0 +1,17 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include "callback.h"
int srtListenCB(void* opaque, SRTSOCKET ns, int hs_version, const struct sockaddr* peeraddr, const char* streamid)
{
return srtListenCBWrapper(opaque, ns, hs_version, (struct sockaddr*)peeraddr, (char*)streamid);
}
void srtConnectCB(void* opaque, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token)
{
srtConnectCBWrapper(opaque, ns, errorcode, (struct sockaddr*)peeraddr, token);
}
*/
import "C"

View file

@ -0,0 +1,242 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
*/
import "C"
import (
"strconv"
"syscall"
)
type SrtInvalidSock struct{}
type SrtRendezvousUnbound struct{}
type SrtSockConnected struct{}
type SrtConnectionRejected struct{}
type SrtConnectTimeout struct{}
type SrtSocketClosed struct{}
type SrtEpollTimeout struct{}
func (m *SrtInvalidSock) Error() string {
return "Socket u indicates no valid socket ID"
}
func (m *SrtRendezvousUnbound) Error() string {
return "Socket u is in rendezvous mode, but it wasn't bound"
}
func (m *SrtSockConnected) Error() string {
return "Socket u is already connected"
}
func (m *SrtConnectionRejected) Error() string {
return "Connection has been rejected"
}
func (m *SrtConnectTimeout) Error() string {
return "Connection has been timed out"
}
func (m *SrtSocketClosed) Error() string {
return "The socket has been closed"
}
func (m *SrtEpollTimeout) Error() string {
return "Operation has timed out"
}
func (m *SrtEpollTimeout) Timeout() bool {
return true
}
func (m *SrtEpollTimeout) Temporary() bool {
return true
}
//MUST be called from same OS thread that generated the error (i.e.: use runtime.LockOSThread())
func srtGetAndClearError() error {
defer C.srt_clearlasterror()
eSysErrno := C.int(0)
errno := C.srt_getlasterror(&eSysErrno)
srterr := SRTErrno(errno)
if eSysErrno != 0 {
return srterr.wrapSysErr(syscall.Errno(eSysErrno))
}
return srterr
}
//Based of off golang errno handling: https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/syscall/syscall_unix.go;l=114
type SRTErrno int
func (e SRTErrno) Error() string {
//Workaround for unknown being -1
if e == Unknown {
return "Internal error when setting the right error code"
}
if 0 <= int(e) && int(e) < len(srterrors) {
s := srterrors[e]
if s != "" {
return s
}
}
return "srterrno: " + strconv.Itoa(int(e))
}
func (e SRTErrno) Is(target error) bool {
//for backwards compat
switch target.(type) {
case *SrtInvalidSock:
return e == EInvSock
case *SrtRendezvousUnbound:
return e == ERdvUnbound
case *SrtSockConnected:
return e == EConnSock
case *SrtConnectionRejected:
return e == EConnRej
case *SrtConnectTimeout:
return e == ETimeout
case *SrtSocketClosed:
return e == ESClosed
}
return false
}
func (e SRTErrno) Temporary() bool {
return e == EAsyncFAIL || e == EAsyncRCV || e == EAsyncSND || e == ECongest || e == ETimeout
}
func (e SRTErrno) Timeout() bool {
return e == ETimeout
}
func (e SRTErrno) wrapSysErr(errno syscall.Errno) error {
return &srtErrnoSysErrnoWrapped{
e: e,
eSys: errno,
}
}
type srtErrnoSysErrnoWrapped struct {
e SRTErrno
eSys syscall.Errno
}
func (e *srtErrnoSysErrnoWrapped) Error() string {
return e.e.Error()
}
func (e *srtErrnoSysErrnoWrapped) Is(target error) bool {
return e.e.Is(target)
}
func (e *srtErrnoSysErrnoWrapped) Temporary() bool {
return e.e.Temporary()
}
func (e *srtErrnoSysErrnoWrapped) Timeout() bool {
return e.e.Timeout()
}
func (e *srtErrnoSysErrnoWrapped) Unwrap() error {
return error(e.eSys)
}
//Shadows SRT_ERRNO srtcore/srt.h line 490+
const (
Unknown = SRTErrno(C.SRT_EUNKNOWN)
Success = SRTErrno(C.SRT_SUCCESS)
//Major: SETUP
EConnSetup = SRTErrno(C.SRT_ECONNSETUP)
ENoServer = SRTErrno(C.SRT_ENOSERVER)
EConnRej = SRTErrno(C.SRT_ECONNREJ)
ESockFail = SRTErrno(C.SRT_ESOCKFAIL)
ESecFail = SRTErrno(C.SRT_ESECFAIL)
ESClosed = SRTErrno(C.SRT_ESCLOSED)
//Major: CONNECTION
EConnFail = SRTErrno(C.SRT_ECONNFAIL)
EConnLost = SRTErrno(C.SRT_ECONNLOST)
ENoConn = SRTErrno(C.SRT_ENOCONN)
//Major: SYSTEMRES
EResource = SRTErrno(C.SRT_ERESOURCE)
EThread = SRTErrno(C.SRT_ETHREAD)
EnoBuf = SRTErrno(C.SRT_ENOBUF)
ESysObj = SRTErrno(C.SRT_ESYSOBJ)
//Major: FILESYSTEM
EFile = SRTErrno(C.SRT_EFILE)
EInvRdOff = SRTErrno(C.SRT_EINVRDOFF)
ERdPerm = SRTErrno(C.SRT_ERDPERM)
EInvWrOff = SRTErrno(C.SRT_EINVWROFF)
EWrPerm = SRTErrno(C.SRT_EWRPERM)
//Major: NOTSUP
EInvOp = SRTErrno(C.SRT_EINVOP)
EBoundSock = SRTErrno(C.SRT_EBOUNDSOCK)
EConnSock = SRTErrno(C.SRT_ECONNSOCK)
EInvParam = SRTErrno(C.SRT_EINVPARAM)
EInvSock = SRTErrno(C.SRT_EINVSOCK)
EUnboundSock = SRTErrno(C.SRT_EUNBOUNDSOCK)
ENoListen = SRTErrno(C.SRT_ENOLISTEN)
ERdvNoServ = SRTErrno(C.SRT_ERDVNOSERV)
ERdvUnbound = SRTErrno(C.SRT_ERDVUNBOUND)
EInvalMsgAPI = SRTErrno(C.SRT_EINVALMSGAPI)
EInvalBufferAPI = SRTErrno(C.SRT_EINVALBUFFERAPI)
EDupListen = SRTErrno(C.SRT_EDUPLISTEN)
ELargeMsg = SRTErrno(C.SRT_ELARGEMSG)
EInvPollID = SRTErrno(C.SRT_EINVPOLLID)
EPollEmpty = SRTErrno(C.SRT_EPOLLEMPTY)
//EBindConflict = SRTErrno(C.SRT_EBINDCONFLICT)
//Major: AGAIN
EAsyncFAIL = SRTErrno(C.SRT_EASYNCFAIL)
EAsyncSND = SRTErrno(C.SRT_EASYNCSND)
EAsyncRCV = SRTErrno(C.SRT_EASYNCRCV)
ETimeout = SRTErrno(C.SRT_ETIMEOUT)
ECongest = SRTErrno(C.SRT_ECONGEST)
//Major: PEERERROR
EPeer = SRTErrno(C.SRT_EPEERERR)
)
//Unknown cannot be here since it would have a negative index!
//Error strings taken from: https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md
var srterrors = [...]string{
Success: "The value set when the last error was cleared and no error has occurred since then",
EConnSetup: "General setup error resulting from internal system state",
ENoServer: "Connection timed out while attempting to connect to the remote address",
EConnRej: "Connection has been rejected",
ESockFail: "An error occurred when trying to call a system function on an internally used UDP socket",
ESecFail: "A possible tampering with the handshake packets was detected, or encryption request wasn't properly fulfilled.",
ESClosed: "A socket that was vital for an operation called in blocking mode has been closed during the operation",
EConnFail: "General connection failure of unknown details",
EConnLost: "The socket was properly connected, but the connection has been broken",
ENoConn: "The socket is not connected",
EResource: "System or standard library error reported unexpectedly for unknown purpose",
EThread: "System was unable to spawn a new thread when requried",
EnoBuf: "System was unable to allocate memory for buffers",
ESysObj: "System was unable to allocate system specific objects",
EFile: "General filesystem error (for functions operating with file transmission)",
EInvRdOff: "Failure when trying to read from a given position in the file",
ERdPerm: "Read permission was denied when trying to read from file",
EInvWrOff: "Failed to set position in the written file",
EWrPerm: "Write permission was denied when trying to write to a file",
EInvOp: "Invalid operation performed for the current state of a socket",
EBoundSock: "The socket is currently bound and the required operation cannot be performed in this state",
EConnSock: "The socket is currently connected and therefore performing the required operation is not possible",
EInvParam: "Call parameters for API functions have some requirements that were not satisfied",
EInvSock: "The API function required an ID of an entity (socket or group) and it was invalid",
EUnboundSock: "The operation to be performed on a socket requires that it first be explicitly bound",
ENoListen: "The socket passed for the operation is required to be in the listen state",
ERdvNoServ: "The required operation cannot be performed when the socket is set to rendezvous mode",
ERdvUnbound: "An attempt was made to connect to a socket set to rendezvous mode that was not first bound",
EInvalMsgAPI: "The function was used incorrectly in the message API",
EInvalBufferAPI: "The function was used incorrectly in the stream (buffer) API",
EDupListen: "The port tried to be bound for listening is already busy",
ELargeMsg: "Size exceeded",
EInvPollID: "The epoll ID passed to an epoll function is invalid",
EPollEmpty: "The epoll container currently has no subscribed sockets",
//EBindConflict: "SRT_EBINDCONFLICT",
EAsyncFAIL: "General asynchronous failure (not in use currently)",
EAsyncSND: "Sending operation is not ready to perform",
EAsyncRCV: "Receiving operation is not ready to perform",
ETimeout: "The operation timed out",
ECongest: "With SRTO_TSBPDMODE and SRTO_TLPKTDROP set to true, some packets were dropped by sender",
EPeer: "Receiver peer is writing to a file that the agent is sending",
}

View file

@ -0,0 +1,66 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
extern void srtLogCB(void* opaque, int level, const char* file, int line, const char* area, const char* message);
*/
import "C"
import (
"sync"
"unsafe"
gopointer "github.com/mattn/go-pointer"
)
type LogCallBackFunc func(level SrtLogLevel, file string, line int, area, message string)
type SrtLogLevel int
const (
// SrtLogLevelEmerg = int(C.LOG_EMERG)
// SrtLogLevelAlert = int(C.LOG_ALERT)
SrtLogLevelCrit SrtLogLevel = SrtLogLevel(C.LOG_CRIT)
SrtLogLevelErr SrtLogLevel = SrtLogLevel(C.LOG_ERR)
SrtLogLevelWarning SrtLogLevel = SrtLogLevel(C.LOG_WARNING)
SrtLogLevelNotice SrtLogLevel = SrtLogLevel(C.LOG_NOTICE)
SrtLogLevelInfo SrtLogLevel = SrtLogLevel(C.LOG_INFO)
SrtLogLevelDebug SrtLogLevel = SrtLogLevel(C.LOG_DEBUG)
SrtLogLevelTrace SrtLogLevel = SrtLogLevel(8)
)
var (
logCBPtr unsafe.Pointer = nil
logCBPtrLock sync.Mutex
)
//export srtLogCBWrapper
func srtLogCBWrapper(arg unsafe.Pointer, level C.int, file *C.char, line C.int, area, message *C.char) {
userCB := gopointer.Restore(arg).(LogCallBackFunc)
go userCB(SrtLogLevel(level), C.GoString(file), int(line), C.GoString(area), C.GoString(message))
}
func SrtSetLogLevel(level SrtLogLevel) {
C.srt_setloglevel(C.int(level))
}
func SrtSetLogHandler(cb LogCallBackFunc) {
ptr := gopointer.Save(cb)
C.srt_setloghandler(ptr, (*C.SRT_LOG_HANDLER_FN)(C.srtLogCB))
storeLogCBPtr(ptr)
}
func SrtUnsetLogHandler() {
C.srt_setloghandler(nil, nil)
storeLogCBPtr(nil)
}
func storeLogCBPtr(ptr unsafe.Pointer) {
logCBPtrLock.Lock()
defer logCBPtrLock.Unlock()
if logCBPtr != nil {
gopointer.Unref(logCBPtr)
}
logCBPtr = ptr
}

View file

@ -0,0 +1,14 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
extern void srtLogCBWrapper (void* opaque, int level, char* file, int line, char* area, char* message);
void srtLogCB(void* opaque, int level, const char* file, int line, const char* area, const char* message)
{
srtLogCBWrapper(opaque, level, (char*)file, line, (char*)area,(char*) message);
}
*/
import "C"

View file

@ -0,0 +1,87 @@
package srtgo
//#include <srt/srt.h>
import "C"
import (
"encoding/binary"
"fmt"
"net"
"syscall"
"unsafe"
)
func ntohs(val uint16) uint16 {
tmp := ((*[unsafe.Sizeof(val)]byte)(unsafe.Pointer(&val)))
return binary.BigEndian.Uint16((*tmp)[:])
}
func udpAddrFromSockaddr(addr *syscall.RawSockaddrAny) (*net.UDPAddr, error) {
var udpAddr net.UDPAddr
switch addr.Addr.Family {
case afINET6:
ptr := (*syscall.RawSockaddrInet6)(unsafe.Pointer(addr))
udpAddr.Port = int(ntohs(ptr.Port))
udpAddr.IP = ptr.Addr[:]
case afINET4:
ptr := (*syscall.RawSockaddrInet4)(unsafe.Pointer(addr))
udpAddr.Port = int(ntohs(ptr.Port))
udpAddr.IP = net.IPv4(
ptr.Addr[0],
ptr.Addr[1],
ptr.Addr[2],
ptr.Addr[3],
)
default:
return nil, fmt.Errorf("unknown address family: %v", addr.Addr.Family)
}
return &udpAddr, nil
}
func sockAddrFromIp4(ip net.IP, port uint16) (*C.struct_sockaddr, int, error) {
var raw syscall.RawSockaddrInet4
raw.Family = afINET4
p := (*[2]byte)(unsafe.Pointer(&raw.Port))
p[0] = byte(port >> 8)
p[1] = byte(port)
copy(raw.Addr[:], ip.To4())
return (*C.struct_sockaddr)(unsafe.Pointer(&raw)), int(sizeofSockAddrInet4), nil
}
func sockAddrFromIp6(ip net.IP, port uint16) (*C.struct_sockaddr, int, error) {
var raw syscall.RawSockaddrInet6
raw.Family = afINET6
p := (*[2]byte)(unsafe.Pointer(&raw.Port))
p[0] = byte(port >> 8)
p[1] = byte(port)
copy(raw.Addr[:], ip.To16())
return (*C.struct_sockaddr)(unsafe.Pointer(&raw)), int(sizeofSockAddrInet6), nil
}
func CreateAddrInet(name string, port uint16) (*C.struct_sockaddr, int, error) {
ip := net.ParseIP(name)
if ip == nil {
ips, err := net.LookupIP(name)
if err != nil {
return nil, 0, fmt.Errorf("Error in CreateAddrInet, LookupIP")
}
ip = ips[0]
}
if ip.To4() != nil {
return sockAddrFromIp4(ip, port)
} else if ip.To16() != nil {
return sockAddrFromIp6(ip, port)
}
return nil, 0, fmt.Errorf("Error in CreateAddrInet, LookupIP")
}

View file

@ -0,0 +1,17 @@
//go:build !windows
package srtgo
import (
"syscall"
"golang.org/x/sys/unix"
)
const (
sizeofSockAddrInet4 = syscall.SizeofSockaddrInet4
sizeofSockAddrInet6 = syscall.SizeofSockaddrInet6
sizeofSockaddrAny = syscall.SizeofSockaddrAny
afINET4 = unix.AF_INET
afINET6 = unix.AF_INET6
)

View file

@ -0,0 +1,29 @@
//go:build windows
package srtgo
import (
"unsafe"
"golang.org/x/sys/windows"
)
const (
afINET4 = windows.AF_INET
afINET6 = windows.AF_INET6
)
var (
sizeofSockAddrInet4 uint64 = 0
sizeofSockAddrInet6 uint64 = 0
sizeofSockaddrAny uint64 = 0
)
func init() {
inet4 := windows.RawSockaddrInet4{}
inet6 := windows.RawSockaddrInet6{}
any := windows.RawSockaddrAny{}
sizeofSockAddrInet4 = uint64(unsafe.Sizeof(inet4))
sizeofSockAddrInet6 = uint64(unsafe.Sizeof(inet6))
sizeofSockaddrAny = uint64(unsafe.Sizeof(any))
}

View file

@ -0,0 +1,269 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
*/
import "C"
import (
"sync"
"sync/atomic"
"time"
)
const (
pollDefault = int32(iota)
pollReady = int32(iota)
pollWait = int32(iota)
)
type PollMode int
const (
ModeRead = PollMode(iota)
ModeWrite
)
/*
pollDesc contains the polling state for the associated SrtSocket
closing: socket is closing, reject all poll operations
pollErr: an error occured on the socket, indicates it's not useable anymore.
unblockRd: is used to unblock the poller when the socket becomes ready for io
rdState: polling state for read operations
rdDeadline: deadline in NS before poll operation times out, -1 means timedout (needs to be cleared), 0 is without timeout
rdSeq: sequence number protects against spurious signalling of timeouts when timer is reset.
rdTimer: timer used to enforce deadline.
*/
type pollDesc struct {
lock sync.Mutex
closing bool
fd C.SRTSOCKET
pollErr bool
unblockRd chan interface{}
rdState int32
rdLock sync.Mutex
rdDeadline int64
rdSeq int64
rdTimer *time.Timer
rtSeq int64
unblockWr chan interface{}
wrState int32
wrLock sync.Mutex
wdDeadline int64
wdSeq int64
wdTimer *time.Timer
wtSeq int64
pollS *pollServer
}
var pdPool = sync.Pool{
New: func() interface{} {
return &pollDesc{
unblockRd: make(chan interface{}, 1),
unblockWr: make(chan interface{}, 1),
rdTimer: time.NewTimer(0),
wdTimer: time.NewTimer(0),
}
},
}
func pollDescInit(s C.SRTSOCKET) *pollDesc {
pd := pdPool.Get().(*pollDesc)
pd.lock.Lock()
defer pd.lock.Unlock()
pd.fd = s
pd.rdState = pollDefault
pd.wrState = pollDefault
pd.pollS = pollServerCtx()
pd.closing = false
pd.pollErr = false
pd.rdSeq++
pd.wdSeq++
pd.pollS.pollOpen(pd)
return pd
}
func (pd *pollDesc) release() {
pd.lock.Lock()
defer pd.lock.Unlock()
if !pd.closing || pd.rdState == pollWait || pd.wrState == pollWait {
panic("returning open or blocked upon pollDesc")
}
pd.fd = 0
pdPool.Put(pd)
}
func (pd *pollDesc) wait(mode PollMode) error {
defer pd.reset(mode)
if err := pd.checkPollErr(mode); err != nil {
return err
}
state := &pd.rdState
unblockChan := pd.unblockRd
expiryChan := pd.rdTimer.C
timerSeq := int64(0)
pd.lock.Lock()
if mode == ModeRead {
timerSeq = pd.rtSeq
pd.rdLock.Lock()
defer pd.rdLock.Unlock()
} else if mode == ModeWrite {
timerSeq = pd.wtSeq
state = &pd.wrState
unblockChan = pd.unblockWr
expiryChan = pd.wdTimer.C
pd.wrLock.Lock()
defer pd.wrLock.Unlock()
}
for {
old := *state
if old == pollReady {
*state = pollDefault
pd.lock.Unlock()
return nil
}
if atomic.CompareAndSwapInt32(state, pollDefault, pollWait) {
break
}
}
pd.lock.Unlock()
wait:
for {
select {
case <-unblockChan:
break wait
case <-expiryChan:
pd.lock.Lock()
if mode == ModeRead {
if timerSeq == pd.rdSeq {
pd.rdDeadline = -1
pd.lock.Unlock()
break wait
}
timerSeq = pd.rtSeq
}
if mode == ModeWrite {
if timerSeq == pd.wdSeq {
pd.wdDeadline = -1
pd.lock.Unlock()
break wait
}
timerSeq = pd.wtSeq
}
pd.lock.Unlock()
}
}
err := pd.checkPollErr(mode)
return err
}
func (pd *pollDesc) close() {
pd.lock.Lock()
defer pd.lock.Unlock()
if pd.closing {
return
}
pd.closing = true
pd.pollS.pollClose(pd)
}
func (pd *pollDesc) checkPollErr(mode PollMode) error {
pd.lock.Lock()
defer pd.lock.Unlock()
if pd.closing {
return &SrtSocketClosed{}
}
if mode == ModeRead && pd.rdDeadline < 0 || mode == ModeWrite && pd.wdDeadline < 0 {
return &SrtEpollTimeout{}
}
if pd.pollErr {
return &SrtSocketClosed{}
}
return nil
}
func (pd *pollDesc) setDeadline(t time.Time, mode PollMode) {
pd.lock.Lock()
defer pd.lock.Unlock()
var d int64
if !t.IsZero() {
d = int64(time.Until(t))
if d == 0 {
d = -1
}
}
if mode == ModeRead || mode == ModeRead+ModeWrite {
pd.rdSeq++
pd.rtSeq = pd.rdSeq
if pd.rdDeadline > 0 {
pd.rdTimer.Stop()
}
pd.rdDeadline = d
if d > 0 {
pd.rdTimer.Reset(time.Duration(d))
}
if d < 0 {
pd.unblock(ModeRead, false, false)
}
}
if mode == ModeWrite || mode == ModeRead+ModeWrite {
pd.wdSeq++
pd.wtSeq = pd.wdSeq
if pd.wdDeadline > 0 {
pd.wdTimer.Stop()
}
pd.wdDeadline = d
if d > 0 {
pd.wdTimer.Reset(time.Duration(d))
}
if d < 0 {
pd.unblock(ModeWrite, false, false)
}
}
}
func (pd *pollDesc) unblock(mode PollMode, pollerr, ioready bool) {
if pollerr {
pd.lock.Lock()
pd.pollErr = pollerr
pd.lock.Unlock()
}
state := &pd.rdState
unblockChan := pd.unblockRd
if mode == ModeWrite {
state = &pd.wrState
unblockChan = pd.unblockWr
}
pd.lock.Lock()
old := atomic.LoadInt32(state)
if ioready {
atomic.StoreInt32(state, pollReady)
}
pd.lock.Unlock()
if old == pollWait {
//make sure we never block here
select {
case unblockChan <- struct{}{}:
//
default:
//
}
}
}
func (pd *pollDesc) reset(mode PollMode) {
if mode == ModeRead {
pd.rdLock.Lock()
pd.rdState = pollDefault
pd.rdLock.Unlock()
} else if mode == ModeWrite {
pd.wrLock.Lock()
pd.wrState = pollDefault
pd.wrLock.Unlock()
}
}

View file

@ -0,0 +1,109 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
*/
import "C"
import (
"sync"
"unsafe"
)
var (
phctx *pollServer
once sync.Once
)
func pollServerCtx() *pollServer {
once.Do(pollServerCtxInit)
return phctx
}
func pollServerCtxInit() {
eid := C.srt_epoll_create()
C.srt_epoll_set(eid, C.SRT_EPOLL_ENABLE_EMPTY)
phctx = &pollServer{
srtEpollDescr: eid,
pollDescs: make(map[C.SRTSOCKET]*pollDesc),
}
go phctx.run()
}
type pollServer struct {
srtEpollDescr C.int
pollDescLock sync.Mutex
pollDescs map[C.SRTSOCKET]*pollDesc
}
func (p *pollServer) pollOpen(pd *pollDesc) {
//use uint because otherwise with ET it would overflow :/ (srt should accept an uint instead, or fix it's SRT_EPOLL_ET definition)
events := C.uint(C.SRT_EPOLL_IN | C.SRT_EPOLL_OUT | C.SRT_EPOLL_ERR | C.SRT_EPOLL_ET)
//via unsafe.Pointer because we cannot cast *C.uint to *C.int directly
//block poller
p.pollDescLock.Lock()
ret := C.srt_epoll_add_usock(p.srtEpollDescr, pd.fd, (*C.int)(unsafe.Pointer(&events)))
if ret == -1 {
panic("ERROR ADDING FD TO EPOLL")
}
p.pollDescs[pd.fd] = pd
p.pollDescLock.Unlock()
}
func (p *pollServer) pollClose(pd *pollDesc) {
sockstate := C.srt_getsockstate(pd.fd)
//Broken/closed sockets get removed internally by SRT lib
if sockstate == C.SRTS_BROKEN || sockstate == C.SRTS_CLOSING || sockstate == C.SRTS_CLOSED || sockstate == C.SRTS_NONEXIST {
return
}
ret := C.srt_epoll_remove_usock(p.srtEpollDescr, pd.fd)
if ret == -1 {
panic("ERROR REMOVING FD FROM EPOLL")
}
p.pollDescLock.Lock()
delete(p.pollDescs, pd.fd)
p.pollDescLock.Unlock()
}
func init() {
}
func (p *pollServer) run() {
timeoutMs := C.int64_t(-1)
fds := [128]C.SRT_EPOLL_EVENT{}
fdlen := C.int(128)
for {
res := C.srt_epoll_uwait(p.srtEpollDescr, &fds[0], fdlen, timeoutMs)
if res == 0 {
continue //Shouldn't happen with -1
} else if res == -1 {
panic("srt_epoll_error")
} else if res > 0 {
max := int(res)
if fdlen < res {
max = int(fdlen)
}
p.pollDescLock.Lock()
for i := 0; i < max; i++ {
s := fds[i].fd
events := fds[i].events
pd := p.pollDescs[s]
if events&C.SRT_EPOLL_ERR != 0 {
pd.unblock(ModeRead, true, false)
pd.unblock(ModeWrite, true, false)
continue
}
if events&C.SRT_EPOLL_IN != 0 {
pd.unblock(ModeRead, false, true)
}
if events&C.SRT_EPOLL_OUT != 0 {
pd.unblock(ModeWrite, false, true)
}
}
p.pollDescLock.Unlock()
}
}
}

View file

@ -0,0 +1,54 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
int srt_recvmsg2_wrapped(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL *mctrl, int *srterror, int *syserror)
{
int ret = srt_recvmsg2(u, buf, len, mctrl);
if (ret < 0) {
*srterror = srt_getlasterror(syserror);
}
return ret;
}
*/
import "C"
import (
"errors"
"syscall"
"unsafe"
)
func srtRecvMsg2Impl(u C.SRTSOCKET, buf []byte, msgctrl *C.SRT_MSGCTRL) (n int, err error) {
srterr := C.int(0)
syserr := C.int(0)
n = int(C.srt_recvmsg2_wrapped(u, (*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf)), msgctrl, &srterr, &syserr))
if n < 0 {
srterror := SRTErrno(srterr)
if syserr < 0 {
srterror.wrapSysErr(syscall.Errno(syserr))
}
err = srterror
n = 0
}
return
}
// Read data from the SRT socket
func (s SrtSocket) Read(b []byte) (n int, err error) {
//Fastpath
if !s.blocking {
s.pd.reset(ModeRead)
}
n, err = srtRecvMsg2Impl(s.socket, b, nil)
for {
if !errors.Is(err, error(EAsyncRCV)) || s.blocking {
return
}
s.pd.wait(ModeRead)
n, err = srtRecvMsg2Impl(s.socket, b, nil)
}
}

View file

@ -0,0 +1,578 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
#include <srt/access_control.h>
#include "callback.h"
static const SRTSOCKET get_srt_invalid_sock() { return SRT_INVALID_SOCK; };
static const int get_srt_error() { return SRT_ERROR; };
static const int get_srt_error_reject_predefined() { return SRT_REJC_PREDEFINED; };
static const int get_srt_error_reject_userdefined() { return SRT_REJC_USERDEFINED; };
*/
import "C"
import (
"errors"
"fmt"
"net"
"runtime"
"strconv"
"sync"
"syscall"
"time"
"unsafe"
gopointer "github.com/mattn/go-pointer"
)
// SRT Socket mode
const (
ModeFailure = iota
ModeListener
ModeCaller
ModeRendezvouz
)
// Binding ops
const (
bindingPre = 0
bindingPost = 1
)
// SrtSocket - SRT socket
type SrtSocket struct {
socket C.int
blocking bool
pd *pollDesc
host string
port uint16
options map[string]string
mode int
pktSize int
pollTimeout int64
}
var (
callbackMutex sync.Mutex
listenCallbackMap map[C.int]unsafe.Pointer = make(map[C.int]unsafe.Pointer)
connectCallbackMap map[C.int]unsafe.Pointer = make(map[C.int]unsafe.Pointer)
)
// Static consts from library
var (
SRT_INVALID_SOCK = C.get_srt_invalid_sock()
SRT_ERROR = C.get_srt_error()
SRTS_CONNECTED = C.SRTS_CONNECTED
)
const defaultPacketSize = 1456
// InitSRT - Initialize srt library
func InitSRT() {
C.srt_startup()
}
// CleanupSRT - Cleanup SRT lib
func CleanupSRT() {
C.srt_cleanup()
}
// NewSrtSocket - Create a new SRT Socket
func NewSrtSocket(host string, port uint16, options map[string]string) *SrtSocket {
s := new(SrtSocket)
s.socket = C.srt_create_socket()
if s.socket == SRT_INVALID_SOCK {
return nil
}
s.host = host
s.port = port
s.options = options
s.pollTimeout = -1
val, exists := options["pktsize"]
if exists {
pktSize, err := strconv.Atoi(val)
if err != nil {
s.pktSize = pktSize
}
}
if s.pktSize <= 0 {
s.pktSize = defaultPacketSize
}
val, exists = options["blocking"]
if exists && val != "0" {
s.blocking = true
}
if !s.blocking {
s.pd = pollDescInit(s.socket)
}
finalizer := func(obj interface{}) {
sf := obj.(*SrtSocket)
sf.Close()
if sf.pd != nil {
sf.pd.release()
}
}
//Cleanup SrtSocket if no references exist anymore
runtime.SetFinalizer(s, finalizer)
var err error
s.mode, err = s.preconfiguration()
if err != nil {
return nil
}
return s
}
func newFromSocket(acceptSocket *SrtSocket, socket C.SRTSOCKET) (*SrtSocket, error) {
s := new(SrtSocket)
s.socket = socket
s.pktSize = acceptSocket.pktSize
s.blocking = acceptSocket.blocking
s.pollTimeout = acceptSocket.pollTimeout
err := acceptSocket.postconfiguration(s)
if err != nil {
return nil, err
}
if !s.blocking {
s.pd = pollDescInit(s.socket)
}
finalizer := func(obj interface{}) {
sf := obj.(*SrtSocket)
sf.Close()
if sf.pd != nil {
sf.pd.release()
}
}
//Cleanup SrtSocket if no references exist anymore
runtime.SetFinalizer(s, finalizer)
return s, nil
}
func (s SrtSocket) GetSocket() C.int {
return s.socket
}
// Listen for incoming connections. The backlog setting defines how many sockets
// may be allowed to wait until they are accepted (excessive connection requests
// are rejected in advance)
func (s *SrtSocket) Listen(backlog int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
nbacklog := C.int(backlog)
sa, salen, err := CreateAddrInet(s.host, s.port)
if err != nil {
return err
}
res := C.srt_bind(s.socket, sa, C.int(salen))
if res == SRT_ERROR {
C.srt_close(s.socket)
return fmt.Errorf("Error in srt_bind: %w", srtGetAndClearError())
}
res = C.srt_listen(s.socket, nbacklog)
if res == SRT_ERROR {
C.srt_close(s.socket)
return fmt.Errorf("Error in srt_listen: %w", srtGetAndClearError())
}
err = s.postconfiguration(s)
if err != nil {
return fmt.Errorf("Error setting post socket options")
}
return nil
}
// Connect to a remote endpoint
func (s *SrtSocket) Connect() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
sa, salen, err := CreateAddrInet(s.host, s.port)
if err != nil {
return err
}
res := C.srt_connect(s.socket, sa, C.int(salen))
if res == SRT_ERROR {
C.srt_close(s.socket)
return srtGetAndClearError()
}
if !s.blocking {
if err := s.pd.wait(ModeWrite); err != nil {
return err
}
}
err = s.postconfiguration(s)
if err != nil {
return fmt.Errorf("Error setting post socket options in connect")
}
return nil
}
// Stats - Retrieve stats from the SRT socket
func (s SrtSocket) Stats() (*SrtStats, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var stats C.SRT_TRACEBSTATS = C.SRT_TRACEBSTATS{}
var b C.int = 1
if C.srt_bstats(s.socket, &stats, b) == SRT_ERROR {
return nil, fmt.Errorf("Error getting stats, %w", srtGetAndClearError())
}
return newSrtStats(&stats), nil
}
// Mode - Return working mode of the SRT socket
func (s SrtSocket) Mode() int {
return s.mode
}
// PacketSize - Return packet size of the SRT socket
func (s SrtSocket) PacketSize() int {
return s.pktSize
}
// PollTimeout - Return polling max time, for connect/read/write operations.
// Only applied when socket is in non-blocking mode.
func (s SrtSocket) PollTimeout() time.Duration {
return time.Duration(s.pollTimeout) * time.Millisecond
}
// SetPollTimeout - Sets polling max time, for connect/read/write operations.
// Only applied when socket is in non-blocking mode.
func (s *SrtSocket) SetPollTimeout(pollTimeout time.Duration) {
s.pollTimeout = pollTimeout.Milliseconds()
}
func (s *SrtSocket) SetDeadline(deadline time.Time) {
s.pd.setDeadline(deadline, ModeRead+ModeWrite)
}
func (s *SrtSocket) SetReadDeadline(deadline time.Time) {
s.pd.setDeadline(deadline, ModeRead)
}
func (s *SrtSocket) SetWriteDeadline(deadline time.Time) {
s.pd.setDeadline(deadline, ModeWrite)
}
// Close the SRT socket
func (s *SrtSocket) Close() {
C.srt_close(s.socket)
s.socket = SRT_INVALID_SOCK
if !s.blocking {
s.pd.close()
}
callbackMutex.Lock()
if ptr, exists := listenCallbackMap[s.socket]; exists {
gopointer.Unref(ptr)
}
if ptr, exists := connectCallbackMap[s.socket]; exists {
gopointer.Unref(ptr)
}
callbackMutex.Unlock()
}
// ListenCallbackFunc specifies a function to be called before a connecting socket is passed to accept
type ListenCallbackFunc func(socket *SrtSocket, version int, addr *net.UDPAddr, streamid string) bool
//export srtListenCBWrapper
func srtListenCBWrapper(arg unsafe.Pointer, socket C.SRTSOCKET, hsVersion C.int, peeraddr *C.struct_sockaddr, streamid *C.char) C.int {
userCB := gopointer.Restore(arg).(ListenCallbackFunc)
s := new(SrtSocket)
s.socket = socket
udpAddr, _ := udpAddrFromSockaddr((*syscall.RawSockaddrAny)(unsafe.Pointer(peeraddr)))
if userCB(s, int(hsVersion), udpAddr, C.GoString(streamid)) {
return 0
}
return SRT_ERROR
}
// SetListenCallback - set a function to be called early in the handshake before a client
// is handed to accept on a listening socket.
// The connection can be rejected by returning false from the callback.
// See examples/echo-receiver for more details.
func (s SrtSocket) SetListenCallback(cb ListenCallbackFunc) {
ptr := gopointer.Save(cb)
C.srt_listen_callback(s.socket, (*C.srt_listen_callback_fn)(C.srtListenCB), ptr)
callbackMutex.Lock()
defer callbackMutex.Unlock()
if listenCallbackMap[s.socket] != nil {
gopointer.Unref(listenCallbackMap[s.socket])
}
listenCallbackMap[s.socket] = ptr
}
// ConnectCallbackFunc specifies a function to be called after a socket or connection in a group has failed.
type ConnectCallbackFunc func(socket *SrtSocket, err error, addr *net.UDPAddr, token int)
//export srtConnectCBWrapper
func srtConnectCBWrapper(arg unsafe.Pointer, socket C.SRTSOCKET, errcode C.int, peeraddr *C.struct_sockaddr, token C.int) {
userCB := gopointer.Restore(arg).(ConnectCallbackFunc)
s := new(SrtSocket)
s.socket = socket
udpAddr, _ := udpAddrFromSockaddr((*syscall.RawSockaddrAny)(unsafe.Pointer(peeraddr)))
userCB(s, SRTErrno(errcode), udpAddr, int(token))
}
// SetConnectCallback - set a function to be called after a socket or connection in a group has failed
// Note that the function is not guaranteed to be called if the socket is set to blocking mode.
func (s SrtSocket) SetConnectCallback(cb ConnectCallbackFunc) {
ptr := gopointer.Save(cb)
C.srt_connect_callback(s.socket, (*C.srt_connect_callback_fn)(C.srtConnectCB), ptr)
callbackMutex.Lock()
defer callbackMutex.Unlock()
if connectCallbackMap[s.socket] != nil {
gopointer.Unref(connectCallbackMap[s.socket])
}
connectCallbackMap[s.socket] = ptr
}
// Rejection reasons
var (
// Start of range for predefined rejection reasons
RejectionReasonPredefined = int(C.get_srt_error_reject_predefined())
// General syntax error in the SocketID specification (also a fallback code for undefined cases)
RejectionReasonBadRequest = RejectionReasonPredefined + 400
// Authentication failed, provided that the user was correctly identified and access to the required resource would be granted
RejectionReasonUnauthorized = RejectionReasonPredefined + 401
// The server is too heavily loaded, or you have exceeded credits for accessing the service and the resource.
RejectionReasonOverload = RejectionReasonPredefined + 402
// Access denied to the resource by any kind of reason
RejectionReasonForbidden = RejectionReasonPredefined + 403
// Resource not found at this time.
RejectionReasonNotFound = RejectionReasonPredefined + 404
// The mode specified in `m` key in StreamID is not supported for this request.
RejectionReasonBadMode = RejectionReasonPredefined + 405
// The requested parameters specified in SocketID cannot be satisfied for the requested resource. Also when m=publish and the data format is not acceptable.
RejectionReasonUnacceptable = RejectionReasonPredefined + 406
// Start of range for application defined rejection reasons
RejectionReasonUserDefined = int(C.get_srt_error_reject_predefined())
)
// SetRejectReason - set custom reason for connection reject
func (s SrtSocket) SetRejectReason(value int) error {
res := C.srt_setrejectreason(s.socket, C.int(value))
if res == SRT_ERROR {
return errors.New(C.GoString(C.srt_getlasterror_str()))
}
return nil
}
// GetSockOptByte - return byte value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptByte(opt int) (byte, error) {
var v byte
l := 1
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
return v, err
}
// GetSockOptBool - return bool value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptBool(opt int) (bool, error) {
var v int32
l := 4
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
if v == 1 {
return true, err
}
return false, err
}
// GetSockOptInt - return int value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptInt(opt int) (int, error) {
var v int32
l := 4
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
return int(v), err
}
// GetSockOptInt64 - return int64 value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptInt64(opt int) (int64, error) {
var v int64
l := 8
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
return v, err
}
// GetSockOptString - return string value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptString(opt int) (string, error) {
buf := make([]byte, 256)
l := len(buf)
err := s.getSockOpt(opt, unsafe.Pointer(&buf[0]), &l)
if err != nil {
return "", err
}
return string(buf[:l]), nil
}
// SetSockOptByte - set byte value using srt_setsockopt
func (s SrtSocket) SetSockOptByte(opt int, value byte) error {
return s.setSockOpt(opt, unsafe.Pointer(&value), 1)
}
// SetSockOptBool - set bool value using srt_setsockopt
func (s SrtSocket) SetSockOptBool(opt int, value bool) error {
val := int(0)
if value {
val = 1
}
return s.setSockOpt(opt, unsafe.Pointer(&val), 4)
}
// SetSockOptInt - set int value using srt_setsockopt
func (s SrtSocket) SetSockOptInt(opt int, value int) error {
return s.setSockOpt(opt, unsafe.Pointer(&value), 4)
}
// SetSockOptInt64 - set int64 value using srt_setsockopt
func (s SrtSocket) SetSockOptInt64(opt int, value int64) error {
return s.setSockOpt(opt, unsafe.Pointer(&value), 8)
}
// SetSockOptString - set string value using srt_setsockopt
func (s SrtSocket) SetSockOptString(opt int, value string) error {
return s.setSockOpt(opt, unsafe.Pointer(&[]byte(value)[0]), len(value))
}
func (s SrtSocket) setSockOpt(opt int, data unsafe.Pointer, size int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
res := C.srt_setsockopt(s.socket, 0, C.SRT_SOCKOPT(opt), data, C.int(size))
if res == -1 {
return fmt.Errorf("Error calling srt_setsockopt %w", srtGetAndClearError())
}
return nil
}
func (s SrtSocket) getSockOpt(opt int, data unsafe.Pointer, size *int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
res := C.srt_getsockopt(s.socket, 0, C.SRT_SOCKOPT(opt), data, (*C.int)(unsafe.Pointer(size)))
if res == -1 {
return fmt.Errorf("Error calling srt_getsockopt %w", srtGetAndClearError())
}
return nil
}
func (s SrtSocket) preconfiguration() (int, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var blocking C.int
if s.blocking {
blocking = C.int(1)
} else {
blocking = C.int(0)
}
result := C.srt_setsockopt(s.socket, 0, C.SRTO_RCVSYN, unsafe.Pointer(&blocking), C.int(unsafe.Sizeof(blocking)))
if result == -1 {
return ModeFailure, fmt.Errorf("could not set SRTO_RCVSYN flag: %w", srtGetAndClearError())
}
var mode int
modeVal, ok := s.options["mode"]
if !ok {
modeVal = "default"
}
if modeVal == "client" || modeVal == "caller" {
mode = ModeCaller
} else if modeVal == "server" || modeVal == "listener" {
mode = ModeListener
} else if modeVal == "default" {
if s.host == "" {
mode = ModeListener
} else {
// Host is given, so check also "adapter"
if _, ok := s.options["adapter"]; ok {
mode = ModeRendezvouz
} else {
mode = ModeCaller
}
}
} else {
mode = ModeFailure
}
if linger, ok := s.options["linger"]; ok {
li, err := strconv.Atoi(linger)
if err == nil {
if err := setSocketLingerOption(s.socket, int32(li)); err != nil {
return ModeFailure, fmt.Errorf("could not set LINGER option %w", err)
}
} else {
return ModeFailure, fmt.Errorf("could not set LINGER option %w", err)
}
}
err := setSocketOptions(s.socket, bindingPre, s.options)
if err != nil {
return ModeFailure, fmt.Errorf("Error setting socket options: %w", err)
}
return mode, nil
}
func (s SrtSocket) postconfiguration(sck *SrtSocket) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var blocking C.int
if s.blocking {
blocking = 1
} else {
blocking = 0
}
res := C.srt_setsockopt(sck.socket, 0, C.SRTO_SNDSYN, unsafe.Pointer(&blocking), C.int(unsafe.Sizeof(blocking)))
if res == -1 {
return fmt.Errorf("Error in postconfiguration setting SRTO_SNDSYN: %w", srtGetAndClearError())
}
res = C.srt_setsockopt(sck.socket, 0, C.SRTO_RCVSYN, unsafe.Pointer(&blocking), C.int(unsafe.Sizeof(blocking)))
if res == -1 {
return fmt.Errorf("Error in postconfiguration setting SRTO_RCVSYN: %w", srtGetAndClearError())
}
err := setSocketOptions(sck.socket, bindingPost, s.options)
return err
}

View file

@ -0,0 +1,191 @@
package srtgo
// #cgo LDFLAGS: -lsrt
// #include <srt/srt.h>
import "C"
import (
"errors"
"fmt"
"strconv"
"syscall"
"unsafe"
)
const (
transTypeLive = 0
transTypeFile = 1
)
const (
tInteger32 = 0
tInteger64 = 1
tString = 2
tBoolean = 3
tTransType = 4
SRTO_TRANSTYPE = C.SRTO_TRANSTYPE
SRTO_MAXBW = C.SRTO_MAXBW
SRTO_PBKEYLEN = C.SRTO_PBKEYLEN
SRTO_PASSPHRASE = C.SRTO_PASSPHRASE
SRTO_MSS = C.SRTO_MSS
SRTO_FC = C.SRTO_FC
SRTO_SNDBUF = C.SRTO_SNDBUF
SRTO_RCVBUF = C.SRTO_RCVBUF
SRTO_IPTTL = C.SRTO_IPTTL
SRTO_IPTOS = C.SRTO_IPTOS
SRTO_INPUTBW = C.SRTO_INPUTBW
SRTO_OHEADBW = C.SRTO_OHEADBW
SRTO_LATENCY = C.SRTO_LATENCY
SRTO_TSBPDMODE = C.SRTO_TSBPDMODE
SRTO_TLPKTDROP = C.SRTO_TLPKTDROP
SRTO_SNDDROPDELAY = C.SRTO_SNDDROPDELAY
SRTO_NAKREPORT = C.SRTO_NAKREPORT
SRTO_CONNTIMEO = C.SRTO_CONNTIMEO
SRTO_LOSSMAXTTL = C.SRTO_LOSSMAXTTL
SRTO_RCVLATENCY = C.SRTO_RCVLATENCY
SRTO_PEERLATENCY = C.SRTO_PEERLATENCY
SRTO_MINVERSION = C.SRTO_MINVERSION
SRTO_STREAMID = C.SRTO_STREAMID
SRTO_CONGESTION = C.SRTO_CONGESTION
SRTO_MESSAGEAPI = C.SRTO_MESSAGEAPI
SRTO_PAYLOADSIZE = C.SRTO_PAYLOADSIZE
SRTO_KMREFRESHRATE = C.SRTO_KMREFRESHRATE
SRTO_KMPREANNOUNCE = C.SRTO_KMPREANNOUNCE
SRTO_ENFORCEDENCRYPTION = C.SRTO_ENFORCEDENCRYPTION
SRTO_PEERIDLETIMEO = C.SRTO_PEERIDLETIMEO
SRTO_PACKETFILTER = C.SRTO_PACKETFILTER
SRTO_STATE = C.SRTO_STATE
)
type socketOption struct {
name string
level int
option int
binding int
dataType int
}
// List of possible srt socket options
var SocketOptions = []socketOption{
{"transtype", 0, SRTO_TRANSTYPE, bindingPre, tTransType},
{"maxbw", 0, SRTO_MAXBW, bindingPre, tInteger64},
{"pbkeylen", 0, SRTO_PBKEYLEN, bindingPre, tInteger32},
{"passphrase", 0, SRTO_PASSPHRASE, bindingPre, tString},
{"mss", 0, SRTO_MSS, bindingPre, tInteger32},
{"fc", 0, SRTO_FC, bindingPre, tInteger32},
{"sndbuf", 0, SRTO_SNDBUF, bindingPre, tInteger32},
{"rcvbuf", 0, SRTO_RCVBUF, bindingPre, tInteger32},
{"ipttl", 0, SRTO_IPTTL, bindingPre, tInteger32},
{"iptos", 0, SRTO_IPTOS, bindingPre, tInteger32},
{"inputbw", 0, SRTO_INPUTBW, bindingPost, tInteger64},
{"oheadbw", 0, SRTO_OHEADBW, bindingPost, tInteger32},
{"latency", 0, SRTO_LATENCY, bindingPre, tInteger32},
{"tsbpdmode", 0, SRTO_TSBPDMODE, bindingPre, tBoolean},
{"tlpktdrop", 0, SRTO_TLPKTDROP, bindingPre, tBoolean},
{"snddropdelay", 0, SRTO_SNDDROPDELAY, bindingPost, tInteger32},
{"nakreport", 0, SRTO_NAKREPORT, bindingPre, tBoolean},
{"conntimeo", 0, SRTO_CONNTIMEO, bindingPre, tInteger32},
{"lossmaxttl", 0, SRTO_LOSSMAXTTL, bindingPre, tInteger32},
{"rcvlatency", 0, SRTO_RCVLATENCY, bindingPre, tInteger32},
{"peerlatency", 0, SRTO_PEERLATENCY, bindingPre, tInteger32},
{"minversion", 0, SRTO_MINVERSION, bindingPre, tInteger32},
{"streamid", 0, SRTO_STREAMID, bindingPre, tString},
{"congestion", 0, SRTO_CONGESTION, bindingPre, tString},
{"messageapi", 0, SRTO_MESSAGEAPI, bindingPre, tBoolean},
{"payloadsize", 0, SRTO_PAYLOADSIZE, bindingPre, tInteger32},
{"kmrefreshrate", 0, SRTO_KMREFRESHRATE, bindingPre, tInteger32},
{"kmpreannounce", 0, SRTO_KMPREANNOUNCE, bindingPre, tInteger32},
{"enforcedencryption", 0, SRTO_ENFORCEDENCRYPTION, bindingPre, tBoolean},
{"peeridletimeo", 0, SRTO_PEERIDLETIMEO, bindingPre, tInteger32},
{"packetfilter", 0, SRTO_PACKETFILTER, bindingPre, tString},
}
func setSocketLingerOption(s C.int, li int32) error {
var lin syscall.Linger
lin.Linger = li
if lin.Linger > 0 {
lin.Onoff = 1
} else {
lin.Onoff = 0
}
res := C.srt_setsockopt(s, bindingPre, C.SRTO_LINGER, unsafe.Pointer(&lin), C.int(unsafe.Sizeof(lin)))
if res == SRT_ERROR {
return errors.New("failed to set linger")
}
return nil
}
func getSocketLingerOption(s *SrtSocket) (int32, error) {
var lin syscall.Linger
size := int(unsafe.Sizeof(lin))
err := s.getSockOpt(C.SRTO_LINGER, unsafe.Pointer(&lin), &size)
if err != nil {
return 0, err
}
if lin.Onoff == 0 {
return 0, nil
}
return lin.Linger, nil
}
// Set socket options for SRT
func setSocketOptions(s C.int, binding int, options map[string]string) error {
for _, so := range SocketOptions {
if val, ok := options[so.name]; ok {
if so.binding == binding {
if so.dataType == tInteger32 {
v, err := strconv.Atoi(val)
v32 := int32(v)
if err == nil {
result := C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v32), C.int32_t(unsafe.Sizeof(v32)))
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
}
} else if so.dataType == tInteger64 {
v, err := strconv.ParseInt(val, 10, 64)
if err == nil {
result := C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
}
} else if so.dataType == tString {
sval := C.CString(val)
defer C.free(unsafe.Pointer(sval))
result := C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(sval), C.int32_t(len(val)))
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
} else if so.dataType == tBoolean {
var result C.int
if val == "1" {
v := C.char(1)
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
} else if val == "0" {
v := C.char(0)
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
}
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
} else if so.dataType == tTransType {
var result C.int
if val == "live" {
var v int32 = C.SRTT_LIVE
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
} else if val == "file" {
var v int32 = C.SRTT_FILE
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
}
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s: %w", so.name, val, srtGetAndClearError())
}
}
}
}
}
return nil
}

View file

@ -0,0 +1,188 @@
package srtgo
// #cgo LDFLAGS: -lsrt
// #include <srt/srt.h>
import "C"
type SrtStats struct {
// Global measurements
MsTimeStamp int64 // time since the UDT entity is started, in milliseconds
PktSentTotal int64 // total number of sent data packets, including retransmissions
PktRecvTotal int64 // total number of received packets
PktSndLossTotal int // total number of lost packets (sender side)
PktRcvLossTotal int // total number of lost packets (receiver side)
PktRetransTotal int // total number of retransmitted packets
PktSentACKTotal int // total number of sent ACK packets
PktRecvACKTotal int // total number of received ACK packets
PktSentNAKTotal int // total number of sent NAK packets
PktRecvNAKTotal int // total number of received NAK packets
UsSndDurationTotal int64 // total time duration when UDT is sending data (idle time exclusive)
PktSndDropTotal int // number of too-late-to-send dropped packets
PktRcvDropTotal int // number of too-late-to play missing packets
PktRcvUndecryptTotal int // number of undecrypted packets
ByteSentTotal int64 // total number of sent data bytes, including retransmissions
ByteRecvTotal int64 // total number of received bytes
ByteRcvLossTotal int64 // total number of lost bytes
ByteRetransTotal int64 // total number of retransmitted bytes
ByteSndDropTotal int64 // number of too-late-to-send dropped bytes
ByteRcvDropTotal int64 // number of too-late-to play missing bytes (estimate based on average packet size)
ByteRcvUndecryptTotal int64 // number of undecrypted bytes
// Local measurements
PktSent int64 // number of sent data packets, including retransmissions
PktRecv int64 // number of received packets
PktSndLoss int // number of lost packets (sender side)
PktRcvLoss int // number of lost packets (receiver side)
PktRetrans int // number of retransmitted packets
PktRcvRetrans int // number of retransmitted packets received
PktSentACK int // number of sent ACK packets
PktRecvACK int // number of received ACK packets
PktSentNAK int // number of sent NAK packets
PktRecvNAK int // number of received NAK packets
MbpsSendRate float64 // sending rate in Mb/s
MbpsRecvRate float64 // receiving rate in Mb/s
UsSndDuration int64 // busy sending time (i.e., idle time exclusive)
PktReorderDistance int // size of order discrepancy in received sequences
PktRcvAvgBelatedTime float64 // average time of packet delay for belated packets (packets with sequence past the ACK)
PktRcvBelated int64 // number of received AND IGNORED packets due to having come too late
PktSndDrop int // number of too-late-to-send dropped packets
PktRcvDrop int // number of too-late-to play missing packets
PktRcvUndecrypt int // number of undecrypted packets
ByteSent int64 // number of sent data bytes, including retransmissions
ByteRecv int64 // number of received bytes
ByteRcvLoss int64 // number of retransmitted Bytes
ByteRetrans int64 // number of retransmitted Bytes
ByteSndDrop int64 // number of too-late-to-send dropped Bytes
ByteRcvDrop int64 // number of too-late-to play missing Bytes (estimate based on average packet size)
ByteRcvUndecrypt int64 // number of undecrypted bytes
// Instant measurements
UsPktSndPeriod float64 // packet sending period, in microseconds
PktFlowWindow int // flow window size, in number of packets
PktCongestionWindow int // congestion window size, in number of packets
PktFlightSize int // number of packets on flight
MsRTT float64 // RTT, in milliseconds
MbpsBandwidth float64 // estimated bandwidth, in Mb/s
ByteAvailSndBuf int // available UDT sender buffer size
ByteAvailRcvBuf int // available UDT receiver buffer size
MbpsMaxBW float64 // Transmit Bandwidth ceiling (Mbps)
ByteMSS int // MTU
PktSndBuf int // UnACKed packets in UDT sender
ByteSndBuf int // UnACKed bytes in UDT sender
MsSndBuf int // UnACKed timespan (msec) of UDT sender
MsSndTsbPdDelay int // Timestamp-based Packet Delivery Delay
PktRcvBuf int // Undelivered packets in UDT receiver
ByteRcvBuf int // Undelivered bytes of UDT receiver
MsRcvBuf int // Undelivered timespan (msec) of UDT receiver
MsRcvTsbPdDelay int // Timestamp-based Packet Delivery Delay
PktSndFilterExtraTotal int // number of control packets supplied by packet filter
PktRcvFilterExtraTotal int // number of control packets received and not supplied back
PktRcvFilterSupplyTotal int // number of packets that the filter supplied extra (e.g. FEC rebuilt)
PktRcvFilterLossTotal int // number of packet loss not coverable by filter
PktSndFilterExtra int // number of control packets supplied by packet filter
PktRcvFilterExtra int // number of control packets received and not supplied back
PktRcvFilterSupply int // number of packets that the filter supplied extra (e.g. FEC rebuilt)
PktRcvFilterLoss int // number of packet loss not coverable by filter
PktReorderTolerance int // packet reorder tolerance value
}
func newSrtStats(stats *C.SRT_TRACEBSTATS) *SrtStats {
s := new(SrtStats)
s.MsTimeStamp = int64(stats.msTimeStamp)
s.PktSentTotal = int64(stats.pktSentTotal)
s.PktRecvTotal = int64(stats.pktRecvTotal)
s.PktSndLossTotal = int(stats.pktSndLossTotal)
s.PktRcvLossTotal = int(stats.pktRcvLossTotal)
s.PktRetransTotal = int(stats.pktRetransTotal)
s.PktSentACKTotal = int(stats.pktSentACKTotal)
s.PktRecvACKTotal = int(stats.pktRecvACKTotal)
s.PktSentNAKTotal = int(stats.pktSentNAKTotal)
s.PktRecvNAKTotal = int(stats.pktRecvNAKTotal)
s.UsSndDurationTotal = int64(stats.usSndDurationTotal)
s.PktSndDropTotal = int(stats.pktSndDropTotal)
s.PktRcvDropTotal = int(stats.pktRcvDropTotal)
s.PktRcvUndecryptTotal = int(stats.pktRcvUndecryptTotal)
s.ByteSentTotal = int64(stats.byteSentTotal)
s.ByteRecvTotal = int64(stats.byteRecvTotal)
s.ByteRcvLossTotal = int64(stats.byteRcvLossTotal)
s.ByteRetransTotal = int64(stats.byteRetransTotal)
s.ByteSndDropTotal = int64(stats.byteSndDropTotal)
s.ByteRcvDropTotal = int64(stats.byteRcvDropTotal)
s.ByteRcvUndecryptTotal = int64(stats.byteRcvUndecryptTotal)
s.PktSent = int64(stats.pktSent)
s.PktRecv = int64(stats.pktRecv)
s.PktSndLoss = int(stats.pktSndLoss)
s.PktRcvLoss = int(stats.pktRcvLoss)
s.PktRetrans = int(stats.pktRetrans)
s.PktRcvRetrans = int(stats.pktRcvRetrans)
s.PktSentACK = int(stats.pktSentACK)
s.PktRecvACK = int(stats.pktRecvACK)
s.PktSentNAK = int(stats.pktSentNAK)
s.PktRecvNAK = int(stats.pktRecvNAK)
s.MbpsSendRate = float64(stats.mbpsSendRate)
s.MbpsRecvRate = float64(stats.mbpsRecvRate)
s.UsSndDuration = int64(stats.usSndDuration)
s.PktReorderDistance = int(stats.pktReorderDistance)
s.PktRcvAvgBelatedTime = float64(stats.pktRcvAvgBelatedTime)
s.PktRcvBelated = int64(stats.pktRcvBelated)
s.PktSndDrop = int(stats.pktSndDrop)
s.PktRcvDrop = int(stats.pktRcvDrop)
s.PktRcvUndecrypt = int(stats.pktRcvUndecrypt)
s.ByteSent = int64(stats.byteSent)
s.ByteRecv = int64(stats.byteRecv)
s.ByteRcvLoss = int64(stats.byteRcvLoss)
s.ByteRetrans = int64(stats.byteRetrans)
s.ByteSndDrop = int64(stats.byteSndDrop)
s.ByteRcvDrop = int64(stats.byteRcvDrop)
s.ByteRcvUndecrypt = int64(stats.byteRcvUndecrypt)
s.UsPktSndPeriod = float64(stats.usPktSndPeriod)
s.PktFlowWindow = int(stats.pktFlowWindow)
s.PktCongestionWindow = int(stats.pktCongestionWindow)
s.PktFlightSize = int(stats.pktFlightSize)
s.MsRTT = float64(stats.msRTT)
s.MbpsBandwidth = float64(stats.mbpsBandwidth)
s.ByteAvailSndBuf = int(stats.byteAvailSndBuf)
s.ByteAvailRcvBuf = int(stats.byteAvailRcvBuf)
s.MbpsMaxBW = float64(stats.mbpsMaxBW)
s.ByteMSS = int(stats.byteMSS)
s.PktSndBuf = int(stats.pktSndBuf)
s.ByteSndBuf = int(stats.byteSndBuf)
s.MsSndBuf = int(stats.msSndBuf)
s.MsSndTsbPdDelay = int(stats.msSndTsbPdDelay)
s.PktRcvBuf = int(stats.pktRcvBuf)
s.ByteRcvBuf = int(stats.byteRcvBuf)
s.MsRcvBuf = int(stats.msRcvBuf)
s.MsRcvTsbPdDelay = int(stats.msRcvTsbPdDelay)
s.PktSndFilterExtraTotal = int(stats.pktSndFilterExtraTotal)
s.PktRcvFilterExtraTotal = int(stats.pktRcvFilterExtraTotal)
s.PktRcvFilterSupplyTotal = int(stats.pktRcvFilterSupplyTotal)
s.PktRcvFilterLossTotal = int(stats.pktRcvFilterLossTotal)
s.PktSndFilterExtra = int(stats.pktSndFilterExtra)
s.PktRcvFilterExtra = int(stats.pktRcvFilterExtra)
s.PktRcvFilterSupply = int(stats.pktRcvFilterSupply)
s.PktRcvFilterLoss = int(stats.pktRcvFilterLoss)
s.PktReorderTolerance = int(stats.pktReorderTolerance)
return s
}

View file

@ -0,0 +1,55 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
int srt_sendmsg2_wrapped(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL *mctrl, int *srterror, int *syserror)
{
int ret = srt_sendmsg2(u, buf, len, mctrl);
if (ret < 0) {
*srterror = srt_getlasterror(syserror);
}
return ret;
}
*/
import "C"
import (
"errors"
"syscall"
"unsafe"
)
func srtSendMsg2Impl(u C.SRTSOCKET, buf []byte, msgctrl *C.SRT_MSGCTRL) (n int, err error) {
srterr := C.int(0)
syserr := C.int(0)
n = int(C.srt_sendmsg2_wrapped(u, (*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf)), msgctrl, &srterr, &syserr))
if n < 0 {
srterror := SRTErrno(srterr)
if syserr < 0 {
srterror.wrapSysErr(syscall.Errno(syserr))
}
err = srterror
n = 0
}
return
}
// Write data to the SRT socket
func (s SrtSocket) Write(b []byte) (n int, err error) {
//Fastpath:
if !s.blocking {
s.pd.reset(ModeWrite)
}
n, err = srtSendMsg2Impl(s.socket, b, nil)
for {
if !errors.Is(err, error(EAsyncSND)) || s.blocking {
return
}
s.pd.wait(ModeWrite)
n, err = srtSendMsg2Impl(s.socket, b, nil)
}
}