/*
* 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[] = {
// 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),
};
//! 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");
}