Move stream frame management into FrameStream class
This commit is contained in:
parent
90ccd0bc4e
commit
f7655400e1
3 changed files with 45 additions and 32 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue