/* * Copyright (C) 2014 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. */ #define LOG_TAG "SoundTriggerHwService" //#define LOG_NDEBUG 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SoundTriggerHwService.h" namespace android { #ifdef SOUND_TRIGGER_USE_STUB_MODULE #define HW_MODULE_PREFIX "stub" #else #define HW_MODULE_PREFIX "primary" #endif SoundTriggerHwService::SoundTriggerHwService() : BnSoundTriggerHwService(), mNextUniqueId(1), mMemoryDealer(new MemoryDealer(1024 * 1024, "SoundTriggerHwService")), mCaptureState(false) { } void SoundTriggerHwService::onFirstRef() { const hw_module_t *mod; int rc; sound_trigger_hw_device *dev; rc = hw_get_module_by_class(SOUND_TRIGGER_HARDWARE_MODULE_ID, HW_MODULE_PREFIX, &mod); if (rc != 0) { ALOGE("couldn't load sound trigger module %s.%s (%s)", SOUND_TRIGGER_HARDWARE_MODULE_ID, "primary", strerror(-rc)); return; } rc = sound_trigger_hw_device_open(mod, &dev); if (rc != 0) { ALOGE("couldn't open sound trigger hw device in %s.%s (%s)", SOUND_TRIGGER_HARDWARE_MODULE_ID, "primary", strerror(-rc)); return; } if (dev->common.version != SOUND_TRIGGER_DEVICE_API_VERSION_CURRENT) { ALOGE("wrong sound trigger hw device version %04x", dev->common.version); return; } sound_trigger_module_descriptor descriptor; rc = dev->get_properties(dev, &descriptor.properties); if (rc != 0) { ALOGE("could not read implementation properties"); return; } descriptor.handle = (sound_trigger_module_handle_t)android_atomic_inc(&mNextUniqueId); ALOGI("loaded default module %s, handle %d", descriptor.properties.description, descriptor.handle); sp client; sp module = new Module(this, dev, descriptor, client); mModules.add(descriptor.handle, module); mCallbackThread = new CallbackThread(this); } SoundTriggerHwService::~SoundTriggerHwService() { if (mCallbackThread != 0) { mCallbackThread->exit(); } for (size_t i = 0; i < mModules.size(); i++) { sound_trigger_hw_device_close(mModules.valueAt(i)->hwDevice()); } } status_t SoundTriggerHwService::listModules(struct sound_trigger_module_descriptor *modules, uint32_t *numModules) { ALOGV("listModules"); if (!captureHotwordAllowed()) { return PERMISSION_DENIED; } AutoMutex lock(mServiceLock); if (numModules == NULL || (*numModules != 0 && modules == NULL)) { return BAD_VALUE; } size_t maxModules = *numModules; *numModules = mModules.size(); for (size_t i = 0; i < mModules.size() && i < maxModules; i++) { modules[i] = mModules.valueAt(i)->descriptor(); } return NO_ERROR; } status_t SoundTriggerHwService::attach(const sound_trigger_module_handle_t handle, const sp& client, sp& moduleInterface) { ALOGV("attach module %d", handle); if (!captureHotwordAllowed()) { return PERMISSION_DENIED; } AutoMutex lock(mServiceLock); moduleInterface.clear(); if (client == 0) { return BAD_VALUE; } ssize_t index = mModules.indexOfKey(handle); if (index < 0) { return BAD_VALUE; } sp module = mModules.valueAt(index); module->setClient(client); IInterface::asBinder(client)->linkToDeath(module); moduleInterface = module; module->setCaptureState_l(mCaptureState); return NO_ERROR; } status_t SoundTriggerHwService::setCaptureState(bool active) { ALOGV("setCaptureState %d", active); AutoMutex lock(mServiceLock); mCaptureState = active; for (size_t i = 0; i < mModules.size(); i++) { mModules.valueAt(i)->setCaptureState_l(active); } return NO_ERROR; } void SoundTriggerHwService::detachModule(sp module) { ALOGV("detachModule"); AutoMutex lock(mServiceLock); module->clearClient(); } static const int kDumpLockRetries = 50; static const int kDumpLockSleep = 60000; static bool tryLock(Mutex& mutex) { bool locked = false; for (int i = 0; i < kDumpLockRetries; ++i) { if (mutex.tryLock() == NO_ERROR) { locked = true; break; } usleep(kDumpLockSleep); } return locked; } status_t SoundTriggerHwService::dump(int fd, const Vector& args __unused) { String8 result; if (checkCallingPermission(String16("android.permission.DUMP")) == false) { result.appendFormat("Permission Denial: can't dump SoundTriggerHwService"); write(fd, result.string(), result.size()); } else { bool locked = tryLock(mServiceLock); // failed to lock - SoundTriggerHwService is probably deadlocked if (!locked) { result.append("SoundTriggerHwService may be deadlocked\n"); write(fd, result.string(), result.size()); } if (locked) mServiceLock.unlock(); } return NO_ERROR; } status_t SoundTriggerHwService::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { return BnSoundTriggerHwService::onTransact(code, data, reply, flags); } // static void SoundTriggerHwService::recognitionCallback(struct sound_trigger_recognition_event *event, void *cookie) { Module *module = (Module *)cookie; if (module == NULL) { return; } sp service = module->service().promote(); if (service == 0) { return; } service->sendRecognitionEvent(event, module); } sp SoundTriggerHwService::prepareRecognitionEvent_l( struct sound_trigger_recognition_event *event) { sp eventMemory; //sanitize event switch (event->type) { case SOUND_MODEL_TYPE_KEYPHRASE: ALOGW_IF(event->data_size != 0 && event->data_offset != sizeof(struct sound_trigger_phrase_recognition_event), "prepareRecognitionEvent_l(): invalid data offset %u for keyphrase event type", event->data_offset); event->data_offset = sizeof(struct sound_trigger_phrase_recognition_event); break; case SOUND_MODEL_TYPE_UNKNOWN: ALOGW_IF(event->data_size != 0 && event->data_offset != sizeof(struct sound_trigger_recognition_event), "prepareRecognitionEvent_l(): invalid data offset %u for unknown event type", event->data_offset); event->data_offset = sizeof(struct sound_trigger_recognition_event); break; default: return eventMemory; } size_t size = event->data_offset + event->data_size; eventMemory = mMemoryDealer->allocate(size); if (eventMemory == 0 || eventMemory->pointer() == NULL) { eventMemory.clear(); return eventMemory; } memcpy(eventMemory->pointer(), event, size); return eventMemory; } void SoundTriggerHwService::sendRecognitionEvent(struct sound_trigger_recognition_event *event, Module *module) { AutoMutex lock(mServiceLock); if (module == NULL) { return; } if (event-> type == SOUND_MODEL_TYPE_KEYPHRASE && event->data_size != 0 && event->data_offset != sizeof(struct sound_trigger_phrase_recognition_event)) { // set some defaults for the phrase if the recognition event won't be parsed properly // TODO: read defaults from the config struct sound_trigger_phrase_recognition_event newEvent; memset(&newEvent, 0, sizeof(struct sound_trigger_phrase_recognition_event)); sp model = module->getModel(event->model); newEvent.num_phrases = 1; newEvent.phrase_extras[0].id = 100; newEvent.phrase_extras[0].recognition_modes = RECOGNITION_MODE_VOICE_TRIGGER; newEvent.phrase_extras[0].confidence_level = 100; newEvent.phrase_extras[0].num_levels = 1; newEvent.phrase_extras[0].levels[0].level = 100; newEvent.phrase_extras[0].levels[0].user_id = 100; newEvent.common.status = event->status; newEvent.common.type = event->type; newEvent.common.model = event->model; newEvent.common.capture_available = event->capture_available; newEvent.common.capture_session = event->capture_session; newEvent.common.capture_delay_ms = event->capture_delay_ms; newEvent.common.capture_preamble_ms = event->capture_preamble_ms; newEvent.common.trigger_in_data = event->trigger_in_data; newEvent.common.audio_config = event->audio_config; newEvent.common.data_size = event->data_size; newEvent.common.data_offset = sizeof(struct sound_trigger_phrase_recognition_event); event = &newEvent.common; } sp eventMemory = prepareRecognitionEvent_l(event); if (eventMemory == 0) { return; } sp strongModule; for (size_t i = 0; i < mModules.size(); i++) { if (mModules.valueAt(i).get() == module) { strongModule = mModules.valueAt(i); break; } } if (strongModule == 0) { return; } sendCallbackEvent_l(new CallbackEvent(CallbackEvent::TYPE_RECOGNITION, eventMemory, strongModule)); } // static void SoundTriggerHwService::soundModelCallback(struct sound_trigger_model_event *event, void *cookie) { Module *module = (Module *)cookie; if (module == NULL) { return; } sp service = module->service().promote(); if (service == 0) { return; } service->sendSoundModelEvent(event, module); } sp SoundTriggerHwService::prepareSoundModelEvent_l(struct sound_trigger_model_event *event) { sp eventMemory; size_t size = event->data_offset + event->data_size; eventMemory = mMemoryDealer->allocate(size); if (eventMemory == 0 || eventMemory->pointer() == NULL) { eventMemory.clear(); return eventMemory; } memcpy(eventMemory->pointer(), event, size); return eventMemory; } void SoundTriggerHwService::sendSoundModelEvent(struct sound_trigger_model_event *event, Module *module) { AutoMutex lock(mServiceLock); sp eventMemory = prepareSoundModelEvent_l(event); if (eventMemory == 0) { return; } sp strongModule; for (size_t i = 0; i < mModules.size(); i++) { if (mModules.valueAt(i).get() == module) { strongModule = mModules.valueAt(i); break; } } if (strongModule == 0) { return; } sendCallbackEvent_l(new CallbackEvent(CallbackEvent::TYPE_SOUNDMODEL, eventMemory, strongModule)); } sp SoundTriggerHwService::prepareServiceStateEvent_l(sound_trigger_service_state_t state) { sp eventMemory; size_t size = sizeof(sound_trigger_service_state_t); eventMemory = mMemoryDealer->allocate(size); if (eventMemory == 0 || eventMemory->pointer() == NULL) { eventMemory.clear(); return eventMemory; } *((sound_trigger_service_state_t *)eventMemory->pointer()) = state; return eventMemory; } // call with mServiceLock held void SoundTriggerHwService::sendServiceStateEvent_l(sound_trigger_service_state_t state, Module *module) { sp eventMemory = prepareServiceStateEvent_l(state); if (eventMemory == 0) { return; } sp strongModule; for (size_t i = 0; i < mModules.size(); i++) { if (mModules.valueAt(i).get() == module) { strongModule = mModules.valueAt(i); break; } } if (strongModule == 0) { return; } sendCallbackEvent_l(new CallbackEvent(CallbackEvent::TYPE_SERVICE_STATE, eventMemory, strongModule)); } // call with mServiceLock held void SoundTriggerHwService::sendCallbackEvent_l(const sp& event) { mCallbackThread->sendCallbackEvent(event); } void SoundTriggerHwService::onCallbackEvent(const sp& event) { ALOGV("onCallbackEvent"); sp module; { AutoMutex lock(mServiceLock); module = event->mModule.promote(); if (module == 0) { return; } } module->onCallbackEvent(event); { AutoMutex lock(mServiceLock); // clear now to execute with mServiceLock locked event->mMemory.clear(); } } #undef LOG_TAG #define LOG_TAG "SoundTriggerHwService::CallbackThread" SoundTriggerHwService::CallbackThread::CallbackThread(const wp& service) : mService(service) { } SoundTriggerHwService::CallbackThread::~CallbackThread() { while (!mEventQueue.isEmpty()) { mEventQueue[0]->mMemory.clear(); mEventQueue.removeAt(0); } } void SoundTriggerHwService::CallbackThread::onFirstRef() { run("soundTrigger cbk", ANDROID_PRIORITY_URGENT_AUDIO); } bool SoundTriggerHwService::CallbackThread::threadLoop() { while (!exitPending()) { sp event; sp service; { Mutex::Autolock _l(mCallbackLock); while (mEventQueue.isEmpty() && !exitPending()) { ALOGV("CallbackThread::threadLoop() sleep"); mCallbackCond.wait(mCallbackLock); ALOGV("CallbackThread::threadLoop() wake up"); } if (exitPending()) { break; } event = mEventQueue[0]; mEventQueue.removeAt(0); service = mService.promote(); } if (service != 0) { service->onCallbackEvent(event); } } return false; } void SoundTriggerHwService::CallbackThread::exit() { Mutex::Autolock _l(mCallbackLock); requestExit(); mCallbackCond.broadcast(); } void SoundTriggerHwService::CallbackThread::sendCallbackEvent( const sp& event) { AutoMutex lock(mCallbackLock); mEventQueue.add(event); mCallbackCond.signal(); } SoundTriggerHwService::CallbackEvent::CallbackEvent(event_type type, sp memory, wp module) : mType(type), mMemory(memory), mModule(module) { } SoundTriggerHwService::CallbackEvent::~CallbackEvent() { } #undef LOG_TAG #define LOG_TAG "SoundTriggerHwService::Module" SoundTriggerHwService::Module::Module(const sp& service, sound_trigger_hw_device* hwDevice, sound_trigger_module_descriptor descriptor, const sp& client) : mService(service), mHwDevice(hwDevice), mDescriptor(descriptor), mClient(client), mServiceState(SOUND_TRIGGER_STATE_NO_INIT) { } SoundTriggerHwService::Module::~Module() { } void SoundTriggerHwService::Module::detach() { ALOGV("detach()"); if (!captureHotwordAllowed()) { return; } { AutoMutex lock(mLock); for (size_t i = 0; i < mModels.size(); i++) { sp model = mModels.valueAt(i); ALOGV("detach() unloading model %d", model->mHandle); if (model->mState == Model::STATE_ACTIVE) { mHwDevice->stop_recognition(mHwDevice, model->mHandle); } mHwDevice->unload_sound_model(mHwDevice, model->mHandle); } mModels.clear(); } if (mClient != 0) { IInterface::asBinder(mClient)->unlinkToDeath(this); } sp service = mService.promote(); if (service == 0) { return; } service->detachModule(this); } status_t SoundTriggerHwService::Module::loadSoundModel(const sp& modelMemory, sound_model_handle_t *handle) { ALOGV("loadSoundModel() handle"); if (!captureHotwordAllowed()) { return PERMISSION_DENIED; } if (modelMemory == 0 || modelMemory->pointer() == NULL) { ALOGE("loadSoundModel() modelMemory is 0 or has NULL pointer()"); return BAD_VALUE; } struct sound_trigger_sound_model *sound_model = (struct sound_trigger_sound_model *)modelMemory->pointer(); size_t structSize; if (sound_model->type == SOUND_MODEL_TYPE_KEYPHRASE) { structSize = sizeof(struct sound_trigger_phrase_sound_model); } else { structSize = sizeof(struct sound_trigger_sound_model); } if (sound_model->data_offset < structSize || sound_model->data_size > (UINT_MAX - sound_model->data_offset) || modelMemory->size() < sound_model->data_offset || sound_model->data_size > (modelMemory->size() - sound_model->data_offset)) { android_errorWriteLog(0x534e4554, "30148546"); ALOGE("loadSoundModel() data_size is too big"); return BAD_VALUE; } AutoMutex lock(mLock); if (mModels.size() >= mDescriptor.properties.max_sound_models) { if (mModels.size() == 0) { return INVALID_OPERATION; } ALOGW("loadSoundModel() max number of models exceeded %d making room for a new one", mDescriptor.properties.max_sound_models); unloadSoundModel_l(mModels.valueAt(0)->mHandle); } status_t status = mHwDevice->load_sound_model(mHwDevice, sound_model, SoundTriggerHwService::soundModelCallback, this, handle); if (status != NO_ERROR) { return status; } audio_session_t session; audio_io_handle_t ioHandle; audio_devices_t device; status = AudioSystem::acquireSoundTriggerSession(&session, &ioHandle, &device); if (status != NO_ERROR) { return status; } sp model = new Model(*handle, session, ioHandle, device, sound_model->type); mModels.replaceValueFor(*handle, model); return status; } status_t SoundTriggerHwService::Module::unloadSoundModel(sound_model_handle_t handle) { ALOGV("unloadSoundModel() model handle %d", handle); if (!captureHotwordAllowed()) { return PERMISSION_DENIED; } AutoMutex lock(mLock); return unloadSoundModel_l(handle); } status_t SoundTriggerHwService::Module::unloadSoundModel_l(sound_model_handle_t handle) { ssize_t index = mModels.indexOfKey(handle); if (index < 0) { return BAD_VALUE; } sp model = mModels.valueAt(index); mModels.removeItem(handle); if (model->mState == Model::STATE_ACTIVE) { mHwDevice->stop_recognition(mHwDevice, model->mHandle); model->mState = Model::STATE_IDLE; } AudioSystem::releaseSoundTriggerSession(model->mCaptureSession); return mHwDevice->unload_sound_model(mHwDevice, handle); } status_t SoundTriggerHwService::Module::startRecognition(sound_model_handle_t handle, const sp& dataMemory) { ALOGV("startRecognition() model handle %d", handle); if (!captureHotwordAllowed()) { return PERMISSION_DENIED; } if (dataMemory == 0 || dataMemory->pointer() == NULL) { ALOGE("startRecognition() dataMemory is 0 or has NULL pointer()"); return BAD_VALUE; } struct sound_trigger_recognition_config *config = (struct sound_trigger_recognition_config *)dataMemory->pointer(); if (config->data_offset < sizeof(struct sound_trigger_recognition_config) || config->data_size > (UINT_MAX - config->data_offset) || dataMemory->size() < config->data_offset || config->data_size > (dataMemory->size() - config->data_offset)) { ALOGE("startRecognition() data_size is too big"); return BAD_VALUE; } AutoMutex lock(mLock); if (mServiceState == SOUND_TRIGGER_STATE_DISABLED) { return INVALID_OPERATION; } sp model = getModel(handle); if (model == 0) { return BAD_VALUE; } if (model->mState == Model::STATE_ACTIVE) { return INVALID_OPERATION; } //TODO: get capture handle and device from audio policy service config->capture_handle = model->mCaptureIOHandle; config->capture_device = model->mCaptureDevice; status_t status = mHwDevice->start_recognition(mHwDevice, handle, config, SoundTriggerHwService::recognitionCallback, this); if (status == NO_ERROR) { model->mState = Model::STATE_ACTIVE; model->mConfig = *config; } return status; } status_t SoundTriggerHwService::Module::stopRecognition(sound_model_handle_t handle) { ALOGV("stopRecognition() model handle %d", handle); if (!captureHotwordAllowed()) { return PERMISSION_DENIED; } AutoMutex lock(mLock); sp model = getModel(handle); if (model == 0) { return BAD_VALUE; } if (model->mState != Model::STATE_ACTIVE) { return INVALID_OPERATION; } mHwDevice->stop_recognition(mHwDevice, handle); model->mState = Model::STATE_IDLE; return NO_ERROR; } void SoundTriggerHwService::Module::onCallbackEvent(const sp& event) { ALOGV("onCallbackEvent type %d", event->mType); sp eventMemory = event->mMemory; if (eventMemory == 0 || eventMemory->pointer() == NULL) { return; } if (mClient == 0) { ALOGI("%s mClient == 0", __func__); return; } switch (event->mType) { case CallbackEvent::TYPE_RECOGNITION: { struct sound_trigger_recognition_event *recognitionEvent = (struct sound_trigger_recognition_event *)eventMemory->pointer(); sp client; { AutoMutex lock(mLock); sp model = getModel(recognitionEvent->model); if (model == 0) { ALOGW("%s model == 0", __func__); return; } if (model->mState != Model::STATE_ACTIVE) { ALOGV("onCallbackEvent model->mState %d != Model::STATE_ACTIVE", model->mState); return; } recognitionEvent->capture_session = model->mCaptureSession; model->mState = Model::STATE_IDLE; client = mClient; } if (client != 0) { client->onRecognitionEvent(eventMemory); } } break; case CallbackEvent::TYPE_SOUNDMODEL: { struct sound_trigger_model_event *soundmodelEvent = (struct sound_trigger_model_event *)eventMemory->pointer(); sp client; { AutoMutex lock(mLock); sp model = getModel(soundmodelEvent->model); if (model == 0) { ALOGW("%s model == 0", __func__); return; } client = mClient; } if (client != 0) { client->onSoundModelEvent(eventMemory); } } break; case CallbackEvent::TYPE_SERVICE_STATE: { sp client; { AutoMutex lock(mLock); client = mClient; } if (client != 0) { client->onServiceStateChange(eventMemory); } } break; default: LOG_ALWAYS_FATAL("onCallbackEvent unknown event type %d", event->mType); } } sp SoundTriggerHwService::Module::getModel( sound_model_handle_t handle) { sp model; ssize_t index = mModels.indexOfKey(handle); if (index >= 0) { model = mModels.valueAt(index); } return model; } void SoundTriggerHwService::Module::binderDied( const wp &who __unused) { ALOGW("client binder died for module %d", mDescriptor.handle); detach(); } // Called with mServiceLock held void SoundTriggerHwService::Module::setCaptureState_l(bool active) { ALOGV("Module::setCaptureState_l %d", active); sp service; sound_trigger_service_state_t state; Vector< sp > events; { AutoMutex lock(mLock); state = (active && !mDescriptor.properties.concurrent_capture) ? SOUND_TRIGGER_STATE_DISABLED : SOUND_TRIGGER_STATE_ENABLED; if (state == mServiceState) { return; } mServiceState = state; service = mService.promote(); if (service == 0) { return; } if (state == SOUND_TRIGGER_STATE_ENABLED) { goto exit; } for (size_t i = 0; i < mModels.size(); i++) { sp model = mModels.valueAt(i); if (model->mState == Model::STATE_ACTIVE) { mHwDevice->stop_recognition(mHwDevice, model->mHandle); // keep model in ACTIVE state so that event is processed by onCallbackEvent() struct sound_trigger_phrase_recognition_event phraseEvent; memset(&phraseEvent, 0, sizeof(struct sound_trigger_phrase_recognition_event)); switch (model->mType) { case SOUND_MODEL_TYPE_KEYPHRASE: phraseEvent.num_phrases = model->mConfig.num_phrases; for (size_t i = 0; i < phraseEvent.num_phrases; i++) { phraseEvent.phrase_extras[i] = model->mConfig.phrases[i]; } break; case SOUND_MODEL_TYPE_UNKNOWN: default: break; } phraseEvent.common.status = RECOGNITION_STATUS_ABORT; phraseEvent.common.type = model->mType; phraseEvent.common.model = model->mHandle; phraseEvent.common.data_size = 0; sp eventMemory = service->prepareRecognitionEvent_l(&phraseEvent.common); if (eventMemory != 0) { events.add(eventMemory); } } } } for (size_t i = 0; i < events.size(); i++) { service->sendCallbackEvent_l(new CallbackEvent(CallbackEvent::TYPE_RECOGNITION, events[i], this)); } exit: service->sendServiceStateEvent_l(state, this); } SoundTriggerHwService::Model::Model(sound_model_handle_t handle, audio_session_t session, audio_io_handle_t ioHandle, audio_devices_t device, sound_trigger_sound_model_type_t type) : mHandle(handle), mState(STATE_IDLE), mCaptureSession(session), mCaptureIOHandle(ioHandle), mCaptureDevice(device), mType(type) { } status_t SoundTriggerHwService::Module::dump(int fd __unused, const Vector& args __unused) { String8 result; return NO_ERROR; } }; // namespace android