Skip to content

Instantly share code, notes, and snippets.

@SanchayanMaity
Last active January 12, 2025 00:01
Show Gist options
  • Select an option

  • Save SanchayanMaity/32f40e5ad179c96e664a3506e7954e9e to your computer and use it in GitHub Desktop.

Select an option

Save SanchayanMaity/32f40e5ad179c96e664a3506e7954e9e to your computer and use it in GitHub Desktop.
Test code for hlssink4
#!/usr/bin/env python3
import os # noqa: I001
import signal
import sys
import subprocess
import gi
gi.require_version("GLib", "2.0")
gi.require_version("Gst", "1.0")
from gi.repository import GLib, Gst # noqa: E402
Gst.init(None)
hlsTestFile = None
glibMainLoop: GLib.MainLoop = None
pipeline: Gst.Element = None
def debugDumpPipeline(phase: str) -> None:
GLib.timeout_add_seconds(
5,
lambda: Gst.debug_bin_to_dot_file_with_ts(
pipeline, Gst.DebugGraphDetails.VERBOSE, "hlssink-" + phase
),
)
def log(*args):
print(*args, file=sys.stderr)
def hasVideoAudio(filename) -> bool:
result = subprocess.run(
[
"ffprobe",
"-v",
"error",
"-show_entries",
"format=nb_streams",
"-of",
"default=noprint_wrappers=1:nokey=1",
filename,
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
print("Number of streams: ", int(result.stdout))
return int(result.stdout) == 2
def busMessage(_bus: Gst.Bus, message: Gst.Message, loop: GLib.MainLoop) -> bool:
t = message.type
if t == Gst.MessageType.EOS:
log("End of stream. Shutting down pipeline.")
loop.quit()
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
debug = f"{err}: {debug}"
log("Pipeline error: ", err, debug)
loop.quit()
return True
def sigintHandler(_signum, _frame):
log("Sending EOS to pipeline")
pipeline.send_event(Gst.Event.new_eos())
glibMainLoop.quit()
def decodebinPadAdded(_dbin: Gst.Element, pad: Gst.Pad) -> None:
global pipeline
caps: Gst.Caps = pad.get_current_caps()
s = caps.get_structure(0)
name = s.get_name()
if name.startswith("video"):
videotee = pipeline.get_by_name("video-tee")
sinkpad = videotee.get_static_pad("sink")
pad.link(sinkpad)
debugDumpPipeline("decodebin-video")
elif name.startswith("audio"):
audiotee = pipeline.get_by_name("audio-tee")
sinkpad = audiotee.get_static_pad("sink")
pad.link(sinkpad)
debugDumpPipeline("decodebin-audio")
else:
return
def fileSourceBin(pipeline: Gst.Element) -> (Gst.Element, Gst.Element):
filesrc = Gst.ElementFactory.make("filesrc")
decodebin = Gst.ElementFactory.make("decodebin")
audiotee = Gst.ElementFactory.make("tee", "audio-tee")
videotee = Gst.ElementFactory.make("tee", "video-tee")
filesrc.set_property("location", hlsTestFile)
pipeline.add(filesrc)
pipeline.add(decodebin)
pipeline.add(audiotee)
pipeline.add(videotee)
decodebin.connect("pad-added", decodebinPadAdded)
filesrc.link(decodebin)
return (audiotee, videotee)
def videoBin(
width: int,
height: int,
fps: int,
bitrate: int,
iframe_only: bool,
) -> Gst.Bin:
clocksync: Gst.Element = Gst.ElementFactory.make("clocksync")
videoconvert: Gst.Element = Gst.ElementFactory.make("videoconvert")
videoscale: Gst.Element = Gst.ElementFactory.make("videoscale")
videorate: Gst.Element = Gst.ElementFactory.make("videorate")
capsfilter: Gst.Element = Gst.ElementFactory.make("capsfilter")
x264enc: Gst.Element = Gst.ElementFactory.make("x264enc")
h264_capsfilter: Gst.Element = Gst.ElementFactory.make("capsfilter")
h264parse: Gst.Element = Gst.ElementFactory.make("h264parse")
queue: Gst.Element = Gst.ElementFactory.make("queue")
caps = Gst.Caps.from_string(
f"""video/x-raw,width={width},height={height},framerate={fps}/1,pixel-aspect-ratio=1/1,format=I420"""
)
capsfilter.set_property("caps", caps)
caps = Gst.Caps.from_string("video/x-h264")
h264_capsfilter.set_property("caps", caps)
x264enc.set_property("bitrate", bitrate)
x264enc.set_property("speed-preset", "ultrafast")
x264enc.set_property("tune", "zerolatency")
if iframe_only:
x264enc.set_property("key-int-max", 1)
else:
x264enc.set_property("key-int-max", fps * 2)
bin = Gst.Bin.new("videobin-" + str(bitrate))
bin.add(clocksync)
bin.add(videoconvert)
bin.add(videoscale)
bin.add(videorate)
bin.add(capsfilter)
bin.add(x264enc)
bin.add(h264_capsfilter)
bin.add(h264parse)
bin.add(queue)
clocksync.link(videoconvert)
videoconvert.link(videorate)
videorate.link(videoscale)
videoscale.link(capsfilter)
capsfilter.link(x264enc)
x264enc.link(h264_capsfilter)
h264_capsfilter.link(h264parse)
h264parse.link(queue)
queue_srcpad: Gst.Pad = queue.get_static_pad("src")
srcpad: Gst.Pad = Gst.GhostPad.new("src", queue_srcpad)
clocksync_sinkpad: Gst.Pad = clocksync.get_static_pad("sink")
sinkpad: Gst.Pad = Gst.GhostPad.new("sink", clocksync_sinkpad)
bin.add_pad(sinkpad)
bin.add_pad(srcpad)
Gst.debug_bin_to_dot_file_with_ts(
bin, Gst.DebugGraphDetails.VERBOSE, "videobin-" + str(bitrate)
)
return bin
def audioBin(bitrate: int) -> Gst.Bin:
clocksync: Gst.Element = Gst.ElementFactory.make("clocksync")
audioconvert: Gst.Element = Gst.ElementFactory.make("audioconvert")
audiorate: Gst.Element = Gst.ElementFactory.make("audiorate")
capsfilter: Gst.Element = Gst.ElementFactory.make("capsfilter")
audioenc: Gst.Element = Gst.ElementFactory.make("avenc_aac")
aacparse: Gst.Element = Gst.ElementFactory.make("aacparse")
caps: Gst.Caps = Gst.Caps.from_string(
"audio/x-raw,channels=2,rate=48000,format=F32LE"
)
capsfilter.set_property("caps", caps)
audioenc.set_property("bitrate", bitrate)
bin = Gst.Bin.new("audiobin-" + str(bitrate))
bin.add(clocksync)
bin.add(audioconvert)
bin.add(audiorate)
bin.add(capsfilter)
bin.add(audioenc)
bin.add(aacparse)
clocksync.link(audioconvert)
audioconvert.link(audiorate)
audiorate.link(capsfilter)
capsfilter.link(audioenc)
audioenc.link(aacparse)
aacparse_srcpad: Gst.Pad = aacparse.get_static_pad("src")
srcpad: Gst.Pad = Gst.GhostPad.new("src", aacparse_srcpad)
clocksync_sinkpad: Gst.Pad = clocksync.get_static_pad("sink")
sinkpad: Gst.Pad = Gst.GhostPad.new("sink", clocksync_sinkpad)
bin.add_pad(sinkpad)
bin.add_pad(srcpad)
return bin
def multipleAudioRenditionMultipleVideoVariant(iframe: bool = False):
global pipeline
hlssink: Gst.Element = Gst.ElementFactory.make("hlssink4")
hlssink.set_property("master-playlist-location", "hlssink/master.m3u8")
hlssink.set_property("playlist-type", "event")
hlssink.set_property("target-duration", 10)
if iframe:
hlssink.set_property("muxer-type", "mpegts")
pipeline.add(hlssink)
(audiotee, videotee) = fileSourceBin(pipeline)
audioBin1: Gst.Bin = audioBin(256000)
pipeline.add(audioBin1)
audioBin1_sinkpad = audioBin1.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin1_sinkpad)
audioBin1Pad: Gst.Pad = audioBin1.get_static_pad("src")
audio1Pad = hlssink.request_pad_simple("audio_%u")
r = Gst.Structure.new_empty("audio1")
r.set_value("media_type", "AUDIO")
r.set_value("uri", "hi-audio/audio.m3u8")
r.set_value("group_id", "aac")
r.set_value("language", "en")
r.set_value("name", "English")
r.set_value("default", True)
r.set_value("autoselect", False)
audio1Pad.set_property("alternate-rendition", r)
audioBin1Pad.link(audio1Pad)
audioBin2: Gst.Bin = audioBin(128000)
pipeline.add(audioBin2)
audioBin2_sinkpad = audioBin2.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin2_sinkpad)
audioBin2Pad: Gst.Pad = audioBin2.get_static_pad("src")
audio2Pad = hlssink.request_pad_simple("audio_%u")
r = Gst.Structure.new_empty("audio2")
r.set_value("media_type", "AUDIO")
r.set_value("uri", "mid-audio/audio.m3u8")
r.set_value("group_id", "aac")
r.set_value("name", "French")
r.set_value("language", "fr")
r.set_value("default", False)
r.set_value("autoselect", True)
audio2Pad.set_property("alternate-rendition", r)
audioBin2Pad.link(audio2Pad)
videoBin1: Gst.Bin = videoBin(1920, 1080, 30, 2500, False)
pipeline.add(videoBin1)
videoBin1_sinkpad = videoBin1.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin1_sinkpad)
videoBin1Pad: Gst.Pad = videoBin1.get_static_pad("src")
video1Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video1")
v.set_value("uri", "hi/video.m3u8")
v.set_value("bandwidth", 2500)
v.set_value("audio", "aac")
video1Pad.set_property("variant", v)
videoBin1Pad.link(video1Pad)
videoBin2: Gst.Bin = videoBin(1280, 720, 30, 1500, False)
pipeline.add(videoBin2)
videoBin2_sinkpad = videoBin2.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin2_sinkpad)
videoBin2Pad: Gst.Pad = videoBin2.get_static_pad("src")
video2Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video2")
v.set_value("uri", "mid/video.m3u8")
v.set_value("bandwidth", 1500)
v.set_value("audio", "aac")
video2Pad.set_property("variant", v)
videoBin2Pad.link(video2Pad)
videoBin3: Gst.Bin = videoBin(640, 360, 24, 700, False)
pipeline.add(videoBin3)
videoBin3_sinkpad = videoBin3.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin3_sinkpad)
videoBin3Pad: Gst.Pad = videoBin3.get_static_pad("src")
video3Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video3")
v.set_value("uri", "low/video.m3u8")
v.set_value("bandwidth", 700)
if not iframe:
v.set_value("audio", "aac")
else:
v.set_value("is-i-frame", True)
video3Pad.set_property("variant", v)
videoBin3Pad.link(video3Pad)
debugDumpPipeline("setup-done")
def singleAudioRenditionMultipleVideoVariant():
global pipeline
hlssink: Gst.Element = Gst.ElementFactory.make("hlssink4")
hlssink.set_property("master-playlist-location", "hlssink/master.m3u8")
hlssink.set_property("playlist-type", "event")
hlssink.set_property("target-duration", 10)
pipeline.add(hlssink)
(audiotee, videotee) = fileSourceBin(pipeline)
audioBin1: Gst.Bin = audioBin(256000)
pipeline.add(audioBin1)
audioBin1_sinkpad = audioBin1.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin1_sinkpad)
audioBin1Pad: Gst.Pad = audioBin1.get_static_pad("src")
audio1Pad = hlssink.request_pad_simple("audio_%u")
r = Gst.Structure.new_empty("audio1")
r.set_value("media_type", "AUDIO")
r.set_value("uri", "hi-audio/audio.m3u8")
r.set_value("group_id", "aac")
r.set_value("language", "en")
r.set_value("name", "English")
r.set_value("default", True)
r.set_value("autoselect", False)
audio1Pad.set_property("alternate-rendition", r)
audioBin1Pad.link(audio1Pad)
videoBin1: Gst.Bin = videoBin(1920, 1080, 30, 2500, False)
pipeline.add(videoBin1)
videoBin1_sinkpad = videoBin1.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin1_sinkpad)
videoBin1Pad: Gst.Pad = videoBin1.get_static_pad("src")
video1Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video1")
v.set_value("uri", "hi/video.m3u8")
v.set_value("bandwidth", 2500)
v.set_value("audio", "aac")
video1Pad.set_property("variant", v)
videoBin1Pad.link(video1Pad)
videoBin2: Gst.Bin = videoBin(1280, 720, 30, 1500, False)
pipeline.add(videoBin2)
videoBin2_sinkpad = videoBin2.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin2_sinkpad)
videoBin2Pad: Gst.Pad = videoBin2.get_static_pad("src")
video2Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video2")
v.set_value("uri", "mid/video.m3u8")
v.set_value("bandwidth", 1500)
v.set_value("audio", "aac")
video2Pad.set_property("variant", v)
videoBin2Pad.link(video2Pad)
videoBin3: Gst.Bin = videoBin(640, 360, 24, 700, False)
pipeline.add(videoBin3)
videoBin3_sinkpad = videoBin3.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin3_sinkpad)
videoBin3Pad: Gst.Pad = videoBin3.get_static_pad("src")
video3Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video3")
v.set_value("uri", "low/video.m3u8")
v.set_value("bandwidth", 700)
v.set_value("audio", "aac")
video3Pad.set_property("variant", v)
videoBin3Pad.link(video3Pad)
debugDumpPipeline("setup-done")
def multipleAudioRenditionSingleVideoVariant():
global pipeline
hlssink: Gst.Element = Gst.ElementFactory.make("hlssink4")
hlssink.set_property("master-playlist-location", "hlssink/master.m3u8")
hlssink.set_property("playlist-type", "event")
hlssink.set_property("target-duration", 10)
pipeline.add(hlssink)
(audiotee, videotee) = fileSourceBin(pipeline)
audioBin1: Gst.Bin = audioBin(256000)
pipeline.add(audioBin1)
audioBin1_sinkpad = audioBin1.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin1_sinkpad)
audioBin1Pad: Gst.Pad = audioBin1.get_static_pad("src")
audio1Pad = hlssink.request_pad_simple("audio_%u")
r = Gst.Structure.new_empty("audio1")
r.set_value("media_type", "AUDIO")
r.set_value("uri", "hi-audio/audio.m3u8")
r.set_value("group_id", "aac")
r.set_value("language", "en")
r.set_value("name", "English")
r.set_value("default", True)
r.set_value("autoselect", False)
audio1Pad.set_property("alternate-rendition", r)
audioBin1Pad.link(audio1Pad)
audioBin2: Gst.Bin = audioBin(128000)
pipeline.add(audioBin2)
audioBin2_sinkpad = audioBin2.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin2_sinkpad)
audioBin2Pad: Gst.Pad = audioBin2.get_static_pad("src")
audio2Pad = hlssink.request_pad_simple("audio_%u")
r = Gst.Structure.new_empty("audio2")
r.set_value("media_type", "AUDIO")
r.set_value("uri", "mid-audio/audio.m3u8")
r.set_value("group_id", "aac")
r.set_value("name", "Hindu")
r.set_value("language", "hi")
r.set_value("default", False)
r.set_value("autoselect", True)
audio2Pad.set_property("alternate-rendition", r)
audioBin2Pad.link(audio2Pad)
audioBin3: Gst.Bin = audioBin(64000)
pipeline.add(audioBin3)
audioBin3_sinkpad = audioBin3.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin3_sinkpad)
audioBin3Pad: Gst.Pad = audioBin3.get_static_pad("src")
audio3Pad = hlssink.request_pad_simple("audio_%u")
r = Gst.Structure.new_empty("audio3")
r.set_value("media_type", "AUDIO")
r.set_value("uri", "low-audio/audio.m3u8")
r.set_value("group_id", "aac")
r.set_value("name", "Danish")
r.set_value("language", "da")
r.set_value("default", False)
r.set_value("autoselect", True)
audio3Pad.set_property("alternate-rendition", r)
audioBin3Pad.link(audio3Pad)
videoBin1: Gst.Bin = videoBin(1920, 1080, 30, 2500, False)
pipeline.add(videoBin1)
videoBin1_sinkpad = videoBin1.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin1_sinkpad)
videoBin1Pad: Gst.Pad = videoBin1.get_static_pad("src")
video1Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video1")
v.set_value("uri", "hi/video.m3u8")
v.set_value("bandwidth", 2500)
v.set_value("audio", "aac")
video1Pad.set_property("variant", v)
videoBin1Pad.link(video1Pad)
debugDumpPipeline("setup-done")
def singleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed():
global pipeline
hlssink: Gst.Element = Gst.ElementFactory.make("hlssink4")
hlssink.set_property("master-playlist-location", "hlssink/master.m3u8")
hlssink.set_property("playlist-type", "event")
hlssink.set_property("target-duration", 10)
hlssink.set_property("muxer-type", "mpegts")
pipeline.add(hlssink)
(audiotee, videotee) = fileSourceBin(pipeline)
videoBin1: Gst.Bin = videoBin(1920, 1080, 30, 2500, False)
pipeline.add(videoBin1)
videoBin1_sinkpad = videoBin1.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin1_sinkpad)
videoBin1Pad: Gst.Pad = videoBin1.get_static_pad("src")
video1Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video1")
v.set_value("uri", "hi/video.m3u8")
v.set_value("bandwidth", 2500)
video1Pad.set_property("variant", v)
videoBin1Pad.link(video1Pad)
videoBin2: Gst.Bin = videoBin(1280, 720, 30, 1500, False)
pipeline.add(videoBin2)
videoBin2_sinkpad = videoBin2.get_static_pad("sink")
videoteeSrcPad = videotee.request_pad_simple("src_%u")
videoteeSrcPad.link(videoBin2_sinkpad)
videoBin2Pad: Gst.Pad = videoBin2.get_static_pad("src")
video2Pad = hlssink.request_pad_simple("video_%u")
v = Gst.Structure.new_empty("video2")
v.set_value("uri", "mid/video.m3u8")
v.set_value("bandwidth", 1500)
video2Pad.set_property("variant", v)
videoBin2Pad.link(video2Pad)
audioBin1: Gst.Bin = audioBin(256000)
pipeline.add(audioBin1)
audioBin1_sinkpad = audioBin1.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin1_sinkpad)
audioBin1Pad: Gst.Pad = audioBin1.get_static_pad("src")
audio1Pad = hlssink.request_pad_simple("audio_%u")
v = Gst.Structure.new_empty("audio1")
v.set_value("uri", "hi/video.m3u8")
v.set_value("bandwidth", 256000)
audio1Pad.set_property("variant", v)
audioBin1Pad.link(audio1Pad)
audioBin2: Gst.Bin = audioBin(128000)
pipeline.add(audioBin2)
audioBin2_sinkpad = audioBin2.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin2_sinkpad)
audioBin2Pad: Gst.Pad = audioBin2.get_static_pad("src")
audio2Pad = hlssink.request_pad_simple("audio_%u")
v = Gst.Structure.new_empty("audio2")
v.set_value("uri", "mid/video.m3u8")
v.set_value("bandwidth", 128000)
audio2Pad.set_property("variant", v)
audioBin2Pad.link(audio2Pad)
audioBin3: Gst.Bin = audioBin(64000)
pipeline.add(audioBin3)
audioBin3_sinkpad = audioBin3.get_static_pad("sink")
audioteeSrcPad = audiotee.request_pad_simple("src_%u")
audioteeSrcPad.link(audioBin3_sinkpad)
audioBin3Pad: Gst.Pad = audioBin3.get_static_pad("src")
audio3Pad = hlssink.request_pad_simple("audio_%u")
v = Gst.Structure.new_empty("audio3")
v.set_value("uri", "low-audio/audio-only.m3u8")
v.set_value("bandwidth", 64000)
audio3Pad.set_property("variant", v)
audioBin3Pad.link(audio3Pad)
debugDumpPipeline("setup-done")
def main(_args) -> int:
global glibMainLoop
global pipeline
global hlsTestFile
file = sys.argv[1]
if not os.path.exists(file):
print("File not found")
return 0
if not hasVideoAudio(file):
print("File must have audio video")
return 0
hlsTestFile = file
print("Using file for hlssink4 testing: ", hlsTestFile)
glibMainLoop = GLib.MainLoop()
signal.signal(signal.SIGINT, sigintHandler)
pipeline = Gst.Pipeline.new()
multipleAudioRenditionMultipleVideoVariant()
# multipleAudioRenditionSingleVideoVariant()
# multipleAudioRenditionMultipleVideoVariant(True)
# singleAudioRenditionMultipleVideoVariant()
# singleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed()
bus = pipeline.get_bus()
bus.add_watch(GLib.PRIORITY_DEFAULT, busMessage, glibMainLoop)
pipeline.set_state(Gst.State.PLAYING)
debugDumpPipeline("playing")
glibMainLoop.run()
pipeline.set_state(Gst.State.NULL)
log("Exited GLib main loop")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment