| /* |
| * 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 <math.h> |
| #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 <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; /* 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_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_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)) { |
| 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_supported = true; |
| break; |
| } |
| } else if (loopback_patch->loopback_source.ext.device.type & AUDIO_DEVICE_IN_LINE) { |
| is_source_supported = true; |
| } |
| } |
| 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) && |
| (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 : |
| break; |
| //Unsupported as of now, need to extend for other sink types |
| } |
| } |
| 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; |
| } |