/* * Copyright (c) 2017-2018, 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 #include #include #include #include #include #include #include #include #include #include #include "audio_utils/primitives.h" #include "audio_hw.h" #include "platform_api.h" #include #include #include #include "audio_extn.h" #include #include #include 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; 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 ((loopback_patch->loopback_source.ext.device.type & AUDIO_DEVICE_IN_HDMI) || (loopback_patch->loopback_source.ext.device.type & AUDIO_DEVICE_IN_SPDIF) || (loopback_patch->loopback_source.ext.device.type & AUDIO_DEVICE_IN_BLUETOOTH_A2DP)) { 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 (loopback_patch->loopback_source.ext.device.type & 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 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); adev->active_input = get_next_active_input(adev); 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; uc_info_rx->devices = active_loopback_patch->patch_stream.out_config.devices; 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; uc_info_tx->devices = active_loopback_patch->patch_stream.in_config.devices; 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)); adev->active_input = &loopback_source_stream; 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)) { 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; 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; 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; }