summaryrefslogtreecommitdiffstats
path: root/ahl-binding
diff options
context:
space:
mode:
Diffstat (limited to 'ahl-binding')
-rw-r--r--ahl-binding/CMakeLists.txt50
-rw-r--r--ahl-binding/ahl-apidef.h237
-rw-r--r--ahl-binding/ahl-apidef.json447
-rw-r--r--ahl-binding/ahl-binding.c1276
-rw-r--r--ahl-binding/ahl-binding.h118
-rw-r--r--ahl-binding/ahl-config.c219
-rw-r--r--ahl-binding/ahl-deviceenum.c332
-rw-r--r--ahl-binding/ahl-json.c296
-rw-r--r--ahl-binding/ahl-json.h26
9 files changed, 3001 insertions, 0 deletions
diff --git a/ahl-binding/CMakeLists.txt b/ahl-binding/CMakeLists.txt
new file mode 100644
index 0000000..0dee304
--- /dev/null
+++ b/ahl-binding/CMakeLists.txt
@@ -0,0 +1,50 @@
+###########################################################################
+# Copyright 2017 Audiokinetic.com
+#
+# author: Francois Thibault <fthibault@audiokinetic.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(audiohighlevel)
+
+ # Define project Targets
+ ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c ahl-config.c ahl-json.c)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX "afb-"
+ LABELS "BINDINGV2"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Change default OPenAPI file
+ SET_OPENAPI_FILENAME("ahl-apidef")
+
+ # Define target includes
+ TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME}
+ PUBLIC ${GLIB_PKG_INCLUDE_DIRS}
+ )
+
+ # Library dependencies (include updates automatically)
+ # Find package for GLIB does not seem to export
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ ahl-policy
+ ahl-utilities
+ afb-utilities
+ ${GLIB_PKG_LIBRARIES}
+ ${link_libraries}
+ )
+
diff --git a/ahl-binding/ahl-apidef.h b/ahl-binding/ahl-apidef.h
new file mode 100644
index 0000000..2c77827
--- /dev/null
+++ b/ahl-binding/ahl-apidef.h
@@ -0,0 +1,237 @@
+
+static const char _afb_description_v2_ahl_4a[] =
+ "{\"openapi\":\"3.0.0\",\"info\":{\"description\":\"Audio high level API "
+ "for AGL applications\",\"title\":\"audiohighlevel\",\"version\":\"1.0\","
+ "\"x-binding-c-generator\":{\"api\":\"ahl-4a\",\"version\":2,\"prefix\":\""
+ "audiohlapi_\",\"postfix\":\"\",\"start\":null,\"onevent\":\"AhlOnEvent\""
+ ",\"init\":\"AhlBindingInit\",\"scope\":\"\",\"private\":false}},\"server"
+ "s\":[{\"url\":\"ws://{host}:{port}/api/audiohl\",\"description\":\"Audio"
+ " high level API for AGL applications.\",\"variables\":{\"host\":{\"defau"
+ "lt\":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":[{"
+ "\"$ref\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"schema"
+ "s\":{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"af"
+ "b-event\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply-v"
+ "2\":{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":[\""
+ "jtype\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"co"
+ "nst\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"st"
+ "atus\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"typ"
+ "e\":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"str"
+ "ing\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"object"
+ "\"}}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"ev"
+ "ent\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-ev"
+ "ent\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}},"
+ "\"endpoint_info\":{\"type\":\"object\",\"required\":[\"endpoint_id\",\"t"
+ "ype\",\"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\",\"endpo"
+ "int_info\"],\"properties\":{\"stream_id\":{\"type\":\"int\"},\"state\":{"
+ "\"type\":\"int\"},\"mute\":{\"type\":\"int\"},\"device_uri\":{\"type\":\""
+ "string\"},\"$ref\":\"#/components/schemas/endpoint_info\"}}},\"x-permiss"
+ "ions\":{\"streamcontrol\":{\"permission\":\"urn:AGL:permission:audio:pub"
+ "lic:streamcontrol\"},\"endpointcontrol\":{\"permission\":\"urn:AGL:permi"
+ "ssion:audio:public:endpointcontrol\"},\"audiostream\":{\"permission\":\""
+ "urn:AGL:permission:audio:public:audiostream\"},\"soundevent\":{\"permiss"
+ "ion\":\"urn:AGL:permission:audio:public:soundevent\"}},\"responses\":{\""
+ "200\":{\"description\":\"A complex object array response\",\"content\":{"
+ "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/afb-re"
+ "ply\"}}}},\"400\":{\"description\":\"Invalid arguments\"}}},\"paths\":{\""
+ "/get_endpoints\":{\"description\":\"Retrieve array of available audio en"
+ "dpoints\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"audio_ro"
+ "le\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query"
+ "\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"e"
+ "num\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\""
+ ",\"response\":{\"description\":\"Array of endpoint info structures\",\"t"
+ "ype\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/endpoint_info"
+ "\"}}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/stream_ope"
+ "n\":{\"description\":\"Request opening a stream\",\"get\":{\"x-permissio"
+ "ns\":{\"$ref\":\"#/components/x-permissions/audiostream\"},\"parameters\""
+ ":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\"schema\""
+ ":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"endpoint_type\",\"r"
+ "equired\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\""
+ ":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"}}],\"re"
+ "sponses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"response\""
+ ":{\"description\":\"Stream information structure\",\"$ref\":\"#/componen"
+ "ts/schemas/stream_info\"}},\"400\":{\"$ref\":\"#/components/responses/40"
+ "0\"}}}},\"/stream_close\":{\"description\":\"Request closing a stream\","
+ "\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/audios"
+ "tream\"},\"parameters\":[{\"in\":\"query\",\"name\":\"stream_id\",\"requ"
+ "ired\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$"
+ "ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/r"
+ "esponses/400\"}}}},\"/set_stream_state\":{\"description\":\"Change strea"
+ "m active and/or mute state\",\"get\":{\"x-permissions\":{\"$ref\":\"#/co"
+ "mponents/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\""
+ ",\"name\":\"stream_id\",\"required\":false,\"schema\":{\"type\":\"int\"}"
+ "},{\"in\":\"query\",\"name\":\"state\",\"required\":false,\"schema\":{\""
+ "type\":\"int\"}},{\"in\":\"query\",\"name\":\"mute\",\"required\":false,"
+ "\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/com"
+ "ponents/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\""
+ "}}}},\"/get_stream_info\":{\"description\":\"Retrieve stream information"
+ "\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"stream_id\",\"r"
+ "equired\":true,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\""
+ "$ref\":\"#/components/responses/200\",\"response\":{\"description\":\"St"
+ "ream information structure\",\"$ref\":\"#/components/schemas/stream_info"
+ "\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/volume\":{\""
+ "description\":\"Set or get volume on endpoint\",\"get\":{\"x-permissions"
+ "\":{\"$ref\":\"#/components/x-permissions/endpointcontrol\"},\"parameter"
+ "s\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"sc"
+ "hema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\""
+ "required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\""
+ ":\"volume\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"resp"
+ "onses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$r"
+ "ef\":\"#/components/responses/400\"}}}},\"/get_endpoint_info\":{\"descri"
+ "ption\":\"Retrieve endpoint information including its properties\",\"get"
+ "\":{\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"requi"
+ "red\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\""
+ "endpoint_id\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"respon"
+ "ses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref"
+ "\":\"#/components/responses/400\"}}}},\"/property\":{\"description\":\"S"
+ "et/get endpoint property value\",\"get\":{\"x-permissions\":{\"$ref\":\""
+ "#/components/x-permissions/endpointcontrol\"},\"parameters\":[{\"in\":\""
+ "query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\""
+ ":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true"
+ ",\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"property_na"
+ "me\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query"
+ "\",\"name\":\"value\",\"required\":false,\"schema\":{\"type\":\"string\""
+ "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"4"
+ "00\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_list_actions\":"
+ "{\"description\":\"Retrieve a list of supported actions for a particular"
+ " audio role\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"audi"
+ "o_role\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"response"
+ "s\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\""
+ ":\"#/components/responses/400\"}}}},\"/post_action\":{\"description\":\""
+ "Post sound or audio device related action event (extendable mechanism)\""
+ ",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/sound"
+ "event\"},\"parameters\":[{\"in\":\"query\",\"name\":\"action_name\",\"re"
+ "quired\":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\":\"action_context\",\"requ"
+ "ired\":false,\"schema\":{\"type\":\"object\"}}],\"responses\":{\"200\":{"
+ "\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/component"
+ "s/responses/400\"}}}},\"/event_subscription\":{\"description\":\"Subscri"
+ "be to audio high level events\",\"get\":{\"parameters\":[{\"in\":\"query"
+ "\",\"name\":\"events\",\"required\":true,\"schema\":{\"type\":\"array\","
+ "\"items\":{\"type\":\"string\"}}},{\"in\":\"query\",\"name\":\"subscribe"
+ "\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"20"
+ "0\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/comp"
+ "onents/responses/400\"}}}}}}"
+;
+
+static const struct afb_auth _afb_auths_v2_ahl_4a[] = {
+ { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:audiostream" },
+ { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:streamcontrol" },
+ { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:endpointcontrol" },
+ { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:soundevent" }
+};
+
+ void audiohlapi_get_endpoints(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_get_stream_info(struct afb_req req);
+ void audiohlapi_volume(struct afb_req req);
+ void audiohlapi_get_endpoint_info(struct afb_req req);
+ void audiohlapi_property(struct afb_req req);
+ void audiohlapi_get_list_actions(struct afb_req req);
+ void audiohlapi_post_action(struct afb_req req);
+ void audiohlapi_event_subscription(struct afb_req req);
+
+static const struct afb_verb_v2 _afb_verbs_v2_ahl_4a[] = {
+ {
+ .verb = "get_endpoints",
+ .callback = audiohlapi_get_endpoints,
+ .auth = NULL,
+ .info = "Retrieve array of available audio endpoints",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "stream_open",
+ .callback = audiohlapi_stream_open,
+ .auth = &_afb_auths_v2_ahl_4a[0],
+ .info = "Request opening a stream",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "stream_close",
+ .callback = audiohlapi_stream_close,
+ .auth = &_afb_auths_v2_ahl_4a[0],
+ .info = "Request closing a stream",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "set_stream_state",
+ .callback = audiohlapi_set_stream_state,
+ .auth = &_afb_auths_v2_ahl_4a[1],
+ .info = "Change stream active and/or mute state",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_stream_info",
+ .callback = audiohlapi_get_stream_info,
+ .auth = NULL,
+ .info = "Retrieve stream information",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "volume",
+ .callback = audiohlapi_volume,
+ .auth = &_afb_auths_v2_ahl_4a[2],
+ .info = "Set or get volume on endpoint",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_endpoint_info",
+ .callback = audiohlapi_get_endpoint_info,
+ .auth = NULL,
+ .info = "Retrieve endpoint information including its properties",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "property",
+ .callback = audiohlapi_property,
+ .auth = &_afb_auths_v2_ahl_4a[2],
+ .info = "Set/get endpoint property value",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_list_actions",
+ .callback = audiohlapi_get_list_actions,
+ .auth = NULL,
+ .info = "Retrieve a list of supported actions for a particular audio role",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "post_action",
+ .callback = audiohlapi_post_action,
+ .auth = &_afb_auths_v2_ahl_4a[3],
+ .info = "Post sound or audio device related action event (extendable mechanism)",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "event_subscription",
+ .callback = audiohlapi_event_subscription,
+ .auth = NULL,
+ .info = "Subscribe to audio high level events",
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = NULL,
+ .callback = NULL,
+ .auth = NULL,
+ .info = NULL,
+ .session = 0
+ }
+};
+
+const struct afb_binding_v2 afbBindingV2 = {
+ .api = "ahl-4a",
+ .specification = _afb_description_v2_ahl_4a,
+ .info = "Audio high level API for AGL applications",
+ .verbs = _afb_verbs_v2_ahl_4a,
+ .preinit = NULL,
+ .init = AhlBindingInit,
+ .onevent = AhlOnEvent,
+ .noconcurrency = 0
+};
+
diff --git a/ahl-binding/ahl-apidef.json b/ahl-binding/ahl-apidef.json
new file mode 100644
index 0000000..59f7a32
--- /dev/null
+++ b/ahl-binding/ahl-apidef.json
@@ -0,0 +1,447 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "description": "Audio high level API for AGL applications",
+ "title": "audiohighlevel",
+ "version": "1.0",
+ "x-binding-c-generator": {
+ "api": "ahl-4a",
+ "version": 2,
+ "prefix": "audiohlapi_",
+ "postfix": "",
+ "start": null,
+ "onevent": "AhlOnEvent",
+ "init": "AhlBindingInit",
+ "scope": "",
+ "private": false
+ }
+ },
+ "servers": [
+ {
+ "url": "ws://{host}:{port}/api/audiohl",
+ "description": "Audio high level API for AGL applications.",
+ "variables": {
+ "host": {
+ "default": "localhost"
+ },
+ "port": {
+ "default": "1234"
+ }
+ },
+ "x-afb-events": [
+ {
+ "$ref": "#/components/schemas/afb-event"
+ }
+ ]
+ }
+ ],
+ "components": {
+ "schemas": {
+ "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": {
+ "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"
+ }
+ }
+ },
+ "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", "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"},
+ "endpointcontrol": { "permission": "urn:AGL:permission:audio:public:endpointcontrol"},
+ "audiostream": { "permission": "urn:AGL:permission:audio:public:audiostream"},
+ "soundevent": {"permission": "urn:AGL:permission:audio:public:soundevent"}
+ },
+ "responses": {
+ "200": {
+ "description": "A complex object array response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/afb-reply"
+ }
+ }
+ }
+ },
+ "400": { "description": "Invalid arguments" }
+ }
+ },
+ "paths": {
+ "/get_endpoints": {
+ "description": "Retrieve array of available audio endpoints",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/200",
+ "response": {
+ "description": "Array of endpoint info structures",
+ "type": "array",
+ "items": { "$ref": "#/components/schemas/endpoint_info"}
+ }
+ },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/stream_open": {
+ "description": "Request opening a stream",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/audiostream" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": false,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/200",
+ "response": {
+ "description": "Stream information structure",
+ "$ref": "#/components/schemas/stream_info"
+ }
+ },
+ "400": { "$ref": "#/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": false,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/set_stream_state": {
+ "description": "Change stream active and/or mute state",
+ "get": {
+ "x-permissions": {
+ "$ref": "#/components/x-permissions/streamcontrol"
+ },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "stream_id",
+ "required": false,
+ "schema": {"type": "int"}
+ },
+ {
+ "in": "query",
+ "name": "state",
+ "required": false,
+ "schema": {"type": "int"}
+ },
+ {
+ "in": "query",
+ "name": "mute",
+ "required": false,
+ "schema": {"type": "int"}
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/get_stream_info": {
+ "description": "Retrieve stream information",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "stream_id",
+ "required": true,
+ "schema": {"type": "int"}
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/200",
+ "response": {
+ "description": "Stream information structure",
+ "$ref": "#/components/schemas/stream_info"
+ }
+ },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/volume": {
+ "description": "Set or get volume on endpoint",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/endpointcontrol" },
+ "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": false,
+ "schema": { "type": "string" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/get_endpoint_info": {
+ "description": "Retrieve endpoint information including its properties",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": true,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/property": {
+ "description": "Set/get endpoint property value",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/endpointcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": true,
+ "schema": { "type": "int" }
+ },
+ {
+ "in": "query",
+ "name": "property_name",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "value",
+ "required": false,
+ "schema": { "type": "string" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/get_list_actions": {
+ "description": "Retrieve a list of supported actions for a particular audio role",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": true,
+ "schema": { "type": "string" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/post_action": {
+ "description": "Post sound or audio device related action event (extendable mechanism)",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/soundevent" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "action_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": "action_context",
+ "required": false,
+ "schema": { "type": "object" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/event_subscription": {
+ "description": "Subscribe to audio high level events",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "events",
+ "required": true,
+ "schema": { "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ {
+ "in": "query",
+ "name": "subscribe",
+ "required": true,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ }
+ }
+}
diff --git a/ahl-binding/ahl-binding.c b/ahl-binding/ahl-binding.c
new file mode 100644
index 0000000..c205c8e
--- /dev/null
+++ b/ahl-binding/ahl-binding.c
@@ -0,0 +1,1276 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "ahl-binding.h"
+#include "ahl-apidef.h" // Generated from JSON OpenAPI
+#include "wrap-json.h"
+#include "ahl-policy.h"
+#include "ahl-json.h"
+
+// Global high-level binding context
+AHLCtxT g_AHLCtx;
+
+static EndpointTypeT EndpointTypeToEnum(char * in_pEndpointTypeStr)
+{
+ if (in_pEndpointTypeStr == NULL) {
+ return ENDPOINTTYPE_MAXVALUE;
+ }
+ else if (strcasecmp(in_pEndpointTypeStr,AHL_ENDPOINTTYPE_SOURCE)==0) {
+ return ENDPOINTTYPE_SOURCE;
+ }
+ else if (strcasecmp(in_pEndpointTypeStr,AHL_ENDPOINTTYPE_SINK)==0) {
+ return ENDPOINTTYPE_SINK;
+ }
+ else
+ return ENDPOINTTYPE_MAXVALUE;
+}
+
+static StreamStateT StreamStateToEnum(char * in_pStreamStateStr)
+{
+ if (in_pStreamStateStr == NULL) {
+ return STREAM_STATE_MAXVALUE;
+ }
+ else if (strcasecmp(in_pStreamStateStr,AHL_STREAM_STATE_IDLE)==0) {
+ return STREAM_STATE_IDLE;
+ }
+ else if (strcasecmp(in_pStreamStateStr,AHL_STREAM_STATE_RUNNING)==0) {
+ return STREAM_STATE_RUNNING;
+ }
+ else if (strcasecmp(in_pStreamStateStr,AHL_STREAM_STATE_PAUSED)==0) {
+ return STREAM_STATE_PAUSED;
+ }
+ else
+ return STREAM_STATE_MAXVALUE;
+}
+
+static StreamMuteT StreamMuteToEnum(char * in_pStreamMuteStr)
+{
+ if (in_pStreamMuteStr == NULL) {
+ return STREAM_MUTE_MAXVALUE;
+ }
+ else if (strcasecmp(in_pStreamMuteStr,AHL_STREAM_UNMUTED)==0) {
+ return STREAM_UNMUTED;
+ }
+ else if (strcasecmp(in_pStreamMuteStr,AHL_STREAM_MUTED)==0) {
+ return STREAM_MUTED;
+ }
+ else
+ return STREAM_MUTE_MAXVALUE;
+}
+
+static streamID_t CreateNewStreamID()
+{
+ streamID_t newID = g_AHLCtx.nextStreamID;
+ g_AHLCtx.nextStreamID++;
+ return newID;
+}
+
+static EndpointInfoT * GetEndpointInfoWithRole(endpointID_t in_endpointID, EndpointTypeT in_endpointType, RoleInfoT * in_pRole)
+{
+ EndpointInfoT * pEndpointInfo = NULL;
+ GPtrArray * pDeviceArray = NULL;
+ if (in_endpointType == ENDPOINTTYPE_SOURCE){
+ pDeviceArray = in_pRole->pSourceEndpoints;
+ }
+ else {
+ pDeviceArray = in_pRole->pSinkEndpoints;
+ }
+ g_assert_nonnull(pDeviceArray);
+
+ for (int j = 0; j < pDeviceArray->len; j++) {
+ EndpointInfoT * pCurEndpointInfo = g_ptr_array_index(pDeviceArray,j);
+ g_assert_nonnull(pCurEndpointInfo);
+ if (pCurEndpointInfo->endpointID == in_endpointID) {
+ pEndpointInfo = pCurEndpointInfo;
+ break;
+ }
+ }
+
+ return pEndpointInfo;
+}
+
+static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT in_endpointType)
+{
+ EndpointInfoT * pEndpointInfo = NULL;
+
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init (&iter, g_AHLCtx.policyCtx.pRoleInfo);
+ while (pEndpointInfo == NULL && g_hash_table_iter_next (&iter, &key, &value))
+ {
+ RoleInfoT * pRoleInfo = (RoleInfoT*)value;
+ pEndpointInfo = GetEndpointInfoWithRole(in_endpointID,in_endpointType,pRoleInfo);
+ }
+
+ return pEndpointInfo;
+}
+
+static StreamInfoT * GetStream(streamID_t in_streamID)
+{
+ if (g_AHLCtx.policyCtx.pStreams == NULL)
+ return NULL;
+
+ return g_hash_table_lookup(g_AHLCtx.policyCtx.pStreams,GINT_TO_POINTER(&in_streamID));
+}
+
+static RoleInfoT * GetRole(char * in_pAudioRoleName)
+{
+ if (g_AHLCtx.policyCtx.pRoleInfo == NULL)
+ return NULL;
+
+ return g_hash_table_lookup(g_AHLCtx.policyCtx.pRoleInfo,in_pAudioRoleName);
+}
+
+static int CloseStream(AHLClientCtxT * in_pClientCtx, streamID_t streamID,struct afb_req * pReq) {
+ StreamInfoT * pStreamInfo = GetStream(streamID);
+ if (pStreamInfo == NULL) {
+ AFB_ERROR("Specified stream not currently active stream_id -> %d",streamID);
+ return AHL_FAIL;
+ }
+
+#ifndef AHL_DISCONNECT_POLICY
+ json_object *pPolicyStreamJ = NULL;
+ int err = StreamInfoToJSON(pStreamInfo, &pPolicyStreamJ);
+ if (err == AHL_POLICY_UTIL_FAIL)
+ {
+ AFB_ERROR("Audio policy violation, Unable to get JSON object for Policy_CloseStream");
+ return AHL_FAIL;
+ }
+ int policyAllowed = Policy_CloseStream(pPolicyStreamJ);
+ if (policyAllowed == AHL_POLICY_REJECT)
+ {
+ AFB_ERROR("Close stream not allowed in current context");
+ return AHL_FAIL;
+ }
+#endif
+ // Unsubscribe client from stream events
+ if (pReq != NULL) {
+ char streamEventName[128];
+ snprintf(streamEventName,128,"ahl_streamstate_%d",streamID);
+ int iValid = afb_event_is_valid(pStreamInfo->streamStateEvent);
+ if (iValid) {
+ err = afb_req_unsubscribe(*pReq,pStreamInfo->streamStateEvent);
+ if (err) {
+ AFB_ERROR("Could not unsubscribe to stream specific state change event");
+ return AHL_FAIL;
+ }
+ }
+ }
+
+ // Remove from stream list (if present)
+ if (g_AHLCtx.policyCtx.pStreams)
+ g_hash_table_remove(g_AHLCtx.policyCtx.pStreams,GINT_TO_POINTER(&pStreamInfo->streamID));
+ free(pStreamInfo);
+ pStreamInfo = NULL;
+
+ // Find index for cases where there are multiple streams per client
+ // Remove from client context stream ID and endpoint ID access rights
+ if (in_pClientCtx->pStreamAccessList) {
+ for (int i = 0; i < in_pClientCtx->pStreamAccessList->len ; i++) {
+ streamID_t iID = g_array_index(in_pClientCtx->pStreamAccessList,streamID_t,i);
+ if (iID == streamID) {
+ g_array_remove_index(in_pClientCtx->pStreamAccessList, i);
+ }
+ }
+ }
+
+ return AHL_SUCCESS;
+}
+
+static int CloseAllClientStreams(AHLClientCtxT * in_pClientCtx, struct afb_req * pReq)
+{
+ g_assert_nonnull(in_pClientCtx);
+ if (in_pClientCtx->pStreamAccessList != NULL) {
+ while( in_pClientCtx->pStreamAccessList->len )
+ {
+ streamID_t streamID = g_array_index(in_pClientCtx->pStreamAccessList,streamID_t,0);
+ int err = CloseStream(in_pClientCtx,streamID,pReq);
+ if (err) {
+ return err;
+ }
+ }
+ }
+
+ return AHL_SUCCESS;
+}
+
+static AHLClientCtxT * AllocateClientContext()
+{
+ AHLClientCtxT * pClientCtx = malloc(sizeof(AHLClientCtxT));
+ pClientCtx->pStreamAccessList = g_array_new(FALSE, TRUE, sizeof(streamID_t));
+ return pClientCtx;
+}
+
+static void TerminateClientContext(void * ptr)
+{
+ AHLClientCtxT * pClientCtx = (AHLClientCtxT *) ptr;
+ if (pClientCtx != NULL) {
+ CloseAllClientStreams(pClientCtx,NULL);
+
+ if (pClientCtx->pStreamAccessList) {
+ g_array_free( pClientCtx->pStreamAccessList, TRUE);
+ pClientCtx->pStreamAccessList = NULL;
+ }
+
+ free(pClientCtx);
+ }
+}
+
+static int CheckStreamAccessControl(AHLClientCtxT * pClientCtx, streamID_t streamID)
+{
+ int iAccessControl = AHL_ACCESS_CONTROL_DENIED;
+ if (pClientCtx && pClientCtx->pStreamAccessList) {
+ for (int i = 0; i < pClientCtx->pStreamAccessList->len ; i++) {
+ streamID_t iID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i);
+ if (iID == streamID) {
+ iAccessControl = AHL_ACCESS_CONTROL_GRANTED;
+ }
+ }
+ }
+ return iAccessControl;
+}
+
+static int CreateEvents()
+{
+ g_AHLCtx.policyCtx.propertyEvent = afb_daemon_make_event(AHL_ENDPOINT_PROPERTY_EVENT);
+ int err = !afb_event_is_valid(g_AHLCtx.policyCtx.propertyEvent);
+ if (err) {
+ AFB_ERROR("Could not create endpoint property change event");
+ return AHL_FAIL;
+ }
+
+ 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");
+ return AHL_FAIL;
+ }
+
+ g_AHLCtx.policyCtx.postActionEvent = afb_daemon_make_event(AHL_POST_ACTION_EVENT);
+ err = !afb_event_is_valid(g_AHLCtx.policyCtx.postActionEvent);
+ if (err) {
+ AFB_ERROR("Could not create post action event call event");
+ return AHL_FAIL;
+ }
+
+ return AHL_SUCCESS;
+}
+
+static void AhlBindingTerm()
+{
+#ifndef AHL_DISCONNECT_POLICY
+ // Policy termination
+ Policy_Term();
+#endif
+
+ // Roles
+ if (g_AHLCtx.policyCtx.pRoleInfo != NULL) {
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init(&iter, g_AHLCtx.policyCtx.pRoleInfo);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ RoleInfoT * pRole = (RoleInfoT *)value;
+ if (pRole)
+ {
+ if(pRole->pRoleName) {
+ g_free(pRole->pRoleName);
+ pRole->pRoleName = NULL;
+ }
+ // Actions
+ if (pRole->pActionList) {
+ for (int i = 0; i < pRole->pActionList->len; i++)
+ {
+ g_ptr_array_remove_index( pRole->pActionList, i ); // Free char * is called by GLib
+ }
+ }
+ // Source endpoints
+ if (pRole->pSourceEndpoints) {
+ for (int i = 0; i < pRole->pSourceEndpoints->len; i++)
+ {
+ EndpointInfoT * pEndpoint = g_ptr_array_remove_index( pRole->pSourceEndpoints, i ); // Free endpoint * is called by GLib
+ if (pEndpoint) {
+ TermEndpointInfo(pEndpoint);
+ }
+ }
+ }
+ // Sink endpoints
+ if (pRole->pSinkEndpoints) {
+ for (int i = 0; i < pRole->pSinkEndpoints->len; i++)
+ {
+ EndpointInfoT * pEndpoint = g_ptr_array_remove_index( pRole->pSinkEndpoints, i ); // Free endpoint * is called by GLib
+ if (pEndpoint) {
+ TermEndpointInfo(pEndpoint);
+ }
+ }
+ }
+ free(pRole);
+ }
+ }
+ g_hash_table_remove_all(g_AHLCtx.policyCtx.pRoleInfo);
+ g_hash_table_destroy(g_AHLCtx.policyCtx.pRoleInfo);
+ g_AHLCtx.policyCtx.pRoleInfo = NULL;
+ }
+
+ if (g_AHLCtx.policyCtx.pStreams) {
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init (&iter, g_AHLCtx.policyCtx.pStreams);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (value)
+ free(value);
+ }
+ g_hash_table_remove_all(g_AHLCtx.policyCtx.pStreams);
+ g_hash_table_destroy(g_AHLCtx.policyCtx.pStreams);
+ g_AHLCtx.policyCtx.pStreams = NULL;
+ }
+
+ if (g_AHLCtx.policyCtx.pHALList) {
+ g_ptr_array_free(g_AHLCtx.policyCtx.pHALList,TRUE);
+ g_AHLCtx.policyCtx.pHALList = NULL;
+ }
+
+ AFB_INFO("Audio high-level binding termination success");
+}
+
+// Binding initialization
+PUBLIC int AhlBindingInit()
+{
+ memset(&g_AHLCtx,0,sizeof(g_AHLCtx));
+
+ // Register exit function
+ atexit(AhlBindingTerm);
+
+ // Create AGL Events
+ int err = CreateEvents();
+ if(err) {
+ // Error messages already reported inside CreateEvents
+ return AHL_FAIL;
+ }
+
+ // Parse high-level binding JSON configuration file (will build device lists)
+ err = ParseHLBConfig();
+ if(err) {
+ // Error messages already reported inside ParseHLBConfig
+ return AHL_FAIL;
+ }
+
+#ifndef AHL_DISCONNECT_POLICY
+ // Policy initialization
+ err = Policy_Init();
+ if(err == AHL_POLICY_REJECT) {
+ //Error messages already reported inside PolicyInit
+ return AHL_FAIL;
+ }
+
+ // Call policy for initalization of all source + sink endpoints for all audio Roles
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init (&iter, g_AHLCtx.policyCtx.pRoleInfo);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ RoleInfoT * pRoleInfo = (RoleInfoT*)value;
+ if (pRoleInfo->pSourceEndpoints){
+ // for all source endpoints
+ for (int j = 0; j < pRoleInfo->pSourceEndpoints->len; j++) {
+ EndpointInfoT * pCurEndpointInfo = g_ptr_array_index(pRoleInfo->pSourceEndpoints,j);
+ g_assert_nonnull(pCurEndpointInfo);
+ json_object *pInPolicyEndpointJ = NULL;
+ err = EndpointInfoToJSON(pCurEndpointInfo, &pInPolicyEndpointJ);
+ if (err) {
+ AFB_ERROR("Unable to Create Endpoint Json object error:%s ",wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+ else
+ {
+ json_object * pOutPolicyEndpointJ = NULL;
+ err = Policy_Endpoint_Init(pInPolicyEndpointJ,&pOutPolicyEndpointJ);
+ if (err == AHL_POLICY_REJECT) {
+ AFB_WARNING("Policy endpoint properties initalization failed for endpoint_id :%d type:%d",pCurEndpointInfo->endpointID, pCurEndpointInfo->type);
+ // continue
+ }
+ json_object_put(pInPolicyEndpointJ);
+ err = UpdateEndpointInfo(pCurEndpointInfo,pOutPolicyEndpointJ);
+ if (err) {
+ AFB_ERROR("Policy endpoint properties update failed for endpoint_id :%d type:%d",pCurEndpointInfo->endpointID, pCurEndpointInfo->type);
+ return AHL_FAIL;
+ }
+ // json_object_put(pOutPolicyEndpointJ);
+ }
+ }
+ }
+
+ if (pRoleInfo->pSinkEndpoints){
+ // for all sink endpoints
+ for (int j = 0; j < pRoleInfo->pSinkEndpoints->len; j++) {
+ EndpointInfoT * pCurEndpointInfo = g_ptr_array_index(pRoleInfo->pSinkEndpoints,j);
+ g_assert_nonnull(pCurEndpointInfo);
+ json_object *pInPolicyEndpointJ = NULL;
+ err = EndpointInfoToJSON(pCurEndpointInfo, &pInPolicyEndpointJ);
+ if (err) {
+ AFB_ERROR("Unable to Create Endpoint Json object error:%s ",wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+ else
+ {
+ json_object *pOutPolicyEndpointJ = NULL;
+ err = Policy_Endpoint_Init(pInPolicyEndpointJ,&pOutPolicyEndpointJ);
+ if (err == AHL_POLICY_REJECT) {
+ AFB_WARNING("Policy endpoint properties initalization failed for endpoint_id :%d type:%d",pCurEndpointInfo->endpointID, pCurEndpointInfo->type);
+ // continue
+ }
+ json_object_put(pInPolicyEndpointJ);
+ err = UpdateEndpointInfo(pCurEndpointInfo,pOutPolicyEndpointJ);
+ if (err) {
+ AFB_ERROR("Policy endpoint properties update failed for endpoint_id :%d type:%d",pCurEndpointInfo->endpointID, pCurEndpointInfo->type);
+ return AHL_FAIL;
+ }
+ //json_object_put(pOutPolicyEndpointJ);
+ }
+ }
+ }
+ }
+#endif // AHL_DISCONNECT_POLICY
+
+ // Initialize list of active streams
+ g_AHLCtx.policyCtx.pStreams = g_hash_table_new(g_int_hash, g_int_equal);
+ if(g_AHLCtx.policyCtx.pStreams == NULL)
+ {
+ AFB_ERROR("Unable to create Active Stream List");
+ return AHL_FAIL;
+ }
+
+ AFB_DEBUG("Audio high-level Binding success");
+ return AHL_SUCCESS;
+}
+
+PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ)
+{
+ AFB_DEBUG("AHL received event %s", evtname);
+
+ // TODO: Handle event from the policy to update internal information (currently not possible since within the same binding)
+
+#ifndef AHL_DISCONNECT_POLICY
+ // Temp: currently forward to policy to handle events (they will be received directly when disconnected into separate binding)
+ Policy_OnEvent(evtname, eventJ);
+#endif
+}
+
+PUBLIC void audiohlapi_get_endpoints(struct afb_req req)
+{
+ json_object *devicesJ = NULL;
+ json_object *deviceJ = NULL;
+ json_object *queryJ = NULL;
+ char * audioRole = NULL;
+ char * pEndpointTypeStr = NULL;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:s,s:s}", "audio_role", &audioRole,"endpoint_type",&pEndpointTypeStr);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ endpointType = EndpointTypeToEnum(pEndpointTypeStr);
+
+ RoleInfoT * pRole = GetRole(audioRole);
+ if ( pRole == NULL )
+ {
+ afb_req_fail_f(req, "Invalid arguments", "Requested audio role does not exist in current configuration -> %s", json_object_get_string(queryJ));
+ return;
+ }
+ else
+ {
+ devicesJ = json_object_new_array();
+ GPtrArray * pDeviceArray = NULL;
+ if (endpointType == ENDPOINTTYPE_SOURCE)
+ pDeviceArray = pRole->pSourceEndpoints;
+ else
+ pDeviceArray = pRole->pSinkEndpoints;
+ if (pDeviceArray) {
+ int iNumberDevices = pDeviceArray->len;
+ for ( int j = 0 ; j < iNumberDevices; j++)
+ {
+ EndpointInfoT * pEndpointInfo = g_ptr_array_index(pDeviceArray,j);
+ if (pEndpointInfo) {
+ JSONPublicPackageEndpoint(pEndpointInfo,&deviceJ);
+ json_object_array_add(devicesJ, deviceJ);
+ }
+ }
+ }
+ }
+
+ afb_req_success(req, devicesJ, "List of endpoints");
+}
+
+PUBLIC void audiohlapi_stream_open(struct afb_req req)
+{
+ json_object *streamInfoJ = NULL;
+ StreamInfoT * pStreamInfo = NULL;
+ json_object *queryJ = NULL;
+ char * audioRole = NULL;
+ char * endpointTypeStr = NULL;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ endpointID_t endpointID = AHL_UNDEFINED;
+ EndpointInfoT * pEndpointInfo = NULL;
+ EndpointSelectionModeT endpointSelMode = ENDPOINTSELMODEMAXVALUE;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointTypeStr, "endpoint_id", &endpointID);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ endpointType = EndpointTypeToEnum(endpointTypeStr);
+
+ // Check if there is already an existing context for this client
+ AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure
+ if (pClientCtx == NULL)
+ {
+ pClientCtx = AllocateClientContext();
+ afb_req_context_set(req, pClientCtx, TerminateClientContext);
+ }
+
+ RoleInfoT * pRole = GetRole(audioRole);
+ if ( pRole == NULL )
+ {
+ afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole);
+ return;
+ }
+
+ GPtrArray * pDeviceArray = NULL;
+ if (endpointType == ENDPOINTTYPE_SOURCE){
+ pDeviceArray = pRole->pSourceEndpoints;
+ }
+ else{
+ pDeviceArray = pRole->pSinkEndpoints;
+ }
+ if (pDeviceArray == NULL || pDeviceArray->len == 0) {
+ afb_req_fail_f(req, "No available devices", "No available devices for role:%s and device type:%s",audioRole,endpointTypeStr);
+ return;
+ }
+
+ if (endpointID == AHL_UNDEFINED)
+ {
+ // Assign a device based on configuration priority (first in the list for requested role and endpoint type)
+ pEndpointInfo = g_ptr_array_index(pDeviceArray,0);
+ endpointSelMode = ENDPOINTSELMODE_AUTO;
+ }
+ else{
+ endpointSelMode = ENDPOINTSELMODE_MANUAL;
+ // Find specified endpoint ID in list of devices
+ int iNumberDevices = pDeviceArray->len;
+ for ( int j = 0 ; j < iNumberDevices; j++)
+ {
+ pEndpointInfo = g_ptr_array_index(pDeviceArray,j);
+ if (pEndpointInfo && pEndpointInfo->endpointID == endpointID) {
+ break;
+ }
+ pEndpointInfo = NULL;
+ }
+ }
+
+ if (pEndpointInfo == NULL) {
+ afb_req_fail_f(req, "Endpoint not available", "Specified endpoint not available for role:%s and device type:%d endpoint id %d",audioRole,endpointType,endpointID);
+ return;
+ }
+
+ pStreamInfo = (StreamInfoT*) malloc(sizeof(StreamInfoT));
+ memset(pStreamInfo,0,sizeof(StreamInfoT));
+
+ // Create stream
+ pStreamInfo->streamID = CreateNewStreamID(); // create new ID
+ pStreamInfo->streamState = STREAM_STATE_IDLE;
+ pStreamInfo->streamMute = STREAM_UNMUTED;
+ pStreamInfo->pEndpointInfo = pEndpointInfo;
+ pStreamInfo->endpointSelMode = endpointSelMode;
+ // Directly from role config for now, but could be programmatically overriden in the future
+ pStreamInfo->pRoleName = pRole->pRoleName;
+ pStreamInfo->iPriority = pRole->iPriority;
+ pStreamInfo->eInterruptBehavior = pRole->eInterruptBehavior;
+
+#ifndef AHL_DISCONNECT_POLICY
+ // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions
+ json_object *pPolicyStreamJ = NULL;
+ err = StreamInfoToJSON(pStreamInfo,&pPolicyStreamJ);
+ if (err)
+ {
+ afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_OpenStream");
+ return;
+ }
+
+ int policyAllowed = Policy_OpenStream(pPolicyStreamJ);
+ if (policyAllowed == AHL_POLICY_REJECT)
+ {
+ afb_req_fail(req, "Audio policy violation", "Open stream not allowed in current context");
+ return;
+ }
+#endif
+
+ char streamEventName[128];
+ snprintf(streamEventName,128,"ahl_streamstate_%d",pStreamInfo->streamID);
+
+ pStreamInfo->streamStateEvent = afb_daemon_make_event(streamEventName);
+ err = !afb_event_is_valid(pStreamInfo->streamStateEvent);
+ if (err) {
+ afb_req_fail(req, "Stream event creation failure", "Could not create stream specific state change event");
+ return;
+ }
+
+ err = afb_req_subscribe(req,pStreamInfo->streamStateEvent);
+ if (err) {
+ afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event");
+ return;
+ }
+
+ // Add to client context stream ID access rights
+ g_array_append_val(pClientCtx->pStreamAccessList, pStreamInfo->streamID);
+
+ // Push stream on active stream list
+ if (g_AHLCtx.policyCtx.pStreams)
+ g_hash_table_insert( g_AHLCtx.policyCtx.pStreams, GINT_TO_POINTER(&pStreamInfo->streamID), pStreamInfo );
+
+ // Package and return stream information to client
+ JSONPublicPackageStream(pStreamInfo,&streamInfoJ);
+
+ afb_req_success(req, streamInfoJ, "Stream info structure");
+}
+
+PUBLIC void audiohlapi_stream_close(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ streamID_t streamID = AHL_UNDEFINED;
+
+ 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;
+ }
+
+ // Check if there is already an existing context for this client
+ AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure
+ if (pClientCtx == NULL)
+ {
+ afb_req_fail(req, "Bad state", "No client context associated with the request (is there an opened stream by this client?)");
+ return;
+ }
+
+ if (streamID == AHL_UNDEFINED) {
+ err = CloseAllClientStreams(pClientCtx,&req);
+ if (err) {
+ afb_req_fail(req, "Error closing streams", "Streams cannot close");
+ return;
+ }
+ }
+ else {
+ err = CloseStream(pClientCtx,streamID,&req);
+ if (err) {
+ afb_req_fail_f(req, "Error closing stream", "Specified stream cannot close stream_id -> %d",streamID);
+ return;
+ }
+ }
+
+ afb_req_success(req, NULL, "Stream close completed");
+}
+
+static int SetStreamState(AHLClientCtxT * in_pClientCtx,struct afb_req * pReq, streamID_t streamID, char * pStreamStateStr, char * pMuteStr) {
+
+ StreamInfoT * pStreamInfo = GetStream(streamID);
+ if (pStreamInfo == NULL) {
+ afb_req_fail_f(*pReq, "Stream not found", "Specified stream not found stream_id -> %d",streamID);
+ return AHL_FAIL;
+ }
+
+ // Verify that this client can control the stream
+ int iStreamAccessControl = CheckStreamAccessControl( in_pClientCtx, streamID );
+ if (iStreamAccessControl == AHL_ACCESS_CONTROL_DENIED)
+ {
+ afb_req_fail(*pReq, "Access control denied", "Set stream state not allowed in current client context");
+ return AHL_FAIL;
+ }
+
+ if (pStreamStateStr != NULL) {
+ StreamStateT streamState = StreamStateToEnum(pStreamStateStr);
+#ifndef AHL_DISCONNECT_POLICY
+ json_object *pPolicyStreamJ = NULL;
+ int err = StreamInfoToJSON(pStreamInfo, &pPolicyStreamJ);
+ if (err == AHL_POLICY_UTIL_FAIL)
+ {
+ afb_req_fail(*pReq, "Audio policy violation", "Unable to get JSON object for Policy_SetStreamState");
+ return AHL_FAIL;
+ }
+
+ json_object *paramJ= json_object_new_int(streamState);
+ json_object_object_add(pPolicyStreamJ, "arg_stream_state", paramJ);
+
+ int policyAllowed = Policy_SetStreamState(pPolicyStreamJ);
+ if (policyAllowed == AHL_POLICY_REJECT)
+ {
+ afb_req_fail(*pReq, "Audio policy violation", "Change stream state not allowed in current context");
+ return AHL_FAIL;
+ }
+#else
+ // Simulate that policy returns target state (accepted)
+ pStreamInfo->streamState = streamState;
+#endif
+ }
+
+ if (pMuteStr != NULL) {
+ StreamMuteT muteState = StreamMuteToEnum(pMuteStr);
+#ifndef AHL_DISCONNECT_POLICY
+ json_object *pPolicyStreamJ = NULL;
+ int err = StreamInfoToJSON(pStreamInfo, &pPolicyStreamJ);
+ if (err == AHL_POLICY_UTIL_FAIL)
+ {
+ afb_req_fail((*pReq), "Audio policy violation", "Unable to get JSON object for Policy_SetStreamMute");
+ return AHL_FAIL;
+ }
+
+ json_object *paramJ= json_object_new_int(muteState);
+ json_object_object_add(pPolicyStreamJ, "mute_state", paramJ);
+
+ int policyAllowed = Policy_SetStreamMute(pPolicyStreamJ);
+ if (policyAllowed == AHL_POLICY_REJECT)
+ {
+ afb_req_fail(*pReq, "Audio policy violation", "Mute stream not allowed in current context");
+ return AHL_FAIL;
+ }
+#else
+ // Simulate that policy returns target state (accepted)
+ pStreamInfo->streamMute = muteState;
+#endif
+ }
+
+ return AHL_SUCCESS;
+}
+
+ PUBLIC void audiohlapi_set_stream_state(struct afb_req req)
+ {
+ json_object *queryJ = NULL;
+ streamID_t streamID = AHL_UNDEFINED;
+ char * streamStateStr = NULL;
+ char * pMuteStr = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s?i,s?s,s?s}", "stream_id", &streamID,"state",&streamStateStr,"mute",&pMuteStr);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+
+ // Check if there is already an existing context for this client
+ AHLClientCtxT * pClientCtx = afb_req_context_get(req); // Retrieve client-specific data structure
+ if (pClientCtx == NULL)
+ {
+ afb_req_fail(req, "Bad state", "No client context associated with the request (is there an opened stream by this client?)");
+ return;
+ }
+
+ if (streamID == AHL_UNDEFINED) {
+ // All stream for this client
+ if (pClientCtx->pStreamAccessList != NULL) {
+ for (int i = 0; i < pClientCtx->pStreamAccessList->len; i++)
+ {
+ streamID_t curStreamID = g_array_index(pClientCtx->pStreamAccessList,streamID_t,i);
+ err = SetStreamState(pClientCtx,&req,curStreamID,streamStateStr,pMuteStr);
+ if (err) {
+ return;
+ }
+ }
+ }
+ }
+ else {
+ err = SetStreamState(pClientCtx,&req,streamID,streamStateStr,pMuteStr);
+ if (err) {
+ return;
+ }
+ }
+
+ afb_req_success(req, NULL, "Set stream state");
+ }
+
+ 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;
+ }
+
+ StreamInfoT * pStreamInfo = GetStream(streamID);
+ if (pStreamInfo == NULL) {
+ afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID);
+ return;
+ }
+
+ JSONPublicPackageStream(pStreamInfo,&streamInfoJ);
+
+ afb_req_success(req, streamInfoJ, "Get stream info completed");
+ }
+
+PUBLIC void audiohlapi_volume(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = AHL_UNDEFINED;
+ char * pEndpointTypeStr = NULL;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ char * volumeStr = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:s,s:i,s?s}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID,"volume",&volumeStr);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ endpointType = EndpointTypeToEnum(pEndpointTypeStr);
+
+ 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;
+ }
+
+ if (volumeStr != NULL) {
+#ifndef AHL_DISCONNECT_POLICY
+ json_object *pPolicyEndpointJ = NULL;
+ err = EndpointInfoToJSON(pEndpointInfo, &pPolicyEndpointJ);
+ if (err == AHL_POLICY_UTIL_FAIL)
+ {
+ afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_SetVolume");
+ return;
+ }
+
+ json_object *paramJ= json_object_new_string(volumeStr);
+ json_object_object_add(pPolicyEndpointJ, "arg_volume", paramJ);
+
+ int policyAllowed = Policy_SetVolume(pPolicyEndpointJ);
+ if (!policyAllowed)
+ {
+ afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context");
+ return;
+ }
+#else
+ // Simulate that policy returns target state (accepted)
+ pEndpointInfo->iVolume = atoi(volumeStr);
+#endif
+ }
+
+ json_object * volumeJ = json_object_new_int(pEndpointInfo->iVolume);
+
+ afb_req_success(req, volumeJ, "Set/get volume completed");
+}
+
+PUBLIC void audiohlapi_get_endpoint_info(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = AHL_UNDEFINED;
+ char * pEndpointTypeStr = NULL;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:s,s:i}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ endpointType = EndpointTypeToEnum(pEndpointTypeStr);
+
+ 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;
+ }
+
+ json_object *endpointInfoJ = NULL;
+ EndpointInfoToJSON(pEndpointInfo,&endpointInfoJ);
+
+ afb_req_success(req, endpointInfoJ, "Retrieved endpoint information and properties");
+}
+
+PUBLIC void audiohlapi_property(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = AHL_UNDEFINED;
+ char * pEndpointTypeStr = NULL;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ char * propertyName = NULL;
+ json_object * propValueJ = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:s,s:i,s:s,s?o}", "endpoint_type", &pEndpointTypeStr,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueJ);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ endpointType = EndpointTypeToEnum(pEndpointTypeStr);
+
+ 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;
+ }
+
+ if (propValueJ != NULL) {
+ #ifndef AHL_DISCONNECT_POLICY
+ json_object *pPolicyEndpointJ = NULL;
+ err = EndpointInfoToJSON(pEndpointInfo, &pPolicyEndpointJ);
+ if (err == AHL_POLICY_UTIL_FAIL)
+ {
+ afb_req_fail(req, "Audio policy violation", "Unable to get JSON object for Policy_SetVolume");
+ return;
+ }
+
+ json_object *paramJ= json_object_new_string(propertyName);
+ json_object_object_add(pPolicyEndpointJ, "arg_property_name", paramJ);
+ json_object_object_add(pPolicyEndpointJ, "arg_property_value", propValueJ);
+
+ // Call policy to allow custom policy actions in current context
+ int policyAllowed = Policy_SetProperty(pPolicyEndpointJ);
+ if (!policyAllowed)
+ {
+ afb_req_fail(req, "Audio policy violation", "Set endpoint property not allowed in current context");
+ return;
+ }
+ #else
+ // Simulate that policy returns target state (accepted)
+ if (pEndpointInfo->pPropTable)
+ g_hash_table_insert(pEndpointInfo->pPropTable, propertyName, propValueJ);
+ #endif
+ }
+
+ // Retrieve cached property value
+ json_object * propertyValJ = (json_object *)g_hash_table_lookup(pEndpointInfo->pPropTable,propertyName);
+ if (propertyValJ == NULL) {
+ afb_req_fail_f(req, "Property not found", "Property information not found: %s",propertyName);
+ return;
+ }
+
+ json_object_get(propertyValJ); // Increase ref count so that framework does not free our JSON object
+
+ afb_req_success(req, propertyValJ, "Set/get property completed");
+}
+
+PUBLIC void audiohlapi_get_list_actions(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ char * audioRole = NULL;
+ json_object * roleActionsJ = NULL;
+
+ queryJ = afb_req_json(req);
+ 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;
+ }
+
+ // Build and return list of actions for specific audio role
+ RoleInfoT * pRole = GetRole(audioRole);
+ if ( pRole == NULL )
+ {
+ afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole);
+ return;
+ }
+
+ roleActionsJ = json_object_new_array();
+ if (pRole->pActionList) {
+ int iNumberActions = pRole->pActionList->len;
+ for ( int i = 0 ; i < iNumberActions; i++)
+ {
+ char * pActionName = g_ptr_array_index(pRole->pActionList,i);
+ json_object * actionJ = json_object_new_string(pActionName);
+ json_object_array_add(roleActionsJ, actionJ);
+ }
+ }
+
+ afb_req_success(req, roleActionsJ, "Retrieved action list for audio role");
+}
+
+PUBLIC void audiohlapi_post_action(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ char * actionName = NULL;
+ char * audioRole = NULL;
+ char * mediaName = NULL;
+ json_object *actionContext = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?s,s?o}", "action_name", &actionName,"audio_role",&audioRole,"media_name",&mediaName,"action_context",&actionContext);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+
+ // Verify if known action for audio role
+ RoleInfoT * pRole = GetRole(audioRole);
+ if ( pRole == NULL )
+ {
+ afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole);
+ return;
+ }
+
+ // Check to find specific action
+ int iActionFound = 0;
+ if (pRole->pActionList) {
+ int iNumberActions = pRole->pActionList->len;
+ char * pTargetActionName = NULL;
+ for ( int i = 0 ; i < iNumberActions; i++)
+ {
+ pTargetActionName = g_ptr_array_index(pRole->pActionList,i);
+ if ( strcasecmp(pTargetActionName,actionName)==0) {
+ iActionFound = 1;
+ break;
+ }
+ }
+ }
+
+ if (!iActionFound) {
+ afb_req_fail_f(req, "Event not found for audio role", "Event -> %s not found for role:%s",actionName,audioRole);
+ return;
+ }
+
+#ifndef AHL_DISCONNECT_POLICY
+ // Call policy to allow custom policy actions in current context (e.g. cancel playback)
+ json_object * pActionInfo = NULL;
+ err = wrap_json_pack(&pActionInfo, "{s:s,s:s,s?s,s?o}", "action_name", &actionName,"audio_role",&audioRole,"media_name",&mediaName,"action_context",&actionContext);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Could not create action JSON object arguments");
+ return;
+ }
+ json_object_get(pActionInfo);
+ int policyAllowed = Policy_PostAction(pActionInfo);
+ if (!policyAllowed)
+ {
+ afb_req_fail(req, "Audio policy violation", "Post sound action not allowed in current context");
+ return;
+ }
+#endif
+
+ afb_req_success(req, NULL, "Posted sound action");
+ }
+
+PUBLIC void audiohlapi_event_subscription(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ json_object * eventArrayJ = NULL;
+ int iSubscribe = 1;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:o,s:i}", "events", &eventArrayJ,"subscribe",&iSubscribe);
+ 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(pEventName == NULL) {
+ afb_req_fail(req, "failed", "Invalid event");
+ return;
+ }
+ else if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) {
+ if (iSubscribe)
+ afb_req_subscribe(req, g_AHLCtx.policyCtx.propertyEvent);
+ else
+ afb_req_unsubscribe(req, g_AHLCtx.policyCtx.propertyEvent);
+ }
+ else if(!strcasecmp(pEventName, AHL_ENDPOINT_VOLUME_EVENT)) {
+ if (iSubscribe)
+ afb_req_subscribe(req, g_AHLCtx.policyCtx.volumeEvent);
+ else
+ afb_req_unsubscribe(req, g_AHLCtx.policyCtx.volumeEvent);
+ }
+ else if(!strcasecmp(pEventName, AHL_POST_ACTION_EVENT)) {
+ if (iSubscribe)
+ afb_req_subscribe(req, g_AHLCtx.policyCtx.postActionEvent);
+ else
+ afb_req_unsubscribe(req, g_AHLCtx.policyCtx.postActionEvent);
+ }
+ else {
+ afb_req_fail(req, "failed", "Invalid event");
+ return;
+ }
+ }
+
+ afb_req_success(req, NULL, "Event subscription update finished");
+}
+
+// Since the policy is currently in the same binding, it cannot raise events on its own
+// This is a first step toward isolation, when policy is migrated in its own binding it can simply raise AGL events
+// This binding will register for these policy events and will execute the code below upon event reception
+PUBLIC void audiohlapi_raise_event(json_object * pEventDataJ)
+{
+ char * pEventName = NULL;
+
+ int err = wrap_json_unpack(pEventDataJ,"{s:s}","event_name", &pEventName);
+ if(err)
+ {
+ AFB_ERROR("Unable to retrieve event name");
+ return;
+ }
+
+ if(strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)==0) {
+ char * pAudioRole = NULL;
+ char * pPropertyName = NULL;
+ endpointID_t endpointID = AHL_UNDEFINED;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ json_object * propValueJ = NULL;
+ int err = wrap_json_unpack(pEventDataJ,"{s:i,s:i,s:s,s:o,s:s}",
+ "endpoint_id", &endpointID,
+ "endpoint_type", &endpointType,
+ "property_name", &pPropertyName,
+ "value",&propValueJ,
+ "audio_role", &pAudioRole);
+ if(err)
+ {
+ AFB_ERROR("Unable to unpack property event");
+ return;
+ }
+ RoleInfoT * pRole = GetRole(pAudioRole);
+ if ( pRole == NULL ){
+ AFB_ERROR("Requested audio role does not exist in current configuration -> %s", pAudioRole);
+ return;
+ }
+ EndpointInfoT * pEndpointInfo = GetEndpointInfoWithRole(endpointID,endpointType,pRole);
+ // update property value
+ if ((pEndpointInfo!=NULL) && (pEndpointInfo->pPropTable!=NULL))
+ {
+ json_type jType = json_object_get_type(propValueJ);
+ switch (jType) {
+ case json_type_double:
+ g_hash_table_insert(pEndpointInfo->pPropTable, pPropertyName, json_object_new_double(json_object_get_double(propValueJ)));
+ break;
+ case json_type_int:
+ g_hash_table_insert(pEndpointInfo->pPropTable, pPropertyName, json_object_new_int(json_object_get_int(propValueJ)));
+ break;
+ case json_type_string:
+ g_hash_table_insert(pEndpointInfo->pPropTable, pPropertyName, json_object_new_string(json_object_get_string(propValueJ)));
+ break;
+ default:
+ AFB_ERROR("Invalid property argument Property value not a valid json object query=%s", json_object_get_string(propValueJ));
+ return ;
+ }
+ }
+ // Remove event name from object
+ json_object_object_del(pEventDataJ,"event_name");
+ afb_event_push(g_AHLCtx.policyCtx.propertyEvent,pEventDataJ);
+ }
+ else if(strcasecmp(pEventName, AHL_ENDPOINT_VOLUME_EVENT)==0) {
+ char * pAudioRole = NULL;
+ endpointID_t endpointID = AHL_UNDEFINED;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ int iVolume = 0;
+ int err = wrap_json_unpack(pEventDataJ,"{s:i,s:i,s:i,s:s}",
+ "endpoint_id", &endpointID,
+ "endpoint_type", &endpointType,
+ "value",&iVolume,
+ "audio_role", &pAudioRole);
+ if(err)
+ {
+ AFB_ERROR("Unable to unpack volume event data");
+ return;
+ }
+ RoleInfoT * pRole = GetRole(pAudioRole);
+ if ( pRole == NULL ){
+ AFB_ERROR("Requested audio role does not exist in current configuration -> %s", pAudioRole);
+ return;
+ }
+ EndpointInfoT * pEndpointInfo = GetEndpointInfoWithRole(endpointID,endpointType,pRole);
+ // update volume value
+ if(pEndpointInfo)
+ {
+ pEndpointInfo->iVolume = iVolume;
+ }
+ else
+ {
+ AFB_ERROR("Unable to find endpoint");
+ }
+ // Remove event name from object
+ json_object_object_del(pEventDataJ,"event_name");
+ afb_event_push(g_AHLCtx.policyCtx.volumeEvent,pEventDataJ);
+ }
+ else if(strcasecmp(pEventName, AHL_POST_ACTION_EVENT)==0) {
+ // Remove event name from object
+ json_object_object_del(pEventDataJ,"event_name");
+ // BUG: This crashes...
+ afb_event_push(g_AHLCtx.policyCtx.postActionEvent,pEventDataJ);
+ }
+ else if(strcasecmp(pEventName, AHL_STREAM_STATE_EVENT)==0) {
+ streamID_t streamID = AHL_UNDEFINED;
+ StreamEventT streamEvent = STREAM_EVENT_MAXVALUE;
+ int err = wrap_json_unpack(pEventDataJ,"{s:i,s:i}",
+ "stream_id", &streamID,
+ "state_event", &streamEvent);
+ if(err)
+ {
+ AFB_ERROR("Unable to unpack stream event data");
+ return;
+ }
+
+ StreamInfoT * pStreamInfo = GetStream(streamID);
+ if (pStreamInfo == NULL) {
+ AFB_ERROR("Specified stream not currently active stream_id -> %d",streamID);
+ return;
+ }
+
+ // update streamstate value
+ switch (streamEvent) {
+ case STREAM_EVENT_START:
+ pStreamInfo->streamState = STREAM_STATE_RUNNING;
+ break;
+ case STREAM_EVENT_STOP:
+ pStreamInfo->streamState = STREAM_STATE_IDLE;
+ break;
+ case STREAM_EVENT_PAUSE:
+ pStreamInfo->streamState = STREAM_STATE_PAUSED;
+ break;
+ case STREAM_EVENT_RESUME:
+ pStreamInfo->streamState = STREAM_STATE_RUNNING;
+ break;
+ case STREAM_EVENT_MUTED:
+ pStreamInfo->streamMute = STREAM_MUTED;
+ break;
+ case STREAM_EVENT_UNMUTED:
+ pStreamInfo->streamMute = STREAM_UNMUTED;
+ break;
+ default:
+ AFB_ERROR("Unknown stream event");
+ }
+
+ // Remove event name from object
+ json_object_object_del(pEventDataJ,"event_name");
+ afb_event_push(pStreamInfo->streamStateEvent,pEventDataJ);
+ }
+ else {
+ AFB_ERROR("Unknown event name");
+ }
+} \ No newline at end of file
diff --git a/ahl-binding/ahl-binding.h b/ahl-binding/ahl-binding.h
new file mode 100644
index 0000000..405144d
--- /dev/null
+++ b/ahl-binding/ahl-binding.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AHL_BINDING_INCLUDE
+#define AHL_BINDING_INCLUDE
+
+//#define AHL_DISCONNECT_POLICY // define for debugging HLB in standalone only
+
+#include <json-c/json.h>
+#include <glib.h>
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+#include "ahl-interface.h"
+#include "ahl-policy-utils.h"
+
+#ifndef PUBLIC
+ #define PUBLIC
+#endif
+
+#define AHL_SUCCESS 0
+#define AHL_FAIL 1
+
+#define AHL_ACCESS_CONTROL_GRANTED 1
+#define AHL_ACCESS_CONTROL_DENIED 0
+
+#define AHL_STR_MAX_LENGTH 256
+
+typedef struct EndpointInfo
+{
+ endpointID_t endpointID; // Unique endpoint ID (per type)
+ EndpointTypeT type; // Source or sink device
+ char * gsDeviceName; // Unique device card name
+ char * gsDisplayName; // Application display name
+ char * gsDeviceURI; // Associated URI
+ char * gsDeviceDomain; // Device URI domain (e.g. alsa or pulse)
+ char * pRoleName; // Role assigned to this endpoint
+ DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information)
+ char * gsHALAPIName; // HAL associated with the device (for volume control)
+ AlsaDeviceInfoT alsaInfo; // ALSA specific device information
+ AudioFormatT format; // Preferred audio format supported (later could be array of supported formats)
+ int iVolume; // Storage for current endpoint volume (policy effected).
+ GHashTable * pPropTable; // Storage for array of properties (policy effected)
+} EndpointInfoT;
+
+typedef struct StreamInfo {
+ streamID_t streamID; // Stream unique ID
+ EndpointInfoT * pEndpointInfo; // Associated endpoint information (reference)
+ StreamStateT streamState; // Stream activity state
+ StreamMuteT streamMute; // Stream mute state
+ struct afb_event streamStateEvent; // Stream specific event for stream state changes
+ EndpointSelectionModeT endpointSelMode; // Automatic (priority based) or manual endpoint selection
+ char * pRoleName; // Role string identifier (from role config but could be programatically overriden later)
+ int iPriority; // Role normalized priority (0-100) (from role config but could be programatically overriden later)
+ InterruptBehaviorT eInterruptBehavior; // Role behavior when interrupting lower priority streams (from role config but could be programatically overriden later)
+} StreamInfoT;
+
+typedef struct RoleInfo {
+ char * pRoleName; // Role string identifier
+ int iPriority; // Role normalized priority (0-100)
+ InterruptBehaviorT eInterruptBehavior; // Role behavior when interrupting lower priority streams
+ GPtrArray * pActionList; // List of supported actions for the role (gchar*)
+ GPtrArray * pSourceEndpoints; // Source endpoints info (EndpointInfoT*)
+ GPtrArray * pSinkEndpoints; // Sink endpoints info (EndpointInfoT*)
+} RoleInfoT;
+
+// Parts of the context that are visible to the policy (for state based decisions)
+typedef struct AHLPolicyCtx {
+ GHashTable * pRoleInfo; // Hash table of role information structure (RoleInfoT*) accessed by role name
+ GHashTable * pStreams; // List of active streams (StreamInfoT*) accessed by streamID
+ GPtrArray * pHALList; // List of HAL dependencies
+ // TODO: Events need to be sent directly by HLB when separation with policy complete
+ struct afb_event propertyEvent; // AGL event used when property changes
+ struct afb_event volumeEvent; // AGL event used when volume changes
+ struct afb_event postActionEvent; // AGL event used on post action call
+} AHLPolicyCtxT;
+
+// Global binding context
+typedef struct AHLCtx {
+ AHLPolicyCtxT policyCtx;
+ endpointID_t nextSourceEndpointID; // Counter to assign new ID
+ endpointID_t nextSinkEndpointID; // Counter to assign new ID
+ endpointID_t nextStreamID; // Counter to assign new ID
+} AHLCtxT;
+
+// Client specific binding context
+typedef struct AHLClientCtx {
+ GArray * pStreamAccessList; // List of streams that client has control over
+} AHLClientCtxT;
+
+// ahl-binding.c
+PUBLIC int AhlBindingInit();
+PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ);
+
+// ahl-deviceenum.c
+int EnumerateDevices(json_object * in_jDeviceArray, char * in_pAudioRole, EndpointTypeT in_deviceType, GPtrArray * out_pEndpointArray);
+EndpointInfoT * InitEndpointInfo();
+void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo );
+// ahl-config.c
+int ParseHLBConfig();
+// ahl-policy.c
+#ifndef AHL_DISCONNECT_POLICY
+PUBLIC void audiohlapi_raise_event(json_object *EventDataJ);
+#endif
+
+#endif // AHL_BINDING_INCLUDE
diff --git a/ahl-binding/ahl-config.c b/ahl-binding/ahl-config.c
new file mode 100644
index 0000000..c60cb9c
--- /dev/null
+++ b/ahl-binding/ahl-config.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <json-c/json.h>
+#include "wrap-json.h"
+#include "filescan-utils.h"
+
+#include "ahl-binding.h"
+
+extern AHLCtxT g_AHLCtx;
+
+static InterruptBehaviorT InterruptBehaviorToEnum(char * in_pInterruptBehaviorStr)
+{
+ g_assert_nonnull(in_pInterruptBehaviorStr);
+ if (strcasecmp(in_pInterruptBehaviorStr,AHL_INTERRUPTBEHAVIOR_CONTINUE)==0) {
+ return INTERRUPTBEHAVIOR_CONTINUE;
+ }
+ else if (strcasecmp(in_pInterruptBehaviorStr,AHL_INTERRUPTBEHAVIOR_CANCEL)==0) {
+ return INTERRUPTBEHAVIOR_CANCEL;
+ }
+ else if (strcasecmp(in_pInterruptBehaviorStr,AHL_INTERRUPTBEHAVIOR_PAUSE)==0) {
+ return INTERRUPTBEHAVIOR_PAUSE;
+ }
+ else
+ return INTERRUPTBEHAVIOR_MAXVALUE;
+}
+
+static json_object* CtlConfigScan(const char *dirList, const char *prefix) {
+ char controlFile [CONTROL_MAXPATH_LEN];
+ strncpy(controlFile, prefix, CONTROL_MAXPATH_LEN);
+ strncat(controlFile, GetBinderName(), CONTROL_MAXPATH_LEN);
+
+ // search for default dispatch config file
+ json_object* responseJ = ScanForConfig(dirList, CTL_SCAN_RECURSIVE, controlFile, ".json");
+
+ return responseJ;
+}
+
+static char* CtlConfigSearch(const char *dirList, const char *prefix) {
+ int index, err;
+
+ // search for default dispatch config file
+ json_object* responseJ = CtlConfigScan (dirList, prefix);
+ if (!responseJ) return NULL;
+
+ // We load 1st file others are just warnings
+ for (index = 0; index < json_object_array_length(responseJ); index++) {
+ json_object *entryJ = json_object_array_get_idx(responseJ, index);
+
+ char *filename;
+ char*fullpath;
+ err = wrap_json_unpack(entryJ, "{s:s, s:s !}", "fullpath", &fullpath, "filename", &filename);
+ if (err) {
+ AFB_ERROR("CTL-INIT HOOPs invalid JSON entry= %s", json_object_get_string(entryJ));
+ return NULL;
+ }
+
+ if (index == 0) {
+ char filepath[CONTROL_MAXPATH_LEN];
+ strncpy(filepath, fullpath, sizeof (filepath));
+ strncat(filepath, "/", sizeof (filepath));
+ strncat(filepath, filename, sizeof (filepath));
+ return (strdup(filepath));
+ }
+ }
+ // no config found
+ return NULL;
+}
+
+int ParseHLBConfig() {
+ char * versionStr = NULL;
+ json_object * jAudioRoles = NUL L;
+ json_object * jHALList = NULL;
+ char * policyModule = NULL;
+
+ const char *dirList=getenv("AAAA_CONFIG_PATH");
+ if (!dirList) dirList=CONTROL_CONFIG_PATH;
+
+ const char *configfile_path =CtlConfigSearch(dirList, "ahl-");
+ if (!configfile_path) {
+ AFB_ERROR("Error: No control-* config found invalid JSON %s ", dirList);
+ return 1;
+ }
+
+ AFB_INFO("High-level config file -> %s\n", configfile_path);
+
+ // Open configuration file
+ json_object *config_JFile=json_object_from_file(configfile_path);
+ if(config_JFile == NULL)
+ {
+ AFB_ERROR("Error: Can't open configuration file -> %s",configfile_path);
+ return AHL_FAIL;
+ }
+
+ int err = wrap_json_unpack(config_JFile, "{s:s,s:s,s:o,s:o}", "version", &versionStr,"policy_module", &policyModule,"audio_roles",&jAudioRoles,"hal_list",&jHALList);
+ if (err) {
+ AFB_ERROR("Invalid configuration file -> %s", configfile_path);
+ return AHL_FAIL;
+ }
+ AFB_INFO("High-level audio API version: %s", "1.0.0");
+ AFB_INFO("Config version: %s", versionStr);
+ AFB_INFO("Policy module: %s", policyModule);
+
+ int iHALListLength = json_object_array_length(jHALList);
+ g_AHLCtx.policyCtx.pHALList = g_ptr_array_new_with_free_func(g_free);
+ int iNumberOfRoles = json_object_array_length(jAudioRoles);
+ g_AHLCtx.policyCtx.pRoleInfo = g_hash_table_new(g_str_hash, g_str_equal);
+
+ for (int i = 0; i < iHALListLength; i++)
+ {
+ char * pHAL = NULL;
+ json_object * jHAL = json_object_array_get_idx(jHALList,i);
+ if (jHAL) {
+ pHAL = (char *)json_object_get_string(jHAL);
+ char * pHALName = g_strdup( pHAL );
+ g_ptr_array_add( g_AHLCtx.policyCtx.pHALList, pHALName );
+
+ // Set dependency on HAL specified
+ err = afb_daemon_require_api_v2(pHAL,1) ;
+ if( err != 0 )
+ {
+ AFB_ERROR("Audio high level API could not set dependency on API: %s",pHAL);
+ return AHL_FAIL;
+ }
+ }
+ }
+
+ 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 * jActions = NULL;
+ char * pRoleName = NULL;
+ char * pInteruptBehavior = NULL;
+
+ int iNumOutDevices = 0;
+ int iNumInDevices = 0;
+ int iNumActions = 0;
+
+ err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s:s,s?o,s?o,s?o}",
+ "name", &pRoleName,
+ "priority",&priority,
+ "interupt_behavior",&pInteruptBehavior,
+ "output",&jOutputDevices,
+ "input",&jInputDevices,
+ "actions",&jActions
+ );
+ if (err) {
+ AFB_ERROR("Invalid audio role configuration : %s", json_object_to_json_string(jAudioRole));
+ return AHL_FAIL;
+ }
+
+ if (jOutputDevices)
+ iNumOutDevices = json_object_array_length(jOutputDevices);
+ if (jInputDevices)
+ iNumInDevices = json_object_array_length(jInputDevices);
+ if (jActions)
+ iNumActions = json_object_array_length(jActions);
+
+ RoleInfoT * pRoleInfo = (RoleInfoT*) malloc(sizeof(RoleInfoT));
+ memset(pRoleInfo,0,sizeof(RoleInfoT));
+ pRoleInfo->pRoleName = g_strdup( pRoleName );
+ pRoleInfo->iPriority = priority;
+ pRoleInfo->eInterruptBehavior = InterruptBehaviorToEnum(pInteruptBehavior);
+
+ // Actions
+ pRoleInfo->pActionList = g_ptr_array_new_with_free_func(g_free);
+ // Parse and validate list of available actions
+ for (int i = 0; i < iNumActions; i++)
+ {
+ json_object * jAction = json_object_array_get_idx(jActions,i);
+ char * pActionName = (char *)json_object_get_string(jAction);
+ if (pActionName)
+ g_ptr_array_add(pRoleInfo->pActionList, g_strdup(pActionName));
+ }
+
+ // Sources
+ pRoleInfo->pSourceEndpoints = g_ptr_array_new_with_free_func(g_free);
+ if (iNumInDevices) {
+ err = EnumerateDevices(jInputDevices,pRoleName,ENDPOINTTYPE_SOURCE,pRoleInfo->pSourceEndpoints);
+ if (err) {
+ AFB_ERROR("Invalid input devices : %s", json_object_to_json_string(jInputDevices));
+ return AHL_FAIL;
+ }
+ }
+ // Sinks
+ pRoleInfo->pSinkEndpoints = g_ptr_array_new_with_free_func(g_free);
+ if (iNumOutDevices) {
+ err = EnumerateDevices(jOutputDevices,pRoleName,ENDPOINTTYPE_SINK,pRoleInfo->pSinkEndpoints);
+ if (err) {
+ AFB_ERROR("Invalid output devices : %s", json_object_to_json_string(jOutputDevices));
+ return AHL_FAIL;
+ }
+ }
+
+ g_hash_table_insert(g_AHLCtx.policyCtx.pRoleInfo, pRoleInfo->pRoleName, pRoleInfo);
+ }
+
+ // Build lists of all device URI referenced in config file (input/output)
+ AFB_DEBUG ("Audio high-level - Parse high-level audio configuration done");
+ return AHL_SUCCESS;
+}
diff --git a/ahl-binding/ahl-deviceenum.c b/ahl-binding/ahl-deviceenum.c
new file mode 100644
index 0000000..763b2ca
--- /dev/null
+++ b/ahl-binding/ahl-deviceenum.c
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm.h>
+#include "ahl-binding.h"
+
+extern AHLCtxT g_AHLCtx;
+
+// TODO: Hash from endpoint ID information instead
+static endpointID_t CreateNewSourceID()
+{
+ endpointID_t newID = g_AHLCtx.nextSourceEndpointID;
+ g_AHLCtx.nextSourceEndpointID++;
+ return newID;
+}
+
+// TODO: Hash from endpoint ID information instead
+static endpointID_t CreateNewSinkID()
+{
+ endpointID_t newID = g_AHLCtx.nextSinkEndpointID;
+ g_AHLCtx.nextSinkEndpointID++;
+ return newID;
+}
+
+// Watchout: This function uses strtok and is destructive on the input string (use a copy)
+static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomain, char ** out_pDevice)
+{
+ *out_pDomain = strtok(in_pDeviceURI, ".");
+ if (*out_pDomain == NULL)
+ {
+ AFB_ERROR("Error tokenizing device URI -> %s",in_pDeviceURI);
+ return 1;
+ }
+ // TODO: Validate domain is known string (e.g. ALSA,Pulse,GStreamer)
+ *out_pDevice = strtok(NULL, ".");
+ if (*out_pDevice == NULL)
+ {
+ AFB_ERROR("Error tokenizing device URI -> %s",in_pDeviceURI);
+ return 1;
+ }
+ return AHL_SUCCESS;
+}
+
+static int IsAlsaDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_ALSA) == 0);
+}
+
+static int IsPulseDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_PULSE) == 0);
+}
+
+static int IsGStreamerDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_GSTREAMER) == 0);
+}
+
+static int IsExternalDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_EXTERNAL) == 0);
+}
+
+static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndpointInfo )
+{
+ g_assert_nonnull(in_pPcmHandle);
+ g_assert_nonnull(out_pEndpointInfo);
+ snd_pcm_type_t pcmType = 0;
+ snd_pcm_info_t * pPcmInfo = NULL;
+ int iAlsaRet = 0;
+ const char * pCardName = NULL;
+ snd_ctl_t * ctlHandle = NULL;
+ snd_ctl_card_info_t * ctlInfo = NULL;
+
+ snd_pcm_info_alloca(&pPcmInfo);
+ snd_ctl_card_info_alloca(&ctlInfo);
+
+ // retrieve PCM type
+ pcmType = snd_pcm_type(in_pPcmHandle);
+ switch (pcmType) {
+ case SND_PCM_TYPE_HW:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_HW;
+ break;
+ case SND_PCM_TYPE_DMIX:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_DMIX;
+ break;
+ case SND_PCM_TYPE_SOFTVOL:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_SOFTVOL;
+ break;
+ case SND_PCM_TYPE_PLUG:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_PLUG;
+ break;
+ default:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_OTHER;
+ break;
+ }
+
+ iAlsaRet = snd_pcm_info(in_pPcmHandle,pPcmInfo);
+ if (iAlsaRet < 0)
+ {
+ AFB_WARNING("Error retrieving PCM device info");
+ return AHL_FAIL;
+ }
+
+ // get card number
+ out_pEndpointInfo->alsaInfo.cardNum = snd_pcm_info_get_card(pPcmInfo);
+ if ( out_pEndpointInfo->alsaInfo.cardNum < 0 )
+ {
+ AFB_WARNING("No Alsa card number available");
+ return AHL_FAIL;
+ }
+
+ // get device number
+ out_pEndpointInfo->alsaInfo.deviceNum = snd_pcm_info_get_device(pPcmInfo);
+ if ( out_pEndpointInfo->alsaInfo.deviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa device number available");
+ return AHL_FAIL;
+ }
+
+ // get sub-device number
+ out_pEndpointInfo->alsaInfo.subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo);
+ if ( out_pEndpointInfo->alsaInfo.subDeviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa subdevice number available");
+ return AHL_FAIL;
+ }
+
+ char cardName[32];
+ sprintf(cardName, "hw:%d", out_pEndpointInfo->alsaInfo.cardNum);
+ iAlsaRet = snd_ctl_open(&ctlHandle, cardName, 0);
+ if ( iAlsaRet < 0 )
+ {
+ AFB_WARNING("Could not open ALSA card control");
+ return AHL_FAIL;
+ }
+
+ iAlsaRet = snd_ctl_card_info(ctlHandle, ctlInfo);
+ if ( iAlsaRet < 0 )
+ {
+ AFB_WARNING("Could not retrieve ALSA card info");
+ snd_ctl_close(ctlHandle);
+ return AHL_FAIL;
+ }
+
+ // Populate unique target card name
+ pCardName = snd_ctl_card_info_get_id(ctlInfo);
+ if (pCardName == NULL)
+ {
+ AFB_WARNING("No Alsa card name available");
+ snd_ctl_close(ctlHandle);
+ return AHL_FAIL;
+ }
+ g_strlcpy(out_pEndpointInfo->gsDeviceName,pCardName,AHL_STR_MAX_LENGTH);
+
+ snd_ctl_close(ctlHandle);
+
+ return AHL_SUCCESS;
+}
+
+EndpointInfoT * InitEndpointInfo()
+{
+ EndpointInfoT * pEndpointInfo = (EndpointInfoT*) malloc(sizeof(EndpointInfoT));
+ memset(pEndpointInfo,0,sizeof(EndpointInfoT));
+ pEndpointInfo->endpointID = AHL_UNDEFINED;
+ pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE;
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE;
+ pEndpointInfo->alsaInfo.cardNum = AHL_UNDEFINED;
+ pEndpointInfo->alsaInfo.deviceNum = AHL_UNDEFINED;
+ pEndpointInfo->alsaInfo.subDeviceNum = AHL_UNDEFINED;
+ pEndpointInfo->format.sampleRate = AHL_UNDEFINED;
+ pEndpointInfo->format.numChannels = AHL_UNDEFINED;
+ pEndpointInfo->format.sampleType = AHL_FORMAT_UNKNOWN;
+ // Assigned by device enumeration
+ pEndpointInfo->gsDeviceName = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->gsDeviceName,0,AHL_STR_MAX_LENGTH*sizeof(char));
+ pEndpointInfo->gsDeviceDomain = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->gsDeviceDomain,0,AHL_STR_MAX_LENGTH*sizeof(char));
+ pEndpointInfo->pRoleName = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->pRoleName,0,AHL_STR_MAX_LENGTH*sizeof(char));
+ pEndpointInfo->gsDeviceURI = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->gsDeviceURI,0,AHL_STR_MAX_LENGTH*sizeof(char));
+ // Assigned by policy initialization
+ pEndpointInfo->gsDisplayName = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->gsDisplayName,0,AHL_STR_MAX_LENGTH*sizeof(char));
+ pEndpointInfo->gsHALAPIName = malloc(AHL_STR_MAX_LENGTH*sizeof(char));
+ memset(pEndpointInfo->gsHALAPIName,0,AHL_STR_MAX_LENGTH*sizeof(char));
+ pEndpointInfo->pPropTable = g_hash_table_new(g_str_hash, g_str_equal);
+ return pEndpointInfo;
+}
+
+void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo )
+{
+ #define SAFE_FREE(__ptr__) if(__ptr__) g_free(__ptr__); __ptr__ = NULL;
+ SAFE_FREE(out_pEndpointInfo->gsDeviceName);
+ SAFE_FREE(out_pEndpointInfo->gsDeviceDomain);
+ SAFE_FREE(out_pEndpointInfo->pRoleName);
+ SAFE_FREE(out_pEndpointInfo->gsDeviceURI);
+ SAFE_FREE(out_pEndpointInfo->gsHALAPIName);
+ SAFE_FREE(out_pEndpointInfo->gsDisplayName);
+
+ if (out_pEndpointInfo->pPropTable) {
+ // Free json_object for all property values
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init (&iter, out_pEndpointInfo->pPropTable);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (value)
+ json_object_put(value);
+ }
+ g_hash_table_remove_all(out_pEndpointInfo->pPropTable);
+ g_hash_table_destroy(out_pEndpointInfo->pPropTable);
+ out_pEndpointInfo->pPropTable = NULL;
+ }
+ // GLib automatically frees item when removed from the array
+}
+
+// For a given audio role
+int EnumerateDevices(json_object * in_jDeviceArray, char * in_pAudioRole, EndpointTypeT in_deviceType, GPtrArray * out_pEndpointArray) {
+
+ g_assert_nonnull(in_jDeviceArray);
+ int iNumberDevices = json_object_array_length(in_jDeviceArray);
+
+ // Parse and validate list of available devices
+ for (int i = 0; i < iNumberDevices; i++)
+ {
+ char * pDeviceURIDomain = NULL;
+ char * pFullDeviceURI = NULL;
+ char * pDeviceURIPCM = NULL;
+ int err = AHL_SUCCESS;
+
+ json_object * jDevice = json_object_array_get_idx(in_jDeviceArray,i);
+ if (jDevice == NULL) {
+ AFB_WARNING("Invalid device array -> %s",json_object_to_json_string(in_jDeviceArray));
+ continue;
+ }
+ // strip domain name from URI
+ pFullDeviceURI = (char *)json_object_get_string(jDevice);
+ char * pFullDeviceURICopy = g_strdup(pFullDeviceURI); // strtok is destructive
+ err = SeparateDomainFromDeviceURI(pFullDeviceURICopy,&pDeviceURIDomain,&pDeviceURIPCM);
+ if (err)
+ {
+ AFB_WARNING("Invalid device URI string -> %s",pFullDeviceURICopy);
+ continue;
+ }
+
+ EndpointInfoT * pEndpointInfo = InitEndpointInfo();
+ g_assert_nonnull(pEndpointInfo);
+
+ // non ALSA URI are simply passed to application (no validation) at this time
+ // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection
+ g_strlcpy(pEndpointInfo->gsDeviceName,pDeviceURIPCM,AHL_STR_MAX_LENGTH);
+ g_strlcpy(pEndpointInfo->gsDeviceDomain,pDeviceURIDomain,AHL_STR_MAX_LENGTH);
+ g_strlcpy(pEndpointInfo->gsDeviceURI,pDeviceURIPCM,AHL_STR_MAX_LENGTH);
+ g_strlcpy(pEndpointInfo->pRoleName ,in_pAudioRole,AHL_STR_MAX_LENGTH);
+
+ g_free(pFullDeviceURICopy);
+ pFullDeviceURICopy = NULL;
+ pDeviceURIDomain = NULL; //Derived from above mem
+ pDeviceURIPCM = NULL; //Derived from above mem
+
+ if (IsAlsaDomain(pEndpointInfo->gsDeviceDomain))
+ {
+ // TODO: Missing support for loose name matching
+ // This will require using ALSA hints to get PCM names
+ // And would iterate over all available devices matching string (possibly all if no filtering is desired for a certain role)
+
+ // Get PCM handle
+ snd_pcm_t * pPcmHandle = NULL;
+ snd_pcm_stream_t streamType = in_deviceType == ENDPOINTTYPE_SOURCE ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
+ err = snd_pcm_open(&pPcmHandle, pEndpointInfo->gsDeviceURI, streamType, 0);
+ if (err < 0)
+ {
+ AFB_NOTICE("Alsa PCM device was not found -> %s", pEndpointInfo->gsDeviceURI);
+ continue;
+ }
+
+ err = FillALSAPCMInfo(pPcmHandle,pEndpointInfo);
+ if (err) {
+ AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pEndpointInfo->gsDeviceURI);
+ snd_pcm_close(pPcmHandle);
+ continue;
+ }
+
+ snd_pcm_close(pPcmHandle);
+ }
+ else if (IsPulseDomain(pEndpointInfo->gsDeviceDomain)) {
+ // Pulse domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA;
+ }
+ else if (IsGStreamerDomain(pEndpointInfo->gsDeviceDomain)){
+ // GStreamer domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA;
+ }
+ else if (IsExternalDomain(pEndpointInfo->gsDeviceDomain)){
+ // External domain
+ pEndpointInfo->deviceURIType = DEVICEURITYPE_NOT_ALSA;
+ }
+ else {
+ // Unknown domain
+ AFB_WARNING("Unknown domain in device URI string -> %s",pFullDeviceURI);
+ continue;
+ }
+
+ pEndpointInfo->endpointID = in_deviceType == ENDPOINTTYPE_SOURCE ? CreateNewSourceID() : CreateNewSinkID();
+ pEndpointInfo->type = in_deviceType;
+
+ // add to structure to list of available devices
+ g_ptr_array_add(out_pEndpointArray, pEndpointInfo);
+
+ } // for all devices
+
+ AFB_DEBUG ("Audio high-level - Enumerate devices done");
+ return AHL_SUCCESS;
+} \ No newline at end of file
diff --git a/ahl-binding/ahl-json.c b/ahl-binding/ahl-json.c
new file mode 100644
index 0000000..5e8bf97
--- /dev/null
+++ b/ahl-binding/ahl-json.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+#include "wrap-json.h"
+#include <json-c/json.h>
+#include <glib.h>
+#include "ahl-binding.h"
+
+static char * DeviceURITypeEnumToStr(DeviceURITypeT in_eDeviceURIType) {
+ switch(in_eDeviceURIType) {
+ case DEVICEURITYPE_ALSA_HW: // Alsa hardware device URI
+ return AHL_DEVICEURITYPE_ALSA_HW;
+ case DEVICEURITYPE_ALSA_DMIX: // Alsa Dmix device URI (only for playback devices)
+ return AHL_DEVICEURITYPE_ALSA_DMIX;
+ case DEVICEURITYPE_ALSA_DSNOOP: // Alsa DSnoop device URI (only for capture devices)
+ return AHL_DEVICEURITYPE_ALSA_DSNOOP;
+ case DEVICEURITYPE_ALSA_SOFTVOL: // Alsa softvol device URI
+ return AHL_DEVICEURITYPE_ALSA_SOFTVOL;
+ case DEVICEURITYPE_ALSA_PLUG: // Alsa plug device URI
+ return AHL_DEVICEURITYPE_ALSA_PLUG;
+ case DEVICEURITYPE_ALSA_OTHER: // Alsa domain URI device of unspecified type
+ return AHL_DEVICEURITYPE_ALSA_OTHER;
+ case DEVICEURITYPE_NOT_ALSA: // Unknown (not ALSA domain)
+ return AHL_DEVICEURITYPE_NOT_ALSA;
+ default:
+ return "Unknown";
+ }
+}
+
+static char * StreamStateEnumToStr(StreamStateT in_eStreamState) {
+ switch(in_eStreamState) {
+ case STREAM_STATE_IDLE:
+ return AHL_STREAM_STATE_IDLE;
+ case STREAM_STATE_RUNNING:
+ return AHL_STREAM_STATE_RUNNING;
+ case STREAM_STATE_PAUSED:
+ return AHL_STREAM_STATE_PAUSED;
+ default:
+ return "Unknown";
+ }
+}
+
+static char * StreamMuteEnumToStr(StreamMuteT in_eStreamMute) {
+ switch(in_eStreamMute) {
+ case STREAM_UNMUTED:
+ return AHL_STREAM_UNMUTED;
+ case STREAM_MUTED:
+ return AHL_STREAM_MUTED;
+ default:
+ return "Unknown";
+ }
+}
+
+static int EndpointPropTableToJSON(GHashTable * pPropTable, json_object **ppProptableJ)
+{
+ if(pPropTable == NULL)
+ {
+ AFB_ERROR("Invalid EndpointPropTableToJSON arguments");
+ return AHL_FAIL;
+ }
+
+ // Create json object for PropTable
+ *ppProptableJ = json_object_new_array();
+ GHashTableIter iter;
+ gpointer key, value;
+ g_hash_table_iter_init (&iter, pPropTable);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if ( key != NULL && value != NULL) {
+ json_object *pPropertyJ = NULL;
+ json_object_get(value);
+ int err = wrap_json_pack(&pPropertyJ, "{s:s,s:o}",
+ "property_name", (char*)key,
+ "property_value", value
+ );
+ if(err)
+ {
+ AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+ json_object_array_add(*ppProptableJ, pPropertyJ);
+ }
+ }
+
+ return AHL_SUCCESS;
+}
+
+int EndpointInfoToJSON(EndpointInfoT * pEndpointInfo, json_object **ppEndpointInfoJ)
+{
+ if(pEndpointInfo == NULL || pEndpointInfo->pPropTable == NULL)
+ {
+ AFB_ERROR("Invalid EndpointInfoToJSON arguments");
+ return AHL_FAIL;
+ }
+
+ json_object * pPropTableJ = NULL;
+ int err = EndpointPropTableToJSON(pEndpointInfo->pPropTable,&pPropTableJ);
+ if (err) {
+ return AHL_FAIL;
+ }
+
+ // Create json object for EndpointInfo
+ err = wrap_json_pack(ppEndpointInfoJ, "{s:i,s:i,s:s,s:s,s:s,s:s,s:s,s:i,s:s,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:o}",
+ "endpoint_id", pEndpointInfo->endpointID,
+ "endpoint_type", pEndpointInfo->type,
+ "device_name", pEndpointInfo->gsDeviceName,
+ "display_name", pEndpointInfo->gsDisplayName,
+ "device_uri", pEndpointInfo->gsDeviceURI,
+ "device_domain", pEndpointInfo->gsDeviceDomain,
+ "audio_role",pEndpointInfo->pRoleName,
+ "device_uri_type", pEndpointInfo->deviceURIType,
+ "hal_api_name", pEndpointInfo->gsHALAPIName,
+ "alsa_cardNum", pEndpointInfo->alsaInfo.cardNum,
+ "alsa_deviceNum", pEndpointInfo->alsaInfo.deviceNum,
+ "alsa_subDeviceNum", pEndpointInfo->alsaInfo.subDeviceNum,
+ "format_samplerate", pEndpointInfo->format.sampleRate,
+ "format_numchannels", pEndpointInfo->format.numChannels,
+ "format_sampletype",pEndpointInfo->format.sampleType,
+ "volume", pEndpointInfo->iVolume,
+ "property_table", pPropTableJ
+ );
+ if (err) {
+ AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+
+ return AHL_SUCCESS;
+}
+
+int StreamInfoToJSON(StreamInfoT * pStreamInfo, json_object **ppStreamInfoJ)
+{
+ if(pStreamInfo == NULL)
+ {
+ AFB_ERROR("Invalid arguments to StreamInfoToJSON");
+ return AHL_FAIL;
+ }
+
+ json_object * pEndpointInfoJ = NULL;
+ int err = EndpointInfoToJSON(pStreamInfo->pEndpointInfo, &pEndpointInfoJ);
+ if (err) {
+ return AHL_FAIL;
+ }
+
+ // Create json object for stream
+ err = wrap_json_pack(ppStreamInfoJ, "{s:i,s:i,s:i,s:I,s:i,s:s,s:i,s:i,s:o}",
+ "stream_id", pStreamInfo->streamID,
+ "stream_state", pStreamInfo->streamState,
+ "stream_mute", pStreamInfo->streamMute,
+ "stream_state_event", &pStreamInfo->streamStateEvent,
+ "endpoint_sel_mod", pStreamInfo->endpointSelMode,
+ "role_name", pStreamInfo->pRoleName,
+ "priority", pStreamInfo->iPriority,
+ "interrupt_behavior", pStreamInfo->eInterruptBehavior,
+ "endpoint_info", pEndpointInfoJ
+ );
+ if (err) {
+ AFB_ERROR("Unable to pack JSON endpoint, =%s", wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+
+ return AHL_SUCCESS;
+}
+
+static int UpdatePropertyList(GHashTable * pPropTable, json_object * pPropTableJ) {
+ if (pPropTable == NULL || pPropTableJ == NULL) {
+ AFB_ERROR("Invalid arguments to UpdatePropertyList");
+ return AHL_FAIL;
+ }
+ // Unpack prop table
+ int nbProperties = json_object_array_length(pPropTableJ);
+ for(int i = 0; i < nbProperties; i++)
+ {
+ json_object * propJ = json_object_array_get_idx(pPropTableJ,i);
+ if (propJ) {
+ char * pPropertyName = NULL;
+ json_object * pPropertyValueJ = NULL;
+ int err = wrap_json_unpack(propJ, "{s:s,s:o}",
+ "property_name", &pPropertyName,
+ "property_value", &pPropertyValueJ);
+ if (err) {
+ AFB_ERROR("Unable to unpack JSON property, = %s", wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+
+ // Object type detection for property value (string = state, numeric = property)
+ json_type jType = json_object_get_type(pPropertyValueJ);
+ switch (jType) {
+ case json_type_double:
+ g_hash_table_insert(pPropTable, pPropertyName, json_object_new_double(json_object_get_double(pPropertyValueJ)));
+ break;
+ case json_type_int:
+ g_hash_table_insert(pPropTable, pPropertyName, json_object_new_int(json_object_get_int(pPropertyValueJ)));
+ break;
+ case json_type_string:
+ g_hash_table_insert(pPropTable, pPropertyName, json_object_new_string(json_object_get_string(pPropertyValueJ)));
+ break;
+ default:
+ AFB_ERROR("Invalid property argument Property value not a valid json object query=%s", json_object_get_string(pPropertyValueJ));
+ return AHL_FAIL;
+ }
+ }
+ }
+
+ return AHL_SUCCESS;
+}
+
+int UpdateEndpointInfo(EndpointInfoT * pEndpoint, json_object * pEndpointInfoJ) {
+
+ if(pEndpoint == NULL || pEndpointInfoJ == NULL)
+ {
+ AFB_ERROR("Invalid arguments to UpdateEndpointInfo");
+ return AHL_FAIL;
+ }
+
+ // Push information to endpoint info struct
+ json_object * pPropTableJ = NULL;
+ char * pDisplayName = NULL;
+ char * pHALName = NULL;
+ int err = wrap_json_unpack(pEndpointInfoJ,"{s:i,s:s,s:s,s:o}",
+ "init_volume",&pEndpoint->iVolume,
+ "display_name",&pDisplayName,
+ "hal_name", &pHALName,
+ "property_table",&pPropTableJ);
+ if (err) {
+ AFB_ERROR("Unable to create Endpoint Json object error:%s ",wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+ g_strlcpy(pEndpoint->gsDisplayName,pDisplayName,AHL_STR_MAX_LENGTH);
+ g_strlcpy(pEndpoint->gsHALAPIName,pHALName,AHL_STR_MAX_LENGTH);
+
+ if (pEndpoint->pPropTable && pPropTableJ) {
+ err = UpdatePropertyList(pEndpoint->pPropTable,pPropTableJ);
+ if (err) {
+ AFB_ERROR("Unable to update property table Json object error:%s ",wrap_json_get_error_string(err));
+ return AHL_FAIL;
+ }
+ }
+
+ return AHL_SUCCESS;
+}
+
+static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * pAudioFormat)
+{
+ wrap_json_pack(audioFormatJ, "{s:i,s:i,s:i}",
+ "sample_rate", pAudioFormat->sampleRate,
+ "num_channels", pAudioFormat->numChannels,
+ "sample_type", pAudioFormat->sampleType);
+}
+
+// Package only information that can useful to application clients when selecting endpoint
+void JSONPublicPackageEndpoint(EndpointInfoT * pEndpointInfo,json_object **endpointInfoJ)
+{
+ json_object *formatInfoJ = NULL;
+ wrap_json_pack(endpointInfoJ, "{s:i,s:s,s:s,s:s,s:s,s:s,s:s}",
+ "endpoint_id", pEndpointInfo->endpointID,
+ "endpoint_type", (pEndpointInfo->type == ENDPOINTTYPE_SOURCE) ? AHL_ENDPOINTTYPE_SOURCE : AHL_ENDPOINTTYPE_SINK,
+ "device_name", pEndpointInfo->gsDeviceName,
+ "display_name", pEndpointInfo->gsDisplayName,
+ "audio_role", pEndpointInfo->pRoleName,
+ "device_domain",pEndpointInfo->gsDeviceDomain,
+ "device_uri_type", DeviceURITypeEnumToStr(pEndpointInfo->deviceURIType));
+ AudioFormatStructToJSON(&formatInfoJ,&pEndpointInfo->format);
+ json_object_object_add(*endpointInfoJ,"format",formatInfoJ);
+
+ json_object *pPropTableJ = NULL;
+ EndpointPropTableToJSON(pEndpointInfo->pPropTable,&pPropTableJ);
+ json_object_object_add(*endpointInfoJ,"properties",pPropTableJ);
+}
+
+// Package only information that can useful to application clients when opening a stream
+void JSONPublicPackageStream(StreamInfoT * pStreamInfo,json_object **streamInfoJ)
+{
+ json_object *endpointInfoJ = NULL;
+ JSONPublicPackageEndpoint(pStreamInfo->pEndpointInfo,&endpointInfoJ);
+ wrap_json_pack(streamInfoJ, "{s:i,s:s,s:s,s:s}",
+ "stream_id", pStreamInfo->streamID,
+ "state", StreamStateEnumToStr(pStreamInfo->streamState),
+ "mute", StreamMuteEnumToStr(pStreamInfo->streamMute),
+ "device_uri",pStreamInfo->pEndpointInfo->gsDeviceURI); // Need to open a stream to have access to the device URI
+ json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ);
+} \ No newline at end of file
diff --git a/ahl-binding/ahl-json.h b/ahl-binding/ahl-json.h
new file mode 100644
index 0000000..ffd683a
--- /dev/null
+++ b/ahl-binding/ahl-json.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AHL_POLICY_JSON_INCLUDE
+#define AHL_POLICY_JSON_INCLUDE
+
+int EndpointInfoToJSON(EndpointInfoT * pEndpointInfo, json_object **ppEndpointInfoJ);
+int StreamInfoToJSON(StreamInfoT * pStreamInfo, json_object **ppStreamInfoJ);
+int UpdateEndpointInfo(EndpointInfoT * pEndpoint,json_object * pEndpointInfoJ);
+void JSONPublicPackageEndpoint(EndpointInfoT * pEndpointInfo,json_object **endpointInfoJ);
+void JSONPublicPackageStream(StreamInfoT * pStreamInfo,json_object **streamInfoJ);
+
+#endif // AHL_POLICY_JSON_INCLUDE