blob: ddca6b884d0eeab3513a554a01126811a43660e3 [file] [log] [blame]
/*
* Copyright (c) 2017, 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 "audio_hw_loopback"
/*#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
#define MAX_NUM_PATCHES 1
#define MAX_NUM_HW_LOOPBACK_PATCHES 1
#define PATCH_HANDLE_INVALID 0xFFFF
#define MAX_SOURCE_PORTS_PER_PATCH 1
#define MAX_SINK_PORTS_PER_PATCH 1
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <dlfcn.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <cutils/properties.h>
#include <cutils/str_parms.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include "audio_utils/primitives.h"
#include "audio_hw.h"
#include "platform_api.h"
#include <platform.h>
#include <system/thread_defs.h>
#include <cutils/sched_policy.h>
#include "audio_extn.h"
#include "sound/compress_params.h"
#include <system/audio.h>
/*
* Unique patch handle ID = (unique_patch_handle_type << 8 | patch_handle_num)
* Eg : HDMI_IN_SPKR_OUT handles can be 0x1000, 0x1001 and so on..
*/
typedef enum patch_handle_type {
AUDIO_PATCH_HDMI_IN_SPKR_OUT=0x10,
AUDIO_PATCH_SPDIF_IN_SPKR_OUT,
AUDIO_PATCH_MIC_IN_SPKR_OUT,
AUDIO_PATCH_MIC_IN_HDMI_OUT
} patch_handle_type_t;
typedef enum patch_state {
PATCH_INACTIVE,// Patch is not created yet
PATCH_CREATED, // Patch created but not in running state yet, probably due
// to lack of proper port config
PATCH_RUNNING, // Patch in running state, moves to this state when patch
// created and proper port config is available
} patch_state_t;
typedef struct loopback_patch {
audio_patch_handle_t patch_handle_id; /* patch unique ID */
struct audio_port_config loopback_source; /* Source port config */
struct audio_port_config loopback_sink; /* Source port config */
struct compress *source_stream; /* Source stream */
struct compress *sink_stream; /* Source stream */
struct stream_inout patch_stream; /* InOut type stream */
patch_state_t patch_state; /* Patch operation state */
} loopback_patch_t;
typedef struct patch_db_struct {
int32_t num_patches;
loopback_patch_t loopback_patch[MAX_NUM_PATCHES];
} patch_db_t;
typedef struct audio_loopback {
struct audio_device *adev;
patch_db_t patch_db;
audio_usecase_t uc_id;
usecase_type_t uc_type;
pthread_mutex_t lock;
} audio_loopback_t;
typedef struct port_info {
audio_port_handle_t id; /* port unique ID */
audio_port_role_t role; /* sink or source */
audio_port_type_t type; /* device, mix ... */
} port_info_t;
/* Audio loopback module struct */
static audio_loopback_t *audio_loopback_mod = NULL;
uint32_t format_to_bitwidth(audio_format_t format)
{
switch (format) {
case AUDIO_FORMAT_PCM_16_BIT:
return 16;
case AUDIO_FORMAT_PCM_8_BIT:
return 8;
case AUDIO_FORMAT_PCM_32_BIT:
return 32;
case AUDIO_FORMAT_PCM_8_24_BIT:
return 32;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
return 24;
default:
return 16;
}
}
/* Initialize patch database */
int init_patch_database(patch_db_t* patch_db)
{
int patch_init_rc = 0, patch_num=0;
patch_db->num_patches = 0;
for (patch_num=0;patch_num < MAX_NUM_PATCHES;patch_num++) {
patch_db->loopback_patch[patch_num].patch_handle_id = (int32_t)
PATCH_HANDLE_INVALID;
}
return patch_init_rc;
}
/* Get patch type based on source and sink ports configuration */
/* Only ports of type 'DEVICE' are supported */
patch_handle_type_t get_loopback_patch_type(loopback_patch_t* loopback_patch)
{
bool is_source_hdmi=false, is_sink_spkr=false;
if (loopback_patch->patch_handle_id != PATCH_HANDLE_INVALID) {
ALOGE("%s, Patch handle already exists", __func__);
return loopback_patch->patch_handle_id;
}
if (loopback_patch->loopback_source.role == AUDIO_PORT_ROLE_SOURCE) {
switch (loopback_patch->loopback_source.type) {
case AUDIO_PORT_TYPE_DEVICE :
if ((loopback_patch->loopback_source.config_mask &
AUDIO_PORT_CONFIG_FORMAT) && (loopback_patch->loopback_source.ext.device.type & AUDIO_DEVICE_IN_HDMI)) {
switch (loopback_patch->loopback_source.format) {
case AUDIO_FORMAT_PCM:
case AUDIO_FORMAT_PCM_16_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
case AUDIO_FORMAT_IEC61937:
case AUDIO_FORMAT_AC3:
case AUDIO_FORMAT_E_AC3:
is_source_hdmi = true;
break;
}
}
break;
default :
break;
//Unsupported as of now, need to extend for other source types
}
}
if (loopback_patch->loopback_sink.role == AUDIO_PORT_ROLE_SINK) {
switch (loopback_patch->loopback_sink.type) {
case AUDIO_PORT_TYPE_DEVICE :
if ((loopback_patch->loopback_sink.config_mask &
AUDIO_PORT_CONFIG_FORMAT) &&
(loopback_patch->loopback_sink.ext.device.type &
AUDIO_DEVICE_OUT_SPEAKER)) {
switch (loopback_patch->loopback_sink.format) {
case AUDIO_FORMAT_PCM:
case AUDIO_FORMAT_PCM_16_BIT:
case AUDIO_FORMAT_PCM_32_BIT:
case AUDIO_FORMAT_PCM_8_24_BIT:
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
is_sink_spkr = true;
break;
default:
break;
}
}
break;
default :
break;
//Unsupported as of now, need to extend for other sink types
}
}
if (is_source_hdmi && is_sink_spkr) {
return AUDIO_PATCH_HDMI_IN_SPKR_OUT;
}
ALOGE("%s, Unsupported source or sink port config", __func__);
return loopback_patch->patch_handle_id;
}
/* Releases an existing loopback session */
/* Conditions : Session setup goes bad or actual session teardown */
int32_t release_loopback_session(loopback_patch_t *active_loopback_patch)
{
int32_t ret = 0;
struct audio_usecase *uc_info;
struct audio_device *adev = audio_loopback_mod->adev;
/* 1. Close the PCM devices */
if (active_loopback_patch->source_stream) {
compress_close(active_loopback_patch->source_stream);
active_loopback_patch->source_stream = NULL;
} else {
ALOGE("%s: Failed to close loopback stream in capture path",
__func__);
}
if (active_loopback_patch->sink_stream) {
compress_close(active_loopback_patch->sink_stream);
active_loopback_patch->sink_stream = NULL;
} else {
ALOGE("%s: Failed to close loopback stream in playback path",
__func__);
}
uc_info = get_usecase_from_list(adev, audio_loopback_mod->uc_id);
if (uc_info == NULL) {
ALOGE("%s: Could not find the loopback usecase (%d) in the list",
__func__, active_loopback_patch->patch_handle_id);
return -EINVAL;
}
active_loopback_patch->patch_state = PATCH_INACTIVE;
/* 2. Get and set stream specific mixer controls */
disable_audio_route(adev, uc_info);
/* 3. Disable the rx and tx devices */
disable_snd_device(adev, uc_info->out_snd_device);
disable_snd_device(adev, uc_info->in_snd_device);
list_remove(&uc_info->list);
free(uc_info);
ALOGD("%s: Release loopback session exit: status(%d)", __func__, ret);
return ret;
}
/* Create a loopback session based on active loopback patch selected */
int create_loopback_session(loopback_patch_t *active_loopback_patch)
{
int32_t ret = 0, bits_per_sample;
struct audio_usecase *uc_info;
int32_t pcm_dev_asm_rx_id, pcm_dev_asm_tx_id;
char dummy_write_buf[64];
struct audio_device *adev = audio_loopback_mod->adev;
struct compr_config source_config, sink_config;
struct snd_codec codec;
struct audio_port_config *source_patch_config = &active_loopback_patch->
loopback_source;
struct audio_port_config *sink_patch_config = &active_loopback_patch->
loopback_sink;
ALOGD("%s: Create loopback session begin", __func__);
uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
if (!uc_info) {
ALOGE("%s: Failure to open loopback session", __func__);
return -ENOMEM;
}
uc_info->id = USECASE_AUDIO_TRANSCODE_LOOPBACK;
uc_info->type = audio_loopback_mod->uc_type;
uc_info->stream.inout = &active_loopback_patch->patch_stream;
uc_info->devices = active_loopback_patch->patch_stream.out_config.devices;
uc_info->in_snd_device = SND_DEVICE_NONE;
uc_info->out_snd_device = SND_DEVICE_NONE;
list_add_tail(&adev->usecase_list, &uc_info->list);
select_devices(adev, uc_info->id);
pcm_dev_asm_rx_id = platform_get_pcm_device_id(uc_info->id, PCM_PLAYBACK);
pcm_dev_asm_tx_id = platform_get_pcm_device_id(uc_info->id, PCM_CAPTURE);
if (pcm_dev_asm_rx_id < 0 || pcm_dev_asm_tx_id < 0 ) {
ALOGE("%s: Invalid PCM devices (asm: rx %d tx %d) for the usecase(%d)",
__func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info->id);
ret = -EIO;
goto exit;
}
ALOGD("%s: LOOPBACK PCM devices (rx: %d tx: %d) usecase(%d)",
__func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info->id);
if (source_patch_config->format == AUDIO_FORMAT_IEC61937) {
// This is needed to set a known format to DSP and handle
// any format change via ADSP event
codec.id = AUDIO_FORMAT_AC3;
}
/* Set config for compress stream open in capture path */
codec.id = get_snd_codec_id(source_patch_config->format);
codec.ch_in = audio_channel_count_from_out_mask(source_patch_config->
channel_mask);
codec.ch_out = 2; // Irrelevant for loopback case in this direction
codec.sample_rate = source_patch_config->sample_rate;
codec.format = hal_format_to_alsa(source_patch_config->format);
source_config.fragment_size = 1024;
source_config.fragments = 1;
source_config.codec = &codec;
/* Open compress stream in capture path */
active_loopback_patch->source_stream = compress_open(adev->snd_card,
pcm_dev_asm_tx_id, COMPRESS_OUT, &source_config);
if (active_loopback_patch->source_stream && !is_compress_ready(
active_loopback_patch->source_stream)) {
ALOGE("%s: %s", __func__, compress_get_error(active_loopback_patch->
source_stream));
ret = -EIO;
goto exit;
} else if (active_loopback_patch->source_stream == NULL) {
ALOGE("%s: Failure to open loopback stream in capture path", __func__);
ret = -EINVAL;
goto exit;
}
/* Set config for compress stream open in playback path */
codec.id = get_snd_codec_id(sink_patch_config->format);
codec.ch_in = 2; // Irrelevant for loopback case in this direction
codec.ch_out = audio_channel_count_from_out_mask(sink_patch_config->
channel_mask);
codec.sample_rate = sink_patch_config->sample_rate;
codec.format = hal_format_to_alsa(sink_patch_config->format);
sink_config.fragment_size = 1024;
sink_config.fragments = 1;
sink_config.codec = &codec;
/* Open compress stream in playback path */
active_loopback_patch->sink_stream = compress_open(adev->snd_card,
pcm_dev_asm_rx_id, COMPRESS_IN, &sink_config);
if (active_loopback_patch->sink_stream && !is_compress_ready(
active_loopback_patch->sink_stream)) {
ALOGE("%s: %s", __func__, compress_get_error(active_loopback_patch->
sink_stream));
ret = -EIO;
goto exit;
} else if (active_loopback_patch->sink_stream == NULL) {
ALOGE("%s: Failure to open loopback stream in playback path", __func__);
ret = -EINVAL;
goto exit;
}
active_loopback_patch->patch_state = PATCH_CREATED;
if (compress_start(active_loopback_patch->source_stream) < 0) {
ALOGE("%s: Failure to start loopback stream in capture path",
__func__);
ret = -EINVAL;
goto exit;
}
/* Dummy compress_write to ensure compress_start does not fail */
compress_write(active_loopback_patch->sink_stream, dummy_write_buf, 64);
if (compress_start(active_loopback_patch->sink_stream) < 0) {
ALOGE("%s: Cannot start loopback stream in playback path",
__func__);
ret = -EINVAL;
goto exit;
}
/* Move patch state to running, now that session is set up */
active_loopback_patch->patch_state = PATCH_RUNNING;
ALOGD("%s: Create loopback session end: status(%d)", __func__, ret);
return ret;
exit:
ALOGE("%s: Problem in Loopback session creation: \
status(%d), releasing session ", __func__, ret);
release_loopback_session(active_loopback_patch);
return ret;
}
void update_patch_stream_config(struct stream_config *stream_cfg ,
struct audio_port_config *port_cfg)
{
stream_cfg->sample_rate = port_cfg->sample_rate;
stream_cfg->channel_mask = port_cfg->channel_mask;
stream_cfg->format = port_cfg->format;
stream_cfg->devices = port_cfg->ext.device.type;
stream_cfg->bit_width = format_to_bitwidth(port_cfg->format);
}
/* API to create audio patch */
int audio_extn_hw_loopback_create_audio_patch(struct audio_hw_device *dev,
unsigned int num_sources,
const struct audio_port_config *sources,
unsigned int num_sinks,
const struct audio_port_config *sinks,
audio_patch_handle_t *handle)
{
int status = 0;
patch_handle_type_t loopback_patch_type=0x0;
loopback_patch_t loopback_patch, *active_loopback_patch = NULL;
ALOGV("%s : Create audio patch begin", __func__);
if ((audio_loopback_mod == NULL) || (dev == NULL)) {
ALOGE("%s, Loopback module not initialized orInvalid device", __func__);
status = -EINVAL;
return status;
}
pthread_mutex_lock(&audio_loopback_mod->lock);
if (audio_loopback_mod->patch_db.num_patches >= MAX_NUM_PATCHES ) {
ALOGE("%s, Exhausted maximum possible patches per device", __func__);
status = -EINVAL;
goto exit_create_patch;
}
/* Port configuration check & validation */
if (num_sources > MAX_SOURCE_PORTS_PER_PATCH ||
num_sinks > MAX_SINK_PORTS_PER_PATCH) {
ALOGE("%s, Unsupported patch configuration, sources %d sinks %d ",
__func__, num_sources, num_sources);
status = -EINVAL;
goto exit_create_patch;
}
/* Use an empty patch from patch database and initialze */
active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
audio_loopback_mod->patch_db.num_patches]);
active_loopback_patch->patch_handle_id = PATCH_HANDLE_INVALID;
active_loopback_patch->patch_state = PATCH_INACTIVE;
memcpy(&active_loopback_patch->loopback_source, &sources[0], sizeof(struct
audio_port_config));
memcpy(&active_loopback_patch->loopback_sink, &sinks[0], sizeof(struct
audio_port_config));
/* Get loopback patch type based on source and sink ports configuration */
loopback_patch_type = get_loopback_patch_type(active_loopback_patch);
if (loopback_patch_type == PATCH_HANDLE_INVALID) {
ALOGE("%s, Unsupported patch type", __func__);
status = -EINVAL;
goto exit_create_patch;
}
update_patch_stream_config(&active_loopback_patch->patch_stream.in_config,
&active_loopback_patch->loopback_source);
update_patch_stream_config(&active_loopback_patch->patch_stream.out_config,
&active_loopback_patch->loopback_sink);
// Lock patch database, create patch handle and add patch handle to the list
active_loopback_patch->patch_handle_id = (loopback_patch_type << 8 |
audio_loopback_mod->patch_db.num_patches);
/* Is usecase transcode loopback? If yes, invoke loopback driver */
if ((active_loopback_patch->loopback_source.type == AUDIO_PORT_TYPE_DEVICE)
&&
(active_loopback_patch->loopback_sink.type == AUDIO_PORT_TYPE_DEVICE)) {
status = create_loopback_session(active_loopback_patch);
if (status != 0)
goto exit_create_patch;
}
// Create callback thread to listen to events from HW data path
/* Fill unique handle ID generated based on active loopback patch */
*handle = audio_loopback_mod->patch_db.loopback_patch[audio_loopback_mod->
patch_db.num_patches].patch_handle_id;
audio_loopback_mod->patch_db.num_patches++;
exit_create_patch :
ALOGV("%s : Create audio patch end, status(%d)", __func__, status);
pthread_mutex_unlock(&audio_loopback_mod->lock);
return status;
}
/* API to release audio patch */
int audio_extn_hw_loopback_release_audio_patch(struct audio_hw_device *dev,
audio_patch_handle_t handle)
{
int status = 0, n=0, patch_index=-1;
bool patch_found = false;
loopback_patch_t *active_loopback_patch = NULL;
ALOGV("%s audio_extn_hw_loopback_release_audio_patch begin %d", __func__, __LINE__);
if ((audio_loopback_mod == NULL) || (dev == NULL)) {
ALOGE("%s, Invalid device", __func__);
status = -1;
return status;
}
pthread_mutex_lock(&audio_loopback_mod->lock);
for (n=0;n < MAX_NUM_PATCHES;n++) {
if (audio_loopback_mod->patch_db.loopback_patch[n].patch_handle_id ==
handle) {
patch_found = true;
patch_index = n;
break;
}
}
if (patch_found) {
active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
patch_index]);
status = release_loopback_session(active_loopback_patch);
} else {
ALOGE("%s, Requested Patch handle does not exist", __func__);
status = -1;
}
pthread_mutex_unlock(&audio_loopback_mod->lock);
ALOGV("%s audio_extn_hw_loopback_release_audio_patch done, status(%d)", __func__,
status);
return status;
}
/* Find port config from patch database based on port info */
struct audio_port_config* get_port_from_patch_db(port_info_t *port,
patch_db_t *audio_patch_db, int *patch_num)
{
int n=0, patch_index=-1;
struct audio_port_config *cur_port=NULL;
if (port->role == AUDIO_PORT_ROLE_SOURCE) {
for (n=0;n < audio_patch_db->num_patches;n++) {
cur_port = &(audio_patch_db->loopback_patch[n].loopback_source);
if ((cur_port->id == port->id) && (cur_port->type == port->type) && (
cur_port->role == port->role)) {
patch_index = n;
break;
}
}
} else if (port->role == AUDIO_PORT_ROLE_SINK) {
for (n=0;n < audio_patch_db->num_patches;n++) {
cur_port = &(audio_patch_db->loopback_patch[n].loopback_sink);
if ((cur_port->id == port->id) && (cur_port->type == port->type) && (
cur_port->role == port->role)) {
patch_index = n;
break;
}
}
}
*patch_num = patch_index;
return cur_port;
}
/* API to get port config based on port unique ID */
int audio_extn_hw_loopback_get_audio_port(struct audio_hw_device *dev,
struct audio_port *port_in)
{
int status = 0, n=0, patch_num=-1;
loopback_patch_t *active_loopback_patch = NULL;
port_info_t *port_info = NULL;
struct audio_port_config *port_out=NULL;
ALOGV("%s %d", __func__, __LINE__);
if ((audio_loopback_mod == NULL) || (dev == NULL)) {
ALOGE("%s, Invalid device", __func__);
status = -1;
return status;
}
pthread_mutex_lock(&audio_loopback_mod->lock);
port_info->id = port_in->id;
port_info->role = port_in->role; /* sink or source */
port_info->type = port_in->type; /* device, mix ... */
port_out = get_port_from_patch_db(port_info, &audio_loopback_mod->patch_db,
&patch_num);
if (port_out == NULL) {
ALOGE("%s, Unable to find a valid matching port in patch \
database,exiting", __func__);
status = -EINVAL;
return status;
}
/* Fill port output properties before returning the port */
memcpy(&port_in->active_config,port_out, sizeof(struct audio_port_config));
/* Multiple fields are not valid for loopback extension usecases, TODO :
enhance for all patch handler cases. */
port_in->num_sample_rates = 1;
port_in->sample_rates[0] = port_out->sample_rate;
port_in->num_channel_masks = 1;
port_in->channel_masks[0] = port_out->channel_mask;
port_in->num_formats = 1;
port_in->formats[0] = port_out->format;
port_in->num_gains = 1;
pthread_mutex_unlock(&audio_loopback_mod->lock);
return status;
}
/* API to set port config based on port unique ID */
int audio_extn_hw_loopback_set_audio_port_config(struct audio_hw_device *dev,
const struct audio_port_config *config)
{
int status = 0, n=0, patch_num=-1;
loopback_patch_t *active_loopback_patch = NULL;
port_info_t *port_info = NULL;
struct audio_port_config *port_out=NULL;
ALOGV("%s %d", __func__, __LINE__);
if ((audio_loopback_mod == NULL) || (dev == NULL)) {
ALOGE("%s, Invalid device", __func__);
status = -1;
return status;
}
pthread_mutex_lock(&audio_loopback_mod->lock);
port_info->id = config->id;
port_info->role = config->role; /* sink or source */
port_info->type = config->type; /* device, mix */
port_out = get_port_from_patch_db(port_info, &audio_loopback_mod->patch_db
, &patch_num);
if (port_out == NULL) {
ALOGE("%s, Unable to find a valid matching port in patch \
database,exiting", __func__);
status = -1;
return status;
}
port_out->config_mask = config->config_mask;
port_out->channel_mask = config->channel_mask;
port_out->format = config->format;
port_out->gain = config->gain;
port_out->sample_rate = config->sample_rate;
/* Currently, port config is not used for anything,
need to restart session */
pthread_mutex_unlock(&audio_loopback_mod->lock);
return status;
}
/* Loopback extension initialization, part of hal init sequence */
int audio_extn_loopback_init(struct audio_device *adev)
{
ALOGV("%s Audio loopback extension initializing", __func__);
int ret = 0, size = 0;
if (audio_loopback_mod != NULL) {
pthread_mutex_lock(&audio_loopback_mod->lock);
if (audio_loopback_mod->adev == adev) {
ALOGV("%s %d : Audio loopback module already exists", __func__,
__LINE__);
} else {
ALOGV("%s %d : Audio loopback module called for invalid device",
__func__, __LINE__);
ret = -EINVAL;
}
goto loopback_done;
}
audio_loopback_mod = malloc(sizeof(struct audio_loopback));
if (audio_loopback_mod == NULL) {
ALOGE("%s, out of memory", __func__);
ret = -ENOMEM;
goto loopback_done;
}
pthread_mutex_init(&audio_loopback_mod->lock,
(const pthread_mutexattr_t *)NULL);
pthread_mutex_lock(&audio_loopback_mod->lock);
audio_loopback_mod->adev = adev;
ret = init_patch_database(&audio_loopback_mod->patch_db);
audio_loopback_mod->uc_id = USECASE_AUDIO_TRANSCODE_LOOPBACK;
audio_loopback_mod->uc_type = TRANSCODE_LOOPBACK;
loopback_done:
if (ret != 0) {
if (audio_loopback_mod != NULL) {
pthread_mutex_unlock(&audio_loopback_mod->lock);
pthread_mutex_destroy(&audio_loopback_mod->lock);
free(audio_loopback_mod);
audio_loopback_mod = NULL;
}
} else {
pthread_mutex_unlock(&audio_loopback_mod->lock);
}
ALOGV("%s Audio loopback extension initialized", __func__);
return ret;
}
void audio_extn_loopback_deinit(struct audio_device *adev)
{
ALOGV("%s Audio loopback extension de-initializing", __func__);
if (audio_loopback_mod == NULL) {
ALOGE("%s, loopback module NULL, cannot deinitialize", __func__);
return;
}
pthread_mutex_lock(&audio_loopback_mod->lock);
if (audio_loopback_mod->adev == adev) {
if (audio_loopback_mod != NULL) {
pthread_mutex_unlock(&audio_loopback_mod->lock);
pthread_mutex_destroy(&audio_loopback_mod->lock);
free(audio_loopback_mod);
audio_loopback_mod = NULL;
}
return;
} else {
ALOGE("%s, loopback module not valid, cannot deinitialize", __func__);
}
pthread_mutex_unlock(&audio_loopback_mod->lock);
return;
}