mirror of https://github.com/ossrs/srs.git
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>pull/4100/head
parent
e3d74fb045
commit
1f9309ae25
@ -0,0 +1,210 @@
|
|||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// # Copyright (c) 2021 Winlin
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
package live
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/haivision/srtgo"
|
||||||
|
"github.com/ossrs/go-oryx-lib/amf0"
|
||||||
|
"github.com/ossrs/go-oryx-lib/errors"
|
||||||
|
"github.com/ossrs/go-oryx-lib/logger"
|
||||||
|
"github.com/ossrs/go-oryx-lib/rtmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startPublish(ctx context.Context, r string, closeAfterPublished bool) error {
|
||||||
|
ctx = logger.WithContext(ctx)
|
||||||
|
logger.Tf(ctx, "Run publish url=%v, cap=%v", r, closeAfterPublished)
|
||||||
|
|
||||||
|
u, err := url.Parse(r)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "parse %v", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "rtmp" {
|
||||||
|
return startPublishRTMP(ctx, u, closeAfterPublished)
|
||||||
|
} else if u.Scheme == "srt" {
|
||||||
|
return startPublishSRT(ctx, u, closeAfterPublished)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("invalid schema %v of %v", u.Scheme, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startPublishSRT(ctx context.Context, u *url.URL, closeAfterPublished bool) (err error) {
|
||||||
|
// Parse host and port.
|
||||||
|
port := 1935
|
||||||
|
if u.Port() != "" {
|
||||||
|
if port, err = strconv.Atoi(u.Port()); err != nil {
|
||||||
|
return errors.Wrapf(err, "parse port %v", u.Port())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := net.LookupIP(u.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "lookup %v", u.Hostname())
|
||||||
|
}
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return errors.Errorf("no ips for %v", u.Hostname())
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "Parse url %v to host=%v, ip=%v, port=%v",
|
||||||
|
u.String(), u.Hostname(), ips[0], port)
|
||||||
|
|
||||||
|
// Setup libsrt.
|
||||||
|
client := srtgo.NewSrtSocket(ips[0].To4().String(), uint16(port),
|
||||||
|
map[string]string{
|
||||||
|
"transtype": "live",
|
||||||
|
"tsbpdmode": "false",
|
||||||
|
"tlpktdrop": "false",
|
||||||
|
"latency": "0",
|
||||||
|
"streamid": fmt.Sprintf("#%v", u.Fragment),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if err := client.Connect(); err != nil {
|
||||||
|
return errors.Wrapf(err, "SRT connect to %v:%v", u.Hostname(), port)
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "Connect to SRT server %v:%v success", u.Hostname(), port)
|
||||||
|
|
||||||
|
// We should wait for a while after connected to SRT server before quit. Because SRT server use timeout
|
||||||
|
// to detect UDP connection status, so we should never reconnect very fast.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
logger.Tf(ctx, "SRT publish stream success, stream=%v", u.Fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
if closeAfterPublished {
|
||||||
|
logger.Tf(ctx, "Close connection after published")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startPublishRTMP(ctx context.Context, u *url.URL, closeAfterPublished bool) (err error) {
|
||||||
|
parts := strings.Split(u.Path, "/")
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return errors.Errorf("invalid path %v", u.Path)
|
||||||
|
}
|
||||||
|
app, stream := strings.Join(parts[:len(parts)-1], "/"), parts[len(parts)-1]
|
||||||
|
|
||||||
|
// Parse host and port.
|
||||||
|
port := 1935
|
||||||
|
if u.Port() != "" {
|
||||||
|
if port, err = strconv.Atoi(u.Port()); err != nil {
|
||||||
|
return errors.Wrapf(err, "parse port %v", u.Port())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := net.LookupIP(u.Hostname())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "lookup %v", u.Hostname())
|
||||||
|
}
|
||||||
|
if len(ips) == 0 {
|
||||||
|
return errors.Errorf("no ips for %v", u.Hostname())
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "Parse url %v to host=%v, ip=%v, port=%v, app=%v, stream=%v",
|
||||||
|
u.String(), u.Hostname(), ips[0], port, app, stream)
|
||||||
|
|
||||||
|
// Connect via TCP client.
|
||||||
|
c, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ips[0], Port: port})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "dial %v %v", u.Hostname(), u.Port())
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
logger.Tf(ctx, "Connect to RTMP server %v:%v success", u.Hostname(), port)
|
||||||
|
|
||||||
|
// RTMP Handshake.
|
||||||
|
rd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
hs := rtmp.NewHandshake(rd)
|
||||||
|
|
||||||
|
if err := hs.WriteC0S0(c); err != nil {
|
||||||
|
return errors.Wrap(err, "write c0")
|
||||||
|
}
|
||||||
|
if err := hs.WriteC1S1(c); err != nil {
|
||||||
|
return errors.Wrap(err, "write c1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = hs.ReadC0S0(c); err != nil {
|
||||||
|
return errors.Wrap(err, "read s1")
|
||||||
|
}
|
||||||
|
s1, err := hs.ReadC1S1(c)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "read s1")
|
||||||
|
}
|
||||||
|
if _, err = hs.ReadC2S2(c); err != nil {
|
||||||
|
return errors.Wrap(err, "read s2")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := hs.WriteC2S2(c, s1); err != nil {
|
||||||
|
return errors.Wrap(err, "write c2")
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "RTMP handshake with %v:%v success", ips[0], port)
|
||||||
|
|
||||||
|
// Do connect and publish.
|
||||||
|
client := rtmp.NewProtocol(c)
|
||||||
|
|
||||||
|
connectApp := rtmp.NewConnectAppPacket()
|
||||||
|
tcURL := fmt.Sprintf("rtmp://%v%v", u.Hostname(), app)
|
||||||
|
connectApp.CommandObject.Set("tcUrl", amf0.NewString(tcURL))
|
||||||
|
if err = client.WritePacket(connectApp, 1); err != nil {
|
||||||
|
return errors.Wrap(err, "write connect app")
|
||||||
|
}
|
||||||
|
|
||||||
|
var connectAppRes *rtmp.ConnectAppResPacket
|
||||||
|
if _, err = client.ExpectPacket(&connectAppRes); err != nil {
|
||||||
|
return errors.Wrap(err, "expect connect app res")
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "RTMP connect app success, tcUrl=%v", tcURL)
|
||||||
|
|
||||||
|
createStream := rtmp.NewCreateStreamPacket()
|
||||||
|
if err = client.WritePacket(createStream, 1); err != nil {
|
||||||
|
return errors.Wrap(err, "write create stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
var createStreamRes *rtmp.CreateStreamResPacket
|
||||||
|
if _, err = client.ExpectPacket(&createStreamRes); err != nil {
|
||||||
|
return errors.Wrap(err, "expect create stream res")
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "RTMP create stream success")
|
||||||
|
|
||||||
|
publish := rtmp.NewPublishPacket()
|
||||||
|
publish.StreamName = *amf0.NewString(stream)
|
||||||
|
if err = client.WritePacket(publish, 1); err != nil {
|
||||||
|
return errors.Wrap(err, "write publish")
|
||||||
|
}
|
||||||
|
logger.Tf(ctx, "RTMP publish stream success, stream=%v", stream)
|
||||||
|
|
||||||
|
if closeAfterPublished {
|
||||||
|
logger.Tf(ctx, "Close connection after published")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// # Copyright (c) 2021 Winlin
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
package live
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ossrs/go-oryx-lib/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statLive struct {
|
||||||
|
Publishers struct {
|
||||||
|
Expect int `json:"expect"`
|
||||||
|
Alive int `json:"alive"`
|
||||||
|
} `json:"publishers"`
|
||||||
|
Subscribers struct {
|
||||||
|
Expect int `json:"expect"`
|
||||||
|
Alive int `json:"alive"`
|
||||||
|
} `json:"subscribers"`
|
||||||
|
PeerConnection interface{} `json:"random-pc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var gStatLive statLive
|
||||||
|
|
||||||
|
func handleStat(ctx context.Context, mux *http.ServeMux, l string) {
|
||||||
|
if strings.HasPrefix(l, ":") {
|
||||||
|
l = "127.0.0.1" + l
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Tf(ctx, "Handle http://%v/api/v1/sb/live", l)
|
||||||
|
mux.HandleFunc("/api/v1/sb/live", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := &struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}{
|
||||||
|
0, &gStatLive,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
logger.Wf(ctx, "marshal %v err %+v", res, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(b)
|
||||||
|
})
|
||||||
|
}
|
@ -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.
|
@ -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.
|
@ -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
|
||||||
|
}
|
@ -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);
|
@ -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"
|
@ -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",
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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"
|
@ -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")
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
29
trunk/3rdparty/srs-bench/vendor/github.com/haivision/srtgo/netutils_windows.go
generated
vendored
29
trunk/3rdparty/srs-bench/vendor/github.com/haivision/srtgo/netutils_windows.go
generated
vendored
@ -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))
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
191
trunk/3rdparty/srs-bench/vendor/github.com/haivision/srtgo/srtsocketoptions.go
generated
vendored
191
trunk/3rdparty/srs-bench/vendor/github.com/haivision/srtgo/srtsocketoptions.go
generated
vendored
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 Yasuhiro Matsumoto
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,29 @@
|
|||||||
|
# go-pointer
|
||||||
|
|
||||||
|
Utility for cgo
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md
|
||||||
|
|
||||||
|
In go 1.6, cgo argument can't be passed Go pointer.
|
||||||
|
|
||||||
|
```
|
||||||
|
var s string
|
||||||
|
C.pass_pointer(pointer.Save(&s))
|
||||||
|
v := *(pointer.Restore(C.get_from_pointer()).(*string))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/mattn/go-pointer
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Yasuhiro Matsumoto (a.k.a mattn)
|
@ -0,0 +1 @@
|
|||||||
|
package pointer
|
@ -0,0 +1,57 @@
|
|||||||
|
package pointer
|
||||||
|
|
||||||
|
// #include <stdlib.h>
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mutex sync.RWMutex
|
||||||
|
store = map[unsafe.Pointer]interface{}{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Save(v interface{}) unsafe.Pointer {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate real fake C pointer.
|
||||||
|
// This pointer will not store any data, but will bi used for indexing purposes.
|
||||||
|
// Since Go doest allow to cast dangling pointer to unsafe.Pointer, we do rally allocate one byte.
|
||||||
|
// Why we need indexing, because Go doest allow C code to store pointers to Go data.
|
||||||
|
var ptr unsafe.Pointer = C.malloc(C.size_t(1))
|
||||||
|
if ptr == nil {
|
||||||
|
panic("can't allocate 'cgo-pointer hack index pointer': ptr == nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
store[ptr] = v
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func Restore(ptr unsafe.Pointer) (v interface{}) {
|
||||||
|
if ptr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.RLock()
|
||||||
|
v = store[ptr]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unref(ptr unsafe.Pointer) {
|
||||||
|
if ptr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
delete(store, ptr)
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
C.free(ptr)
|
||||||
|
}
|
28
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
28
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
@ -1,16 +0,0 @@
|
|||||||
//go:build go1.17
|
|
||||||
// +build go1.17
|
|
||||||
|
|
||||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
|
||||||
// merged/removed with assertion_compare_go1.17_test.go and
|
|
||||||
// assertion_compare_legacy.go
|
|
||||||
|
|
||||||
package assert
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
// Wrapper around reflect.Value.CanConvert, for compatibility
|
|
||||||
// reasons.
|
|
||||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
|
||||||
return value.CanConvert(to)
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
//go:build !go1.17
|
|
||||||
// +build !go1.17
|
|
||||||
|
|
||||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
|
||||||
// merged/removed with assertion_compare_go1.17_test.go and
|
|
||||||
// assertion_compare_can_convert.go
|
|
||||||
|
|
||||||
package assert
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
// Older versions of Go does not have the reflect.Value.CanConvert
|
|
||||||
// method.
|
|
||||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
32
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
32
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
59
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
59
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
207
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
207
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
27
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/http_assertions.go
generated
vendored
27
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/assert/http_assertions.go
generated
vendored
65
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/require/require.go
generated
vendored
65
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/require/require.go
generated
vendored
59
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
59
trunk/3rdparty/srs-bench/vendor/github.com/stretchr/testify/require/require_forward.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/cmsghdr_bsd.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/cmsghdr_bsd.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/cmsghdr_stub.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/cmsghdr_stub.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/cmsghdr_unix.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/cmsghdr_unix.go
generated
vendored
2
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/iovec_32bit.go
generated
vendored
2
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/iovec_32bit.go
generated
vendored
2
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/iovec_64bit.go
generated
vendored
2
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/iovec_64bit.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/mmsghdr_stub.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/mmsghdr_stub.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/mmsghdr_unix.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/mmsghdr_unix.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/msghdr_bsdvar.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/msghdr_bsdvar.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/msghdr_stub.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/msghdr_stub.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/msghdr_zos_s390x.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/msghdr_zos_s390x.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_mmsg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_mmsg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_msg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_msg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_nommsg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_nommsg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_nomsg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/rawconn_nomsg.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/sys_const_unix.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/sys_const_unix.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/zsys_aix_ppc64.go
generated
vendored
1
trunk/3rdparty/srs-bench/vendor/golang.org/x/net/internal/socket/zsys_aix_ppc64.go
generated
vendored
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue