1039 lines
39 KiB
C
1039 lines
39 KiB
C
/*
|
|
* Copyright (c) 2017-2020, 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
|
|
#define HW_LOOPBACK_RX_VOLUME "Trans Loopback RX Volume"
|
|
#define HW_LOOPBACK_RX_UNITY_GAIN 0x2000
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <pthread.h>
|
|
#include <errno.h>
|
|
#include <dlfcn.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/prctl.h>
|
|
#include <cutils/atomic.h>
|
|
#include <cutils/properties.h>
|
|
#include <cutils/str_parms.h>
|
|
#include <cutils/sched_policy.h>
|
|
#include <log/log.h>
|
|
#include "audio_utils/primitives.h"
|
|
#include "audio_hw.h"
|
|
#include "platform_api.h"
|
|
#include <platform.h>
|
|
#include <system/thread_defs.h>
|
|
#include "audio_extn.h"
|
|
#include <sound/compress_params.h>
|
|
#include <sound/compress_offload.h>
|
|
#include <system/audio.h>
|
|
|
|
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; /* Sink port config */
|
|
struct compress *source_stream; /* Source stream */
|
|
struct compress *sink_stream; /* Sink 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_rx;
|
|
audio_usecase_t uc_id_tx;
|
|
usecase_type_t uc_type_rx;
|
|
usecase_type_t uc_type_tx;
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Set loopback volume : for mute implementation */
|
|
static int hw_loopback_set_volume(struct audio_device *adev, int value)
|
|
{
|
|
int32_t ret = 0;
|
|
struct mixer_ctl *ctl;
|
|
char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT];
|
|
snprintf(mixer_ctl_name, sizeof(mixer_ctl_name),
|
|
"Transcode Loopback Rx Volume");
|
|
|
|
ALOGD("%s: (%d)\n", __func__, value);
|
|
|
|
ALOGD("%s: Setting HW loopback volume to %d \n", __func__, value);
|
|
ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name);
|
|
if (!ctl) {
|
|
ALOGE("%s: Could not get ctl for mixer cmd - %s",
|
|
__func__, mixer_ctl_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if(mixer_ctl_set_value(ctl, 0, value) < 0) {
|
|
ALOGE("%s: Couldn't set HW Loopback Volume: [%d]", __func__, value);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ALOGV("%s: exit", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
bool is_supported_sink_device(audio_devices_t sink_device_mask)
|
|
{
|
|
if((sink_device_mask & AUDIO_DEVICE_OUT_SPEAKER) ||
|
|
(sink_device_mask & AUDIO_DEVICE_OUT_WIRED_HEADSET) ||
|
|
(sink_device_mask & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) ||
|
|
(sink_device_mask & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP) ||
|
|
(sink_device_mask & AUDIO_DEVICE_OUT_LINE)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Get patch type based on source and sink ports configuration */
|
|
/* Only ports of type 'DEVICE' are supported */
|
|
audio_patch_handle_t get_loopback_patch_type(loopback_patch_t* loopback_patch)
|
|
{
|
|
bool is_source_supported = false, is_sink_supported = false;
|
|
audio_devices_t source_device = loopback_patch->loopback_source.ext.device.type;
|
|
audio_devices_t sink_device = loopback_patch->loopback_sink.ext.device.type;
|
|
|
|
source_device &= ~AUDIO_DEVICE_BIT_IN;
|
|
|
|
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)) {
|
|
if ((source_device & AUDIO_DEVICE_IN_HDMI) ||
|
|
(source_device & AUDIO_DEVICE_IN_SPDIF) ||
|
|
(source_device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) ||
|
|
(source_device & AUDIO_DEVICE_IN_HDMI_ARC)) {
|
|
|
|
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:
|
|
case AUDIO_FORMAT_AAC_LATM_LC:
|
|
case AUDIO_FORMAT_AAC_LATM_HE_V1:
|
|
case AUDIO_FORMAT_AAC_LATM_HE_V2:
|
|
case AUDIO_FORMAT_SBC:
|
|
is_source_supported = true;
|
|
break;
|
|
}
|
|
} else if (source_device & AUDIO_DEVICE_IN_LINE) {
|
|
is_source_supported = true;
|
|
}
|
|
}
|
|
break;
|
|
default :
|
|
//Unsupported as of now, need to extend for other source types
|
|
break;
|
|
}
|
|
}
|
|
|
|
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) &&
|
|
(is_supported_sink_device(loopback_patch->loopback_sink.ext.device.type))) {
|
|
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_supported = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
ALOGE("%s, Unsupported sink port device %d", __func__,loopback_patch->loopback_sink.ext.device.type);
|
|
}
|
|
break;
|
|
default :
|
|
//Unsupported as of now, need to extend for other sink types
|
|
break;
|
|
}
|
|
}
|
|
if (is_source_supported && is_sink_supported) {
|
|
return AUDIO_DEVICE_BIT_IN | source_device | sink_device;
|
|
}
|
|
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_rx, *uc_info_tx;
|
|
struct audio_device *adev = audio_loopback_mod->adev;
|
|
struct stream_inout *inout = &active_loopback_patch->patch_stream;
|
|
struct audio_port_config *source_patch_config = &active_loopback_patch->
|
|
loopback_source;
|
|
int32_t pcm_dev_asm_rx_id = platform_get_pcm_device_id(USECASE_AUDIO_TRANSCODE_LOOPBACK_RX,
|
|
PCM_PLAYBACK);
|
|
|
|
/* 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_tx = get_usecase_from_list(adev, audio_loopback_mod->uc_id_tx);
|
|
if (uc_info_tx == NULL) {
|
|
ALOGE("%s: Could not find the loopback usecase (%d) in the list",
|
|
__func__, active_loopback_patch->patch_handle_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
disable_audio_route(adev, uc_info_tx);
|
|
|
|
/* Disable tx device */
|
|
disable_snd_device(adev, uc_info_tx->in_snd_device);
|
|
|
|
/* Reset backend device to default state */
|
|
platform_invalidate_backend_config(adev->platform,uc_info_tx->in_snd_device);
|
|
|
|
list_remove(&uc_info_tx->list);
|
|
free(uc_info_tx);
|
|
|
|
uc_info_rx = get_usecase_from_list(adev, audio_loopback_mod->uc_id_rx);
|
|
if (uc_info_rx == NULL) {
|
|
ALOGE("%s: Could not find the loopback usecase (%d) in the list",
|
|
__func__, active_loopback_patch->patch_handle_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (adev->offload_effects_stop_output != NULL)
|
|
adev->offload_effects_stop_output(active_loopback_patch->patch_handle_id, pcm_dev_asm_rx_id);
|
|
|
|
active_loopback_patch->patch_state = PATCH_INACTIVE;
|
|
|
|
/* Get and set stream specific mixer controls */
|
|
disable_audio_route(adev, uc_info_rx);
|
|
|
|
/* Disable the rx device */
|
|
disable_snd_device(adev, uc_info_rx->out_snd_device);
|
|
|
|
list_remove(&uc_info_rx->list);
|
|
free(uc_info_rx);
|
|
|
|
if (inout->ip_hdlr_handle) {
|
|
ret = audio_extn_ip_hdlr_intf_close(inout->ip_hdlr_handle, true, inout);
|
|
if (ret < 0)
|
|
ALOGE("%s: audio_extn_ip_hdlr_intf_close failed %d",__func__, ret);
|
|
}
|
|
|
|
/* close adsp hdrl session before standby */
|
|
if (inout->adsp_hdlr_stream_handle) {
|
|
ret = audio_extn_adsp_hdlr_stream_close(inout->adsp_hdlr_stream_handle);
|
|
if (ret)
|
|
ALOGE("%s: adsp_hdlr_stream_close failed %d",__func__, ret);
|
|
inout->adsp_hdlr_stream_handle = NULL;
|
|
}
|
|
|
|
if (inout->ip_hdlr_handle) {
|
|
audio_extn_ip_hdlr_intf_deinit(inout->ip_hdlr_handle);
|
|
inout->ip_hdlr_handle = NULL;
|
|
}
|
|
|
|
ALOGD("%s: Release loopback session exit: status(%d)", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Callback funtion called in the case of failures */
|
|
int loopback_stream_cb(stream_callback_event_t event, void *param, void *cookie)
|
|
{
|
|
if (event == AUDIO_EXTN_STREAM_CBK_EVENT_ERROR) {
|
|
pthread_mutex_lock(&audio_loopback_mod->lock);
|
|
release_loopback_session(cookie);
|
|
audio_loopback_mod->patch_db.num_patches--;
|
|
pthread_mutex_unlock(&audio_loopback_mod->lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef SNDRV_COMPRESS_RENDER_WINDOW
|
|
static loopback_patch_t *get_active_loopback_patch(audio_patch_handle_t handle)
|
|
{
|
|
int n = 0;
|
|
int patch_index = -1;
|
|
loopback_patch_t *active_loopback_patch = NULL;
|
|
|
|
for (n=0; n < MAX_NUM_PATCHES; n++) {
|
|
if (audio_loopback_mod->patch_db.num_patches > 0) {
|
|
if (audio_loopback_mod->patch_db.loopback_patch[n].patch_handle_id == handle) {
|
|
patch_index = n;
|
|
break;
|
|
}
|
|
} else {
|
|
ALOGE("%s, No active audio loopback patch", __func__);
|
|
return active_loopback_patch;
|
|
}
|
|
}
|
|
|
|
if ((patch_index > -1) && (patch_index < MAX_NUM_PATCHES))
|
|
active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
|
|
patch_index]);
|
|
else
|
|
ALOGE("%s, Requested Patch handle does not exist", __func__);
|
|
|
|
return active_loopback_patch;
|
|
}
|
|
|
|
int audio_extn_hw_loopback_set_render_window(audio_patch_handle_t handle,
|
|
struct audio_out_render_window_param *render_window)
|
|
{
|
|
struct snd_compr_metadata metadata = {0};
|
|
int ret = 0;
|
|
loopback_patch_t *active_loopback_patch = get_active_loopback_patch(handle);
|
|
|
|
if (active_loopback_patch == NULL) {
|
|
ALOGE("%s: Invalid patch handle", __func__);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (render_window == NULL) {
|
|
ALOGE("%s: Invalid render_window", __func__);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
metadata.key = SNDRV_COMPRESS_RENDER_WINDOW;
|
|
/*render window start value */
|
|
metadata.value[0] = 0xFFFFFFFF & render_window->render_ws; /* lsb */
|
|
metadata.value[1] = \
|
|
(0xFFFFFFFF00000000 & render_window->render_ws) >> 32; /* msb*/
|
|
/*render window end value */
|
|
metadata.value[2] = 0xFFFFFFFF & render_window->render_we; /* lsb */
|
|
metadata.value[3] = \
|
|
(0xFFFFFFFF00000000 & render_window->render_we) >> 32; /* msb*/
|
|
|
|
ret = compress_set_metadata(active_loopback_patch->sink_stream, &metadata);
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
#else
|
|
int audio_extn_hw_loopback_set_render_window(struct audio_hw_device *dev,
|
|
audio_patch_handle_t handle __unused,
|
|
struct audio_out_render_window_param *render_window __unused)
|
|
{
|
|
ALOGD("%s:: configuring render window not supported", __func__);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined SNDRV_COMPRESS_LATENCY_MODE
|
|
static void transcode_loopback_util_set_latency_mode(
|
|
loopback_patch_t *active_loopback_patch,
|
|
uint32_t latency_mode)
|
|
{
|
|
struct snd_compr_metadata metadata;
|
|
|
|
metadata.key = SNDRV_COMPRESS_LATENCY_MODE;
|
|
metadata.value[0] = latency_mode;
|
|
ALOGV("%s: Setting latency mode %d",__func__, latency_mode);
|
|
compress_set_metadata(active_loopback_patch->source_stream,&metadata);
|
|
}
|
|
#else
|
|
static void transcode_loopback_util_set_latency_mode(
|
|
loopback_patch_t *active_loopback_patch __unused,
|
|
uint32_t latency_mode __unused)
|
|
{
|
|
ALOGD("%s:: Latency mode configuration not supported", __func__);
|
|
}
|
|
#endif
|
|
|
|
/* 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_rx, *uc_info_tx;
|
|
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;
|
|
struct stream_inout *inout = &active_loopback_patch->patch_stream;
|
|
struct adsp_hdlr_stream_cfg hdlr_stream_cfg;
|
|
struct stream_in loopback_source_stream;
|
|
char prop_value[PROPERTY_VALUE_MAX] = {0};
|
|
|
|
ALOGD("%s: Create loopback session begin", __func__);
|
|
|
|
uc_info_rx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
|
|
|
|
if (!uc_info_rx) {
|
|
ALOGE("%s: Failure to open loopback session", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
uc_info_tx = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
|
|
|
|
if (!uc_info_tx) {
|
|
free(uc_info_rx);
|
|
ALOGE("%s: Failure to open loopback session", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
uc_info_rx->id = USECASE_AUDIO_TRANSCODE_LOOPBACK_RX;
|
|
uc_info_rx->type = audio_loopback_mod->uc_type_rx;
|
|
uc_info_rx->stream.inout = &active_loopback_patch->patch_stream;
|
|
list_init(&uc_info_rx->device_list);
|
|
assign_devices(&uc_info_rx->device_list,
|
|
&active_loopback_patch->patch_stream.out_config.device_list);
|
|
uc_info_rx->in_snd_device = SND_DEVICE_NONE;
|
|
uc_info_rx->out_snd_device = SND_DEVICE_NONE;
|
|
|
|
|
|
uc_info_tx->id = USECASE_AUDIO_TRANSCODE_LOOPBACK_TX;
|
|
uc_info_tx->type = audio_loopback_mod->uc_type_tx;
|
|
uc_info_tx->stream.inout = &active_loopback_patch->patch_stream;
|
|
list_init(&uc_info_tx->device_list);
|
|
assign_devices(&uc_info_tx->device_list,
|
|
&active_loopback_patch->patch_stream.in_config.device_list);
|
|
uc_info_tx->in_snd_device = SND_DEVICE_NONE;
|
|
uc_info_tx->out_snd_device = SND_DEVICE_NONE;
|
|
|
|
list_add_tail(&adev->usecase_list, &uc_info_rx->list);
|
|
list_add_tail(&adev->usecase_list, &uc_info_tx->list);
|
|
|
|
loopback_source_stream.source = AUDIO_SOURCE_UNPROCESSED;
|
|
loopback_source_stream.device = inout->in_config.devices;
|
|
loopback_source_stream.channel_mask = inout->in_config.channel_mask;
|
|
loopback_source_stream.bit_width = inout->in_config.bit_width;
|
|
loopback_source_stream.sample_rate = inout->in_config.sample_rate;
|
|
loopback_source_stream.format = inout->in_config.format;
|
|
|
|
memcpy(&loopback_source_stream.usecase, uc_info_rx,
|
|
sizeof(struct audio_usecase));
|
|
select_devices(adev, uc_info_rx->id);
|
|
select_devices(adev, uc_info_tx->id);
|
|
|
|
pcm_dev_asm_rx_id = platform_get_pcm_device_id(uc_info_rx->id, PCM_PLAYBACK);
|
|
pcm_dev_asm_tx_id = platform_get_pcm_device_id(uc_info_tx->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 RX usecase(%d), TX usecase(%d)",
|
|
__func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info_rx->id, uc_info_tx->id);
|
|
ret = -EIO;
|
|
goto exit;
|
|
}
|
|
|
|
ALOGD("%s: LOOPBACK PCM devices (rx: %d tx: %d) RX usecase(%d) TX usecase(%d)",
|
|
__func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info_rx->id, uc_info_tx->id);
|
|
|
|
/* setup a channel for client <--> adsp communication for stream events */
|
|
inout->dev = adev;
|
|
inout->client_callback = loopback_stream_cb;
|
|
inout->client_cookie = active_loopback_patch;
|
|
hdlr_stream_cfg.pcm_device_id = pcm_dev_asm_rx_id;
|
|
hdlr_stream_cfg.flags = 0;
|
|
hdlr_stream_cfg.type = PCM_PLAYBACK;
|
|
ret = audio_extn_adsp_hdlr_stream_open(&inout->adsp_hdlr_stream_handle,
|
|
&hdlr_stream_cfg);
|
|
if (ret) {
|
|
ALOGE("%s: adsp_hdlr_stream_open failed %d", __func__, ret);
|
|
inout->adsp_hdlr_stream_handle = NULL;
|
|
goto exit;
|
|
}
|
|
if (audio_extn_ip_hdlr_intf_supported(source_patch_config->format,false, true) ||
|
|
audio_extn_ip_hdlr_intf_supported_for_copp(adev->platform)) {
|
|
ret = audio_extn_ip_hdlr_intf_init(&inout->ip_hdlr_handle, NULL, NULL, adev,
|
|
USECASE_AUDIO_TRANSCODE_LOOPBACK_RX);
|
|
if (ret < 0) {
|
|
ALOGE("%s: audio_extn_ip_hdlr_intf_init failed %d", __func__, ret);
|
|
inout->ip_hdlr_handle = NULL;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
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));
|
|
active_loopback_patch->source_stream = NULL;
|
|
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;
|
|
|
|
/* Do not alter the location of sending latency mode property */
|
|
/* Mode set on any stream but before both streams are open */
|
|
if(property_get("vendor.audio.transcode.latency.mode", prop_value, "")) {
|
|
uint32_t latency_mode = atoi(prop_value);
|
|
transcode_loopback_util_set_latency_mode(active_loopback_patch,
|
|
latency_mode);
|
|
}
|
|
|
|
/* 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));
|
|
active_loopback_patch->sink_stream = NULL;
|
|
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;
|
|
}
|
|
if (inout->ip_hdlr_handle) {
|
|
ret = audio_extn_ip_hdlr_intf_open(inout->ip_hdlr_handle, true, inout,
|
|
USECASE_AUDIO_TRANSCODE_LOOPBACK_RX);
|
|
if (ret < 0) {
|
|
ALOGE("%s: audio_extn_ip_hdlr_intf_open failed %d",__func__, ret);
|
|
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);
|
|
|
|
if (adev->offload_effects_start_output != NULL)
|
|
adev->offload_effects_start_output(active_loopback_patch->patch_handle_id,
|
|
pcm_dev_asm_rx_id, adev->mixer);
|
|
|
|
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;
|
|
reassign_device_list(&stream_cfg->device_list, 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;
|
|
audio_patch_handle_t loopback_patch_id = 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]);
|
|
|
|
memset(active_loopback_patch, 0, sizeof(loopback_patch_t));
|
|
|
|
active_loopback_patch->patch_handle_id = PATCH_HANDLE_INVALID;
|
|
active_loopback_patch->patch_state = PATCH_INACTIVE;
|
|
active_loopback_patch->patch_stream.ip_hdlr_handle = NULL;
|
|
active_loopback_patch->patch_stream.adsp_hdlr_stream_handle = NULL;
|
|
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_id = get_loopback_patch_type(active_loopback_patch);
|
|
|
|
if (loopback_patch_id == 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_id;
|
|
|
|
/* 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 && (audio_loopback_mod->patch_db.num_patches > 0)) {
|
|
active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
|
|
patch_index]);
|
|
status = release_loopback_session(active_loopback_patch);
|
|
audio_loopback_mod->patch_db.num_patches--;
|
|
} 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;
|
|
port_info_t port_info;
|
|
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;
|
|
port_info_t port_info;
|
|
struct audio_port_config *port_out=NULL;
|
|
struct audio_device *adev = audio_loopback_mod->adev;
|
|
int loopback_gain = HW_LOOPBACK_RX_UNITY_GAIN;
|
|
|
|
ALOGV("%s %d", __func__, __LINE__);
|
|
|
|
if ((audio_loopback_mod == NULL) || (dev == NULL)) {
|
|
ALOGE("%s, Invalid device", __func__);
|
|
status = -EINVAL;
|
|
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 = -EINVAL;
|
|
goto exit_set_port_config;
|
|
}
|
|
|
|
port_out->config_mask |= config->config_mask;
|
|
if(config->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK)
|
|
port_out->channel_mask = config->channel_mask;
|
|
if(config->config_mask & AUDIO_PORT_CONFIG_FORMAT)
|
|
port_out->format = config->format;
|
|
if(config->config_mask & AUDIO_PORT_CONFIG_GAIN)
|
|
port_out->gain = config->gain;
|
|
if(config->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE)
|
|
port_out->sample_rate = config->sample_rate;
|
|
|
|
/* Convert gain in millibels to ratio and convert to Q13 */
|
|
loopback_gain = pow(10, (float)((float)port_out->gain.values[0]/2000)) *
|
|
(1 << 13);
|
|
ALOGV("%s, Port config gain_in_mbells: %d, gain_in_q13 : %d", __func__,
|
|
port_out->gain.values[0], loopback_gain);
|
|
if((port_out->config_mask & AUDIO_PORT_CONFIG_GAIN) &&
|
|
port_out->gain.mode == AUDIO_GAIN_MODE_JOINT ) {
|
|
status = hw_loopback_set_volume(adev, loopback_gain);
|
|
if (status) {
|
|
ALOGE("%s, Error setting loopback gain config: status %d",
|
|
__func__, status);
|
|
}
|
|
} else {
|
|
ALOGE("%s, Unsupported port config ,exiting", __func__);
|
|
status = -EINVAL;
|
|
}
|
|
|
|
/* Currently, port config is not used for anything,
|
|
need to restart session */
|
|
exit_set_port_config:
|
|
pthread_mutex_unlock(&audio_loopback_mod->lock);
|
|
return status;
|
|
}
|
|
|
|
/* Loopback extension initialization, part of hal init sequence */
|
|
int audio_extn_hw_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_rx = USECASE_AUDIO_TRANSCODE_LOOPBACK_RX;
|
|
audio_loopback_mod->uc_id_tx = USECASE_AUDIO_TRANSCODE_LOOPBACK_TX;
|
|
audio_loopback_mod->uc_type_rx = TRANSCODE_LOOPBACK_RX;
|
|
audio_loopback_mod->uc_type_tx = TRANSCODE_LOOPBACK_TX;
|
|
|
|
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_hw_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;
|
|
}
|