Skip to content

fix(gstreamer): prevent memory leak when updating video frames#9768

Open
mn89h wants to merge 3 commits intolvgl:masterfrom
mn89h:fix-gstreamer-memory
Open

fix(gstreamer): prevent memory leak when updating video frames#9768
mn89h wants to merge 3 commits intolvgl:masterfrom
mn89h:fix-gstreamer-memory

Conversation

@mn89h
Copy link
Contributor

@mn89h mn89h commented Feb 24, 2026

Reuse a persistent image descriptor and pixel buffer instead of creating a new image source each frame. This prevents repeated texture allocations (notably on OpenGL).

@mn89h mn89h requested a review from AndreCostaaa as a code owner February 24, 2026 16:40
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

Hi 👋, thank you for your PR!

We've run benchmarks in an emulated environment. Here are the results:

ARM Emulated 32b - lv_conf_perf32b

Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
All scenes avg. 29 37 7 7 0
Detailed Results Per Scene
Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
Empty screen 11 33 0 0 0
Moving wallpaper 2 33 1 1 0
Single rectangle 0 50 0 0 0
Multiple rectangles 0 33 0 0 0
Multiple RGB images 0 39 0 0 0
Multiple ARGB images 11 39 (-1) 3 3 0
Rotated ARGB images 55 (+1) 44 15 15 0
Multiple labels 7 35 0 0 0
Screen sized text 97 47 20 20 0
Multiple arcs 33 33 7 7 0
Containers 1 (+1) 37 0 0 0
Containers with overlay 98 21 44 44 0
Containers with opa 18 38 1 1 0
Containers with opa_layer 18 (-1) 33 (-1) 6 6 0
Containers with scrolling 44 45 12 12 0
Widgets demo 72 39 17 17 0
All scenes avg. 29 37 7 7 0

ARM Emulated 64b - lv_conf_perf64b

Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
All scenes avg. 25 37 6 6 0
Detailed Results Per Scene
Scene Name Avg CPU (%) Avg FPS Avg Time (ms) Render Time (ms) Flush Time (ms)
Empty screen 11 33 0 0 0
Moving wallpaper 1 33 0 0 0
Single rectangle 0 50 0 0 0
Multiple rectangles 0 35 0 0 0
Multiple RGB images 0 39 0 0 0
Multiple ARGB images 11 42 0 0 0
Rotated ARGB images 29 33 9 9 0
Multiple labels 2 35 0 0 0
Screen sized text 84 46 18 18 0
Multiple arcs 40 33 6 6 0
Containers 4 37 0 0 0
Containers with overlay 88 (-1) 23 (+1) 41 41 0
Containers with opa 14 38 (+2) 1 (+1) 1 (+1) 0
Containers with opa_layer 9 (+2) 36 (-1) 2 (+1) 2 (+1) 0
Containers with scrolling 48 (+1) 48 12 12 0
Widgets demo 68 40 15 15 0
All scenes avg. 25 37 6 6 0

Disclaimer: These benchmarks were run in an emulated environment using QEMU with instruction counting mode.
The timing values represent relative performance metrics within this specific virtualized setup and should
not be interpreted as absolute real-world performance measurements. Values are deterministic and useful for
comparing different LVGL features and configurations, but may not correlate directly with performance on
physical hardware. The measurements are intended for comparative analysis only.


🤖 This comment was automatically generated by a bot.

Reuse a persistent image descriptor and pixel buffer instead of
creating a new image source each frame. This prevents repeated
texture allocations (notably on OpenGL).
@mn89h mn89h force-pushed the fix-gstreamer-memory branch from 5a1af6b to a70d132 Compare February 25, 2026 09:35
Copy link
Collaborator

@AndreCostaaa AndreCostaaa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Malte, what is the motivation for this change?

@mn89h
Copy link
Contributor Author

mn89h commented Feb 27, 2026

Hi Andre :) I had the problem of memory leaks with GStreamer in conjunction with OpenGLES. It fixes two things: first, it stops the GLES backend from constantly allocating and leaking new textures in VRAM. Since I'm now reusing a persistent pixel buffer and only calling lv_image_set_src once, the renderer can just update the existing texture in-place instead of creating a new one X times a second.
The second thing is a quick drain for the frame queue. If the UI thread lagged behind the video's framerate, that async queue would keep growing infinitely with unrendered GstSamples.

- check for current state and return if no update needed
- wait for pipeline transitioning before start freeing memory
- drop image cache before freeing pixel buffer and on resolution change
@mn89h
Copy link
Contributor Author

mn89h commented Feb 27, 2026

I have discovered some more things, especially in caps handling after first frame, as well as some memory related stuff. In my tests it seems stable now, especially with many state changes after longer periods, where i would repeatedly get crashes.

@mn89h mn89h force-pushed the fix-gstreamer-memory branch from f449e4e to ac20d4b Compare February 27, 2026 13:17
- update caps not only on first_frame, but also on changes (e.g. resolution)
- added null check when pad is added
@mn89h mn89h force-pushed the fix-gstreamer-memory branch from ac20d4b to 47fb81f Compare February 27, 2026 13:19
@AndreCostaaa
Copy link
Collaborator

The second thing is a quick drain for the frame queue. If the UI thread lagged behind the video's framerate, that async queue would keep growing infinitely with unrendered GstSamples.

The pipeline is configured to send a frame every LV_DEF_REFR_PERIOD and internally we're fetching a sample every LV_DEF_REFR_PERIOD / 5 ms

Is the lag caused by a huge render time/flush time ?

Copy link
Collaborator

@AndreCostaaa AndreCostaaa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm of the opinion we need to figure something else out for the frame updating part of the PR

What you're going for is the original implementation of gstreamer but it relies on copying each frame which is slow and not necessary, it was updated afterwards to avoid the memcpy. Specially if the problem is with draw opengles, I believe we need to figure something else with it.

Also note that for LVGL v9.5 I recommend using DRAW_NANOVG instead of DRAW_OPENGLES

I'll take a look at it as well

I like the other changes tho, can we split these into different PRs?


if(streamer->pixel_buffer) {
lv_image_cache_drop(&streamer->frame);
free(streamer->pixel_buffer);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
free(streamer->pixel_buffer);
lv_free(streamer->pixel_buffer);

Or better yet, what about using realloc instead of free + malloc?

}

/* Copy new pixels */
memcpy(streamer->pixel_buffer, map.data, required_size);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

            memcpy(streamer->pixel_buffer, map.data, required_size);
      
      
        
            lv_memcpy(streamer->pixel_buffer, map.data, required_size);

This is a blocker for the PR, memcpying each frame is something we want to avoid, it will make gstreamer slower for everyone that is not using draw_opengles


/* Copy new pixels */
memcpy(streamer->pixel_buffer, map.data, required_size);
if(!streamer->image_src_set) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can get rid of this image_src_set attribute and instead just move lv_image_set_src inside the

streamer->pixel_buffer == NULL || streamer->pixel_buffer_size != required_size condition

if(streamer->pixel_buffer) {
/* Drop the image from cache before freeing the buffer */
lv_image_cache_drop(&streamer->frame);
free(streamer->pixel_buffer);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
free(streamer->pixel_buffer);
lv_free(streamer->pixel_buffer);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants