diff --git a/examples/timestamp_capture.py b/examples/timestamp_capture.py new file mode 100644 index 00000000..13da96bc --- /dev/null +++ b/examples/timestamp_capture.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 + +import time + +from picamera2 import Picamera2 + + +def check_request_timestamp(request, check_time): + md = request.get_metadata() + # 'SensorTimestamp' is when the first pixel was read out, so it started being + # exposed 'ExposureTime' earlier. + exposure_start_time = md['SensorTimestamp'] - 1000 * md['ExposureTime'] + if exposure_start_time < check_time: + print("ERROR: request captured too early by", check_time - exposure_start_time, "nanoseconds") + + +picam2 = Picamera2() +picam2.start() + +time.sleep(1) + +# Capture a request that is guaranteed not to have started being exposed before +# we make the request. Do it 30 times as a test. Note that this kind of thing is +# likely cut the apparent framerate because of all the stopping and waiting for sync. + +for _ in range(30): + check_time = time.monotonic_ns() + request = picam2.capture_request(flush=True) + check_request_timestamp(request, check_time) + request.release() + +# Capture a request where nothing started being exposed until 1/2s in the +# future. Do this 10 times as a test. + +for _ in range(10): + check_time = time.monotonic_ns() + 5e8 + request = picam2.capture_request(flush=check_time) + check_request_timestamp(request, check_time) + request.release() + +# Let's do this last test again, but using asynchronous operation. + +for _ in range(10): + check_time = time.monotonic_ns() + 5e8 + job = picam2.capture_request(flush=check_time, wait=False) + # Now we can do other stuff while waiting for the capture. + request = picam2.wait(job) + check_request_timestamp(request, check_time) + request.release() diff --git a/picamera2/picamera2.py b/picamera2/picamera2.py index c60d59d1..5f768a21 100644 --- a/picamera2/picamera2.py +++ b/picamera2/picamera2.py @@ -1309,12 +1309,24 @@ def set_frame_drops_(self, num_frames): return (True, None) def drop_frames_(self): - if not self.completed_requests: - return (False, None) - if self._frame_drops == 0: + while self.completed_requests: + if self._frame_drops == 0: + return (True, None) + self.completed_requests.pop(0).release() + self._frame_drops -= 1 + return (False, None) + + def wait_for_timestamp_(self, timestamp_ns): + # No wait requested. This function in the job is done. + if not timestamp_ns: return (True, None) - self.completed_requests.pop(0).release() - self._frame_drops -= 1 + while self.completed_requests: + # Check if frame started being exposed after the timestamp. + md = self.completed_requests[0].get_metadata() + frame_timestamp_ns = md['SensorTimestamp'] - 1000 * md['ExposureTime'] + if frame_timestamp_ns >= timestamp_ns: + return (True, None) + self.completed_requests.pop(0).release() return (False, None) def drop_frames(self, num_frames, wait=None, signal_function=None): @@ -1413,13 +1425,17 @@ def capture_request_(self): return (False, None) return (True, self.completed_requests.pop(0)) - def capture_request(self, wait=None, signal_function=None): + def capture_request(self, wait=None, signal_function=None, flush=None): """Fetch the next completed request from the camera system. You will be holding a reference to this request so you must release it again to return it to the camera system. """ - functions = [self.capture_request_] + # flush will be the timestamp in ns that we wait for (if any) + if flush is True: + flush = time.monotonic_ns() + functions = [partial(self.wait_for_timestamp_, flush), + self.capture_request_] return self.dispatch_functions(functions, wait, signal_function) def switch_mode_capture_request_and_stop(self, camera_config, wait=None, signal_function=None): diff --git a/tests/test_list.txt b/tests/test_list.txt index 5dfda496..06b39af0 100644 --- a/tests/test_list.txt +++ b/tests/test_list.txt @@ -37,6 +37,7 @@ examples/still_capture_with_config.py examples/still_during_video.py examples/switch_mode.py examples/switch_mode_2.py +examples/timestamp_capture.py examples/title_bar.py examples/tuning_file.py examples/video_with_config.py