Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save fingul/ac2288dd9b1698c61cfaae826501f433 to your computer and use it in GitHub Desktop.
Save fingul/ac2288dd9b1698c61cfaae826501f433 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment