aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTai Vuong <tvuong@audiokinetic.com>2017-10-04 14:21:28 -0400
committerTai Vuong <tvuong@audiokinetic.com>2017-10-04 14:21:28 -0400
commit539f65bb5ad87238422a5ca26c5b524c3be44bd1 (patch)
tree01fa955edf1e9b597a4a4247b3a35390c7b6ddbc
parentd69dc9732886074e9f400961b500e70d5c8305d7 (diff)
Post AudioWorkshop Work phase v1
-rw-r--r--README.md7
m---------afb-utilities0
-rw-r--r--afb-utilities/CMakeLists.txt37
-rw-r--r--afb-utilities/LICENSE201
-rw-r--r--afb-utilities/README.md11
-rw-r--r--afb-utilities/filescan-utils.c122
-rw-r--r--afb-utilities/filescan-utils.h45
-rw-r--r--afb-utilities/wrap-json.c939
-rw-r--r--afb-utilities/wrap-json.h46
-rw-r--r--afb-utilities/wrap-json.md305
-rw-r--r--htdocs/audiohl.html47
-rw-r--r--src/ahl-apidef.h339
-rw-r--r--src/ahl-apidef.json204
-rw-r--r--src/ahl-binding.c730
-rw-r--r--src/ahl-binding.h106
-rw-r--r--src/ahl-config.c66
-rw-r--r--src/ahl-deviceenum.c154
-rw-r--r--src/ahl-interface.h112
-rw-r--r--src/ahl-policy.c123
19 files changed, 2963 insertions, 631 deletions
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
-Subproject 1860c197a402f76f960eb2e373daf1259592cb6
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 <rfulup@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# 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 <fulup@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/prctl.h>
+#include <dirent.h>
+
+#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 <fulup@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * 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 <afb/afb-binding.h>
+#include <json-c/json.h>
+
+#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 <jose.bollo@iot.bzh>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include <string.h>
+
+#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 <stdio.h>
+
+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, "<validation>", 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 <jose.bollo@iot.bzh>
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#pragma once
+
+#include <stdarg.h>
+#include <json-c/json.h>
+
+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
+&lt;apiref-pack&gt;, 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 <petri@digip.org>
+
+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 @@
<select select_id='property_name_list' onclick='p_name=this.value'>
<option value='balance'>Balance</option>
<option value='fade'>Fade</option>
- <option value='eq_low'>EQ Low</option>
+ <option value='eq_bass'>EQ Bass</option>
<option value='eq_mid'>EQ Mid</option>
- <option value='eq_high'>EQ High</option>
+ <option value='eq_treble'>EQ Treble</option>
<option selected value=''>Select...</option>
</select>
-
- <b>State Name</b>
- <select select_id='state_name_list' onclick='st_name=this.value'>
- <option value='mute'>Mute</option>
- <option value='active'>Active</option>
- <option selected value=''>Select...</option>
- </select>
-
- <b>State Value</b>
- <select select_id='state_value_list' onclick='st_val=this.value'>
- <option value='on'>On</option>
- <option value='off'>Off</option>
- <option selected value=''>Select...</option>
- </select>
- <b>Volume Value</b>
- <select select_id='value_list' onclick='val=this.value'>
+ <b>Volume/Property Value</b>
+ <select select_id='value_list' onclick='val=this.value'>
<option value='0'>0</option>
<option value='20'>20</option>
<option value='40'>40</option>
@@ -111,6 +97,14 @@
<option selected value=''>Select...</option>
</select>
+ <b>Stream Active/Mute State</b>
+ <select select_id='state_value_list' onclick='st_state=this.selectedIndex'>
+ <option value='off'>Off</option>
+ <option value='on'>On</option>
+ <option selected value=''>Select...</option>
+ </select>
+
+
<br>
<ol>
<li><button onclick="callbinder('audiohl','get_sources', {audio_role:ar})">get_sources(audio_role)</button></li>
@@ -118,15 +112,18 @@
<li><button onclick="callbinder('audiohl','stream_open', {audio_role:ar,endpoint_type:ep_type})">stream_open(audio_role,endpoint_type)</button></li>
<li><button onclick="callbinder('audiohl','stream_open', {audio_role:ar,endpoint_type:ep_type,endpoint_id:ep_id})">stream_open(audio_role,enpoint_type,endpoint_id)</button></li>
<li><button onclick="callbinder('audiohl','stream_close', {stream_id:s_id})">stream_close(stream_id)</button></li>
+ <li><button onclick="callbinder('audiohl','get_stream_info', {stream_id:s_id})">get_stream_info(stream_id)</button></li>
+ <li><button onclick="callbinder('audiohl','set_stream_state', {stream_id:s_id,state:st_state})">set_stream_state(stream_id,stream_state)</button></li>
+ <li><button onclick="callbinder('audiohl','set_stream_mute', {stream_id:s_id,mute:st_state})">set_stream_mute(stream_id,mute)</button></li>
<li><button onclick="callbinder('audiohl','set_volume', {endpoint_type:ep_type,endpoint_id:ep_id,volume:val})">set_volume(endpoint_type,endpoint_id,value)</button></li>
<li><button onclick="callbinder('audiohl','get_volume', {endpoint_type:ep_type,endpoint_id:ep_id})">get_volume(endpoint_type,endpoint_id)</button></li>
- <li><button onclick="callbinder('audiohl','set_property', {endpoint_type:ep_type,endpoint_id:ep_id,property_name:p_name,value:val})">set_property(endpoint_type,endpoint_id,property,value)</button></li>
- <li><button onclick="callbinder('audiohl','get_property', {endpoint_type:ep_type,endpoint_id:ep_id,property_name:'balance'})">get_property(endpoint_type,endpoint_id,'balance')</button></li>
- <li><button onclick="callbinder('audiohl','set_state', {endpoint_type:ep_type,endpoint_id:ep_id,state_name:st_name,state_value:st_val})">set_state(endpoint_type,endpoint_id,stateName,stateVal)</button></li>
- <li><button onclick="callbinder('audiohl','get_state', {endpoint_type:ep_type,endpoint_id:ep_id,state_name:st_name})">get_state(endpoint_type,endpoint_id,stateName)</button></li>
- <li><button onclick="callbinder('audiohl','post_sound_event', {event_name:'PlaySound',audio_role:'notification',media_name:'HomeButton.wav'})">post_sound_event('PlaySound','HomeButton.wav')</button></li>
- <li><button onclick="callbinder('audiohl','subscribe', {events:['SinkDeviceChanges','StreamStatusChanges']})">subscribe(['SinkDeviceChanges','StreamStatusChanges'])</button>
- <li><button onclick="callbinder('audiohl','unsubscribe', {events:['SinkDeviceChanges','StreamStatusChanges']})">unsubscribe(['SinkDeviceChanges','StreamStatusChanges'])</button>
+ <li><button onclick="callbinder('audiohl','get_list_properties', {endpoint_type:ep_type,endpoint_id:ep_id})">get_list_properties(endpoint_type,endpoint_id)</button></li>
+ <li><button onclick="callbinder('audiohl','set_property', {endpoint_type:ep_type,endpoint_id:ep_id,property_name:p_name,value:val})">set_property(endpoint_type,endpoint_id,property,value)</button></li>
+ <li><button onclick="callbinder('audiohl','get_property', {endpoint_type:ep_type,endpoint_id:ep_id,property_name:'balance'})">get_property(endpoint_type,endpoint_id,'balance')</button></li>
+ <li><button onclick="callbinder('audiohl','get_list_events', {audio_role:ar})">get_list_events(audio_role)</button></li>
+ <li><button onclick="callbinder('audiohl','post_event', {event_name:'play_sound',audio_role:ar,media_name:'HomeButton.wav'})">post_event(play_sound,audio_role,'HomeButton.wav')</button></li>
+ <li><button onclick="callbinder('audiohl','subscribe', {events:['ahl_endpoint_property_event','ahl_endpoint_volume_event','ahl_post_event']})">subscribe(['ahl_endpoint_property_event','ahl_endpoint_volume_event','ahl_post_event'])</button>
+ <li><button onclick="callbinder('audiohl','unsubscribe', {events:['ahl_endpoint_property_event','ahl_endpoint_volume_event','ahl_post_event']})">unsubscribe(['ahl_endpoint_property_event','ahl_endpoint_volume_event','ahl_post_event'])</button>
</li>
<br>
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);
@@ -181,9 +201,30 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = {
.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
},
@@ -195,9 +236,16 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = {
.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
+}