/*
* 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 "offload_effect_bundle"
//#define LOG_NDEBUG 0
#include <stdlib.h>
#include <cutils/list.h>
#include <cutils/log.h>
#include <system/thread_defs.h>
#include <tinyalsa/asoundlib.h>
#include <hardware/audio_effect.h>
#include "bundle.h"
#include "equalizer.h"
#include "bass_boost.h"
#include "virtualizer.h"
#include "reverb.h"
enum {
EFFECT_STATE_UNINITIALIZED,
EFFECT_STATE_INITIALIZED,
EFFECT_STATE_ACTIVE,
};
const effect_descriptor_t *descriptors[] = {
&equalizer_descriptor,
&bassboost_descriptor,
&virtualizer_descriptor,
&aux_env_reverb_descriptor,
&ins_env_reverb_descriptor,
&aux_preset_reverb_descriptor,
&ins_preset_reverb_descriptor,
NULL,
};
pthread_once_t once = PTHREAD_ONCE_INIT;
int init_status;
/*
* list of created effects.
* Updated by offload_effects_bundle_hal_start_output()
* and offload_effects_bundle_hal_stop_output()
*/
struct listnode created_effects_list;
/*
* list of active output streams.
* Updated by offload_effects_bundle_hal_start_output()
* and offload_effects_bundle_hal_stop_output()
*/
struct listnode active_outputs_list;
/*
* lock must be held when modifying or accessing
* created_effects_list or active_outputs_list
*/
pthread_mutex_t lock;
/*
* Local functions
*/
static void init_once() {
list_init(&created_effects_list);
list_init(&active_outputs_list);
pthread_mutex_init(&lock, NULL);
init_status = 0;
}
int lib_init()
{
pthread_once(&once, init_once);
return init_status;
}
bool effect_exists(effect_context_t *context)
{
struct listnode *node;
list_for_each(node, &created_effects_list) {
effect_context_t *fx_ctxt = node_to_item(node,
effect_context_t,
effects_list_node);
if (fx_ctxt == context) {
return true;
}
}
return false;
}
output_context_t *get_output(audio_io_handle_t output)
{
struct listnode *node;
list_for_each(node, &active_outputs_list) {
output_context_t *out_ctxt = node_to_item(node,
output_context_t,
outputs_list_node);
if (out_ctxt->handle == output)
return out_ctxt;
}
return NULL;
}
void add_effect_to_output(output_context_t * output, effect_context_t *context)
{
struct listnode *fx_node;
list_for_each(fx_node, &output->effects_list) {
effect_context_t *fx_ctxt = node_to_item(fx_node,
effect_context_t,
output_node);
if (fx_ctxt == context)
return;
}
list_add_tail(&output->effects_list, &context->output_node);
if (context->ops.start)
context->ops.start(context, output);
}
void remove_effect_from_output(output_context_t * output,
effect_context_t *context)
{
struct listnode *fx_node;
list_for_each(fx_node, &output->effects_list) {
effect_context_t *fx_ctxt = node_to_item(fx_node,
effect_context_t,
output_node);
if (fx_ctxt == context) {
if (context->ops.stop)
context->ops.stop(context, output);
list_remove(&context->output_node);
return;
}
}
}
bool effects_enabled()
{
struct listnode *out_node;
list_for_each(out_node, &active_outputs_list) {
struct listnode *fx_node;
output_context_t *out_ctxt = node_to_item(out_node,
output_context_t,
outputs_list_node);
list_for_each(fx_node, &out_ctxt->effects_list) {
effect_context_t *fx_ctxt = node_to_item(fx_node,
effect_context_t,
output_node);
if ((fx_ctxt->state == EFFECT_STATE_ACTIVE) &&
(fx_ctxt->ops.process != NULL))
return true;
}
}
return false;
}
/*
* Interface from audio HAL
*/
__attribute__ ((visibility ("default")))
int offload_effects_bundle_hal_start_output(audio_io_handle_t output, int pcm_id)
{
int ret = 0;
struct listnode *node;
char mixer_string[128];
output_context_t * out_ctxt = NULL;
ALOGV("%s output %d pcm_id %d", __func__, output, pcm_id);
if (lib_init() != 0)
return init_status;
pthread_mutex_lock(&lock);
if (get_output(output) != NULL) {
ALOGW("%s output already started", __func__);
ret = -ENOSYS;
goto exit;
}
out_ctxt = (output_context_t *)
malloc(sizeof(output_context_t));
out_ctxt->handle = output;
out_ctxt->pcm_device_id = pcm_id;
/* populate the mixer control to send offload parameters */
snprintf(mixer_string, sizeof(mixer_string),
"%s %d", "Audio Effects Config", out_ctxt->pcm_device_id);
out_ctxt->mixer = mixer_open(MIXER_CARD);
if (!out_ctxt->mixer) {
ALOGE("Failed to open mixer");
out_ctxt->ctl = NULL;
ret = -EINVAL;
free(out_ctxt);
goto exit;
} else {
out_ctxt->ctl = mixer_get_ctl_by_name(out_ctxt->mixer, mixer_string);
if (!out_ctxt->ctl) {
ALOGE("mixer_get_ctl_by_name failed");
mixer_close(out_ctxt->mixer);
out_ctxt->mixer = NULL;
ret = -EINVAL;
free(out_ctxt);
goto exit;
}
}
list_init(&out_ctxt->effects_list);
list_for_each(node, &created_effects_list) {
effect_context_t *fx_ctxt = node_to_item(node,
effect_context_t,
effects_list_node);
if (fx_ctxt->out_handle == output) {
if (fx_ctxt->ops.start)
fx_ctxt->ops.start(fx_ctxt, out_ctxt);
list_add_tail(&out_ctxt->effects_list, &fx_ctxt->output_node);
}
}
list_add_tail(&active_outputs_list, &out_ctxt->outputs_list_node);
exit:
pthread_mutex_unlock(&lock);
return ret;
}
__attribute__ ((visibility ("default")))
int offload_effects_bundle_hal_stop_output(audio_io_handle_t output, int pcm_id)
{
int ret = 0;
struct listnode *node;
struct listnode *fx_node;
output_context_t *out_ctxt;
ALOGV("%s output %d pcm_id %d", __func__, output, pcm_id);
if (lib_init() != 0)
return init_status;
pthread_mutex_lock(&lock);
out_ctxt = get_output(output);
if (out_ctxt == NULL) {
ALOGW("%s output not started", __func__);
ret = -ENOSYS;
goto exit;
}
if (out_ctxt->mixer)
mixer_close(out_ctxt->mixer);
list_for_each(fx_node, &out_ctxt->effects_list) {
effect_context_t *fx_ctxt = node_to_item(fx_node,
effect_context_t,
output_node);
if (fx_ctxt->ops.stop)
fx_ctxt->ops.stop(fx_ctxt, out_ctxt);
}
list_remove(&out_ctxt->outputs_list_node);
free(out_ctxt);
exit:
pthread_mutex_unlock(&lock);
return ret;
}
/*
* Effect operations
*/
int set_config(effect_context_t *context, effect_config_t *config)
{
context->config = *config;
if (context->ops.reset)
context->ops.reset(context);
return 0;
}
void get_config(effect_context_t *context, effect_config_t *config)
{
*config = context->config;
}
/*
* Effect Library Interface Implementation
*/
int effect_lib_create(const effect_uuid_t *uuid,
int32_t sessionId,
int32_t ioId,
effect_handle_t *pHandle) {
int ret;
int i;
ALOGV("%s: sessionId: %d, ioId: %d", __func__, sessionId, ioId);
if (lib_init() != 0)
return init_status;
if (pHandle == NULL || uuid == NULL)
return -EINVAL;
for (i = 0; descriptors[i] != NULL; i++) {
if (memcmp(uuid, &descriptors[i]->uuid, sizeof(effect_uuid_t)) == 0)
break;
}
if (descriptors[i] == NULL)
return -EINVAL;
effect_context_t *context;
if (memcmp(uuid, &equalizer_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) {
equalizer_context_t *eq_ctxt = (equalizer_context_t *)
calloc(1, sizeof(equalizer_context_t));
context = (effect_context_t *)eq_ctxt;
context->ops.init = equalizer_init;
context->ops.reset = equalizer_reset;
context->ops.set_parameter = equalizer_set_parameter;
context->ops.get_parameter = equalizer_get_parameter;
context->ops.set_device = equalizer_set_device;
context->ops.enable = equalizer_enable;
context->ops.disable = equalizer_disable;
context->ops.start = equalizer_start;
context->ops.stop = equalizer_stop;
context->desc = &equalizer_descriptor;
eq_ctxt->ctl = NULL;
} else if (memcmp(uuid, &bassboost_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) {
bassboost_context_t *bass_ctxt = (bassboost_context_t *)
calloc(1, sizeof(bassboost_context_t));
context = (effect_context_t *)bass_ctxt;
context->ops.init = bassboost_init;
context->ops.reset = bassboost_reset;
context->ops.set_parameter = bassboost_set_parameter;
context->ops.get_parameter = bassboost_get_parameter;
context->ops.set_device = bassboost_set_device;
context->ops.enable = bassboost_enable;
context->ops.disable = bassboost_disable;
context->ops.start = bassboost_start;
context->ops.stop = bassboost_stop;
context->desc = &bassboost_descriptor;
bass_ctxt->ctl = NULL;
} else if (memcmp(uuid, &virtualizer_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) {
virtualizer_context_t *virt_ctxt = (virtualizer_context_t *)
calloc(1, sizeof(virtualizer_context_t));
context = (effect_context_t *)virt_ctxt;
context->ops.init = virtualizer_init;
context->ops.reset = virtualizer_reset;
context->ops.set_parameter = virtualizer_set_parameter;
context->ops.get_parameter = virtualizer_get_parameter;
context->ops.set_device = virtualizer_set_device;
context->ops.enable = virtualizer_enable;
context->ops.disable = virtualizer_disable;
context->ops.start = virtualizer_start;
context->ops.stop = virtualizer_stop;
context->desc = &virtualizer_descriptor;
virt_ctxt->ctl = NULL;
} else if ((memcmp(uuid, &aux_env_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) ||
(memcmp(uuid, &ins_env_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) ||
(memcmp(uuid, &aux_preset_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) ||
(memcmp(uuid, &ins_preset_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0)) {
reverb_context_t *reverb_ctxt = (reverb_context_t *)
calloc(1, sizeof(reverb_context_t));
context = (effect_context_t *)reverb_ctxt;
context->ops.init = reverb_init;
context->ops.reset = reverb_reset;
context->ops.set_parameter = reverb_set_parameter;
context->ops.get_parameter = reverb_get_parameter;
context->ops.set_device = reverb_set_device;
context->ops.enable = reverb_enable;
context->ops.disable = reverb_disable;
context->ops.start = reverb_start;
context->ops.stop = reverb_stop;
if (memcmp(uuid, &aux_env_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) {
context->desc = &aux_env_reverb_descriptor;
reverb_auxiliary_init(reverb_ctxt);
} else if (memcmp(uuid, &ins_env_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) {
context->desc = &ins_env_reverb_descriptor;
reverb_insert_init(reverb_ctxt);
} else if (memcmp(uuid, &aux_preset_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) {
context->desc = &aux_preset_reverb_descriptor;
reverb_auxiliary_init(reverb_ctxt);
} else if (memcmp(uuid, &ins_preset_reverb_descriptor.uuid,
sizeof(effect_uuid_t)) == 0) {
context->desc = &ins_preset_reverb_descriptor;
reverb_preset_init(reverb_ctxt);
}
reverb_ctxt->ctl = NULL;
} else {
return -EINVAL;
}
context->itfe = &effect_interface;
context->state = EFFECT_STATE_UNINITIALIZED;
context->out_handle = (audio_io_handle_t)ioId;
ret = context->ops.init(context);
if (ret < 0) {
ALOGW("%s init failed", __func__);
free(context);
return ret;
}
context->state = EFFECT_STATE_INITIALIZED;
pthread_mutex_lock(&lock);
list_add_tail(&created_effects_list, &context->effects_list_node);
output_context_t *out_ctxt = get_output(ioId);
if (out_ctxt != NULL)
add_effect_to_output(out_ctxt, context);
pthread_mutex_unlock(&lock);
*pHandle = (effect_handle_t)context;
ALOGV("%s created context %p", __func__, context);
return 0;
}
int effect_lib_release(effect_handle_t handle)
{
effect_context_t *context = (effect_context_t *)handle;
int status;
if (lib_init() != 0)
return init_status;
ALOGV("%s context %p", __func__, handle);
pthread_mutex_lock(&lock);
status = -EINVAL;
if (effect_exists(context)) {
output_context_t *out_ctxt = get_output(context->out_handle);
if (out_ctxt != NULL)
remove_effect_from_output(out_ctxt, context);
list_remove(&context->effects_list_node);
if (context->ops.release)
context->ops.release(context);
free(context);
status = 0;
}
pthread_mutex_unlock(&lock);
return status;
}
int effect_lib_get_descriptor(const effect_uuid_t *uuid,
effect_descriptor_t *descriptor)
{
int i;
if (lib_init() != 0)
return init_status;
if (descriptor == NULL || uuid == NULL) {
ALOGV("%s called with NULL pointer", __func__);
return -EINVAL;
}
for (i = 0; descriptors[i] != NULL; i++) {
if (memcmp(uuid, &descriptors[i]->uuid, sizeof(effect_uuid_t)) == 0) {
*descriptor = *descriptors[i];
return 0;
}
}
return -EINVAL;
}
/*
* Effect Control Interface Implementation
*/
/* Stub function for effect interface: never called for offloaded effects */
int effect_process(effect_handle_t self,
audio_buffer_t *inBuffer __unused,
audio_buffer_t *outBuffer __unused)
{
effect_context_t * context = (effect_context_t *)self;
int status = 0;
ALOGW("%s Called ?????", __func__);
pthread_mutex_lock(&lock);
if (!effect_exists(context)) {
status = -ENOSYS;
goto exit;
}
if (context->state != EFFECT_STATE_ACTIVE) {
status = -ENODATA;
goto exit;
}
exit:
pthread_mutex_unlock(&lock);
return status;
}
int effect_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
void *pCmdData, uint32_t *replySize, void *pReplyData)
{
effect_context_t * context = (effect_context_t *)self;
int retsize;
int status = 0;
pthread_mutex_lock(&lock);
if (!effect_exists(context)) {
status = -ENOSYS;
goto exit;
}
if (context == NULL || context->state == EFFECT_STATE_UNINITIALIZED) {
status = -ENOSYS;
goto exit;
}
switch (cmdCode) {
case EFFECT_CMD_INIT:
if (pReplyData == NULL || *replySize != sizeof(int)) {
status = -EINVAL;
goto exit;
}
if (context->ops.init)
*(int *) pReplyData = context->ops.init(context);
else
*(int *) pReplyData = 0;
break;
case EFFECT_CMD_SET_CONFIG:
if (pCmdData == NULL || cmdSize != sizeof(effect_config_t)
|| pReplyData == NULL || *replySize != sizeof(int)) {
status = -EINVAL;
goto exit;
}
*(int *) pReplyData = set_config(context, (effect_config_t *) pCmdData);
break;
case EFFECT_CMD_GET_CONFIG:
if (pReplyData == NULL ||
*replySize != sizeof(effect_config_t)) {
status = -EINVAL;
goto exit;
}
if (!context->offload_enabled) {
status = -EINVAL;
goto exit;
}
get_config(context, (effect_config_t *)pReplyData);
break;
case EFFECT_CMD_RESET:
if (context->ops.reset)
context->ops.reset(context);
break;
case EFFECT_CMD_ENABLE:
if (pReplyData == NULL || *replySize != sizeof(int)) {
status = -EINVAL;
goto exit;
}
if (context->state != EFFECT_STATE_INITIALIZED) {
status = -ENOSYS;
goto exit;
}
context->state = EFFECT_STATE_ACTIVE;
if (context->ops.enable)
context->ops.enable(context);
ALOGV("%s EFFECT_CMD_ENABLE", __func__);
*(int *)pReplyData = 0;
break;
case EFFECT_CMD_DISABLE:
if (pReplyData == NULL || *replySize != sizeof(int)) {
status = -EINVAL;
goto exit;
}
if (context->state != EFFECT_STATE_ACTIVE) {
status = -ENOSYS;
goto exit;
}
context->state = EFFECT_STATE_INITIALIZED;
if (context->ops.disable)
context->ops.disable(context);
ALOGV("%s EFFECT_CMD_DISABLE", __func__);
*(int *)pReplyData = 0;
break;
case EFFECT_CMD_GET_PARAM: {
if (pCmdData == NULL ||
cmdSize < (int)(sizeof(effect_param_t) + sizeof(uint32_t)) ||
pReplyData == NULL ||
*replySize < (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint16_t)) ||
// constrain memcpy below
((effect_param_t *)pCmdData)->psize > *replySize - sizeof(effect_param_t)) {
status = -EINVAL;
ALOGV("EFFECT_CMD_GET_PARAM invalid command cmdSize %d *replySize %d",
cmdSize, *replySize);
goto exit;
}
if (!context->offload_enabled) {
status = -EINVAL;
goto exit;
}
effect_param_t *q = (effect_param_t *)pCmdData;
memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + q->psize);
effect_param_t *p = (effect_param_t *)pReplyData;
if (context->ops.get_parameter)
context->ops.get_parameter(context, p, replySize);
} break;
case EFFECT_CMD_SET_PARAM: {
if (pCmdData == NULL ||
cmdSize < (int)(sizeof(effect_param_t) + sizeof(uint32_t) +
sizeof(uint16_t)) ||
pReplyData == NULL || *replySize != sizeof(int32_t)) {
status = -EINVAL;
ALOGV("EFFECT_CMD_SET_PARAM invalid command cmdSize %d *replySize %d",
cmdSize, *replySize);
goto exit;
}
*(int32_t *)pReplyData = 0;
effect_param_t *p = (effect_param_t *)pCmdData;
if (context->ops.set_parameter)
*(int32_t *)pReplyData = context->ops.set_parameter(context, p,
*replySize);
} break;
case EFFECT_CMD_SET_DEVICE: {
uint32_t device;
ALOGV("\t EFFECT_CMD_SET_DEVICE start");
if (pCmdData == NULL || cmdSize < sizeof(uint32_t)) {
status = -EINVAL;
ALOGV("EFFECT_CMD_SET_DEVICE invalid command cmdSize %d", cmdSize);
goto exit;
}
device = *(uint32_t *)pCmdData;
if (context->ops.set_device)
context->ops.set_device(context, device);
} break;
case EFFECT_CMD_SET_VOLUME: {
// if pReplyData is NULL, VOL_CTRL is delegated to another effect
if (pReplyData == NULL) {
break;
}
if (pCmdData == NULL || cmdSize != 2 * sizeof(uint32_t) ||
replySize == NULL || *replySize < 2*sizeof(int32_t)) {
return -EINVAL;
}
memcpy(pReplyData, pCmdData, sizeof(int32_t)*2);
} break;
case EFFECT_CMD_SET_AUDIO_MODE:
break;
case EFFECT_CMD_OFFLOAD: {
output_context_t *out_ctxt;
if (cmdSize != sizeof(effect_offload_param_t) || pCmdData == NULL
|| pReplyData == NULL || *replySize != sizeof(int)) {
ALOGV("%s EFFECT_CMD_OFFLOAD bad format", __func__);
status = -EINVAL;
break;
}
effect_offload_param_t* offload_param = (effect_offload_param_t*)pCmdData;
ALOGV("%s EFFECT_CMD_OFFLOAD offload %d output %d", __func__,
offload_param->isOffload, offload_param->ioHandle);
*(int *)pReplyData = 0;
context->offload_enabled = offload_param->isOffload;
if (context->out_handle == offload_param->ioHandle)
break;
out_ctxt = get_output(context->out_handle);
if (out_ctxt != NULL)
remove_effect_from_output(out_ctxt, context);
context->out_handle = offload_param->ioHandle;
out_ctxt = get_output(context->out_handle);
if (out_ctxt != NULL)
add_effect_to_output(out_ctxt, context);
} break;
default:
if (cmdCode >= EFFECT_CMD_FIRST_PROPRIETARY && context->ops.command)
status = context->ops.command(context, cmdCode, cmdSize,
pCmdData, replySize, pReplyData);
else {
ALOGW("%s invalid command %d", __func__, cmdCode);
status = -EINVAL;
}
break;
}
exit:
pthread_mutex_unlock(&lock);
return status;
}
/* Effect Control Interface Implementation: get_descriptor */
int effect_get_descriptor(effect_handle_t self,
effect_descriptor_t *descriptor)
{
effect_context_t *context = (effect_context_t *)self;
if (!effect_exists(context) || (descriptor == NULL))
return -EINVAL;
*descriptor = *context->desc;
return 0;
}
bool effect_is_active(effect_context_t * ctxt) {
return ctxt->state == EFFECT_STATE_ACTIVE;
}
/* effect_handle_t interface implementation for offload effects */
const struct effect_interface_s effect_interface = {
effect_process,
effect_command,
effect_get_descriptor,
NULL,
};
__attribute__ ((visibility ("default")))
audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
.tag = AUDIO_EFFECT_LIBRARY_TAG,
.version = EFFECT_LIBRARY_API_VERSION,
.name = "Offload Effects Bundle Library",
.implementor = "The Android Open Source Project",
.create_effect = effect_lib_create,
.release_effect = effect_lib_release,
.get_descriptor = effect_lib_get_descriptor,
};