I’ve built a video player in Qt for Python using the abillity to set the pixmap of a QLabel to display video frames. However, this strategy appears to use a tremendous amount of memory, until eventually the application runs out of memory and crashes.
As you can see, the memory usage increases linearly for about 15 seconds, at which point the application crashed.
Below is a minimum reproducable example. The particular video I used was in 4k, which certaintly exacerbates the problem, however this still happens with videos with lower quality, just slower.
from PySide6.QtCore import QSize, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel
from PySide6.QtGui import QImage, QPixmap
import cv2
import sys
import time
import threading
class MainWindow(QMainWindow):
def __init__(self, app):
super().__init__()
self.app = app
self.label = QLabel()
self.setFixedSize(QSize(400, 300))
self.setCentralWidget(self.label)
path = "path/to/video.mp4"
self.cap = cv2.VideoCapture(path)
threading.Thread(target=self.showFrame, daemon=True).start()
def showFrame(self):
success, frame = self.cap.read()
if success:
playerWidth = self.label.width()
playerHeight = self.label.height()
height, width, channel = frame.shape
bytes_per_line = 3 * width
q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format_BGR888)
q_img_resized = q_img.scaled(playerWidth, playerHeight, Qt.KeepAspectRatio)
pixmap = QPixmap(q_img_resized)
self.label.setPixmap(pixmap)
time.sleep(1 / 30)
print("MainWindow Size:", self.getSize(self))
print("App Size:", self.getSize(self.app))
print("Label Size:", self.getSize(self.label))
self.showFrame()
else:
self.cap.release()
def getSize(self, obj, seen=None):
size = sys.getsizeof(obj)
if seen is None:
seen = set()
obj_id = id(obj)
if obj_id in seen:
return 0
seen.add(obj_id)
if isinstance(obj, dict):
size += sum([self.getSize(v, seen) for v in obj.values()])
size += sum([self.getSize(k, seen) for k in obj.keys()])
elif hasattr(obj, '__dict__'):
size += self.getSize(obj.__dict__, seen)
elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)):
size += sum([self.getSize(i, seen) for i in obj])
return size
app = QApplication(sys.argv)
window = MainWindow(app)
window.show()
app.exec()
I’m using the getSize() function to make sure the mainWindow, app, and label objects aren’t the culprit. They stay the same exact same size the entire time, which makes me think that this a memory leak on the part of the QLabel. Also, I’m not using a QTimer because there is a frame analysis process in my app which the video player must wait for, which sometimes slows video playback in an unpredictable manner. I understand I could probably still refactor my code to use a QTimer, but I’m unconvinced this would solve my problem.
So, is there any way to clear the QLabel’s cache, or just display video frames in a better way (without using a QMediaPlayer)?
tbreimer is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.