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 #include <cinttypes>
18
19 #include "chre/util/macros.h"
20 #include "chre/util/nanoapp/audio.h"
21 #include "chre/util/nanoapp/log.h"
22 #include "chre/util/time.h"
23 #include "chre_api/chre.h"
24
25 #define LOG_TAG "[AudioStress]"
26
27 /**
28 * @file
29 *
30 * This nanoapp is designed to subscribe to audio for varying durations of
31 * time and verify that audio data is delivered when it is expected to be.
32 */
33
34 using chre::Milliseconds;
35 using chre::Nanoseconds;
36 using chre::Seconds;
37
38 namespace {
39
40 //! The required buffer size for the stress test.
41 constexpr Nanoseconds kBufferDuration = Nanoseconds(Seconds(2));
42
43 //! The required sample format for the stress test.
44 constexpr uint8_t kBufferFormat = CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM;
45
46 //! The required sample rate for the stress test.
47 constexpr uint32_t kBufferSampleRate = 16000;
48
49 //! The maximum amount of time that audio will not be delivered for.
50 constexpr Seconds kMaxAudioGap = Seconds(300);
51
52 //! The list of durations to subscribe to audio for. Even durations are for when
53 //! audio is enabled and odd is for when audio is disabled.
54 constexpr Milliseconds kStressPlan[] = {
55 // clang-format off
56 // Enabled, Disabled
57 Milliseconds(20000), Milliseconds(20000),
58 Milliseconds(30000), Milliseconds(200),
59 Milliseconds(10000), Milliseconds(1000),
60 Milliseconds(10000), Milliseconds(1999),
61 Milliseconds(8000), Milliseconds(60000),
62 Milliseconds(1000), Milliseconds(1000),
63 Milliseconds(1000), Milliseconds(1000),
64 Milliseconds(1000), Milliseconds(1000),
65 Milliseconds(1000), Milliseconds(1000),
66 Milliseconds(1000), Milliseconds(1000),
67 Milliseconds(1000), Milliseconds(1000),
68 Milliseconds(1000), Milliseconds(1000),
69 Milliseconds(1000), Milliseconds(1000),
70 // clang-format on
71 };
72
73 //! The discovered audio handle found at startup.
74 uint32_t gAudioHandle;
75
76 //! The current position in the stress plan.
77 size_t gTestPosition = 0;
78
79 //! The timer handle to advance through the stress test.
80 uint32_t gTimerHandle;
81
82 //! Whether or not audio is currently suspended. If audio is delivered when this
83 //! is set to true, this is considered a test failure.
84 bool gAudioIsSuspended = true;
85
86 //! The timestamp of the last audio data event.
87 Nanoseconds gLastAudioTimestamp;
88
89 /**
90 * @return true when the current test phase is expecting audio data events to be
91 * delivered.
92 */
audioIsExpected()93 bool audioIsExpected() {
94 // Even test intervals are expected to return audio events. The current test
95 // interval is gTestPosition - 1 so there is no need to invert the bit.
96 return (gTestPosition % 2);
97 }
98
99 /**
100 * Discovers an audio source to use for the stress test. The gAudioHandle will
101 * be set if the audio source was found.
102 *
103 * @return true if a matching source was discovered successfully.
104 */
discoverAudioHandle()105 bool discoverAudioHandle() {
106 bool success = false;
107 struct chreAudioSource source;
108 for (uint32_t i = 0; !success && chreAudioGetSource(i, &source); i++) {
109 LOGI("Found audio source '%s' with %" PRIu32 "Hz %s data", source.name,
110 source.sampleRate, chre::getChreAudioFormatString(source.format));
111 LOGI(" buffer duration: [%" PRIu64 "ns, %" PRIu64 "ns]",
112 source.minBufferDuration, source.maxBufferDuration);
113
114 if (source.sampleRate == kBufferSampleRate &&
115 source.minBufferDuration <= kBufferDuration.toRawNanoseconds() &&
116 source.maxBufferDuration >= kBufferDuration.toRawNanoseconds() &&
117 source.format == kBufferFormat) {
118 gAudioHandle = i;
119 success = true;
120 }
121 }
122
123 if (!success) {
124 LOGW("Failed to find suitable audio source");
125 }
126
127 return success;
128 }
129
checkTestPassing()130 void checkTestPassing() {
131 auto lastAudioDuration = Nanoseconds(chreGetTime()) - gLastAudioTimestamp;
132 if (lastAudioDuration > kMaxAudioGap) {
133 LOGE("Test fail - audio not received for %" PRIu64 "ns",
134 lastAudioDuration.toRawNanoseconds());
135 chreAbort(-1);
136 }
137 }
138
requestAudioForCurrentTestState(const Nanoseconds & testStateDuration)139 bool requestAudioForCurrentTestState(const Nanoseconds &testStateDuration) {
140 bool success = false;
141 LOGD("Test stage %zu", gTestPosition);
142 if (audioIsExpected()) {
143 if (!chreAudioConfigureSource(gAudioHandle, true,
144 kBufferDuration.toRawNanoseconds(),
145 kBufferDuration.toRawNanoseconds())) {
146 LOGE("Failed to enable audio");
147 } else {
148 LOGI("Enabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
149 success = true;
150 }
151 } else {
152 if (!chreAudioConfigureSource(0, false, 0, 0)) {
153 LOGE("Failed to disable audio");
154 } else {
155 LOGI("Disabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
156 success = true;
157 }
158 }
159
160 return success;
161 }
162
advanceTestPosition()163 bool advanceTestPosition() {
164 checkTestPassing();
165 gTimerHandle = chreTimerSet(kStressPlan[gTestPosition].toRawNanoseconds(),
166 nullptr, true /* oneShot */);
167 bool success = (gTimerHandle != CHRE_TIMER_INVALID);
168 if (!success) {
169 LOGE("Failed to set timer");
170 } else {
171 // Grab the duration prior to incrementing the test position.
172 Nanoseconds timerDuration = kStressPlan[gTestPosition++];
173 if (gTestPosition >= ARRAY_SIZE(kStressPlan)) {
174 gTestPosition = 0;
175 }
176
177 success = requestAudioForCurrentTestState(timerDuration);
178 }
179
180 return success;
181 }
182
handleTimerEvent()183 void handleTimerEvent() {
184 if (!advanceTestPosition()) {
185 LOGE("Test fail");
186 }
187 }
188
handleAudioDataEvent(const chreAudioDataEvent * audioDataEvent)189 void handleAudioDataEvent(const chreAudioDataEvent *audioDataEvent) {
190 LOGI("Handling audio data event");
191 gLastAudioTimestamp = Nanoseconds(audioDataEvent->timestamp);
192
193 if (gAudioIsSuspended) {
194 LOGE("Test fail - received audio when suspended");
195 } else if (!audioIsExpected()) {
196 LOGE("Test fail - received audio unexpectedly");
197 } else {
198 LOGI("Test passing - received audio when expected");
199 }
200 }
201
handleAudioSamplingChangeEvent(const chreAudioSourceStatusEvent * audioSourceStatusEvent)202 void handleAudioSamplingChangeEvent(
203 const chreAudioSourceStatusEvent *audioSourceStatusEvent) {
204 LOGI("Handling audio sampling change event - suspended: %d",
205 audioSourceStatusEvent->status.suspended);
206 gAudioIsSuspended = audioSourceStatusEvent->status.suspended;
207 }
208
209 } // namespace
210
nanoappStart()211 bool nanoappStart() {
212 LOGI("start");
213 gLastAudioTimestamp = Nanoseconds(chreGetTime());
214 return (discoverAudioHandle() && advanceTestPosition());
215 }
216
nanoappHandleEvent(uint32_t senderInstanceId,uint16_t eventType,const void * eventData)217 void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
218 const void *eventData) {
219 switch (eventType) {
220 case CHRE_EVENT_TIMER:
221 handleTimerEvent();
222 break;
223
224 case CHRE_EVENT_AUDIO_DATA:
225 handleAudioDataEvent(static_cast<const chreAudioDataEvent *>(eventData));
226 break;
227
228 case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
229 handleAudioSamplingChangeEvent(
230 static_cast<const chreAudioSourceStatusEvent *>(eventData));
231 break;
232
233 default:
234 LOGW("Unexpected event %" PRIu16, eventType);
235 break;
236 }
237 }
238
nanoappEnd()239 void nanoappEnd() {
240 LOGI("stop");
241 }
242