diff --git a/docs/src/libs/video_support/gstreamer.rst b/docs/src/libs/video_support/gstreamer.rst index f0a465eab3..7eb9a839a9 100644 --- a/docs/src/libs/video_support/gstreamer.rst +++ b/docs/src/libs/video_support/gstreamer.rst @@ -27,6 +27,7 @@ LVGL's GStreamer implementation provides comprehensive media playback capabiliti * Video4Linux2 (V4L2) camera devices on Linux * Audio capture from ALSA and PulseAudio devices * Test sources for audio and video development +* WebRTC streaming for real-time communication applications **URI Scheme Support:** @@ -42,6 +43,10 @@ Using the URI factory (:c:macro:`LV_GSTREAMER_FACTORY_URI_DECODE`), you can spec GStreamer's ``uridecodebin`` automatically selects the appropriate source element and decoder based on the URI scheme and media format. +.. note:: + + WebRTC streaming is supported via a dedicated WebRTC source factory (for example, ``webrtcsrc``) configured with a signalling URI (for example, ``signaller::uri``), and is not provided through :c:macro:`LV_GSTREAMER_FACTORY_URI_DECODE` / ``uridecodebin``. + **Playback Control:** * Play, pause, and stop operations @@ -255,6 +260,15 @@ The GStreamer widget supports various media sources through different factories: "/path/to/video.mp4"); +**WebRTC Factory:** + +.. code-block:: c + + /* WebRTC stream */ + lv_gstreamer_set_src(streamer, LV_GSTREAMER_FACTORY_WEBRTCSRC, + LV_GSTREAMER_PROPERTY_WEBRTCSRC, + "ws://signalserver:port/"); + Playback Control ---------------- @@ -326,6 +340,20 @@ Once media is loaded (LV_EVENT_READY), you can access: - Current volume level via ``lv_gstreamer_get_volume()`` - Current playback state via ``lv_gstreamer_get_state()`` +WebRTC Notes +************ + +WebRTC is using the Rust plugin "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" +So to be able to stream to an LVGL player application you will need to run a signaller server and then a simpler pipeline like so: + +.. code-block:: bash + + ./gst-webrtc-signalling-server & + gst-launch-1.0 videotestsrc pattern=ball ! webrtcsink + +Then, in your LVGL application, configure WebRTC by passing the signalling server URI (for example, ``ws://localhost:8443``) to ``lv_gstreamer_set_src(..., LV_GSTREAMER_PROPERTY_WEBRTCSRC, ...)``. + + .. _gstreamer_example: Example diff --git a/examples/libs/gstreamer/lv_example_gstreamer_1.c b/examples/libs/gstreamer/lv_example_gstreamer_1.c index 1304bc3b98..3fc9504ee5 100644 --- a/examples/libs/gstreamer/lv_example_gstreamer_1.c +++ b/examples/libs/gstreamer/lv_example_gstreamer_1.c @@ -40,7 +40,11 @@ void lv_example_gstreamer_1(void) * specify various URI schemes as media sources including local files (file://), * web streams (http://, https://), RTSP streams (rtsp://), UDP streams (udp://), * and many others. GStreamer's uridecodebin automatically selects the appropriate - * source element and decoder based on the URI scheme and media format. */ + * source element and decoder based on the URI scheme and media format. + * WebRTC streams, however, are provided via a dedicated WebRTC source (webrtcsrc) + * configured with a signalling server URI (e.g. ws://[ipsignallerserver]:[port]) + * through its signaller-related properties, rather than via LV_GSTREAMER_FACTORY_URI_DECODE. + */ lv_gstreamer_set_src(event_data.streamer, LV_GSTREAMER_FACTORY_URI_DECODE, LV_GSTREAMER_PROPERTY_URI_DECODE, "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm"); diff --git a/src/libs/gstreamer/lv_gstreamer.c b/src/libs/gstreamer/lv_gstreamer.c index 5b56a00ab3..270ed628de 100644 --- a/src/libs/gstreamer/lv_gstreamer.c +++ b/src/libs/gstreamer/lv_gstreamer.c @@ -47,6 +47,8 @@ static void gstreamer_update_frame(lv_gstreamer_t * streamer); static lv_result_t gstreamer_make_and_add_to_pipeline(lv_gstreamer_t * streamer, const lv_gstreamer_pipeline_element_t * elements, size_t element_count); static lv_result_t gstreamer_send_state_changed(lv_gstreamer_t * streamer, lv_gstreamer_stream_state_t state); +static bool gstreamer_element_has_property(GstElement * element, const char * property_name); +static bool gstreamer_set_child_proxy_string(GstElement * element, const char * property_name, const char * value); /********************** * STATIC VARIABLES @@ -135,31 +137,53 @@ lv_result_t lv_gstreamer_set_src(lv_obj_t * obj, const char * factory_name, cons } if(property != NULL && source != NULL) { - g_object_set(G_OBJECT(head), property, source, NULL); + /* LV_GSTREAMER_PROPERTY_WEBRTCSRC is a child-proxy property path */ + if(lv_streq(property, LV_GSTREAMER_PROPERTY_WEBRTCSRC)) { + if(!gstreamer_set_child_proxy_string(head, property, source)) { + gst_object_unref(pipeline); + LV_LOG_ERROR("Failed to set '%s' via child proxy", property); + return LV_RESULT_INVALID; + } + } + else { + g_object_set(G_OBJECT(head), property, source, NULL); + } } /* The uri decode source element will automatically handle parsing and decoding for us * for other source types, we need to add a parser and a decoder ourselves element*/ if(!lv_streq(LV_GSTREAMER_FACTORY_URI_DECODE, factory_name)) { - GstElement * decodebin = gst_element_factory_make("decodebin", "lv_gstreamer_decodebin"); - if(!decodebin) { - gst_object_unref(pipeline); - LV_LOG_ERROR("Failed to create decodebin element"); - return LV_RESULT_INVALID; - } - if(!gst_bin_add(GST_BIN(pipeline), decodebin)) { - gst_object_unref(decodebin); - gst_object_unref(pipeline); - LV_LOG_ERROR("Failed to add decodebin element to pipeline"); - return LV_RESULT_INVALID; + /* webrtcsrc plugins require its own handling to be able to connect on the first available video stream */ + if(lv_streq(LV_GSTREAMER_FACTORY_WEBRTCSRC, factory_name)) { + LV_LOG_INFO("Setting up webrtc pipeline"); + if(gstreamer_element_has_property(head, "connect-to-first-producer")) { + g_object_set(G_OBJECT(head), "connect-to-first-producer", TRUE, NULL); + } + else { + LV_LOG_WARN("webrtcsrc property 'connect-to-first-producer' is not available in this plugin build"); + } } + else { + GstElement * decodebin = gst_element_factory_make("decodebin", "lv_gstreamer_decodebin"); + if(!decodebin) { + gst_object_unref(pipeline); + LV_LOG_ERROR("Failed to create decodebin element"); + return LV_RESULT_INVALID; + } + if(!gst_bin_add(GST_BIN(pipeline), decodebin)) { + gst_object_unref(decodebin); + gst_object_unref(pipeline); + LV_LOG_ERROR("Failed to add decodebin element to pipeline"); + return LV_RESULT_INVALID; + } - if(!gst_element_link(head, decodebin)) { - gst_object_unref(pipeline); - LV_LOG_ERROR("Failed to link source with parsebin elements"); - return LV_RESULT_INVALID; + if(!gst_element_link(head, decodebin)) { + gst_object_unref(pipeline); + LV_LOG_ERROR("Failed to link source with parsebin elements"); + return LV_RESULT_INVALID; + } + head = decodebin; } - head = decodebin; } /* At this point we don't yet know the input format @@ -412,7 +436,18 @@ static lv_result_t gstreamer_poll_bus(lv_gstreamer_t * streamer) GError * err; gchar * debug; gst_message_parse_error(msg, &err, &debug); - LV_LOG_ERROR("GStreamer error: %s", err->message); + GstObject * src = GST_MESSAGE_SRC(msg); + const gchar * name = NULL; + if(src != NULL) { + name = GST_OBJECT_NAME(src); + } + if(!name) { + name = "unknown"; + } + LV_LOG_ERROR("GStreamer error from %s: %s", name, err->message); + if(debug && debug[0] != '\0') { + LV_LOG_ERROR("GStreamer error details: %s", debug); + } g_error_free(err); g_free(debug); break; @@ -563,11 +598,50 @@ static lv_result_t gstreamer_make_and_add_to_pipeline(lv_gstreamer_t * streamer, return LV_RESULT_OK; } +static bool gstreamer_element_has_property(GstElement * element, const char * property_name) +{ + LV_ASSERT_NULL(element); + LV_ASSERT_NULL(property_name); + + GObjectClass * klass = G_OBJECT_GET_CLASS(element); + return g_object_class_find_property(klass, property_name) != NULL; +} + +static bool gstreamer_set_child_proxy_string(GstElement * element, const char * property_name, const char * value) +{ + LV_ASSERT_NULL(element); + LV_ASSERT_NULL(property_name); + LV_ASSERT_NULL(value); + + if(!GST_IS_CHILD_PROXY(element)) { + LV_LOG_WARN("Element does not support child proxy for property '%s'", property_name); + return false; + } + + GObject * target = NULL; + GParamSpec * pspec = NULL; + if(!gst_child_proxy_lookup(GST_CHILD_PROXY(element), property_name, &target, &pspec)) { + LV_LOG_WARN("Property '%s' not found in child proxy", property_name); + return false; + } + + if(target) { + g_object_unref(target); + } + + GValue gvalue = G_VALUE_INIT; + g_value_init(&gvalue, G_TYPE_STRING); + g_value_set_string(&gvalue, value); + gst_child_proxy_set_property(GST_CHILD_PROXY(element), property_name, &gvalue); + g_value_unset(&gvalue); + return true; +} + static void on_decode_pad_added(GstElement * element, GstPad * pad, gpointer user_data) { LV_UNUSED(element); lv_gstreamer_t * streamer = (lv_gstreamer_t *)user_data; - GstCaps * caps = gst_pad_get_current_caps(pad); + GstCaps * caps = gst_pad_query_caps(pad, NULL); GstStructure * structure = gst_caps_get_structure(caps, 0); const gchar * name = gst_structure_get_name(structure); diff --git a/src/libs/gstreamer/lv_gstreamer.h b/src/libs/gstreamer/lv_gstreamer.h index e7503ce70e..3d31bfa274 100644 --- a/src/libs/gstreamer/lv_gstreamer.h +++ b/src/libs/gstreamer/lv_gstreamer.h @@ -62,6 +62,9 @@ extern "C" { #define LV_GSTREAMER_FACTORY_APP "appsrc" #define LV_GSTREAMER_PROPERTY_APP NULL +#define LV_GSTREAMER_FACTORY_WEBRTCSRC "webrtcsrc" +#define LV_GSTREAMER_PROPERTY_WEBRTCSRC "signaller::uri" + /********************** * TYPEDEFS **********************/