OBS Studio SRT for low latency video streaming is insane

i just got OBS Studio SRT low latency video streaming working between 2 machines
and its insanely fast

much better than the previous method i was using to stream to obs using udp
which had a latency of just under 2 seconds

srt has a latency of about 1/4 of a second to 1/2 a second

and i can stream with hardware encoding using either nvenv ( on my dell with an nvidia gpu )
or from my Macbook Pro Retina running Ubuntu using vaapi

srt is the new kid on the block for streaming and replaces rtmp

i had to build ffmpeg with poudriere and enable SRT first
which took about 6 hours on my Dell XPS 15 2019 with 16 gig of ram

ill have to do a tutorial on using poudriere to build ffmpeg with the SRT option
and how to set up obs studio

you also need to open a udp port using pf on freebsd
in my case i opened port 6882


on the receiving machine you create a media source
with srt in listener mode

20241215_15h53m19s_grim.png



you may also need to bind obs to a specific ip in the settings

20241215_16h00m36s_grim.png



select the scene with the media source for srt in obs

and then run sockstat you should see the port listed

Code:
sockstat -l4

Code:
USER     COMMAND    PID   FD  PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
djwilcox obs        16186 39  udp4   192.168.1.131:6882    *:*

and on the streaming machine you put in the address of the receiving machine

obs1.png


obs studio on a Dell XPS 15 2019 running Freebsd 14.2
receiving a srt stream from my Macbook Pro Retina 2015 running Ubuntu 24.10 running obs

20241215_14h56m14s_grim.png
 

Attachments

  • 20241215_15h11m11s_grim.png
    20241215_15h11m11s_grim.png
    81.1 KB · Views: 375
thoughts on other uses for srt streaming

srt streaming could also be used to stream video files
or internet streams from one machine to another on your network using ffmpeg

for example streaming videos or youtube streams from your laptop
to another computer plugged into the tv

or streaming security cameras in real time

srt can be used with obs studio or ffmpeg on its own without obs

you can use ffmpeg with the following srt commands

Code:
srt-ffplay
srt-file-transmit
srt-live-transmit
srt-tunnel

which ill have to read up on

ffmpeg srt

vlc has support for playing srt streams
not sure about mpv
 
the srt package which is a dependency of obs studio installs the srt commands

which are the commands listed above


on ubuntu you have srt-tools package

Code:
sudo apt install srt-tools

going to have a little play around
 
streaming file with ffmpeg to obs using srt

Code:
ffmpeg -re -i input.mp4 -c copy -f mpegts "srt://192.168.1.131:6882?pkt_size"
 
obs listener using media source

Code:
srt://192.168.1.131:6882?mode=listener&latency=10000&timeout=5000000

obs caller stream settings

Code:
srt://192.168.1.131:6882?mode=caller&timeout=5000000&transtype=live
 
you can stream a file with ffmpeg and srt
and receive the stream with another computer using mpv

start ffmpeg streaming

Code:
ffmpeg -re -i input.mp4 -c copy -f mpegts "srt://192.168.1.131:6882?pkt_size"

then start mpv playing

Code:
mpv 'srt://192.168.1.131:6882?mode=listener&latency=10000&timeout=5000000'
 
Thank you for this nice tutorial.... I am a fan of OBS will give this a try... 🙏
hi mate

i have the latency less than 1/2 a second
really amazing

its as fast as NDI if you ever used that

you need to build ffmpeg with the SRT option
which isnt enabled in the freebsd pkg

here how to build ffmpeg with the SRT option using poudriere


Code:
DISABLE_LICENSES=yes
multimedia_ffmpeg_SET= SRT

 
you can stream from obs to mpv

mpv listener

Code:
mpv 'srt://192.168.1.131:6882?mode=listener&latency=10000&timeout=5000000'

obs caller in settings / stream

Code:
srt://192.168.1.131:6882?mode=caller&timeout=5000000&transtype=live

mpv playing the stream from obs studio

mpv-obs.jpg



obs-mpv2.jpg
 
streaming with ffmpeg

Code:
ffmpeg -re -i input.mp4 -c copy -f mpegts "srt://192.168.1.131:6882?pkt_size"

to mpv

Code:
mpv 'srt://192.168.1.131:6882?mode=listener&latency=10000&timeout=5000000'

