242 lines
7.3 KiB
C++
242 lines
7.3 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <chre.h>
|
|
#include <cinttypes>
|
|
|
|
#include "chre/util/macros.h"
|
|
#include "chre/util/nanoapp/audio.h"
|
|
#include "chre/util/nanoapp/log.h"
|
|
#include "chre/util/time.h"
|
|
|
|
#define LOG_TAG "[AudioStress]"
|
|
|
|
/**
|
|
* @file
|
|
*
|
|
* This nanoapp is designed to subscribe to audio for varying durations of
|
|
* time and verify that audio data is delivered when it is expected to be.
|
|
*/
|
|
|
|
using chre::Milliseconds;
|
|
using chre::Nanoseconds;
|
|
using chre::Seconds;
|
|
|
|
namespace {
|
|
|
|
//! The required buffer size for the stress test.
|
|
constexpr Nanoseconds kBufferDuration = Nanoseconds(Seconds(2));
|
|
|
|
//! The required sample format for the stress test.
|
|
constexpr uint8_t kBufferFormat = CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM;
|
|
|
|
//! The required sample rate for the stress test.
|
|
constexpr uint32_t kBufferSampleRate = 16000;
|
|
|
|
//! The maximum amount of time that audio will not be delivered for.
|
|
constexpr Seconds kMaxAudioGap = Seconds(300);
|
|
|
|
//! The list of durations to subscribe to audio for. Even durations are for when
|
|
//! audio is enabled and odd is for when audio is disabled.
|
|
constexpr Milliseconds kStressPlan[] = {
|
|
// clang-format off
|
|
// Enabled, Disabled
|
|
Milliseconds(20000), Milliseconds(20000),
|
|
Milliseconds(30000), Milliseconds(200),
|
|
Milliseconds(10000), Milliseconds(1000),
|
|
Milliseconds(10000), Milliseconds(1999),
|
|
Milliseconds(8000), Milliseconds(60000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
Milliseconds(1000), Milliseconds(1000),
|
|
// clang-format on
|
|
};
|
|
|
|
//! The discovered audio handle found at startup.
|
|
uint32_t gAudioHandle;
|
|
|
|
//! The current position in the stress plan.
|
|
size_t gTestPosition = 0;
|
|
|
|
//! The timer handle to advance through the stress test.
|
|
uint32_t gTimerHandle;
|
|
|
|
//! Whether or not audio is currently suspended. If audio is delivered when this
|
|
//! is set to true, this is considered a test failure.
|
|
bool gAudioIsSuspended = true;
|
|
|
|
//! The timestamp of the last audio data event.
|
|
Nanoseconds gLastAudioTimestamp;
|
|
|
|
/**
|
|
* @return true when the current test phase is expecting audio data events to be
|
|
* delivered.
|
|
*/
|
|
bool audioIsExpected() {
|
|
// Even test intervals are expected to return audio events. The current test
|
|
// interval is gTestPosition - 1 so there is no need to invert the bit.
|
|
return (gTestPosition % 2);
|
|
}
|
|
|
|
/**
|
|
* Discovers an audio source to use for the stress test. The gAudioHandle will
|
|
* be set if the audio source was found.
|
|
*
|
|
* @return true if a matching source was discovered successfully.
|
|
*/
|
|
bool discoverAudioHandle() {
|
|
bool success = false;
|
|
struct chreAudioSource source;
|
|
for (uint32_t i = 0; !success && chreAudioGetSource(i, &source); i++) {
|
|
LOGI("Found audio source '%s' with %" PRIu32 "Hz %s data", source.name,
|
|
source.sampleRate, chre::getChreAudioFormatString(source.format));
|
|
LOGI(" buffer duration: [%" PRIu64 "ns, %" PRIu64 "ns]",
|
|
source.minBufferDuration, source.maxBufferDuration);
|
|
|
|
if (source.sampleRate == kBufferSampleRate &&
|
|
source.minBufferDuration <= kBufferDuration.toRawNanoseconds() &&
|
|
source.maxBufferDuration >= kBufferDuration.toRawNanoseconds() &&
|
|
source.format == kBufferFormat) {
|
|
gAudioHandle = i;
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
LOGW("Failed to find suitable audio source");
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void checkTestPassing() {
|
|
auto lastAudioDuration = Nanoseconds(chreGetTime()) - gLastAudioTimestamp;
|
|
if (lastAudioDuration > kMaxAudioGap) {
|
|
LOGE("Test fail - audio not received for %" PRIu64 "ns",
|
|
lastAudioDuration.toRawNanoseconds());
|
|
chreAbort(-1);
|
|
}
|
|
}
|
|
|
|
bool requestAudioForCurrentTestState(const Nanoseconds &testStateDuration) {
|
|
bool success = false;
|
|
LOGD("Test stage %zu", gTestPosition);
|
|
if (audioIsExpected()) {
|
|
if (!chreAudioConfigureSource(gAudioHandle, true,
|
|
kBufferDuration.toRawNanoseconds(),
|
|
kBufferDuration.toRawNanoseconds())) {
|
|
LOGE("Failed to enable audio");
|
|
} else {
|
|
LOGI("Enabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
|
|
success = true;
|
|
}
|
|
} else {
|
|
if (!chreAudioConfigureSource(0, false, 0, 0)) {
|
|
LOGE("Failed to disable audio");
|
|
} else {
|
|
LOGI("Disabled audio for %" PRIu64, testStateDuration.toRawNanoseconds());
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool advanceTestPosition() {
|
|
checkTestPassing();
|
|
gTimerHandle = chreTimerSet(kStressPlan[gTestPosition].toRawNanoseconds(),
|
|
nullptr, true /* oneShot */);
|
|
bool success = (gTimerHandle != CHRE_TIMER_INVALID);
|
|
if (!success) {
|
|
LOGE("Failed to set timer");
|
|
} else {
|
|
// Grab the duration prior to incrementing the test position.
|
|
Nanoseconds timerDuration = kStressPlan[gTestPosition++];
|
|
if (gTestPosition >= ARRAY_SIZE(kStressPlan)) {
|
|
gTestPosition = 0;
|
|
}
|
|
|
|
success = requestAudioForCurrentTestState(timerDuration);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void handleTimerEvent() {
|
|
if (!advanceTestPosition()) {
|
|
LOGE("Test fail");
|
|
}
|
|
}
|
|
|
|
void handleAudioDataEvent(const chreAudioDataEvent *audioDataEvent) {
|
|
LOGI("Handling audio data event");
|
|
gLastAudioTimestamp = Nanoseconds(audioDataEvent->timestamp);
|
|
|
|
if (gAudioIsSuspended) {
|
|
LOGE("Test fail - received audio when suspended");
|
|
} else if (!audioIsExpected()) {
|
|
LOGE("Test fail - received audio unexpectedly");
|
|
} else {
|
|
LOGI("Test passing - received audio when expected");
|
|
}
|
|
}
|
|
|
|
void handleAudioSamplingChangeEvent(
|
|
const chreAudioSourceStatusEvent *audioSourceStatusEvent) {
|
|
LOGI("Handling audio sampling change event - suspended: %d",
|
|
audioSourceStatusEvent->status.suspended);
|
|
gAudioIsSuspended = audioSourceStatusEvent->status.suspended;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool nanoappStart() {
|
|
LOGI("start");
|
|
gLastAudioTimestamp = Nanoseconds(chreGetTime());
|
|
return (discoverAudioHandle() && advanceTestPosition());
|
|
}
|
|
|
|
void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
|
|
const void *eventData) {
|
|
switch (eventType) {
|
|
case CHRE_EVENT_TIMER:
|
|
handleTimerEvent();
|
|
break;
|
|
|
|
case CHRE_EVENT_AUDIO_DATA:
|
|
handleAudioDataEvent(static_cast<const chreAudioDataEvent *>(eventData));
|
|
break;
|
|
|
|
case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
|
|
handleAudioSamplingChangeEvent(
|
|
static_cast<const chreAudioSourceStatusEvent *>(eventData));
|
|
break;
|
|
|
|
default:
|
|
LOGW("Unexpected event %" PRIu16, eventType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void nanoappEnd() {
|
|
LOGI("stop");
|
|
}
|