Liquidsoap – Multi-Bitrate DASH streaming configuration

This is an example of multi-bitrate DASH streaming configuration. In this example Liquidsoap will be configured to output three different MPEGTS video streams with various frame size, bitrates and one stereo audio MPEGTS stream all via  UDP.  This configuration can be used with the same setup as my previous blog post about setting up Liquidsoap MPEGTS DASH streaming. This example is using Liquidsoap v1.3.3.

Please note that this is more of a proof of concept than practical use. A hardware encoder plugin for Gstreamer would help.

Liquidsoap Configuration: MPEGTS via UDP for Multi-Bitrate DASH Streaming
set("log.stdout",true)
set("log.level",5)
set("gstreamer.debug_level",2)

set("frame.video.width",1280)
set("frame.video.height",720)
set("frame.video.samplerate",25)
set("gstreamer.add_borders", true)

input = single("/home/user/Videos/Test/test-1280x720-25fps.ogv")

#MPEGTS Output For DASH Streaming.
#You must start Shaka Packager first before starting Liquidsoap.

#Audio Channel. Port 5000
output.gstreamer.audio(
pipeline=
"audioconvert ! fdkaacenc bitrate=128000 ! queue ! mpegtsmux alignment=7 name=muxer ! queue ! udpsink host=127.0.0.1 port=5000
sync=true",
mksafe(input))

#Video Channels.
#360p Video at 600kbps. Port 5001
output.gstreamer.video(
pipeline=
"videoscale ! video/x-raw,width=480,height=360 ! videoconvert ! x264enc bitrate=600 key-int-max=72 pass=qual quantizer=20 tune
=zerolatency ! video/x-h264,profile=baseline ! queue ! mpegtsmux alignment=7 name=muxer ! queue ! udpsink host=127.0.0.1 port=5001 s
ync=true",
mksafe(input))

#480p Video at 1000kbps. Port 5002
output.gstreamer.video(
pipeline=
"videoscale ! video/x-raw,width=640,height=480 ! videoconvert ! x264enc bitrate=1000 key-int-max=72 pass=qual quantizer=20 tun
e=zerolatency ! video/x-h264,profile=main ! queue ! mpegtsmux alignment=7 name=muxer ! queue ! udpsink host=127.0.0.1 port=5002 sync
=true",
mksafe(input))

#720p Video at 3000kbps. Port 5003
output.gstreamer.video(
pipeline=
"videoscale ! video/x-raw,width=1280,height=720 ! videoconvert ! x264enc bitrate=3000 key-int-max=72 pass=qual quantizer=20 tu
ne=zerolatency ! video/x-h264,profile=main ! queue ! mpegtsmux alignment=7 name=muxer ! queue ! udpsink host=127.0.0.1 port=5003 syn
c=true",
mksafe(input))
Shaka Packager Command

The packager will take the UDP output from Liquidsoap and encapsulate it into the DASH format for playback via the Shaka Player or any other DASH player.

$packager \
'in=udp://127.0.0.1:5000?interface=127.0.0.1,stream=audio,init_segment=audio_init.mp4,segment_template=audio_$Number$.m4s' \
'in=udp://127.0.0.1:5001?interface=127.0.0.1,stream=video,init_segment=h264_360p_init.mp4,segment_template=h264_360p_$Number$.m4s' \
'in=udp://127.0.0.1:5002?interface=127.0.0.1,stream=video,init_segment=h264_480p_init.mp4,segment_template=h264_480p_$Number$.m4s' \
'in=udp://127.0.0.1:5003?interface=127.0.0.1,stream=video,init_segment=h264_720p_init.mp4,segment_template=h264_720p_$Number$.m4s' \
--temp_dir /home/user/tmp -mpd_output test_h264.mpd

That is pretty much all there is to it. You could add higher or lower frame size and bitrates if you wanted to. However, this configuration is pushing the limits of Liquidsoap.

Liquidsoap – Streaming DASH via UDP

