summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Aillet <jonathan.aillet@iot.bzh>2019-09-24 16:07:04 +0200
committerJonathan Aillet <jonathan.aillet@iot.bzh>2019-10-01 10:57:28 +0200
commit3c407194add77ca55ab12b1ab6ea7cf54f89c092 (patch)
treea496925abb406ed580bef3b3a196eb6ea6e31e0c
parent002dccbe68aae864f816f53b6ac92528bd42ab6d (diff)
Add events when PCM availability changes
Add PCM availability application framework events to be notified when ALSA PCM availability changes. Use subscribe verb and json '{ "event" : "pcm", "name" : "hw:3,2,1", "stream" : 0 }' to subscribe to playback PCM 'hw:3,2,1' availability events. Use subscribe verb and json '{ "event" : "pcm", "name" : "hw:1,2,3", "stream" : 1 }'' to subscribe to capture PCM 'hw:1,2,3' availability events. from removed card events. To handle PCM events, inode notification (using inotify) is used on '/dev/snd/pcm*' file (e.g. '/dev/snd/pcmC0D0p') to monitor PCM availability. BUG-AGL: SPEC-2835 Change-Id: I5bfaef73b896fa7213e1308035eeaff1464f62f6 Signed-off-by: Jonathan Aillet <jonathan.aillet@iot.bzh>
-rw-r--r--alsa-binding/Alsa-RegEvt.c347
-rw-r--r--conf.d/cmake/config.cmake1
2 files changed, 347 insertions, 1 deletions
diff --git a/alsa-binding/Alsa-RegEvt.c b/alsa-binding/Alsa-RegEvt.c
index a9cce2b..1a803cb 100644
--- a/alsa-binding/Alsa-RegEvt.c
+++ b/alsa-binding/Alsa-RegEvt.c
@@ -20,6 +20,8 @@
#include <sys/inotify.h>
+#include <urcu/list.h>
+
#include <afb-timer.h>
#include <wrap-json.h>
@@ -32,6 +34,9 @@
#define CARD_DIR_TO_WATCH "/dev/snd/by-path"
+#define PCM_DEV_FILE_STRING "/dev/snd/pcmC%iD%i%c"
+#define PCM_EVENT_NAME "pcm-%s-%i,%i,%i-monitoring"
+
#ifndef MAX_SND_HAL
#define MAX_SND_HAL 10
#endif
@@ -39,10 +44,28 @@
typedef enum {
UNKNOWN_EVENT = 0,
CARD_CONTROL_CHANGE_EVENTS = 1,
- CARD_MONITORING_EVENTS = 2
+ CARD_MONITORING_EVENTS = 2,
+ PCM_AVAILABILITY_EVENTS = 3
} EventTypeT;
typedef struct {
+ unsigned int card;
+ unsigned int device;
+ unsigned int subdevice;
+ snd_pcm_stream_t stream;
+ int available;
+
+ char *pcmFileToMonitor;
+ int fd;
+ int wd;
+
+ sd_event_source *src;
+ afb_event_t afbevt;
+
+ struct cds_list_head node;
+} pcmEvtHandleT;
+
+typedef struct {
int fd;
int wd;
sd_event_source *src;
@@ -65,6 +88,7 @@ typedef struct {
typedef struct {
sndHandleT sndHandles[MAX_SND_CARD];
sndCardEvtHandleT *cardMonitoring;
+ struct cds_list_head pcmMonitoringHead;
afb_api_t apiHandle;
} sndCardsT;
@@ -105,6 +129,9 @@ STATIC EventTypeT alsaGetDevIdFromString(char *eventTypeString)
if(! strcasecmp(eventTypeString, "cards"))
return CARD_MONITORING_EVENTS;
+ if(! strcasecmp(eventTypeString, "pcm"))
+ return PCM_AVAILABILITY_EVENTS;
+
return UNKNOWN_EVENT;
}
@@ -141,6 +168,31 @@ PUBLIC queryModeE alsaGetModeFromQuery(json_object *queryJ)
return mode;
}
+STATIC void freePcmHandle(pcmEvtHandleT *pcmEventHandle)
+{
+ if(! pcmEventHandle)
+ return;
+
+ if(pcmEventHandle->node.prev && pcmEventHandle->node.next)
+ cds_list_del(&pcmEventHandle->node);
+
+ if(pcmEventHandle->src)
+ sd_event_source_unref(pcmEventHandle->src);
+
+ if(afb_event_is_valid(pcmEventHandle->afbevt))
+ afb_event_unref(pcmEventHandle->afbevt);
+
+ if(pcmEventHandle->fd != -1 && pcmEventHandle->wd != -1)
+ inotify_rm_watch(pcmEventHandle->fd, pcmEventHandle->wd);
+
+ if(pcmEventHandle->fd != -1)
+ close(pcmEventHandle->fd);
+
+ free(pcmEventHandle->pcmFileToMonitor);
+
+ free(pcmEventHandle);
+}
+
STATIC void freeSndCardHandle(sndCardEvtHandleT **cardMonitoringHandle)
{
sndCardEvtHandleT *cardMonitoringHandleToFree;
@@ -268,6 +320,99 @@ STATIC int sndCardTimerForCreatedFileCB(TimerHandleT *context)
return 1;
}
+STATIC int pcmEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData)
+{
+ int err, pcmStatus, subscriberCount = -1;
+ ssize_t length, i = 0;
+ char buffer[INOTIFY_EVENT_BUF_LEN], *pcmName;
+
+ struct inotify_event *event;
+
+ pcmEvtHandleT *pcmEventHandle = (pcmEvtHandleT *) userData;
+
+ json_object *eventJ = NULL;
+
+ if(revents & EPOLLERR) {
+ AFB_ERROR("An error has been send by event loop polling, prevent new errors to be fired by deleting event src");
+ freePcmHandle(pcmEventHandle);
+ return -1;
+ }
+
+ if(revents & EPOLLIN) {
+ length = read(fd, buffer, INOTIFY_EVENT_BUF_LEN);
+ if(length < 0) {
+ AFB_ERROR("Nothing read using inotify");
+ freePcmHandle(pcmEventHandle);
+ return -2;
+ }
+
+ while(i < length) {
+ event = (struct inotify_event *) &buffer[i];
+
+ i += INOTIFY_EVENT_SIZE + event->len;
+
+ if((event->mask & IN_DELETE) || (event->mask & IN_DELETE_SELF)) {
+ AFB_WARNING("Monitored file has been deleted, delete monitoring");
+ freePcmHandle(pcmEventHandle);
+ return -3;
+ }
+
+ if((event->mask & IN_OPEN) || (event->mask & IN_CLOSE)) {
+ pcmStatus = alsaIsPcmAvailableUsingId(pcmEventHandle->card,
+ pcmEventHandle->device,
+ pcmEventHandle->subdevice,
+ pcmEventHandle->stream);
+ if(pcmEventHandle->available != pcmStatus) {
+ pcmEventHandle->available = pcmStatus;
+
+ err = asprintf(&pcmName,
+ "hw:%i,%i,%i",
+ pcmEventHandle->card,
+ pcmEventHandle->device,
+ pcmEventHandle->subdevice);
+ if(err <= 0) {
+ AFB_ERROR("Did not succeed to generate pcm name string (card : %i, device : %i, subdevice : %i)",
+ pcmEventHandle->card,
+ pcmEventHandle->device,
+ pcmEventHandle->subdevice);
+ freePcmHandle(pcmEventHandle);
+ return -4;
+ }
+
+ err = wrap_json_pack(&eventJ,
+ "{s:s, s:i, s:i}",
+ "name", pcmName,
+ "stream", pcmEventHandle->stream,
+ "available", pcmEventHandle->available);
+ if(err) {
+ AFB_ERROR("Did not succeed to generate pcm event json info (name : %s, stream : %s, available : %i)",
+ pcmName,
+ (pcmEventHandle->stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
+ pcmEventHandle->available);
+ free(pcmName);
+ freePcmHandle(pcmEventHandle);
+ return -5;
+ }
+
+ subscriberCount = afb_event_push(pcmEventHandle->afbevt, eventJ);
+ }
+ }
+ }
+ }
+
+ if(! subscriberCount) {
+ AFB_WARNING("Nobody listening for pcm %s hw:%i,%i,%i availability events, stop monitoring from now on",
+ (pcmEventHandle->stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
+ pcmEventHandle->card,
+ pcmEventHandle->device,
+ pcmEventHandle->subdevice);
+ freePcmHandle(pcmEventHandle);
+ return -6;
+ }
+
+ return 0;
+}
+
// This routine is called when sound cards are added/removed (when inotify '/dev/snd/by-path' events are fired)
STATIC int sndCardEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData)
{
@@ -415,6 +560,201 @@ STATIC int sndCtlEventCB(sd_event_source* src, int fd, uint32_t revents, void* u
return 0;
}
+STATIC afb_event_t alsaEvtSubscribeUnsubscribePcmAvailabilityEvent(afb_req_t request,
+ json_object *queryJ,
+ sndCardsT *sndCards,
+ EventSubscribeUnsubscribeT subscriptionType)
+{
+ int err,
+ card = NO_CARD_SELECTED,
+ device = NO_DEVICE_SELECTED,
+ subdevice = NO_SUBDEVICE_SELECTED;
+
+ char *pcmName = NULL, *pcmEventName = NULL;
+
+ snd_pcm_stream_t stream;
+
+ pcmEvtHandleT *requestedPcmEventHandle = NULL, *currentPcmEventHandle;
+
+ if(! request || ! queryJ || ! sndCards) {
+ afb_req_fail(request, "invalid-args", "Arguments of call are invalid");
+ return NULL;
+ }
+
+ err = wrap_json_unpack(queryJ,
+ "{s:s, s?:i, s?:i, s?:i, s?:s, s:i !}",
+ "event", NULL,
+ "card", &card,
+ "device", &device,
+ "subdevice", &subdevice,
+ "name", &pcmName,
+ "stream", &stream);
+ if(err || (! pcmName && (card == NO_CARD_SELECTED || device == NO_DEVICE_SELECTED || subdevice == NO_SUBDEVICE_SELECTED))) {
+ afb_req_fail_f(request, "invalid_request", "Request json is invalid (%s)", json_object_get_string(queryJ));
+ return NULL;
+ }
+
+ if(pcmName && (sscanf(pcmName, "hw:%i,%i,%i", &card, &device, &subdevice) != 3)) {
+ afb_req_fail_f(request, "invalid_request", "Request pcm name is invalid (%s)", pcmName);
+ return NULL;
+ }
+
+ if(card < 0 || card > MAX_SND_CARD ||
+ device < 0 || device > MAX_CARD_DEVICES ||
+ subdevice < 0 || subdevice > MAX_DEVICE_SUBDEVICES) {
+ afb_req_fail_f(request,
+ "invalid_request",
+ "Requested pcm is not valid (card : %i, device : %i, subdevice : %i)",
+ card,
+ device,
+ subdevice);
+ return NULL;
+ }
+
+ cds_list_for_each_entry(currentPcmEventHandle, &sndCards->pcmMonitoringHead, node) {
+ if(currentPcmEventHandle->card == card &&
+ currentPcmEventHandle->device == device &&
+ currentPcmEventHandle->subdevice == subdevice &&
+ currentPcmEventHandle->stream == stream) {
+ requestedPcmEventHandle = currentPcmEventHandle;
+ break;
+ }
+ }
+
+ if(requestedPcmEventHandle) {
+ return requestedPcmEventHandle->afbevt;
+ }
+ else if(subscriptionType == EVENT_UNSUBSCRIBE) {
+ afb_req_fail_f(request,
+ "unknown_event",
+ "Can not unsubscribe unknown event (card : %i, device : %i, subdevice : %i, stream : %i)",
+ card,
+ device,
+ subdevice,
+ stream);
+ return NULL;
+ }
+
+ requestedPcmEventHandle = calloc(1, sizeof(pcmEvtHandleT));
+ if(! requestedPcmEventHandle) {
+ afb_req_fail_f(request,
+ "eventhandle_alloc",
+ "Error when tried to allocate pcm event data structure (card : %i, device : %i, stream : %i, stream : %i)",
+ card,
+ device,
+ subdevice,
+ stream);
+ return NULL;;
+ }
+
+ requestedPcmEventHandle->card = card;
+ requestedPcmEventHandle->device = device;
+ requestedPcmEventHandle->subdevice = subdevice;
+ requestedPcmEventHandle->stream = stream;
+ requestedPcmEventHandle->fd = -1;
+ requestedPcmEventHandle->wd = -1;
+
+ err = asprintf(&requestedPcmEventHandle->pcmFileToMonitor,
+ PCM_DEV_FILE_STRING,
+ card,
+ device,
+ (stream == SND_PCM_STREAM_PLAYBACK) ? 'p' : 'c');
+ if(err <= 0) {
+ afb_req_fail_f(request,
+ "monitored_file",
+ "Did not succeed to generate pcm file to monitor string (card : %i, device : %i, stream : %i)",
+ card,
+ device,
+ stream);
+ freePcmHandle(requestedPcmEventHandle);
+ return NULL;
+ }
+
+ err = asprintf(&pcmEventName,
+ PCM_EVENT_NAME,
+ (stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
+ card,
+ device,
+ subdevice);
+ if(err <= 0) {
+ afb_req_fail_f(request,
+ "pcm_event_name",
+ "Did not succeed to generate pcm event name string (card : %i, device : %i, stream : %i, stream : %i)",
+ card,
+ device,
+ subdevice,
+ stream);
+ freePcmHandle(requestedPcmEventHandle);
+ return NULL;
+ }
+
+ requestedPcmEventHandle->afbevt = afb_daemon_make_event(pcmEventName);
+ if(! afb_event_is_valid(requestedPcmEventHandle->afbevt)) {
+ afb_req_fail_f(request, "pcm-monitoring-event", "Cannot register new %s event", pcmEventName);
+ free(pcmEventName);
+ freePcmHandle(requestedPcmEventHandle);
+ return NULL;
+ }
+
+ free(pcmEventName);
+
+ requestedPcmEventHandle->fd = inotify_init();
+ if(requestedPcmEventHandle->fd < 0) {
+ afb_req_fail_f(request, "inotify-init", "Error %s happened while getting file descriptor for inotify", strerror(errno));
+ freePcmHandle(requestedPcmEventHandle);
+ return NULL;
+ }
+
+ requestedPcmEventHandle->wd = inotify_add_watch(requestedPcmEventHandle->fd,
+ requestedPcmEventHandle->pcmFileToMonitor,
+ IN_ALL_EVENTS);
+ if(requestedPcmEventHandle->wd < 0) {
+ afb_req_fail_f(request,
+ "inotify-watch",
+ "Error %s happened while setting watcher on '%s' file using inotify",
+ strerror(errno),
+ requestedPcmEventHandle->pcmFileToMonitor);
+ freePcmHandle(requestedPcmEventHandle);
+ return NULL;
+ }
+
+ requestedPcmEventHandle->available = alsaIsPcmAvailableUsingId(card, device, subdevice, stream);
+ if(requestedPcmEventHandle->available < 0) {
+ afb_req_fail_f(request,
+ "pcm-availability",
+ "Did not succeed to get pcm current availability (card : %i, device : %i, stream : %i, stream : %i)",
+ card,
+ device,
+ subdevice,
+ stream);
+ freePcmHandle(requestedPcmEventHandle);
+ return NULL;
+ }
+
+ err = sd_event_add_io(afb_daemon_get_event_loop(),
+ &requestedPcmEventHandle->src,
+ requestedPcmEventHandle->fd,
+ EPOLLIN,
+ pcmEventCB,
+ requestedPcmEventHandle);
+ if(err < 0) {
+ afb_req_fail_f(request,
+ "register-mainloop",
+ "Cannot hook pcm %s hw:%i,%i,%i event to mainloop err=%d",
+ (stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture",
+ card,
+ device,
+ subdevice,
+ err);
+ freePcmHandle(requestedPcmEventHandle);
+ return NULL;
+ }
+
+ cds_list_add_tail(&requestedPcmEventHandle->node, &sndCards->pcmMonitoringHead);
+
+ return requestedPcmEventHandle->afbevt;
+}
+
// Subscribe to each time a sound card is added/removed (using inotify '/dev/snd/by-path' events)
STATIC afb_event_t alsaEvtSubscribeUnsubscribeSoundCardEvent(afb_req_t request,
sndCardsT *sndCards,
@@ -580,6 +920,7 @@ PUBLIC void alsaEvtSubscribeUnsubscribe(afb_req_t request, EventSubscribeUnsubsc
if(! sndCards) {
sndCards = calloc(1, sizeof(sndCardsT));
sndCards->apiHandle = afb_req_get_api(request);
+ CDS_INIT_LIST_HEAD(&sndCards->pcmMonitoringHead);
}
queryJ = afb_req_json(request);
@@ -600,6 +941,10 @@ PUBLIC void alsaEvtSubscribeUnsubscribe(afb_req_t request, EventSubscribeUnsubsc
eventToSubscribeUnsubscribe = alsaEvtSubscribeUnsubscribeSoundCardEvent(request, sndCards, subscriptionType);
break;
+ case PCM_AVAILABILITY_EVENTS:
+ eventToSubscribeUnsubscribe = alsaEvtSubscribeUnsubscribePcmAvailabilityEvent(request, queryJ, sndCards, subscriptionType);
+ break;
+
case UNKNOWN_EVENT:
default:
afb_req_fail_f(request, "unknown-event", "Invalid query='%s'", json_object_get_string(queryJ));
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake
index 27b0dfd..55dd171 100644
--- a/conf.d/cmake/config.cmake
+++ b/conf.d/cmake/config.cmake
@@ -75,6 +75,7 @@ set (PKG_REQUIRED_LIST
libmicrohttpd>=0.9.55
libafbwsc
alsa>=1.1.2
+ liburcu
afb-helpers
)