-
-
Save digitalsignalperson/ef81c1afdc04d49f5ae3f6f97b2d29af to your computer and use it in GitHub Desktop.
| mkfifo /tmp/gfifo | |
| ./xdp-screen-cast-ffmpeg.py & # or run in another terminal | |
| # Note the gstreamer pipeline | |
| # 'pipewiresrc fd=%d path=%u ! videorate ! video/x-raw,framerate=60/1 ! videoconvert ! filesink location=/tmp/gfifo' | |
| # I specified a 60/1 frameerate. You can do other pre-processing here, or offload it to ffmpeg. | |
| # Encode the stream and pipe to netcat | |
| # note: must specify correct resolution here | |
| # we can now use h264_nvenc, mpegts, and anything else ffmpeg has | |
| ffmpeg -f rawvideo -pixel_format bgra -video_size 2560x1600 -i /tmp/gfifo \ | |
| -c:v h264_nvenc -preset:v llhq -tune ull -zerolatency 1 -delay 0 -f mpegts - \ | |
| | nc -l -p 9001 | |
| # on remote system do | |
| nc $ip_of_server 9001 | mpv --profile=low-latency --untimed --no-demuxer-thread - |
| #!/usr/bin/python3 | |
| # Source from: https://gitlab.gnome.org/-/snippets/19 | |
| import re | |
| import signal | |
| import dbus | |
| from gi.repository import GLib | |
| from dbus.mainloop.glib import DBusGMainLoop | |
| import gi | |
| gi.require_version('Gst', '1.0') | |
| from gi.repository import GObject, Gst | |
| DBusGMainLoop(set_as_default=True) | |
| Gst.init(None) | |
| loop = GLib.MainLoop() | |
| bus = dbus.SessionBus() | |
| request_iface = 'org.freedesktop.portal.Request' | |
| screen_cast_iface = 'org.freedesktop.portal.ScreenCast' | |
| pipeline = None | |
| def terminate(): | |
| if pipeline is not None: | |
| self.player.set_state(Gst.State.NULL) | |
| loop.quit() | |
| request_token_counter = 0 | |
| session_token_counter = 0 | |
| sender_name = re.sub(r'\.', r'_', bus.get_unique_name()[1:]) | |
| def new_request_path(): | |
| global request_token_counter | |
| request_token_counter = request_token_counter + 1 | |
| token = 'u%d'%request_token_counter | |
| path = '/org/freedesktop/portal/desktop/request/%s/%s'%(sender_name, token) | |
| return (path, token) | |
| def new_session_path(): | |
| global session_token_counter | |
| session_token_counter = session_token_counter + 1 | |
| token = 'u%d'%session_token_counter | |
| path = '/org/freedesktop/portal/desktop/session/%s/%s'%(sender_name, token) | |
| return (path, token) | |
| def screen_cast_call(method, callback, *args, options={}): | |
| (request_path, request_token) = new_request_path() | |
| bus.add_signal_receiver(callback, | |
| 'Response', | |
| request_iface, | |
| 'org.freedesktop.portal.Desktop', | |
| request_path) | |
| options['handle_token'] = request_token | |
| method(*(args + (options, )), | |
| dbus_interface=screen_cast_iface) | |
| def on_gst_message(bus, message): | |
| type = message.type | |
| if type == Gst.MessageType.EOS or type == Gst.MessageType.ERROR: | |
| terminate() | |
| def play_pipewire_stream(node_id): | |
| empty_dict = dbus.Dictionary(signature="sv") | |
| fd_object = portal.OpenPipeWireRemote(session, empty_dict, | |
| dbus_interface=screen_cast_iface) | |
| fd = fd_object.take() | |
| pipeline = Gst.parse_launch('pipewiresrc fd=%d path=%u ! videorate ! video/x-raw,framerate=60/1 ! videoconvert ! filesink location=/tmp/gfifo' %(fd, node_id)) | |
| pipeline.set_state(Gst.State.PLAYING) | |
| pipeline.get_bus().connect('message', on_gst_message) | |
| def on_start_response(response, results): | |
| if response != 0: | |
| print("Failed to start: %s"%response) | |
| terminate() | |
| return | |
| print("streams:") | |
| for (node_id, stream_properties) in results['streams']: | |
| print("stream {}".format(node_id)) | |
| play_pipewire_stream(node_id) | |
| def on_select_sources_response(response, results): | |
| if response != 0: | |
| print("Failed to select sources: %d"%response) | |
| terminate() | |
| return | |
| print("sources selected") | |
| global session | |
| screen_cast_call(portal.Start, on_start_response, | |
| session, '') | |
| def on_create_session_response(response, results): | |
| if response != 0: | |
| print("Failed to create session: %d"%response) | |
| terminate() | |
| return | |
| global session | |
| session = results['session_handle'] | |
| print("session %s created"%session) | |
| screen_cast_call(portal.SelectSources, on_select_sources_response, | |
| session, | |
| options={ 'multiple': False, | |
| 'types': dbus.UInt32(1|2) }) | |
| portal = bus.get_object('org.freedesktop.portal.Desktop', | |
| '/org/freedesktop/portal/desktop') | |
| (session_path, session_token) = new_session_path() | |
| screen_cast_call(portal.CreateSession, on_create_session_response, | |
| options={ 'session_handle_token': session_token }) | |
| try: | |
| loop.run() | |
| except KeyboardInterrupt: | |
| terminate() |
I'm on arch linux, it might be different on another OS.
For me
./xdp-screen-cast-ffmpeg.py
session /org/freedesktop/portal/desktop/session/1_1470/u1 created
sources selected
streams:
stream 176
If you get a dbus interface error, maybe it's on a different path. Does the original snippet work for you? https://gitlab.gnome.org/-/snippets/19
You could try installing D-feet (or now D-Spy) to look in the session bus for the interface
Note - anyone without an nvidia card, change h264_nvenc to plain h264.
For anyone looking for local playback, you can omit ffmpeg and mpv and simply run ffplay -f rawvideo -pixel_format bgra -video_size 1920x1080 -i /tmp/gfifo. I get a bit of latency still, but less than encoding and decoding. Apparently there are also -fflags nobuffer, or -tune zerolatency, these caused some problems for me and haven't yet looked into other flags to tune it towards live playback of raw video.
Note to self: add a way to get the screen resolution and give this to ffplay automatically.
I'm not sure why this no longer works for me. Whether trying mpv or ffplay, I get the first image, and maybe a very lagged out blur of movement, but then it's all frozen.
By the way, debugging output can be enabled by adding this after the import of Gst, GObject:
from gi.repository import GObject, Gst
Gst.debug_set_active(True)
Gst.debug_set_default_threshold(4)
GObject.threads_init()Also hopefully this gist is no longer needed soon. There's been work on a "pipewiregrab" patch for ffmpeg https://ffmpeg.org/pipermail/ffmpeg-devel/2024-August/thread.html#331913
Took a look at why this stopped working for me again... really dumb but an extra space between the mpv args seems to create havoc.
For debugging I send the stream to a file
pipeline = Gst.parse_launch('pipewiresrc fd=%d path=%u ! videorate ! video/x-raw,framerate=60/1 ! videoconvert ! filesink location=/home/andy/xdp-video.raw' %(fd, node_id))
and now I can run it with mpv with
mpv --demuxer=rawvideo --demuxer-rawvideo-w=2560 --demuxer-rawvideo-h=1440 --demuxer-rawvideo-format=bgra --demuxer-rawvideo-fps=60 --demuxer-rawvideo-mp-format=bgra --profile=low-latency --untimed --no-demuxer-thread ~/xdp-video.raw
or ffplay per stellarpower's comment above.
But when streaming with the fifo, ffplay seems pretty bad like 1 frame every 5 seconds. Streaming with mpv, better but still very laggy. Maybe related to my gpu setup will have to poke at it.
This is not working unfortunately, it says interface
org.freedesktop.portal.ScreenCaston object/org/freedesktop/portal/desktopdoesn't exist (actual error not in English sorry). In fact I've been trying to understand this code and I'm completely lost. I've been looking here and here :|