748 lines
20 KiB
C
748 lines
20 KiB
C
/*
|
|
* Copyright (c) 2016-2019, 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_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 <pthread.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/poll.h>
|
|
#include <cutils/list.h>
|
|
#include <cutils/hashmap.h>
|
|
#include <log/log.h>
|
|
#include <cutils/str_parms.h>
|
|
#include <ctype.h>
|
|
|
|
#include "audio_hw.h"
|
|
#include "audio_extn.h"
|
|
|
|
#ifdef DYNAMIC_LOG_ENABLED
|
|
#include <log_xml_parser.h>
|
|
#define LOG_MASK HAL_MOD_FILE_SND_MONITOR
|
|
#include <log_utils.h>
|
|
#endif
|
|
|
|
//#define MONITOR_DEVICE_EVENTS
|
|
#define CPE_MAGIC_NUM 0x2000
|
|
#define MAX_CPE_SLEEP_RETRY 2
|
|
#define CPE_SLEEP_WAIT 100
|
|
|
|
#define SPLI_STATE_PATH "/proc/wcd-spi-ac/svc-state"
|
|
#define SLPI_MAGIC_NUM 0x3000
|
|
#define MAX_SLPI_SLEEP_RETRY 2
|
|
#define SLPI_SLEEP_WAIT_MS 100
|
|
|
|
#define MAX_SLEEP_RETRY 100
|
|
#define AUDIO_INIT_SLEEP_WAIT 100 /* 100 ms */
|
|
|
|
#define AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE "ext_audio_device"
|
|
#define INIT_MAP_SIZE 5
|
|
|
|
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) {
|
|
ALOGE("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);
|
|
|
|
if (!state) {
|
|
free(s);
|
|
return -1;
|
|
}
|
|
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 = -1;
|
|
char path[128] = {0};
|
|
char *ptr = NULL, *saveptr = NULL, *card_id = NULL;
|
|
int line_no=0;
|
|
unsigned int num_cards=0, num_cpe=0;
|
|
FILE *fp = NULL;
|
|
int fd = -1, ret = -1;
|
|
|
|
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;
|
|
|
|
card_id = strtok_r(saveptr+1, "]", &saveptr);
|
|
if (!card_id)
|
|
continue;
|
|
|
|
// Only consider sound cards associated with ADSP
|
|
if ((strncasecmp(card_id, "msm", 3) != 0) &&
|
|
(strncasecmp(card_id, "sdm", 3) != 0) &&
|
|
(strncasecmp(card_id, "sdc", 3) != 0) &&
|
|
(strncasecmp(card_id, "sm", 2) != 0) &&
|
|
(strncasecmp(card_id, "trinket", 7) != 0) &&
|
|
(strncasecmp(card_id, "apq", 3) != 0) &&
|
|
(strncasecmp(card_id, "sa", 2) != 0) &&
|
|
(strncasecmp(card_id, "kona", 4) != 0) &&
|
|
(strncasecmp(card_id, "atoll", 5) != 0) &&
|
|
(strncasecmp(card_id, "bengal", 6) != 0) &&
|
|
(strncasecmp(card_id, "lito", 4) != 0)) {
|
|
ALOGW("Skip over non-ADSP snd card %s", card_id);
|
|
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) {
|
|
close(fd);
|
|
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) {
|
|
close(fd);
|
|
continue;
|
|
}
|
|
|
|
num_cpe++;
|
|
num_cards++;
|
|
}
|
|
if (line)
|
|
free(line);
|
|
fclose(fp);
|
|
|
|
/* Add fd to query for SLPI status */
|
|
if (access(SPLI_STATE_PATH, R_OK) < 0) {
|
|
ALOGV("access to %s failed: %s", SPLI_STATE_PATH, strerror(errno));
|
|
} else {
|
|
tries = MAX_SLPI_SLEEP_RETRY;
|
|
ALOGV("Open %s", SPLI_STATE_PATH);
|
|
while (tries--) {
|
|
if ((fd = open(SPLI_STATE_PATH, O_RDONLY)) < 0) {
|
|
ALOGW("Open %s failed %s, retry", SPLI_STATE_PATH,
|
|
strerror(errno));
|
|
usleep(SLPI_SLEEP_WAIT_MS * 1000);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (fd >= 0) {
|
|
ret = add_new_sndcard(SLPI_MAGIC_NUM, fd);
|
|
if (ret != 0)
|
|
close(fd);
|
|
else
|
|
num_cards++;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
#ifdef MONITOR_DEVICE_EVENTS
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
|
|
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};
|
|
bool is_cpe = ((s->card >= CPE_MAGIC_NUM) && (s->card < SLPI_MAGIC_NUM));
|
|
bool is_slpi = (s->card == SLPI_MAGIC_NUM);
|
|
char *key = NULL;
|
|
/*
|
|
* cpe actual card num is (card - CPE_MAGIC_NUM), so subtract accordingly.
|
|
* SLPI actual fd num is (card - SLPI_MAGIC_NUM), so subtract accordingly.
|
|
*/
|
|
snprintf(val, sizeof(val), "%d,%s",
|
|
s->card - (is_cpe ? CPE_MAGIC_NUM : (is_slpi ? SLPI_MAGIC_NUM : 0)),
|
|
status == CARD_STATUS_ONLINE ? "ONLINE" : "OFFLINE");
|
|
key = (is_cpe ? "CPE_STATUS" :
|
|
(is_slpi ? "SLPI_STATUS" :
|
|
"SND_CARD_STATUS"));
|
|
if (str_parms_add_str(params, key, 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) {
|
|
int errno_ = errno;
|
|
ALOGE("poll() failed w/ err %s", strerror(errno_));
|
|
switch (errno_) {
|
|
case EINTR:
|
|
case ENOMEM:
|
|
sleep(2);
|
|
continue;
|
|
default:
|
|
/* above errors can be caused due to current system
|
|
state .. any other error is not expected */
|
|
LOG_ALWAYS_FATAL("unxpected poll() system call failure");
|
|
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
|
|
// POLLERR - can this happen?
|
|
// POLLHUP - adev must not close pipe
|
|
// POLLNVAL - fd is valid
|
|
LOG_ALWAYS_FATAL("unxpected error in pipe poll fd 0x%x",
|
|
pfd[0].revents);
|
|
// FIXME: If not fatal, then need some logic to close
|
|
// these fds on error
|
|
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
|
|
// POLLERR - can this happen as we are reading from a fs?
|
|
// POLLHUP - not valid for cardN/state
|
|
// POLLNVAL - fd is valid
|
|
LOG_ALWAYS_FATAL("unxpected error in card poll fd 0x%x",
|
|
pfd[i].revents);
|
|
// FIXME: If not fatal, then need some logic to close
|
|
// these fds on error
|
|
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
|
|
// POLLERR - can this happen as we are reading from a fs?
|
|
// POLLHUP - not valid for switch/state
|
|
// POLLNVAL - fd is valid
|
|
LOG_ALWAYS_FATAL("unxpected error in dev poll fd 0x%x",
|
|
pfd[i].revents);
|
|
// FIXME: If not fatal, then need some logic to close
|
|
// these fds on error
|
|
pfd[i].fd *= -1;
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
if (pfd)
|
|
free(pfd);
|
|
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(INIT_MAP_SIZE, 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 snd_mon_deinit()
|
|
{
|
|
if (!sndmonitor.initcheck)
|
|
return -1;
|
|
|
|
write(sndmonitor.intpipe[1], "Q", 1);
|
|
pthread_join(sndmonitor.monitor_thread, (void **) NULL);
|
|
free_dev_events();
|
|
listeners_deinit();
|
|
free_sndcards();
|
|
close(sndmonitor.intpipe[0]);
|
|
close(sndmonitor.intpipe[1]);
|
|
sndmonitor.initcheck = 0;
|
|
return 0;
|
|
}
|
|
|
|
int 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)
|
|
goto pipe_error;
|
|
|
|
if (enum_sndcards() < 0)
|
|
goto enum_sncards_error;
|
|
|
|
if (listeners_init() < 0)
|
|
goto listeners_error;
|
|
|
|
#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) {
|
|
goto monitor_thread_create_error;
|
|
}
|
|
sndmonitor.initcheck = true;
|
|
return 0;
|
|
|
|
monitor_thread_create_error:
|
|
listeners_deinit();
|
|
listeners_error:
|
|
free_sndcards();
|
|
enum_sncards_error:
|
|
close(sndmonitor.intpipe[0]);
|
|
close(sndmonitor.intpipe[1]);
|
|
pipe_error:
|
|
return -ENODEV;
|
|
}
|
|
|
|
int 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 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);
|
|
}
|