/* * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "keep_alive" /*#define LOG_NDEBUG 0*/ #include #include #include "audio_hw.h" #include "audio_extn.h" #include "platform_api.h" #include #define SILENCE_INTERVAL 2 /*In secs*/ typedef enum { STATE_DEINIT = -1, STATE_IDLE, STATE_ACTIVE, } state_t; typedef enum { REQUEST_WRITE, } request_t; typedef struct { pthread_mutex_t lock; pthread_mutex_t sleep_lock; pthread_cond_t cond; pthread_cond_t wake_up_cond; pthread_t thread; state_t state; struct listnode cmd_list; struct pcm *pcm; bool done; void * userdata; } keep_alive_t; struct keep_alive_cmd { struct listnode node; request_t req; }; static keep_alive_t ka; static struct pcm_config silence_config = { .channels = 2, .rate = DEFAULT_OUTPUT_SAMPLING_RATE, .period_size = DEEP_BUFFER_OUTPUT_PERIOD_SIZE, .period_count = DEEP_BUFFER_OUTPUT_PERIOD_COUNT, .format = PCM_FORMAT_S16_LE, .start_threshold = DEEP_BUFFER_OUTPUT_PERIOD_SIZE / 4, .stop_threshold = INT_MAX, .avail_min = DEEP_BUFFER_OUTPUT_PERIOD_SIZE / 4, }; static void * keep_alive_loop(void * context); void audio_extn_keep_alive_init(struct audio_device *adev) { ka.userdata = adev; ka.state = STATE_IDLE; ka.pcm = NULL; pthread_mutex_init(&ka.lock, (const pthread_mutexattr_t *) NULL); pthread_cond_init(&ka.cond, (const pthread_condattr_t *) NULL); pthread_cond_init(&ka.wake_up_cond, (const pthread_condattr_t *) NULL); pthread_mutex_init(&ka.sleep_lock, (const pthread_mutexattr_t *) NULL); list_init(&ka.cmd_list); if (pthread_create(&ka.thread, (const pthread_attr_t *) NULL, keep_alive_loop, NULL) < 0) { ALOGW("Failed to create keep_alive_thread"); /* can continue without keep alive */ ka.state = STATE_DEINIT; } } static void send_cmd_l(request_t r) { if (ka.state == STATE_DEINIT) return; struct keep_alive_cmd *cmd = (struct keep_alive_cmd *)calloc(1, sizeof(struct keep_alive_cmd)); cmd->req = r; list_add_tail(&ka.cmd_list, &cmd->node); pthread_cond_signal(&ka.cond); } static int close_silence_stream() { if (!ka.pcm) return -ENODEV; pcm_close(ka.pcm); ka.pcm = NULL; return 0; } static int open_silence_stream() { unsigned int flags = PCM_OUT|PCM_MONOTONIC; if (ka.pcm) return -EEXIST; int silence_pcm_dev_id = platform_get_pcm_device_id(USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE, PCM_PLAYBACK); ALOGD("opening silence device %d", silence_pcm_dev_id); struct audio_device * adev = (struct audio_device *)ka.userdata; ka.pcm = pcm_open(adev->snd_card, silence_pcm_dev_id, flags, &silence_config); ALOGD("opened silence device %d", silence_pcm_dev_id); if (ka.pcm == NULL || !pcm_is_ready(ka.pcm)) { ALOGE("%s: %s", __func__, pcm_get_error(ka.pcm)); if (ka.pcm != NULL) { pcm_close(ka.pcm); ka.pcm = NULL; } return -1; } return 0; } static int set_mixer_control(struct mixer *mixer, const char * mixer_ctl_name, const char *mixer_val) { struct mixer_ctl *ctl; if ((mixer == NULL) || (mixer_ctl_name == NULL) || (mixer_val == NULL)) { ALOGE("%s: Invalid input", __func__); return -EINVAL; } ALOGD("setting mixer ctl %s with value %s", mixer_ctl_name, mixer_val); ctl = mixer_get_ctl_by_name(mixer, mixer_ctl_name); if (!ctl) { ALOGE("%s: could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name); return -EINVAL; } return mixer_ctl_set_enum_by_string(ctl, mixer_val); } /* must be called with adev lock held */ void audio_extn_keep_alive_start() { struct audio_device * adev = (struct audio_device *)ka.userdata; char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT]; int app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT], len = 0, rc; struct mixer_ctl *ctl; int acdb_dev_id, snd_device; struct listnode *node; struct audio_usecase *usecase; int32_t sample_rate = DEFAULT_OUTPUT_SAMPLING_RATE; pthread_mutex_lock(&ka.lock); if (ka.state == STATE_DEINIT) { ALOGE(" %s : Invalid state ",__func__); goto exit; } if (audio_extn_passthru_is_active()) { ALOGE(" %s : Pass through is already active", __func__); goto exit; } if (ka.state == STATE_ACTIVE) { ALOGV(" %s : Keep alive state is already Active",__func__ ); goto exit; } /* Dont start keep_alive if any other PCM session is routed to HDMI*/ list_for_each(node, &adev->usecase_list) { usecase = node_to_item(node, struct audio_usecase, list); if (usecase->type == PCM_PLAYBACK && usecase->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) goto exit; } ka.done = false; /*configure app type */ int silence_pcm_dev_id = platform_get_pcm_device_id(USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE, PCM_PLAYBACK); snprintf(mixer_ctl_name, sizeof(mixer_ctl_name), "Audio Stream %d App Type Cfg", silence_pcm_dev_id); 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); rc = -EINVAL; goto exit; } /* Configure HDMI/DP Backend with default values, this as well * helps reconfigure HDMI/DP backend after passthrough. */ int ext_disp_type = platform_get_ext_disp_type(adev->platform); switch(ext_disp_type) { case EXT_DISPLAY_TYPE_HDMI: snd_device = SND_DEVICE_OUT_HDMI; set_mixer_control(adev->mixer, "HDMI RX Format", "LPCM"); set_mixer_control(adev->mixer, "HDMI_RX SampleRate", "KHZ_48"); set_mixer_control(adev->mixer, "HDMI_RX Channels", "Two"); break; case EXT_DISPLAY_TYPE_DP: snd_device = SND_DEVICE_OUT_DISPLAY_PORT; set_mixer_control(adev->mixer, "Display Port RX Format", "LPCM"); set_mixer_control(adev->mixer, "Display Port RX SampleRate", "KHZ_48"); set_mixer_control(adev->mixer, "Display Port RX Channels", "Two"); break; default: ALOGE("%s: Invalid external display type:%d", __func__, ext_disp_type); goto exit; } acdb_dev_id = platform_get_snd_device_acdb_id(snd_device); if (acdb_dev_id < 0) { ALOGE("%s: Couldn't get the acdb dev id", __func__); rc = -EINVAL; goto exit; } sample_rate = DEFAULT_OUTPUT_SAMPLING_RATE; app_type_cfg[len++] = platform_get_default_app_type(adev->platform); app_type_cfg[len++] = acdb_dev_id; app_type_cfg[len++] = sample_rate; ALOGI("%s:%d PLAYBACK app_type %d, acdb_dev_id %d, sample_rate %d", __func__, __LINE__, platform_get_default_app_type(adev->platform), acdb_dev_id, sample_rate); mixer_ctl_set_array(ctl, app_type_cfg, len); /*send calibration*/ usecase = calloc(1, sizeof(struct audio_usecase)); usecase->type = PCM_PLAYBACK; usecase->out_snd_device = snd_device; platform_send_audio_calibration(adev->platform, usecase, platform_get_default_app_type(adev->platform), sample_rate); /*apply audio route */ switch(ext_disp_type) { case EXT_DISPLAY_TYPE_HDMI: audio_route_apply_and_update_path(adev->audio_route, "silence-playback hdmi"); break; case EXT_DISPLAY_TYPE_DP: audio_route_apply_and_update_path(adev->audio_route, "silence-playback display-port"); break; default: ALOGE("%s: Invalid external display type:%d", __func__, ext_disp_type); goto exit; } if (open_silence_stream() == 0) { send_cmd_l(REQUEST_WRITE); while (ka.state != STATE_ACTIVE) { pthread_cond_wait(&ka.cond, &ka.lock); } } exit: pthread_mutex_unlock(&ka.lock); } /* must be called with adev lock held */ void audio_extn_keep_alive_stop() { struct audio_device * adev = (struct audio_device *)ka.userdata; pthread_mutex_lock(&ka.lock); if ((ka.state == STATE_DEINIT) || (ka.state == STATE_IDLE)) goto exit; pthread_mutex_lock(&ka.sleep_lock); ka.done = true; pthread_cond_signal(&ka.wake_up_cond); pthread_mutex_unlock(&ka.sleep_lock); while (ka.state != STATE_IDLE) { pthread_cond_wait(&ka.cond, &ka.lock); } close_silence_stream(); /*apply audio route */ int ext_disp_type = platform_get_ext_disp_type(adev->platform); switch(ext_disp_type) { case EXT_DISPLAY_TYPE_HDMI: audio_route_reset_and_update_path(adev->audio_route, "silence-playback hdmi"); break; case EXT_DISPLAY_TYPE_DP: audio_route_reset_and_update_path(adev->audio_route, "silence-playback display-port"); break; default: ALOGE("%s: Invalid external display type:%d", __func__, ext_disp_type); } exit: pthread_mutex_unlock(&ka.lock); } bool audio_extn_keep_alive_is_active() { return ka.state == STATE_ACTIVE; } int audio_extn_keep_alive_set_parameters(struct audio_device *adev __unused, struct str_parms *parms) { char value[32]; int ret; ret = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_CONNECT, value, sizeof(value)); if (ret >= 0) { int val = atoi(value); if (val & AUDIO_DEVICE_OUT_AUX_DIGITAL) { if (!audio_extn_passthru_is_active()) { ALOGV("start keep alive"); audio_extn_keep_alive_start(); } } } ret = str_parms_get_str(parms, AUDIO_PARAMETER_DEVICE_DISCONNECT, value, sizeof(value)); if (ret >= 0) { int val = atoi(value); if (val & AUDIO_DEVICE_OUT_AUX_DIGITAL) { ALOGV("stop keep_alive"); audio_extn_keep_alive_stop(); } } return 0; } static void * keep_alive_loop(void * context __unused) { struct keep_alive_cmd *cmd = NULL; struct listnode *item; uint8_t * silence = NULL; int32_t bytes = 0; struct timespec ts; while (true) { pthread_mutex_lock(&ka.lock); if (list_empty(&ka.cmd_list)) { pthread_cond_wait(&ka.cond, &ka.lock); pthread_mutex_unlock(&ka.lock); continue; } item = list_head(&ka.cmd_list); cmd = node_to_item(item, struct keep_alive_cmd, node); list_remove(item); if (cmd->req != REQUEST_WRITE) { free(cmd); pthread_mutex_unlock(&ka.lock); continue; } free(cmd); ka.state = STATE_ACTIVE; pthread_cond_signal(&ka.cond); pthread_mutex_unlock(&ka.lock); if (!silence) { /* 50 ms */ bytes = (silence_config.rate * silence_config.channels * sizeof(int16_t)) / 20; silence = (uint8_t *)calloc(1, bytes); } while (!ka.done) { ALOGV("write %d bytes of silence", bytes); pcm_write(ka.pcm, (void *)silence, bytes); /* This thread does not have to write silence continuously. * Just something to keep the connection alive is sufficient. * Hence a short burst of silence periodically. */ pthread_mutex_lock(&ka.sleep_lock); clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += SILENCE_INTERVAL; ts.tv_nsec = 0; if (!ka.done) pthread_cond_timedwait(&ka.wake_up_cond, &ka.sleep_lock, &ts); pthread_mutex_unlock(&ka.sleep_lock); } pthread_mutex_lock(&ka.lock); ka.state = STATE_IDLE; pthread_cond_signal(&ka.cond); pthread_mutex_unlock(&ka.lock); } return 0; }