510 lines
19 KiB
C++
510 lines
19 KiB
C++
/*
|
|
* 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
|