Implementation of the SRT protocol in pure Go with minimal dependencies.
This implementation of the SRT protocol has live streaming of video/audio in mind. Because of this, the buffer mode and File Transfer Congestion Control (FileCC) are not implemented.
âś… | Handshake v4 and v5 |
âś… | Message mode |
âś… | Caller-Listener Handshake |
âś… | Timestamp-Based Packet Delivery (TSBPD) |
âś… | Too-Late Packet Drop (TLPKTDROP) |
âś… | Live Congestion Control (LiveCC) |
âś… | NAK and Peridoc NAK |
âś… | Encryption |
❌ | Buffer mode |
❌ | Rendezvous Handshake |
❌ | File Transfer Congestion Control (FileCC) |
❌ | Connection Bonding |
The parts that are implemented are based on what has been published in the SRT RFC.
A Go version of 1.18+ is required.
go get github.com/datarhei/gosrt
import "github.com/datarhei/gosrt"
conn, err := srt.Dial("srt", "golang.org:6000", srt.Config{
StreamId: "...",
})
if err != nil {
// handle error
}
buffer := make([]byte, 2048)
for {
n, err := conn.Read(buffer)
if err != nil {
// handle error
}
// handle received data
}
conn.Close()
In the contrib/client
directory you'll find a complete example of a SRT client.
import "github.com/datarhei/gosrt"
ln, err := srt.Listen("srt", ":6000", srt.Config{...})
if err != nil {
// handle error
}
for {
conn, mode, err := ln.Accept(func(req ConnRequest) ConnType {
// check connection request
return srt.REJECT
})
if err != nil {
// handle error
}
if mode == srt.REJECT {
// rejected connection, ignore
continue
}
if mode == srt.PUBLISH {
go handlePublish(conn)
} else { // srt.SUBSCRIBE
go handleSubscribe(conn)
}
}
In the contrib/server
directory you'll find a complete example of a SRT server. For your convenience
this modules provides the Server
type which is a light framework for creating your own SRT server. The
example server is based on this type.
The Accept
function from the Listener
expects a function that handles the connection requests. It can
return 3 different values: srt.PUBLISH
, srt.SUBSCRIBE
, and srt.REJECT
. srt.PUBLISH
means that the
server expects the caller to send data, whereas srt.SUBSCRIBE
means that the server will send data to
the caller. This is opiniated towards a streaming server, however in your implementation of a listener
you are free to handle connections requests to your liking.
In the contrib/client
directory you'll find an example implementation of a SRT client.
Build the client application with
cd contrib/client && go build
The application requires only two options:
Option | Description |
---|---|
-from |
Address to read from |
-to |
Address to write to |
Both options accept an address. Valid addresses are: -
for stdin
, resp. stdout
, a srt://
address, or an udp://
address.
A SRT URL is of the form srt://[host]:[port]/?[options]
where options are in the form of a HTTP
query string. These are the
known options (similar to srt-live-transmit):
Option | Values | Description |
---|---|---|
mode |
listener or caller |
Enforce listener or caller mode. |
congestion |
live |
Congestion control. Currently only live is supported. |
conntimeo |
ms |
Connection timeout. |
drifttracer |
bool |
Enable drift tracer. Not implemented. |
enforcedencryption |
bool |
Accept connection only if both parties have encryption enabled. |
fc |
bytes |
Flow control window size. |
inputbw |
bytes |
Input bandwidth. Ignored. |
iptos |
0...255 | IP socket type of service. Broken. |
ipttl |
1...255 | Defines IP socket "time to live" option. Broken. |
ipv6only |
bool |
Use IPv6 only. Not implemented. |
kmpreannounce |
packets |
Duration of Stream Encryption key switchover. |
kmrefreshrate |
packets |
Stream encryption key refresh rate. |
latency |
ms |
Maximum accepted transmission latency. |
lossmaxttl |
ms |
Packet reorder tolerance. Not implemented. |
maxbw |
bytes |
Bandwidth limit. Ignored. |
mininputbw |
bytes |
Minimum allowed estimate of inputbw . |
messageapi |
bool |
Enable SRT message mode. Must be false . |
mss |
76... | MTU size. |
nakreport |
bool |
Enable periodic NAK reports. |
oheadbw |
10...100 | Limits bandwidth overhead. Percents. Ignored. |
packetfilter |
string |
Set up the packet filter. Not implemented. |
passphrase |
string |
Password for the encrypted transmission. |
payloadsize |
bytes |
Maximum payload size. |
pbkeylen |
16 , 24 , or 32 |
Crypto key length in bytes. |
peeridletimeo |
ms |
Peer idle timeout. |
peerlatency |
ms |
Minimum receiver latency to be requested by sender. |
rcvbuf |
bytes |
Receiver buffer size. |
rcvlatency |
ms |
Receiver-side latency. |
sndbuf |
bytes |
Sender buffer size. |
snddropdelay |
ms |
Sender's delay before dropping packets. |
streamid |
string |
Stream ID (settable in caller mode only, visible on the listener peer). |
tlpktdrop |
bool |
Drop too late packets. |
transtype |
live |
Transmission type. Must be live . |
tsbpdmode |
bool |
Enable timestamp-based packet delivery mode. |
Reading from a SRT sender and play with ffplay
:
./client -from "srt://127.0.0.1:6001/?mode=listener&streamid=..." -to - | ffplay -f mpegts -i -
Reading from UDP and sending to a SRT server:
./client -from udp://:6000 -to "srt://127.0.0.1:6001/?mode=caller&streamid=..."
Simulate point-to-point transfer on localhost. Open one console and start ffmpeg
(you need at least version 4.3.2, built with SRT enabled) to send to an UDP address:
ffmpeg \
-f lavfi \
-re \
-i testsrc2=rate=25:size=640x360 \
-codec:v libx264 \
-b:v 1024k \
-maxrate:v 1024k \
-bufsize:v 1024k \
-preset ultrafast \
-r 25 \
-g 50 \
-pix_fmt yuv420p \
-flags2 local_header \
-f mpegts \
"udp://127.0.0.1:6000?pkt_size=1316"
In another console read from the UDP and start a SRT listenr:
./client -from udp://:6000 -to "srt://127.0.0.1:6001/?mode=listener&streamid=foobar"
In the third console connect to that stream and play the video with ffplay
:
./client -from "srt://127.0.0.1:6001/?mode=caller&streamid=foobar" -to - | ffplay -f mpegts -i -
In the contrib/server
directory you'll find an example implementation of a SRT server. This server allows you to publish
a stream that can be read by many clients.
Build the client application with
cd contrib/server && go build
The application has these options:
Option | Default | Description |
---|---|---|
-addr |
required | Address to listen on |
-app |
/ |
Path prefix for streamid |
-token |
(not set) | Token query param for streamid |
-passphrase |
(not set) | Passphrase for de- and enrcypting the data |
-logtopics |
(not set) | Topics for the log output |
-profile |
false |
Enable profiling |
This example server expects the streamID (without any prefix) to be an URL path with optional query parameter, e.g. /live/stream
. If the -app
option is used, then the path must start with that path, e.g. the value is /live
then the streamID must start with that value. The -token
option can be used to define a token for that stream as some kind of access control, e.g. with -token foobar
the streamID might look like
/live/stream?token=foobar
.
Use -passphrase
in order to enable and enforce encryption.
Use -logtopics
in order to write debug output. The value are a comma separated list of topics you want to be written to stderr
, e.g. connection,listen
. Check the Logging section in order to find out more about the different topics.
Use -profile
in order to write a CPU profile.
In SRT the StreamID is used to transport somewhat arbitrary information from the caller to the listener. The provided example server uses this machanism to decide who is the sender and who is the receiver. The server must know if the connecting client wants to publish a stream or if it wants to subscribe to a stream.
The example server looks for the publish:
prefix in the StreamID. If this prefix is present, the server assumes that it is the receiver
and the client will send the data. The subcribing clients must use the same StreamID (withouth the publish:
prefix) in order to be able to
receive data.
If you implement your own server you are free to interpret the streamID as you wish.
Running a server listening on port 6001 with defaults:
./server -addr ":6001"
Now you can use the contributed client to publish a stream:
./client -from ... -to "srt://127.0.0.1:6001/?mode=caller&streamid=publish:/live/stream"
or directly from ffmpeg
:
ffmpeg \
-f lavfi \
-re \
-i testsrc2=rate=25:size=640x360 \
-codec:v libx264 \
-b:v 1024k \
-maxrate:v 1024k \
-bufsize:v 1024k \
-preset ultrafast \
-r 25 \
-g 50 \
-pix_fmt yuv420p \
-flags2 local_header \
-f mpegts \
-transtype live \
"srt://127.0.0.1:6001?streamid=publish:/live/stream"
If the server is not on localhost, you might adjust the peerlatency
in order to avoid packet loss: -peerlatency 1000000
.
Now you can play the stream:
ffplay -f mpegts -transtype live -i "srt://127.0.0.1:6001?streamid=/live/stream"
You will most likely first see some error messages from ffplay
because it tries to make sense of the received data until a keyframe arrives. If you
get more errors during playback, you might increase the receive buffer by adding e.g. -rcvlatency 1000000
to the command line.
The stream can be encrypted with a passphrase. First start the server with a passphrase. If you are using srt-live-transmit
, the passphrase has to be at least 10 characters long otherwise it will not be accepted.
./server -addr :6001 -passphrase foobarfoobar
Send an encrpyted stream to the server:
ffmpeg \
-f lavfi \
-re \
-i testsrc2=rate=25:size=640x360 \
-codec:v libx264 \
-b:v 1024k \
-maxrate:v 1024k \
-bufsize:v 1024k \
-preset ultrafast \
-r 25 \
-g 50 \
-pix_fmt yuv420p \
-flags2 local_header \
-f mpegts \
-transtype live \
"srt://127.0.0.1:6001?streamid=publish:/live/stream&passphrase=foobarfoobar"
Receive an encrypted stream from the server:
ffplay -f mpegts -transtype live -i "srt://127.0.0.1:6001?streamid=/live/stream&passphrase=foobarfoobar"
You will most likely first see some error messages from ffplay
because it tries to make sense of the received data until a keyframe arrives. If you
get more errors during playback, you might increase the receive buffer by adding e.g. -rcvlatency 1000000
to the command line.
This SRT module has a built-in logging facility for debugging purposes. Check the Logger
interface and the NewLogger(topics []string)
function. Because logging everything would be too much output if you wonly want to debug something specific, you have the possibility to limit the logging to specific areas like everything regarding a connection or only the handshake. That's why there are various topics.
In the contributed server you see an example of how logging is used. Here's the essence:
logger := srt.NewLogger([]string{"connection", "handshake"})
config := srt.DefaultConfig
config.Logger = logger
ln, err := srt.Listen("udp", ":6000", config)
if err != nil {
// handle error
}
go func() {
for m := range logger.Listen() {
fmt.Fprintf(os.Stderr, "%#08x %s (in %s:%d)\n%s \n", m.SocketId, m.Topic, m.File, m.Line, m.Message)
}
}()
for {
conn, mode, err := ln.Accept(acceptFn)
...
}
Currently known topics are:
connection:close
connection:error
connection:filter
connection:new
connection:rtt
connection:tsbpd
control:recv:ACK:cif
control:recv:ACK:dump
control:recv:ACK:error
control:recv:ACKACK:dump
control:recv:ACKACK:error
control:recv:KM:cif
control:recv:KM:dump
control:recv:KM:error
control:recv:NAK:cif
control:recv:NAK:dump
control:recv:NAK:error
control:recv:keepalive:dump
control:recv:shutdown:dump
control:send:ACK:cif
control:send:ACK:dump
control:send:ACKACK:dump
control:send:KM:cif
control:send:KM:dump
control:send:KM:error
control:send:NAK:cif
control:send:NAK:dump
control:send:keepalive:dump
control:send:shutdown:cif
control:send:shutdown:dump
data:recv:dump
data:send:dump
dial
handshake:recv:cif
handshake:recv:dump
handshake:recv:error
handshake:send:cif
handshake:send:dump
listen
packet:recv:dump
packet:send:dump
You can run make logtopics
in order to extract the list of topics.
The docker image you can build with docker build -t srt .
provides the example SRT client and server as mentioned in the paragraph above.
E.g. run the server with docker run -it --rm -p 6001:6001/udp srt srt-server -addr :6001
.