So now that you have that magical UDP stream going, what to do with it? Why  not a DASH stream using the HTML5 Shaka Player? DASH is D)umb A)ss S)treaming H)ibbtyjibbty also known as the Hard Drive Killer. Cause you ain’t gunna be doing this on a mechanical drive for very long. Who thought the idea of creating millions of tiny files constantly was a good idea? We totally needed a new wheel worse than the one before it, right? I mean, who wants to install various different media players when you now can install various different browsers. Anyways, on to the show!

This is relatively straight forward with a lot of ambiguity.  The path of the stream is as follows; Liquidsoap output MPEGTS via UDP into Shaka Packager. Shaka Packager encapsulates the MPEGTS stream into a DASH stream for playback via a web server and HTML5 clients and other media players that support DASH (such as VLC).  You will want to download the Shaka Packager and Shaka Player.  This is a bit beyond the scope of this article oh how to get the Shaka Player compiled, but just trudge through it.

Liquidsoap Configuration

First part we need to configure Liquidsoap. This will be a basic configuration with the key parts being the output settings. Most notably and very important is that the Shaka Packager does not support the RTP container so it’s MPEGTS flat out.

set("log.stdout",true)
set("log.level",5)
set("gstreamer.debug_level",2)

set("frame.video.width",512)
set("frame.video.height",384)
set("frame.video.samplerate",25)
set("gstreamer.add_borders", true)

# The input file,
# any format supported by liquidsoap
input = single("/home/user/Videos/Test/video-720x576-25fps.ogv")

#MPEGTS via UDP Streaming (Shaka Packager doesn't support RTP container).
output.gstreamer.audio_video(
 video_pipeline=
 "videoconvert ! x264enc pass=qual quantizer=20 tune=zerolatency ! video/x-h264,profile=baseline ! queue ! muxer.",
 audio_pipeline=
 "audioconvert ! fdkaacenc bitrate=128000 ! queue ! muxer.",
 pipeline=
 "mpegtsmux alignment=7 name=muxer ! queue ! udpsink host=127.0.0.1 port=5000 sync=true",
 mksafe(input))

The Shaka Packager doesn’t support MPEGTS in RTP via UDP. If you encapsulate your MPEGTS stream via RTP you will get this error from packager.

[0522/093109:INFO:demuxer.cc(89)] Demuxer::Run() on file 'udp://127.0.0.1:5000?interface=127.0.0.1'.
[0522/093109:INFO:demuxer.cc(161)] Initialize Demuxer for file 'udp://127.0.0.1:5000?interface=127.0.0.1'.
[0522/093116:ERROR:demuxer.cc(207)] Not implemented reached in shaka::Status shaka::media::Demuxer::InitializeParser()
[0522/093116:ERROR:packager_main.cc(477)] Packaging Error: 4 (UNIMPLEMENTED): Container not supported.
Shaka Packager

The Shaka Packager is just a fairly simple program. I just downloaded the binaries and put them in my /home/user/bin/.

In this scenario and since we are using UDP as the transport, the Shaka Packager will need to be started first and listening for the MPEGTS stream packets BEFORE Liquidsoap starts. This is an important and key step in this.

For this example we will start the Shaka Packager program in the root web directory of our website the DASH manifest and media files will be created in this directory.

[/var/www/html/]$packager \
 'in=udp://127.0.0.1:5000interface=127.0.0.1,stream=audio,init_segment=audio_init.mp4,segment_template=audio_$Number$.m4s'  \ 'in=udp://127.0.0.1:5000interface=127.0.0.1,stream=video,init_segment=h264_384p_init.mp4,segment_template=h264_384p_$Number$.m4s' \
--mpd_output h264.mpd

The Shaka Packager is fairly straight forward. Since the Audio and Video is in a single stream we call both streams for our audio and video.  It will create all the necessary media files and the MPD manifest file in the current directory.  Once Shaka Packager is running it will just print out that it’s initialized the demuxer for input from the UDP stream.  It is now time to start Liquidsoap.

