| /* |
| * 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/core/audio_request_manager.h" |
| |
| #include "chre/core/audio_util.h" |
| #include "chre/core/event_loop_manager.h" |
| #include "chre/platform/fatal_error.h" |
| #include "chre/platform/system_time.h" |
| #include "chre/util/nested_data_ptr.h" |
| #include "chre/util/system/debug_dump.h" |
| #include "chre/util/time.h" |
| |
| /* |
| * TODO(P1-62e045): Evict pending audio events from the event queue as needed. |
| * |
| * Under the following conditions, an audio data event may be posted to the |
| * CHRE event queue when it should not be. |
| * |
| * 1. Nanoapp changes request |
| * 2. Nanoapp removes request |
| * |
| * A previously scheduled audio data event may be residing in the event queue |
| * and will be dispatched to the nanoapp after it has cancelled the request. |
| * |
| * The solution is to evict any audio events for a given audio handle that are |
| * directed to a nanoapp before scheduling the next request to the platform. |
| */ |
| |
| namespace chre { |
| |
| void AudioRequestManager::init() { |
| mPlatformAudio.init(); |
| |
| size_t sourceCount = mPlatformAudio.getSourceCount(); |
| if (!mAudioRequestLists.reserve(sourceCount)) { |
| FATAL_ERROR_OOM(); |
| } |
| |
| for (size_t i = 0; i < sourceCount; i++) { |
| mAudioRequestLists.emplace_back(); |
| } |
| } |
| |
| bool AudioRequestManager::configureSource(const Nanoapp *nanoapp, |
| uint32_t handle, bool enable, |
| uint64_t bufferDuration, |
| uint64_t deliveryInterval) { |
| uint32_t numSamples; |
| return validateConfigureSourceArguments(handle, enable, bufferDuration, |
| deliveryInterval, &numSamples) && |
| doConfigureSource(nanoapp->getInstanceId(), handle, enable, numSamples, |
| Nanoseconds(deliveryInterval)); |
| } |
| |
| void AudioRequestManager::handleAudioDataEvent( |
| const struct chreAudioDataEvent *audioDataEvent) { |
| uint32_t handle = audioDataEvent->handle; |
| if (handle >= mAudioRequestLists.size()) { |
| LOGE("Received audio event for unknown handle %" PRIu32, handle); |
| } else { |
| mAudioRequestLists[handle].lastEventTimestamp = |
| SystemTime::getMonotonicTime(); |
| } |
| |
| auto callback = [](uint16_t /*type*/, void *data, void * /*extraData*/) { |
| auto *event = static_cast<struct chreAudioDataEvent *>(data); |
| EventLoopManagerSingleton::get() |
| ->getAudioRequestManager() |
| .handleAudioDataEventSync(event); |
| }; |
| |
| // Cast off the event const so that it can be provided to the callback as |
| // non-const. The event is provided to nanoapps as const and the runtime |
| // itself will not modify this memory so this is safe. |
| EventLoopManagerSingleton::get()->deferCallback( |
| SystemCallbackType::AudioHandleDataEvent, |
| const_cast<struct chreAudioDataEvent *>(audioDataEvent), callback); |
| } |
| |
| void AudioRequestManager::handleAudioAvailability(uint32_t handle, |
| bool available) { |
| auto callback = [](uint16_t /*type*/, void *data, void *extraData) { |
| uint32_t cbHandle = NestedDataPtr<uint32_t>(data); |
| bool cbAvailable = NestedDataPtr<bool>(extraData); |
| EventLoopManagerSingleton::get() |
| ->getAudioRequestManager() |
| .handleAudioAvailabilitySync(cbHandle, cbAvailable); |
| }; |
| |
| EventLoopManagerSingleton::get()->deferCallback( |
| SystemCallbackType::AudioAvailabilityChange, |
| NestedDataPtr<uint32_t>(handle), callback, |
| NestedDataPtr<bool>(available)); |
| } |
| |
| void AudioRequestManager::logStateToBuffer(DebugDumpWrapper &debugDump) const { |
| debugDump.print("\nAudio:\n"); |
| for (size_t i = 0; i < mAudioRequestLists.size(); i++) { |
| uint32_t handle = static_cast<uint32_t>(i); |
| struct chreAudioSource source; |
| mPlatformAudio.getAudioSource(handle, &source); |
| |
| Nanoseconds timeSinceLastAudioEvent = |
| SystemTime::getMonotonicTime() - |
| mAudioRequestLists[i].lastEventTimestamp; |
| debugDump.print( |
| " handle=%" PRIu32 ", name=\"%s\", available=%d, sampleRate=%" PRIu32 |
| ", buffer(ms)=[%" PRIu64 ",%" PRIu64 "], format=%" PRIu8 |
| ", timeSinceLastAudioEvent(ms)=%" PRIu64 "\n", |
| handle, source.name, mAudioRequestLists[i].available, source.sampleRate, |
| Milliseconds(Nanoseconds(source.minBufferDuration)).getMilliseconds(), |
| Milliseconds(Nanoseconds(source.maxBufferDuration)).getMilliseconds(), |
| source.format, Milliseconds(timeSinceLastAudioEvent).getMilliseconds()); |
| |
| for (const auto &request : mAudioRequestLists[i].requests) { |
| for (const auto &instanceId : request.instanceIds) { |
| debugDump.print(" nanoappId=%" PRIu16 ", numSamples=%" PRIu32 |
| ", interval(ms)=%" PRIu64 "\n", |
| instanceId, request.numSamples, |
| Milliseconds(Nanoseconds(request.deliveryInterval)) |
| .getMilliseconds()); |
| } |
| } |
| } |
| } |
| |
| bool AudioRequestManager::validateConfigureSourceArguments( |
| uint32_t handle, bool enable, uint64_t bufferDuration, |
| uint64_t deliveryInterval, uint32_t *numSamples) { |
| bool success = false; |
| if (handle >= mAudioRequestLists.size()) { |
| LOGE("Provided audio handle out of range"); |
| } else if (enable) { |
| chreAudioSource audioSource; |
| if (!mPlatformAudio.getAudioSource(handle, &audioSource)) { |
| LOGE("Failed to query for audio source"); |
| } else if (bufferDuration > deliveryInterval) { |
| LOGE("Buffer duration must be less than or equal to delivery interval"); |
| } else if (bufferDuration < audioSource.minBufferDuration || |
| bufferDuration > audioSource.maxBufferDuration) { |
| LOGE("Invalid buffer duration %" PRIu64 " not in range [%" PRIu64 |
| ",%" PRIu64 "]", |
| bufferDuration, audioSource.minBufferDuration, |
| audioSource.maxBufferDuration); |
| } else { |
| *numSamples = AudioUtil::getSampleCountFromRateAndDuration( |
| audioSource.sampleRate, Nanoseconds(bufferDuration)); |
| success = true; |
| } |
| } else { |
| // Disabling the request, no need to validate bufferDuration or |
| // deliveryInterval. |
| success = true; |
| } |
| return success; |
| } |
| |
| bool AudioRequestManager::doConfigureSource(uint16_t instanceId, |
| uint32_t handle, bool enable, |
| uint32_t numSamples, |
| Nanoseconds deliveryInterval) { |
| size_t requestIndex; |
| size_t requestInstanceIdIndex; |
| auto *audioRequest = findAudioRequestByInstanceId( |
| handle, instanceId, &requestIndex, &requestInstanceIdIndex); |
| |
| AudioRequestList &requestList = mAudioRequestLists[handle]; |
| size_t lastNumRequests = requestList.requests.size(); |
| |
| bool success = false; |
| if (audioRequest == nullptr) { |
| if (enable) { |
| success = |
| createAudioRequest(handle, instanceId, numSamples, deliveryInterval); |
| } else { |
| LOGW("Nanoapp disabling nonexistent audio request"); |
| } |
| } else { |
| if (audioRequest->instanceIds.size() > 1) { |
| // If there are other clients listening in this configuration, remove |
| // just the instance ID. |
| audioRequest->instanceIds.erase(requestInstanceIdIndex); |
| } else { |
| // If this is the last client listening in this configuration, remove |
| // the entire request. |
| requestList.requests.erase(requestIndex); |
| } |
| |
| // If the client is disabling, there is nothing to do, otherwise a |
| // request must be created successfully. |
| if (!enable) { |
| success = true; |
| } else { |
| success = |
| createAudioRequest(handle, instanceId, numSamples, deliveryInterval); |
| } |
| } |
| |
| if (success && |
| (EventLoopManagerSingleton::get()->getSettingManager().getSettingEnabled( |
| Setting::MICROPHONE))) { |
| scheduleNextAudioDataEvent(handle); |
| updatePlatformHandleEnabled(handle, lastNumRequests); |
| } |
| |
| return success; |
| } |
| |
| void AudioRequestManager::updatePlatformHandleEnabled(uint32_t handle, |
| size_t lastNumRequests) { |
| size_t numRequests = mAudioRequestLists[handle].requests.size(); |
| if (lastNumRequests == 0 && numRequests > 0) { |
| mPlatformAudio.setHandleEnabled(handle, true /* enabled */); |
| } else if (lastNumRequests > 0 && numRequests == 0) { |
| mPlatformAudio.setHandleEnabled(handle, false /* enabled */); |
| } |
| } |
| |
| bool AudioRequestManager::createAudioRequest(uint32_t handle, |
| uint16_t instanceId, |
| uint32_t numSamples, |
| Nanoseconds deliveryInterval) { |
| AudioRequestList &requestList = mAudioRequestLists[handle]; |
| |
| size_t matchingRequestIndex; |
| auto *matchingAudioRequest = findAudioRequestByConfiguration( |
| handle, numSamples, deliveryInterval, &matchingRequestIndex); |
| |
| bool success = false; |
| if (matchingAudioRequest != nullptr) { |
| if (!matchingAudioRequest->instanceIds.push_back(instanceId)) { |
| LOG_OOM(); |
| } else { |
| success = true; |
| } |
| } else { |
| Nanoseconds timeNow = SystemTime::getMonotonicTime(); |
| Nanoseconds nextEventTimestamp = timeNow + deliveryInterval; |
| if (!requestList.requests.emplace_back(numSamples, deliveryInterval, |
| nextEventTimestamp)) { |
| LOG_OOM(); |
| } else if (!requestList.requests.back().instanceIds.push_back(instanceId)) { |
| requestList.requests.pop_back(); |
| LOG_OOM(); |
| } else { |
| success = true; |
| } |
| } |
| |
| if (success) { |
| bool suspended = !EventLoopManagerSingleton::get() |
| ->getSettingManager() |
| .getSettingEnabled(Setting::MICROPHONE); |
| postAudioSamplingChangeEvent(instanceId, handle, requestList.available, |
| suspended); |
| } |
| |
| return success; |
| } |
| |
| uint32_t AudioRequestManager::disableAllAudioRequests(const Nanoapp *nanoapp) { |
| uint32_t numRequestDisabled = 0; |
| |
| const uint32_t numRequests = static_cast<uint32_t>(mAudioRequestLists.size()); |
| for (uint32_t handle = 0; handle < numRequests; ++handle) { |
| AudioRequest *audioRequest = findAudioRequestByInstanceId( |
| handle, nanoapp->getInstanceId(), nullptr /*index*/, nullptr |
| /*instanceIdIndex*/); |
| |
| if (audioRequest != nullptr) { |
| numRequestDisabled++; |
| doConfigureSource(nanoapp->getInstanceId(), handle, false /*enable*/, |
| 0 /*numSamples*/, Nanoseconds() /*deliveryInterval*/); |
| } |
| } |
| |
| return numRequestDisabled; |
| } |
| |
| AudioRequestManager::AudioRequest * |
| AudioRequestManager::findAudioRequestByInstanceId(uint32_t handle, |
| uint16_t instanceId, |
| size_t *index, |
| size_t *instanceIdIndex) { |
| AudioRequest *foundAudioRequest = nullptr; |
| auto &requests = mAudioRequestLists[handle].requests; |
| for (size_t i = 0; i < requests.size(); i++) { |
| auto &audioRequest = requests[i]; |
| size_t foundInstanceIdIndex = audioRequest.instanceIds.find(instanceId); |
| if (foundInstanceIdIndex != audioRequest.instanceIds.size()) { |
| foundAudioRequest = &audioRequest; |
| if (index != nullptr) { |
| *index = i; |
| } |
| if (instanceIdIndex != nullptr) { |
| *instanceIdIndex = foundInstanceIdIndex; |
| } |
| break; |
| } |
| } |
| |
| return foundAudioRequest; |
| } |
| |
| AudioRequestManager::AudioRequest * |
| AudioRequestManager::findAudioRequestByConfiguration( |
| uint32_t handle, uint32_t numSamples, Nanoseconds deliveryInterval, |
| size_t *index) { |
| AudioRequest *foundAudioRequest = nullptr; |
| auto &requests = mAudioRequestLists[handle].requests; |
| for (size_t i = 0; i < requests.size(); i++) { |
| auto &audioRequest = requests[i]; |
| if (audioRequest.numSamples == numSamples && |
| audioRequest.deliveryInterval == deliveryInterval) { |
| foundAudioRequest = &audioRequest; |
| *index = i; |
| break; |
| } |
| } |
| |
| return foundAudioRequest; |
| } |
| |
| AudioRequestManager::AudioRequest *AudioRequestManager::findNextAudioRequest( |
| uint32_t handle) { |
| Nanoseconds earliestNextEventTimestamp = Nanoseconds(UINT64_MAX); |
| AudioRequest *nextRequest = nullptr; |
| |
| auto &reqList = mAudioRequestLists[handle]; |
| for (auto &req : reqList.requests) { |
| if (req.nextEventTimestamp < earliestNextEventTimestamp) { |
| earliestNextEventTimestamp = req.nextEventTimestamp; |
| nextRequest = &req; |
| } |
| } |
| |
| return nextRequest; |
| } |
| |
| void AudioRequestManager::handleAudioDataEventSync( |
| struct chreAudioDataEvent *event) { |
| uint32_t handle = event->handle; |
| if (handle < mAudioRequestLists.size()) { |
| auto &reqList = mAudioRequestLists[handle]; |
| AudioRequest *nextAudioRequest = reqList.nextAudioRequest; |
| if (nextAudioRequest != nullptr) { |
| postAudioDataEventFatal(event, nextAudioRequest->instanceIds); |
| nextAudioRequest->nextEventTimestamp = |
| SystemTime::getMonotonicTime() + nextAudioRequest->deliveryInterval; |
| } else { |
| LOGW("Received audio data event with no pending audio request"); |
| mPlatformAudio.releaseAudioDataEvent(event); |
| } |
| |
| scheduleNextAudioDataEvent(handle); |
| } else { |
| LOGE("Audio data event handle out of range: %" PRIu32, handle); |
| } |
| } |
| |
| void AudioRequestManager::handleAudioAvailabilitySync(uint32_t handle, |
| bool available) { |
| if (handle < mAudioRequestLists.size()) { |
| if (mAudioRequestLists[handle].available != available) { |
| bool suspended = !EventLoopManagerSingleton::get() |
| ->getSettingManager() |
| .getSettingEnabled(Setting::MICROPHONE); |
| mAudioRequestLists[handle].available = available; |
| postAudioSamplingChangeEvents(handle, suspended); |
| } |
| |
| scheduleNextAudioDataEvent(handle); |
| } else { |
| LOGE("Audio availability handle out of range: %" PRIu32, handle); |
| } |
| } |
| |
| void AudioRequestManager::scheduleNextAudioDataEvent(uint32_t handle) { |
| if (!EventLoopManagerSingleton::get()->getSettingManager().getSettingEnabled( |
| Setting::MICROPHONE)) { |
| LOGD("Mic access disabled, doing nothing"); |
| return; |
| } |
| |
| auto &reqList = mAudioRequestLists[handle]; |
| AudioRequest *nextRequest = findNextAudioRequest(handle); |
| |
| // Clear the next request and it will be reset below if needed. |
| reqList.nextAudioRequest = nullptr; |
| if (reqList.available && (nextRequest != nullptr)) { |
| Nanoseconds curTime = SystemTime::getMonotonicTime(); |
| Nanoseconds eventDelay = Nanoseconds(0); |
| if (nextRequest->nextEventTimestamp > curTime) { |
| eventDelay = nextRequest->nextEventTimestamp - curTime; |
| } |
| reqList.nextAudioRequest = nextRequest; |
| mPlatformAudio.requestAudioDataEvent(handle, nextRequest->numSamples, |
| eventDelay); |
| } else { |
| mPlatformAudio.cancelAudioDataEventRequest(handle); |
| } |
| } |
| |
| void AudioRequestManager::postAudioSamplingChangeEvents(uint32_t handle, |
| bool suspended) { |
| const auto &requestList = mAudioRequestLists[handle]; |
| for (const auto &request : requestList.requests) { |
| for (const auto &instanceId : request.instanceIds) { |
| postAudioSamplingChangeEvent(instanceId, handle, requestList.available, |
| suspended); |
| } |
| } |
| } |
| |
| void AudioRequestManager::postAudioSamplingChangeEvent(uint16_t instanceId, |
| uint32_t handle, |
| bool available, |
| bool suspended) { |
| auto *event = memoryAlloc<struct chreAudioSourceStatusEvent>(); |
| event->handle = handle; |
| event->status.enabled = true; |
| event->status.suspended = !available || suspended; |
| |
| EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie( |
| CHRE_EVENT_AUDIO_SAMPLING_CHANGE, event, freeEventDataCallback, |
| instanceId); |
| } |
| |
| void AudioRequestManager::postAudioDataEventFatal( |
| struct chreAudioDataEvent *event, |
| const DynamicVector<uint16_t> &instanceIds) { |
| if (instanceIds.empty()) { |
| LOGW("Received audio data event for no clients"); |
| mPlatformAudio.releaseAudioDataEvent(event); |
| } else { |
| for (const auto &instanceId : instanceIds) { |
| EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie( |
| CHRE_EVENT_AUDIO_DATA, event, freeAudioDataEventCallback, instanceId); |
| } |
| |
| mAudioDataEventRefCounts.emplace_back( |
| event, static_cast<uint32_t>(instanceIds.size())); |
| } |
| } |
| |
| void AudioRequestManager::handleFreeAudioDataEvent( |
| struct chreAudioDataEvent *audioDataEvent) { |
| size_t audioDataEventRefCountIndex = |
| mAudioDataEventRefCounts.find(AudioDataEventRefCount(audioDataEvent)); |
| if (audioDataEventRefCountIndex == mAudioDataEventRefCounts.size()) { |
| LOGE("Freeing invalid audio data event"); |
| } else { |
| auto &audioDataEventRefCount = |
| mAudioDataEventRefCounts[audioDataEventRefCountIndex]; |
| if (audioDataEventRefCount.refCount == 0) { |
| LOGE("Attempting to free an event with zero published events"); |
| } else { |
| audioDataEventRefCount.refCount--; |
| if (audioDataEventRefCount.refCount == 0) { |
| mAudioDataEventRefCounts.erase(audioDataEventRefCountIndex); |
| mPlatformAudio.releaseAudioDataEvent(audioDataEvent); |
| } |
| } |
| } |
| } |
| |
| void AudioRequestManager::freeAudioDataEventCallback(uint16_t eventType, |
| void *eventData) { |
| UNUSED_VAR(eventType); |
| auto *event = static_cast<struct chreAudioDataEvent *>(eventData); |
| EventLoopManagerSingleton::get() |
| ->getAudioRequestManager() |
| .handleFreeAudioDataEvent(event); |
| } |
| |
| void AudioRequestManager::onSettingChanged(Setting setting, bool enabled) { |
| if (setting == Setting::MICROPHONE) { |
| for (size_t i = 0; i < mAudioRequestLists.size(); ++i) { |
| uint32_t handle = static_cast<uint32_t>(i); |
| if (mAudioRequestLists[i].available) { |
| if (!enabled) { |
| LOGD("Canceling data event request for handle %" PRIu32, handle); |
| postAudioSamplingChangeEvents(handle, true /* suspended */); |
| mPlatformAudio.cancelAudioDataEventRequest(handle); |
| } else { |
| LOGD("Scheduling data event for handle %" PRIu32, handle); |
| postAudioSamplingChangeEvents(handle, false /* suspended */); |
| scheduleNextAudioDataEvent(handle); |
| } |
| } |
| } |
| } |
| } |
| |
| } // namespace chre |