BELABOX / belacoder

https://www.twitch.tv/rationalirl
GNU General Public License v3.0
66 stars 31 forks source link

Dynamic input switch #6

Closed josuah closed 5 months ago

josuah commented 5 months ago

With devices like the OrangePi or Radxa Rock5 that support multiple USB3 sources and HDMI RX, it is possible to implement a many-source switching scheme with GStreamer's input-selector.

For instance, with one HDMI RX and one HDMI <-> USB3 adapter (or USB3 mode of the camera if supported):

from microdot import Microdot, Response, redirect
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst

Gst.init(None)

txt0 = '''
    v4l2src device=/dev/video0 !
    identity name=ptsfixup signal-handoffs=TRUE !
    identity drop-buffer-flags=GST_BUFFER_FLAG_DROPPABLE !
    videoconvert ! videorate ! video/x-raw,framerate=30/1,format=NV12 !
    sel.sink_0

    v4l2src device=/dev/video1 ! image/jpeg,width=1280,height=720,framerate=30/1 !
    identity name=v_delay signal-handoffs=TRUE !
    mppjpegdec !
    videoconvert ! videorate ! video/x-raw,framerate=30/1,format=NV12 !
    sel.sink_1

    input-selector name=sel !
    mpph265enc qp-max=51 qos=true gop=60 rc-mode=vbr bps=600000 !
    h265parse config-interval=-1 !
    queue max-size-time=10000000000 max-size-buffers=1000 max-size-bytes=41943040 !
    mux.

    alsasrc device=hw:CARD=rockchiphdmiin !
    identity name=a_delay signal-handoffs=TRUE !
    volume volume=1.0 !
    audioconvert !
    voaacenc bitrate=128000 !
    aacparse !
    queue max-size-time=10000000000 max-size-buffers=1000 !
    mux.

    mpegtsmux name=mux !
    queue !
    srtclientsink uri=srt://1.2.3.4:1234
'''

txt1 = '''
    videotestsrc pattern=1 !
    sel.sink_0

    videotestsrc pattern=11 !
    sel.sink_1

    input-selector name=sel !
    autovideosink
'''

pipeline = Gst.parse_launch(txt0)
pipeline.set_state(Gst.State.PLAYING)

def switch(padname):
    sel = pipeline.get_by_name('sel')
    pad = sel.get_static_pad(padname)
    sel.set_property('active-pad', pad)

app = Microdot()

Response.default_content_type = "text/html"

HEAD_HTML = """
<head>
<style>button { display: block; width: 100%; height: 10%; }</style>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
</head>
"""

INDEX_HTML = HEAD_HTML +"""
<body>
<form action="/switch" method="post"><input type="hidden" name="padname" value="sink_0"/><button type="submit">HDMI</button></form>
<form action="/switch" method="post"><input type="hidden" name="padname" value="sink_1"/><button type="submit">USB</button></form>
</body>
"""

pipe = None
proc = None

def command(str):
    global pipe
    pipe.send(str)

@app.post("/switch")
async def command_scene(req):
    padname = req.form.get("padname")
    print(f'switching to "{padname}"')
    switch(padname)
    return redirect("/")

@app.route("/")
async def index(request):
    return INDEX_HTML

print('starting http service on 0.0.0.0:5000')
app.run()

This spawns a small web server that can be controlled i.e. from a phone, and a GStreamer in the background.

Hitting a button switch to the equivalent "scene" (or rather GStreamer Pad). It might be possible to be clever and automatically list all the pads.

txt0 contains a pipeline for the OrangePi 5 Plus. txt1 contains a pipeline that is hardware independent for testing the mechanism.

The Orange Pi 5 Plus version is largely inspired from yours.

This allows to remove one device (HDMI source selector) for the simpler use cases, leading to a more lightweight backpack, heavier wallet, and merrier Belabox.

Thanks for all the good work on this project with well-deserved praise!