FFmpeg cookbook
Here are examples of what is possible with FFmpeg support in liquidsoap:
Relaying without re-encoding
With FFmpeg support, liquidsoap can relay encoded streams without re-encoding them, enabling delivery to multiple destinations. Here is an example:
# Input the stream,
# from an Icecast server or any other source
encoded_source = input.http("https://icecast.radiofrance.fr/fip-hifi.aac")
# Send to one server here:
output.icecast(
%ffmpeg(format = "adts", %audio.copy),
fallible=true,
mount="/restream",
host="streaming.example.com",
port=8000,
password="xxx",
encoded_source
)
# An another one here:
output.icecast(
%ffmpeg(format = "adts", %audio.copy),
fallible=true,
mount="/restream",
host="streaming2.example.com",
port=8000,
password="xxx",
encoded_source
)mksafe cannot be used here because the content is not
plain PCM samples, which that operator is designed to produce. To make
the source infallible, either provide a single(...) source
with matching encoded content, or create an infallible source using
ffmpeg.encode.audio.
On-demand relaying without re-encoding
This extends the previous example to relay a stream only when listeners are connected, without re-encoding.
The format must be one that ffmpeg can handle;
mp3 is a good choice.
In the script below, the encoded format of the stream must match a
blank file (or any other file). output.harbor serves data
from the file when no listeners are connected and starts or stops the
underlying input as listeners come and go:
stream = input.http(start=false, "https://wwoz-sc.streamguys1.com/wwoz-hi.mp3")
listeners_count = ref(0)
def on_connect(_) =
listeners_count := listeners_count() + 1
if
listeners_count() > 0 and not stream.is_started()
then
log(
"Starting input"
)
stream.start()
end
end
def on_disconnect(_) =
listeners_count := listeners_count() - 1
if
listeners_count() == 0 and stream.is_started()
then
log(
"Stopping input"
)
stream.stop()
end
end
blank = single("/tmp/blank.mp3")
stream = fallback(track_sensitive=false, [stream, blank])
o =
output.harbor(
%ffmpeg(format = "mp3", %audio.copy),
format="audio/mpeg",
mount="relay",
stream
)
o.on_connect(synchronous=true, on_connect)
o.on_disconnect(synchronous=true, on_disconnect)Shared encoding
Liquidsoap can encode once and share the result across multiple outputs, minimizing CPU usage. Here is an example adapted from the previous one:
# Input the stream, from an Icecast server or any other source
source = input.http("https://icecast.radiofrance.fr/fip-hifi.aac")
# Make it infallible:
source = mksafe(source)
# Encode it in mp3:
source = ffmpeg.encode.audio(%ffmpeg(%audio(codec = "libmp3lame")), source)
# Send to one server here:
output.icecast(
%ffmpeg(format = "mp3", %audio.copy),
mount="/restream",
host="streaming.example.com",
port=8000,
password="xxx",
source
)
# An another one here:
output.icecast(
%ffmpeg(format = "mp3", %audio.copy),
mount="/restream",
host="streaming2.example.com",
port=8000,
password="xxx",
source
)Shared encoding is especially useful for video, which is computationally expensive. Here is an example sharing audio and video encoding across multiple destinations — both Icecast and YouTube/Facebook via RTMP:
# An audio source...
audio = sine()
# Encode it in mp3
audio = ffmpeg.encode.audio(%ffmpeg(%audio(codec = "libmp3lame")), audio)
# Send it to icecast
output.icecast(
%ffmpeg(format = "mp3", %audio.copy),
host="...",
password="...",
mount="/stream",
audio
)
# A video source, for instance a static image
video = single("image.png")
# Encode it in h264 format
video = ffmpeg.encode.video(%ffmpeg(%video(codec = "libx264")), video)
# Mux it with the audio
stream = source.mux.video(video=video, audio)
# Copy encoder for the rtmp stream
enc = %ffmpeg(format = "flv", %audio.copy, %video.copy)
# Send to YouTube
key = "..."
url = "rtmp://a.rtmp.youtube.com/live2/#{key}"
output.url(url=url, enc, stream)
# Send to Facebook
key = "..."
url = "rtmps://live-api-s.facebook.com:443/rtmp/#{key}"
output.url(self_sync=true, url=url, enc, stream)Add transparent logo and video
See: https://github.com/savonet/liquidsoap/discussions/1862
Live switch between encoded content
This is an ongoing development effort. If you encounter issues, please reach out via the online support channels.
Starting with liquidsoap 2.1.x, live switching on
encoded content with delivery to multiple outputs is gradually becoming
possible.
This requires solid knowledge of media codecs, containers, and ffmpeg bitstream filters. Different containers store codec binary data in incompatible ways, requiring bitstream filters to adapt the data. Additional filters may need to be written to support more input/output and codec combinations.
Here is a tested use case: live switch between a playlist of MP4 files and an RTMP FLV input:
s1 = input.rtmp(listen=false, "rtmp://....")
s1 = ffmpeg.filter.bitstream.h264_mp4toannexb(s1)
s2 = playlist("/path/to/playlist")
s2 = ffmpeg.filter.bitstream.h264_mp4toannexb(s2)
s = fallback(track_sensitive=false, [s1, s2])
mpegts =
%ffmpeg(format = "mpegts", fflags = "-autobsf", %audio.copy, %video.copy)
streams = [("mpegts", mpegts)]
output_dir = "/tmp/hls"
output.file.hls(
playlist="live.m3u8",
fallible=true,
segment_duration=5.,
output_dir,
streams,
s
)- The
h264_mp4toannexbfilter is needed on each stream to ensure the MP4 data conforms to what the MPEG-TS container expects. - FFmpeg’s automatic bitstream filter insertion must be disabled via
-autobsf. FFmpeg does not support this kind of live switch natively and its auto-inserted filters will not work.
Future work includes extending this to also support RTMP output from the same data.