diff options
author | Jonathan Aillet <jonathan.aillet@iot.bzh> | 2019-09-24 16:07:04 +0200 |
---|---|---|
committer | Jonathan Aillet <jonathan.aillet@iot.bzh> | 2019-10-01 10:57:28 +0200 |
commit | 3c407194add77ca55ab12b1ab6ea7cf54f89c092 (patch) | |
tree | a496925abb406ed580bef3b3a196eb6ea6e31e0c | |
parent | 002dccbe68aae864f816f53b6ac92528bd42ab6d (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.c | 347 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 1 |
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 ) |