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 #define NANOS_PER_BURST (NANOS_PER_SECOND * HW_FRAMES_PER_BURST / SAMPLE_RATE)
34
35 class ClockModelTestFixture: public ::testing::Test {
36 public:
ClockModelTestFixture()37 ClockModelTestFixture() {
38 }
39
SetUp()40 void SetUp() {
41 model.setSampleRate(SAMPLE_RATE);
42 model.setFramesPerBurst(HW_FRAMES_PER_BURST);
43 }
44
TearDown()45 void TearDown() {
46 }
47
~ClockModelTestFixture()48 ~ClockModelTestFixture() {
49 // cleanup any pending stuff, but no exceptions allowed
50 }
51
52 // Test processing of timestamps when the hardware may be slightly off from
53 // the expected sample rate.
checkDriftingClock(double hardwareFramesPerSecond,int numLoops)54 void checkDriftingClock(double hardwareFramesPerSecond, int numLoops) {
55 const int64_t startTimeNanos = 500000000; // arbitrary
56 model.start(startTimeNanos);
57
58 const int64_t startPositionFrames = HW_FRAMES_PER_BURST; // hardware
59 // arbitrary time for first burst
60 const int64_t markerTime = startTimeNanos + NANOS_PER_MILLISECOND
61 + (200 * NANOS_PER_MICROSECOND);
62
63 // Should set initial marker.
64 model.processTimestamp(startPositionFrames, markerTime);
65 ASSERT_EQ(startPositionFrames, model.convertTimeToPosition(markerTime));
66
67 double elapsedTimeSeconds = startTimeNanos / (double) NANOS_PER_SECOND;
68 for (int i = 0; i < numLoops; i++) {
69 // Calculate random delay over several bursts.
70 const double timeDelaySeconds = 10.0 * drand48() * NANOS_PER_BURST / NANOS_PER_SECOND;
71 elapsedTimeSeconds += timeDelaySeconds;
72 const int64_t elapsedTimeNanos = (int64_t)(elapsedTimeSeconds * NANOS_PER_SECOND);
73 const int64_t currentTimeNanos = startTimeNanos + elapsedTimeNanos;
74 // Simulate DSP running at the specified rate.
75 const int64_t currentTimeFrames = startPositionFrames +
76 (int64_t)(hardwareFramesPerSecond * elapsedTimeSeconds);
77 const int64_t numBursts = currentTimeFrames / HW_FRAMES_PER_BURST;
78 const int64_t alignedPosition = startPositionFrames + (numBursts * HW_FRAMES_PER_BURST);
79
80 // Apply drifting timestamp.
81 model.processTimestamp(alignedPosition, currentTimeNanos);
82
83 ASSERT_EQ(alignedPosition, model.convertTimeToPosition(currentTimeNanos));
84 }
85 }
86
87 IsochronousClockModel model;
88 };
89
90 // Check default setup.
TEST_F(ClockModelTestFixture,clock_setup)91 TEST_F(ClockModelTestFixture, clock_setup) {
92 ASSERT_EQ(SAMPLE_RATE, model.getSampleRate());
93 ASSERT_EQ(HW_FRAMES_PER_BURST, model.getFramesPerBurst());
94 }
95
96 // Test delta calculations.
TEST_F(ClockModelTestFixture,clock_deltas)97 TEST_F(ClockModelTestFixture, clock_deltas) {
98 int64_t position = model.convertDeltaTimeToPosition(NANOS_PER_SECOND);
99 ASSERT_EQ(SAMPLE_RATE, position);
100
101 // Deltas are not quantized.
102 // Compare time to the equivalent position in frames.
103 constexpr int64_t kNanosPerBurst = HW_FRAMES_PER_BURST * NANOS_PER_SECOND / SAMPLE_RATE;
104 position = model.convertDeltaTimeToPosition(NANOS_PER_SECOND + (kNanosPerBurst / 2));
105 ASSERT_EQ(SAMPLE_RATE + (HW_FRAMES_PER_BURST / 2), position);
106
107 int64_t time = model.convertDeltaPositionToTime(SAMPLE_RATE);
108 ASSERT_EQ(NANOS_PER_SECOND, time);
109
110 // Compare position in frames to the equivalent time.
111 time = model.convertDeltaPositionToTime(SAMPLE_RATE + (HW_FRAMES_PER_BURST / 2));
112 ASSERT_EQ(NANOS_PER_SECOND + (kNanosPerBurst / 2), time);
113 }
114
115 // start() should force the internal markers
TEST_F(ClockModelTestFixture,clock_start)116 TEST_F(ClockModelTestFixture, clock_start) {
117 const int64_t startTime = 100000;
118 model.start(startTime);
119
120 int64_t position = model.convertTimeToPosition(startTime);
121 EXPECT_EQ(0, position);
122
123 int64_t time = model.convertPositionToTime(position);
124 EXPECT_EQ(startTime, time);
125
126 time = startTime + (500 * NANOS_PER_MICROSECOND);
127 position = model.convertTimeToPosition(time);
128 EXPECT_EQ(0, position);
129 }
130
131 // timestamps moves the window if outside the bounds
TEST_F(ClockModelTestFixture,clock_timestamp)132 TEST_F(ClockModelTestFixture, clock_timestamp) {
133 const int64_t startTime = 100000000;
134 model.start(startTime);
135
136 const int64_t position = HW_FRAMES_PER_BURST; // hardware
137 int64_t markerTime = startTime + NANOS_PER_MILLISECOND + (200 * NANOS_PER_MICROSECOND);
138
139 // Should set marker.
140 model.processTimestamp(position, markerTime);
141 EXPECT_EQ(position, model.convertTimeToPosition(markerTime));
142
143 // convertTimeToPosition rounds down
144 EXPECT_EQ(position, model.convertTimeToPosition(markerTime + (73 * NANOS_PER_MICROSECOND)));
145
146 // convertPositionToTime rounds up
147 EXPECT_EQ(markerTime + NANOS_PER_BURST, model.convertPositionToTime(position + 17));
148 }
149
150 #define NUM_LOOPS_DRIFT 10000
151
152 // test nudging the window by using a drifting HW clock
TEST_F(ClockModelTestFixture,clock_no_drift)153 TEST_F(ClockModelTestFixture, clock_no_drift) {
154 checkDriftingClock(SAMPLE_RATE, NUM_LOOPS_DRIFT);
155 }
156
157 // These slow drift rates caused errors when I disabled the code that handles
158 // drifting in the clock model. So I think the test is valid.
159 // It is unlikely that real hardware would be off by more than this amount.
TEST_F(ClockModelTestFixture,clock_slow_drift)160 TEST_F(ClockModelTestFixture, clock_slow_drift) {
161 checkDriftingClock(0.998 * SAMPLE_RATE, NUM_LOOPS_DRIFT);
162 }
163
TEST_F(ClockModelTestFixture,clock_fast_drift)164 TEST_F(ClockModelTestFixture, clock_fast_drift) {
165 checkDriftingClock(1.002 * SAMPLE_RATE, NUM_LOOPS_DRIFT);
166 }