you have to start mpv as the listener first
then start ffmpeg

you have to be quick

and remember to open a udp port with pf

vlc also supports srt streams as well

ffmpeg-mpv.jpg
 
srt-live-transmit and mpv

the issue with using mpv to receive the srt streams is it can quit before it opens the stream

using this command

Code:
mpv 'srt://192.168.1.131:6882?mode=listener&latency=10000&timeout=5000000'

mpv has an idle option you can set to yes but that still doesnt solve the problem

so a better way is to use srt-live-transmit as the listener and pipe the stream into mpv

Code:
srt-live-transmit 'srt://192.168.1.131:6882?mode=listener&latency=10000&timeout=5000000' file://con | mpv --fs -
 
i created a couple of scripts to stream any video supported by yt-dlp with ffmpeg and srt
to another machine which receives the stream with srt-live-transmit which is then piped into mpv

the scripts are called srt-mpv and srt-yt

Code:
srt-mpv


which uses srt-live-transmit and mpv to play a srt stream

Code:
srt-yt


which streams urls supported by yt-dlp with ffmpeg
to the the machine running srt-mpv

heres how they work

srt-mpv is the listener

srt-mpv has default settings for the ip, port and latency

you can edit the script and change the defaults

Code:
#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default latency
latency_default='10000'

# default port
port_default='6882'

but you can also over ride the defaults on the command line
with the

-i = ip address to listen on, which is your lan ip
-p = port to listen on
-l = latency

Code:
srt-mpv -h

Code:
srt-mpv -i ip -p port -l latency

so if you want to use the defaults from the script you just run

Code:
srt-mpv

if you want to over ride the defaults on the command line you would do the following
to change the ip, port and latency

Code:
srt-mpv -i 192.168.1.3 -p 6881 -l 2000000

srt use udp so you need to open a udp port with pf

srt-yt is the caller

srt-yt has a default ip and port set in the script
which you can edit and change

Code:
#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default port
port_default='6882'

you can also over ride the default ip and port on the command line

Code:
srt-yt -h

Code:
srt-yt -i ip/dns -p port -u url

to use the default ip and port from the script you would run

Code:
srt-yt -u 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'

to over ride the ip and port

Code:
srt-yt -i 192.168.1.3 -p 6881 -u 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'

srt-mpv receiving a srt stream send by the srt-yt script
of a youtube video of beyonce

beyonce.png


srt-yt script streaming the youtube video with yt-dlp
to the receiving machine running the srt-mpv script

ffmpeg.png



srt-mpv script
link at top of the page

Code:
#!/bin/sh

#===============================================================================
# srt-mpv - receive a srt stream with mpv
#===============================================================================


#===============================================================================
# script usage
#===============================================================================

usage () {
# if argument passed to function echo it
[ -z "${1}" ] || echo "! ${1}"
# display help
echo "\
$(basename "$0") -i ip -p port -l latency"
exit 2
}

#===============================================================================
# error messages
#===============================================================================

INVALID_OPT_ERR='Invalid option:'
REQ_ARG_ERR='requires an argument'
WRONG_ARGS_ERR='wrong number of arguments passed to script'


#===============================================================================
# getopts check options passed to script
#===============================================================================

while getopts 'i:p:l:h' opt
do
  case ${opt} in
     i) ip="${OPTARG}";;
     p) port="${OPTARG}";;
     l) latency="${OPTARG}";;
     h) usage;;
     \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;;
     :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;;
  esac
done
shift $((OPTIND-1))

#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default latency
latency_default='10000'

# default port
port_default='6882'


#===============================================================================
# srt_recv function
#===============================================================================

srt_recv () {
srt-live-transmit "srt://${ip:=${ip_default}}:${port:=${port_default}}?mode=listener&latency=${latency:=${latency_default}}&timeout=5000000" file://con | mpv --fs -
}

#===============================================================================
# run srt_recv function
#===============================================================================

srt_recv

srt-yt script
link at top of the page

Code:
#!/bin/sh


#===============================================================================
# srt-yt
#===============================================================================


#===============================================================================
# script usage
#===============================================================================

usage () {
# if argument passed to function echo it
[ -z "${1}" ] || echo "! ${1}"
# display help
echo "\
$(basename "$0") -i ip/dns -p port -u url"
exit 2
}


