Home > Net >  Weird python issue - increasing CPU usage overtime?
Weird python issue - increasing CPU usage overtime?

Time:11-17

I am making a video player using pygame, it takes numpy arrays of frames from a video file and streams them to a pygame window using a buffer.

I'm getting a weird issue where CPU usage is increasing (program is slowing down) over time, then CPU usage is sharply decreasing (program is speeding up). Memory usage stays pretty much constant, so I don't think it's a memory leak. This affects the program as I'm trying to stream a video, and long processing times turn it into a slideshow.

I would expect fluctuations, but not that steep. I cannot seem to figure what is going on here, I would really appreciate some help in debugging this!


Source code to replicate the issue at the end of the question. You can use any .mp4 file, but the exact one I used is enter image description here

import cv2
import time
import pygame
import gc
from datetime import datetime

#Debug code
runtime_start = str(datetime.now().strftime("%H-%M-%S"))
def log(prefix, message):
    string = "["   str(datetime.now().strftime("%H:%M:%S"))   "] "   "("   str(prefix)   ") "   str(message)
    print(string)
    with open(file="log"   runtime_start   ".txt", mode="a", encoding="utf-8") as file:
        file.write(string   "\n")
### ### ###

#Globals
buffer_size = 3
frame_buffer = []
### ### ###

#Functions
def loadVideoFileToMemory(file):
    """
    Load file to memory.
    """
    global capture
    capture = cv2.VideoCapture(file)
    

def getFrameFromVideo(file, frame_number):
    """
    Captures a frame from the loaded file, returns a numpy image array.
    """
    #capture = cv2.VideoCapture(file)
    capture.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
    ret, frame = capture.read()

    #image = ImageTk.PhotoImage(image=Image.fromarray(frame))
    image = cv2.resize(frame, (90*6, 160*6))
    del frame
    gc.collect()
    return image
    
def setBuffer(start_frame):
    """
    Fills the buffer at a set starting frame before playing the video.
    """
    frame_buffer.clear()
    for i in range(buffer_size):
        frame_buffer.append(getFrameFromVideo("test_video1.mp4", start_frame   i))

def updateBuffer(frame):
    """
    Updates frame_buffer, deals with garabge collection.
    """
    del frame_buffer[0]
    gc.collect()
    if len(frame_buffer) < buffer_size: #Limits to buffer size
        frame_array = getFrameFromVideo("test_video1.mp4", frame   buffer_size)
        frame_buffer.append(frame_array)
        del frame_array
        gc.collect()


def displayImageToPygame(image, window):
    """
    Converts a numpy image array to a pygame surface and blits to screen.
    """
    window.blit(pygame.surfarray.make_surface(image), (0,0))


def createPygameWindow():
    """
    Creates a pygame window, sets the frame_buffer and starts the main thread.
    """
    window = pygame.display.set_mode((160*6, 90*6))
    pygame.display.flip()

    #Starting frame
    current_frame = 0
    setBuffer(current_frame) #Set from this position
    displayImageToPygame(frame_buffer[0], window) #Show starting frame


    pygameThread(window, current_frame)


def pygameThread(window, current_frame):
    """
    Main thread. Plays the video.
    """
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

    
        timer_start = time.time() #DEBUG: PROCESSING TIME

        current_frame  = 1
        displayImageToPygame(frame_buffer[1], window)
        updateBuffer(current_frame)
        pygame.display.flip()

        #DEBUG: POST TO LOG.TXT
        log("Variable: current_frame", current_frame)
        log("Variable: len(frame_buffer)", len(frame_buffer))
        timer_end = time.time() #DEBUG: PROCESSING TIME
        log("Process time",  str(round(timer_end - timer_start, 2))   " seconds")
        ###
### ### ###
       
video_file = "test_video1.mp4" #Replace this with a path to an .mp4 file. You can download the exact file I'm using here: https://drive.google.com/file/d/1_tfTVHmaoTEYxkLrjVS8NiAvdes3Gd77/view?usp=share_link
loadVideoFileToMemory(video_file)
createPygameWindow()

CodePudding user response:

Ok, so i could reproduce what you describe (which was very easy with the sources you provided).

Removing the GC also did nothing for me, as you already observed.

Now i did the following test: instead of getting each frame in order, i got a random frame from the video and recorded those times.

Here's the (trivial) code snippet to do that:

from random import randint, seed
[..]
def pygameThread(window, current_frame):
    """
    Main thread. Plays the video.
    """
    seed()
[..]

        current_frame  = 1
        rand_frame = randint(1,500) # taking one of the first 500, doesn't really matter
        displayImageToPygame(frame_buffer[1], window)
        #updateBuffer(current_frame)
        updateBuffer(rand_frame)

and more interestingly here's the recorded timing:

[09:02:37] Current_frame 202 len(frame_buffer)3 Process time 0.06 seconds
[09:02:37] Current_frame 423 len(frame_buffer)3 Process time 0.09 seconds
[09:02:37] Current_frame 258 len(frame_buffer)3 Process time 0.03 seconds
[09:02:37] Current_frame 464 len(frame_buffer)3 Process time 0.1 seconds
[09:02:37] Current_frame 176 len(frame_buffer)3 Process time 0.04 seconds
[09:02:37] Current_frame 463 len(frame_buffer)3 Process time 0.1 seconds
[09:02:37] Current_frame 425 len(frame_buffer)3 Process time 0.09 seconds
[09:02:37] Current_frame 154 len(frame_buffer)3 Process time 0.11 seconds
[09:02:37] Current_frame 54 len(frame_buffer)3 Process time 0.05 seconds
[09:02:37] Current_frame 110 len(frame_buffer)3 Process time 0.08 seconds
[09:02:38] Current_frame 133 len(frame_buffer)3 Process time 0.1 seconds
[09:02:38] Current_frame 41 len(frame_buffer)3 Process time 0.04 seconds
[09:02:38] Current_frame 471 len(frame_buffer)3 Process time 0.11 seconds
[09:02:38] Current_frame 458 len(frame_buffer)3 Process time 0.1 seconds
[09:02:38] Current_frame 44 len(frame_buffer)3 Process time 0.05 seconds
[09:02:38] Current_frame 406 len(frame_buffer)3 Process time 0.07 seconds
[09:02:38] Current_frame 66 len(frame_buffer)3 Process time 0.06 seconds
[09:02:38] Current_frame 439 len(frame_buffer)3 Process time 0.09 seconds
[09:02:38] Current_frame 32 len(frame_buffer)3 Process time 0.04 seconds
[09:02:38] Current_frame 89 len(frame_buffer)3 Process time 0.07 seconds
[09:02:38] Current_frame 221 len(frame_buffer)3 Process time 0.06 seconds

So as we can see, just accessing the frames takes a variable amount of time, independant of processing time. My educated guess is that accessing key frames in the stream is fast, while reconstructing the following frames takes longer. Maybe try running some decompression first, before running your algorithm ?

You should also run this test for a longer time than i have and plot it again, to make sure that there really is no increase over time.

CodePudding user response:

Seeking is random access in a media file.

Setting CAP_PROP_POS_FRAMES is seeking.

Do not seek if you don't have to. For most video codecs, it is more costly than sequential access. It can also be imprecise because seeking may decide, because it's cheaper to do that, to merely jump to the nearest "keyframe", instead of the exact time index you want.

Your access pattern appears to be one jump followed by sequential decoding from there. For this entire operation, you should seek once, then decode sequentially.

Calling .read() on the VideoCapture object is sequential decoding. It automatically advances in the video.

  • Related