/* * 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 "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 DIR_TO_WATCH "/dev/snd/by-path" #ifndef MAX_SND_HAL #define MAX_SND_HAL 10 #endif typedef enum { SUBSCRIPTION_CARD_ADDED_EVENTS = 0, SUBSCRIPTION_CARD_REMOVED_EVENTS = 1 } SubscriptionCardEventTypeT; typedef struct { int fd; int wd; 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; int mode; afb_event_t afbevt; } ctrlEvtHandleT; typedef struct { int available; ctrlEvtHandleT *evtHandle; } sndHandleT; typedef struct { sndHandleT sndHandles[MAX_SND_CARD]; sndCardEvtHandleT *cardAdded; sndCardEvtHandleT *cardRemoved; 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; } 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 updateSelectedAlsaCardsAvailabilityAndFireEvents(sndCardsT *sndCards, int first, int last) { int idx, available; char *cardId; 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->cardAdded) afb_event_push(sndCards->cardAdded->afbevt, getCardInfo(idx)); } else if (! available && sndCards->sndHandles[idx].available) { sndCards->sndHandles[idx].available = 0; if (sndCards->cardRemoved && (asprintf(&cardId, "hw:%i", idx) > 0)) afb_event_push(sndCards->cardRemoved->afbevt, json_object_new_string(cardId)); 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; } } } } 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; } // 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) { ssize_t length, i = 0; char buffer[INOTIFY_EVENT_BUF_LEN]; char *createdFile; struct inotify_event *event; sndCardCardFileEventHandleT *createdFileHandle; TimerHandleT *createdFileTimerHandle; sndCardsT *sndCards = (sndCardsT *) userData; if (revents & EPOLLERR) { AFB_ERROR("An error has been send by event loop polling, prevent new errors to be fired by deleting event src"); sd_event_source_unref(src); afb_event_unref(sndCards->cardAdded->afbevt); afb_event_unref(sndCards->cardRemoved->afbevt); inotify_rm_watch(sndCards->cardAdded->fd, sndCards->cardAdded->wd); close(sndCards->cardAdded->fd); free(sndCards->cardAdded); free(sndCards->cardRemoved); sndCards->cardAdded = NULL; sndCards->cardRemoved = NULL; goto OnErrorExit; } if (revents & EPOLLIN) { length = read(fd, buffer, INOTIFY_EVENT_BUF_LEN); if (length < 0) { AFB_ERROR("Nothing read using inotify"); goto OnErrorExit; } while (i < length) { event = (struct inotify_event *) &buffer[i]; if(event->len && (event->mask & IN_CREATE) && ! (event->mask & IN_ISDIR) && (asprintf(&createdFile, "%s/%s", 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->len && (event->mask & IN_DELETE) && ! (event->mask & IN_ISDIR)) updateAllAlsaCardsAvailabilityAndFireEvents(sndCards); i += INOTIFY_EVENT_SIZE + event->len; } } return 0; OnErrorExit: return -1; } // 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; sndHandleT *sndHandle = (sndHandleT *) userData; ctrlEvtHandleT *evtHandle = (ctrlEvtHandleT*) sndHandle->evtHandle; snd_ctl_event_t *eventId; json_object *ctlEventJ; unsigned int mask; int iface; int device; int subdev; const char*ctlName; ctlRequestT ctlRequest; snd_ctl_elem_id_t *elemId; 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"); sd_event_source_unref(src); afb_event_unref(sndHandle->evtHandle->afbevt); snd_ctl_close(sndHandle->evtHandle->ctlDev); free(sndHandle->evtHandle); sndHandle->evtHandle = NULL; goto OnErrorExit; } if ((revents & EPOLLHUP) != 0) { AFB_NOTICE("SndCtl hanghup [car disconnected]"); goto ExitOnSucess; } if ((revents & EPOLLIN) != 0) { // initialise event structure on stack snd_ctl_event_alloca(&eventId); snd_ctl_elem_id_alloca(&elemId); err = snd_ctl_read(evtHandle->ctlDev, eventId); if (err < 0) goto OnErrorExit; // we only process sndctrl element if (snd_ctl_event_get_type(eventId) != SND_CTL_EVENT_ELEM) goto ExitOnSucess; // we only process value changed events mask = snd_ctl_event_elem_get_mask(eventId); if (!(mask & SND_CTL_EVENT_MASK_VALUE)) goto ExitOnSucess; snd_ctl_event_elem_get_id(eventId, elemId); err = alsaGetSingleCtl(evtHandle->ctlDev, elemId, &ctlRequest, evtHandle->mode); if (err) goto OnErrorExit; // 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)); } if (evtHandle->mode >= QUERY_COMPACT) { ctlName = snd_ctl_event_elem_get_name(eventId); json_object_object_add(ctlEventJ, "name", json_object_new_string(ctlName)); } if (evtHandle->mode >= QUERY_VERBOSE) { iface = snd_ctl_event_elem_get_interface(eventId); device = snd_ctl_event_elem_get_device(eventId); subdev = snd_ctl_event_elem_get_subdevice(eventId); json_object_object_add(ctlEventJ, "ifc", json_object_new_int(iface)); json_object_object_add(ctlEventJ, "dev", json_object_new_int(device)); json_object_object_add(ctlEventJ, "sub", json_object_new_int(subdev)); } AFB_DEBUG("sndCtlEventCB=%s", json_object_get_string(ctlEventJ)); afb_event_push(evtHandle->afbevt, ctlEventJ); } ExitOnSucess: return 0; OnErrorExit: AFB_WARNING("sndCtlEventCB: Error detected"); return -1; } // Subscribe to each time a sound card is added/removed (using inotify '/dev/snd/by-path' events) STATIC afb_event_t alsaEvtSubscribeSoundCardEvent(afb_req_t request, sndCardsT *sndCards, SubscriptionCardEventTypeT subscriptionType) { int fd = -1, wd = -1, err; // Update card list updateAllAlsaCardsAvailabilityAndFireEvents(sndCards); if (! sndCards->cardAdded && ! sndCards->cardRemoved) { fd = inotify_init(); if (fd < 0) { afb_req_fail_f(request, "inotify-init", "Error %s happened while getting file descriptor for inotify", strerror(errno)); goto OnErrorExit; } wd = inotify_add_watch(fd, DIR_TO_WATCH, IN_CREATE | IN_DELETE); if (wd < 0) { afb_req_fail_f(request, "inotify-watch", "Error %s happened while setting watchdog on '%s' directory using inotify", strerror(errno), DIR_TO_WATCH); goto OnErrorExit; } sndCards->cardAdded = malloc(sizeof (sndCardEvtHandleT)); if (! sndCards->cardAdded) { afb_req_fail(request, "card-added-event", "Error while allocating card added event handle"); goto OnErrorExit; } sndCards->cardRemoved = malloc(sizeof (sndCardEvtHandleT)); if (! sndCards->cardRemoved) { afb_req_fail(request, "card-removed-event", "Error while allocating card removed event handle"); goto OnErrorExit; } sndCards->cardAdded->fd = fd; sndCards->cardRemoved->fd = fd; sndCards->cardAdded->wd = wd; sndCards->cardRemoved->wd = wd; sndCards->cardAdded->afbevt = afb_daemon_make_event("soundcard-added"); if (!afb_event_is_valid(sndCards->cardAdded->afbevt)) { afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", "soundcard-added"); goto OnErrorExit; } sndCards->cardRemoved->afbevt = afb_daemon_make_event("soundcard-removed"); if (!afb_event_is_valid(sndCards->cardRemoved->afbevt)) { afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", "soundcard-added"); goto OnErrorExit; } // register sound event to binder main loop err = sd_event_add_io(afb_daemon_get_event_loop(), &sndCards->cardAdded->src, fd, EPOLLIN, sndCardEventCB, sndCards); if (err < 0) { afb_req_fail_f(request, "register-mainloop", "Cannot hook sound card events to mainloop err=%d", err); goto OnErrorExit; } sndCards->cardRemoved->src = sndCards->cardAdded->src; } if(subscriptionType == SUBSCRIPTION_CARD_ADDED_EVENTS) return sndCards->cardAdded->afbevt; if(subscriptionType == SUBSCRIPTION_CARD_REMOVED_EVENTS) return sndCards->cardRemoved->afbevt; return NULL; OnErrorExit: if(afb_event_is_valid(sndCards->cardRemoved->afbevt)) afb_event_unref(sndCards->cardRemoved->afbevt); if(afb_event_is_valid(sndCards->cardAdded->afbevt)) afb_event_unref(sndCards->cardAdded->afbevt); free(sndCards->cardRemoved); free(sndCards->cardAdded); if (wd != -1) inotify_rm_watch(fd, wd); if (fd != -1) close(fd); return NULL; } // Subscribe to every Alsa CtlEvent send by a given board STATIC afb_event_t alsaEvtSubscribeAlsaControlEvent(afb_req_t request, sndCardsT *sndCards, char* devId, queryModeE mode) { 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)); goto OnErrorExit; } 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)); goto OnErrorExit; } 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); goto OnErrorExit; } currentSndHandle = &sndCards->sndHandles[cardId]; // if not subscription exist for the event let's create one if (! currentSndHandle->evtHandle) { currentSndHandle->evtHandle = malloc(sizeof (ctrlEvtHandleT)); currentSndHandle->evtHandle->ctlDev = ctlDev; currentSndHandle->evtHandle->mode = mode; // subscribe for sndctl events attached to devid err = snd_ctl_subscribe_events(currentSndHandle->evtHandle->ctlDev, 1); if (err < 0) { afb_req_fail_f(request, "subscribe-fail", "Cannot subscribe events from devid=%s err=%d", devId, err); goto OnErrorExit; } // 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); goto OnErrorExit; } // 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); goto OnErrorExit; } } else if (ctlDev) { snd_ctl_close(ctlDev); } return currentSndHandle->evtHandle->afbevt; OnErrorExit: if (ctlDev) snd_ctl_close(ctlDev); return NULL; } // Subscribe to alsacore event PUBLIC void alsaEvtSubcribe(afb_req_t request) { int err; queryModeE mode; char *devId = NULL; static sndCardsT *sndCards = NULL; afb_event_t eventToSubscribe; json_object *queryJ; if(! sndCards) { sndCards = calloc(1, sizeof(sndCardsT)); sndCards->apiHandle = afb_req_get_api(request); } queryJ = afb_req_json(request); devId = alsaGetDevIdFromQuery(queryJ); if (!devId) { afb_req_fail_f(request, "devid-missing", "Invalid query='%s'", json_object_get_string(queryJ)); return; } mode = alsaGetModeFromQuery(queryJ); if(! strcasecmp(devId, "added")) eventToSubscribe = alsaEvtSubscribeSoundCardEvent(request, sndCards, SUBSCRIPTION_CARD_ADDED_EVENTS); else if(! strcasecmp(devId, "removed")) eventToSubscribe = alsaEvtSubscribeSoundCardEvent(request, sndCards, SUBSCRIPTION_CARD_REMOVED_EVENTS); else eventToSubscribe = alsaEvtSubscribeAlsaControlEvent(request, sndCards, devId, mode); if(! eventToSubscribe) return; // subscribe to binder event err = afb_req_subscribe(request, eventToSubscribe); if (err) { afb_req_fail_f(request, "register-eventname", "Cannot subscribe binder event"); return; } afb_req_success(request, NULL, NULL); } // 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; }