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

Pi camera is now persistent in StreamingCamera

parent 61beb211
Loading
Loading
Loading
Loading
+13 −7
Original line number Diff line number Diff line
@@ -106,6 +106,15 @@ class BaseCamera(object):
        # Stop worker thread
        self.stop_worker()

    def wait_for_camera(self, timeout=5):
        """Wait for camera object, with 5 second timeout."""
        timeout_time = time.time() + timeout
        while not self.camera:
            if time.time() > timeout_time:
                raise TimeoutError("Timeout waiting for camera")
            else:
                pass

    # START AND STOP WORKER THREAD

    def start_worker(self, timeout: int=5) -> bool:
@@ -131,6 +140,7 @@ class BaseCamera(object):

    def stop_worker(self, timeout: int=5) -> bool:
        """Flags worker thread for stop. Waits for thread to close, or times out."""
        logging.debug("Stopping worker thread")
        timeout_time = time.time() + timeout

        self.stop = True
@@ -200,9 +210,7 @@ class BaseCamera(object):
    def _thread(self):
        """Camera background thread."""
        self.frames_iterator = self.frames()

        # Update state
        self.state['stream_active'] = True
        logging.debug("Entering worker thread.")

        for frame in self.frames_iterator:
            self.frame = frame
@@ -217,15 +225,13 @@ class BaseCamera(object):

            try:
                if self.stop is True:
                    logging.debug("Worker thread flagged for stop.")
                    self.frames_iterator.close()
                    break

            except AttributeError:
                pass

        logging.debug("BaseCamera worker thread exiting...")
        # Reset thread
        self.thread = None
        # Destroy any stored camera object (used especially for handling Pi cameras)
        self.camera = None
        # Update state
        self.state['stream_active'] = False
 No newline at end of file
+95 −63
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ class StreamingCamera(BaseCamera):
        """Raspberry Pi camera implementation of StreamingCamera."""
        # Run BaseCamera init
        BaseCamera.__init__(self)
        # Attach to Pi camera
        self.camera = picamera.PiCamera()

        # Camera settings
        self.settings.update({
@@ -83,7 +85,11 @@ class StreamingCamera(BaseCamera):

    def close(self):
        """Close the Raspberry Pi StreamingCamera"""
        BaseCamera.close(self)  # Run BaseCamera close method
        # Run BaseCamera close method
        BaseCamera.close(self)
        # Detatch Pi camera
        if self.camera:
            self.camera.close()

    # HANDLE SETTINGS

@@ -96,7 +102,16 @@ class StreamingCamera(BaseCamera):
        """Open config_picamera.yaml file and write to camera."""
        global DEFAULT_CONFIG

        self.start_worker()
        paused_stream = False

        if not self.state['record_active']:  # If not recording a video

            #self.start_worker()

            if self.state['stream_active']:  # If stream is active
                logging.info("Pausing stream to update settings.")
                self.pause_stream_for_capture()  # Pause stream
                paused_stream = True  # Remember to unpause stream when done

            if not config_path:
                config = load_config(DEFAULT_CONFIG)
@@ -140,9 +155,16 @@ class StreamingCamera(BaseCamera):
            if 'digital_gain' in config:
                set_digital_gain(self.camera, config['digital_gain'])
            
            if paused_stream:  # If stream was paused to update settings
                logging.info("Resuming stream.")
                self.resume_stream_for_capture()

        else:
            raise Exception("Cannot update camera settings while recording is active.")

    def change_zoom(self, zoom_value: int=1) -> None:
        """Change the camera zoom, handling recentering and scaling."""
        self.start_worker()
        #self.start_worker()

        zoom_value = float(zoom_value)
        if zoom_value < 1:
@@ -165,7 +187,7 @@ class StreamingCamera(BaseCamera):

    def start_preview(self) -> bool:
        """Start the onboard GPU camera preview."""
        self.start_worker()
        #self.start_worker()

        self.camera.start_preview()
        self.state['preview_active'] = True
@@ -173,7 +195,7 @@ class StreamingCamera(BaseCamera):

    def stop_preview(self) -> bool:
        """Stop the onboard GPU camera preview."""
        self.start_worker()
        #self.start_worker()

        self.camera.stop_preview()
        self.state['preview_active'] = False
@@ -202,7 +224,7 @@ class StreamingCamera(BaseCamera):

        # Start recording method only if a current recording is not running
        if not self.state['record_active']:
            self.start_worker()
            #self.start_worker()

            # If no target is specified, store to StreamingCamera
            if not target:
@@ -319,7 +341,7 @@ class StreamingCamera(BaseCamera):
            (default 'h264')
        resize ((int, int)): Resize the captured image.
        """
        self.start_worker()
        #self.start_worker()

        # If no target is specified, store to StreamingCamera
        if not target:
@@ -367,7 +389,7 @@ class StreamingCamera(BaseCamera):
        use_video_port (bool): Capture from the video port used for streaming (lower resolution, faster)
        resize ((int, int)): Resize the captured image.
        """
        self.start_worker()
        #self.start_worker()

        if use_video_port:
            resolution = self.settings['video_resolution']
@@ -415,24 +437,28 @@ class StreamingCamera(BaseCamera):
        # Run this initialisation method
        self.initialisation()

        with picamera.PiCamera() as self.camera:
            # Let camera warm up
            time.sleep(0.1)
        self.wait_for_camera()

        # Settings config
        self.update_settings()
        # Set stream resolution
        self.camera.resolution = self.settings['video_resolution']

            # streaming
        # Create stream
        self.stream = io.BytesIO()

            # start recording on video splitter port 1
        # Start recording on video splitter port 1
        self.camera.start_recording(
            self.stream,
            format='mjpeg',
            quality=self.settings['jpeg_quality'],
            splitter_port=1)

        # Update state
        logging.debug("STREAM ACTIVE")
        self.state['stream_active'] = True

        try:  # While the iterator is not closed
            while True:
                # reset stream for next frame
                self.stream.seek(0)
@@ -441,8 +467,14 @@ class StreamingCamera(BaseCamera):
                time.sleep(1/self.settings['framerate']*0.1)
                # yield the result to be read
                frame = self.stream.getvalue()

                # ensure the size of package is right
                if len(frame) == 0:
                    pass
                else:
                    yield frame
        finally:  # When GeneratorExit or StopIteration raised, run cleanup code
            logging.debug("Stopping stream recording on port 1")
            self.camera.stop_recording(splitter_port=1)
            self.state['stream_active'] = False
            logging.debug("FRAME ITERATOR END")
+32 −44
Original line number Diff line number Diff line
@@ -26,16 +26,6 @@ success_string = """
    """


def wait_for_cam(camera, timeout=5):
    """Wait for camera object in global namespace, with 5 second timeout."""
    timeout_time = time.time() + timeout
    while not camera:
        if time.time() > timeout_time:
            raise TimeoutError("Timeout waiting for camera")
        else:
            pass


class TestCaptureMethods(unittest.TestCase):

    def test_still_capture(self):
@@ -46,7 +36,7 @@ class TestCaptureMethods(unittest.TestCase):
            for resize in [None, (640, 480)]:

                # Wait for camera
                wait_for_cam(camera)
                camera.wait_for_camera()

                # Capture to a context (auto-deletes files when done)
                with camera.capture(
@@ -114,7 +104,7 @@ class TestCaptureMethods(unittest.TestCase):
        for use_video_port in [True, False]:
            for resize in [None, (640, 480)]:
                # Wait for camera
                wait_for_cam(camera)
                camera.wait_for_camera()

                # Capture
                stream = camera.capture(
@@ -151,7 +141,7 @@ class TestUnencodedMethods(unittest.TestCase):
                print("{}, {}, {}".format(id, resize, use_video_port))

                # Wait for camera
                wait_for_cam(camera)
                camera.wait_for_camera()

                # Capture RGB array
                yuv = camera.array(
@@ -183,7 +173,7 @@ class TestUnencodedMethods(unittest.TestCase):
                print("{}, {}, {}".format(id, resize, use_video_port))

                # Wait for camera
                wait_for_cam(camera)
                camera.wait_for_camera()

                # Capture RGB array
                rgb = camera.array(
@@ -216,7 +206,7 @@ class TestRecordMethods(unittest.TestCase):
        for write_to_file in [True, False, None]:

            # Wait for camera
            wait_for_cam(camera)
            camera.wait_for_camera()

            # Start recording
            with camera.start_recording(
@@ -249,7 +239,7 @@ class TestRecordMethods(unittest.TestCase):
        global camera

        # Wait for camera
        wait_for_cam(camera)
        camera.wait_for_camera()

        # Start recording
        stream = camera.start_recording()
@@ -270,32 +260,30 @@ class TestRecordMethods(unittest.TestCase):


class TestThreadStarting(unittest.TestCase):
    def test_capture_restarting_stream(self):
    def test_restarting_stream(self):
        """Tests that a capture call restarts the camera worker thread."""
        global camera

        # Wait for camera
        wait_for_cam(camera)
        camera.wait_for_camera()

        # Force-stop the camera worker thread (should return True)
        self.assertTrue(camera.stop_worker())

        # Check Pi camera has been disconnected
        self.assertFalse(camera.camera)
        self.assertTrue(camera.camera)
        self.assertFalse(camera.thread)

        # Restart worker thread by initialising a capture
        with camera.capture(
                use_video_port=True) as stream:
        # Restart worker thread
        self.assertTrue(camera.start_worker())

            # Ensure StreamObject 'stream' has
            # a valid BytesIO object and byte string
            self.assertTrue(isinstance(stream.data, io.IOBase))
            self.assertTrue(isinstance(stream.binary, (bytes, bytearray)))
        self.assertTrue(camera.camera)
        self.assertTrue(camera.thread)
        self.assertIsInstance(camera.stream, io.IOBase)


if __name__ == '__main__':
    camera = None
    with StreamingCamera() as camera:
    camera = StreamingCamera()

    suites = [
        unittest.TestLoader().loadTestsFromTestCase(TestCaptureMethods),