Skip to content

Instantly share code, notes, and snippets.

@vpalmisano
Last active February 17, 2024 19:36
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save vpalmisano/5716e8b52b258386fdd5c4fb93c97f11 to your computer and use it in GitHub Desktop.
Save vpalmisano/5716e8b52b258386fdd5c4fb93c97f11 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
function show_usage()
{
echo
echo "USAGE"
echo "-----"
echo
echo " SERVER_URL=https://my.mediasoup-demo.org:4443 ROOM_ID=test MEDIA_FILE=./test.mp4 ./gstreamer.sh"
echo
echo " where:"
echo " - SERVER_URL is the URL of the mediasoup-demo API server"
echo " - ROOM_ID is the id of the mediasoup-demo room (it must exist in advance)"
echo " - MEDIA_FILE is the path to a audio+video file (such as a .mp4 file)"
echo
echo "REQUIREMENTS"
echo "------------"
echo
echo " - gstreamer: stream audio and video (https://gstreamer.freedesktop.org)"
echo " - httpie: command line HTTP client (https://httpie.org)"
echo " - jq: command-line JSON processor (https://stedolan.github.io/jq)"
echo
}
echo
if [ -z "${SERVER_URL}" ] ; then
>&2 echo "ERROR: missing SERVER_URL environment variable"
show_usage
exit 1
fi
if [ -z "${ROOM_ID}" ] ; then
>&2 echo "ERROR: missing ROOM_ID environment variable"
show_usage
exit 1
fi
if [ -z "${MEDIA_FILE}" ] ; then
>&2 echo "ERROR: missing MEDIA_FILE environment variable"
show_usage
exit 1
fi
if [ "$(command -v gst-launch-1.0)" == "" ] ; then
>&2 echo "ERROR: gst-launch-1.0 command not found, must install GStreamer"
show_usage
exit 1
fi
if [ "$(command -v http)" == "" ] ; then
>&2 echo "ERROR: http command not found, must install httpie"
show_usage
exit 1
fi
if [ "$(command -v jq)" == "" ] ; then
>&2 echo "ERROR: jq command not found, must install jq"
show_usage
exit 1
fi
set -e
BROADCASTER_ID=$(LC_CTYPE=C tr -dc A-Za-z0-9 < /dev/urandom | fold -w ${1:-32} | head -n 1)
HTTPIE_COMMAND="http --check-status --verify=no"
AUDIO_SSRC=1111
AUDIO_PT=100
VIDEO_SSRC=2222
VIDEO_PT=101
# Simulcast settings
MAX_BITRATE=512
VIDEO_ENCODINGS="[\
{ \"ssrc\":$[VIDEO_SSRC], \"maxBitrate\":$[MAX_BITRATE]000, \"scaleResolutionDownBy\": 4, \"scalabilityMode\": \"S1T1\" },\
{ \"ssrc\":$[VIDEO_SSRC+1], \"maxBitrate\":$[MAX_BITRATE*2]000, \"scaleResolutionDownBy\": 2, \"scalabilityMode\": \"S1T1\" },\
{ \"ssrc\":$[VIDEO_SSRC+2], \"maxBitrate\":$[MAX_BITRATE*4]000, \"scaleResolutionDownBy\": 1, \"scalabilityMode\": \"S1T1\" }\
]"
WIDTH=320
HEIGHT=180
#
# Verify that a room with id ROOM_ID does exist by sending a simlpe HTTP GET. If
# not abort since we are not allowed to initiate a room..
#
echo ">>> verifying that room '${ROOM_ID}' exists..."
${HTTPIE_COMMAND} \
GET ${SERVER_URL}/rooms/${ROOM_ID} > /dev/null
#
# Create a Broadcaster entity in the server by sending a POST with our metadata.
# Note that this is not related to mediasoup at all, but will become just a JS
# object in the Node.js application to hold our metadata and mediasoup Transports
# and Producers.
#
echo ">>> creating Broadcaster..."
${HTTPIE_COMMAND} \
POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters \
id="${BROADCASTER_ID}" \
displayName="Broadcaster" \
device:='{"name": "GStreamer"}' \
> /dev/null
#
# Upon script termination delete the Broadcaster in the server by sending a
# HTTP DELETE.
#
trap 'echo ">>> script exited with status code $?"; ${HTTPIE_COMMAND} DELETE ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID} > /dev/null' EXIT
#
# Create a PlainTransport in the mediasoup to send our audio using plain RTP
# over UDP. Do it via HTTP post specifying type:"plain" and comedia:true and
# rtcpMux:false.
#
echo ">>> creating mediasoup PlainTransport for producing audio..."
res=$(${HTTPIE_COMMAND} \
POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports \
type="plain" \
comedia:=true \
rtcpMux:=false \
2> /dev/null)
#
# Parse JSON response into Shell variables and extract the PlainTransport id,
# IP, port and RTCP port.
#
eval "$(echo ${res} | jq -r '@sh "audioTransportId=\(.id) audioTransportIp=\(.ip) audioTransportPort=\(.port) audioTransportRtcpPort=\(.rtcpPort)"')"
#
# Create a PlainTransport in the mediasoup to send our video using plain RTP
# over UDP. Do it via HTTP post specifying type:"plain" and comedia:true and
# rtcpMux:false.
#
echo ">>> creating mediasoup PlainTransport for producing video..."
res=$(${HTTPIE_COMMAND} \
POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports \
type="plain" \
comedia:=true \
rtcpMux:=false \
2> /dev/null)
#
# Parse JSON response into Shell variables and extract the PlainTransport id,
# IP, port and RTCP port.
#
eval "$(echo ${res} | jq -r '@sh "videoTransportId=\(.id) videoTransportIp=\(.ip) videoTransportPort=\(.port) videoTransportRtcpPort=\(.rtcpPort)"')"
#
# Create a mediasoup Producer to send audio by sending our RTP parameters via a
# HTTP POST.
#
echo ">>> creating mediasoup audio Producer..."
${HTTPIE_COMMAND} -v \
POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports/${audioTransportId}/producers \
kind="audio" \
rtpParameters:="{ \"codecs\": [{ \"mimeType\":\"audio/opus\", \"payloadType\":${AUDIO_PT}, \"clockRate\":48000, \"channels\":2, \"parameters\":{ \"sprop-stereo\":1 } }], \"encodings\": [{ \"ssrc\":${AUDIO_SSRC} }] }" \
> /dev/null
#
# Create a mediasoup Producer to send video by sending our RTP parameters via a
# HTTP POST.
#
echo ">>> creating mediasoup video Producer..."
${HTTPIE_COMMAND} -v \
POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports/${videoTransportId}/producers \
kind="video" \
rtpParameters:="{ \"codecs\": [{ \"mimeType\":\"video/vp8\", \"payloadType\":${VIDEO_PT}, \"clockRate\":90000, \"rtcpFeedback\": [{ \"type\": \"ccm\", \"parameter\": \"fir\" }, { \"type\": \"nack\", \"parameter\": \"\" }, { \"type\": \"nack\", \"parameter\": \"pli\" }] }], \"encodings\": ${VIDEO_ENCODINGS} }" \
> /dev/null
#
# Run gstreamer command and make it send audio and video RTP with codec payload and
# SSRC values matching those that we have previously signaled in the Producers
# creation above. Also, tell gstreamer to send the RTP to the mediasoup
# PlainTransports' ip and port.
#
echo ">>> running gstreamer..."
GST_DEBUG=2 gst-launch-1.0 \
rtpbin name=rtpbin latency=200 rtp-profile=avpf \
filesrc location=${MEDIA_FILE} \
! decodebin name=decodebin \
! audioconvert \
! audioresample \
! opusenc \
! rtpopuspay pt=${AUDIO_PT} ssrc=${AUDIO_SSRC} \
! rtprtxqueue max-size-time=1000 max-size-packets=0 \
! rtpbin.send_rtp_sink_0 \
rtpbin.send_rtp_src_0 ! udpsink host=${audioTransportIp} port=${audioTransportPort} \
rtpbin.send_rtcp_src_0 ! udpsink host=${audioTransportIp} port=${audioTransportRtcpPort} sync=false async=false \
funnel name=rtp_funnell \
! udpsink host=${videoTransportIp} port=${videoTransportPort} \
funnel name=rtcp_funnell \
! udpsink host=${videoTransportIp} port=${videoTransportRtcpPort} sync=false async=false \
decodebin. \
! videoconvert \
! tee name=video_tee \
video_tee. \
! queue \
! videoconvert \
! videoscale \
! videorate \
! "video/x-raw,format=I420,width=$[WIDTH],height=$[HEIGHT],framerate=25/1" \
! textoverlay text="$[WIDTH]x$[HEIGHT]" \
! vp8enc target-bitrate=$[MAX_BITRATE] deadline=1 cpu-used=-4 keyframe-max-dist=25 threads=4 token-partitions=2 \
! rtpvp8pay pt=${VIDEO_PT} ssrc=$[VIDEO_SSRC] picture-id-mode=2 \
! rtprtxqueue max-size-time=1000 max-size-packets=0 \
! rtpbin.send_rtp_sink_1 \
rtpbin.send_rtp_src_1 ! rtp_funnell.sink_0 \
rtpbin.send_rtcp_src_1 ! rtcp_funnell.sink_0 \
video_tee. \
! queue \
! videoconvert \
! videoscale \
! videorate \
! "video/x-raw,format=I420,width=$[WIDTH*2],height=$[HEIGHT*2],framerate=25/1" \
! textoverlay text="$[WIDTH*2]x$[HEIGHT*2]" \
! vp8enc target-bitrate=$[MAX_BITRATE*2] deadline=1 cpu-used=-4 keyframe-max-dist=25 threads=4 token-partitions=2 \
! rtpvp8pay pt=${VIDEO_PT} ssrc=$[VIDEO_SSRC+1] picture-id-mode=2 \
! rtprtxqueue max-size-time=1000 max-size-packets=0 \
! rtpbin.send_rtp_sink_2 \
rtpbin.send_rtp_src_2 ! rtp_funnell.sink_1 \
rtpbin.send_rtcp_src_2 ! rtcp_funnell.sink_1 \
video_tee. \
! queue \
! videoconvert \
! videoscale \
! videorate \
! "video/x-raw,format=I420,width=$[WIDTH*4],height=$[HEIGHT*4],framerate=25/1" \
! textoverlay text="$[WIDTH*4]x$[HEIGHT*4]" \
! vp8enc target-bitrate=$[MAX_BITRATE*4] deadline=1 cpu-used=-4 keyframe-max-dist=25 threads=4 token-partitions=2 \
! rtpvp8pay pt=${VIDEO_PT} ssrc=$[VIDEO_SSRC+2] picture-id-mode=2 \
! rtprtxqueue max-size-time=1000 max-size-packets=0 \
! rtpbin.send_rtp_sink_3 \
rtpbin.send_rtp_src_3 ! rtp_funnell.sink_2 \
rtpbin.send_rtcp_src_3 ! rtcp_funnell.sink_2
@kmescharikov
Copy link

Hi! Can you help me, please? What is "encodings" you pass to your consumer with this setup?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment