willprice / python-omxplayer-wrapper

:tv: Control OMXPlayer, the Raspberry Pi media player, from Python
http://python-omxplayer-wrapper.readthedocs.io
GNU Lesser General Public License v3.0
253 stars 71 forks source link

[Errno 24] after opening and closing videos constantly #176

Open acmele opened 5 years ago

acmele commented 5 years ago

Issue Report

Description

Alternating between two player instances to put a video display on a tour. This eventually produces a [Errno 24] Too many open files after about an hour.

Problem reproduction

this is the current test code

# Project: Chart Compatible Impath Decoder Emulator for Raspberry pi
# Python 3 

#Test for video switching for monitors on tour

import time
import socket
import sys
from _thread import *
import os
import subprocess
import re
import logging
from omxplayer.player import OMXPlayer
from datetime import datetime

def LoadStream(val):
    #Val = 0 'tmpSDP.sdp dbus "omxplayer.player0'
    #Val = 1 'tmpSDP.sdp dbus "omxplayer.player1'
    #Val = 2 'cnv.sdp' dbus "omxplayer.player1

    omx_arg = ['--timeout', '6000', '--live', '--blank', '--refresh', '--no-keys']  

    if (val == 0):
        return OMXPlayer('tmpSDP1.sdp', args=omx_arg, dbus_name="omxplayer.player0")
    elif (val == 1):
        return OMXPlayer('tmpSDP2.sdp', args=omx_arg, dbus_name="omxplayer.player1")
    else:
        return OMXPlayer('cnv.sdp', args=omx_arg, dbus_name="omxplayer.player1")

log_name = datetime.now().strftime('decoder_%H_%M_%d_%m_%Y.log')    
logging.basicConfig(filename= log_name, level=logging.WARNING, format='%(asctime)s %(message)s') #change to 'WARNING' when in production 'DEBUG' and 'INFO' are valid choices

player_turn = True #true = Player1 will hide and player0 will play 
omx_test = True #The decoder is not in an error recovery state

player1 = LoadStream(1)
player1.play()

#code will load a new player instance, kill the old instance to give the illusion of a seamless transfer then repeat.

while True:
    time.sleep(3)
    try:
        if player_turn:
            player0 = LoadStream(0)
            time.sleep(2)
            player1.hide_video()
            player1.stop()
            player1.quit()
            time.sleep(1)
            player0.play()
            time.sleep(1)
            if player0.is_playing():                
                pass
            omx_test = True             
            player_turn = False
            logging.info('Playing on Player0')
        else:
            player1 = LoadStream(1)
            time.sleep(2)
            player0.hide_video()
            player0.stop()
            player0.quit()
            time.sleep(1)
            player1.play()  
            time.sleep(1)           
            if player1.is_playing():
                pass
            omx_test = True
            player_turn = True
            logging.info('Playing on Player1')  
    except Exception as e:
        error_check = True
        logging.warning('failure to load')
        logging.warning('failure to load error: %s', e)
        while error_check:
            player1.quit()
            time.sleep(2)
            player0.quit()
            time.sleep(2)
            try:
                player1 = LoadStream(2)
                player1.play()
                time.sleep(2)
                if player1.is_playing():
                    player_turn = True
                    omx_test = False
                    error_check = False
            except Exception as e:
                logging.error('CNV display error: %s', e)               
                player1.quit()
    pass
pass

Please let me know if you need more details, amateur here and first time posting.

gym1champ commented 5 years ago

SImilar problem here! Looking for a fix

psyferre commented 5 years ago

Same problem here. Fixing #78 would make this a lot easier to avoid, I would think.

psyferre commented 5 years ago

I've been trying to debug this problem, and I think I've found the issue - but not a fix. Each time the player gets loaded with a source, I see an entry in lsof of type STREAM for the main python process. When player.stop() or .quit() is called, that entry does not go away. For example, after playing a sound effect 7 times, here's an excerpt of lsof | grep 'python':

python    12611                   pi    0u      CHR      136,2      0t0          5 /dev/pts/2
python    12611                   pi    1u      CHR      136,2      0t0          5 /dev/pts/2
python    12611                   pi    2u      CHR      136,2      0t0          5 /dev/pts/2
python    12611                   pi    3u     unix 0x9be72d00      0t0     219115 type=STREAM
python    12611                   pi    4u     unix 0x9be74c00      0t0     219245 type=STREAM
python    12611                   pi    5u     unix 0x9be71b00      0t0     218986 type=STREAM
python    12611                   pi    6u     unix 0x9be71800      0t0     219102 type=STREAM
python    12611                   pi    7u     unix 0x9be70000      0t0     219122 type=STREAM
python    12611                   pi    8u     unix 0x9be76a00      0t0     219986 type=STREAM
python    12611                   pi    9u     unix 0x9be76700      0t0     220066 type=STREAM

So far I haven't figured out a way to kill those open streams without killing the main python process, but I'll report back if I do.

michaelnimbs commented 5 years ago

I ran into the same problem and solved it for me with the following workaround:

You should not create new instances of OMXPlayer, only create them once (player0 and player1 for you). Instead call load() with the new video to play. With this approach the number of open streams does not increase anymore.

megapegabot commented 4 years ago

@michaelnimbs You are my savior, everything works. You just need to use the method load()

matthijskooijman commented 4 years ago

Looking at the lsof output, the open fds are UNIX sockets, so I suspect this is the connection to the dbus daemon. Looking at the source, I indeed see that OMXPlayer._connection is created, but it is never actually closed.

I suspect that it would be solved by disconnecting dbus on quit, e.g. by adding this to the end of the quit() method (alternatively, you can call this after you call quit from your program as well so you do not need to modify the library, then replace self with your player instance):

 self._connection._bus.close()
 self._connection = None

Anyone care to test this?

jpc commented 3 years ago

Hi, using a single instance and only calling load does not seem like it will help because the DBus connection is opened every time a new process is spawned. (see https://github.com/willprice/python-omxplayer-wrapper/blob/master/omxplayer/player.py#L163)

It may be that in most cases the garbage collector closes the connections but I encountered resource exhaustion at least once with 140 DBus sockets open in a long running Python process which used omxplayer-wrapper.

I'll monkey-patch the quit method to call self._connection._bus.close() and see if this resolves the problem.

jpc commented 3 years ago

In case it helps someone, a quick patch looks like this:

def patch_OMXPLayer_quit():
    old_quit = OMXPlayer.quit
    def new_quit(self):
        self._connection._bus.close()
        old_quit(self)
    OMXPlayer.quit = new_quit
patch_OMXPLayer_quit()

I'll let you know if it solved the problem definitely.

jpc commented 3 years ago

Ok, I've tested this for quite a long time now and the change I posted definitively fixed the problem with leaking bus connections.

willprice commented 3 years ago

Thanks @jpc for looking into this, would you be able to open a PR with this change and i'll merge it in!

bonnyone commented 2 years ago

I add @matthijskooijman suggestion in player.py at function quit() and it's work.(Tested 72 hours none stop playing 9 mp4 files, until now pi still playing without [Errno 24])

def quit():
    if self._connection._bus is not None:
        self._connection._bus.close()
        logger.debug('[Errno 24][patch]BusConnection closed')
    """
    Quit the player, blocking until the process has died
    """
    ......
willprice commented 2 years ago

Thanks for the confirmation @bonnyone. I'll make the change and make a new release later today.

bonnyone commented 2 years ago

@willprice U r welcome.

This is my code for test

def threadingPlayer():
    while True:
        try:
            movie = movies[movie_index]
            print('********************************')
            print(movie)
            print('********************************')
            player = OMXPlayer(Path(movie),
                               args=['--no-osd', '--no-keys', '-o', 'hdmi'],
                               dbus_name='org.mpris.MediaPlayer2.omxplayer1')
            player.set_volume(player_volume)
            sleep(2.5)
            player.play_sync()
            player.quit()
            player = None
            print('Play Next')
            movie_index = movie_index + 1
            if movie_index == len(movies):
                movie_index = 0
        except OSError as e:
            logger.error(e)
        except Exception as e:
            logger.error(e)

threading.Thread(target=threadingPlayer).start()