| /* |
| * Copyright (C) 2017 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 <cmath> |
| |
| #include "chre/util/macros.h" |
| #include "chre/util/nanoapp/audio.h" |
| #include "chre/util/nanoapp/log.h" |
| #include "chre/util/time.h" |
| #include "kiss_fftr.h" |
| |
| #define LOG_TAG "[AudioWorld]" |
| |
| #ifdef CHRE_NANOAPP_INTERNAL |
| namespace chre { |
| namespace { |
| #endif // CHRE_NANOAPP_INTERNAL |
| |
| using chre::Milliseconds; |
| using chre::Nanoseconds; |
| |
| //! The number of frequencies to generate an FFT over. |
| constexpr size_t kNumFrequencies = 128; |
| |
| //! True if audio has successfully been requested. |
| bool gAudioRequested = false; |
| |
| //! The requested audio handle. |
| uint32_t gAudioHandle; |
| |
| //! State for Kiss FFT and logging. |
| uint8_t gKissFftBuffer[4096]; |
| kiss_fftr_cfg gKissFftConfig; |
| kiss_fft_cpx gKissFftOutput[(kNumFrequencies / 2) + 1]; |
| Milliseconds gFirstAudioEventTimestamp = Milliseconds(0); |
| |
| /** |
| * Returns a graphical representation of a uint16_t value. |
| * |
| * @param value the value to visualize. |
| * @return a character that visually represents the value. |
| */ |
| char getFftCharForValue(uint16_t value) { |
| constexpr uint16_t kFftLowLimit = 128; |
| constexpr uint16_t kFftMedLimit = 256; |
| constexpr uint16_t kFftHighLimit = 512; // Texas Hold'em ༼⁰o⁰;༽ |
| constexpr uint16_t kFftVeryHighLimit = 1024; |
| |
| if (value < kFftLowLimit) { |
| return ' '; |
| } else if (value >= kFftLowLimit && value < kFftMedLimit) { |
| return '_'; |
| } else if (value >= kFftMedLimit && value < kFftHighLimit) { |
| return '.'; |
| } else if (value >= kFftHighLimit && value < kFftVeryHighLimit) { |
| return 'x'; |
| } else { |
| return 'X'; |
| } |
| } |
| |
| /** |
| * Initializes Kiss FFT. |
| */ |
| void initKissFft() { |
| size_t kissFftBufferSize = sizeof(gKissFftBuffer); |
| gKissFftConfig = kiss_fftr_alloc(kNumFrequencies, false, gKissFftBuffer, |
| &kissFftBufferSize); |
| if (gKissFftConfig == NULL) { |
| LOGE("Failed to init Kiss FFT, needs minimum %zu buffer size", |
| kissFftBufferSize); |
| } else { |
| LOGI("Initialized Kiss FFT, using %zu/%zu of the buffer", kissFftBufferSize, |
| sizeof(gKissFftBuffer)); |
| } |
| } |
| |
| /** |
| * Logs an audio data event with an FFT visualization of the received audio |
| * data. |
| * |
| * @param event the audio data event to log. |
| */ |
| void handleAudioDataEvent(const struct chreAudioDataEvent *event) { |
| kiss_fftr(gKissFftConfig, event->samplesS16, gKissFftOutput); |
| |
| char fftStr[ARRAY_SIZE(gKissFftOutput) + 1]; |
| fftStr[ARRAY_SIZE(gKissFftOutput)] = '\0'; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(gKissFftOutput); i++) { |
| float value = |
| sqrtf(powf(gKissFftOutput[i].r, 2) + powf(gKissFftOutput[i].i, 2)); |
| fftStr[i] = getFftCharForValue(static_cast<uint16_t>(value)); |
| } |
| |
| Milliseconds timestamp = Milliseconds(Nanoseconds(event->timestamp)); |
| if (gFirstAudioEventTimestamp == Milliseconds(0)) { |
| gFirstAudioEventTimestamp = timestamp; |
| } |
| |
| Milliseconds adjustedTimestamp = timestamp - gFirstAudioEventTimestamp; |
| LOGD("Audio data - FFT [%s] at %" PRIu64 "ms with %" PRIu32 " samples", |
| fftStr, adjustedTimestamp.getMilliseconds(), event->sampleCount); |
| } |
| |
| void handleAudioSamplingChangeEvent( |
| const struct chreAudioSourceStatusEvent *event) { |
| LOGD("Audio sampling status event for handle %" PRIu32 ", suspended: %d", |
| event->handle, event->status.suspended); |
| } |
| |
| bool nanoappStart() { |
| LOGI("Started"); |
| |
| struct chreAudioSource audioSource; |
| for (uint32_t i = 0; chreAudioGetSource(i, &audioSource); i++) { |
| LOGI("Found audio source '%s' with %" PRIu32 "Hz %s data", audioSource.name, |
| audioSource.sampleRate, |
| chre::getChreAudioFormatString(audioSource.format)); |
| LOGI(" buffer duration: [%" PRIu64 "ns, %" PRIu64 "ns]", |
| audioSource.minBufferDuration, audioSource.maxBufferDuration); |
| |
| if (i == 0) { |
| // Only request audio data from the first source, but continue discovery. |
| if (chreAudioConfigureSource(i, true, audioSource.minBufferDuration, |
| audioSource.minBufferDuration)) { |
| gAudioRequested = true; |
| gAudioHandle = i; |
| LOGI("Requested audio from handle %" PRIu32 " successfully", i); |
| } else { |
| LOGE("Failed to request audio from handle %" PRIu32, i); |
| } |
| } |
| } |
| |
| initKissFft(); |
| return true; |
| } |
| |
| void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType, |
| const void *eventData) { |
| switch (eventType) { |
| case CHRE_EVENT_AUDIO_DATA: |
| handleAudioDataEvent( |
| static_cast<const struct chreAudioDataEvent *>(eventData)); |
| break; |
| case CHRE_EVENT_AUDIO_SAMPLING_CHANGE: |
| handleAudioSamplingChangeEvent( |
| static_cast<const struct chreAudioSourceStatusEvent *>(eventData)); |
| break; |
| default: |
| LOGW("Unknown event received"); |
| break; |
| } |
| } |
| |
| void nanoappEnd() { |
| if (gAudioRequested) { |
| chreAudioConfigureSource(gAudioHandle, false /* enable */, |
| 0 /* bufferDuration */, 0 /* deliveryInterval */); |
| } |
| LOGI("Stopped"); |
| } |
| |
| #ifdef CHRE_NANOAPP_INTERNAL |
| } // anonymous namespace |
| } // namespace chre |
| |
| #include "chre/platform/static_nanoapp_init.h" |
| #include "chre/util/nanoapp/app_id.h" |
| |
| CHRE_STATIC_NANOAPP_INIT(AudioWorld, chre::kAudioWorldAppId, 0); |
| #endif // CHRE_NANOAPP_INTERNAL |