Commit f7655400 authored by Joel Collins's avatar Joel Collins
Browse files

Move stream frame management into FrameStream class

parent 90ccd0bc
Loading
Loading
Loading
Loading
Loading
+40 −1
Original line number Diff line number Diff line
@@ -27,11 +27,17 @@ class FrameStream(io.BytesIO):
    def __init__(self, *args, **kwargs):
        # Array of TrackerFrame objects
        io.BytesIO.__init__(self, *args, **kwargs)
        # Array of TrackerFramer objects
        self.frames = []
        # Last acquired TrackerFramer object
        self.last = None

        # Are we currently tracking frame sizes?
        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):
        if not self.tracking:
            logging.debug("Started tracking frame data")
@@ -46,11 +52,36 @@ class FrameStream(io.BytesIO):
        self.frames = []

    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:
            frame = TrackerFrame(size=len(s), time=time.time())
            self.frames.append(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):
@@ -65,7 +96,10 @@ class BaseCamera(metaclass=ABCMeta):
        self.lock = StrictLock(name="Camera", timeout=None)

        self.stream = FrameStream()

        # Iterator function that yields new frames as they are acquired
        self.frames_iterator = None

        self.frame = None
        self.last_access = 0
        self.event = ClientEvent()
@@ -180,13 +214,18 @@ class BaseCamera(metaclass=ABCMeta):

    def _thread(self):
        """Camera background thread."""
        # Set the camera object's frame iterator
        self.frames_iterator = self.frames()
        logging.debug("Entering worker thread.")

        self.stream_active = True

        for frame in self.frames_iterator:
            # Store most recent 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

            try:
+3 −13
Original line number Diff line number Diff line
@@ -198,20 +198,10 @@ class MissingCamera(BaseCamera):
        try:
            while True:
                time.sleep(1)  # Only serve frames at 1fps
                # Reset stream
                self.stream.seek(0)
                self.stream.truncate()

                # Generate new dumm image
                # Generate new dummy image
                self.generate_new_dummy_image()
                # Get frame data
                frame = self.stream.getvalue()

                # ensure the size of package is right
                if len(frame) == 0:
                    pass
                else:
                    yield frame
                # Wait for the next frame and then yield it
                yield self.stream.getframe()

        # When GeneratorExit or StopIteration raised, run cleanup code
        finally:
+2 −18
Original line number Diff line number Diff line
@@ -565,24 +565,8 @@ class PiCameraStreamer(BaseCamera):
        # While the iterator is not closed
        try:
            while True:
                # Reset the stream for the next frame
                self.stream.seek(0)
                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
                # Wait for the next frame and then yield it
                yield self.stream.getframe()
        # When GeneratorExit or StopIteration raised, run cleanup code
        finally:
            # Stop stream recording (and set resolution)