1# Copyright 2014 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""CameraITS test to measure jitter in camera timestamps."""
15
16import logging
17import os.path
18
19import matplotlib
20from matplotlib import pylab
21from mobly import test_runner
22
23import its_base_test
24import camera_properties_utils
25import capture_request_utils
26import its_session_utils
27
28_NS_TO_MS = 1.0E-6
29_NAME = os.path.basename(__file__).split('.')[0]
30_NUM_FRAMES = 50
31_START_FRAME = 2  # 1 frame delay to allow faster latency to 1st frame
32_TEST_FPS = 30  # frames per second
33# PASS/FAIL thresholds
34_MIN_AVG_FRAME_DELTA = 30  # at least 30ms delta between frames
35_MAX_INIT_FRAME_DELTA = 100  # no more than 100ms between first 2 frames
36_MAX_VAR_FRAME_DELTA = 0.01  # variance of frame deltas
37_MAX_FRAME_DELTA_JITTER = 0.3  # max ms gap from the average frame delta
38
39
40class JitterTest(its_base_test.ItsBaseTest):
41  """Measure jitter in camera timestamps."""
42
43  def test_jitter(self):
44    with its_session_utils.ItsSession(
45        device_id=self.dut.serial,
46        camera_id=self.camera_id,
47        hidden_physical_id=self.hidden_physical_id) as cam:
48      props = cam.get_camera_properties()
49      props = cam.override_with_hidden_physical_camera_props(props)
50      camera_properties_utils.skip_unless(
51          camera_properties_utils.manual_sensor(props) and
52          camera_properties_utils.sensor_fusion(props))
53
54      req, fmt = capture_request_utils.get_fastest_manual_capture_settings(
55          props)
56      req['android.control.aeTargetFpsRange'] = [_TEST_FPS, _TEST_FPS]
57      caps = cam.do_capture([req] * _NUM_FRAMES, [fmt])
58
59      # Log the millisecond delta between the start of each exposure
60      tstamps = [c['metadata']['android.sensor.timestamp'] for c in caps]
61      if (tstamps[1]-tstamps[0])*_NS_TO_MS > _MAX_INIT_FRAME_DELTA:
62        raise AssertionError('Initial frame timestamp delta too great! '
63                             f'tstamp[1]: {tstamps[1]}ms, '
64                             f'tstamp[0]: {tstamps[0]}ms, '
65                             f'ATOL: {_MAX_INIT_FRAME_DELTA}ms')
66      deltas = [
67          tstamps[i] - tstamps[i-1] for i in range(_START_FRAME, len(tstamps))
68      ]
69      deltas_ms = [d * _NS_TO_MS for d in deltas]
70      avg = sum(deltas_ms) / len(deltas_ms)
71      var = sum([d * d for d in deltas_ms]) / len(deltas_ms) - avg * avg
72      range0 = min(deltas_ms) - avg
73      range1 = max(deltas_ms) - avg
74
75      logging.debug('Average: %s', avg)
76      logging.debug('Variance: %s', var)
77      logging.debug('Jitter range: %s to %s', range0, range1)
78
79      # Draw a plot.
80      pylab.figure()
81      pylab.plot(range(len(deltas_ms)), deltas_ms, '-bo')
82      pylab.title(_NAME)
83      pylab.xlabel('frame number')
84      pylab.ylabel('jitter (ms)')
85      name_with_log_path = os.path.join(self.log_path, _NAME)
86      matplotlib.pyplot.savefig(f'{name_with_log_path}_deltas.png')
87
88      # Test for pass/fail.
89      if avg <= _MIN_AVG_FRAME_DELTA:
90        raise AssertionError(
91            f'avg: {avg:.4f}ms, ATOL: {_MIN_AVG_FRAME_DELTA}ms'
92        )
93      if var >= _MAX_VAR_FRAME_DELTA:
94        raise AssertionError(
95            f'var: {var:.4f}ms, ATOL: {_MAX_VAR_FRAME_DELTA}ms'
96        )
97      if (abs(range0) >= _MAX_FRAME_DELTA_JITTER or
98          abs(range1) >= _MAX_FRAME_DELTA_JITTER):
99        raise AssertionError(
100            f'range0: {range0:.4f}ms, range1: {range1:.4f}ms, '
101            f'ATOL: {_MAX_FRAME_DELTA_JITTER}'
102        )
103
104
105if __name__ == '__main__':
106  test_runner.main()
107