#===============================================================================
# error messages
#===============================================================================

INVALID_OPT_ERR='Invalid option:'
REQ_ARG_ERR='requires an argument'
WRONG_ARGS_ERR='wrong number of arguments passed to script'


#===============================================================================
# check number of aruments passed to script
#===============================================================================

[ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}"


#===============================================================================
# getopts check options passed to script
#===============================================================================

while getopts ':u:i:p:h' opt
do
  case ${opt} in
     u) infile="${OPTARG}";;
     i) ip="${OPTARG}";;
     p) port="${OPTARG}";;
     h) usage;;
     \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;;
     :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;;
  esac
done
shift $((OPTIND-1))


#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default port
port_default='6882'


#===============================================================================
# url
#===============================================================================

# yt-dlp url
url=$(yt-dlp -f b -g --no-playlist "${infile}")

# check number of stream links
streamcount=$(echo "${url}" | awk 'END{print NR}')


#===============================================================================
# one stream containing audio and video
#===============================================================================

one_stream () {
ffmpeg \
-hide_banner \
-re \
-i "${url}" \
-c:a copy -c:v copy \
-tune zerolatency \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


#===============================================================================
# two streams containing audio and video
#===============================================================================

two_streams () {
video_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $1}')
audio_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $2}')

# ffmpeg join audio and video and stream
ffmpeg \
-hide_banner \
-re \
-i "${video_url}" \
-i "${audio_url}" \
-c:a copy -c:v copy \
-tune zerolatency \
-map 0:0 -map 1:0 \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


#===============================================================================
# case statement check for number of streams
#===============================================================================

case "${streamcount}" in
    1) one_stream;; # single stream
    2) two_streams;; # audio and video stream
    *) usage;;
esac
 
latency 1000
thats the secret sauce

changed srt-mpv to use latency 1000 as the default
reduces the start up to less than a second

heres how it works

you start srt-mpv as the listener

Code:
srt-mpv

on the second machine
you run srt-yt with -u option and the url

Code:
srt-yt -u 'https://www.youtube.com/watch?v=o-YBDTqX_ZU'

the srt-yt script uses yt-dlp and checks if there are one or two stream
if the video is from youtube its uses dash which have separate audio and video streams

as opposed to a video stream that contains both the audio and video stream

if there are two streams its maps them together into one new stream

then srt-yt sends the stream urls to the machine running srt-mpv
srt-yt just copies the streams it doesnt re encode them

it just relays the internet stream urls to another machine

when you start srt-yt after it has grabbed the stream urls
it uses ffmpeg to stream to the machine running srt-mpv

as soon as ffmpeg starts to stream srt-mpv is waiting using
srt-live-transmit to receive the stream and pipe it into mpv

ffmpeg.png



then the video is played on the machine running srt-mpv

rick.png


srt-mpv


Code:
#!/bin/sh

#===============================================================================
# srt-mpv - receive a srt stream with mpv
#===============================================================================


#===============================================================================
# script usage
#===============================================================================

usage () {
# if argument passed to function echo it
[ -z "${1}" ] || echo "! ${1}"
# display help
echo "\
$(basename "$0") -i ip -p port -l latency"
exit 2
}

#===============================================================================
# error messages
#===============================================================================

INVALID_OPT_ERR='Invalid option:'
REQ_ARG_ERR='requires an argument'
WRONG_ARGS_ERR='wrong number of arguments passed to script'


#===============================================================================
# getopts check options passed to script
#===============================================================================

while getopts 'i:p:l:h' opt
do
  case ${opt} in
     i) ip="${OPTARG}";;
     p) port="${OPTARG}";;
     l) latency="${OPTARG}";;
     h) usage;;
     \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;;
     :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;;
  esac
done
shift $((OPTIND-1))

#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default latency
latency_default='1000'

# default port
port_default='6882'


#===============================================================================
# srt_recv function
#===============================================================================

srt_recv () {
srt-live-transmit "srt://${ip:=${ip_default}}:${port:=${port_default}}?mode=listener&latency=${latency:=${latency_default}}&timeout=5000000" file://con | mpv --fs -
}

#===============================================================================
# run srt_recv function
#===============================================================================

srt_recv

srt-yt


Code:
#!/bin/sh


#===============================================================================
# srt-yt
#===============================================================================


#===============================================================================
# script usage
#===============================================================================

usage () {
# if argument passed to function echo it
[ -z "${1}" ] || echo "! ${1}"
# display help
echo "\
$(basename "$0") -i ip/dns -p port -u url"
exit 2
}


#===============================================================================
# error messages
#===============================================================================

INVALID_OPT_ERR='Invalid option:'
REQ_ARG_ERR='requires an argument'
WRONG_ARGS_ERR='wrong number of arguments passed to script'


#===============================================================================
# check number of aruments passed to script
#===============================================================================

[ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}"


#===============================================================================
# getopts check options passed to script
#===============================================================================

while getopts ':u:i:p:h' opt
do
  case ${opt} in
     u) infile="${OPTARG}";;
     i) ip="${OPTARG}";;
     p) port="${OPTARG}";;
     h) usage;;
     \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;;
     :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;;
  esac
done
shift $((OPTIND-1))


#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default port
port_default='6882'


#===============================================================================
# url
#===============================================================================

# yt-dlp url
url=$(yt-dlp -f b -g --no-playlist "${infile}")

# check number of stream links
streamcount=$(echo "${url}" | awk 'END{print NR}')


#===============================================================================
# one stream containing audio and video
#===============================================================================

one_stream () {
ffmpeg \
-hide_banner \
-re \
-i "${url}" \
-c:a copy -c:v copy \
-tune zerolatency \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


#===============================================================================
# two streams containing audio and video
#===============================================================================

two_streams () {
video_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $1}')
audio_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $2}')

# ffmpeg join audio and video and stream
ffmpeg \
-hide_banner \
-re \
-i "${video_url}" \
-i "${audio_url}" \
-c:a copy -c:v copy \
-tune zerolatency \
-map 0:0 -map 1:0 \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


#===============================================================================
# case statement check for number of streams
#===============================================================================

case "${streamcount}" in
    1) one_stream;; # single stream
    2) two_streams;; # audio and video stream
    *) usage;;
esac
 
new script

srt-trim


srt-yt can stream a whole video supported by yt-dlp to a srt receiver
ether obs studio or using srt-live-transmit and mpv with the srt-mpv script

srt-trim lets you stream a clip with a start and end point from any site supported by yt-dlp
to a srt receiver which could be obs studio or using srt-mpv

srt-trim uses ffmpeg to create a start and a to point
with the -s and -t options

you define a start point and then the amount of time after the start you want to stream for

to stream a video from 1 minute for 1 minute the command would look like this
with the default ip and port define in the script which you can change

Code:
srt-trim -u 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' -s 00:01:00 -t 00:01:00

or over ride the default ip and port with the -i and -p options

Code:
srt-trim -i 192.168.1.131 -p 6882 -u 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' -s 00:01:00 -t 00:01:00

so you dont set the -t option to 00:02:00
that would record from 1 minute for 2 mintues

thats just the way ffmpeg works
dont blame me

to make things easier i created a script that allows you to put in the start and end times
and then it will give you the duration for the -t option

Code:
sexagesimal-time -h

Code:
calculate sexagesimal duration by subtacting end time from start time
sexagesimal-time -s 00:00:00 -e 00:00:00


the srt-trim script has a default ip and port set
so you can just edit the script and change those

you can also over ride the defaults on the command line

Code:
#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default port
port_default='6882'

Code:
srt-trim -h

Code:
srt-trim -i ip/dns -p port -u input -s 00:00:00 -t 00:00:00

you can create a media source in obs studio that acts as a receiver for the srt stream

Code:
srt://192.168.1.131:6882?mode=listener&latency=1000&timeout=500000

remember to open a udp port with pf
in the above example im using port 6882

20241216_18h13m32s_grim.png



and then set obs to bind to a specific ip

20241216_18h13m46s_grim.png


srt-trim with obs studio as the receiver
using the default ip and port in the script

Code:
srt-trim -u 'https://www.youtube.com/watch?v=Wmc8bQoL-J0' -s 00:01:00 -t 00:01:00

20241216_18h17m58s_grim.png


srt-mpv as the receiver

Code:
srt-mpv

20241216_19h01m15s_grim.png



srt-trim script code

