/* * 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. References: https://github.com/fulup-bzh/AlsaJsonGateway (original code) http://alsa-lib.sourcearchive.com/documentation/1.0.20/modules.html http://alsa-lib.sourcearchive.com/documentation/1.0.8/group__Control_gd48d44da8e3bfe150e928267008b8ff5.html http://alsa.opensrc.org/HowTo_access_a_mixer_control https://github.com/gch1p/alsa-volume-monitor/blob/master/main.c https://github.com/DongheonKim/android_hardware_alsa-sound/blob/master/ALSAControl.cpp (ALSA low level API) https://www.kernel.org/doc/html/v4.11/sound/index.html */ #define _GNU_SOURCE // needed for vasprintf #include #include #include "Alsa-ApiHat.h" // extracted IOCTLs from #define _IOR_HACKED(type,nr,size) _IOC(_IOC_READ,(type),(nr),size) #define SNDRV_CTL_IOCTL_CARD_INFO(size) _IOR_HACKED('U', 0x01, size) PUBLIC void NumidsListParse(ActionSetGetT action, controlQueryValuesT *queryControlValues, ctlRequestT *ctlRequest) { int length; for (int idx = 0; idx < queryControlValues->count; idx++) { json_object *jId, *valuesJ; ctlRequest[idx].used = 0; ctlRequest[idx].tag = NULL; ctlRequest[idx].valuesJ = NULL; // when only one NUMID is provided it might not be encapsulated in a JSON array if (json_type_array == json_object_get_type(queryControlValues->numidsJ)) ctlRequest[idx].jToken = json_object_array_get_idx(queryControlValues->numidsJ, idx); else ctlRequest[idx].jToken = queryControlValues->numidsJ; enum json_type jtype = json_object_get_type(ctlRequest[idx].jToken); switch (jtype) { case json_type_int: // if NUMID is not an array then it should be an integer numid with no value ctlRequest[idx].numId = json_object_get_int(ctlRequest[idx].jToken); // Special SET simple short numid form [numid, [VAL1...VALX]] if (action == ACTION_SET && queryControlValues->count == 2) { ctlRequest[idx].valuesJ = json_object_array_get_idx(queryControlValues->numidsJ, 1); queryControlValues->count = 1; //In this form count==2 , when only one numid is to set idx++; continue; } else break; case json_type_string: ctlRequest[idx].tag = json_object_get_string(ctlRequest[idx].jToken); ctlRequest[idx].numId = 0; break; case json_type_array: // NUMID is an array 1st slot should be numid, optionally values may come after length = (int) json_object_array_length(ctlRequest[idx].jToken); // numid must be in 1st slot of numid json array ctlRequest[idx].numId = json_object_get_int(json_object_array_get_idx(ctlRequest[idx].jToken, 0)); if (action == ACTION_GET) continue; // In Write mode second value should be the value if (action == ACTION_SET && length == 2) { ctlRequest[idx].valuesJ = json_object_array_get_idx(ctlRequest[idx].jToken, 1); continue; } // no numid value ctlRequest[idx].used = -1; break; case json_type_object: // numid+values formated as {id:xxx, val:[aa,bb...,nn]} if (!json_object_object_get_ex(ctlRequest[idx].jToken, "id", &jId)) { AFB_NOTICE("Invalid Json=%s missing 'id'", json_object_get_string(ctlRequest[idx].jToken)); ctlRequest[idx].used = -1; } else { ctlRequest[idx].numId = json_object_get_int(jId); if (action == ACTION_SET) { if (!json_object_object_get_ex(ctlRequest[idx].jToken, "val", &valuesJ)) { AFB_NOTICE("Invalid Json=%s missing 'val'", json_object_get_string(ctlRequest[idx].jToken)); ctlRequest[idx].used = -1; } else ctlRequest[idx].valuesJ = valuesJ; } } break; default: ctlRequest[idx].used = -1; } } } STATIC json_object *DB2StringJsonOject(long dB) { char label [32]; if (dB < 0) { snprintf(label, sizeof (label), "-%li.%02lidB", -dB / 100, -dB % 100); } else { snprintf(label, sizeof (label), "%li.%02lidB", dB / 100, dB % 100); } // json function takes care of string copy return (json_object_new_string(label)); } // Direct port from amixer TLV decode routine. This code is too complex for me. // I hopefully did not break it when porting it. STATIC json_object *decodeTlv(unsigned int *tlv, unsigned int tlv_size, int mode) { char label[20]; unsigned int type = tlv[0]; unsigned int size; unsigned int idx = 0; const char *chmap_type = NULL; json_object * decodeTlvJson = json_object_new_object(); if (tlv_size < (unsigned int) (2 * sizeof (unsigned int))) { printf("TLV size error!\n"); return NULL; } type = tlv[idx++]; size = tlv[idx++]; tlv_size -= (unsigned int) (2 * sizeof (unsigned int)); if (size > tlv_size) { fprintf(stderr, "TLV size error (%i, %i, %i)!\n", type, size, tlv_size); return NULL; } switch (type) { case SND_CTL_TLVT_CONTAINER: { json_object * containerJson = json_object_new_array(); size += (unsigned int) (sizeof (unsigned int) - 1); size /= (unsigned int) (sizeof (unsigned int)); while (idx < size) { json_object *embedJson; if (tlv[idx + 1] > (size - idx) * sizeof (unsigned int)) { fprintf(stderr, "TLV size error in compound!\n"); return NULL; } embedJson = decodeTlv(tlv + idx, tlv[idx + 1] + 8, mode); json_object_array_add(containerJson, embedJson); idx += (unsigned int) (2 + (tlv[idx + 1] + sizeof (unsigned int) - 1) / sizeof (unsigned int)); } json_object_object_add(decodeTlvJson, "container", containerJson); break; } case SND_CTL_TLVT_DB_SCALE: { json_object * dbscaleJson = json_object_new_object(); if (size != 2 * sizeof (unsigned int)) { json_object * arrayJson = json_object_new_array(); while (size > 0) { if (mode >= QUERY_VERBOSE) { snprintf(label, sizeof (label), "0x%08x,", tlv[idx++]); json_object_array_add(arrayJson, json_object_new_string(label)); } else { json_object_array_add(arrayJson, json_object_new_int(tlv[idx++])); } size -= (unsigned int) sizeof (unsigned int); } json_object_object_add(dbscaleJson, "array", arrayJson); } else { if (mode >= QUERY_VERBOSE) { json_object_object_add(dbscaleJson, "min", DB2StringJsonOject((int) tlv[2])); json_object_object_add(dbscaleJson, "step", DB2StringJsonOject(tlv[3] & 0xffff)); json_object_object_add(dbscaleJson, "mute", DB2StringJsonOject((tlv[3] >> 16) & 1)); } else { json_object_object_add(dbscaleJson, "min", json_object_new_int((int) tlv[2])); json_object_object_add(dbscaleJson, "step", json_object_new_int(tlv[3] & 0xffff)); json_object_object_add(dbscaleJson, "mute", json_object_new_int((tlv[3] >> 16) & 1)); } } json_object_object_add(decodeTlvJson, "dbscale", dbscaleJson); break; } #ifdef SND_CTL_TLVT_DB_LINEAR case SND_CTL_TLVT_DB_LINEAR: { json_object * dbLinearJson = json_object_new_object(); if (size != 2 * sizeof (unsigned int)) { json_object * arrayJson = json_object_new_array(); while (size > 0) { if (mode >= QUERY_VERBOSE) { snprintf(label, sizeof (label), "0x%08x,", tlv[idx++]); json_object_array_add(arrayJson, json_object_new_string(label)); } else { json_object_array_add(arrayJson, json_object_new_int(tlv[idx++])); } size -= (unsigned int) sizeof (unsigned int); } json_object_object_add(dbLinearJson, "offset", arrayJson); } else { if (mode >= QUERY_VERBOSE) { json_object_object_add(dbLinearJson, "min", DB2StringJsonOject((int) tlv[2])); json_object_object_add(dbLinearJson, "max", DB2StringJsonOject((int) tlv[3])); } else { json_object_object_add(dbLinearJson, "min", json_object_new_int((int) tlv[2])); json_object_object_add(dbLinearJson, "max", json_object_new_int((int) tlv[3])); } } json_object_object_add(decodeTlvJson, "dblinear", dbLinearJson); break; } #endif #ifdef SND_CTL_TLVT_DB_RANGE case SND_CTL_TLVT_DB_RANGE: { json_object *dbRangeJson = json_object_new_object(); if ((size % (6 * sizeof (unsigned int))) != 0) { json_object *arrayJson = json_object_new_array(); while (size > 0) { if (mode >= QUERY_VERBOSE) { snprintf(label, sizeof (label), "0x%08x,", tlv[idx++]); json_object_array_add(arrayJson, json_object_new_string(label)); } else { json_object_array_add(arrayJson, json_object_new_int(tlv[idx++])); } size -= (unsigned int) sizeof (unsigned int); } json_object_object_add(dbRangeJson, "dbrange", arrayJson); break; } while (size > 0) { json_object * embedJson = json_object_new_object(); json_object_object_add(embedJson, "rangemin", json_object_new_int(tlv[idx++])); json_object_object_add(embedJson, "rangemax", json_object_new_int(tlv[idx++])); embedJson = decodeTlv(tlv + idx, 4 * sizeof (unsigned int), mode); json_object_object_add(embedJson, "tlv", embedJson); idx += 4; size -= (unsigned int) (6 * sizeof (unsigned int)); json_object_array_add(dbRangeJson, embedJson); } json_object_object_add(decodeTlvJson, "dbrange", dbRangeJson); break; } #endif #ifdef SND_CTL_TLVT_DB_MINMAX case SND_CTL_TLVT_DB_MINMAX: case SND_CTL_TLVT_DB_MINMAX_MUTE: { json_object * dbMinMaxJson = json_object_new_object(); if (size != 2 * sizeof (unsigned int)) { json_object * arrayJson = json_object_new_array(); while (size > 0) { if (mode >= QUERY_VERBOSE) { snprintf(label, sizeof (label), "0x%08x,", tlv[idx++]); json_object_array_add(arrayJson, json_object_new_string(label)); } else { json_object_array_add(arrayJson, json_object_new_int(tlv[idx++])); } size -= (unsigned int) sizeof (unsigned int); } json_object_object_add(dbMinMaxJson, "array", arrayJson); } else { if (mode >= QUERY_VERBOSE) { json_object_object_add(dbMinMaxJson, "min", DB2StringJsonOject((int) tlv[2])); json_object_object_add(dbMinMaxJson, "max", DB2StringJsonOject((int) tlv[3])); } else { json_object_object_add(dbMinMaxJson, "min", json_object_new_int((int) tlv[2])); json_object_object_add(dbMinMaxJson, "max", json_object_new_int((int) tlv[3])); } } if (type == SND_CTL_TLVT_DB_MINMAX_MUTE) { json_object_object_add(decodeTlvJson, "dbminmaxmute", dbMinMaxJson); } else { json_object_object_add(decodeTlvJson, "dbminmax", dbMinMaxJson); } break; } #endif #ifdef SND_CTL_TLVT_CHMAP_FIXED case SND_CTL_TLVT_CHMAP_FIXED: chmap_type = "fixed"; /* Fall through */ case SND_CTL_TLVT_CHMAP_VAR: if (!chmap_type) chmap_type = "variable"; /* Fall through */ case SND_CTL_TLVT_CHMAP_PAIRED: if (!chmap_type) chmap_type = "paired"; json_object * chmapJson = json_object_new_object(); json_object * arrayJson = json_object_new_array(); while (size > 0) { snprintf(label, sizeof (label), "%s", snd_pcm_chmap_name(tlv[idx++])); size -= (unsigned int) sizeof (unsigned int); json_object_array_add(arrayJson, json_object_new_string(label)); } json_object_object_add(chmapJson, chmap_type, arrayJson); json_object_object_add(decodeTlvJson, "chmap", chmapJson); break; #endif default: { printf("unk-%i-", type); json_object * arrayJson = json_object_new_array(); while (size > 0) { snprintf(label, sizeof (label), "0x%08x,", tlv[idx++]); size -= (unsigned int) sizeof (unsigned int); json_object_array_add(arrayJson, json_object_new_string(label)); } break; json_object_object_add(decodeTlvJson, "unknown", arrayJson); } } return (decodeTlvJson); } PUBLIC int getCardNbFromCardPath(char *cardPath) { int err, cardPathFileFd, fileCardId; snd_ctl_card_info_t *currentCardInfo; if (! cardPath) return -1; cardPathFileFd = open(cardPath, O_RDONLY); if (cardPathFileFd < 0) { AFB_ERROR("CardPath '%s' error %i during open", cardPath, cardPathFileFd); return -2; } snd_ctl_card_info_alloca(¤tCardInfo); err = ioctl(cardPathFileFd, SNDRV_CTL_IOCTL_CARD_INFO(snd_ctl_card_info_sizeof()), currentCardInfo); close(cardPathFileFd); if (err < 0) { AFB_ERROR("CardPath '%s' error %i during ioctl", cardPath, err); return -3; } fileCardId = snd_ctl_card_info_get_card(currentCardInfo); if (fileCardId < 0 || fileCardId >= MAX_SND_CARD) { AFB_ERROR("CardPath '%s' returned an incorrect card number %i", cardPath, fileCardId); return -4; } return fileCardId; } STATIC int searchInQueryForAlsaCardToProbe(queryCardInfo *queryInfo, int *cardToProbe) { int err; if(! queryInfo) { AFB_ERROR("Structure to look for an ALSA card is NULL"); return -1; } if(queryInfo->cardNb == NO_CARD_SELECTED && ! queryInfo->cardPath && ! queryInfo->cardId && ! queryInfo->cardShortName && ! queryInfo->cardLongName && ! queryInfo->cardDriver && ! queryInfo->cardMixerName && ! queryInfo->cardComponents) { AFB_WARNING("Not enough information specified in structure to look for an ALSA card"); return -2; } *cardToProbe = NO_CARD_SELECTED; if(queryInfo->cardNb >= 0 && queryInfo->cardNb < MAX_SND_CARD) { *cardToProbe = queryInfo->cardNb; } else if(queryInfo->cardNb != NO_CARD_SELECTED) { AFB_ERROR("ALSA card number %i is invalid", queryInfo->cardNb); return -3; } if(queryInfo->cardPath) { *cardToProbe = getCardNbFromCardPath(queryInfo->cardPath); if(*cardToProbe < 0 || *cardToProbe >= MAX_SND_CARD) { AFB_ERROR("CardPath '%s' returned an incorrect card number %i", queryInfo->cardPath, *cardToProbe); return -4; } else if(queryInfo->cardNb != NO_CARD_SELECTED && *cardToProbe != queryInfo->cardNb) { AFB_WARNING("CardPath '%s' (id %i) is not corresponding to the requested card number %i", queryInfo->cardPath, *cardToProbe, queryInfo->cardNb); return -5; } queryInfo->cardNb = *cardToProbe; } if(*cardToProbe != NO_CARD_SELECTED) return 0; err = snd_card_next(cardToProbe); if(err < 0) { AFB_ERROR("Error %i happened when tried to get next ALSA card number", err); return -6; } if(*cardToProbe < 0) { AFB_NOTICE("No ALSA card connected"); return -7; } return 1; } STATIC int jumpToCardNextDevice(unsigned int card, int *currentDevice) { int err; char cardString[6]; snd_ctl_t *cardHandle; if(card >= MAX_SND_CARD) { AFB_ERROR("ALSA card number %i is not valid", card); return -1; } if(*currentDevice != NO_DEVICE_SELECTED && (*currentDevice < 0 || *currentDevice > MAX_CARD_DEVICES)) { AFB_ERROR("ALSA card device %i is not valid", *currentDevice); return -2; } snprintf(cardString, sizeof(cardString), "hw:%d", card); err = snd_ctl_open(&cardHandle, cardString, 0); if(err < 0) { AFB_ERROR("Error %i happened when tried to open ALSA card %i", err, card); return -3; } err = snd_ctl_pcm_next_device(cardHandle, currentDevice); if(err < 0) { AFB_ERROR("Error %i happened when tried to get ALSA card %i next device number", err, card); snd_ctl_close(cardHandle); return -4; } snd_ctl_close(cardHandle); return 0; } STATIC int checkIfCardIsCorrespondingToQuery(queryCardInfo *queryInfo, int card) { int err; char cardString[6]; snd_ctl_t *cardHandle; snd_ctl_card_info_t *cardInfo; snd_ctl_card_info_alloca(&cardInfo); if(! queryInfo) { AFB_ERROR("Structure to look for an ALSA card is NULL"); return -1; } if(queryInfo->cardNb == NO_CARD_SELECTED && ! queryInfo->cardPath && ! queryInfo->cardId && ! queryInfo->cardShortName && ! queryInfo->cardLongName && ! queryInfo->cardDriver && ! queryInfo->cardMixerName && ! queryInfo->cardComponents) { AFB_WARNING("Not enough information specified in structure to look for an ALSA card"); return -2; } if(card < 0 || card >= MAX_SND_CARD) { AFB_ERROR("ALSA card number %i is not valid", card); return -3; } snprintf(cardString, sizeof(cardString), "hw:%d", card); err = snd_ctl_open(&cardHandle, cardString, 0); if(err < 0) { AFB_ERROR("Error %i happened when tried to open ALSA card %i", err, card); return -4; } err = snd_ctl_card_info(cardHandle, cardInfo); if(err < 0) { AFB_ERROR("Error %i happened when tried to get ALSA card %i info", err, card); snd_ctl_close(cardHandle); return -5; } if((queryInfo->cardId && strcmp(queryInfo->cardId, snd_ctl_card_info_get_id(cardInfo)) != 0) || (queryInfo->cardShortName && strcmp(queryInfo->cardShortName, snd_ctl_card_info_get_name(cardInfo)) != 0) || (queryInfo->cardLongName && strcmp(queryInfo->cardLongName, snd_ctl_card_info_get_longname(cardInfo)) != 0) || (queryInfo->cardDriver && strcmp(queryInfo->cardDriver, snd_ctl_card_info_get_driver(cardInfo)) != 0) || (queryInfo->cardMixerName && strcmp(queryInfo->cardMixerName, snd_ctl_card_info_get_mixername(cardInfo)) != 0) || (queryInfo->cardComponents && strcmp(queryInfo->cardComponents, snd_ctl_card_info_get_components(cardInfo)) != 0)) { AFB_DEBUG("Card %i tested but not corresponding to requested card:\n" "Card ID: '%s', requested ID: '%s'\n" "Card short name: '%s', requested short name: '%s'\n" "Card long name: '%s', requested long name: '%s'\n" "Card driver: '%s', requested driver: '%s'\n" "Card mixer name: '%s', requested mixer name: '%s'\n" "Card components: '%s', requested components: '%s'", snd_ctl_card_info_get_card(cardInfo), snd_ctl_card_info_get_id(cardInfo), queryInfo->cardId ? queryInfo->cardId : "none", snd_ctl_card_info_get_name(cardInfo), queryInfo->cardShortName ? queryInfo->cardShortName : "none", snd_ctl_card_info_get_longname(cardInfo), queryInfo->cardLongName ? queryInfo->cardLongName : "none", snd_ctl_card_info_get_driver(cardInfo), queryInfo->cardDriver ? queryInfo->cardDriver : "none", snd_ctl_card_info_get_mixername(cardInfo), queryInfo->cardMixerName ? queryInfo->cardMixerName : "none", snd_ctl_card_info_get_components(cardInfo), queryInfo->cardComponents ? queryInfo->cardComponents : "none"); snd_ctl_close(cardHandle); return 0; } snd_ctl_close(cardHandle); return 1; } STATIC int checkIfPlaybackDeviceIsCorrespondingToQuery(queryCardInfo *queryInfo, int card, int device) { int err; char cardString[6]; snd_ctl_t *cardHandle; snd_pcm_info_t *cardPcminfo; snd_pcm_info_alloca(&cardPcminfo); if(! queryInfo) { AFB_ERROR("Structure to look for an ALSA card is NULL"); return -1; } if(queryInfo->playbackDeviceNb == NO_DEVICE_SELECTED && ! queryInfo->playbackDeviceId && ! queryInfo->playbackDeviceName) { AFB_WARNING("Not enough information specified in structure to look for a device on card %i", card); return -2; } if(card < 0 || card >= MAX_SND_CARD) { AFB_ERROR("ALSA card number %i is not valid", card); return -3; } if(device < 0 || device >= MAX_CARD_DEVICES) { AFB_ERROR("ALSA card device %i is not valid", device); return -4; } snprintf(cardString, sizeof(cardString), "hw:%d", card); err = snd_ctl_open(&cardHandle, cardString, 0); if(err < 0) { AFB_ERROR("Error %i happened when tried to open ALSA card %i", err, card); return -5; } snd_pcm_info_set_device(cardPcminfo, device); snd_pcm_info_set_subdevice(cardPcminfo, 0); snd_pcm_info_set_stream(cardPcminfo, SND_PCM_STREAM_PLAYBACK); err = snd_ctl_pcm_info(cardHandle, cardPcminfo); if(err == -ENOENT) { snd_ctl_close(cardHandle); return 0; } else if(err < 0) { AFB_ERROR("Error %i happened when tried to get ALSA card %i device %i info", err, card, device); snd_ctl_close(cardHandle); return -6; } if((queryInfo->playbackDeviceId && strcmp(queryInfo->playbackDeviceId, snd_pcm_info_get_id(cardPcminfo)) != 0) || (queryInfo->playbackDeviceName && strcmp(queryInfo->playbackDeviceName, snd_pcm_info_get_name(cardPcminfo)) != 0)) { AFB_DEBUG("Card %i, device %i tested but not corresponding to requested device:\n" "Device ID: '%s', requested ID: '%s'\n" "Device name: '%s', requested name: '%s'", card, device, snd_pcm_info_get_id(cardPcminfo), queryInfo->playbackDeviceId ? queryInfo->playbackDeviceId : "none", snd_pcm_info_get_name(cardPcminfo), queryInfo->playbackDeviceName ? queryInfo->playbackDeviceName : "none"); snd_ctl_close(cardHandle); return 0; } snd_ctl_close(cardHandle); return 1; } PUBLIC json_object *getCardInfo(int card) { int err; char cardString[6]; snd_ctl_t *cardHandle; snd_ctl_card_info_t *cardInfo; json_object *cardInfoJ; snd_ctl_card_info_alloca(&cardInfo); if(card < 0 || card >= MAX_SND_CARD) { AFB_ERROR("ALSA card number %i is not valid", card); return NULL; } snprintf(cardString, sizeof(cardString), "hw:%d", card); err = snd_ctl_open(&cardHandle, cardString, 0); if(err < 0) { AFB_ERROR("Error %i happened when tried to open ALSA card %i", err, card); return NULL; } err = snd_ctl_card_info(cardHandle, cardInfo); if(err < 0) { AFB_ERROR("Error %i happened when tried to get ALSA card %i info", err, card); snd_ctl_close(cardHandle); return NULL; } wrap_json_pack(&cardInfoJ, "{s:i, s:s, s:s, s:s, s:s, s:s, s:s}", "cardNb", snd_ctl_card_info_get_card(cardInfo), "cardId", snd_ctl_card_info_get_id(cardInfo), "cardShortName", snd_ctl_card_info_get_name(cardInfo), "cardLongName", snd_ctl_card_info_get_longname(cardInfo), "cardDriver", snd_ctl_card_info_get_driver(cardInfo), "cardMixerName", snd_ctl_card_info_get_mixername(cardInfo), "cardComponents", snd_ctl_card_info_get_components(cardInfo)); snd_ctl_close(cardHandle); return cardInfoJ; } STATIC json_object *getDeviceInfo(int card, int device) { int err; char cardString[6]; snd_ctl_t *cardHandle; snd_pcm_info_t *cardPcminfo; json_object *deviceInfoJ; snd_pcm_info_alloca(&cardPcminfo); if(card < 0 || card >= MAX_SND_CARD) { AFB_ERROR("ALSA card number %i is not valid", card); return NULL; } if(device < 0 || device >= MAX_CARD_DEVICES) { AFB_ERROR("ALSA card device %i is not valid", device); return NULL; } snprintf(cardString, sizeof(cardString), "hw:%d", card); err = snd_ctl_open(&cardHandle, cardString, 0); if(err < 0) { AFB_ERROR("Error %i happened when tried to open ALSA card %i", err, card); return NULL; } snd_pcm_info_set_device(cardPcminfo, device); snd_pcm_info_set_subdevice(cardPcminfo, 0); snd_pcm_info_set_stream(cardPcminfo, SND_PCM_STREAM_PLAYBACK); err = snd_ctl_pcm_info(cardHandle, cardPcminfo); if(err == -ENOENT) { snd_ctl_close(cardHandle); return NULL; } else if(err < 0) { AFB_ERROR("Error %i happened when tried to get ALSA card %i device %i info", err, card, device); snd_ctl_close(cardHandle); return NULL; } wrap_json_pack(&deviceInfoJ, "{s:i, s:s, s:s}", "playbackDeviceNb", (int) snd_pcm_info_get_device(cardPcminfo), "playbackDeviceId", snd_pcm_info_get_id(cardPcminfo), "playbackDeviceName", snd_pcm_info_get_name(cardPcminfo)); snd_ctl_close(cardHandle); return deviceInfoJ; } // Retrieve info for one given card STATIC json_object *alsaCardProbe(queryCardInfo *queryInfo) { int cardToProbe = NO_CARD_SELECTED, correspondingCard = NO_CARD_SELECTED, playbackDeviceToProbe, multipleCardToCheck, isCardCorresponding, isDeviceCorresponding; unsigned int searchingForDevice = 1; json_object *correspondingCardJ, *correspondingDeviceJ = NULL, *correspondingCardAndDeviceJ, *toReturnJ; if(! queryInfo) { AFB_NOTICE("Structure to look for an ALSA card is NULL"); return NULL; } if(queryInfo->cardNb == NO_CARD_SELECTED && ! queryInfo->cardPath && ! queryInfo->cardId && ! queryInfo->cardShortName && ! queryInfo->cardLongName && ! queryInfo->cardDriver && ! queryInfo->cardMixerName && ! queryInfo->cardComponents) { AFB_NOTICE("Not enough information specified in structure to look for an ALSA card"); return NULL; } else if(queryInfo->cardNb != NO_CARD_SELECTED && (queryInfo->cardNb < 0 || queryInfo->cardNb > MAX_SND_CARD)) { AFB_WARNING("ALSA card %i is not valid", queryInfo->cardNb); return NULL; } if(queryInfo->playbackDeviceNb == NO_DEVICE_SELECTED && ! queryInfo->playbackDeviceId && ! queryInfo->playbackDeviceName) { searchingForDevice = 0; } else if(queryInfo->playbackDeviceNb != NO_DEVICE_SELECTED && (queryInfo->playbackDeviceNb < 0 || queryInfo->playbackDeviceNb > MAX_CARD_DEVICES)) { AFB_WARNING("ALSA card device %i is not valid", queryInfo->playbackDeviceNb); return NULL; } multipleCardToCheck = searchInQueryForAlsaCardToProbe(queryInfo, &cardToProbe); if(multipleCardToCheck < 0) return NULL; toReturnJ = json_object_new_array(); if(! toReturnJ) { AFB_ERROR("Error while allocating answer json array"); return NULL; } while(cardToProbe >= 0 && cardToProbe < MAX_SND_CARD) { isCardCorresponding = checkIfCardIsCorrespondingToQuery(queryInfo, cardToProbe); if(isCardCorresponding < 0) { json_object_put(toReturnJ); return NULL; } else if(isCardCorresponding) { correspondingCard = cardToProbe; } if(! multipleCardToCheck || (snd_card_next(&cardToProbe) < 0)) cardToProbe = NO_CARD_SELECTED; if(! isCardCorresponding) continue; correspondingCardJ = getCardInfo(correspondingCard); if(! searchingForDevice) { json_object_array_add(toReturnJ, correspondingCardJ); continue; } playbackDeviceToProbe = queryInfo->playbackDeviceNb; correspondingCardAndDeviceJ = NULL; if(queryInfo->playbackDeviceNb != NO_DEVICE_SELECTED) { playbackDeviceToProbe = queryInfo->playbackDeviceNb; } else if(jumpToCardNextDevice(correspondingCard, &playbackDeviceToProbe)) { json_object_put(toReturnJ); json_object_put(correspondingCardJ); return NULL; } while(playbackDeviceToProbe >= 0 && playbackDeviceToProbe < MAX_CARD_DEVICES) { isDeviceCorresponding = checkIfPlaybackDeviceIsCorrespondingToQuery(queryInfo, correspondingCard, playbackDeviceToProbe); if(isDeviceCorresponding < 0) { json_object_put(correspondingCardJ); json_object_put(toReturnJ); return NULL; } if(isDeviceCorresponding) correspondingDeviceJ = getDeviceInfo(correspondingCard, playbackDeviceToProbe); if(queryInfo->playbackDeviceNb != NO_DEVICE_SELECTED) { playbackDeviceToProbe = NO_DEVICE_SELECTED; } else if(jumpToCardNextDevice(correspondingCard, &playbackDeviceToProbe)) { if(correspondingDeviceJ) json_object_put(correspondingDeviceJ); json_object_put(correspondingCardJ); json_object_put(toReturnJ); return NULL; } if(! isDeviceCorresponding || ! correspondingDeviceJ) continue; correspondingCardAndDeviceJ = wrap_json_clone(correspondingCardJ); wrap_json_object_add(correspondingCardAndDeviceJ, correspondingDeviceJ); json_object_array_add(toReturnJ, correspondingCardAndDeviceJ); } if(! correspondingCardAndDeviceJ) AFB_DEBUG("Card %i tested and corresponding to requested card, but the requested device was not found", correspondingCard); json_object_put(correspondingCardJ); if(! multipleCardToCheck) break; } switch(json_object_array_length(toReturnJ)) { case 0: AFB_DEBUG("Requested card/device not found"); return NULL; case 1: correspondingCardJ = json_object_get(json_object_array_get_idx(toReturnJ, 0)); json_object_put(toReturnJ); return correspondingCardJ; default: return toReturnJ; } } PUBLIC void alsaGetInfo(afb_req_t request) { int card = NO_CARD_SELECTED; unsigned int idx, count; json_object *requestJ, *currentCardRequestJ, *currentCardInfoJ, *toReturnJ = NULL; json_type requestJType; queryCardInfo queryInfo; requestJ = afb_req_json(request); if(! requestJ) { if((snd_card_next(&card) >= 0) && (card >= 0)) { toReturnJ = json_object_new_array(); while(card >= 0) { currentCardInfoJ = getCardInfo(card); if(currentCardInfoJ) json_object_array_add(toReturnJ, currentCardInfoJ); if(snd_card_next(&card) < 0) card = NO_CARD_SELECTED; } } afb_req_success(request, toReturnJ, "All sound cards properties are available in returned Json"); return; } requestJType = json_object_get_type(requestJ); switch(requestJType) { case json_type_object: count = 1; break; case json_type_array: count = (unsigned int) json_object_array_length(requestJ); toReturnJ = json_object_new_array(); break; default: afb_req_fail_f(request, "invalid-request", "Invalid request json '%s'", json_object_get_string(requestJ)); return; } for(idx = 0; idx < count; idx++) { if(requestJType == json_type_array) currentCardRequestJ = json_object_array_get_idx(requestJ, idx); else currentCardRequestJ = requestJ; memset(&queryInfo, 0, sizeof(queryInfo)); queryInfo.cardNb = NO_CARD_SELECTED; queryInfo.playbackDeviceNb = NO_DEVICE_SELECTED; if((wrap_json_unpack(currentCardRequestJ, "{s?:i, s?:s, s?:s, s?:s, s?:s, s?:s, s?:s, s?:s, s?:i, s?:s, s?:s !}", "cardNb", (int *) &queryInfo.cardNb, "cardPath", &queryInfo.cardPath, "cardId", &queryInfo.cardId, "cardShortName", &queryInfo.cardShortName, "cardLongName", &queryInfo.cardLongName, "cardDriver", &queryInfo.cardDriver, "cardMixerName", &queryInfo.cardMixerName, "cardComponents", &queryInfo.cardComponents, "playbackDeviceNb", (int *) &queryInfo.playbackDeviceNb, "playbackDeviceId", &queryInfo.playbackDeviceId, "playbackDeviceName", &queryInfo.playbackDeviceName)) || ((queryInfo.cardNb < 0 || queryInfo.cardNb >= MAX_SND_CARD) && (! queryInfo.cardPath) && (! queryInfo.cardId) && (! queryInfo.cardShortName) && (! queryInfo.cardLongName) && (! queryInfo.cardDriver) && (! queryInfo.cardMixerName) && (! queryInfo.cardComponents))) { if(toReturnJ) json_object_put(toReturnJ); afb_req_fail_f(request, "invalid-request", "Invalid request (case %u) json '%s'", idx, json_object_get_string(currentCardRequestJ)); return; } currentCardInfoJ = alsaCardProbe(&queryInfo); switch(requestJType) { case json_type_object: if(currentCardInfoJ) afb_req_success(request, currentCardInfoJ, "Requested sound card properties are available in returned Json"); else afb_req_fail_f(request, "sndcard-not-found", "Sound card requested is not connected (request : '%s')", json_object_get_string(currentCardRequestJ)); return; case json_type_array: if(currentCardInfoJ) json_object_array_add(toReturnJ, currentCardInfoJ); else json_object_array_add(toReturnJ, json_object_new_string("sndcard-not-found")); break; default: break; } } afb_req_success(request, toReturnJ, "Found requested sound card(s) properties are available in returned Json"); } // pack Alsa element's ACL into a JSON object STATIC json_object *getControlAcl(snd_ctl_elem_info_t *info) { json_object * jsonAclCtl = json_object_new_object(); json_object_object_add(jsonAclCtl, "read", json_object_new_boolean(snd_ctl_elem_info_is_readable(info))); json_object_object_add(jsonAclCtl, "write", json_object_new_boolean(snd_ctl_elem_info_is_writable(info))); json_object_object_add(jsonAclCtl, "inact", json_object_new_boolean(snd_ctl_elem_info_is_inactive(info))); json_object_object_add(jsonAclCtl, "volat", json_object_new_boolean(snd_ctl_elem_info_is_volatile(info))); json_object_object_add(jsonAclCtl, "lock", json_object_new_boolean(snd_ctl_elem_info_is_locked(info))); // if TLV is readable we insert its ACL if (!snd_ctl_elem_info_is_tlv_readable(info)) { json_object * jsonTlv = json_object_new_object(); json_object_object_add(jsonTlv, "read", json_object_new_boolean(snd_ctl_elem_info_is_tlv_readable(info))); json_object_object_add(jsonTlv, "write", json_object_new_boolean(snd_ctl_elem_info_is_tlv_writable(info))); json_object_object_add(jsonTlv, "command", json_object_new_boolean(snd_ctl_elem_info_is_tlv_commandable(info))); json_object_object_add(jsonAclCtl, "tlv", jsonTlv); } return (jsonAclCtl); } // process ALSA control and store resulting value into ctlRequest PUBLIC int alsaSetSingleCtl(snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, ctlRequestT *ctlRequest) { snd_ctl_elem_value_t *elemData; snd_ctl_elem_info_t *elemInfo; int count, length, err, valueIsArray = 0; // let's make sure we are processing the right control if (ctlRequest->numId != snd_ctl_elem_id_get_numid(elemId)) goto OnErrorExit; // set info event ID and get value snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); // map ctlInfo to ctlId elemInfo is updated !!! if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) { AFB_NOTICE("Fail to load ALSA NUMID=%d Values='%s'", ctlRequest->numId, json_object_get_string(ctlRequest->valuesJ)); goto OnErrorExit; } if (!snd_ctl_elem_info_is_writable(elemInfo)) { AFB_NOTICE("Not Writable ALSA NUMID=%d Values='%s'", ctlRequest->numId, json_object_get_string(ctlRequest->valuesJ)); goto OnErrorExit; } count = snd_ctl_elem_info_get_count(elemInfo); if (count == 0) goto OnErrorExit; enum json_type jtype = json_object_get_type(ctlRequest->valuesJ); switch (jtype) { case json_type_array: length = (int) json_object_array_length(ctlRequest->valuesJ); valueIsArray = 1; break; case json_type_int: length = 1; valueIsArray = 0; break; default: length = 0; break; } if (length == 0) { AFB_ERROR("Invalid values NUMID='%d' Values='%s' count='%d' wanted='%d'", ctlRequest->numId, json_object_get_string(ctlRequest->valuesJ), length, count); goto OnErrorExit; } else if (valueIsArray && length < count) { AFB_WARNING("Json Array provided to set values is to short (%i), control has %i values, last array value will be used to set remaining control value(s)", length, count); } else if (valueIsArray && length > count) { AFB_WARNING("Json Array provided to set values is to long (%i), control has %i values, last array value(s) will be dropped", length, count); } snd_ctl_elem_value_alloca(&elemData); snd_ctl_elem_value_set_id(elemData, elemId); // map ctlInfo to ctlId elemInfo is updated !!! if (snd_ctl_elem_read(ctlDev, elemData) < 0) goto OnErrorExit; // Loop on every control value and push to sndcard for (int index = 0; index < count; index++) { json_object *element; int value; // when not enough value duplicate last provided one if (!valueIsArray) element = ctlRequest->valuesJ; else { if (index < length) element = json_object_array_get_idx(ctlRequest->valuesJ, index); else element = json_object_array_get_idx(ctlRequest->valuesJ, length - 1); } value = json_object_get_int(element); snd_ctl_elem_value_set_integer(elemData, index, value); } err = snd_ctl_elem_write(ctlDev, elemData); if (err < 0) { AFB_ERROR("Fail to write ALSA NUMID=%d Values='%s' Error=%s", ctlRequest->numId, json_object_get_string(ctlRequest->valuesJ), snd_strerror(err)); goto OnErrorExit; } ctlRequest->used = 1; return 0; OnErrorExit: ctlRequest->used = -1; return -1; } // process ALSA control and store then into ctlRequest PUBLIC int alsaGetSingleCtl(snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, ctlRequestT *ctlRequest, queryModeE queryMode) { snd_ctl_elem_type_t elemType; snd_ctl_elem_value_t *elemData; snd_ctl_elem_info_t *elemInfo; int count, idx, err; // set info event ID and get value snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; count = snd_ctl_elem_info_get_count(elemInfo); if (count == 0) goto OnErrorExit; if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; elemType = snd_ctl_elem_info_get_type(elemInfo); snd_ctl_elem_value_alloca(&elemData); snd_ctl_elem_value_set_id(elemData, elemId); if (snd_ctl_elem_read(ctlDev, elemData) < 0) goto OnErrorExit; int numid = snd_ctl_elem_info_get_numid(elemInfo); ctlRequest->valuesJ = json_object_new_object(); json_object_object_add(ctlRequest->valuesJ, "id", json_object_new_int(numid)); if (queryMode >= 1) json_object_object_add(ctlRequest->valuesJ, "name", json_object_new_string(snd_ctl_elem_id_get_name(elemId))); if (queryMode >= 2) json_object_object_add(ctlRequest->valuesJ, "iface", json_object_new_string(snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(elemId)))); if (queryMode >= 3) json_object_object_add(ctlRequest->valuesJ, "actif", json_object_new_boolean(!snd_ctl_elem_info_is_inactive(elemInfo))); json_object *jsonValuesCtl = json_object_new_array(); for (idx = 0; idx < count; idx++) { // start from one in amixer.c !!! switch (elemType) { case SND_CTL_ELEM_TYPE_BOOLEAN: { json_object_array_add(jsonValuesCtl, json_object_new_boolean(snd_ctl_elem_value_get_boolean(elemData, idx))); break; } case SND_CTL_ELEM_TYPE_INTEGER: json_object_array_add(jsonValuesCtl, json_object_new_int((int) snd_ctl_elem_value_get_integer(elemData, idx))); break; case SND_CTL_ELEM_TYPE_INTEGER64: json_object_array_add(jsonValuesCtl, json_object_new_int64(snd_ctl_elem_value_get_integer64(elemData, idx))); break; case SND_CTL_ELEM_TYPE_ENUMERATED: json_object_array_add(jsonValuesCtl, json_object_new_int(snd_ctl_elem_value_get_enumerated(elemData, idx))); break; case SND_CTL_ELEM_TYPE_BYTES: json_object_array_add(jsonValuesCtl, json_object_new_int((int) snd_ctl_elem_value_get_byte(elemData, idx))); break; case SND_CTL_ELEM_TYPE_IEC958: { json_object *jsonIec958Ctl = json_object_new_object(); snd_aes_iec958_t iec958; snd_ctl_elem_value_get_iec958(elemData, &iec958); json_object_object_add(jsonIec958Ctl, "AES0", json_object_new_int(iec958.status[0])); json_object_object_add(jsonIec958Ctl, "AES1", json_object_new_int(iec958.status[1])); json_object_object_add(jsonIec958Ctl, "AES2", json_object_new_int(iec958.status[2])); json_object_object_add(jsonIec958Ctl, "AES3", json_object_new_int(iec958.status[3])); json_object_array_add(jsonValuesCtl, jsonIec958Ctl); break; } default: json_object_array_add(jsonValuesCtl, json_object_new_string("?unknown?")); break; } } json_object_object_add(ctlRequest->valuesJ, "val", jsonValuesCtl); if (queryMode >= 1) { // in simple mode do not print usable values json_object *jsonClassCtl = json_object_new_object(); json_object_object_add(jsonClassCtl, "type", json_object_new_int(elemType)); json_object_object_add(jsonClassCtl, "count", json_object_new_int(count)); switch (elemType) { case SND_CTL_ELEM_TYPE_INTEGER: json_object_object_add(jsonClassCtl, "min", json_object_new_int((int) snd_ctl_elem_info_get_min(elemInfo))); json_object_object_add(jsonClassCtl, "max", json_object_new_int((int) snd_ctl_elem_info_get_max(elemInfo))); json_object_object_add(jsonClassCtl, "step", json_object_new_int((int) snd_ctl_elem_info_get_step(elemInfo))); break; case SND_CTL_ELEM_TYPE_INTEGER64: json_object_object_add(jsonClassCtl, "min", json_object_new_int64(snd_ctl_elem_info_get_min64(elemInfo))); json_object_object_add(jsonClassCtl, "max", json_object_new_int64(snd_ctl_elem_info_get_max64(elemInfo))); json_object_object_add(jsonClassCtl, "step", json_object_new_int64(snd_ctl_elem_info_get_step64(elemInfo))); break; case SND_CTL_ELEM_TYPE_ENUMERATED: { unsigned int item, items = snd_ctl_elem_info_get_items(elemInfo); json_object *jsonEnum = json_object_new_array(); for (item = 0; item < items; item++) { snd_ctl_elem_info_set_item(elemInfo, item); if ((err = snd_ctl_elem_info(ctlDev, elemInfo)) >= 0) { json_object_array_add(jsonEnum, json_object_new_string(snd_ctl_elem_info_get_item_name(elemInfo))); } } json_object_object_add(jsonClassCtl, "enums", jsonEnum); break; } default: break; // ignore any unknown type } // add collected class info with associated ACLs json_object_object_add(ctlRequest->valuesJ, "ctl", jsonClassCtl); if (queryMode >= QUERY_FULL) json_object_object_add(ctlRequest->valuesJ, "acl", getControlAcl(elemInfo)); // check for tlv [direct port from amixer.c] if (snd_ctl_elem_info_is_tlv_readable(elemInfo)) { unsigned int *tlv = alloca(TLV_BYTE_SIZE); if ((err = snd_ctl_elem_tlv_read(ctlDev, elemId, tlv, 4096)) < 0) { AFB_NOTICE("Control numid=%d err=%s element TLV read error\n", numid, snd_strerror(err)); goto OnErrorExit; } else { json_object_object_add(ctlRequest->valuesJ, "tlv", decodeTlv(tlv, TLV_BYTE_SIZE, queryMode)); } } } ctlRequest->used = 1; return 0; OnErrorExit: ctlRequest->used = -1; return -1; } // assign multiple control to the same value STATIC void alsaSetGetCtls(ActionSetGetT action, afb_req_t request) { int err = 0, done; unsigned int ctlCount; queryModeE mode; char *devId = NULL; snd_ctl_t *ctlDev = NULL; snd_ctl_elem_list_t *ctlList; json_object *queryJ, *numidsJ, *sndctls; controlQueryValuesT queryControlValues; ctlRequestT *ctlRequest; 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); // Phrase Numids + optional values done = json_object_object_get_ex(queryJ, "ctl", &numidsJ); if (!done && action == ACTION_GET) { queryControlValues.count = 0; } else if (!done && action == ACTION_SET) { afb_req_fail_f(request, "invalid-set", "To set an ALSA control value(s), they must be specified in query=%s", json_object_get_string(queryJ)); return; } else { enum json_type jtype = json_object_get_type(numidsJ); switch (jtype) { case json_type_array: queryControlValues.numidsJ = numidsJ; queryControlValues.count = (int) json_object_array_length(numidsJ); break; case json_type_int: case json_type_object: case json_type_string: queryControlValues.count = 1; queryControlValues.numidsJ = numidsJ; break; default: afb_req_fail_f(request, "numid-notarray", "NumId=%s NumId not valid JSON array", json_object_get_string(numidsJ)); return; } } if ((err = snd_ctl_open(&ctlDev, devId, 0)) < 0) { afb_req_fail_f(request, "sndcrl-notfound", "devid='%s' load fail error=%s\n", devId, snd_strerror(err)); goto close_ctl; } snd_ctl_elem_list_alloca(&ctlList); if ((err = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { afb_req_fail_f(request, "listInit-failed", "devid='%s' load fail error=%s\n", devId, snd_strerror(err)); goto close_ctl; } if ((err = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { afb_req_fail_f(request, "listAlloc-failed", "devid='%s' load fail error=%s\n", devId, snd_strerror(err)); goto close_ctl; } if ((err = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { afb_req_fail_f(request, "listOpen-failed", "devid='%s' load fail error=%s\n", devId, snd_strerror(err)); goto free_elem_list; } // Parse numids string (empty == all) ctlCount = snd_ctl_elem_list_get_used(ctlList); // if more than one crl requested prepare an array for response if (queryControlValues.count != 1 && action == ACTION_GET) sndctls = json_object_new_array(); else sndctls = NULL; if (queryControlValues.count == 0) { ctlRequest = alloca(sizeof (ctlRequestT)*(ctlCount)); ctlRequest->tag = NULL; // Map all existing ctl as requested (loop on all ctlDev controls) for (int ctlIndex = 0; ctlIndex < ctlCount; ctlIndex++) { snd_ctl_elem_id_t *elemId; snd_ctl_elem_id_alloca(&elemId); snd_ctl_elem_list_get_id(ctlList, ctlIndex, elemId); err = alsaGetSingleCtl(ctlDev, elemId, &ctlRequest[ctlIndex], mode); if (err) { afb_req_fail_f(request, "control_get", "Error while trying to get control id=%i (name=%s) properties (control %i out of %i)", snd_ctl_elem_list_get_numid(ctlList, ctlIndex), snd_ctl_elem_list_get_name(ctlList, ctlIndex), (ctlIndex + 1), ctlCount); goto free_response_array; } else if (queryControlValues.count == 1) { sndctls = ctlRequest[ctlIndex].valuesJ; } else { json_object_array_add(sndctls, ctlRequest[ctlIndex].valuesJ); } } } else { int found, ctlIndex; ctlRequest = alloca(sizeof (ctlRequestT)*(queryControlValues.count)); NumidsListParse(action, &queryControlValues, ctlRequest); for (int jdx = 0; jdx < queryControlValues.count; jdx++) { found = 0; ctlIndex = 0; while (ctlIndex < ctlCount) { int numid = snd_ctl_elem_list_get_numid(ctlList, ctlIndex); const char *tag = snd_ctl_elem_list_get_name(ctlList, ctlIndex); if (numid < 0) { AFB_NOTICE("snd_ctl_elem_list_get_numid index=%d fail", ctlIndex); ctlIndex++; continue; } if ((numid == ctlRequest[jdx].numId) || (ctlRequest[jdx].tag && !strcasecmp(tag, ctlRequest[jdx].tag))) { ctlRequest[jdx].numId = numid; found = 1; break; } ctlIndex++; } if (!found) { if (ctlRequest[jdx].tag) { afb_req_fail_f(request, "control_not_found", "Error while trying to find control using its name=%s (query %i out of %i)", ctlRequest[jdx].tag, (jdx + 1), queryControlValues.count); goto free_response_array; } else { afb_req_fail_f(request, "control_not_found", "Error while trying to find control using its numid=%i (query %i out of %i)", ctlRequest[jdx].numId, (jdx + 1), queryControlValues.count); goto free_response_array; } } snd_ctl_elem_id_t *elemId; snd_ctl_elem_id_alloca(&elemId); snd_ctl_elem_list_get_id(ctlList, ctlIndex, elemId); switch (action) { case ACTION_GET: err = alsaGetSingleCtl(ctlDev, elemId, &ctlRequest[jdx], mode); if (err) { afb_req_fail_f(request, "control_get", "Error while trying to get control id=%i (name=%s) properties (query %i out of %i)", snd_ctl_elem_list_get_numid(ctlList, ctlIndex), snd_ctl_elem_list_get_name(ctlList, ctlIndex), (jdx + 1), queryControlValues.count); goto free_response_array; } break; case ACTION_SET: err = alsaSetSingleCtl(ctlDev, elemId, &ctlRequest[jdx]); if (err) { afb_req_fail_f(request, "control_set", "Error while trying to set control id=%i (name=%s) values using values=%s (query %i out of %i)", snd_ctl_elem_list_get_numid(ctlList, ctlIndex), snd_ctl_elem_list_get_name(ctlList, ctlIndex), json_object_get_string(ctlRequest[jdx].valuesJ), (jdx + 1), queryControlValues.count); goto free_response_array; } break; default: afb_req_fail_f(request, "unknown_action", "Action not known for control id=%i (name=%s), should be GET or SET (query %i out of %i)", snd_ctl_elem_list_get_numid(ctlList, ctlIndex), snd_ctl_elem_list_get_name(ctlList, ctlIndex), (jdx + 1), queryControlValues.count); goto free_response_array; } if (action == ACTION_GET) { if (queryControlValues.count == 1) sndctls = ctlRequest[jdx].valuesJ; else json_object_array_add(sndctls, ctlRequest[jdx].valuesJ); } } } // send response afb_req_success_f(request, sndctls, NULL); goto free_elem_list; free_response_array: if (sndctls) json_object_put(sndctls); free_elem_list: snd_ctl_elem_list_free_space(ctlList); snd_ctl_elem_list_clear(ctlList); close_ctl: if (ctlDev) snd_ctl_close(ctlDev); return; } PUBLIC void alsaGetCtls(afb_req_t request) { alsaSetGetCtls(ACTION_GET, request); } PUBLIC void alsaSetCtls(afb_req_t request) { alsaSetGetCtls(ACTION_SET, request); }