blob: 7d8951a691cbe4d7099b35bcac921b946943ccab [file] [log] [blame]
/*
* Copyright (c) 2014-2018, 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.
*
* Changes from Qualcomm Innovation Center are provided under the following license:
*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause-Clear
*/
#define LOG_TAG "keep_alive"
/*#define LOG_NDEBUG 0*/
#include <cutils/properties.h>
#include <stdlib.h>
#include <log/log.h>
#include "audio_hw.h"
#include "audio_extn.h"
#include "platform_api.h"
#include <platform.h>
#include <pthread.h>
#ifdef DYNAMIC_LOG_ENABLED
#include <log_xml_parser.h>
#define LOG_MASK HAL_MOD_FILE_KEEP_ALIVE
#include <log_utils.h>
#endif
#define SILENCE_INTERVAL 2 /*In secs*/
typedef enum {
STATE_DEINIT = -1,
STATE_IDLE,
STATE_ACTIVE,
STATE_DISABLED,
} state_t;
typedef enum {
REQUEST_WRITE,
REQUEST_QUIT,
} 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;
struct stream_out *out;
ka_mode_t prev_mode;
bool done;
void * userdata;
struct listnode active_devices;
} 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);
static int keep_alive_cleanup();
static int keep_alive_start_l();
static void send_cmd_l(request_t r)
{
if (ka.state == STATE_DEINIT || ka.state == STATE_DISABLED)
return;
struct keep_alive_cmd *cmd =
(struct keep_alive_cmd *)calloc(1, sizeof(struct keep_alive_cmd));
if (cmd == NULL) {
ALOGE("%s: cmd is NULL", __func__);
return;
}
cmd->req = r;
list_add_tail(&ka.cmd_list, &cmd->node);
pthread_cond_signal(&ka.cond);
}
void keep_alive_init(struct audio_device *adev)
{
ka.userdata = adev;
ka.state = STATE_IDLE;
ka.pcm = NULL;
pthread_condattr_t attr;
if (property_get_bool("vendor.audio.keep_alive.disabled", true)) {
ALOGE("keep alive disabled");
ka.state = STATE_DISABLED;
return;
}
ka.done = false;
ka.prev_mode = KEEP_ALIVE_OUT_NONE;
list_init(&ka.active_devices);
pthread_mutex_init(&ka.lock, (const pthread_mutexattr_t *) NULL);
pthread_cond_init(&ka.cond, (const pthread_condattr_t *) NULL);
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
pthread_cond_init(&ka.wake_up_cond, &attr);
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;
return;
}
ALOGV("%s init done", __func__);
}
void keep_alive_deinit()
{
if (ka.state == STATE_DEINIT || ka.state == STATE_DISABLED)
return;
ka.userdata = NULL;
ka.done = true;
pthread_mutex_lock(&ka.lock);
send_cmd_l(REQUEST_QUIT);
pthread_mutex_unlock(&ka.lock);
pthread_join(ka.thread, (void **) NULL);
pthread_mutex_destroy(&ka.lock);
pthread_cond_destroy(&ka.cond);
pthread_cond_destroy(&ka.wake_up_cond);
pthread_mutex_destroy(&ka.sleep_lock);
ALOGV("%s deinit done", __func__);
}
void get_device_id_from_mode(ka_mode_t ka_mode,
struct listnode *out_devices)
{
struct audio_device * adev = (struct audio_device *)ka.userdata;
switch (ka_mode)
{
case KEEP_ALIVE_OUT_PRIMARY:
if (adev->primary_output) {
if (is_audio_out_device_type(&adev->primary_output->device_list))
assign_output_devices(out_devices, &adev->primary_output->device_list);
else
reassign_device_list(out_devices, AUDIO_DEVICE_OUT_SPEAKER, "");
}
else {
reassign_device_list(out_devices, AUDIO_DEVICE_OUT_SPEAKER, "");
}
break;
case KEEP_ALIVE_OUT_HDMI:
reassign_device_list(out_devices, AUDIO_DEVICE_OUT_AUX_DIGITAL, "");
break;
case KEEP_ALIVE_OUT_NONE:
default:
break;
}
}
void keep_alive_start(ka_mode_t ka_mode)
{
struct audio_device * adev = (struct audio_device *)ka.userdata;
struct listnode out_devices;
pthread_mutex_lock(&ka.lock);
list_init(&out_devices);
ALOGV("%s: mode %x", __func__, ka_mode);
if ((ka.state == STATE_DISABLED)||(ka.state == STATE_DEINIT)) {
ALOGE(" %s : Unexpected state %x",__func__, ka.state);
goto exit;
}
get_device_id_from_mode(ka_mode, &out_devices);
if (compare_devices(&out_devices, &ka.active_devices) &&
(ka.state == STATE_ACTIVE)) {
ALOGV(" %s : Already feeding silence to device %x",__func__,
get_device_types(&out_devices));
ka.prev_mode |= ka_mode;
goto exit;
}
ALOGV(" %s : active devices %x, new device %x",__func__,
get_device_types(&ka.active_devices), get_device_types(&out_devices));
if (list_empty(&out_devices))
goto exit;
if (audio_extn_passthru_is_active()) {
update_device_list(&ka.active_devices, AUDIO_DEVICE_OUT_AUX_DIGITAL,
"", false);
if (list_empty(&ka.active_devices))
goto exit;
}
append_devices(&ka.active_devices, &out_devices);
ka.prev_mode |= ka_mode;
if (ka.state == STATE_ACTIVE) {
assign_devices(&ka.out->device_list, &ka.active_devices);
select_devices(adev, USECASE_AUDIO_PLAYBACK_SILENCE);
} else if (ka.state == STATE_IDLE) {
keep_alive_start_l();
}
exit:
pthread_mutex_unlock(&ka.lock);
}
/* must be called with adev lock held */
static int keep_alive_start_l()
{
struct audio_device * adev = (struct audio_device *)ka.userdata;
unsigned int flags = PCM_OUT|PCM_MONOTONIC;
struct audio_usecase *usecase;
int rc = 0;
int silence_pcm_dev_id =
platform_get_pcm_device_id(USECASE_AUDIO_PLAYBACK_SILENCE,
PCM_PLAYBACK);
ka.done = false;
usecase = calloc(1, sizeof(struct audio_usecase));
if (usecase == NULL) {
ALOGE("%s: usecase is NULL", __func__);
rc = -ENOMEM;
goto exit;
}
ka.out = (struct stream_out *)calloc(1, sizeof(struct stream_out));
if (ka.out == NULL) {
ALOGE("%s: keep_alive out is NULL", __func__);
free(usecase);
rc = -ENOMEM;
goto exit;
}
ka.out->flags = 0;
list_init(&ka.out->device_list);
assign_devices(&ka.out->device_list, &ka.active_devices);
ka.out->dev = adev;
ka.out->format = AUDIO_FORMAT_PCM_16_BIT;
ka.out->sample_rate = DEFAULT_OUTPUT_SAMPLING_RATE;
ka.out->channel_mask = AUDIO_CHANNEL_OUT_STEREO;
ka.out->supported_channel_masks[0] = AUDIO_CHANNEL_OUT_STEREO;
ka.out->config = silence_config;
usecase->stream.out = ka.out;
usecase->type = PCM_PLAYBACK;
usecase->id = USECASE_AUDIO_PLAYBACK_SILENCE;
list_init(&usecase->device_list);
usecase->out_snd_device = SND_DEVICE_NONE;
usecase->in_snd_device = SND_DEVICE_NONE;
list_add_tail(&adev->usecase_list, &usecase->list);
select_devices(adev, USECASE_AUDIO_PLAYBACK_SILENCE);
ALOGD("opening pcm device for silence playback %x", silence_pcm_dev_id);
ka.pcm = pcm_open(adev->snd_card, silence_pcm_dev_id,
flags, &silence_config);
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;
}
goto exit;
}
send_cmd_l(REQUEST_WRITE);
while (ka.state != STATE_ACTIVE) {
pthread_cond_wait(&ka.cond, &ka.lock);
}
return rc;
exit:
keep_alive_cleanup();
return rc;
}
void keep_alive_stop(ka_mode_t ka_mode)
{
struct audio_device * adev = (struct audio_device *)ka.userdata;
struct listnode out_devices;
if (ka.state == STATE_DISABLED)
return;
pthread_mutex_lock(&ka.lock);
ALOGV("%s: mode %x", __func__, ka_mode);
list_init(&out_devices);
if (ka_mode && (ka.state != STATE_ACTIVE)) {
get_device_id_from_mode(ka_mode, &out_devices);
ALOGV(" %s : Can't stop, keep_alive",__func__);
ALOGV(" %s : keep_alive is not running on device %x",__func__,
get_device_types(&out_devices));
ka.prev_mode |= ka_mode;
goto exit;
}
get_device_id_from_mode(ka_mode, &out_devices);
if (ka.prev_mode & ka_mode) {
ka.prev_mode &= ~ka_mode;
get_device_id_from_mode(ka.prev_mode, &ka.active_devices);
}
if (list_empty(&ka.active_devices)) {
keep_alive_cleanup();
} else if (!compare_devices(&ka.out->device_list, &ka.active_devices)) {
assign_devices(&ka.out->device_list, &ka.active_devices);
select_devices(adev, USECASE_AUDIO_PLAYBACK_SILENCE);
}
exit:
pthread_mutex_unlock(&ka.lock);
}
/* must be called with adev lock held */
static int keep_alive_cleanup()
{
struct audio_device * adev = (struct audio_device *)ka.userdata;
struct audio_usecase *uc_info;
ka.done = true;
if (ka.out != NULL)
free(ka.out);
pthread_mutex_lock(&ka.sleep_lock);
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);
}
ALOGV("%s: keep_alive state changed to %x", __func__, ka.state);
uc_info = get_usecase_from_list(adev, USECASE_AUDIO_PLAYBACK_SILENCE);
if (uc_info == NULL) {
ALOGE("%s: Could not find keep alive usecase in the list", __func__);
} else {
disable_audio_route(adev, uc_info);
disable_snd_device(adev, uc_info->out_snd_device);
list_remove(&uc_info->list);
free(uc_info);
}
pcm_close(ka.pcm);
ka.pcm = NULL;
clear_devices(&ka.active_devices);
return 0;
}
int keep_alive_set_parameters(struct audio_device *adev __unused,
struct str_parms *parms __unused)
{
char value[32];
int ret, pcm_device_id=0;
if (ka.state == STATE_DISABLED)
return 0;
if ((ka.state == STATE_ACTIVE) && (ka.prev_mode & KEEP_ALIVE_OUT_PRIMARY)){
ret = str_parms_get_str(parms, AUDIO_PARAMETER_STREAM_ROUTING,
value, sizeof(value));
if (ret >= 0) {
pcm_device_id = atoi(value);
if(pcm_device_id > 0)
{
audio_extn_keep_alive_start(KEEP_ALIVE_OUT_PRIMARY);
}
}
}
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_QUIT) {
free(cmd);
pthread_mutex_unlock(&ka.lock);
break;
} else if (cmd->req != REQUEST_WRITE) {
free(cmd);
pthread_mutex_unlock(&ka.lock);
continue;
}
free(cmd);
ka.state = STATE_ACTIVE;
ALOGV("%s: state changed to %x", __func__, ka.state);
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_MONOTONIC, &ts);
ts.tv_sec += SILENCE_INTERVAL;
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;
ALOGV("%s: state changed to %x", __func__, ka.state);
pthread_cond_signal(&ka.cond);
pthread_mutex_unlock(&ka.lock);
}
return 0;
}