• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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