audio: sndmonitor
Monitor sound card status and report online/offline
events back to the streams registered with it and
to audio_dev.
Bug: 30075678
Change-Id: Iea74d5c83f7fe92ba4a24f4f4650ce255bdb48e8
diff --git a/hal/Android.mk b/hal/Android.mk
index 6125573..fc2be7d 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -128,6 +128,11 @@
LOCAL_SRC_FILES += audio_extn/hwdep_cal.c
endif
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_SND_MONITOR)), true)
+ LOCAL_CFLAGS += -DSND_MONITOR_ENABLED
+ LOCAL_SRC_FILES += audio_extn/sndmonitor.c
+endif
+
LOCAL_MODULE := audio.primary.$(TARGET_BOARD_PLATFORM)
LOCAL_MODULE_RELATIVE_PATH := hw
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index 715284a..5ce3093 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -132,4 +132,18 @@
void hw_info_append_hw_type(void *hw_info, snd_device_t snd_device,
char *device_name);
#endif /* HW_VARIANTS_ENABLED */
+
+typedef void (* snd_mon_cb)(void * stream, struct str_parms * parms);
+#ifndef SND_MONITOR_ENABLED
+#define audio_extn_snd_mon_init() (0)
+#define audio_extn_snd_mon_deinit() (0)
+#define audio_extn_snd_mon_register_listener(stream, cb) (0)
+#define audio_extn_snd_mon_unregister_listener(stream) (0)
+#else
+int audio_extn_snd_mon_init();
+int audio_extn_snd_mon_deinit();
+int audio_extn_snd_mon_register_listener(void *stream, snd_mon_cb cb);
+int audio_extn_snd_mon_unregister_listener(void *stream);
+#endif
+
#endif /* AUDIO_EXTN_H */
diff --git a/hal/audio_extn/sndmonitor.c b/hal/audio_extn/sndmonitor.c
new file mode 100644
index 0000000..4b6332c
--- /dev/null
+++ b/hal/audio_extn/sndmonitor.c
@@ -0,0 +1,615 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "audio_hw_sndmonitor"
+/*#define LOG_NDEBUG 0*/
+#define LOG_NDDEBUG 0
+
+/* monitor sound card, cpe state
+
+ audio_dev registers for a callback from this module in adev_open
+ Each stream in audio_hal registers for a callback in
+ adev_open_*_stream.
+
+ A thread is spawned to poll() on sound card state files in /proc.
+ On observing a sound card state change, this thread invokes the
+ callbacks registered.
+
+ Callbacks are deregistered in adev_close_*_stream and adev_close
+*/
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+#include <cutils/list.h>
+#include <cutils/hashmap.h>
+#include <cutils/log.h>
+#include <cutils/str_parms.h>
+#include <ctype.h>
+
+#include "audio_hw.h"
+#include "audio_extn.h"
+
+//#define MONITOR_DEVICE_EVENTS
+#define CPE_MAGIC_NUM 0x2000
+#define MAX_CPE_SLEEP_RETRY 2
+#define CPE_SLEEP_WAIT 100
+
+#define MAX_SLEEP_RETRY 100
+#define AUDIO_INIT_SLEEP_WAIT 100 /* 100 ms */
+
+#define AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE "ext_audio_device"
+
+typedef enum {
+ audio_event_on,
+ audio_event_off
+} audio_event_status;
+
+typedef struct {
+ int card;
+ int fd;
+ struct listnode node; // membership in sndcards list
+ card_status_t status;
+} sndcard_t;
+
+typedef struct {
+ char * dev;
+ int fd;
+ int status;
+ struct listnode node; // membership in deviceevents list;
+} dev_event_t;
+
+typedef void (* notifyfn)(const void * target, const char * msg);
+
+typedef struct {
+ const void * target;
+ notifyfn notify;
+ struct listnode cards;
+ unsigned int num_cards;
+ struct listnode dev_events;
+ unsigned int num_dev_events;
+ pthread_t monitor_thread;
+ int intpipe[2];
+ Hashmap * listeners; // from stream * -> callback func
+ bool initcheck;
+} sndmonitor_state_t;
+
+static sndmonitor_state_t sndmonitor;
+
+static char * read_state(int fd)
+{
+ struct stat buf;
+ if (fstat(fd, &buf) < 0)
+ return NULL;
+
+ off_t pos = lseek(fd, 0, SEEK_CUR);
+ off_t avail = buf.st_size - pos;
+ if (avail <= 0) {
+ ALOGD("avail %ld", avail);
+ return NULL;
+ }
+
+ char * state = (char *)calloc(avail+1, sizeof(char));
+ if (!state)
+ return NULL;
+
+ ssize_t bytes=read(fd, state, avail);
+ if (bytes <= 0)
+ return NULL;
+
+ // trim trailing whitespace
+ while (bytes && isspace(*(state+bytes-1))) {
+ *(state + bytes - 1) = '\0';
+ --bytes;
+ }
+ lseek(fd, 0, SEEK_SET);
+ return state;
+}
+
+static int add_new_sndcard(int card, int fd)
+{
+ sndcard_t * s = (sndcard_t *)calloc(sizeof(sndcard_t), 1);
+
+ if (!s)
+ return -1;
+
+ s->card = card;
+ s->fd = fd; // dup?
+
+ char * state = read_state(fd);
+ bool online = state && !strcmp(state, "ONLINE");
+
+ ALOGV("card %d initial state %s %d", card, state, online);
+
+ if (state)
+ free(state);
+
+ s->status = online ? CARD_STATUS_ONLINE : CARD_STATUS_OFFLINE;
+ list_add_tail(&sndmonitor.cards, &s->node);
+ return 0;
+}
+
+static int enum_sndcards()
+{
+ const char* cards = "/proc/asound/cards";
+ int tries = 10;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t bytes_read;
+ char path[128] = {0};
+ char *ptr, *saveptr;
+ int line_no=0;
+ unsigned int num_cards=0, num_cpe=0;
+ FILE *fp;
+ int fd, ret;
+
+ while (--tries) {
+ if ((fp = fopen(cards, "r")) == NULL) {
+ ALOGE("Cannot open %s file to get list of sound cards", cards);
+ usleep(100000);
+ continue;
+ }
+ break;
+ }
+
+ if (!tries)
+ return -ENODEV;
+
+ while ((bytes_read = getline(&line, &len, fp) != -1)) {
+ // skip every other line to to match
+ // the output format of /proc/asound/cards
+ if (line_no++ % 2)
+ continue;
+
+ ptr = strtok_r(line, " [", &saveptr);
+ if (!ptr)
+ continue;
+
+ snprintf(path, sizeof(path), "/proc/asound/card%s/state", ptr);
+ ALOGV("Opening sound card state : %s", path);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Open %s failed : %s", path, strerror(errno));
+ continue;
+ }
+
+ ret = add_new_sndcard(atoi(ptr), fd);
+ if (ret != 0)
+ continue;
+
+ num_cards++;
+
+ // query cpe state for this card as well
+ tries=MAX_CPE_SLEEP_RETRY;
+ snprintf(path, sizeof(path), "/proc/asound/card%s/cpe0_state", ptr);
+
+ if (access(path, R_OK) < 0) {
+ ALOGW("access %s failed w/ err %s", path, strerror(errno));
+ continue;
+ }
+
+ ALOGV("Open cpe state card state %s", path);
+ while (--tries) {
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ ALOGW("Open cpe state card state failed, retry : %s", path);
+ usleep(CPE_SLEEP_WAIT*1000);
+ continue;
+ }
+ break;
+ }
+
+ if (!tries)
+ continue;
+
+ ret = add_new_sndcard(CPE_MAGIC_NUM+num_cpe, fd);
+ if (ret != 0)
+ continue;
+
+ num_cpe++;
+ num_cards++;
+ }
+ if (line)
+ free(line);
+ fclose(fp);
+ ALOGV("sndmonitor registerer num_cards %d", num_cards);
+ sndmonitor.num_cards = num_cards;
+ return num_cards ? 0 : -1;
+}
+
+static void free_sndcards()
+{
+ while (!list_empty(&sndmonitor.cards)) {
+ struct listnode * n = list_head(&sndmonitor.cards);
+ sndcard_t * s = node_to_item(n, sndcard_t, node);
+ list_remove(n);
+ close(s->fd);
+ free(s);
+ }
+}
+
+static int add_new_dev_event(char * d_name, int fd)
+{
+ dev_event_t * d = (dev_event_t *)calloc(sizeof(dev_event_t), 1);
+
+ if (!d)
+ return -1;
+
+ d->dev = strdup(d_name);
+ d->fd = fd;
+ list_add_tail(&sndmonitor.dev_events, &d->node);
+ return 0;
+}
+
+static int enum_dev_events()
+{
+ const char* events_dir = "/sys/class/switch/";
+ DIR *dp;
+ struct dirent* in_file;
+ int fd;
+ char path[128] = {0};
+ unsigned int num_dev_events = 0;
+
+ if ((dp = opendir(events_dir)) == NULL) {
+ ALOGE("Cannot open switch directory %s err %s",
+ events_dir, strerror(errno));
+ return -1;
+ }
+
+ while ((in_file = readdir(dp)) != NULL) {
+ if (!strstr(in_file->d_name, "qc_"))
+ continue;
+
+ snprintf(path, sizeof(path), "%s/%s/state",
+ events_dir, in_file->d_name);
+
+ ALOGV("Opening audio dev event state : %s ", path);
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Open %s failed : %s", path, strerror(errno));
+ } else {
+ if (!add_new_dev_event(in_file->d_name, fd))
+ num_dev_events++;
+ }
+ }
+ closedir(dp);
+ sndmonitor.num_dev_events = num_dev_events;
+ return num_dev_events ? 0 : -1;
+}
+
+static void free_dev_events()
+{
+ while (!list_empty(&sndmonitor.dev_events)) {
+ struct listnode * n = list_head(&sndmonitor.dev_events);
+ dev_event_t * d = node_to_item(n, dev_event_t, node);
+ list_remove(n);
+ close(d->fd);
+ free(d->dev);
+ free(d);
+ }
+}
+
+static int notify(const struct str_parms * params)
+{
+ if (!params)
+ return -1;
+
+ char * str = str_parms_to_str((struct str_parms *)params);
+
+ if (!str)
+ return -1;
+
+ if (sndmonitor.notify)
+ sndmonitor.notify(sndmonitor.target, str);
+
+ ALOGV("%s", str);
+ free(str);
+ return 0;
+}
+
+int on_dev_event(dev_event_t * dev_event)
+{
+ char state_buf[2];
+ if (read(dev_event->fd, state_buf, 1) <= 0)
+ return -1;
+
+ lseek(dev_event->fd, 0, SEEK_SET);
+ state_buf[1]='\0';
+ if (atoi(state_buf) == dev_event->status)
+ return 0;
+
+ dev_event->status = atoi(state_buf);
+
+ struct str_parms * params = str_parms_create();
+
+ if (!params)
+ return -1;
+
+ char val[32] = {0};
+ snprintf(val, sizeof(val), "%s,%s", dev_event->dev,
+ dev_event->status ? "ON" : "OFF");
+
+ if (str_parms_add_str(params, AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE, val) < 0)
+ return -1;
+
+ int ret = notify(params);
+ str_parms_destroy(params);
+ return ret;
+}
+
+bool on_sndcard_state_update(sndcard_t * s)
+{
+ char rd_buf[9]={0};
+ card_status_t status;
+
+ if (read(s->fd, rd_buf, 8) <= 0)
+ return -1;
+
+ rd_buf[8] = '\0';
+ lseek(s->fd, 0, SEEK_SET);
+
+ ALOGV("card num %d, new state %s", s->card, rd_buf);
+
+ bool is_cpe = (s->card >= CPE_MAGIC_NUM);
+ if (strstr(rd_buf, "OFFLINE"))
+ status = CARD_STATUS_OFFLINE;
+ else if (strstr(rd_buf, "ONLINE"))
+ status = CARD_STATUS_ONLINE;
+ else {
+ ALOGE("unknown state");
+ return 0;
+ }
+
+ if (status == s->status) // no change
+ return 0;
+
+ s->status = status;
+
+ struct str_parms * params = str_parms_create();
+
+ if (!params)
+ return -1;
+
+ char val[32] = {0};
+ // cpe actual card num is (card - MAGIC_NUM). so subtract accordingly
+ snprintf(val, sizeof(val), "%d,%s", s->card - (is_cpe ? CPE_MAGIC_NUM : 0),
+ status == CARD_STATUS_ONLINE ? "ONLINE" : "OFFLINE");
+
+ if (str_parms_add_str(params, is_cpe ? "CPE_STATUS" : "SND_CARD_STATUS",
+ val) < 0)
+ return -1;
+
+ int ret = notify(params);
+ str_parms_destroy(params);
+ return ret;
+}
+
+void * monitor_thread_loop(void * args __unused)
+{
+ ALOGV("Start threadLoop()");
+ unsigned int num_poll_fds = sndmonitor.num_cards +
+ sndmonitor.num_dev_events + 1/*pipe*/;
+ struct pollfd * pfd = (struct pollfd *)calloc(sizeof(struct pollfd),
+ num_poll_fds);
+ if (!pfd)
+ return NULL;
+
+ pfd[0].fd = sndmonitor.intpipe[0];
+ pfd[0].events = POLLPRI|POLLIN;
+
+ int i=1;
+ struct listnode *node;
+ list_for_each(node, &sndmonitor.cards) {
+ sndcard_t * s = node_to_item(node, sndcard_t, node);
+ pfd[i].fd = s->fd;
+ pfd[i].events = POLLPRI;
+ ++i;
+ }
+
+ list_for_each(node, &sndmonitor.dev_events) {
+ dev_event_t * d = node_to_item(node, dev_event_t, node);
+ pfd[i].fd = d->fd;
+ pfd[i].events = POLLPRI;
+ ++i;
+ }
+
+ while (1) {
+ if (poll(pfd, num_poll_fds, -1) < 0) {
+ ALOGE("poll() failed w/ err %s", strerror(errno));
+ break;
+ }
+ ALOGV("out of poll()");
+
+#define READY_TO_READ(p) ((p)->revents & (POLLIN|POLLPRI))
+#define ERROR_IN_FD(p) ((p)->revents & (POLLERR|POLLHUP|POLLNVAL))
+
+ // check if requested to exit
+ if (READY_TO_READ(&pfd[0])) {
+ char buf[2]={0};
+ read(pfd[0].fd, buf, 1);
+ if (!strcmp(buf, "Q"))
+ break;
+ } else if (ERROR_IN_FD(&pfd[0])) {
+ // do not consider for poll again
+ pfd[0].fd *= -1;
+ }
+
+ i=1;
+ list_for_each(node, &sndmonitor.cards) {
+ sndcard_t * s = node_to_item(node, sndcard_t, node);
+ if (READY_TO_READ(&pfd[i]))
+ on_sndcard_state_update(s);
+ else if (ERROR_IN_FD(&pfd[i])) {
+ // do not consider for poll again
+ pfd[i].fd *= -1;
+ }
+ ++i;
+ }
+
+ list_for_each(node, &sndmonitor.dev_events) {
+ dev_event_t * d = node_to_item(node, dev_event_t, node);
+ if (READY_TO_READ(&pfd[i]))
+ on_dev_event(d);
+ else if (ERROR_IN_FD(&pfd[i])) {
+ // do not consider for poll again
+ pfd[i].fd *= -1;
+ }
+ ++i;
+ }
+ }
+ return NULL;
+}
+
+// ---- listener static APIs ---- //
+static int hashfn(void * key)
+{
+ return (int)key;
+}
+
+static bool hasheq(void * key1, void *key2)
+{
+ return key1 == key2;
+}
+
+static bool snd_cb(void* key, void* value, void* context)
+{
+ snd_mon_cb cb = (snd_mon_cb)value;
+ cb(key, context);
+ return true;
+}
+
+static void snd_mon_update(const void * target __unused, const char * msg)
+{
+ // target can be used to check if this message is intended for the
+ // recipient or not. (using some statically saved state)
+
+ struct str_parms *parms = str_parms_create_str(msg);
+
+ if (!parms)
+ return;
+
+ hashmapLock(sndmonitor.listeners);
+ hashmapForEach(sndmonitor.listeners, snd_cb, parms);
+ hashmapUnlock(sndmonitor.listeners);
+
+ str_parms_destroy(parms);
+}
+
+static int listeners_init()
+{
+ sndmonitor.listeners = hashmapCreate(5, hashfn, hasheq);
+ if (!sndmonitor.listeners)
+ return -1;
+ return 0;
+}
+
+static int listeners_deinit()
+{
+ // XXX TBD
+ return -1;
+}
+
+static int add_listener(void *stream, snd_mon_cb cb)
+{
+ Hashmap * map = sndmonitor.listeners;
+ hashmapLock(map);
+ hashmapPut(map, stream, cb);
+ hashmapUnlock(map);
+ return 0;
+}
+
+static int del_listener(void * stream)
+{
+ Hashmap * map = sndmonitor.listeners;
+ hashmapLock(map);
+ hashmapRemove(map, stream);
+ hashmapUnlock(map);
+ return 0;
+}
+
+// --- public APIs --- //
+
+int audio_extn_snd_mon_deinit()
+{
+ if (!sndmonitor.initcheck)
+ return -1;
+
+ write(sndmonitor.intpipe[1], "Q", 1);
+ pthread_join(sndmonitor.monitor_thread, (void **) NULL);
+ free_sndcards();
+ free_dev_events();
+ sndmonitor.initcheck = 0;
+ return 0;
+}
+
+int audio_extn_snd_mon_init()
+{
+ sndmonitor.notify = snd_mon_update;
+ sndmonitor.target = NULL; // unused for now
+ list_init(&sndmonitor.cards);
+ list_init(&sndmonitor.dev_events);
+ sndmonitor.initcheck = false;
+
+ if (pipe(sndmonitor.intpipe) < 0)
+ return -ENODEV;
+
+ if (enum_sndcards() < 0)
+ return -ENODEV;
+
+ if (listeners_init() < 0)
+ return -ENODEV;
+
+#ifdef MONITOR_DEVICE_EVENTS
+ enum_dev_events(); // failure here isn't fatal
+#endif
+
+ int ret = pthread_create(&sndmonitor.monitor_thread,
+ (const pthread_attr_t *) NULL,
+ monitor_thread_loop, NULL);
+
+ if (ret) {
+ free_sndcards();
+ free_dev_events();
+ close(sndmonitor.intpipe[0]);
+ close(sndmonitor.intpipe[1]);
+ return -ENODEV;
+ }
+ sndmonitor.initcheck = true;
+ return 0;
+}
+
+int audio_extn_snd_mon_register_listener(void *stream, snd_mon_cb cb)
+{
+ if (!sndmonitor.initcheck) {
+ ALOGW("sndmonitor initcheck failed, cannot register");
+ return -1;
+ }
+
+ return add_listener(stream, cb);
+}
+
+int audio_extn_snd_mon_unregister_listener(void * stream)
+{
+ if (!sndmonitor.initcheck) {
+ ALOGW("sndmonitor initcheck failed, cannot deregister");
+ return -1;
+ }
+
+ ALOGV("deregister listener for stream %p ", stream);
+ return del_listener(stream);
+}
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 527e9c5..1e185e5 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -348,6 +348,27 @@
adev->adm_abandon_focus(adev->adm_data, in->capture_handle);
}
+static int parse_snd_card_status(struct str_parms * parms, int * card,
+ card_status_t * status)
+{
+ char value[32]={0};
+ char state[32]={0};
+
+ int ret = str_parms_get_str(parms, "SND_CARD_STATUS", value, sizeof(value));
+
+ if (ret < 0)
+ return -1;
+
+ // sscanf should be okay as value is of max length 32.
+ // same as sizeof state.
+ if (sscanf(value, "%d,%s", card, state) < 2)
+ return -1;
+
+ *status = !strcmp(state, "ONLINE") ? CARD_STATUS_ONLINE :
+ CARD_STATUS_OFFLINE;
+ return 0;
+}
+
__attribute__ ((visibility ("default")))
bool audio_hw_send_gain_dep_calibration(int level) {
bool ret_val = false;
@@ -1006,6 +1027,14 @@
struct audio_device *adev = in->dev;
ALOGV("%s: enter: usecase(%d)", __func__, in->usecase);
+
+ if (in->card_status == CARD_STATUS_OFFLINE ||
+ adev->card_status == CARD_STATUS_OFFLINE) {
+ ALOGW("in->card_status or adev->card_status offline, try again");
+ ret = -EAGAIN;
+ goto error_config;
+ }
+
in->pcm_device_id = platform_get_pcm_device_id(in->usecase, PCM_CAPTURE);
if (in->pcm_device_id < 0) {
ALOGE("%s: Could not find PCM device id for the usecase(%d)",
@@ -1074,7 +1103,6 @@
ret = pcm_start(in->pcm);
}
audio_extn_perf_lock_release();
-
ALOGV("%s: exit", __func__);
return ret;
@@ -1373,6 +1401,14 @@
ALOGV("%s: enter: usecase(%d: %s) devices(%#x)",
__func__, out->usecase, use_case_table[out->usecase], out->devices);
+
+ if (out->card_status == CARD_STATUS_OFFLINE ||
+ adev->card_status == CARD_STATUS_OFFLINE) {
+ ALOGW("out->card_status or adev->card_status offline, try again");
+ ret = -EAGAIN;
+ goto error_config;
+ }
+
out->pcm_device_id = platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK);
if (out->pcm_device_id < 0) {
ALOGE("%s: Invalid PCM device id(%d) for the usecase(%d)",
@@ -1848,6 +1884,46 @@
return -ENOSYS;
}
+// note: this call is safe only if the stream_cb is
+// removed first in close_output_stream (as is done now).
+static void out_snd_mon_cb(void * stream, struct str_parms * parms)
+{
+ if (!stream || !parms)
+ return;
+
+ struct stream_out *out = (struct stream_out *)stream;
+ struct audio_device *adev = out->dev;
+
+ card_status_t status;
+ int card;
+ if (parse_snd_card_status(parms, &card, &status) < 0)
+ return;
+
+ pthread_mutex_lock(&adev->lock);
+ bool valid_cb = (card == adev->snd_card);
+ pthread_mutex_unlock(&adev->lock);
+
+ if (!valid_cb)
+ return;
+
+ ALOGV("out_snd_mon_cb for card %d usecase %s", card,
+ use_case_table[out->usecase]);
+
+ lock_output_stream(out);
+ if (out->card_status != status)
+ out->card_status = status;
+ pthread_mutex_unlock(&out->lock);
+
+ // a better solution would be to report error back to AF and let
+ // it put the stream to standby
+ if (status == CARD_STATUS_OFFLINE) {
+ ALOGW("new state == offline, move stream to standby");
+ out_standby(&out->stream.common);
+ }
+
+ return;
+}
+
#ifdef NO_AUDIO_OUT
static ssize_t out_write_for_no_output(struct audio_stream_out *stream,
const void *buffer, size_t bytes)
@@ -2256,6 +2332,43 @@
return 0;
}
+static void in_snd_mon_cb(void * stream, struct str_parms * parms)
+{
+ if (!stream || !parms)
+ return;
+
+ struct stream_in *in = (struct stream_in *)stream;
+ struct audio_device *adev = in->dev;
+
+ card_status_t status;
+ int card;
+ if (parse_snd_card_status(parms, &card, &status) < 0)
+ return;
+
+ pthread_mutex_lock(&adev->lock);
+ bool valid_cb = (card == adev->snd_card);
+ pthread_mutex_unlock(&adev->lock);
+
+ if (!valid_cb)
+ return;
+
+ lock_input_stream(in);
+ if (in->card_status != status)
+ in->card_status = status;
+ pthread_mutex_unlock(&in->lock);
+
+ ALOGW("in_snd_mon_cb for card %d usecase %s, status %s", card,
+ use_case_table[in->usecase],
+ status == CARD_STATUS_OFFLINE ? "offline" : "online");
+
+ // a better solution would be to report error back to AF and let
+ // it put the stream to standby
+ if (status == CARD_STATUS_OFFLINE)
+ in_standby(&in->stream.common);
+
+ return;
+}
+
static ssize_t in_read(struct audio_stream_in *stream, void *buffer,
size_t bytes)
{
@@ -2651,6 +2764,19 @@
config->channel_mask = out->stream.common.get_channels(&out->stream.common);
config->sample_rate = out->stream.common.get_sample_rate(&out->stream.common);
+
+ /*
+ By locking output stream before registering, we allow the callback
+ to update stream's state only after stream's initial state is set to
+ adev state.
+ */
+ lock_output_stream(out);
+ audio_extn_snd_mon_register_listener(out, out_snd_mon_cb);
+ pthread_mutex_lock(&adev->lock);
+ out->card_status = adev->card_status;
+ pthread_mutex_unlock(&adev->lock);
+ pthread_mutex_unlock(&out->lock);
+
*stream_out = &out->stream;
ALOGV("%s: exit", __func__);
return 0;
@@ -2669,6 +2795,10 @@
struct audio_device *adev = out->dev;
ALOGV("%s: enter", __func__);
+
+ // must deregister from sndmonitor first to prevent races
+ // between the callback and close_stream
+ audio_extn_snd_mon_unregister_listener(out);
out_standby(&stream->common);
if (out->usecase == USECASE_AUDIO_PLAYBACK_OFFLOAD) {
destroy_offload_callback_thread(out);
@@ -3010,6 +3140,13 @@
get sound trigger pcm if present */
audio_extn_sound_trigger_check_and_get_session(in);
+ lock_input_stream(in);
+ audio_extn_snd_mon_register_listener(in, in_snd_mon_cb);
+ pthread_mutex_lock(&adev->lock);
+ in->card_status = adev->card_status;
+ pthread_mutex_unlock(&adev->lock);
+ pthread_mutex_unlock(&in->lock);
+
*stream_in = &in->stream;
ALOGV("%s: exit", __func__);
return 0;
@@ -3025,6 +3162,9 @@
{
ALOGV("%s", __func__);
+ // must deregister from sndmonitor first to prevent races
+ // between the callback and close_stream
+ audio_extn_snd_mon_unregister_listener(stream);
in_standby(&stream->common);
free(stream);
@@ -3177,6 +3317,7 @@
if (!adev)
return 0;
+ audio_extn_snd_mon_unregister_listener(adev);
pthread_mutex_lock(&adev_init_lock);
if ((--audio_device_ref_count) == 0) {
@@ -3219,6 +3360,29 @@
}
}
+static void adev_snd_mon_cb(void * stream __unused, struct str_parms * parms)
+{
+ int card;
+ card_status_t status;
+
+ if (!parms)
+ return;
+
+ if (parse_snd_card_status(parms, &card, &status) < 0)
+ return;
+
+ pthread_mutex_lock(&adev->lock);
+ bool valid_cb = (card == adev->snd_card);
+ if (valid_cb) {
+ if (adev->card_status != status) {
+ adev->card_status = status;
+ platform_snd_card_update(adev->platform, status);
+ }
+ }
+ pthread_mutex_unlock(&adev->lock);
+ return;
+}
+
static int adev_open(const hw_module_t *module, const char *name,
hw_device_t **device)
{
@@ -3285,7 +3449,6 @@
pthread_mutex_unlock(&adev_init_lock);
return -EINVAL;
}
-
adev->extspk = audio_extn_extspk_init(adev);
audio_extn_sound_trigger_init(adev);
@@ -3390,6 +3553,11 @@
adev->adm_data = adev->adm_init();
audio_extn_perf_lock_init();
+ audio_extn_snd_mon_init();
+ pthread_mutex_lock(&adev->lock);
+ audio_extn_snd_mon_register_listener(NULL, adev_snd_mon_cb);
+ adev->card_status = CARD_STATUS_ONLINE;
+ pthread_mutex_unlock(&adev->lock);
ALOGD("%s: exit", __func__);
return 0;
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index b586c57..55c3be4 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -51,6 +51,11 @@
#define MAX_SUPPORTED_CHANNEL_MASKS 2
#define DEFAULT_HDMI_OUT_CHANNELS 2
+typedef enum card_status_t {
+ CARD_STATUS_OFFLINE,
+ CARD_STATUS_ONLINE
+} card_status_t;
+
/* These are the supported use cases by the hardware.
* Each usecase is mapped to a specific PCM device.
* Refer to pcm_device_table[].
@@ -181,6 +186,7 @@
int af_period_multiplier;
bool routing_change;
struct audio_device *dev;
+ card_status_t card_status;
};
struct stream_in {
@@ -208,6 +214,7 @@
bool routing_change;
struct audio_device *dev;
audio_format_t format;
+ card_status_t card_status;
};
typedef enum {
@@ -272,6 +279,8 @@
void *platform;
void *extspk;
+ card_status_t card_status;
+
void *visualizer_lib;
int (*visualizer_start_output)(audio_io_handle_t, int);
int (*visualizer_stop_output)(audio_io_handle_t, int);
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index a6d9789..6179d01 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -2208,3 +2208,9 @@
}
return 0;
}
+
+int platform_snd_card_update(void *platform __unsed,
+ card_status_t status __unused)
+{
+ return -1;
+}
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index bd12e93..b8efd28 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -1112,3 +1112,9 @@
{
return 0;
}
+
+int platform_snd_card_update(void *platform __unsed,
+ card_status_t status __unused)
+{
+ return -1;
+}
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index e4bdbdc..441e2a1 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -105,6 +105,7 @@
typedef void (*acdb_send_voice_cal_t)(int, int);
typedef int (*acdb_reload_vocvoltable_t)(int);
typedef int (*acdb_send_gain_dep_cal_t)(int, int, int, int, int);
+typedef int (*acdb_send_custom_top_t) (void);
/* Audio calibration related functions */
struct platform_data {
@@ -119,11 +120,21 @@
bool speaker_lr_swap;
void *acdb_handle;
+#if defined (PLATFORM_MSM8994) || (PLATFORM_MSM8996)
+ acdb_init_v2_cvd_t acdb_init;
+#elif defined (PLATFORM_MSM8084)
+ acdb_init_v2_t acdb_init;
+#else
+ acdb_init_t acdb_init;
+#endif
acdb_deallocate_t acdb_deallocate;
acdb_send_audio_cal_t acdb_send_audio_cal;
acdb_send_voice_cal_t acdb_send_voice_cal;
acdb_reload_vocvoltable_t acdb_reload_vocvoltable;
acdb_send_gain_dep_cal_t acdb_send_gain_dep_cal;
+ acdb_send_custom_top_t acdb_send_custom_top;
+ bool acdb_initialized;
+
struct csd_data *csd;
char ec_ref_mixer_path[64];
@@ -946,6 +957,39 @@
return;
}
+static int platform_acdb_init(void *platform)
+{
+ struct platform_data *my_data = (struct platform_data *)platform;
+ struct audio_device *adev = my_data->adev;
+
+ if (!my_data->acdb_init) {
+ ALOGE("%s: no acdb_init fn provided", __func__);
+ return -1;
+ }
+
+ if (my_data->acdb_initialized) {
+ ALOGW("acdb is already initialized");
+ return 0;
+ }
+
+#if defined (PLATFORM_MSM8994) || (PLATFORM_MSM8996)
+ char *cvd_version = calloc(1, MAX_CVD_VERSION_STRING_SIZE);
+ if (!cvd_version)
+ ALOGE("failed to allocate cvd_version");
+ else {
+ get_cvd_version(cvd_version, adev);
+ my_data->acdb_init((char *)my_data->snd_card_name, cvd_version, 0);
+ free(cvd_version);
+ }
+#elif defined (PLATFORM_MSM8084)
+ my_data->acdb_init((char *)my_data->snd_card_name);
+#else
+ my_data->acdb_init();
+#endif
+ my_data->acdb_initialized = true;
+ return 0;
+}
+
void *platform_init(struct audio_device *adev)
{
char value[PROPERTY_VALUE_MAX];
@@ -1198,39 +1242,38 @@
acdb_init_v2_cvd_t acdb_init;
acdb_init = (acdb_init_v2_cvd_t)dlsym(my_data->acdb_handle,
"acdb_loader_init_v2");
- if (acdb_init == NULL) {
- ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__, dlerror());
- goto acdb_init_fail;
- }
+ if (acdb_init == NULL)
+ ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__,
+ dlerror());
- cvd_version = calloc(1, MAX_CVD_VERSION_STRING_SIZE);
- get_cvd_version(cvd_version, adev);
- if (!cvd_version)
- ALOGE("failed to allocate cvd_version");
- else
- acdb_init((char *)snd_card_name, cvd_version, 0);
- free(cvd_version);
#elif defined (PLATFORM_MSM8084)
acdb_init_v2_t acdb_init;
acdb_init = (acdb_init_v2_t)dlsym(my_data->acdb_handle,
"acdb_loader_init_v2");
- if (acdb_init == NULL) {
- ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__, dlerror());
- goto acdb_init_fail;
- }
- acdb_init((char *)snd_card_name);
+ if (acdb_init == NULL)
+ ALOGE("%s: dlsym error %s for acdb_loader_init_v2", __func__,
+ dlerror());
+
#else
acdb_init_t acdb_init;
acdb_init = (acdb_init_t)dlsym(my_data->acdb_handle,
"acdb_loader_init_ACDB");
if (acdb_init == NULL)
- ALOGE("%s: dlsym error %s for acdb_loader_init_ACDB", __func__, dlerror());
- else
- acdb_init();
+ ALOGE("%s: dlsym error %s for acdb_loader_init_ACDB", __func__,
+ dlerror());
#endif
- }
+ my_data->acdb_init = acdb_init;
-acdb_init_fail:
+ my_data->acdb_send_custom_top = (acdb_send_custom_top_t)
+ dlsym(my_data->acdb_handle,
+ "acdb_loader_send_common_custom_topology");
+
+ if (!my_data->acdb_send_custom_top)
+ ALOGE("%s: Could not find the symbol acdb_get_default_app_type from %s",
+ __func__, LIB_ACDB_LOADER);
+
+ platform_acdb_init(my_data);
+ }
audio_extn_spkr_prot_init(adev);
@@ -2783,3 +2826,15 @@
return num_gain_tbl_entry;
}
+
+int platform_snd_card_update(void *platform, card_status_t status)
+{
+ struct platform_data *my_data = (struct platform_data *)platform;
+ struct audio_device *adev = my_data->adev;
+
+ if (status == CARD_STATUS_ONLINE) {
+ if (my_data->acdb_send_custom_top)
+ my_data->acdb_send_custom_top();
+ }
+ return 0;
+}
diff --git a/hal/platform_api.h b/hal/platform_api.h
index c813793..41e600e 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -26,6 +26,8 @@
uint32_t level;
};
+enum card_status_t;
+
void *platform_init(struct audio_device *adev);
void platform_deinit(void *platform);
const char *platform_get_snd_device_name(snd_device_t snd_device);
@@ -109,4 +111,5 @@
bool platform_check_and_set_capture_backend_cfg(struct audio_device* adev,
struct audio_usecase *usecase, snd_device_t snd_device);
+int platform_snd_card_update(void *platform, enum card_status_t status);
#endif // AUDIO_PLATFORM_API_H