From eb6a78be524aaee70fca55b86a7b065763591178 Mon Sep 17 00:00:00 2001 From: fulup Date: Thu, 10 Aug 2017 17:37:24 +0200 Subject: Work in Progress --- ALSA-afb/Alsa-AddCtl.c | 372 ------------- ALSA-afb/Alsa-ApiHat.c | 59 -- ALSA-afb/Alsa-ApiHat.h | 74 --- ALSA-afb/Alsa-RegEvt.c | 395 -------------- ALSA-afb/Alsa-SetGet.c | 809 ---------------------------- ALSA-afb/Alsa-Ucm.c | 433 --------------- ALSA-afb/CMakeLists.txt | 41 -- ALSA-afb/README.md | 33 -- Alsa-Plugin/Alsa-Policy-Hook/CMakeLists.txt | 1 - Alsa-afb/Alsa-AddCtl.c | 372 +++++++++++++ Alsa-afb/Alsa-ApiHat.c | 59 ++ Alsa-afb/Alsa-ApiHat.h | 74 +++ Alsa-afb/Alsa-RegEvt.c | 395 ++++++++++++++ Alsa-afb/Alsa-SetGet.c | 809 ++++++++++++++++++++++++++++ Alsa-afb/Alsa-Ucm.c | 433 +++++++++++++++ Alsa-afb/CMakeLists.txt | 41 ++ Alsa-afb/README.md | 33 ++ Audio-Common/audio-common.h | 3 +- Controler-afb/CMakeLists.txt | 24 +- Controler-afb/ctl-binding.c | 8 +- Controler-afb/ctl-binding.h | 80 ++- Controler-afb/ctl-dispatch.c | 318 +++++++++++ Controler-afb/ctl-events.c | 16 + Controler-afb/ctl-lua.c | 281 ++++++---- Controler-afb/ctl-plugin-sample.c | 64 +++ Controler-afb/ctl-policy.c | 196 ------- conf.d/app-templates | 2 +- conf.d/cmake/config.cmake | 22 +- data/onload-control-policy.json | 23 +- data/onload-control-script.lua | 8 +- nbproject/configurations.xml | 258 +++++---- 31 files changed, 3035 insertions(+), 2701 deletions(-) delete mode 100644 ALSA-afb/Alsa-AddCtl.c delete mode 100644 ALSA-afb/Alsa-ApiHat.c delete mode 100644 ALSA-afb/Alsa-ApiHat.h delete mode 100644 ALSA-afb/Alsa-RegEvt.c delete mode 100644 ALSA-afb/Alsa-SetGet.c delete mode 100644 ALSA-afb/Alsa-Ucm.c delete mode 100644 ALSA-afb/CMakeLists.txt delete mode 100644 ALSA-afb/README.md create mode 100644 Alsa-afb/Alsa-AddCtl.c create mode 100644 Alsa-afb/Alsa-ApiHat.c create mode 100644 Alsa-afb/Alsa-ApiHat.h create mode 100644 Alsa-afb/Alsa-RegEvt.c create mode 100644 Alsa-afb/Alsa-SetGet.c create mode 100644 Alsa-afb/Alsa-Ucm.c create mode 100644 Alsa-afb/CMakeLists.txt create mode 100644 Alsa-afb/README.md create mode 100644 Controler-afb/ctl-dispatch.c create mode 100644 Controler-afb/ctl-plugin-sample.c delete mode 100644 Controler-afb/ctl-policy.c diff --git a/ALSA-afb/Alsa-AddCtl.c b/ALSA-afb/Alsa-AddCtl.c deleted file mode 100644 index f7ef92e..0000000 --- a/ALSA-afb/Alsa-AddCtl.c +++ /dev/null @@ -1,372 +0,0 @@ -/* - * AlsaUseCase -- 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://kernel.readthedocs.io/en/sphinx-samples/writing-an-alsa-driver.html#control-names - * https://01.org/linuxgraphics/gfx-docs/drm/sound/designs/control-names.html - - */ -#define _GNU_SOURCE // needed for vasprintf - -#include -#include -#include -#include - -#include "Alsa-ApiHat.h" - -// Performs like a toggle switch for attenuation, because they're bool (ref:user-ctl-element-set.c) - -static const unsigned int *allocate_bool_fake_tlv(void) { - static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(range, -10000, 0); - unsigned int *tlv = malloc(sizeof (range)); - if (tlv == NULL) return NULL; - memcpy(tlv, range, sizeof (range)); - return tlv; -} - -static const unsigned int *allocate_int_dbscale_tlv(int min, int step, int mute) { - // SNDRV_CTL_TLVD_DECLARE_DB_SCALE(range, min, step, mute); - size_t tlvSize = sizeof (4 * sizeof (unsigned int)); - unsigned int *tlv = malloc(tlvSize); - tlv[0] = SNDRV_CTL_TLVT_DB_LINEAR; - tlv[1] = (int) tlvSize; - tlv[2] = min * 100; - tlv[3] = ((step * 100) & SNDRV_CTL_TLVD_DB_SCALE_MASK) | ((mute * 100) ? SNDRV_CTL_TLVD_DB_SCALE_MUTE : 0); - return tlv; -} - -static const unsigned int *allocate_int_linear_tlv(int max, int min) { - // SNDRV_CTL_TLVD_DECLARE_DB_LINEAR (range, min, max); - size_t tlvSize = sizeof (4 * sizeof (unsigned int)); - unsigned int *tlv = malloc(tlvSize); - tlv[0] = SNDRV_CTL_TLVT_DB_LINEAR; - tlv[1] = (int) tlvSize; - tlv[2] = -min * 100; - tlv[3] = max * 100; - return tlv; -} - -STATIC json_object * addOneSndCtl(afb_req request, snd_ctl_t *ctlDev, json_object *ctlJ, halQueryMode queryMode) { - int err, done, ctlNumid, ctlValue, shouldCreate; - json_object *tmpJ; - const char *ctlName; - ctlRequestT ctlRequest; - int ctlMax, ctlMin, ctlStep, ctlCount, ctlSubDev, ctlSndDev; - snd_ctl_elem_type_t ctlType; - snd_ctl_elem_info_t *elemInfo; - snd_ctl_elem_id_t *elemId; - snd_ctl_elem_value_t *elemValue; - const unsigned int *elemTlv = NULL; - - // parse json ctl object - json_object_object_get_ex(ctlJ, "name", &tmpJ); - ctlName = json_object_get_string(tmpJ); - - json_object_object_get_ex(ctlJ, "ctl", &tmpJ); - ctlNumid = json_object_get_int(tmpJ); - - if (!ctlNumid && !ctlName) { - afb_req_fail_f(request, "ctl-invalid", "crl=%s name or numid missing", json_object_get_string(ctlJ)); - goto OnErrorExit; - } - - - // We are facing a software ctl - if (ctlNumid == CTL_AUTO) { - done = json_object_object_get_ex(ctlJ, "type", &tmpJ); - if (done) ctlType = json_object_get_int(tmpJ); - else ctlType = SND_CTL_ELEM_TYPE_INTEGER; - - json_object_object_get_ex(ctlJ, "value", &tmpJ); - ctlValue = json_object_get_int(tmpJ); - - // default for json_object_get_int is zero - json_object_object_get_ex(ctlJ, "min", &tmpJ); - ctlMin = json_object_get_int(tmpJ); - - done = json_object_object_get_ex(ctlJ, "max", &tmpJ); - if (done) ctlMax = json_object_get_int(tmpJ); - else { - if (ctlType == SND_CTL_ELEM_TYPE_BOOLEAN) ctlMax = 1; - else ctlMax = 100; - } - - done = json_object_object_get_ex(ctlJ, "step", &tmpJ); - if (!done) ctlStep = 1; - else ctlStep = json_object_get_int(tmpJ); - - done = json_object_object_get_ex(ctlJ, "count", &tmpJ); - if (!done) ctlCount = 1; - else ctlCount = json_object_get_int(tmpJ); - - json_object_object_get_ex(ctlJ, "snddev", &tmpJ); - ctlSndDev = json_object_get_int(tmpJ); - - json_object_object_get_ex(ctlJ, "subdev", &tmpJ); - ctlSubDev = json_object_get_int(tmpJ); - } - - // Assert that this ctls is not used - snd_ctl_elem_info_alloca(&elemInfo); - if (ctlName) snd_ctl_elem_info_set_name(elemInfo, ctlName); - if (ctlNumid > 0)snd_ctl_elem_info_set_numid(elemInfo, ctlNumid); - snd_ctl_elem_info_set_interface(elemInfo, SND_CTL_ELEM_IFACE_MIXER); - - // If control exist try to reuse it - err = snd_ctl_elem_info(ctlDev, elemInfo); - if (err) { - shouldCreate = 1; - } else { - int count, min, max, sndDev, subDev; - snd_ctl_elem_type_t type; - - // ctl exit let's get associated elemID - snd_ctl_elem_id_alloca(&elemId); - snd_ctl_elem_info_get_id(elemInfo, elemId); - - // If this is a hardware ctl only update value - if (ctlNumid != CTL_AUTO) goto UpdateDefaultVal; - - count = snd_ctl_elem_info_get_count(elemInfo); - min = (int) snd_ctl_elem_info_get_min(elemInfo); - max = (int) snd_ctl_elem_info_get_max(elemInfo); - type = snd_ctl_elem_info_get_type(elemInfo); - sndDev = (int) snd_ctl_elem_info_get_device(elemInfo); - subDev = (int) snd_ctl_elem_info_get_subdevice(elemInfo); - - if (count == ctlCount && min == ctlMin && max == ctlMax && type == ctlType && sndDev == ctlSndDev && subDev == ctlSubDev) { - // Adjust value to best fit request - shouldCreate = 0; - } else { - err = snd_ctl_elem_remove(ctlDev, elemId); - shouldCreate = 1; - if (err < 0) { - AFB_ERROR("addOneSndCtl: ctlName=%s numid=%d fail to reset", snd_ctl_elem_info_get_name(elemInfo), snd_ctl_elem_info_get_numid(elemInfo)); - goto DoNotUpdate; - }; - - // Control was deleted need to refresh elemInfo - snd_ctl_elem_info_alloca(&elemInfo); - if (ctlName) snd_ctl_elem_info_set_name(elemInfo, ctlName); - snd_ctl_elem_info_set_interface(elemInfo, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_info(ctlDev, elemInfo); - } - } - - // Add requested ID into elemInfo - snd_ctl_elem_info_set_device(elemInfo, ctlSndDev); - snd_ctl_elem_info_set_subdevice(elemInfo, ctlSubDev); - - switch (ctlType) { - case SND_CTL_ELEM_TYPE_BOOLEAN: - if (shouldCreate) { - err = snd_ctl_add_boolean_elem_set(ctlDev, elemInfo, 1, ctlCount); - if (err) { - AFB_ERROR("AddOntSndCtl Boolean: devid='%s' name='%s' count=%d", snd_ctl_name(ctlDev), ctlName, ctlCount); - afb_req_fail_f(request, "ctl-invalid-bool", "devid='%s' name='%s' count=%d", snd_ctl_name(ctlDev), ctlName, ctlCount); - goto OnErrorExit; - } - } - - // Create a dummy TLV - elemTlv = allocate_bool_fake_tlv(); - - break; - - case SND_CTL_ELEM_TYPE_INTEGER: - if (shouldCreate) { - err = snd_ctl_add_integer_elem_set(ctlDev, elemInfo, 1, ctlCount, ctlMin, ctlMax, ctlStep); - if (err) { - AFB_ERROR("AddOntSndCtl Integer: devid='%s' name='%s' count=%d min=%d max=%d step=%d", snd_ctl_name(ctlDev), ctlName, ctlCount, ctlMin, ctlMax, ctlStep); - afb_req_fail_f(request, "ctl-invalid-bool", "devid='%s' name='%s' count=%d min=%d max=%d step=%d", snd_ctl_name(ctlDev), ctlName, ctlCount, ctlMin, ctlMax, ctlStep); - goto OnErrorExit; - } - } - - // Fulup needed to be tested with some dB expert !!! - json_object *dbscaleJ; - if (json_object_object_get_ex(ctlJ, "dbscale", &dbscaleJ)) { - int min, max; - - if (json_object_get_type(dbscaleJ) != json_type_object) { - afb_req_fail_f(request, "ctl-invalid-dbscale", "devid=%s crl=%s invalid json in integer control", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); - goto OnErrorExit; - - json_object_object_get_ex(ctlJ, "min", &tmpJ); - min = json_object_get_int(tmpJ); - if (min >= 0) { - afb_req_fail_f(request, "ctl-invalid-dbscale", "devid=%s crl=%s min should be a negative number", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); - goto OnErrorExit; - } - - // default value 0=mute - json_object_object_get_ex(ctlJ, "max", &tmpJ); - max = json_object_get_int(tmpJ); - - // default value 1dB - json_object_object_get_ex(ctlJ, "step", &tmpJ); - int step = json_object_get_int(tmpJ); - if (step <= 0) step = 1; - - elemTlv = allocate_int_dbscale_tlv(min, max, step); - - } - } else { - // provide a fake linear TLV - elemTlv = allocate_int_linear_tlv(ctlMin, ctlMax); - } - break; - - case SND_CTL_ELEM_TYPE_ENUMERATED: - - if (shouldCreate) { - json_object *enumsJ; - json_object_object_get_ex(ctlJ, "enums", &enumsJ); - if (json_object_get_type(enumsJ) != json_type_array) { - afb_req_fail_f(request, "ctl-missing-enums", "devid=%s crl=%s mandatory enum=xxx missing in enumerated control", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); - goto OnErrorExit; - } - - int length = json_object_array_length(enumsJ); - const char **enumlabels = malloc(length * sizeof (char*)); - - for (int jdx = 0; jdx < length; jdx++) { - tmpJ = json_object_array_get_idx(enumsJ, jdx); - enumlabels[jdx] = json_object_get_string(tmpJ); - } - - err = snd_ctl_add_enumerated_elem_set(ctlDev, elemInfo, 1, ctlCount, length, enumlabels); - if (err) { - afb_req_fail_f(request, "ctl-invalid-bool", "devid=%s crl=%s invalid enumerated control", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); - goto OnErrorExit; - } - - // Fulup should be an empty container - elemTlv = allocate_bool_fake_tlv(); - } - break; - - // Long Term Waiting ToDoList - case SND_CTL_ELEM_TYPE_INTEGER64: - case SND_CTL_ELEM_TYPE_BYTES: - default: - afb_req_fail_f(request, "ctl-invalid-type", "crl=%s unsupported type type=%d numid=%d", json_object_get_string(ctlJ), ctlType, ctlNumid); - goto OnErrorExit; - } - -UpdateDefaultVal: - - // Set Value to default - snd_ctl_elem_value_alloca(&elemValue); - for (int idx = 0; idx < snd_ctl_elem_info_get_count(elemInfo); idx++) { - snd_ctl_elem_value_set_integer(elemValue, idx, ctlValue); - } - - // write default values in newly created control - snd_ctl_elem_id_alloca(&elemId); - snd_ctl_elem_info(ctlDev, elemInfo); - snd_ctl_elem_info_get_id(elemInfo, elemId); - snd_ctl_elem_value_set_id(elemValue, elemId); - err = snd_ctl_elem_write(ctlDev, elemValue); - if (err < 0) { - AFB_WARNING("addOneSndCtl: crl=%s numid=%d fail to write Values error=%s", json_object_get_string(ctlJ), snd_ctl_elem_info_get_numid(elemInfo), snd_strerror(err)); - goto DoNotUpdate; - } - - // write a default null TLV (if usefull should be implemented for every ctl type) - if (elemTlv) { - err = snd_ctl_elem_tlv_write(ctlDev, elemId, elemTlv); - if (err < 0) { - AFB_WARNING("addOneSndCtl: crl=%s numid=%d fail to write TLV error=%s", json_object_get_string(ctlJ), snd_ctl_elem_info_get_numid(elemInfo), snd_strerror(err)); - goto DoNotUpdate; - } - } - -DoNotUpdate: - // return newly created as a JSON object - alsaGetSingleCtl(ctlDev, elemId, &ctlRequest, queryMode); - if (ctlRequest.used < 0) { - AFB_WARNING("addOneSndCtl: crl=%s numid=%d Fail to get value", json_object_get_string(ctlJ), snd_ctl_elem_info_get_numid(elemInfo)); - } - return ctlRequest.valuesJ; - -OnErrorExit: - return NULL; -} - -PUBLIC void alsaAddCustomCtls(afb_req request) { - int err; - json_object *ctlsJ, *ctlsValues, *ctlValues; - enum json_type; - snd_ctl_t *ctlDev = NULL; - const char *devid, *mode; - - devid = afb_req_value(request, "devid"); - if (devid == NULL) { - afb_req_fail_f(request, "devid-missing", "devid MUST be defined for alsaAddCustomCtls"); - goto OnErrorExit; - } - - // open control interface for devid - err = snd_ctl_open(&ctlDev, devid, 0); - if (err < 0) { - afb_req_fail_f(request, "devid-unknown", "SndCard devid=[%s] Not Found err=%s", devid, snd_strerror(err)); - goto OnErrorExit; - } - - // get verbosity level - halQueryMode queryMode = QUERY_QUIET; - mode = afb_req_value(request, "mode"); - if (mode != NULL) { - sscanf(mode, "%i", (int*) &queryMode); - } - - // extract sound controls and parse json - ctlsJ = json_tokener_parse(afb_req_value(request, "ctl")); - if (!ctlsJ) { - afb_req_fail_f(request, "ctls-missing", "ctls MUST be defined as a JSON array for alsaAddCustomCtls"); - goto OnErrorExit; - } - - switch (json_object_get_type(ctlsJ)) { - case json_type_object: - ctlsValues = addOneSndCtl(request, ctlDev, ctlsJ, queryMode); - if (!ctlsValues) goto OnErrorExit; - break; - - case json_type_array: - ctlsValues = json_object_new_array(); - for (int idx = 0; idx < json_object_array_length(ctlsJ); idx++) { - json_object *ctlJ = json_object_array_get_idx(ctlsJ, idx); - ctlValues = addOneSndCtl(request, ctlDev, ctlJ, queryMode); - if (ctlValues) json_object_array_add(ctlsValues, ctlValues); - else goto OnErrorExit; - } - break; - - default: - afb_req_fail_f(request, "ctls-invalid", "ctls=%s not valid JSON array", json_object_get_string(ctlsJ)); - goto OnErrorExit; - } - - // get ctl as a json response - afb_req_success(request, ctlsValues, NULL); - -OnErrorExit: - if (ctlDev) snd_ctl_close(ctlDev); - return; -} \ No newline at end of file diff --git a/ALSA-afb/Alsa-ApiHat.c b/ALSA-afb/Alsa-ApiHat.c deleted file mode 100644 index 2c3c5c1..0000000 --- a/ALSA-afb/Alsa-ApiHat.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 "IoT.bzh" - * Author Fulup Ar Foll - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Alsa-ApiHat.h" - -/* - * array of the verbs exported to afb-daemon - */ -static const struct afb_verb_v2 api_verbs[] = { - /* VERB'S NAME FUNCTION TO CALL */ - { .verb = "ping", .callback = pingtest}, - { .verb = "getinfo", .callback = alsaGetInfo}, - { .verb = "getctl", .callback = alsaGetCtls}, - { .verb = "setctl", .callback = alsaSetCtls}, - { .verb = "subscribe", .callback = alsaEvtSubcribe}, - { .verb = "getcardid", .callback = alsaGetCardId}, - { .verb = "halregister", .callback = alsaRegisterHal}, - { .verb = "hallist", .callback = alsaActiveHal}, - { .verb = "ucmquery", .callback = alsaUseCaseQuery}, - { .verb = "ucmset", .callback = alsaUseCaseSet}, - { .verb = "ucmget", .callback = alsaUseCaseGet}, - { .verb = "ucmreset", .callback = alsaUseCaseReset}, - { .verb = "ucmclose", .callback = alsaUseCaseClose}, - { .verb = "addcustomctl", .callback = alsaAddCustomCtls}, - { .verb = NULL} /* marker for end of the array */ -}; - -/* - * description of the binding for afb-daemon - */ -const struct afb_binding_v2 afbBindingV2 = { - .api = "alsacore", - .verbs = api_verbs, -}; diff --git a/ALSA-afb/Alsa-ApiHat.h b/ALSA-afb/Alsa-ApiHat.h deleted file mode 100644 index b05cfbe..0000000 --- a/ALSA-afb/Alsa-ApiHat.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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. - */ - - -#ifndef ALSALIBMAPPING_H -#define ALSALIBMAPPING_H - - -#include -#include -#include "audio-common.h" - -typedef enum { - ACTION_SET, - ACTION_GET -} ActionSetGetT; - -// generic structure to pass parsed query values -typedef struct { - const char *devid; - json_object *numidsJ; - halQueryMode mode; - int count; -} queryValuesT; - -// use to store crl numid user request -typedef struct { - unsigned int numId; - json_object *jToken; - json_object *valuesJ; - int used; -} ctlRequestT; - -// import from AlsaAfbBinding -extern const struct afb_binding_interface *afbIface; -PUBLIC json_object *alsaCheckQuery (struct afb_req request, queryValuesT *queryValues); - -// AlseCoreSetGet exports -PUBLIC int alsaGetSingleCtl (snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, ctlRequestT *ctlRequest, halQueryMode queryMode); -PUBLIC void alsaGetInfo (struct afb_req request); -PUBLIC void alsaGetCtls(struct afb_req request); -PUBLIC void alsaSetCtls(struct afb_req request); - - -// AlsaUseCase exports -PUBLIC void alsaUseCaseQuery(struct afb_req request); -PUBLIC void alsaUseCaseSet(struct afb_req request); -PUBLIC void alsaUseCaseGet(struct afb_req request); -PUBLIC void alsaUseCaseClose(struct afb_req request); -PUBLIC void alsaUseCaseReset(struct afb_req request); -PUBLIC void alsaAddCustomCtls(struct afb_req request); - -// AlsaRegEvt -PUBLIC void alsaEvtSubcribe (struct afb_req request); -PUBLIC void alsaGetCardId (struct afb_req request); -PUBLIC void alsaRegisterHal (struct afb_req request); -PUBLIC void alsaActiveHal (struct afb_req request); - -#endif /* ALSALIBMAPPING_H */ - diff --git a/ALSA-afb/Alsa-RegEvt.c b/ALSA-afb/Alsa-RegEvt.c deleted file mode 100644 index 080b6cc..0000000 --- a/ALSA-afb/Alsa-RegEvt.c +++ /dev/null @@ -1,395 +0,0 @@ -/* - * 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 "Alsa-ApiHat.h" - -// 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; - struct afb_event afbevt; -} evtHandleT; - -typedef struct { - int ucount; - int cardId; - evtHandleT *evtHandle; -} sndHandleT; - -typedef struct { - char *devid; - char *apiprefix; - char *shortname; - char *longname; -} cardRegistryT; - -cardRegistryT *cardRegistry[MAX_SND_CARD + 1]; - -PUBLIC json_object *alsaCheckQuery(afb_req request, queryValuesT *queryValues) { - - json_object *tmpJ; - int done; - - // get query from request - json_object *queryInJ = afb_req_json(request); - - done = json_object_object_get_ex(queryInJ, "devid", &tmpJ); - if (!done) { - afb_req_fail_f(request, "devid-missing", "Invalid query='%s'", json_object_get_string(queryInJ)); - goto OnErrorExit; - } - queryValues->devid = json_object_get_string(tmpJ); - - done = json_object_object_get_ex(queryInJ, "mode", &tmpJ); - if (!done) queryValues->mode = QUERY_QUIET; // default quiet - else queryValues->mode = json_object_get_int(tmpJ); - - return queryInJ; - -OnErrorExit: - return NULL; -} - -// This routine is called when ALSA event are fired - -STATIC int sndCtlEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { - int err, ctlNumid; - evtHandleT *evtHandle = (evtHandleT*) userData; - 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 & 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: ignored unsupported event type"); - return (0); -} - -// Subscribe to every Alsa CtlEvent send by a given board - -PUBLIC void alsaEvtSubcribe(afb_req request) { - static sndHandleT sndHandles[MAX_SND_CARD]; - evtHandleT *evtHandle = NULL; - snd_ctl_t *ctlDev = NULL; - int err, idx, cardId, idxFree = -1; - snd_ctl_card_info_t *cardinfo; - queryValuesT queryValues; - - json_object *queryJ = alsaCheckQuery(request, &queryValues); - if (!queryJ) goto OnErrorExit; - - // open control interface for devid - err = snd_ctl_open(&ctlDev, queryValues.devid, SND_CTL_READONLY); - if (err < 0) { - afb_req_fail_f(request, "devid-unknown", "SndCard devid=%s Not Found err=%s", queryValues.devid, snd_strerror(err)); - goto OnErrorExit; - } - - snd_ctl_card_info_alloca(&cardinfo); - if ((err = snd_ctl_card_info(ctlDev, cardinfo)) < 0) { - afb_req_fail_f(request, "devid-invalid", "SndCard devid=%s Not Found err=%s", queryValues.devid, snd_strerror(err)); - goto OnErrorExit; - } - - cardId = snd_ctl_card_info_get_card(cardinfo); - - // search for an existing subscription and mark 1st free slot - for (idx = 0; idx < MAX_SND_CARD; idx++) { - if (sndHandles[idx].ucount > 0 && cardId == sndHandles[idx].cardId) { - evtHandle = sndHandles[idx].evtHandle; - break; - } else if (idxFree == -1) idxFree = idx; - }; - - // if not subscription exist for the event let's create one - if (idx == MAX_SND_CARD) { - - // reach MAX_SND_CARD event registration - if (idxFree == -1) { - afb_req_fail_f(request, "register-toomany", "Cannot register new event Maxcard==%d", idx); - goto OnErrorExit; - } - - evtHandle = malloc(sizeof (evtHandleT)); - evtHandle->ctlDev = ctlDev; - evtHandle->mode = queryValues.mode; - sndHandles[idxFree].ucount = 0; - sndHandles[idxFree].cardId = cardId; - sndHandles[idxFree].evtHandle = evtHandle; - - // subscribe for sndctl events attached to devid - err = snd_ctl_subscribe_events(evtHandle->ctlDev, 1); - if (err < 0) { - afb_req_fail_f(request, "subscribe-fail", "Cannot subscribe events from devid=%s err=%d", queryValues.devid, err); - goto OnErrorExit; - } - - // get pollfd attach to this sound board - snd_ctl_poll_descriptors(evtHandle->ctlDev, &evtHandle->pfds, 1); - - // register sound event to binder main loop - err = sd_event_add_io(afb_daemon_get_event_loop(), &evtHandle->src, evtHandle->pfds.fd, EPOLLIN, sndCtlEventCB, evtHandle); - if (err < 0) { - afb_req_fail_f(request, "register-mainloop", "Cannot hook events to mainloop devid=%s err=%d", queryValues.devid, err); - goto OnErrorExit; - } - - // create binder event attached to devid name - evtHandle->afbevt = afb_daemon_make_event(queryValues.devid); - if (!afb_event_is_valid(evtHandle->afbevt)) { - afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", queryValues.devid); - goto OnErrorExit; - } - - // everything looks OK let's move forward - idx = idxFree; - } - - // subscribe to binder event - err = afb_req_subscribe(request, evtHandle->afbevt); - if (err != 0) { - afb_req_fail_f(request, "register-eventname", "Cannot subscribe binder event name=%s [invalid channel]", queryValues.devid); - goto OnErrorExit; - } - - // increase usage count and return success - sndHandles[idx].ucount++; - afb_req_success(request, NULL, NULL); - return; - -OnErrorExit: - if (ctlDev) snd_ctl_close(ctlDev); - return; -} - -// Subscribe to every Alsa CtlEvent send by a given board - -STATIC json_object *alsaProbeCardId(afb_req request) { - char devid [10]; - const char *ctlName, *shortname, *longname; - int card, err, index, idx; - json_object *responseJ; - snd_ctl_t *ctlDev; - snd_ctl_card_info_t *cardinfo; - - const char *sndname = afb_req_value(request, "sndname"); - if (sndname == NULL) { - afb_req_fail_f(request, "argument-missing", "sndname=SndCardName missing"); - goto OnErrorExit; - } - - // loop on potential card number - snd_ctl_card_info_alloca(&cardinfo); - 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); - - // check if short|long name match - if (!strcmp(sndname, ctlName)) break; - if (!strcmp(sndname, shortname)) break; - if (!strcmp(sndname, longname)) break; - } - - if (card == MAX_SND_CARD) { - afb_req_fail_f(request, "ctlDev-notfound", "Fail to find card with name=%s", sndname); - goto OnErrorExit; - } - - // 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, "devid", json_object_new_string(devid)); - json_object_object_add(responseJ, "shortname", json_object_new_string(shortname)); - json_object_object_add(responseJ, "longname", json_object_new_string(longname)); - - // search for a HAL binder card mapping name to api prefix - for (idx = 0; (idx < MAX_SND_CARD && 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 request) { - - json_object *responseJ = alsaProbeCardId(request); - if (responseJ) afb_req_success(request, responseJ, NULL); -} - -// Return list of active resgistrated HAL with corresponding sndcard - -PUBLIC void alsaActiveHal(afb_req request) { - json_object *responseJ = json_object_new_array(); - - for (int idx = 0; idx < MAX_SND_CARD; 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]->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 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_CARD) { - 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); - - 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, "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; -} - diff --git a/ALSA-afb/Alsa-SetGet.c b/ALSA-afb/Alsa-SetGet.c deleted file mode 100644 index 04f2929..0000000 --- a/ALSA-afb/Alsa-SetGet.c +++ /dev/null @@ -1,809 +0,0 @@ -/* - * 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 "Alsa-ApiHat.h" - -PUBLIC void NumidsListParse(ActionSetGetT action, queryValuesT *queryValues, ctlRequestT *ctlRequest) { - int length; - - for (int idx = 0; idx < queryValues->count; idx++) { - json_object *jId, *valuesJ; - ctlRequest[idx].used = 0; - 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(queryValues->numidsJ)) ctlRequest[idx].jToken = json_object_array_get_idx(queryValues->numidsJ, idx); - else ctlRequest[idx].jToken = queryValues->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 && queryValues->count == 2) { - ctlRequest[idx].valuesJ = json_object_array_get_idx(queryValues->numidsJ, 1); - queryValues->count = 1; //In this form count==2 , when only one numid is to set - idx++; - continue; - } else - break; - - case json_type_array: - // NUMID is an array 1st slot should be numid, optionally values may come after - length = 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) || !json_object_object_get_ex(ctlRequest[idx].jToken, "val", &valuesJ)) { - AFB_NOTICE("Invalid Json=%s missing 'id'|'val'", json_object_get_string(ctlRequest[idx].jToken)); - ctlRequest[idx].used = -1; - } else { - ctlRequest[idx].numId = json_object_get_int(jId); - if (action == ACTION_SET) ctlRequest[idx].valuesJ = valuesJ; - } - break; - - default: - ctlRequest[idx].used = -1; - } - } -} - -STATIC json_object *DB2StringJsonOject(long dB) { - char label [20]; - 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); -} - - -// retreive info for one given card - -STATIC json_object* alsaCardProbe(const char *rqtSndId) { - const char *info, *name; - const char *devid, *driver; - json_object *ctlDev; - snd_ctl_t *handle; - snd_ctl_card_info_t *cardinfo; - int err; - - if ((err = snd_ctl_open(&handle, rqtSndId, 0)) < 0) { - AFB_INFO("alsaCardProbe '%s' Not Found", rqtSndId); - return NULL; - } - - snd_ctl_card_info_alloca(&cardinfo); - if ((err = snd_ctl_card_info(handle, cardinfo)) < 0) { - snd_ctl_close(handle); - AFB_WARNING("SndCard '%s' info error: %s", rqtSndId, snd_strerror(err)); - return NULL; - } - - // start a new json object to store card info - ctlDev = json_object_new_object(); - - devid = snd_ctl_card_info_get_id(cardinfo); - json_object_object_add(ctlDev, "devid", json_object_new_string(devid)); - name = snd_ctl_card_info_get_name(cardinfo); - json_object_object_add(ctlDev, "name", json_object_new_string(name)); - - if (AFB_GET_VERBOSITY > 1) { - json_object_object_add(ctlDev, "devid", json_object_new_string(rqtSndId)); - driver = snd_ctl_card_info_get_driver(cardinfo); - json_object_object_add(ctlDev, "driver", json_object_new_string(driver)); - info = strdup(snd_ctl_card_info_get_longname(cardinfo)); - json_object_object_add(ctlDev, "info", json_object_new_string(info)); - AFB_INFO("AJG: Soundcard Devid=%-5s devid=%-7s Name=%s\n", rqtSndId, devid, info); - } - - // free card handle and return info - snd_ctl_close(handle); - return (ctlDev); -} - -// Loop on every potential Sound card and register active one - -PUBLIC void alsaGetInfo(afb_req request) { - int card; - json_object *ctlDev, *ctlDevs; - char devid[32]; - - const char *rqtSndId = afb_req_value(request, "devid"); - - // if no specific card requested loop on all - - if (rqtSndId != NULL) { - // only one card was requested let's probe it - ctlDev = alsaCardProbe(rqtSndId); - if (ctlDev != NULL) afb_req_success(request, ctlDev, NULL); - else afb_req_fail_f(request, "sndscard-notfound", "SndCard '%s' Not Found", rqtSndId); - - } else { - // return an array of ctlDev - ctlDevs = json_object_new_array(); - - // loop on potential card number - for (card = 0; card < MAX_SND_CARD; card++) { - - // build card devid and probe it - snprintf(devid, sizeof (devid), "hw:%i", card); - ctlDev = alsaCardProbe(devid); - - // Alsa has hole within card list [ignore them] - if (ctlDev != NULL) { - // add current ctlDev to ctlDevs object - json_object_array_add(ctlDevs, ctlDev); - } - } - afb_req_success(request, ctlDevs, NULL); - } -} - -// 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 = 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_NOTICE("Invalid values NUMID='%d' Values='%s' count='%d' wanted='%d'", ctlRequest->numId, json_object_get_string(ctlRequest->valuesJ), length, count); - goto OnErrorExit; - } - - 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_NOTICE("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, halQueryMode 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 request) { - ctlRequestT *ctlRequest; - const char *warmsg = NULL; - int err = 0, status = 0, done; - unsigned int ctlCount; - snd_ctl_t *ctlDev; - snd_ctl_elem_list_t *ctlList; - queryValuesT queryValues; - json_object *queryJ, *numidsJ, *sndctls; - - queryJ = alsaCheckQuery(request, &queryValues); - if (!queryJ) goto OnErrorExit; - - // Prase Numids + optional values - done = json_object_object_get_ex(queryJ, "ctl", &numidsJ); - if (!done) queryValues.count = 0; - else { - enum json_type jtype = json_object_get_type(numidsJ); - switch (jtype) { - case json_type_array: - queryValues.numidsJ = numidsJ; - queryValues.count = json_object_array_length(numidsJ); - break; - - case json_type_int: - case json_type_object: - queryValues.count = 1; - queryValues.numidsJ = numidsJ; - break; - - default: - afb_req_fail_f(request, "numid-notarray", "NumId=%s NumId not valid JSON array", json_object_get_string(numidsJ)); - goto OnErrorExit; - } - } - - if ((err = snd_ctl_open(&ctlDev, queryValues.devid, 0)) < 0) { - afb_req_fail_f(request, "sndcrl-notfound", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); - goto OnErrorExit; - } - - 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", queryValues.devid, snd_strerror(err)); - goto OnErrorExit; - } - - 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", queryValues.devid, snd_strerror(err)); - goto OnErrorExit; - } - - if ((err = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - afb_req_fail_f(request, "listOpen-failed", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); - goto OnErrorExit; - } - - // Parse numids string (empty == all) - ctlCount = snd_ctl_elem_list_get_used(ctlList); - if (queryValues.count == 0) { - ctlRequest = alloca(sizeof (ctlRequestT)*(ctlCount)); - } else { - ctlRequest = alloca(sizeof (ctlRequestT)*(queryValues.count)); - NumidsListParse(action, &queryValues, ctlRequest); - } - - // if more than one crl requested prepare an array for response - if (queryValues.count != 1 && action == ACTION_GET) sndctls = json_object_new_array(); - else sndctls = NULL; - - // Loop on all ctlDev controls - for (int ctlIndex = 0; ctlIndex < ctlCount; ctlIndex++) { - unsigned int selected = 0; - int jdx; - - if (queryValues.count == 0 && action == ACTION_GET) { - selected = 1; // check is this numid is selected within query - jdx = ctlIndex; // map all existing ctl as requested - } else { - int numid = snd_ctl_elem_list_get_numid(ctlList, ctlIndex); - if (numid < 0) { - AFB_NOTICE("snd_ctl_elem_list_get_numid index=%d fail", ctlIndex); - continue; - } - // check if current control was requested in query numids list - for (jdx = 0; jdx < queryValues.count; jdx++) { - if (numid == ctlRequest[jdx].numId) { - selected = 1; - break; - } - } - } - - // control is selected open ctlid and get value - if (selected) { - 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], queryValues.mode); - break; - - case ACTION_SET: - err = alsaSetSingleCtl(ctlDev, elemId, &ctlRequest[jdx]); - break; - - default: - err = 1; - } - if (err) status++; - else { - // Do not embed response in an array when only one ctl was requested - if (action == ACTION_GET) { - if (queryValues.count > 1) json_object_array_add(sndctls, ctlRequest[jdx].valuesJ); - else sndctls = ctlRequest[jdx].valuesJ; - } - } - } - } - - // if we had error let's add them into response message info - json_object *warningsJ = json_object_new_array(); - for (int jdx = 0; jdx < queryValues.count; jdx++) { - if (ctlRequest[jdx].used <= 0) { - json_object *failctl = json_object_new_object(); - if (ctlRequest[jdx].numId == -1) json_object_object_add(failctl, "warning", json_object_new_string("Numid Invalid")); - else { - if (ctlRequest[jdx].used == 0) json_object_object_add(failctl, "warning", json_object_new_string("Numid Does Not Exist")); - if (ctlRequest[jdx].used == -1) json_object_object_add(failctl, "warning", json_object_new_string("Value Refused")); - } - - json_object_object_add(failctl, "ctl", ctlRequest[jdx].jToken); - - json_object_array_add(warningsJ, failctl); - } - /* WARNING!!!! Check with Jose why following put free valuesJ - if (ctlRequest[jdx].jToken) json_object_put(ctlRequest[jdx].jToken); - if (ctlRequest[jdx].valuesJ) json_object_put(ctlRequest[jdx].valuesJ); - */ - } - - if (json_object_array_length(warningsJ) > 0) warmsg = json_object_get_string(warningsJ); - else json_object_put(warningsJ); - - // send response+warning if any - afb_req_success(request, sndctls, warmsg); - snd_ctl_elem_list_clear(ctlList); - -OnErrorExit: - return; -} - -PUBLIC void alsaGetCtls(afb_req request) { - alsaSetGetCtls(ACTION_GET, request); -} - -PUBLIC void alsaSetCtls(afb_req request) { - alsaSetGetCtls(ACTION_SET, request); -} - - diff --git a/ALSA-afb/Alsa-Ucm.c b/ALSA-afb/Alsa-Ucm.c deleted file mode 100644 index 016fe10..0000000 --- a/ALSA-afb/Alsa-Ucm.c +++ /dev/null @@ -1,433 +0,0 @@ -/* - * AlsaUseCase -- 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: - http://www.alsa-project.org/alsa-doc/alsa-lib/group__ucm.html - https://www.alsa-project.org/main/index.php/DAPM - http://alsa-lib.sourcearchive.com/documentation/1.0.24.1-2/group__Use_ga4332c6bb50481bbdaf21be11551fb930.html - https://android.googlesource.com/platform/hardware/qcom/audio/+/jb-mr1-dev/libalsa-intf/alsa_ucm.h - - Sample alsaucm commands using /usr/share/alsa/ucm/PandaBoard - - alsaucm -c PandaBoard list _verbs - - alsaucm -c PandaBoard list _devices/HiFi - - alsaucm -c PandaBoard list _modifiers/HiFi #need to uncomment modifiers section - - alsaucm -c PandaBoard list TQ/HiFi - - alsaucm -c PandaBoard get TQ/HiFi/Voice - - alsaucm -c PandaBoard get PlaybackPCM//HiFi - - alsaucm -c PandaBoard set _verb HiFi - - alsaucm -c PandaBoard set _verb HiFi _enadev Headset - - alsaucm -c 'HDA Intel PCH' set _verb HiFi set _enadev Headphone set _enamod RecordMedia - - alsaucm -c 'HDA Intel PCH' set _verb HiFi get OutputDspName// - - */ -#define _GNU_SOURCE // needed for vasprintf - -#include -#include -#include - -#include "Alsa-ApiHat.h" - -typedef struct { - snd_use_case_mgr_t *ucm; - int cardId; - char *cardName; -} ucmHandleT; - -static ucmHandleT ucmHandles[MAX_SND_CARD]; - -// Cache opened UCM handles - -STATIC int alsaUseCaseOpen(struct afb_req request, queryValuesT *queryValues, int allowNewMgr) { - snd_ctl_t *ctlDev; - snd_ctl_card_info_t *cardinfo; - snd_use_case_mgr_t *ucmHandle; - const char *cardName; - int cardId, idx, idxFree = -1, err; - - // open control interface for devid - err = snd_ctl_open(&ctlDev, queryValues->devid, SND_CTL_READONLY); - if (err < 0) { - ctlDev = NULL; - afb_req_fail_f(request, "devid-unknown", "SndCard devid=[%s] Not Found err=%d", queryValues->devid, err); - goto OnErrorExit; - } - - snd_ctl_card_info_alloca(&cardinfo); - if ((err = snd_ctl_card_info(ctlDev, cardinfo)) < 0) { - afb_req_fail_f(request, "devid-invalid", "SndCard devid=[%s] Not Found err=%s", queryValues->devid, snd_strerror(err)); - goto OnErrorExit; - } - - // search for an existing subscription and mark 1st free slot - cardId = snd_ctl_card_info_get_card(cardinfo); - for (idx = 0; idx < MAX_SND_CARD; idx++) { - if (ucmHandles[idx].ucm != NULL) { - if (ucmHandles[idx].cardId == cardId) goto OnSuccessExit; - } else if (idxFree == -1) idxFree = idx; - }; - - if (!allowNewMgr) { - afb_req_fail_f(request, "ucm-nomgr", "SndCard devid=[%s] no exiting UCM manager session", queryValues->devid); - goto OnErrorExit; - } - - if (idxFree < 0 && idx == MAX_SND_CARD) { - afb_req_fail_f(request, "ucm-toomany", "SndCard devid=[%s] too many open UCM Max=%d", queryValues->devid, MAX_SND_CARD); - goto OnErrorExit; - } - - idx = idxFree; - cardName = snd_ctl_card_info_get_name(cardinfo); - err = snd_use_case_mgr_open(&ucmHandle, cardName); - if (err) { - afb_req_fail_f(request, "ucm-open", "SndCard devid=[%s] name=[%s] No UCM Profile err=%s", queryValues->devid, cardName, snd_strerror(err)); - goto OnErrorExit; - } - ucmHandles[idx].ucm = ucmHandle; - ucmHandles[idx].cardId = cardId; - ucmHandles[idx].cardName = strdup(cardName); - -OnSuccessExit: - if (ctlDev) snd_ctl_close(ctlDev); - return idx; - -OnErrorExit: - if (ctlDev) snd_ctl_close(ctlDev); - return -1; -} - -PUBLIC void alsaUseCaseQuery(struct afb_req request) { - int verbCount, ucmIdx; - const char **verbList; - snd_use_case_mgr_t *ucmHandle; - queryValuesT queryValues; - json_object *ucmJs; - const char *cardName; - - json_object *queryJ = alsaCheckQuery(request, &queryValues); - if (!queryJ) goto OnErrorExit; - - ucmIdx = alsaUseCaseOpen(request, &queryValues, TRUE); - if (ucmIdx < 0) goto OnErrorExit; - ucmHandle = ucmHandles [ucmIdx].ucm; - cardName = ucmHandles [ucmIdx].cardName; - - verbCount = snd_use_case_get_list(ucmHandle, "_verbs", &verbList); - if (verbCount < 0) { - afb_req_fail_f(request, "ucm-list", "SndCard devid=[%s] name=[%s] No UCM Verbs", queryValues.devid, cardName); - goto OnErrorExit; - } - - ucmJs = json_object_new_array(); - for (int idx = 0; idx < verbCount; idx += 2) { - int devCount, modCount, tqCount; - const char **devList, **modList, **tqList; - json_object *ucmJ = json_object_new_object(); - char identifier[32]; - - json_object_object_add(ucmJ, "verb", json_object_new_string(verbList[idx])); - if (verbList[idx + 1]) json_object_object_add(ucmJ, "info", json_object_new_string(verbList[idx + 1])); - - AFB_DEBUG("Verb[%d] Action=%s Info=%s", idx, verbList[idx], verbList[idx + 1]); - - snprintf(identifier, sizeof (identifier), "_devices/%s", verbList[idx]); - devCount = snd_use_case_get_list(ucmHandle, identifier, &devList); - if (devCount > 0) { - json_object *devsJ = json_object_new_array(); - - for (int jdx = 0; jdx < devCount; jdx += 2) { - json_object *devJ = json_object_new_object(); - AFB_DEBUG("device[%d] Action=%s Info=%s", jdx, devList[jdx], devList[jdx + 1]); - json_object_object_add(devJ, "dev", json_object_new_string(devList[jdx])); - if (devList[jdx + 1]) json_object_object_add(devJ, "info", json_object_new_string(devList[jdx + 1])); - json_object_array_add(devsJ, devJ); - } - json_object_object_add(ucmJ, "devices", devsJ); - snd_use_case_free_list(devList, devCount); - } - - snprintf(identifier, sizeof (identifier), "_modifiers/%s", verbList[idx]); - modCount = snd_use_case_get_list(ucmHandle, identifier, &modList); - if (modCount > 0) { - json_object *modsJ = json_object_new_array(); - - for (int jdx = 0; jdx < modCount; jdx += 2) { - json_object *modJ = json_object_new_object(); - AFB_DEBUG("modifier[%d] Action=%s Info=%s", jdx, modList[jdx], modList[jdx + 1]); - json_object_object_add(modJ, "mod", json_object_new_string(modList[jdx])); - if (modList[jdx + 1]) json_object_object_add(modJ, "info", json_object_new_string(modList[jdx + 1])); - json_object_array_add(modsJ, modJ); - } - json_object_object_add(ucmJ, "modifiers", modsJ); - snd_use_case_free_list(modList, modCount); - } - - snprintf(identifier, sizeof (identifier), "TQ/%s", verbList[idx]); - tqCount = snd_use_case_get_list(ucmHandle, identifier, &tqList); - if (tqCount > 0) { - json_object *tqsJ = json_object_new_array(); - - for (int jdx = 0; jdx < tqCount; jdx += 2) { - json_object *tqJ = json_object_new_object(); - AFB_DEBUG("toneqa[%d] Action=%s Info=%s", jdx, tqList[jdx], tqList[jdx + 1]); - json_object_object_add(tqJ, "tq", json_object_new_string(tqList[jdx])); - if (tqList[jdx + 1]) json_object_object_add(tqJ, "info", json_object_new_string(tqList[jdx + 1])); - json_object_array_add(tqsJ, tqJ); - } - json_object_object_add(ucmJ, "tqs", tqsJ); - snd_use_case_free_list(tqList, tqCount); - } - - json_object_array_add(ucmJs, ucmJ); - } - - afb_req_success(request, ucmJs, NULL); - snd_use_case_free_list(verbList, verbCount); - -OnErrorExit: - return; -} - -STATIC json_object *ucmGetValue(ucmHandleT *ucmHandle, const char *verb, const char *mod, const char *label) { - char identifier[80]; - char *value; - int err; - json_object *jValue; - - // handle optional parameters - if (!mod) mod = ""; - if (!verb) verb = ""; - - if (!label) { - AFB_NOTICE("ucmGetValue cardname=[%s] value label missing", ucmHandle->cardName); - goto OnErrorExit; - } - - snprintf(identifier, sizeof (identifier), "%s/%s/%s", label, mod, verb); - err = snd_use_case_get(ucmHandle->ucm, identifier, (const char**) &value); // Note: value casting is a known "FEATURE" of AlsaUCM API - if (err) { - AFB_DEBUG("ucmGetValue cardname=[%s] identifier=[%s] error=%s", ucmHandle->cardName, identifier, snd_strerror(err)); - goto OnErrorExit; - } - - // copy value into json object and free string - jValue = json_object_new_string(value); - free(value); - return (jValue); - -OnErrorExit: - return (NULL); -} - -PUBLIC void alsaUseCaseGet(struct afb_req request) { - int ucmIdx, labelCount; - queryValuesT queryValues; - json_object *jResponse = json_object_new_object(); - json_object *jWarnings = json_object_new_array(); - const char *warnings = NULL; - - json_object *queryJ = alsaCheckQuery(request, &queryValues); - if (!queryJ) goto OnErrorExit; - ; - - ucmIdx = alsaUseCaseOpen(request, &queryValues, TRUE); - if (ucmIdx < 0) goto OnErrorExit; - - const char *cardName = ucmHandles[ucmIdx].cardName; - - const char *verb = afb_req_value(request, "verb"); - const char *mod = afb_req_value(request, "mod"); - const char *dev = afb_req_value(request, "dev"); - - if (dev && mod) { - afb_req_fail_f(request, "ucmget-labels", "SndCard devid=[%s] name=[%s] UCM mod+dev incompatible", queryValues.devid, cardName); - goto OnErrorExit; - } - - // device selection is handle as a modifier - if (dev) mod = dev; - - const char *labels = afb_req_value(request, "value"); - if (!labels) { - afb_req_fail_f(request, "ucmget-labels", "SndCard devid=[%s] name=[%s] UCM values name missing", queryValues.devid, cardName); - goto OnErrorExit; - } - - json_object *jLabels = json_tokener_parse(labels); - if (!jLabels) { - afb_req_fail_f(request, "ucmget-notjson", "labels=%s not a valid json entry", labels); - goto OnErrorExit; - }; - - enum json_type jtype = json_object_get_type(jLabels); - switch (jtype) { - json_object *tmpJ; - - case json_type_array: - labelCount = json_object_array_length(jLabels); - break; - - case json_type_string: - tmpJ = json_object_new_array(); - labelCount = 1; - json_object_array_add(tmpJ, jLabels); - jLabels = tmpJ; - break; - - default: - afb_req_fail_f(request, "ucmget-notarray", "labels=%s not valid JSON array", labels); - goto OnErrorExit; - } - - for (int idx = 0; idx < labelCount; idx++) { - json_object *jValue, *jLabel; - const char *label; - - jLabel = json_object_array_get_idx(jLabels, idx); - label = json_object_get_string(jLabel); - jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, mod, label); - if (jValue) json_object_object_add(jResponse, label, jValue); - else { - json_object_array_add(jWarnings, jLabel); - } - } - - // use info section to notified not found values label - if (json_object_array_length(jWarnings) > 0) { - json_object *tmpJ = json_object_new_object(); - json_object_object_add(tmpJ, "no-context", jWarnings); - warnings = json_object_get_string(tmpJ); - } - afb_req_success(request, jResponse, warnings); - -OnErrorExit: - return; -} - -PUBLIC void alsaUseCaseSet(struct afb_req request) { - int err, ucmIdx; - queryValuesT queryValues; - json_object *jResponse = json_object_new_object(); - - json_object *queryJ = alsaCheckQuery(request, &queryValues); - if (!queryJ) goto OnErrorExit; - - - ucmIdx = alsaUseCaseOpen(request, &queryValues, TRUE); - if (ucmIdx < 0) goto OnErrorExit; - - snd_use_case_mgr_t *ucmMgr = ucmHandles[ucmIdx].ucm; - const char *cardName = ucmHandles[ucmIdx].cardName; - - const char *verb = afb_req_value(request, "verb"); - const char *mod = afb_req_value(request, "mod"); - const char *dev = afb_req_value(request, "dev"); - // Known identifiers: _verb - set current verb = value _enadev - enable given device = value _disdev - disable given device = value _swdev/{old_device} - new_device = value - - if (verb) { - err = snd_use_case_set(ucmMgr, "_verb", verb); - if (err) { - afb_req_fail_f(request, "ucmset-verb", "SndCard devid=[%s] name=[%s] Invalid UCM verb=[%s] err=%s", queryValues.devid, cardName, verb, snd_strerror(err)); - goto OnErrorExit; - } - } - - if (dev) { - err = snd_use_case_set(ucmMgr, "_enadev", dev); - if (err) { - afb_req_fail_f(request, "ucmset-dev", "SndCard devid=[%s] name=[%s] Invalid UCMverb=[%s] dev=%s err=%s", queryValues.devid, cardName, verb, dev, snd_strerror(err)); - goto OnErrorExit; - } - } - - if (mod) { - err = snd_use_case_set(ucmMgr, "_enamod", mod); - if (err) { - afb_req_fail_f(request, "ucmset-mod", "SndCard devid=[%s] name=[%s] Invalid UCM verb=[%s] mod=[%s] err=%s", queryValues.devid, cardName, verb, mod, snd_strerror(err)); - goto OnErrorExit; - } - } - - // label are requested transfert request to get - if (afb_req_value(request, "value")) return alsaUseCaseGet(request); - - if (queryValues.mode <= 3) { - json_object *jValue; - - jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, dev, "OutputDspName"); - if (jValue) json_object_object_add(jResponse, "OutputDspName", jValue); - - jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, dev, "PlaybackPCM"); - if (jValue) json_object_object_add(jResponse, "PlaybackPCM", jValue); - - jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, mod, "CapturePCM"); - if (jValue) json_object_object_add(jResponse, "CapturePCM", jValue); - } - afb_req_success(request, jResponse, NULL); - -OnErrorExit: - return; -} - -PUBLIC void alsaUseCaseReset(struct afb_req request) { - int err, ucmIdx; - queryValuesT queryValues; - - json_object *queryJ = alsaCheckQuery(request, &queryValues); - if (!queryJ) goto OnErrorExit; - - ucmIdx = alsaUseCaseOpen(request, &queryValues, FALSE); - if (ucmIdx < 0) goto OnErrorExit; - - err = snd_use_case_mgr_reset(ucmHandles[ucmIdx].ucm); - if (err) { - afb_req_fail_f(request, "ucmreset-fail", "devid=%s Card Name=%s", queryValues.devid, ucmHandles[ucmIdx].cardName); - goto OnErrorExit; - } - - afb_req_success(request, NULL, NULL); - -OnErrorExit: - return; -} - -PUBLIC void alsaUseCaseClose(struct afb_req request) { - int err, ucmIdx; - queryValuesT queryValues; - - json_object *queryJ = alsaCheckQuery(request, &queryValues); - if (!queryJ) goto OnErrorExit; - - ucmIdx = alsaUseCaseOpen(request, &queryValues, FALSE); - if (ucmIdx < 0) goto OnErrorExit; - - err = snd_use_case_mgr_close(ucmHandles[ucmIdx].ucm); - if (err) { - afb_req_fail_f(request, "ucmreset-close", "devid=%s Card Name=%s", queryValues.devid, ucmHandles[ucmIdx].cardName); - goto OnErrorExit; - } - - // do not forget to release sound card name string - free(ucmHandles[ucmIdx].cardName); - - afb_req_success(request, NULL, NULL); - -OnErrorExit: - return; -} - - diff --git a/ALSA-afb/CMakeLists.txt b/ALSA-afb/CMakeLists.txt deleted file mode 100644 index 876e837..0000000 --- a/ALSA-afb/CMakeLists.txt +++ /dev/null @@ -1,41 +0,0 @@ -########################################################################### -# Copyright 2015, 2016, 2017 IoT.bzh -# -# author: Fulup Ar Foll -# -# 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. -########################################################################### - -# Add target to project dependency list -PROJECT_TARGET_ADD(alsa-lowlevel) - - # Define project Targets - ADD_LIBRARY(${TARGET_NAME} MODULE Alsa-ApiHat.c Alsa-SetGet.c Alsa-Ucm.c Alsa-AddCtl.c Alsa-RegEvt.c) - - # Binder exposes a unique public entry point - SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES - PREFIX "afb-" - LABELS "BINDING" - LINK_FLAGS ${BINDINGS_LINK_FLAG} - OUTPUT_NAME ${TARGET_NAME} - ) - - # Library dependencies (include updates automatically) - TARGET_LINK_LIBRARIES(${TARGET_NAME} - audio-common - ${link_libraries} - ) - - # installation directory - INSTALL(TARGETS ${TARGET_NAME} - LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR}) diff --git a/ALSA-afb/README.md b/ALSA-afb/README.md deleted file mode 100644 index 93d2d4d..0000000 --- a/ALSA-afb/README.md +++ /dev/null @@ -1,33 +0,0 @@ ------------------------------------------------------------------------- - AlsaCore Low level binding maps AlsaLib APIs ------------------------------------------------------------------------- - -Testing: (from project directory bindings) - * start binder: ~/opt/bin/afb-daemon --ldpaths=./build --token=mysecret --roothttp=htdocs - * connect browser on http://localhost:1234?devid=hw:0 - - # List Avaliable Sound cards - http://localhost:1234/api/alsacore/getinfo - - # Get Info on a given Sound Card - http://localhost:1234/api/alsacore/getinfo?devid=hw:0 - - # Get shortname/longname for a given card - http://localhost:1234/api/alsacore/getcardid?devid=hw:0 - - # Get all controls from a given sound card - http://localhost:1234/api/alsacore/getctl?devid=hw:0 - - # Get detail on a given control (optional mode=0=verbose,1,2) - http://localhost:1234/api/alsacore/getctl?devid=hw:0&numid=1&mode=0 - -# Debug event with afb-client-demo -``` - ~/opt/bin/afb-client-demo localhost:1234/api?token=mysecret - alsacore subscribe {"devid":"hw:0"} -``` - -# Open AlsaMixer and play with Volume -``` - alsamixer -D hw:0 -``` diff --git a/Alsa-Plugin/Alsa-Policy-Hook/CMakeLists.txt b/Alsa-Plugin/Alsa-Policy-Hook/CMakeLists.txt index e8342a1..6f4503e 100644 --- a/Alsa-Plugin/Alsa-Policy-Hook/CMakeLists.txt +++ b/Alsa-Plugin/Alsa-Policy-Hook/CMakeLists.txt @@ -19,7 +19,6 @@ # Activate ALSA dynamic build build mode get resolve "snd_dlsym_start" add_compile_options(-DPIC) - PROJECT_TARGET_ADD(policy_hook_cb) # Define targets diff --git a/Alsa-afb/Alsa-AddCtl.c b/Alsa-afb/Alsa-AddCtl.c new file mode 100644 index 0000000..f7ef92e --- /dev/null +++ b/Alsa-afb/Alsa-AddCtl.c @@ -0,0 +1,372 @@ +/* + * AlsaUseCase -- 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://kernel.readthedocs.io/en/sphinx-samples/writing-an-alsa-driver.html#control-names + * https://01.org/linuxgraphics/gfx-docs/drm/sound/designs/control-names.html + + */ +#define _GNU_SOURCE // needed for vasprintf + +#include +#include +#include +#include + +#include "Alsa-ApiHat.h" + +// Performs like a toggle switch for attenuation, because they're bool (ref:user-ctl-element-set.c) + +static const unsigned int *allocate_bool_fake_tlv(void) { + static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(range, -10000, 0); + unsigned int *tlv = malloc(sizeof (range)); + if (tlv == NULL) return NULL; + memcpy(tlv, range, sizeof (range)); + return tlv; +} + +static const unsigned int *allocate_int_dbscale_tlv(int min, int step, int mute) { + // SNDRV_CTL_TLVD_DECLARE_DB_SCALE(range, min, step, mute); + size_t tlvSize = sizeof (4 * sizeof (unsigned int)); + unsigned int *tlv = malloc(tlvSize); + tlv[0] = SNDRV_CTL_TLVT_DB_LINEAR; + tlv[1] = (int) tlvSize; + tlv[2] = min * 100; + tlv[3] = ((step * 100) & SNDRV_CTL_TLVD_DB_SCALE_MASK) | ((mute * 100) ? SNDRV_CTL_TLVD_DB_SCALE_MUTE : 0); + return tlv; +} + +static const unsigned int *allocate_int_linear_tlv(int max, int min) { + // SNDRV_CTL_TLVD_DECLARE_DB_LINEAR (range, min, max); + size_t tlvSize = sizeof (4 * sizeof (unsigned int)); + unsigned int *tlv = malloc(tlvSize); + tlv[0] = SNDRV_CTL_TLVT_DB_LINEAR; + tlv[1] = (int) tlvSize; + tlv[2] = -min * 100; + tlv[3] = max * 100; + return tlv; +} + +STATIC json_object * addOneSndCtl(afb_req request, snd_ctl_t *ctlDev, json_object *ctlJ, halQueryMode queryMode) { + int err, done, ctlNumid, ctlValue, shouldCreate; + json_object *tmpJ; + const char *ctlName; + ctlRequestT ctlRequest; + int ctlMax, ctlMin, ctlStep, ctlCount, ctlSubDev, ctlSndDev; + snd_ctl_elem_type_t ctlType; + snd_ctl_elem_info_t *elemInfo; + snd_ctl_elem_id_t *elemId; + snd_ctl_elem_value_t *elemValue; + const unsigned int *elemTlv = NULL; + + // parse json ctl object + json_object_object_get_ex(ctlJ, "name", &tmpJ); + ctlName = json_object_get_string(tmpJ); + + json_object_object_get_ex(ctlJ, "ctl", &tmpJ); + ctlNumid = json_object_get_int(tmpJ); + + if (!ctlNumid && !ctlName) { + afb_req_fail_f(request, "ctl-invalid", "crl=%s name or numid missing", json_object_get_string(ctlJ)); + goto OnErrorExit; + } + + + // We are facing a software ctl + if (ctlNumid == CTL_AUTO) { + done = json_object_object_get_ex(ctlJ, "type", &tmpJ); + if (done) ctlType = json_object_get_int(tmpJ); + else ctlType = SND_CTL_ELEM_TYPE_INTEGER; + + json_object_object_get_ex(ctlJ, "value", &tmpJ); + ctlValue = json_object_get_int(tmpJ); + + // default for json_object_get_int is zero + json_object_object_get_ex(ctlJ, "min", &tmpJ); + ctlMin = json_object_get_int(tmpJ); + + done = json_object_object_get_ex(ctlJ, "max", &tmpJ); + if (done) ctlMax = json_object_get_int(tmpJ); + else { + if (ctlType == SND_CTL_ELEM_TYPE_BOOLEAN) ctlMax = 1; + else ctlMax = 100; + } + + done = json_object_object_get_ex(ctlJ, "step", &tmpJ); + if (!done) ctlStep = 1; + else ctlStep = json_object_get_int(tmpJ); + + done = json_object_object_get_ex(ctlJ, "count", &tmpJ); + if (!done) ctlCount = 1; + else ctlCount = json_object_get_int(tmpJ); + + json_object_object_get_ex(ctlJ, "snddev", &tmpJ); + ctlSndDev = json_object_get_int(tmpJ); + + json_object_object_get_ex(ctlJ, "subdev", &tmpJ); + ctlSubDev = json_object_get_int(tmpJ); + } + + // Assert that this ctls is not used + snd_ctl_elem_info_alloca(&elemInfo); + if (ctlName) snd_ctl_elem_info_set_name(elemInfo, ctlName); + if (ctlNumid > 0)snd_ctl_elem_info_set_numid(elemInfo, ctlNumid); + snd_ctl_elem_info_set_interface(elemInfo, SND_CTL_ELEM_IFACE_MIXER); + + // If control exist try to reuse it + err = snd_ctl_elem_info(ctlDev, elemInfo); + if (err) { + shouldCreate = 1; + } else { + int count, min, max, sndDev, subDev; + snd_ctl_elem_type_t type; + + // ctl exit let's get associated elemID + snd_ctl_elem_id_alloca(&elemId); + snd_ctl_elem_info_get_id(elemInfo, elemId); + + // If this is a hardware ctl only update value + if (ctlNumid != CTL_AUTO) goto UpdateDefaultVal; + + count = snd_ctl_elem_info_get_count(elemInfo); + min = (int) snd_ctl_elem_info_get_min(elemInfo); + max = (int) snd_ctl_elem_info_get_max(elemInfo); + type = snd_ctl_elem_info_get_type(elemInfo); + sndDev = (int) snd_ctl_elem_info_get_device(elemInfo); + subDev = (int) snd_ctl_elem_info_get_subdevice(elemInfo); + + if (count == ctlCount && min == ctlMin && max == ctlMax && type == ctlType && sndDev == ctlSndDev && subDev == ctlSubDev) { + // Adjust value to best fit request + shouldCreate = 0; + } else { + err = snd_ctl_elem_remove(ctlDev, elemId); + shouldCreate = 1; + if (err < 0) { + AFB_ERROR("addOneSndCtl: ctlName=%s numid=%d fail to reset", snd_ctl_elem_info_get_name(elemInfo), snd_ctl_elem_info_get_numid(elemInfo)); + goto DoNotUpdate; + }; + + // Control was deleted need to refresh elemInfo + snd_ctl_elem_info_alloca(&elemInfo); + if (ctlName) snd_ctl_elem_info_set_name(elemInfo, ctlName); + snd_ctl_elem_info_set_interface(elemInfo, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_info(ctlDev, elemInfo); + } + } + + // Add requested ID into elemInfo + snd_ctl_elem_info_set_device(elemInfo, ctlSndDev); + snd_ctl_elem_info_set_subdevice(elemInfo, ctlSubDev); + + switch (ctlType) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + if (shouldCreate) { + err = snd_ctl_add_boolean_elem_set(ctlDev, elemInfo, 1, ctlCount); + if (err) { + AFB_ERROR("AddOntSndCtl Boolean: devid='%s' name='%s' count=%d", snd_ctl_name(ctlDev), ctlName, ctlCount); + afb_req_fail_f(request, "ctl-invalid-bool", "devid='%s' name='%s' count=%d", snd_ctl_name(ctlDev), ctlName, ctlCount); + goto OnErrorExit; + } + } + + // Create a dummy TLV + elemTlv = allocate_bool_fake_tlv(); + + break; + + case SND_CTL_ELEM_TYPE_INTEGER: + if (shouldCreate) { + err = snd_ctl_add_integer_elem_set(ctlDev, elemInfo, 1, ctlCount, ctlMin, ctlMax, ctlStep); + if (err) { + AFB_ERROR("AddOntSndCtl Integer: devid='%s' name='%s' count=%d min=%d max=%d step=%d", snd_ctl_name(ctlDev), ctlName, ctlCount, ctlMin, ctlMax, ctlStep); + afb_req_fail_f(request, "ctl-invalid-bool", "devid='%s' name='%s' count=%d min=%d max=%d step=%d", snd_ctl_name(ctlDev), ctlName, ctlCount, ctlMin, ctlMax, ctlStep); + goto OnErrorExit; + } + } + + // Fulup needed to be tested with some dB expert !!! + json_object *dbscaleJ; + if (json_object_object_get_ex(ctlJ, "dbscale", &dbscaleJ)) { + int min, max; + + if (json_object_get_type(dbscaleJ) != json_type_object) { + afb_req_fail_f(request, "ctl-invalid-dbscale", "devid=%s crl=%s invalid json in integer control", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); + goto OnErrorExit; + + json_object_object_get_ex(ctlJ, "min", &tmpJ); + min = json_object_get_int(tmpJ); + if (min >= 0) { + afb_req_fail_f(request, "ctl-invalid-dbscale", "devid=%s crl=%s min should be a negative number", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); + goto OnErrorExit; + } + + // default value 0=mute + json_object_object_get_ex(ctlJ, "max", &tmpJ); + max = json_object_get_int(tmpJ); + + // default value 1dB + json_object_object_get_ex(ctlJ, "step", &tmpJ); + int step = json_object_get_int(tmpJ); + if (step <= 0) step = 1; + + elemTlv = allocate_int_dbscale_tlv(min, max, step); + + } + } else { + // provide a fake linear TLV + elemTlv = allocate_int_linear_tlv(ctlMin, ctlMax); + } + break; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + + if (shouldCreate) { + json_object *enumsJ; + json_object_object_get_ex(ctlJ, "enums", &enumsJ); + if (json_object_get_type(enumsJ) != json_type_array) { + afb_req_fail_f(request, "ctl-missing-enums", "devid=%s crl=%s mandatory enum=xxx missing in enumerated control", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); + goto OnErrorExit; + } + + int length = json_object_array_length(enumsJ); + const char **enumlabels = malloc(length * sizeof (char*)); + + for (int jdx = 0; jdx < length; jdx++) { + tmpJ = json_object_array_get_idx(enumsJ, jdx); + enumlabels[jdx] = json_object_get_string(tmpJ); + } + + err = snd_ctl_add_enumerated_elem_set(ctlDev, elemInfo, 1, ctlCount, length, enumlabels); + if (err) { + afb_req_fail_f(request, "ctl-invalid-bool", "devid=%s crl=%s invalid enumerated control", snd_ctl_name(ctlDev), json_object_get_string(ctlJ)); + goto OnErrorExit; + } + + // Fulup should be an empty container + elemTlv = allocate_bool_fake_tlv(); + } + break; + + // Long Term Waiting ToDoList + case SND_CTL_ELEM_TYPE_INTEGER64: + case SND_CTL_ELEM_TYPE_BYTES: + default: + afb_req_fail_f(request, "ctl-invalid-type", "crl=%s unsupported type type=%d numid=%d", json_object_get_string(ctlJ), ctlType, ctlNumid); + goto OnErrorExit; + } + +UpdateDefaultVal: + + // Set Value to default + snd_ctl_elem_value_alloca(&elemValue); + for (int idx = 0; idx < snd_ctl_elem_info_get_count(elemInfo); idx++) { + snd_ctl_elem_value_set_integer(elemValue, idx, ctlValue); + } + + // write default values in newly created control + snd_ctl_elem_id_alloca(&elemId); + snd_ctl_elem_info(ctlDev, elemInfo); + snd_ctl_elem_info_get_id(elemInfo, elemId); + snd_ctl_elem_value_set_id(elemValue, elemId); + err = snd_ctl_elem_write(ctlDev, elemValue); + if (err < 0) { + AFB_WARNING("addOneSndCtl: crl=%s numid=%d fail to write Values error=%s", json_object_get_string(ctlJ), snd_ctl_elem_info_get_numid(elemInfo), snd_strerror(err)); + goto DoNotUpdate; + } + + // write a default null TLV (if usefull should be implemented for every ctl type) + if (elemTlv) { + err = snd_ctl_elem_tlv_write(ctlDev, elemId, elemTlv); + if (err < 0) { + AFB_WARNING("addOneSndCtl: crl=%s numid=%d fail to write TLV error=%s", json_object_get_string(ctlJ), snd_ctl_elem_info_get_numid(elemInfo), snd_strerror(err)); + goto DoNotUpdate; + } + } + +DoNotUpdate: + // return newly created as a JSON object + alsaGetSingleCtl(ctlDev, elemId, &ctlRequest, queryMode); + if (ctlRequest.used < 0) { + AFB_WARNING("addOneSndCtl: crl=%s numid=%d Fail to get value", json_object_get_string(ctlJ), snd_ctl_elem_info_get_numid(elemInfo)); + } + return ctlRequest.valuesJ; + +OnErrorExit: + return NULL; +} + +PUBLIC void alsaAddCustomCtls(afb_req request) { + int err; + json_object *ctlsJ, *ctlsValues, *ctlValues; + enum json_type; + snd_ctl_t *ctlDev = NULL; + const char *devid, *mode; + + devid = afb_req_value(request, "devid"); + if (devid == NULL) { + afb_req_fail_f(request, "devid-missing", "devid MUST be defined for alsaAddCustomCtls"); + goto OnErrorExit; + } + + // open control interface for devid + err = snd_ctl_open(&ctlDev, devid, 0); + if (err < 0) { + afb_req_fail_f(request, "devid-unknown", "SndCard devid=[%s] Not Found err=%s", devid, snd_strerror(err)); + goto OnErrorExit; + } + + // get verbosity level + halQueryMode queryMode = QUERY_QUIET; + mode = afb_req_value(request, "mode"); + if (mode != NULL) { + sscanf(mode, "%i", (int*) &queryMode); + } + + // extract sound controls and parse json + ctlsJ = json_tokener_parse(afb_req_value(request, "ctl")); + if (!ctlsJ) { + afb_req_fail_f(request, "ctls-missing", "ctls MUST be defined as a JSON array for alsaAddCustomCtls"); + goto OnErrorExit; + } + + switch (json_object_get_type(ctlsJ)) { + case json_type_object: + ctlsValues = addOneSndCtl(request, ctlDev, ctlsJ, queryMode); + if (!ctlsValues) goto OnErrorExit; + break; + + case json_type_array: + ctlsValues = json_object_new_array(); + for (int idx = 0; idx < json_object_array_length(ctlsJ); idx++) { + json_object *ctlJ = json_object_array_get_idx(ctlsJ, idx); + ctlValues = addOneSndCtl(request, ctlDev, ctlJ, queryMode); + if (ctlValues) json_object_array_add(ctlsValues, ctlValues); + else goto OnErrorExit; + } + break; + + default: + afb_req_fail_f(request, "ctls-invalid", "ctls=%s not valid JSON array", json_object_get_string(ctlsJ)); + goto OnErrorExit; + } + + // get ctl as a json response + afb_req_success(request, ctlsValues, NULL); + +OnErrorExit: + if (ctlDev) snd_ctl_close(ctlDev); + return; +} \ No newline at end of file diff --git a/Alsa-afb/Alsa-ApiHat.c b/Alsa-afb/Alsa-ApiHat.c new file mode 100644 index 0000000..2c3c5c1 --- /dev/null +++ b/Alsa-afb/Alsa-ApiHat.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Alsa-ApiHat.h" + +/* + * array of the verbs exported to afb-daemon + */ +static const struct afb_verb_v2 api_verbs[] = { + /* VERB'S NAME FUNCTION TO CALL */ + { .verb = "ping", .callback = pingtest}, + { .verb = "getinfo", .callback = alsaGetInfo}, + { .verb = "getctl", .callback = alsaGetCtls}, + { .verb = "setctl", .callback = alsaSetCtls}, + { .verb = "subscribe", .callback = alsaEvtSubcribe}, + { .verb = "getcardid", .callback = alsaGetCardId}, + { .verb = "halregister", .callback = alsaRegisterHal}, + { .verb = "hallist", .callback = alsaActiveHal}, + { .verb = "ucmquery", .callback = alsaUseCaseQuery}, + { .verb = "ucmset", .callback = alsaUseCaseSet}, + { .verb = "ucmget", .callback = alsaUseCaseGet}, + { .verb = "ucmreset", .callback = alsaUseCaseReset}, + { .verb = "ucmclose", .callback = alsaUseCaseClose}, + { .verb = "addcustomctl", .callback = alsaAddCustomCtls}, + { .verb = NULL} /* marker for end of the array */ +}; + +/* + * description of the binding for afb-daemon + */ +const struct afb_binding_v2 afbBindingV2 = { + .api = "alsacore", + .verbs = api_verbs, +}; diff --git a/Alsa-afb/Alsa-ApiHat.h b/Alsa-afb/Alsa-ApiHat.h new file mode 100644 index 0000000..b05cfbe --- /dev/null +++ b/Alsa-afb/Alsa-ApiHat.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + + +#ifndef ALSALIBMAPPING_H +#define ALSALIBMAPPING_H + + +#include +#include +#include "audio-common.h" + +typedef enum { + ACTION_SET, + ACTION_GET +} ActionSetGetT; + +// generic structure to pass parsed query values +typedef struct { + const char *devid; + json_object *numidsJ; + halQueryMode mode; + int count; +} queryValuesT; + +// use to store crl numid user request +typedef struct { + unsigned int numId; + json_object *jToken; + json_object *valuesJ; + int used; +} ctlRequestT; + +// import from AlsaAfbBinding +extern const struct afb_binding_interface *afbIface; +PUBLIC json_object *alsaCheckQuery (struct afb_req request, queryValuesT *queryValues); + +// AlseCoreSetGet exports +PUBLIC int alsaGetSingleCtl (snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, ctlRequestT *ctlRequest, halQueryMode queryMode); +PUBLIC void alsaGetInfo (struct afb_req request); +PUBLIC void alsaGetCtls(struct afb_req request); +PUBLIC void alsaSetCtls(struct afb_req request); + + +// AlsaUseCase exports +PUBLIC void alsaUseCaseQuery(struct afb_req request); +PUBLIC void alsaUseCaseSet(struct afb_req request); +PUBLIC void alsaUseCaseGet(struct afb_req request); +PUBLIC void alsaUseCaseClose(struct afb_req request); +PUBLIC void alsaUseCaseReset(struct afb_req request); +PUBLIC void alsaAddCustomCtls(struct afb_req request); + +// AlsaRegEvt +PUBLIC void alsaEvtSubcribe (struct afb_req request); +PUBLIC void alsaGetCardId (struct afb_req request); +PUBLIC void alsaRegisterHal (struct afb_req request); +PUBLIC void alsaActiveHal (struct afb_req request); + +#endif /* ALSALIBMAPPING_H */ + diff --git a/Alsa-afb/Alsa-RegEvt.c b/Alsa-afb/Alsa-RegEvt.c new file mode 100644 index 0000000..080b6cc --- /dev/null +++ b/Alsa-afb/Alsa-RegEvt.c @@ -0,0 +1,395 @@ +/* + * 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 "Alsa-ApiHat.h" + +// 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; + struct afb_event afbevt; +} evtHandleT; + +typedef struct { + int ucount; + int cardId; + evtHandleT *evtHandle; +} sndHandleT; + +typedef struct { + char *devid; + char *apiprefix; + char *shortname; + char *longname; +} cardRegistryT; + +cardRegistryT *cardRegistry[MAX_SND_CARD + 1]; + +PUBLIC json_object *alsaCheckQuery(afb_req request, queryValuesT *queryValues) { + + json_object *tmpJ; + int done; + + // get query from request + json_object *queryInJ = afb_req_json(request); + + done = json_object_object_get_ex(queryInJ, "devid", &tmpJ); + if (!done) { + afb_req_fail_f(request, "devid-missing", "Invalid query='%s'", json_object_get_string(queryInJ)); + goto OnErrorExit; + } + queryValues->devid = json_object_get_string(tmpJ); + + done = json_object_object_get_ex(queryInJ, "mode", &tmpJ); + if (!done) queryValues->mode = QUERY_QUIET; // default quiet + else queryValues->mode = json_object_get_int(tmpJ); + + return queryInJ; + +OnErrorExit: + return NULL; +} + +// This routine is called when ALSA event are fired + +STATIC int sndCtlEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { + int err, ctlNumid; + evtHandleT *evtHandle = (evtHandleT*) userData; + 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 & 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: ignored unsupported event type"); + return (0); +} + +// Subscribe to every Alsa CtlEvent send by a given board + +PUBLIC void alsaEvtSubcribe(afb_req request) { + static sndHandleT sndHandles[MAX_SND_CARD]; + evtHandleT *evtHandle = NULL; + snd_ctl_t *ctlDev = NULL; + int err, idx, cardId, idxFree = -1; + snd_ctl_card_info_t *cardinfo; + queryValuesT queryValues; + + json_object *queryJ = alsaCheckQuery(request, &queryValues); + if (!queryJ) goto OnErrorExit; + + // open control interface for devid + err = snd_ctl_open(&ctlDev, queryValues.devid, SND_CTL_READONLY); + if (err < 0) { + afb_req_fail_f(request, "devid-unknown", "SndCard devid=%s Not Found err=%s", queryValues.devid, snd_strerror(err)); + goto OnErrorExit; + } + + snd_ctl_card_info_alloca(&cardinfo); + if ((err = snd_ctl_card_info(ctlDev, cardinfo)) < 0) { + afb_req_fail_f(request, "devid-invalid", "SndCard devid=%s Not Found err=%s", queryValues.devid, snd_strerror(err)); + goto OnErrorExit; + } + + cardId = snd_ctl_card_info_get_card(cardinfo); + + // search for an existing subscription and mark 1st free slot + for (idx = 0; idx < MAX_SND_CARD; idx++) { + if (sndHandles[idx].ucount > 0 && cardId == sndHandles[idx].cardId) { + evtHandle = sndHandles[idx].evtHandle; + break; + } else if (idxFree == -1) idxFree = idx; + }; + + // if not subscription exist for the event let's create one + if (idx == MAX_SND_CARD) { + + // reach MAX_SND_CARD event registration + if (idxFree == -1) { + afb_req_fail_f(request, "register-toomany", "Cannot register new event Maxcard==%d", idx); + goto OnErrorExit; + } + + evtHandle = malloc(sizeof (evtHandleT)); + evtHandle->ctlDev = ctlDev; + evtHandle->mode = queryValues.mode; + sndHandles[idxFree].ucount = 0; + sndHandles[idxFree].cardId = cardId; + sndHandles[idxFree].evtHandle = evtHandle; + + // subscribe for sndctl events attached to devid + err = snd_ctl_subscribe_events(evtHandle->ctlDev, 1); + if (err < 0) { + afb_req_fail_f(request, "subscribe-fail", "Cannot subscribe events from devid=%s err=%d", queryValues.devid, err); + goto OnErrorExit; + } + + // get pollfd attach to this sound board + snd_ctl_poll_descriptors(evtHandle->ctlDev, &evtHandle->pfds, 1); + + // register sound event to binder main loop + err = sd_event_add_io(afb_daemon_get_event_loop(), &evtHandle->src, evtHandle->pfds.fd, EPOLLIN, sndCtlEventCB, evtHandle); + if (err < 0) { + afb_req_fail_f(request, "register-mainloop", "Cannot hook events to mainloop devid=%s err=%d", queryValues.devid, err); + goto OnErrorExit; + } + + // create binder event attached to devid name + evtHandle->afbevt = afb_daemon_make_event(queryValues.devid); + if (!afb_event_is_valid(evtHandle->afbevt)) { + afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", queryValues.devid); + goto OnErrorExit; + } + + // everything looks OK let's move forward + idx = idxFree; + } + + // subscribe to binder event + err = afb_req_subscribe(request, evtHandle->afbevt); + if (err != 0) { + afb_req_fail_f(request, "register-eventname", "Cannot subscribe binder event name=%s [invalid channel]", queryValues.devid); + goto OnErrorExit; + } + + // increase usage count and return success + sndHandles[idx].ucount++; + afb_req_success(request, NULL, NULL); + return; + +OnErrorExit: + if (ctlDev) snd_ctl_close(ctlDev); + return; +} + +// Subscribe to every Alsa CtlEvent send by a given board + +STATIC json_object *alsaProbeCardId(afb_req request) { + char devid [10]; + const char *ctlName, *shortname, *longname; + int card, err, index, idx; + json_object *responseJ; + snd_ctl_t *ctlDev; + snd_ctl_card_info_t *cardinfo; + + const char *sndname = afb_req_value(request, "sndname"); + if (sndname == NULL) { + afb_req_fail_f(request, "argument-missing", "sndname=SndCardName missing"); + goto OnErrorExit; + } + + // loop on potential card number + snd_ctl_card_info_alloca(&cardinfo); + 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); + + // check if short|long name match + if (!strcmp(sndname, ctlName)) break; + if (!strcmp(sndname, shortname)) break; + if (!strcmp(sndname, longname)) break; + } + + if (card == MAX_SND_CARD) { + afb_req_fail_f(request, "ctlDev-notfound", "Fail to find card with name=%s", sndname); + goto OnErrorExit; + } + + // 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, "devid", json_object_new_string(devid)); + json_object_object_add(responseJ, "shortname", json_object_new_string(shortname)); + json_object_object_add(responseJ, "longname", json_object_new_string(longname)); + + // search for a HAL binder card mapping name to api prefix + for (idx = 0; (idx < MAX_SND_CARD && 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 request) { + + json_object *responseJ = alsaProbeCardId(request); + if (responseJ) afb_req_success(request, responseJ, NULL); +} + +// Return list of active resgistrated HAL with corresponding sndcard + +PUBLIC void alsaActiveHal(afb_req request) { + json_object *responseJ = json_object_new_array(); + + for (int idx = 0; idx < MAX_SND_CARD; 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]->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 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_CARD) { + 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); + + 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, "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; +} + diff --git a/Alsa-afb/Alsa-SetGet.c b/Alsa-afb/Alsa-SetGet.c new file mode 100644 index 0000000..04f2929 --- /dev/null +++ b/Alsa-afb/Alsa-SetGet.c @@ -0,0 +1,809 @@ +/* + * 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 "Alsa-ApiHat.h" + +PUBLIC void NumidsListParse(ActionSetGetT action, queryValuesT *queryValues, ctlRequestT *ctlRequest) { + int length; + + for (int idx = 0; idx < queryValues->count; idx++) { + json_object *jId, *valuesJ; + ctlRequest[idx].used = 0; + 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(queryValues->numidsJ)) ctlRequest[idx].jToken = json_object_array_get_idx(queryValues->numidsJ, idx); + else ctlRequest[idx].jToken = queryValues->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 && queryValues->count == 2) { + ctlRequest[idx].valuesJ = json_object_array_get_idx(queryValues->numidsJ, 1); + queryValues->count = 1; //In this form count==2 , when only one numid is to set + idx++; + continue; + } else + break; + + case json_type_array: + // NUMID is an array 1st slot should be numid, optionally values may come after + length = 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) || !json_object_object_get_ex(ctlRequest[idx].jToken, "val", &valuesJ)) { + AFB_NOTICE("Invalid Json=%s missing 'id'|'val'", json_object_get_string(ctlRequest[idx].jToken)); + ctlRequest[idx].used = -1; + } else { + ctlRequest[idx].numId = json_object_get_int(jId); + if (action == ACTION_SET) ctlRequest[idx].valuesJ = valuesJ; + } + break; + + default: + ctlRequest[idx].used = -1; + } + } +} + +STATIC json_object *DB2StringJsonOject(long dB) { + char label [20]; + 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); +} + + +// retreive info for one given card + +STATIC json_object* alsaCardProbe(const char *rqtSndId) { + const char *info, *name; + const char *devid, *driver; + json_object *ctlDev; + snd_ctl_t *handle; + snd_ctl_card_info_t *cardinfo; + int err; + + if ((err = snd_ctl_open(&handle, rqtSndId, 0)) < 0) { + AFB_INFO("alsaCardProbe '%s' Not Found", rqtSndId); + return NULL; + } + + snd_ctl_card_info_alloca(&cardinfo); + if ((err = snd_ctl_card_info(handle, cardinfo)) < 0) { + snd_ctl_close(handle); + AFB_WARNING("SndCard '%s' info error: %s", rqtSndId, snd_strerror(err)); + return NULL; + } + + // start a new json object to store card info + ctlDev = json_object_new_object(); + + devid = snd_ctl_card_info_get_id(cardinfo); + json_object_object_add(ctlDev, "devid", json_object_new_string(devid)); + name = snd_ctl_card_info_get_name(cardinfo); + json_object_object_add(ctlDev, "name", json_object_new_string(name)); + + if (AFB_GET_VERBOSITY > 1) { + json_object_object_add(ctlDev, "devid", json_object_new_string(rqtSndId)); + driver = snd_ctl_card_info_get_driver(cardinfo); + json_object_object_add(ctlDev, "driver", json_object_new_string(driver)); + info = strdup(snd_ctl_card_info_get_longname(cardinfo)); + json_object_object_add(ctlDev, "info", json_object_new_string(info)); + AFB_INFO("AJG: Soundcard Devid=%-5s devid=%-7s Name=%s\n", rqtSndId, devid, info); + } + + // free card handle and return info + snd_ctl_close(handle); + return (ctlDev); +} + +// Loop on every potential Sound card and register active one + +PUBLIC void alsaGetInfo(afb_req request) { + int card; + json_object *ctlDev, *ctlDevs; + char devid[32]; + + const char *rqtSndId = afb_req_value(request, "devid"); + + // if no specific card requested loop on all + + if (rqtSndId != NULL) { + // only one card was requested let's probe it + ctlDev = alsaCardProbe(rqtSndId); + if (ctlDev != NULL) afb_req_success(request, ctlDev, NULL); + else afb_req_fail_f(request, "sndscard-notfound", "SndCard '%s' Not Found", rqtSndId); + + } else { + // return an array of ctlDev + ctlDevs = json_object_new_array(); + + // loop on potential card number + for (card = 0; card < MAX_SND_CARD; card++) { + + // build card devid and probe it + snprintf(devid, sizeof (devid), "hw:%i", card); + ctlDev = alsaCardProbe(devid); + + // Alsa has hole within card list [ignore them] + if (ctlDev != NULL) { + // add current ctlDev to ctlDevs object + json_object_array_add(ctlDevs, ctlDev); + } + } + afb_req_success(request, ctlDevs, NULL); + } +} + +// 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 = 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_NOTICE("Invalid values NUMID='%d' Values='%s' count='%d' wanted='%d'", ctlRequest->numId, json_object_get_string(ctlRequest->valuesJ), length, count); + goto OnErrorExit; + } + + 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_NOTICE("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, halQueryMode 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 request) { + ctlRequestT *ctlRequest; + const char *warmsg = NULL; + int err = 0, status = 0, done; + unsigned int ctlCount; + snd_ctl_t *ctlDev; + snd_ctl_elem_list_t *ctlList; + queryValuesT queryValues; + json_object *queryJ, *numidsJ, *sndctls; + + queryJ = alsaCheckQuery(request, &queryValues); + if (!queryJ) goto OnErrorExit; + + // Prase Numids + optional values + done = json_object_object_get_ex(queryJ, "ctl", &numidsJ); + if (!done) queryValues.count = 0; + else { + enum json_type jtype = json_object_get_type(numidsJ); + switch (jtype) { + case json_type_array: + queryValues.numidsJ = numidsJ; + queryValues.count = json_object_array_length(numidsJ); + break; + + case json_type_int: + case json_type_object: + queryValues.count = 1; + queryValues.numidsJ = numidsJ; + break; + + default: + afb_req_fail_f(request, "numid-notarray", "NumId=%s NumId not valid JSON array", json_object_get_string(numidsJ)); + goto OnErrorExit; + } + } + + if ((err = snd_ctl_open(&ctlDev, queryValues.devid, 0)) < 0) { + afb_req_fail_f(request, "sndcrl-notfound", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); + goto OnErrorExit; + } + + 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", queryValues.devid, snd_strerror(err)); + goto OnErrorExit; + } + + 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", queryValues.devid, snd_strerror(err)); + goto OnErrorExit; + } + + if ((err = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { + afb_req_fail_f(request, "listOpen-failed", "devid='%s' load fail error=%s\n", queryValues.devid, snd_strerror(err)); + goto OnErrorExit; + } + + // Parse numids string (empty == all) + ctlCount = snd_ctl_elem_list_get_used(ctlList); + if (queryValues.count == 0) { + ctlRequest = alloca(sizeof (ctlRequestT)*(ctlCount)); + } else { + ctlRequest = alloca(sizeof (ctlRequestT)*(queryValues.count)); + NumidsListParse(action, &queryValues, ctlRequest); + } + + // if more than one crl requested prepare an array for response + if (queryValues.count != 1 && action == ACTION_GET) sndctls = json_object_new_array(); + else sndctls = NULL; + + // Loop on all ctlDev controls + for (int ctlIndex = 0; ctlIndex < ctlCount; ctlIndex++) { + unsigned int selected = 0; + int jdx; + + if (queryValues.count == 0 && action == ACTION_GET) { + selected = 1; // check is this numid is selected within query + jdx = ctlIndex; // map all existing ctl as requested + } else { + int numid = snd_ctl_elem_list_get_numid(ctlList, ctlIndex); + if (numid < 0) { + AFB_NOTICE("snd_ctl_elem_list_get_numid index=%d fail", ctlIndex); + continue; + } + // check if current control was requested in query numids list + for (jdx = 0; jdx < queryValues.count; jdx++) { + if (numid == ctlRequest[jdx].numId) { + selected = 1; + break; + } + } + } + + // control is selected open ctlid and get value + if (selected) { + 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], queryValues.mode); + break; + + case ACTION_SET: + err = alsaSetSingleCtl(ctlDev, elemId, &ctlRequest[jdx]); + break; + + default: + err = 1; + } + if (err) status++; + else { + // Do not embed response in an array when only one ctl was requested + if (action == ACTION_GET) { + if (queryValues.count > 1) json_object_array_add(sndctls, ctlRequest[jdx].valuesJ); + else sndctls = ctlRequest[jdx].valuesJ; + } + } + } + } + + // if we had error let's add them into response message info + json_object *warningsJ = json_object_new_array(); + for (int jdx = 0; jdx < queryValues.count; jdx++) { + if (ctlRequest[jdx].used <= 0) { + json_object *failctl = json_object_new_object(); + if (ctlRequest[jdx].numId == -1) json_object_object_add(failctl, "warning", json_object_new_string("Numid Invalid")); + else { + if (ctlRequest[jdx].used == 0) json_object_object_add(failctl, "warning", json_object_new_string("Numid Does Not Exist")); + if (ctlRequest[jdx].used == -1) json_object_object_add(failctl, "warning", json_object_new_string("Value Refused")); + } + + json_object_object_add(failctl, "ctl", ctlRequest[jdx].jToken); + + json_object_array_add(warningsJ, failctl); + } + /* WARNING!!!! Check with Jose why following put free valuesJ + if (ctlRequest[jdx].jToken) json_object_put(ctlRequest[jdx].jToken); + if (ctlRequest[jdx].valuesJ) json_object_put(ctlRequest[jdx].valuesJ); + */ + } + + if (json_object_array_length(warningsJ) > 0) warmsg = json_object_get_string(warningsJ); + else json_object_put(warningsJ); + + // send response+warning if any + afb_req_success(request, sndctls, warmsg); + snd_ctl_elem_list_clear(ctlList); + +OnErrorExit: + return; +} + +PUBLIC void alsaGetCtls(afb_req request) { + alsaSetGetCtls(ACTION_GET, request); +} + +PUBLIC void alsaSetCtls(afb_req request) { + alsaSetGetCtls(ACTION_SET, request); +} + + diff --git a/Alsa-afb/Alsa-Ucm.c b/Alsa-afb/Alsa-Ucm.c new file mode 100644 index 0000000..016fe10 --- /dev/null +++ b/Alsa-afb/Alsa-Ucm.c @@ -0,0 +1,433 @@ +/* + * AlsaUseCase -- 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: + http://www.alsa-project.org/alsa-doc/alsa-lib/group__ucm.html + https://www.alsa-project.org/main/index.php/DAPM + http://alsa-lib.sourcearchive.com/documentation/1.0.24.1-2/group__Use_ga4332c6bb50481bbdaf21be11551fb930.html + https://android.googlesource.com/platform/hardware/qcom/audio/+/jb-mr1-dev/libalsa-intf/alsa_ucm.h + + Sample alsaucm commands using /usr/share/alsa/ucm/PandaBoard + - alsaucm -c PandaBoard list _verbs + - alsaucm -c PandaBoard list _devices/HiFi + - alsaucm -c PandaBoard list _modifiers/HiFi #need to uncomment modifiers section + - alsaucm -c PandaBoard list TQ/HiFi + - alsaucm -c PandaBoard get TQ/HiFi/Voice + - alsaucm -c PandaBoard get PlaybackPCM//HiFi + - alsaucm -c PandaBoard set _verb HiFi + - alsaucm -c PandaBoard set _verb HiFi _enadev Headset + - alsaucm -c 'HDA Intel PCH' set _verb HiFi set _enadev Headphone set _enamod RecordMedia + - alsaucm -c 'HDA Intel PCH' set _verb HiFi get OutputDspName// + + */ +#define _GNU_SOURCE // needed for vasprintf + +#include +#include +#include + +#include "Alsa-ApiHat.h" + +typedef struct { + snd_use_case_mgr_t *ucm; + int cardId; + char *cardName; +} ucmHandleT; + +static ucmHandleT ucmHandles[MAX_SND_CARD]; + +// Cache opened UCM handles + +STATIC int alsaUseCaseOpen(struct afb_req request, queryValuesT *queryValues, int allowNewMgr) { + snd_ctl_t *ctlDev; + snd_ctl_card_info_t *cardinfo; + snd_use_case_mgr_t *ucmHandle; + const char *cardName; + int cardId, idx, idxFree = -1, err; + + // open control interface for devid + err = snd_ctl_open(&ctlDev, queryValues->devid, SND_CTL_READONLY); + if (err < 0) { + ctlDev = NULL; + afb_req_fail_f(request, "devid-unknown", "SndCard devid=[%s] Not Found err=%d", queryValues->devid, err); + goto OnErrorExit; + } + + snd_ctl_card_info_alloca(&cardinfo); + if ((err = snd_ctl_card_info(ctlDev, cardinfo)) < 0) { + afb_req_fail_f(request, "devid-invalid", "SndCard devid=[%s] Not Found err=%s", queryValues->devid, snd_strerror(err)); + goto OnErrorExit; + } + + // search for an existing subscription and mark 1st free slot + cardId = snd_ctl_card_info_get_card(cardinfo); + for (idx = 0; idx < MAX_SND_CARD; idx++) { + if (ucmHandles[idx].ucm != NULL) { + if (ucmHandles[idx].cardId == cardId) goto OnSuccessExit; + } else if (idxFree == -1) idxFree = idx; + }; + + if (!allowNewMgr) { + afb_req_fail_f(request, "ucm-nomgr", "SndCard devid=[%s] no exiting UCM manager session", queryValues->devid); + goto OnErrorExit; + } + + if (idxFree < 0 && idx == MAX_SND_CARD) { + afb_req_fail_f(request, "ucm-toomany", "SndCard devid=[%s] too many open UCM Max=%d", queryValues->devid, MAX_SND_CARD); + goto OnErrorExit; + } + + idx = idxFree; + cardName = snd_ctl_card_info_get_name(cardinfo); + err = snd_use_case_mgr_open(&ucmHandle, cardName); + if (err) { + afb_req_fail_f(request, "ucm-open", "SndCard devid=[%s] name=[%s] No UCM Profile err=%s", queryValues->devid, cardName, snd_strerror(err)); + goto OnErrorExit; + } + ucmHandles[idx].ucm = ucmHandle; + ucmHandles[idx].cardId = cardId; + ucmHandles[idx].cardName = strdup(cardName); + +OnSuccessExit: + if (ctlDev) snd_ctl_close(ctlDev); + return idx; + +OnErrorExit: + if (ctlDev) snd_ctl_close(ctlDev); + return -1; +} + +PUBLIC void alsaUseCaseQuery(struct afb_req request) { + int verbCount, ucmIdx; + const char **verbList; + snd_use_case_mgr_t *ucmHandle; + queryValuesT queryValues; + json_object *ucmJs; + const char *cardName; + + json_object *queryJ = alsaCheckQuery(request, &queryValues); + if (!queryJ) goto OnErrorExit; + + ucmIdx = alsaUseCaseOpen(request, &queryValues, TRUE); + if (ucmIdx < 0) goto OnErrorExit; + ucmHandle = ucmHandles [ucmIdx].ucm; + cardName = ucmHandles [ucmIdx].cardName; + + verbCount = snd_use_case_get_list(ucmHandle, "_verbs", &verbList); + if (verbCount < 0) { + afb_req_fail_f(request, "ucm-list", "SndCard devid=[%s] name=[%s] No UCM Verbs", queryValues.devid, cardName); + goto OnErrorExit; + } + + ucmJs = json_object_new_array(); + for (int idx = 0; idx < verbCount; idx += 2) { + int devCount, modCount, tqCount; + const char **devList, **modList, **tqList; + json_object *ucmJ = json_object_new_object(); + char identifier[32]; + + json_object_object_add(ucmJ, "verb", json_object_new_string(verbList[idx])); + if (verbList[idx + 1]) json_object_object_add(ucmJ, "info", json_object_new_string(verbList[idx + 1])); + + AFB_DEBUG("Verb[%d] Action=%s Info=%s", idx, verbList[idx], verbList[idx + 1]); + + snprintf(identifier, sizeof (identifier), "_devices/%s", verbList[idx]); + devCount = snd_use_case_get_list(ucmHandle, identifier, &devList); + if (devCount > 0) { + json_object *devsJ = json_object_new_array(); + + for (int jdx = 0; jdx < devCount; jdx += 2) { + json_object *devJ = json_object_new_object(); + AFB_DEBUG("device[%d] Action=%s Info=%s", jdx, devList[jdx], devList[jdx + 1]); + json_object_object_add(devJ, "dev", json_object_new_string(devList[jdx])); + if (devList[jdx + 1]) json_object_object_add(devJ, "info", json_object_new_string(devList[jdx + 1])); + json_object_array_add(devsJ, devJ); + } + json_object_object_add(ucmJ, "devices", devsJ); + snd_use_case_free_list(devList, devCount); + } + + snprintf(identifier, sizeof (identifier), "_modifiers/%s", verbList[idx]); + modCount = snd_use_case_get_list(ucmHandle, identifier, &modList); + if (modCount > 0) { + json_object *modsJ = json_object_new_array(); + + for (int jdx = 0; jdx < modCount; jdx += 2) { + json_object *modJ = json_object_new_object(); + AFB_DEBUG("modifier[%d] Action=%s Info=%s", jdx, modList[jdx], modList[jdx + 1]); + json_object_object_add(modJ, "mod", json_object_new_string(modList[jdx])); + if (modList[jdx + 1]) json_object_object_add(modJ, "info", json_object_new_string(modList[jdx + 1])); + json_object_array_add(modsJ, modJ); + } + json_object_object_add(ucmJ, "modifiers", modsJ); + snd_use_case_free_list(modList, modCount); + } + + snprintf(identifier, sizeof (identifier), "TQ/%s", verbList[idx]); + tqCount = snd_use_case_get_list(ucmHandle, identifier, &tqList); + if (tqCount > 0) { + json_object *tqsJ = json_object_new_array(); + + for (int jdx = 0; jdx < tqCount; jdx += 2) { + json_object *tqJ = json_object_new_object(); + AFB_DEBUG("toneqa[%d] Action=%s Info=%s", jdx, tqList[jdx], tqList[jdx + 1]); + json_object_object_add(tqJ, "tq", json_object_new_string(tqList[jdx])); + if (tqList[jdx + 1]) json_object_object_add(tqJ, "info", json_object_new_string(tqList[jdx + 1])); + json_object_array_add(tqsJ, tqJ); + } + json_object_object_add(ucmJ, "tqs", tqsJ); + snd_use_case_free_list(tqList, tqCount); + } + + json_object_array_add(ucmJs, ucmJ); + } + + afb_req_success(request, ucmJs, NULL); + snd_use_case_free_list(verbList, verbCount); + +OnErrorExit: + return; +} + +STATIC json_object *ucmGetValue(ucmHandleT *ucmHandle, const char *verb, const char *mod, const char *label) { + char identifier[80]; + char *value; + int err; + json_object *jValue; + + // handle optional parameters + if (!mod) mod = ""; + if (!verb) verb = ""; + + if (!label) { + AFB_NOTICE("ucmGetValue cardname=[%s] value label missing", ucmHandle->cardName); + goto OnErrorExit; + } + + snprintf(identifier, sizeof (identifier), "%s/%s/%s", label, mod, verb); + err = snd_use_case_get(ucmHandle->ucm, identifier, (const char**) &value); // Note: value casting is a known "FEATURE" of AlsaUCM API + if (err) { + AFB_DEBUG("ucmGetValue cardname=[%s] identifier=[%s] error=%s", ucmHandle->cardName, identifier, snd_strerror(err)); + goto OnErrorExit; + } + + // copy value into json object and free string + jValue = json_object_new_string(value); + free(value); + return (jValue); + +OnErrorExit: + return (NULL); +} + +PUBLIC void alsaUseCaseGet(struct afb_req request) { + int ucmIdx, labelCount; + queryValuesT queryValues; + json_object *jResponse = json_object_new_object(); + json_object *jWarnings = json_object_new_array(); + const char *warnings = NULL; + + json_object *queryJ = alsaCheckQuery(request, &queryValues); + if (!queryJ) goto OnErrorExit; + ; + + ucmIdx = alsaUseCaseOpen(request, &queryValues, TRUE); + if (ucmIdx < 0) goto OnErrorExit; + + const char *cardName = ucmHandles[ucmIdx].cardName; + + const char *verb = afb_req_value(request, "verb"); + const char *mod = afb_req_value(request, "mod"); + const char *dev = afb_req_value(request, "dev"); + + if (dev && mod) { + afb_req_fail_f(request, "ucmget-labels", "SndCard devid=[%s] name=[%s] UCM mod+dev incompatible", queryValues.devid, cardName); + goto OnErrorExit; + } + + // device selection is handle as a modifier + if (dev) mod = dev; + + const char *labels = afb_req_value(request, "value"); + if (!labels) { + afb_req_fail_f(request, "ucmget-labels", "SndCard devid=[%s] name=[%s] UCM values name missing", queryValues.devid, cardName); + goto OnErrorExit; + } + + json_object *jLabels = json_tokener_parse(labels); + if (!jLabels) { + afb_req_fail_f(request, "ucmget-notjson", "labels=%s not a valid json entry", labels); + goto OnErrorExit; + }; + + enum json_type jtype = json_object_get_type(jLabels); + switch (jtype) { + json_object *tmpJ; + + case json_type_array: + labelCount = json_object_array_length(jLabels); + break; + + case json_type_string: + tmpJ = json_object_new_array(); + labelCount = 1; + json_object_array_add(tmpJ, jLabels); + jLabels = tmpJ; + break; + + default: + afb_req_fail_f(request, "ucmget-notarray", "labels=%s not valid JSON array", labels); + goto OnErrorExit; + } + + for (int idx = 0; idx < labelCount; idx++) { + json_object *jValue, *jLabel; + const char *label; + + jLabel = json_object_array_get_idx(jLabels, idx); + label = json_object_get_string(jLabel); + jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, mod, label); + if (jValue) json_object_object_add(jResponse, label, jValue); + else { + json_object_array_add(jWarnings, jLabel); + } + } + + // use info section to notified not found values label + if (json_object_array_length(jWarnings) > 0) { + json_object *tmpJ = json_object_new_object(); + json_object_object_add(tmpJ, "no-context", jWarnings); + warnings = json_object_get_string(tmpJ); + } + afb_req_success(request, jResponse, warnings); + +OnErrorExit: + return; +} + +PUBLIC void alsaUseCaseSet(struct afb_req request) { + int err, ucmIdx; + queryValuesT queryValues; + json_object *jResponse = json_object_new_object(); + + json_object *queryJ = alsaCheckQuery(request, &queryValues); + if (!queryJ) goto OnErrorExit; + + + ucmIdx = alsaUseCaseOpen(request, &queryValues, TRUE); + if (ucmIdx < 0) goto OnErrorExit; + + snd_use_case_mgr_t *ucmMgr = ucmHandles[ucmIdx].ucm; + const char *cardName = ucmHandles[ucmIdx].cardName; + + const char *verb = afb_req_value(request, "verb"); + const char *mod = afb_req_value(request, "mod"); + const char *dev = afb_req_value(request, "dev"); + // Known identifiers: _verb - set current verb = value _enadev - enable given device = value _disdev - disable given device = value _swdev/{old_device} - new_device = value + + if (verb) { + err = snd_use_case_set(ucmMgr, "_verb", verb); + if (err) { + afb_req_fail_f(request, "ucmset-verb", "SndCard devid=[%s] name=[%s] Invalid UCM verb=[%s] err=%s", queryValues.devid, cardName, verb, snd_strerror(err)); + goto OnErrorExit; + } + } + + if (dev) { + err = snd_use_case_set(ucmMgr, "_enadev", dev); + if (err) { + afb_req_fail_f(request, "ucmset-dev", "SndCard devid=[%s] name=[%s] Invalid UCMverb=[%s] dev=%s err=%s", queryValues.devid, cardName, verb, dev, snd_strerror(err)); + goto OnErrorExit; + } + } + + if (mod) { + err = snd_use_case_set(ucmMgr, "_enamod", mod); + if (err) { + afb_req_fail_f(request, "ucmset-mod", "SndCard devid=[%s] name=[%s] Invalid UCM verb=[%s] mod=[%s] err=%s", queryValues.devid, cardName, verb, mod, snd_strerror(err)); + goto OnErrorExit; + } + } + + // label are requested transfert request to get + if (afb_req_value(request, "value")) return alsaUseCaseGet(request); + + if (queryValues.mode <= 3) { + json_object *jValue; + + jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, dev, "OutputDspName"); + if (jValue) json_object_object_add(jResponse, "OutputDspName", jValue); + + jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, dev, "PlaybackPCM"); + if (jValue) json_object_object_add(jResponse, "PlaybackPCM", jValue); + + jValue = ucmGetValue(&ucmHandles[ucmIdx], verb, mod, "CapturePCM"); + if (jValue) json_object_object_add(jResponse, "CapturePCM", jValue); + } + afb_req_success(request, jResponse, NULL); + +OnErrorExit: + return; +} + +PUBLIC void alsaUseCaseReset(struct afb_req request) { + int err, ucmIdx; + queryValuesT queryValues; + + json_object *queryJ = alsaCheckQuery(request, &queryValues); + if (!queryJ) goto OnErrorExit; + + ucmIdx = alsaUseCaseOpen(request, &queryValues, FALSE); + if (ucmIdx < 0) goto OnErrorExit; + + err = snd_use_case_mgr_reset(ucmHandles[ucmIdx].ucm); + if (err) { + afb_req_fail_f(request, "ucmreset-fail", "devid=%s Card Name=%s", queryValues.devid, ucmHandles[ucmIdx].cardName); + goto OnErrorExit; + } + + afb_req_success(request, NULL, NULL); + +OnErrorExit: + return; +} + +PUBLIC void alsaUseCaseClose(struct afb_req request) { + int err, ucmIdx; + queryValuesT queryValues; + + json_object *queryJ = alsaCheckQuery(request, &queryValues); + if (!queryJ) goto OnErrorExit; + + ucmIdx = alsaUseCaseOpen(request, &queryValues, FALSE); + if (ucmIdx < 0) goto OnErrorExit; + + err = snd_use_case_mgr_close(ucmHandles[ucmIdx].ucm); + if (err) { + afb_req_fail_f(request, "ucmreset-close", "devid=%s Card Name=%s", queryValues.devid, ucmHandles[ucmIdx].cardName); + goto OnErrorExit; + } + + // do not forget to release sound card name string + free(ucmHandles[ucmIdx].cardName); + + afb_req_success(request, NULL, NULL); + +OnErrorExit: + return; +} + + diff --git a/Alsa-afb/CMakeLists.txt b/Alsa-afb/CMakeLists.txt new file mode 100644 index 0000000..876e837 --- /dev/null +++ b/Alsa-afb/CMakeLists.txt @@ -0,0 +1,41 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll +# +# 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. +########################################################################### + +# Add target to project dependency list +PROJECT_TARGET_ADD(alsa-lowlevel) + + # Define project Targets + ADD_LIBRARY(${TARGET_NAME} MODULE Alsa-ApiHat.c Alsa-SetGet.c Alsa-Ucm.c Alsa-AddCtl.c Alsa-RegEvt.c) + + # Binder exposes a unique public entry point + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "afb-" + LABELS "BINDING" + LINK_FLAGS ${BINDINGS_LINK_FLAG} + OUTPUT_NAME ${TARGET_NAME} + ) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + audio-common + ${link_libraries} + ) + + # installation directory + INSTALL(TARGETS ${TARGET_NAME} + LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR}) diff --git a/Alsa-afb/README.md b/Alsa-afb/README.md new file mode 100644 index 0000000..93d2d4d --- /dev/null +++ b/Alsa-afb/README.md @@ -0,0 +1,33 @@ +------------------------------------------------------------------------ + AlsaCore Low level binding maps AlsaLib APIs +------------------------------------------------------------------------ + +Testing: (from project directory bindings) + * start binder: ~/opt/bin/afb-daemon --ldpaths=./build --token=mysecret --roothttp=htdocs + * connect browser on http://localhost:1234?devid=hw:0 + + # List Avaliable Sound cards + http://localhost:1234/api/alsacore/getinfo + + # Get Info on a given Sound Card + http://localhost:1234/api/alsacore/getinfo?devid=hw:0 + + # Get shortname/longname for a given card + http://localhost:1234/api/alsacore/getcardid?devid=hw:0 + + # Get all controls from a given sound card + http://localhost:1234/api/alsacore/getctl?devid=hw:0 + + # Get detail on a given control (optional mode=0=verbose,1,2) + http://localhost:1234/api/alsacore/getctl?devid=hw:0&numid=1&mode=0 + +# Debug event with afb-client-demo +``` + ~/opt/bin/afb-client-demo localhost:1234/api?token=mysecret + alsacore subscribe {"devid":"hw:0"} +``` + +# Open AlsaMixer and play with Volume +``` + alsamixer -D hw:0 +``` diff --git a/Audio-Common/audio-common.h b/Audio-Common/audio-common.h index 7bf258a..e7057d3 100644 --- a/Audio-Common/audio-common.h +++ b/Audio-Common/audio-common.h @@ -23,9 +23,8 @@ #define AUDIO_INTERF_H #define AFB_BINDING_VERSION 2 - -#include #include +#include // Waiting for official macro from José #define AFB_GET_VERBOSITY afb_get_verbosity_v2() diff --git a/Controler-afb/CMakeLists.txt b/Controler-afb/CMakeLists.txt index afebdf2..08bc377 100644 --- a/Controler-afb/CMakeLists.txt +++ b/Controler-afb/CMakeLists.txt @@ -33,7 +33,7 @@ endmacro(SET_TARGET_GENSKEL) PROJECT_TARGET_ADD(control-afb) # Define project Targets - ADD_LIBRARY(${TARGET_NAME} MODULE ctl-binding.c ctl-events.c ctl-policy.c ctl-lua.c + ADD_LIBRARY(${TARGET_NAME} MODULE ctl-binding.c ctl-events.c ctl-dispatch.c ctl-lua.c ) # Generate API-v2 hat from OpenAPI json definition @@ -45,7 +45,6 @@ PROJECT_TARGET_ADD(control-afb) LABELS "BINDING" LINK_FLAGS ${BINDINGS_LINK_FLAG} OUTPUT_NAME ${TARGET_NAME} - ) # Library dependencies (include updates automatically) @@ -56,5 +55,24 @@ PROJECT_TARGET_ADD(control-afb) # installation directory INSTALL(TARGETS ${TARGET_NAME} - LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR}) + LIBRARY DESTINATION CMAKE_INSTALL_PREFIX ) + +PROJECT_TARGET_ADD(ctl-plugin-sample) + + # Define targets + ADD_LIBRARY(${TARGET_NAME} MODULE ctl-plugin-sample.c) + + # Alsa Plugin properties + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "audio-" + OUTPUT_NAME ${TARGET_NAME} + ) + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + ${link_libraries} + ) + + # installation directory + INSTALL(TARGETS ${TARGET_NAME} + LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR}/controler) diff --git a/Controler-afb/ctl-binding.c b/Controler-afb/ctl-binding.c index 45c4e1c..fe9388f 100644 --- a/Controler-afb/ctl-binding.c +++ b/Controler-afb/ctl-binding.c @@ -31,17 +31,17 @@ PUBLIC void ctlapi_navigation (afb_req request) { - ctlapi_authorize (CTLAPI_NAVIGATION, request); + ctlapi_dispatch (CTLAPI_NAVIGATION, request); } PUBLIC void ctlapi_multimedia (afb_req request) { - ctlapi_authorize (CTLAPI_MULTIMEDIA, request); + ctlapi_dispatch (CTLAPI_MULTIMEDIA, request); } PUBLIC void ctlapi_emergency (afb_req request) { - ctlapi_authorize (CTLAPI_EMERGENCY, request); + ctlapi_dispatch (CTLAPI_EMERGENCY, request); } PUBLIC void ctlapi_monitor (afb_req request) { @@ -65,7 +65,7 @@ PUBLIC int CtlBindingInit () { int errcount=0; errcount += TimerEvtInit(); - errcount += PolicyInit(); + errcount += DispatchInit(); errcount += LuaLibInit(); AFB_DEBUG ("Audio Policy Control Binding Done errcount=%d", errcount); diff --git a/Controler-afb/ctl-binding.h b/Controler-afb/ctl-binding.h index e53d2ca..77d1b6e 100644 --- a/Controler-afb/ctl-binding.h +++ b/Controler-afb/ctl-binding.h @@ -14,33 +14,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include -#include "audio-common.h" #ifndef CONTROLER_BINDING_INCLUDE #define CONTROLER_BINDING_INCLUDE +#define AFB_BINDING_VERSION 2 +#include +#include + +#ifndef PUBLIC + #define PUBLIC +#endif +#define STATIC static + // polctl-binding.c PUBLIC int CtlBindingInit (); // ctl-timerevt.c // ---------------------- -#define DEFAULT_PAUSE_DELAY 3000 -#define DEFAULT_TEST_COUNT 1 -typedef int (*timerCallbackT)(void *context); -typedef struct { - int value; - const char *label; -} AutoTestCtxT; - -typedef struct TimerHandleS { - int count; - int delay; - AutoTestCtxT *context; - timerCallbackT callback; - sd_event_source *evtSource; -} TimerHandleT; + PUBLIC int TimerEvtInit (void); PUBLIC afb_event TimerEvtGet(void); @@ -49,46 +42,39 @@ PUBLIC void ctlapi_event_test (afb_req request); // ctl-policy // ----------- -typedef struct PolicyActionS{ - const char* label; - const char* api; - const char* verb; - json_object *argsJ; - const char *info; - int timeout; - json_object* (*actionCB)(struct PolicyActionS *action,json_object *response, void *context); -} PolicyActionT; - -typedef struct { - const char* label; - const char *info; - const char *version; - void *context; -} PolicyHandleT; - -typedef struct { - char *sharelib; - void *dlHandle; - PolicyHandleT **handle; - PolicyActionT **onload; - PolicyActionT **events; - PolicyActionT **controls; -} PolicyCtlConfigT; - typedef enum { CTLAPI_NAVIGATION, CTLAPI_MULTIMEDIA, CTLAPI_EMERGENCY, CTL_NONE=-1 -} PolicyCtlEnumT; +} DispatchCtlEnumT; -PUBLIC int PolicyInit(void); -PUBLIC json_object* ScanForConfig (char* searchPath, char * pre, char *ext); -PUBLIC void ctlapi_authorize (PolicyCtlEnumT control, afb_req request); + +typedef enum { + CTL_MODE_NONE=0, + CTL_MODE_API, + CTL_MODE_CB, + CTL_MODE_LUA, +} CtlRequestModeT; + +typedef struct DispatchActionS{ + const char *info; + const char* label; + CtlRequestModeT mode; + const char* api; + const char* call; + json_object *argsJ; + int timeout; + json_object* (*actionCB)(struct DispatchActionS *action,json_object *response, void *context); +} DispatchActionT; + +PUBLIC int DispatchInit(void); +PUBLIC void ctlapi_dispatch (DispatchCtlEnumT control, afb_req request); // ctl-lua.c PUBLIC int LuaLibInit (); +PUBLIC json_object* ScanForConfig (char* searchPath, char * pre, char *ext); PUBLIC void ctlapi_lua_docall (afb_req request); PUBLIC void ctlapi_lua_dostring (afb_req request); PUBLIC void ctlapi_lua_doscript (afb_req request); diff --git a/Controler-afb/ctl-dispatch.c b/Controler-afb/ctl-dispatch.c new file mode 100644 index 0000000..cda3431 --- /dev/null +++ b/Controler-afb/ctl-dispatch.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll + * + * 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, something express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Reference: + * Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values + */ + +#define _GNU_SOURCE +#include +#include +#include + + +#include + +#include "wrap-json.h" +#include "ctl-binding.h" + + +typedef struct { + const char* label; + const char *info; + const char *version; + DispatchActionT *actions; +} DispatchHandleT; + + +typedef struct { + const char* label; + const char *info; + const char *version; + char *plugin; + void *dlHandle; + DispatchHandleT *onload; + DispatchHandleT *events; + DispatchHandleT *controls; +} DispatchConfigT; + +STATIC DispatchConfigT *ctlHandle = NULL; + +PUBLIC void ctlapi_dispatch (DispatchCtlEnumT control, afb_req request) { + json_object*tmpJ; + + json_object* argsJ= afb_req_json(request); + int done=json_object_object_get_ex(argsJ, "closing", &tmpJ); + if (done) return; + +} + +// List Avaliable Configuration Files +PUBLIC void ctlapi_config (struct afb_req request) { + json_object*tmpJ; + char *dirList; + + json_object* argsJ = afb_req_json(request); + if (argsJ && json_object_object_get_ex (argsJ, "cfgpath" , &tmpJ)) { + dirList = strdup (json_object_get_string(tmpJ)); + } else { + dirList = strdup (CONTROL_DISPATCH_PATH); + AFB_NOTICE ("CONFIG-MISSING: use default CONTROL_DISPATCH_PATH=%s", CONTROL_DISPATCH_PATH); + } + + // get list of config file + struct json_object *responseJ = ScanForConfig(dirList, "onload", "json"); + + if (json_object_array_length(responseJ) == 0) { + afb_req_fail(request, "CONFIGPATH:EMPTY", "No Config Found in CONTROL_DISPATCH_PATH"); + } else { + afb_req_success(request, responseJ, NULL); + } + + return; +} + +// unpack individual action object +STATIC int DispatchLoadOneAction (json_object *actionJ, DispatchActionT *action) { + char *api, *verb, *callback, *lua; + int err, modeCount=0; + + err= wrap_json_unpack(actionJ, "{ss,s?s,s?s,s?o,s?s,s?s,s?s !}" + , "label",&action->label, "info",&action->info, "callback",&callback, "lua", &lua, "args",&action->argsJ, "api",&api, "verb", &verb); + if (err) { + AFB_ERROR ("DISPATCH-LOAD-ACTION Missing something label|info|callback|lua|(api+verb)|args in %s", json_object_get_string(actionJ)); + goto OnErrorExit; + } + + if (lua) { + action->mode = CTL_MODE_LUA; + action->call=lua; + modeCount++; + } + + if (api && verb) { + action->mode = CTL_MODE_API; + action->api=api; + action->call=verb; + modeCount++; + } + + if (callback) { + action->mode = CTL_MODE_CB; + action->call=callback; + modeCount++; + + action->actionCB = dlsym(ctlHandle->dlHandle, callback); + if (!action->actionCB) { + AFB_ERROR ("DISPATCH-LOAD-ACTION fail to find calbback=%s in %s", callback, ctlHandle->plugin); + goto OnErrorExit; + } + } + + // make sure at least one mode is selected + if (modeCount == 0) { + AFB_ERROR ("DISPATCH-LOAD-ACTION Missing something lua|callback|(api+verb) in %s", json_object_get_string(actionJ)); + goto OnErrorExit; + } + + if (modeCount > 1) { + AFB_ERROR ("DISPATCH-LOAD-ACTION ToMany lua|callback|(api+verb) in %s", json_object_get_string(actionJ)); + goto OnErrorExit; + } + return 0; + +OnErrorExit: + return -1; +}; + +STATIC DispatchActionT *DispatchLoadActions (json_object *actionsJ) { + int err; + DispatchActionT *actions; + + // action array is close with a nullvalue; + if (json_object_get_type(actionsJ) == json_type_array) { + int count = json_object_array_length(actionsJ); + actions = calloc (count+1, sizeof(DispatchActionT)); + + for (int idx=0; idx < count; idx++) { + json_object *actionJ = json_object_array_get_idx(actionsJ, idx); + err = DispatchLoadOneAction (actionJ, &actions[idx]); + if (err) goto OnErrorExit; + } + + } else { + actions = calloc (2, sizeof(DispatchActionT)); + err = DispatchLoadOneAction (actionsJ, &actions[0]); + if (err) goto OnErrorExit; + } + + return actions; + + OnErrorExit: + return NULL; + +} + +STATIC DispatchConfigT *DispatchLoadConfig (const char* filepath) { + json_object *dispatchConfigJ, *ignoreJ; + int err; + + // Load JSON file + dispatchConfigJ= json_object_from_file(filepath); + if (!dispatchConfigJ) goto OnErrorExit; + + AFB_INFO ("DISPATCH-LOAD-CONFIG: loading config filepath=%s", filepath); + + json_object *metadataJ=NULL, *onloadJ=NULL, *controlsJ=NULL, *eventsJ=NULL; + err= wrap_json_unpack(dispatchConfigJ, "{s?o,so,s?o,s?o,s?o !}", "$schema", &ignoreJ, "metadata",&metadataJ, "onload",&onloadJ, "controls",&controlsJ, "events",&eventsJ); + if (err) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG Missing something metadata|[onload]|[controls]|[events] in %s", json_object_get_string(dispatchConfigJ)); + goto OnErrorExit; + } + + DispatchConfigT *dispatchConfig = calloc (1, sizeof(DispatchConfigT)); + if (metadataJ) { + err= wrap_json_unpack(metadataJ, "{so,s?s,ss !}", "label", &dispatchConfig->label, "info",&dispatchConfig->info, "version",&dispatchConfig->version); + if (err) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG:METADATA Missing something label|version|[label] in %s", json_object_get_string(metadataJ)); + goto OnErrorExit; + } + } + + if (onloadJ) { + json_object * actionsJ; + DispatchHandleT *dispatchHandle = calloc (1, sizeof(DispatchHandleT)); + + err= wrap_json_unpack(onloadJ, "{so,s?s,s?s,s?o !}", "label",&dispatchHandle->label, "info",&dispatchHandle->info, "plugin", &dispatchConfig->plugin, "actions",&actionsJ); + if (err) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG:ONLOAD Missing something label|[info]|[plugin]|[actions] in %s", json_object_get_string(onloadJ)); + goto OnErrorExit; + } + + if (dispatchConfig->plugin) { + + // search for default policy config file + json_object *pluginPathJ = ScanForConfig(CONTROL_PLUGIN_PATH , dispatchConfig->plugin, NULL); + if (!pluginPathJ || json_object_array_length(pluginPathJ) == 0) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG:PLUGIN Missing plugin=%s in path=%s", dispatchConfig->plugin, CONTROL_PLUGIN_PATH); + goto OnErrorExit; + } + + char *filename; char*dirpath; + err= wrap_json_unpack (json_object_array_get_idx(pluginPathJ,1), "{s:s, s:s !}", "dirpath", &dirpath,"filename", &filename); + if (err) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG:PLUGIN HOOPs invalid plugin file path = %s", json_object_get_string(pluginPathJ)); + goto OnErrorExit; + } + + if (json_object_array_length(pluginPathJ) > 1) { + AFB_WARNING ("DISPATCH-LOAD-CONFIG:PLUGIN plugin multiple instances in searchpath will use %s/%s", dirpath, filepath); + } + + char filepath[255]; + strncpy(filepath, dirpath, sizeof(filepath)); + strncat(filepath, "/", sizeof(filepath)); + strncat(filepath, filename, sizeof(filepath)); + dispatchConfig->dlHandle = dlopen(filepath, RTLD_NOW); + if (!dispatchConfig->dlHandle) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG:PLIUGIN Fail to load filepath=%s err= %s", filepath, dlerror()); + goto OnErrorExit; + } + } + + dispatchHandle->actions= DispatchLoadActions(actionsJ); + dispatchConfig->onload= dispatchHandle; + } + + if (controlsJ) { + json_object * actionsJ; + DispatchHandleT *dispatchHandle = calloc (1, sizeof(DispatchHandleT)); + + err= wrap_json_unpack(controlsJ, "{so,s?s,so !}", "label",&dispatchHandle->label, "info",&dispatchHandle->info, "actions",&actionsJ); + if (err) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG:CONTROLS Missing something label|[info]|actions in %s", json_object_get_string(controlsJ)); + goto OnErrorExit; + } + dispatchHandle->actions= DispatchLoadActions(actionsJ); + dispatchConfig->onload= dispatchHandle; + } + + if (eventsJ) { + json_object * actionsJ; + DispatchHandleT *dispatchHandle = calloc (1, sizeof(DispatchHandleT)); + + err= wrap_json_unpack(eventsJ, "{so,s?s,so !}", "label",&dispatchHandle->label, "info",&dispatchHandle->info, "actions",&actionsJ); + if (err) { + AFB_ERROR ("DISPATCH-LOAD-CONFIG:EVENTS Missing something label|[info]|actions in %s", json_object_get_string(eventsJ)); + goto OnErrorExit; + } + dispatchHandle->actions= DispatchLoadActions(actionsJ); + dispatchConfig->onload= dispatchHandle; + } + + return dispatchConfig; + +OnErrorExit: + return NULL; +} + +// Load default config file at init +PUBLIC int DispatchInit () { + int index, err; + + + // search for default dispatch config file + json_object* responseJ = ScanForConfig(CONTROL_DISPATCH_PATH, "onload", "json"); + + for (index=0; index < json_object_array_length(responseJ); index++) { + json_object *entryJ=json_object_array_get_idx(responseJ, index); + + char *filename; char*dirpath; + err= wrap_json_unpack (entryJ, "{s:s, s:s !}", "dirpath", &dirpath,"filename", &filename); + if (err) { + AFB_ERROR ("DISPATCH-INIT HOOPs invalid config file path = %s", json_object_get_string(entryJ)); + goto OnErrorExit; + } + + if (strcasestr(filename, CONTROL_DISPATCH_FILE)) { + char filepath[255]; + strncpy(filepath, dirpath, sizeof(filepath)); + strncat(filepath, "/", sizeof(filepath)); + strncat(filepath, filename, sizeof(filepath)); + ctlHandle = DispatchLoadConfig (filepath); + if (!ctlHandle) { + AFB_ERROR ("DISPATCH-INIT:ERROR No File to load [%s]", filepath); + goto OnErrorExit; + } + break; + } + } + + // no dispatch config found remove control API from binder + if (index == 0) { + AFB_WARNING ("DISPATCH-INIT:WARNING No Control dispatch file [%s]", CONTROL_DISPATCH_FILE); + } + + AFB_NOTICE ("DISPATCH-INIT:SUCCES: Audio Control Dispatch Init"); + return 0; + +OnErrorExit: + AFB_NOTICE ("DISPATCH-INIT:ERROR: Audio Control Dispatch Init"); + return 1; +} + + + diff --git a/Controler-afb/ctl-events.c b/Controler-afb/ctl-events.c index f444848..6f710e3 100644 --- a/Controler-afb/ctl-events.c +++ b/Controler-afb/ctl-events.c @@ -23,6 +23,22 @@ #include "ctl-binding.h" +#define DEFAULT_PAUSE_DELAY 3000 +#define DEFAULT_TEST_COUNT 1 +typedef int (*timerCallbackT)(void *context); +typedef struct { + int value; + const char *label; +} AutoTestCtxT; + +typedef struct TimerHandleS { + int count; + int delay; + AutoTestCtxT *context; + timerCallbackT callback; + sd_event_source *evtSource; +} TimerHandleT; + static afb_event afbevt; STATIC int TimerNext (sd_event_source* source, uint64_t timer, void* handle) { diff --git a/Controler-afb/ctl-lua.c b/Controler-afb/ctl-lua.c index 59ffbe8..8b40e38 100644 --- a/Controler-afb/ctl-lua.c +++ b/Controler-afb/ctl-lua.c @@ -31,6 +31,8 @@ #include "ctl-binding.h" #include "wrap-json.h" +#define LUA_FIRST_ARG 2 // when using luaL_newlib calllback receive libtable as 1st arg +#define LUA_MSG_MAX_LENGTH 255 static lua_State* luaState; @@ -55,14 +57,25 @@ typedef struct { } LuaCallServiceT; typedef enum { + AFB_MSG_INFO, AFB_MSG_WARNING, AFB_MSG_NOTICE, + AFB_MSG_DEBUG, + AFB_MSG_ERROR, } LuaAfbMessageT; +/* + * Note(Fulup): I fail to use luaL_setmetatable and replaced it with a simple opaque + * handle while waiting for someone smarter than me to find a better solution + * https://stackoverflow.com/questions/45596493/lua-using-lua-newuserdata-from-lua-pcall + */ + STATIC LuaAfbContextT *LuaCtxCheck (lua_State *luaState, int index) { LuaAfbContextT *afbContext; - luaL_checktype(luaState, index, LUA_TUSERDATA); - afbContext = (LuaAfbContextT *)luaL_checkudata(luaState, index, CTX_TOKEN); + //luaL_checktype(luaState, index, LUA_TUSERDATA); + //afbContext = (LuaAfbContextT *)luaL_checkudata(luaState, index, CTX_TOKEN); + luaL_checktype(luaState, index, LUA_TLIGHTUSERDATA); + afbContext = (LuaAfbContextT *) lua_touserdata(luaState, index); if (afbContext == NULL && afbContext->ctxMagic != CTX_MAGIC) { luaL_error(luaState, "Fail to retrieve user data context=%s", CTX_TOKEN); AFB_ERROR ("afbContextCheck error retrieving afbContext"); @@ -72,20 +85,21 @@ STATIC LuaAfbContextT *LuaCtxCheck (lua_State *luaState, int index) { } STATIC LuaAfbContextT *LuaCtxPush (lua_State *luaState, afb_req request, const char* info) { - LuaAfbContextT *afbContext = (LuaAfbContextT *)lua_newuserdata(luaState, sizeof(LuaAfbContextT)); + // LuaAfbContextT *afbContext = (LuaAfbContextT *)lua_newuserdata(luaState, sizeof(LuaAfbContextT)); + // luaL_setmetatable(luaState, CTX_TOKEN); + LuaAfbContextT *afbContext = (LuaAfbContextT *)calloc(1, sizeof(LuaAfbContextT)); + lua_pushlightuserdata(luaState, afbContext); if (!afbContext) { AFB_ERROR ("LuaCtxPush fail to allocate user data context"); return NULL; } - luaL_setmetatable(luaState, CTX_TOKEN); afbContext->ctxMagic=CTX_MAGIC; afbContext->info=strdup(info); afbContext->request= request; return afbContext; } -STATIC void LuaCtxFree (LuaAfbContextT *afbContext) { - +STATIC void LuaCtxFree (LuaAfbContextT *afbContext) { free (afbContext->info); } @@ -95,73 +109,57 @@ PUBLIC json_object* ScanForConfig (char* searchPath, char *pre, char *ext) { DIR *dirHandle; char *dirPath; char* dirList= strdup(searchPath); - size_t extLen = strlen(ext); + size_t extLen=0; - responseJ = json_object_new_array(); - for (dirPath= strtok(dirList, ":"); dirPath && *dirPath; dirPath=strtok(NULL,":")) { - struct dirent *dirEnt; - + void ScanDir (char *dirpath) { + struct dirent *dirEnt; dirHandle = opendir (dirPath); if (!dirHandle) { AFB_NOTICE ("CONFIG-SCANNING dir=%s not readable", dirPath); - continue; + return; } AFB_NOTICE ("CONFIG-SCANNING:ctl_listconfig scanning: %s", dirPath); while ((dirEnt = readdir(dirHandle)) != NULL) { + + // recursively search embedded directories ignoring any directory starting by '.' or '_' + if (dirEnt->d_type == DT_DIR) { + char newpath[CONTROL_MAXPATH_LEN]; + if (dirEnt->d_name[0]=='.' || dirEnt->d_name[0]=='_') continue; + + strncpy(newpath, dirpath, sizeof(newpath)); + strncat(newpath, "/", sizeof(newpath)); + strncat(newpath, dirEnt->d_name, sizeof(newpath)); + } + // Unknown type is accepted to support dump filesystems if (dirEnt->d_type == DT_REG || dirEnt->d_type == DT_UNKNOWN) { - + // check prefix and extention - size_t extIdx=strlen(dirEnt->d_name) - extLen; + size_t extIdx=strlen(dirEnt->d_name)-extLen; if (extIdx <= 0) continue; if (pre && !strcasestr (dirEnt->d_name, pre)) continue; if (ext && strcasecmp (ext, &dirEnt->d_name[extIdx])) continue; - + struct json_object *pathJ = json_object_new_object(); json_object_object_add(pathJ, "dirpath", json_object_new_string(dirPath)); json_object_object_add(pathJ, "filename", json_object_new_string(dirEnt->d_name)); json_object_array_add(responseJ, pathJ); } + closedir(dirHandle); } } - - free (dirList); - return (responseJ); -} -STATIC int LuaPrintMessage(LuaAfbMessageT action, lua_State* luaState) { + if (ext) extLen=strlen(ext); + responseJ = json_object_new_array(); - int count = lua_gettop(luaState); - for (int idx = 1; idx <= count; ++idx) { - const char *str = lua_tostring(luaState, idx); // Get string - - switch (action) { - case AFB_MSG_NOTICE: - AFB_NOTICE(str); - break; - - case AFB_MSG_WARNING: - AFB_WARNING(str); - break; - - default: - AFB_ERROR(str); - } + // loop recursively on dir + for (dirPath= strtok(dirList, ":"); dirPath && *dirPath; dirPath=strtok(NULL,":")) { + ScanDir (dirPath); } - return 0; // no value return -} - -STATIC int LuaPrintWarning(lua_State* luaState) { - - LuaPrintMessage (AFB_MSG_WARNING, luaState); - return 0; // no value return -} - -STATIC int LuaPrintNotice(lua_State* luaState) { - - LuaPrintMessage (AFB_MSG_NOTICE, luaState); - return 0; // no value return + + free (dirList); + return (responseJ); } STATIC int LuaPushArgument (json_object *arg) { @@ -197,9 +195,8 @@ STATIC int LuaPushArgument (json_object *arg) { return 1; } -static json_object *LuaPopArgs (lua_State* luaState, int count); STATIC json_object *PopOneArg (lua_State* luaState, int idx); -static json_object *LuaTableToJson (lua_State* luaState, int index) { +STATIC json_object *LuaTableToJson (lua_State* luaState, int index) { json_object *tableJ= json_object_new_object(); const char *key; @@ -207,9 +204,9 @@ static json_object *LuaTableToJson (lua_State* luaState, int index) { lua_pushnil(luaState); // 1st key for (int jdx=1; lua_next(luaState, index) != 0; jdx++) { - printf("jdx=%d %s - %s\n", jdx, - lua_typename(luaState, lua_type(luaState, -2)), - lua_typename(luaState, lua_type(luaState, -1))); + //printf("jdx=%d %s - %s\n", jdx, + //lua_typename(luaState, lua_type(luaState, -2)), + //lua_typename(luaState, lua_type(luaState, -1))); // uses 'key' (at index -2) and 'value' (at index -1) if (lua_type(luaState,-2) == LUA_TSTRING) key= lua_tostring(luaState, -2); @@ -228,7 +225,8 @@ static json_object *LuaTableToJson (lua_State* luaState, int index) { STATIC json_object *PopOneArg (lua_State* luaState, int idx) { json_object *value=NULL; - switch(lua_type(luaState, idx)) { + int luaType = lua_type(luaState, idx); + switch(luaType) { case LUA_TNUMBER: value= json_object_new_double(lua_tonumber(luaState, idx)); break; @@ -239,33 +237,31 @@ STATIC json_object *PopOneArg (lua_State* luaState, int idx) { value= json_object_new_string(lua_tostring(luaState, idx)); break; case LUA_TTABLE: { - AFB_NOTICE ("-++-- idx=%d ", idx); + AFB_NOTICE ("-++-- luatable idx=%d ", idx); value= LuaTableToJson(luaState, idx); break; } default: - AFB_NOTICE ("script returned Unknown/Unsupported idx=%d type: %s", idx, lua_typename(luaState, idx)); + AFB_NOTICE ("PopOneArg: script returned Unknown/Unsupported idx=%d type:%d/%s", idx, luaType, lua_typename(luaState, luaType)); value=NULL; } - if (value) AFB_NOTICE ("---- idx=%d value=%s", idx, json_object_get_string(value)); return value; } -static json_object *LuaPopArgs (lua_State* luaState, int count) { - - +static json_object *LuaPopArgs (lua_State* luaState, int start) { json_object *responseJ; - if(count <=0) return NULL; + int stop = lua_gettop(luaState); + if(stop-start <=0) return NULL; // start at 2 because we are using a function array lib - if (count == 2) { - responseJ=PopOneArg (luaState, 2); + if (start == stop) { + responseJ=PopOneArg (luaState, start); } else { // loop on remaining return arguments responseJ= json_object_new_array(); - for (int idx=2; idx <= count; idx++) { + for (int idx=start; idx <= stop; idx++) { json_object_array_add(responseJ, PopOneArg (luaState, idx)); } } @@ -274,18 +270,104 @@ static json_object *LuaPopArgs (lua_State* luaState, int count) { } -STATIC int LuaAfbSuccess(lua_State* luaState) { +STATIC void LuaFormatMessage(lua_State* luaState, LuaAfbMessageT action) { + char *message; + json_object *responseJ= LuaPopArgs(luaState, LUA_FIRST_ARG); + if (!responseJ) { + message="--"; + goto PrintMessage; + } - int count = lua_gettop(luaState); + // if we have only on argument just return the value. + if (json_object_get_type(responseJ)!=json_type_array || json_object_array_length(responseJ) <2) { + message= (char*)json_object_get_string(responseJ); + goto PrintMessage; + } + + // extract format and push all other parameter on the stack + message = alloca (LUA_MSG_MAX_LENGTH); + const char *format = json_object_get_string(json_object_array_get_idx(responseJ, 0)); + + int arrayIdx=1; + int targetIdx=0; + + for (int idx=0; format[idx] !='\0'; idx++) { + + if (format[idx]=='%' && format[idx] !='\0') { + json_object *slotJ= json_object_array_get_idx(responseJ, arrayIdx); + + switch (format[++idx]) { + case 'd': + targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"%d", json_object_get_int(slotJ)); + break; + case 'f': + targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"%f", json_object_get_double(slotJ)); + break; + + case 's': + default: + targetIdx += snprintf (&message[targetIdx], LUA_MSG_MAX_LENGTH-targetIdx,"%s", json_object_get_string(slotJ)); + } + + } else { + message[targetIdx++] = format[idx]; + } + } + +PrintMessage: + switch (action) { + case AFB_MSG_WARNING: + AFB_WARNING (message); + break; + case AFB_MSG_NOTICE: + AFB_NOTICE (message); + break; + case AFB_MSG_DEBUG: + AFB_DEBUG (message); + break; + case AFB_MSG_INFO: + AFB_INFO (message); + break; + case AFB_MSG_ERROR: + default: + AFB_ERROR (message); + } +} +STATIC int LuaPrintInfo(lua_State* luaState) { + LuaFormatMessage (luaState, AFB_MSG_INFO); + return 0; // no value return +} - //AFB_NOTICE ("LuaAfbSuccess args=%s",json_object_get_string(LuaPopArgs(luaState, count))); - - LuaAfbContextT *afbContext= LuaCtxCheck(luaState, 1); +STATIC int LuaPrintError(lua_State* luaState) { + LuaFormatMessage (luaState, AFB_MSG_ERROR); + return 0; // no value return +} + +STATIC int LuaPrintWarning(lua_State* luaState) { + LuaFormatMessage (luaState, AFB_MSG_WARNING); + return 0; // no value return +} + +STATIC int LuaPrintNotice(lua_State* luaState) { + LuaFormatMessage (luaState, AFB_MSG_NOTICE); + return 0; // no value return +} + +STATIC int LuaPrintDebug(lua_State* luaState) { + LuaFormatMessage (luaState, AFB_MSG_DEBUG); + return 0; // no value return +} + +STATIC int LuaAfbSuccess(lua_State* luaState) { + LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIRST_ARG); if (!afbContext) goto OnErrorExit; + + // ignore context argument + json_object *responseJ= LuaPopArgs(luaState, LUA_FIRST_ARG+1); - afb_req_success(afbContext->request, LuaPopArgs(luaState, count), NULL); + afb_req_success(afbContext->request, responseJ, NULL); LuaCtxFree(afbContext); return 0; @@ -296,13 +378,12 @@ STATIC int LuaAfbSuccess(lua_State* luaState) { } STATIC int LuaAfbFail(lua_State* luaState) { - - LuaAfbContextT *afbContext= LuaCtxCheck(luaState, 1); + LuaAfbContextT *afbContext= LuaCtxCheck(luaState, LUA_FIRST_ARG); if (!afbContext) goto OnErrorExit; - int count = lua_gettop(luaState); + json_object *responseJ= LuaPopArgs(luaState, LUA_FIRST_ARG+1); - afb_req_fail(afbContext->request, afbContext->info, json_object_get_string(LuaPopArgs(luaState, count))); + afb_req_fail(afbContext->request, afbContext->info, json_object_get_string(responseJ)); LuaCtxFree(afbContext); return 0; @@ -402,15 +483,14 @@ PUBLIC void LuaDoAction (LuaDoActionT action, afb_req request) { goto OnErrorExit; } + + // load function (should exist in CONTROL_PATH_LUA + lua_getglobal(luaState, func); + // Push AFB client context on the stack LuaAfbContextT *afbContext= LuaCtxPush(luaState, request, func); if (!afbContext) goto OnErrorExit; - - // load function (should exist in CONTROL_PATH_LUA - lua_getglobal(luaState, func); - - - + // push arguments on the stack if (json_object_get_type(args) != json_type_array) { count= LuaPushArgument (args); @@ -420,7 +500,7 @@ PUBLIC void LuaDoAction (LuaDoActionT action, afb_req request) { if (err) break; } } - + break; } @@ -431,14 +511,18 @@ PUBLIC void LuaDoAction (LuaDoActionT action, afb_req request) { // scan luascript search path once static json_object *luaScriptPathJ =NULL; - if (!luaScriptPathJ) luaScriptPathJ= ScanForConfig(CONTROL_PATH_LUA , NULL, "lua"); + if (!luaScriptPathJ) luaScriptPathJ= ScanForConfig(CONTROL_LUA_PATH , NULL, "lua"); err= wrap_json_unpack (queryJ, "{s:s, s?o s?o !}", "script", &script,"args", &args, "arg", &args); if (err) { AFB_ERROR ("LUA-DOCALL-SYNTAX missing script|(args,arg) args=%s", json_object_get_string(queryJ)); goto OnErrorExit; } - + + // Push AFB client context on the stack + LuaAfbContextT *afbContext= LuaCtxPush(luaState, request, script); + if (!afbContext) goto OnErrorExit; + // push arguments on the stack if (json_object_get_type(args) != json_type_array) { lua_createtable(luaState, 1, 0); @@ -452,9 +536,6 @@ PUBLIC void LuaDoAction (LuaDoActionT action, afb_req request) { } } - LuaAfbContextT *afbContext= LuaCtxPush(luaState, request, script); - if (!afbContext) goto OnErrorExit; - for (index=0; index < json_object_array_length(luaScriptPathJ); index++) { char *filename; char*dirpath; json_object *entryJ=json_object_array_get_idx(luaScriptPathJ, index); @@ -480,10 +561,10 @@ PUBLIC void LuaDoAction (LuaDoActionT action, afb_req request) { } if (index == json_object_array_length(luaScriptPathJ)) { - AFB_ERROR ("LUA-DOSCRIPT HOOPs script=%s not in path=%s", script, CONTROL_PATH_LUA); + AFB_ERROR ("LUA-DOSCRIPT HOOPs script=%s not in path=%s", script, CONTROL_LUA_PATH); goto OnErrorExit; - - } + } + break; } @@ -491,10 +572,9 @@ PUBLIC void LuaDoAction (LuaDoActionT action, afb_req request) { AFB_ERROR ("LUA-DOCALL-ACTION unknown query=%s", json_object_get_string(queryJ)); goto OnErrorExit; } - - + // effectively exec LUA code (afb_reply/fail done later from callback) - err=lua_pcall(luaState, count, 0, 0); + err=lua_pcall(luaState, count+1, 0, 0); if (err) { AFB_ERROR ("LUA-DO-EXEC:FAIL query=%s err=%s", json_object_get_string(queryJ), lua_tostring(luaState,-1) ); goto OnErrorExit; @@ -521,7 +601,10 @@ PUBLIC void ctlapi_lua_doscript (afb_req request) { static const luaL_Reg afbFunction[] = { {"notice" , LuaPrintNotice}, + {"info" , LuaPrintInfo}, {"warning", LuaPrintWarning}, + {"debug" , LuaPrintDebug}, + {"error" , LuaPrintError}, {"service", LuaAfbService}, {"success", LuaAfbSuccess}, {"fail" , LuaAfbFail}, @@ -533,7 +616,7 @@ PUBLIC int LuaLibInit () { int err, index; // search for default policy config file - json_object *luaScriptPathJ = ScanForConfig(CONTROL_PATH_LUA , "onload", "lua"); + json_object *luaScriptPathJ = ScanForConfig(CONTROL_LUA_PATH , "onload", "lua"); // open a new LUA interpretor luaState = luaL_newstate(); @@ -560,7 +643,7 @@ PUBLIC int LuaLibInit () { goto OnErrorExit; } - char filepath[255]; + char filepath[CONTROL_MAXPATH_LEN]; strncpy(filepath, dirpath, sizeof(filepath)); strncat(filepath, "/", sizeof(filepath)); strncat(filepath, filename, sizeof(filepath)); @@ -575,16 +658,14 @@ PUBLIC int LuaLibInit () { if (err) { AFB_ERROR ("LUA-LOAD HOOPs Error in LUA exec scripts=%s err=%s", filepath, lua_tostring(luaState,-1)); goto OnErrorExit; - } - + } } // no policy config found remove control API from binder if (index == 0) { - AFB_WARNING ("POLICY-INIT:WARNING No Control LUA file in path=[%s]", CONTROL_PATH_LUA); + AFB_WARNING ("POLICY-INIT:WARNING No Control LUA file in path=[%s]", CONTROL_LUA_PATH); } - - + AFB_DEBUG ("Audio control-LUA Init Done"); return 0; diff --git a/Controler-afb/ctl-plugin-sample.c b/Controler-afb/ctl-plugin-sample.c new file mode 100644 index 0000000..f232573 --- /dev/null +++ b/Controler-afb/ctl-plugin-sample.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author Fulup Ar Foll + * + * 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. + * + * Sample plugin for Controller + */ + + +#define _GNU_SOURCE +#include +#include + +#include "ctl-binding.h" + +#define MY_PLUGIN_MAGIC 987654321 + +typedef struct { + int magic; + int count; +} MyPluginCtxT; + +STATIC const char* jsonToString (json_object *valueJ) { + const char *value; + if (valueJ) + value=json_object_get_string(valueJ); + else + value="NULL"; + + return value; +} + +PUBLIC int SamplePolicyInstall (DispatchActionT *action, json_object *response, void *context) { + + MyPluginCtxT *pluginCtx= (MyPluginCtxT*)calloc (1, sizeof(MyPluginCtxT)); + pluginCtx->magic = MY_PLUGIN_MAGIC; + pluginCtx->count = 0; + + AFB_INFO ("CONTROLER-PLUGIN-SAMPLE SamplePolicyInstall action=%s args=%s", action->label, jsonToString(action->argsJ)); + + return 0; +} + +PUBLIC int SamplePolicyCount (DispatchActionT *action, json_object *response, void *context) { + + MyPluginCtxT *pluginCtx= (MyPluginCtxT*)context; + //pluginCtx->magic = MY_PLUGIN_MAGIC; + //pluginCtx->count = 0; + + AFB_INFO ("CONTROLER-PLUGIN-SAMPLE SamplePolicyCount action=%s args=%s count=%d", action->label, jsonToString(action->argsJ), pluginCtx->count); + + return 0; +} diff --git a/Controler-afb/ctl-policy.c b/Controler-afb/ctl-policy.c deleted file mode 100644 index 7ad73fa..0000000 --- a/Controler-afb/ctl-policy.c +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2016 "IoT.bzh" - * Author Fulup Ar Foll - * - * 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, something express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define _GNU_SOURCE -#include -#include - -#include - -#include "wrap-json.h" -#include "ctl-binding.h" - -STATIC PolicyCtlConfigT *ctlHandle = NULL; - - -PUBLIC void ctlapi_authorize (PolicyCtlEnumT control, afb_req request) { - json_object*tmpJ; - - json_object* argsJ= afb_req_json(request); - int done=json_object_object_get_ex(argsJ, "closing", &tmpJ); - if (done) return; - -} - - -// List Avaliable Configuration Files -PUBLIC void ctlapi_config (struct afb_req request) { - json_object*tmpJ; - char *dirList; - - json_object* argsJ = afb_req_json(request); - if (argsJ && json_object_object_get_ex (argsJ, "cfgpath" , &tmpJ)) { - dirList = strdup (json_object_get_string(tmpJ)); - } else { - dirList = strdup (CONTROL_PATH_POLICY); - AFB_NOTICE ("CONFIG-MISSING: use default CONTROL_PATH_POLICY=%s", CONTROL_PATH_POLICY); - } - - // get list of config file - struct json_object *responseJ = ScanForConfig(dirList, "onload", "json"); - - if (json_object_array_length(responseJ) == 0) { - afb_req_fail(request, "CONFIGPATH:EMPTY", "No Config Found in CONTROL_PATH_POLICY"); - } else { - afb_req_success(request, responseJ, NULL); - } - - return; -} - -STATIC PolicyActionT *PolicyLoadActions (json_object *actionsJ) { - int err; - PolicyActionT *actions; - char *callback; // need to search in DL lib - - // unpack individual action object - int actionUnpack (json_object *actionJ, PolicyActionT *action) { - - err= wrap_json_unpack(actionJ, "{ss,s?s,s?o,s?s,s?s,s?s !}" - , "label",&action->label, "info",&action->info, "callback",&callback, "args",&action->argsJ, "api",&action->api, "verb", &action->verb); - if (err) { - AFB_ERROR ("POLICY-LOAD-ACTION Missing something label|info|callback|api|verb|args in %s", json_object_get_string(actionJ)); - return -1; - } - if (!callback || !(action->api && action->verb)) { - AFB_ERROR ("POLICY-LOAD-ACTION Missing something callback|(api+verb) in %s", json_object_get_string(actionJ)); - return -1; - } - return 0; - }; - - // action array is close with a nullvalue; - if (json_object_get_type(actionsJ) == json_type_array) { - int count = json_object_array_length(actionsJ); - actions = calloc (count+1, sizeof(PolicyActionT)); - - for (int idx=0; idx < count; idx++) { - json_object *actionJ = json_object_array_get_idx(actionsJ, idx); - err = actionUnpack (actionJ, &actions[idx]); - if (err) goto OnErrorExit; - } - - } else { - actions = calloc (2, sizeof(PolicyActionT)); - err = actionUnpack (actionsJ, &actions[0]); - if (err) goto OnErrorExit; - } - - return actions; - - OnErrorExit: - return NULL; - -} - -// load control policy from file using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values -STATIC PolicyCtlConfigT *PolicyLoadConfig (const char* filepath) { - json_object *policyConfigJ, *ignoreJ, *actionsJ; - PolicyCtlConfigT *policyConfig = calloc (1, sizeof(PolicyCtlConfigT)); - int err; - - // Load JSON file - policyConfigJ= json_object_from_file(filepath); - if (!policyConfigJ) goto OnErrorExit; - - json_object *metadataJ, *onloadJ, *controlsJ, *eventsJ; - err= wrap_json_unpack(policyConfigJ, "{s?o,so,s?o,so,so !}", "$schema", &ignoreJ, "metadata",&metadataJ, "onload",&onloadJ, "controls",&controlsJ, "events",&eventsJ); - if (err) { - AFB_ERROR ("POLICY-LOAD-ERRROR Missing something metadata|onload|controls|events in %s", json_object_get_string(policyConfigJ)); - goto OnErrorExit; - } - - PolicyHandleT *policyHandle = calloc (1, sizeof(PolicyHandleT)); - err= wrap_json_unpack(metadataJ, "{so,s?s,s?s !}", "label", &policyHandle->label, "info",&policyHandle->info, "version",&policyHandle->version); - if (err) { - AFB_ERROR ("POLICY-LOAD-CONFIG Missing something label|info|version in %s", json_object_get_string(metadataJ)); - goto OnErrorExit; - } - - if (onloadJ) { - err= wrap_json_unpack(onloadJ, "{s?o,s?s,s?s !}", "info",&ignoreJ, "label",&ignoreJ, "actions",&actionsJ); - if (err) { - AFB_ERROR ("POLICY-LOAD-CONFIG Missing something label|info|plugin|actions in %s", json_object_get_string(metadataJ)); - goto OnErrorExit; - } - policyConfig->onload= PolicyLoadActions (actionsJ); - } - - return policyConfig; - -OnErrorExit: - return NULL; -} - -// Load default config file at init -PUBLIC int PolicyInit () { - int index, err; - - - // search for default policy config file - json_object* responseJ = ScanForConfig(CONTROL_PATH_POLICY, "onload", "json"); - return 0; - - for (index=0; index < json_object_array_length(responseJ); index++) { - json_object *entryJ=json_object_array_get_idx(responseJ, index); - - char *filename; char*dirpath; - err= wrap_json_unpack (entryJ, "{s:s, s:s !}", "dirpath", &dirpath,"filename", &filename); - if (err) { - AFB_ERROR ("POLICY-INIT HOOPs invalid config file path = %s", json_object_get_string(entryJ)); - goto OnErrorExit; - } - - if (strcasestr(filename, CONTROL_FILE_POLICY)) { - char filepath[255]; - strncpy(filepath, dirpath, sizeof(filepath)); - strncat(filepath, "/", sizeof(filepath)); - strncat(filepath, filename, sizeof(filepath)); - ctlHandle = PolicyLoadConfig (filepath); - if (!ctlHandle) { - AFB_ERROR ("POLICY-INIT:ERROR No File to load [%s]", filepath); - goto OnErrorExit; - } - break; - } - } - - // no policy config found remove control API from binder - if (index == 0) { - AFB_WARNING ("POLICY-INIT:WARNING No Control policy file [%s]", CONTROL_FILE_POLICY); - } - - AFB_NOTICE ("POLICY-INIT:SUCCES: Audio Control Policy Init"); - return 0; - -OnErrorExit: - AFB_NOTICE ("POLICY-INIT:ERROR: Audio Control Policy Init"); - return 1; -} - - - diff --git a/conf.d/app-templates b/conf.d/app-templates index dee5836..5b4dfe2 160000 --- a/conf.d/app-templates +++ b/conf.d/app-templates @@ -1 +1 @@ -Subproject commit dee58363ddb98f8e63239035f1a8f1ab151c5e96 +Subproject commit 5b4dfe23ef1141d8f7595a157ca7a8ee5ad8ad07 diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index 6fad621..5b741d8 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -65,25 +65,13 @@ set (PKG_REQUIRED_LIST lua>=5.3 ) -# LANG Specific compile flags set for all build types -# set(CMAKE_C_FLAGS "") -# set(CMAKE_CXX_FLAGS "") - -# Do not optimise when debugging -set(CMAKE_C_FLAGS_DEBUG "-g -ggdb -Wp,-U_FORTIFY_SOURCE") - -# (BUG!!!) as PKG_CONFIG_PATH does not work [should be an env variable] -# --------------------------------------------------------------------- -set(CMAKE_INSTALL_PREFIX $ENV{HOME}/opt) -set(CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX}/lib64/pkgconfig ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) -set(LD_LIBRARY_PATH ${CMAKE_INSTALL_PREFIX}/lib64 ${CMAKE_INSTALL_PREFIX}/lib) - # Define CONTROL_CDEV_NAME should match MOST driver values # --------------------------------------------------------- - add_compile_options(-DCONTROL_FILE_POLICY="onload-control-policy.json") - add_compile_options(-DCONTROL_PATH_POLICY="/etc/default/audio-agent/policy:$ENV{HOME}/.config/audio-agent:${CMAKE_INSTALL_PREFIX}/audio-agent/policy:${CMAKE_SOURCE_DIR}/data") - - add_compile_options(-DCONTROL_PATH_LUA="/etc/default/audio-agent/policy:$ENV{HOME}/.config/audio-agent:${CMAKE_INSTALL_PREFIX}/audio-agent/policy:${CMAKE_SOURCE_DIR}/data") + add_compile_options(-DCONTROL_MAXPATH_LEN=255) + add_compile_options(-DCONTROL_DISPATCH_FILE="onload-control-policy.json") + add_compile_options(-DCONTROL_DISPATCH_PATH="${CMAKE_CURRENT_BINARY_DIR}/Controler-afb:${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/controler") + add_compile_options(-DCONTROL_LUA_PATH="/etc/default/audio-agent/policy:$ENV{HOME}/.config/audio-agent:${CMAKE_INSTALL_PREFIX}/audio-agent/policy:${CMAKE_SOURCE_DIR}/data") + add_compile_options(-DCONTROL_PLUGIN_PATH="${BINDINGS_INSTALL_DIR}/controler:/usr/lib/${PROJECT_NAME}") # Print a helper message when every thing is finished # ---------------------------------------------------- diff --git a/data/onload-control-policy.json b/data/onload-control-policy.json index d910893..736e188 100644 --- a/data/onload-control-policy.json +++ b/data/onload-control-policy.json @@ -1,22 +1,31 @@ { "$schema": "ToBeDone", "metadata": { - "label": "sample-audio-policy", + "label": "sample-audio-control", "info": "Provide Default Audio Policy for Multimedia, Navigation and Emergency", "version": "1.0" }, "onload": { - "info": "controler initialisation config", - "plugin": "sample-audio-policy.so", + "label": "onload-config", + "info": "onload initialisation config", + "plugin": "audio-ctl-plugin-sample.so", "actions": [ { - "info": "Call policy sharelib install entrypoint", + "label": "onload-sample-cb", + "info": "Call control sharelib install entrypoint", "callback": "SamplePolicyInstall", "args": {"arg1" : "first_arg", "nextarg": "second arg value"} }, { + "label": "onload-sample-api", "info": "Assert AlsaCore Presence", "api": "alsacore", - "verb": "ping" + "verb": "ping", + "args": "test" + }, { + "label": "onload-sample-lua", + "info": "Assert LUA Engine", + "lua": "Test_Lua_Engine", + "args": "ping" } ] }, @@ -25,7 +34,7 @@ "label": "multimedia", "actions": [ { - "label": "multimedia-policy-cb", + "label": "multimedia-control-cb", "info": "Call Sharelib Sample Callback", "callback": "samplePolicyCB", "args": { @@ -33,7 +42,7 @@ "arg2": "toto" } }, { - "label": "multimedia-policy-ucm", + "label": "multimedia-control-ucm", "info": "Subcall AlSA UCM navigation", "api": "alsacore", "verb": "ucmset", diff --git a/data/onload-control-script.lua b/data/onload-control-script.lua index ac316cd..f50bf7a 100644 --- a/data/onload-control-script.lua +++ b/data/onload-control-script.lua @@ -21,17 +21,17 @@ count=0 -- Adjust Volume function of vehicle speed -function Adjust_Volume_Speed (speed_meters_second) +function Adjust_Volume_Speed (request, speed_meters_second) - AFB:notice("In Adjust_Volume_Speed") + AFB:notice("In Adjust_Volume_Speed speed=%d", speed_meters_second); - print (string.format("***** Adjust_Volume_Speed speed=%d count=%d", speed_meters_second, count)); + print (string.format("*****(From Lua) Adjust_Volume_Speed speed=%d count=%d", speed_meters_second, count)); -- compute volume volume = speed_meters_second * 2 count=count+1 - AFB:success (1234, volume, count, 5678) + AFB:success (request, 1234, volume, count, 5678) end diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index e46fe8e..a8b33db 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -2,7 +2,7 @@ - + Alsa-AddCtl.c Alsa-ApiHat.c Alsa-RegEvt.c @@ -44,9 +44,10 @@ ctl-binding.c + ctl-dispatch.c ctl-events.c ctl-lua.c - ctl-policy.c + ctl-plugin-sample.c @@ -116,7 +117,7 @@ - + true - + - + - + - + - + - + @@ -181,18 +182,56 @@ + + Audio-Common + build/Controler-afb + + + control_afb_EXPORTS + + + + + + + Audio-Common + build/Controler-afb + + + control_afb_EXPORTS + + + Audio-Common + build/Controler-afb + + + control_afb_EXPORTS + + + Audio-Common + build/Controler-afb + + + control_afb_EXPORTS + - + + + build/Controler-afb + + + ctl_plugin_sample_EXPORTS + - + ../../../opt/include @@ -314,21 +353,23 @@ /usr/include/p11-kit-1 /usr/include/json-c /usr/include/lua5.3 - Audio-Common - build/ALSA-afb + build/Alsa-Plugin/Alsa-Policy-Hook - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 MAX_SND_CARD=16 + PIC TLV_BYTE_SIZE=256 - alsa_lowlevel_EXPORTS + policy_hook_cb_EXPORTS - + ../../../opt/include @@ -336,17 +377,19 @@ /usr/include/p11-kit-1 /usr/include/json-c /usr/include/lua5.3 - build/Alsa-Plugin/Alsa-Policy-Hook + Audio-Common + build/Alsa-afb - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 MAX_SND_CARD=16 - PIC TLV_BYTE_SIZE=256 - policy_hook_cb_EXPORTS + alsa_lowlevel_EXPORTS @@ -362,9 +405,11 @@ build/Audio-Common - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 MAX_SND_CARD=16 TLV_BYTE_SIZE=256 @@ -395,17 +440,16 @@ /usr/include/p11-kit-1 /usr/include/json-c /usr/include/lua5.3 - Audio-Common - build/Controler-afb - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 MAX_SND_CARD=16 TLV_BYTE_SIZE=256 - control_afb_EXPORTS @@ -433,9 +477,11 @@ build/HAL-afb/HAL-interface - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 TLV_BYTE_SIZE=256 @@ -466,9 +512,11 @@ build/HAL-afb/HDA-intel - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 TLV_BYTE_SIZE=256 hal_intel_hda_EXPORTS @@ -488,9 +536,11 @@ build/HAL-afb/Jabra-Solemate - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 TLV_BYTE_SIZE=256 hal_jabra_usb_EXPORTS @@ -510,9 +560,11 @@ build/HAL-afb/Scarlett-Focusrite - CONTROL_FILE_POLICY="onload-control-policy.json" - CONTROL_PATH_LUA="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" - CONTROL_PATH_POLICY="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/home/fulup/opt/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_DISPATCH_FILE="onload-control-policy.json" + CONTROL_DISPATCH_PATH="/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Controler-afb:/usr/local/audio-bindings/controler" + CONTROL_LUA_PATH="/etc/default/audio-agent/policy:/home/fulup/.config/audio-agent:/usr/local/audio-agent/policy:/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/data" + CONTROL_MAXPATH_LEN=255 + CONTROL_PLUGIN_PATH="/home/fulup/opt/audio-bindings/controler:/usr/lib/audio-bindings" MAX_LINEAR_DB_SCALE=24 TLV_BYTE_SIZE=256 hal_scalett_usb_EXPORTS @@ -579,7 +631,14 @@ true - + + + + + ../../../opt/include/alsa @@ -590,14 +649,14 @@ - + build/ALSA-afb - + ../../../opt/include/alsa @@ -608,7 +667,7 @@ - + ../../../opt/include/alsa @@ -619,7 +678,7 @@ - + ../../../opt/include/alsa @@ -630,13 +689,6 @@ - - - - @@ -768,14 +820,6 @@ - - - - ../../../opt/include/afb - ALSA-afb - - - @@ -788,6 +832,14 @@ + + + + ../../../opt/include/afb + ALSA-afb + + + @@ -935,7 +987,29 @@ true - + + + + + + + + + + + ../../../opt/include/alsa @@ -946,14 +1020,14 @@ - + build/ALSA-afb - + ../../../opt/include/alsa @@ -964,7 +1038,7 @@ - + ../../../opt/include/alsa @@ -975,7 +1049,7 @@ - + ../../../opt/include/alsa @@ -986,28 +1060,6 @@ - - - - - - - - - - @@ -1138,14 +1190,6 @@ - - - - ../../../opt/include/afb - ALSA-afb - - - @@ -1167,6 +1211,14 @@ + + + + ../../../opt/include/afb + ALSA-afb + + + -- cgit 1.2.3-korg