blob: bd76f4d6930f58b0a90cf2d42304efe0cbeb7b9d [file] [log] [blame]
/*
* 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 <general_test/basic_audio_test.h>
#include <shared/send_message.h>
#include <shared/time_util.h>
using nanoapp_testing::kOneSecondInNanoseconds;
using nanoapp_testing::sendFatalFailureToHost;
using nanoapp_testing::sendSuccessToHost;
namespace general_test {
namespace {
//! This is a reasonably high limit on the number of audio sources that a system
//! would expose. Use this to verify that there are no gaps in the source
//! handles.
constexpr uint32_t kMaxAudioSources = 128;
//! This is a reasonably high limit on the sample rate for a source that the
//! system would expose. Sampling rates above 96kHz are likely to be too high
//! for always-on low-power use-cases. Yes, this omits 192kHz, but that is
//! generally reserved for professional audio/recording and mixing applications.
//! Even 96kHz is a stretch, but capping it here allows room to grow. Expected
//! values are more like 16kHz.
constexpr uint32_t kMaxAudioSampleRate = 96000;
//! Provide a floor for the sampling rate of an audio source that the system
//! would expose. Nyquist theorem dictates that the maximum frequency that can
//! be reproduced from given sequence of samples is equal to half that of the
//! sampling rate. This sets a lower bound to try to detect bugs or glitches.
constexpr uint32_t kMinAudioSampleRate = 4000;
//! Provide a floor for buffer duration. This ensures that at the maximum
//! sample rate possible, a minimum number of samples will be delivered in
//! a batch.
constexpr uint64_t kMinBufferDuration =
(kOneSecondInNanoseconds / kMaxAudioSampleRate) * 10;
//! Provide a ceiling for the maximum buffer duration. This is to catch buggy
//! descriptors of audio sources who expose very long buffers of data which are
//! not practical for always-on, low-power use-cases.
constexpr uint64_t kMaxBufferDuration = kOneSecondInNanoseconds * 120;
//! While a variety of sample rates are supported, for the purposes of basic
//! audio data validation, we buffer about 4 seconds worth of PCM audio data
//! sampled at 16KHz.
constexpr uint32_t kRequiredSampleRate = 16000;
/**
* @return true if the character is ASCII printable.
*/
bool isAsciiPrintable(char c) {
// A simple enough test to verify that a character is printable. These
// constants can be verified by reviewing an ASCII chart. All printable
// characters that we care about for CHRE lie between these two bounds and are
// contiguous.
return (c >= ' ' && c <= '~');
}
/**
* @return true if the supplied string is printable, null-terminated and not
* longer than the supplied length (including null-terminator).
*/
bool verifyStringWithLength(const char *str, size_t length) {
bool nullTerminatorFound = false;
bool isPrintable = true;
for (size_t i = 0; i < length; i++) {
if (str[i] == '\0') {
nullTerminatorFound = true;
break;
} else if (!isAsciiPrintable(str[i])) {
isPrintable = false;
break;
}
}
return (isPrintable && nullTerminatorFound);
}
/**
* Validates the fields of a chreAudioSource provided by the framework and posts
* a failure if the source descriptor is malformed.
*
* @return true if the source was valid.
*/
bool validateAudioSource(uint32_t handle,
const struct chreAudioSource &source) {
bool valid = false;
if (!verifyStringWithLength(source.name, CHRE_AUDIO_SOURCE_NAME_MAX_SIZE)) {
sendFatalFailureToHost("Invalid audio source name for handle ", &handle);
} else if (source.sampleRate > kMaxAudioSampleRate ||
source.sampleRate < kMinAudioSampleRate) {
sendFatalFailureToHost("Invalid audio sample rate for handle ", &handle);
} else if (source.minBufferDuration < kMinBufferDuration ||
source.minBufferDuration > kMaxBufferDuration) {
sendFatalFailureToHost("Invalid min buffer duration for handle ", &handle);
} else if (source.maxBufferDuration < kMinBufferDuration ||
source.maxBufferDuration > kMaxBufferDuration) {
sendFatalFailureToHost("Invalid max buffer duration for handle ", &handle);
} else if (source.format != CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW &&
source.format != CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM) {
sendFatalFailureToHost("Invalid audio format for handle ", &handle);
} else {
valid = true;
}
return valid;
}
bool validateMinimumAudioSource(const struct chreAudioSource &source) {
// CHQTS requires a 16kHz, PCM-format, 2 second buffer.
constexpr uint64_t kRequiredBufferDuration = 2 * kOneSecondInNanoseconds;
// Ensure that the minimum buffer size is less than or equal to the required
// size.
return (source.sampleRate == kRequiredSampleRate &&
source.minBufferDuration <= kRequiredBufferDuration &&
source.maxBufferDuration >= kRequiredBufferDuration &&
source.format == CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM);
}
/**
* Attempts to query for all audio sources up to kMaxAudioSources and posts a
* failure if a gap is found in the handles or the provided descriptor is
* invalid.
*/
void validateAudioSources() {
uint32_t validHandleCount = 0;
bool previousSourceFound = true;
bool minimumRequirementMet = false;
for (uint32_t handle = 0; handle < kMaxAudioSources; handle++) {
struct chreAudioSource audioSource;
bool sourceFound = chreAudioGetSource(handle, &audioSource);
if (sourceFound) {
validHandleCount++;
if (!previousSourceFound) {
sendFatalFailureToHost("Gap detected in audio handles at ", &handle);
} else {
bool valid = validateAudioSource(handle, audioSource);
if (valid && !minimumRequirementMet) {
minimumRequirementMet = validateMinimumAudioSource(audioSource);
}
}
}
previousSourceFound = sourceFound;
}
if (validHandleCount > 0) {
if (!minimumRequirementMet) {
sendFatalFailureToHost(
"Failed to meet minimum audio source requirements");
}
if (validHandleCount == kMaxAudioSources) {
sendFatalFailureToHost("System is reporting too many audio sources");
}
}
}
/**
* Attempts to request audio data from the default microphone handle (0),
* posts a failure if the data request failed
*/
void requestAudioData() {
constexpr uint32_t kAudioHandle = 0;
struct chreAudioSource audioSource;
if (!chreAudioGetSource(kAudioHandle, &audioSource)) {
sendFatalFailureToHost("Failed to query source for handle 0");
} else if (!chreAudioConfigureSource(kAudioHandle, true /* enable */,
audioSource.minBufferDuration,
audioSource.minBufferDuration)) {
sendFatalFailureToHost("Failed to request audio data for handle 0");
}
}
/**
* Check if the audio samples are all zeros
*
* @return true on check passing
*/
bool checkSamplesAllZeros(int16_t *data, size_t dataLen) {
for (size_t i = 0; i < dataLen; ++i) {
if (data[i] != 0) {
return true;
}
}
return false;
}
/**
* Check if adjacent audio samples are unique
*
* @return true on check pass
*/
bool checkSamplesAllSame(int16_t *data, size_t dataLen) {
if (dataLen > 0) {
const int16_t controlValue = data[0];
for (size_t i = 1; i < dataLen; ++i) {
if (data[i] != controlValue) {
return true;
}
}
}
return false;
}
void handleAudioDataEvent(const chreAudioDataEvent *dataEvent) {
constexpr uint32_t kAudioHandle = 0;
// A count of the number of data events we've received - we stop
// the test after receiving 2 data events.
static uint8_t numDataEventsSoFar = 1;
if (dataEvent == nullptr) {
sendFatalFailureToHost("Null event data");
} else if (dataEvent->samplesS16 == nullptr) {
sendFatalFailureToHost("Null audio data frame");
} else if (dataEvent->sampleCount == 0) {
sendFatalFailureToHost("0 samples in audio data frame");
} else {
struct chreAudioSource audioSource;
if (!chreAudioGetSource(kAudioHandle, &audioSource)) {
sendFatalFailureToHost("Failed to get audio source for handle 0");
} else {
// Per the CHRE Audio API requirements, it is expected that we exactly
// the number of samples that we ask for - we verify that here.
const size_t kNumSamplesExpected =
audioSource.minBufferDuration * kRequiredSampleRate;
if (dataEvent->sampleCount != kNumSamplesExpected) {
sendFatalFailureToHost(
"Failed to receive the expected number of samples in audio data "
"event");
}
}
}
if (numDataEventsSoFar == 2) {
if (!chreAudioConfigureSource(kAudioHandle, false /* enable */,
0 /* bufferDuration */,
0 /* deliveryInterval */)) {
sendFatalFailureToHost("Failed to disable audio source for handle 0");
}
} else {
++numDataEventsSoFar;
}
if (!checkSamplesAllZeros(dataEvent->samplesS16, dataEvent->sampleCount)) {
sendFatalFailureToHost("All audio samples were zeros");
} else if (!checkSamplesAllSame(dataEvent->samplesS16,
dataEvent->sampleCount)) {
sendFatalFailureToHost("All audio samples were identical");
} else {
sendSuccessToHost();
}
}
} // namespace
} // anonymous namespace
BasicAudioTest::BasicAudioTest()
: Test(CHRE_API_VERSION_1_2), mInMethod(false), mState(State::kPreStart) {}
void BasicAudioTest::setUp(uint32_t messageSize, const void * /* message */) {
if (messageSize != 0) {
sendFatalFailureToHost("Beginning message expects 0 additional bytes, got ",
&messageSize);
}
validateAudioSources();
mState = State::kExpectingAudioData;
requestAudioData();
}
void BasicAudioTest::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
const void *eventData) {
if (mInMethod) {
sendFatalFailureToHost("handleEvent() invoked while already in method.");
}
mInMethod = true;
if (mState == State::kPreStart) {
unexpectedEvent(eventType);
} else {
switch (eventType) {
case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
/* This event is received, but not relevant to this test since we're
* mostly interested in the audio data, so we ignore it. */
break;
case CHRE_EVENT_AUDIO_DATA:
handleAudioDataEvent(
static_cast<const chreAudioDataEvent *>(eventData));
break;
default:
unexpectedEvent(eventType);
break;
}
}
mInMethod = false;
}
} // namespace general_test