1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // Unit tests for Isochronous Clock Model
18
19 #include <math.h>
20 #include <stdlib.h>
21
22
23 #include <aaudio/AAudio.h>
24 #include <audio_utils/clock.h>
25 #include <client/IsochronousClockModel.h>
26 #include <gtest/gtest.h>
27
28 using namespace aaudio;
29
30 // We can use arbitrary values here because we are not opening a real audio stream.
31 #define SAMPLE_RATE 48000
32 #define HW_FRAMES_PER_BURST 48
33 // Sometimes we need a (double) value to avoid misguided Build warnings.
34 #define NANOS_PER_BURST ((double) NANOS_PER_SECOND * HW_FRAMES_PER_BURST / SAMPLE_RATE)
35
36 class ClockModelTestFixture: public ::testing::Test {
37 public:
ClockModelTestFixture()38 ClockModelTestFixture() {
39 }
40
SetUp()41 void SetUp() {
42 model.setSampleRate(SAMPLE_RATE);
43 model.setFramesPerBurst(HW_FRAMES_PER_BURST);
44 }
45
TearDown()46 void TearDown() {
47 }
48
~ClockModelTestFixture()49 ~ClockModelTestFixture() {
50 // cleanup any pending stuff, but no exceptions allowed
51 }
52
53 /** Test processing of timestamps when the hardware may be slightly off from
54 * the expected sample rate.
55 * @param hardwareFramesPerSecond sample rate that may be slightly off
56 * @param numLoops number of iterations
57 * @param hardwarePauseTime number of seconds to jump forward at halfway point
58 */
checkDriftingClock(double hardwareFramesPerSecond,int numLoops,double hardwarePauseTime=0.0)59 void checkDriftingClock(double hardwareFramesPerSecond,
60 int numLoops,
61 double hardwarePauseTime = 0.0) {
62 int checksToSkip = 0;
63 const int64_t startTimeNanos = 500000000; // arbitrary
64 int64_t jumpOffsetNanos = 0;
65
66 srand48(123456); // arbitrary seed for repeatable test results
67 model.start(startTimeNanos);
68
69 const int64_t startPositionFrames = HW_FRAMES_PER_BURST; // hardware
70 // arbitrary time for first burst
71 const int64_t markerTime = startTimeNanos + NANOS_PER_MILLISECOND
72 + (200 * NANOS_PER_MICROSECOND);
73
74 // Should set initial marker.
75 model.processTimestamp(startPositionFrames, markerTime);
76 ASSERT_EQ(startPositionFrames, model.convertTimeToPosition(markerTime));
77
78 double elapsedTimeSeconds = 0.0;
79 for (int i = 0; i < numLoops; i++) {
80 // Calculate random delay over several bursts.
81 const double timeDelaySeconds = 10.0 * drand48() * NANOS_PER_BURST / NANOS_PER_SECOND;
82 elapsedTimeSeconds += timeDelaySeconds;
83 const int64_t elapsedTimeNanos = (int64_t)(elapsedTimeSeconds * NANOS_PER_SECOND);
84 const int64_t currentTimeNanos = startTimeNanos + elapsedTimeNanos;
85 // Simulate DSP running at the specified rate.
86 const int64_t currentTimeFrames = startPositionFrames +
87 (int64_t)(hardwareFramesPerSecond * elapsedTimeSeconds);
88 const int64_t numBursts = currentTimeFrames / HW_FRAMES_PER_BURST;
89 const int64_t hardwarePosition = startPositionFrames
90 + (numBursts * HW_FRAMES_PER_BURST);
91
92 // Simulate a pause in the DSP where the position freezes for a length of time.
93 if (i == numLoops / 2) {
94 jumpOffsetNanos = (int64_t)(hardwarePauseTime * NANOS_PER_SECOND);
95 checksToSkip = 5; // Give the model some time to catch up.
96 }
97
98 // Apply drifting timestamp. Add a random time to simulate the
99 // random sampling of the clock that occurs when polling the DSP clock.
100 int64_t sampledTimeNanos = (int64_t) (currentTimeNanos
101 + jumpOffsetNanos
102 + (drand48() * NANOS_PER_BURST));
103 model.processTimestamp(hardwarePosition, sampledTimeNanos);
104
105 if (checksToSkip > 0) {
106 checksToSkip--;
107 } else {
108 // When the model is drifting it may be pushed forward or backward.
109 const int64_t modelPosition = model.convertTimeToPosition(sampledTimeNanos);
110 if (hardwareFramesPerSecond >= SAMPLE_RATE) { // fast hardware
111 ASSERT_LE(hardwarePosition - HW_FRAMES_PER_BURST, modelPosition);
112 ASSERT_GE(hardwarePosition + HW_FRAMES_PER_BURST, modelPosition);
113 } else {
114 // Slow hardware. If this fails then the model may be drifting
115 // forward in time too slowly. Increase kDriftNanos.
116 ASSERT_LE(hardwarePosition, modelPosition);
117 ASSERT_GE(hardwarePosition + (2 * HW_FRAMES_PER_BURST), modelPosition);
118 }
119 }
120 }
121 }
122
123 IsochronousClockModel model;
124 };
125
126 // Check default setup.
TEST_F(ClockModelTestFixture,clock_setup)127 TEST_F(ClockModelTestFixture, clock_setup) {
128 ASSERT_EQ(SAMPLE_RATE, model.getSampleRate());
129 ASSERT_EQ(HW_FRAMES_PER_BURST, model.getFramesPerBurst());
130 }
131
132 // Test delta calculations.
TEST_F(ClockModelTestFixture,clock_deltas)133 TEST_F(ClockModelTestFixture, clock_deltas) {
134 int64_t position = model.convertDeltaTimeToPosition(NANOS_PER_SECOND);
135 ASSERT_EQ(SAMPLE_RATE, position);
136
137 // Deltas are not quantized.
138 // Compare time to the equivalent position in frames.
139 constexpr int64_t kNanosPerBurst = HW_FRAMES_PER_BURST * NANOS_PER_SECOND / SAMPLE_RATE;
140 position = model.convertDeltaTimeToPosition(NANOS_PER_SECOND + (kNanosPerBurst / 2));
141 ASSERT_EQ(SAMPLE_RATE + (HW_FRAMES_PER_BURST / 2), position);
142
143 int64_t time = model.convertDeltaPositionToTime(SAMPLE_RATE);
144 ASSERT_EQ(NANOS_PER_SECOND, time);
145
146 // Compare position in frames to the equivalent time.
147 time = model.convertDeltaPositionToTime(SAMPLE_RATE + (HW_FRAMES_PER_BURST / 2));
148 ASSERT_EQ(NANOS_PER_SECOND + (kNanosPerBurst / 2), time);
149 }
150
151 // start() should force the internal markers
TEST_F(ClockModelTestFixture,clock_start)152 TEST_F(ClockModelTestFixture, clock_start) {
153 const int64_t startTime = 100000;
154 model.start(startTime);
155
156 int64_t position = model.convertTimeToPosition(startTime);
157 EXPECT_EQ(0, position);
158
159 int64_t time = model.convertPositionToTime(position);
160 EXPECT_EQ(startTime, time);
161
162 time = startTime + (500 * NANOS_PER_MICROSECOND);
163 position = model.convertTimeToPosition(time);
164 EXPECT_EQ(0, position);
165 }
166
167 // timestamps moves the window if outside the bounds
TEST_F(ClockModelTestFixture,clock_timestamp)168 TEST_F(ClockModelTestFixture, clock_timestamp) {
169 const int64_t startTime = 100000000;
170 model.start(startTime);
171
172 const int64_t position = HW_FRAMES_PER_BURST; // hardware
173 int64_t markerTime = startTime + NANOS_PER_MILLISECOND + (200 * NANOS_PER_MICROSECOND);
174
175 // Should set marker.
176 model.processTimestamp(position, markerTime);
177 EXPECT_EQ(position, model.convertTimeToPosition(markerTime));
178
179 // convertTimeToPosition rounds down
180 EXPECT_EQ(position, model.convertTimeToPosition(markerTime + (73 * NANOS_PER_MICROSECOND)));
181
182 // convertPositionToTime rounds up
183 EXPECT_EQ(markerTime + (int64_t)NANOS_PER_BURST, model.convertPositionToTime(position + 17));
184 }
185
186 #define NUM_LOOPS_DRIFT 200000
187
TEST_F(ClockModelTestFixture,clock_no_drift)188 TEST_F(ClockModelTestFixture, clock_no_drift) {
189 checkDriftingClock(SAMPLE_RATE, NUM_LOOPS_DRIFT);
190 }
191
192 // Test drifting hardware clocks.
193 // It is unlikely that real hardware would be off by more than this amount.
194
195 // Test a slow clock. This will cause the times to be later than expected.
196 // This will push the clock model window forward and cause it to drift.
TEST_F(ClockModelTestFixture,clock_slow_drift)197 TEST_F(ClockModelTestFixture, clock_slow_drift) {
198 checkDriftingClock(0.99998 * SAMPLE_RATE, NUM_LOOPS_DRIFT);
199 }
200
201 // Test a fast hardware clock. This will cause the times to be earlier
202 // than expected. This will cause the clock model to jump backwards quickly.
TEST_F(ClockModelTestFixture,clock_fast_drift)203 TEST_F(ClockModelTestFixture, clock_fast_drift) {
204 checkDriftingClock(1.00002 * SAMPLE_RATE, NUM_LOOPS_DRIFT);
205 }
206
207 // Simulate a pause in the DSP, which can occur if the DSP reroutes the audio.
TEST_F(ClockModelTestFixture,clock_jump_forward_500)208 TEST_F(ClockModelTestFixture, clock_jump_forward_500) {
209 checkDriftingClock(SAMPLE_RATE, NUM_LOOPS_DRIFT, 0.500);
210 }
211