diff --git a/docs/src/details/libs/gstreamer.rst b/docs/src/details/libs/gstreamer.rst new file mode 100644 index 0000000000..fed358381f --- /dev/null +++ b/docs/src/details/libs/gstreamer.rst @@ -0,0 +1,311 @@ +.. _gstreamer: + +========= +GStreamer +========= + +**GStreamer** is a pipeline-based multimedia framework that links together a wide variety of media processing systems to complete complex workflows. + +The framework uses a pipeline model where media data flows from source elements through various processing elements (decoders, converters, filters) to sink elements (display, speakers, files). + +The GStreamer extension in LVGL provides video and audio playback capabilities with support for various media formats, streaming protocols, and media sources. It leverages GStreamer's powerful pipeline architecture to handle media decoding, processing, and rendering. + +For detailed information about GStreamer, see: https://gstreamer.freedesktop.org/ + +Features +******** + +LVGL's GStreamer implementation provides comprehensive media playback capabilities: + +**Media Source Support:** + +* Local files via file:// URIs +* Network streaming with HTTP/HTTPS support +* RTSP streaming for live video feeds +* UDP streaming for low-latency applications +* Multicast streaming for efficient network distribution +* Video4Linux2 (V4L2) camera devices on Linux +* Audio capture from ALSA and PulseAudio devices +* Test sources for audio and video development + +**URI Scheme Support:** + +Using the URI factory (``LV_GSTREAMER_FACTORY_URI_DECODE``), you can specify various URI schemes as media sources: + +* **Local files**: ``file://path/to/video.mp4`` +* **Web streams**: ``http://example.com/stream.webm``, ``https://secure.example.com/video.mp4`` +* **RTSP streams**: ``rtsp://camera.local/stream`` +* **UDP streams**: ``udp://239.255.12.42:1234`` +* **Multicast**: ``multicast://239.255.12.42:1234`` +* **V4L2 cameras**: ``v4l2:///dev/video0`` +* **Audio devices**: ``alsa://hw:0,0``, ``pulse://default`` + +GStreamer's ``uridecodebin`` automatically selects the appropriate source element and decoder based on the URI scheme and media format. + +**Playback Control:** + +* Play, pause, and stop operations +* Precise seeking to specific positions +* Volume control with 0-100% range +* Playback rate control (slow motion and fast forward) +* Real-time position and duration queries +* State management (NULL, READY, PAUSED, PLAYING) + +**Media Format Support:** + +GStreamer supports a wide variety of media formats through its plugin system: + +* **Video**: H.264, H.265/HEVC, VP8, VP9, AV1, MPEG-4, WebM, and many more +* **Audio**: AAC, MP3, Ogg Vorbis, FLAC, Opus, PCM, and others +* **Containers**: MP4, WebM, AVI, MKV, MOV, FLV, and more + + +Requirements +************ + +The GStreamer extension requires **GStreamer 1.0** or later with the following components: + +:gstreamer-1.0: Core GStreamer framework +:gstreamer-video-1.0: Video handling and processing utilities +:gstreamer-app-1.0: Application integration utilities + +Dependencies +------------ + +Follow the official GStreamer documentation to install its development libraries on your system: https://gstreamer.freedesktop.org/documentation/installing/index.html?gi-language=c + +Setup +***** + +1. **Install Dependencies** + + Install the GStreamer development libraries for your platform as shown in the Dependencies section above. + +2. **Enable GStreamer Support** + + Set :c:macro:`LV_USE_GSTREAMER` to ``1`` in ``lv_conf.h``. + +3. **CMake Integration** + + **Option 1: Direct linking with LVGL (Recommended)** + +.. code-block:: cmake + + find_package(PkgConfig REQUIRED) + + # Find GStreamer packages + pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) + pkg_check_modules(GSTREAMER_VIDEO REQUIRED gstreamer-video-1.0) + pkg_check_modules(GSTREAMER_APP REQUIRED gstreamer-app-1.0) + + # Link with LVGL + target_include_directories(lvgl PUBLIC + ${GSTREAMER_INCLUDE_DIRS} + ${GSTREAMER_VIDEO_INCLUDE_DIRS} + ${GSTREAMER_APP_INCLUDE_DIRS}) + target_link_libraries(lvgl PUBLIC + ${GSTREAMER_LIBRARIES} + ${GSTREAMER_VIDEO_LIBRARIES} + ${GSTREAMER_APP_LIBRARIES}) + +4. **Manual Compilation with pkg-config** + + You can also compile manually using pkg-config to query the necessary flags: + +.. code-block:: bash + + # Get compilation flags + gcc $(pkg-config --cflags --libs gstreamer-1.0 gstreamer-video-1.0 gstreamer-app-1.0) \ + -o your_app your_app.c lvgl.a + +5. **Basic Setup Example** + +.. code-block:: c + + int main(void) + { + /* Initialize LVGL */ + lv_init(); + + /* Setup display driver */ + lv_display_t *display = lv_display_create(800, 480); + /* ... configure display driver ... */ + + /* Create and run your GStreamer application */ + lv_example_gstreamer_1(); + + while (1) { + lv_timer_handler(); + } + + return 0; + } + +Usage +***** + +Basic GStreamer Player Creation +------------------------------- + +Here's how to create a basic GStreamer player and load media: + +.. code-block:: c + + /* Create a GStreamer object */ + lv_obj_t * streamer = lv_gstreamer_create(lv_screen_active()); + + /* Set the media source using URI factory */ + lv_result_t result = lv_gstreamer_set_src(streamer, + LV_GSTREAMER_FACTORY_URI_DECODE, + LV_GSTREAMER_PROPERTY_URI_DECODE, + "https://example.com/video.webm"); + + if (result != LV_RESULT_OK) { + LV_LOG_ERROR("Failed to set GStreamer source"); + return; + } + + /* Start playback */ + lv_gstreamer_play(streamer); + +Media Source Configuration +-------------------------- + +The GStreamer widget supports various media sources through different factories: + +**URI Factory (Recommended):** + +.. code-block:: c + + /* Load from web URL */ + lv_gstreamer_set_src(streamer, LV_GSTREAMER_FACTORY_URI_DECODE, + LV_GSTREAMER_PROPERTY_URI_DECODE, + "https://example.com/stream.webm"); + + /* Load from local file */ + lv_gstreamer_set_src(streamer, LV_GSTREAMER_FACTORY_URI_DECODE, + LV_GSTREAMER_PROPERTY_URI_DECODE, + "file:///path/to/video.mp4"); + + /* RTSP stream */ + lv_gstreamer_set_src(streamer, LV_GSTREAMER_FACTORY_URI_DECODE, + LV_GSTREAMER_PROPERTY_URI_DECODE, + "rtsp://camera.local/stream"); + +**File Factory:** + +.. code-block:: c + + /* Direct file access */ + lv_gstreamer_set_src(streamer, LV_GSTREAMER_FACTORY_FILE, + LV_GSTREAMER_PROPERTY_FILE, + "/path/to/video.mp4"); + + +Playback Control +---------------- + +Control media playback with these functions: + +.. code-block:: c + + /* Basic playback control */ + lv_gstreamer_play(streamer); + lv_gstreamer_pause(streamer); + lv_gstreamer_stop(streamer); + + /* Get current state */ + lv_gstreamer_state_t state = lv_gstreamer_get_state(streamer); + + /* Seek to position (in milliseconds) */ + lv_gstreamer_set_position(streamer, 30000); /* Seek to 30 seconds */ + + /* Get current position and duration */ + uint32_t position = lv_gstreamer_get_position(streamer); + uint32_t duration = lv_gstreamer_get_duration(streamer); + + /* Set playback rate - values relative to 256 (1x speed) */ + lv_gstreamer_set_rate(streamer, 128); /* 0.5x speed */ + lv_gstreamer_set_rate(streamer, 256); /* 1.0x speed (normal) */ + lv_gstreamer_set_rate(streamer, 512); /* 2.0x speed */ + +Volume Control +-------------- + +Manage audio volume with built-in controls: + +.. code-block:: c + + /* Set volume (0-100%) */ + lv_gstreamer_set_volume(streamer, 75); + + /* Get current volume */ + uint8_t volume = lv_gstreamer_get_volume(streamer); + +Event Handling +-------------- + +Handle GStreamer events using LVGL's event system: + +.. code-block:: c + + static void gstreamer_event_cb(lv_event_t * e) + { + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * streamer = lv_event_get_target_obj(e); + + if(code == LV_EVENT_READY) { + LV_LOG_USER("Stream ready - Duration: %" LV_PRIu32 " ms", + lv_gstreamer_get_duration(streamer)); + LV_LOG_USER("Resolution: %" LV_PRId32 "x%" LV_PRId32, + lv_image_get_src_width(streamer), + lv_image_get_src_height(streamer)); + } + } + + /* Add event callback */ + lv_obj_add_event_cb(streamer, gstreamer_event_cb, LV_EVENT_ALL, NULL); + +Widget Architecture +******************* + +The GStreamer widget extends the ``lv_image`` widget, which means: + +- All standard ``lv_obj`` functions work with GStreamer widgets (positioning, sizing, styling, events) +- All ``lv_image`` functions are available for image-related operations +- Video frames are rendered as image content that updates automatically during playback + +State Management +---------------- + +The GStreamer widget maintains these states: + +- ``LV_GSTREAMER_STATE_NULL``: Initial state, no media loaded +- ``LV_GSTREAMER_STATE_READY``: Media loaded and ready to play +- ``LV_GSTREAMER_STATE_PAUSED``: Playback paused +- ``LV_GSTREAMER_STATE_PLAYING``: Active playback + +Media Information Access +------------------------ + +Once media is loaded (LV_EVENT_READY), you can access: + +- Video resolution via ``lv_image_get_src_width()`` and ``lv_image_get_src_height()`` +- Media duration via ``lv_gstreamer_get_duration()`` +- Current playback position via ``lv_gstreamer_get_position()`` +- Current volume level via ``lv_gstreamer_get_volume()`` +- Current playback state via ``lv_gstreamer_get_state()`` + +.. _gstreamer_example: + +Examples +******** + +.. include:: ../../examples/libs/gstreamer/index.rst + +.. _gstreamer_api: + +API +*** + +.. API startswith: lv_gstreamer_ diff --git a/docs/src/details/libs/index.rst b/docs/src/details/libs/index.rst index c2ef4ddf71..4320ba4826 100644 --- a/docs/src/details/libs/index.rst +++ b/docs/src/details/libs/index.rst @@ -17,6 +17,7 @@ freetype fs gif + gstreamer gltf lfs libjpeg_turbo diff --git a/examples/libs/gstreamer/index.rst b/examples/libs/gstreamer/index.rst new file mode 100644 index 0000000000..d8f41c5a42 --- /dev/null +++ b/examples/libs/gstreamer/index.rst @@ -0,0 +1,5 @@ +Loads a video from the internet using the gstreamer widget +---------------------------------------------------------- + +.. lv_example:: libs/gstreamer/lv_example_gstreamer_1 + :language: c diff --git a/examples/libs/gstreamer/lv_example_gstreamer.h b/examples/libs/gstreamer/lv_example_gstreamer.h new file mode 100644 index 0000000000..4008e12783 --- /dev/null +++ b/examples/libs/gstreamer/lv_example_gstreamer.h @@ -0,0 +1,39 @@ + +/** + * @file lv_example_gstreamer.h + * + */ + +#ifndef LV_EXAMPLE_GSTREAMER_H +#define LV_EXAMPLE_GSTREAMER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_gstreamer_1(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXAMPLE_GSTREAMER_H*/ diff --git a/examples/libs/gstreamer/lv_example_gstreamer_1.c b/examples/libs/gstreamer/lv_example_gstreamer_1.c new file mode 100644 index 0000000000..cf7c5df68c --- /dev/null +++ b/examples/libs/gstreamer/lv_example_gstreamer_1.c @@ -0,0 +1,217 @@ +#include "../../lv_examples.h" + +#if LV_BUILD_EXAMPLES + +#if LV_USE_GSTREAMER +typedef struct { + lv_obj_t * streamer; + lv_obj_t * pp_button; + lv_obj_t * button_label; + lv_obj_t * position_label; + lv_obj_t * duration_label; + lv_subject_t position_subject; +} event_data_t; + +static void volume_setter_create(event_data_t * event_data); +static void control_bar_create(event_data_t * event_data); +static void update_duration_label(lv_obj_t * label, uint32_t duration); +static void volume_observer_cb(lv_observer_t * observer, lv_subject_t * subject); +static void update_position_slider(lv_timer_t * timer); +static void play_pause_pressed(lv_event_t * e); +static void streamer_ready(lv_event_t * e); + +/** + * Loads a video from the internet using the gstreamer widget + */ +void lv_example_gstreamer_1(void) +{ + static event_data_t event_data; + + event_data.streamer = lv_gstreamer_create(lv_screen_active()); + + /* the gstreamer widget inherits the `lv_image` widget, + * meaning you can also provide it lv_image functions, like + lv_image_set_scale(event_data.streamer, 100); + lv_image_set_rotation(event_data.streamer, 100); + */ + + /* Set the current src of the streamer. + * Using the `URI` "factory", we can + * 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. */ + 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"); + + lv_obj_center(event_data.streamer); + + /* The LV_EVENT_READY will fire when the stream is ready at that point you can query the stream + * information like its resolution and duration. See `streamer_ready` */ + lv_obj_add_event_cb(event_data.streamer, streamer_ready, LV_EVENT_READY, &event_data); + + /* Play the stream immediately */ + lv_gstreamer_play(event_data.streamer); + + /* Create a slider to modify the stream volume and a label to visualize the current value */ + volume_setter_create(&event_data); + + /* Create a slider to see the position in the stream with 2 text label on each side + * One for the current position in the stream and the other for the total duration of the stream + * Also add a pause/play button*/ + control_bar_create(&event_data); + + /* Create a timer that will update the slider position based on the stream position + * Make it 3 times faster than the refresh rate for a smoother effect */ + lv_timer_create(update_position_slider, LV_DEF_REFR_PERIOD, &event_data); +} + +static void volume_setter_create(event_data_t * event_data) +{ + lv_obj_t * cont = lv_obj_create(lv_screen_active()); + lv_obj_remove_style_all(cont); + lv_obj_set_style_pad_all(cont, 8, 0); + lv_obj_set_style_pad_gap(cont, 8, 0); + lv_obj_set_style_radius(cont, 8, 0); + lv_obj_set_style_bg_color(cont, lv_color_white(), 0); + lv_obj_set_style_bg_opa(cont, LV_OPA_70, 0); + lv_obj_set_size(cont, 40, lv_pct(60)); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_align(cont, LV_ALIGN_RIGHT_MID, -8, -40); + + lv_obj_t * volume_slider = lv_slider_create(cont); + lv_obj_set_size(volume_slider, 13, LV_PCT(50)); + lv_obj_set_flex_grow(volume_slider, 1); + + lv_obj_t * volume_label = lv_label_create(cont); + lv_obj_set_style_text_align(volume_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_width(volume_label, 50); + + /* We use `lv_subject` to simplify binding the data between multiple objects. + * Here the data is shared between the slider, the label and the gstreamer widgets */ + static lv_subject_t volume_subject; + lv_subject_init_int(&volume_subject, 50); + lv_subject_add_observer_obj(&volume_subject, volume_observer_cb, event_data->streamer, NULL); + lv_slider_bind_value(volume_slider, &volume_subject); + lv_label_bind_text(volume_label, &volume_subject, LV_SYMBOL_VOLUME_MID "\n%3" LV_PRId32 "%%"); + +} + + +static void control_bar_create(event_data_t * event_data) +{ + lv_subject_init_int(&event_data->position_subject, 0); + + lv_obj_t * cont = lv_obj_create(lv_screen_active()); + lv_obj_remove_style_all(cont); + lv_obj_set_style_pad_all(cont, 8, 0); + lv_obj_set_style_margin_hor(cont, 8, 0); + lv_obj_set_style_pad_gap(cont, 8, 0); + lv_obj_set_style_radius(cont, 8, 0); + lv_obj_set_style_bg_color(cont, lv_color_white(), 0); + lv_obj_set_style_bg_opa(cont, LV_OPA_70, 0); + + lv_obj_set_size(cont, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_align(cont, LV_ALIGN_BOTTOM_MID, 0, -10); + + event_data->position_label = lv_label_create(cont); + lv_obj_set_width(event_data->position_label, 80); + lv_label_set_text_static(event_data->position_label, "0:00:000"); + + /* Create a button a button to play/pause the stream */ + event_data->pp_button = lv_button_create(cont); + lv_obj_center(event_data->pp_button); + lv_obj_add_event_cb(event_data->pp_button, play_pause_pressed, LV_EVENT_CLICKED, event_data); + event_data->button_label = lv_label_create(event_data->pp_button); + lv_label_set_text_static(event_data->button_label, LV_SYMBOL_PAUSE); + + lv_obj_t * position_slider = lv_bar_create(cont); + lv_bar_set_range(position_slider, 0, 1000); + lv_obj_set_flex_grow(position_slider, 1); + lv_slider_bind_value(position_slider, &event_data->position_subject); + + event_data->duration_label = lv_label_create(cont); + lv_obj_set_width(event_data->duration_label, 80); + lv_label_set_text_static(event_data->duration_label, "0:00:000"); + lv_obj_set_style_text_align(event_data->duration_label, LV_TEXT_ALIGN_RIGHT, 0); + +} + +static void update_duration_label(lv_obj_t * label, uint32_t duration) +{ + const uint32_t minutes = duration / 60000; + const uint32_t seconds = (duration / 1000) % 60; + const uint32_t milliseconds = duration % 1000; + + lv_label_set_text_fmt(label, "%" LV_PRIu32 ":%02" LV_PRIu32 ":%03" LV_PRIu32, minutes, seconds, milliseconds); +} + +static void volume_observer_cb(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_t * streamer = lv_observer_get_target_obj(observer); + int32_t volume = lv_subject_get_int(subject); + LV_LOG_USER("Setting volume %" PRId32, volume); + lv_gstreamer_set_volume(streamer, (uint8_t)volume); +} + +static void update_position_slider(lv_timer_t * timer) +{ + event_data_t * event_data = (event_data_t *)lv_timer_get_user_data(timer); + + uint32_t duration = lv_gstreamer_get_duration(event_data->streamer); + uint32_t position = lv_gstreamer_get_position(event_data->streamer); + int32_t position_perc = lv_map(position, 0, duration, 0, 1000); + lv_subject_set_int(&event_data->position_subject, position_perc); + update_duration_label(event_data->position_label, position); +} + + +static void play_pause_pressed(lv_event_t * e) +{ + event_data_t * event_data = (event_data_t *)lv_event_get_user_data(e); + + if(lv_streq(lv_label_get_text(event_data->button_label), LV_SYMBOL_PLAY)) { + lv_label_set_text(event_data->button_label, LV_SYMBOL_PAUSE); + lv_gstreamer_play(event_data->streamer); + } + else { + lv_label_set_text(event_data->button_label, LV_SYMBOL_PLAY); + lv_gstreamer_pause(event_data->streamer); + } +} +static void streamer_ready(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + event_data_t * event_data = (event_data_t *)lv_event_get_user_data(e); + lv_obj_t * btn = event_data->pp_button; + lv_obj_t * streamer = event_data->streamer; + + if(code == LV_EVENT_READY) { + lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, 0); + uint32_t duration = lv_gstreamer_get_duration(streamer); + LV_LOG_USER("Video is starting"); + LV_LOG_USER("\tStream resolution %" LV_PRId32 "x%" LV_PRId32, lv_image_get_src_width(streamer), + lv_image_get_src_height(streamer)); + LV_LOG_USER("\tStream duration %" LV_PRIu32, duration); + update_duration_label(event_data->duration_label, duration); + } +} + +#else + +void lv_example_gstreamer_1(void) +{ + /*TODO + *fallback for online examples*/ + + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, "GStreamer web support is coming soon"); + lv_obj_center(label); +} + +#endif + +#endif diff --git a/examples/libs/lv_example_libs.h b/examples/libs/lv_example_libs.h index c53dd14e79..b97154ce96 100644 --- a/examples/libs/lv_example_libs.h +++ b/examples/libs/lv_example_libs.h @@ -19,6 +19,7 @@ extern "C" { #include "freetype/lv_example_freetype.h" #include "gif/lv_example_gif.h" #include "gltf/lv_example_gltf.h" +#include "gstreamer/lv_example_gstreamer.h" #include "lodepng/lv_example_lodepng.h" #include "libpng/lv_example_libpng.h" #include "qrcode/lv_example_qrcode.h" diff --git a/lv_conf_template.h b/lv_conf_template.h index fe371d8603..510a55579d 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -970,6 +970,8 @@ #define LV_GIF_CACHE_DECODE_DATA 0 #endif +/** GStreamer library */ +#define LV_USE_GSTREAMER 0 /** Decode bin images to RAM */ #define LV_BIN_DECODER_RAM_LOAD 0 diff --git a/lvgl.h b/lvgl.h index 43592f346f..ccf46e945b 100644 --- a/lvgl.h +++ b/lvgl.h @@ -109,6 +109,7 @@ extern "C" { #include "src/libs/gltf/gltf_data/lv_gltf_model.h" #include "src/libs/gltf/gltf_view/lv_gltf.h" #include "src/libs/gif/lv_gif.h" +#include "src/libs/gstreamer/lv_gstreamer.h" #include "src/libs/qrcode/lv_qrcode.h" #include "src/libs/tjpgd/lv_tjpgd.h" #include "src/libs/libjpeg_turbo/lv_libjpeg_turbo.h" diff --git a/src/libs/gstreamer/lv_gstreamer.c b/src/libs/gstreamer/lv_gstreamer.c new file mode 100644 index 0000000000..616b5ad1d8 --- /dev/null +++ b/src/libs/gstreamer/lv_gstreamer.c @@ -0,0 +1,628 @@ +/** + * @file lv_gstreamer.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_gstreamer_internal.h" + +#if LV_USE_GSTREAMER + +#include +#include +#include "../../core/lv_obj_class_private.h" + +/********************* + * DEFINES + *********************/ + +#define MY_CLASS (&lv_gstreamer_class) + + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_gstreamer_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_gstreamer_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static lv_result_t gstreamer_create_pipeline(lv_gstreamer_t * streamer, GstElement * pipeline, GstElement * head); +static void on_decode_pad_added(GstElement * element, GstPad * pad, gpointer user_data); +static GstFlowReturn on_new_sample(GstElement * sink, gpointer user_data); +static void gstreamer_timer_cb(lv_timer_t * timer); +static void gstreamer_poll_bus(lv_gstreamer_t * streamer); +static void gstreamer_update_frame(lv_gstreamer_t * streamer); + +/********************** + * STATIC VARIABLES + **********************/ + +const lv_obj_class_t lv_gstreamer_class = { + .constructor_cb = lv_gstreamer_constructor, + .destructor_cb = lv_gstreamer_destructor, + .width_def = LV_SIZE_CONTENT, + .height_def = LV_SIZE_CONTENT, + .instance_size = sizeof(lv_gstreamer_t), + .base_class = &lv_image_class, + .name = "lv_gstreamer", +}; + +/********************** + * MACROS + **********************/ + +#if LV_COLOR_DEPTH == 16 + #define GST_FORMAT "RGB16" + #define IMAGE_FORMAT LV_COLOR_FORMAT_RGB565 +#elif LV_COLOR_DEPTH == 24 + #define GST_FORMAT "BGR" + #define IMAGE_FORMAT LV_COLOR_FORMAT_RGB888 +#elif LV_COLOR_DEPTH == 32 + #define GST_FORMAT "BGRA" + #define IMAGE_FORMAT LV_COLOR_FORMAT_ARGB8888 +#else + #error Unsupported LV_COLOR_DEPTH +#endif + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_gstreamer_create(lv_obj_t * parent) +{ + if(!gst_is_initialized()) { + gst_init(0, NULL); + } + + LV_TRACE_OBJ_CREATE("begin"); + + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + + LV_TRACE_OBJ_CREATE("end"); + return obj; +} + +lv_result_t lv_gstreamer_set_src(lv_obj_t * obj, const char * factory_name, const char * property, const char * source) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(factory_name); + + if(!obj || !factory_name) { + LV_LOG_WARN("Refusing to set source with invalid params. Obj: %p Factory Name: %s", obj, factory_name); + return LV_RESULT_INVALID; + } + + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + if(streamer->pipeline) { + LV_LOG_WARN("LVGL doesn't allow modifying the GStreamer source. Create a new widget with a new src instead"); + return LV_RESULT_INVALID; + } + GstElement * pipeline = gst_pipeline_new("lv_gstreamer_pipeline"); + if(!pipeline) { + LV_LOG_ERROR("Failed to create gstreamer pipeline"); + return LV_RESULT_INVALID; + } + + GstElement * head = gst_element_factory_make(factory_name, "lv_gstreamer_source"); + if(!head) { + gst_object_unref(pipeline); + LV_LOG_ERROR("Failed to create source from factory '%s'", factory_name); + return LV_RESULT_INVALID; + } + + if(!gst_bin_add(GST_BIN(pipeline), head)) { + gst_object_unref(head); + gst_object_unref(pipeline); + LV_LOG_ERROR("Failed to add source element to pipeline"); + return LV_RESULT_INVALID; + } + + if(property != NULL && source != NULL) { + 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; + } + + 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; + } + + lv_result_t res = gstreamer_create_pipeline(streamer, pipeline, head); + if(res == LV_RESULT_INVALID) { + LV_LOG_ERROR("Pipeline creation failed"); + gst_object_unref(pipeline); + return res; + } + + streamer->pipeline = pipeline; + return LV_RESULT_OK; +} + +void lv_gstreamer_play(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + if(!obj) { + return; + } + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + if(!streamer->pipeline) { + LV_LOG_WARN("Cannot play: GStreamer pipeline not initialized"); + return; + } + GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_PLAYING); + if(ret == GST_STATE_CHANGE_FAILURE) { + LV_LOG_ERROR("Unable to play pipeline"); + } +} + +void lv_gstreamer_pause(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + if(!obj) { + return; + } + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + if(!streamer->pipeline) { + LV_LOG_WARN("Cannot pause: GStreamer pipeline not initialized"); + return; + } + GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_PAUSED); + + if(ret == GST_STATE_CHANGE_FAILURE) { + LV_LOG_ERROR("Unable to pause pipeline"); + } +} + +void lv_gstreamer_stop(lv_obj_t * obj) +{ + + LV_ASSERT_OBJ(obj, MY_CLASS); + if(!obj) { + return; + } + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + if(!streamer->pipeline) { + LV_LOG_WARN("Cannot stop: GStreamer pipeline not initialized"); + return; + } + GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_READY); + if(ret == GST_STATE_CHANGE_FAILURE) { + LV_LOG_ERROR("Unable to stop pipeline"); + } +} +void lv_gstreamer_set_position(lv_obj_t * obj, uint32_t position) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + if(!obj) { + return; + } + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + if(!streamer->pipeline) { + LV_LOG_WARN("Cannot set position: GStreamer pipeline not initialized"); + return; + } + gint64 seek_position = (gint64)position * GST_MSECOND; + + if(!gst_element_seek_simple(streamer->pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, + seek_position)) { + LV_LOG_WARN("Seek operation failed"); + } +} + +uint32_t lv_gstreamer_get_duration(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + if(!streamer->pipeline) { + return 0; + } + + gint64 duration; + if(gst_element_query_duration(streamer->pipeline, GST_FORMAT_TIME, &duration)) { + return (uint32_t)(duration / GST_MSECOND); + } + + return 0; +} + +uint32_t lv_gstreamer_get_position(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + if(!streamer->pipeline) { + return 0; + } + + gint64 position; + if(gst_element_query_position(streamer->pipeline, GST_FORMAT_TIME, &position)) { + return (uint32_t)(position / GST_MSECOND); + } + + return 0; +} + +lv_gstreamer_state_t lv_gstreamer_get_state(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + if(!streamer->pipeline) { + return LV_GSTREAMER_STATE_NULL; + } + + GstState state, pending; + GstStateChangeReturn ret = gst_element_get_state(streamer->pipeline, &state, &pending, 0); + + if(ret == GST_STATE_CHANGE_FAILURE) { + return LV_GSTREAMER_STATE_NULL; + } + + switch(state) { + case GST_STATE_NULL: + return LV_GSTREAMER_STATE_NULL; + case GST_STATE_READY: + return LV_GSTREAMER_STATE_READY; + case GST_STATE_PAUSED: + return LV_GSTREAMER_STATE_PAUSED; + case GST_STATE_PLAYING: + return LV_GSTREAMER_STATE_PLAYING; + default: + return LV_GSTREAMER_STATE_NULL; + } +} + +void lv_gstreamer_set_volume(lv_obj_t * obj, uint8_t volume) +{ + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + if(!streamer->pipeline) { + return; + } + + g_object_set(streamer->audio_volume, "volume", volume / 100.f, NULL); +} + +uint8_t lv_gstreamer_get_volume(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + if(!streamer->pipeline) { + return 0; + } + + gdouble volume; + g_object_get(streamer->audio_volume, "volume", &volume, NULL); + + return (uint8_t)(volume * 100.f); +} + +/** + * Set the speed rate of this gstreamer + * @param gstreamer pointer to a gstreamer object + * @param rate the rate factor. Example values: + * - 256: 1x + * - <256: slow down + * - >256: speed up + * - 128: 0.5x + * - 512: 2x + */ +void lv_gstreamer_set_rate(lv_obj_t * obj, uint32_t rate) +{ + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + gdouble gst_rate = (gdouble)rate / 256.0; + + gint64 current_pos; + + if(!gst_element_query_position(streamer->pipeline, GST_FORMAT_TIME, ¤t_pos)) { + LV_LOG_WARN("Failed to query current position which is required to set the stream rate"); + return; + } + + /* Perform the seek with new rate from the current position */ + if(!gst_element_seek(streamer->pipeline, gst_rate, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, + GST_SEEK_TYPE_SET, current_pos, + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { + LV_LOG_WARN("Failed to change stream rate"); + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_gstreamer_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + lv_memzero(&streamer->frame, sizeof(streamer->frame)); + + streamer->gstreamer_timer = lv_timer_create(gstreamer_timer_cb, LV_DEF_REFR_PERIOD / 5, streamer); + LV_ASSERT_NULL(streamer->gstreamer_timer); + + streamer->frame_queue = g_async_queue_new(); + LV_ASSERT_NULL(streamer->frame_queue); + streamer->last_sample = NULL; + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void gstreamer_poll_bus(lv_gstreamer_t * streamer) +{ + GstBus * bus = gst_element_get_bus(streamer->pipeline); + GstMessage * msg; + + while((msg = gst_bus_pop(bus)) != NULL) { + const GstMessageType message_type = GST_MESSAGE_TYPE(msg); + switch(message_type) { + case GST_MESSAGE_ERROR: { + GError * err; + gchar * debug; + gst_message_parse_error(msg, &err, &debug); + LV_LOG_ERROR("GStreamer error: %s", err->message); + g_error_free(err); + g_free(debug); + break; + } + case GST_MESSAGE_EOS: + LV_LOG_INFO("End of stream"); + break; + case GST_MESSAGE_STATE_CHANGED: { + GstState old_state, new_state; + gst_message_parse_state_changed(msg, &old_state, &new_state, NULL); + LV_LOG_TRACE("State changed: %s -> %s", gst_element_state_get_name(old_state), + gst_element_state_get_name(new_state)); + break; + } + default: + LV_LOG_TRACE("Received message %d", message_type); + break; + } + gst_message_unref(msg); + } + gst_object_unref(bus); +} + +static void gstreamer_update_frame(lv_gstreamer_t * streamer) +{ + GstSample * sample = g_async_queue_try_pop(streamer->frame_queue); + + if(!sample) { + return; + } + + const bool first_frame = !streamer->is_video_info_valid; + if(first_frame) { + GstCaps * caps = gst_sample_get_caps(sample); + if(!caps || !gst_video_info_from_caps(&streamer->video_info, caps)) { + LV_LOG_ERROR("Failed to get video info from caps"); + gst_sample_unref(sample); + return; + } + streamer->is_video_info_valid = true; + } + + + GstBuffer * buffer = gst_sample_get_buffer(sample); + GstMapInfo map; + if(buffer && gst_buffer_map(buffer, &map, GST_MAP_READ)) { + streamer->frame = (lv_image_dsc_t) { + .data = map.data, + .data_size = map.size, + .header = { + .magic = LV_IMAGE_HEADER_MAGIC, + .cf = IMAGE_FORMAT, + .h = GST_VIDEO_INFO_HEIGHT(&streamer->video_info), + .w = GST_VIDEO_INFO_WIDTH(&streamer->video_info), + .stride = GST_VIDEO_INFO_PLANE_STRIDE(&streamer->video_info, 0), + } + }; + lv_image_set_src((lv_obj_t *)streamer, &streamer->frame); + } + /* We send the event AFTER setting the image source so that users can query the + * resolution on this specific event callback */ + if(first_frame) { + lv_obj_send_event((lv_obj_t *)streamer, LV_EVENT_READY, streamer); + } + + if(streamer->last_sample) { + gst_sample_unref(streamer->last_sample); + } + streamer->last_sample = sample; +} +static void gstreamer_timer_cb(lv_timer_t * timer) +{ + lv_gstreamer_t * streamer = lv_timer_get_user_data(timer); + + if(!streamer->pipeline) { + return; + } + + gstreamer_poll_bus(streamer); + gstreamer_update_frame(streamer); +} + +static void lv_gstreamer_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj; + + if(streamer->pipeline) { + gst_element_set_state(streamer->pipeline, GST_STATE_NULL); + gst_object_unref(streamer->pipeline); + } + if(streamer->last_sample) { + gst_sample_unref(streamer->last_sample); + } + + g_async_queue_unref(streamer->frame_queue); +} + +static lv_result_t gstreamer_create_pipeline(lv_gstreamer_t * streamer, GstElement * pipeline, + GstElement * decode_element) +{ + + /* The caller has already added head and whatever comes before it to the pipeline. + * So inside this function, we only need to handle adding the elements that are created here */ + GstElement * video_app_sink; + GstElement * video_rate; + GstElement * video_queue; + GstElement * audio_resample; + GstElement * audio_sink; + struct { + const char * factory; + const char * name; + GstElement ** store; + } const elements[] = { + {"videoconvert", "lv_gstreamer_video_convert", &streamer->video_convert}, + {"audioconvert", "lv_gstreamer_audio_convert", &streamer->audio_convert}, + {"volume", "lv_gstreamer_audio_volume", &streamer->audio_volume}, + {"videorate", "lv_gstreamer_video_rate", &video_rate}, + {"queue", "lv_gstreamer_video_queue", &video_queue}, + {"appsink", "lv_gstreamer_video_sink", &video_app_sink}, + {"audioresample", "lv_gstreamer_audio_resample", &audio_resample}, + {"autoaudiosink", "lv_gstreamer_audio_sink", &audio_sink}, + }; + const size_t element_count = sizeof(elements) / sizeof(elements[0]); + for(size_t i = 0; i < element_count; ++i) { + GstElement * el = gst_element_factory_make(elements[i].factory, elements[i].name); + if(!el) { + /* The previous elements were added to the pipeline so we don't need to unref them explicitly + * Unrefing the pipeline is enough and is done by caller*/ + LV_LOG_ERROR("Failed to create %s element", elements[i].name); + return LV_RESULT_INVALID; + } + *(elements[i].store) = el; + if(!gst_bin_add(GST_BIN(pipeline), el)) { + gst_object_unref(el); + LV_LOG_ERROR("Failed to add %s element to pipeline", elements[i].name); + return LV_RESULT_INVALID; + } + } + + /* Here we set the fps we want the pipeline to produce and the color format + * This is achieved by the video_convert and video_rate elements that will automaticall throttle and + * convert the image to the format we desire*/ + uint32_t target_fps = 1000 / LV_DEF_REFR_PERIOD; + char caps[128]; + lv_snprintf(caps, sizeof(caps), "video/x-raw,format=%s,framerate=%" LV_PRIu32 "/1", GST_FORMAT, target_fps); + + GstCaps * appsink_caps = gst_caps_from_string(caps); + g_object_set(G_OBJECT(video_app_sink), "emit-signals", TRUE, "sync", TRUE, "max-buffers", 1, "drop", TRUE, "caps", + appsink_caps, NULL); + gst_caps_unref(appsink_caps); + + if(!gst_element_link_many(streamer->video_convert, video_rate, video_queue, video_app_sink, NULL)) { + LV_LOG_ERROR("Failed to link video convert to sink"); + return LV_RESULT_INVALID; + } + + if(!gst_element_link_many(streamer->audio_convert, audio_resample, streamer->audio_volume, audio_sink, NULL)) { + LV_LOG_ERROR("Failed to link audio convert to sink"); + return LV_RESULT_INVALID; + } + + g_signal_connect(video_app_sink, "new-sample", G_CALLBACK(on_new_sample), streamer); + + /* At this point we don't yet know the input format + * Once the source starts receiving the data, it will create the necessary pads, + * i.e one pad for audio and one for video + * We add a callback so that we automatically connect to the data once it's figured out*/ + g_signal_connect(decode_element, "pad-added", G_CALLBACK(on_decode_pad_added), streamer); + return LV_RESULT_OK; +} + +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); + + GstStructure * structure = gst_caps_get_structure(caps, 0); + const gchar * name = gst_structure_get_name(structure); + + LV_LOG_TRACE("Pad discovered %s", name); + + if(g_str_has_prefix(name, "video/")) { + GstPad * video_convert_sink_pad = gst_element_get_static_pad(streamer->video_convert, "sink"); + if(!gst_pad_is_linked(video_convert_sink_pad)) { + if(gst_pad_link(pad, video_convert_sink_pad) != GST_PAD_LINK_OK) { + LV_LOG_ERROR("Failed to link discovered pad '%s' to videoconvert", name); + } + } + else { + LV_LOG_WARN("Received another video pad '%s' but our video pipeline is already linked - Ignoring", name); + } + gst_object_unref(video_convert_sink_pad); + } + else if(g_str_has_prefix(name, "audio/")) { + GstPad * audio_convert_sink_pad = gst_element_get_static_pad(streamer->audio_convert, "sink"); + if(!gst_pad_is_linked(audio_convert_sink_pad)) { + if(gst_pad_link(pad, audio_convert_sink_pad) != GST_PAD_LINK_OK) { + LV_LOG_ERROR("Failed to link discovered pad '%s' to audioconvert", name); + } + } + else { + LV_LOG_WARN("Received another audio pad '%s' but our audio pipeline is already linked - Ignoring", name); + } + gst_object_unref(audio_convert_sink_pad); + } + + gst_caps_unref(caps); +} + +static GstFlowReturn on_new_sample(GstElement * sink, gpointer user_data) +{ + /* This function is called from a thread other than the main one so we can't call anything related to LVGL here + * Instead, we acquire the new sample (the new frame) and push it to the queue so that we can retrieve it from an LVGL timer + * Note that the pipeline spits out a new frame every LV_DEF_REFR_PERIOD as per the way it's set up so we shouldn't ever lose any + * frames with this method*/ + lv_gstreamer_t * streamer = (lv_gstreamer_t *)user_data; + GstSample * sample; + + g_signal_emit_by_name(sink, "pull-sample", &sample); + if(!sample) { + return GST_FLOW_OK; + } + + g_async_queue_push(streamer->frame_queue, sample); + return GST_FLOW_OK; +} +#endif diff --git a/src/libs/gstreamer/lv_gstreamer.h b/src/libs/gstreamer/lv_gstreamer.h new file mode 100644 index 0000000000..3f85daab52 --- /dev/null +++ b/src/libs/gstreamer/lv_gstreamer.h @@ -0,0 +1,188 @@ +/** + * @file lv_gstreamer.h + * + */ + +#ifndef LV_GSTREAMER_H +#define LV_GSTREAMER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../lv_conf_internal.h" + +#if LV_USE_GSTREAMER +#include "../../core/lv_obj.h" + + +/********************* + * DEFINES + *********************/ + +LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_gstreamer_class; + +/* Using the `URI` "factory", we can 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. */ +#define LV_GSTREAMER_FACTORY_URI_DECODE "uridecodebin" +#define LV_GSTREAMER_PROPERTY_URI_DECODE "uri" + +#define LV_GSTREAMER_FACTORY_FILE "filesrc" +#define LV_GSTREAMER_PROPERTY_FILE "location" + +/** These sources are untested. For most of them, URI_DECODE can probably be used instead */ +#ifdef LV_GSTREAMER_ENABLE_UNTESTED_SOURCES +#define LV_GSTREAMER_FACTORY_HTTP "souphttpsrc" +#define LV_GSTREAMER_PROPERTY_HTTP "location" + +#define LV_GSTREAMER_FACTORY_HTTPS "souphttpsrc" +#define LV_GSTREAMER_PROPERTY_HTTPS "location" + +#define LV_GSTREAMER_FACTORY_V4L2_CAMERA "v4l2src" +#define LV_GSTREAMER_PROPERTY_V4L2_CAMERA "device" + +#define LV_GSTREAMER_FACTORY_ALSA_AUDIO "alsasrc" +#define LV_GSTREAMER_PROPERTY_ALSA_AUDIO "device" + +#define LV_GSTREAMER_FACTORY_PULSE_AUDIO "pulsesrc" +#define LV_GSTREAMER_PROPERTY_PULSE_AUDIO "device" + +#define LV_GSTREAMER_FACTORY_TEST_AUDIO "audiotestsrc" +#define LV_GSTREAMER_PROPERTY_TEST_AUDIO NULL + +#define LV_GSTREAMER_FACTORY_TEST_VIDEO "videotestsrc" +#define LV_GSTREAMER_PROPERTY_TEST_VIDEO NULL + +#define LV_GSTREAMER_FACTORY_APP "appsrc" +#define LV_GSTREAMER_PROPERTY_APP NULL +#endif + +/********************** + * TYPEDEFS + **********************/ + +typedef enum { + LV_GSTREAMER_STATE_NULL, + LV_GSTREAMER_STATE_READY, + LV_GSTREAMER_STATE_PAUSED, + LV_GSTREAMER_STATE_PLAYING +} lv_gstreamer_state_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a gstreamer object + * @param parent pointer to an object, it will be the parent of the new gstreamer + * @return pointer to the created gstreamer + */ +lv_obj_t * lv_gstreamer_create(lv_obj_t * parent); + +/** + * Add a source to this gstreamer object + * @param gstreamer pointer to a gstreamer object + * @param factory_name the factory name for the source of this gstreamer object. + * for common factory names, check `LV_GSTREAMER_FACTORY_XXX` defines + * @param property the property name for the gstreamer source object + * for common properties, see `LV_GSTREAMER_PROPERTY_XXX` defines + * Passing NULL will create the source object but not set its source + * @param source the property value for the gstreamer source object + * Passing NULL will create the source object but not set its source + * @return LV_RESULT_OK if the source was correctly set else LV_RESULT_INVALID + */ +lv_result_t lv_gstreamer_set_src(lv_obj_t * gstreamer, const char * factory_name, const char * property, + const char * source); + +/** + * Play this gstreamer + * @param gstreamer pointer to a gstreamer object + */ +void lv_gstreamer_play(lv_obj_t * gstreamer); + +/** + * Pause this gstreamer + * @param gstreamer pointer to a gstreamer object + */ +void lv_gstreamer_pause(lv_obj_t * gstreamer); + +/** + * Stop this gstreamer + * @param gstreamer pointer to a gstreamer object + */ +void lv_gstreamer_stop(lv_obj_t * gstreamer); + +/** + * Seek a position in this gstreamer + * @param gstreamer pointer to a gstreamer object + * @param position position to seek to + */ +void lv_gstreamer_set_position(lv_obj_t * gstreamer, uint32_t position); + +/** + * Get the duration of this gstreamer + * @param gstreamer pointer to a gstreamer object + * @return the duration (in ms) of the gstreamer object + */ +uint32_t lv_gstreamer_get_duration(lv_obj_t * gstreamer); + +/** + * Get the position of this gstreamer + * @param gstreamer pointer to a gstreamer object + * @return the position (in ms) of the gstreamer object + */ +uint32_t lv_gstreamer_get_position(lv_obj_t * gstreamer); + +/** + * Get the state of this gstreamer + * @param gstreamer pointer to a gstreamer object + */ +lv_gstreamer_state_t lv_gstreamer_get_state(lv_obj_t * gstreamer); + +/** + * Set the volume of this gstreamer + * @param gstreamer pointer to a gstreamer object + * @param volume the value to set in the range [0..100]. Higher values are clamped + */ +void lv_gstreamer_set_volume(lv_obj_t * gstreamer, uint8_t volume); + +/** + * Get the volume of this gstreamer + * @param gstreamer pointer to a gstreamer object + * @return the volume for this gstreamer + */ +uint8_t lv_gstreamer_get_volume(lv_obj_t * gstreamer); + +/** + * Set the speed rate of this gstreamer + * @param gstreamer pointer to a gstreamer object + * @param rate the rate factor. Example values: + * - 256: 1x + * - <256: slow down + * - >256: speed up + * - 128: 0.5x + * - 512: 2x + */ +void lv_gstreamer_set_rate(lv_obj_t * gstreamer, uint32_t rate); + + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_GSTREAMER*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_GSTREAMER_H*/ diff --git a/src/libs/gstreamer/lv_gstreamer_internal.h b/src/libs/gstreamer/lv_gstreamer_internal.h new file mode 100644 index 0000000000..7f35e8fd63 --- /dev/null +++ b/src/libs/gstreamer/lv_gstreamer_internal.h @@ -0,0 +1,74 @@ +/** + * @file lv_gstreamer_internal.h + * + */ + +#ifndef LV_GSTREAMER_INTERNAL_H +#define LV_GSTREAMER_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../lv_conf_internal.h" + +#if LV_USE_GSTREAMER + +#include +#include + +#include "../../widgets/image/lv_image_private.h" + +#include "lv_gstreamer.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +struct _lv_gstreamer_t { + lv_image_t image; + lv_image_dsc_t frame; + GstVideoInfo video_info; + GstElement * pipeline; + GstElement * audio_convert; + GstElement * video_convert; + GstElement * audio_volume; + GstSample * last_sample; + lv_timer_t * gstreamer_timer; + GAsyncQueue * frame_queue; + bool is_video_info_valid; +}; + +typedef struct { + uint8_t * frame_data; + uint32_t width; + uint32_t height; + uint32_t stride; + size_t data_size; +} frame_data_t; + +typedef struct _lv_gstreamer_t lv_gstreamer_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#endif /* LV_USE_GSTREAMER != 0 */ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_GSTREAMER_INTERNAL_H*/ diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 63cd681498..a5d9e6cd9f 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -3079,6 +3079,14 @@ #endif #endif +/** GStreamer library */ +#ifndef LV_USE_GSTREAMER + #ifdef CONFIG_LV_USE_GSTREAMER + #define LV_USE_GSTREAMER CONFIG_LV_USE_GSTREAMER + #else + #define LV_USE_GSTREAMER 0 + #endif +#endif /** Decode bin images to RAM */ #ifndef LV_BIN_DECODER_RAM_LOAD