/* * AlsaLibMapping -- provide low level interface with ALSA lib (extracted from alsa-json-gateway code) * Copyright (C) 2015,2016,2017, Fulup Ar Foll fulup@iot.bzh * * 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 _GNU_SOURCE // needed for vasprintf #include #include #include #include #include "Alsa-ApiHat.h" // Defines for inotify events #define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event)) #define INOTIFY_EVENT_BUF_LEN (1024 * (INOTIFY_EVENT_SIZE + 16)) #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 typedef enum { UNKNOWN_EVENT = 0, CARD_CONTROL_CHANGE_EVENTS = 1, 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; sd_event_source *src; afb_event_t afbevt; struct cds_list_head node; } pcmEvtHandleT; typedef struct { sd_event_source *src; afb_event_t afbevt; } sndCardEvtHandleT; // generic sndctrl event handle hook to event callback when pooling typedef struct { struct pollfd pfds; sd_event_source *src; snd_ctl_t *ctlDev; afb_event_t afbevt; } ctrlEvtHandleT; typedef struct { int available; ctrlEvtHandleT *evtHandle; } sndHandleT; typedef struct { sndHandleT sndHandles[MAX_SND_CARD]; sndCardEvtHandleT *cardMonitoring; struct cds_list_head pcmMonitoringHead; afb_api_t apiHandle; } sndCardsT; typedef struct { char *createdFileName; sndCardsT *sndCards; } sndCardCardFileEventHandleT; typedef struct { int cardid; char *devid; char *apiprefix; char *drivername; char *shortname; char *longname; } cardRegistryT; cardRegistryT *cardRegistry[MAX_SND_HAL + 1]; STATIC int getHalIdxFromCardid (int cardid) { for (int idx = 0; idx < MAX_SND_HAL; idx++) { if (!cardRegistry[idx]) return -1; if (cardRegistry[idx]->cardid == cardid) return idx; } return -1; } STATIC EventTypeT alsaGetDevIdFromString(char *eventTypeString) { if(! eventTypeString) return UNKNOWN_EVENT; if(! strcasecmp(eventTypeString, "controls")) return CARD_CONTROL_CHANGE_EVENTS; if(! strcasecmp(eventTypeString, "cards")) return CARD_MONITORING_EVENTS; if(! strcasecmp(eventTypeString, "pcm")) return PCM_AVAILABILITY_EVENTS; return UNKNOWN_EVENT; } STATIC EventTypeT alsaGetEventTypeFromQuery(json_object *queryJ) { char *eventTypeString = NULL; if(wrap_json_unpack(queryJ, "{s:s}", "event", &eventTypeString)) return UNKNOWN_EVENT; if(! eventTypeString) return UNKNOWN_EVENT; return alsaGetDevIdFromString(eventTypeString); } PUBLIC char *alsaGetDevIdFromQuery(json_object *queryJ) { char *devId = NULL; if(wrap_json_unpack(queryJ, "{s:s}", "devid", &devId)) return NULL; return devId; } PUBLIC queryModeE alsaGetModeFromQuery(json_object *queryJ) { queryModeE mode = QUERY_QUIET; if(wrap_json_unpack(queryJ, "{s:i}", "mode", &mode)) return QUERY_QUIET; 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); free(pcmEventHandle->pcmFileToMonitor); free(pcmEventHandle); } STATIC void freeSndCardHandle(sndCardEvtHandleT **cardMonitoringHandle) { sndCardEvtHandleT *cardMonitoringHandleToFree; if(! cardMonitoringHandle || ! *cardMonitoringHandle) return; cardMonitoringHandleToFree = *cardMonitoringHandle; if(cardMonitoringHandleToFree->src) sd_event_source_unref(cardMonitoringHandleToFree->src); if(afb_event_is_valid(cardMonitoringHandleToFree->afbevt)) afb_event_unref(cardMonitoringHandleToFree->afbevt); free(cardMonitoringHandleToFree); *cardMonitoringHandle = NULL; } STATIC void freeCardAlsaControlHandle(ctrlEvtHandleT **cardControlHandle) { ctrlEvtHandleT *cardControlHandleToFree; if(! cardControlHandle || ! *cardControlHandle) return; cardControlHandleToFree = *cardControlHandle; if(cardControlHandleToFree->src) sd_event_source_unref(cardControlHandleToFree->src); if(afb_event_is_valid(cardControlHandleToFree->afbevt)) afb_event_unref(cardControlHandleToFree->afbevt); if(cardControlHandleToFree->ctlDev) snd_ctl_close(cardControlHandleToFree->ctlDev); free(cardControlHandleToFree); *cardControlHandle = NULL; } STATIC int updatePcmAvailabilityAndFireEvent(pcmEvtHandleT *pcmEventHandle) { int err, pcmStatus, subscriberCount = -1; char *pcmName; json_object *eventJ = NULL; pcmStatus = alsaIsPcmAvailableUsingId(pcmEventHandle->card, pcmEventHandle->device, pcmEventHandle->subdevice, pcmEventHandle->stream); if(pcmEventHandle->available == pcmStatus) return 0; 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 -1; } 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 -2; } 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 -3; } return 0; } STATIC void updateSelectedAlsaCardsAvailabilityAndFireEvents(sndCardsT *sndCards, int first, int last) { int idx, available, subscriberCount = -1; char *cardId; json_object *generatedEventJ = NULL; if(first < 0 || first >= MAX_SND_CARD) return; if(last < 0 || last >= MAX_SND_CARD) return; if(last < first) return; for(idx = first; idx < (last + 1); idx++) { available = snd_card_load(idx); if(available && ! sndCards->sndHandles[idx].available) { sndCards->sndHandles[idx].available = 1; if(sndCards->cardMonitoring && ! wrap_json_pack(&generatedEventJ, "{s:o}", "cardAdded", getCardInfo(idx))) subscriberCount = afb_event_push(sndCards->cardMonitoring->afbevt, generatedEventJ); } else if(! available && sndCards->sndHandles[idx].available) { sndCards->sndHandles[idx].available = 0; if(sndCards->cardMonitoring && (asprintf(&cardId, "hw:%i", idx) > 0) && ! wrap_json_pack(&generatedEventJ, "{s:s}", "cardRemoved", cardId)) subscriberCount = afb_event_push(sndCards->cardMonitoring->afbevt, generatedEventJ); if(sndCards->sndHandles[idx].evtHandle) { sd_event_source_unref(sndCards->sndHandles[idx].evtHandle->src); afb_event_unref(sndCards->sndHandles[idx].evtHandle->afbevt); snd_ctl_close(sndCards->sndHandles[idx].evtHandle->ctlDev); free(sndCards->sndHandles[idx].evtHandle); sndCards->sndHandles[idx].evtHandle = NULL; } } } if(! subscriberCount) { AFB_WARNING("Nobody listening for 'card-monitoring' events, stop monitoring from now on"); freeSndCardHandle(&sndCards->cardMonitoring); } } STATIC void updateAllAlsaCardsAvailabilityAndFireEvents(sndCardsT *sndCards) { updateSelectedAlsaCardsAvailabilityAndFireEvents(sndCards, 0, (MAX_SND_CARD - 1)); } // This routine is called every few microseconds to verify when the newly created sound card file will be openable (using timer event firing) STATIC int sndCardTimerForCreatedFileCB(TimerHandleT *context) { int createdFileCardNb; sndCardCardFileEventHandleT *createdFileHandle = (sndCardCardFileEventHandleT *) context->context; createdFileCardNb = getCardNbFromCardPath(createdFileHandle->createdFileName); if(createdFileCardNb < 0) { AFB_ERROR("Error %i happened when tried to get created card name file '%s' card nb", createdFileCardNb, createdFileHandle->createdFileName); return -1; } updateSelectedAlsaCardsAvailabilityAndFireEvents(createdFileHandle->sndCards, createdFileCardNb, createdFileCardNb); free(createdFileHandle->createdFileName); free(createdFileHandle); context->count = 1; return 1; } #ifdef FORCE_STATUS_REFRESH_MS STATIC int forceEventRefreshCB(TimerHandleT *context) { int err; sndCardsT *sndCards = (sndCardsT *) context->context; pcmEvtHandleT *currentPcmToRefresh; if(! sndCards) { AFB_ERROR("No 'sndCardsT' data available, impossible to refresh"); context->count = 1; return -1; } updateAllAlsaCardsAvailabilityAndFireEvents(sndCards); if(cds_list_empty(&sndCards->pcmMonitoringHead)) { AFB_DEBUG("No pcm monitoring launched, nothing to refresh"); } else { cds_list_for_each_entry(currentPcmToRefresh, &sndCards->pcmMonitoringHead, node) { err = updatePcmAvailabilityAndFireEvent(currentPcmToRefresh); if(err) AFB_ERROR("Error %i happened when tried to refresh %s pcm hw:%i,%i,%i availability", err, (currentPcmToRefresh->stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture", currentPcmToRefresh->card, currentPcmToRefresh->device, currentPcmToRefresh->subdevice); } } context->count = 2; return 1; } #endif STATIC int pcmEventCB(sd_event_source* src, const struct inotify_event *event, void* userData) { int err; pcmEvtHandleT *pcmEventHandle = (pcmEvtHandleT *) userData; if((event->mask & IN_DELETE) || (event->mask & IN_DELETE_SELF)) { AFB_WARNING("Monitored file has been deleted, delete monitoring"); freePcmHandle(pcmEventHandle); return -1; } if((event->mask & IN_OPEN) || (event->mask & IN_CLOSE)) { err = updatePcmAvailabilityAndFireEvent(pcmEventHandle); if(err) { AFB_ERROR("Error %i happened when tried to refresh %s pcm hw:%i,%i,%i availability", err, (pcmEventHandle->stream == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture", pcmEventHandle->card, pcmEventHandle->device, pcmEventHandle->subdevice); freePcmHandle(pcmEventHandle); return -2; } } 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, const struct inotify_event *event, void* userData) { char *createdFile; sndCardCardFileEventHandleT *createdFileHandle; TimerHandleT *createdFileTimerHandle; sndCardsT *sndCards = (sndCardsT *) userData; if((event->mask & IN_CREATE) && ! (event->mask & IN_ISDIR) && (asprintf(&createdFile, "%s/%s", CARD_DIR_TO_WATCH, event->name) > 0)) { createdFileHandle = calloc(1, sizeof(sndCardCardFileEventHandleT)); createdFileHandle->createdFileName = strdup(createdFile); createdFileHandle->sndCards = sndCards; createdFileTimerHandle = calloc(1, sizeof(TimerHandleT)); createdFileTimerHandle->uid = "sound card file creation delay"; createdFileTimerHandle->count = 10; createdFileTimerHandle->delay = 2; TimerEvtStart(sndCards->apiHandle, createdFileTimerHandle, sndCardTimerForCreatedFileCB, (void *) createdFileHandle); } if((event->mask & IN_DELETE) && ! (event->mask & IN_ISDIR)) updateAllAlsaCardsAvailabilityAndFireEvents(sndCards); return 0; } // This routine is called when ALSA control events are fired STATIC int sndCtlEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { int err, ctlNumid, subscriberCount = -1; unsigned int mask; snd_ctl_elem_id_t *elemId; snd_ctl_event_t *eventId; sndHandleT *sndHandle = (sndHandleT *) userData; ctlRequestT ctlRequest; json_object *ctlEventJ, *ctlEventAdditionalInfoJ; if((revents & EPOLLERR) != 0) { AFB_ERROR("An error has been send by event loop polling, prevent new errors to be fired by deleting event src"); freeCardAlsaControlHandle(&sndHandle->evtHandle); return -1; } if((revents & EPOLLHUP) != 0) { AFB_NOTICE("SndCtl hanghup [car disconnected]"); return 0; } if((revents & EPOLLIN) != 0) { // initialise event structure on stack snd_ctl_event_alloca(&eventId); snd_ctl_elem_id_alloca(&elemId); err = snd_ctl_read(sndHandle->evtHandle->ctlDev, eventId); if(err < 0) { AFB_ERROR("Did not succeed to read control event"); freeCardAlsaControlHandle(&sndHandle->evtHandle); return -2; } // we only process sndctrl element if(snd_ctl_event_get_type(eventId) != SND_CTL_EVENT_ELEM) return 0; // we only process value changed events mask = snd_ctl_event_elem_get_mask(eventId); if(! (mask & SND_CTL_EVENT_MASK_VALUE)) return 0; snd_ctl_event_elem_get_id(eventId, elemId); err = alsaGetSingleCtl(sndHandle->evtHandle->ctlDev, elemId, &ctlRequest, QUERY_FULL); if(err) { AFB_WARNING("Did not succeed to get control info and values, a custom control may have been removed"); return 0; } // If CTL as a value use it as container for response if(ctlRequest.valuesJ) { ctlEventJ = ctlRequest.valuesJ; } else { ctlEventJ = json_object_new_object(); ctlNumid = snd_ctl_event_elem_get_numid(eventId); json_object_object_add(ctlEventJ, "id", json_object_new_int(ctlNumid)); } err = wrap_json_pack(&ctlEventAdditionalInfoJ, "{s:s, s:i, s:i, s:i}", "name", snd_ctl_event_elem_get_name(eventId), "card", snd_ctl_event_elem_get_interface(eventId), "dev", snd_ctl_event_elem_get_device(eventId), "sub", snd_ctl_event_elem_get_subdevice(eventId)); if(! err) wrap_json_object_add(ctlEventJ, ctlEventAdditionalInfoJ); AFB_DEBUG("sndCtlEventCB=%s", json_object_get_string(ctlEventJ)); subscriberCount = afb_event_push(sndHandle->evtHandle->afbevt, ctlEventJ); } if(! subscriberCount) { AFB_WARNING("Nobody listening for card %i control events, stop monitoring from now on", snd_ctl_event_elem_get_interface(eventId)); freeCardAlsaControlHandle(&sndHandle->evtHandle); return -4; } 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; 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->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_inotify(afb_daemon_get_event_loop(), &requestedPcmEventHandle->src, requestedPcmEventHandle->pcmFileToMonitor, IN_ALL_EVENTS, 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, EventSubscribeUnsubscribeT subscriptionType) { int err; if(sndCards->cardMonitoring) return sndCards->cardMonitoring->afbevt; else if(subscriptionType == EVENT_UNSUBSCRIBE) return NULL; updateAllAlsaCardsAvailabilityAndFireEvents(sndCards); sndCards->cardMonitoring = malloc(sizeof (sndCardEvtHandleT)); if(! sndCards->cardMonitoring) { afb_req_fail(request, "card-monitoring-event", "Error while allocating card added monitoring handle"); return NULL; } sndCards->cardMonitoring->afbevt = afb_daemon_make_event("card-monitoring"); if(! afb_event_is_valid(sndCards->cardMonitoring->afbevt)) { afb_req_fail_f(request, "card-monitoring-event", "Cannot register new binder card-monitoring event"); freeSndCardHandle(&sndCards->cardMonitoring); return NULL; } // register sound event to binder main loop err = sd_event_add_inotify(afb_daemon_get_event_loop(), &sndCards->cardMonitoring->src, CARD_DIR_TO_WATCH, IN_CREATE | IN_DELETE, sndCardEventCB, sndCards); if(err < 0) { afb_req_fail_f(request, "register-mainloop", "Cannot hook sound card events to mainloop err=%d", err); freeSndCardHandle(&sndCards->cardMonitoring); return NULL; } return sndCards->cardMonitoring->afbevt; } // Subscribe to every Alsa CtlEvent send by a given board STATIC afb_event_t alsaEvtSubscribeUnsubscribeAlsaControlEvent(afb_req_t request, sndCardsT *sndCards, char* devId, EventSubscribeUnsubscribeT subscriptionType) { sndHandleT *currentSndHandle = NULL; snd_ctl_t *ctlDev = NULL; int err, cardId; snd_ctl_card_info_t *cardinfo; // open control interface for devid err = snd_ctl_open(&ctlDev, devId, SND_CTL_READONLY); if(err < 0) { afb_req_fail_f(request, "devid-unknown", "SndCard devid=%s Not Found err=%s", devId, snd_strerror(err)); return NULL; } snd_ctl_card_info_alloca(&cardinfo); err = snd_ctl_card_info(ctlDev, cardinfo); if(err < 0) { afb_req_fail_f(request, "devid-invalid", "SndCard devid=%s Not Found err=%s", devId, snd_strerror(err)); snd_ctl_close(ctlDev); return NULL; } cardId = snd_ctl_card_info_get_card(cardinfo); if(cardId < 0 || cardId >= MAX_SND_CARD) { afb_req_fail_f(request, "devid-invalid", "SndCard cardId=%i invalid", cardId); snd_ctl_close(ctlDev); return NULL; } currentSndHandle = &sndCards->sndHandles[cardId]; if(currentSndHandle->evtHandle) { snd_ctl_close(ctlDev); return currentSndHandle->evtHandle->afbevt; } else if(subscriptionType == EVENT_UNSUBSCRIBE) { snd_ctl_close(ctlDev); return NULL; } currentSndHandle->evtHandle = malloc(sizeof (ctrlEvtHandleT)); currentSndHandle->evtHandle->ctlDev = ctlDev; // subscribe for sndctl events attached to devid err = snd_ctl_subscribe_events(currentSndHandle->evtHandle->ctlDev, 1); if(err < 0) { afb_req_fail_f(request, "alsa-ctl-subscribe-fail", "Cannot subscribe events from devid=%s err=%d", devId, err); freeCardAlsaControlHandle(¤tSndHandle->evtHandle); return NULL; } // get pollfd attach to this sound board snd_ctl_poll_descriptors(currentSndHandle->evtHandle->ctlDev, ¤tSndHandle->evtHandle->pfds, 1); // register sound event to binder main loop err = sd_event_add_io(afb_daemon_get_event_loop(), ¤tSndHandle->evtHandle->src, currentSndHandle->evtHandle->pfds.fd, EPOLLIN, sndCtlEventCB, currentSndHandle); if(err < 0) { afb_req_fail_f(request, "register-mainloop", "Cannot hook events to mainloop devid=%s err=%d", devId, err); freeCardAlsaControlHandle(¤tSndHandle->evtHandle); return NULL; } // create binder event attached to devid name currentSndHandle->evtHandle->afbevt = afb_daemon_make_event(devId); if(! afb_event_is_valid(currentSndHandle->evtHandle->afbevt)) { afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", devId); freeCardAlsaControlHandle(¤tSndHandle->evtHandle); return NULL; } return currentSndHandle->evtHandle->afbevt; } // Subscribe to alsacore event PUBLIC void alsaEvtSubscribeUnsubscribe(afb_req_t request, EventSubscribeUnsubscribeT subscriptionType) { int err = -1; EventTypeT eventType; char *devId = NULL; static sndCardsT *sndCards = NULL; afb_event_t eventToSubscribeUnsubscribe; json_object *queryJ; if(! sndCards) { sndCards = calloc(1, sizeof(sndCardsT)); sndCards->apiHandle = afb_req_get_api(request); CDS_INIT_LIST_HEAD(&sndCards->pcmMonitoringHead); #ifdef FORCE_STATUS_REFRESH_MS TimerHandleT *refreshStatusTimerHandle = calloc(1, sizeof(TimerHandleT)); refreshStatusTimerHandle->uid = "Refresh status for event generation timer"; refreshStatusTimerHandle->count = 2; refreshStatusTimerHandle->delay = FORCE_STATUS_REFRESH_MS; TimerEvtStart(sndCards->apiHandle, refreshStatusTimerHandle, forceEventRefreshCB, (void *) sndCards); #endif } queryJ = afb_req_json(request); eventType = alsaGetEventTypeFromQuery(queryJ); switch(eventType) { case CARD_CONTROL_CHANGE_EVENTS: devId = alsaGetDevIdFromQuery(queryJ); if(! devId) { afb_req_fail_f(request, "devid-missing", "Invalid query='%s'", json_object_get_string(queryJ)); return; } eventToSubscribeUnsubscribe = alsaEvtSubscribeUnsubscribeAlsaControlEvent(request, sndCards, devId, subscriptionType); break; case CARD_MONITORING_EVENTS: 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)); return; } if(! eventToSubscribeUnsubscribe) return; // subscribe/unsubscribe to binder event if(subscriptionType == EVENT_SUBSCRIBE) err = afb_req_subscribe(request, eventToSubscribeUnsubscribe); else if(subscriptionType == EVENT_UNSUBSCRIBE) err = afb_req_unsubscribe(request, eventToSubscribeUnsubscribe); if(err) { afb_req_fail_f(request, "register-eventname", "Cannot %s binder event", (subscriptionType == EVENT_SUBSCRIBE) ? "subscribe" : "unsubscribe"); return; } afb_req_success(request, NULL, NULL); } PUBLIC void alsaEvtSubscribe(afb_req_t request) { alsaEvtSubscribeUnsubscribe(request, EVENT_SUBSCRIBE); } PUBLIC void alsaEvtUnsubscribe(afb_req_t request) { alsaEvtSubscribeUnsubscribe(request, EVENT_UNSUBSCRIBE); } // Subscribe to every Alsa CtlEvent send by a given board STATIC json_object *alsaProbeCardId(afb_req_t request) { char devid [32]; const char *ctlName, *shortname, *longname, *mixername, *drivername; int done, mode, card, err, index, idx; json_object *responseJ, *tmpJ; snd_ctl_t *ctlDev; snd_ctl_card_info_t *cardinfo; json_object* queryJ = afb_req_json(request); done = json_object_object_get_ex(queryJ, "sndname", &tmpJ); if (!done || json_object_get_type(tmpJ) != json_type_string) { afb_req_fail_f(request, "argument-missing", "sndname=SndCardName missing"); goto OnErrorExit; } const char *sndname = json_object_get_string(tmpJ); done = json_object_object_get_ex(queryJ, "mode", &tmpJ); if (!done) { mode = 0; } else { mode = json_object_get_int(tmpJ); } // loop on potential card number snd_ctl_card_info_alloca(&cardinfo); char *driverId = NULL; // when not name match use drivername as backup plan for (card = 0; card < MAX_SND_CARD; card++) { // build card devid and probe it snprintf(devid, sizeof (devid), "hw:%i", card); // open control interface for devid err = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY); if (err < 0) continue; // extract sound card information snd_ctl_card_info(ctlDev, cardinfo); index = snd_ctl_card_info_get_card(cardinfo); ctlName = snd_ctl_card_info_get_id(cardinfo); shortname = snd_ctl_card_info_get_name(cardinfo); longname = snd_ctl_card_info_get_longname(cardinfo); mixername = snd_ctl_card_info_get_mixername(cardinfo); drivername = snd_ctl_card_info_get_driver(cardinfo); snd_ctl_close(ctlDev); // check if short|long name match if (!strcasecmp(sndname, ctlName)) break; if (!strcasecmp(sndname, shortname)) break; // if name does not match search for a free HAL with driver name matching if (driverId==NULL && getHalIdxFromCardid(card)<0 && !strcasecmp(sndname, drivername)) driverId=strdup(devid); } if (card == MAX_SND_CARD) { if (!driverId) { afb_req_fail_f(request, "ctlDev-notfound", "Fail to find card with name=%s", sndname); goto OnErrorExit; } // refresh devid and clean up driverId strncpy (devid, driverId, sizeof(devid)); free(driverId); err = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY); if (err < 0) { afb_req_fail_f(request, "ctlDev-notfound", "Fail to find card with name=%s devid=%s", sndname, devid); goto OnErrorExit; } // Sound not found by name, backup to driver name snd_ctl_card_info(ctlDev, cardinfo); index = snd_ctl_card_info_get_card(cardinfo); ctlName = snd_ctl_card_info_get_id(cardinfo); shortname = snd_ctl_card_info_get_name(cardinfo); longname = snd_ctl_card_info_get_longname(cardinfo); mixername = snd_ctl_card_info_get_mixername(cardinfo); drivername = snd_ctl_card_info_get_driver(cardinfo); AFB_WARNING("alsaProbeCardId Fallback to HAL=%s ==> devid=%s name=%s long=%s\n ", drivername, devid, shortname, longname); snd_ctl_close(ctlDev); } // proxy ctlevent as a binder event responseJ = json_object_new_object(); json_object_object_add(responseJ, "index", json_object_new_int(index)); json_object_object_add(responseJ, "cardid", json_object_new_int(card)); json_object_object_add(responseJ, "devid", json_object_new_string(devid)); json_object_object_add(responseJ, "shortname", json_object_new_string(shortname)); if (mode > 0) { json_object_object_add(responseJ, "longname", json_object_new_string(longname)); json_object_object_add(responseJ, "mixername", json_object_new_string(mixername)); json_object_object_add(responseJ, "drivername", json_object_new_string(drivername)); } // search for a HAL binder card mapping name to api prefix for (idx = 0; (idx < MAX_SND_HAL && cardRegistry[idx]); idx++) { if (!strcmp(cardRegistry[idx]->shortname, shortname)) { json_object_object_add(responseJ, "halapi", json_object_new_string(cardRegistry[idx]->apiprefix)); break; } } return responseJ; OnErrorExit: return NULL; } // Make alsaProbeCardId compatible with AFB request PUBLIC void alsaGetCardId(afb_req_t request) { json_object *responseJ = alsaProbeCardId(request); if (responseJ) afb_req_success(request, responseJ, NULL); } // Return HAL information about a given sound card ID STATIC int getHalApiFromCardid(int cardid, json_object *responseJ) { int idx = getHalIdxFromCardid (cardid); if (idx < 0) goto OnErrorExit; json_object_object_add(responseJ, "api", json_object_new_string(cardRegistry[idx]->apiprefix)); if (cardRegistry[idx]->shortname)json_object_object_add(responseJ, "shortname", json_object_new_string(cardRegistry[idx]->shortname)); if (cardRegistry[idx]->longname) json_object_object_add(responseJ, "longname", json_object_new_string(cardRegistry[idx]->longname)); return 0; OnErrorExit: return -1; } // Return list of active resgistrated HAL with corresponding sndcard PUBLIC void alsaActiveHal(afb_req_t request) { json_object *responseJ = json_object_new_array(); for (int idx = 0; idx < MAX_SND_HAL; idx++) { if (!cardRegistry[idx]) break; json_object *haldevJ = json_object_new_object(); json_object_object_add(haldevJ, "api", json_object_new_string(cardRegistry[idx]->apiprefix)); if (cardRegistry[idx]->devid) json_object_object_add(haldevJ, "devid", json_object_new_string(cardRegistry[idx]->devid)); if (cardRegistry[idx]->shortname)json_object_object_add(haldevJ, "shortname", json_object_new_string(cardRegistry[idx]->shortname)); if (cardRegistry[idx]->drivername)json_object_object_add(haldevJ, "drivername", json_object_new_string(cardRegistry[idx]->drivername)); if (cardRegistry[idx]->longname) json_object_object_add(haldevJ, "longname", json_object_new_string(cardRegistry[idx]->longname)); json_object_array_add(responseJ, haldevJ); } afb_req_success(request, responseJ, NULL); } // Register loaded HAL with board Name and API prefix PUBLIC void alsaRegisterHal(afb_req_t request) { static int index = 0; json_object *responseJ; const char *shortname, *apiPrefix; apiPrefix = afb_req_value(request, "prefix"); if (apiPrefix == NULL) { afb_req_fail_f(request, "argument-missing", "prefix=BindingApiPrefix missing"); goto OnErrorExit; } shortname = afb_req_value(request, "sndname"); if (shortname == NULL) { afb_req_fail_f(request, "argument-missing", "sndname=SndCardName missing"); goto OnErrorExit; } if (index == MAX_SND_HAL) { afb_req_fail_f(request, "alsahal-toomany", "Fail to register sndname=[%s]", shortname); goto OnErrorExit; } // alsaGetCardId should be check to register only valid card responseJ = alsaProbeCardId(request); if (responseJ) { json_object *tmpJ; int done; cardRegistry[index] = malloc(sizeof (cardRegistry)); cardRegistry[index]->apiprefix = strdup(apiPrefix); cardRegistry[index]->shortname = strdup(shortname); json_object_object_get_ex(responseJ, "cardid", &tmpJ); cardRegistry[index]->cardid = json_object_get_int(tmpJ); done = json_object_object_get_ex(responseJ, "devid", &tmpJ); if (done) cardRegistry[index]->devid = strdup(json_object_get_string(tmpJ)); else cardRegistry[index]->devid = NULL; done = json_object_object_get_ex(responseJ, "drivername", &tmpJ); if (done) cardRegistry[index]->drivername = strdup(json_object_get_string(tmpJ)); else cardRegistry[index]->drivername = NULL; done = json_object_object_get_ex(responseJ, "longname", &tmpJ); if (done) cardRegistry[index]->longname = strdup(json_object_get_string(tmpJ)); else cardRegistry[index]->longname = NULL; // make sure register close with a null value index++; cardRegistry[index] = NULL; afb_req_success(request, responseJ, NULL); } // If OK return sound card Alsa ID+Info return; OnErrorExit: return; } PUBLIC void alsaPcmInfo (afb_req_t request) { int done, mode, err; json_object *tmpJ, *responseJ = NULL; snd_pcm_t *pcmHandle = NULL; snd_pcm_info_t * pcmInfo = NULL; json_object* queryJ = afb_req_json(request); done = json_object_object_get_ex(queryJ, "name", &tmpJ); if (!done || json_object_get_type(tmpJ) != json_type_string) { afb_req_fail_f(request, "name:invalid", "PCM 'name:xxx' missing or not a string query='%s'", json_object_get_string(queryJ)); goto OnErrorExit; } const char *pcmName = json_object_get_string(tmpJ); done = json_object_object_get_ex(queryJ, "stream", &tmpJ); if (done && json_object_get_type(tmpJ) != json_type_int) { afb_req_fail_f(request, "stream:invalid", "PCM 'stream:SND_PCM_STREAM_PLAYBACK/SND_PCM_STREAM_CAPTURE' should be integer query='%s'", json_object_get_string(queryJ)); goto OnErrorExit; } snd_pcm_stream_t pcmStream = json_object_get_int(tmpJ); done = json_object_object_get_ex(queryJ, "mode", &tmpJ); if (!done) { mode = 0; } else { mode = json_object_get_int(tmpJ); } // open PCM from its name err= snd_pcm_open(&pcmHandle, pcmName, pcmStream, 0); if (err < 0) { afb_req_fail_f(request, "pcm:invalid", "PCM 'name:%s' does cannot open error=%s", pcmName, snd_strerror(err)); goto OnErrorExit; } // get pcm info snd_pcm_info_alloca(&pcmInfo); err = snd_pcm_info(pcmHandle,pcmInfo); if (err < 0) { afb_req_fail_f(request, "pcm:error", "PCM 'name:%s' fail to retrieve info error=%s", pcmName, snd_strerror(err)); goto OnErrorExit; } // get sub-device number int cardId = snd_pcm_info_get_card(pcmInfo); if (cardId < 0 ) { afb_req_fail_f(request, "pcm:error", "PCM 'name:%s' fail to retrieve sndcard device error=%s", pcmName, snd_strerror(cardId)); goto OnErrorExit; } // prepare an object for response responseJ = json_object_new_object(); err = getHalApiFromCardid (cardId, responseJ); if (err < 0 ) { afb_req_fail_f(request, "pcm:error", "PCM 'name:%s' snddev=hw:%d fail to retrieve hal API", pcmName, cardId); goto OnErrorExit; } json_object_object_add(responseJ, "type", json_object_new_int(snd_pcm_type(pcmHandle))); // in mode mode we return full info about PCM if (mode > 0) { json_object_object_add(responseJ, "stream", json_object_new_int(snd_pcm_info_get_stream (pcmInfo))); json_object_object_add(responseJ, "cardid", json_object_new_int(snd_pcm_info_get_card(pcmInfo))); json_object_object_add(responseJ, "devid" , json_object_new_int(snd_pcm_info_get_device (pcmInfo))); json_object_object_add(responseJ, "subid" , json_object_new_int(snd_pcm_info_get_subdevice (pcmInfo))); } // in super mode we also return information about snd card if (mode > 1) { json_object_object_add(responseJ, "id" , json_object_new_string(snd_pcm_info_get_id (pcmInfo))); json_object_object_add(responseJ, "name" , json_object_new_string(snd_pcm_info_get_name (pcmInfo))); json_object_object_add(responseJ, "subdev" , json_object_new_string(snd_pcm_info_get_subdevice_name (pcmInfo))); } afb_req_success(request, responseJ, NULL); snd_pcm_close (pcmHandle); return; OnErrorExit: if (responseJ) json_object_put (responseJ); if (pcmHandle) snd_pcm_close (pcmHandle); return; }