Once you start Liquidsoap, the Shaka Packager doesn’t announce anything so it is difficult to know that it is working.  With this current setup after about a minute or two the Shaka Packager will spit an error similar to this.

[0522/095415:WARNING:representation.cc(384)] Found a gap of size 343 > kRoundingErrorGrace (5). The new segment starts at 324900702 but the previous segment ends at 324900359.

If you get this error then you know it’s working. As long as the gap size doesn’t grow the stream should be good.

Shaka Player

So now that you got the stream files all going, you could point VLC to the http://localhost/h264.mpd file and VLC should be happy to play the DASH stream.  But now onto the Shaka Player. It is beyond the scope of me to tell you how to install it. But I feel you.

Following the basic guide for the Shaka Player, just copy the shaka-player.compiled.js script into the same directory as the media files. Then create a simple index.html file.

<!DOCTYPE html>
<html>
<head>
<!-- Shaka Player compiled library: -->
<script src="shaka-player.compiled.js"></script>
<!-- Your application source: -->
<script src="myapp.js"></script>
</head>
<body>
<video id="video" width="640" poster="//shaka-player-demo.appspot.com/assets/poster.jpg" controls autoplay></video>
</body>
</html>

Then you will want to create the myapp.js file which is essentially a kind of configuration and initialization file for the Shaka Player. This is the file that points to the DASH manifest (MPD) file that the Packager has created.

// myapp.js

var manifestUri = '//localhost/h264.mpd';

function initApp() {
// Install built-in polyfills to patch browser incompatibilities.
shaka.polyfill.installAll();

// Check to see if the browser supports the basic APIs Shaka needs.
if (shaka.Player.isBrowserSupported()) {
// Everything looks good!
initPlayer();
} else {
// This browser does not have the minimum set of APIs we need.
console.error('Browser not supported!');
}
}

function initPlayer() {
// Create a Player instance.
var video = document.getElementById('video');
var player = new shaka.Player(video);

// Attach player to the window to make it easy to access in the JS console.
window.player = player;

// Listen for error events.
player.addEventListener('error', onErrorEvent);

// Try to load a manifest.
// This is an asynchronous process.
player.load(manifestUri).then(function() {
// This runs if the asynchronous load is successful.
console.log('The video has now been loaded!');
}).catch(onError); // onError is executed if the asynchronous load fails.
}

function onErrorEvent(event) {
// Extract the shaka.util.Error object from the event.
onError(event.detail);
}

function onError(error) {
// Log the error.
console.error('Error code', error.code, 'object', error);
}

document.addEventListener('DOMContentLoaded', initApp);

You should now have a functioning DASH stream with Liquidsoap.  This is a basic example and can be expanded on with enough CPU resources and x264 hardware encoding via GStreamer.  The idea of DASH is to have multiple bit rates at various frame sizes so that you can cover a wide variety of client types and connectivity.  I haven’t tested this out, but you could start with a larger frame size setting in Liquidsoap then create multiple video outputs with the various frame sizes. You will only need one audio output.

Have fun!

Liquidsoap – MPEGTS Stream into RTP over UDP

I finally have the magical unicorn configuration you’ve been looking for. My previous entry showed you how to create an MPEGTS stream via TCP. Here is the configuration for the MPEGTS stream encapsulated into RTP for UDP. This is a single stream with video and audio, no stupid ass SDP file required. This isn’t suggested for clients to connect and use but as an intermediate transport means. Simple instructions on how to get ffmpeg (ffplay) and VLC to access the streams. You may need to consult satanic priests or launch a car into space as a sacrifice for anything beyond these settings.

Obvious settings are host and port. Your encoding schemes are constrained to what the mpegtsmux capabilities are.

