| /* |
| * 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 |