Reputation: 1
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.
Upvotes: 0
Views: 62