blob: 2175371458c6f6fe7d4c3ff3bd9c0378708e1729 [file] [log] [blame]
/*
* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define LOG_TAG "sthal_SoundTriggerSession"
#define ATRACE_TAG (ATRACE_TAG_AUDIO | ATRACE_TAG_HAL)
#define LOG_NDEBUG 0
/*#define VERY_VERY_VERBOSE_LOGGING*/
#ifdef VERY_VERY_VERBOSE_LOGGING
#define ALOGVV ALOGV
#else
#define ALOGVV(a...) do { } while(0)
#endif
#include "SoundTriggerSession.h"
#include <log/log.h>
#include <utils/Trace.h>
#include <cstring>
#include <chrono>
#include <thread>
#include "PalApi.h"
SoundTriggerSession::SoundTriggerSession(sound_model_handle_t handle,
audio_hw_call_back_t callback)
{
state_ = IDLE;
sm_handle_ = handle;
rec_config_payload_ = nullptr;
rec_config_ = nullptr;
hal_callback_ = callback;
pal_handle_ = nullptr;
}
SoundTriggerSession::~SoundTriggerSession()
{
if (pal_handle_ && state_ != IDLE)
pal_stream_close(pal_handle_);
pal_handle_ = nullptr;
if (rec_config_payload_) {
free(rec_config_payload_);
rec_config_payload_ = nullptr;
}
rec_config_ = nullptr;
}
int SoundTriggerSession::pal_callback(
pal_stream_handle_t *stream_handle,
uint32_t event_id,
uint32_t *event_data,
uint32_t event_size,
uint64_t cookie)
{
int32_t status = 0;
bool lock_status = false;
unsigned int size = 0;
int i = 0;
int j = 0;
recognition_callback_t callback;
SoundTriggerSession *session = nullptr;
struct pal_st_recognition_event *event = nullptr;
struct sound_trigger_recognition_event *st_event = nullptr;
struct sound_trigger_phrase_recognition_event *phrase_event = nullptr;
struct pal_st_phrase_recognition_event *pal_phrase_event = nullptr;
if (!stream_handle || !event_data) {
status = -EINVAL;
ALOGE("%s: error, invalid stream handle or event data", __func__);
goto exit;
}
ALOGD("%s: stream_handle (%p), event_id (%x), event_size (%d),"
"cookie (%" PRIu64 ")", __func__, stream_handle, event_id,
event_size, cookie);
session = (SoundTriggerSession *)cookie;
/*
* Sometimes Client may call unload directly, which may get blocked
* in PAL when releasing second stage engine thread, as it is waiting
* for this callback to finish. Check if session state changes to non
* ACTIVE state.
*/
do {
lock_status = session->ses_mutex_.try_lock();
} while(!lock_status && (session->state_ == ACTIVE));
if (session->state_ != ACTIVE) {
ALOGW("%s: skip notification as client has stopped", __func__);
goto exit;
}
event = (struct pal_st_recognition_event *)event_data;
if (event->type == PAL_SOUND_MODEL_TYPE_GENERIC) {
// parse event and check if keyword detected
size = sizeof(struct sound_trigger_recognition_event) +
event->data_size;
st_event = (struct sound_trigger_recognition_event *)calloc(1, size);
if (!st_event) {
status = -ENOMEM;
ALOGE("%s: error, failed to allocate recognition event", __func__);
goto exit;
}
st_event->data_offset = sizeof(struct sound_trigger_recognition_event);
} else if (event->type == PAL_SOUND_MODEL_TYPE_KEYPHRASE) {
size = sizeof(struct sound_trigger_phrase_recognition_event) +
event->data_size;
phrase_event =
(struct sound_trigger_phrase_recognition_event *)calloc(1, size);
if (!phrase_event) {
status = -ENOMEM;
ALOGE("%s: error, failed to allocate recognition event", __func__);
goto exit;
}
st_event = (struct sound_trigger_recognition_event *)phrase_event;
st_event->data_offset =
sizeof(struct sound_trigger_phrase_recognition_event);
// copy data only related to phrase event
pal_phrase_event = (struct pal_st_phrase_recognition_event *)event;
phrase_event->num_phrases = pal_phrase_event->num_phrases;
for (i = 0; i < phrase_event->num_phrases; i++) {
phrase_event->phrase_extras[i].id =
pal_phrase_event->phrase_extras[i].id;
phrase_event->phrase_extras[i].recognition_modes =
pal_phrase_event->phrase_extras[i].recognition_modes;
phrase_event->phrase_extras[i].confidence_level =
pal_phrase_event->phrase_extras[i].confidence_level;
phrase_event->phrase_extras[i].num_levels =
pal_phrase_event->phrase_extras[i].num_levels;
for (j = 0; j < phrase_event->phrase_extras[i].num_levels; j++) {
phrase_event->phrase_extras[i].levels[j].user_id =
pal_phrase_event->phrase_extras[i].levels[j].user_id;
phrase_event->phrase_extras[i].levels[j].level =
pal_phrase_event->phrase_extras[i].levels[j].level;
}
}
} else {
ALOGE("%s: Invalid event type :%d", __func__, event->type);
status = -EINVAL;
goto exit;
}
// copy members inside structrue
st_event->status = event->status;
st_event->type = (sound_trigger_sound_model_type_t)event->type;
st_event->model = session->GetSoundModelHandle();
st_event->capture_available = event->capture_available;
st_event->capture_session = session->GetCaptureHandle();
st_event->capture_delay_ms = event->capture_delay_ms;
st_event->capture_preamble_ms = event->capture_preamble_ms;
st_event->trigger_in_data = event->trigger_in_data;
st_event->audio_config.sample_rate = event->media_config.sample_rate;
if (event->media_config.ch_info.channels == 1)
st_event->audio_config.channel_mask = AUDIO_CHANNEL_IN_MONO;
else if (event->media_config.ch_info.channels == 2)
st_event->audio_config.channel_mask = AUDIO_CHANNEL_IN_STEREO;
st_event->audio_config.format = AUDIO_FORMAT_PCM_16_BIT;
st_event->data_size = event->data_size;
// copy opaque data
memcpy((uint8_t *)st_event + st_event->data_offset,
(uint8_t *)event + event->data_offset,
st_event->data_size);
// callback to SoundTriggerService
session->GetRecognitionCallback(&callback);
session->ses_mutex_.unlock();
lock_status = false;
ATRACE_BEGIN("sthal: client detection callback");
callback(st_event, session->GetCookie());
ATRACE_END();
exit:
// release resources allocated
if (phrase_event)
free(phrase_event);
else if (st_event)
free(st_event);
if (lock_status)
session->ses_mutex_.unlock();
ALOGV("%s: Exit, status %d", __func__, status);
return status;
}
bool SoundTriggerSession::IsACDSoundModel(struct sound_trigger_sound_model *sound_model)
{
//todo: get this from PAL instead of hardcoding.
const sound_trigger_uuid_t qc_acd_uuid = { 0x4e93281b, 0x296e, 0x4d73, 0x9833,
{ 0x27, 0x10, 0xc3, 0xc7, 0xc1, 0xdb } };
if (sound_model &&
!std::memcmp(&sound_model->vendor_uuid, &qc_acd_uuid,
sizeof(sound_trigger_uuid_t)))
return true;
else
return false;
}
int SoundTriggerSession::OpenPALStream(pal_stream_type_t stream_type)
{
int status = 0;
struct pal_stream_attributes stream_attributes;
struct pal_device device;
ALOGV("%s: Enter", __func__);
device.id = PAL_DEVICE_IN_HANDSET_VA_MIC; // To-Do: convert into PAL Device
device.config.sample_rate = 48000;
device.config.bit_width = 16;
device.config.ch_info.channels = 2;
device.config.ch_info.ch_map[0] = PAL_CHMAP_CHANNEL_FL;
device.config.ch_info.ch_map[1] = PAL_CHMAP_CHANNEL_FR;
device.config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE;
stream_attributes.type = stream_type;
stream_attributes.flags = (pal_stream_flags_t)0;
stream_attributes.direction = PAL_AUDIO_INPUT;
stream_attributes.in_media_config.sample_rate = 16000;
stream_attributes.in_media_config.bit_width = 16;
stream_attributes.in_media_config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE;
stream_attributes.in_media_config.ch_info.channels = 1;
stream_attributes.in_media_config.ch_info.ch_map[0] = PAL_CHMAP_CHANNEL_FL;
ALOGD("%s:(%x:status)%d", __func__, status, __LINE__);
status = pal_stream_open(&stream_attributes,
1,
&device,
0,
nullptr,
&pal_callback,
(uint64_t)this,
&pal_handle_);
ALOGD("%s:(%x:status)%d", __func__, status, __LINE__);
if (status) {
ALOGE("%s: Pal Stream Open Error (%x)", __func__, status);
status = -EINVAL;
goto exit;
}
exit:
ALOGV("%s: Exit, status = %d", __func__, status);
return status;
}
int SoundTriggerSession::StopRecognition_l()
{
int status = 0;
ALOGV("%s: Enter", __func__);
// deregister from audio hal
RegisterHalEvent(false);
// stop pal stream
status = pal_stream_stop(pal_handle_);
if (status) {
ALOGE("%s: error, failed to stop pal stream, status = %d",
__func__, status);
}
if (rec_config_payload_) {
free(rec_config_payload_);
rec_config_payload_ = nullptr;
}
rec_config_ = nullptr;
state_ = STOPPED;
ALOGV("%s: Exit, status = %d", __func__, status);
return status;
}
int SoundTriggerSession::LoadSoundModel(
struct sound_trigger_sound_model *sound_model)
{
int status = 0;
struct pal_st_sound_model *pal_common_sm = nullptr;
struct pal_st_phrase_sound_model *pal_phrase_sm = nullptr;
struct sound_trigger_sound_model *common_sm = nullptr;
struct sound_trigger_phrase_sound_model *phrase_sm = nullptr;
pal_param_payload *param_payload = nullptr;
unsigned int size = 0;
pal_stream_type_t stream_type = PAL_STREAM_VOICE_UI;
ALOGV("%s: Enter", __func__);
std::lock_guard<std::mutex> lck(ses_mutex_);
if (IsACDSoundModel(sound_model))
stream_type = PAL_STREAM_ACD;
// open pal stream
status = OpenPALStream(stream_type);
if (status) {
ALOGE("%s: error, failed to open PAL stream", __func__);
goto exit;
}
// parse sound model into pal_sound_model
if (sound_model->type == SOUND_MODEL_TYPE_GENERIC) {
common_sm = sound_model;
if ((stream_type != PAL_STREAM_ACD) &&
(!common_sm->data_size ||
(common_sm->data_offset < sizeof(*common_sm)))) {
ALOGE("%s: Invalid Generic sound model params "
"data size=%d, data offset=%d", __func__,
common_sm->data_size, common_sm->data_offset);
status = -EINVAL;
goto exit;
}
size = sizeof(struct pal_st_sound_model) +
common_sm->data_size;
param_payload = (pal_param_payload *)calloc(1,
sizeof(pal_param_payload) + size);
if (!param_payload) {
ALOGE("%s: error, failed to allocate pal sound model", __func__);
status = -ENOMEM;
goto exit;
}
param_payload->payload_size = size;
pal_common_sm = (struct pal_st_sound_model *)param_payload->payload;
pal_common_sm->type = (pal_st_sound_model_type_t)common_sm->type;
memcpy(&pal_common_sm->uuid, &common_sm->uuid,
sizeof(struct st_uuid));
memcpy(&pal_common_sm->vendor_uuid, &common_sm->vendor_uuid,
sizeof(struct st_uuid));
pal_common_sm->data_size = common_sm->data_size;
pal_common_sm->data_offset = sizeof(struct pal_st_sound_model);
// data_size is zero for ACD streams and non-zero for other streams
if (pal_common_sm->data_size)
memcpy((uint8_t *)pal_common_sm + pal_common_sm->data_offset,
(uint8_t *)common_sm + common_sm->data_offset,
pal_common_sm->data_size);
} else if (sound_model->type == SOUND_MODEL_TYPE_KEYPHRASE) {
phrase_sm = (struct sound_trigger_phrase_sound_model *)sound_model;
if ((phrase_sm->common.data_size == 0) ||
(phrase_sm->common.data_offset < sizeof(*phrase_sm)) ||
(phrase_sm->common.type != SOUND_MODEL_TYPE_KEYPHRASE) ||
(phrase_sm->num_phrases == 0)) {
ALOGE("%s: Invalid phrase sound model params "
"data size=%d, data offset=%d, type=%d phrases=%d",
__func__, phrase_sm->common.data_size,
phrase_sm->common.data_offset, phrase_sm->common.type,
phrase_sm->num_phrases);
status = -EINVAL;
goto exit;
}
size = sizeof(struct pal_st_phrase_sound_model) +
phrase_sm->common.data_size;
param_payload = (pal_param_payload *)calloc(1,
sizeof(pal_param_payload) + size);
if (!param_payload) {
ALOGE("%s: error, failed to allocate pal phrase sound model",
__func__);
status = -ENOMEM;
goto exit;
}
param_payload->payload_size = size;
pal_phrase_sm =
(struct pal_st_phrase_sound_model *)param_payload->payload;
pal_phrase_sm->common.type =
(pal_st_sound_model_type_t)phrase_sm->common.type;
memcpy(&pal_phrase_sm->common.uuid, &phrase_sm->common.uuid,
sizeof(struct st_uuid));
memcpy(&pal_phrase_sm->common.vendor_uuid,
&phrase_sm->common.vendor_uuid,
sizeof(struct st_uuid));
pal_phrase_sm->common.data_size = phrase_sm->common.data_size;
pal_phrase_sm->common.data_offset =
sizeof(struct pal_st_phrase_sound_model);
pal_phrase_sm->num_phrases = phrase_sm->num_phrases;
for (int i = 0; i < pal_phrase_sm->num_phrases; i++) {
pal_phrase_sm->phrases[i].id = phrase_sm->phrases[i].id;
pal_phrase_sm->phrases[i].recognition_mode =
phrase_sm->phrases[i].recognition_mode;
pal_phrase_sm->phrases[i].num_users =
phrase_sm->phrases[i].num_users;
for (int j = 0; j < pal_phrase_sm->phrases[i].num_users; j++)
pal_phrase_sm->phrases[i].users[j] =
phrase_sm->phrases[i].users[j];
memcpy(pal_phrase_sm->phrases[i].locale,
phrase_sm->phrases[i].locale,
SOUND_TRIGGER_MAX_LOCALE_LEN);
memcpy(pal_phrase_sm->phrases[i].text,
phrase_sm->phrases[i].text,
SOUND_TRIGGER_MAX_STRING_LEN);
}
memcpy((uint8_t *)pal_phrase_sm + pal_phrase_sm->common.data_offset,
(uint8_t *)phrase_sm + phrase_sm->common.data_offset,
pal_phrase_sm->common.data_size);
pal_common_sm = (struct pal_st_sound_model *)pal_phrase_sm;
} else {
ALOGE("%s: error, unknown sound model type %d",
__func__, sound_model->type);
status = -EINVAL;
goto exit;
}
// load sound model with pal api
status = pal_stream_set_param(pal_handle_,
PAL_PARAM_ID_LOAD_SOUND_MODEL,
param_payload);
if (status) {
ALOGE("%s: error, failed to load sound model into PAL, status = %d",
__func__, status);
goto exit;
}
state_ = LOADED;
exit:
if (param_payload)
free(param_payload);
ALOGV("%s: Exit, status = %d", __func__, status);
return status;
}
int SoundTriggerSession::UnloadSoundModel()
{
int status = 0;
ALOGV("%s: Enter", __func__);
std::lock_guard<std::mutex> lck(ses_mutex_);
if (state_ == ACTIVE) {
status = StopRecognition_l();
if (status) {
ALOGE("%s: error, failed to stop recognition, status = %d",
__func__, status);
}
}
status = pal_stream_close(pal_handle_);
if (status) {
ALOGE("%s: error, failed to close pal stream, status = %d",
__func__, status);
}
pal_handle_ = nullptr;
if (rec_config_payload_) {
free(rec_config_payload_);
rec_config_payload_ = nullptr;
}
rec_config_ = nullptr;
state_ = IDLE;
exit:
ALOGV("%s: Exit, status = %d", __func__, status);
return status;
}
int SoundTriggerSession::StartRecognition(
const struct sound_trigger_recognition_config *config,
recognition_callback_t callback,
void *cookie,
uint32_t version)
{
int status = 0;
unsigned int size = 0;
ALOGV("%s: Enter, state = %d", __func__, state_);
std::lock_guard<std::mutex> lck(ses_mutex_);
if (rec_config_payload_) {
free(rec_config_payload_); // valid due to subsequent start after a detection
rec_config_payload_ = nullptr;
}
size = sizeof(struct pal_st_recognition_config) +
config->data_size;
rec_config_payload_ = (pal_param_payload *)calloc(1,
sizeof(pal_param_payload) + size);
if (!rec_config_payload_) {
ALOGE("%s: error, failed to allocate pal recognition config", __func__);
status = -ENOMEM;
goto exit;
}
rec_config_payload_->payload_size = size;
rec_config_ = (struct pal_st_recognition_config *)rec_config_payload_->payload;
rec_config_->capture_handle = (int32_t)config->capture_handle;
rec_config_->capture_device = (uint32_t)config->capture_device;
rec_config_->capture_requested = config->capture_requested;
rec_config_->num_phrases = config->num_phrases;
for (int i = 0; i < rec_config_->num_phrases; i++) {
rec_config_->phrases[i].id = config->phrases[i].id;
rec_config_->phrases[i].recognition_modes =
config->phrases[i].recognition_modes;
rec_config_->phrases[i].confidence_level =
config->phrases[i].confidence_level;
rec_config_->phrases[i].num_levels = config->phrases[i].num_levels;
for (int j = 0; j < rec_config_->phrases[i].num_levels; j++) {
rec_config_->phrases[i].levels[j].user_id =
config->phrases[i].levels[j].user_id;
rec_config_->phrases[i].levels[j].level =
config->phrases[i].levels[j].level;
}
}
rec_config_->data_size = config->data_size;
rec_config_->data_offset = sizeof(struct pal_st_recognition_config);
rec_config_->cookie = (uint8_t *)this;
if (version == SOUND_TRIGGER_DEVICE_API_VERSION_1_3) {
memcpy((uint8_t *)rec_config_ + rec_config_->data_offset,
(uint8_t *)config + config->data_offset -
sizeof(struct sound_trigger_recognition_config_header),
rec_config_->data_size);
} else {
memcpy((uint8_t *)rec_config_ + rec_config_->data_offset,
(uint8_t *)config + config->data_offset,
rec_config_->data_size);
}
// set recognition config
status = pal_stream_set_param(pal_handle_,
PAL_PARAM_ID_RECOGNITION_CONFIG,
rec_config_payload_);
if (status) {
ALOGE("%s: error, failed to set recognition config, status = %d",
__func__, status);
goto exit;
}
rec_callback_ = callback;
cookie_ = cookie;
// start pal stream
status = pal_stream_start(pal_handle_);
if (status) {
ALOGE("%s: error, failed to start pal stream, status = %d",
__func__, status);
goto exit;
}
state_ = ACTIVE;
// register to audio hal
RegisterHalEvent(true);
exit:
if (status && rec_config_payload_) {
free(rec_config_payload_);
rec_config_payload_ = nullptr;
rec_config_ = nullptr;
}
ALOGV("%s: Exit, status = %d", __func__, status);
return status;
}
int SoundTriggerSession::StopRecognition()
{
int status = 0;
ALOGV("%s: Enter", __func__);
std::lock_guard<std::mutex> lck(ses_mutex_);
// deregister from audio hal
RegisterHalEvent(false);
// stop pal stream
status = pal_stream_stop(pal_handle_);
if (status) {
ALOGE("%s: error, failed to stop pal stream, status = %d",
__func__, status);
}
if (rec_config_payload_) {
free(rec_config_payload_);
rec_config_payload_ = nullptr;
}
rec_config_ = nullptr;
state_ = STOPPED;
ALOGV("%s: Exit, status = %d", __func__, status);
return status;
}
void SoundTriggerSession::RegisterHalEvent(bool is_register)
{
struct sound_trigger_event_info event_info;
if ((rec_config_ && rec_config_->capture_requested) && hal_callback_) {
if (is_register) {
ALOGD("%s:[c%d] ST_EVENT_SESSION_REGISTER capture_handle %d",
__func__, sm_handle_, rec_config_->capture_handle);
event_info.st_ses.p_ses = (void *)pal_handle_;
event_info.st_ses.capture_handle = rec_config_->capture_handle;
hal_callback_(ST_EVENT_SESSION_REGISTER, &event_info);
} else {
ALOGD("%s:[c%d] ST_EVENT_SESSION_DEREGISTER capture_handle %d",
__func__, sm_handle_, rec_config_->capture_handle);
event_info.st_ses.p_ses = (void *)pal_handle_;
event_info.st_ses.capture_handle = rec_config_->capture_handle;
hal_callback_(ST_EVENT_SESSION_DEREGISTER, &event_info);
}
}
}
sound_model_handle_t SoundTriggerSession::GetSoundModelHandle()
{
return sm_handle_;
}
int SoundTriggerSession::GetCaptureHandle()
{
int handle = 0;
if (rec_config_)
handle = rec_config_->capture_handle;
return handle;
}
int SoundTriggerSession::GetModuleVersion(char version[])
{
int status = 0;
pal_param_payload *param_payload = nullptr;
struct version_arch_payload *version_payload = nullptr;
ALOGV("%s: Enter", __func__);
std::lock_guard<std::mutex> lck(ses_mutex_);
status = OpenPALStream(PAL_STREAM_VOICE_UI);
if (status) {
ALOGE("%s: Failed to open pal stream, status = %d", __func__, status);
goto exit;
}
status = pal_stream_get_param(pal_handle_,
PAL_PARAM_ID_WAKEUP_MODULE_VERSION, &param_payload);
if (status) {
ALOGE("%s: Failed to get version, status = %d", __func__, status);
goto exit;
}
version_payload = (struct version_arch_payload *)param_payload;
snprintf(version, SOUND_TRIGGER_MAX_STRING_LEN, "%d, %s",
version_payload->version, version_payload->arch);
exit:
if (pal_handle_) {
status = pal_stream_close(pal_handle_);
if (status) {
ALOGE("%s: error, failed to close pal stream, status = %d",
__func__, status);
}
pal_handle_ = nullptr;
}
ALOGV("%s: Exit", __func__);
return 0;
}
void *SoundTriggerSession::GetCookie()
{
return cookie_;
}
void SoundTriggerSession::GetRecognitionCallback(
recognition_callback_t *callback)
{
*callback = rec_callback_;
}