From 539f65bb5ad87238422a5ca26c5b524c3be44bd1 Mon Sep 17 00:00:00 2001 From: Tai Vuong Date: Wed, 4 Oct 2017 14:21:28 -0400 Subject: Post AudioWorkshop Work phase v1 --- README.md | 7 +- afb-utilities | 1 - afb-utilities/CMakeLists.txt | 37 ++ afb-utilities/LICENSE | 201 +++++++++ afb-utilities/README.md | 11 + afb-utilities/filescan-utils.c | 122 ++++++ afb-utilities/filescan-utils.h | 45 ++ afb-utilities/wrap-json.c | 939 +++++++++++++++++++++++++++++++++++++++++ afb-utilities/wrap-json.h | 46 ++ afb-utilities/wrap-json.md | 305 +++++++++++++ htdocs/audiohl.html | 47 +-- src/ahl-apidef.h | 339 ++++++++------- src/ahl-apidef.json | 204 +++++---- src/ahl-binding.c | 730 +++++++++++++++++++++++--------- src/ahl-binding.h | 106 +++-- src/ahl-config.c | 66 +-- src/ahl-deviceenum.c | 154 ++++--- src/ahl-interface.h | 112 +++-- src/ahl-policy.c | 123 ++++-- 19 files changed, 2963 insertions(+), 632 deletions(-) delete mode 160000 afb-utilities create mode 100644 afb-utilities/CMakeLists.txt create mode 100644 afb-utilities/LICENSE create mode 100644 afb-utilities/README.md create mode 100644 afb-utilities/filescan-utils.c create mode 100644 afb-utilities/filescan-utils.h create mode 100644 afb-utilities/wrap-json.c create mode 100644 afb-utilities/wrap-json.h create mode 100644 afb-utilities/wrap-json.md diff --git a/README.md b/README.md index f9b55ed..4c09ecc 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,8 @@ ``` # Initial clone with submodules -git clone --recurse-submodules https://github.com/Audiokinetic-Automotive/afb-audiohighlevel.git -cd audio-binding - -# Do not forget submodules with pulling -git pull --recurse-submodules https://github.com/Audiokinetic-Automotive/afb-audiohighlevel.git +git clone https://github.com/Audiokinetic-Automotive/afb-audiohighlevel.git +cd afb-audiohighlevel ``` diff --git a/afb-utilities b/afb-utilities deleted file mode 160000 index 1860c19..0000000 --- a/afb-utilities +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1860c197a402f76f960eb2e373daf1259592cb67 diff --git a/afb-utilities/CMakeLists.txt b/afb-utilities/CMakeLists.txt new file mode 100644 index 0000000..fe260fa --- /dev/null +++ b/afb-utilities/CMakeLists.txt @@ -0,0 +1,37 @@ +########################################################################### +# 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. +########################################################################### + +# This is a CMakeLists.txt file meant to be included as submodule into an AGL +# app using app-templates subdmodules + +# Add target to project dependency list +PROJECT_TARGET_ADD(afb-utilities) + + # Define targets + ADD_LIBRARY(${TARGET_NAME} STATIC wrap-json.c filescan-utils.c) + + # Library properties + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + OUTPUT_NAME ${TARGET_NAME} + ) + + # Define target includes + TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + ) + diff --git a/afb-utilities/LICENSE b/afb-utilities/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/afb-utilities/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/afb-utilities/README.md b/afb-utilities/README.md new file mode 100644 index 0000000..03a0bcc --- /dev/null +++ b/afb-utilities/README.md @@ -0,0 +1,11 @@ +AFB Utilities +============= + +You should find useful utilities to integrates as submodule in your bindings +development. + +From your AFB apps repository do the following to integrates this repo: + +```bash +git submodule add git@github.com:iotbzh/afb-utilities +``` diff --git a/afb-utilities/filescan-utils.c b/afb-utilities/filescan-utils.c new file mode 100644 index 0000000..46e461b --- /dev/null +++ b/afb-utilities/filescan-utils.c @@ -0,0 +1,122 @@ +/* + * 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 "filescan-utils.h" + +// List Avaliable Configuration Files +PUBLIC json_object* ScanForConfig (const char* searchPath, CtlScanDirModeT mode, const char *pre, const char *ext) { + json_object *responseJ; + char *dirPath; + char* dirList= strdup(searchPath); + size_t extLen=0; + + void ScanDir (char *searchPath) { + DIR *dirHandle; + struct dirent *dirEnt; + dirHandle = opendir (searchPath); + if (!dirHandle) { + AFB_DEBUG ("CONFIG-SCANNING dir=%s not readable", searchPath); + return; + } + + //AFB_NOTICE ("CONFIG-SCANNING:ctl_listconfig scanning: %s", searchPath); + while ((dirEnt = readdir(dirHandle)) != NULL) { + + // recursively search embedded directories ignoring any directory starting by '.' or '_' + if (dirEnt->d_type == DT_DIR && mode == CTL_SCAN_RECURSIVE) { + char newpath[CONTROL_MAXPATH_LEN]; + if (dirEnt->d_name[0]=='.' || dirEnt->d_name[0]=='_') continue; + + strncpy(newpath, searchPath, sizeof(newpath)); + strncat(newpath, "/", sizeof(newpath)); + strncat(newpath, dirEnt->d_name, sizeof(newpath)); + ScanDir(newpath); + continue; + } + + // 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; + 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, "fullpath", json_object_new_string(searchPath)); + json_object_object_add(pathJ, "filename", json_object_new_string(dirEnt->d_name)); + json_object_array_add(responseJ, pathJ); + } + } + closedir(dirHandle); + } + + if (ext) extLen=strlen(ext); + responseJ = json_object_new_array(); + + // loop recursively on dir + for (dirPath= strtok(dirList, ":"); dirPath && *dirPath; dirPath=strtok(NULL,":")) { + ScanDir (dirPath); + } + + free (dirList); + return (responseJ); +} + +PUBLIC const char *GetMidleName(const char*name) { + char *fullname = strdup(name); + + for (int idx = 0; fullname[idx] != '\0'; idx++) { + int start; + if (fullname[idx] == '-') { + start = idx + 1; + for (int jdx = start; ; jdx++) { + if (fullname[jdx] == '-' || fullname[jdx] == '.' || fullname[jdx] == '\0') { + fullname[jdx] = '\0'; + return &fullname[start]; + break; + } + } + break; + } + } + return ""; +} + +PUBLIC const char *GetBinderName() { + char psName[17]; + static char *binderName=NULL; + + if (binderName) return binderName; + + binderName= getenv("AFB_BINDER_NAME"); + if (!binderName) { + // retrieve binder name from process name afb-name-trailer + prctl(PR_GET_NAME, psName,NULL,NULL,NULL); + binderName=(char*)GetMidleName(psName); + } + + return binderName; +} diff --git a/afb-utilities/filescan-utils.h b/afb-utilities/filescan-utils.h new file mode 100644 index 0000000..a97d030 --- /dev/null +++ b/afb-utilities/filescan-utils.h @@ -0,0 +1,45 @@ +/* + * 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. + * + * reference: + * amixer contents; amixer controls; + * http://www.tldp.org/HOWTO/Alsa-sound-6.html + */ + +#ifndef FILESCAN_UTILS_H +#define FILESCAN_UTILS_H + +#define AFB_BINDING_VERSION 2 +#include +#include + +#ifndef PUBLIC + #define PUBLIC +#endif +#define STATIC static + +// ctl-misc.c +typedef enum { + CTL_SCAN_FLAT=0, + CTL_SCAN_RECURSIVE=1, +} CtlScanDirModeT; + +PUBLIC const char *GetMidleName(const char*name); +PUBLIC const char *GetBinderName(); +PUBLIC json_object* ScanForConfig (const char* searchPath, CtlScanDirModeT mode, const char *pre, const char *ext); + +#endif /* FILESCAN_UTILS_H */ + diff --git a/afb-utilities/wrap-json.c b/afb-utilities/wrap-json.c new file mode 100644 index 0000000..164e127 --- /dev/null +++ b/afb-utilities/wrap-json.c @@ -0,0 +1,939 @@ +/* + Copyright (C) 2016, 2017 "IoT.bzh" + + author: José Bollo + + 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. +*/ + +#include + +#include "wrap-json.h" + +#define STACKCOUNT 32 +#define STRCOUNT 8 + +enum { + wrap_json_error_none, + wrap_json_error_null_object, + wrap_json_error_truncated, + wrap_json_error_internal_error, + wrap_json_error_out_of_memory, + wrap_json_error_invalid_character, + wrap_json_error_too_long, + wrap_json_error_too_deep, + wrap_json_error_null_spec, + wrap_json_error_null_key, + wrap_json_error_null_string, + wrap_json_error_out_of_range, + wrap_json_error_incomplete, + wrap_json_error_missfit_type, + wrap_json_error_key_not_found, + _wrap_json_error_count_ +}; + +static const char ignore_all[] = " \t\n\r,:"; +static const char pack_accept_arr[] = "][{snbiIfoO"; +static const char pack_accept_key[] = "s}"; +#define pack_accept_any (&pack_accept_arr[1]) + +static const char unpack_accept_arr[] = "*!][{snbiIfFoO"; +static const char unpack_accept_key[] = "*!s}"; +#define unpack_accept_any (&unpack_accept_arr[3]) + +static const char *pack_errors[_wrap_json_error_count_] = +{ + [wrap_json_error_none] = "unknown error", + [wrap_json_error_null_object] = "null object", + [wrap_json_error_truncated] = "truncated", + [wrap_json_error_internal_error] = "internal error", + [wrap_json_error_out_of_memory] = "out of memory", + [wrap_json_error_invalid_character] = "invalid character", + [wrap_json_error_too_long] = "too long", + [wrap_json_error_too_deep] = "too deep", + [wrap_json_error_null_spec] = "spec is NULL", + [wrap_json_error_null_key] = "key is NULL", + [wrap_json_error_null_string] = "string is NULL", + [wrap_json_error_out_of_range] = "array too small", + [wrap_json_error_incomplete] = "incomplete container", + [wrap_json_error_missfit_type] = "missfit of type", + [wrap_json_error_key_not_found] = "key not found" +}; + +int wrap_json_get_error_position(int rc) +{ + if (rc < 0) + rc = -rc; + return (rc >> 4) + 1; +} + +int wrap_json_get_error_code(int rc) +{ + if (rc < 0) + rc = -rc; + return rc & 15; +} + +const char *wrap_json_get_error_string(int rc) +{ + rc = wrap_json_get_error_code(rc); + if (rc >= sizeof pack_errors / sizeof *pack_errors) + rc = 0; + return pack_errors[rc]; +} + + + +static inline const char *skip(const char *d) +{ + while (*d && strchr(ignore_all, *d)) + d++; + return d; +} + +int wrap_json_vpack(struct json_object **result, const char *desc, va_list args) +{ + /* TODO: the case of structs with key being single char should be optimized */ + int nstr, notnull, nullable, rc; + size_t sz, dsz, ssz; + char *s; + char c; + const char *d; + char buffer[256]; + struct { const char *str; size_t sz; } strs[STRCOUNT]; + struct { struct json_object *cont, *key; const char *acc; char type; } stack[STACKCOUNT], *top; + struct json_object *obj; + + ssz = sizeof buffer; + s = buffer; + top = stack; + top->key = NULL; + top->cont = NULL; + top->acc = pack_accept_any; + top->type = 0; + d = desc; + if (!d) + goto null_spec; + d = skip(d); + for(;;) { + c = *d; + if (!c) + goto truncated; + if (!strchr(top->acc, c)) + goto invalid_character; + d = skip(++d); + switch(c) { + case 's': + nullable = 0; + notnull = 0; + nstr = 0; + sz = 0; + for (;;) { + strs[nstr].str = va_arg(args, const char*); + if (strs[nstr].str) + notnull = 1; + if (*d == '?') { + d = skip(++d); + nullable = 1; + } + switch(*d) { + case '%': strs[nstr].sz = va_arg(args, size_t); d = skip(++d); break; + case '#': strs[nstr].sz = (size_t)va_arg(args, int); d = skip(++d); break; + default: strs[nstr].sz = strs[nstr].str ? strlen(strs[nstr].str) : 0; break; + } + sz += strs[nstr++].sz; + if (*d == '?') { + d = skip(++d); + nullable = 1; + } + if (*d != '+') + break; + if (nstr >= STRCOUNT) + goto too_long; + d = skip(++d); + } + if (*d == '*') + nullable = 1; + if (notnull) { + if (sz > ssz) { + ssz += ssz; + if (ssz < sz) + ssz = sz; + s = alloca(sz); + } + dsz = sz; + while (nstr) { + nstr--; + dsz -= strs[nstr].sz; + memcpy(&s[dsz], strs[nstr].str, strs[nstr].sz); + } + obj = json_object_new_string_len(s, (int)sz); + if (!obj) + goto out_of_memory; + } else if (nullable) + obj = NULL; + else + goto null_string; + break; + case 'n': + obj = NULL; + break; + case 'b': + obj = json_object_new_boolean(va_arg(args, int)); + if (!obj) + goto out_of_memory; + break; + case 'i': + obj = json_object_new_int(va_arg(args, int)); + if (!obj) + goto out_of_memory; + break; + case 'I': + obj = json_object_new_int64(va_arg(args, int64_t)); + if (!obj) + goto out_of_memory; + break; + case 'f': + obj = json_object_new_double(va_arg(args, double)); + if (!obj) + goto out_of_memory; + break; + case 'o': + case 'O': + obj = va_arg(args, struct json_object*); + if (*d == '?') + d = skip(++d); + else if (*d != '*' && !obj) + goto null_object; + if (c == 'O') + json_object_get(obj); + break; + case '[': + case '{': + if (++top >= &stack[STACKCOUNT]) + goto too_deep; + top->key = NULL; + if (c == '[') { + top->type = ']'; + top->acc = pack_accept_arr; + top->cont = json_object_new_array(); + } else { + top->type = '}'; + top->acc = pack_accept_key; + top->cont = json_object_new_object(); + } + if (!top->cont) + goto out_of_memory; + continue; + case '}': + case ']': + if (c != top->type || top <= stack) + goto internal_error; + obj = (top--)->cont; + if (*d == '*' && !(c == '}' ? json_object_object_length(obj) : json_object_array_length(obj))) { + json_object_put(obj); + obj = NULL; + } + break; + default: + goto internal_error; + } + switch (top->type) { + case 0: + if (top != stack) + goto internal_error; + if (*d) + goto invalid_character; + *result = obj; + return 0; + case ']': + if (obj || *d != '*') + json_object_array_add(top->cont, obj); + if (*d == '*') + d = skip(++d); + break; + case '}': + if (!obj) + goto null_key; + top->key = obj; + top->acc = pack_accept_any; + top->type = ':'; + break; + case ':': + if (obj || *d != '*') + json_object_object_add(top->cont, json_object_get_string(top->key), obj); + if (*d == '*') + d = skip(++d); + json_object_put(top->key); + top->key = NULL; + top->acc = pack_accept_key; + top->type = '}'; + break; + default: + goto internal_error; + } + } + +null_object: + rc = wrap_json_error_null_object; + goto error; +truncated: + rc = wrap_json_error_truncated; + goto error; +internal_error: + rc = wrap_json_error_internal_error; + goto error; +out_of_memory: + rc = wrap_json_error_out_of_memory; + goto error; +invalid_character: + rc = wrap_json_error_invalid_character; + goto error; +too_long: + rc = wrap_json_error_too_long; + goto error; +too_deep: + rc = wrap_json_error_too_deep; + goto error; +null_spec: + rc = wrap_json_error_null_spec; + goto error; +null_key: + rc = wrap_json_error_null_key; + goto error; +null_string: + rc = wrap_json_error_null_string; + goto error; +error: + do { + json_object_put(top->key); + json_object_put(top->cont); + } while (--top >= stack); + *result = NULL; + rc = rc | (int)((d - desc) << 4); + return -rc; +} + +int wrap_json_pack(struct json_object **result, const char *desc, ...) +{ + int rc; + va_list args; + + va_start(args, desc); + rc = wrap_json_vpack(result, desc, args); + va_end(args); + return rc; +} + +static int vunpack(struct json_object *object, const char *desc, va_list args, int store) +{ + int rc = 0, optionnal, ignore; + char c, xacc[2] = { 0, 0 }; + const char *acc; + const char *d, *fit = NULL; + const char *key = NULL; + const char **ps = NULL; + double *pf = NULL; + int *pi = NULL; + int64_t *pI = NULL; + size_t *pz = NULL; + struct { struct json_object *parent; const char *acc; int index, count; char type; } stack[STACKCOUNT], *top; + struct json_object *obj; + struct json_object **po; + + xacc[0] = 0; + ignore = 0; + top = NULL; + acc = unpack_accept_any; + d = desc; + if (!d) + goto null_spec; + d = skip(d); + obj = object; + for(;;) { + fit = d; + c = *d; + if (!c) + goto truncated; + if (!strchr(acc, c)) + goto invalid_character; + d = skip(++d); + switch(c) { + case 's': + if (xacc[0] == '}') { + /* expects a key */ + key = va_arg(args, const char *); + if (!key) + goto null_key; + if (*d != '?') + optionnal = 0; + else { + optionnal = 1; + d = skip(++d); + } + if (ignore) + ignore++; + else { + if (json_object_object_get_ex(top->parent, key, &obj)) { + /* found */ + top->index++; + } else { + /* not found */ + if (!optionnal) + goto key_not_found; + ignore = 1; + obj = NULL; + } + } + xacc[0] = ':'; + acc = unpack_accept_any; + continue; + } + /* get a string */ + if (store) + ps = va_arg(args, const char **); + if (!ignore) { + if (!json_object_is_type(obj, json_type_string)) + goto missfit; + if (store && ps) + *ps = json_object_get_string(obj); + } + if (*d == '%') { + d = skip(++d); + if (store) { + pz = va_arg(args, size_t *); + if (!ignore && pz) + *pz = (size_t)json_object_get_string_len(obj); + } + } + break; + case 'n': + if (!ignore && !json_object_is_type(obj, json_type_null)) + goto missfit; + break; + case 'b': + if (store) + pi = va_arg(args, int *); + + if (!ignore) { + if (!json_object_is_type(obj, json_type_boolean)) + goto missfit; + if (store && pi) + *pi = json_object_get_boolean(obj); + } + break; + case 'i': + if (store) + pi = va_arg(args, int *); + + if (!ignore) { + if (!json_object_is_type(obj, json_type_int)) + goto missfit; + if (store && pi) + *pi = json_object_get_int(obj); + } + break; + case 'I': + if (store) + pI = va_arg(args, int64_t *); + + if (!ignore) { + if (!json_object_is_type(obj, json_type_int)) + goto missfit; + if (store && pI) + *pI = json_object_get_int64(obj); + } + break; + case 'f': + case 'F': + if (store) + pf = va_arg(args, double *); + + if (!ignore) { + if (!(json_object_is_type(obj, json_type_double) || (c == 'F' && json_object_is_type(obj, json_type_int)))) + goto missfit; + if (store && pf) + *pf = json_object_get_double(obj); + } + break; + case 'o': + case 'O': + if (store) { + po = va_arg(args, struct json_object **); + if (!ignore && po) { + if (c == 'O') + obj = json_object_get(obj); + *po = obj; + } + } + break; + + case '[': + case '{': + if (!top) + top = stack; + else if (++top >= &stack[STACKCOUNT]) + goto too_deep; + + top->acc = acc; + top->type = xacc[0]; + top->index = 0; + top->parent = obj; + if (ignore) + ignore++; + if (c == '[') { + if (!ignore) { + if (!json_object_is_type(obj, json_type_array)) + goto missfit; + top->count = json_object_array_length(obj); + } + xacc[0] = ']'; + acc = unpack_accept_arr; + } else { + if (!ignore) { + if (!json_object_is_type(obj, json_type_object)) + goto missfit; + top->count = json_object_object_length(obj); + } + xacc[0] = '}'; + acc = unpack_accept_key; + continue; + } + break; + case '}': + case ']': + if (!top || c != xacc[0]) + goto internal_error; + acc = top->acc; + xacc[0] = top->type; + top = top == stack ? NULL : top - 1; + if (ignore) + ignore--; + break; + case '!': + if (*d != xacc[0]) + goto invalid_character; + if (!ignore && top->index != top->count) + goto incomplete; + /*@fallthrough@*/ + case '*': + acc = xacc; + continue; + default: + goto internal_error; + } + switch (xacc[0]) { + case 0: + if (top) + goto internal_error; + if (*d) + goto invalid_character; + return 0; + case ']': + if (!ignore) { + key = strchr(unpack_accept_arr, *d); + if (key && key >= unpack_accept_any) { + if (top->index >= top->count) + goto out_of_range; + obj = json_object_array_get_idx(top->parent, top->index++); + } + } + break; + case ':': + acc = unpack_accept_key; + xacc[0] = '}'; + if (ignore) + ignore--; + break; + default: + goto internal_error; + } + } +truncated: + rc = wrap_json_error_truncated; + goto error; +internal_error: + rc = wrap_json_error_internal_error; + goto error; +invalid_character: + rc = wrap_json_error_invalid_character; + goto error; +too_deep: + rc = wrap_json_error_too_deep; + goto error; +null_spec: + rc = wrap_json_error_null_spec; + goto error; +null_key: + rc = wrap_json_error_null_key; + goto error; +out_of_range: + rc = wrap_json_error_out_of_range; + goto error; +incomplete: + rc = wrap_json_error_incomplete; + goto error; +missfit: + rc = wrap_json_error_missfit_type; + goto errorfit; +key_not_found: + rc = wrap_json_error_key_not_found; + goto error; +errorfit: + d = fit; +error: + rc = rc | (int)((d - desc) << 4); + return -rc; +} + +int wrap_json_vcheck(struct json_object *object, const char *desc, va_list args) +{ + return vunpack(object, desc, args, 0); +} + +int wrap_json_check(struct json_object *object, const char *desc, ...) +{ + int rc; + va_list args; + + va_start(args, desc); + rc = vunpack(object, desc, args, 0); + va_end(args); + return rc; +} + +int wrap_json_vmatch(struct json_object *object, const char *desc, va_list args) +{ + return !vunpack(object, desc, args, 0); +} + +int wrap_json_match(struct json_object *object, const char *desc, ...) +{ + int rc; + va_list args; + + va_start(args, desc); + rc = vunpack(object, desc, args, 0); + va_end(args); + return !rc; +} + +int wrap_json_vunpack(struct json_object *object, const char *desc, va_list args) +{ + return vunpack(object, desc, args, 1); +} + +int wrap_json_unpack(struct json_object *object, const char *desc, ...) +{ + int rc; + va_list args; + + va_start(args, desc); + rc = vunpack(object, desc, args, 1); + va_end(args); + return rc; +} + +static void object_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure) +{ + struct json_object_iterator it = json_object_iter_begin(object); + struct json_object_iterator end = json_object_iter_end(object); + while (!json_object_iter_equal(&it, &end)) { + callback(closure, json_object_iter_peek_value(&it), json_object_iter_peek_name(&it)); + json_object_iter_next(&it); + } +} + +static void array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure) +{ + int n = json_object_array_length(object); + int i = 0; + while(i < n) + callback(closure, json_object_array_get_idx(object, i++)); +} + +void wrap_json_optarray_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure) +{ + if (json_object_is_type(object, json_type_array)) + array_for_all(object, callback, closure); + else + callback(closure, object); +} + +void wrap_json_array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure) +{ + if (json_object_is_type(object, json_type_array)) + array_for_all(object, callback, closure); +} + +void wrap_json_object_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure) +{ + if (json_object_is_type(object, json_type_object)) + object_for_all(object, callback, closure); +} + +void wrap_json_optobject_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure) +{ + if (json_object_is_type(object, json_type_object)) + object_for_all(object, callback, closure); + else + callback(closure, object, NULL); +} + +void wrap_json_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure) +{ + if (!object) + /* do nothing */; + else if (json_object_is_type(object, json_type_object)) + object_for_all(object, callback, closure); + else if (!json_object_is_type(object, json_type_array)) + callback(closure, object, NULL); + else { + int n = json_object_array_length(object); + int i = 0; + while(i < n) + callback(closure, json_object_array_get_idx(object, i++), NULL); + } +} + +#if defined(WRAP_JSON_TEST) +#include + +void p(const char *desc, ...) +{ + int rc; + va_list args; + struct json_object *result; + + va_start(args, desc); + rc = wrap_json_vpack(&result, desc, args); + va_end(args); + if (!rc) + printf(" SUCCESS %s\n\n", json_object_to_json_string(result)); + else + printf(" ERROR[char %d err %d] %s\n\n", wrap_json_get_error_position(rc), wrap_json_get_error_code(rc), wrap_json_get_error_string(rc)); + json_object_put(result); +} + +const char *xs[10]; +int *xi[10]; +int64_t *xI[10]; +double *xf[10]; +struct json_object *xo[10]; +size_t xz[10]; + +void u(const char *value, const char *desc, ...) +{ + unsigned m, k; + int rc; + va_list args; + struct json_object *obj, *o; + + memset(xs, 0, sizeof xs); + memset(xi, 0, sizeof xi); + memset(xI, 0, sizeof xI); + memset(xf, 0, sizeof xf); + memset(xo, 0, sizeof xo); + memset(xz, 0, sizeof xz); + obj = json_tokener_parse(value); + va_start(args, desc); + rc = wrap_json_vunpack(obj, desc, args); + va_end(args); + if (rc) + printf(" ERROR[char %d err %d] %s\n\n", wrap_json_get_error_position(rc), wrap_json_get_error_code(rc), wrap_json_get_error_string(rc)); + else { + value = NULL; + printf(" SUCCESS"); + va_start(args, desc); + k = m = 0; + while(*desc) { + switch(*desc) { + case '{': m = (m << 1) | 1; k = 1; break; + case '}': m = m >> 1; k = m&1; break; + case '[': m = m << 1; k = 0; break; + case ']': m = m >> 1; k = m&1; break; + case 's': printf(" s:%s", k ? va_arg(args, const char*) : *(va_arg(args, const char**)?:&value)); k ^= m&1; break; + case '%': printf(" %%:%zu", *va_arg(args, size_t*)); k = m&1; break; + case 'n': printf(" n"); k = m&1; break; + case 'b': printf(" b:%d", *va_arg(args, int*)); k = m&1; break; + case 'i': printf(" i:%d", *va_arg(args, int*)); k = m&1; break; + case 'I': printf(" I:%lld", *va_arg(args, int64_t*)); k = m&1; break; + case 'f': printf(" f:%f", *va_arg(args, double*)); k = m&1; break; + case 'F': printf(" F:%f", *va_arg(args, double*)); k = m&1; break; + case 'o': printf(" o:%s", json_object_to_json_string(*va_arg(args, struct json_object**))); k = m&1; break; + case 'O': o = *va_arg(args, struct json_object**); printf(" O:%s", json_object_to_json_string(o)); json_object_put(o); k = m&1; break; + default: break; + } + desc++; + } + va_end(args); + printf("\n\n"); + } + json_object_put(obj); +} + +#define P(...) do{ printf("pack(%s)\n",#__VA_ARGS__); p(__VA_ARGS__); } while(0) +#define U(...) do{ printf("unpack(%s)\n",#__VA_ARGS__); u(__VA_ARGS__); } while(0) + +int main() +{ + char buffer[4] = {'t', 'e', 's', 't'}; + + P("n"); + P("b", 1); + P("b", 0); + P("i", 1); + P("I", (uint64_t)0x123456789abcdef); + P("f", 3.14); + P("s", "test"); + P("s?", "test"); + P("s?", NULL); + P("s#", "test asdf", 4); + P("s%", "test asdf", (size_t)4); + P("s#", buffer, 4); + P("s%", buffer, (size_t)4); + P("s++", "te", "st", "ing"); + P("s#+#+", "test", 1, "test", 2, "test"); + P("s%+%+", "test", (size_t)1, "test", (size_t)2, "test"); + P("{}", 1.0); + P("[]", 1.0); + P("o", json_object_new_int(1)); + P("o?", json_object_new_int(1)); + P("o?", NULL); + P("O", json_object_new_int(1)); + P("O?", json_object_new_int(1)); + P("O?", NULL); + P("{s:[]}", "foo"); + P("{s+#+: []}", "foo", "barbar", 3, "baz"); + P("{s:s,s:o,s:O}", "a", NULL, "b", NULL, "c", NULL); + P("{s:**}", "a", NULL); + P("{s:s*,s:o*,s:O*}", "a", NULL, "b", NULL, "c", NULL); + P("[i,i,i]", 0, 1, 2); + P("[s,o,O]", NULL, NULL, NULL); + P("[**]", NULL); + P("[s*,o*,O*]", NULL, NULL, NULL); + P(" s ", "test"); + P("[ ]"); + P("[ i , i, i ] ", 1, 2, 3); + P("{\n\n1"); + P("[}"); + P("{]"); + P("["); + P("{"); + P("[i]a", 42); + P("ia", 42); + P("s", NULL); + P("+", NULL); + P(NULL); + P("{s:i}", NULL, 1); + P("{ {}: s }", "foo"); + P("{ s: {}, s:[ii{} }", "foo", "bar", 12, 13); + P("[[[[[ [[[[[ [[[[ }]]]] ]]]] ]]]]]"); + + U("true", "b", &xi[0]); + U("false", "b", &xi[0]); + U("null", "n"); + U("42", "i", &xi[0]); + U("123456789", "I", &xI[0]); + U("3.14", "f", &xf[0]); + U("12345", "F", &xf[0]); + U("3.14", "F", &xf[0]); + U("\"foo\"", "s", &xs[0]); + U("\"foo\"", "s%", &xs[0], &xz[0]); + U("{}", "{}"); + U("[]", "[]"); + U("{}", "o", &xo[0]); + U("{}", "O", &xo[0]); + U("{\"foo\":42}", "{si}", "foo", &xi[0]); + U("[1,2,3]", "[i,i,i]", &xi[0], &xi[1], &xi[2]); + U("{\"a\":1,\"b\":2,\"c\":3}", "{s:i, s:i, s:i}", "a", &xi[0], "b", &xi[1], "c", &xi[2]); + U("42", "z"); + U("null", "[i]"); + U("[]", "[}"); + U("{}", "{]"); + U("[]", "["); + U("{}", "{"); + U("[42]", "[i]a", &xi[0]); + U("42", "ia", &xi[0]); + U("[]", NULL); + U("\"foo\"", "s", NULL); + U("42", "s", NULL); + U("42", "n"); + U("42", "b", NULL); + U("42", "f", NULL); + U("42", "[i]", NULL); + U("42", "{si}", "foo", NULL); + U("\"foo\"", "n"); + U("\"foo\"", "b", NULL); + U("\"foo\"", "i", NULL); + U("\"foo\"", "I", NULL); + U("\"foo\"", "f", NULL); + U("\"foo\"", "F", NULL); + U("true", "s", NULL); + U("true", "n"); + U("true", "i", NULL); + U("true", "I", NULL); + U("true", "f", NULL); + U("true", "F", NULL); + U("[42]", "[ii]", &xi[0], &xi[1]); + U("{\"foo\":42}", "{si}", NULL, &xi[0]); + U("{\"foo\":42}", "{si}", "baz", &xi[0]); + U("[1,2,3]", "[iii!]", &xi[0], &xi[1], &xi[2]); + U("[1,2,3]", "[ii!]", &xi[0], &xi[1]); + U("[1,2,3]", "[ii]", &xi[0], &xi[1]); + U("[1,2,3]", "[ii*]", &xi[0], &xi[1]); + U("{\"foo\":42,\"baz\":45}", "{sisi}", "baz", &xi[0], "foo", &xi[1]); + U("{\"foo\":42,\"baz\":45}", "{sisi*}", "baz", &xi[0], "foo", &xi[1]); + U("{\"foo\":42,\"baz\":45}", "{sisi!}", "baz", &xi[0], "foo", &xi[1]); + U("{\"foo\":42,\"baz\":45}", "{si}", "baz", &xi[0], "foo", &xi[1]); + U("{\"foo\":42,\"baz\":45}", "{si*}", "baz", &xi[0], "foo", &xi[1]); + U("{\"foo\":42,\"baz\":45}", "{si!}", "baz", &xi[0], "foo", &xi[1]); + U("[1,{\"foo\":2,\"bar\":null},[3,4]]", "[i{sisn}[ii]]", &xi[0], "foo", &xi[1], "bar", &xi[2], &xi[3]); + U("[1,2,3]", "[ii!i]", &xi[0], &xi[1], &xi[2]); + U("[1,2,3]", "[ii*i]", &xi[0], &xi[1], &xi[2]); + U("{\"foo\":1,\"bar\":2}", "{si!si}", "foo", &xi[1], "bar", &xi[2]); + U("{\"foo\":1,\"bar\":2}", "{si*si}", "foo", &xi[1], "bar", &xi[2]); + U("{\"foo\":{\"baz\":null,\"bar\":null}}", "{s{sn!}}", "foo", "bar"); + U("[[1,2,3]]", "[[ii!]]", &xi[0], &xi[1]); + U("{}", "{s?i}", "foo", &xi[0]); + U("{\"foo\":1}", "{s?i}", "foo", &xi[0]); + U("{}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]); + U("{\"foo\":[1,2]}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]); + U("{\"bar\":{\"baz\":{\"quux\":15}}}", "{s?[ii]s?{s{si!}}}", "foo", &xi[0], &xi[1], "bar", "baz", "quux", &xi[2]); + U("{\"foo\":{\"bar\":4}}", "{s?{s?i}}", "foo", "bar", &xi[0]); + U("{\"foo\":{}}", "{s?{s?i}}", "foo", "bar", &xi[0]); + U("{}", "{s?{s?i}}", "foo", "bar", &xi[0]); + U("{\"foo\":42,\"baz\":45}", "{s?isi!}", "baz", &xi[0], "foo", &xi[1]); + U("{\"foo\":42}", "{s?isi!}", "baz", &xi[0], "foo", &xi[1]); + return 0; +} + +#endif + +#if 0 + + + /* Unpack the same item twice */ + j = json_pack("{s:s, s:i, s:b}", "foo", "bar", "baz", 42, "quux", 1); + if(!json_unpack_ex(j, &error, 0, "{s:s,s:s!}", "foo", &s, "foo", &s)) + fail("json_unpack object with strict validation failed"); + { + const char *possible_errors[] = { + "2 object item(s) left unpacked: baz, quux", + "2 object item(s) left unpacked: quux, baz" + }; + check_errors(possible_errors, 2, "", 1, 10, 10); + } + json_decref(j); + +#endif diff --git a/afb-utilities/wrap-json.h b/afb-utilities/wrap-json.h new file mode 100644 index 0000000..cb2d0bf --- /dev/null +++ b/afb-utilities/wrap-json.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2016, 2017 "IoT.bzh" + + author: José Bollo + + 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. +*/ + +#pragma once + +#include +#include + +extern int wrap_json_get_error_position(int rc); +extern int wrap_json_get_error_code(int rc); +extern const char *wrap_json_get_error_string(int rc); + +extern int wrap_json_vpack(struct json_object **result, const char *desc, va_list args); +extern int wrap_json_pack(struct json_object **result, const char *desc, ...); + +extern int wrap_json_vunpack(struct json_object *object, const char *desc, va_list args); +extern int wrap_json_unpack(struct json_object *object, const char *desc, ...); +extern int wrap_json_vcheck(struct json_object *object, const char *desc, va_list args); +extern int wrap_json_check(struct json_object *object, const char *desc, ...); +extern int wrap_json_vmatch(struct json_object *object, const char *desc, va_list args); +extern int wrap_json_match(struct json_object *object, const char *desc, ...); + +extern void wrap_json_optarray_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure); +extern void wrap_json_array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure); + +extern void wrap_json_optarray_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure); +extern void wrap_json_array_for_all(struct json_object *object, void (*callback)(void*,struct json_object*), void *closure); +extern void wrap_json_object_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure); +extern void wrap_json_optobject_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure); +extern void wrap_json_for_all(struct json_object *object, void (*callback)(void*,struct json_object*,const char*), void *closure); + diff --git a/afb-utilities/wrap-json.md b/afb-utilities/wrap-json.md new file mode 100644 index 0000000..92940f1 --- /dev/null +++ b/afb-utilities/wrap-json.md @@ -0,0 +1,305 @@ +WRAP-JSON facility +================== + +The facility wrap-json is based on the pack/unpack API on the +libray jansson. The two chapters below are copied from the +documentation of jansson library copyrighted by Petri Lehtinen +(see at end). + +Building Values +--------------- + +This section describes functions that help to create, or *pack*, complex +JSON values, especially nested objects and arrays. Value building is +based on a *format string* that is used to tell the functions about the +expected arguments. + +For example, the format string `"i"` specifies a single integer value, +while the format string `"[ssb]"` or the equivalent `"[s, s, b]"` +specifies an array value with two strings and a boolean as its items: + + /* Create the JSON integer 42 */ + wrap_json_pack(&result, "i", 42); + + /* Create the JSON array ["foo", "bar", true] */ + wrap_json_pack(&result, "[ssb]", "foo", "bar", 1); + +Here's the full list of format specifiers. The type in parentheses +denotes the resulting JSON type, and the type in brackets (if any) +denotes the C type that is expected as the corresponding argument or +arguments. + +`s` (string) \[const char \*\] + +: Convert a null terminated UTF-8 string to a JSON string. + +`s?` (string) \[const char \*\] + +: Like `s`, but if the argument is *NULL*, output a JSON null value. + +`s*` (string) \[const char \*\] + +: Like `s`, but if the argument is *NULL*, do not output any value. + This format can only be used inside an object or an array. If used + inside an object, the corresponding key is additionally suppressed + when the value is omitted. See below for an example. + +`s#` (string) \[const char \*, int\] + +: Convert a UTF-8 buffer of a given length to a JSON string. + +`s%` (string) \[const char \*, size\_t\] + +: Like `s#` but the length argument is of type size\_t. + +`+` \[const char \*\] + +: Like `s`, but concatenate to the previous string. Only valid after + `s`, `s#`, `+` or `+#`. + +`+#` \[const char \*, int\] + +: Like `s#`, but concatenate to the previous string. Only valid after + `s`, `s#`, `+` or `+#`. + +`+%` (string) \[const char \*, size\_t\] + +: Like `+#` but the length argument is of type size\_t. + +`n` (null) + +: Output a JSON null value. No argument is consumed. + +`b` (boolean) \[int\] + +: Convert a C int to JSON boolean value. Zero is converted to `false` + and non-zero to `true`. + +`i` (integer) \[int\] + +: Convert a C int to JSON integer. + +`I` (integer) \[json\_int\_t\] + +: Convert a C json\_int\_t to JSON integer. + +`f` (real) \[double\] + +: Convert a C double to JSON real. + +`o` (any value) \[json\_t \*\] + +: Output any given JSON value as-is. If the value is added to an array + or object, the reference to the value passed to `o` is stolen by the + container. + +`O` (any value) \[json\_t \*\] + +: Like `o`, but the argument's reference count is incremented. This is + useful if you pack into an array or object and want to keep the + reference for the JSON value consumed by `O` to yourself. + +`o?`, `O?` (any value) \[json\_t \*\] + +: Like `o` and `O`, respectively, but if the argument is *NULL*, + output a JSON null value. + +`o*`, `O*` (any value) \[json\_t \*\] + +: Like `o` and `O`, respectively, but if the argument is *NULL*, do + not output any value. This format can only be used inside an object + or an array. If used inside an object, the corresponding key is + additionally suppressed. See below for an example. + +`[fmt]` (array) + +: Build an array with contents from the inner format string. `fmt` may + contain objects and arrays, i.e. recursive value building is + supported. + +`{fmt}` (object) + +: Build an object with contents from the inner format string `fmt`. + The first, third, etc. format specifier represent a key, and must be + a string (see `s`, `s#`, `+` and `+#` above), as object keys are + always strings. The second, fourth, etc. format specifier represent + a value. Any value may be an object or array, i.e. recursive value + building is supported. + +Whitespace, `:` and `,` are ignored. + +More examples: + + /* Build an empty JSON object */ + wrap_json_pack(&result, "{}"); + + /* Build the JSON object {"foo": 42, "bar": 7} */ + wrap_json_pack(&result, "{sisi}", "foo", 42, "bar", 7); + + /* Like above, ':', ',' and whitespace are ignored */ + wrap_json_pack(&result, "{s:i, s:i}", "foo", 42, "bar", 7); + + /* Build the JSON array [[1, 2], {"cool": true}] */ + wrap_json_pack(&result, "[[i,i],{s:b}]", 1, 2, "cool", 1); + + /* Build a string from a non-null terminated buffer */ + char buffer[4] = {'t', 'e', 's', 't'}; + wrap_json_pack(&result, "s#", buffer, 4); + + /* Concatenate strings together to build the JSON string "foobarbaz" */ + wrap_json_pack(&result, "s++", "foo", "bar", "baz"); + + /* Create an empty object or array when optional members are missing */ + wrap_json_pack(&result, "{s:s*,s:o*,s:O*}", "foo", NULL, "bar", NULL, "baz", NULL); + wrap_json_pack(&result, "[s*,o*,O*]", NULL, NULL, NULL); + +Parsing and Validating Values +----------------------------- + +This section describes functions that help to validate complex values +and extract, or *unpack*, data from them. Like building values +<apiref-pack>, this is also based on format strings. + +While a JSON value is unpacked, the type specified in the format string +is checked to match that of the JSON value. This is the validation part +of the process. In addition to this, the unpacking functions can also +check that all items of arrays and objects are unpacked. This check be +enabled with the format specifier `!` or by using the flag +`JSON_STRICT`. See below for details. + +Here's the full list of format specifiers. The type in parentheses +denotes the JSON type, and the type in brackets (if any) denotes the C +type whose address should be passed. + +`s` (string) \[const char \*\] + +: Convert a JSON string to a pointer to a null terminated UTF-8 + string. The resulting string is extracted by using + json\_string\_value() internally, so it exists as long as there are + still references to the corresponding JSON string. + +`s%` (string) \[const char \*, size\_t \*\] + +: Convert a JSON string to a pointer to a null terminated UTF-8 string + and its length. + +`n` (null) + +: Expect a JSON null value. Nothing is extracted. + +`b` (boolean) \[int\] + +: Convert a JSON boolean value to a C int, so that `true` is converted + to 1 and `false` to 0. + +`i` (integer) \[int\] + +: Convert a JSON integer to C int. + +`I` (integer) \[json\_int\_t\] + +: Convert a JSON integer to C json\_int\_t. + +`f` (real) \[double\] + +: Convert a JSON real to C double. + +`F` (integer or real) \[double\] + +: Convert a JSON number (integer or real) to C double. + +`o` (any value) \[json\_t \*\] + +: Store a JSON value with no conversion to a json\_t pointer. + +`O` (any value) \[json\_t \*\] + +: Like `O`, but the JSON value's reference count is incremented. + +`[fmt]` (array) + +: Convert each item in the JSON array according to the inner format + string. `fmt` may contain objects and arrays, i.e. recursive value + extraction is supported. + +`{fmt}` (object) + +: Convert each item in the JSON object according to the inner format + string `fmt`. The first, third, etc. format specifier represent a + key, and must be `s`. The corresponding argument to unpack functions + is read as the object key. The second fourth, etc. format specifier + represent a value and is written to the address given as the + corresponding argument. **Note** that every other argument is read + from and every other is written to. + + `fmt` may contain objects and arrays as values, i.e. recursive value + extraction is supported. + +`!` + +: This special format specifier is used to enable the check that all + object and array items are accessed, on a per-value basis. It must + appear inside an array or object as the last format specifier before + the closing bracket or brace. + +`*` + +: This special format specifier is the opposite of `!`. This is the default. + It must appear inside an array or object as the last format specifier + before the closing bracket or brace. + +Whitespace, `:` and `,` are ignored. + +Examples: + + /* root is the JSON integer 42 */ + int myint; + wrap_json_unpack(root, "i", &myint); + assert(myint == 42); + + /* root is the JSON object {"foo": "bar", "quux": true} */ + const char *str; + int boolean; + wrap_json_unpack(root, "{s:s, s:b}", "foo", &str, "quux", &boolean); + assert(strcmp(str, "bar") == 0 && boolean == 1); + + /* root is the JSON array [[1, 2], {"baz": null} */ + wrap_json_check(root, "[[i,i], {s:n}]", "baz"); + /* returns 0 for validation success, nothing is extracted */ + + /* root is the JSON array [1, 2, 3, 4, 5] */ + int myint1, myint2; + wrap_json_unpack(root, "[ii!]", &myint1, &myint2); + /* returns -1 for failed validation */ + + /* root is an empty JSON object */ + int myint = 0, myint2 = 0, myint3 = 0; + wrap_json_unpack(root, "{s?i, s?[ii]}", + "foo", &myint1, + "bar", &myint2, &myint3); + /* myint1, myint2 or myint3 is no touched as "foo" and "bar" don't exist */ + + +Copyright +--------- + +Copyright (c) 2009-2016 Petri Lehtinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/htdocs/audiohl.html b/htdocs/audiohl.html index 31a28fe..41af265 100644 --- a/htdocs/audiohl.html +++ b/htdocs/audiohl.html @@ -81,27 +81,13 @@ - - State Name - - - State Value - - Volume Value - @@ -111,6 +97,14 @@ + Stream Active/Mute State + + +
  1. @@ -118,15 +112,18 @@
  2. +
  3. +
  4. +
  5. -
  6. -
  7. -
  8. -
  9. -
  10. -
  11. -
  12. +
  13. +
  14. +
  15. +
  16. +
  17. +
  18. +

  19. diff --git a/src/ahl-apidef.h b/src/ahl-apidef.h index 9a39470..e23d1e0 100644 --- a/src/ahl-apidef.h +++ b/src/ahl-apidef.h @@ -1,138 +1,155 @@ static const char _afb_description_v2_audiohl[] = - "{\"openapi\":\"3.0.0\",\"$schema\":\"http:iot.bzh/download/openapi/schem" - "a-3.0/default-schema.json\",\"info\":{\"description\":\"Audio high level" - " API for AGL applications\",\"title\":\"audiohighlevel\",\"version\":\"1" - ".0\",\"x-binding-c-generator\":{\"api\":\"audiohl\",\"version\":2,\"pref" - "ix\":\"audiohlapi_\",\"postfix\":\"\",\"start\":null,\"onevent\":null,\"" - "init\":\"AhlBindingInit\",\"scope\":\"\",\"private\":false}},\"servers\"" - ":[{\"url\":\"ws://{host}:{port}/api/audiohl\",\"description\":\"Audio hi" - "gh level API for AGL applications.\",\"variables\":{\"host\":{\"default\"" - ":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":[{\"$r" - "ef\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"schemas\":" - "{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"afb-ev" - "ent\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply-v2\":" - "{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":[\"jty" - "pe\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const" - "\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"statu" - "s\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"type\"" - ":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string" - "\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"object\"}" - "}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"event" - "\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-event" - "\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}},\"e" - "ndpoint_info\":{\"type\":\"object\",\"required\":[\"endpoint_id\",\"type" - "\",\"device_name\",\"device_uri\"],\"properties\":{\"endpoint_id\":{\"ty" - "pe\":\"int\"},\"type\":{\"type\":\"enum\"},\"device_name\":{\"type\":\"s" - "tring\"},\"device_uri_type\":{\"type\":\"string\"}}},\"stream_info\":{\"" - "type\":\"object\",\"required\":[\"stream_id\",\"endpoint_info\"],\"prope" - "rties\":{\"stream_id\":{\"type\":\"int\"},\"$ref\":\"#/components/schema" - "s/endpoint_info\"}}},\"x-permissions\":{\"streamcontrol\":{\"permission\"" - ":\"urn:AGL:permission:audio:public:streamcontrol\"},\"routingcontrol\":{" - "\"permission\":\"urn:AGL:permission:audio:public:routingcontrol\"},\"sou" - "ndevent\":{\"permission\":\"urn:AGL:permission:audio:public:soundevent\"" - "}},\"responses\":{\"200\":{\"description\":\"A complex object array resp" - "onse\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/afb-reply\"}}}},\"400\":{\"description\":\"Invalid argumen" - "ts\"}}},\"paths\":{\"/get_sources\":{\"description\":\"Retrieve array of" - " available audio sources\",\"get\":{\"parameters\":[{\"in\":\"query\",\"" - "name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"}" - "}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"res" - "ponse\":{\"description\":\"Array of endpoint info structures\",\"type\":" - "\"array\",\"items\":{\"$ref\":\"#/components/schemas/endpoint_info\"}}}," - "\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_sinks\":{\"d" - "escription\":\"Retrieve array of available audio sinks\",\"get\":{\"para" - "meters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\"" - "schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/co" - "mponents/responses/200\",\"response\":{\"description\":\"Array of endpoi" - "nt info structures\",\"type\":\"array\",\"items\":{\"$ref\":\"#/componen" - "ts/schemas/endpoint_info\"}}},\"400\":{\"$ref\":\"#/components/responses" - "/400\"}}}},\"/stream_open\":{\"description\":\"Request opening a stream\"" - ",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/strea" - "mcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"" - "required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"na" - "me\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}}" - ",{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":false,\"schema\"" - ":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/re" - "sponses/200\",\"response\":{\"description\":\"Stream information structu" - "re\",\"$ref\":\"#/components/schemas/stream_info\"}},\"400\":{\"$ref\":\"" - "#/components/responses/400\"}}}},\"/stream_close\":{\"description\":\"Re" - "quest closing a stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#/compon" - "ents/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\",\"" - "name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"" - "responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{" - "\"$ref\":\"#/components/responses/400\"}}}},\"/set_volume\":{\"descripti" - "on\":\"Set volume\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/" - "x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\"" - ":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"" - "in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"schema\":{\"" - "type\":\"int\"}},{\"in\":\"query\",\"name\":\"volume\",\"required\":true" - ",\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"ramp_tim" - "e_ms\",\"required\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":" - "{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#" - "/components/responses/400\"}}}},\"/get_volume\":{\"description\":\"Get v" - "olume\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_t" - "ype\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\"" - ",\"name\":\"endpoint_id\",\"required\":true,\"schema\":{\"type\":\"int\"" - "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"re" - "sponse\":{\"description\":\"Endpoint volume value\",\"type\":\"double\"}" - "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_property\"" - ":{\"description\":\"Set property value\",\"get\":{\"x-permissions\":{\"$" - "ref\":\"#/components/x-permissions/streamcontrol\"},\"parameters\":[{\"i" - "n\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"" - "type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\"" - ":false,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"prope" - "rty_name\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"" - "query\",\"name\":\"value\",\"required\":true,\"schema\":{\"type\":\"stri" - "ng\"}},{\"in\":\"query\",\"name\":\"ramp_time_ms\",\"required\":false,\"" - "schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compo" - "nents/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}" - "}}},\"/get_property\":{\"description\":\"Get property value\",\"get\":{\"" - "parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":" - "true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoi" - "nt_id\",\"required\":false,\"schema\":{\"type\":\"int\"}},{\"in\":\"quer" - "y\",\"name\":\"property_name\",\"required\":true,\"schema\":{\"type\":\"" + "{\"openapi\":\"3.0.0\",\"info\":{\"description\":\"Audio high level API " + "for AGL applications\",\"title\":\"audiohighlevel\",\"version\":\"1.0\"," + "\"x-binding-c-generator\":{\"api\":\"audiohl\",\"version\":2,\"prefix\":" + "\"audiohlapi_\",\"postfix\":\"\",\"start\":null,\"onevent\":\"AhlOnEvent" + "\",\"init\":\"AhlBindingInit\",\"scope\":\"\",\"private\":false}},\"serv" + "ers\":[{\"url\":\"ws://{host}:{port}/api/audiohl\",\"description\":\"Aud" + "io high level API for AGL applications.\",\"variables\":{\"host\":{\"def" + "ault\":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":" + "[{\"$ref\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"sche" + "mas\":{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"" + "afb-event\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply" + "-v2\":{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":" + "[\"jtype\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"" + "const\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"" + "status\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"t" + "ype\":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"s" + "tring\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"obje" + "ct\"}}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"" + "event\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-" + "event\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}" + "},\"endpoint_info\":{\"type\":\"object\",\"required\":[\"endpoint_id\",\"" + "type\",\"device_name\",\"device_uri\"],\"properties\":{\"endpoint_id\":{" + "\"type\":\"int\"},\"type\":{\"type\":\"enum\"},\"device_name\":{\"type\"" + ":\"string\"},\"device_uri_type\":{\"type\":\"string\"}}},\"stream_info\"" + ":{\"type\":\"object\",\"required\":[\"stream_id\",\"state\",\"mute\",\"e" + "ndpoint_info\"],\"properties\":{\"stream_id\":{\"type\":\"int\"},\"state" + "\":{\"type\":\"int\"},\"mute\":{\"type\":\"int\"},\"device_uri\":{\"type" + "\":\"string\"},\"$ref\":\"#/components/schemas/endpoint_info\"}}},\"x-pe" + "rmissions\":{\"streamcontrol\":{\"permission\":\"urn:AGL:permission:audi" + "o:public:streamcontrol\"},\"routingcontrol\":{\"permission\":\"urn:AGL:p" + "ermission:audio:public:routingcontrol\"},\"audiostream\":{\"permission\"" + ":\"urn:AGL:permission:audio:public:audiostream\"},\"prioritysignal\":{\"" + "permission\":\"urn:AGL:permission:audio:public:prioritysignal\"},\"sound" + "event\":{\"permission\":\"urn:AGL:permission:audio:public:soundevent\"}," + "\"streammonitor\":{\"permission\":\"urn:AGL:permission:audio:public:stre" + "ammonitor\"}},\"responses\":{\"200\":{\"description\":\"A complex object" + " array response\",\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/afb-reply\"}}}},\"400\":{\"description\":\"Inval" + "id arguments\"}}},\"paths\":{\"/get_sources\":{\"description\":\"Retriev" + "e array of available audio sources\",\"get\":{\"parameters\":[{\"in\":\"" + "query\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"" "string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/20" - "0\",\"response\":{\"description\":\"Property value\",\"type\":\"double\"" - "}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_state\":{" - "\"description\":\"Set state\",\"get\":{\"x-permissions\":{\"$ref\":\"#/c" - "omponents/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query" - "\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"e" - "num\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"s" - "chema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state_name\",\"" - "required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"na" - "me\":\"state_value\",\"required\":true,\"schema\":{\"type\":\"string\"}}" - "],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400" - "\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_state\":{\"descri" - "ption\":\"Get state value\",\"get\":{\"parameters\":[{\"in\":\"query\",\"" - "name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\"" - "}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"schema" - "\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state_name\",\"requi" - "red\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"" - "$ref\":\"#/components/responses/200\",\"response\":{\"description\":\"En" - "dpoint state value\",\"type\":\"string\"}},\"400\":{\"$ref\":\"#/compone" - "nts/responses/400\"}}}},\"/post_sound_event\":{\"description\":\"Post so" - "und event\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permis" - "sions/soundevent\"},\"parameters\":[{\"in\":\"query\",\"name\":\"event_n" - "ame\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"quer" - "y\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"str" - "ing\"}},{\"in\":\"query\",\"name\":\"media_name\",\"required\":false,\"s" - "chema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"audio_contex" - "t\",\"required\":false,\"schema\":{\"type\":\"object\"}}],\"responses\":" - "{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#" - "/components/responses/400\"}}}},\"/subscribe\":{\"description\":\"Subscr" - "ibe to audio high level events\",\"get\":{\"parameters\":[{\"in\":\"quer" - "y\",\"name\":\"events\",\"required\":true,\"schema\":{\"type\":\"array\"" - ",\"items\":{\"type\":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#" - "/components/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/" - "400\"}}}},\"/unsubscribe\":{\"description\":\"Unubscribe to audio high l" - "evel events\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"even" - "ts\",\"required\":true,\"schema\":{\"type\":\"array\",\"items\":{\"type\"" - ":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/components/response" - "s/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}}}}" + "0\",\"response\":{\"description\":\"Array of endpoint info structures\"," + "\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/endpoint_i" + "nfo\"}}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_sin" + "ks\":{\"description\":\"Retrieve array of available audio sinks\",\"get\"" + ":{\"parameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\"" + ":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\"" + ":\"#/components/responses/200\",\"response\":{\"description\":\"Array of" + " endpoint info structures\",\"type\":\"array\",\"items\":{\"$ref\":\"#/c" + "omponents/schemas/endpoint_info\"}}},\"400\":{\"$ref\":\"#/components/re" + "sponses/400\"}}}},\"/stream_open\":{\"description\":\"Request opening a " + "stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissio" + "ns/audiostream\"},\"parameters\":[{\"in\":\"query\",\"name\":\"audio_rol" + "e\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\"" + ",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enu" + "m\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":false,\"sc" + "hema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compone" + "nts/responses/200\",\"response\":{\"description\":\"Stream information s" + "tructure\",\"$ref\":\"#/components/schemas/stream_info\"}},\"400\":{\"$r" + "ef\":\"#/components/responses/400\"}}}},\"/stream_close\":{\"description" + "\":\"Request closing a stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#" + "/components/x-permissions/audiostream\"},\"parameters\":[{\"in\":\"query" + "\",\"name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"int\"" + "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"4" + "00\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_stream_state\":" + "{\"description\":\"Change stream active state\",\"get\":{\"x-permissions" + "\":{\"$ref\":\"#/components/x-permissions/streamcontrol\"},\"parameters\"" + ":[{\"in\":\"query\",\"name\":\"stream_id\",\"required\":true,\"schema\":" + "{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state\",\"required\":tr" + "ue,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/" + "components/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/4" + "00\"}}}},\"/set_stream_mute\":{\"description\":\"Change stream mute stat" + "e\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/st" + "reamcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"stream_id\"," + "\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"nam" + "e\":\"mute\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"respons" + "es\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\"" + ":\"#/components/responses/400\"}}}},\"/get_stream_info\":{\"description\"" + ":\"Retrieve stream information\",\"get\":{\"x-permissions\":{\"$ref\":\"" + "#/components/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"qu" + "ery\",\"name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"in" + "t\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"" + "response\":{\"description\":\"Stream information structure\",\"$ref\":\"" + "#/components/schemas/stream_info\"}},\"400\":{\"$ref\":\"#/components/re" + "sponses/400\"}}}},\"/set_volume\":{\"description\":\"Set volume\",\"get\"" + ":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/streamcontrol" + "\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"requi" + "red\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"" + "endpoint_id\",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"" + "query\",\"name\":\"volume\",\"required\":true,\"schema\":{\"type\":\"str" + "ing\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" + "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_volume\":{" + "\"description\":\"Get volume\",\"get\":{\"parameters\":[{\"in\":\"query\"" + ",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enu" + "m\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"sch" + "ema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/componen" + "ts/responses/200\",\"response\":{\"description\":\"Endpoint volume value" + "\",\"type\":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"" + "}}}},\"/get_list_properties\":{\"description\":\"Retrieve a list of supp" + "orted properties for a particular endpoint\",\"get\":{\"parameters\":[{\"" + "in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{" + "\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"require" + "d\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref" + "\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/resp" + "onses/400\"}}}},\"/set_property\":{\"description\":\"Set property value\"" + ",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/strea" + "mcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\"" + ",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"n" + "ame\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"}}," + "{\"in\":\"query\",\"name\":\"property_name\",\"required\":true,\"schema\"" + ":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"value\",\"required\"" + ":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\"" + ":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/respon" + "ses/400\"}}}},\"/get_property\":{\"description\":\"Get property value\"," + "\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"" + "required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name" + "\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"}},{\"" + "in\":\"query\",\"name\":\"property_name\",\"required\":true,\"schema\":{" + "\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/r" + "esponses/200\",\"response\":{\"description\":\"Property value\",\"type\"" + ":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/ge" + "t_list_events\":{\"description\":\"Retrieve a list of supported events f" + "or a particular audio role\",\"get\":{\"parameters\":[{\"in\":\"query\"," + "\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"" + "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"4" + "00\":{\"$ref\":\"#/components/responses/400\"}}}},\"/post_event\":{\"des" + "cription\":\"Post sound or audio device related event (extendable mechan" + "ism)\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions" + "/soundevent\"},\"parameters\":[{\"in\":\"query\",\"name\":\"event_name\"" + ",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"" + "name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"}" + "},{\"in\":\"query\",\"name\":\"media_name\",\"required\":false,\"schema\"" + ":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"event_context\",\"r" + "equired\":false,\"schema\":{\"type\":\"object\"}}],\"responses\":{\"200\"" + ":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/compone" + "nts/responses/400\"}}}},\"/subscribe\":{\"description\":\"Subscribe to a" + "udio high level events\",\"get\":{\"parameters\":[{\"in\":\"query\",\"na" + "me\":\"events\",\"required\":true,\"schema\":{\"type\":\"array\",\"items" + "\":{\"type\":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/compone" + "nts/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}" + "},\"/unsubscribe\":{\"description\":\"Unubscribe to audio high level eve" + "nts\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"events\",\"r" + "equired\":true,\"schema\":{\"type\":\"array\",\"items\":{\"type\":\"stri" + "ng\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" + "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}}}}" ; static const struct afb_auth _afb_auths_v2_audiohl[] = { + { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:audiostream" }, { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:streamcontrol" }, { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:soundevent" } }; @@ -141,13 +158,16 @@ static const struct afb_auth _afb_auths_v2_audiohl[] = { void audiohlapi_get_sinks(struct afb_req req); void audiohlapi_stream_open(struct afb_req req); void audiohlapi_stream_close(struct afb_req req); + void audiohlapi_set_stream_state(struct afb_req req); + void audiohlapi_set_stream_mute(struct afb_req req); + void audiohlapi_get_stream_info(struct afb_req req); void audiohlapi_set_volume(struct afb_req req); void audiohlapi_get_volume(struct afb_req req); + void audiohlapi_get_list_properties(struct afb_req req); void audiohlapi_set_property(struct afb_req req); void audiohlapi_get_property(struct afb_req req); - void audiohlapi_set_state(struct afb_req req); - void audiohlapi_get_state(struct afb_req req); - void audiohlapi_post_sound_event(struct afb_req req); + void audiohlapi_get_list_events(struct afb_req req); + void audiohlapi_post_event(struct afb_req req); void audiohlapi_subscribe(struct afb_req req); void audiohlapi_unsubscribe(struct afb_req req); @@ -180,10 +200,31 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { .info = "Request closing a stream", .session = AFB_SESSION_NONE_V2 }, + { + .verb = "set_stream_state", + .callback = audiohlapi_set_stream_state, + .auth = &_afb_auths_v2_audiohl[1], + .info = "Change stream active state", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "set_stream_mute", + .callback = audiohlapi_set_stream_mute, + .auth = &_afb_auths_v2_audiohl[1], + .info = "Change stream mute state", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get_stream_info", + .callback = audiohlapi_get_stream_info, + .auth = &_afb_auths_v2_audiohl[1], + .info = "Retrieve stream information", + .session = AFB_SESSION_NONE_V2 + }, { .verb = "set_volume", .callback = audiohlapi_set_volume, - .auth = &_afb_auths_v2_audiohl[0], + .auth = &_afb_auths_v2_audiohl[1], .info = "Set volume", .session = AFB_SESSION_NONE_V2 }, @@ -194,10 +235,17 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { .info = "Get volume", .session = AFB_SESSION_NONE_V2 }, + { + .verb = "get_list_properties", + .callback = audiohlapi_get_list_properties, + .auth = NULL, + .info = "Retrieve a list of supported properties for a particular endpoint", + .session = AFB_SESSION_NONE_V2 + }, { .verb = "set_property", .callback = audiohlapi_set_property, - .auth = &_afb_auths_v2_audiohl[0], + .auth = &_afb_auths_v2_audiohl[1], .info = "Set property value", .session = AFB_SESSION_NONE_V2 }, @@ -209,24 +257,17 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { .session = AFB_SESSION_NONE_V2 }, { - .verb = "set_state", - .callback = audiohlapi_set_state, - .auth = &_afb_auths_v2_audiohl[0], - .info = "Set state", - .session = AFB_SESSION_NONE_V2 - }, - { - .verb = "get_state", - .callback = audiohlapi_get_state, + .verb = "get_list_events", + .callback = audiohlapi_get_list_events, .auth = NULL, - .info = "Get state value", + .info = "Retrieve a list of supported events for a particular audio role", .session = AFB_SESSION_NONE_V2 }, { - .verb = "post_sound_event", - .callback = audiohlapi_post_sound_event, - .auth = &_afb_auths_v2_audiohl[1], - .info = "Post sound event", + .verb = "post_event", + .callback = audiohlapi_post_event, + .auth = &_afb_auths_v2_audiohl[2], + .info = "Post sound or audio device related event (extendable mechanism)", .session = AFB_SESSION_NONE_V2 }, { @@ -259,7 +300,7 @@ const struct afb_binding_v2 afbBindingV2 = { .verbs = _afb_verbs_v2_audiohl, .preinit = NULL, .init = AhlBindingInit, - .onevent = NULL, + .onevent = AhlOnEvent, .noconcurrency = 0 }; diff --git a/src/ahl-apidef.json b/src/ahl-apidef.json index 45067ea..d486337 100644 --- a/src/ahl-apidef.json +++ b/src/ahl-apidef.json @@ -1,6 +1,5 @@ { "openapi": "3.0.0", - "$schema": "http:iot.bzh/download/openapi/schema-3.0/default-schema.json", "info": { "description": "Audio high level API for AGL applications", "title": "audiohighlevel", @@ -11,7 +10,7 @@ "prefix": "audiohlapi_", "postfix": "", "start": null, - "onevent": null, + "onevent": "AhlOnEvent", "init": "AhlBindingInit", "scope": "", "private": false @@ -107,23 +106,23 @@ }, "stream_info": { "type": "object", - "required": [ "stream_id", "endpoint_info" ], + "required": [ "stream_id", "state", "mute", "endpoint_info" ], "properties": { "stream_id": { "type": "int" }, + "state": { "type": "int" }, + "mute": { "type": "int" }, + "device_uri": { "type": "string" }, "$ref": "#/components/schemas/endpoint_info" } } }, "x-permissions": { - "streamcontrol": { - "permission": "urn:AGL:permission:audio:public:streamcontrol" - }, - "routingcontrol": { - "permission": "urn:AGL:permission:audio:public:routingcontrol" - }, - "soundevent": { - "permission": "urn:AGL:permission:audio:public:soundevent" - } + "streamcontrol": { "permission": "urn:AGL:permission:audio:public:streamcontrol"}, + "routingcontrol": { "permission": "urn:AGL:permission:audio:public:routingcontrol"}, + "audiostream": { "permission": "urn:AGL:permission:audio:public:audiostream"}, + "prioritysignal": { "permission": "urn:AGL:permission:audio:public:prioritysignal"}, + "soundevent": {"permission": "urn:AGL:permission:audio:public:soundevent"}, + "streammonitor": {"permission": "urn:AGL:permission:audio:public:streammonitor"} }, "responses": { "200": { @@ -194,7 +193,7 @@ "/stream_open": { "description": "Request opening a stream", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { "$ref": "#/components/x-permissions/audiostream" }, "parameters": [ { "in": "query", @@ -230,7 +229,7 @@ "/stream_close": { "description": "Request closing a stream", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { "$ref": "#/components/x-permissions/audiostream" }, "parameters": [ { "in": "query", @@ -245,34 +244,54 @@ } } }, - "/set_volume": { - "description": "Set volume", + "/set_stream_state": { + "description": "Change stream active state", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { + "$ref": "#/components/x-permissions/streamcontrol" + }, "parameters": [ { "in": "query", - "name": "endpoint_type", + "name": "stream_id", "required": true, - "schema": { "type": "enum" } + "schema": {"type": "int"} }, { "in": "query", - "name": "endpoint_id", + "name": "state", "required": true, - "schema": { "type": "int" } + "schema": {"type": "int"} + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200" }, + "400": { + "$ref": "#/components/responses/400" + } + } + } + }, + "/set_stream_mute": { + "description": "Change stream mute state", + "get": { + "x-permissions": { + "$ref": "#/components/x-permissions/streamcontrol" + }, + "parameters": [ { "in": "query", - "name": "volume", + "name": "stream_id", "required": true, - "schema": { "type": "string" } + "schema": {"type": "int"} }, { "in": "query", - "name": "ramp_time_ms", - "required": false, - "schema": { "type": "int" } + "name": "mute", + "required": true, + "schema": {"type": "int"} } ], "responses": { @@ -281,37 +300,34 @@ } } }, - "/get_volume": { - "description": "Get volume", + "/get_stream_info": { + "description": "Retrieve stream information", "get": { + "x-permissions": { + "$ref": "#/components/x-permissions/streamcontrol" + }, "parameters": [ { "in": "query", - "name": "endpoint_type", - "required": true, - "schema": { "type": "enum" } - }, - { - "in": "query", - "name": "endpoint_id", + "name": "stream_id", "required": true, - "schema": { "type": "int" } + "schema": {"type": "int"} } ], "responses": { "200": { "$ref": "#/components/responses/200", "response": { - "description": "Endpoint volume value", - "type": "double" + "description": "Stream information structure", + "$ref": "#/components/schemas/stream_info" } }, "400": { "$ref": "#/components/responses/400" } } } }, - "/set_property": { - "description": "Set property value", + "/set_volume": { + "description": "Set volume", "get": { "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, "parameters": [ @@ -324,36 +340,53 @@ { "in": "query", "name": "endpoint_id", - "required": false, + "required": true, "schema": { "type": "int" } }, { "in": "query", - "name": "property_name", + "name": "volume", "required": true, "schema": { "type": "string" } - }, + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/get_volume": { + "description": "Get volume", + "get": { + "parameters": [ { "in": "query", - "name": "value", + "name": "endpoint_type", "required": true, - "schema": { "type": "string" } + "schema": { "type": "enum" } }, { "in": "query", - "name": "ramp_time_ms", - "required": false, + "name": "endpoint_id", + "required": true, "schema": { "type": "int" } } ], "responses": { - "200": { "$ref": "#/components/responses/200" }, + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Endpoint volume value", + "type": "double" + } + }, "400": { "$ref": "#/components/responses/400" } } } }, - "/get_property": { - "description": "Get property value", + "/get_list_properties": { + "description": "Retrieve a list of supported properties for a particular endpoint", "get": { "parameters": [ { @@ -367,28 +400,16 @@ "name": "endpoint_id", "required": false, "schema": { "type": "int" } - }, - { - "in": "query", - "name": "property_name", - "required": true, - "schema": { "type": "string" } } ], "responses": { - "200": { - "$ref": "#/components/responses/200", - "response": { - "description": "Property value", - "type": "double" - } - }, + "200": { "$ref": "#/components/responses/200" }, "400": { "$ref": "#/components/responses/400" } } } }, - "/set_state": { - "description": "Set state", + "/set_property": { + "description": "Set property value", "get": { "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, "parameters": [ @@ -401,18 +422,18 @@ { "in": "query", "name": "endpoint_id", - "required": true, + "required": false, "schema": { "type": "int" } }, { "in": "query", - "name": "state_name", + "name": "property_name", "required": true, "schema": { "type": "string" } }, { "in": "query", - "name": "state_value", + "name": "value", "required": true, "schema": { "type": "string" } } @@ -423,8 +444,8 @@ } } }, - "/get_state": { - "description": "Get state value", + "/get_property": { + "description": "Get property value", "get": { "parameters": [ { @@ -436,12 +457,12 @@ { "in": "query", "name": "endpoint_id", - "required": true, + "required": false, "schema": { "type": "int" } }, { "in": "query", - "name": "state_name", + "name": "property_name", "required": true, "schema": { "type": "string" } } @@ -450,16 +471,37 @@ "200": { "$ref": "#/components/responses/200", "response": { - "description": "Endpoint state value", - "type": "string" + "description": "Property value", + "type": "double" } }, "400": { "$ref": "#/components/responses/400" } } } }, - "/post_sound_event": { - "description": "Post sound event", + "/get_list_events": { + "description": "Retrieve a list of supported events for a particular audio role", + "get": { + "parameters": [ + { + "in": "query", + "name": "audio_role", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200" + }, + "400": { + "$ref": "#/components/responses/400" + } + } + } + }, + "/post_event": { + "description": "Post sound or audio device related event (extendable mechanism)", "get": { "x-permissions": { "$ref": "#/components/x-permissions/soundevent" }, "parameters": [ @@ -483,7 +525,7 @@ }, { "in": "query", - "name": "audio_context", + "name": "event_context", "required": false, "schema": { "type": "object" } } @@ -530,12 +572,8 @@ } ], "responses": { - "200": { - "$ref": "#/components/responses/200" - }, - "400": { - "$ref": "#/components/responses/400" - } + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } } } } diff --git a/src/ahl-binding.c b/src/ahl-binding.c index 4b075c0..3f8d71d 100644 --- a/src/ahl-binding.c +++ b/src/ahl-binding.c @@ -26,61 +26,44 @@ // Global high-level binding context AHLCtxT g_AHLCtx; -// Workshop development topics: -// - Associating streamID with a client id of afb. Has to be done with sessions? -// - Declaring dependencies on other binding services (examples) -// - json_object_put to free JSON objects? potential leaks currently -// - no events on new HAL registered to alsacore? -// - unregistering event subscription examples? -// - discussion how to use property and event system to dispatch external parameterization (e.g. Wwise/Fiberdyne) -// - discussion where to best isolate policy (controller or HLB plugin) -// - Other HLB attributes to pass through (e.g. interrupted behavior) -// - DBScale TLV warning -// - GLib (internal) dependency -// - HAL registration dependency (initialization order) -// - Binding startup arguments for config file path -// - Can we use the same HAL for different card numbers? -// - Example use of volume ramping in HAL? -// - Binding termination function -// - AGL persistence framework? -// - How to provide API services with config.xml (secrets and all) +//afb_req_context_set ?? +// usr = afb_req_context_get(req); +// afb_req_context_clear(req); - -// Helper macros/func for packaging JSON objects from C structures -#define EndpointInfoStructToJSON(__JSON_OBJECT__, __ENDPOINTINFOSTRUCT__) \ - wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:s,s:i}",\ - "endpoint_id", __ENDPOINTINFOSTRUCT__.endpointID, \ - "endpoint_type", __ENDPOINTINFOSTRUCT__.type, \ - "device_name", __ENDPOINTINFOSTRUCT__.deviceName, \ - "device_uri_type", __ENDPOINTINFOSTRUCT__.deviceURIType); - -static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT streamInfo) +static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * pAudioFormat) { - json_object *endpointInfoJ; - EndpointInfoStructToJSON(endpointInfoJ,streamInfo.endpointInfo); - wrap_json_pack(streamInfoJ, "{s:i}", "stream_id", streamInfo.streamID); - json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); + wrap_json_pack(audioFormatJ, "{s:i,s:i,s:i}", + "sample_rate", pAudioFormat->sampleRate, + "num_channels", pAudioFormat->numChannels, + "sample_type", pAudioFormat->sampleType); } -static streamID_t CreateNewStreamID() +// Helper macros/func for packaging JSON objects from C structures +static void EndpointInfoStructToJSON(json_object **endpointInfoJ, EndpointInfoT * pEndpointInfo) { - streamID_t newID = g_AHLCtx.nextStreamID; - g_AHLCtx.nextStreamID++; - return newID; + json_object *formatInfoJ = NULL; + wrap_json_pack(endpointInfoJ, "{s:i,s:i,s:s,s:i}", + "endpoint_id", pEndpointInfo->endpointID, + "endpoint_type", pEndpointInfo->type, + "device_name", pEndpointInfo->gsDeviceName->str, + "device_uri_type", pEndpointInfo->deviceURIType); + AudioFormatStructToJSON(&formatInfoJ,&pEndpointInfo->format); + json_object_object_add(*endpointInfoJ,"format",formatInfoJ); } - -static int FindRoleIndex( const char * in_pAudioRole) + +static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT * pStreamInfo) { - int index = -1; // Not found - for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) - { - GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); - if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) - index = i; - } - return index; + json_object *endpointInfoJ = NULL; + EndpointInfoStructToJSON(&endpointInfoJ,pStreamInfo->pEndpointInfo); + wrap_json_pack(streamInfoJ, "{s:i,s:i,s:i,s:s}", + "stream_id", pStreamInfo->streamID, + "state", pStreamInfo->streamState, + "mute", pStreamInfo->streamMute, + "device_uri",pStreamInfo->pEndpointInfo->gsDeviceURI->str); + json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); } + static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT in_endpointType) { EndpointInfoT * pEndpointInfo = NULL; @@ -104,6 +87,104 @@ static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT return pEndpointInfo; } +static StreamInfoT * GetActiveStream(streamID_t in_streamID) +{ + int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; + StreamInfoT * pStreamInfo = NULL; + for ( int i = 0; i < iNumActiveStreams ; i++ ) { + StreamInfoT * pCurStreamInfo = &g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); + if (pCurStreamInfo->streamID == in_streamID){ + pStreamInfo = pCurStreamInfo; + break; + } + } + return pStreamInfo; +} + +static int CreateEvents() +{ + int err = 0; + + g_AHLCtx.policyCtx.propertyEvent = afb_daemon_make_event(AHL_ENDPOINT_PROPERTY_EVENT); + err = !afb_event_is_valid(g_AHLCtx.policyCtx.propertyEvent); + if (err) { + AFB_ERROR("Could not create endpoint property change event"); + err++; + } + + g_AHLCtx.policyCtx.volumeEvent = afb_daemon_make_event(AHL_ENDPOINT_VOLUME_EVENT); + err = !afb_event_is_valid(g_AHLCtx.policyCtx.volumeEvent); + if (err) { + AFB_ERROR("Could not create endpoint volume change event"); + err++; + } + + g_AHLCtx.policyCtx.postEvent = afb_daemon_make_event(AHL_POST_EVENT); + err = !afb_event_is_valid(g_AHLCtx.policyCtx.postEvent); + if (err) { + AFB_ERROR("Could not create post event call event"); + err++; + } + + return err; +} + +static void AhlBindingTerm() +{ + // Policy termination + Policy_Term(); + + // Events + for (int i = 0; i < g_AHLCtx.policyCtx.pEventList->len; i++) + { + // For each event within the role + GArray * pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, i ); + for (int j = 0 ; j < pRoleEventArray->len; j++) + { + GString * gsEventName = &g_array_index(pRoleEventArray,GString,j); + g_string_free(gsEventName,TRUE); + } + g_array_free(pRoleEventArray,TRUE); + pRoleEventArray = NULL; + } + g_ptr_array_free(g_AHLCtx.policyCtx.pEventList,TRUE); + g_AHLCtx.policyCtx.pEventList = NULL; + + // Endpoints + TermEndpoints(); + + // TODO: Need to free g_strings in HAL list + g_array_free(g_AHLCtx.pHALList,TRUE); + g_hash_table_remove_all(g_AHLCtx.policyCtx.pRolePriority); + g_hash_table_destroy(g_AHLCtx.policyCtx.pRolePriority); + // TODO: Need to free g_strings in audio roles list + g_array_free(g_AHLCtx.policyCtx.pAudioRoles,TRUE); + g_array_free(g_AHLCtx.policyCtx.pInterruptBehavior,TRUE); + g_array_free(g_AHLCtx.policyCtx.pActiveStreams,TRUE); + + AFB_INFO("Audio high-level Binding succesTermination"); +} + +static streamID_t CreateNewStreamID() +{ + streamID_t newID = g_AHLCtx.nextStreamID; + g_AHLCtx.nextStreamID++; + return newID; +} + +static int FindRoleIndex( const char * in_pAudioRole) +{ + int index = -1; // Not found + for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) + { + GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); + if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) + index = i; + } + return index; +} + + // Binding initialization PUBLIC int AhlBindingInit() { @@ -121,12 +202,28 @@ PUBLIC int AhlBindingInit() // Parse high-level binding JSON configuration file (will build device lists) errcount += ParseHLBConfig(); + atexit(AhlBindingTerm); + + // This API uses services from low level audio + errcount = afb_daemon_require_api_v2("alsacore",1) ; + if( errcount != 0 ) + { + AFB_ERROR("Audio high level API requires alsacore API to be available"); + return 1; + } + + errcount += CreateEvents(); + + // Parse high-level binding JSON configuration file (will build device lists) + errcount += ParseHLBConfig(); + + // Policy initialization + errcount += Policy_Init(); + // Initialize list of active streams g_AHLCtx.policyCtx.pActiveStreams = g_array_new(FALSE,TRUE,sizeof(StreamInfoT)); - // TODO: Register for device changes ALSA low level audio service - - // TODO: Perform other binding initialization tasks (e.g. broadcast service ready event?) + // TODO: Register for device changes ALSA low level audio service or HAL // TODO: Use AGL persistence framework to retrieve and set inital state/volumes/properties @@ -134,11 +231,15 @@ PUBLIC int AhlBindingInit() return errcount; } -// TODO: AhlBindingTerm()? -// // TODO: Use AGL persistence framework to retrieve and set inital state/volumes +PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ) +{ + // TODO: Implement handling events from HAL... + AFB_DEBUG("AHL received event %s", evtname); +} -// TODO: OnEventFunction -// if ALSA device availability change -> update source / sink list +// TODO: OnEventFunction when it actually subscribe to other binding events +// TODO: Dynamic device handling +// if HAL device availability change -> update source / sink list // Call policy to attempt to assign role based on information (e.g. device name) // Policy should also determine priority (insert device in right spot in the list) @@ -156,26 +257,23 @@ PUBLIC void audiohlapi_get_sources(struct afb_req req) return; } - sourcesJ = json_object_new_array(); - AFB_DEBUG("Filtering devices according to specified audio role=%s", audioRole); - // Check that the role requested exists in current configuration - int roleIndex = FindRoleIndex(audioRole); - if ( roleIndex == -1) + // List device only for current audio role and device type (pick the role device list) + int iRoleIndex = FindRoleIndex(audioRole); + if ( iRoleIndex < 0) { afb_req_fail_f(req, "Invalid arguments", "Requested audio role does not exist in current configuration -> %s", json_object_get_string(queryJ)); return; } - // List device only for current audio role and device type (pick the role device list) - int iRoleIndex = FindRoleIndex(audioRole); - if (iRoleIndex >= 0) + else { + sourcesJ = json_object_new_array(); GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, iRoleIndex ); int iNumberDevices = pRoleSourceDeviceArray->len; for ( int j = 0 ; j < iNumberDevices; j++) { EndpointInfoT sourceInfo = g_array_index(pRoleSourceDeviceArray,EndpointInfoT,j); - EndpointInfoStructToJSON(sourceJ, sourceInfo); + EndpointInfoStructToJSON(&sourceJ, &sourceInfo); json_object_array_add(sourcesJ, sourceJ); } } @@ -197,26 +295,23 @@ PUBLIC void audiohlapi_get_sinks(struct afb_req req) return; } - sinksJ = json_object_new_array(); - AFB_DEBUG("Filtering devices according to specified audio role=%s", audioRole); - // Check that the role requested exists in current configuration - int roleIndex = FindRoleIndex(audioRole); - if ( roleIndex == -1) + // List device only for current audio role and device type (pick the role device list) + int iRoleIndex = FindRoleIndex(audioRole); + if ( iRoleIndex < 0) { afb_req_fail_f(req, "Invalid arguments", "Requested audio role does not exist in current configuration -> %s", json_object_get_string(queryJ)); return; } - // List device only for current audio role and device type (pick the role device list) - int iRoleIndex = FindRoleIndex(audioRole); - if (iRoleIndex >= 0) + else { + sinksJ = json_object_new_array(); GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, iRoleIndex ); int iNumberDevices = pRoleSinkDeviceArray->len; for ( int j = 0 ; j < iNumberDevices; j++) { EndpointInfoT sinkInfo = g_array_index(pRoleSinkDeviceArray,EndpointInfoT,j); - EndpointInfoStructToJSON(sinkJ, sinkInfo); + EndpointInfoStructToJSON(&sinkJ, &sinkInfo); json_object_array_add(sinksJ, sinkJ); } } @@ -230,10 +325,10 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) StreamInfoT streamInfo; json_object *queryJ = NULL; char * audioRole = NULL; - EndpointTypeT endpointType; - endpointID_t endpointID = UNDEFINED_ID; - int policyAllowed = 0; - EndpointInfoT endpointInfo; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + endpointID_t endpointID = AHL_UNDEFINED; + int policyAllowed = AHL_POLICY_REJECT; + EndpointInfoT * pEndpointInfo = NULL; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:s,s:i,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointType, "endpoint_id", &endpointID); @@ -261,32 +356,32 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) return; } - if (endpointID == UNDEFINED_ID) + if (endpointID == AHL_UNDEFINED) { // Assign a device based on configuration priority (first in the list for requested role and endpoint type) - endpointInfo = g_array_index(pRoleDeviceArray,EndpointInfoT,0); + pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,0); + streamInfo.endpointSelMode = AHL_ENDPOINTSELMODE_AUTO; } else{ + streamInfo.endpointSelMode = AHL_ENDPOINTSELMODE_MANUAL; // Find specified endpoint ID in list of devices int iNumberDevices = pRoleDeviceArray->len; - int iEndpointFound = 0; for ( int j = 0 ; j < iNumberDevices; j++) { - endpointInfo = g_array_index(pRoleDeviceArray,EndpointInfoT,j); - if (endpointInfo.endpointID == endpointID) { - iEndpointFound = 1; + pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + if (pEndpointInfo->endpointID == endpointID) { break; } } - if (iEndpointFound == 0) { + if (pEndpointInfo == NULL) { afb_req_fail_f(req, "Endpoint not available", "Specified endpoint not available for role:%s and device type:%d endpoint id %d",audioRole,endpointType,endpointID); return; } } // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions - policyAllowed = Policy_OpenStream(audioRole, endpointType, endpointID); - if (policyAllowed == AUDIOHL_POLICY_REJECT) + policyAllowed = Policy_OpenStream(audioRole, endpointType, pEndpointInfo->endpointID); + if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Open stream not allowed in current context"); return; @@ -294,12 +389,30 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) // Create stream streamInfo.streamID = CreateNewStreamID(); // create new ID - streamInfo.endpointInfo = endpointInfo; + streamInfo.streamState = STREAM_STATUS_READY; + streamInfo.streamMute = STREAM_UNMUTED; + streamInfo.pEndpointInfo = pEndpointInfo; + + char streamEventName[128]; + snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); + + streamInfo.streamStateEvent = afb_daemon_make_event(streamEventName); + err = !afb_event_is_valid(streamInfo.streamStateEvent); + if (err) { + afb_req_fail(req, "Stream event creation failure", "Could not create stream specific state change event"); + return; + } + + err = afb_req_subscribe(req,streamInfo.streamStateEvent); + if (err) { + afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event"); + return; + } // Push stream on active stream list g_array_append_val( g_AHLCtx.policyCtx.pActiveStreams, streamInfo ); - StreamInfoStructToJSON(&streamInfoJ,streamInfo); + StreamInfoStructToJSON(&streamInfoJ,&streamInfo); afb_req_success(req, streamInfoJ, "Stream info structure"); } @@ -307,7 +420,8 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) PUBLIC void audiohlapi_stream_close(struct afb_req req) { json_object *queryJ = NULL; - streamID_t streamID = UNDEFINED_ID; + streamID_t streamID = AHL_UNDEFINED; + int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID); @@ -319,12 +433,36 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing + // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions + policyAllowed = Policy_CloseStream(streamID); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Close stream not allowed in current context"); + return; + } + // Remove from active stream list (if present) int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; int iStreamFound = 0; for ( int i = 0; i < iNumActiveStreams ; i++ ) { StreamInfoT streamInfo = g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); if (streamInfo.streamID == streamID){ + // Unsubscribe client from stream events + char streamEventName[128]; + snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); + int iValid = afb_event_is_valid(streamInfo.streamStateEvent); + if (iValid) { + err = afb_req_unsubscribe(req,streamInfo.streamStateEvent); + if (err) { + afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event"); + return; + } + } + else{ + AFB_WARNING("Stream event no longer valid and therefore not unsubscribed"); + break; + } + g_array_remove_index(g_AHLCtx.policyCtx.pActiveStreams,i); iStreamFound = 1; break; @@ -339,35 +477,142 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) afb_req_success(req, NULL, "Stream close completed"); } -// Endpoints + PUBLIC void audiohlapi_set_stream_state(struct afb_req req) + { + json_object *queryJ = NULL; + streamID_t streamID = AHL_UNDEFINED; + StreamStateT streamState = STREAM_STATUS_MAXVALUE; + int policyAllowed = AHL_POLICY_REJECT; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "stream_id", &streamID,"state",&streamState); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d, state:%d", streamID,streamState); + + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + policyAllowed = Policy_SetStreamState(streamID,streamState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Change stream state not allowed in current context"); + return; + } + + pStreamInfo->streamState = streamState; + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",pStreamInfo->streamMute,"state",streamState); + if (err) { + afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + return; + } + afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); + + afb_req_success(req, NULL, "Set stream state"); + } + + PUBLIC void audiohlapi_set_stream_mute(struct afb_req req) + { + json_object *queryJ = NULL; + streamID_t streamID = AHL_UNDEFINED; + StreamMuteT muteState = STREAM_MUTE_MAXVALUE; + int policyAllowed = AHL_POLICY_REJECT; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "stream_id", &streamID,"mute",&muteState); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d, mute:%d", streamID,muteState); + + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + policyAllowed = Policy_SetStreamMute(streamID,muteState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Mute stream not allowed in current context"); + return; + } + + pStreamInfo->streamMute = muteState; + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",muteState,"state",pStreamInfo->streamState); + if (err) { + afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + return; + } + afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); + + afb_req_success(req, NULL, "Set stream mute completed"); + } + +PUBLIC void audiohlapi_get_stream_info(struct afb_req req) + { + json_object *queryJ = NULL; + streamID_t streamID = AHL_UNDEFINED; + json_object * streamInfoJ = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); + + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + StreamInfoStructToJSON(&streamInfoJ,pStreamInfo); + + afb_req_success(req, streamInfoJ, "Get stream info completed"); + } + PUBLIC void audiohlapi_set_volume(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * volumeStr = NULL; - int rampTimeMS = 0; - int policyAllowed = 0; + int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"volume",&volumeStr,"ramp_time_ms",&rampTimeMS); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"volume",&volumeStr); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s ramp_time_ms: %d", endpointType,endpointID,volumeStr,rampTimeMS); - - // TODO: Parse volume string to support increment/absolute/percent notation + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s", endpointType,endpointID,volumeStr); + + // TODO: Parse volume string to support increment/absolute/percent notation (or delegate to action / policy layer to interpret) int vol = atoi(volumeStr); - policyAllowed = Policy_SetVolume(endpointType, endpointID, volumeStr, rampTimeMS); // TODO: Potentially retrieve modified value by policy (e.g. volume limit) + // TODO: Policy needs way to set cached endpoint volume value (eg.) + policyAllowed = Policy_SetVolume(endpointType, endpointID, volumeStr); // TODO: Potentially retrieve modified value by policy (e.g. volume limit) if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context"); return; } - // TODO: Cache during device enumeration for efficiency + // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -376,27 +621,36 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) } // Using audio role available from endpoint to target the right HAL control (build string based on convention) - char halControlName[AUDIOHL_MAXHALCONTROLNAME_LENGTH]; - strncpy(halControlName,pEndpointInfo->audioRole,AUDIOHL_MAXHALCONTROLNAME_LENGTH); - strcat(halControlName,"_Ramp"); // Or _Vol for direct control (no ramping) + GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Ramp"); // Or _Vol for direct control (no ramping) // Set endpoint volume using HAL services (leveraging ramps etc.) json_object *j_response, *j_query = NULL; // Package query - err = wrap_json_pack(&j_query,"{s:s,s:i}","label",halControlName, "val",vol); + err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",vol); if (err) { afb_req_fail_f(req, "Invalid query for HAL ctlset", "Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); return; } + // TODO: Move this to ref implmentation policy // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->halAPIName, "ctlset", j_query, &j_response); + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); if (err) { - afb_req_fail_f(req, "HAL ctlset failure", "Could not ctlset on HAL: %s",pEndpointInfo->halAPIName); + afb_req_fail_f(req, "HAL ctlset failure", "Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); return; } AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id",endpointID,"endpoint_type",endpointType,"value",vol); + if (err) { + afb_req_fail_f(req, "Invalid event data for volume event", "Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); afb_req_success(req, NULL, "Set volume completed"); } @@ -404,9 +658,9 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) PUBLIC void audiohlapi_get_volume(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; - json_object *volumeJ; + json_object * volumeJ = NULL; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); @@ -416,7 +670,7 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); - // TODO: Cache during device enumeration for efficiency + // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -425,24 +679,25 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) } // Using audio role available from endpoint to target the right HAL control (build string based on convention) - char halControlName[AUDIOHL_MAXHALCONTROLNAME_LENGTH]; - strncpy(halControlName,pEndpointInfo->audioRole,AUDIOHL_MAXHALCONTROLNAME_LENGTH); - strcat(halControlName,"_Vol"); // Use current value, not ramp target + GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Use current value, not ramp target // Set endpoint volume using HAL services (leveraging ramps etc.) json_object *j_response, *j_query = NULL; + // TODO: Returned cached endpoint volume value (controlled by policy) // Package query - err = wrap_json_pack(&j_query,"{s:s}","label",halControlName); + err = wrap_json_pack(&j_query,"{s:s}","label",gsHALControlName->str); if (err) { afb_req_fail_f(req, "Invalid query for HAL ctlget", "Invalid query for HAL ctlget: %s",json_object_to_json_string(j_query)); return; } + // TODO: Return cached value or move to policy // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->halAPIName, "ctlget", j_query, &j_response); + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlget", j_query, &j_response); if (err) { - afb_req_fail_f(req, "HAL ctlget failure", "Could not ctlget on HAL: %s",pEndpointInfo->halAPIName); + afb_req_fail_f(req, "HAL ctlget failure", "Could not ctlget on HAL: %s",pEndpointInfo->gsHALAPIName->str); return; } AFB_INFO("HAL ctlget response=%s", json_object_to_json_string(j_response)); @@ -464,38 +719,77 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) afb_req_success(req, volumeJ, "Retrieved volume value"); } +// Properties +PUBLIC void audiohlapi_get_list_properties(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = AHL_UNDEFINED; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + json_object * endpointPropsJ = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); + + EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); + if (pEndpointInfo == NULL) + { + afb_req_fail_f(req, "Endpoint not found", "Endpoint information not found for id:%d type%d",endpointID,endpointType); + return; + } + + // Build and return list of properties for specific endpoint + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, pEndpointInfo->pPropTable); + endpointPropsJ = json_object_new_array(); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + json_object * propJ = json_object_new_string(key); + json_object_array_add(endpointPropsJ, propJ); + } + + afb_req_success(req, endpointPropsJ, "Retrieved property list for endpoint"); +} + PUBLIC void audiohlapi_set_property(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; char * propValueStr = NULL; - int rampTimeMS = 0; - int policyAllowed = 0; + int policyAllowed = AHL_POLICY_REJECT; + + // TODO: object type detection (string = state, numeric = property) queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr,"ramp_time_ms",&rampTimeMS); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s ramp_time_ms:%d", endpointType,endpointID,propertyName,propValueStr,rampTimeMS); + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s", endpointType,endpointID,propertyName,propValueStr); // TODO: Parse property value string to support increment/absolute/percent notation // Call policy to allow custom policy actions in current context - policyAllowed = Policy_SetProperty(endpointType, endpointID, propertyName, propValueStr, rampTimeMS); // TODO: Potentially retrieve modified value by policy (e.g. parameter limit) + policyAllowed = Policy_SetProperty(endpointType, endpointID, propertyName, propValueStr); // TODO: Potentially retrieve modified value by policy (e.g. parameter limit) if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Set endpoint property not allowed in current context"); return; } - // TODO: Set endpoint property (dispatch on right service target) - // Property targets (Internal,Wwise,Fiberdyne) (e.g. Wwise.ParamX, Fiberdyne.ParamY, Internal.ParamZ) - // Cache value in property list - // TBD + // TODO: Policy to dispatch on right service target + // TODO: Policy tp cache value in property list + + afb_event_push(g_AHLCtx.policyCtx.propertyEvent,queryJ); afb_req_success(req, NULL, "Set property completed"); } @@ -503,21 +797,22 @@ PUBLIC void audiohlapi_set_property(struct afb_req req) PUBLIC void audiohlapi_get_property(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; json_object *propertyValJ; double value = 0.0; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s?i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); - // TODO: Retrieve cached property value + // TODO: Retriev189e cached property value + // TODO Account for properties with string types value = 93.0; // TODO: Get actual property value propertyValJ = json_object_new_double(value); @@ -525,94 +820,97 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) afb_req_success(req, propertyValJ, "Retrieved property value"); } -PUBLIC void audiohlapi_set_state(struct afb_req req) +// Audio related events + +PUBLIC void audiohlapi_get_list_events(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; - EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; - char * stateName = NULL; - char * stateValue = NULL; - int policyAllowed = 0; + char * audioRole = NULL; + json_object * roleEventsJ = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName,"state_value",&stateValue); + int err = wrap_json_unpack(queryJ, "{s:s}", "audio_role",&audioRole); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s state_value:%s", endpointType,endpointID,stateName,stateValue); + AFB_DEBUG("Parsed input arguments = audio_role:%s",audioRole); - // Check that state provided is within list of known state for this config - char * pDefaultStateValue = g_hash_table_lookup(g_AHLCtx.pDefaultStatesHT, stateName); - if (pDefaultStateValue == NULL) - { - afb_req_fail_f(req, "Invalid arguments", "State provided is not known to configuration query=%s", stateName); + // Build and return list of events for specific audio role + int iRoleIndex = FindRoleIndex(audioRole); + if (iRoleIndex < 0) { + afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole); return; } - // Call policy to allow custom policy actions in current context - policyAllowed = Policy_SetState(endpointType, endpointID, stateName, stateValue); // TODO: Potentially retrieve modified value by policy (e.g. state change) - if (!policyAllowed) + GArray * pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, iRoleIndex ); + roleEventsJ = json_object_new_array(); + int iNumberEvents = pRoleEventArray->len; + for ( int i = 0 ; i < iNumberEvents; i++) { - afb_req_fail(req, "Audio policy violation", "Set endpoint state not allowed in current context"); - return; + GString gsEventName = g_array_index(pRoleEventArray,GString,i); + json_object * eventJ = json_object_new_string(gsEventName.str); + json_object_array_add(roleEventsJ, eventJ); } - - // Change the state of the endpoint as requested - - afb_req_success(req, NULL, "Set endpoint state completed"); + + afb_req_success(req, roleEventsJ, "Retrieved event list for audio role"); } -PUBLIC void audiohlapi_get_state(struct afb_req req) +PUBLIC void audiohlapi_post_event(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; - EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; - json_object *stateValJ; - char * stateName = NULL; - char * stateValue = NULL; + char * eventName = NULL; + char * audioRole = NULL; + char * mediaName = NULL; + json_object *eventContext = NULL; + int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName); + int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"event_context",&eventContext); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s", endpointType,endpointID,stateName); - - stateValJ = json_object_new_string(stateValue); - // return cached value + AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%s", eventName,audioRole); - afb_req_success(req, stateValJ, "Retrieved state value"); -} + // Verify if known event for audio role + int iRoleIndex = FindRoleIndex(audioRole); + if (iRoleIndex < 0) { + afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole); + return; + } -// Sound events -PUBLIC void audiohlapi_post_sound_event(struct afb_req req) -{ - json_object *queryJ = NULL; - char * eventName = NULL; - char * mediaName = NULL; - char * audioRole = NULL; - json_object *audioContext = NULL; - int policyAllowed = 0; - - queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"audio_context",&audioContext); - if (err) { - afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + GArray * pRoleEventArray = NULL; + pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, iRoleIndex ); + if (pRoleEventArray->len == 0) { + afb_req_fail_f(req, "No available events", "No available events for role:%s",audioRole); + return; + } + // Check to find specific event + int iEventFound = 0; + for (int i = 0; i < pRoleEventArray->len; i++) + { + GString gs = g_array_index( pRoleEventArray, GString, i ); + if ( strcasecmp(gs.str,eventName) == 0 ) + { + iEventFound = 1; + break; + } + } + if (!iEventFound) { + afb_req_fail_f(req, "Event not found for audio role", "Event not found for roke:%s",audioRole); return; } - AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%s media_name:%s", eventName,audioRole,mediaName); // Call policy to allow custom policy actions in current context (e.g. cancel playback) - policyAllowed = Policy_PostSoundEvent(eventName, audioRole, mediaName, (void*)audioContext); // TODO: Potentially retrieve modified value by policy (e.g. change media) + policyAllowed = Policy_PostEvent(eventName, audioRole, mediaName, (void*)eventContext); if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Post sound event not allowed in current context"); return; } - // TODO: Post sound event to rendering services (e.g. gstreamer file player wrapper or simple ALSA player) + afb_event_push(g_AHLCtx.policyCtx.postEvent,queryJ); afb_req_success(req, NULL, "Posted sound event"); } @@ -621,24 +919,78 @@ PUBLIC void audiohlapi_post_sound_event(struct afb_req req) // Monitoring PUBLIC void audiohlapi_subscribe(struct afb_req req) { - // json_object *queryJ = NULL; - - // queryJ = afb_req_json(req); + json_object *queryJ = NULL; + json_object * eventArrayJ = NULL; - // TODO: Iterate through array length, parsing the string value to actual events - // TODO: Subscribe to appropriate events from other services + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:o}", "events", &eventArrayJ); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + int iNumEvents = json_object_array_length(eventArrayJ); + for (int i = 0; i < iNumEvents; i++) + { + char * pEventName = NULL; + json_object * jEvent = json_object_array_get_idx(eventArrayJ,i); + pEventName = (char *)json_object_get_string(jEvent); + if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { + afb_req_subscribe(req, g_AHLCtx.policyCtx.propertyEvent); + AFB_DEBUG("Client subscribed to endpoint property events"); + } + else if(!strcasecmp(pEventName, AHL_ENDPOINT_VOLUME_EVENT)) { + afb_req_subscribe(req, g_AHLCtx.policyCtx.volumeEvent); + AFB_DEBUG("Client subscribed to endpoint volume events"); + } + else if(!strcasecmp(pEventName, AHL_POST_EVENT)) { + afb_req_subscribe(req, g_AHLCtx.policyCtx.postEvent); + AFB_DEBUG("Client subscribed to post event calls events"); + } + else { + afb_req_fail(req, "failed", "Invalid event"); + return; + } + } afb_req_success(req, NULL, "Subscribe to events finished"); } PUBLIC void audiohlapi_unsubscribe(struct afb_req req) { - // json_object *queryJ = NULL; - - // queryJ = afb_req_json(req); - - // TODO: Iterate through array length, parsing the string value to actual events - // TODO: Unsubscribe to appropriate events from other services + json_object *queryJ = NULL; + json_object * eventArrayJ = NULL; - afb_req_success(req, NULL, "Subscribe to events finished"); + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:o}", "events", &eventArrayJ); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + int iNumEvents = json_object_array_length(eventArrayJ); + for (int i = 0; i < iNumEvents; i++) + { + char * pEventName = NULL; + json_object * jEvent = json_object_array_get_idx(eventArrayJ,i); + pEventName = (char *)json_object_get_string(jEvent); + if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { + afb_req_unsubscribe(req, g_AHLCtx.policyCtx.propertyEvent); + AFB_DEBUG("Client unsubscribed to endpoint property events"); + } + else if(!strcasecmp(pEventName, AHL_ENDPOINT_VOLUME_EVENT)) { + afb_req_unsubscribe(req, g_AHLCtx.policyCtx.volumeEvent); + AFB_DEBUG("Client unsubscribed to endpoint volume events"); + } + else if(!strcasecmp(pEventName, AHL_POST_EVENT)) { + afb_req_unsubscribe(req, g_AHLCtx.policyCtx.postEvent); + AFB_DEBUG("Client unsubscribed to post event calls events"); + } + else { + afb_req_fail(req, "failed", "Invalid event"); + return; + } + } + + afb_req_success(req, NULL, "Unsubscribe to events finished"); } diff --git a/src/ahl-binding.h b/src/ahl-binding.h index 2422963..508ca0a 100644 --- a/src/ahl-binding.h +++ b/src/ahl-binding.h @@ -29,45 +29,71 @@ #define PUBLIC #endif -/////////////// Binding private information ////////////////// +#define AHL_POLICY_ACCEPT 1 +#define AHL_POLICY_REJECT 0 -#define AUDIOHL_MAX_DEVICE_URI_LENGTH 256 -#define AUDIOHL_MAX_DEVICE_NAME_LENGTH 256 -#define AUDIOHL_MAX_AUDIOROLE_LENGTH 128 -#define AUDIOHL_MAX_HALAPINAME_LENGTH 64 -#define AUDIOHL_POLICY_ACCEPT 1 -#define AUDIOHL_POLICY_REJECT 0 +#define AHL_UNDEFINED -1 + +typedef int endpointID_t; +typedef int streamID_t; + +typedef enum EndpointSelectionMode { + AHL_ENDPOINTSELMODE_AUTO = 0, // Automatic endpoint selection based on config priority + AHL_ENDPOINTSELMODE_MANUAL, // Explicit endpoint selection + AHL_ENDPOINTSELMODEMAXVALUE, // Enum count, keep at the end +} EndpointSelectionModeT; + +typedef struct AudioFormat { + int sampleRate; // Sample rate + int numChannels; // Number of channels + SampleTypeT sampleType; // Sample type + // TODO: Interleaving? + // TODO: Sample sub format? +} AudioFormatT; + +typedef struct AlsaDeviceInfo { + int cardNum; // HW card number + int deviceNum; // HW device number + int subDeviceNum; // HW sub device number +} AlsaDeviceInfoT; typedef struct EndpointInfo { - endpointID_t endpointID; // Unique endpoint ID (per type) - EndpointTypeT type; // Source or sink device - char deviceName[AUDIOHL_MAX_DEVICE_NAME_LENGTH]; // Device name for applications to display - char deviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // Associated URI - DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information) - char audioRole[AUDIOHL_MAX_AUDIOROLE_LENGTH]; // Audio role that registered this endpoint -> private - char halAPIName[AUDIOHL_MAX_AUDIOROLE_LENGTH]; // HAL associated with the device (for volume control) -> private - int cardNum; // HW card number -> private - int deviceNum; // HW device number -> private - int subDeviceNum; // HW sub device number -> private - // Cached endpoint properties - GHashTable * pStatesHT; // Keep all known states in key value pairs + endpointID_t endpointID; // Unique endpoint ID (per type) + EndpointTypeT type; // Source or sink device + GString * gsDeviceName; // Device name for applications to display + GString * gsDeviceURI; // Associated URI + DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information) + GString * gsAudioRole; // Audio role that registered this endpoint + GString * gsHALAPIName; // HAL associated with the device (for volume control) + AlsaDeviceInfoT alsaInfo; // ALSA specific device information + AudioFormatT format; // Preferred audio format supported (later could be array of supported formats) + int iVolume; // Storage for current endpoint volume (policy effected). Target volume during ramping? + GHashTable * pPropTable; // Storage for array of properties (policy effected) } EndpointInfoT; typedef struct StreamInfo { - streamID_t streamID; - EndpointInfoT endpointInfo; + streamID_t streamID; // Stream unique ID + EndpointInfoT * pEndpointInfo; // Associated endpoint information + StreamStateT streamState; // Stream activity state + StreamMuteT streamMute; // Stream mute state + struct afb_event streamStateEvent; // Stream specific event for stream state changes + EndpointSelectionModeT endpointSelMode; // Automatic (priority based) or manual endpoint selection } StreamInfoT; // Parts of the context that are visible to the policy (for state based decisions) typedef struct AHLPolicyCtx { GPtrArray * pSourceEndpoints; // Array of source end points for each audio role (GArray*) GPtrArray * pSinkEndpoints; // Array of sink end points for each audio role (GArray*) - GArray * pRolePriority; // List of role priorities (int). TODO: Should be hash table with role name as key + GPtrArray * pEventList; // Event list per audio roles (GArray*) + GHashTable * pRolePriority; // List of role priorities (int). + GArray * pInterruptBehavior; // List of interrupt behavior per audio role (int/enum). GArray * pAudioRoles; // List of audio roles (GString) GArray * pActiveStreams; // List of active streams (StreamInfoT) - int iNumberRoles; // Number of audio roles from configuration - // TODO: Global properties -> exposed to policy + int iNumberRoles; // Number of audio roles from configuration + struct afb_event propertyEvent; // AGL event used when property changes + struct afb_event volumeEvent; // AGL event used when volume changes + struct afb_event postEvent; // AGL event used on post event call } AHLPolicyCtxT; // Global binding context @@ -77,23 +103,31 @@ typedef struct AHLCtx { endpointID_t nextSinkEndpointID; // Counter to assign new ID endpointID_t nextStreamID; // Counter to assign new ID GArray * pHALList; // List of HAL dependencies - GHashTable * pDefaultStatesHT; // List of states and default values known to configuration } AHLCtxT; +// ahl-binding.c PUBLIC int AhlBindingInit(); +PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ); + // ahl-deviceenum.c -PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName); -PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName); +int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName); +int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName); +void TermEndpoints(); // ahl-config.c -PUBLIC int ParseHLBConfig(); +int ParseHLBConfig(); // ahl-policy.c -PUBLIC int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID); -PUBLIC int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr, int rampTimeMS); -PUBLIC int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr, int rampTimeMS); -PUBLIC int Policy_SetState(EndpointTypeT endpointType, endpointID_t endpointID, char *pStateName, char *pStateValue); -PUBLIC int Policy_PostSoundEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext); -PUBLIC int Policy_AudioDeviceChange(); - -#define AUDIOHL_MAXHALCONTROLNAME_LENGTH 128 +int Policy_Endpoint_Property_Init(EndpointInfoT * io_pEndpointInfo); +int Policy_Init(); +void Policy_Term(); +int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID); +int Policy_CloseStream(streamID_t streamID); +int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ); +int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute); +int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr); +int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr); +int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext); +int Policy_AudioDeviceChange(); + + #endif // AHL_BINDING_INCLUDE diff --git a/src/ahl-config.c b/src/ahl-config.c index 432910f..dcf828f 100644 --- a/src/ahl-config.c +++ b/src/ahl-config.c @@ -23,15 +23,12 @@ #include "ahl-binding.h" -// TODO: Term() to free all allocs upon exit... - extern AHLCtxT g_AHLCtx; -PUBLIC int ParseHLBConfig() { +int ParseHLBConfig() { char * versionStr = NULL; json_object * jAudioRoles = NULL; json_object * jHALList = NULL; - json_object * jStateList = NULL; // TODO: This should be retrieve from binding startup arguments char configfile_path[256]; @@ -46,40 +43,27 @@ PUBLIC int ParseHLBConfig() { return 1; } - int err = wrap_json_unpack(config_JFile, "{s:s,s:o,s:o,s:o}", "version", &versionStr,"audio_roles",&jAudioRoles,"hal_list",&jHALList,"state_list",&jStateList); + int err = wrap_json_unpack(config_JFile, "{s:s,s:o,s:o}", "version", &versionStr,"audio_roles",&jAudioRoles,"hal_list",&jHALList); if (err) { AFB_ERROR("Invalid configuration file -> %s", configfile_path); return 1; } - // Parse and populate list of known states - g_AHLCtx.pDefaultStatesHT = g_hash_table_new(g_str_hash, g_str_equal); - int iStateListLength = json_object_array_length(jStateList); - for ( int i = 0; i < iStateListLength; i++) - { - char * pStateName = NULL; - char * pStateVal = NULL; - json_object * jStateobject = json_object_array_get_idx(jStateList,i); - err = wrap_json_unpack(jStateobject, "{s:s,s:s}", "name", &pStateName,"default_val",&pStateVal); - if (err) { - AFB_ERROR("Invalid state object -> %s", json_object_get_string(jStateobject)); - return 1; - } - - g_hash_table_insert(g_AHLCtx.pDefaultStatesHT, pStateName, pStateVal); - } - int iHALListLength = json_object_array_length(jHALList); int iNumberOfRoles = json_object_array_length(jAudioRoles); + g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles; + // Dynamically allocated based on number or roles found g_AHLCtx.pHALList = g_array_sized_new(FALSE, TRUE, sizeof(GString), iHALListLength); - g_AHLCtx.policyCtx.pRolePriority = g_array_sized_new(FALSE, TRUE, sizeof(int), iNumberOfRoles); + g_AHLCtx.policyCtx.pRolePriority = g_hash_table_new(g_str_hash, g_str_equal); g_AHLCtx.policyCtx.pAudioRoles = g_array_sized_new(FALSE, TRUE, sizeof(GString), iNumberOfRoles); + g_AHLCtx.policyCtx.pInterruptBehavior = g_array_sized_new(FALSE, TRUE, sizeof(int), iNumberOfRoles); g_AHLCtx.policyCtx.pSourceEndpoints = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.pSinkEndpoints = g_ptr_array_sized_new(iNumberOfRoles); + g_AHLCtx.policyCtx.pEventList = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles; - for (unsigned int i = 0; i < iHALListLength; i++) + for (int i = 0; i < iHALListLength; i++) { char * pHAL = NULL; json_object * jHAL = json_object_array_get_idx(jHALList,i); @@ -96,17 +80,27 @@ PUBLIC int ParseHLBConfig() { } } - for (unsigned int i = 0; i < iNumberOfRoles; i++) + for (int i = 0; i < iNumberOfRoles; i++) { int priority = 0; json_object * jAudioRole = json_object_array_get_idx(jAudioRoles,i); json_object * jOutputDevices = NULL; json_object * jInputDevices = NULL; + json_object * jEvents = NULL; char * pRoleName = NULL; + InterruptedBehaviorT interupBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; //Default + int iNumOutDevices = 0; int iNumInDevices = 0; - - err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s?o,s?o}", "name", &pRoleName,"priority",&priority,"output",&jOutputDevices,"input",&jInputDevices); + int iNumEvents = 0; + + err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s?o,s?o,s?o,s?i}", + "name", &pRoleName, + "priority",&priority, + "output",&jOutputDevices, + "input",&jInputDevices, + "events",&jEvents, + "interupt_behavior",&interupBehavior); if (err) { AFB_ERROR("Invalid audio role configuration : %s", json_object_to_json_string(jAudioRole)); return 1; @@ -116,10 +110,14 @@ PUBLIC int ParseHLBConfig() { iNumOutDevices = json_object_array_length(jOutputDevices); if (jInputDevices) iNumInDevices = json_object_array_length(jInputDevices); + if (jEvents) + iNumEvents = json_object_array_length(jEvents); GString * gRoleName = g_string_new( pRoleName ); g_array_append_val( g_AHLCtx.policyCtx.pAudioRoles, *gRoleName ); - g_array_append_val( g_AHLCtx.policyCtx.pRolePriority, priority ); + g_hash_table_insert(g_AHLCtx.policyCtx.pRolePriority, pRoleName, &priority); + + g_array_append_val(g_AHLCtx.policyCtx.pInterruptBehavior, interupBehavior); // Sources GArray * pRoleSourceDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); @@ -131,6 +129,7 @@ PUBLIC int ParseHLBConfig() { return 1; } } + // Sinks GArray * pRoleSinkDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); g_ptr_array_add(g_AHLCtx.policyCtx.pSinkEndpoints,pRoleSinkDeviceArray); if (iNumOutDevices) { @@ -140,6 +139,17 @@ PUBLIC int ParseHLBConfig() { return 1; } } + // Events + GArray * pEventsArray = g_array_new(FALSE, TRUE, sizeof(GString)); + g_ptr_array_add(g_AHLCtx.policyCtx.pEventList,pEventsArray); + // Parse and validate list of available events + for (int i = 0; i < iNumEvents; i++) + { + json_object * jEvent = json_object_array_get_idx(jEvents,i); + char * pEventName = (char *)json_object_get_string(jEvent); + GString * gsEventName = g_string_new(pEventName); + g_array_append_val(pEventsArray, *gsEventName); + } } // Build lists of all device URI referenced in config file (input/output) diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c index b866d52..c69108e 100644 --- a/src/ahl-deviceenum.c +++ b/src/ahl-deviceenum.c @@ -62,22 +62,22 @@ static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomai static int IsAlsaDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_ALSA) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_ALSA) == 0); } static int IsPulseDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_PULSE) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_PULSE) == 0); } static int IsGStreamerDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_GSTREAMER) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_GSTREAMER) == 0); } static int IsExternalDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_EXTERNAL) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_EXTERNAL) == 0); } static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndpointInfo ) @@ -130,11 +130,11 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp goto End; // Could potentially assign a "default" name and carry on with this device } - strncpy(out_pEndpointInfo->deviceName,pDeviceName,AUDIOHL_MAX_DEVICE_NAME_LENGTH); // Overwritten by HAL name if available + g_string_assign(out_pEndpointInfo->gsDeviceName,pDeviceName); // Overwritten by HAL name if available // get card number - out_pEndpointInfo->cardNum = snd_pcm_info_get_card(pPcmInfo); - if ( out_pEndpointInfo->cardNum < 0 ) + out_pEndpointInfo->alsaInfo.cardNum = snd_pcm_info_get_card(pPcmInfo); + if ( out_pEndpointInfo->alsaInfo.cardNum < 0 ) { AFB_WARNING("No Alsa card number available"); retVal = 1; @@ -142,8 +142,8 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp } // get device number - out_pEndpointInfo->deviceNum = snd_pcm_info_get_device(pPcmInfo); - if ( out_pEndpointInfo->deviceNum < 0 ) + out_pEndpointInfo->alsaInfo.deviceNum = snd_pcm_info_get_device(pPcmInfo); + if ( out_pEndpointInfo->alsaInfo.deviceNum < 0 ) { AFB_WARNING("No Alsa device number available"); retVal = 1; @@ -151,8 +151,8 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp } // get sub-device number - out_pEndpointInfo->subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo); - if ( out_pEndpointInfo->subDeviceNum < 0 ) + out_pEndpointInfo->alsaInfo.subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo); + if ( out_pEndpointInfo->alsaInfo.subDeviceNum < 0 ) { AFB_WARNING("No Alsa subdevice number available"); retVal = 1; @@ -199,9 +199,9 @@ static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) // Retrieve card number (e.g. hw:0) int iCardNum = atoi(pDevIDStr+3); - if (iCardNum == io_pEndpointInfo->cardNum) { - strncpy(io_pEndpointInfo->halAPIName,pAPIName,AUDIOHL_MAX_AUDIOROLE_LENGTH); - strncpy(io_pEndpointInfo->deviceName,pShortName,AUDIOHL_MAX_DEVICE_NAME_LENGTH); + if (iCardNum == io_pEndpointInfo->alsaInfo.cardNum) { + g_string_assign(io_pEndpointInfo->gsHALAPIName,pAPIName); + g_string_assign(io_pEndpointInfo->gsDeviceName,pShortName); found = 1; break; } @@ -209,20 +209,79 @@ static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) return !found; } -static int InitializeEndpointStates( EndpointInfoT * out_pEndpointInfo ) +static void InitEndpointInfo( EndpointInfoT * out_pEndpointInfo ) { - //out_pEndpointInfo = g_array_sized_new(FALSE,TRUE,sizeof) - // for list of known states - return 0; + out_pEndpointInfo->endpointID = AHL_UNDEFINED; + out_pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE; + out_pEndpointInfo->gsDeviceName = g_string_new("Unassigned"); + out_pEndpointInfo->gsDeviceURI = g_string_new("Unassigned"); + out_pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE; + out_pEndpointInfo->gsAudioRole = g_string_new("Unassigned"); + out_pEndpointInfo->gsHALAPIName = g_string_new("Unassigned"); + out_pEndpointInfo->alsaInfo.cardNum = AHL_UNDEFINED; + out_pEndpointInfo->alsaInfo.deviceNum = AHL_UNDEFINED; + out_pEndpointInfo->alsaInfo.subDeviceNum = AHL_UNDEFINED; + out_pEndpointInfo->format.sampleRate = AHL_UNDEFINED; + out_pEndpointInfo->format.numChannels = AHL_UNDEFINED; + out_pEndpointInfo->format.sampleType = AHL_FORMAT_UNKNOWN; + out_pEndpointInfo->pPropTable = g_hash_table_new(g_str_hash, g_str_equal); +} + +static void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo ) +{ + g_string_free(out_pEndpointInfo->gsDeviceName,TRUE); + g_string_free(out_pEndpointInfo->gsDeviceURI,TRUE); + g_string_free(out_pEndpointInfo->gsAudioRole,TRUE); + g_string_free(out_pEndpointInfo->gsHALAPIName,TRUE); + // TODO: Free json_object for all property values + g_hash_table_remove_all(out_pEndpointInfo->pPropTable); + g_hash_table_destroy(out_pEndpointInfo->pPropTable); +} + +void TermEndpoints() +{ + // Sources for each role + for (int i = 0; i < g_AHLCtx.policyCtx.pSourceEndpoints->len; i++) + { + // For each endpoint within the role + GArray * pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, i ); + for (int j = 0 ; j < pRoleDeviceArray->len; j++) + { + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + TermEndpointInfo(pEndpointInfo); + } + g_array_free(pRoleDeviceArray,TRUE); + pRoleDeviceArray = NULL; + } + g_ptr_array_free(g_AHLCtx.policyCtx.pSourceEndpoints,TRUE); + g_AHLCtx.policyCtx.pSourceEndpoints = NULL; + + // Sinks for each role + for (int i = 0; i < g_AHLCtx.policyCtx.pSinkEndpoints->len; i++) + { + // For each endpoint within the role + GArray * pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, i ); + for (int j = 0 ; j < pRoleDeviceArray->len; j++) + { + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + TermEndpointInfo(pEndpointInfo); + } + g_array_free(pRoleDeviceArray,TRUE); + pRoleDeviceArray = NULL; + } + g_ptr_array_free(g_AHLCtx.policyCtx.pSinkEndpoints,TRUE); + g_AHLCtx.policyCtx.pSinkEndpoints = NULL; } +#define AUDIOHL_MAX_DEVICE_URI_LENGTH 128 + // For a given audio role -PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName) { +int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName) { int iNumberDevices = json_object_array_length(in_jSourceArray); // Parse and validate list of available devices - for (unsigned int i = 0; i < iNumberDevices; i++) + for (int i = 0; i < iNumberDevices; i++) { char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive char * pDeviceURIDomain = NULL; @@ -242,13 +301,12 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI); continue; } + + InitEndpointInfo(&endpointInfo); // non ALSA URI are simply passed to application (no validation) at this time // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection - endpointInfo.cardNum = -1; - endpointInfo.deviceNum = -1; - endpointInfo.cardNum = -1; - strncpy(endpointInfo.deviceName,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_NAME_LENGTH); // Overwritten by HAL name if available + g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); // Overwritten by HAL name if available if (IsAlsaDomain(pDeviceURIDomain)) { @@ -275,10 +333,11 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch snd_pcm_close(pPcmHandle); + // TODO: Consider policy call to determine URI for set volume execution for a particular role (hw?) // Retrieve HAL API name err = RetrieveAssociatedHALAPIName(&endpointInfo); if (err) { - AFB_WARNING("SetVolume will fail without HAL association ->%s",endpointInfo.deviceURI); + AFB_WARNING("SetVolume will fail without HAL association ->%s",endpointInfo.gsDeviceURI->str); // Choose not to skip anyhow... } } @@ -302,16 +361,15 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch continue; } - err = InitializeEndpointStates( &endpointInfo ); - if (err) { - AFB_ERROR("Cannot initialize endpoint states for URI -> %s",fullDeviceURI); - continue; - } - - strncpy(endpointInfo.deviceURI,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_URI_LENGTH); - strncpy(endpointInfo.audioRole,in_pRoleName,AUDIOHL_MAX_AUDIOROLE_LENGTH); + g_string_assign(endpointInfo.gsDeviceURI,pDeviceURIPCM); + g_string_assign(endpointInfo.gsAudioRole,in_pRoleName); endpointInfo.endpointID = CreateNewSourceID(); endpointInfo.type = ENDPOINTTYPE_SOURCE; + err = Policy_Endpoint_Property_Init(&endpointInfo); + if (err) { + AFB_WARNING("Policy endpoint properties initalization failed for endpointid :%d type:%d",endpointInfo.endpointID, endpointInfo.type); + // Choose not to skip anyhow... + } // add to structure to list of available source devices GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, in_iRoleIndex ); @@ -324,12 +382,12 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch } // For a given audio role -PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName) { +int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName) { int iNumberDevices = json_object_array_length(in_jSinkArray); // Parse and validate list of available devices - for (unsigned int i = 0; i < iNumberDevices; i++) + for (int i = 0; i < iNumberDevices; i++) { char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive char * pDeviceURIDomain = NULL; @@ -353,10 +411,12 @@ PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * // non ALSA URI are simply passed to application (no validation) at this time // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection - endpointInfo.cardNum = -1; - endpointInfo.deviceNum = -1; - endpointInfo.cardNum = -1; - strncpy(endpointInfo.deviceName,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_NAME_LENGTH); + InitEndpointInfo(&endpointInfo); + + endpointInfo.alsaInfo.cardNum = -1; + endpointInfo.alsaInfo.deviceNum = -1; + endpointInfo.alsaInfo.cardNum = -1; + g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); if (IsAlsaDomain(pDeviceURIDomain)) { @@ -383,6 +443,7 @@ PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * snd_pcm_close(pPcmHandle); + // TODO: Consider policy call to determine URI for set volume execution for a particular role (hw?) // Retrieve HAL API name err = RetrieveAssociatedHALAPIName(&endpointInfo); if (err) { @@ -412,16 +473,15 @@ PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * continue; } - err = InitializeEndpointStates( &endpointInfo ); - if (err) { - AFB_ERROR("Cannot initialize endpoint states for URI -> %s",fullDeviceURI); - continue; - } - - strncpy(endpointInfo.deviceURI,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_URI_LENGTH); - strncpy(endpointInfo.audioRole,in_pRoleName,AUDIOHL_MAX_AUDIOROLE_LENGTH); + g_string_assign(endpointInfo.gsDeviceURI,pDeviceURIPCM); + g_string_assign(endpointInfo.gsAudioRole,in_pRoleName); endpointInfo.endpointID = CreateNewSinkID(); endpointInfo.type = ENDPOINTTYPE_SINK; + err = Policy_Endpoint_Property_Init(&endpointInfo); + if (err) { + AFB_WARNING("Policy endpoint properties initalization failed for endpointid :%d type:%d",endpointInfo.endpointID, endpointInfo.type); + // Choose not to skip anyhow... + } // add to structure to list of available source devices GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, in_iRoleIndex ); diff --git a/src/ahl-interface.h b/src/ahl-interface.h index 781bb05..edead36 100644 --- a/src/ahl-interface.h +++ b/src/ahl-interface.h @@ -18,28 +18,12 @@ #ifndef AHL_INTERFACE_INCLUDE #define AHL_INTERFACE_INCLUDE -#define UNDEFINED_ID -1 - -typedef int endpointID_t; -typedef int streamID_t; - typedef enum EndpointType { ENDPOINTTYPE_SOURCE = 0, // source devices ENDPOINTTYPE_SINK, // sink devices ENDPOINTTYPE_MAXVALUE // Enum count, keep at the end } EndpointTypeT; -// Standardized name for common audio roles (not enforced in any way, just helps system being more compatible) -#define AUDIOROLE_WARNING "warning" // Safety-relevant or critical alerts/alarms -#define AUDIOROLE_GUIDANCE "guidance" // Important user information where user action is expected (e.g. navigation instruction) -#define AUDIOROLE_NOTIFICATION "notification" // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...) -#define AUDIOROLE_COMMUNICATION "communications" // Voice communications (e.g. handsfree, speech recognition) -#define AUDIOROLE_ENTERTAINMENT "entertainment" // Multimedia content (e.g. tuner, media player, etc.) -#define AUDIOROLE_SYSTEM "system" // System level content or development -#define AUDIOROLE_STARTUP "startup" // Early (startup) sound -#define AUDIOROLE_SHUTDOWN "shutdown" // Late (shutdown) sound -#define AUDIOROLE_NONE "none" // Non-assigned / legacy applications - typedef enum DeviceURIType { DEVICEURITYPE_ALSA_HW = 0, // Alsa hardware device URI DEVICEURITYPE_ALSA_DMIX, // Alsa Dmix device URI (only for playback devices) @@ -52,23 +36,87 @@ typedef enum DeviceURIType { DEVICEURITYPE_MAXVALUE // Enum count, keep at the end } DeviceURITypeT; -// Standardized list of properties (string used for extensibility) -#define AUDIOHL_PROPERTY_BALANCE "balance" -#define AUDIOHL_PROPERTY_FADE "fade" -#define AUDIOHL_PROPERTY_EQ_LOW "eq_low" -#define AUDIOHL_PROPERTY_EQ_MID "eq_mid" -#define AUDIOHL_PROPERTY_EQ_HIGH "eq_high" +typedef enum StreamState { + STREAM_STATUS_READY = 0, // Stream is inactive + STREAM_STATUS_RUNNING, // Stream is running + STREAM_STATUS_MAXVALUE, // Enum count, keep at the end +} StreamStateT; + +typedef enum StreamMute { + STREAM_UNMUTED = 0, // Stream is not muted + STREAM_MUTED, // Stream is muted + STREAM_MUTE_MAXVALUE, // Enum count, keep at the end +} StreamMuteT; + +// Define default behavior of audio role when interrupted by higher priority sources +typedef enum InterruptedBehavior { + AHL_INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media may be ducked) + AHL_INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) + AHL_INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) + AHL_INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end +} InterruptedBehaviorT; + +#define AHL_ENDPOINT_PROPERTY_EVENT "ahl_endpoint_property_event" +#define AHL_ENDPOINT_VOLUME_EVENT "ahl_endpoint_volume_event" +#define AHL_POST_EVENT "ahl_post_event" + +// CPU endianness assumed in all formats +typedef enum SampleType { + AHL_FORMAT_UNKNOWN = -1, // Unknown + AHL_FORMAT_U8 = 0, // Unsigned 8 bit + AHL_FORMAT_S16, // Signed 16 bit Little Endian + AHL_FORMAT_S24, // Signed 24 bit Little Endian using low three bytes in 32-bit word + AHL_FORMAT_S32, // Signed 32 bit Little Endian + AHL_FORMAT_FLOAT, // Float 32 bit Little Endian, Range -1.0 to 1.0 + AHL_FORMAT_FLOAT64, // Float 64 bit Little Endian, Range -1.0 to 1.0 + AHL_FORMAT_IEC958, // IEC-958 Little Endian (SPDIF) + AHL_FORMAT_MU_LAW, // Mu-Law + AHL_FORMAT_A_LAW, // A-Law + AHL_FORMAT_IMA_ADPCM, // Ima-ADPCM + AHL_FORMAT_MPEG, // MPEG + AHL_FORMAT_GSM, // GSM + AHL_FORMAT_G723, // G723 + AHL_FORMAT_DSD, // Direct stream digital + AHL_FORMAT_MAXVALUE, // Enum count, keep at the end +} SampleTypeT; + +// Stream event types +typedef enum StreamEventType { + AHL_STREAMEVENT_START = 0, // Audio streaming should start + AHL_STREAMEVENT_STOP, // Audio streaming should stop + AHL_STREAMEVENT_UNMUTE, // Audio stream unmuted + AHL_STREAMEVENT_MUTE, // Audio stream muted + AHL_STREAMEVENT_MAXVALUE, // Enum count, keep at the end +} StreamEventTypeT; + +// Known audio domain string definitions (for configuration file format and device URI interpretation) +#define AHL_DOMAIN_ALSA "alsa" +#define AHL_DOMAIN_PULSE "pulse" +#define AHL_DOMAIN_GSTREAMER "gstreamer" +#define AHL_DOMAIN_EXTERNAL "external" + +// Standardized name for common audio roles (not enforced in any way, just helps compatibility) +#define AHL_ROLE_WARNING "warning" // Safety-relevant or critical alerts/alarms +#define AHL_ROLE_GUIDANCE "guidance" // Important user information where user action is expected (e.g. navigation instruction) +#define AHL_ROLE_NOTIFICATION "notification" // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...) +#define AHL_ROLE_COMMUNICATION "communications" // Voice communications (e.g. handsfree, speech recognition) +#define AHL_ROLE_ENTERTAINMENT "entertainment" // Multimedia content (e.g. tuner, media player, etc.) +#define AHL_ROLE_SYSTEM "system" // System level content or development +#define AHL_ROLE_STARTUP "startup" // Early (startup) sound +#define AHL_ROLE_SHUTDOWN "shutdown" // Late (shutdown) sound +#define AHL_ROLE_NONE "none" // Non-assigned / legacy applications + +// Standardized list of properties (not enforced in any way, just helps compatibility) +#define AHL_PROPERTY_BALANCE "balance" +#define AHL_PROPERTY_FADE "fade" +#define AHL_PROPERTY_EQ_LOW "eq_bass" +#define AHL_PROPERTY_EQ_MID "eq_mid" +#define AHL_PROPERTY_EQ_HIGH "eq_treble" -// Standardized list of state names/values (string used for extensibility) -#define AUDIOHL_STATE_NAME_ACTIVE "active" -#define AUDIOHL_STATE_NAME_MUTE "mute" -#define AUDIOHL_STATE_VALUE_ON "on" -#define AUDIOHL_STATE_VALUE_OFF "off" +// Standardized list of events +#define AHL_EVENTS_PLAYSOUND "play_sound" +#define AHL_EVENTS_ECHOCANCEL_ENABLE "echocancel_enable" +#define AHL_EVENTS_ECHOCANCEL_DISABLE "echocancel_disable" -// Known audio domain string definitions (for configuration file format) -#define AUDIOHL_DOMAIN_ALSA "Alsa" -#define AUDIOHL_DOMAIN_PULSE "Pulse" -#define AUDIOHL_DOMAIN_GSTREAMER "GStreamer" -#define AUDIOHL_DOMAIN_EXTERNAL "External" #endif // AHL_INTERFACE_INCLUDE diff --git a/src/ahl-policy.c b/src/ahl-policy.c index 7741afd..fa050af 100644 --- a/src/ahl-policy.c +++ b/src/ahl-policy.c @@ -21,70 +21,119 @@ #include "ahl-binding.h" - // This file provides example of custom, business logic driven policy actions that can affect behavior of the high level -// TODO: Currently only isolated in separate source file. Objective is to make this to at least a shared lib plug-in -// Going a step further would be to implement this within afb-controler policy plug-in (would require bi-directional access to HLB context) +// TODO: Currently only isolated in separate source file. Objective is to make this to at least a shared lib plug-in (shared C context) +// Going a step further would be to implement this within a distinct policy binding (requires to switch to JSON interface) extern AHLCtxT g_AHLCtx; // TODO: Cannot stay if moved to external module -// Attribute of high-level binding (parsed), policy enforced -typedef enum InterruptedBehavior { - INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media will be ducked) - INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) - INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) - INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end -} InterruptedBehaviorT; - -PUBLIC int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID) +static void Add_Endpoint_Property_Numeric( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) { - return 1; // Policy allowed + json_object * propValueJ = json_object_new_int(in_iPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -PUBLIC int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr, int rampTimeMS) +static void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, char * in_pPropertyValue) { - return 1; // Policy allowed + json_object * propValueJ = json_object_new_string(in_pPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -PUBLIC int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr, int rampTimeMS) +int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID) { - return 1; // Policy allowed + // TODO: Example rule -> when system is in shutdown or low power mode, no audio stream can be opened (return AHL_POLICY_REJECT) + + return AHL_POLICY_ACCEPT; } +int Policy_CloseStream(streamID_t streamID) +{ + // For completeness, unlikely to have any policy rules here -PUBLIC int Policy_SetState(EndpointTypeT endpointType, endpointID_t endpointID, char *pStateName, char *pStateValue) + return AHL_POLICY_ACCEPT; +} + +int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ) { + // If higher priority audio role stream requires audio ducking (and un-ducking) of other streams (e.g. navigation ducks entertainment) + // TODO: Could potentially provide a fairly generic system using interupt behavior information and audio role priority (implemented and can be customized here) + // Specific exception can also be + + // Source exclusion. E.g. When a source (e.g tuner) with same audio role as already active stream (e.g. media player) with same endpoint target, + // the former source is stopped (i.e. raise streamstate stop event) + + // If source on communication role is active (e.g. handsfree call), activating entertainment sources is prohibited + + // If handsfree or speech recognition (communication role) is started during entertainment playback, mute all entertainment streams (any endpoints except RSE) + + // Startup or Shutdown audio stream mute entertainment (unmut when stream no longer active) + + // Optional: Mute endpoint before activation, unmute afterwards (after a delay?) to avoid noises + + + return AHL_POLICY_ACCEPT; +} +int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute) +{ + return AHL_POLICY_ACCEPT; +} - //Mute - if(strcmp(pStateName, AUDIOHL_STATE_NAME_MUTE) == 0) - { - if(strcmp(pStateName, AUDIOHL_STATE_NAME_MUTE) == 0) +int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr) +{ + return AHL_POLICY_ACCEPT; +} +int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr) +{ + return AHL_POLICY_ACCEPT; +} - return AUDIOHL_POLICY_ACCEPT; - } +int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext) +{ + // TODO: Any event with media specified should trigger action on provided rendering services (e.g. Wwise binding, gstreamer file player wrapper, MPDC? simple ALSA player (aplay)?) - //Retrieve global context + // Example (when the policy is hooked to CAN events). Post audio playback events other than safety during reverse gear engaged declined + // Example post HMI audio role playback events declined when higher priority streams are active - //Active rule check - - //Ducking rule settings + return AHL_POLICY_ACCEPT; +} +int Policy_AudioDeviceChange() +{ + // Allow or disallow a new audio endpoint to be used by the system + // TODO: Policy assigns audio role(s) for device (or default) + // TODO: Raise events to engage device switching if active stream in audio role assigned to the new endpoint + + return AHL_POLICY_ACCEPT; +} - return AUDIOHL_POLICY_ACCEPT; +int Policy_Endpoint_Property_Init(EndpointInfoT * io_EndpointInfo) +{ + // Populate list of supported properties for specified endpoint (use helper functions) + // Setup initial values for all properties + + // TODO: Switch on different known endpoints to populate different properties + + // Test example + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_LOW,3); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_MID,0); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_BALANCE,0); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_FADE,30); + Add_Endpoint_Property_String(io_EndpointInfo,"preset_name","flat"); + + return 0; // No errors } -PUBLIC int Policy_PostSoundEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext) +int Policy_Init() { - return 1; // Policy allowed + // Other policy specific initialization + return 0; // No errors } -PUBLIC int Policy_AudioDeviceChange() +void Policy_Term() { - // Allow or disallow a new audio device - // Raise events to engage device switching - // Policy can also assign audio role(s) for device - return 1; // Policy allowed -} \ No newline at end of file + // Policy termination +} -- cgit 1.2.3-korg From 9b7e1d0361d1a5eee415e453ae79925084552c68 Mon Sep 17 00:00:00 2001 From: Tai Vuong Date: Mon, 16 Oct 2017 10:09:38 -0400 Subject: add policy and reference implementation --- README.md | 1 - htdocs/CMakeLists.txt | 2 +- htdocs/assets/content-background-car-wide.png | Bin 0 -> 1215422 bytes htdocs/assets/content-background-car.png | Bin 0 -> 244208 bytes htdocs/assets/content-background.png | Bin 0 -> 210766 bytes htdocs/assets/emergency.png | Bin 0 -> 20039 bytes htdocs/assets/favicon.ico | Bin 0 -> 1150 bytes htdocs/assets/iot-bzh-logo-small.png | Bin 0 -> 14449 bytes htdocs/assets/messages.png | Bin 0 -> 14597 bytes htdocs/assets/music-pause.png | Bin 0 -> 100761 bytes htdocs/assets/music-play.png | Bin 0 -> 110798 bytes htdocs/assets/phone-call-emit1.png | Bin 0 -> 46021 bytes htdocs/assets/phone-call-emit2.png | Bin 0 -> 56311 bytes htdocs/assets/phone-call-emit3.png | Bin 0 -> 70218 bytes htdocs/assets/phone-call.png | Bin 0 -> 37089 bytes htdocs/audiobackend.html | 35 + htdocs/audiohl.html | 108 +-- htdocs/index.html | 7 +- src/ahl-binding.c | 400 +++++---- src/ahl-binding.h | 42 +- src/ahl-config.c | 34 +- src/ahl-deviceenum.c | 81 +- src/ahl-interface.h | 42 +- src/ahl-policy.c | 1118 ++++++++++++++++++++++++- 24 files changed, 1506 insertions(+), 364 deletions(-) create mode 100644 htdocs/assets/content-background-car-wide.png create mode 100644 htdocs/assets/content-background-car.png create mode 100644 htdocs/assets/content-background.png create mode 100644 htdocs/assets/emergency.png create mode 100644 htdocs/assets/favicon.ico create mode 100644 htdocs/assets/iot-bzh-logo-small.png create mode 100644 htdocs/assets/messages.png create mode 100644 htdocs/assets/music-pause.png create mode 100644 htdocs/assets/music-play.png create mode 100644 htdocs/assets/phone-call-emit1.png create mode 100644 htdocs/assets/phone-call-emit2.png create mode 100644 htdocs/assets/phone-call-emit3.png create mode 100644 htdocs/assets/phone-call.png create mode 100644 htdocs/audiobackend.html diff --git a/README.md b/README.md index 4c09ecc..046c2e7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ ``` # Initial clone with submodules git clone https://github.com/Audiokinetic-Automotive/afb-audiohighlevel.git -cd afb-audiohighlevel ``` diff --git a/htdocs/CMakeLists.txt b/htdocs/CMakeLists.txt index 2e51461..c08a493 100644 --- a/htdocs/CMakeLists.txt +++ b/htdocs/CMakeLists.txt @@ -23,7 +23,7 @@ ################################################## PROJECT_TARGET_ADD(htdocs) - file(GLOB SOURCE_FILES "*.html" "*.js" "*.jpg" "*.css") + file(GLOB SOURCE_FILES LIST_DIRECTORIES true "*.html" "*.js" "*.jpg" "*.css") add_input_files("${SOURCE_FILES}") diff --git a/htdocs/assets/content-background-car-wide.png b/htdocs/assets/content-background-car-wide.png new file mode 100644 index 0000000..073d1b9 Binary files /dev/null and b/htdocs/assets/content-background-car-wide.png differ diff --git a/htdocs/assets/content-background-car.png b/htdocs/assets/content-background-car.png new file mode 100644 index 0000000..4bcde72 Binary files /dev/null and b/htdocs/assets/content-background-car.png differ diff --git a/htdocs/assets/content-background.png b/htdocs/assets/content-background.png new file mode 100644 index 0000000..9be581c Binary files /dev/null and b/htdocs/assets/content-background.png differ diff --git a/htdocs/assets/emergency.png b/htdocs/assets/emergency.png new file mode 100644 index 0000000..c959b04 Binary files /dev/null and b/htdocs/assets/emergency.png differ diff --git a/htdocs/assets/favicon.ico b/htdocs/assets/favicon.ico new file mode 100644 index 0000000..eeb7ab7 Binary files /dev/null and b/htdocs/assets/favicon.ico differ diff --git a/htdocs/assets/iot-bzh-logo-small.png b/htdocs/assets/iot-bzh-logo-small.png new file mode 100644 index 0000000..2c3b2ae Binary files /dev/null and b/htdocs/assets/iot-bzh-logo-small.png differ diff --git a/htdocs/assets/messages.png b/htdocs/assets/messages.png new file mode 100644 index 0000000..4919452 Binary files /dev/null and b/htdocs/assets/messages.png differ diff --git a/htdocs/assets/music-pause.png b/htdocs/assets/music-pause.png new file mode 100644 index 0000000..790a6c4 Binary files /dev/null and b/htdocs/assets/music-pause.png differ diff --git a/htdocs/assets/music-play.png b/htdocs/assets/music-play.png new file mode 100644 index 0000000..37c18e3 Binary files /dev/null and b/htdocs/assets/music-play.png differ diff --git a/htdocs/assets/phone-call-emit1.png b/htdocs/assets/phone-call-emit1.png new file mode 100644 index 0000000..a8d863b Binary files /dev/null and b/htdocs/assets/phone-call-emit1.png differ diff --git a/htdocs/assets/phone-call-emit2.png b/htdocs/assets/phone-call-emit2.png new file mode 100644 index 0000000..14c8cbe Binary files /dev/null and b/htdocs/assets/phone-call-emit2.png differ diff --git a/htdocs/assets/phone-call-emit3.png b/htdocs/assets/phone-call-emit3.png new file mode 100644 index 0000000..5283a40 Binary files /dev/null and b/htdocs/assets/phone-call-emit3.png differ diff --git a/htdocs/assets/phone-call.png b/htdocs/assets/phone-call.png new file mode 100644 index 0000000..40007d2 Binary files /dev/null and b/htdocs/assets/phone-call.png differ diff --git a/htdocs/audiobackend.html b/htdocs/audiobackend.html new file mode 100644 index 0000000..a7c8f41 --- /dev/null +++ b/htdocs/audiobackend.html @@ -0,0 +1,35 @@ + + + + Audio High Level Test + + + + + + + +

    +
    +
      +
    1. +
    2. +
    3. +
    4. +
    5. +
    6. +
    7. +
    8. + + + +
      +
    + + diff --git a/htdocs/audiohl.html b/htdocs/audiohl.html index 41af265..ec4dd43 100644 --- a/htdocs/audiohl.html +++ b/htdocs/audiohl.html @@ -7,103 +7,47 @@ - + - +

    + Audio Role + + + + + + + + +
    Endpoint Type - Endpoint ID - - Stream ID - + +
    + +
    + +
    Property - Volume/Property Value - - + +
    + +
    Stream Active/Mute State - +

      @@ -119,7 +63,7 @@
    1. -
    2. +
    3. diff --git a/htdocs/index.html b/htdocs/index.html index 3c43f9b..532cfce 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -1,11 +1,10 @@ - AGL-AudioBindins tests + AGL Audio Agent -

      audio-bindings test

      +

      AAA binding tests

      1. AlsaCore Low Level Binding
      2. AlsaHAL Hardware Abstraction Layer -
      3. AudioControl Control/Policy API
      4. High Level Audio API -
      5. High Level Audio API Demo +
      6. Audio Backend Test diff --git a/src/ahl-binding.c b/src/ahl-binding.c index 3f8d71d..fe3dcee 100644 --- a/src/ahl-binding.c +++ b/src/ahl-binding.c @@ -24,11 +24,7 @@ #include "wrap-json.h" // Global high-level binding context -AHLCtxT g_AHLCtx; - -//afb_req_context_set ?? -// usr = afb_req_context_get(req); -// afb_req_context_clear(req); +AHLCtxT g_AHLCtx; static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * pAudioFormat) { @@ -42,10 +38,11 @@ static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * p static void EndpointInfoStructToJSON(json_object **endpointInfoJ, EndpointInfoT * pEndpointInfo) { json_object *formatInfoJ = NULL; - wrap_json_pack(endpointInfoJ, "{s:i,s:i,s:s,s:i}", + wrap_json_pack(endpointInfoJ, "{s:i,s:i,s:s,s:s,s:i}", "endpoint_id", pEndpointInfo->endpointID, "endpoint_type", pEndpointInfo->type, "device_name", pEndpointInfo->gsDeviceName->str, + "display_name", pEndpointInfo->gsDisplayName->str, "device_uri_type", pEndpointInfo->deviceURIType); AudioFormatStructToJSON(&formatInfoJ,&pEndpointInfo->format); json_object_object_add(*endpointInfoJ,"format",formatInfoJ); @@ -63,6 +60,27 @@ static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT * pStr json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); } +static streamID_t CreateNewStreamID() +{ + streamID_t newID = g_AHLCtx.nextStreamID; + g_AHLCtx.nextStreamID++; + return newID; +} + +static int FindRoleIndex( const char * in_pAudioRole) +{ + int index = -1; // Not found + for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) + { + GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); + if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) + { + index = i; + break; + } + } + return index; +} static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT in_endpointType) { @@ -101,6 +119,46 @@ static StreamInfoT * GetActiveStream(streamID_t in_streamID) return pStreamInfo; } +static AHLClientCtxT * AllocateClientContext() +{ + AHLClientCtxT * pClientCtx = malloc(sizeof(AHLClientCtxT)); + pClientCtx->pEndpointAccessList = g_array_new(FALSE, TRUE, sizeof(endpointID_t)); + pClientCtx->pStreamAccessList = g_array_new(FALSE, TRUE, sizeof(streamID_t)); + return pClientCtx; +} + +static void TerminateClientContext(void * ptr) +{ + AHLClientCtxT * pClientCtx = (AHLClientCtxT *) ptr; + g_array_free( pClientCtx->pEndpointAccessList, TRUE); + g_array_free( pClientCtx->pStreamAccessList, TRUE); + free(ptr); +} + +static int CheckStreamAccessControl(AHLClientCtxT * pClientCtx, streamID_t streamID) +{ + int iAccessControl = AHL_ACCESS_CONTROL_DENIED; + for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { + streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); + if (iID == streamID) { + iAccessControl = AHL_ACCESS_CONTROL_GRANTED; + } + } + return iAccessControl; +} + +static int CheckEndpointAccessControl(AHLClientCtxT * pClientCtx, endpointID_t endpointID) +{ + int iAccessControl = AHL_ACCESS_CONTROL_DENIED; + for (int i = 0; i < pClientCtx->pEndpointAccessList->len ; i++) { + endpointID_t iID = g_array_index(pClientCtx->pEndpointAccessList,endpointID_t,i); + if (iID == endpointID) { + iAccessControl = AHL_ACCESS_CONTROL_GRANTED; + } + } + return iAccessControl; +} + static int CreateEvents() { int err = 0; @@ -155,56 +213,29 @@ static void AhlBindingTerm() // TODO: Need to free g_strings in HAL list g_array_free(g_AHLCtx.pHALList,TRUE); + g_AHLCtx.pHALList = NULL; g_hash_table_remove_all(g_AHLCtx.policyCtx.pRolePriority); g_hash_table_destroy(g_AHLCtx.policyCtx.pRolePriority); + g_AHLCtx.policyCtx.pRolePriority = NULL; // TODO: Need to free g_strings in audio roles list g_array_free(g_AHLCtx.policyCtx.pAudioRoles,TRUE); + g_AHLCtx.policyCtx.pAudioRoles = NULL; g_array_free(g_AHLCtx.policyCtx.pInterruptBehavior,TRUE); + g_AHLCtx.policyCtx.pInterruptBehavior = NULL; g_array_free(g_AHLCtx.policyCtx.pActiveStreams,TRUE); + g_AHLCtx.policyCtx.pActiveStreams = NULL; AFB_INFO("Audio high-level Binding succesTermination"); } -static streamID_t CreateNewStreamID() -{ - streamID_t newID = g_AHLCtx.nextStreamID; - g_AHLCtx.nextStreamID++; - return newID; -} - -static int FindRoleIndex( const char * in_pAudioRole) -{ - int index = -1; // Not found - for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) - { - GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); - if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) - index = i; - } - return index; -} - - // Binding initialization PUBLIC int AhlBindingInit() { int errcount = 0; - int err = 0; - - // This API uses services from low level audio - err = afb_daemon_require_api_v2("alsacore",1) ; - if( err != 0 ) - { - AFB_ERROR("Audio high level API requires alsacore API to be available"); - return 1; - } - - // Parse high-level binding JSON configuration file (will build device lists) - errcount += ParseHLBConfig(); atexit(AhlBindingTerm); - // This API uses services from low level audio + // This API uses services from low level audio. TODO: This dependency can be removed. errcount = afb_daemon_require_api_v2("alsacore",1) ; if( errcount != 0 ) { @@ -221,11 +252,10 @@ PUBLIC int AhlBindingInit() errcount += Policy_Init(); // Initialize list of active streams + g_AHLCtx.iNumActiveClients = 0; g_AHLCtx.policyCtx.pActiveStreams = g_array_new(FALSE,TRUE,sizeof(StreamInfoT)); - // TODO: Register for device changes ALSA low level audio service or HAL - - // TODO: Use AGL persistence framework to retrieve and set inital state/volumes/properties + // TODO: Use AGL persistence framework to retrieve and set initial volumes/properties AFB_DEBUG("Audio high-level Binding success errcount=%d", errcount); return errcount; @@ -233,23 +263,19 @@ PUBLIC int AhlBindingInit() PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ) { - // TODO: Implement handling events from HAL... AFB_DEBUG("AHL received event %s", evtname); + + //forward to policy to handle events + Policy_OnEvent(evtname, eventJ); } -// TODO: OnEventFunction when it actually subscribe to other binding events -// TODO: Dynamic device handling -// if HAL device availability change -> update source / sink list -// Call policy to attempt to assign role based on information (e.g. device name) -// Policy should also determine priority (insert device in right spot in the list) - PUBLIC void audiohlapi_get_sources(struct afb_req req) { json_object *sourcesJ = NULL; json_object *sourceJ = NULL; json_object *queryJ = NULL; char * audioRole = NULL; - + queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:s}", "audio_role", &audioRole); if (err) { @@ -338,6 +364,15 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) } AFB_DEBUG("Parsed input arguments = audio_role:%s endpoint_type:%d endpoint_id:%d", audioRole,endpointType,endpointID); + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + g_AHLCtx.iNumActiveClients++; + pClientCtx = AllocateClientContext(); + afb_req_context_set(req, pClientCtx, TerminateClientContext); + } + int iRoleIndex = FindRoleIndex(audioRole); if (iRoleIndex < 0) { afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole); @@ -379,23 +414,23 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) } } + // Create stream + streamInfo.streamID = CreateNewStreamID(); // create new ID + streamInfo.streamState = STREAM_STATE_IDLE; + streamInfo.streamMute = STREAM_UNMUTED; + streamInfo.pEndpointInfo = pEndpointInfo; + // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions - policyAllowed = Policy_OpenStream(audioRole, endpointType, pEndpointInfo->endpointID); + policyAllowed = Policy_OpenStream(&streamInfo); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Open stream not allowed in current context"); return; - } - - // Create stream - streamInfo.streamID = CreateNewStreamID(); // create new ID - streamInfo.streamState = STREAM_STATUS_READY; - streamInfo.streamMute = STREAM_UNMUTED; - streamInfo.pEndpointInfo = pEndpointInfo; + } char streamEventName[128]; snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); - + streamInfo.streamStateEvent = afb_daemon_make_event(streamEventName); err = !afb_event_is_valid(streamInfo.streamStateEvent); if (err) { @@ -409,6 +444,10 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) return; } + // Add to client context stream ID and endpoint ID access rights + g_array_append_val(pClientCtx->pStreamAccessList, streamInfo.streamID); + g_array_append_val(pClientCtx->pEndpointAccessList, streamInfo.pEndpointInfo->endpointID); + // Push stream on active stream list g_array_append_val( g_AHLCtx.policyCtx.pActiveStreams, streamInfo ); @@ -431,10 +470,30 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) } AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); - // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + return; + } + + // Verify that this client can control the stream + int iStreamAccessControl = CheckStreamAccessControl( pClientCtx, streamID ); + if (iStreamAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Close stream not allowed in current client context"); + return; + } // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions - policyAllowed = Policy_CloseStream(streamID); + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + policyAllowed = Policy_CloseStream(pStreamInfo); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Close stream not allowed in current context"); @@ -454,7 +513,7 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) if (iValid) { err = afb_req_unsubscribe(req,streamInfo.streamStateEvent); if (err) { - afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event"); + afb_req_fail(req, "Stream event subscription failure", "Could not unsubscribe to stream specific state change event"); return; } } @@ -474,6 +533,22 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } + // Find index for cases where there are multiple streams per client + // Remove from client context stream ID and endpoint ID access rights + for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { + streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); + if (iID == streamID) { + g_array_remove_index(pClientCtx->pStreamAccessList, i); + g_array_remove_index(pClientCtx->pEndpointAccessList, i); + } + } + + if (pClientCtx->pStreamAccessList->len == 0 && pClientCtx->pEndpointAccessList == 0) { + // If no more streams/endpoints owner, clear session + afb_req_context_clear(req); + g_AHLCtx.iNumActiveClients--; + } + afb_req_success(req, NULL, "Stream close completed"); } @@ -498,22 +573,29 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } - policyAllowed = Policy_SetStreamState(streamID,streamState); - if (policyAllowed == AHL_POLICY_REJECT) + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) { - afb_req_fail(req, "Audio policy violation", "Change stream state not allowed in current context"); + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); return; } - pStreamInfo->streamState = streamState; - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",pStreamInfo->streamMute,"state",streamState); - if (err) { - afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + // Verify that this client can control the stream + int iStreamAccessControl = CheckStreamAccessControl( pClientCtx, streamID ); + if (iStreamAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set stream state not allowed in current client context"); return; } - afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); + int AudioRoleIndex = FindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); + + policyAllowed = Policy_SetStreamState(pStreamInfo, AudioRoleIndex, streamState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Change stream state not allowed in current context"); + return; + } afb_req_success(req, NULL, "Set stream state"); } @@ -539,28 +621,33 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } - policyAllowed = Policy_SetStreamMute(streamID,muteState); - if (policyAllowed == AHL_POLICY_REJECT) + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) { - afb_req_fail(req, "Audio policy violation", "Mute stream not allowed in current context"); + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); return; } - pStreamInfo->streamMute = muteState; + // Verify that this client can control the stream + int iStreamAccessControl = CheckStreamAccessControl( pClientCtx, streamID ); + if (iStreamAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set stream mute state not allowed in current client context"); + return; + } - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",muteState,"state",pStreamInfo->streamState); - if (err) { - afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + policyAllowed = Policy_SetStreamMute(pStreamInfo,muteState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Mute stream not allowed in current context"); return; } - afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); afb_req_success(req, NULL, "Set stream mute completed"); } -PUBLIC void audiohlapi_get_stream_info(struct afb_req req) + PUBLIC void audiohlapi_get_stream_info(struct afb_req req) { json_object *queryJ = NULL; streamID_t streamID = AHL_UNDEFINED; @@ -601,18 +688,6 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s", endpointType,endpointID,volumeStr); - // TODO: Parse volume string to support increment/absolute/percent notation (or delegate to action / policy layer to interpret) - int vol = atoi(volumeStr); - - // TODO: Policy needs way to set cached endpoint volume value (eg.) - policyAllowed = Policy_SetVolume(endpointType, endpointID, volumeStr); // TODO: Potentially retrieve modified value by policy (e.g. volume limit) - if (!policyAllowed) - { - afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context"); - return; - } - - // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -620,37 +695,28 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) return; } - // Using audio role available from endpoint to target the right HAL control (build string based on convention) - GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); - g_string_append(gsHALControlName,"_Ramp"); // Or _Vol for direct control (no ramping) - - // Set endpoint volume using HAL services (leveraging ramps etc.) - json_object *j_response, *j_query = NULL; - - // Package query - err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",vol); - if (err) { - afb_req_fail_f(req, "Invalid query for HAL ctlset", "Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); return; } - // TODO: Move this to ref implmentation policy - // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); - if (err) { - afb_req_fail_f(req, "HAL ctlset failure", "Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + // Verify that this client can control the stream + int iEndpointAccessControl = CheckEndpointAccessControl( pClientCtx, endpointID ); + if (iEndpointAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set volume not allowed in current client context"); return; } - AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id",endpointID,"endpoint_type",endpointType,"value",vol); - if (err) { - afb_req_fail_f(req, "Invalid event data for volume event", "Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + policyAllowed = Policy_SetVolume(pEndpointInfo, volumeStr); + if (!policyAllowed) + { + afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context"); return; } - afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); afb_req_success(req, NULL, "Set volume completed"); } @@ -670,7 +736,6 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); - // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -678,43 +743,7 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) return; } - // Using audio role available from endpoint to target the right HAL control (build string based on convention) - GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); - g_string_append(gsHALControlName,"_Vol"); // Use current value, not ramp target - - // Set endpoint volume using HAL services (leveraging ramps etc.) - json_object *j_response, *j_query = NULL; - - // TODO: Returned cached endpoint volume value (controlled by policy) - // Package query - err = wrap_json_pack(&j_query,"{s:s}","label",gsHALControlName->str); - if (err) { - afb_req_fail_f(req, "Invalid query for HAL ctlget", "Invalid query for HAL ctlget: %s",json_object_to_json_string(j_query)); - return; - } - - // TODO: Return cached value or move to policy - // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlget", j_query, &j_response); - if (err) { - afb_req_fail_f(req, "HAL ctlget failure", "Could not ctlget on HAL: %s",pEndpointInfo->gsHALAPIName->str); - return; - } - AFB_INFO("HAL ctlget response=%s", json_object_to_json_string(j_response)); - - // Parse response - json_object * jRespObj = NULL; - json_object_object_get_ex(j_response, "response", &jRespObj); - json_object * jVal = NULL; - json_object_object_get_ex(jRespObj, "val", &jVal); - int val1 = 0, val2 = 0; // Why 2 values? - err = wrap_json_unpack(jVal, "[ii]", &val1, &val2); - if (err) { - afb_req_fail_f(req,"Volume retrieve failed", "Could not retrieve volume value -> %s", json_object_get_string(jVal)); - return; - } - - volumeJ = json_object_new_double((double)val1); + volumeJ = json_object_new_double((double)pEndpointInfo->iVolume); afb_req_success(req, volumeJ, "Retrieved volume value"); } @@ -763,32 +792,49 @@ PUBLIC void audiohlapi_set_property(struct afb_req req) endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; - char * propValueStr = NULL; + json_object * propValueJ = NULL; int policyAllowed = AHL_POLICY_REJECT; - - // TODO: object type detection (string = state, numeric = property) queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:o}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueJ); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s", endpointType,endpointID,propertyName,propValueStr); - - // TODO: Parse property value string to support increment/absolute/percent notation + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); + + + EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); + if (pEndpointInfo == NULL) + { + afb_req_fail_f(req, "Endpoint not found", "Endpoint information not found for id:%d type%d",endpointID,endpointType); + return; + } + + // Check if there is already an existing context for this client + AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure + if (pClientCtx == NULL) + { + afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + return; + } + + // Verify that this client can control the stream + int iEndpointAccessControl = CheckEndpointAccessControl( pClientCtx, endpointID ); + if (iEndpointAccessControl == AHL_ACCESS_CONTROL_DENIED) + { + afb_req_fail(req, "Access control denied", "Set property not allowed in current client context"); + return; + } // Call policy to allow custom policy actions in current context - policyAllowed = Policy_SetProperty(endpointType, endpointID, propertyName, propValueStr); // TODO: Potentially retrieve modified value by policy (e.g. parameter limit) + policyAllowed = Policy_SetProperty(pEndpointInfo, propertyName, propValueJ); if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Set endpoint property not allowed in current context"); return; } - // TODO: Policy to dispatch on right service target - // TODO: Policy tp cache value in property list - afb_event_push(g_AHLCtx.policyCtx.propertyEvent,queryJ); afb_req_success(req, NULL, "Set property completed"); @@ -800,8 +846,6 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; - json_object *propertyValJ; - double value = 0.0; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); @@ -811,11 +855,21 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); - // TODO: Retriev189e cached property value - // TODO Account for properties with string types + EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); + if (pEndpointInfo == NULL) + { + afb_req_fail_f(req, "Endpoint not found", "Endpoint information not found for id:%d type%d",endpointID,endpointType); + return; + } + + // Retrieve cached property value + json_object * propertyValJ = (json_object *)g_hash_table_lookup(pEndpointInfo->pPropTable,propertyName); + if (propertyValJ == NULL) { + afb_req_fail_f(req, "Property not found", "Property information not found: %s",propertyName); + return; + } - value = 93.0; // TODO: Get actual property value - propertyValJ = json_object_new_double(value); + json_object_get(propertyValJ); // Increase ref count so that framework does not free our JSON object afb_req_success(req, propertyValJ, "Retrieved property value"); } diff --git a/src/ahl-binding.h b/src/ahl-binding.h index 508ca0a..8f308d5 100644 --- a/src/ahl-binding.h +++ b/src/ahl-binding.h @@ -31,12 +31,22 @@ #define AHL_POLICY_ACCEPT 1 #define AHL_POLICY_REJECT 0 +#define AHL_ACCESS_CONTROL_GRANTED 1 +#define AHL_ACCESS_CONTROL_DENIED 0 #define AHL_UNDEFINED -1 typedef int endpointID_t; typedef int streamID_t; +// Define default behavior of audio role when interrupted by higher priority sources +typedef enum InterruptedBehavior { + AHL_INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media may be ducked) + AHL_INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) + AHL_INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) + AHL_INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end +} InterruptedBehaviorT; + typedef enum EndpointSelectionMode { AHL_ENDPOINTSELMODE_AUTO = 0, // Automatic endpoint selection based on config priority AHL_ENDPOINTSELMODE_MANUAL, // Explicit endpoint selection @@ -61,7 +71,8 @@ typedef struct EndpointInfo { endpointID_t endpointID; // Unique endpoint ID (per type) EndpointTypeT type; // Source or sink device - GString * gsDeviceName; // Device name for applications to display + GString * gsDeviceName; // Unique device card name + GString * gsDisplayName; // Application display name GString * gsDeviceURI; // Associated URI DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information) GString * gsAudioRole; // Audio role that registered this endpoint @@ -75,7 +86,7 @@ typedef struct EndpointInfo typedef struct StreamInfo { streamID_t streamID; // Stream unique ID EndpointInfoT * pEndpointInfo; // Associated endpoint information - StreamStateT streamState; // Stream activity state + StreamStateT streamState; // Stream activity state StreamMuteT streamMute; // Stream mute state struct afb_event streamStateEvent; // Stream specific event for stream state changes EndpointSelectionModeT endpointSelMode; // Automatic (priority based) or manual endpoint selection @@ -103,8 +114,15 @@ typedef struct AHLCtx { endpointID_t nextSinkEndpointID; // Counter to assign new ID endpointID_t nextStreamID; // Counter to assign new ID GArray * pHALList; // List of HAL dependencies + int iNumActiveClients; // Number of clients with active stream(s) } AHLCtxT; +// Client specific binding context +typedef struct AHLClientCtx { + GArray * pEndpointAccessList; // List of endpoints that client has control over + GArray * pStreamAccessList; // List of streams that client has control over +} AHLClientCtxT; + // ahl-binding.c PUBLIC int AhlBindingInit(); PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ); @@ -117,17 +135,17 @@ void TermEndpoints(); int ParseHLBConfig(); // ahl-policy.c int Policy_Endpoint_Property_Init(EndpointInfoT * io_pEndpointInfo); -int Policy_Init(); -void Policy_Term(); -int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID); -int Policy_CloseStream(streamID_t streamID); -int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ); -int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute); -int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr); -int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr); +int Policy_OpenStream(StreamInfoT * pStreamInfo); +int Policy_CloseStream(StreamInfoT * pStreamInfo); +int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, StreamStateT streamState); +int Policy_SetStreamMute(StreamInfoT * pStreamInfo, StreamMuteT streamMute); int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext); int Policy_AudioDeviceChange(); - - +int Policy_SetVolume(EndpointInfoT * f_pEndpointInfo, char *volumeStr); +//Todo +int Policy_SetProperty(EndpointInfoT * f_pEndpointInfo, char *propertyName, json_object *propValue); +int Policy_Init(); +void Policy_Term(); +void Policy_OnEvent(const char *evtname, json_object *eventJ); #endif // AHL_BINDING_INCLUDE diff --git a/src/ahl-config.c b/src/ahl-config.c index dcf828f..aa55b28 100644 --- a/src/ahl-config.c +++ b/src/ahl-config.c @@ -29,6 +29,7 @@ int ParseHLBConfig() { char * versionStr = NULL; json_object * jAudioRoles = NULL; json_object * jHALList = NULL; + char * policyModule = NULL; // TODO: This should be retrieve from binding startup arguments char configfile_path[256]; @@ -43,11 +44,13 @@ int ParseHLBConfig() { return 1; } - int err = wrap_json_unpack(config_JFile, "{s:s,s:o,s:o}", "version", &versionStr,"audio_roles",&jAudioRoles,"hal_list",&jHALList); + int err = wrap_json_unpack(config_JFile, "{s:s,s:s,s:o,s:o}", "version", &versionStr,"policy_module", &policyModule,"audio_roles",&jAudioRoles,"hal_list",&jHALList); if (err) { AFB_ERROR("Invalid configuration file -> %s", configfile_path); return 1; } + AFB_INFO("Version: %s", versionStr); + AFB_INFO("Policy module: %s", policyModule); int iHALListLength = json_object_array_length(jHALList); int iNumberOfRoles = json_object_array_length(jAudioRoles); @@ -61,7 +64,6 @@ int ParseHLBConfig() { g_AHLCtx.policyCtx.pSourceEndpoints = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.pSinkEndpoints = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.pEventList = g_ptr_array_sized_new(iNumberOfRoles); - g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles; for (int i = 0; i < iHALListLength; i++) { @@ -88,19 +90,20 @@ int ParseHLBConfig() { json_object * jInputDevices = NULL; json_object * jEvents = NULL; char * pRoleName = NULL; - InterruptedBehaviorT interupBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; //Default + char * pInteruptBehavior = NULL; int iNumOutDevices = 0; int iNumInDevices = 0; int iNumEvents = 0; - err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s?o,s?o,s?o,s?i}", + err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s:s,s?o,s?o,s?o}", "name", &pRoleName, "priority",&priority, + "interupt_behavior",&pInteruptBehavior, "output",&jOutputDevices, "input",&jInputDevices, - "events",&jEvents, - "interupt_behavior",&interupBehavior); + "events",&jEvents + ); if (err) { AFB_ERROR("Invalid audio role configuration : %s", json_object_to_json_string(jAudioRole)); return 1; @@ -115,9 +118,24 @@ int ParseHLBConfig() { GString * gRoleName = g_string_new( pRoleName ); g_array_append_val( g_AHLCtx.policyCtx.pAudioRoles, *gRoleName ); - g_hash_table_insert(g_AHLCtx.policyCtx.pRolePriority, pRoleName, &priority); + g_hash_table_insert(g_AHLCtx.policyCtx.pRolePriority, pRoleName, GINT_TO_POINTER(priority)); - g_array_append_val(g_AHLCtx.policyCtx.pInterruptBehavior, interupBehavior); + // Map interupt behavior string to enum value + InterruptedBehaviorT interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; + if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_CONTINUE_STR) == 0 ) { + interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; + } + else if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_CANCEL_STR) == 0 ) { + interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CANCEL; + } + else if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_PAUSE_STR) == 0 ) { + interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_PAUSE; + } + else { + AFB_ERROR("Unknown interrupt behavior : %s", pInteruptBehavior); + return 1; + } + g_array_append_val(g_AHLCtx.policyCtx.pInterruptBehavior, interuptBehavior); // Sources GArray * pRoleSourceDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c index c69108e..4722333 100644 --- a/src/ahl-deviceenum.c +++ b/src/ahl-deviceenum.c @@ -27,6 +27,7 @@ extern AHLCtxT g_AHLCtx; +// TODO: Hash from endpoint ID information instead static endpointID_t CreateNewSourceID() { endpointID_t newID = g_AHLCtx.nextSourceEndpointID; @@ -34,6 +35,7 @@ static endpointID_t CreateNewSourceID() return newID; } +// TODO: Hash from endpoint ID information instead static endpointID_t CreateNewSinkID() { endpointID_t newID = g_AHLCtx.nextSinkEndpointID; @@ -42,6 +44,7 @@ static endpointID_t CreateNewSinkID() } // Watchout: This function uses strtok and is destructive on the input string (use a copy) +// TODO: Perhaps it would be clearer to separate domain and device URI in both API inputs and outputs static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomain, char ** out_pDevice) { *out_pDomain = strtok(in_pDeviceURI, "."); @@ -85,8 +88,13 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp snd_pcm_type_t pcmType = 0; snd_pcm_info_t * pPcmInfo = NULL; int iAlsaRet = 0; - const char * pDeviceName = NULL; + const char * pCardName = NULL; int retVal = 0; + snd_ctl_t * ctlHandle = NULL; + snd_ctl_card_info_t * ctlInfo = NULL; + + snd_pcm_info_alloca(&pPcmInfo); + snd_ctl_card_info_alloca(&ctlInfo); // retrieve PCM type pcmType = snd_pcm_type(in_pPcmHandle); @@ -100,45 +108,27 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp case SND_PCM_TYPE_SOFTVOL: out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_SOFTVOL; break; + case SND_PCM_TYPE_PLUG: + out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_PLUG; + break; default: out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_OTHER; break; } - iAlsaRet = snd_pcm_info_malloc(&pPcmInfo); - if (iAlsaRet < 0) - { - AFB_WARNING("Error allocating PCM info structure"); - retVal = 1; - goto End; - } - iAlsaRet = snd_pcm_info(in_pPcmHandle,pPcmInfo); if (iAlsaRet < 0) { AFB_WARNING("Error retrieving PCM device info"); - retVal = 1; - goto End; - } - - // Populate target device name (for application display) - pDeviceName = snd_pcm_info_get_name(pPcmInfo); - if (pDeviceName == NULL) - { - AFB_WARNING("No Alsa device name available"); - retVal = 1; - goto End; - // Could potentially assign a "default" name and carry on with this device + return 1; } - g_string_assign(out_pEndpointInfo->gsDeviceName,pDeviceName); // Overwritten by HAL name if available // get card number out_pEndpointInfo->alsaInfo.cardNum = snd_pcm_info_get_card(pPcmInfo); if ( out_pEndpointInfo->alsaInfo.cardNum < 0 ) { AFB_WARNING("No Alsa card number available"); - retVal = 1; - goto End; + return 1; } // get device number @@ -146,8 +136,7 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp if ( out_pEndpointInfo->alsaInfo.deviceNum < 0 ) { AFB_WARNING("No Alsa device number available"); - retVal = 1; - goto End; + return 1; } // get sub-device number @@ -155,15 +144,37 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp if ( out_pEndpointInfo->alsaInfo.subDeviceNum < 0 ) { AFB_WARNING("No Alsa subdevice number available"); - retVal = 1; - goto End; + return 1; + } + + char cardName[32]; + sprintf(cardName, "hw:%d", out_pEndpointInfo->alsaInfo.cardNum); + iAlsaRet = snd_ctl_open(&ctlHandle, cardName, 0); + if ( iAlsaRet < 0 ) + { + AFB_WARNING("Could not open ALSA card control"); + return 1; + } + + iAlsaRet = snd_ctl_card_info(ctlHandle, ctlInfo); + if ( iAlsaRet < 0 ) + { + AFB_WARNING("Could not retrieve ALSA card info"); + snd_ctl_close(ctlHandle); + return 1; } -End: - if(pPcmInfo) { - snd_pcm_info_free(pPcmInfo); - pPcmInfo = NULL; + // Populate unique target card name + pCardName = snd_ctl_card_info_get_id(ctlInfo); + if (pCardName == NULL) + { + AFB_WARNING("No Alsa card name available"); + snd_ctl_close(ctlHandle); + return 1; } + g_string_assign(out_pEndpointInfo->gsDeviceName,pCardName); + + snd_ctl_close(ctlHandle); return retVal; } @@ -201,7 +212,7 @@ static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) int iCardNum = atoi(pDevIDStr+3); if (iCardNum == io_pEndpointInfo->alsaInfo.cardNum) { g_string_assign(io_pEndpointInfo->gsHALAPIName,pAPIName); - g_string_assign(io_pEndpointInfo->gsDeviceName,pShortName); + g_string_assign(io_pEndpointInfo->gsDisplayName,pShortName); found = 1; break; } @@ -214,6 +225,7 @@ static void InitEndpointInfo( EndpointInfoT * out_pEndpointInfo ) out_pEndpointInfo->endpointID = AHL_UNDEFINED; out_pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE; out_pEndpointInfo->gsDeviceName = g_string_new("Unassigned"); + out_pEndpointInfo->gsDisplayName = g_string_new("Unassigned"); out_pEndpointInfo->gsDeviceURI = g_string_new("Unassigned"); out_pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE; out_pEndpointInfo->gsAudioRole = g_string_new("Unassigned"); @@ -230,6 +242,7 @@ static void InitEndpointInfo( EndpointInfoT * out_pEndpointInfo ) static void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo ) { g_string_free(out_pEndpointInfo->gsDeviceName,TRUE); + g_string_free(out_pEndpointInfo->gsDisplayName,TRUE); g_string_free(out_pEndpointInfo->gsDeviceURI,TRUE); g_string_free(out_pEndpointInfo->gsAudioRole,TRUE); g_string_free(out_pEndpointInfo->gsHALAPIName,TRUE); @@ -306,7 +319,7 @@ int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in // non ALSA URI are simply passed to application (no validation) at this time // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection - g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); // Overwritten by HAL name if available + g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); if (IsAlsaDomain(pDeviceURIDomain)) { diff --git a/src/ahl-interface.h b/src/ahl-interface.h index edead36..04b2865 100644 --- a/src/ahl-interface.h +++ b/src/ahl-interface.h @@ -29,6 +29,7 @@ typedef enum DeviceURIType { DEVICEURITYPE_ALSA_DMIX, // Alsa Dmix device URI (only for playback devices) DEVICEURITYPE_ALSA_DSNOOP, // Alsa DSnoop device URI (only for capture devices) DEVICEURITYPE_ALSA_SOFTVOL, // Alsa softvol device URI + DEVICEURITYPE_ALSA_PLUG, // Alsa plug device URI DEVICEURITYPE_ALSA_OTHER, // Alsa domain URI device of unspecified type DEVICEURITYPE_PULSE, // Pulse device URI DEVICEURITYPE_GSTREAMER, // GStreamer device URI @@ -37,9 +38,10 @@ typedef enum DeviceURIType { } DeviceURITypeT; typedef enum StreamState { - STREAM_STATUS_READY = 0, // Stream is inactive - STREAM_STATUS_RUNNING, // Stream is running - STREAM_STATUS_MAXVALUE, // Enum count, keep at the end + STREAM_STATE_IDLE = 0, // Stream is inactive + STREAM_STATE_RUNNING, // Stream is active and running + STREAM_STATE_PAUSED, // Stream is active but paused + STREAM_STATE_MAXVALUE // Enum count, keep at the end } StreamStateT; typedef enum StreamMute { @@ -48,13 +50,20 @@ typedef enum StreamMute { STREAM_MUTE_MAXVALUE, // Enum count, keep at the end } StreamMuteT; -// Define default behavior of audio role when interrupted by higher priority sources -typedef enum InterruptedBehavior { - AHL_INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media may be ducked) - AHL_INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) - AHL_INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) - AHL_INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end -} InterruptedBehaviorT; +typedef enum StreamEvent { + STREAM_EVENT_START = 0, // Stream is inactive + STREAM_EVENT_STOP, // Stream is running + STREAM_EVENT_PAUSE, // Audio stream paused + STREAM_EVENT_RESUME, // Audio stream resumed + STREAM_EVENT_MUTED, // Audio stream muted + STREAM_EVENT_UNMUTED, // Audio stream unmuted + STREAM_STATUS_MAXVALUE // Enum count, keep at the end +} StreamEventT; + +// Define default behavior of audio role when interrupted by higher priority sources (in configuration) +#define AHL_INTERRUPTEDBEHAVIOR_CONTINUE_STR "continue" // Continue to play when interrupted (e.g. media may be ducked) +#define AHL_INTERRUPTEDBEHAVIOR_CANCEL_STR "cancel" // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) +#define AHL_INTERRUPTEDBEHAVIOR_PAUSE_STR "pause" // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) #define AHL_ENDPOINT_PROPERTY_EVENT "ahl_endpoint_property_event" #define AHL_ENDPOINT_VOLUME_EVENT "ahl_endpoint_volume_event" @@ -80,15 +89,6 @@ typedef enum SampleType { AHL_FORMAT_MAXVALUE, // Enum count, keep at the end } SampleTypeT; -// Stream event types -typedef enum StreamEventType { - AHL_STREAMEVENT_START = 0, // Audio streaming should start - AHL_STREAMEVENT_STOP, // Audio streaming should stop - AHL_STREAMEVENT_UNMUTE, // Audio stream unmuted - AHL_STREAMEVENT_MUTE, // Audio stream muted - AHL_STREAMEVENT_MAXVALUE, // Enum count, keep at the end -} StreamEventTypeT; - // Known audio domain string definitions (for configuration file format and device URI interpretation) #define AHL_DOMAIN_ALSA "alsa" #define AHL_DOMAIN_PULSE "pulse" @@ -99,7 +99,7 @@ typedef enum StreamEventType { #define AHL_ROLE_WARNING "warning" // Safety-relevant or critical alerts/alarms #define AHL_ROLE_GUIDANCE "guidance" // Important user information where user action is expected (e.g. navigation instruction) #define AHL_ROLE_NOTIFICATION "notification" // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...) -#define AHL_ROLE_COMMUNICATION "communications" // Voice communications (e.g. handsfree, speech recognition) +#define AHL_ROLE_COMMUNICATION "communication" // Voice communications (e.g. handsfree, speech recognition) #define AHL_ROLE_ENTERTAINMENT "entertainment" // Multimedia content (e.g. tuner, media player, etc.) #define AHL_ROLE_SYSTEM "system" // System level content or development #define AHL_ROLE_STARTUP "startup" // Early (startup) sound @@ -113,7 +113,7 @@ typedef enum StreamEventType { #define AHL_PROPERTY_EQ_MID "eq_mid" #define AHL_PROPERTY_EQ_HIGH "eq_treble" -// Standardized list of events +// Standardized list of events (not enforced in any way, just helps compatibility) #define AHL_EVENTS_PLAYSOUND "play_sound" #define AHL_EVENTS_ECHOCANCEL_ENABLE "echocancel_enable" #define AHL_EVENTS_ECHOCANCEL_DISABLE "echocancel_disable" diff --git a/src/ahl-policy.c b/src/ahl-policy.c index fa050af..110f7e9 100644 --- a/src/ahl-policy.c +++ b/src/ahl-policy.c @@ -18,8 +18,11 @@ #define _GNU_SOURCE #include #include - +#include #include "ahl-binding.h" +#include "wrap-json.h" + +#define MAX_ACTIVE_STREAM_POLICY 30 // This file provides example of custom, business logic driven policy actions that can affect behavior of the high level // TODO: Currently only isolated in separate source file. Objective is to make this to at least a shared lib plug-in (shared C context) @@ -27,65 +30,895 @@ extern AHLCtxT g_AHLCtx; // TODO: Cannot stay if moved to external module -static void Add_Endpoint_Property_Numeric( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) +typedef struct StreamPolicyInfo { + int RolePriority; + int iVolume; + int iVolumeSavedMute; + streamID_t streamID; + InterruptedBehaviorT interruptBehavior; +} StreamPolicyInfoT; + + +typedef struct EndPointPolicyInfo { + int endpointKey; + endpointID_t endpointID; + EndpointTypeT type; + GArray * streamInfo; //List of playing or duck stream at a given endpoint +} EndPointPolicyInfoT; + +typedef enum SystemState { + SYSTEM_STARTUP = 0, // Startup + SYSTEM_SHUTDOWN, // ShutDown + SYSTEM_NORMAL, // Normal + SYSTEM_LOW_POWER, // Low Power, save mode + SYSTEM_MAXVALUE // Enum count, keep at the end +} SystemStateT; + + +// Global Policy Local context +typedef struct PolicyLocalCtx { + GArray * pSourceEndpoints; // List of Source Endpoint with playing stream or interrupted stream + GArray * pSinkEndpoints; // List of Sink Endpoint with playing stream or interrupted stream + GArray * pStreamOpenPerPolicy; //List of number of openstream per policy + GArray * pMaxStreamOpenPerPolicy; //List of number of openstream per policy + GArray * pVolDuckPerPolicy; //List of number of openstream per policy + SystemStateT systemState; +} PolicyLocalCtxT; + +PolicyLocalCtxT g_PolicyCtx; + + +//Helper Functions +static void Add_Endpoint_Property_Double( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, double in_dPropertyValue) +{ + json_object * propValueJ = json_object_new_double(in_dPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); +} + + +static void Add_Endpoint_Property_Int( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) { json_object * propValueJ = json_object_new_int(in_iPropertyValue); g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -static void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, char * in_pPropertyValue) +static void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, const char * in_pPropertyValue) { json_object * propValueJ = json_object_new_string(in_pPropertyValue); g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID) +static int PolicySetVolume(EndpointInfoT * pEndpointInfo, int iVolume) +{ + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + GString * gsHALControlName; + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Playback_Volume"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + AFB_WARNING("Endpoint %s is not support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); + if (err) + { + AFB_ERROR("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); + if (err) + { + AFB_ERROR("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume); + if (err) + { + AFB_ERROR("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return err; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); + + pEndpointInfo->iVolume = iVolume; + + return 0; +} + + +static int PolicySetVolumeMute(EndpointInfoT * pEndpointInfo, int iVolume) +{ + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + GString * gsHALControlName; + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Playback_Volume"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + AFB_WARNING("Endpoint %s is not support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); + if (err) + { + AFB_ERROR("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); + if (err) + { + AFB_ERROR("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume); + if (err) + { + AFB_ERROR("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return err; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); + + return 0; +} + + +static int PolicySetVolumeRamp(EndpointInfoT * pEndpointInfo, int iVolume) { - // TODO: Example rule -> when system is in shutdown or low power mode, no audio stream can be opened (return AHL_POLICY_REJECT) + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + GString * gsHALControlName; + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Ramp"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Ramp"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + AFB_WARNING("Endpoint %s is not a support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); + if (err) + { + AFB_WARNING("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); + if (err) + { + AFB_WARNING("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i, s:s}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume, "audio_role",gsHALControlName->str); + if (err) + { + AFB_WARNING("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return err; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); + + pEndpointInfo->iVolume = iVolume; + + return 0; +} + +static int PolicyGetVolume(EndpointInfoT * pEndpointInfo) +{ + + + GString * gsHALControlName; + + // Using audio role available from endpoint to target the right HAL control (build string based on convention) + switch(pEndpointInfo->deviceURIType) + { + case DEVICEURITYPE_ALSA_HW: + gsHALControlName = g_string_new("Master_Playback_Volume"); + break; + case DEVICEURITYPE_ALSA_DMIX: + case DEVICEURITYPE_ALSA_DSNOOP: + case DEVICEURITYPE_ALSA_PLUG: + case DEVICEURITYPE_ALSA_SOFTVOL: + gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) + break; + default: + //Set volume to zero for display purpose only. + //Not support yet + pEndpointInfo->iVolume = 0; + AFB_WARNING("Endpoint %s is a support Device Type and can't get volume",pEndpointInfo->gsDeviceName->str); + break; + } + + // Set endpoint volume using HAL services (leveraging ramps etc.) + json_object *j_response, *j_query = NULL; + + // Package query + int err = wrap_json_pack(&j_query,"{s:s}","label",gsHALControlName->str); + if (err) + { + AFB_WARNING("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); + return err; + } + + //TODO implement Volume limitation based on policy + + // Set the volume using the HAL + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlget", j_query, &j_response); + if (err) + { + AFB_WARNING("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); + return err; + } + AFB_DEBUG("HAL ctlget response=%s", json_object_to_json_string(j_response)); + + // Parse response + json_object * jRespObj = NULL; + json_object_object_get_ex(j_response, "response", &jRespObj); + json_object * jVal = NULL; + int val1 = 0, val2 = 0; // Why 2 values? + + json_object_object_get_ex(jRespObj, "val", &jVal); + int nbElement = json_object_array_length(jVal); + if(nbElement == 2) + { + err = wrap_json_unpack(jVal, "[ii]", &val1, &val2); + if (err) { + AFB_ERROR("Volume retrieve failed Could not retrieve volume value -> %s", json_object_get_string(jVal)); + return -1; + } + + } + else + { + err = wrap_json_unpack(jVal, "[i]", &val1); + if (err) { + AFB_ERROR("Volume retrieve failed Could not retrieve volume value -> %s", json_object_get_string(jVal)); + return -1; + } + + } + + pEndpointInfo->iVolume = val1; + + return 0; +} + +static void PolicyPostStateEvent(struct afb_event streamStateEvent, StreamEventT eventState) +{ + + json_object * eventDataJ = NULL; + int err = wrap_json_pack(&eventDataJ,"{s:i}","stateEvent",eventState); + if (err) + { + AFB_ERROR("Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + } + else + { + afb_event_push(streamStateEvent,eventDataJ); + } +} + +//This function is based on ALSA right now but it could be adapt to support other framework like pulseaudio or gstreamer +static int PolicyGenEndPointKey(EndpointInfoT *pEndPointInfo) +{ + return (pEndPointInfo->type << 24)|(pEndPointInfo->alsaInfo.cardNum << 16)|(pEndPointInfo->alsaInfo.deviceNum << 8)|(pEndPointInfo->alsaInfo.subDeviceNum); +} + +static EndPointPolicyInfoT *PolicySearchEndPoint(EndpointTypeT type, int key) +{ + GArray *pcurEndpointArray = NULL; + + if(type==ENDPOINTTYPE_SINK) + { + pcurEndpointArray = g_PolicyCtx.pSinkEndpoints; + } + else + { + pcurEndpointArray = g_PolicyCtx.pSourceEndpoints; + } + + for(int i=0; ilen; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(pcurEndpointArray,EndPointPolicyInfoT,i); + + if(pCurEndpoint->endpointKey == key) + { + return pCurEndpoint; + } + } + + return NULL; +} + +static StreamPolicyInfoT *PolicySearchStream(EndPointPolicyInfoT * pCurEndpoint, int streamID) +{ + + for(int i=0; istreamInfo->len; i++) + { + StreamPolicyInfoT * pCurStream = &g_array_index(pCurEndpoint->streamInfo,StreamPolicyInfoT,i); + + if(pCurStream->streamID == streamID) + { + return pCurStream; + } + } + + return NULL; +} + + +static StreamInfoT * PolicyGetActiveStream(streamID_t in_streamID) +{ + int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; + StreamInfoT * pStreamInfo = NULL; + for ( int i = 0; i < iNumActiveStreams ; i++ ) { + StreamInfoT * pCurStreamInfo = &g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); + if (pCurStreamInfo->streamID == in_streamID){ + pStreamInfo = pCurStreamInfo; + break; + } + } + return pStreamInfo; +} + + +static int PolicyFindRoleIndex( const char * in_pAudioRole) +{ + int index = -1; // Not found + for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) + { + GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); + if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) + { + index = i; + break; + } + } + return index; +} + +static int PolicyRemoveStream(EndPointPolicyInfoT *pCurrEndPointPolicy, int RemoveIndex) +{ + //Validate + if(RemoveIndex >= pCurrEndPointPolicy->streamInfo->len) + { + return -1; + + } + + + g_array_remove_index(pCurrEndPointPolicy->streamInfo,RemoveIndex); + + if(pCurrEndPointPolicy->streamInfo->len == 0) + { + + //Free streem + g_array_free(pCurrEndPointPolicy->streamInfo,TRUE); + pCurrEndPointPolicy->streamInfo = NULL; + + GArray *pcurEndpointArray = NULL; + + if(pCurrEndPointPolicy->type==ENDPOINTTYPE_SINK) + { + pcurEndpointArray = g_PolicyCtx.pSinkEndpoints; + } + else + { + pcurEndpointArray = g_PolicyCtx.pSourceEndpoints; + } + + for(int i=0; ilen; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(pcurEndpointArray,EndPointPolicyInfoT,i); + if(pCurEndpoint->endpointKey == pCurrEndPointPolicy->endpointKey) + { + g_array_remove_index(pcurEndpointArray, i); + return 0; + } + } + } + + return 0; +} + +static int PolicyRunningIdleTransition(EndPointPolicyInfoT *pCurrEndPointPolicy,StreamInfoT * pStreamInfo) +{ + if(pCurrEndPointPolicy == NULL) + { + //Remove endpoint + AFB_ERROR("No Active Endpoint has been found"); + return -1; + } + + if(pCurrEndPointPolicy->streamInfo->len>0) + { + //Search for the matching stream + int iNumStream = pCurrEndPointPolicy->streamInfo->len; + for(int i=0; istreamInfo,StreamPolicyInfoT,i); + if(currentPolicyStreamInfo.streamID == pStreamInfo->streamID) + { + + //Unduck case + if((i==(pCurrEndPointPolicy->streamInfo->len-1)) && (pCurrEndPointPolicy->streamInfo->len > 1)) + { + //remove the current stream + g_array_remove_index(pCurrEndPointPolicy->streamInfo, i); + + //check the last element(Akways highest priority) + StreamPolicyInfoT HighPriorityStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); + + //Get Stream Info + StreamInfoT * pInterruptStreamInfo = PolicyGetActiveStream(HighPriorityStreamInfo.streamID); + if (pInterruptStreamInfo == NULL) { + AFB_ERROR("Stream not found, Specified stream not currently active stream_id -> %d",HighPriorityStreamInfo.streamID); + return -1; + } + + int err; + switch(currentPolicyStreamInfo.interruptBehavior) + { + case AHL_INTERRUPTEDBEHAVIOR_CONTINUE: + //unduck and set Volume back to original value + err= PolicySetVolumeRamp(pInterruptStreamInfo->pEndpointInfo, HighPriorityStreamInfo.iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return -1; + } + break; + case AHL_INTERRUPTEDBEHAVIOR_PAUSE: + pInterruptStreamInfo->streamState = STREAM_STATE_RUNNING; + PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_RESUME); + break; + + case AHL_INTERRUPTEDBEHAVIOR_CANCEL: + AFB_ERROR("StreamID with Cancel InterruptedBehavior can't be unInterrupted"); + return -1; + break; + default: + AFB_ERROR("Unsupported Intterupt Behavior"); + return -1; + break; + } + + } + else + { + //remove the current stream + PolicyRemoveStream(pCurrEndPointPolicy, i); + } + return 0; + } + + } + } + + AFB_ERROR("StreamID does not match any playing stream"); + return -1; +} + +static int PolicyIdleRunningTransition(EndPointPolicyInfoT *pCurrEndPointPolicy, StreamInfoT * pStreamInfo, int AudioRoleIndex, int EndPointKey) +{ + int stream_priority; + int ori_key_pos; + + bool bKeyFound=g_hash_table_lookup_extended(g_AHLCtx.policyCtx.pRolePriority,pStreamInfo->pEndpointInfo->gsAudioRole->str,&ori_key_pos,&stream_priority); + if(bKeyFound==false) + { + AFB_ERROR("Can't find stream priority, request will be rejected"); + return -1; + } + + //stream_priority = GPOINTER_TO_INT(value); + + + InterruptedBehaviorT InterruptBehavior = g_array_index(g_AHLCtx.policyCtx.pInterruptBehavior,InterruptedBehaviorT,AudioRoleIndex); + int err; + if(pCurrEndPointPolicy == NULL) //No stream is playing on this endpoint + { + EndPointPolicyInfoT newEndPointPolicyInfo ; + StreamPolicyInfoT newStreamPolicyInfo; + + //create EndPoint and add playing stream + newEndPointPolicyInfo.endpointKey = EndPointKey; + newEndPointPolicyInfo.endpointID = pStreamInfo->pEndpointInfo->endpointID; + newEndPointPolicyInfo.type = pStreamInfo->pEndpointInfo->type; + newEndPointPolicyInfo.streamInfo = g_array_new(FALSE,TRUE,sizeof(StreamPolicyInfoT)); + + newStreamPolicyInfo.RolePriority = stream_priority; + newStreamPolicyInfo.iVolume = pStreamInfo->pEndpointInfo->iVolume; + newStreamPolicyInfo.streamID = pStreamInfo->streamID; + newStreamPolicyInfo.interruptBehavior = InterruptBehavior; + + g_array_append_val(newEndPointPolicyInfo.streamInfo, newStreamPolicyInfo); + g_array_append_val(g_PolicyCtx.pSinkEndpoints, newEndPointPolicyInfo); + + + /* + int *pVolume = &g_array_index(g_PolicyCtx.pVolInitPerPolicy,int,AudioRoleIndex); + pStreamInfo->pEndpointInfo->iVolume = *pVolume; + + err= PolicySetVolumeRamp(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return err; + } */ + } + else + { + //Currently contains playing or duck stream + if(pCurrEndPointPolicy->streamInfo->len > 0) + { + //check the last element + StreamPolicyInfoT HighPriorityStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); + if((stream_priority) >= HighPriorityStreamInfo.RolePriority) + { + //Get Stream Info + StreamInfoT * pInterruptStreamInfo = PolicyGetActiveStream(HighPriorityStreamInfo.streamID); + if (pInterruptStreamInfo == NULL) { + AFB_ERROR("Stream not found Specified stream not currently active stream_id -> %d",HighPriorityStreamInfo.streamID); + return -1; + } + + switch(InterruptBehavior) + { + case AHL_INTERRUPTEDBEHAVIOR_CONTINUE: + //Save the current Volume and set the docking volume + HighPriorityStreamInfo.iVolume = pInterruptStreamInfo->pEndpointInfo->iVolume; + + int *pVolume = &g_array_index(g_PolicyCtx.pVolDuckPerPolicy,int,AudioRoleIndex); + err= PolicySetVolumeRamp(pInterruptStreamInfo->pEndpointInfo, *pVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return -1; + } + break; + case AHL_INTERRUPTEDBEHAVIOR_PAUSE: + pInterruptStreamInfo->streamState = STREAM_STATE_PAUSED; + PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + break; + + case AHL_INTERRUPTEDBEHAVIOR_CANCEL: + pInterruptStreamInfo->streamState = STREAM_STATE_IDLE; + PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + g_array_remove_index(pCurrEndPointPolicy->streamInfo, pCurrEndPointPolicy->streamInfo->len-1); + + break; + default: + AFB_ERROR("Unsupported Intterupt Behavior"); + return AHL_POLICY_REJECT; + break; + + } + + //Add the playing stream at index 0 + StreamPolicyInfoT newStreamPolicyInfo; + newStreamPolicyInfo.RolePriority = stream_priority; + newStreamPolicyInfo.iVolume = pStreamInfo->pEndpointInfo->iVolume; + newStreamPolicyInfo.streamID = pStreamInfo->streamID; + newStreamPolicyInfo.interruptBehavior = InterruptBehavior; + + //Insert at the end, become highest priority streamID + g_array_append_val(pCurrEndPointPolicy->streamInfo, newStreamPolicyInfo); + + err= PolicySetVolumeRamp(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return err; + } + + } + else + { + //Higher Priority Stream is playing + AFB_NOTICE("Higher Priority Stream is playing"); + return -1; + } + + } + else + { + //Remove endpoint + AFB_ERROR("Active EndPoint is not attached to any active stream"); + return -1; + + } + } + + return 0; +} + +//Policy API +int Policy_OpenStream(StreamInfoT * pStreamInfo) +{ + // Example rule -> when system is in shutdown or low power mode, no audio stream can be opened (return AHL_POLICY_REJECT) + // Should receive event from lower level layer + if(g_PolicyCtx.systemState != SYSTEM_NORMAL) + { + return AHL_POLICY_REJECT; + } + + //Implement Policy open stream rules, limit to a certain number of stream open based on policy + int index = PolicyFindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); + int *pNumberOpenStream = &g_array_index(g_PolicyCtx.pStreamOpenPerPolicy,int,index); + int MaxNumberOpenStream = g_array_index(g_PolicyCtx.pMaxStreamOpenPerPolicy,int,index); + + *pNumberOpenStream +=1; + if((*pNumberOpenStream) > MaxNumberOpenStream ) + { + return AHL_POLICY_REJECT; + } + + //Get actual Volume + int err=PolicyGetVolume(pStreamInfo->pEndpointInfo); + if(err != 0) + { + AFB_WARNING("Can't get volume of Endpoint %s",pStreamInfo->pEndpointInfo->gsDeviceName->str); + } return AHL_POLICY_ACCEPT; } -int Policy_CloseStream(streamID_t streamID) +int Policy_CloseStream(StreamInfoT * pStreamInfo) { - // For completeness, unlikely to have any policy rules here + //Decrement the number of openstream + int index = PolicyFindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); + int *pNumberOpenStream = &g_array_index(g_PolicyCtx.pStreamOpenPerPolicy,int,index); + + *pNumberOpenStream -= 1; return AHL_POLICY_ACCEPT; } -int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ) +int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, StreamStateT streamState) { + //DONE // If higher priority audio role stream requires audio ducking (and un-ducking) of other streams (e.g. navigation ducks entertainment) - // TODO: Could potentially provide a fairly generic system using interupt behavior information and audio role priority (implemented and can be customized here) + // Could potentially provide a fairly generic system using interupt behavior information and audio role priority (implemented and can be customized here) // Specific exception can also be - // Source exclusion. E.g. When a source (e.g tuner) with same audio role as already active stream (e.g. media player) with same endpoint target, // the former source is stopped (i.e. raise streamstate stop event) - - // If source on communication role is active (e.g. handsfree call), activating entertainment sources is prohibited - - // If handsfree or speech recognition (communication role) is started during entertainment playback, mute all entertainment streams (any endpoints except RSE) - + // If source on communication role is active (e.g. handsfree call), activating entertainment sources is prohibited // Startup or Shutdown audio stream mute entertainment (unmut when stream no longer active) + //TODO // Optional: Mute endpoint before activation, unmute afterwards (after a delay?) to avoid noises + int err; - + //Change of state + if(pStreamInfo->streamState != streamState) + { + //seach corresponding endpoint and gather information on it + int key = PolicyGenEndPointKey(pStreamInfo->pEndpointInfo); + EndPointPolicyInfoT *pCurrEndPointPolicy = PolicySearchEndPoint(pStreamInfo->pEndpointInfo->type , key); + + switch(pStreamInfo->streamState) + { + case STREAM_STATE_IDLE: + switch(streamState) + { + case STREAM_STATE_RUNNING: + err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pStreamInfo, AudioRoleIndex, key); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_RUNNING; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_START); + break; + case STREAM_STATE_PAUSED: + err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pStreamInfo, AudioRoleIndex, key); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_PAUSED; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + break; + default: + return AHL_POLICY_REJECT; + break; + } + break; + case STREAM_STATE_RUNNING: + switch(streamState) + { + case STREAM_STATE_IDLE: + err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pStreamInfo); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_IDLE; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + break; + case STREAM_STATE_PAUSED: + pStreamInfo->streamState = STREAM_STATE_PAUSED; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + break; + default: + return AHL_POLICY_REJECT; + break; + } + break; + case STREAM_STATE_PAUSED: + switch(streamState) + { + case STREAM_STATE_IDLE: + err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pStreamInfo); + if(err) + { + return AHL_POLICY_REJECT; + } + pStreamInfo->streamState = STREAM_STATE_IDLE; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + break; + case STREAM_STATE_RUNNING: + pStreamInfo->streamState = STREAM_STATE_RUNNING; + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_RESUME); + break; + default: + return AHL_POLICY_REJECT; + break; + } + break; + default: + return AHL_POLICY_REJECT; + break; + } + } return AHL_POLICY_ACCEPT; } -int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute) +int Policy_SetStreamMute(StreamInfoT * pStreamInfo, StreamMuteT streamMute) { + int err; + + if(streamMute == STREAM_MUTED) + { + err= PolicySetVolumeMute(pStreamInfo->pEndpointInfo, 0); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return AHL_POLICY_REJECT; + } + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_MUTED); + } + else + { + err= PolicySetVolumeMute(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); + if(err) + { + AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); + return AHL_POLICY_REJECT; + } + PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_UNMUTED); + + + } + + pStreamInfo->streamMute = streamMute; + return AHL_POLICY_ACCEPT; } -int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr) +int Policy_SetVolume(EndpointInfoT * f_pEndpointInfo, char *volumeStr) { + + // TODO: Parse volume string to support increment/absolute/percent notation (or delegate to action / policy layer to interpret) + int vol = atoi(volumeStr); + + //Set the volume + int err = PolicySetVolumeRamp(f_pEndpointInfo, vol); + if (err) + { + AFB_ERROR("Set Volume return with errorcode%i", err); + return AHL_POLICY_REJECT; + } + return AHL_POLICY_ACCEPT; } -int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr) +int Policy_SetProperty(EndpointInfoT * f_pEndpointInfo, char *propertyName, json_object *propValue) { + + gpointer *key_value=NULL; + key_value=g_hash_table_lookup(f_pEndpointInfo->pPropTable,propertyName); + if(key_value==NULL) + { + AFB_ERROR("Can't find property %s, request will be rejected", propertyName); + return AHL_POLICY_REJECT; + } + + // Object type detection for property value (string = state, numeric = property) + json_type jType = json_object_get_type(propValue); + switch (jType) { + case json_type_double: + Add_Endpoint_Property_Double(f_pEndpointInfo,propertyName,json_object_get_double(propValue)); + case json_type_int: + Add_Endpoint_Property_Int(f_pEndpointInfo,propertyName,json_object_get_int(propValue)); + case json_type_string: + Add_Endpoint_Property_String(f_pEndpointInfo,propertyName,json_object_get_string(propValue)); + break; + default: + AFB_ERROR("Invalid property argument Property value not a valid json object query=%s", json_object_get_string(propValue )); + return AHL_POLICY_REJECT; + } + return AHL_POLICY_ACCEPT; } @@ -112,16 +945,16 @@ int Policy_AudioDeviceChange() int Policy_Endpoint_Property_Init(EndpointInfoT * io_EndpointInfo) { // Populate list of supported properties for specified endpoint (use helper functions) - // Setup initial values for all properties + // Setup initial values for all properties GHashTabl // TODO: Switch on different known endpoints to populate different properties // Test example - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_LOW,3); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_MID,0); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_BALANCE,0); - Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_FADE,30); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_LOW,3); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_MID,0); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_BALANCE,0); + Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_FADE,30); Add_Endpoint_Property_String(io_EndpointInfo,"preset_name","flat"); return 0; // No errors @@ -129,11 +962,240 @@ int Policy_Endpoint_Property_Init(EndpointInfoT * io_EndpointInfo) int Policy_Init() { - // Other policy specific initialization + // Initialize Ressources + g_PolicyCtx.pSourceEndpoints =g_array_new(FALSE,TRUE,sizeof(EndPointPolicyInfoT)); + g_PolicyCtx.pSinkEndpoints = g_array_new(FALSE,TRUE,sizeof(EndPointPolicyInfoT)); + g_PolicyCtx.pStreamOpenPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); + g_PolicyCtx.pMaxStreamOpenPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); + g_PolicyCtx.pVolDuckPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); + + int initial_value=0; + int max_value=0; + int vol_init_value=0; + int vol_duck_value=0; + GArray * pRoleDeviceArray = NULL; + //Init the number of open stream + for(int i=0; ilen; j++) + { + + //Init all Volume + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + int err= PolicySetVolumeRamp(pEndpointInfo, vol_init_value); + if(err) + { + AFB_WARNING("Endpoint:%s Set Volume return with errorcode%i",pEndpointInfo->gsDeviceName->str, err); + //try to read volume instead + err=PolicyGetVolume(pEndpointInfo); + if(err != 0) + { + AFB_WARNING("Can't get volume of Endpoint %s",pEndpointInfo->gsDeviceName->str); + } + } + + /* + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + int err=PolicyGetVolume(pEndpointInfo); + if(err != 0) + { + AFB_WARNING("Can't get volume of Endpoint %s",pEndpointInfo->gsDeviceName->str); + } +*/ + } + } + } + + + //Set System Normal for now, this should be set by an event + //TODO: Receive event from low level + g_PolicyCtx.systemState = SYSTEM_NORMAL; + + //register audio backend events + json_object *queryurl, *responseJ, *devidJ, *eventsJ; + + eventsJ = json_object_new_array(); + json_object_array_add(eventsJ, json_object_new_string("audiod_system_event")); + queryurl = json_object_new_object(); + json_object_object_add(queryurl, "events", eventsJ); + int returnResult = afb_service_call_sync("audiod", "subscribe", queryurl, &responseJ); + if (returnResult) { + AFB_ERROR("Fail subscribing to Audio Backend System events"); + return -1; + } + + return 0; // No errors } - + void Policy_Term() { - // Policy termination + + //Free Ressources + for(int i=0; ilen; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSourceEndpoints,EndPointPolicyInfoT,i); + g_array_free(pCurEndpoint->streamInfo,TRUE); + pCurEndpoint->streamInfo= NULL; + } + + for(int i=0; ilen; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSinkEndpoints,EndPointPolicyInfoT,i); + g_array_free(pCurEndpoint->streamInfo,TRUE); + pCurEndpoint->streamInfo = NULL; + } + + g_array_free(g_PolicyCtx.pSourceEndpoints,TRUE); + g_PolicyCtx.pSourceEndpoints = NULL; + g_array_free(g_PolicyCtx.pSinkEndpoints,TRUE); + g_PolicyCtx.pSinkEndpoints = NULL; + + g_array_free(g_PolicyCtx.pStreamOpenPerPolicy,TRUE); + g_PolicyCtx.pStreamOpenPerPolicy = NULL; + g_array_free(g_PolicyCtx.pMaxStreamOpenPerPolicy,TRUE); + g_PolicyCtx.pMaxStreamOpenPerPolicy = NULL; + g_array_free(g_PolicyCtx.pVolDuckPerPolicy, TRUE); + g_PolicyCtx.pVolDuckPerPolicy = NULL; } + +static void PolicySpeedModify(int speed) +{ + + for(int i=0; ilen; i++) + { + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSinkEndpoints,EndPointPolicyInfoT,i); + if(pCurEndpoint == NULL) + { + AFB_WARNING("Sink Endpoint not found"); + return; + + } + //check if active + if(pCurEndpoint->streamInfo->len > 0 ) + { + StreamPolicyInfoT * pCurStream = &g_array_index(pCurEndpoint->streamInfo,StreamPolicyInfoT,pCurEndpoint->streamInfo->len-1); + + + //Get Stream Info + StreamInfoT * pActiveStreamInfo = PolicyGetActiveStream(pCurStream->streamID); + if (pActiveStreamInfo == NULL) { + AFB_WARNING("Stream not found, Specified stream not currently active stream_id -> %d",pCurStream->streamID); + return; + } + + if(strcasecmp(pActiveStreamInfo->pEndpointInfo->gsAudioRole->str,AHL_ROLE_ENTERTAINMENT)==0) + { + + if(speed > 30 && speed < 100) + { + int volume =speed; + PolicySetVolumeRamp(pActiveStreamInfo->pEndpointInfo,volume); + } + + + } + + } + + } +} + + +void Policy_OnEvent(const char *evtname, json_object *eventJ) +{ + AFB_DEBUG("Policy received event %s", evtname); + + char *eventName = NULL; + json_object *event_parameter = NULL; + int speed = 0; + + + if(strcasecmp(evtname, "audiod/system_events")==0) + { + + int err = wrap_json_unpack(eventJ, "{s:s,s:o}", "event_name", &eventName, "event_parameter", &event_parameter); + if (err) { + AFB_WARNING("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(eventJ)); + return; + } + + if(strcasecmp(eventName, "speed")==0) + { + AFB_NOTICE("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(event_parameter)); + err = wrap_json_unpack(event_parameter, "{s:i}", "speed_value", &speed); + if (err) { + AFB_WARNING("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(event_parameter)); + return; + } + + //When speed change Modify volume on Endpoint where entertainment change + PolicySpeedModify(speed); + } + + } + + + + +} \ No newline at end of file -- cgit 1.2.3-korg From 8a584f01b46d251fdc5de8b071eff755d99f0090 Mon Sep 17 00:00:00 2001 From: Tai Vuong Date: Fri, 27 Oct 2017 21:40:20 -0400 Subject: Add JSON object parameters for policy functions call, fix various bug and code stabilisation --- conf.d/project/.asoundrc | 164 +++ conf.d/project/CMakeLists.txt | 24 - conf.d/project/agl-ahl-config.json | 128 ++ conf.d/project/alsa.d/asoundrc.sample | 146 -- .../project/alsa.d/ucm.sample/HDA Intel PCH.conf | 6 - conf.d/project/alsa.d/ucm.sample/HiFi.conf | 84 -- conf.d/project/alsa.d/ucm.sample/README | 2 - conf.d/project/json.d/ahl-binding.json | 127 -- conf.d/project/json.d/onload-audio-control.json | 137 -- htdocs/audio-control.html | 46 - htdocs/audio-logic.html | 9 - htdocs/audiohl-demo.html | 47 - htdocs/audiohl.html | 100 +- src/CMakeLists.txt | 2 +- src/ahl-apidef.h | 261 ++-- src/ahl-apidef.json | 60 +- src/ahl-binding.c | 1245 ++++++++++++----- src/ahl-binding.h | 180 ++- src/ahl-config.c | 126 +- src/ahl-deviceenum.c | 350 ++--- src/ahl-interface.h | 115 +- src/ahl-policy-utils.c | 312 +++++ src/ahl-policy-utils.h | 33 + src/ahl-policy.c | 1424 +++++++++----------- src/ahl-policy.h | 90 ++ 25 files changed, 2801 insertions(+), 2417 deletions(-) create mode 100644 conf.d/project/.asoundrc delete mode 100644 conf.d/project/CMakeLists.txt create mode 100644 conf.d/project/agl-ahl-config.json delete mode 100644 conf.d/project/alsa.d/asoundrc.sample delete mode 100644 conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf delete mode 100644 conf.d/project/alsa.d/ucm.sample/HiFi.conf delete mode 100644 conf.d/project/alsa.d/ucm.sample/README delete mode 100644 conf.d/project/json.d/ahl-binding.json delete mode 100644 conf.d/project/json.d/onload-audio-control.json delete mode 100644 htdocs/audio-control.html delete mode 100644 htdocs/audio-logic.html delete mode 100644 htdocs/audiohl-demo.html create mode 100644 src/ahl-policy-utils.c create mode 100644 src/ahl-policy-utils.h create mode 100644 src/ahl-policy.h diff --git a/conf.d/project/.asoundrc b/conf.d/project/.asoundrc new file mode 100644 index 0000000..cba88e5 --- /dev/null +++ b/conf.d/project/.asoundrc @@ -0,0 +1,164 @@ + + +##### AGL Conf ##### +pcm.SoftMixer { + type dmix + ipc_key 1024 + ipc_key_add_uid false + ipc_perm 0666 # mixing for all users + + # Define target effective sound card (cannot be a plugin) + slave { + pcm "hw:0" # Main sound card + # periods 8 + period_size 1024 + buffer_size 4096 + } + + # DMIX can only map two channels + bindings { + 0 0 + 1 1 + } +} + +pcm.SoftMixer_DriverHR { + type dmix + ipc_key 1024 + ipc_key_add_uid false + ipc_perm 0666 # mixing for all users + + # Define target effective sound card (cannot be a plugin) + slave { + pcm "hw:1" # Alternate sound card / dummy + # periods 8 + period_size 1024 + buffer_size 4096 + } + + # DMIX can only map two channels + bindings { + 0 0 + 1 1 + } +} + +pcm.SoftMixer_RSE { + type dmix + ipc_key 1024 + ipc_key_add_uid false + ipc_perm 0666 # mixing for all users + + # Define target effective sound card (cannot be a plugin) + slave { + pcm "hw:2" # Alternate sound card / dummy + # periods 8 + period_size 1024 + buffer_size 4096 + } + + # DMIX can only map two channels + bindings { + 0 0 + 1 1 + } +} + +pcm.Entertainment_Main { + type softvol + slave.pcm "SoftMixer" + control{ + name "Entertainment_Vol" + card 0 + } +} + +pcm.Guidance_Main { + type softvol + slave.pcm "SoftMixer" + control{ + name "Guidance_Vol" + card 0 + } +} + +pcm.Communications_Main { + type softvol + slave.pcm "SoftMixer" + control{ + name "Communications_Vol" + card 0 + } +} + +pcm.Notification_Main { + type softvol + slave.pcm "SoftMixer" + control{ + name "Notification_Vol" + card 0 + } +} + +pcm.Warning_Main { + type softvol + slave.pcm "SoftMixer" + control{ + name "Warning_Vol" + card 0 + } +} + +pcm.Entertainment_DriverHR { + type softvol + slave.pcm "SoftMixer_DriverHR" + control{ + name "Entertainment_Vol" + card 1 + } +} + +pcm.Guidance_DriverHR { + type softvol + slave.pcm "SoftMixer_DriverHR" + control{ + name "Guidance_Vol" + card 1 + } +} + +pcm.Communications_DriverHR { + type softvol + slave.pcm "SoftMixer_DriverHR" + control{ + name "Communications_Vol" + card 1 + } +} + +pcm.Notification_DriverHR { + type softvol + slave.pcm "SoftMixer_DriverHR" + control{ + name "Notification_Vol" + card 1 + } +} + +pcm.Warning_DriverHR { + type softvol + slave.pcm "SoftMixer_DriverHR" + control{ + name "Warning_Vol" + card 1 + } +} + +pcm.Entertainment_RSE { + type softvol + slave.pcm "SoftMixer_RSE" + control{ + name "Entertainment_Vol" + card 2 + } +} diff --git a/conf.d/project/CMakeLists.txt b/conf.d/project/CMakeLists.txt deleted file mode 100644 index fd4d454..0000000 --- a/conf.d/project/CMakeLists.txt +++ /dev/null @@ -1,24 +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. -########################################################################### - - - -# Include anything not starting with _ -PROJECT_SUBDIRS_ADD(${PROJECT_SRC_DIR_PATTERN}) - - diff --git a/conf.d/project/agl-ahl-config.json b/conf.d/project/agl-ahl-config.json new file mode 100644 index 0000000..f49c0be --- /dev/null +++ b/conf.d/project/agl-ahl-config.json @@ -0,0 +1,128 @@ +{ + "version": "0.2.0", + "policy_module": "AudioPolicy_v1", + "description": "High-level binding configuration file", + "note": "Devices and routings are always listed in order of priority (for device selection rules)", + "hal_list": ["ensoniq","usbaudio"], + "audio_roles": [ + { + "name": "Warning", + "id": 0, + "description": "Safety-relevant or critical alerts/alarms", + "priority": 100, + "output": [ + "alsa.plug:Warning_Main", + "alsa.plug:Warning_DriverHR" + ], + "actions": [ + "emergency_brake", + "collision_warning", + "blind_spot_warning" + ], + "interupt_behavior": "pause" + }, + { + "name": "Guidance", + "id": 1, + "description": "Important user information where user action is expected (e.g. navigation instruction)", + "priority": 25, + "output": [ + "alsa.plug:Guidance_Main", + "alsa.plug:Guidance_DriverHR" + ], + "actions": [ + "lane_guidance_left", + "lane_guidance_right", + "destination_reached" + ], + "interupt_behavior": "continue" + }, + { + "name": "Notification", + "id": 2, + "description": "HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...)", + "priority": 0, + "output": [ + "alsa.plug:Notification_Main", + "alsa.plug:Notification_DriverHR" + ], + "actions": [ + "home", + "context_switch", + "accept", + "cancel", + "selection_change" + ], + "interupt_behavior": "cancel" + }, + { + "name": "Communication", + "id": 3, + "description": "Voice communications (e.g. handsfree, speech recognition)", + "priority": 50, + "output": [ + "alsa.plug:Communications_Main", + "alsa.plug:Communications_DriverHR", + ], + "input": [ + "alsa.hw:0", + ], + "actions": [ + "bt_device_connected", + "bt_device_disconnected", + "sms_received" + ], + "interupt_behavior": "continue" + }, + { + "name": "Entertainment", + "id": 4, + "description": "Multimedia content (e.g. tuner, media player, etc.)", + "priority": 0, + "output": [ + "alsa.plug:Entertainment_Main", + "alsa.plug:Entertainment_DriverHR", + ], + "interupt_behavior": "pause" + }, + { + "name": "System", + "id": 5, + "description": "System level content or development", + "priority": 100, + "output": [ + "alsa.hw:0" + ], + "input": [ + "alsa.hw:0" + ], + "interupt_behavior": "continue" + }, + { + "name": "Startup", + "id": 6, + "description": "Early (startup) sound", + "priority": 100, + "output": [ + "alsa.hw:0" + ], + "actions": [ + "welcome_sound" + ], + "interupt_behavior": "pause" + }, + { + "name": "Shutdown", + "id": 7, + "description": "Late (shutdown) sound", + "priority": 100, + "output": [ + "alsa.hw:0" + ], + "actions": [ + "goodbye_sound" + ], + "interupt_behavior": "cancel" + } + ] +} diff --git a/conf.d/project/alsa.d/asoundrc.sample b/conf.d/project/alsa.d/asoundrc.sample deleted file mode 100644 index 8976077..0000000 --- a/conf.d/project/alsa.d/asoundrc.sample +++ /dev/null @@ -1,146 +0,0 @@ -# -# Author: Fulup Ar Foll -# Object: PCM hook type -# -# Test : Note: Jabra_USB=hw:v1340 -# Check SoundCard speaker-test -Dhw:v1340 -c2 -twav -# Check MixerPCM speaker-test -DMyMixerPCM -c2 -twav -# Check HookPCM speaker-test -DMyNavigationHook -c2 -twav -# Check NavPCM speaker-test -DMyNavPCM -c2 -twav -# MultiMedia aplay -DDMyNavPCM /usr/share/sounds/alsa/test.wav -# -# Bug/Feature: when softvol control is initialised from plugin and not -# from AGL binding. At 1st run ctl has invalid TLV and cannot be -# use. Bypass Solution: -# * start audio-binder before playing sound (binding create control before softvol plugin) -# * run a dummy aplay -DMyNavPCM "" to get a clean control -# -# References: https://www.spinics.net/lists/alsa-devel/msg54235.html -# -------------------------------------------------------------------- - -# Mixer PCM allow to play multiple stream simultaneously -# ------------------------------------------------------ -pcm.MyMixerPCM { - type dmix - ipc_key 1024 - ipc_key_add_uid false - ipc_perm 0666 # mixing for all users - - # Define target effective sound card (cannot be a plugin) - slave { - pcm "hw:v1340" #Jabra Solmate - period_time 0 - period_size 1024 - buffer_size 8192 - rate 44100 - } - - # DMIX can only map two channels - bindings { - 0 0 - 1 1 - } -} - -# Define a Hook_type with a private sharelib -# ------------------------------------------- -pcm_hook_type.MyHookPlugin { - install "AlsaInstallHook" - lib "/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Alsa-Plugin/Alsa-Policy-Hook/policy_hook_cb.so" -} - - -# Define a HookedPCM that point to Hook_type sharelib -# ---------------------------------------------------- -pcm.MyNavigationHook { - type hooks - slave.pcm "MyMixerPCM" - # Defined used hook sharelib and provide arguments/config to install func - hooks.0 { - type "MyHookPlugin" - hook_args { - verbose true # print few log messages (default false); - - # Every Call should return OK in order PCM to open (default timeout 100ms) - uri "ws://localhost:1234/api?token=audio-agent-token&uuid=audio-agent-session" - request { - # Request autorisation to write on navigation - RequestNavigation { - api "control" - verb "dispatch" - query "{'target':'navigation', 'args':{'device':'Jabra SOLEMATE v1.34.0'}}" - } - } - # map event reception to self generated signal - event { - pause 30 - resume 31 - stop 3 - } - } - } -} - -# If hardware does not support mixer emulate it with softvol -# ----------------------------------------------------------- -pcm.MyMultimediaPCM { - type softvol - - # Point Slave on HOOK for policies control - slave.pcm "MyNavigationHook" - - # resolution=HAL(valMax+1) (default=256) - resolution 256 - - # name should match with HAL but do not set card=xx - control.name "Playback Navigation" - - # Make this plugin visible from aplay -L - hint { - show on - description "Navigation SolftVol PCM" - } -} - -# If hardware does not support mixer emulate it with softvol -# ----------------------------------------------------------- -pcm.MyNavPCM { - type softvol - - # Point Slave on HOOK for policies control - slave.pcm "MyNavigationHook" - - # resolution=HAL(valMax+1) (default=256) - resolution 256 - - # name should match with HAL but do not set card=xx - control.name "Playback Navigation" - - # Make this plugin visible from aplay -L - hint { - show on - description "Navigation SolftVol PCM" - } -} - -# If hardware does not support mixer emulate it with softvol -# ----------------------------------------------------------- -pcm.MyAlarmPCM { - type softvol - - # Point Slave on HOOK for policies control - slave.pcm "MyNavigationHook" - - # resolution=HAL(valMax+1) (default=256) - resolution 256 - - # name should match with HAL but do not set card=xx - control.name "Playback Navigation" - - # Make this plugin visible from aplay -L - hint { - show on - description "Navigation SolftVol PCM" - } -} - diff --git a/conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf b/conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf deleted file mode 100644 index f6608a0..0000000 --- a/conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf +++ /dev/null @@ -1,6 +0,0 @@ -Comment "Leon internal card" - -SectionUseCase."HiFi" { - File "HiFi.conf" - Comment "Default" -} diff --git a/conf.d/project/alsa.d/ucm.sample/HiFi.conf b/conf.d/project/alsa.d/ucm.sample/HiFi.conf deleted file mode 100644 index 9a53c8c..0000000 --- a/conf.d/project/alsa.d/ucm.sample/HiFi.conf +++ /dev/null @@ -1,84 +0,0 @@ -SectionVerb { - EnableSequence [ - cdev "hw:PCH" - - cset "name='Master Playback Switch' on" - cset "name='Headphone Playback Switch' off" - cset "name='Speaker Playback Switch' on" - - cset "name='Capture Switch' on" - cset "name='Capture Volume' 39" - cset "name='Mic Boost Volume' 2" - cset "name='Internal Mic Boost Volume' 0" - #cset "name='Capture Source' 0" - ] - DisableSequence [ - ] - Value { - TQ "Music" - OutputDspName "speaker_eq" - PlaybackPCM "hw:PCH,0" - } -} - -SectionDevice."Headphone".0 { - Value { - JackName "Headphone Jack" - OutputDspName "Jheadphone" - } - EnableSequence [ - cdev "hw:PCH" - - cset "name='Speaker Playback Switch' off" - cset "name='Headphone Playback Switch' on" - ] - DisableSequence [ - cdev "hw:PCH" - - cset "name='Headphone Playback Switch' off" - cset "name='Speaker Playback Switch' on" - ] -} - -SectionDevice."Mic".0 { - Value { - JackName "Mic Jack" - } - EnableSequence [ - cdev "hw:PCH" - - #cset "name='Capture Source' 1" - ] - DisableSequence [ - cdev "hw:PCH" - - cset "name='Capture Source' 0" - ] -} - -SectionModifier."RecordMedia".0 { - SupportedDevice [ - "Headphone" - ] - EnableSequence [ - cdev "hw:PCH" - ] - - DisableSequence [ - cdev "hw:PCH" - ] - - TransitionSequence."ToModifierName" [ - cdev "hw:PCH" - ] - - # Optional TQ and ALSA PCMs - Value { - TQ Voice - CapturePCM "hw:1" - PlaybackVolume "name='Master Playback Volume',index=2" - PlaybackSwitch "name='Master Playback Switch',index=2" - } - -} - diff --git a/conf.d/project/alsa.d/ucm.sample/README b/conf.d/project/alsa.d/ucm.sample/README deleted file mode 100644 index e7f08ae..0000000 --- a/conf.d/project/alsa.d/ucm.sample/README +++ /dev/null @@ -1,2 +0,0 @@ -Should match sound card name ex: "HDA Intel PCH" -cp -r . /usr/share/alsa/ucm diff --git a/conf.d/project/json.d/ahl-binding.json b/conf.d/project/json.d/ahl-binding.json deleted file mode 100644 index d53a9aa..0000000 --- a/conf.d/project/json.d/ahl-binding.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "description" : "Audio Plugin configuration files", - "Audioroles" : { - "Description" : "Specify configuration and default value for Audio", - "Warning" :{ - "Attributes" :{ - "Interruptible" : "No", - "Routing" : "None" - }, - "devices" :{ - "output" : ["Speaker", "Lineout", "HDMI", "Default"] - } - }, - "Guidance" : { - "Attributes" :{ - "Interruptible" : "Yes", - "Routing" : "None" - }, - "devices" :{ - "output" : ["Speaker", "Lineout", "Default"] - } - }, - "Notification" : { - "Attributes" :{ - "Interruptible" : "Yes", - "Routing" : "None" - }, - "devices" :{ - "output" : ["Speaker", "Lineout", "HDMI", "Default"] - } - }, - "Communications" :{ - "Attributes" :{ - "Interruptible" : "Yes", - "Routing" : "Custom" - }, - "devices" :{ - "output" : [ "Speaker", "Lineout", "Default"], - "input" : [ "BT", "USB", "Headset", "Linein", "Default"] - }, - "Routing" :{ - "phone":{ - "Input" : "BT", - "Output" : "Speaker" - }, - "usb":{ - "Input" : "USB", - "Output" : "Speaker" - }, - "headset":{ - "Input" : "Headset", - "Output" : "Speaker" - }, - "phone_aux":{ - "Input" : "BT", - "Output" : "Lineout" - }, - "usb_aux":{ - "Input" : "USB", - "Output" : "Lineout" - } - - } - - - }, - "Entertainment" : { - "Attributes" :{ - "Interruptible" : "Yes", - "Routing" : "Custom" - }, - "devices" :{ - "output" : [ "Headphone", "Speaker", "Lineout", "Default"], - "input" : [ "BT", "USB", "Linein", "Default"] - }, - "Routing" : { - "phone":{ - "Input" : "BT", - "Output" : "Speaker" - }, - "phone_headphone":{ - "Input" : "BT", - "Output" : "Headphone" - }, - "phone_aux":{ - "Input" : "BT", - "Output" : "Lineout" - }, - "usb":{ - "Input" : "USB", - "Output" : "Speaker" - }, - "usb_headphone":{ - "Input" : "USB", - "Output" : "Headphone" - }, - "usb_aux":{ - "Input" : "USB", - "Output" : "Lineout" - }, - "aux":{ - "Input" : "Linein", - "Output" : "Speaker" - }, - "aux_headphone":{ - "Input" : "Linein", - "Output" : "Headphone" - }, - "aux_aux":{ - "Input" : "Linein", - "Output" : "Lineout" - } - - } - }, - "System" : { - "Attributes" :{ - "Interruptible" : "Yes", - "Routing" : "All" - }, - "devices" :{ - "output" : [ "Speaker", "Headset", "Headphone", "Handset", "BT", "HDMI", "Lineout", "USB", "Default"], - "input" : [ "BT", "Linein", "USB", "Voice", "Default"] - } - } - } -} \ No newline at end of file diff --git a/conf.d/project/json.d/onload-audio-control.json b/conf.d/project/json.d/onload-audio-control.json deleted file mode 100644 index 14d6970..0000000 --- a/conf.d/project/json.d/onload-audio-control.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "$schema": "ToBeDone", - "metadata": { - "label": "audio-policy-control-configuration", - "info": "Provide Default Audio Policy Control configuration", - "version": "1.0" - }, - "onload": [{ - "label": "onload-default", - "info": "onload initialisation config", - "plugin": { - "label" : "_MyPlug", - "sharelib": "ctl-audio-plugin-sample.ctlso", - "lua2c": ["Lua2cHelloWorld1", "Lua2cHelloWorld2"] - }, - "require": ["intel-hda"], - "actions": [ - { - "label": "onload-sample-cb", - "info": "Call control sharelib install entrypoint", - "callback": "SamplePolicyInit", - "args": { - "arg1": "first_arg", - "nextarg": "second arg value" - } - }, { - "label": "onload-sample-api", - "info": "Assert AlsaCore Presence", - "api": "alsacore", - "verb": "ping", - "args": {"data": "none"} - }, { - "label": "onload-hal-lua", - "info": "Load avaliable HALs", - "lua": "_Alsa_Get_Hal" - } - ] - }], - "controls": - [ - { - "label": "duckentertainment_lowprioritypriority", - "api": "intel-hda", - "verb": "SetRTPC", - "args": { - "value": "-30" - } - }, - - { - "label": "multimedia", - "actions": { - "label": "multimedia-control-lua", - "info": "Call Lua Script function Test_Lua_Engin", - "lua": "_Audio_Set_Multimedia" - } - }, { - "label": "navigation", - "actions": { - "label": "navigation-control-lua", - "info": "Call Lua Script to set Navigation", - "lua": "_Audio_Set_Navigation" - } - }, { - "label": "emergency", - "actions": { - "label": "emergency-control-ucm", - "lua": "_Audio_Set_Emergency" - } - }, { - "label": "multi-step-sample", - "info" : "all actions must succeed for control to be accepted", - "actions": [{ - "label": "multimedia-control-cb", - "info": "Call Sharelib Sample Callback", - "callback": "sampleControlNavigation", - "args": { - "arg1": "snoopy", - "arg2": "toto" - } - }, { - "label": "duckentertainment_lowprioritypriority", - "api": "hal", - "verb": "SetRTPC", - "args": { - "value": "-30" - } - }, { - "label": "navigation-control-lua", - "info": "Call Lua Script to set Navigation", - "lua": "_Audio_Set_Navigation" - }] - } - ], - "events": - [ - { - "label": "SampleEvent1", - "info": "define action when receiving a given event", - "actions": [ - { - "label": "Event Callback-1", - "callback": "SampleControlEvent", - "args": { - "arg": "action-1" - } - }, { - "label": "Event Callback-2", - "callback": "SampleControlEvent", - "args": { - "arg": "action-2" - } - } - ] - }, - { - "label": "SampleEvent2", - "info": "define action when receiving a given event", - "actions": [ - { - "label": "Event Callback-1", - "callback": "SampleControlEvent", - "args": { - "arg": "action-1" - } - }, { - "label": "Event Callback-2", - "callback": "SampleControlEvent", - "args": { - "arg": "action-2" - } - } - ] - } - ] -} - diff --git a/htdocs/audio-control.html b/htdocs/audio-control.html deleted file mode 100644 index 6b7872f..0000000 --- a/htdocs/audio-control.html +++ /dev/null @@ -1,46 +0,0 @@ - - - Basic Audio Hardware Abstraction Layer Test - - - - - - - -

        Simple Audio Control Test

        - -

        - Selected HAL - - - API Verbosity - -
        -
        - -
          - -
        1. -
        2. -
        3. -
          -
        4. -
        5. -
        6. -
        7. - -
        - - diff --git a/htdocs/audio-logic.html b/htdocs/audio-logic.html deleted file mode 100644 index c31282a..0000000 --- a/htdocs/audio-logic.html +++ /dev/null @@ -1,9 +0,0 @@ - - - High Level API Simple Test Page - - - - - -ToBeDone \ No newline at end of file diff --git a/htdocs/audiohl-demo.html b/htdocs/audiohl-demo.html deleted file mode 100644 index e222707..0000000 --- a/htdocs/audiohl-demo.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - Audio High Level Test - - - - - - - - - -
      7. Use Case 1: Automatic EndPoint Selection
      8. -
        -
          -
        1. -
        2. -
        3. -
          -
        - -
      9. Use Case 2: User Select EndPoint
      10. -
        -
          -
        1. -
        2. -
        3. -
        4. -
          -
        - -
      11. Use Case 3: Select Routing
      12. -
        -
          -
        1. -
        2. -
          -
        - - diff --git a/htdocs/audiohl.html b/htdocs/audiohl.html index ec4dd43..418cf7a 100644 --- a/htdocs/audiohl.html +++ b/htdocs/audiohl.html @@ -1,15 +1,16 @@ + Audio High Level Test - + - - - -

        + + + +

        Audio Role @@ -24,59 +25,66 @@
        Endpoint Type - + +


        - Property - + + + +
        + Stream Mute State +
        + Property +
        - -
        - Stream Active/Mute State -
        + +
        + +

          -
        1. -
        2. -
        3. -
        4. -
        5. -
        6. -
        7. -
        8. -
        9. -
        10. -
        11. -
        12. -
        13. -
        14. -
        15. -
        16. -
        17. -
        18. - -
          +
        19. +
        20. +
        21. +
        22. +
        23. +
        24. +
        25. +
        26. +
        27. +
        28. +
        29. +
        30. +
        31. +
        32. +
        33. +
        34. +
        - + +
          +
        1. Question +
          
          +                
        2. Response +
          
          +                    
        3. Events: +
          
          +        
        + \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0f6ca9..4429b0b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,7 +39,7 @@ PKG_CHECK_MODULES(GLIB_PKG REQUIRED glib-2.0) PROJECT_TARGET_ADD(audiohighlevel) # Define project Targets - ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c ahl-config.c ahl-policy.c) + ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c ahl-config.c ahl-policy-utils.c ahl-policy.c) # Generate API-v2 hat from OpenAPI json definition SET_TARGET_GENSKEL(${TARGET_NAME} ahl-apidef) diff --git a/src/ahl-apidef.h b/src/ahl-apidef.h index e23d1e0..fd957ce 100644 --- a/src/ahl-apidef.h +++ b/src/ahl-apidef.h @@ -29,128 +29,127 @@ static const char _afb_description_v2_audiohl[] = "\":{\"type\":\"int\"},\"mute\":{\"type\":\"int\"},\"device_uri\":{\"type" "\":\"string\"},\"$ref\":\"#/components/schemas/endpoint_info\"}}},\"x-pe" "rmissions\":{\"streamcontrol\":{\"permission\":\"urn:AGL:permission:audi" - "o:public:streamcontrol\"},\"routingcontrol\":{\"permission\":\"urn:AGL:p" - "ermission:audio:public:routingcontrol\"},\"audiostream\":{\"permission\"" - ":\"urn:AGL:permission:audio:public:audiostream\"},\"prioritysignal\":{\"" - "permission\":\"urn:AGL:permission:audio:public:prioritysignal\"},\"sound" - "event\":{\"permission\":\"urn:AGL:permission:audio:public:soundevent\"}," - "\"streammonitor\":{\"permission\":\"urn:AGL:permission:audio:public:stre" - "ammonitor\"}},\"responses\":{\"200\":{\"description\":\"A complex object" - " array response\",\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/afb-reply\"}}}},\"400\":{\"description\":\"Inval" - "id arguments\"}}},\"paths\":{\"/get_sources\":{\"description\":\"Retriev" - "e array of available audio sources\",\"get\":{\"parameters\":[{\"in\":\"" - "query\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"" - "string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/20" - "0\",\"response\":{\"description\":\"Array of endpoint info structures\"," - "\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/endpoint_i" - "nfo\"}}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_sin" - "ks\":{\"description\":\"Retrieve array of available audio sinks\",\"get\"" - ":{\"parameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\"" - ":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\"" - ":\"#/components/responses/200\",\"response\":{\"description\":\"Array of" - " endpoint info structures\",\"type\":\"array\",\"items\":{\"$ref\":\"#/c" - "omponents/schemas/endpoint_info\"}}},\"400\":{\"$ref\":\"#/components/re" - "sponses/400\"}}}},\"/stream_open\":{\"description\":\"Request opening a " - "stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissio" - "ns/audiostream\"},\"parameters\":[{\"in\":\"query\",\"name\":\"audio_rol" - "e\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\"" + "o:public:streamcontrol\"},\"endpointcontrol\":{\"permission\":\"urn:AGL:" + "permission:audio:public:endpointcontrol\"},\"audiostream\":{\"permission" + "\":\"urn:AGL:permission:audio:public:audiostream\"},\"soundevent\":{\"pe" + "rmission\":\"urn:AGL:permission:audio:public:soundevent\"}},\"responses\"" + ":{\"200\":{\"description\":\"A complex object array response\",\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/af" + "b-reply\"}}}},\"400\":{\"description\":\"Invalid arguments\"}}},\"paths\"" + ":{\"/get_sources\":{\"description\":\"Retrieve array of available audio " + "sources\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"audio_ro" + "le\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":" + "{\"200\":{\"$ref\":\"#/components/responses/200\",\"response\":{\"descri" + "ption\":\"Array of endpoint info structures\",\"type\":\"array\",\"items" + "\":{\"$ref\":\"#/components/schemas/endpoint_info\"}}},\"400\":{\"$ref\"" + ":\"#/components/responses/400\"}}}},\"/get_sinks\":{\"description\":\"Re" + "trieve array of available audio sinks\",\"get\":{\"parameters\":[{\"in\"" + ":\"query\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\"" + ":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses" + "/200\",\"response\":{\"description\":\"Array of endpoint info structures" + "\",\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/endpoin" + "t_info\"}}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/stre" + "am_open\":{\"description\":\"Request opening a stream\",\"get\":{\"x-per" + "missions\":{\"$ref\":\"#/components/x-permissions/audiostream\"},\"param" + "eters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\"s" + "chema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"endpoint_typ" + "e\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\"," + "\"name\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"" + "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"re" + "sponse\":{\"description\":\"Stream information structure\",\"$ref\":\"#/" + "components/schemas/stream_info\"}},\"400\":{\"$ref\":\"#/components/resp" + "onses/400\"}}}},\"/stream_close\":{\"description\":\"Request closing a s" + "tream\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permission" + "s/audiostream\"},\"parameters\":[{\"in\":\"query\",\"name\":\"stream_id\"" + ",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\"" + ":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/compone" + "nts/responses/400\"}}}},\"/set_stream_state\":{\"description\":\"Change " + "stream active state\",\"get\":{\"x-permissions\":{\"$ref\":\"#/component" + "s/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\",\"nam" + "e\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in" + "\":\"query\",\"name\":\"state\",\"required\":true,\"schema\":{\"type\":\"" + "int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" + "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_stream_mut" + "e\":{\"description\":\"Change stream mute state\",\"get\":{\"x-permissio" + "ns\":{\"$ref\":\"#/components/x-permissions/streamcontrol\"},\"parameter" + "s\":[{\"in\":\"query\",\"name\":\"stream_id\",\"required\":true,\"schema" + "\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"mute\",\"required\":" + "true,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"" + "#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/responses" + "/400\"}}}},\"/get_stream_info\":{\"description\":\"Retrieve stream infor" + "mation\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"stream_id" + "\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"20" + "0\":{\"$ref\":\"#/components/responses/200\",\"response\":{\"description" + "\":\"Stream information structure\",\"$ref\":\"#/components/schemas/stre" + "am_info\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_" + "volume\":{\"description\":\"Set volume on endpoint\",\"get\":{\"x-permis" + "sions\":{\"$ref\":\"#/components/x-permissions/endpointcontrol\"},\"para" + "meters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":true" + ",\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_i" + "d\",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"" + "name\":\"volume\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"" + "responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{" + "\"$ref\":\"#/components/responses/400\"}}}},\"/get_volume\":{\"descripti" + "on\":\"Get endpoint volume\",\"get\":{\"parameters\":[{\"in\":\"query\"," + "\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum" + "\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"sche" + "ma\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/component" + "s/responses/200\",\"response\":{\"description\":\"Endpoint volume value\"" + ",\"type\":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}" + "}}},\"/get_endpoint_info\":{\"description\":\"Retrieve endpoint informat" + "ion including its properties\",\"get\":{\"parameters\":[{\"in\":\"query\"" ",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enu" "m\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":false,\"sc" "hema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compone" - "nts/responses/200\",\"response\":{\"description\":\"Stream information s" - "tructure\",\"$ref\":\"#/components/schemas/stream_info\"}},\"400\":{\"$r" - "ef\":\"#/components/responses/400\"}}}},\"/stream_close\":{\"description" - "\":\"Request closing a stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#" - "/components/x-permissions/audiostream\"},\"parameters\":[{\"in\":\"query" - "\",\"name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"int\"" - "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"4" - "00\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_stream_state\":" - "{\"description\":\"Change stream active state\",\"get\":{\"x-permissions" - "\":{\"$ref\":\"#/components/x-permissions/streamcontrol\"},\"parameters\"" - ":[{\"in\":\"query\",\"name\":\"stream_id\",\"required\":true,\"schema\":" - "{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state\",\"required\":tr" - "ue,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/" - "components/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/4" - "00\"}}}},\"/set_stream_mute\":{\"description\":\"Change stream mute stat" - "e\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/st" - "reamcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"stream_id\"," - "\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"nam" - "e\":\"mute\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"respons" - "es\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\"" - ":\"#/components/responses/400\"}}}},\"/get_stream_info\":{\"description\"" - ":\"Retrieve stream information\",\"get\":{\"x-permissions\":{\"$ref\":\"" - "#/components/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"qu" - "ery\",\"name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"in" - "t\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"" - "response\":{\"description\":\"Stream information structure\",\"$ref\":\"" - "#/components/schemas/stream_info\"}},\"400\":{\"$ref\":\"#/components/re" - "sponses/400\"}}}},\"/set_volume\":{\"description\":\"Set volume\",\"get\"" - ":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/streamcontrol" - "\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"requi" - "red\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"" - "endpoint_id\",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"" - "query\",\"name\":\"volume\",\"required\":true,\"schema\":{\"type\":\"str" - "ing\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" - "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_volume\":{" - "\"description\":\"Get volume\",\"get\":{\"parameters\":[{\"in\":\"query\"" - ",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enu" - "m\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"sch" - "ema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/componen" - "ts/responses/200\",\"response\":{\"description\":\"Endpoint volume value" - "\",\"type\":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"" - "}}}},\"/get_list_properties\":{\"description\":\"Retrieve a list of supp" - "orted properties for a particular endpoint\",\"get\":{\"parameters\":[{\"" - "in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{" - "\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"require" - "d\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref" - "\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/resp" - "onses/400\"}}}},\"/set_property\":{\"description\":\"Set property value\"" - ",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/strea" - "mcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\"" - ",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"n" - "ame\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"}}," - "{\"in\":\"query\",\"name\":\"property_name\",\"required\":true,\"schema\"" - ":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"value\",\"required\"" - ":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\"" - ":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/respon" - "ses/400\"}}}},\"/get_property\":{\"description\":\"Get property value\"," - "\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"" + "nts/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}" + "},\"/set_property\":{\"description\":\"Set endpoint property value\",\"g" + "et\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/endpointc" + "ontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"" "required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name" "\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"}},{\"" "in\":\"query\",\"name\":\"property_name\",\"required\":true,\"schema\":{" - "\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/r" - "esponses/200\",\"response\":{\"description\":\"Property value\",\"type\"" - ":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/ge" - "t_list_events\":{\"description\":\"Retrieve a list of supported events f" - "or a particular audio role\",\"get\":{\"parameters\":[{\"in\":\"query\"," - "\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"" - "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"4" - "00\":{\"$ref\":\"#/components/responses/400\"}}}},\"/post_event\":{\"des" - "cription\":\"Post sound or audio device related event (extendable mechan" - "ism)\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions" - "/soundevent\"},\"parameters\":[{\"in\":\"query\",\"name\":\"event_name\"" - ",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"" - "name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"}" - "},{\"in\":\"query\",\"name\":\"media_name\",\"required\":false,\"schema\"" - ":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"event_context\",\"r" - "equired\":false,\"schema\":{\"type\":\"object\"}}],\"responses\":{\"200\"" - ":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/compone" - "nts/responses/400\"}}}},\"/subscribe\":{\"description\":\"Subscribe to a" - "udio high level events\",\"get\":{\"parameters\":[{\"in\":\"query\",\"na" - "me\":\"events\",\"required\":true,\"schema\":{\"type\":\"array\",\"items" - "\":{\"type\":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/compone" - "nts/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}" - "},\"/unsubscribe\":{\"description\":\"Unubscribe to audio high level eve" - "nts\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"events\",\"r" - "equired\":true,\"schema\":{\"type\":\"array\",\"items\":{\"type\":\"stri" - "ng\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" - "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}}}}" + "\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"value\",\"required\":" + "true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\"" + ":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/respon" + "ses/400\"}}}},\"/get_property\":{\"description\":\"Get endpoint property" + " value\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_" + "type\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query" + "\",\"name\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"in" + "t\"}},{\"in\":\"query\",\"name\":\"property_name\",\"required\":true,\"s" + "chema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/com" + "ponents/responses/200\",\"response\":{\"description\":\"Property value\"" + ",\"type\":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}" + "}}},\"/get_list_actions\":{\"description\":\"Retrieve a list of supporte" + "d actions for a particular audio role\",\"get\":{\"parameters\":[{\"in\"" + ":\"query\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\"" + ":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses" + "/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/post_act" + "ion\":{\"description\":\"Post sound or audio device related action event" + " (extendable mechanism)\",\"get\":{\"x-permissions\":{\"$ref\":\"#/compo" + "nents/x-permissions/soundevent\"},\"parameters\":[{\"in\":\"query\",\"na" + "me\":\"action_name\",\"required\":true,\"schema\":{\"type\":\"string\"}}" + ",{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\"schema\":" + "{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"media_name\",\"requi" + "red\":false,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\"" + ":\"action_context\",\"required\":false,\"schema\":{\"type\":\"object\"}}" + "],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400" + "\":{\"$ref\":\"#/components/responses/400\"}}}},\"/subscribe\":{\"descri" + "ption\":\"Subscribe to audio high level events\",\"get\":{\"parameters\"" + ":[{\"in\":\"query\",\"name\":\"events\",\"required\":true,\"schema\":{\"" + "type\":\"array\",\"items\":{\"type\":\"string\"}}}],\"responses\":{\"200" + "\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/compo" + "nents/responses/400\"}}}},\"/unsubscribe\":{\"description\":\"Unubscribe" + " to audio high level events\",\"get\":{\"parameters\":[{\"in\":\"query\"" + ",\"name\":\"events\",\"required\":true,\"schema\":{\"type\":\"array\",\"" + "items\":{\"type\":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/co" + "mponents/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400" + "\"}}}}}}" ; static const struct afb_auth _afb_auths_v2_audiohl[] = { { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:audiostream" }, { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:streamcontrol" }, + { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:endpointcontrol" }, { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:soundevent" } }; @@ -163,11 +162,11 @@ static const struct afb_auth _afb_auths_v2_audiohl[] = { void audiohlapi_get_stream_info(struct afb_req req); void audiohlapi_set_volume(struct afb_req req); void audiohlapi_get_volume(struct afb_req req); - void audiohlapi_get_list_properties(struct afb_req req); + void audiohlapi_get_endpoint_info(struct afb_req req); void audiohlapi_set_property(struct afb_req req); void audiohlapi_get_property(struct afb_req req); - void audiohlapi_get_list_events(struct afb_req req); - void audiohlapi_post_event(struct afb_req req); + void audiohlapi_get_list_actions(struct afb_req req); + void audiohlapi_post_action(struct afb_req req); void audiohlapi_subscribe(struct afb_req req); void audiohlapi_unsubscribe(struct afb_req req); @@ -217,57 +216,57 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { { .verb = "get_stream_info", .callback = audiohlapi_get_stream_info, - .auth = &_afb_auths_v2_audiohl[1], + .auth = NULL, .info = "Retrieve stream information", .session = AFB_SESSION_NONE_V2 }, { .verb = "set_volume", .callback = audiohlapi_set_volume, - .auth = &_afb_auths_v2_audiohl[1], - .info = "Set volume", + .auth = &_afb_auths_v2_audiohl[2], + .info = "Set volume on endpoint", .session = AFB_SESSION_NONE_V2 }, { .verb = "get_volume", .callback = audiohlapi_get_volume, .auth = NULL, - .info = "Get volume", + .info = "Get endpoint volume", .session = AFB_SESSION_NONE_V2 }, { - .verb = "get_list_properties", - .callback = audiohlapi_get_list_properties, + .verb = "get_endpoint_info", + .callback = audiohlapi_get_endpoint_info, .auth = NULL, - .info = "Retrieve a list of supported properties for a particular endpoint", + .info = "Retrieve endpoint information including its properties", .session = AFB_SESSION_NONE_V2 }, { .verb = "set_property", .callback = audiohlapi_set_property, - .auth = &_afb_auths_v2_audiohl[1], - .info = "Set property value", + .auth = &_afb_auths_v2_audiohl[2], + .info = "Set endpoint property value", .session = AFB_SESSION_NONE_V2 }, { .verb = "get_property", .callback = audiohlapi_get_property, .auth = NULL, - .info = "Get property value", + .info = "Get endpoint property value", .session = AFB_SESSION_NONE_V2 }, { - .verb = "get_list_events", - .callback = audiohlapi_get_list_events, + .verb = "get_list_actions", + .callback = audiohlapi_get_list_actions, .auth = NULL, - .info = "Retrieve a list of supported events for a particular audio role", + .info = "Retrieve a list of supported actions for a particular audio role", .session = AFB_SESSION_NONE_V2 }, { - .verb = "post_event", - .callback = audiohlapi_post_event, - .auth = &_afb_auths_v2_audiohl[2], - .info = "Post sound or audio device related event (extendable mechanism)", + .verb = "post_action", + .callback = audiohlapi_post_action, + .auth = &_afb_auths_v2_audiohl[3], + .info = "Post sound or audio device related action event (extendable mechanism)", .session = AFB_SESSION_NONE_V2 }, { diff --git a/src/ahl-apidef.json b/src/ahl-apidef.json index d486337..3ab6f4a 100644 --- a/src/ahl-apidef.json +++ b/src/ahl-apidef.json @@ -118,11 +118,9 @@ }, "x-permissions": { "streamcontrol": { "permission": "urn:AGL:permission:audio:public:streamcontrol"}, - "routingcontrol": { "permission": "urn:AGL:permission:audio:public:routingcontrol"}, + "endpointcontrol": { "permission": "urn:AGL:permission:audio:public:endpointcontrol"}, "audiostream": { "permission": "urn:AGL:permission:audio:public:audiostream"}, - "prioritysignal": { "permission": "urn:AGL:permission:audio:public:prioritysignal"}, - "soundevent": {"permission": "urn:AGL:permission:audio:public:soundevent"}, - "streammonitor": {"permission": "urn:AGL:permission:audio:public:streammonitor"} + "soundevent": {"permission": "urn:AGL:permission:audio:public:soundevent"} }, "responses": { "200": { @@ -135,9 +133,7 @@ } } }, - "400": { - "description": "Invalid arguments" - } + "400": { "description": "Invalid arguments" } } }, "paths": { @@ -185,8 +181,7 @@ "items": { "$ref": "#/components/schemas/endpoint_info"} } }, - "400": { - "$ref": "#/components/responses/400" } + "400": { "$ref": "#/components/responses/400" } } } }, @@ -265,12 +260,8 @@ } ], "responses": { - "200": { - "$ref": "#/components/responses/200" - }, - "400": { - "$ref": "#/components/responses/400" - } + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } } } }, @@ -303,9 +294,6 @@ "/get_stream_info": { "description": "Retrieve stream information", "get": { - "x-permissions": { - "$ref": "#/components/x-permissions/streamcontrol" - }, "parameters": [ { "in": "query", @@ -327,9 +315,9 @@ } }, "/set_volume": { - "description": "Set volume", + "description": "Set volume on endpoint", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { "$ref": "#/components/x-permissions/endpointcontrol" }, "parameters": [ { "in": "query", @@ -357,7 +345,7 @@ } }, "/get_volume": { - "description": "Get volume", + "description": "Get endpoint volume", "get": { "parameters": [ { @@ -385,8 +373,8 @@ } } }, - "/get_list_properties": { - "description": "Retrieve a list of supported properties for a particular endpoint", + "/get_endpoint_info": { + "description": "Retrieve endpoint information including its properties", "get": { "parameters": [ { @@ -409,9 +397,9 @@ } }, "/set_property": { - "description": "Set property value", + "description": "Set endpoint property value", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { "$ref": "#/components/x-permissions/endpointcontrol" }, "parameters": [ { "in": "query", @@ -445,7 +433,7 @@ } }, "/get_property": { - "description": "Get property value", + "description": "Get endpoint property value", "get": { "parameters": [ { @@ -479,8 +467,8 @@ } } }, - "/get_list_events": { - "description": "Retrieve a list of supported events for a particular audio role", + "/get_list_actions": { + "description": "Retrieve a list of supported actions for a particular audio role", "get": { "parameters": [ { @@ -491,23 +479,19 @@ } ], "responses": { - "200": { - "$ref": "#/components/responses/200" - }, - "400": { - "$ref": "#/components/responses/400" - } + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } } } }, - "/post_event": { - "description": "Post sound or audio device related event (extendable mechanism)", + "/post_action": { + "description": "Post sound or audio device related action event (extendable mechanism)", "get": { "x-permissions": { "$ref": "#/components/x-permissions/soundevent" }, "parameters": [ { "in": "query", - "name": "event_name", + "name": "action_name", "required": true, "schema": { "type": "string" } }, @@ -525,7 +509,7 @@ }, { "in": "query", - "name": "event_context", + "name": "action_context", "required": false, "schema": { "type": "object" } } diff --git a/src/ahl-binding.c b/src/ahl-binding.c index fe3dcee..cd89b64 100644 --- a/src/ahl-binding.c +++ b/src/ahl-binding.c @@ -1,6 +1,5 @@ /* * Copyright (C) 2017 "Audiokinetic Inc" - * Author Francois Thibault * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +21,106 @@ #include "ahl-binding.h" #include "ahl-apidef.h" // Generated from JSON OpenAPI #include "wrap-json.h" +#include "ahl-policy.h" +#include "ahl-policy-utils.h" // Global high-level binding context AHLCtxT g_AHLCtx; +// TODO: Helpers that could be common +static EndpointTypeT EndpointTypeToEnum(char * in_pEndpointTypeStr) +{ + if (in_pEndpointTypeStr == NULL) { + return ENDPOINTTYPE_MAXVALUE; + } + else if (strcasecmp(in_pEndpointTypeStr,AHL_ENDPOINTTYPE_SOURCE)==0) { + return ENDPOINTTYPE_SOURCE; + } + else if (strcasecmp(in_pEndpointTypeStr,AHL_ENDPOINTTYPE_SINK)==0) { + return ENDPOINTTYPE_SINK; + } + else + return ENDPOINTTYPE_MAXVALUE; +} + +static StreamStateT StreamStateToEnum(char * in_pStreamStateStr) +{ + if (in_pStreamStateStr == NULL) { + return STREAM_STATE_MAXVALUE; + } + else if (strcasecmp(in_pStreamStateStr,AHL_STREAM_STATE_IDLE)==0) { + return STREAM_STATE_IDLE; + } + else if (strcasecmp(in_pStreamStateStr,AHL_STREAM_STATE_RUNNING)==0) { + return STREAM_STATE_RUNNING; + } + else if (strcasecmp(in_pStreamStateStr,AHL_STREAM_STATE_PAUSED)==0) { + return STREAM_STATE_PAUSED; + } + else + return STREAM_STATE_MAXVALUE; +} + +static StreamMuteT StreamMuteToEnum(char * in_pStreamMuteStr) +{ + if (in_pStreamMuteStr == NULL) { + return STREAM_MUTE_MAXVALUE; + } + else if (strcasecmp(in_pStreamMuteStr,AHL_STREAM_UNMUTED)==0) { + return STREAM_UNMUTED; + } + else if (strcasecmp(in_pStreamMuteStr,AHL_STREAM_MUTED)==0) { + return STREAM_MUTED; + } + else + return STREAM_MUTE_MAXVALUE; +} + +static char * DeviceURITypeEnumToStr(DeviceURITypeT in_eDeviceURIType) { + switch(in_eDeviceURIType) { + case DEVICEURITYPE_ALSA_HW: // Alsa hardware device URI + return AHL_DEVICEURITYPE_ALSA_HW; + case DEVICEURITYPE_ALSA_DMIX: // Alsa Dmix device URI (only for playback devices) + return AHL_DEVICEURITYPE_ALSA_DMIX; + case DEVICEURITYPE_ALSA_DSNOOP: // Alsa DSnoop device URI (only for capture devices) + return AHL_DEVICEURITYPE_ALSA_DSNOOP; + case DEVICEURITYPE_ALSA_SOFTVOL: // Alsa softvol device URI + return AHL_DEVICEURITYPE_ALSA_SOFTVOL; + case DEVICEURITYPE_ALSA_PLUG: // Alsa plug device URI + return AHL_DEVICEURITYPE_ALSA_PLUG; + case DEVICEURITYPE_ALSA_OTHER: // Alsa domain URI device of unspecified type + return AHL_DEVICEURITYPE_ALSA_OTHER; + case DEVICEURITYPE_NOT_ALSA: // Unknown (not ALSA domain) + return AHL_DEVICEURITYPE_NOT_ALSA; + default: + return "Unknown"; + } +} + +static char * StreamStateEnumToStr(StreamStateT in_eStreamState) { + switch(in_eStreamState) { + case STREAM_STATE_IDLE: + return AHL_STREAM_STATE_IDLE; + case STREAM_STATE_RUNNING: + return AHL_STREAM_STATE_RUNNING; + case STREAM_STATE_PAUSED: + return AHL_STREAM_STATE_PAUSED; + default: + return "Unknown"; + } +} + +static char * StreamMuteEnumToStr(StreamMuteT in_eStreamMute) { + switch(in_eStreamMute) { + case STREAM_UNMUTED: + return AHL_STREAM_UNMUTED; + case STREAM_MUTED: + return AHL_STREAM_MUTED; + default: + return "Unknown"; + } +} + static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * pAudioFormat) { wrap_json_pack(audioFormatJ, "{s:i,s:i,s:i}", @@ -34,29 +129,52 @@ static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * p "sample_type", pAudioFormat->sampleType); } -// Helper macros/func for packaging JSON objects from C structures +// Package only information that can useful to application clients when selecting endpoint static void EndpointInfoStructToJSON(json_object **endpointInfoJ, EndpointInfoT * pEndpointInfo) { json_object *formatInfoJ = NULL; - wrap_json_pack(endpointInfoJ, "{s:i,s:i,s:s,s:s,s:i}", + wrap_json_pack(endpointInfoJ, "{s:i,s:s,s:s,s:s,s:s,s:s,s:s}", "endpoint_id", pEndpointInfo->endpointID, - "endpoint_type", pEndpointInfo->type, - "device_name", pEndpointInfo->gsDeviceName->str, - "display_name", pEndpointInfo->gsDisplayName->str, - "device_uri_type", pEndpointInfo->deviceURIType); + "endpoint_type", (pEndpointInfo->type == ENDPOINTTYPE_SOURCE) ? AHL_ENDPOINTTYPE_SOURCE : AHL_ENDPOINTTYPE_SINK, + "device_name", pEndpointInfo->gsDeviceName, + "display_name", pEndpointInfo->gsDisplayName, + "audio_role", pEndpointInfo->pRoleName, + "device_domain",pEndpointInfo->gsDeviceDomain, + "device_uri_type", DeviceURITypeEnumToStr(pEndpointInfo->deviceURIType)); AudioFormatStructToJSON(&formatInfoJ,&pEndpointInfo->format); json_object_object_add(*endpointInfoJ,"format",formatInfoJ); + + // Properties + if (pEndpointInfo->pPropTable) { + json_object *pPropTableJ = json_object_new_array(); + + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, pEndpointInfo->pPropTable); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if((key!=NULL) && (value!=NULL)) + { + json_object *pPropertyJ = NULL; + json_object_get((json_object*)value); // Don't let the framework free our object when the request is done + wrap_json_pack(&pPropertyJ, "{s:s,s:o}","property_name", (char*)key, "property_value", (json_object*)value); + json_object_array_add(pPropTableJ, pPropertyJ); + } + } + json_object_object_add(*endpointInfoJ,"properties",pPropTableJ); + } } +// Package only information that can useful to application clients when opening a stream static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT * pStreamInfo) { json_object *endpointInfoJ = NULL; EndpointInfoStructToJSON(&endpointInfoJ,pStreamInfo->pEndpointInfo); - wrap_json_pack(streamInfoJ, "{s:i,s:i,s:i,s:s}", + wrap_json_pack(streamInfoJ, "{s:i,s:s,s:s,s:s}", "stream_id", pStreamInfo->streamID, - "state", pStreamInfo->streamState, - "mute", pStreamInfo->streamMute, - "device_uri",pStreamInfo->pEndpointInfo->gsDeviceURI->str); + "state", StreamStateEnumToStr(pStreamInfo->streamState), + "mute", StreamMuteEnumToStr(pStreamInfo->streamMute), + "device_uri",pStreamInfo->pEndpointInfo->gsDeviceURI); // Need to open a stream to have access to the device URI json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); } @@ -67,62 +185,91 @@ static streamID_t CreateNewStreamID() return newID; } -static int FindRoleIndex( const char * in_pAudioRole) +static EndpointInfoT * GetEndpointInfoWithRole(endpointID_t in_endpointID, EndpointTypeT in_endpointType, RoleInfoT * in_pRole) { - int index = -1; // Not found - for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) - { - GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); - if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) - { - index = i; + EndpointInfoT * pEndpointInfo = NULL; + GPtrArray * pDeviceArray = NULL; + if (in_endpointType == ENDPOINTTYPE_SOURCE){ + pDeviceArray = in_pRole->pSourceEndpoints; + } + else { + pDeviceArray = in_pRole->pSinkEndpoints; + } + g_assert_nonnull(pDeviceArray); + + for (int j = 0; j < pDeviceArray->len; j++) { + EndpointInfoT * pCurEndpointInfo = g_ptr_array_index(pDeviceArray,j); + g_assert_nonnull(pCurEndpointInfo); + if (pCurEndpointInfo->endpointID == in_endpointID) { + pEndpointInfo = pCurEndpointInfo; break; } } - return index; + + return pEndpointInfo; +} + +static int ReplaceEndpointInfoWithRole(endpointID_t in_endpointID, EndpointTypeT in_endpointType, RoleInfoT * in_pRole, EndpointInfoT * in_pNewEndpoint) +{ + GPtrArray * pDeviceArray = NULL; + if (in_endpointType == ENDPOINTTYPE_SOURCE){ + pDeviceArray = in_pRole->pSourceEndpoints; + } + else { + pDeviceArray = in_pRole->pSinkEndpoints; + } + g_assert_nonnull(pDeviceArray); + + for (int j = 0; j < pDeviceArray->len; j++) { + EndpointInfoT * pCurEndpointInfo = g_ptr_array_index(pDeviceArray,j); + g_assert_nonnull(pCurEndpointInfo); + if (pCurEndpointInfo->endpointID == in_endpointID) { + g_ptr_array_insert(pDeviceArray,j,in_pNewEndpoint); + g_ptr_array_remove_index(pDeviceArray,j+1); + TermEndpointInfo(pCurEndpointInfo); + // GLib automatically frees item upon array removal + return AHL_SUCCESS; + } + } + + return AHL_FAIL; } static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT in_endpointType) { EndpointInfoT * pEndpointInfo = NULL; - for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) + + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, g_AHLCtx.policyCtx.pRoleInfo); + while (pEndpointInfo == NULL && g_hash_table_iter_next (&iter, &key, &value)) { - GArray * pRoleDeviceArray = NULL; - if (in_endpointType == ENDPOINTTYPE_SOURCE){ - pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, i ); - } - else{ - pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, i ); - } - for (int j = 0; j < pRoleDeviceArray->len; j++) { - EndpointInfoT * pCurEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); - if (pCurEndpointInfo->endpointID == in_endpointID) { - pEndpointInfo = pCurEndpointInfo; - break; - } - } + RoleInfoT * pRoleInfo = (RoleInfoT*)value; + pEndpointInfo = GetEndpointInfoWithRole(in_endpointID,in_endpointType,pRoleInfo); } + return pEndpointInfo; } -static StreamInfoT * GetActiveStream(streamID_t in_streamID) +static StreamInfoT * GetStream(streamID_t in_streamID) { - int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; - StreamInfoT * pStreamInfo = NULL; - for ( int i = 0; i < iNumActiveStreams ; i++ ) { - StreamInfoT * pCurStreamInfo = &g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); - if (pCurStreamInfo->streamID == in_streamID){ - pStreamInfo = pCurStreamInfo; - break; - } - } - return pStreamInfo; + if (g_AHLCtx.policyCtx.pStreams == NULL) + return NULL; + + return g_hash_table_lookup(g_AHLCtx.policyCtx.pStreams,GINT_TO_POINTER(&in_streamID)); +} + +static RoleInfoT * GetRole(char * in_pAudioRoleName) +{ + if (g_AHLCtx.policyCtx.pRoleInfo == NULL) + return NULL; + + return g_hash_table_lookup(g_AHLCtx.policyCtx.pRoleInfo,in_pAudioRoleName); } static AHLClientCtxT * AllocateClientContext() { AHLClientCtxT * pClientCtx = malloc(sizeof(AHLClientCtxT)); - pClientCtx->pEndpointAccessList = g_array_new(FALSE, TRUE, sizeof(endpointID_t)); pClientCtx->pStreamAccessList = g_array_new(FALSE, TRUE, sizeof(streamID_t)); return pClientCtx; } @@ -130,31 +277,50 @@ static AHLClientCtxT * AllocateClientContext() static void TerminateClientContext(void * ptr) { AHLClientCtxT * pClientCtx = (AHLClientCtxT *) ptr; - g_array_free( pClientCtx->pEndpointAccessList, TRUE); - g_array_free( pClientCtx->pStreamAccessList, TRUE); - free(ptr); -} + if (pClientCtx != NULL) { -static int CheckStreamAccessControl(AHLClientCtxT * pClientCtx, streamID_t streamID) -{ - int iAccessControl = AHL_ACCESS_CONTROL_DENIED; - for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { - streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); - if (iID == streamID) { - iAccessControl = AHL_ACCESS_CONTROL_GRANTED; - } + // Avoid having policy in bad state if client loses WS connection (e.g. app termination without close stream) + // Force close streams in those cases. + + if (pClientCtx->pStreamAccessList != NULL) { +#ifndef AHL_DISCONNECT_POLICY + for (int i = 0; i < pClientCtx->pStreamAccessList->len; i++) + { + streamID_t streamID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); + // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions + StreamInfoT * pStreamInfo = GetStream(streamID); + if (pStreamInfo == NULL) { + AFB_ERROR("Specified stream not currently active stream_id -> %d",streamID); + return; + } + + json_object *pPolicyStreamJ = NULL; + int err = PolicyStreamStructToJSON(pStreamInfo, &pPolicyStreamJ); + if (err == AHL_FAIL) + { + AFB_ERROR("Audio policy violation, Unable to get JSON object for Policy_CloseStream"); + return; + } + Policy_CloseStream(pPolicyStreamJ); + } +#endif + g_array_free( pClientCtx->pStreamAccessList, TRUE); + pClientCtx->pStreamAccessList = NULL; + } + free(pClientCtx); } - return iAccessControl; } -static int CheckEndpointAccessControl(AHLClientCtxT * pClientCtx, endpointID_t endpointID) +static int CheckStreamAccessControl(AHLClientCtxT * pClientCtx, streamID_t streamID) { int iAccessControl = AHL_ACCESS_CONTROL_DENIED; - for (int i = 0; i < pClientCtx->pEndpointAccessList->len ; i++) { - endpointID_t iID = g_array_index(pClientCtx->pEndpointAccessList,endpointID_t,i); - if (iID == endpointID) { - iAccessControl = AHL_ACCESS_CONTROL_GRANTED; - } + if (pClientCtx && pClientCtx->pStreamAccessList) { + for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { + streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); + if (iID == streamID) { + iAccessControl = AHL_ACCESS_CONTROL_GRANTED; + } + } } return iAccessControl; } @@ -167,21 +333,21 @@ static int CreateEvents() err = !afb_event_is_valid(g_AHLCtx.policyCtx.propertyEvent); if (err) { AFB_ERROR("Could not create endpoint property change event"); - err++; + return err; } g_AHLCtx.policyCtx.volumeEvent = afb_daemon_make_event(AHL_ENDPOINT_VOLUME_EVENT); err = !afb_event_is_valid(g_AHLCtx.policyCtx.volumeEvent); if (err) { AFB_ERROR("Could not create endpoint volume change event"); - err++; + return err; } - g_AHLCtx.policyCtx.postEvent = afb_daemon_make_event(AHL_POST_EVENT); - err = !afb_event_is_valid(g_AHLCtx.policyCtx.postEvent); + g_AHLCtx.policyCtx.postActionEvent = afb_daemon_make_event(AHL_POST_ACTION_EVENT); + err = !afb_event_is_valid(g_AHLCtx.policyCtx.postActionEvent); if (err) { - AFB_ERROR("Could not create post event call event"); - err++; + AFB_ERROR("Could not create post action event call event"); + return err; } return err; @@ -189,90 +355,205 @@ static int CreateEvents() static void AhlBindingTerm() { +#ifndef AHL_DISCONNECT_POLICY // Policy termination Policy_Term(); - - // Events - for (int i = 0; i < g_AHLCtx.policyCtx.pEventList->len; i++) - { - // For each event within the role - GArray * pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, i ); - for (int j = 0 ; j < pRoleEventArray->len; j++) +#endif + + // Roles + if (g_AHLCtx.policyCtx.pRoleInfo != NULL) { + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init(&iter, g_AHLCtx.policyCtx.pRoleInfo); + while (g_hash_table_iter_next (&iter, &key, &value)) { - GString * gsEventName = &g_array_index(pRoleEventArray,GString,j); - g_string_free(gsEventName,TRUE); + RoleInfoT * pRole = (RoleInfoT *)value; + if (pRole) + { + if(pRole->pRoleName) { + g_free(pRole->pRoleName); + pRole->pRoleName = NULL; + } + // Actions + if (pRole->pActionList) { + for (int i = 0; i < pRole->pActionList->len; i++) + { + g_ptr_array_remove_index( pRole->pActionList, i ); // Free char * is called by GLib + } + } + // Source endpoints + if (pRole->pSourceEndpoints) { + for (int i = 0; i < pRole->pSourceEndpoints->len; i++) + { + EndpointInfoT * pEndpoint = g_ptr_array_remove_index( pRole->pSourceEndpoints, i ); // Free endpoint * is called by GLib + if (pEndpoint) { + TermEndpointInfo(pEndpoint); + } + } + } + // Sink endpoints + if (pRole->pSinkEndpoints) { + for (int i = 0; i < pRole->pSinkEndpoints->len; i++) + { + EndpointInfoT * pEndpoint = g_ptr_array_remove_index( pRole->pSinkEndpoints, i ); // Free endpoint * is called by GLib + if (pEndpoint) { + TermEndpointInfo(pEndpoint); + } + } + } + free(pRole); + } } - g_array_free(pRoleEventArray,TRUE); - pRoleEventArray = NULL; - } - g_ptr_array_free(g_AHLCtx.policyCtx.pEventList,TRUE); - g_AHLCtx.policyCtx.pEventList = NULL; - - // Endpoints - TermEndpoints(); - - // TODO: Need to free g_strings in HAL list - g_array_free(g_AHLCtx.pHALList,TRUE); - g_AHLCtx.pHALList = NULL; - g_hash_table_remove_all(g_AHLCtx.policyCtx.pRolePriority); - g_hash_table_destroy(g_AHLCtx.policyCtx.pRolePriority); - g_AHLCtx.policyCtx.pRolePriority = NULL; - // TODO: Need to free g_strings in audio roles list - g_array_free(g_AHLCtx.policyCtx.pAudioRoles,TRUE); - g_AHLCtx.policyCtx.pAudioRoles = NULL; - g_array_free(g_AHLCtx.policyCtx.pInterruptBehavior,TRUE); - g_AHLCtx.policyCtx.pInterruptBehavior = NULL; - g_array_free(g_AHLCtx.policyCtx.pActiveStreams,TRUE); - g_AHLCtx.policyCtx.pActiveStreams = NULL; - - AFB_INFO("Audio high-level Binding succesTermination"); + g_hash_table_remove_all(g_AHLCtx.policyCtx.pRoleInfo); + g_hash_table_destroy(g_AHLCtx.policyCtx.pRoleInfo); + g_AHLCtx.policyCtx.pRoleInfo = NULL; + } + + if (g_AHLCtx.policyCtx.pStreams) { + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, g_AHLCtx.policyCtx.pStreams); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if (value) + free(value); + } + g_hash_table_remove_all(g_AHLCtx.policyCtx.pStreams); + g_hash_table_destroy(g_AHLCtx.policyCtx.pStreams); + g_AHLCtx.policyCtx.pStreams = NULL; + } + + if (g_AHLCtx.policyCtx.pHALList) { + g_ptr_array_free(g_AHLCtx.policyCtx.pHALList,TRUE); + g_AHLCtx.policyCtx.pHALList = NULL; + } + + AFB_INFO("Audio high-level binding termination success"); } // Binding initialization PUBLIC int AhlBindingInit() { - int errcount = 0; + int err = 0; + memset(&g_AHLCtx,0,sizeof(g_AHLCtx)); + + // Register exit function atexit(AhlBindingTerm); - // This API uses services from low level audio. TODO: This dependency can be removed. - errcount = afb_daemon_require_api_v2("alsacore",1) ; - if( errcount != 0 ) + //Create AGL Events + err=CreateEvents(); + if(err) { - AFB_ERROR("Audio high level API requires alsacore API to be available"); - return 1; + //Error messages already reported inside CreateEvents + return err; } - errcount += CreateEvents(); - // Parse high-level binding JSON configuration file (will build device lists) - errcount += ParseHLBConfig(); - + err = ParseHLBConfig(); + if(err) + { + //Error messages already reported inside ParseHLBConfig + return err; + } + +#ifndef AHL_DISCONNECT_POLICY // Policy initialization - errcount += Policy_Init(); + err = Policy_Init(); + if(err == AHL_POLICY_REJECT) + { + //Error messages already reported inside PolicyInit + return err; + } + + // for all audio Roles + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, g_AHLCtx.policyCtx.pRoleInfo); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + RoleInfoT * pRoleInfo = (RoleInfoT*)value; + if (pRoleInfo->pSourceEndpoints){ + // for all source endpoints + for (int j = 0; j < pRoleInfo->pSourceEndpoints->len; j++) { + EndpointInfoT * pCurEndpointInfo = g_ptr_array_index(pRoleInfo->pSourceEndpoints,j); + g_assert_nonnull(pCurEndpointInfo); + json_object *pPolicyEndpointJ = NULL; + err = PolicyEndpointStructToJSON(pCurEndpointInfo, &pPolicyEndpointJ); + if (err == AHL_FAIL) + { + AFB_ERROR("Unable to Create Endpoint Json object error:%s ",wrap_json_get_error_string(err)); + return err; + } + else + { + err = Policy_Endpoint_Init(pPolicyEndpointJ); + if (err == AHL_POLICY_REJECT) { + AFB_ERROR("Policy endpoint properties initalization failed for endpoint_id :%d type:%d",pCurEndpointInfo->endpointID, pCurEndpointInfo->type); + } + //free pPolicyEndpointJ + json_object_put(pPolicyEndpointJ); + } + } + } + if (pRoleInfo->pSinkEndpoints){ + // for all sink endpoints + for (int j = 0; j < pRoleInfo->pSinkEndpoints->len; j++) { + EndpointInfoT * pCurEndpointInfo = g_ptr_array_index(pRoleInfo->pSinkEndpoints,j); + g_assert_nonnull(pCurEndpointInfo); + json_object *pPolicyEndpointJ = NULL; + err = PolicyEndpointStructToJSON(pCurEndpointInfo, &pPolicyEndpointJ); + if (err == AHL_FAIL) + { + AFB_ERROR("Unable to Create Endpoint Json object error:%s ",wrap_json_get_error_string(err)); + return err; + } + else + { + err = Policy_Endpoint_Init(pPolicyEndpointJ); + if (err== AHL_POLICY_REJECT) { + AFB_ERROR("Policy endpoint properties initalization failed for endpoint_id :%d type:%d",pCurEndpointInfo->endpointID, pCurEndpointInfo->type); + } + //free pPolicyEndpointJ + json_object_put(pPolicyEndpointJ); + } + + } + } + } +#endif // Initialize list of active streams - g_AHLCtx.iNumActiveClients = 0; - g_AHLCtx.policyCtx.pActiveStreams = g_array_new(FALSE,TRUE,sizeof(StreamInfoT)); + g_AHLCtx.policyCtx.pStreams = g_hash_table_new(g_int_hash, g_int_equal); + if(g_AHLCtx.policyCtx.pStreams == NULL) + { + AFB_ERROR("Unable to create Active Stream List"); + return err; + } // TODO: Use AGL persistence framework to retrieve and set initial volumes/properties - AFB_DEBUG("Audio high-level Binding success errcount=%d", errcount); - return errcount; + AFB_DEBUG("Audio high-level Binding success"); + return err; } PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ) { AFB_DEBUG("AHL received event %s", evtname); + + // TODO: Handle event from the policy to update internal information (currently not possible since within the same binding) - //forward to policy to handle events +#ifndef AHL_DISCONNECT_POLICY + // Temp: currently forward to policy to handle events (they will be received directly when disconnected into separate binding) Policy_OnEvent(evtname, eventJ); +#endif } PUBLIC void audiohlapi_get_sources(struct afb_req req) { - json_object *sourcesJ = NULL; - json_object *sourceJ = NULL; + json_object *devicesJ = NULL; + json_object *deviceJ = NULL; json_object *queryJ = NULL; char * audioRole = NULL; @@ -284,36 +565,40 @@ PUBLIC void audiohlapi_get_sources(struct afb_req req) } AFB_DEBUG("Filtering devices according to specified audio role=%s", audioRole); - // List device only for current audio role and device type (pick the role device list) - int iRoleIndex = FindRoleIndex(audioRole); - if ( iRoleIndex < 0) + + RoleInfoT * pRole = GetRole(audioRole); + if ( pRole == NULL ) { afb_req_fail_f(req, "Invalid arguments", "Requested audio role does not exist in current configuration -> %s", json_object_get_string(queryJ)); return; } else { - sourcesJ = json_object_new_array(); - GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, iRoleIndex ); - int iNumberDevices = pRoleSourceDeviceArray->len; - for ( int j = 0 ; j < iNumberDevices; j++) - { - EndpointInfoT sourceInfo = g_array_index(pRoleSourceDeviceArray,EndpointInfoT,j); - EndpointInfoStructToJSON(&sourceJ, &sourceInfo); - json_object_array_add(sourcesJ, sourceJ); + devicesJ = json_object_new_array(); + GPtrArray * pDeviceArray = pRole->pSourceEndpoints; + if (pDeviceArray) { + int iNumberDevices = pDeviceArray->len; + for ( int j = 0 ; j < iNumberDevices; j++) + { + EndpointInfoT * pEndpointInfo = g_ptr_array_index(pDeviceArray,j); + if (pEndpointInfo) { + EndpointInfoStructToJSON(&deviceJ, pEndpointInfo); + json_object_array_add(devicesJ, deviceJ); + } + } } } - afb_req_success(req, sourcesJ, "List of sources"); + afb_req_success(req, devicesJ, "List of sources"); } PUBLIC void audiohlapi_get_sinks(struct afb_req req) { - json_object *sinksJ = NULL; - json_object *sinkJ = NULL; + json_object *devicesJ = NULL; + json_object *deviceJ = NULL; json_object *queryJ = NULL; char * audioRole = NULL; - + queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:s}", "audio_role", &audioRole); if (err) { @@ -322,136 +607,162 @@ PUBLIC void audiohlapi_get_sinks(struct afb_req req) } AFB_DEBUG("Filtering devices according to specified audio role=%s", audioRole); - // List device only for current audio role and device type (pick the role device list) - int iRoleIndex = FindRoleIndex(audioRole); - if ( iRoleIndex < 0) + + RoleInfoT * pRole = GetRole(audioRole); + if ( pRole == NULL ) { afb_req_fail_f(req, "Invalid arguments", "Requested audio role does not exist in current configuration -> %s", json_object_get_string(queryJ)); return; } else { - sinksJ = json_object_new_array(); - GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, iRoleIndex ); - int iNumberDevices = pRoleSinkDeviceArray->len; - for ( int j = 0 ; j < iNumberDevices; j++) - { - EndpointInfoT sinkInfo = g_array_index(pRoleSinkDeviceArray,EndpointInfoT,j); - EndpointInfoStructToJSON(&sinkJ, &sinkInfo); - json_object_array_add(sinksJ, sinkJ); + devicesJ = json_object_new_array(); + GPtrArray * pDeviceArray = pRole->pSinkEndpoints; + if (pDeviceArray) { + int iNumberDevices = pDeviceArray->len; + for ( int j = 0 ; j < iNumberDevices; j++) + { + EndpointInfoT * pEndpointInfo = g_ptr_array_index(pDeviceArray,j); + EndpointInfoStructToJSON(&deviceJ, pEndpointInfo); + json_object_array_add(devicesJ, deviceJ); + } } } - afb_req_success(req, sinksJ, "List of sinks"); + afb_req_success(req, devicesJ, "List of sinks"); } PUBLIC void audiohlapi_stream_open(struct afb_req req) { json_object *streamInfoJ = NULL; - StreamInfoT streamInfo; + StreamInfoT * pStreamInfo = NULL; json_object *queryJ = NULL; char * audioRole = NULL; + char * endpointTypeStr = NULL; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; endpointID_t endpointID = AHL_UNDEFINED; - int policyAllowed = AHL_POLICY_REJECT; EndpointInfoT * pEndpointInfo = NULL; + EndpointSelectionModeT endpointSelMode = ENDPOINTSELMODEMAXVALUE; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:s,s:i,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointType, "endpoint_id", &endpointID); + int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointTypeStr, "endpoint_id", &endpointID); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = audio_role:%s endpoint_type:%d endpoint_id:%d", audioRole,endpointType,endpointID); + AFB_DEBUG("Parsed input arguments = audio_role:%s endpoint_type:%s endpoint_id:%d", audioRole,endpointTypeStr,endpointID); + endpointType = EndpointTypeToEnum(endpointTypeStr); // Check if there is already an existing context for this client AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure if (pClientCtx == NULL) { - g_AHLCtx.iNumActiveClients++; pClientCtx = AllocateClientContext(); afb_req_context_set(req, pClientCtx, TerminateClientContext); } - int iRoleIndex = FindRoleIndex(audioRole); - if (iRoleIndex < 0) { + RoleInfoT * pRole = GetRole(audioRole); + if ( pRole == NULL ) + { afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole); return; } - GArray * pRoleDeviceArray = NULL; + GPtrArray * pDeviceArray = NULL; if (endpointType == ENDPOINTTYPE_SOURCE){ - pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, iRoleIndex ); + pDeviceArray = pRole->pSourceEndpoints; } else{ - pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, iRoleIndex ); + pDeviceArray = pRole->pSinkEndpoints; } - if (pRoleDeviceArray->len == 0) { - afb_req_fail_f(req, "No available devices", "No available devices for role:%s and device type:%d",audioRole,endpointType); + if (pDeviceArray == NULL || pDeviceArray->len == 0) { + afb_req_fail_f(req, "No available devices", "No available devices for role:%s and device type:%s",audioRole,endpointTypeStr); return; } if (endpointID == AHL_UNDEFINED) { // Assign a device based on configuration priority (first in the list for requested role and endpoint type) - pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,0); - streamInfo.endpointSelMode = AHL_ENDPOINTSELMODE_AUTO; + pEndpointInfo = g_ptr_array_index(pDeviceArray,0); + endpointSelMode = ENDPOINTSELMODE_AUTO; + } else{ - streamInfo.endpointSelMode = AHL_ENDPOINTSELMODE_MANUAL; + endpointSelMode = ENDPOINTSELMODE_MANUAL; // Find specified endpoint ID in list of devices - int iNumberDevices = pRoleDeviceArray->len; + int iNumberDevices = pDeviceArray->len; for ( int j = 0 ; j < iNumberDevices; j++) { - pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); - if (pEndpointInfo->endpointID == endpointID) { + pEndpointInfo = g_ptr_array_index(pDeviceArray,j); + if (pEndpointInfo && pEndpointInfo->endpointID == endpointID) { break; } + pEndpointInfo = NULL; } - if (pEndpointInfo == NULL) { - afb_req_fail_f(req, "Endpoint not available", "Specified endpoint not available for role:%s and device type:%d endpoint id %d",audioRole,endpointType,endpointID); - return; - } + } - // Create stream - streamInfo.streamID = CreateNewStreamID(); // create new ID - streamInfo.streamState = STREAM_STATE_IDLE; - streamInfo.streamMute = STREAM_UNMUTED; - streamInfo.pEndpointInfo = pEndpointInfo; + if (pEndpointInfo == NULL) { + afb_req_fail_f(req, "Endpoint not available", "Specified endpoint not available for role:%s and device type:%d endpoint id %d",audioRole,endpointType,endpointID); + return; + } + pStreamInfo = (StreamInfoT*) malloc(sizeof(StreamInfoT)); + memset(pStreamInfo,0,sizeof(StreamInfoT)); + + // Create stream + pStreamInfo->streamID = CreateNewStreamID(); // create new ID + pStreamInfo->streamState = STREAM_STATE_IDLE; + pStreamInfo->streamMute = STREAM_UNMUTED; + pStreamInfo->pEndpointInfo = pEndpointInfo; + pStreamInfo->endpointSelMode = endpointSelMode; + // Directly from role config for now, but could be programmatically overriden in the future + pStreamInfo->pRoleName = pRole->pRoleName; + pStreamInfo->iPriority = pRole->iPriority; + pStreamInfo->eInterruptBehavior = pRole->eInterruptBehavior; + +#ifndef AHL_DISCONNECT_POLICY // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions - policyAllowed = Policy_OpenStream(&streamInfo); + json_object *pPolicyStreamJ = NULL; + err = PolicyStreamStructToJSON(pStreamInfo, &pPolicyStreamJ); + if (err == AHL_FAIL) + { + afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_OpenStream"); + return; + } + + int policyAllowed = Policy_OpenStream(pPolicyStreamJ); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Open stream not allowed in current context"); return; - } + } +#endif char streamEventName[128]; - snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); + snprintf(streamEventName,128,"ahl_streamstate_%d",pStreamInfo->streamID); - streamInfo.streamStateEvent = afb_daemon_make_event(streamEventName); - err = !afb_event_is_valid(streamInfo.streamStateEvent); + pStreamInfo->streamStateEvent = afb_daemon_make_event(streamEventName); + err = !afb_event_is_valid(pStreamInfo->streamStateEvent); if (err) { afb_req_fail(req, "Stream event creation failure", "Could not create stream specific state change event"); return; } - err = afb_req_subscribe(req,streamInfo.streamStateEvent); + err = afb_req_subscribe(req,pStreamInfo->streamStateEvent); if (err) { afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event"); return; } - // Add to client context stream ID and endpoint ID access rights - g_array_append_val(pClientCtx->pStreamAccessList, streamInfo.streamID); - g_array_append_val(pClientCtx->pEndpointAccessList, streamInfo.pEndpointInfo->endpointID); + // Add to client context stream ID access rights + g_array_append_val(pClientCtx->pStreamAccessList, pStreamInfo->streamID); // Push stream on active stream list - g_array_append_val( g_AHLCtx.policyCtx.pActiveStreams, streamInfo ); + if (g_AHLCtx.policyCtx.pStreams) + g_hash_table_insert( g_AHLCtx.policyCtx.pStreams, GINT_TO_POINTER(&pStreamInfo->streamID), pStreamInfo ); - StreamInfoStructToJSON(&streamInfoJ,&streamInfo); + StreamInfoStructToJSON(&streamInfoJ,pStreamInfo); afb_req_success(req, streamInfoJ, "Stream info structure"); } @@ -460,7 +771,6 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) { json_object *queryJ = NULL; streamID_t streamID = AHL_UNDEFINED; - int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID); @@ -470,11 +780,18 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) } AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); + + StreamInfoT * pStreamInfo = GetStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + // Check if there is already an existing context for this client AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure if (pClientCtx == NULL) { - afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + afb_req_fail(req, "Bad state", "No client context associated with the request (is there an opened stream by this client?)"); return; } @@ -486,67 +803,55 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } - // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions - StreamInfoT * pStreamInfo = GetActiveStream(streamID); - if (pStreamInfo == NULL) { - afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); +#ifndef AHL_DISCONNECT_POLICY + json_object *pPolicyStreamJ = NULL; + err = PolicyStreamStructToJSON(pStreamInfo, &pPolicyStreamJ); + if (err == AHL_FAIL) + { + afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_CloseStream"); return; - } - - policyAllowed = Policy_CloseStream(pStreamInfo); + } + // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions + int policyAllowed = Policy_CloseStream(pPolicyStreamJ); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Close stream not allowed in current context"); return; } +#endif - // Remove from active stream list (if present) - int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; - int iStreamFound = 0; - for ( int i = 0; i < iNumActiveStreams ; i++ ) { - StreamInfoT streamInfo = g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); - if (streamInfo.streamID == streamID){ - // Unsubscribe client from stream events - char streamEventName[128]; - snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); - int iValid = afb_event_is_valid(streamInfo.streamStateEvent); - if (iValid) { - err = afb_req_unsubscribe(req,streamInfo.streamStateEvent); - if (err) { - afb_req_fail(req, "Stream event subscription failure", "Could not unsubscribe to stream specific state change event"); - return; - } - } - else{ - AFB_WARNING("Stream event no longer valid and therefore not unsubscribed"); - break; - } - - g_array_remove_index(g_AHLCtx.policyCtx.pActiveStreams,i); - iStreamFound = 1; - break; + // Unsubscribe client from stream events + char streamEventName[128]; + snprintf(streamEventName,128,"ahl_streamstate_%d",streamID); + int iValid = afb_event_is_valid(pStreamInfo->streamStateEvent); + if (iValid) { + err = afb_req_unsubscribe(req,pStreamInfo->streamStateEvent); + if (err) { + afb_req_fail(req, "Stream event subscription failure", "Could not unsubscribe to stream specific state change event"); + return; } } - if (iStreamFound == 0) { - afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); - return; - } + // Remove from stream list (if present) + if (g_AHLCtx.policyCtx.pStreams) + g_hash_table_remove(g_AHLCtx.policyCtx.pStreams,GINT_TO_POINTER(&pStreamInfo->streamID)); + free(pStreamInfo); + pStreamInfo = NULL; // Find index for cases where there are multiple streams per client // Remove from client context stream ID and endpoint ID access rights - for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { - streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); - if (iID == streamID) { - g_array_remove_index(pClientCtx->pStreamAccessList, i); - g_array_remove_index(pClientCtx->pEndpointAccessList, i); - } - } + if (pClientCtx->pStreamAccessList) { + for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) { + streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i); + if (iID == streamID) { + g_array_remove_index(pClientCtx->pStreamAccessList, i); + } + } - if (pClientCtx->pStreamAccessList->len == 0 && pClientCtx->pEndpointAccessList == 0) { - // If no more streams/endpoints owner, clear session - afb_req_context_clear(req); - g_AHLCtx.iNumActiveClients--; + if (pClientCtx->pStreamAccessList->len == 0) { + // If no more streams/endpoints owner, clear session + afb_req_context_clear(req); + } } afb_req_success(req, NULL, "Stream close completed"); @@ -556,20 +861,19 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) { json_object *queryJ = NULL; streamID_t streamID = AHL_UNDEFINED; - StreamStateT streamState = STREAM_STATUS_MAXVALUE; - int policyAllowed = AHL_POLICY_REJECT; + char * streamStateStr = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "stream_id", &streamID,"state",&streamState); + int err = wrap_json_unpack(queryJ, "{s:i,s:s}", "stream_id", &streamID,"state",&streamStateStr); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = stream_id:%d, state:%d", streamID,streamState); - - StreamInfoT * pStreamInfo = GetActiveStream(streamID); + AFB_DEBUG("Parsed input arguments = stream_id:%d, state:%s", streamID,streamStateStr); + + StreamInfoT * pStreamInfo = GetStream(streamID); if (pStreamInfo == NULL) { - afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + afb_req_fail_f(req, "Stream not found", "Specified stream not found stream_id -> %d",streamID); return; } @@ -577,7 +881,7 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure if (pClientCtx == NULL) { - afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + afb_req_fail(req, "Bad state", "No client context associated with the request (is there an opened stream by this client?)"); return; } @@ -588,14 +892,30 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) afb_req_fail(req, "Access control denied", "Set stream state not allowed in current client context"); return; } - int AudioRoleIndex = FindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); - policyAllowed = Policy_SetStreamState(pStreamInfo, AudioRoleIndex, streamState); + StreamStateT streamState = StreamStateToEnum(streamStateStr); +#ifndef AHL_DISCONNECT_POLICY + json_object *pPolicyStreamJ = NULL; + err = PolicyStreamStructToJSON(pStreamInfo, &pPolicyStreamJ); + if (err == AHL_FAIL) + { + afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_SetStreamState"); + return; + } + + json_object *paramJ= json_object_new_int(streamState); + json_object_object_add(pPolicyStreamJ, "arg_stream_state", paramJ); + + int policyAllowed = Policy_SetStreamState(pPolicyStreamJ); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Change stream state not allowed in current context"); return; } +#else + // Simulate that policy returns target state (accepted) + pStreamInfo->streamState = streamState; +#endif afb_req_success(req, NULL, "Set stream state"); } @@ -604,18 +924,17 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) { json_object *queryJ = NULL; streamID_t streamID = AHL_UNDEFINED; - StreamMuteT muteState = STREAM_MUTE_MAXVALUE; - int policyAllowed = AHL_POLICY_REJECT; + char * pMuteStr = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "stream_id", &streamID,"mute",&muteState); + int err = wrap_json_unpack(queryJ, "{s:i,s:s}", "stream_id", &streamID,"mute",&pMuteStr); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = stream_id:%d, mute:%d", streamID,muteState); + AFB_DEBUG("Parsed input arguments = stream_id:%d, mute:%s", streamID,pMuteStr); - StreamInfoT * pStreamInfo = GetActiveStream(streamID); + StreamInfoT * pStreamInfo = GetStream(streamID); if (pStreamInfo == NULL) { afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); return; @@ -625,7 +944,7 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure if (pClientCtx == NULL) { - afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + afb_req_fail(req, "Bad state", "No client context associated with the request (is there an opened stream by this client?)"); return; } @@ -637,12 +956,30 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) return; } - policyAllowed = Policy_SetStreamMute(pStreamInfo,muteState); + + StreamMuteT muteState = StreamMuteToEnum(pMuteStr); +#ifndef AHL_DISCONNECT_POLICY + json_object *pPolicyStreamJ = NULL; + err = PolicyStreamStructToJSON(pStreamInfo, &pPolicyStreamJ); + if (err == AHL_FAIL) + { + afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_SetStreamMute"); + return; + } + + json_object *paramJ= json_object_new_int(muteState); + json_object_object_add(pPolicyStreamJ, "mute_state", paramJ); + + int policyAllowed = Policy_SetStreamMute(pPolicyStreamJ); if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Mute stream not allowed in current context"); return; } +#else + // Simulate that policy returns target state (accepted) + pStreamInfo->streamMute = muteState; +#endif afb_req_success(req, NULL, "Set stream mute completed"); } @@ -661,7 +998,7 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) } AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); - StreamInfoT * pStreamInfo = GetActiveStream(streamID); + StreamInfoT * pStreamInfo = GetStream(streamID); if (pStreamInfo == NULL) { afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); return; @@ -676,17 +1013,18 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) { json_object *queryJ = NULL; endpointID_t endpointID = AHL_UNDEFINED; + char * pEndpointTypeStr = NULL; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * volumeStr = NULL; - int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"volume",&volumeStr); + int err = wrap_json_unpack(queryJ, "{s:s,s:i,s:s}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID,"volume",&volumeStr); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s", endpointType,endpointID,volumeStr); + AFB_DEBUG("Parsed input arguments = endpoint_type:%s endpoint_id:%d volume:%s", pEndpointTypeStr,endpointID,volumeStr); + endpointType = EndpointTypeToEnum(pEndpointTypeStr); EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) @@ -695,28 +1033,28 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) return; } - // Check if there is already an existing context for this client - AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure - if (pClientCtx == NULL) +#ifndef AHL_DISCONNECT_POLICY + json_object *pPolicyEndpointJ = NULL; + err = PolicyEndpointStructToJSON(pEndpointInfo, &pPolicyEndpointJ); + if (err == AHL_FAIL) { - afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_SetVolume"); return; - } + } - // Verify that this client can control the stream - int iEndpointAccessControl = CheckEndpointAccessControl( pClientCtx, endpointID ); - if (iEndpointAccessControl == AHL_ACCESS_CONTROL_DENIED) - { - afb_req_fail(req, "Access control denied", "Set volume not allowed in current client context"); - return; - } + json_object *paramJ= json_object_new_string(volumeStr); + json_object_object_add(pPolicyEndpointJ, "arg_volume", paramJ); - policyAllowed = Policy_SetVolume(pEndpointInfo, volumeStr); + int policyAllowed = Policy_SetVolume(pPolicyEndpointJ); if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context"); return; } +#else + // Simulate that policy returns target state (accepted) + pEndpointInfo->iVolume = atoi(volumeStr); +#endif afb_req_success(req, NULL, "Set volume completed"); } @@ -725,16 +1063,18 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) { json_object *queryJ = NULL; endpointID_t endpointID = AHL_UNDEFINED; + char * pEndpointTypeStr = NULL; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; json_object * volumeJ = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); + int err = wrap_json_unpack(queryJ, "{s:s,s:i}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); + AFB_DEBUG("Parsed input arguments = endpoint_type:%s endpoint_id:%d", pEndpointTypeStr,endpointID); + endpointType = EndpointTypeToEnum(pEndpointTypeStr); EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) @@ -743,26 +1083,27 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) return; } - volumeJ = json_object_new_double((double)pEndpointInfo->iVolume); + volumeJ = json_object_new_int(pEndpointInfo->iVolume); afb_req_success(req, volumeJ, "Retrieved volume value"); } // Properties -PUBLIC void audiohlapi_get_list_properties(struct afb_req req) +PUBLIC void audiohlapi_get_endpoint_info(struct afb_req req) { json_object *queryJ = NULL; endpointID_t endpointID = AHL_UNDEFINED; + char * pEndpointTypeStr = NULL; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; - json_object * endpointPropsJ = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); + int err = wrap_json_unpack(queryJ, "{s:s,s:i}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); + AFB_DEBUG("Parsed input arguments = endpoint_type:%s endpoint_id:%d", pEndpointTypeStr,endpointID); + endpointType = EndpointTypeToEnum(pEndpointTypeStr); EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) @@ -771,38 +1112,29 @@ PUBLIC void audiohlapi_get_list_properties(struct afb_req req) return; } - // Build and return list of properties for specific endpoint - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init (&iter, pEndpointInfo->pPropTable); - endpointPropsJ = json_object_new_array(); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - json_object * propJ = json_object_new_string(key); - json_object_array_add(endpointPropsJ, propJ); - } + json_object *endpointInfoJ = NULL; + EndpointInfoStructToJSON(&endpointInfoJ,pEndpointInfo); - afb_req_success(req, endpointPropsJ, "Retrieved property list for endpoint"); + afb_req_success(req, endpointInfoJ, "Retrieved endpoint information and properties"); } PUBLIC void audiohlapi_set_property(struct afb_req req) { json_object *queryJ = NULL; endpointID_t endpointID = AHL_UNDEFINED; + char * pEndpointTypeStr = NULL; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; json_object * propValueJ = NULL; - int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:o}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueJ); + int err = wrap_json_unpack(queryJ, "{s:s,s:i,s:s,s:o}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueJ); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); - + AFB_DEBUG("Parsed input arguments = endpoint_type:%s endpoint_id:%d property_name:%s", pEndpointTypeStr,endpointID,propertyName); + endpointType = EndpointTypeToEnum(pEndpointTypeStr); EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) @@ -811,31 +1143,33 @@ PUBLIC void audiohlapi_set_property(struct afb_req req) return; } - // Check if there is already an existing context for this client - AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure - if (pClientCtx == NULL) +#ifndef AHL_DISCONNECT_POLICY + json_object *pPolicyEndpointJ = NULL; + err = PolicyEndpointStructToJSON(pEndpointInfo, &pPolicyEndpointJ); + if (err == AHL_FAIL) { - afb_req_fail(req, "No context associated with the request", "No context associated with the request"); + afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_SetVolume"); return; - } + } - // Verify that this client can control the stream - int iEndpointAccessControl = CheckEndpointAccessControl( pClientCtx, endpointID ); - if (iEndpointAccessControl == AHL_ACCESS_CONTROL_DENIED) - { - afb_req_fail(req, "Access control denied", "Set property not allowed in current client context"); - return; - } + json_object *paramJ= json_object_new_string(propertyName); + json_object_object_add(pPolicyEndpointJ, "arg_property_name", paramJ); + json_object_object_add(pPolicyEndpointJ, "arg_property_value", propValueJ); // Call policy to allow custom policy actions in current context - policyAllowed = Policy_SetProperty(pEndpointInfo, propertyName, propValueJ); + int policyAllowed = Policy_SetProperty(pPolicyEndpointJ); if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Set endpoint property not allowed in current context"); return; } +#else + // Simulate that policy returns target state (accepted) + if (pEndpointInfo->pPropTable) + g_hash_table_insert(pEndpointInfo->pPropTable, propertyName, propValueJ); +#endif - afb_event_push(g_AHLCtx.policyCtx.propertyEvent,queryJ); + //afb_event_push(g_AHLCtx.policyCtx.propertyEvent,queryJ); afb_req_success(req, NULL, "Set property completed"); } @@ -844,16 +1178,18 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) { json_object *queryJ = NULL; endpointID_t endpointID = AHL_UNDEFINED; + char * pEndpointTypeStr = NULL; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); + int err = wrap_json_unpack(queryJ, "{s:s,s:i,s:s}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID,"property_name",&propertyName); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); + AFB_DEBUG("Parsed input arguments = endpoint_type:%s endpoint_id:%d property_name:%s", pEndpointTypeStr,endpointID,propertyName); + endpointType = EndpointTypeToEnum(pEndpointTypeStr); EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) @@ -870,17 +1206,19 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) } json_object_get(propertyValJ); // Increase ref count so that framework does not free our JSON object + //AFB_WARNING("properties %s", json_object_get_string(propertyValJ)); + //json_object_get(propertyValJ); // Increase ref count so that framework does not free our JSON object afb_req_success(req, propertyValJ, "Retrieved property value"); } // Audio related events -PUBLIC void audiohlapi_get_list_events(struct afb_req req) +PUBLIC void audiohlapi_get_list_actions(struct afb_req req) { json_object *queryJ = NULL; char * audioRole = NULL; - json_object * roleEventsJ = NULL; + json_object * roleActionsJ = NULL; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:s}", "audio_role",&audioRole); @@ -890,83 +1228,85 @@ PUBLIC void audiohlapi_get_list_events(struct afb_req req) } AFB_DEBUG("Parsed input arguments = audio_role:%s",audioRole); - // Build and return list of events for specific audio role - int iRoleIndex = FindRoleIndex(audioRole); - if (iRoleIndex < 0) { + // Build and return list of actions for specific audio role + RoleInfoT * pRole = GetRole(audioRole); + if ( pRole == NULL ) + { afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole); return; } - GArray * pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, iRoleIndex ); - roleEventsJ = json_object_new_array(); - int iNumberEvents = pRoleEventArray->len; - for ( int i = 0 ; i < iNumberEvents; i++) - { - GString gsEventName = g_array_index(pRoleEventArray,GString,i); - json_object * eventJ = json_object_new_string(gsEventName.str); - json_object_array_add(roleEventsJ, eventJ); + roleActionsJ = json_object_new_array(); + if (pRole->pActionList) { + int iNumberActions = pRole->pActionList->len; + for ( int i = 0 ; i < iNumberActions; i++) + { + char * pActionName = g_ptr_array_index(pRole->pActionList,i); + json_object * actionJ = json_object_new_string(pActionName); + json_object_array_add(roleActionsJ, actionJ); + } } - afb_req_success(req, roleEventsJ, "Retrieved event list for audio role"); + afb_req_success(req, roleActionsJ, "Retrieved action list for audio role"); } -PUBLIC void audiohlapi_post_event(struct afb_req req) +PUBLIC void audiohlapi_post_action(struct afb_req req) { json_object *queryJ = NULL; - char * eventName = NULL; + char * actionName = NULL; char * audioRole = NULL; char * mediaName = NULL; - json_object *eventContext = NULL; - int policyAllowed = AHL_POLICY_REJECT; + json_object *actionContext = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"event_context",&eventContext); + int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?s,s?o}", "action_name", &actionName,"audio_role",&audioRole,"media_name",&mediaName,"action_context",&actionContext); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%s", eventName,audioRole); + AFB_DEBUG("Parsed input arguments = action_name:%s audio_role:%s", actionName,audioRole); - // Verify if known event for audio role - int iRoleIndex = FindRoleIndex(audioRole); - if (iRoleIndex < 0) { + // Verify if known action for audio role + RoleInfoT * pRole = GetRole(audioRole); + if ( pRole == NULL ) + { afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole); return; } - GArray * pRoleEventArray = NULL; - pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, iRoleIndex ); - if (pRoleEventArray->len == 0) { - afb_req_fail_f(req, "No available events", "No available events for role:%s",audioRole); - return; - } - // Check to find specific event - int iEventFound = 0; - for (int i = 0; i < pRoleEventArray->len; i++) - { - GString gs = g_array_index( pRoleEventArray, GString, i ); - if ( strcasecmp(gs.str,eventName) == 0 ) + // Check to find specific action + int iActionFound = 0; + if (pRole->pActionList) { + int iNumberActions = pRole->pActionList->len; + char * pTargetActionName = NULL; + for ( int i = 0 ; i < iNumberActions; i++) { - iEventFound = 1; - break; + pTargetActionName = g_ptr_array_index(pRole->pActionList,i); + if ( strcasecmp(pTargetActionName,actionName)==0) { + iActionFound = 1; + break; + } } } - if (!iEventFound) { - afb_req_fail_f(req, "Event not found for audio role", "Event not found for roke:%s",audioRole); + + if (!iActionFound) { + afb_req_fail_f(req, "Event not found for audio role", "Event -> %s not found for role:%s",actionName,audioRole); return; } +#ifndef AHL_DISCONNECT_POLICY // Call policy to allow custom policy actions in current context (e.g. cancel playback) - policyAllowed = Policy_PostEvent(eventName, audioRole, mediaName, (void*)eventContext); + int policyAllowed = Policy_PostAction(queryJ); if (!policyAllowed) { - afb_req_fail(req, "Audio policy violation", "Post sound event not allowed in current context"); + afb_req_fail(req, "Audio policy violation", "Post sound action not allowed in current context"); return; } +#endif - afb_event_push(g_AHLCtx.policyCtx.postEvent,queryJ); + //afb_event_push(g_AHLCtx.policyCtx.postEvent,queryJ); - afb_req_success(req, NULL, "Posted sound event"); + afb_req_success(req, NULL, "Posted sound action"); } @@ -989,7 +1329,11 @@ PUBLIC void audiohlapi_subscribe(struct afb_req req) char * pEventName = NULL; json_object * jEvent = json_object_array_get_idx(eventArrayJ,i); pEventName = (char *)json_object_get_string(jEvent); - if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { + if(pEventName == NULL) { + afb_req_fail(req, "failed", "Invalid event"); + return; + } + else if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { afb_req_subscribe(req, g_AHLCtx.policyCtx.propertyEvent); AFB_DEBUG("Client subscribed to endpoint property events"); } @@ -997,8 +1341,8 @@ PUBLIC void audiohlapi_subscribe(struct afb_req req) afb_req_subscribe(req, g_AHLCtx.policyCtx.volumeEvent); AFB_DEBUG("Client subscribed to endpoint volume events"); } - else if(!strcasecmp(pEventName, AHL_POST_EVENT)) { - afb_req_subscribe(req, g_AHLCtx.policyCtx.postEvent); + else if(!strcasecmp(pEventName, AHL_POST_ACTION_EVENT)) { + afb_req_subscribe(req, g_AHLCtx.policyCtx.postActionEvent); AFB_DEBUG("Client subscribed to post event calls events"); } else { @@ -1028,7 +1372,11 @@ PUBLIC void audiohlapi_unsubscribe(struct afb_req req) char * pEventName = NULL; json_object * jEvent = json_object_array_get_idx(eventArrayJ,i); pEventName = (char *)json_object_get_string(jEvent); - if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { + if(pEventName == NULL) { + afb_req_fail(req, "failed", "Invalid event"); + return; + } + else if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { afb_req_unsubscribe(req, g_AHLCtx.policyCtx.propertyEvent); AFB_DEBUG("Client unsubscribed to endpoint property events"); } @@ -1036,8 +1384,8 @@ PUBLIC void audiohlapi_unsubscribe(struct afb_req req) afb_req_unsubscribe(req, g_AHLCtx.policyCtx.volumeEvent); AFB_DEBUG("Client unsubscribed to endpoint volume events"); } - else if(!strcasecmp(pEventName, AHL_POST_EVENT)) { - afb_req_unsubscribe(req, g_AHLCtx.policyCtx.postEvent); + else if(!strcasecmp(pEventName, AHL_POST_ACTION_EVENT)) { + afb_req_unsubscribe(req, g_AHLCtx.policyCtx.postActionEvent); AFB_DEBUG("Client unsubscribed to post event calls events"); } else { @@ -1048,3 +1396,182 @@ PUBLIC void audiohlapi_unsubscribe(struct afb_req req) afb_req_success(req, NULL, "Unsubscribe to events finished"); } + +// Since the policy is currently in the same binding, it cannot raise events on its own +// This is a first step toward isolation, when policy is migrated in its own binding it can simply raise AGL events +// This binding will register for these policy events and will execute the code below upon event reception +PUBLIC void audiohlapi_raise_event(json_object * pEventDataJ) +{ + char * pEventName = NULL; + + int err = wrap_json_unpack(pEventDataJ,"{s:s}","event_name", &pEventName); + if(err) + { + AFB_ERROR("Unable to retrieve event name"); + return; + } + + if(strcasecmp(pEventName, AHL_ENDPOINT_INIT_EVENT)==0) { + char * pAudioRole = NULL; + endpointID_t endpointID = AHL_UNDEFINED; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + int err = wrap_json_unpack(pEventDataJ,"{s:i,s:i,s:s}", + "endpoint_id", &endpointID, + "endpoint_type", &endpointType, + "audio_role", &pAudioRole); + if(err) + { + AFB_ERROR("Unable to unpack property init event"); + return; + } + + RoleInfoT * pRole = GetRole(pAudioRole); + if ( pRole == NULL ){ + AFB_ERROR("Requested audio role does not exist in current configuration -> %s", pAudioRole); + return; + } + + EndpointInfoT * pEndpointInfo = InitEndpointInfo(); + g_assert_nonnull(pEndpointInfo); + PolicyCtxJSONToEndpoint(pEventDataJ,pEndpointInfo); + + err = ReplaceEndpointInfoWithRole(endpointID,endpointType,pRole,pEndpointInfo); + if(err == AHL_FAIL) + { + AFB_ERROR("Can't update EndpointInfo aborting"); + return; + } + + // Remove event name from object + // json_object_object_del(pEventDataJ,"event_name"); + //afb_event_push(g_AHLCtx.policyCtx.propertyEvent,pEventDataJ); // Not broadcasted to application at this time + } + else if(strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)==0) { + char * pAudioRole = NULL; + char * pPropertyName = NULL; + endpointID_t endpointID = AHL_UNDEFINED; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + json_object * propValueJ = NULL; + int err = wrap_json_unpack(pEventDataJ,"{s:i,s:i,s:s,s:o,s:s}", + "endpoint_id", &endpointID, + "endpoint_type", &endpointType, + "property_name", &pPropertyName, + "value",&propValueJ, + "audio_role", &pAudioRole); + if(err) + { + AFB_ERROR("Unable to unpack property event"); + return; + } + RoleInfoT * pRole = GetRole(pAudioRole); + if ( pRole == NULL ){ + AFB_ERROR("Requested audio role does not exist in current configuration -> %s", pAudioRole); + return; + } + EndpointInfoT * pEndpointInfo = GetEndpointInfoWithRole(endpointID,endpointType,pRole); + // update property value + if (pEndpointInfo->pPropTable) + { + json_type jType = json_object_get_type(propValueJ); + switch (jType) { + case json_type_double: + Add_Endpoint_Property_Double(pEndpointInfo,pPropertyName,json_object_get_double(propValueJ)); + break; + case json_type_int: + Add_Endpoint_Property_Int(pEndpointInfo,pPropertyName,json_object_get_int(propValueJ)); + break; + case json_type_string: + Add_Endpoint_Property_String(pEndpointInfo,pPropertyName,json_object_get_string(propValueJ)); + break; + default: + AFB_ERROR("Invalid property argument Property value not a valid json object query=%s", json_object_get_string(propValueJ)); + return ; + } + } + // Remove event name from object + json_object_object_del(pEventDataJ,"event_name"); + afb_event_push(g_AHLCtx.policyCtx.propertyEvent,pEventDataJ); + } + else if(strcasecmp(pEventName, AHL_ENDPOINT_VOLUME_EVENT)==0) { + char * pAudioRole = NULL; + endpointID_t endpointID = AHL_UNDEFINED; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + int iVolume = 0; + int err = wrap_json_unpack(pEventDataJ,"{s:i,s:i,s:i,s:s}", + "endpoint_id", &endpointID, + "endpoint_type", &endpointType, + "value",&iVolume, + "audio_role", &pAudioRole); + if(err) + { + AFB_ERROR("Unable to unpack volume event data"); + return; + } + RoleInfoT * pRole = GetRole(pAudioRole); + if ( pRole == NULL ){ + AFB_ERROR("Requested audio role does not exist in current configuration -> %s", pAudioRole); + return; + } + EndpointInfoT * pEndpointInfo = GetEndpointInfoWithRole(endpointID,endpointType,pRole); + // update volume value + pEndpointInfo->iVolume = iVolume; + // Remove event name from object + json_object_object_del(pEventDataJ,"event_name"); + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,pEventDataJ); + } + else if(strcasecmp(pEventName, AHL_POST_ACTION_EVENT)==0) { + // Remove event name from object + json_object_object_del(pEventDataJ,"event_name"); + afb_event_push(g_AHLCtx.policyCtx.postActionEvent,pEventDataJ); + } + else if(strcasecmp(pEventName, AHL_STREAM_STATE_EVENT)==0) { + streamID_t streamID = AHL_UNDEFINED; + StreamEventT streamEvent = STREAM_EVENT_MAXVALUE; + int err = wrap_json_unpack(pEventDataJ,"{s:i,s:i}", + "stream_id", &streamID, + "state_event", &streamEvent); + if(err) + { + AFB_ERROR("Unable to unpack stream event data"); + return; + } + + StreamInfoT * pStreamInfo = GetStream(streamID); + if (pStreamInfo == NULL) { + AFB_ERROR("Specified stream not currently active stream_id -> %d",streamID); + return; + } + + // update streamstate value + switch (streamEvent) { + case STREAM_EVENT_START: + pStreamInfo->streamState = STREAM_STATE_RUNNING; + break; + case STREAM_EVENT_STOP: + pStreamInfo->streamState = STREAM_STATE_IDLE; + break; + case STREAM_EVENT_PAUSE: + pStreamInfo->streamState = STREAM_STATE_PAUSED; + break; + case STREAM_EVENT_RESUME: + pStreamInfo->streamState = STREAM_STATE_RUNNING; + break; + case STREAM_EVENT_MUTED: + pStreamInfo->streamMute = STREAM_MUTED; + break; + case STREAM_EVENT_UNMUTED: + pStreamInfo->streamMute = STREAM_UNMUTED; + break; + default: + AFB_ERROR("Unknown stream event"); + } + + // Remove event name from object + json_object_object_del(pEventDataJ,"event_name"); + AFB_ERROR("pEventDataJ=%s", json_object_get_string(pEventDataJ)); + afb_event_push(pStreamInfo->streamStateEvent,pEventDataJ); + } + else { + AFB_ERROR("Unknown event name"); + } +} \ No newline at end of file diff --git a/src/ahl-binding.h b/src/ahl-binding.h index 8f308d5..af122c3 100644 --- a/src/ahl-binding.h +++ b/src/ahl-binding.h @@ -1,6 +1,5 @@ /* * Copyright (C) 2017 "Audiokinetic Inc" - * Author Francois Thibault * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,18 +18,25 @@ #define AHL_BINDING_INCLUDE #define AFB_BINDING_VERSION 2 -#include + +//#define AHL_DISCONNECT_POLICY // define for debugging HLB in standalone only + #include #include #include "ahl-interface.h" +#include #ifndef PUBLIC #define PUBLIC #endif +#define AHL_SUCCESS 0 +#define AHL_FAIL 1 + #define AHL_POLICY_ACCEPT 1 #define AHL_POLICY_REJECT 0 + #define AHL_ACCESS_CONTROL_GRANTED 1 #define AHL_ACCESS_CONTROL_DENIED 0 @@ -39,19 +45,75 @@ typedef int endpointID_t; typedef int streamID_t; -// Define default behavior of audio role when interrupted by higher priority sources -typedef enum InterruptedBehavior { - AHL_INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media may be ducked) - AHL_INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) - AHL_INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) - AHL_INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end -} InterruptedBehaviorT; - -typedef enum EndpointSelectionMode { - AHL_ENDPOINTSELMODE_AUTO = 0, // Automatic endpoint selection based on config priority - AHL_ENDPOINTSELMODE_MANUAL, // Explicit endpoint selection - AHL_ENDPOINTSELMODEMAXVALUE, // Enum count, keep at the end -} EndpointSelectionModeT; +#define AHL_STR_MAX_LENGTH 256 + +typedef enum EndpointType { + ENDPOINTTYPE_SOURCE = 0, // source devices + ENDPOINTTYPE_SINK, // sink devices + ENDPOINTTYPE_MAXVALUE // Enum count, keep at the end +} EndpointTypeT; + +typedef enum StreamState { + STREAM_STATE_IDLE = 0, // Stream is inactive + STREAM_STATE_RUNNING, // Stream is active and running + STREAM_STATE_PAUSED, // Stream is active but paused + STREAM_STATE_MAXVALUE // Enum count, keep at the end +} StreamStateT; + +typedef enum StreamMute { + STREAM_UNMUTED = 0, // Stream is not muted + STREAM_MUTED, // Stream is muted + STREAM_MUTE_MAXVALUE, // Enum count, keep at the end +} StreamMuteT; + +typedef enum StreamEvent { + STREAM_EVENT_START = 0, // Stream is inactive + STREAM_EVENT_STOP, // Stream is running + STREAM_EVENT_PAUSE, // Audio stream paused + STREAM_EVENT_RESUME, // Audio stream resumed + STREAM_EVENT_MUTED, // Audio stream muted + STREAM_EVENT_UNMUTED, // Audio stream unmuted + STREAM_EVENT_MAXVALUE // Enum count, keep at the end +} StreamEventT; + +// Define default behavior of audio role when interrupting lower priority sources +typedef enum InterruptBehavior { + INTERRUPTBEHAVIOR_CONTINUE = 0, // Continue to play lower priority source when interrupted (e.g. media may be ducked) + INTERRUPTBEHAVIOR_CANCEL, // Abort playback of lower priority source when interrupted (e.g. non-important HMI feedback that does not make sense later) + INTERRUPTBEHAVIOR_PAUSE, // Pause lower priority source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) + INTERRUPTBEHAVIOR_MAXVALUE, // Enum count, keep at the end +} InterruptBehaviorT; + +typedef enum DeviceURIType { + DEVICEURITYPE_ALSA_HW = 0, // Alsa hardware device URI + DEVICEURITYPE_ALSA_DMIX, // Alsa Dmix device URI (only for playback devices) + DEVICEURITYPE_ALSA_DSNOOP, // Alsa DSnoop device URI (only for capture devices) + DEVICEURITYPE_ALSA_SOFTVOL, // Alsa softvol device URI + DEVICEURITYPE_ALSA_PLUG, // Alsa plug device URI + DEVICEURITYPE_ALSA_OTHER, // Alsa domain URI device of unspecified type + DEVICEURITYPE_NOT_ALSA, // Unknown (not ALSA domain) + DEVICEURITYPE_MAXVALUE // Enum count, keep at the end +} DeviceURITypeT; + +// CPU endianness assumed in all formats +typedef enum SampleType { + AHL_FORMAT_UNKNOWN = -1, // Unknown + AHL_FORMAT_U8 = 0, // Unsigned 8 bit + AHL_FORMAT_S16, // Signed 16 bit Little Endian + AHL_FORMAT_S24, // Signed 24 bit Little Endian using low three bytes in 32-bit word + AHL_FORMAT_S32, // Signed 32 bit Little Endian + AHL_FORMAT_FLOAT, // Float 32 bit Little Endian, Range -1.0 to 1.0 + AHL_FORMAT_FLOAT64, // Float 64 bit Little Endian, Range -1.0 to 1.0 + AHL_FORMAT_IEC958, // IEC-958 Little Endian (SPDIF) + AHL_FORMAT_MU_LAW, // Mu-Law + AHL_FORMAT_A_LAW, // A-Law + AHL_FORMAT_IMA_ADPCM, // Ima-ADPCM + AHL_FORMAT_MPEG, // MPEG + AHL_FORMAT_GSM, // GSM + AHL_FORMAT_G723, // G723 + AHL_FORMAT_DSD, // Direct stream digital + AHL_FORMAT_MAXVALUE, // Enum count, keep at the end +} SampleTypeT; typedef struct AudioFormat { int sampleRate; // Sample rate @@ -67,44 +129,59 @@ typedef struct AlsaDeviceInfo { int subDeviceNum; // HW sub device number } AlsaDeviceInfoT; +typedef enum EndpointSelectionMode { + ENDPOINTSELMODE_AUTO = 0, // Automatic endpoint selection based on config priority + ENDPOINTSELMODE_MANUAL, // Explicit endpoint selection + ENDPOINTSELMODEMAXVALUE, // Enum count, keep at the end +} EndpointSelectionModeT; + typedef struct EndpointInfo { endpointID_t endpointID; // Unique endpoint ID (per type) EndpointTypeT type; // Source or sink device - GString * gsDeviceName; // Unique device card name - GString * gsDisplayName; // Application display name - GString * gsDeviceURI; // Associated URI + char * gsDeviceName; // Unique device card name + char * gsDisplayName; // Application display name + char * gsDeviceURI; // Associated URI + char * gsDeviceDomain; // Device URI domain (e.g. alsa or pulse) + char * pRoleName; // Role assigned to this endpoint DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information) - GString * gsAudioRole; // Audio role that registered this endpoint - GString * gsHALAPIName; // HAL associated with the device (for volume control) + char * gsHALAPIName; // HAL associated with the device (for volume control) AlsaDeviceInfoT alsaInfo; // ALSA specific device information AudioFormatT format; // Preferred audio format supported (later could be array of supported formats) - int iVolume; // Storage for current endpoint volume (policy effected). Target volume during ramping? + int iVolume; // Storage for current endpoint volume (policy effected). GHashTable * pPropTable; // Storage for array of properties (policy effected) } EndpointInfoT; typedef struct StreamInfo { - streamID_t streamID; // Stream unique ID - EndpointInfoT * pEndpointInfo; // Associated endpoint information - StreamStateT streamState; // Stream activity state - StreamMuteT streamMute; // Stream mute state - struct afb_event streamStateEvent; // Stream specific event for stream state changes - EndpointSelectionModeT endpointSelMode; // Automatic (priority based) or manual endpoint selection + streamID_t streamID; // Stream unique ID + EndpointInfoT * pEndpointInfo; // Associated endpoint information (reference) + StreamStateT streamState; // Stream activity state + StreamMuteT streamMute; // Stream mute state + struct afb_event streamStateEvent; // Stream specific event for stream state changes + EndpointSelectionModeT endpointSelMode; // Automatic (priority based) or manual endpoint selection + char * pRoleName; // Role string identifier (from role config but could be programatically overriden later) + int iPriority; // Role normalized priority (0-100) (from role config but could be programatically overriden later) + InterruptBehaviorT eInterruptBehavior; // Role behavior when interrupting lower priority streams (from role config but could be programatically overriden later) } StreamInfoT; +typedef struct RoleInfo { + char * pRoleName; // Role string identifier + int iPriority; // Role normalized priority (0-100) + InterruptBehaviorT eInterruptBehavior; // Role behavior when interrupting lower priority streams + GPtrArray * pActionList; // List of supported actions for the role (gchar*) + GPtrArray * pSourceEndpoints; // Source endpoints info (EndpointInfoT*) + GPtrArray * pSinkEndpoints; // Sink endpoints info (EndpointInfoT*) +} RoleInfoT; + // Parts of the context that are visible to the policy (for state based decisions) typedef struct AHLPolicyCtx { - GPtrArray * pSourceEndpoints; // Array of source end points for each audio role (GArray*) - GPtrArray * pSinkEndpoints; // Array of sink end points for each audio role (GArray*) - GPtrArray * pEventList; // Event list per audio roles (GArray*) - GHashTable * pRolePriority; // List of role priorities (int). - GArray * pInterruptBehavior; // List of interrupt behavior per audio role (int/enum). - GArray * pAudioRoles; // List of audio roles (GString) - GArray * pActiveStreams; // List of active streams (StreamInfoT) - int iNumberRoles; // Number of audio roles from configuration - struct afb_event propertyEvent; // AGL event used when property changes - struct afb_event volumeEvent; // AGL event used when volume changes - struct afb_event postEvent; // AGL event used on post event call + GHashTable * pRoleInfo; // Hash table of role information structure (RoleInfoT*) accessed by role name + GHashTable * pStreams; // List of active streams (StreamInfoT*) accessed by streamID + GPtrArray * pHALList; // List of HAL dependencies + // TODO: Events need to be sent directly by HLB when separation with policy complete + struct afb_event propertyEvent; // AGL event used when property changes + struct afb_event volumeEvent; // AGL event used when volume changes + struct afb_event postActionEvent; // AGL event used on post action call } AHLPolicyCtxT; // Global binding context @@ -113,14 +190,11 @@ typedef struct AHLCtx { endpointID_t nextSourceEndpointID; // Counter to assign new ID endpointID_t nextSinkEndpointID; // Counter to assign new ID endpointID_t nextStreamID; // Counter to assign new ID - GArray * pHALList; // List of HAL dependencies - int iNumActiveClients; // Number of clients with active stream(s) } AHLCtxT; // Client specific binding context typedef struct AHLClientCtx { - GArray * pEndpointAccessList; // List of endpoints that client has control over - GArray * pStreamAccessList; // List of streams that client has control over + GArray * pStreamAccessList; // List of streams that client has control over } AHLClientCtxT; // ahl-binding.c @@ -128,24 +202,14 @@ PUBLIC int AhlBindingInit(); PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ); // ahl-deviceenum.c -int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName); -int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName); -void TermEndpoints(); +int EnumerateDevices(json_object * in_jDeviceArray, char * in_pAudioRole, EndpointTypeT in_deviceType, GPtrArray * out_pEndpointArray); +EndpointInfoT * InitEndpointInfo(); +void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo ); // ahl-config.c int ParseHLBConfig(); // ahl-policy.c -int Policy_Endpoint_Property_Init(EndpointInfoT * io_pEndpointInfo); -int Policy_OpenStream(StreamInfoT * pStreamInfo); -int Policy_CloseStream(StreamInfoT * pStreamInfo); -int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, StreamStateT streamState); -int Policy_SetStreamMute(StreamInfoT * pStreamInfo, StreamMuteT streamMute); -int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext); -int Policy_AudioDeviceChange(); -int Policy_SetVolume(EndpointInfoT * f_pEndpointInfo, char *volumeStr); -//Todo -int Policy_SetProperty(EndpointInfoT * f_pEndpointInfo, char *propertyName, json_object *propValue); -int Policy_Init(); -void Policy_Term(); -void Policy_OnEvent(const char *evtname, json_object *eventJ); +#ifndef AHL_DISCONNECT_POLICY +PUBLIC void audiohlapi_raise_event(json_object *EventDataJ); +#endif #endif // AHL_BINDING_INCLUDE diff --git a/src/ahl-config.c b/src/ahl-config.c index aa55b28..168311e 100644 --- a/src/ahl-config.c +++ b/src/ahl-config.c @@ -1,6 +1,5 @@ /* * Copyright (C) 2017 "Audiokinetic Inc" - * Author Francois Thibault * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +24,22 @@ extern AHLCtxT g_AHLCtx; +static InterruptBehaviorT InterruptBehaviorToEnum(char * in_pInterruptBehaviorStr) +{ + g_assert_nonnull(in_pInterruptBehaviorStr); + if (strcasecmp(in_pInterruptBehaviorStr,AHL_INTERRUPTBEHAVIOR_CONTINUE)==0) { + return INTERRUPTBEHAVIOR_CONTINUE; + } + else if (strcasecmp(in_pInterruptBehaviorStr,AHL_INTERRUPTBEHAVIOR_CANCEL)==0) { + return INTERRUPTBEHAVIOR_CANCEL; + } + else if (strcasecmp(in_pInterruptBehaviorStr,AHL_INTERRUPTBEHAVIOR_PAUSE)==0) { + return INTERRUPTBEHAVIOR_PAUSE; + } + else + return INTERRUPTBEHAVIOR_MAXVALUE; +} + int ParseHLBConfig() { char * versionStr = NULL; json_object * jAudioRoles = NULL; @@ -33,7 +48,7 @@ int ParseHLBConfig() { // TODO: This should be retrieve from binding startup arguments char configfile_path[256]; - sprintf(configfile_path, "%s/opt/config/ahl-config.json", getenv("HOME")); + sprintf(configfile_path, "%s", getenv("AHL_CONFIG_FILE")); AFB_INFO("High-level config file -> %s\n", configfile_path); // Open configuration file @@ -49,36 +64,31 @@ int ParseHLBConfig() { AFB_ERROR("Invalid configuration file -> %s", configfile_path); return 1; } - AFB_INFO("Version: %s", versionStr); + AFB_INFO("High-level audio API version: %s", "1.0.0"); + AFB_INFO("Config version: %s", versionStr); AFB_INFO("Policy module: %s", policyModule); int iHALListLength = json_object_array_length(jHALList); + g_AHLCtx.policyCtx.pHALList = g_ptr_array_new_with_free_func(g_free); int iNumberOfRoles = json_object_array_length(jAudioRoles); - g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles; - - // Dynamically allocated based on number or roles found - g_AHLCtx.pHALList = g_array_sized_new(FALSE, TRUE, sizeof(GString), iHALListLength); - g_AHLCtx.policyCtx.pRolePriority = g_hash_table_new(g_str_hash, g_str_equal); - g_AHLCtx.policyCtx.pAudioRoles = g_array_sized_new(FALSE, TRUE, sizeof(GString), iNumberOfRoles); - g_AHLCtx.policyCtx.pInterruptBehavior = g_array_sized_new(FALSE, TRUE, sizeof(int), iNumberOfRoles); - g_AHLCtx.policyCtx.pSourceEndpoints = g_ptr_array_sized_new(iNumberOfRoles); - g_AHLCtx.policyCtx.pSinkEndpoints = g_ptr_array_sized_new(iNumberOfRoles); - g_AHLCtx.policyCtx.pEventList = g_ptr_array_sized_new(iNumberOfRoles); + g_AHLCtx.policyCtx.pRoleInfo = g_hash_table_new(g_str_hash, g_str_equal); for (int i = 0; i < iHALListLength; i++) { char * pHAL = NULL; json_object * jHAL = json_object_array_get_idx(jHALList,i); - pHAL = (char *)json_object_get_string(jHAL); - GString * gHALName = g_string_new( pHAL ); - g_array_append_val( g_AHLCtx.pHALList, *gHALName ); - - // Set dependency on HAL - err = afb_daemon_require_api_v2(pHAL,1) ; - if( err != 0 ) - { - AFB_ERROR("Audio high level API could not set dependenvy on API: %s",pHAL); - return 1; + if (jHAL) { + pHAL = (char *)json_object_get_string(jHAL); + char * pHALName = g_strdup( pHAL ); + g_ptr_array_add( g_AHLCtx.policyCtx.pHALList, pHALName ); + + // Set dependency on HAL specified + err = afb_daemon_require_api_v2(pHAL,1) ; + if( err != 0 ) + { + AFB_ERROR("Audio high level API could not set dependency on API: %s",pHAL); + return 1; + } } } @@ -88,13 +98,13 @@ int ParseHLBConfig() { json_object * jAudioRole = json_object_array_get_idx(jAudioRoles,i); json_object * jOutputDevices = NULL; json_object * jInputDevices = NULL; - json_object * jEvents = NULL; + json_object * jActions = NULL; char * pRoleName = NULL; char * pInteruptBehavior = NULL; int iNumOutDevices = 0; int iNumInDevices = 0; - int iNumEvents = 0; + int iNumActions = 0; err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s:s,s?o,s?o,s?o}", "name", &pRoleName, @@ -102,7 +112,7 @@ int ParseHLBConfig() { "interupt_behavior",&pInteruptBehavior, "output",&jOutputDevices, "input",&jInputDevices, - "events",&jEvents + "actions",&jActions ); if (err) { AFB_ERROR("Invalid audio role configuration : %s", json_object_to_json_string(jAudioRole)); @@ -113,61 +123,47 @@ int ParseHLBConfig() { iNumOutDevices = json_object_array_length(jOutputDevices); if (jInputDevices) iNumInDevices = json_object_array_length(jInputDevices); - if (jEvents) - iNumEvents = json_object_array_length(jEvents); - - GString * gRoleName = g_string_new( pRoleName ); - g_array_append_val( g_AHLCtx.policyCtx.pAudioRoles, *gRoleName ); - g_hash_table_insert(g_AHLCtx.policyCtx.pRolePriority, pRoleName, GINT_TO_POINTER(priority)); - - // Map interupt behavior string to enum value - InterruptedBehaviorT interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; - if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_CONTINUE_STR) == 0 ) { - interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; - } - else if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_CANCEL_STR) == 0 ) { - interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_CANCEL; - } - else if ( strcasecmp(pInteruptBehavior,AHL_INTERRUPTEDBEHAVIOR_PAUSE_STR) == 0 ) { - interuptBehavior = AHL_INTERRUPTEDBEHAVIOR_PAUSE; - } - else { - AFB_ERROR("Unknown interrupt behavior : %s", pInteruptBehavior); - return 1; + if (jActions) + iNumActions = json_object_array_length(jActions); + + RoleInfoT * pRoleInfo = (RoleInfoT*) malloc(sizeof(RoleInfoT)); + memset(pRoleInfo,0,sizeof(RoleInfoT)); + pRoleInfo->pRoleName = g_strdup( pRoleName ); + pRoleInfo->iPriority = priority; + pRoleInfo->eInterruptBehavior = InterruptBehaviorToEnum(pInteruptBehavior); + + // Actions + pRoleInfo->pActionList = g_ptr_array_new_with_free_func(g_free); + // Parse and validate list of available actions + for (int i = 0; i < iNumActions; i++) + { + json_object * jAction = json_object_array_get_idx(jActions,i); + char * pActionName = (char *)json_object_get_string(jAction); + if (pActionName) + g_ptr_array_add(pRoleInfo->pActionList, g_strdup(pActionName)); } - g_array_append_val(g_AHLCtx.policyCtx.pInterruptBehavior, interuptBehavior); // Sources - GArray * pRoleSourceDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); - g_ptr_array_add(g_AHLCtx.policyCtx.pSourceEndpoints,pRoleSourceDeviceArray); + pRoleInfo->pSourceEndpoints = g_ptr_array_new_with_free_func(g_free); if (iNumInDevices) { - err = EnumerateSources(jInputDevices,i,pRoleName); + err = EnumerateDevices(jInputDevices,pRoleName,ENDPOINTTYPE_SOURCE,pRoleInfo->pSourceEndpoints); if (err) { AFB_ERROR("Invalid input devices : %s", json_object_to_json_string(jInputDevices)); return 1; } } // Sinks - GArray * pRoleSinkDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); - g_ptr_array_add(g_AHLCtx.policyCtx.pSinkEndpoints,pRoleSinkDeviceArray); + pRoleInfo->pSinkEndpoints = g_ptr_array_new_with_free_func(g_free); if (iNumOutDevices) { - err = EnumerateSinks(jOutputDevices,i,pRoleName); + err = EnumerateDevices(jOutputDevices,pRoleName,ENDPOINTTYPE_SINK,pRoleInfo->pSinkEndpoints); if (err) { AFB_ERROR("Invalid output devices : %s", json_object_to_json_string(jOutputDevices)); return 1; } } - // Events - GArray * pEventsArray = g_array_new(FALSE, TRUE, sizeof(GString)); - g_ptr_array_add(g_AHLCtx.policyCtx.pEventList,pEventsArray); - // Parse and validate list of available events - for (int i = 0; i < iNumEvents; i++) - { - json_object * jEvent = json_object_array_get_idx(jEvents,i); - char * pEventName = (char *)json_object_get_string(jEvent); - GString * gsEventName = g_string_new(pEventName); - g_array_append_val(pEventsArray, *gsEventName); - } + + g_hash_table_insert(g_AHLCtx.policyCtx.pRoleInfo, pRoleInfo->pRoleName, pRoleInfo); + } // Build lists of all device URI referenced in config file (input/output) diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c index 4722333..7e67d7a 100644 --- a/src/ahl-deviceenum.c +++ b/src/ahl-deviceenum.c @@ -1,6 +1,5 @@ /* * Copyright (C) 2017 "Audiokinetic Inc" - * Author Francois Thibault * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +23,7 @@ #include "wrap-json.h" #include "ahl-binding.h" +#include "ahl-policy.h" extern AHLCtxT g_AHLCtx; @@ -44,7 +44,6 @@ static endpointID_t CreateNewSinkID() } // Watchout: This function uses strtok and is destructive on the input string (use a copy) -// TODO: Perhaps it would be clearer to separate domain and device URI in both API inputs and outputs static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomain, char ** out_pDevice) { *out_pDomain = strtok(in_pDeviceURI, "."); @@ -85,6 +84,8 @@ static int IsExternalDomain(const char * in_pDomainStr) static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndpointInfo ) { + g_assert_nonnull(in_pPcmHandle); + g_assert_nonnull(out_pEndpointInfo); snd_pcm_type_t pcmType = 0; snd_pcm_info_t * pPcmInfo = NULL; int iAlsaRet = 0; @@ -172,336 +173,159 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp snd_ctl_close(ctlHandle); return 1; } - g_string_assign(out_pEndpointInfo->gsDeviceName,pCardName); + out_pEndpointInfo->gsDeviceName = g_strdup(pCardName); snd_ctl_close(ctlHandle); return retVal; } -static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) +EndpointInfoT * InitEndpointInfo() { - json_object *j_response, *j_query = NULL; - int err; - err = afb_service_call_sync("alsacore", "hallist", j_query, &j_response); - if (err) { - AFB_ERROR("Could not retrieve list of HAL from ALSA core"); - return 1; - } - AFB_DEBUG("ALSAcore hallist response=%s", json_object_to_json_string(j_response)); - - // Look through returned list for matching card - int found = 0; - json_object * jRespObj = NULL; - json_object_object_get_ex(j_response, "response", &jRespObj); - int iNumHAL = json_object_array_length(jRespObj); - for ( int i = 0 ; i < iNumHAL; i++) - { - json_object * jHAL = json_object_array_get_idx(jRespObj,i); - char * pDevIDStr = NULL; - char * pAPIName = NULL; - char * pShortName = NULL; - - int err = wrap_json_unpack(jHAL, "{s:s,s:s,s:s}", "devid", &pDevIDStr,"api", &pAPIName,"shortname",&pShortName); - if (err) { - AFB_ERROR("Could not retrieve devid string=%s", json_object_get_string(jHAL)); - return 1; - } - - // Retrieve card number (e.g. hw:0) - int iCardNum = atoi(pDevIDStr+3); - if (iCardNum == io_pEndpointInfo->alsaInfo.cardNum) { - g_string_assign(io_pEndpointInfo->gsHALAPIName,pAPIName); - g_string_assign(io_pEndpointInfo->gsDisplayName,pShortName); - found = 1; - break; - } - } - return !found; -} - -static void InitEndpointInfo( EndpointInfoT * out_pEndpointInfo ) -{ - out_pEndpointInfo->endpointID = AHL_UNDEFINED; - out_pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE; - out_pEndpointInfo->gsDeviceName = g_string_new("Unassigned"); - out_pEndpointInfo->gsDisplayName = g_string_new("Unassigned"); - out_pEndpointInfo->gsDeviceURI = g_string_new("Unassigned"); - out_pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE; - out_pEndpointInfo->gsAudioRole = g_string_new("Unassigned"); - out_pEndpointInfo->gsHALAPIName = g_string_new("Unassigned"); - out_pEndpointInfo->alsaInfo.cardNum = AHL_UNDEFINED; - out_pEndpointInfo->alsaInfo.deviceNum = AHL_UNDEFINED; - out_pEndpointInfo->alsaInfo.subDeviceNum = AHL_UNDEFINED; - out_pEndpointInfo->format.sampleRate = AHL_UNDEFINED; - out_pEndpointInfo->format.numChannels = AHL_UNDEFINED; - out_pEndpointInfo->format.sampleType = AHL_FORMAT_UNKNOWN; - out_pEndpointInfo->pPropTable = g_hash_table_new(g_str_hash, g_str_equal); + EndpointInfoT * pEndpointInfo = (EndpointInfoT*) malloc(sizeof(EndpointInfoT)); + memset(pEndpointInfo,0,sizeof(EndpointInfoT)); + pEndpointInfo->endpointID = AHL_UNDEFINED; + pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE; + pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE; + pEndpointInfo->alsaInfo.cardNum = AHL_UNDEFINED; + pEndpointInfo->alsaInfo.deviceNum = AHL_UNDEFINED; + pEndpointInfo->alsaInfo.subDeviceNum = AHL_UNDEFINED; + pEndpointInfo->format.sampleRate = AHL_UNDEFINED; + pEndpointInfo->format.numChannels = AHL_UNDEFINED; + pEndpointInfo->format.sampleType = AHL_FORMAT_UNKNOWN; + pEndpointInfo->pPropTable = g_hash_table_new(g_str_hash, g_str_equal); + return pEndpointInfo; } -static void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo ) +void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo ) { - g_string_free(out_pEndpointInfo->gsDeviceName,TRUE); - g_string_free(out_pEndpointInfo->gsDisplayName,TRUE); - g_string_free(out_pEndpointInfo->gsDeviceURI,TRUE); - g_string_free(out_pEndpointInfo->gsAudioRole,TRUE); - g_string_free(out_pEndpointInfo->gsHALAPIName,TRUE); - // TODO: Free json_object for all property values - g_hash_table_remove_all(out_pEndpointInfo->pPropTable); - g_hash_table_destroy(out_pEndpointInfo->pPropTable); -} - -void TermEndpoints() -{ - // Sources for each role - for (int i = 0; i < g_AHLCtx.policyCtx.pSourceEndpoints->len; i++) - { - // For each endpoint within the role - GArray * pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, i ); - for (int j = 0 ; j < pRoleDeviceArray->len; j++) - { - EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); - TermEndpointInfo(pEndpointInfo); - } - g_array_free(pRoleDeviceArray,TRUE); - pRoleDeviceArray = NULL; - } - g_ptr_array_free(g_AHLCtx.policyCtx.pSourceEndpoints,TRUE); - g_AHLCtx.policyCtx.pSourceEndpoints = NULL; - - // Sinks for each role - for (int i = 0; i < g_AHLCtx.policyCtx.pSinkEndpoints->len; i++) - { - // For each endpoint within the role - GArray * pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, i ); - for (int j = 0 ; j < pRoleDeviceArray->len; j++) + #define SAFE_FREE(__ptr__) if(__ptr__) g_free(__ptr__); __ptr__ = NULL; + SAFE_FREE(out_pEndpointInfo->gsDeviceName); + SAFE_FREE(out_pEndpointInfo->gsDisplayName); + SAFE_FREE(out_pEndpointInfo->gsDeviceDomain); + SAFE_FREE(out_pEndpointInfo->pRoleName); + SAFE_FREE(out_pEndpointInfo->gsDeviceURI); + SAFE_FREE(out_pEndpointInfo->gsHALAPIName); + + if (out_pEndpointInfo->pPropTable) { + // Free json_object for all property values + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, out_pEndpointInfo->pPropTable); + while (g_hash_table_iter_next (&iter, &key, &value)) { - EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); - TermEndpointInfo(pEndpointInfo); + if (value) + json_object_put(value); } - g_array_free(pRoleDeviceArray,TRUE); - pRoleDeviceArray = NULL; + g_hash_table_remove_all(out_pEndpointInfo->pPropTable); + g_hash_table_destroy(out_pEndpointInfo->pPropTable); + out_pEndpointInfo->pPropTable = NULL; } - g_ptr_array_free(g_AHLCtx.policyCtx.pSinkEndpoints,TRUE); - g_AHLCtx.policyCtx.pSinkEndpoints = NULL; + // GLib automatically frees item when removed from the array } -#define AUDIOHL_MAX_DEVICE_URI_LENGTH 128 - // For a given audio role -int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName) { +int EnumerateDevices(json_object * in_jDeviceArray, char * in_pAudioRole, EndpointTypeT in_deviceType, GPtrArray * out_pEndpointArray) { - int iNumberDevices = json_object_array_length(in_jSourceArray); + g_assert_nonnull(in_jDeviceArray); + int iNumberDevices = json_object_array_length(in_jDeviceArray); // Parse and validate list of available devices for (int i = 0; i < iNumberDevices; i++) { - char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive char * pDeviceURIDomain = NULL; char * pFullDeviceURI = NULL; char * pDeviceURIPCM = NULL; int err = 0; - EndpointInfoT endpointInfo; - - json_object * jSource = json_object_array_get_idx(in_jSourceArray,i); + json_object * jDevice = json_object_array_get_idx(in_jDeviceArray,i); + if (jDevice == NULL) { + AFB_WARNING("Invalid device array -> %s",json_object_to_json_string(in_jDeviceArray)); + continue; + } // strip domain name from URI - pFullDeviceURI = (char *)json_object_get_string(jSource); - strncpy(fullDeviceURI,pFullDeviceURI,AUDIOHL_MAX_DEVICE_URI_LENGTH); - err = SeparateDomainFromDeviceURI(fullDeviceURI,&pDeviceURIDomain,&pDeviceURIPCM); + pFullDeviceURI = (char *)json_object_get_string(jDevice); + char * pFullDeviceURICopy = g_strdup(pFullDeviceURI); // strtok is destructive + err = SeparateDomainFromDeviceURI(pFullDeviceURICopy,&pDeviceURIDomain,&pDeviceURIPCM); if (err) { - AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI); + AFB_WARNING("Invalid device URI string -> %s",pFullDeviceURICopy); continue; } - InitEndpointInfo(&endpointInfo); + EndpointInfoT * pEndpointInfo = InitEndpointInfo(); + g_assert_nonnull(pEndpointInfo); // non ALSA URI are simply passed to application (no validation) at this time // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection - g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); + pEndpointInfo->gsDeviceName = g_strdup(pDeviceURIPCM); + pEndpointInfo->gsDeviceDomain = g_strdup(pDeviceURIDomain); + pEndpointInfo->gsDeviceURI = g_strdup(pDeviceURIPCM); + pEndpointInfo->pRoleName = g_strdup(in_pAudioRole); + + g_free(pFullDeviceURICopy); + pFullDeviceURICopy = NULL; + pDeviceURIDomain = NULL; //Derived from above mem + pDeviceURIPCM = NULL; //Derived from above mem - if (IsAlsaDomain(pDeviceURIDomain)) + if (IsAlsaDomain(pEndpointInfo->gsDeviceDomain)) { // TODO: Missing support for loose name matching // This will require using ALSA hints to get PCM names // And would iterate over all available devices matching string (possibly all if no filtering is desired for a certain role) - snd_pcm_t * pPcmHandle = NULL; - // Get PCM handle - err = snd_pcm_open(&pPcmHandle, pDeviceURIPCM, SND_PCM_STREAM_CAPTURE, 0); + snd_pcm_t * pPcmHandle = NULL; + snd_pcm_stream_t streamType = in_deviceType == ENDPOINTTYPE_SOURCE ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; + err = snd_pcm_open(&pPcmHandle, pEndpointInfo->gsDeviceURI, streamType, 0); if (err < 0) { - AFB_NOTICE("Alsa PCM device was not found -> %s", pDeviceURIPCM); + AFB_NOTICE("Alsa PCM device was not found -> %s", pEndpointInfo->gsDeviceURI); continue; } - err = FillALSAPCMInfo(pPcmHandle,&endpointInfo); + err = FillALSAPCMInfo(pPcmHandle,pEndpointInfo); if (err) { - AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pDeviceURIPCM); + AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pEndpointInfo->gsDeviceURI); snd_pcm_close(pPcmHandle); continue; } snd_pcm_close(pPcmHandle); - - // TODO: Consider policy call to determine URI for set volume execution for a particular role (hw?) - // Retrieve HAL API name - err = RetrieveAssociatedHALAPIName(&endpointInfo); - if (err) { - AFB_WARNING("SetVolume will fail without HAL association ->%s",endpointInfo.gsDeviceURI->str); - // Choose not to skip anyhow... - } } - else if (IsPulseDomain(pDeviceURIDomain)) { + else if (IsPulseDomain(pEndpointInfo->gsDeviceDomain)) { // Pulse domain // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on - endpointInfo.deviceURIType = DEVICEURITYPE_PULSE; + pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA; } - else if (IsGStreamerDomain(pDeviceURIDomain)){ + else if (IsGStreamerDomain(pEndpointInfo->gsDeviceDomain)){ // GStreamer domain // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on - endpointInfo.deviceURIType = DEVICEURITYPE_GSTREAMER; + pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA; } - else if (IsExternalDomain(pDeviceURIDomain)){ + else if (IsExternalDomain(pEndpointInfo->gsDeviceDomain)){ // External domain - endpointInfo.deviceURIType = DEVICEURITYPE_EXTERNAL; - } - else { - // Unknown domain - AFB_WARNING("Unknown domain in device URI string -> %s",fullDeviceURI); - continue; - } - - g_string_assign(endpointInfo.gsDeviceURI,pDeviceURIPCM); - g_string_assign(endpointInfo.gsAudioRole,in_pRoleName); - endpointInfo.endpointID = CreateNewSourceID(); - endpointInfo.type = ENDPOINTTYPE_SOURCE; - err = Policy_Endpoint_Property_Init(&endpointInfo); - if (err) { - AFB_WARNING("Policy endpoint properties initalization failed for endpointid :%d type:%d",endpointInfo.endpointID, endpointInfo.type); - // Choose not to skip anyhow... - } - - // add to structure to list of available source devices - GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, in_iRoleIndex ); - g_array_append_val(pRoleSourceDeviceArray, endpointInfo); - - } // for all devices - - AFB_DEBUG ("Audio high-level - Enumerate sources done"); - return 0; -} - -// For a given audio role -int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName) { - - int iNumberDevices = json_object_array_length(in_jSinkArray); - - // Parse and validate list of available devices - for (int i = 0; i < iNumberDevices; i++) - { - char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive - char * pDeviceURIDomain = NULL; - char * pFullDeviceURI = NULL; - char * pDeviceURIPCM = NULL; - int err = 0; - EndpointInfoT endpointInfo; - - json_object * jSink = json_object_array_get_idx(in_jSinkArray,i); - - // strip domain name from URI - pFullDeviceURI = (char*)json_object_get_string(jSink); - strncpy(fullDeviceURI,pFullDeviceURI,AUDIOHL_MAX_DEVICE_URI_LENGTH); - err = SeparateDomainFromDeviceURI(fullDeviceURI,&pDeviceURIDomain,&pDeviceURIPCM); - if (err) - { - AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI); - continue; - } - - // non ALSA URI are simply passed to application (no validation) at this time - // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection - - InitEndpointInfo(&endpointInfo); - - endpointInfo.alsaInfo.cardNum = -1; - endpointInfo.alsaInfo.deviceNum = -1; - endpointInfo.alsaInfo.cardNum = -1; - g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); - - if (IsAlsaDomain(pDeviceURIDomain)) - { - // TODO: Missing support for loose name matching - // This will require using ALSA hints to get PCM names - // And would iterate over all available devices matching string (possibly all if no filtering is desired for a certain role) - - snd_pcm_t * pPcmHandle = NULL; - - // get PCM handle - err = snd_pcm_open(&pPcmHandle, pDeviceURIPCM, SND_PCM_STREAM_PLAYBACK, 0); - if (err < 0) - { - AFB_NOTICE("Alsa PCM device was not found -> %s", pDeviceURIPCM); - continue; - } - - err = FillALSAPCMInfo(pPcmHandle,&endpointInfo); - if (err) { - AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pDeviceURIPCM); - snd_pcm_close(pPcmHandle); - continue; - } - - snd_pcm_close(pPcmHandle); - - // TODO: Consider policy call to determine URI for set volume execution for a particular role (hw?) - // Retrieve HAL API name - err = RetrieveAssociatedHALAPIName(&endpointInfo); - if (err) { - //AFB_WARNING("SetVolume w fail without HAL association ->%s",endpointInfo.deviceURI); - continue; - } - } - else if (IsPulseDomain(pDeviceURIDomain)) { - // Pulse domain - // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on - endpointInfo.deviceURIType = DEVICEURITYPE_PULSE; - - } - else if (IsGStreamerDomain(pDeviceURIDomain)){ - // GStreamer domain - // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on - endpointInfo.deviceURIType = DEVICEURITYPE_GSTREAMER; - } - else if (IsExternalDomain(pDeviceURIDomain)){ - // External domain - - endpointInfo.deviceURIType = DEVICEURITYPE_EXTERNAL; + pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA; } else { // Unknown domain - AFB_WARNING("Unknown domain in device URI string -> %s",fullDeviceURI); + AFB_WARNING("Unknown domain in device URI string -> %s",pFullDeviceURI); continue; } - g_string_assign(endpointInfo.gsDeviceURI,pDeviceURIPCM); - g_string_assign(endpointInfo.gsAudioRole,in_pRoleName); - endpointInfo.endpointID = CreateNewSinkID(); - endpointInfo.type = ENDPOINTTYPE_SINK; - err = Policy_Endpoint_Property_Init(&endpointInfo); - if (err) { - AFB_WARNING("Policy endpoint properties initalization failed for endpointid :%d type:%d",endpointInfo.endpointID, endpointInfo.type); - // Choose not to skip anyhow... - } + pEndpointInfo->endpointID = in_deviceType == ENDPOINTTYPE_SOURCE ? CreateNewSourceID() : CreateNewSinkID(); + pEndpointInfo->type = in_deviceType; + // Assigned by policy initialization + pEndpointInfo->gsDisplayName = malloc(AHL_STR_MAX_LENGTH*sizeof(char)); + memset(pEndpointInfo->gsDisplayName,0,AHL_STR_MAX_LENGTH*sizeof(char)); + pEndpointInfo->gsHALAPIName = malloc(AHL_STR_MAX_LENGTH*sizeof(char)); + memset(pEndpointInfo->gsDisplayName,0,AHL_STR_MAX_LENGTH*sizeof(char)); - // add to structure to list of available source devices - GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, in_iRoleIndex ); - g_array_append_val(pRoleSinkDeviceArray, endpointInfo); + // add to structure to list of available devices + g_ptr_array_add(out_pEndpointArray, pEndpointInfo); } // for all devices - AFB_DEBUG ("Audio high-level - Enumerate sinks done"); + AFB_DEBUG ("Audio high-level - Enumerate devices done"); return 0; -} +} \ No newline at end of file diff --git a/src/ahl-interface.h b/src/ahl-interface.h index 04b2865..0488e96 100644 --- a/src/ahl-interface.h +++ b/src/ahl-interface.h @@ -1,6 +1,5 @@ /* * Copyright (C) 2017 "Audiokinetic Inc" - * Author Francois Thibault * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,76 +17,39 @@ #ifndef AHL_INTERFACE_INCLUDE #define AHL_INTERFACE_INCLUDE -typedef enum EndpointType { - ENDPOINTTYPE_SOURCE = 0, // source devices - ENDPOINTTYPE_SINK, // sink devices - ENDPOINTTYPE_MAXVALUE // Enum count, keep at the end -} EndpointTypeT; - -typedef enum DeviceURIType { - DEVICEURITYPE_ALSA_HW = 0, // Alsa hardware device URI - DEVICEURITYPE_ALSA_DMIX, // Alsa Dmix device URI (only for playback devices) - DEVICEURITYPE_ALSA_DSNOOP, // Alsa DSnoop device URI (only for capture devices) - DEVICEURITYPE_ALSA_SOFTVOL, // Alsa softvol device URI - DEVICEURITYPE_ALSA_PLUG, // Alsa plug device URI - DEVICEURITYPE_ALSA_OTHER, // Alsa domain URI device of unspecified type - DEVICEURITYPE_PULSE, // Pulse device URI - DEVICEURITYPE_GSTREAMER, // GStreamer device URI - DEVICEURITYPE_EXTERNAL, // Device URI for external ECU device - DEVICEURITYPE_MAXVALUE // Enum count, keep at the end -} DeviceURITypeT; - -typedef enum StreamState { - STREAM_STATE_IDLE = 0, // Stream is inactive - STREAM_STATE_RUNNING, // Stream is active and running - STREAM_STATE_PAUSED, // Stream is active but paused - STREAM_STATE_MAXVALUE // Enum count, keep at the end -} StreamStateT; - -typedef enum StreamMute { - STREAM_UNMUTED = 0, // Stream is not muted - STREAM_MUTED, // Stream is muted - STREAM_MUTE_MAXVALUE, // Enum count, keep at the end -} StreamMuteT; - -typedef enum StreamEvent { - STREAM_EVENT_START = 0, // Stream is inactive - STREAM_EVENT_STOP, // Stream is running - STREAM_EVENT_PAUSE, // Audio stream paused - STREAM_EVENT_RESUME, // Audio stream resumed - STREAM_EVENT_MUTED, // Audio stream muted - STREAM_EVENT_UNMUTED, // Audio stream unmuted - STREAM_STATUS_MAXVALUE // Enum count, keep at the end -} StreamEventT; - -// Define default behavior of audio role when interrupted by higher priority sources (in configuration) -#define AHL_INTERRUPTEDBEHAVIOR_CONTINUE_STR "continue" // Continue to play when interrupted (e.g. media may be ducked) -#define AHL_INTERRUPTEDBEHAVIOR_CANCEL_STR "cancel" // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) -#define AHL_INTERRUPTEDBEHAVIOR_PAUSE_STR "pause" // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) +///// API ///// +// Endpoint types +#define AHL_ENDPOINTTYPE_SOURCE "source" // source devices +#define AHL_ENDPOINTTYPE_SINK "sink" // sink devices + +// Stream state +#define AHL_STREAM_STATE_IDLE "idle" // Stream is inactive +#define AHL_STREAM_STATE_RUNNING "running" // Stream is active and running +#define AHL_STREAM_STATE_PAUSED "paused" // Stream is active but paused + +// Stream mute state +#define AHL_STREAM_UNMUTED "off" // Stream is not muted +#define AHL_STREAM_MUTED "on" // Stream is muted + +// Property/Volume/Action events #define AHL_ENDPOINT_PROPERTY_EVENT "ahl_endpoint_property_event" #define AHL_ENDPOINT_VOLUME_EVENT "ahl_endpoint_volume_event" -#define AHL_POST_EVENT "ahl_post_event" - -// CPU endianness assumed in all formats -typedef enum SampleType { - AHL_FORMAT_UNKNOWN = -1, // Unknown - AHL_FORMAT_U8 = 0, // Unsigned 8 bit - AHL_FORMAT_S16, // Signed 16 bit Little Endian - AHL_FORMAT_S24, // Signed 24 bit Little Endian using low three bytes in 32-bit word - AHL_FORMAT_S32, // Signed 32 bit Little Endian - AHL_FORMAT_FLOAT, // Float 32 bit Little Endian, Range -1.0 to 1.0 - AHL_FORMAT_FLOAT64, // Float 64 bit Little Endian, Range -1.0 to 1.0 - AHL_FORMAT_IEC958, // IEC-958 Little Endian (SPDIF) - AHL_FORMAT_MU_LAW, // Mu-Law - AHL_FORMAT_A_LAW, // A-Law - AHL_FORMAT_IMA_ADPCM, // Ima-ADPCM - AHL_FORMAT_MPEG, // MPEG - AHL_FORMAT_GSM, // GSM - AHL_FORMAT_G723, // G723 - AHL_FORMAT_DSD, // Direct stream digital - AHL_FORMAT_MAXVALUE, // Enum count, keep at the end -} SampleTypeT; +#define AHL_ENDPOINT_INIT_EVENT "ahl_endpoint_init_event" +#define AHL_POST_ACTION_EVENT "ahl_post_action" +#define AHL_STREAM_STATE_EVENT "ahl_stream_state_event" +#define AHL_ENDPOINT_INIT_EVENT "ahl_endpoint_init_event" + + +// Stream state event types +#define AHL_STREAM_EVENT_START "start" // Stream is inactive +#define AHL_STREAM_EVENT_STOP "stop" // Stream is running +#define AHL_STREAM_EVENT_PAUSE "pause" // Audio stream paused +#define AHL_STREAM_EVENT_RESUME "resume" // Audio stream resumed +#define AHL_STREAM_EVENT_MUTE "mute" // Audio stream muted +#define AHL_STREAM_EVENT_UNMUTE "unmute" // Audio stream unmuted + +///// Interpret returned or configuration information ///// // Known audio domain string definitions (for configuration file format and device URI interpretation) #define AHL_DOMAIN_ALSA "alsa" @@ -95,6 +57,22 @@ typedef enum SampleType { #define AHL_DOMAIN_GSTREAMER "gstreamer" #define AHL_DOMAIN_EXTERNAL "external" +// ALSA Device URI type +#define AHL_DEVICEURITYPE_ALSA_HW "hw" // Alsa hardware device URI +#define AHL_DEVICEURITYPE_ALSA_DMIX "dmix" // Alsa Dmix device URI (only for playback devices) +#define AHL_DEVICEURITYPE_ALSA_DSNOOP "dsnoop" // Alsa DSnoop device URI (only for capture devices) +#define AHL_DEVICEURITYPE_ALSA_SOFTVOL "softvol" // Alsa softvol device URI +#define AHL_DEVICEURITYPE_ALSA_PLUG "plug" // Alsa plug device URI +#define AHL_DEVICEURITYPE_ALSA_OTHER "other" // Alsa domain URI device of unspecified type +#define AHL_DEVICEURITYPE_NOT_ALSA "nonalsa" + +// Define default behavior of audio role when interrupting lower priority sources (in configuration) +#define AHL_INTERRUPTBEHAVIOR_CONTINUE "continue" // Continue to play when interrupted (e.g. media may be ducked) +#define AHL_INTERRUPTBEHAVIOR_CANCEL "cancel" // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) +#define AHL_INTERRUPTBEHAVIOR_PAUSE "pause" // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) + +///// Naming convention ///// + // Standardized name for common audio roles (not enforced in any way, just helps compatibility) #define AHL_ROLE_WARNING "warning" // Safety-relevant or critical alerts/alarms #define AHL_ROLE_GUIDANCE "guidance" // Important user information where user action is expected (e.g. navigation instruction) @@ -118,5 +96,4 @@ typedef enum SampleType { #define AHL_EVENTS_ECHOCANCEL_ENABLE "echocancel_enable" #define AHL_EVENTS_ECHOCANCEL_DISABLE "echocancel_disable" - #endif // AHL_INTERFACE_INCLUDE diff --git a/src/ahl-policy-utils.c b/src/ahl-policy-utils.c new file mode 100644 index 0000000..52c5862 --- /dev/null +++ b/src/ahl-policy-utils.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2017 "Audiokinetic Inc" + * + * 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. + */ + +#include "ahl-policy-utils.h" +#include "wrap-json.h" +#include +#include + +void Add_Endpoint_Property_Double( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, double in_dPropertyValue) +{ + json_object * propValueJ = json_object_new_double(in_dPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); +} + + +void Add_Endpoint_Property_Int( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) +{ + json_object * propValueJ = json_object_new_int(in_iPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); +} + +void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, const char * in_pPropertyValue) +{ + json_object * propValueJ = json_object_new_string(in_pPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); +} + +int PolicyCtxStructToJSON(AHLPolicyCtxT * pPolicyCtx, json_object **ppPolicyCxtJ) +{ + if(pPolicyCtx == NULL) + { + AFB_ERROR("Policy context is NULL"); + return AHL_FAIL; + } + + //Create json object for HALList + json_object *HallListJ = json_object_new_array(); + for(int i=0; ipHALList->len; i++) + { + char *hallname = g_ptr_array_index(pPolicyCtx->pHALList, i); + json_object *hallNameJ = json_object_new_string(hallname); + json_object_array_add(HallListJ, hallNameJ); + } + + int err = wrap_json_pack(ppPolicyCxtJ, "{s:i,s:o}", + "number_roles", (int)g_hash_table_size(pPolicyCtx->pRoleInfo), + "halllist", HallListJ); + if (err) { + AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err)); + } + + return AHL_SUCCESS; +} + + + int PolicyCtxJSONToStruct(json_object *CtxJ, int *pNbAudioRole, GPtrArray *pHALList) +{ + if(CtxJ == NULL || pHALList == NULL) + { + AFB_ERROR("Invalid arguments to PolicyCtxJSONToStruct"); + return AHL_FAIL; + } + + json_object *HallListJ = NULL; + int err = wrap_json_unpack(CtxJ, "{s:i, s:o}", + "number_roles", &pNbAudioRole, + "halllist", &HallListJ); + if (err) { + AFB_ERROR("Unable to unpack JSON endpoint, =%s", wrap_json_get_error_string(err)); + return AHL_FAIL; + } + + if(HallListJ) + { + int nbHall = json_object_array_length(HallListJ); + //Create HallList + for(int i=0; ipPropTable == NULL) + { + AFB_ERROR("Invalid PolicyEndpointStructToJSON arguments"); + return AHL_FAIL; + } + + //Create json object for PropTable + json_object *pPropTableJ = json_object_new_array(); + if(pEndpointInfo->pPropTable) { + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, pEndpointInfo->pPropTable); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + json_object *pPropertyJ = NULL; + int error=wrap_json_pack(&pPropertyJ, "{s:s,s:o}", + "property_name", (char*)key, + "property_value", value + ); + if(error) + { + AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(error)); + return AHL_FAIL; + } + json_object_array_add(pPropTableJ, pPropertyJ); + } + AFB_DEBUG("json object query=%s", json_object_get_string(pPropTableJ)); + } + + //Create json object for Endpoint + int err= wrap_json_pack(ppPolicyEndpointJ, "{s:i,s:i,s:s,s:s,s:s,s:s,s:s,s:i,s:s,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:o}", + "endpoint_id", pEndpointInfo->endpointID, + "endpoint_type", pEndpointInfo->type, + "device_name", pEndpointInfo->gsDeviceName, + "display_name", pEndpointInfo->gsDisplayName, + "device_uri", pEndpointInfo->gsDeviceURI, + "device_domain", pEndpointInfo->gsDeviceDomain, + "audio_role",pEndpointInfo->pRoleName, + "device_uri_type", pEndpointInfo->deviceURIType, + "hal_api_name", pEndpointInfo->gsHALAPIName, + "alsa_cardNum", pEndpointInfo->alsaInfo.cardNum, + "alsa_deviceNum", pEndpointInfo->alsaInfo.deviceNum, + "alsa_subDeviceNum", pEndpointInfo->alsaInfo.subDeviceNum, + "format_samplerate", pEndpointInfo->format.sampleRate, + "format_numchannels", pEndpointInfo->format.numChannels, + "format_sampletype",pEndpointInfo->format.sampleType, + "volume", pEndpointInfo->iVolume, + "property_table", pPropTableJ + ); + if (err) { + AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err)); + return AHL_FAIL; + } + AFB_DEBUG("JSON endpoint information=%s", json_object_get_string(*ppPolicyEndpointJ)); + return AHL_SUCCESS; +} + +int PolicyStreamStructToJSON(StreamInfoT * pPolicyStream, json_object **ppPolicyStreamJ) +{ + if(pPolicyStream == NULL) + { + AFB_ERROR("Invalid arguments to PolicyStreamStructToJSON"); + return AHL_FAIL; + } + + json_object * pEndpointJ = NULL; + int iRet = PolicyEndpointStructToJSON(pPolicyStream->pEndpointInfo, &pEndpointJ); + if (iRet) { + return iRet; + } + + //Create json object for stream + int err = wrap_json_pack(ppPolicyStreamJ, "{s:i,s:i,s:i,s:I,s:i,s:s,s:i,s:i,s:o}", + "stream_id", pPolicyStream->streamID, + "stream_state", pPolicyStream->streamState, + "stream_mute", pPolicyStream->streamMute, + "stream_state_event", &pPolicyStream->streamStateEvent, + "endpoint_sel_mod", pPolicyStream->endpointSelMode, + "role_name", pPolicyStream->pRoleName, + "priority", pPolicyStream->iPriority, + "interrupt_behavior", pPolicyStream->eInterruptBehavior, + "endpoint_info", pEndpointJ + ); + if (err) { + AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err)); + return AHL_FAIL; + } + + AFB_DEBUG("JSON stream information=%s", json_object_get_string(*ppPolicyStreamJ)); + + return AHL_SUCCESS; +} + +int PolicyCtxJSONToEndpoint(json_object *pEndpointJ, EndpointInfoT * pEndpointInfo) +{ + if(pEndpointJ == NULL || pEndpointInfo == NULL /*|| pEndpointInfo->pPropTable == NULL */ ) + { + AFB_ERROR("Invalid arguments for PolicyCtxJSONToEndpoint"); + return AHL_FAIL; + } + + //Unpack Endpoint + json_object *pPropTableJ = NULL; + int err = wrap_json_unpack(pEndpointJ, "{s:i,s:i,s:s,s:s,s:s,s:s,s:s,s:i,s:s,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:o}", + "endpoint_id", &pEndpointInfo->endpointID, + "endpoint_type", &pEndpointInfo->type, + "device_name", &pEndpointInfo->gsDeviceName, + "display_name", &pEndpointInfo->gsDisplayName, + "device_uri", &pEndpointInfo->gsDeviceURI, + "device_domain", &pEndpointInfo->gsDeviceDomain, + "audio_role", &pEndpointInfo->pRoleName, + "device_uri_type", &pEndpointInfo->deviceURIType, + "hal_api_name", &pEndpointInfo->gsHALAPIName, + "alsa_cardNum", &pEndpointInfo->alsaInfo.cardNum, + "alsa_deviceNum", &pEndpointInfo->alsaInfo.deviceNum, + "alsa_subDeviceNum", &pEndpointInfo->alsaInfo.subDeviceNum, + "format_samplerate", &pEndpointInfo->format.sampleRate, + "format_numchannels", &pEndpointInfo->format.numChannels, + "format_sampletype",&pEndpointInfo->format.sampleType, + "volume", &pEndpointInfo->iVolume, + "property_table", &pPropTableJ + ); + if (err) { + AFB_ERROR("Unable to unpack JSON endpoint, =%s", wrap_json_get_error_string(err)); + return AHL_FAIL; + } + + // Unpack prop table + if(pPropTableJ) + { + pEndpointInfo->pPropTable = g_hash_table_new(g_str_hash, g_str_equal); + + int nbProperties = json_object_array_length(pPropTableJ); + for(int i=0; istreamID, + "stream_state", &pPolicyStream->streamState, + "stream_mute", &pPolicyStream->streamMute, + "stream_state_event", &pPolicyStream->streamStateEvent, + "endpoint_sel_mod", &pPolicyStream->endpointSelMode, + "role_name", &pPolicyStream->pRoleName, + "priority", &pPolicyStream->iPriority, + "interrupt_behavior", &pPolicyStream->eInterruptBehavior, + "endpoint_info", &pEndpointJ + ); + + if (err) { + AFB_ERROR("Unable to parse JSON stream information=%s", json_object_get_string(pStreamJ)); + return AHL_FAIL; + } + + int iRet = PolicyCtxJSONToEndpoint(pEndpointJ,pPolicyStream->pEndpointInfo); + if (iRet) { + return iRet; + } + return AHL_SUCCESS; +} + \ No newline at end of file diff --git a/src/ahl-policy-utils.h b/src/ahl-policy-utils.h new file mode 100644 index 0000000..6774f13 --- /dev/null +++ b/src/ahl-policy-utils.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 "Audiokinetic Inc" + * + * 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 AHL_POLICY_UTILS_INCLUDE +#define AHL_POLICY_UTILS_INCLUDE + +#include "ahl-binding.h" + +void Add_Endpoint_Property_Double( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, double in_dPropertyValue); +void Add_Endpoint_Property_Int( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue); +void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, const char * in_pPropertyValue); + +int PolicyCtxStructToJSON(AHLPolicyCtxT * pPolicyCtx, json_object **ppPolicyCxtJ); +int PolicyCtxJSONToStruct(json_object *CtxJ, int *pNbAudioRole,GPtrArray *pHALList); +int PolicyEndpointStructToJSON(EndpointInfoT * pPolicyEndpoint, json_object **ppPolicyEndpointJ); +int PolicyCtxJSONToEndpoint(json_object *pEndpointJ, EndpointInfoT * pPolicyStream); +int PolicyStreamStructToJSON(StreamInfoT * pPolicyStream, json_object **ppPolicyStreamJ); +int PolicyCtxJSONToStream(json_object *pStreamJ, StreamInfoT * pPolicyStream); + +#endif // AHL_POLICY_UTILS_INCLUDE diff --git a/src/ahl-policy.c b/src/ahl-policy.c index 110f7e9..ba15fd2 100644 --- a/src/ahl-policy.c +++ b/src/ahl-policy.c @@ -1,6 +1,5 @@ /* * Copyright (C) 2017 "Audiokinetic Inc" - * Author Francois Thibault * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,218 +18,117 @@ #include #include #include -#include "ahl-binding.h" #include "wrap-json.h" +#include "ahl-policy-utils.h" +#include "ahl-policy.h" -#define MAX_ACTIVE_STREAM_POLICY 30 - -// This file provides example of custom, business logic driven policy actions that can affect behavior of the high level -// TODO: Currently only isolated in separate source file. Objective is to make this to at least a shared lib plug-in (shared C context) -// Going a step further would be to implement this within a distinct policy binding (requires to switch to JSON interface) - -extern AHLCtxT g_AHLCtx; // TODO: Cannot stay if moved to external module - -typedef struct StreamPolicyInfo { - int RolePriority; - int iVolume; - int iVolumeSavedMute; - streamID_t streamID; - InterruptedBehaviorT interruptBehavior; -} StreamPolicyInfoT; - - -typedef struct EndPointPolicyInfo { - int endpointKey; - endpointID_t endpointID; - EndpointTypeT type; - GArray * streamInfo; //List of playing or duck stream at a given endpoint -} EndPointPolicyInfoT; - -typedef enum SystemState { - SYSTEM_STARTUP = 0, // Startup - SYSTEM_SHUTDOWN, // ShutDown - SYSTEM_NORMAL, // Normal - SYSTEM_LOW_POWER, // Low Power, save mode - SYSTEM_MAXVALUE // Enum count, keep at the end -} SystemStateT; - - -// Global Policy Local context -typedef struct PolicyLocalCtx { - GArray * pSourceEndpoints; // List of Source Endpoint with playing stream or interrupted stream - GArray * pSinkEndpoints; // List of Sink Endpoint with playing stream or interrupted stream - GArray * pStreamOpenPerPolicy; //List of number of openstream per policy - GArray * pMaxStreamOpenPerPolicy; //List of number of openstream per policy - GArray * pVolDuckPerPolicy; //List of number of openstream per policy - SystemStateT systemState; -} PolicyLocalCtxT; +#ifndef AHL_DISCONNECT_POLICY +// Global Context PolicyLocalCtxT g_PolicyCtx; -//Helper Functions -static void Add_Endpoint_Property_Double( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, double in_dPropertyValue) -{ - json_object * propValueJ = json_object_new_double(in_dPropertyValue); - g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); -} - - -static void Add_Endpoint_Property_Int( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) -{ - json_object * propValueJ = json_object_new_int(in_iPropertyValue); - g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); -} - -static void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, const char * in_pPropertyValue) -{ - json_object * propValueJ = json_object_new_string(in_pPropertyValue); - g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); -} - -static int PolicySetVolume(EndpointInfoT * pEndpointInfo, int iVolume) +// Helper Functions +static int getStreamConfig(char *pAudioRole, StreamConfigT *pStreamConfig) { - - // Using audio role available from endpoint to target the right HAL control (build string based on convention) - GString * gsHALControlName; - switch(pEndpointInfo->deviceURIType) + if(pAudioRole == NULL || pStreamConfig==NULL) { - case DEVICEURITYPE_ALSA_HW: - gsHALControlName = g_string_new("Master_Playback_Volume"); - break; - case DEVICEURITYPE_ALSA_DMIX: - case DEVICEURITYPE_ALSA_DSNOOP: - case DEVICEURITYPE_ALSA_PLUG: - case DEVICEURITYPE_ALSA_SOFTVOL: - gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); - g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) - break; - default: - //Set volume to zero for display purpose only. - //Not support yet - AFB_WARNING("Endpoint %s is not support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); - break; + return POLICY_FAIL; } - // Set endpoint volume using HAL services (leveraging ramps etc.) - json_object *j_response, *j_query = NULL; - - // Package query - int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); - if (err) + if ( strcasecmp(pAudioRole,AHL_ROLE_WARNING) == 0 ) { - AFB_ERROR("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); - return err; + pStreamConfig->iNbMaxStream = 4; + pStreamConfig->iVolumeInit = 80; + pStreamConfig->iVolumeDuckValue = 0; } - - //TODO implement Volume limitation based on policy - - // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); - if (err) + else if ( strcasecmp(pAudioRole,AHL_ROLE_GUIDANCE) == 0 ) { - AFB_ERROR("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); - return err; + pStreamConfig->iNbMaxStream = 10; + pStreamConfig->iVolumeInit = 70; + pStreamConfig->iVolumeDuckValue = 30; } - AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); - - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume); - if (err) + else if ( strcasecmp(pAudioRole,AHL_ROLE_NOTIFICATION) == 0 ) { - AFB_ERROR("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); - return err; + pStreamConfig->iNbMaxStream = 4; + pStreamConfig->iVolumeInit = 80; + pStreamConfig->iVolumeDuckValue = 10; } - afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); - - pEndpointInfo->iVolume = iVolume; - - return 0; -} - - -static int PolicySetVolumeMute(EndpointInfoT * pEndpointInfo, int iVolume) -{ - - // Using audio role available from endpoint to target the right HAL control (build string based on convention) - GString * gsHALControlName; - switch(pEndpointInfo->deviceURIType) + else if ( strcasecmp(pAudioRole,AHL_ROLE_COMMUNICATION) == 0 ) { - case DEVICEURITYPE_ALSA_HW: - gsHALControlName = g_string_new("Master_Playback_Volume"); - break; - case DEVICEURITYPE_ALSA_DMIX: - case DEVICEURITYPE_ALSA_DSNOOP: - case DEVICEURITYPE_ALSA_PLUG: - case DEVICEURITYPE_ALSA_SOFTVOL: - gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); - g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) - break; - default: - //Set volume to zero for display purpose only. - //Not support yet - AFB_WARNING("Endpoint %s is not support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); - break; + pStreamConfig->iNbMaxStream = 10; + pStreamConfig->iVolumeInit = 70; + pStreamConfig->iVolumeDuckValue = 10; } - - // Set endpoint volume using HAL services (leveraging ramps etc.) - json_object *j_response, *j_query = NULL; - - // Package query - int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); - if (err) + else if ( strcasecmp(pAudioRole,AHL_ROLE_ENTERTAINMENT) == 0 ) { - AFB_ERROR("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); - return err; + pStreamConfig->iNbMaxStream = MAX_ACTIVE_STREAM_POLICY; + pStreamConfig->iVolumeInit = 60; + pStreamConfig->iVolumeDuckValue = 40; } - - //TODO implement Volume limitation based on policy - - // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); - if (err) + else if ( strcasecmp(pAudioRole,AHL_ROLE_SYSTEM) == 0 ) { - AFB_ERROR("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); - return err; + pStreamConfig->iNbMaxStream = 2; + pStreamConfig->iVolumeInit = 100; + pStreamConfig->iVolumeDuckValue = 0; } - AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); - - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume); - if (err) + else if ( strcasecmp(pAudioRole,AHL_ROLE_STARTUP) == 0 ) { - AFB_ERROR("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); - return err; + pStreamConfig->iNbMaxStream = 1; + pStreamConfig->iVolumeInit = 90; + pStreamConfig->iVolumeDuckValue = 0; } - afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); - - return 0; + else if ( strcasecmp(pAudioRole,AHL_ROLE_SHUTDOWN) == 0 ) + { + pStreamConfig->iNbMaxStream = 1; + pStreamConfig->iVolumeInit = 90; + pStreamConfig->iVolumeDuckValue = 0; + } + return POLICY_SUCCESS; } - -static int PolicySetVolumeRamp(EndpointInfoT * pEndpointInfo, int iVolume) +static int PolicySetVolume(int iEndpointID, int iEndpointType, char *pHalApiName, char *AudioRole, DeviceURITypeT deviceType, int iVolume, bool bMute) { - + if(pHalApiName == NULL) + { + AFB_WARNING("SetVolume cannot be accomplished without proper HAL association"); + return POLICY_FAIL; + } + + if(AudioRole == NULL) + { + AFB_ERROR("Invalid AudioRole : %s",AudioRole); + return POLICY_FAIL; + } + // Using audio role available from endpoint to target the right HAL control (build string based on convention) - GString * gsHALControlName; - switch(pEndpointInfo->deviceURIType) + GString * gsHALControlName = NULL; + switch(deviceType) { case DEVICEURITYPE_ALSA_HW: - gsHALControlName = g_string_new("Master_Ramp"); + gsHALControlName = g_string_new("Master_Playback_Volume"); break; case DEVICEURITYPE_ALSA_DMIX: case DEVICEURITYPE_ALSA_DSNOOP: case DEVICEURITYPE_ALSA_PLUG: case DEVICEURITYPE_ALSA_SOFTVOL: - gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); - g_string_append(gsHALControlName,"_Ramp"); // Or _Vol for direct control (no ramping) + gsHALControlName = g_string_new(AudioRole); + if(bMute == false) + { + AFB_DEBUG("Using ramp"); + g_string_append(gsHALControlName,"_Ramp"); + } + else + { + AFB_DEBUG("Not using ramp"); + g_string_append(gsHALControlName,"_Vol"); // no ramping + } break; default: //Set volume to zero for display purpose only. //Not support yet - AFB_WARNING("Endpoint %s is not a support Device Type and can't set volume",pEndpointInfo->gsDeviceName->str); + AFB_WARNING("Device Type %i is not support and can't set volume on HalName %s",deviceType, pHalApiName); + return POLICY_FAIL; break; } @@ -241,44 +139,43 @@ static int PolicySetVolumeRamp(EndpointInfoT * pEndpointInfo, int iVolume) int err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",iVolume); if (err) { - AFB_WARNING("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); - return err; + AFB_ERROR("Invalid query for HAL ctlset: %s with errorcode: %i",json_object_to_json_string(j_query), err); + return POLICY_FAIL; } //TODO implement Volume limitation based on policy // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); - if (err) - { - AFB_WARNING("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); - return err; - } - AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); - - // Package event data - json_object * eventDataJ = NULL; - err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i, s:s}","endpoint_id", pEndpointInfo->endpointID,"endpoint_type",pEndpointInfo->type,"value",iVolume, "audio_role",gsHALControlName->str); - if (err) - { - AFB_WARNING("Invalid event data for volume event, Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); - return err; - } - afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); + err = afb_service_call_sync(pHalApiName, "ctlset", j_query, &j_response); + if (err) + { + AFB_ERROR("Could not ctlset on HAL: %s with errorcode: %i",pHalApiName, err); + return POLICY_FAIL; + } + AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + if (bMute == false) { + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:s,s:i,s:i,s:i,s:s}","event_name", AHL_ENDPOINT_VOLUME_EVENT,"endpoint_id", iEndpointID, "endpoint_type", iEndpointType,"value",iVolume, "audio_role", AudioRole); + if (err) + { + AFB_ERROR("Invalid event data for volume event %s with errorcode: %i",json_object_to_json_string(eventDataJ), err); + return POLICY_FAIL; + } - pEndpointInfo->iVolume = iVolume; + audiohlapi_raise_event(eventDataJ); + } - return 0; + return POLICY_SUCCESS; } -static int PolicyGetVolume(EndpointInfoT * pEndpointInfo) +static int PolicyGetVolume(int iEndpointID, int iEndpointType, char *pHalApiName, char *AudioRole, DeviceURITypeT deviceType, int *pVolume) { - - - GString * gsHALControlName; + GString * gsHALControlName = NULL; // Using audio role available from endpoint to target the right HAL control (build string based on convention) - switch(pEndpointInfo->deviceURIType) + switch(deviceType) { case DEVICEURITYPE_ALSA_HW: gsHALControlName = g_string_new("Master_Playback_Volume"); @@ -287,37 +184,37 @@ static int PolicyGetVolume(EndpointInfoT * pEndpointInfo) case DEVICEURITYPE_ALSA_DSNOOP: case DEVICEURITYPE_ALSA_PLUG: case DEVICEURITYPE_ALSA_SOFTVOL: - gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + gsHALControlName = g_string_new(AudioRole); g_string_append(gsHALControlName,"_Vol"); // Or _Vol for direct control (no ramping) break; default: - //Set volume to zero for display purpose only. - //Not support yet - pEndpointInfo->iVolume = 0; - AFB_WARNING("Endpoint %s is a support Device Type and can't get volume",pEndpointInfo->gsDeviceName->str); - break; + // Set volume to zero for display purpose only. + // Not supported yet + *pVolume = 0; + AFB_WARNING("Can't get volume on HAL: %s for device type: %d",pHalApiName,deviceType); + return POLICY_FAIL; } // Set endpoint volume using HAL services (leveraging ramps etc.) - json_object *j_response, *j_query = NULL; + json_object *j_response = NULL, *j_query = NULL; // Package query int err = wrap_json_pack(&j_query,"{s:s}","label",gsHALControlName->str); if (err) { - AFB_WARNING("Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); - return err; + AFB_WARNING("Invalid query for HAL ctlset: %s, errorcode: %i",json_object_to_json_string(j_query),err); + return POLICY_FAIL; } //TODO implement Volume limitation based on policy - // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlget", j_query, &j_response); + // Get the volume using the HAL + err = afb_service_call_sync(pHalApiName, "ctlget", j_query, &j_response); if (err) { - AFB_WARNING("Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); - return err; - } + AFB_WARNING("Could not ctlset on HAL: %s, errorcode: %i",pHalApiName, err); + return POLICY_FAIL; + } AFB_DEBUG("HAL ctlget response=%s", json_object_to_json_string(j_response)); // Parse response @@ -333,7 +230,7 @@ static int PolicyGetVolume(EndpointInfoT * pEndpointInfo) err = wrap_json_unpack(jVal, "[ii]", &val1, &val2); if (err) { AFB_ERROR("Volume retrieve failed Could not retrieve volume value -> %s", json_object_get_string(jVal)); - return -1; + return POLICY_FAIL; } } @@ -342,38 +239,43 @@ static int PolicyGetVolume(EndpointInfoT * pEndpointInfo) err = wrap_json_unpack(jVal, "[i]", &val1); if (err) { AFB_ERROR("Volume retrieve failed Could not retrieve volume value -> %s", json_object_get_string(jVal)); - return -1; + return POLICY_FAIL; } } - pEndpointInfo->iVolume = val1; + *pVolume = val1; + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:s,s:i,s:i,s:i,s:s}","event_name", AHL_ENDPOINT_VOLUME_EVENT,"endpoint_id", iEndpointID, "endpoint_type", iEndpointType,"value",*pVolume, "audio_role", AudioRole); + if (err) + { + AFB_ERROR("Invalid event data for volume event %s with errorcode: %i",json_object_to_json_string(eventDataJ), err); + return POLICY_FAIL; + } + + audiohlapi_raise_event(eventDataJ); - return 0; + return POLICY_SUCCESS; } -static void PolicyPostStateEvent(struct afb_event streamStateEvent, StreamEventT eventState) +static void PolicyPostStateEvent(int iStreamID, StreamEventT eventState) { json_object * eventDataJ = NULL; - int err = wrap_json_pack(&eventDataJ,"{s:i}","stateEvent",eventState); + int err = wrap_json_pack(&eventDataJ,"{s:s,s:i,s:i}","event_name", AHL_STREAM_STATE_EVENT,"stream_id",iStreamID, "state_event",eventState); if (err) { AFB_ERROR("Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); } else { - afb_event_push(streamStateEvent,eventDataJ); + audiohlapi_raise_event(eventDataJ); } } -//This function is based on ALSA right now but it could be adapt to support other framework like pulseaudio or gstreamer -static int PolicyGenEndPointKey(EndpointInfoT *pEndPointInfo) -{ - return (pEndPointInfo->type << 24)|(pEndPointInfo->alsaInfo.cardNum << 16)|(pEndPointInfo->alsaInfo.deviceNum << 8)|(pEndPointInfo->alsaInfo.subDeviceNum); -} - -static EndPointPolicyInfoT *PolicySearchEndPoint(EndpointTypeT type, int key) +static EndPointPolicyInfoT *PolicySearchEndPoint(EndpointTypeT type, char *pDeviceName) { GArray *pcurEndpointArray = NULL; @@ -390,7 +292,7 @@ static EndPointPolicyInfoT *PolicySearchEndPoint(EndpointTypeT type, int key) { EndPointPolicyInfoT * pCurEndpoint = &g_array_index(pcurEndpointArray,EndPointPolicyInfoT,i); - if(pCurEndpoint->endpointKey == key) + if(strcasecmp(pCurEndpoint->pDeviceName,pDeviceName)==0) { return pCurEndpoint; } @@ -399,395 +301,403 @@ static EndPointPolicyInfoT *PolicySearchEndPoint(EndpointTypeT type, int key) return NULL; } -static StreamPolicyInfoT *PolicySearchStream(EndPointPolicyInfoT * pCurEndpoint, int streamID) -{ - for(int i=0; istreamInfo->len; i++) +static int PolicyAddEndPoint(StreamInfoT *pStreamInfo) +{ + EndPointPolicyInfoT *pPolicyEndPoint = PolicySearchEndPoint(pStreamInfo->pEndpointInfo->type, pStreamInfo->pEndpointInfo->gsDeviceName); + if(pPolicyEndPoint == NULL) { - StreamPolicyInfoT * pCurStream = &g_array_index(pCurEndpoint->streamInfo,StreamPolicyInfoT,i); - - if(pCurStream->streamID == streamID) + //create EndPoint and add playing stream + EndPointPolicyInfoT newEndPointPolicyInfo; + newEndPointPolicyInfo.endpointID = pStreamInfo->pEndpointInfo->endpointID; + newEndPointPolicyInfo.type = pStreamInfo->pEndpointInfo->type; + newEndPointPolicyInfo.deviceType = pStreamInfo->pEndpointInfo->deviceURIType; + newEndPointPolicyInfo.pDeviceName = strdup(pStreamInfo->pEndpointInfo->gsDeviceName); + newEndPointPolicyInfo.pHalApiName = strdup(pStreamInfo->pEndpointInfo->gsHALAPIName); + newEndPointPolicyInfo.iVolume = pStreamInfo->pEndpointInfo->iVolume; + newEndPointPolicyInfo.streamInfo = g_array_new(FALSE, TRUE, sizeof(StreamPolicyInfoT));; + + if(pStreamInfo->pEndpointInfo->type == ENDPOINTTYPE_SINK) { - return pCurStream; + g_array_append_val(g_PolicyCtx.pSinkEndpoints, newEndPointPolicyInfo); } + else + { + g_array_append_val(g_PolicyCtx.pSourceEndpoints, newEndPointPolicyInfo); + } + } - - return NULL; + return POLICY_SUCCESS; } -static StreamInfoT * PolicyGetActiveStream(streamID_t in_streamID) +static int PolicyAddStream(EndPointPolicyInfoT *pCurrEndPointPolicy, StreamInfoT *pStreamInfo) { - int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; - StreamInfoT * pStreamInfo = NULL; - for ( int i = 0; i < iNumActiveStreams ; i++ ) { - StreamInfoT * pCurStreamInfo = &g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); - if (pCurStreamInfo->streamID == in_streamID){ - pStreamInfo = pCurStreamInfo; - break; - } - } - return pStreamInfo; + StreamPolicyInfoT newStreamPolicyInfo; + + newStreamPolicyInfo.streamID = pStreamInfo->streamID; + newStreamPolicyInfo.RolePriority = pStreamInfo->iPriority; + newStreamPolicyInfo.pAudioRole = pStreamInfo->pRoleName; + newStreamPolicyInfo.interruptBehavior = pStreamInfo->eInterruptBehavior; + newStreamPolicyInfo.iDuckVolume = 0; + g_array_append_val(pCurrEndPointPolicy->streamInfo, newStreamPolicyInfo); + return POLICY_SUCCESS; } - -static int PolicyFindRoleIndex( const char * in_pAudioRole) +static int PolicyRunningIdleTransition(EndPointPolicyInfoT *pCurrEndPointPolicy,StreamInfoT * pStreamInfo) { - int index = -1; // Not found - for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++) + int err=0; + if(pCurrEndPointPolicy == NULL || pCurrEndPointPolicy->streamInfo->len == 0) { - GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i ); - if ( strcasecmp(gs.str,in_pAudioRole) == 0 ) + //Remove endpoint + AFB_ERROR("StreamID not found in active Endpoint when Running to Idle transition is request"); + return POLICY_FAIL; + } + //Search for the matching stream + for(int i=0; istreamInfo->len; i++) + { + StreamPolicyInfoT currentPolicyStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,i); + if(currentPolicyStreamInfo.streamID == pStreamInfo->streamID) { - index = i; - break; + //remove the current stream + g_array_remove_index(pCurrEndPointPolicy->streamInfo, i); + if(pCurrEndPointPolicy->streamInfo->len > 0) //need to unduck + { + //check the last element(Akways highest priority) + StreamPolicyInfoT HighPriorityStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); + switch(currentPolicyStreamInfo.interruptBehavior) + { + case INTERRUPTBEHAVIOR_CONTINUE: + //unduck and set Volume back to original value + err= PolicySetVolume(pCurrEndPointPolicy->endpointID, + pCurrEndPointPolicy->type, + pCurrEndPointPolicy->pHalApiName, + HighPriorityStreamInfo.pAudioRole, + pCurrEndPointPolicy->deviceType, + HighPriorityStreamInfo.iDuckVolume, + false); + if(err) + { + return POLICY_FAIL; + } + + return POLICY_SUCCESS; + break; + case INTERRUPTBEHAVIOR_PAUSE: + //pInterruptStreamInfo->streamState = STREAM_STATE_RUNNING; + PolicyPostStateEvent(HighPriorityStreamInfo.streamID,STREAM_EVENT_RESUME); + return POLICY_SUCCESS; + break; + + case INTERRUPTBEHAVIOR_CANCEL: + PolicyPostStateEvent(HighPriorityStreamInfo.streamID,STREAM_EVENT_START); + return POLICY_SUCCESS; + break; + default: + AFB_ERROR("Unsupported Intterupt Behavior"); + return POLICY_FAIL; + break; + } + } } - } - return index; + } + return POLICY_SUCCESS; } -static int PolicyRemoveStream(EndPointPolicyInfoT *pCurrEndPointPolicy, int RemoveIndex) +static int PolicyIdleRunningTransition(EndPointPolicyInfoT *pCurrEndPointPolicy, StreamInfoT * pStreamInfo) { - //Validate - if(RemoveIndex >= pCurrEndPointPolicy->streamInfo->len) + int err=0; + if(pCurrEndPointPolicy->streamInfo == NULL) { - return -1; - + AFB_ERROR("pCurrEndPointPolicy->streamInfo is null on an active endpoint"); + return POLICY_FAIL; } - - g_array_remove_index(pCurrEndPointPolicy->streamInfo,RemoveIndex); - - if(pCurrEndPointPolicy->streamInfo->len == 0) + if(pCurrEndPointPolicy->streamInfo->len == 0) //No stream is playing on this endpoint + { + PolicyAddStream(pCurrEndPointPolicy, pStreamInfo); + } + else //Interrupt case { - - //Free streem - g_array_free(pCurrEndPointPolicy->streamInfo,TRUE); - pCurrEndPointPolicy->streamInfo = NULL; - - GArray *pcurEndpointArray = NULL; - - if(pCurrEndPointPolicy->type==ENDPOINTTYPE_SINK) + //check the last element + StreamPolicyInfoT *pCurrentActiveStreamInfo = &g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); + g_assert_nonnull(pCurrentActiveStreamInfo); + if(pStreamInfo->iPriority >= pCurrentActiveStreamInfo->RolePriority) { - pcurEndpointArray = g_PolicyCtx.pSinkEndpoints; + switch(pStreamInfo->eInterruptBehavior) + { + case INTERRUPTBEHAVIOR_CONTINUE: + //Save the current Volume and set the docking volume + pCurrentActiveStreamInfo->iDuckVolume = pStreamInfo->pEndpointInfo->iVolume; + StreamConfigT StreamConfig; + err = getStreamConfig(pStreamInfo->pRoleName, &StreamConfig); + if(err == POLICY_FAIL) + { + AFB_ERROR("Error getting stream configuration for audiorole: %s", pStreamInfo->pRoleName); + return POLICY_FAIL; + } + err= PolicySetVolume(pCurrEndPointPolicy->endpointID, + pCurrEndPointPolicy->type, + pCurrEndPointPolicy->pHalApiName, + pCurrentActiveStreamInfo->pAudioRole, + pCurrEndPointPolicy->deviceType, + StreamConfig.iVolumeDuckValue, + false); + if(err) + { + AFB_ERROR("Set Volume return with errorcode%i for streamID: %i and Hal:%s", err, pCurrentActiveStreamInfo->streamID, pCurrEndPointPolicy->pHalApiName); + return POLICY_FAIL; + } + break; + case INTERRUPTBEHAVIOR_PAUSE: + PolicyPostStateEvent(pCurrentActiveStreamInfo->streamID,STREAM_EVENT_PAUSE); + break; + + case INTERRUPTBEHAVIOR_CANCEL: + PolicyPostStateEvent(pCurrentActiveStreamInfo->streamID,STREAM_EVENT_STOP); + g_array_remove_index(pCurrEndPointPolicy->streamInfo, pCurrEndPointPolicy->streamInfo->len-1); + break; + default: + AFB_ERROR("Unsupported Intterupt Behavior"); + return AHL_POLICY_REJECT; + break; + + } + + //Add the playing stream at last + PolicyAddStream(pCurrEndPointPolicy, pStreamInfo); } else { - pcurEndpointArray = g_PolicyCtx.pSourceEndpoints; + //Higher Priority Stream is playing + AFB_ERROR("Higher Priority Stream is playing"); + return POLICY_FAIL; } - for(int i=0; ilen; i++) - { - EndPointPolicyInfoT * pCurEndpoint = &g_array_index(pcurEndpointArray,EndPointPolicyInfoT,i); - if(pCurEndpoint->endpointKey == pCurrEndPointPolicy->endpointKey) - { - g_array_remove_index(pcurEndpointArray, i); - return 0; - } - } + } - - return 0; + + return POLICY_SUCCESS; } -static int PolicyRunningIdleTransition(EndPointPolicyInfoT *pCurrEndPointPolicy,StreamInfoT * pStreamInfo) +static void PolicySpeedModify(int speed) { - if(pCurrEndPointPolicy == NULL) + + for(int i=0; ilen; i++) { - //Remove endpoint - AFB_ERROR("No Active Endpoint has been found"); - return -1; - } + EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSinkEndpoints,EndPointPolicyInfoT,i); + if(pCurEndpoint == NULL) + { + AFB_WARNING("Sink Endpoint not found"); + return; + } - if(pCurrEndPointPolicy->streamInfo->len>0) - { - //Search for the matching stream - int iNumStream = pCurrEndPointPolicy->streamInfo->len; - for(int i=0; istreamInfo->len > 0 ) { - StreamPolicyInfoT currentPolicyStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,i); - if(currentPolicyStreamInfo.streamID == pStreamInfo->streamID) + StreamPolicyInfoT * pCurStream = &g_array_index(pCurEndpoint->streamInfo,StreamPolicyInfoT,pCurEndpoint->streamInfo->len-1); + if(strcasecmp(pCurStream->pAudioRole,AHL_ROLE_ENTERTAINMENT)==0) { - - //Unduck case - if((i==(pCurrEndPointPolicy->streamInfo->len-1)) && (pCurrEndPointPolicy->streamInfo->len > 1)) - { - //remove the current stream - g_array_remove_index(pCurrEndPointPolicy->streamInfo, i); - - //check the last element(Akways highest priority) - StreamPolicyInfoT HighPriorityStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); - - //Get Stream Info - StreamInfoT * pInterruptStreamInfo = PolicyGetActiveStream(HighPriorityStreamInfo.streamID); - if (pInterruptStreamInfo == NULL) { - AFB_ERROR("Stream not found, Specified stream not currently active stream_id -> %d",HighPriorityStreamInfo.streamID); - return -1; - } - - int err; - switch(currentPolicyStreamInfo.interruptBehavior) - { - case AHL_INTERRUPTEDBEHAVIOR_CONTINUE: - //unduck and set Volume back to original value - err= PolicySetVolumeRamp(pInterruptStreamInfo->pEndpointInfo, HighPriorityStreamInfo.iVolume); - if(err) - { - AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); - return -1; - } - break; - case AHL_INTERRUPTEDBEHAVIOR_PAUSE: - pInterruptStreamInfo->streamState = STREAM_STATE_RUNNING; - PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_RESUME); - break; - - case AHL_INTERRUPTEDBEHAVIOR_CANCEL: - AFB_ERROR("StreamID with Cancel InterruptedBehavior can't be unInterrupted"); - return -1; - break; - default: - AFB_ERROR("Unsupported Intterupt Behavior"); - return -1; - break; - } - - } - else + if(speed > 30 && speed < 100) { - //remove the current stream - PolicyRemoveStream(pCurrEndPointPolicy, i); - } - return 0; - } - - } + int volume =speed; + PolicySetVolume(pCurEndpoint->endpointID, + pCurEndpoint->type, + pCurEndpoint->pHalApiName, + pCurStream->pAudioRole, + pCurEndpoint->deviceType, + volume, + false); + } + } + } } - - AFB_ERROR("StreamID does not match any playing stream"); - return -1; } -static int PolicyIdleRunningTransition(EndPointPolicyInfoT *pCurrEndPointPolicy, StreamInfoT * pStreamInfo, int AudioRoleIndex, int EndPointKey) +static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) { - int stream_priority; - int ori_key_pos; - - bool bKeyFound=g_hash_table_lookup_extended(g_AHLCtx.policyCtx.pRolePriority,pStreamInfo->pEndpointInfo->gsAudioRole->str,&ori_key_pos,&stream_priority); - if(bKeyFound==false) + if(g_PolicyCtx.pHALList) { - AFB_ERROR("Can't find stream priority, request will be rejected"); - return -1; + for(int i=0; ilen; i++) + { + HalInfoT *pHalInfo = g_ptr_array_index(g_PolicyCtx.pHALList, i); + // Retrieve card number (e.g. hw:0) + int iCardNum = atoi(pHalInfo->pDevID+3); + if (iCardNum == io_pEndpointInfo->alsaInfo.cardNum) { + io_pEndpointInfo->gsHALAPIName=strdup(pHalInfo->pAPIName); + io_pEndpointInfo->gsDisplayName=strdup(pHalInfo->pDisplayName); + return POLICY_SUCCESS; + } + } } - //stream_priority = GPOINTER_TO_INT(value); + io_pEndpointInfo->gsHALAPIName=strdup(AHL_POLICY_UNDEFINED_HALNAME); + io_pEndpointInfo->gsDisplayName=strdup(AHL_POLICY_UNDEFINED_DISPLAYNAME); + + return POLICY_FAIL; +} +static int GetHALList(void) +{ + json_object *j_response, *j_query = NULL; + int err; + err = afb_service_call_sync("alsacore", "hallist", j_query, &j_response); + if (err) { + AFB_ERROR("Could not retrieve list of HAL from ALSA core"); + return POLICY_FAIL; + } + AFB_DEBUG("ALSAcore hallist response=%s", json_object_to_json_string(j_response)); - InterruptedBehaviorT InterruptBehavior = g_array_index(g_AHLCtx.policyCtx.pInterruptBehavior,InterruptedBehaviorT,AudioRoleIndex); - int err; - if(pCurrEndPointPolicy == NULL) //No stream is playing on this endpoint + // Look through returned list for matching card + json_object * jRespObj = NULL; + json_object_object_get_ex(j_response, "response", &jRespObj); + int iNumHAL = json_object_array_length(jRespObj); + for ( int i = 0 ; i < iNumHAL; i++) { - EndPointPolicyInfoT newEndPointPolicyInfo ; - StreamPolicyInfoT newStreamPolicyInfo; - - //create EndPoint and add playing stream - newEndPointPolicyInfo.endpointKey = EndPointKey; - newEndPointPolicyInfo.endpointID = pStreamInfo->pEndpointInfo->endpointID; - newEndPointPolicyInfo.type = pStreamInfo->pEndpointInfo->type; - newEndPointPolicyInfo.streamInfo = g_array_new(FALSE,TRUE,sizeof(StreamPolicyInfoT)); - - newStreamPolicyInfo.RolePriority = stream_priority; - newStreamPolicyInfo.iVolume = pStreamInfo->pEndpointInfo->iVolume; - newStreamPolicyInfo.streamID = pStreamInfo->streamID; - newStreamPolicyInfo.interruptBehavior = InterruptBehavior; - - g_array_append_val(newEndPointPolicyInfo.streamInfo, newStreamPolicyInfo); - g_array_append_val(g_PolicyCtx.pSinkEndpoints, newEndPointPolicyInfo); + json_object * jHAL = json_object_array_get_idx(jRespObj,i); + char * pDevIDStr = NULL; + char * pAPIName = NULL; + char * pShortName = NULL; + int err = wrap_json_unpack(jHAL, "{s:s,s:s,s:s}", "devid", &pDevIDStr,"api", &pAPIName,"shortname",&pShortName); + if (err) { + AFB_ERROR("Could not retrieve devid string=%s", json_object_get_string(jHAL)); + return POLICY_FAIL; + } - /* - int *pVolume = &g_array_index(g_PolicyCtx.pVolInitPerPolicy,int,AudioRoleIndex); - pStreamInfo->pEndpointInfo->iVolume = *pVolume; - - err= PolicySetVolumeRamp(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); - if(err) - { - AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); - return err; - } */ - } - else - { - //Currently contains playing or duck stream - if(pCurrEndPointPolicy->streamInfo->len > 0) + HalInfoT *pHalInfo = (HalInfoT*)malloc(sizeof(HalInfoT)); + if(pHalInfo == NULL) { - //check the last element - StreamPolicyInfoT HighPriorityStreamInfo = g_array_index(pCurrEndPointPolicy->streamInfo,StreamPolicyInfoT,pCurrEndPointPolicy->streamInfo->len-1); - if((stream_priority) >= HighPriorityStreamInfo.RolePriority) - { - //Get Stream Info - StreamInfoT * pInterruptStreamInfo = PolicyGetActiveStream(HighPriorityStreamInfo.streamID); - if (pInterruptStreamInfo == NULL) { - AFB_ERROR("Stream not found Specified stream not currently active stream_id -> %d",HighPriorityStreamInfo.streamID); - return -1; - } - - switch(InterruptBehavior) - { - case AHL_INTERRUPTEDBEHAVIOR_CONTINUE: - //Save the current Volume and set the docking volume - HighPriorityStreamInfo.iVolume = pInterruptStreamInfo->pEndpointInfo->iVolume; - - int *pVolume = &g_array_index(g_PolicyCtx.pVolDuckPerPolicy,int,AudioRoleIndex); - err= PolicySetVolumeRamp(pInterruptStreamInfo->pEndpointInfo, *pVolume); - if(err) - { - AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); - return -1; - } - break; - case AHL_INTERRUPTEDBEHAVIOR_PAUSE: - pInterruptStreamInfo->streamState = STREAM_STATE_PAUSED; - PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); - break; - - case AHL_INTERRUPTEDBEHAVIOR_CANCEL: - pInterruptStreamInfo->streamState = STREAM_STATE_IDLE; - PolicyPostStateEvent(pInterruptStreamInfo->streamStateEvent,STREAM_EVENT_STOP); - g_array_remove_index(pCurrEndPointPolicy->streamInfo, pCurrEndPointPolicy->streamInfo->len-1); - - break; - default: - AFB_ERROR("Unsupported Intterupt Behavior"); - return AHL_POLICY_REJECT; - break; - - } - - //Add the playing stream at index 0 - StreamPolicyInfoT newStreamPolicyInfo; - newStreamPolicyInfo.RolePriority = stream_priority; - newStreamPolicyInfo.iVolume = pStreamInfo->pEndpointInfo->iVolume; - newStreamPolicyInfo.streamID = pStreamInfo->streamID; - newStreamPolicyInfo.interruptBehavior = InterruptBehavior; - - //Insert at the end, become highest priority streamID - g_array_append_val(pCurrEndPointPolicy->streamInfo, newStreamPolicyInfo); - - err= PolicySetVolumeRamp(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); - if(err) - { - AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i", pInterruptStreamInfo->pEndpointInfo->gsDeviceName->str, err); - return err; - } - - } - else - { - //Higher Priority Stream is playing - AFB_NOTICE("Higher Priority Stream is playing"); - return -1; - } - + AFB_ERROR("Unable to allocate memory for HalInfo"); + return POLICY_FAIL; } - else - { - //Remove endpoint - AFB_ERROR("Active EndPoint is not attached to any active stream"); - return -1; - } + pHalInfo->pDevID = strdup(pDevIDStr); + pHalInfo->pAPIName = strdup(pAPIName); + pHalInfo->pDisplayName = strdup(pShortName); + + g_ptr_array_add( g_PolicyCtx.pHALList, pHalInfo); } - return 0; + return POLICY_SUCCESS; } -//Policy API -int Policy_OpenStream(StreamInfoT * pStreamInfo) +// +// Policy API Functions +// +int Policy_OpenStream(json_object *pPolicyStreamJ) { + StreamInfoT PolicyStream; + EndpointInfoT EndpointInfo; + PolicyStream.pEndpointInfo =&EndpointInfo; + + int err = PolicyCtxJSONToStream(pPolicyStreamJ, &PolicyStream); + if(err == AHL_FAIL) + { + return AHL_POLICY_ACCEPT; + } + // Example rule -> when system is in shutdown or low power mode, no audio stream can be opened (return AHL_POLICY_REJECT) // Should receive event from lower level layer if(g_PolicyCtx.systemState != SYSTEM_NORMAL) { return AHL_POLICY_REJECT; } - - //Implement Policy open stream rules, limit to a certain number of stream open based on policy - int index = PolicyFindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); - int *pNumberOpenStream = &g_array_index(g_PolicyCtx.pStreamOpenPerPolicy,int,index); - int MaxNumberOpenStream = g_array_index(g_PolicyCtx.pMaxStreamOpenPerPolicy,int,index); - - *pNumberOpenStream +=1; - if((*pNumberOpenStream) > MaxNumberOpenStream ) + + StreamConfigT StreamConfig; + err = getStreamConfig(PolicyStream.pRoleName, &StreamConfig); + if(err == POLICY_FAIL) { - return AHL_POLICY_REJECT; + return AHL_POLICY_ACCEPT; } - //Get actual Volume - int err=PolicyGetVolume(pStreamInfo->pEndpointInfo); - if(err != 0) - { - AFB_WARNING("Can't get volume of Endpoint %s",pStreamInfo->pEndpointInfo->gsDeviceName->str); + if(PolicyStream.pEndpointInfo->deviceURIType != DEVICEURITYPE_NOT_ALSA) { + err=PolicyGetVolume(PolicyStream.pEndpointInfo->endpointID, + PolicyStream.pEndpointInfo->type, + PolicyStream.pEndpointInfo->gsHALAPIName, + PolicyStream.pEndpointInfo->pRoleName, + PolicyStream.pEndpointInfo->deviceURIType, + &PolicyStream.pEndpointInfo->iVolume); + if(err == POLICY_FAIL) + { + return AHL_POLICY_REJECT; + } } + err = PolicyAddEndPoint(&PolicyStream); + if(err == POLICY_FAIL) + { + return AHL_POLICY_REJECT; + } return AHL_POLICY_ACCEPT; } -int Policy_CloseStream(StreamInfoT * pStreamInfo) +int Policy_CloseStream(json_object *pPolicyStreamJ) { - //Decrement the number of openstream - int index = PolicyFindRoleIndex(pStreamInfo->pEndpointInfo->gsAudioRole->str); - int *pNumberOpenStream = &g_array_index(g_PolicyCtx.pStreamOpenPerPolicy,int,index); - - *pNumberOpenStream -= 1; + //TODO remove Endpoint when there is no stream + StreamInfoT PolicyStream; + EndpointInfoT EndpointInfo; + PolicyStream.pEndpointInfo =&EndpointInfo; + int err = PolicyCtxJSONToStream(pPolicyStreamJ, &PolicyStream); + if(err == AHL_FAIL) + { + return AHL_POLICY_ACCEPT; + } return AHL_POLICY_ACCEPT; } -int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, StreamStateT streamState) +int Policy_SetStreamState(json_object *pPolicyStreamJ) { - //DONE - // If higher priority audio role stream requires audio ducking (and un-ducking) of other streams (e.g. navigation ducks entertainment) - // Could potentially provide a fairly generic system using interupt behavior information and audio role priority (implemented and can be customized here) - // Specific exception can also be - // Source exclusion. E.g. When a source (e.g tuner) with same audio role as already active stream (e.g. media player) with same endpoint target, - // the former source is stopped (i.e. raise streamstate stop event) - // If source on communication role is active (e.g. handsfree call), activating entertainment sources is prohibited - // Startup or Shutdown audio stream mute entertainment (unmut when stream no longer active) - //TODO // Optional: Mute endpoint before activation, unmute afterwards (after a delay?) to avoid noises - int err; + StreamInfoT PolicyStream; + EndpointInfoT EndpointInfo; + PolicyStream.pEndpointInfo =&EndpointInfo; + + + StreamStateT streamState = 0; + StreamInfoT * pPolicyStream = &PolicyStream; + int err = PolicyCtxJSONToStream(pPolicyStreamJ, pPolicyStream); + if(err == AHL_FAIL) + { + return AHL_POLICY_ACCEPT; + } + + json_object *streamStateJ=NULL; + + if(!json_object_object_get_ex(pPolicyStreamJ, "arg_stream_state", &streamStateJ)) + { + return AHL_POLICY_ACCEPT; + } + streamState = (StreamStateT)json_object_get_int(streamStateJ); //Change of state - if(pStreamInfo->streamState != streamState) + if(pPolicyStream->streamState != streamState) { - //seach corresponding endpoint and gather information on it - int key = PolicyGenEndPointKey(pStreamInfo->pEndpointInfo); - EndPointPolicyInfoT *pCurrEndPointPolicy = PolicySearchEndPoint(pStreamInfo->pEndpointInfo->type , key); + //seach corresponding endpoint and gather information on it + EndPointPolicyInfoT *pCurrEndPointPolicy = PolicySearchEndPoint(pPolicyStream->pEndpointInfo->type , pPolicyStream->pEndpointInfo->gsDeviceName); - switch(pStreamInfo->streamState) + switch(pPolicyStream->streamState) { case STREAM_STATE_IDLE: switch(streamState) { case STREAM_STATE_RUNNING: - err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pStreamInfo, AudioRoleIndex, key); + err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pPolicyStream); if(err) { return AHL_POLICY_REJECT; } - pStreamInfo->streamState = STREAM_STATE_RUNNING; - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_START); + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_START); break; case STREAM_STATE_PAUSED: - err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pStreamInfo, AudioRoleIndex, key); + err = PolicyIdleRunningTransition(pCurrEndPointPolicy, pPolicyStream); if(err) { return AHL_POLICY_REJECT; } - pStreamInfo->streamState = STREAM_STATE_PAUSED; - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_PAUSE); break; default: return AHL_POLICY_REJECT; @@ -798,17 +708,15 @@ int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, Stream switch(streamState) { case STREAM_STATE_IDLE: - err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pStreamInfo); + err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pPolicyStream); if(err) { return AHL_POLICY_REJECT; - } - pStreamInfo->streamState = STREAM_STATE_IDLE; - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + } + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_STOP); break; case STREAM_STATE_PAUSED: - pStreamInfo->streamState = STREAM_STATE_PAUSED; - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_PAUSE); + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_PAUSE); break; default: return AHL_POLICY_REJECT; @@ -819,17 +727,15 @@ int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, Stream switch(streamState) { case STREAM_STATE_IDLE: - err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pStreamInfo); + err = PolicyRunningIdleTransition(pCurrEndPointPolicy, pPolicyStream); if(err) { return AHL_POLICY_REJECT; } - pStreamInfo->streamState = STREAM_STATE_IDLE; - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_STOP); + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_STOP); break; case STREAM_STATE_RUNNING: - pStreamInfo->streamState = STREAM_STATE_RUNNING; - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_RESUME); + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_RESUME); break; default: return AHL_POLICY_REJECT; @@ -844,46 +750,101 @@ int Policy_SetStreamState(StreamInfoT * pStreamInfo, int AudioRoleIndex, Stream return AHL_POLICY_ACCEPT; } -int Policy_SetStreamMute(StreamInfoT * pStreamInfo, StreamMuteT streamMute) +int Policy_SetStreamMute(json_object *pPolicyStreamJ) { - int err; - - if(streamMute == STREAM_MUTED) + StreamMuteT streamMute = 0; + StreamInfoT PolicyStream; + EndpointInfoT EndpointInfo; + PolicyStream.pEndpointInfo =&EndpointInfo; + StreamInfoT * pPolicyStream = &PolicyStream; + + int err = PolicyCtxJSONToStream(pPolicyStreamJ, pPolicyStream); + if(err == AHL_FAIL) { - err= PolicySetVolumeMute(pStreamInfo->pEndpointInfo, 0); - if(err) - { - AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); - return AHL_POLICY_REJECT; - } - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_MUTED); + return AHL_POLICY_ACCEPT; } - else - { - err= PolicySetVolumeMute(pStreamInfo->pEndpointInfo, pStreamInfo->pEndpointInfo->iVolume); - if(err) - { - AFB_ERROR("Endpoint:%s Set Volume return with errorcode%i",pStreamInfo->pEndpointInfo->gsDeviceName->str, err); - return AHL_POLICY_REJECT; - } - PolicyPostStateEvent(pStreamInfo->streamStateEvent,STREAM_EVENT_UNMUTED); + json_object *streamMuteJ=NULL; + if(!json_object_object_get_ex(pPolicyStreamJ, "mute_state", &streamMuteJ)) + { + return AHL_POLICY_ACCEPT; } + streamMute = (StreamMuteT)json_object_get_int(streamMuteJ); + + if(streamMute != pPolicyStream->streamMute) + { + if(streamMute == STREAM_MUTED) + { + + err= PolicySetVolume(pPolicyStream->pEndpointInfo->endpointID, + pPolicyStream->pEndpointInfo->type, + pPolicyStream->pEndpointInfo->gsHALAPIName, + pPolicyStream->pRoleName, + pPolicyStream->pEndpointInfo->deviceURIType, + 0, + true); + if(err) + { + AFB_ERROR("StreamID:%i Set Volume return with errorcode%i",pPolicyStream->streamID, err); + return AHL_POLICY_REJECT; + } + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_MUTED); + } + else + { + err= PolicySetVolume(pPolicyStream->pEndpointInfo->endpointID, + pPolicyStream->pEndpointInfo->type, + pPolicyStream->pEndpointInfo->gsHALAPIName, + pPolicyStream->pRoleName, + pPolicyStream->pEndpointInfo->deviceURIType, + pPolicyStream->pEndpointInfo->iVolume, + true); + if(err) + { + AFB_ERROR("Endpoint:%i Set Volume return with errorcode%i",pPolicyStream->streamID, err); + return AHL_POLICY_REJECT; + } + PolicyPostStateEvent(pPolicyStream->streamID,STREAM_EVENT_UNMUTED); - pStreamInfo->streamMute = streamMute; + } + pPolicyStream->streamMute = streamMute; + } + return AHL_POLICY_ACCEPT; } -int Policy_SetVolume(EndpointInfoT * f_pEndpointInfo, char *volumeStr) +int Policy_SetVolume(json_object *pPolicyEndpointJ) { + char *volumeStr = NULL; + EndpointInfoT EndpointInfo; + + int err = PolicyCtxJSONToEndpoint(pPolicyEndpointJ, &EndpointInfo); + if(err == AHL_FAIL) + { + return AHL_POLICY_ACCEPT; + } + + json_object *volumeJ=NULL; + + if(!json_object_object_get_ex(pPolicyEndpointJ, "arg_volume", &volumeJ)) + { + return AHL_POLICY_ACCEPT; + } + volumeStr = (char*)json_object_get_string(volumeJ); // TODO: Parse volume string to support increment/absolute/percent notation (or delegate to action / policy layer to interpret) int vol = atoi(volumeStr); //Set the volume - int err = PolicySetVolumeRamp(f_pEndpointInfo, vol); + err= PolicySetVolume(EndpointInfo.endpointID, + EndpointInfo.type, + EndpointInfo.gsHALAPIName, + EndpointInfo.pRoleName, + EndpointInfo.deviceURIType, + vol, + false); if (err) { AFB_ERROR("Set Volume return with errorcode%i", err); @@ -893,186 +854,186 @@ int Policy_SetVolume(EndpointInfoT * f_pEndpointInfo, char *volumeStr) return AHL_POLICY_ACCEPT; } -int Policy_SetProperty(EndpointInfoT * f_pEndpointInfo, char *propertyName, json_object *propValue) +int Policy_SetProperty(json_object *pPolicyEndpointJ) { + char *propertyName = NULL; + EndpointInfoT EndpointInfo; + + int err = PolicyCtxJSONToEndpoint(pPolicyEndpointJ, &EndpointInfo); + if(err == AHL_FAIL) + { + return AHL_POLICY_ACCEPT; + } + + json_object *propertyNameJ=NULL; + + if(!json_object_object_get_ex(pPolicyEndpointJ, "arg_property_name", &propertyNameJ)) + { + return AHL_POLICY_ACCEPT; + } + propertyName = (char*)json_object_get_string(propertyNameJ); + + json_object *propValueJ; + if(!json_object_object_get_ex(pPolicyEndpointJ, "arg_property_value", &propValueJ)) + { + return AHL_POLICY_ACCEPT; + } + gpointer *key_value=NULL; - key_value=g_hash_table_lookup(f_pEndpointInfo->pPropTable,propertyName); - if(key_value==NULL) + + key_value = g_hash_table_lookup(EndpointInfo.pPropTable,propertyName); + if(key_value == NULL) { AFB_ERROR("Can't find property %s, request will be rejected", propertyName); return AHL_POLICY_REJECT; } - // Object type detection for property value (string = state, numeric = property) - json_type jType = json_object_get_type(propValue); - switch (jType) { - case json_type_double: - Add_Endpoint_Property_Double(f_pEndpointInfo,propertyName,json_object_get_double(propValue)); - case json_type_int: - Add_Endpoint_Property_Int(f_pEndpointInfo,propertyName,json_object_get_int(propValue)); - case json_type_string: - Add_Endpoint_Property_String(f_pEndpointInfo,propertyName,json_object_get_string(propValue)); - break; - default: - AFB_ERROR("Invalid property argument Property value not a valid json object query=%s", json_object_get_string(propValue )); - return AHL_POLICY_REJECT; + //Get JsonObjectype + json_type currentjType = json_object_get_type((json_object*)key_value); + json_type newjType = json_object_get_type(propValueJ); + + //Apply policy on set property if needed here + //Here we only validate that the type is the same + if(currentjType != newjType) + { + AFB_ERROR("Property Value Type is wrong"); + return AHL_POLICY_REJECT; + } + + + //Create a new Json Object + json_object *pEventDataJ = NULL; + err = wrap_json_pack(&pEventDataJ,"{s:s,s:i,s:i,s:s,s:o,s:s}", + "event_name", AHL_ENDPOINT_PROPERTY_EVENT, + "endpoint_id", EndpointInfo.endpointID, + "endpoint_type", EndpointInfo.type, + "property_name", propertyName, + "value",propValueJ, + "audio_role", EndpointInfo.pRoleName); + if(err) + { + AFB_ERROR("Unable to pack property event"); + return AHL_POLICY_REJECT; } + //Raise Event to update HLB + audiohlapi_raise_event(pEventDataJ); return AHL_POLICY_ACCEPT; } -int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext) +int Policy_PostAction(json_object *pPolicyActionJ) { + char * actionName = NULL; + char * audioRole = NULL; + char * mediaName = NULL; + json_object *actionContext = NULL; + + int err = wrap_json_unpack(pPolicyActionJ, "{s:s,s:s,s?s,s?o}", "action_name", &actionName,"audio_role",&audioRole,"media_name",&mediaName,"action_context",&actionContext); + if (err) { + AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err)); + return AHL_POLICY_REJECT; + } + // TODO: Any event with media specified should trigger action on provided rendering services (e.g. Wwise binding, gstreamer file player wrapper, MPDC? simple ALSA player (aplay)?) - // Example (when the policy is hooked to CAN events). Post audio playback events other than safety during reverse gear engaged declined - // Example post HMI audio role playback events declined when higher priority streams are active + + //In this use case just return the action back to highlevel binding. + + json_object *pEventDataJ = NULL; + err = wrap_json_pack(&pEventDataJ, "{s:s,s:s,s:s,s?s,s?o}", "event_name", AHL_POST_ACTION_EVENT, "action_name", &actionName,"audio_role",&audioRole,"media_name",&mediaName,"action_context",&actionContext); + if (err) { + AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err)); + return AHL_POLICY_REJECT; + } + audiohlapi_raise_event(pEventDataJ); return AHL_POLICY_ACCEPT; } -int Policy_AudioDeviceChange() +int Policy_Endpoint_Init(json_object *pPolicyEndpointJ) { - // Allow or disallow a new audio endpoint to be used by the system - // TODO: Policy assigns audio role(s) for device (or default) - // TODO: Raise events to engage device switching if active stream in audio role assigned to the new endpoint - - return AHL_POLICY_ACCEPT; -} + EndpointInfoT EndpointInfo; + + int err = PolicyCtxJSONToEndpoint(pPolicyEndpointJ, &EndpointInfo); + if(err == AHL_FAIL) + { + return AHL_POLICY_REJECT; + } -int Policy_Endpoint_Property_Init(EndpointInfoT * io_EndpointInfo) -{ - // Populate list of supported properties for specified endpoint (use helper functions) - // Setup initial values for all properties GHashTabl + if (EndpointInfo.deviceURIType != DEVICEURITYPE_NOT_ALSA) { + // Update Hal Name + err = RetrieveAssociatedHALAPIName(&EndpointInfo); + if (err) { + AFB_ERROR("HAL not found for Device %s", EndpointInfo.gsDeviceName); + return AHL_POLICY_REJECT; + } - // TODO: Switch on different known endpoints to populate different properties + //Set Init Volume + StreamConfigT StreamConfig; + getStreamConfig(EndpointInfo.pRoleName, &StreamConfig); + err = PolicySetVolume(EndpointInfo.endpointID, + EndpointInfo.type, + EndpointInfo.gsHALAPIName, + EndpointInfo.pRoleName, + EndpointInfo.deviceURIType, + StreamConfig.iVolumeInit, + false); + if(err) { + return AHL_POLICY_REJECT; + } + } // Test example - Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_LOW,3); - Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_MID,0); - Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); - Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_BALANCE,0); - Add_Endpoint_Property_Int(io_EndpointInfo,AHL_PROPERTY_FADE,30); - Add_Endpoint_Property_String(io_EndpointInfo,"preset_name","flat"); - - return 0; // No errors + Add_Endpoint_Property_Int(&EndpointInfo,AHL_PROPERTY_EQ_LOW,3); + Add_Endpoint_Property_Int(&EndpointInfo,AHL_PROPERTY_EQ_MID,0); + Add_Endpoint_Property_Int(&EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); + Add_Endpoint_Property_Int(&EndpointInfo,AHL_PROPERTY_BALANCE,0); + Add_Endpoint_Property_Int(&EndpointInfo,AHL_PROPERTY_FADE,30); + Add_Endpoint_Property_String(&EndpointInfo,"preset_name","flat"); + + + gpointer *key_value = g_hash_table_lookup(EndpointInfo.pPropTable,AHL_PROPERTY_BALANCE); + if(key_value == NULL) + { + AFB_ERROR("Can't find property %s, request will be rejected", AHL_PROPERTY_BALANCE); + return AHL_POLICY_REJECT; + } + + //Create a new Json Object + json_object *pNewPolicyEndpointJ = NULL; + err = PolicyEndpointStructToJSON(&EndpointInfo, &pNewPolicyEndpointJ); + if (err == AHL_FAIL) + { + return AHL_POLICY_REJECT; + } + json_object *paramJ= json_object_new_string(AHL_ENDPOINT_INIT_EVENT); + json_object_object_add(pNewPolicyEndpointJ, "event_name", paramJ); + + //Raise Event to update HLB + audiohlapi_raise_event(pNewPolicyEndpointJ); + + return AHL_POLICY_ACCEPT; // No errors } int Policy_Init() { // Initialize Ressources g_PolicyCtx.pSourceEndpoints =g_array_new(FALSE,TRUE,sizeof(EndPointPolicyInfoT)); - g_PolicyCtx.pSinkEndpoints = g_array_new(FALSE,TRUE,sizeof(EndPointPolicyInfoT)); - g_PolicyCtx.pStreamOpenPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); - g_PolicyCtx.pMaxStreamOpenPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); - g_PolicyCtx.pVolDuckPerPolicy = g_array_sized_new(FALSE, TRUE, sizeof(int), g_AHLCtx.policyCtx.iNumberRoles); - - int initial_value=0; - int max_value=0; - int vol_init_value=0; - int vol_duck_value=0; - GArray * pRoleDeviceArray = NULL; - //Init the number of open stream - for(int i=0; ilen; j++) - { - - //Init all Volume - EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); - int err= PolicySetVolumeRamp(pEndpointInfo, vol_init_value); - if(err) - { - AFB_WARNING("Endpoint:%s Set Volume return with errorcode%i",pEndpointInfo->gsDeviceName->str, err); - //try to read volume instead - err=PolicyGetVolume(pEndpointInfo); - if(err != 0) - { - AFB_WARNING("Can't get volume of Endpoint %s",pEndpointInfo->gsDeviceName->str); - } - } - - /* - EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); - int err=PolicyGetVolume(pEndpointInfo); - if(err != 0) - { - AFB_WARNING("Can't get volume of Endpoint %s",pEndpointInfo->gsDeviceName->str); - } -*/ - } - } - } - + //Get HalList + GetHALList(); //Set System Normal for now, this should be set by an event //TODO: Receive event from low level g_PolicyCtx.systemState = SYSTEM_NORMAL; //register audio backend events - json_object *queryurl, *responseJ, *devidJ, *eventsJ; + //This is to simulate can bus, only used for demo + json_object *queryurl, *responseJ, *eventsJ; eventsJ = json_object_new_array(); json_object_array_add(eventsJ, json_object_new_string("audiod_system_event")); @@ -1081,17 +1042,19 @@ int Policy_Init() int returnResult = afb_service_call_sync("audiod", "subscribe", queryurl, &responseJ); if (returnResult) { AFB_ERROR("Fail subscribing to Audio Backend System events"); - return -1; + return AHL_POLICY_REJECT; } - - - return 0; // No errors + return AHL_POLICY_ACCEPT; } void Policy_Term() { - //Free Ressources + if (g_PolicyCtx.pHALList) { + g_ptr_array_free(g_PolicyCtx.pHALList,TRUE); + g_PolicyCtx.pHALList = NULL; + } + for(int i=0; ilen; i++) { EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSourceEndpoints,EndPointPolicyInfoT,i); @@ -1110,70 +1073,17 @@ void Policy_Term() g_PolicyCtx.pSourceEndpoints = NULL; g_array_free(g_PolicyCtx.pSinkEndpoints,TRUE); g_PolicyCtx.pSinkEndpoints = NULL; - - g_array_free(g_PolicyCtx.pStreamOpenPerPolicy,TRUE); - g_PolicyCtx.pStreamOpenPerPolicy = NULL; - g_array_free(g_PolicyCtx.pMaxStreamOpenPerPolicy,TRUE); - g_PolicyCtx.pMaxStreamOpenPerPolicy = NULL; - g_array_free(g_PolicyCtx.pVolDuckPerPolicy, TRUE); - g_PolicyCtx.pVolDuckPerPolicy = NULL; -} - -static void PolicySpeedModify(int speed) -{ - - for(int i=0; ilen; i++) - { - EndPointPolicyInfoT * pCurEndpoint = &g_array_index(g_PolicyCtx.pSinkEndpoints,EndPointPolicyInfoT,i); - if(pCurEndpoint == NULL) - { - AFB_WARNING("Sink Endpoint not found"); - return; - - } - //check if active - if(pCurEndpoint->streamInfo->len > 0 ) - { - StreamPolicyInfoT * pCurStream = &g_array_index(pCurEndpoint->streamInfo,StreamPolicyInfoT,pCurEndpoint->streamInfo->len-1); - - - //Get Stream Info - StreamInfoT * pActiveStreamInfo = PolicyGetActiveStream(pCurStream->streamID); - if (pActiveStreamInfo == NULL) { - AFB_WARNING("Stream not found, Specified stream not currently active stream_id -> %d",pCurStream->streamID); - return; - } - - if(strcasecmp(pActiveStreamInfo->pEndpointInfo->gsAudioRole->str,AHL_ROLE_ENTERTAINMENT)==0) - { - - if(speed > 30 && speed < 100) - { - int volume =speed; - PolicySetVolumeRamp(pActiveStreamInfo->pEndpointInfo,volume); - } - - - } - - } - - } } - void Policy_OnEvent(const char *evtname, json_object *eventJ) { AFB_DEBUG("Policy received event %s", evtname); - char *eventName = NULL; json_object *event_parameter = NULL; int speed = 0; - if(strcasecmp(evtname, "audiod/system_events")==0) { - int err = wrap_json_unpack(eventJ, "{s:s,s:o}", "event_name", &eventName, "event_parameter", &event_parameter); if (err) { AFB_WARNING("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(eventJ)); @@ -1188,14 +1098,10 @@ void Policy_OnEvent(const char *evtname, json_object *eventJ) AFB_WARNING("Invalid arguments, Args not a valid json object query=%s", json_object_get_string(event_parameter)); return; } - //When speed change Modify volume on Endpoint where entertainment change PolicySpeedModify(speed); } - } +} - - - -} \ No newline at end of file +#endif // AHL_DISCONNECT_POLICY \ No newline at end of file diff --git a/src/ahl-policy.h b/src/ahl-policy.h new file mode 100644 index 0000000..dfb3d7c --- /dev/null +++ b/src/ahl-policy.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 "Audiokinetic Inc" + * + * 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 AHL_POLICY_INCLUDE +#define AHL_POLICY_INCLUDE +#include "ahl-binding.h" + +#ifndef AHL_DISCONNECT_POLICY + +#define MAX_ACTIVE_STREAM_POLICY 30 +#define POLICY_FAIL 1 +#define POLICY_SUCCESS 0 + +#define AHL_POLICY_UNDEFINED_HALNAME "UNDEFINED" +#define AHL_POLICY_UNDEFINED_DISPLAYNAME "DeviceNotFound" + +typedef enum SystemState { + SYSTEM_STARTUP = 0, // Startup + SYSTEM_SHUTDOWN, // ShutDown + SYSTEM_NORMAL, // Normal + SYSTEM_LOW_POWER, // Low Power, save mode + SYSTEM_MAXVALUE // Enum count, keep at the end +} SystemStateT; + +typedef struct StreamPolicyInfo { + streamID_t streamID; + int RolePriority; + char * pAudioRole; + InterruptBehaviorT interruptBehavior; + int iDuckVolume; //duck Volume +} StreamPolicyInfoT; + +typedef struct EndPointPolicyInfo { + endpointID_t endpointID; + EndpointTypeT type; + DeviceURITypeT deviceType; + char * pDeviceName; + char * pHalApiName; + int iVolume; //Current Volume + GArray * streamInfo; //List of playing or duck stream at a given endpoint +} EndPointPolicyInfoT; + + +typedef struct HalInfo { + char *pDevID; + char *pAPIName; + char *pDisplayName; +} HalInfoT; + +typedef struct StreamConfig { + int iNbMaxStream; + int iVolumeInit; + int iVolumeDuckValue; +} StreamConfigT; + +// Global Policy Local context +typedef struct PolicyLocalCtx { + GArray * pSourceEndpoints; // List of Source Endpoint with playing stream or interrupted stream + GArray * pSinkEndpoints; // List of Sink Endpoint with playing stream or interrupted stream + GPtrArray * pHALList; + SystemStateT systemState; +} PolicyLocalCtxT; + +int Policy_Endpoint_Init(json_object *pPolicyEndpointJ); +int Policy_OpenStream(json_object *pPolicyStreamJ); +int Policy_CloseStream(json_object *pPolicyStreamJ); +int Policy_SetStreamState(json_object *pPolicyStreamJ); +int Policy_SetStreamMute(json_object *pPolicyStreamJ); +int Policy_PostAction(json_object *pPolicyActionJ); +int Policy_SetVolume(json_object *pPolicyEndpointJ); +int Policy_SetProperty(json_object *pPolicyEndpointJ); +int Policy_Init(); +void Policy_Term(); +void Policy_OnEvent(const char *evtname, json_object *eventJ); + +#endif // AHL_DISCONNECT_POLICY +#endif // AHL_POLICY_INCLUDE \ No newline at end of file -- cgit 1.2.3-korg