[Python] Why is my VLC Media Player application leaking GPU memory on Raspberry Pi 4B+ and 5?

Discussão em 'Python' iniciado por Stack, Julho 8, 2025.

  1. Stack

    Stack Membro Participativo

    We have a Python-based media player application that loops through media files and displays them on a screen.

    After running anywhere from ~3-24 hours, the display will sometimes freeze while playing a video. When this happens, dmesg shows the following messages (these particular messages are from when we were still using drm for the video output. We now use xcb_xv , but we have the same problem):

    ...
    ...
    [ 3.028998] vc4-drm gpu: [drm] fb0: vc4drmfb frame buffer device
    [ 9139.538040] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    [ 9139.545459] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    [ 9139.552522] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    [ 9139.583064] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    [ 9151.090537] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    [ 9151.096344] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    [ 9191.649563] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    [ 9191.675813] vc4-drm gpu: [drm] *ERROR* Failed to allocate DLIST entry. Requested size=9. ret=-28
    ...
    ...


    This indicates that GPU memory has been leaked and there is no longer enough available to continue playing the videos. The device (Raspberry Pi) continues to function otherwise.

    For video files, VLC is used for playback. At the end of each video, we attempt to clean up all resources VLC used to display the video. We have also tried keeping a video play counter, which we used to completely tear down and rebuild the VLC player instance after every 50 video plays. This did not resolve the issue.

    Here is the code:

    import os
    import time
    import json
    import vlc
    import subprocess
    from pathlib import Path
    import pygame
    from pygame import display, image, time as pygame_time
    from jplayer.utils import scene_update_flag_utils as sufu
    from jplayer.utils import logging_utils as lu
    from jplayer.utils import system_utils as su
    from jplayer.clients.japi_client import JApiClient

    logger = lu.get_logger("jplayer_app")

    VIDEO_REINIT_THRESHOLD = 50


    class MediaPlayer:
    def __init__(self, media_directory, scenes_directory):
    logger.info("Initializing MediaPlayer ...")
    self.media_directory = Path(media_directory)
    self.scenes_directory = Path(scenes_directory)
    self.default_image_path = Path('/home/static_images/ready_for_content.jpeg')
    self.supported_images = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.heic'}
    self.supported_videos = {'.mp4', '.avi', '.mov'}
    self.current_scenes = []
    self.video_count = 0

    # Get orientation
    self.jclient = JApiClient(logger)
    orientation = self.jclient.jplayer_info.get("orientation", "LANDSCAPE")
    logger.info(f" Detected Orientation: {orientation}")
    self.screen_config = su.get_screen_config(orientation)

    # Initialize Pygame for image display
    pygame.init()
    display.init()

    # Set up fullscreen display with proper orientation
    display_dims = self.screen_config.get_display_dimensions()
    self.screen = display.set_mode(display_dims, pygame.FULLSCREEN)
    self.screen_width, self.screen_height = display_dims

    # Initialize VLC
    self.initialize_vlc()

    logger.info("Done initializing MediaPlayer.")

    def initialize_vlc(self):
    """Initialize the VLC instance and player."""
    vlc_args = [
    '--vout=xcb_xv',
    '--no-video-title-show',
    '--file-caching=2000'
    ]
    self.vlc_instance = vlc.Instance(vlc_args)
    self.vlc_player = self.vlc_instance.media_player_new()

    # Tell VLC to render to the pygame window
    if os.name == 'nt': # Windows
    self.vlc_player.set_hwnd(pygame.display.get_wm_info()['window'])
    else: # Linux/Raspberry Pi
    self.vlc_player.set_xwindow(pygame.display.get_wm_info()['window'])

    def reinitialize_vlc(self):
    """Reinitialize VLC after releasing resources to free up GPU memory."""
    logger.info("Reinitializing VLC to reclaim GPU resources...")
    self.vlc_player.release()
    self.vlc_instance.release()
    self.initialize_vlc()
    self.video_count = 0 # Reset the counter after reinitialization
    logger.info("VLC reinitialized successfully.")

    def load_scenes(self):
    """Load and parse scene configuration files."""
    scenes = []
    for file in self.scenes_directory.glob('*.json'):
    try:
    with open(file, 'r') as f:
    scene = json.load(f)
    required_fields = {'id', 'media_file', 'order'}
    if not all(field in scene for field in required_fields):
    logger.info(f"Scene file {file} missing required fields")
    continue

    media_path = self.media_directory / scene['media_file']
    if not media_path.exists():
    logger.info(f"Media file not found: {media_path}")
    continue

    scene['media_path'] = media_path
    scenes.append(scene)
    except Exception as e:
    logger.error(f"Error loading scene file {file}: {e}")
    continue

    return sorted(scenes, key=lambda x: x['order'])

    def display_image(self, scene):
    """Display an image for the specified duration."""
    try:
    img = image.load(str(scene['media_path']))
    img = pygame.transform.scale(img, (self.screen_width, self.screen_height))

    if self.screen_config.get_pygame_rotation() != 0:
    img = pygame.transform.rotate(img, self.screen_config.get_pygame_rotation())

    self.screen.blit(img, (0, 0))
    display.flip()

    duration = int(scene.get('time_to_display', 7))
    pygame_time.wait(duration * 1000)
    return True
    except Exception as e:
    logger.info(f"Error displaying image {scene['media_path']}: {e}")
    return False

    def display_video(self, scene):
    """Play a video file using VLC."""
    media = None
    try:
    # Stop any existing playback and force cleanup
    if self.vlc_player.is_playing():
    self.vlc_player.stop()

    # Create new media instance
    media = self.vlc_instance.media_new(str(scene['media_path']))
    media.add_option(":no-audio") # Disable audio processing if not needed
    media.add_option(":no-metadata-network-access") # Prevent network access
    media.add_option(":no-video-title-show") # Disable title display

    # Set media and immediately check if it was successful
    self.vlc_player.set_media(media)
    if not self.vlc_player.get_media():
    logger.info(" Failed to set media")
    return False

    # Attempt playback
    play_result = self.vlc_player.play()
    if play_result == -1:
    logger.info(f" Failed to start playback for scene {scene.get('id')}")
    return False

    time.sleep(1)

    while self.vlc_player.is_playing():
    logger.info(" video is playing")
    pygame.event.pump()
    time.sleep(0.25)

    for event in pygame.event.get():
    if event.type == pygame.QUIT:
    self.vlc_player.stop()
    if media:
    media.release()
    return False

    # Explicitly stop playback
    self.vlc_player.stop()

    # Small delay to ensure cleanup
    time.sleep(0.1)

    # If we got here, the video played successfully
    self.video_count += 1
    if self.video_count >= VIDEO_REINIT_THRESHOLD:
    self.reinitialize_vlc()

    return True

    except Exception as e:
    logger.error(f"Error playing video {scene.get('id')}: {e}")
    return False

    finally:
    # Aggressive cleanup
    if self.vlc_player.is_playing():
    self.vlc_player.stop()
    # Force VLC to release GPU resources
    self.vlc_player.set_media(None)
    if media:
    media.release()
    media = None

    def display_default_scene(self):
    """Display the default 'ready for content' scene."""
    try:
    if not self.default_image_path.exists():
    logger.error(f"Default image not found at {self.default_image_path}")
    return False

    img = image.load(str(self.default_image_path))
    img = pygame.transform.scale(img, (self.screen_width, self.screen_height))

    if self.screen_config.get_pygame_rotation() != 0:
    img = pygame.transform.rotate(img, self.screen_config.get_pygame_rotation())

    self.screen.blit(img, (0, 0))
    display.flip()
    pygame_time.wait(7000)
    return True
    except Exception as e:
    logger.error(f"Error displaying default image: {e}")
    return False

    def run(self):
    """Main loop to display media files."""
    try:
    while True:
    if sufu.should_reload_scenes():
    self.current_scenes = self.load_scenes()
    logger.info("Reloaded scenes due to update flag")
    sufu.reset_update_flag_to_zero()
    elif not self.current_scenes:
    self.current_scenes = self.load_scenes()
    logger.info("Initial scene load")

    if not self.current_scenes:
    logger.info("No valid scenes found, displaying default scene")
    self.display_default_scene()
    time.sleep(15)
    continue

    logger.info("Iterating through scenes ...")
    for scene in self.current_scenes:
    logger.info(f" Displaying scene {scene.get('id')} at media path {scene.get('media_path')}")
    for event in pygame.event.get():
    if event.type == pygame.QUIT:
    return

    suffix = scene['media_path'].suffix.lower()
    if suffix in self.supported_images:
    if not self.display_image(scene):
    continue
    elif suffix in self.supported_videos:
    if not self.display_video(scene):
    continue

    finally:
    self.vlc_player.release()
    pygame.quit()


    if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description='Display images and videos in a loop based on scene configurations')
    parser.add_argument('media_directory', help='Directory containing media files (images and videos)')
    parser.add_argument('scenes_directory', help='Directory containing scene configuration JSON files')

    args = parser.parse_args()

    player = MediaPlayer(args.media_directory, args.scenes_directory)
    player.run()


    We've tried to clean up resources as aggressively as we know how/are able to. Unfortunately, we're still experiencing the leak. We've also tried increasing GPU memory which did not resolve the issue.

    Continue reading...

Compartilhe esta Página