#MPEGTS encapsulated to RTP for UDP Streaming.
#Stream can be played by these players.
#ffplay udp://localhost:5000
#vlc rtp://@:5000
output.gstreamer.audio_video(
video_pipeline=
"videoconvert ! x264enc pass=qual quantizer=20 tune=zerolatency ! video/x-h264,profile=baseline ! queue ! muxer.",
audio_pipeline=
"audioconvert ! fdkaacenc bitrate=128000 ! queue ! muxer.",
pipeline=
"mpegtsmux alignment=7 name=muxer ! rtpmp2tpay ! queue ! udpsink host=127.0.0.1 port=5000 sync=true",
source)

Liquidsoap – Gstreamer MPEGTS stream how to

This is a working example using Liquidsoap and it’s Gstreamer output capabilities. You are probably here because there is little to no information on how to configure this other than ambiguous examples with no explanation. I will try to attempt to explain some of the specific configuration options and my experience. First some of the requirements. This article is based on Liquidsoap v1.3.3 and Gstreamer v1.12. You will need several of the plugin packages required for the Gstreamer configuration. I recommend ffmpeg for testing purposes, Gstreamer client commands for testing are beyond painful. This example will require a computer with a CPU with AVX and/or AVX2 extensions. If the CPU does not have these extensions, it is most likely not powerful enough to do the x264 real time encoding. If this is the case you will need to stick with Liquidsoaps built in theora/vorbis video stream at about maximum of 512×376 video dimensions. At the time of this writing, I am not aware of GPU assisted encoding in Gstreamer. You may also need to sacrifice up to four Beef & Bean burritos, unfortunately there is no vegan option at the time of this writing.


#Setup Log Output And Levels For Testing. Once done testing, set to false and 3.
set("log.stdout",true)
set("log.level",10)

#Set Video Frame Width, Height, and Frame Rate. Liquidsoap will resize and if necessary add borders to the videos that are larger or smaller than these dimensions.
#Larger dimensions will increase CPU usage.
#Important: ALL VIDEOS IN SOURCE PLAYLIST MUST BE THE SAME FRAME RATE. If not you will be guaranteed desynchronized audio.
set("frame.video.width",720)
set("frame.video.height",576)
set("frame.video.samplerate",25)
set("gstreamer.add_borders", true)

#Playlist of your video files you want to stream.
#Again I reiterate: ALL VIDEOS IN SOURCE PLAYLIST MUST BE THE SAME FRAME RATE. You are going to be doing a lot of re-encoding.
source = playlist("/home/user/Videos/")

#The output pipeline you've been dreaming for. You can put the knife down now.
#
#video_pipeline: x264enc is the encoder. The default bitrate is 2048kbits, this setting is in kbits. Higher dimensions will require higher bitrate settings.
# tune=zerolatency is required for this setup. We're in the real time baby!
# pass=qual quantizer=20 are Constant Quality encoding and Quality quantizer. Consult a witch doctor for these settings.
# video/x-h264 defines our video stream mime type. profile=baseline is required. Any other profile you are on your own!
#audio_pipeline: fdkaacenc is the encoder. Other examples show the voaacenc. This is old, use the recommended fdkaacenc instead.
# bitrate setting for fdkaacenc is in bytes.
#pipeline: mpegtsmux is the MPEGTS container used for the video/audio stream.
# tcpserversink is the final destination output. The default for host=/port= are localhost:4953 so are not set in this example.
# recover-policy=keyframe sync-method=latest-keyframe These two settings are required. They set proper keyframes for connecting clients,
# otherwise the clients will never sync up as they will never receive a keyframe.
output.gstreamer.audio_video(
video_pipeline="videoconvert ! x264enc pass=qual quantizer=20 tune=zerolatency ! video/x-h264,profile=baseline ! queue ! muxer.",
audio_pipeline="audioconvert ! fdkaacenc bitrate=128000 ! queue ! muxer.",
pipeline="mpegtsmux name=muxer ! tcpserversink recover-policy=keyframe sync-method=latest-keyframe",
mksafe(source))

