Casandro / tvheadend_to_hls

A small web service that exports tvheadend services via HLS
GNU General Public License v2.0
2 stars 2 forks source link

Use gstreamer instead of ffmpeg? #5

Open ali1234 opened 3 months ago

ali1234 commented 3 months ago

As a proof of concept I implemented hls with a gst-launch-1.0 pipeline. This is a drop in replacement:

            self.stream=subprocess.Popen(
                [
                    "gst-launch-1.0",

                    # uncomment for debug:
                    #"-v",

                    # sources:
                    # test source:
                    #*shlex.split("videotestsrc is-live=true"),
                    # tvh source:
                    *shlex.split(f"souphttpsrc location={self.tvh_url} ! decodebin "),

                    # deinterlace: optional
                    # if resizing the video, we probably want to deinterlace it,
                    # otherwise it will look terrible.
                    "!", "deinterlace",

                    # resize: this is optional
                    *shlex.split("! videoscale ! video/x-raw,width=320,height=240"),

                    # re-encode to x264
                    # profile=main guarantees browser will be able to play it
                    # (they only like yuv420 or I420 as gstreamer calls it)
                    *shlex.split("! x264enc ! video/x-h264,profile=main ! h264parse"),

                    # send to hlssink
                    *shlex.split(f"! hlssink2 max-files=5 send-keyframe-requests=true target-duration=2"),
                    f"location={config['hls_local_path'] + '/segment%05d.ts'}",
                    f"playlist-location={self.m3u8_file}",
                ],
                # stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL
            )

The real benefit of using gstreamer instead of ffmpeg is that it is primarily a library rather than a CLI utility, which means the pipeline can be built dynamically and even reconfigured while it is running. That opens the door for channel changes without interrupting the stream, allowing a more "STB-like" user experience, where the channel changes without having to navigate to a different page. Pipelines can also fork so it should be able to generate multiple quality levels.

Gstreamer tends to be packaged in a way that allows a subset of functionality to be installed, which should help keep the docker container size and build time down.

Casandro commented 3 months ago

Interresting, I should look into it.

Casandro commented 3 months ago

Apparently there are python bindings, which might integrate it in a smoother way. I'll try to find out how it fares with hardware acceleration. That would open up new possibilities for more concurrent channels as well as multiple bitrates per channel.

ali1234 commented 3 months ago

For hardware acceleration just replace x264enc with vaapih264enc on Intel/AMD/Mesa or nvh264enc on Nvidia - although they probably have additional dependencies.

ali1234 commented 3 months ago

Here is another proof-of-concept, dynamically altering the pipeline from python:

https://github.com/ali1234/tvheadend_to_hls/tree/gstreamer-poc

This can't play from tvheadend. In the interest of clarity I removed basically all the existing code. It should still respect the path options though.

Run it as normal and open browser. You should see:

image

I didn't build any UI, so open a terminal and run curl http://0.0.0.0:8888/channel?channel=hello. A few seconds later the stream should change to this:

image

There should be no interruption to the stream when this happens, although it takes a few seconds due to buffering.

In theory it is possible to change the location property on a souphttpsrc in order to start playing a different channel from tvheadend, but I haven't tried that yet. There may be a problem while the source prerolls causing the stream to pause for a while. Will have a look at that tomorrow.

Casandro commented 3 months ago

Well I certainly don't want to switch the input of the encoder. I'd like to have one encoder per used channel, so one can use HLS effectively, by using a caching proxy server. For your idea WebRTC might be a better option. It's lower latency and allows you to encode the stream for each individual user.