Move stream frame management into FrameStream class

This commit is contained in:
Joel Collins 2020-11-16 11:25:39 +00:00
parent 90ccd0bc4e
commit f7655400e1
3 changed files with 45 additions and 32 deletions

View file

@ -27,11 +27,17 @@ class FrameStream(io.BytesIO):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Array of TrackerFrame objects # Array of TrackerFrame objects
io.BytesIO.__init__(self, *args, **kwargs) io.BytesIO.__init__(self, *args, **kwargs)
# Array of TrackerFramer objects
self.frames = [] self.frames = []
# Last acquired TrackerFramer object
self.last = None self.last = None
# Are we currently tracking frame sizes?
self.tracking = False self.tracking = False
# Event to track if a new frame is available since the last getvalue() call
self.new_frame = threading.Event()
def start_tracking(self): def start_tracking(self):
if not self.tracking: if not self.tracking:
logging.debug("Started tracking frame data") logging.debug("Started tracking frame data")
@ -46,11 +52,36 @@ class FrameStream(io.BytesIO):
self.frames = [] self.frames = []
def write(self, s): def write(self, s):
"""
Write a new frame to the FrameStream. Does a few things:
1. If tracking frame size, store the size in self.frames
2. Rewind and truncate the stream (delete previous frame)
3. Store the new frame image
4. Set the new_frame event
"""
if self.tracking: if self.tracking:
frame = TrackerFrame(size=len(s), time=time.time()) frame = TrackerFrame(size=len(s), time=time.time())
self.frames.append(frame) self.frames.append(frame)
self.last = frame self.last = frame
io.BytesIO.write(self, s) # Reset the stream for the next frame
super().seek(0)
super().truncate()
# Write the new frame
super().write(s)
# Set the new frame event
self.new_frame.set()
def getvalue(self) -> bytes:
"""
Clear tne new_frame event and return frame data
"""
self.new_frame.clear()
return super().getvalue()
def getframe(self) -> bytes:
"""Wait for a new frame to be available, then return it"""
self.new_frame.wait()
return self.getvalue()
class BaseCamera(metaclass=ABCMeta): class BaseCamera(metaclass=ABCMeta):
@ -65,7 +96,10 @@ class BaseCamera(metaclass=ABCMeta):
self.lock = StrictLock(name="Camera", timeout=None) self.lock = StrictLock(name="Camera", timeout=None)
self.stream = FrameStream() self.stream = FrameStream()
# Iterator function that yields new frames as they are acquired
self.frames_iterator = None self.frames_iterator = None
self.frame = None self.frame = None
self.last_access = 0 self.last_access = 0
self.event = ClientEvent() self.event = ClientEvent()
@ -180,13 +214,18 @@ class BaseCamera(metaclass=ABCMeta):
def _thread(self): def _thread(self):
"""Camera background thread.""" """Camera background thread."""
# Set the camera object's frame iterator
self.frames_iterator = self.frames() self.frames_iterator = self.frames()
logging.debug("Entering worker thread.") logging.debug("Entering worker thread.")
self.stream_active = True self.stream_active = True
for frame in self.frames_iterator: for frame in self.frames_iterator:
# Store most recent frame
self.frame = frame self.frame = frame
# Signal to clients that a new frame is available
# We use this event because each client could be
# reading frames slower than we're acquiring them.
self.event.set() # send signal to clients self.event.set() # send signal to clients
try: try:

View file

@ -198,20 +198,10 @@ class MissingCamera(BaseCamera):
try: try:
while True: while True:
time.sleep(1) # Only serve frames at 1fps time.sleep(1) # Only serve frames at 1fps
# Reset stream # Generate new dummy image
self.stream.seek(0)
self.stream.truncate()
# Generate new dumm image
self.generate_new_dummy_image() self.generate_new_dummy_image()
# Get frame data # Wait for the next frame and then yield it
frame = self.stream.getvalue() yield self.stream.getframe()
# ensure the size of package is right
if len(frame) == 0:
pass
else:
yield frame
# When GeneratorExit or StopIteration raised, run cleanup code # When GeneratorExit or StopIteration raised, run cleanup code
finally: finally:

View file

@ -565,24 +565,8 @@ class PiCameraStreamer(BaseCamera):
# While the iterator is not closed # While the iterator is not closed
try: try:
while True: while True:
# Reset the stream for the next frame # Wait for the next frame and then yield it
self.stream.seek(0) yield self.stream.getframe()
self.stream.truncate()
# Sleep for the approximate camera framerate
time.sleep(1 / self.camera.framerate * 0.1)
# Get the latest frame data from the stream
frame = self.stream.getvalue()
# This loop iterates faster than the PiCamera will collect
# frames. On iterations between frames, the buffer will be
# empty (because we truncated it earlier). In these cases
# we just pass and loop again until a frame is actually present.
# This also means that the size of FrameStream is always 1 frame,
# since we truncate it here before a new frame can be appended.
if len(frame) == 0:
pass
else:
yield frame
# When GeneratorExit or StopIteration raised, run cleanup code # When GeneratorExit or StopIteration raised, run cleanup code
finally: finally:
# Stop stream recording (and set resolution) # Stop stream recording (and set resolution)