From 5f4c3b244d8a67205d3f66909638a22dbebd96ff Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Sun, 24 Oct 2021 12:59:06 -0400 Subject: [PATCH 1/2] fix(359): Support fractional fps. --- pyrdp/convert/MP4EventHandler.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyrdp/convert/MP4EventHandler.py b/pyrdp/convert/MP4EventHandler.py index fe19b9108..f0ae714a5 100644 --- a/pyrdp/convert/MP4EventHandler.py +++ b/pyrdp/convert/MP4EventHandler.py @@ -10,6 +10,7 @@ from pyrdp.player.RenderingEventHandler import RenderingEventHandler import logging +from math import floor import av import qimage2ndarray @@ -38,7 +39,7 @@ def screen(self) -> QImage: class MP4EventHandler(RenderingEventHandler): - def __init__(self, filename: str, fps=30, progress=None): + def __init__(self, filename: str, fps=25, progress=None): """ Construct an event handler that outputs to an Mp4 file. @@ -57,16 +58,17 @@ def __init__(self, filename: str, fps=30, progress=None): # we could probably batch the encoding of several frames and benefit from threads # but trying this as-is lead to no gains # (actually a degradation but that could be statistically irrelevant) - #self.stream.thread_count = 4 + # self.stream.thread_count = 4 self.stream.pix_fmt = 'yuv420p' self.progress = progress self.scale = False self.mouse = (0, 0) self.fps = fps - self.delta = 1000 // fps # ms per frame + self.delta = 1000 / fps # ms per frame self.log = logging.getLogger(__name__) self.log.info('Begin MP4 export to %s: %d FPS', filename, fps) self.timestamp = self.prevTimestamp = None + self.drift = 0 # Support non-integer frame rates. super().__init__(MP4Image()) @@ -84,8 +86,12 @@ def onPDUReceived(self, pdu: PlayerPDU): dt = self.delta else: dt = self.timestamp - self.prevTimestamp # ms - nframes = (dt // self.delta) - if nframes > 0: + + # Prevent drifting when fps doesn't perfectly divide a second. + nframes = (dt / self.delta) + self.drift + self.drift = nframes - floor(nframes) # update drift. + + if floor(nframes) > 0: # Only take whole frames. for _ in range(nframes): self.writeFrame() self.prevTimestamp = ts From cd3892567b4f3ead1efbfe1a8d7453082d581531 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Sun, 24 Oct 2021 13:13:41 -0400 Subject: [PATCH 2/2] fix: render whole frames only. --- pyrdp/convert/MP4EventHandler.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pyrdp/convert/MP4EventHandler.py b/pyrdp/convert/MP4EventHandler.py index f0ae714a5..fd51a88ee 100644 --- a/pyrdp/convert/MP4EventHandler.py +++ b/pyrdp/convert/MP4EventHandler.py @@ -89,13 +89,14 @@ def onPDUReceived(self, pdu: PlayerPDU): # Prevent drifting when fps doesn't perfectly divide a second. nframes = (dt / self.delta) + self.drift - self.drift = nframes - floor(nframes) # update drift. + nwhole = floor(nframes) + self.drift = nframes - nwhole # update drift. - if floor(nframes) > 0: # Only take whole frames. - for _ in range(nframes): + if nwhole > 0: # Only take whole frames. + for _ in range(nwhole): self.writeFrame() self.prevTimestamp = ts - self.log.debug('Rendered %d still frame(s)', nframes) + self.log.debug('Rendered %d still frame(s) Drift=%f', nwhole, self.drift) def cleanup(self): # Add one second worth of padding so that the video doesn't end too abruptly.