Code:
#!/bin/sh


#===============================================================================
# srt-trim - trim an online clip and stream it
#===============================================================================


#===============================================================================
# script usage
#===============================================================================

usage () {
# if argument passed to function echo it
[ -z "${1}" ] || echo "! ${1}"
# display help
echo "\
$(basename "$0") -i ip/dns -p port -u input -s 00:00:00 -t 00:00:00"
exit 2
}


#===============================================================================
# error messages
#===============================================================================

INVALID_OPT_ERR='Invalid option:'
REQ_ARG_ERR='requires an argument'
WRONG_ARGS_ERR='wrong number of arguments passed to script'


#===============================================================================
# check number of aruments passed to script
#===============================================================================

[ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}"


#===============================================================================
# getopts check options passed to script
#===============================================================================

while getopts 'i:p:u:s:t:h' opt
do
  case ${opt} in
     u) input="${OPTARG}";;
     i) ip="${OPTARG}";;
     p) port="${OPTARG}";;
     s) start="${OPTARG}";;
     t) end="${OPTARG}";;
     h) usage;;
     \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;;
     :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;;
  esac
done
shift $((OPTIND-1))


#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default port
port_default='6882'


#===============================================================================
# url
#===============================================================================

# yt-dlp url
url=$(yt-dlp -f b -g --no-playlist "${input}")

# check number of stream links
streamcount=$(echo "${url}" | awk 'END{print NR}')


#===============================================================================
# one stream containing audio and video
#===============================================================================

# copy audio and video streams
one_stream () {
ffmpeg \
-hide_banner \
-y \
-re \
-i "${url}" \
-c:a copy -c:v copy \
-tune zerolatency \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


# trim video and re-encode
one_stream_trim () {
ffmpeg \
-hide_banner \
-y \
-re \
-ss "${start}" \
-i "${url}" \
-t "${end}" \
-c:a aac \
-c:v libx264 -profile:v high \
-tune zerolatency \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


#===============================================================================
# two streams containing audio and video
#===============================================================================


# copy audio and video streams
two_streams () {
video_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $1}')
audio_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $2}')

# ffmpeg join audio and video and stream
ffmpeg \
-hide_banner \
-y \
-re \
-i "${video_url}" \
-i "${audio_url}" \
-c:a copy -c:v copy \
-tune zerolatency \
-map 0:0 -map 1:0 \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


# trim video and re-encode
two_streams_trim () {
video_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $1}')
audio_url=$(echo "${url}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $2}')

# ffmpeg join audio and video and stream
ffmpeg \
-hide_banner \
-y \
-re \
-ss "${start}" \
-i "${video_url}" \
-i "${audio_url}" \
-t "${end}" \
-c:a aac \
-c:v libx264 -profile:v high \
-tune zerolatency \
-map 0:0 -map 1:0 \
-f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}?pkt_size=1316"
}


#===============================================================================
# case statement check for number of streams
#===============================================================================

if [ -z "${start}" ]; then
   case "${streamcount}" in
       1) one_stream;; # single stream
       2) two_streams;; # audio and video stream
       *) usage;;
   esac
else
   case "${streamcount}" in
       1) one_stream_trim;; # single stream
       2) two_streams_trim;; # audio and video stream
       *) usage;;
   esac
fi
 
Demo of SRT streaming from OBS Studio
running on Ubuntu 24.10 on a Macbook Pro Retina

to Freebsd 14.2 running OBS Studio recording with Nvenc encoding
with the mic recorded with Ardour and piped into OBS with Jack

im just posting the video so you can see the quality
and the latency between the two machines when im talking

if you notice there is no delay when im focused on something on Ubuntu like text
and what im talking about

Its a video about building Emacs 30 on Ubuntu 24.10

 
Great post! I've been really interested in SRT streaming lately, but I’m struggling to get sub-1-second latency with OBS. I’ve tried a few different approaches, but the latency always stays around 1 second. Have you run into this issue before?
 
hi mate

i found that setting the latency to about 1000 on the listener
gives a latency of less than a second

Code:
srt://192.168.1.131:6882?mode=listener&latency=1000&timeout=5000000

you can also use mpv as a srt receiver


and send a stream supported by yt-dlp to a srt receiver


or trim a clip and send it to a srt receiver

 
Back
Top