First you will want to check your LIQ file for errors and then fire up Liquidsoap, if it didn’t end immediately then that is a good sign. The most likely culprit will be not having one of the Gstreamer plugins, which can be easily installed by your package manager.


$ liquidsoap --check why.liq
$

(This is good, anything else your fumble fingers fucked something up)


2018/05/01 17:06:47 [output(dot)gstreamer:5] GStreamer pipeline: appsrc name="video_src" block=true caps="video/x-raw,format=RGBA,width=720,height=576,framerate=25/1,pixel-aspect-ratio=1/1" format=time blocksize=1658880 ! videoconvert ! x264enc pass=qual quantizer=20 tune=zerolatency ! video/x-h264,profile=baseline ! queue ! muxer. appsrc name="audio_src" block=true caps="audio/x-raw,format=S16LE,layout=interleaved,channels=2,rate=44100" format=time ! audioconvert ! fdkaacenc bitrate=128000 ! queue ! muxer. mpegtsmux name=muxer ! tcpserversink recover-policy=keyframe sync-method=latest-keyframe

Look for this output. Any errors in the Gstreamer configuration will occur at this point and exit Liquidsoap. If everything is good and Liquidsoap is happy it will be streaming to the localhost on port 4953. Now let’s see the video stream!


$ ffplay tcp://localhost:4953
Input #0, mpegts, from 'tcp://localhost:4953':0KB sq= 0B f=0/0
Duration: N/A, start: 3614.480000, bitrate: N/A
Program 1
Stream #0:0[0x41]: Video: h264 (Constrained Baseline) (HDMV / 0x564D4448), yuv420p(tv, bt470bg/smpte170m/bt709, progressive), 720x576 [SAR 1:1 DAR 5:4], 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1[0x42](en): Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 138 kb/s
3623.20 A-V: -0.030 fd= 3 aq= 22KB vq= 7KB sq= 0B f=0/0

ffplay is part of the ffmpeg package and makes it really easy to test the stream. There should be NO errors. If ffplay is reporting errors the most likely issue is you’ve made changes to the x264enc settings. There is a small margin of error to the conformity of the x264enc settings. The mpegtsmux only supports a handful of video and audio codecs, so be sure to check on what it supports before throwing random encoders at it. It is not recommended that this be used for end clients but as an intermediate type of output. For example, you can use ffmpeg to connect to the stream, transcode and push to an Icecast server.


ffmpeg -re -i tcp://localhost:4953 -f webm -content_type video/webm -c:v libvpx -b:v 1500K -flags:v +global_header -cpu-used 0 -qmin 10 -qmax 42 -deadline realtime -quality realtime -c:a libvorbis -flags:a +global_header icecast://source:yourpassword@localhost:8000/test.webm

Congratulations! You have now entered the foray of alcoholism and drug addiction!

AutoNarcosis Radio! All Gay, All Day!

I’ve added a little widget on the side panel for some streaming audio. I’ve been having fun playing around with LiquidSoap and IceCast2. It seems to work really well. The only problem is, about 99% of the people I tell about it seem competely unable to stream audio. Are people that retarded? Apparently so! The stream is not always on, so if it doesn’t work, most likely I am not listening to music myself.  It’s random as to what I am playing. It could be anything from Dance/Disco to Heavy Metal.  I’ve had this setup before a long time ago using SAM Broadcaster and Windows Media Services and WMA format. Not that anyone ever listened to it much.

LiquidSoap is an Open Source automated DJ media player/streamer. I found it looking for an alternative to SAM Broadcaster because that is just way too much money and kind of an over kill for what I was looking for.  IceCast2 is a media streaming server. I point the output of LiquidSoap to IceCast2 and clients (thats you) connect to IceCast2 server.  I intitally tried to use OGG format, however, most players fail to play OGG streams properly so switched to MP3.

I am running this all off Windows, but it works just as well (if not better) on Linux.