summaryrefslogtreecommitdiffstats
path: root/ahl-binding
diff options
context:
space:
mode:
Diffstat (limited to 'ahl-binding')
-rw-r--r--ahl-binding/CMakeLists.txt26
-rw-r--r--ahl-binding/ahl-apidef.h237
-rw-r--r--ahl-binding/ahl-apidef.json448
-rw-r--r--ahl-binding/ahl-binding.c1276
-rw-r--r--ahl-binding/ahl-binding.cpp389
-rw-r--r--ahl-binding/ahl-binding.h119
-rw-r--r--ahl-binding/ahl-binding.hpp78
-rw-r--r--ahl-binding/ahl-config.c251
-rw-r--r--ahl-binding/ahl-deviceenum.c336
-rw-r--r--ahl-binding/ahl-json.c296
-rw-r--r--ahl-binding/ahl-json.h26
-rw-r--r--ahl-binding/config_entry.cpp62
-rw-r--r--ahl-binding/config_entry.hpp49
-rw-r--r--ahl-binding/interrupt.cpp34
-rw-r--r--ahl-binding/interrupt.hpp28
-rw-r--r--ahl-binding/jsonc_utils.hpp91
-rw-r--r--ahl-binding/role.cpp283
-rw-r--r--ahl-binding/role.hpp79
18 files changed, 1109 insertions, 2999 deletions
diff --git a/ahl-binding/CMakeLists.txt b/ahl-binding/CMakeLists.txt
index 0dee304..bd2be0c 100644
--- a/ahl-binding/CMakeLists.txt
+++ b/ahl-binding/CMakeLists.txt
@@ -20,30 +20,36 @@
PROJECT_TARGET_ADD(audiohighlevel)
# Define project Targets
- ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c ahl-config.c ahl-json.c)
+ ADD_LIBRARY(${TARGET_NAME} MODULE
+ config_entry.cpp
+ role.cpp
+ interrupt.cpp
+ ahl-binding.cpp
+ )
# Binder exposes a unique public entry point
SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
- PREFIX "afb-"
- LABELS "BINDINGV2"
- LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ PREFIX "afb-"
+ LABELS "BINDING"
+ 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}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/../controller/ctl-lib
+ ${CMAKE_CURRENT_SOURCE_DIR}/../afb-utilities
)
# 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
+ #ahl-policy
+
+ afb-helpers
+ ctl-utilities
${GLIB_PKG_LIBRARIES}
${link_libraries}
)
diff --git a/ahl-binding/ahl-apidef.h b/ahl-binding/ahl-apidef.h
deleted file mode 100644
index a9cb6dd..0000000
--- a/ahl-binding/ahl-apidef.h
+++ /dev/null
@@ -1,237 +0,0 @@
-
-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,\"noconcur"
- "rency\":false}},\"servers\":[{\"url\":\"ws://{host}:{port}/api/audiohl\""
- ",\"description\":\"Audio high level API for AGL applications.\",\"variab"
- "les\":{\"host\":{\"default\":\"localhost\"},\"port\":{\"default\":\"1234"
- "\"}},\"x-afb-events\":[{\"$ref\":\"#/components/schemas/afb-event\"}]}],"
- "\"components\":{\"schemas\":{\"afb-reply\":{\"$ref\":\"#/components/sche"
- "mas/afb-reply-v2\"},\"afb-event\":{\"$ref\":\"#/components/schemas/afb-e"
- "vent-v2\"},\"afb-reply-v2\":{\"title\":\"Generic response.\",\"type\":\""
- "object\",\"required\":[\"jtype\",\"request\"],\"properties\":{\"jtype\":"
- "{\"type\":\"string\",\"const\":\"afb-reply\"},\"request\":{\"type\":\"ob"
- "ject\",\"required\":[\"status\"],\"properties\":{\"status\":{\"type\":\""
- "string\"},\"info\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"}"
- ",\"uuid\":{\"type\":\"string\"},\"reqid\":{\"type\":\"string\"}}},\"resp"
- "onse\":{\"type\":\"object\"}}},\"afb-event-v2\":{\"type\":\"object\",\"r"
- "equired\":[\"jtype\",\"event\"],\"properties\":{\"jtype\":{\"type\":\"st"
- "ring\",\"const\":\"afb-event\"},\"event\":{\"type\":\"string\"},\"data\""
- ":{\"type\":\"object\"}}},\"endpoint_info\":{\"type\":\"object\",\"requir"
- "ed\":[\"endpoint_id\",\"type\",\"device_name\",\"device_uri\"],\"propert"
- "ies\":{\"endpoint_id\":{\"type\":\"int\"},\"type\":{\"type\":\"enum\"},\""
- "device_name\":{\"type\":\"string\"},\"device_uri_type\":{\"type\":\"stri"
- "ng\"}}},\"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/en"
- "dpoint_info\"}}},\"x-permissions\":{\"streamcontrol\":{\"permission\":\""
- "urn:AGL:permission:audio:public:streamcontrol\"},\"endpointcontrol\":{\""
- "permission\":\"urn:AGL:permission:audio:public:endpointcontrol\"},\"audi"
- "ostream\":{\"permission\":\"urn:AGL:permission:audio:public:audiostream\""
- "},\"soundevent\":{\"permission\":\"urn:AGL:permission:audio:public:sound"
- "event\"}},\"responses\":{\"200\":{\"description\":\"A complex object arr"
- "ay 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\":tru"
- "e,\"schema\":{\"type\":\"enum\"}}],\"responses\":{\"200\":{\"$ref\":\"#/"
- "components/responses/200\",\"response\":{\"description\":\"Array of endp"
- "oint info structures\",\"type\":\"array\",\"items\":{\"$ref\":\"#/compon"
- "ents/schemas/endpoint_info\"}}},\"400\":{\"$ref\":\"#/components/respons"
- "es/400\"}}}},\"/stream_open\":{\"description\":\"Request opening a strea"
- "m\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/au"
- "diostream\"},\"parameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\""
- "required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"na"
- "me\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}}"
- ",{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":false,\"schema\""
- ":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/re"
- "sponses/200\",\"response\":{\"description\":\"Stream information structu"
- "re\",\"$ref\":\"#/components/schemas/stream_info\"}},\"400\":{\"$ref\":\""
- "#/components/responses/400\"}}}},\"/stream_close\":{\"description\":\"Re"
- "quest closing a stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#/compon"
- "ents/x-permissions/audiostream\"},\"parameters\":[{\"in\":\"query\",\"na"
- "me\":\"stream_id\",\"required\":false,\"schema\":{\"type\":\"int\"}}],\""
- "responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{"
- "\"$ref\":\"#/components/responses/400\"}}}},\"/set_stream_state\":{\"des"
- "cription\":\"Change stream active and/or mute state\",\"get\":{\"x-permi"
- "ssions\":{\"$ref\":\"#/components/x-permissions/streamcontrol\"},\"param"
- "eters\":[{\"in\":\"query\",\"name\":\"stream_id\",\"required\":false,\"s"
- "chema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state\",\"requi"
- "red\":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\",\"resp"
- "onse\":{\"description\":\"Stream information structure\",\"$ref\":\"#/co"
- "mponents/schemas/stream_info\"}},\"400\":{\"$ref\":\"#/components/respon"
- "ses/400\"}}}},\"/volume\":{\"description\":\"Set or get volume on endpoi"
- "nt\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/e"
- "ndpointcontrol\"},\"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 in"
- "cluding its properties\",\"get\":{\"parameters\":[{\"in\":\"query\",\"na"
- "me\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}}"
- ",{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"schema\""
- ":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/re"
- "sponses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/p"
- "roperty\":{\"description\":\"Set/get endpoint property value\",\"get\":{"
- "\"x-permissions\":{\"$ref\":\"#/components/x-permissions/endpointcontrol"
- "\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"requi"
- "red\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\""
- "endpoint_id\",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\""
- "query\",\"name\":\"property_name\",\"required\":true,\"schema\":{\"type\""
- ":\"string\"}},{\"in\":\"query\",\"name\":\"value\",\"required\":false,\""
- "schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/co"
- "mponents/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400"
- "\"}}}},\"/get_list_actions\":{\"description\":\"Retrieve a list of suppo"
- "rted actions for a particular audio role\",\"get\":{\"parameters\":[{\"i"
- "n\":\"query\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"ty"
- "pe\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/respo"
- "nses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/post"
- "_action\":{\"description\":\"Post sound or audio device related action e"
- "vent (extendable mechanism)\",\"get\":{\"x-permissions\":{\"$ref\":\"#/c"
- "omponents/x-permissions/soundevent\"},\"parameters\":[{\"in\":\"query\","
- "\"name\":\"action_name\",\"required\":true,\"schema\":{\"type\":\"string"
- "\"}},{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\"schem"
- "a\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"media_name\",\"r"
- "equired\":false,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"na"
- "me\":\"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\"}}}}}}"
-;
-
-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
deleted file mode 100644
index 35ffae6..0000000
--- a/ahl-binding/ahl-apidef.json
+++ /dev/null
@@ -1,448 +0,0 @@
-{
- "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,
- "noconcurrency": 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
deleted file mode 100644
index a8b7702..0000000
--- a/ahl-binding/ahl-binding.c
+++ /dev/null
@@ -1,1276 +0,0 @@
-/*
- * 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 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 state change event for streamID:%i with AudioRole: %s", streamID, pStreamInfo->pRoleName);
- 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) {
- g_ptr_array_unref(pRole->pSourceEndpoints);
- }
- // Sink endpoints
- if (pRole->pSinkEndpoints) {
- g_ptr_array_unref(pRole->pSinkEndpoints);
- }
- 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
- AFB_DEBUG("Audio high-level 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 source endpoint JSON object from endpoint info for endpoint_id:%d error:%s ",pCurEndpointInfo->endpointID, 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 source 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 source 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 sink endpoint JSON object from endpoint info for endpoint_id:%d error:%s ",pCurEndpointInfo->endpointID, 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 sink 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 sink 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 initialization 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:%s does not exist in current configuration -> %s", audioRole, 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, "Cannot convert stream info to JSON", "Unable to get JSON object for Policy_OpenStream");
- return;
- }
-
- int policyAllowed = Policy_OpenStream(pPolicyStreamJ);
- if (policyAllowed == AHL_POLICY_REJECT)
- {
- afb_req_fail_f(req, "Audio policy violation", "Open stream for role: %s on endpoint_id:%d not allowed in current context",pRole->pRoleName,pEndpointInfo->endpointID);
- 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_f(req, "Stream event creation failure", "Could not create stream specific state change event: %s",streamEventName);
- return;
- }
-
- err = afb_req_subscribe(req,pStreamInfo->streamStateEvent);
- if (err) {
- afb_req_fail_f(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event for event name: %s",streamEventName);
- 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, "Missing client info context", "No client context associated with the request (is there an opened stream by this client?)");
- return;
- }
-
- if (streamID == AHL_UNDEFINED) {
- AFB_DEBUG("StreamID not specified. Closing all stream for client");
- err = CloseAllClientStreams(pClientCtx,&req);
- if (err) {
- afb_req_fail(req, "Error closing streams", "Streams cannot close");
- return;
- }
- }
- else {
- AFB_DEBUG("Closing streamID:%d",streamID);
- 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, int iMuteValue) {
-
- 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_f(*pReq, "Access control denied", "Current client is not the owner of streamID:%d", streamID);
- 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_f(*pReq, "JSON packaging error", "Unable to create stream JSON object for Policy_SetStreamState for streamID:%d",streamID);
- 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_f(*pReq, "Audio policy violation", "Change stream state not allowed by policy in current context for streamID:%d",streamID);
- return AHL_FAIL;
- }
-#else
- // Simulate that policy returns target state (accepted)
- pStreamInfo->streamState = streamState;
-#endif
- }
-
-#ifndef AHL_DISCONNECT_POLICY
- json_object *pPolicyStreamJ = NULL;
- int err = StreamInfoToJSON(pStreamInfo, &pPolicyStreamJ);
- if (err == AHL_POLICY_UTIL_FAIL)
- {
- afb_req_fail_f((*pReq), "JSON packaging error", "Unable to create stream JSON object for Policy_SetStreamMute for streamID:%d",streamID);
- return AHL_FAIL;
- }
-
- json_object *paramJ= json_object_new_int(iMuteValue);
- json_object_object_add(pPolicyStreamJ, "mute_state", paramJ);
-
- int policyAllowed = Policy_SetStreamMute(pPolicyStreamJ);
- if (policyAllowed == AHL_POLICY_REJECT)
- {
- afb_req_fail_f(*pReq, "Audio policy violation", "Mute stream not allowed by policy in current context for streamID:%d",streamID);
- return AHL_FAIL;
- }
-#else
- // Simulate that policy returns target state (accepted)
- pStreamInfo->streamMute = (StreamMuteT)(*piMuteValue);
-#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;
- int iMuteValue = 0;
-
- queryJ = afb_req_json(req);
- int err = wrap_json_unpack(queryJ, "{s?i,s?s,s?i}", "stream_id", &streamID,"state",&streamStateStr,"mute",&iMuteValue);
- 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, "Missing client info context", "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,iMuteValue);
- if (err) {
- // Error displayed within SetStreamState
- return;
- }
- }
- }
- }
- else {
- err = SetStreamState(pClientCtx,&req,streamID,streamStateStr,iMuteValue);
- if (err) {
- // Error displayed within SetStreamState
- 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 endpoint_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_f(req, "JSON packaging error", "Unable to create endpoint JSON object for Policy_SetVolume for endpoint_id:%d type:%d",endpointID,endpointType);
- return;
- }
-
- json_object *paramJ= json_object_new_string(volumeStr);
- json_object_object_add(pPolicyEndpointJ, "arg_volume", paramJ);
-
- json_object * pPolicyVolumeReply = NULL;
- int policyAllowed = Policy_SetVolume(pPolicyEndpointJ,&pPolicyVolumeReply);
- if (!policyAllowed)
- {
- afb_req_fail_f(req, "Audio policy violation", "Set volume not allowed by policy in current context for endpoint_id:%d type:%d",endpointID,endpointType);
- return;
- }
-
- err = wrap_json_unpack(pPolicyVolumeReply,"{s:i}","volume",&pEndpointInfo->iVolume);
- if (err) {
- afb_req_fail_f(req, "Invalid policy reply", "Policy volume change reply for endpoint_id:%d type:%d not a valid JSON object=%s", endpointID,endpointType,json_object_get_string(pPolicyVolumeReply));
- 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_f(req, "JSON packaging error", "Unable to create endpoint JSON object for Policy_SetProperty for endpoint_id:%d type:%d",endpointID,endpointType);
- 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
- json_object * pPropertyReply = NULL;
- int policyAllowed = Policy_SetProperty(pPolicyEndpointJ,&pPropertyReply);
- if (!policyAllowed)
- {
- afb_req_fail_f(req, "Policy violation", "Policy rejected set property control in current context for endpointID: %d type:%d",endpointID,endpointType);
- return;
- }
-
- json_object * pPropReplyValue = NULL;
- err = wrap_json_unpack(pPropertyReply,"{s:o}","value",&pPropReplyValue);
- if (err) {
- afb_req_fail_f(req, "JSON parse error", "Policy returned property value for endpointID:%d type:%d not a valid JSON object=%s", endpointID,endpointType,json_object_get_string(pPropertyReply));
- return;
- }
- if (pEndpointInfo->pPropTable && pPropReplyValue) {
- json_object_get(pPropReplyValue);
- g_hash_table_insert(pEndpointInfo->pPropTable, propertyName, pPropReplyValue);
- }
- #else
- // Simulate that policy returns target state (accepted)
- if (pEndpointInfo->pPropTable && propValueJ)
- json_object_get(propValueJ);
- 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 information not found", "Property information not found for property:%s endpointID:%d type:%d",propertyName,endpointID,endpointType);
- 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, "Action not found for audio role", "Action -> %s not found for role:%s",actionName,audioRole);
- return;
- }
-
- // Disable this to avoid problems (temporary solution)
-// #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_f(req, "Audio policy violation", "Post sound action not allowed by policy in current context for action:%s and audiorole:%s",actionName,audioRole);
-// 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;
- }
-
- json_type jType = json_object_get_type(eventArrayJ);
- int iNumEvents = 0;
- if(jType == json_type_array)
- {
- 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, "JSON arguments parse error", "Empty 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_f(req, "JSON arguments parse error", "Invalid event:%s",pEventName);
- 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 %s",json_object_get_string(pEventDataJ));
- 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 for event: %s",pEventName);
- 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 id:%d type:%d for role:%s",endpointID,endpointType,pAudioRole);
- }
- // 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 for event %s",pEventName);
- 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 %s",pEventName);
- }
-} \ No newline at end of file
diff --git a/ahl-binding/ahl-binding.cpp b/ahl-binding/ahl-binding.cpp
new file mode 100644
index 0000000..37e6847
--- /dev/null
+++ b/ahl-binding/ahl-binding.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include "ahl-binding.hpp"
+
+afb_dynapi* AFB_default; // BUG: Is it possible to get rid of this ?
+
+/**
+ * @brief Callback invoked on new api creation.
+ * @param[in] handle Handle to the new api.
+ * @return Status code, zero if success.
+ */
+int ahl_api_create(void*, struct afb_dynapi* handle)
+{
+ return ahl_binding_t::instance().preinit(handle);
+}
+
+/**
+ * @brief Entry point for dynamic API.
+ * @param[in] handle Handle to start with for API creation.
+ * @return Status code, zero if success.
+ */
+int afbBindingVdyn(afb_dynapi* handle)
+{
+ using namespace std::placeholders;
+ assert(handle != nullptr);
+
+ AFB_default = handle;
+
+ return afb_dynapi_new_api(
+ handle,
+ HL_API_NAME,
+ HL_API_INFO,
+ 1,
+ ahl_api_create,
+ nullptr
+ );
+}
+
+/**
+ * @brief Callback invoked when API enter the init phase.
+ * @return Status code, zero if success.
+ */
+int ahl_api_init(afb_dynapi*)
+{
+ return ahl_binding_t::instance().init();
+}
+
+/**
+ * @brief Callback invoked when an event is received.
+ * @param[in] e Event's name.
+ * @param[in] o Event's args.
+ */
+void ahl_api_on_event(afb_dynapi*, const char* e, struct json_object* o)
+{
+ ahl_binding_t::instance().event(e, o);
+}
+
+/**
+ * @brief Callback invoked when a 'roles' section is found in config file.
+ * @param[in] o Config section to handle.
+ * @return Status code, zero if success.
+ */
+int ahl_api_config_roles(afb_dynapi*, CtlSectionT*, json_object* o)
+{
+ return ahl_binding_t::instance().parse_roles_config(o);
+}
+
+/**
+ * @brief Callback invoked when clients call the verb 'get_roles'.
+ * @param[in] req Request to handle.
+ */
+void ahl_api_get_roles(afb_request* req)
+{
+ ahl_binding_t::instance().get_roles(req);
+}
+
+/**
+ * @brief Callback invoked when clients call a 'role' verb.
+ * @param[in] req Request to handle.
+ *
+ * Handle dynamic verbs based on role name ('multimedia', 'navigation', ...)
+ */
+void ahl_api_role(afb_request* req)
+{
+ role_t* role = (role_t*)req->vcbdata;
+ assert(role != nullptr);
+
+ role->invoke(req);
+}
+
+/**
+ * @brief Default constructor.
+ */
+ahl_binding_t::ahl_binding_t()
+ : handle_{nullptr}
+{
+}
+
+/**
+ * @brief Get the singleton instance.
+ * @return The unique instance.
+ */
+ahl_binding_t& ahl_binding_t::instance()
+{
+ static ahl_binding_t s;
+ return s;
+}
+
+/**
+ * @brief This method is called during the pre-init phase of loading the binding.
+ * @param[in] handle Handle to the api.
+ * @return Status code, zero if success.
+ */
+int ahl_binding_t::preinit(afb_dynapi* handle)
+{
+ handle_ = handle;
+
+ try
+ {
+ load_static_verbs();
+ load_controller_configs();
+
+ if (afb_dynapi_on_event(handle_, ahl_api_on_event))
+ throw std::runtime_error("Failed to register event handler callback.");
+
+ if (afb_dynapi_on_init(handle_, ahl_api_init))
+ throw std::runtime_error("Failed to register init handler callback.");
+ }
+ catch(std::exception& e)
+ {
+ AFB_DYNAPI_ERROR(handle, "%s", e.what());
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Initialize the API.
+ */
+int ahl_binding_t::init()
+{
+ using namespace std::placeholders;
+
+ if (afb_dynapi_require_api(handle_, HAL_MGR_API, 1))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to require '%s' API!", HAL_MGR_API);
+ return -1;
+ }
+ AFB_DYNAPI_NOTICE(handle_, "Required '%s' API found!", HAL_MGR_API);
+
+ if (afb_dynapi_require_api(handle_, "smixer", 1))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to require 'smixer' API!");
+ return -1;
+ }
+ AFB_DYNAPI_NOTICE(handle_, "Required 'smixer' API found!");
+
+ afb_dynapi_seal(handle_);
+ AFB_DYNAPI_NOTICE(handle_, "API is now sealed!");
+
+ if (update_streams()) return -1;
+ return 0;
+}
+
+/**
+ * @brief Update audio roles definition by binding to streams.
+ * @return Status code, zero if success.
+ */
+int ahl_binding_t::update_streams()
+{
+ json_object* loaded = nullptr;
+ json_object* response = nullptr;
+ size_t i = 0, j = 0;
+ size_t hals_count = 0, streams_count = 0;
+
+ if (afb_dynapi_call_sync(handle_, "4a-hal-manager", "loaded", json_object_new_object(), &loaded))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to call 'loaded' verb on '4a-hal-manager' API!");
+ if (loaded) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(loaded));
+ return -1;
+ }
+ response = json_object_object_get(loaded, "response");
+ hals_count = json_object_array_length(response);
+
+ for(i = 0; i < hals_count; ++i)
+ {
+ json_object* info = nullptr;
+ json_object* streams = nullptr;
+ const char* halname = json_object_get_string(json_object_array_get_idx(response, i));
+ AFB_DYNAPI_DEBUG(handle_, "Found an active HAL: %s", halname);
+
+ if (afb_dynapi_call_sync(handle_, halname, "info", json_object_new_object(), &info))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to call 'info' verb on '%s' API!", halname);
+ if (info) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(info));
+ return -1;
+ }
+
+ streams = json_object_object_get(json_object_object_get(info, "response"), "streams");
+ streams_count = json_object_array_length(streams);
+ for(j = 0; j < streams_count; ++j)
+ {
+ update_stream(
+ halname,
+ json_object_get_string(json_object_object_get(json_object_array_get_idx(streams, j), "name")),
+ json_object_get_string(json_object_object_get(json_object_array_get_idx(streams, j), "cardId"))
+ );
+ }
+
+ json_object_put(info);
+ }
+ json_object_put(loaded);
+
+ return 0;
+}
+
+/**
+ * @brief Update the stream info for audio roles.
+ * @param[in] halname The hal on which the stream is.
+ * @param[in] stream The name of the stream.
+ * @param[in] deviceid The device ID to return when opening an audio role.
+ */
+void ahl_binding_t::update_stream(std::string halname, std::string stream, std::string deviceid)
+{
+ for(auto& r : roles_)
+ {
+ if(r.stream() == stream)
+ {
+ if (r.device_uri().size())
+ AFB_DYNAPI_WARNING(handle_, "Multiple stream with same name: '%s'.", stream.c_str());
+ else
+ {
+ r.device_uri(deviceid);
+ r.hal(halname);
+ }
+ }
+ }
+}
+
+void ahl_binding_t::event(std::string name, json_object* arg)
+{
+ AFB_DYNAPI_DEBUG(handle_, "Event '%s' received with the following arg: %s", name.c_str(), json_object_to_json_string(arg));
+}
+
+void ahl_binding_t::load_static_verbs()
+{
+ if (afb_dynapi_add_verb(
+ handle_,
+ "get_roles",
+ "Retrieve array of available audio roles",
+ ahl_api_get_roles,
+ nullptr,
+ nullptr,
+ AFB_SESSION_NONE_V2))
+ {
+ throw std::runtime_error("Failed to add 'get_role' verb to the API.");
+ }
+}
+
+void ahl_binding_t::load_controller_configs()
+{
+ char* dir_list = getenv("CONTROL_CONFIG_PATH");
+ if (!dir_list) dir_list = strdup(CONTROL_CONFIG_PATH);
+ struct json_object* config_files = CtlConfigScan(dir_list, "policy");
+ if (!config_files) throw std::runtime_error("No config files found!");
+
+ // Only one file should be found this way, but read all just in case
+ size_t config_files_count = json_object_array_length(config_files);
+ for(size_t i = 0; i < config_files_count; ++i)
+ {
+ config_entry_t file {json_object_array_get_idx(config_files, i)};
+
+ if(load_controller_config(file.filepath()) < 0)
+ {
+ std::stringstream ss;
+ ss << "Failed to load config file '"
+ << file.filename()
+ << "' from '"
+ << file.fullpath()
+ << "'!";
+ throw std::runtime_error(ss.str());
+ }
+ }
+}
+
+int ahl_binding_t::load_controller_config(const std::string& path)
+{
+ CtlConfigT* controller_config;
+
+ controller_config = CtlLoadMetaData(handle_, path.c_str());
+ if (!controller_config)
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to load controller from config file!");
+ return -1;
+ }
+
+ static CtlSectionT controller_sections[] =
+ {
+ {.key = "plugins", .uid = nullptr, .info = nullptr, .loadCB = PluginConfig, .handle = nullptr, .actions = nullptr},
+ {.key = "onload", .uid = nullptr, .info = nullptr, .loadCB = OnloadConfig, .handle = nullptr, .actions = nullptr},
+ {.key = "controls", .uid = nullptr, .info = nullptr, .loadCB = ControlConfig, .handle = nullptr, .actions = nullptr},
+ {.key = "events", .uid = nullptr, .info = nullptr, .loadCB = EventConfig, .handle = nullptr, .actions = nullptr},
+ {.key = "roles", .uid = nullptr, .info = nullptr, .loadCB = ahl_api_config_roles, .handle = nullptr, .actions = nullptr },
+ {.key = nullptr, .uid = nullptr, .info = nullptr, .loadCB = nullptr, .handle = nullptr, .actions = nullptr}
+ };
+
+ CtlLoadSections(handle_, controller_config, controller_sections);
+
+ return 0;
+}
+
+int ahl_binding_t::parse_roles_config(json_object* o)
+{
+ assert(o != nullptr);
+ assert(json_object_is_type(o, json_type_array));
+
+ if (roles_.size()) return 0; // Roles already added, ignore.
+
+ size_t count = json_object_array_length(o);
+ roles_.reserve(count);
+ for(size_t i = 0; i < count; ++i)
+ {
+ json_object* jr = json_object_array_get_idx(o, i);
+ assert(jr != nullptr);
+
+ roles_.push_back(role_t(jr));
+ role_t& r = roles_[roles_.size() - 1];
+ if(create_api_verb(&r))
+ return -1;
+ }
+
+ return 0;
+}
+
+int ahl_binding_t::create_api_verb(role_t* r)
+{
+ AFB_DYNAPI_NOTICE(handle_, "New audio role: %s", r->uid().c_str());
+
+ if (afb_dynapi_add_verb(
+ handle_,
+ r->uid().c_str(),
+ r->description().c_str(),
+ ahl_api_role,
+ r,
+ nullptr,
+ AFB_SESSION_NONE_V2))
+ {
+ AFB_DYNAPI_ERROR(handle_, "Failed to add '%s' verb to the API.",
+ r->uid().c_str());
+ return -1;
+ }
+
+ return 0;
+}
+
+void ahl_binding_t::get_roles(afb_request* req)
+{
+ json_object* result = json_object_new_array();
+ for(const auto& r : roles_)
+ json_object_array_add(result, json_object_new_string(r.uid().c_str()));
+ afb_request_success(req, result, nullptr);
+}
+
+const std::vector<role_t> ahl_binding_t::roles() const
+{
+ return roles_;
+}
+
+afb_dynapi* ahl_binding_t::handle() const
+{
+ return handle_;
+}
diff --git a/ahl-binding/ahl-binding.h b/ahl-binding/ahl-binding.h
deleted file mode 100644
index 6ceb040..0000000
--- a/ahl-binding/ahl-binding.h
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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_WARNING 1
-#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( gpointer data );
-// 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-binding.hpp b/ahl-binding/ahl-binding.hpp
new file mode 100644
index 0000000..66fce84
--- /dev/null
+++ b/ahl-binding/ahl-binding.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <exception>
+#include <functional>
+#include <sstream>
+#include <vector>
+#include <list>
+#include <map>
+#include <cassert>
+
+#include "config_entry.hpp"
+#include "role.hpp"
+
+#define HL_API_NAME "ahl-4a"
+#define HL_API_INFO "Audio high level API for AGL applications"
+#define HAL_MGR_API "4a-hal-manager"
+
+extern "C" {
+
+ #define AFB_BINDING_VERSION 0
+ #include <afb/afb-binding.h>
+ #include <string.h>
+ #include <ctl-config.h>
+};
+
+class ahl_binding_t
+{
+ using role_action = std::function<void(afb_request*, std::string, std::string, json_object*)>;
+
+private:
+ afb_dynapi* handle_;
+ std::vector<role_t> roles_;
+
+ explicit ahl_binding_t();
+
+ void load_static_verbs();
+
+
+ void load_controller_configs();
+ int load_controller_config(const std::string& path);
+ int update_streams();
+ void update_stream(std::string hal, std::string stream, std::string deviceuri);
+ int create_api_verb(role_t* r);
+
+ void policy_open(afb_request* req, const role_t& role);
+
+public:
+ static ahl_binding_t& instance();
+ int preinit(afb_dynapi* handle);
+ int init();
+ void event(std::string name, json_object* arg);
+ void get_roles(afb_request* req);
+
+ const std::vector<role_t> roles() const;
+ afb_dynapi* handle() const;
+
+ void audiorole(afb_request* req);
+ int parse_roles_config(json_object* o);
+};
+
+
diff --git a/ahl-binding/ahl-config.c b/ahl-binding/ahl-config.c
deleted file mode 100644
index 0bd16f2..0000000
--- a/ahl-binding/ahl-config.c
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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, "-config.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 = NULL;
- 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 ahl-* config found invalid JSON %s ", dirList);
- return AHL_FAIL;
- }
-
- AFB_NOTICE("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 = 0;
- int iNumberOfRoles = 0;
-
- if(jHALList)
- {
- json_type jTypeHalList = json_object_get_type(jHALList);
- if(jTypeHalList ==json_type_array)
- {
- iHALListLength = json_object_array_length(jHALList);
- }
- }
-
- if(jAudioRoles)
- {
- json_type jTypeAudioRole = json_object_get_type(jAudioRoles);
- if(jTypeAudioRole ==json_type_array)
- {
- iNumberOfRoles = json_object_array_length(jAudioRoles);
- }
- }
- g_AHLCtx.policyCtx.pHALList = g_ptr_array_new_with_free_func(g_free);
- 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 );
- AFB_INFO("High-level binding add dependency on HAL: %s", 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;
- }
-
- AFB_DEBUG("Parsing configuration audio role: %s", pRoleName);
-
- if (jOutputDevices)
- {
- json_type jTypeOutputDevices = json_object_get_type(jOutputDevices);
- if(jTypeOutputDevices == json_type_array)
- iNumOutDevices = json_object_array_length(jOutputDevices);
- }
- if (jInputDevices)
- {
- json_type jTypeInputDevices = json_object_get_type(jInputDevices);
- if(jTypeInputDevices == json_type_array)
- iNumInDevices = json_object_array_length(jInputDevices);
- }
- if (jActions)
- {
- json_type jTypeActions = json_object_get_type(jActions);
- if(jTypeActions == json_type_array)
- 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(TermEndpointInfo);
- if (iNumInDevices) {
- err = EnumerateDevices(jInputDevices,pRoleName,ENDPOINTTYPE_SOURCE,pRoleInfo->pSourceEndpoints);
- if (err) {
- AFB_ERROR("Invalid input devices for role %s : %s", pRoleName, json_object_to_json_string(jInputDevices));
- return AHL_FAIL;
- }
- }
- // Sinks
- pRoleInfo->pSinkEndpoints = g_ptr_array_new_with_free_func(TermEndpointInfo);
- if (iNumOutDevices) {
- err = EnumerateDevices(jOutputDevices,pRoleName,ENDPOINTTYPE_SINK,pRoleInfo->pSinkEndpoints);
- if (err) {
- AFB_ERROR("Invalid output devices for role %s: %s", pRoleName, 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
deleted file mode 100644
index 75b0715..0000000
--- a/ahl-binding/ahl-deviceenum.c
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * 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 AHL_FAIL;
- }
- // 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 AHL_FAIL;
- }
- 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( gpointer data )
-{
- EndpointInfoT * out_pEndpointInfo = (EndpointInfoT *)data;
- #define SAFE_FREE(__ptr__) if(__ptr__) g_free(__ptr__); __ptr__ = NULL;
- if(out_pEndpointInfo)
- {
- 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;
- }
- }
-}
-
-// 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);
- g_assert_nonnull(in_pAudioRole);
- 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 for audiorole:%s -> %s",in_pAudioRole,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 for audiorole:%s",pFullDeviceURICopy,in_pAudioRole);
- 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 for audiorole:%s", pEndpointInfo->gsDeviceURI,in_pAudioRole);
- continue;
- }
-
- err = FillALSAPCMInfo(pPcmHandle,pEndpointInfo);
- if (err) {
- AFB_WARNING("Unable to retrieve PCM information for PCM -> %s for audiorole:%s",pEndpointInfo->gsDeviceURI,in_pAudioRole);
- 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 for audiorole:%s",pFullDeviceURI,in_pAudioRole);
- 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
deleted file mode 100644
index b711b43..0000000
--- a/ahl-binding/ahl-json.c
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * 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,"property_table",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
deleted file mode 100644
index ffd683a..0000000
--- a/ahl-binding/ahl-json.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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
diff --git a/ahl-binding/config_entry.cpp b/ahl-binding/config_entry.cpp
new file mode 100644
index 0000000..65d7a40
--- /dev/null
+++ b/ahl-binding/config_entry.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config_entry.hpp"
+
+config_entry_t::config_entry_t(std::string fp, std::string fn)
+ : fullpath_{fp}
+ , filename_{fn}
+{
+}
+
+config_entry_t::config_entry_t(struct json_object* j)
+{
+ jcast(fullpath_, j, "fullpath");
+ jcast(filename_, j, "filename");
+}
+
+config_entry_t& config_entry_t::operator<<(struct json_object* j)
+{
+ jcast(fullpath_, j, "fullpath");
+ jcast(filename_, j, "filename");
+ return *this;
+}
+
+std::string config_entry_t::fullpath() const
+{
+ return fullpath_;
+}
+
+std::string config_entry_t::filename() const
+{
+ return filename_;
+}
+
+std::string config_entry_t::filepath() const
+{
+ return fullpath_ + '/' + filename_;
+}
+
+void config_entry_t::fullpath(std::string fp)
+{
+ fullpath_ = fp;
+}
+
+void config_entry_t::finename(std::string fn)
+{
+ filename_ = fn;
+}
diff --git a/ahl-binding/config_entry.hpp b/ahl-binding/config_entry.hpp
new file mode 100644
index 0000000..492eed2
--- /dev/null
+++ b/ahl-binding/config_entry.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jsonc_utils.hpp"
+
+class config_entry_t
+{
+private:
+ std::string fullpath_;
+ std::string filename_;
+
+public:
+ explicit config_entry_t() = default;
+ explicit config_entry_t(const config_entry_t&) = default;
+ explicit config_entry_t(config_entry_t&&) = default;
+
+ ~config_entry_t() = default;
+
+ config_entry_t& operator=(const config_entry_t&) = default;
+ config_entry_t& operator=(config_entry_t&&) = default;
+
+ explicit config_entry_t(std::string fp, std::string fn);
+ explicit config_entry_t(struct json_object* j);
+ config_entry_t& operator<<(struct json_object* j);
+
+ std::string fullpath() const;
+ std::string filename() const;
+ std::string filepath() const;
+
+ void fullpath(std::string fp);
+ void finename(std::string fn);
+
+};
diff --git a/ahl-binding/interrupt.cpp b/ahl-binding/interrupt.cpp
new file mode 100644
index 0000000..fc54139
--- /dev/null
+++ b/ahl-binding/interrupt.cpp
@@ -0,0 +1,34 @@
+#include "interrupt.hpp"
+
+interrupt_t::interrupt_t(json_object* o)
+{
+ jcast(type_, o, "type");
+ args_ = json_object_object_get(o, "args");
+}
+
+interrupt_t& interrupt_t::operator<<(json_object* o)
+{
+ jcast(type_, o, "type");
+ args_ = json_object_object_get(o, "args");
+ return *this;
+}
+
+std::string interrupt_t::type() const
+{
+ return type_;
+}
+
+json_object* interrupt_t::args() const
+{
+ return args_;
+}
+
+void interrupt_t::type(std::string v)
+{
+ type_ = v;
+}
+
+void interrupt_t::args(json_object* v)
+{
+ args_ = v;
+}
diff --git a/ahl-binding/interrupt.hpp b/ahl-binding/interrupt.hpp
new file mode 100644
index 0000000..caf5cda
--- /dev/null
+++ b/ahl-binding/interrupt.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "jsonc_utils.hpp"
+
+class interrupt_t
+{
+private:
+ std::string type_;
+ json_object* args_;
+
+public:
+ explicit interrupt_t() = default;
+ explicit interrupt_t(const interrupt_t&) = default;
+ explicit interrupt_t(interrupt_t&&) = default;
+ ~interrupt_t() = default;
+
+ interrupt_t& operator=(const interrupt_t&) = default;
+ interrupt_t& operator=(interrupt_t&&) = default;
+
+ explicit interrupt_t(json_object* o);
+ interrupt_t& operator<<(json_object* o);
+
+ std::string type() const;
+ json_object* args() const;
+
+ void type(std::string v);
+ void args(json_object* v);
+};
diff --git a/ahl-binding/jsonc_utils.hpp b/ahl-binding/jsonc_utils.hpp
new file mode 100644
index 0000000..5e82fca
--- /dev/null
+++ b/ahl-binding/jsonc_utils.hpp
@@ -0,0 +1,91 @@
+#pragma once
+
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+#include <json-c/json.h>
+
+template<class T>
+inline T& jcast(T& v, json_object* o)
+{
+ v << o;
+ return v;
+}
+
+template<class T>
+inline T& jcast_array(T& v, json_object* o)
+{
+ if (o == nullptr) return v;
+ auto sz = json_object_array_length(o);
+ for(auto i = 0; i < sz ; ++i)
+ {
+ typename T::value_type item;
+ jcast(item, json_object_array_get_idx(o, i));
+ v.push_back(item);
+ }
+ return v;
+}
+
+template<>
+inline bool& jcast<bool>(bool& v, json_object* o)
+{
+ v = (json_object_get_boolean(o) == TRUE);
+ return v;
+}
+
+template<>
+inline double& jcast<double>(double& v, json_object* o)
+{
+ v = json_object_get_double(o);
+ return v;
+}
+
+template<>
+inline int32_t& jcast<int32_t>(int32_t& v, json_object* o)
+{
+ v = json_object_get_int(o);
+ return v;
+}
+
+template<>
+inline int64_t& jcast<int64_t>(int64_t& v, json_object* o)
+{
+ v = json_object_get_int(o);
+ return v;
+}
+
+template<>
+inline std::string& jcast<std::string>(std::string& v, json_object* o)
+{
+ const char* tmp = json_object_get_string(o);
+ v = tmp ? tmp : "";
+ return v;
+}
+
+template<class T>
+inline T& jcast(T& v, json_object* o, std::string field)
+{
+ return jcast<T>(v, json_object_object_get(o, field.c_str()));
+}
+
+template<class T>
+inline T& jcast_array(T& v, json_object* o, std::string field)
+{
+ return jcast_array<T>(v, json_object_object_get(o, field.c_str()));
+}
diff --git a/ahl-binding/role.cpp b/ahl-binding/role.cpp
new file mode 100644
index 0000000..2be3dc5
--- /dev/null
+++ b/ahl-binding/role.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "role.hpp"
+#include "jsonc_utils.hpp"
+#include "ahl-binding.hpp"
+
+role_t::role_t(json_object* j)
+{
+ jcast(uid_, j, "uid");
+ jcast(description_, j, "description");
+ jcast(priority_, j, "priority");
+ jcast(stream_, j, "stream");
+ jcast_array(interrupts_, j, "interrupts");
+ opened_ = false;
+}
+
+role_t& role_t::operator<<(json_object* j)
+{
+ jcast(uid_, j, "uid");
+ jcast(description_, j, "description");
+ jcast(priority_, j, "priority");
+ jcast(stream_, j, "stream");
+ jcast_array(interrupts_, j, "interrupts");
+ return *this;
+}
+
+std::string role_t::uid() const
+{
+ return uid_;
+}
+
+std::string role_t::description() const
+{
+ return description_;
+}
+
+std::string role_t::hal() const
+{
+ return hal_;
+}
+
+std::string role_t::stream() const
+{
+ return stream_;
+}
+
+int role_t::priority() const
+{
+ return priority_;
+}
+
+std::string role_t::device_uri() const
+{
+ return device_uri_;
+}
+
+bool role_t::opened() const
+{
+ return opened_;
+}
+
+void role_t::uid(std::string v)
+{
+ uid_ = v;
+}
+
+void role_t::description(std::string v)
+{
+ description_ = v;
+}
+
+void role_t::hal(std::string v)
+{
+ hal_ = v;
+}
+
+void role_t::stream(std::string v)
+{
+ stream_ = v;
+}
+
+void role_t::priority(int v)
+{
+ priority_ = v;
+}
+
+void role_t::device_uri(std::string v)
+{
+ device_uri_ = v;
+}
+
+const std::vector<interrupt_t>& role_t::interrupts() const
+{
+ return interrupts_;
+}
+
+int role_t::apply_policy(afb_request* req)
+{
+ if(interrupts_.size())
+ {
+ const interrupt_t& i = interrupts_[0];
+ /*if (i.type() == "mute")
+ {
+ }
+ else if (i.type() == "continue")
+ {
+ }
+ else if (i.type() == "cancel")
+ {
+ }
+ else */if (i.type() == "ramp")
+ {
+ for(const auto& r: ahl_binding_t::instance().roles())
+ {
+ if (r.opened() && priority_ > r.priority())
+ {
+ // { "ramp" : { "uid" : "ramp-slow", "volume" : 30 } }
+ json_object* arg = json_object_new_object();
+ json_object_object_add(arg, "ramp", i.args());
+ json_object_get(i.args());
+ json_object* result = nullptr;
+
+ AFB_DYNAPI_NOTICE(ahl_binding_t::instance().handle(),
+ "Call '%s'/'%s' '%s",
+ r.hal().c_str(), r.stream().c_str(), json_object_to_json_string(arg));
+
+ if(afb_dynapi_call_sync(ahl_binding_t::instance().handle(), r.hal().c_str(), r.stream().c_str(), arg, &result))
+ {
+ afb_request_fail(req, "Failed to call 'ramp' action on stream", nullptr);
+ return -1;
+ }
+ AFB_DYNAPI_NOTICE(ahl_binding_t::instance().handle(),
+ "POLICY: Applying a ramp to '%s' stream because '%s' is opened and have higher priority!",
+ r.stream().c_str(), stream_.c_str());
+ }
+ }
+ }
+ else
+ {
+ afb_request_fail(req, "Unkown interrupt uid!", nullptr);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+void role_t::invoke(afb_request* req)
+{
+ json_object* arg = afb_request_json(req);
+ if (arg == nullptr)
+ {
+ afb_request_fail(req, "No valid argument!", nullptr);
+ return;
+ }
+
+ json_object* jaction = json_object_object_get(arg, "action");
+ if (jaction == nullptr)
+ {
+ afb_request_fail(req, "No valid action!", nullptr);
+ return;
+ }
+
+ std::string action = json_object_get_string(jaction);
+ if (action.size() == 0)
+ {
+ afb_request_fail(req, "No valid action!", nullptr);
+ return;
+ }
+
+ if (action == "open") open(req, arg);
+ else if (action == "close") close(req, arg);
+ else if (action == "volume") volume(req, arg);
+ else if (action == "interrupt") interrupt(req, arg);
+ else afb_request_fail(req, "Unknown action!", nullptr);
+}
+
+void role_t::open(afb_request* r, json_object* o)
+{
+ if (opened_)
+ {
+ afb_request_fail(r, "Already opened!", nullptr);
+ return;
+ }
+
+ if (!apply_policy(r))
+ {
+ afb_request_context_set(r, this, nullptr);
+ opened_ = true;
+
+ json_object* result = json_object_new_object();
+ json_object_object_add(result, "device_uri", json_object_new_string(device_uri_.c_str()));
+
+ afb_request_success(r, result, nullptr);
+ }
+}
+
+void role_t::close(afb_request* r, json_object* o)
+{
+ if (!opened_)
+ {
+ afb_request_success(r, nullptr, "Already closed!");
+ return;
+ }
+
+ if(!afb_request_context_get(r))
+ {
+ afb_request_fail(r, "Stream is opened by another client!", nullptr);
+ return;
+ }
+
+ opened_ = false;
+ afb_request_success(r, nullptr, "Stream closed!");
+}
+
+void role_t::volume(afb_request* r, json_object* o)
+{
+ if(!afb_request_has_permission(r, "urn:AGL:permission::public:4a-audio-mixer"))
+ {
+ if (!opened_)
+ {
+ afb_request_fail(r, "You have to open the stream first!", nullptr);
+ return;
+ }
+
+ if(!afb_request_context_get(r))
+ {
+ afb_request_fail(r, "Stream is opened by another client!", nullptr);
+ return;
+ }
+ }
+ else
+ {
+ AFB_DYNAPI_NOTICE(ahl_binding_t::instance().handle(), "Granted special audio-mixer permission to change volume");
+ }
+
+ json_object* value = json_object_object_get(o, "value");
+ if(!value)
+ {
+ afb_request_fail(r, "No value given!", nullptr);
+ return;
+ }
+
+ json_object_get(value);
+
+ json_object* a = json_object_new_object();
+ json_object_object_add(a, "volume", value);
+
+ afb_dynapi_call(
+ r->dynapi,
+ hal_.c_str(),
+ stream_.c_str(),
+ a,
+ [](void* closure, int status, json_object* result, afb_dynapi* handle)
+ {
+ AFB_DYNAPI_DEBUG(handle, "Got the following answer: %s", json_object_to_json_string(result));
+ afb_request* r = (afb_request*)closure;
+
+ json_object_get(result);
+ if (status) afb_request_fail(r, json_object_to_json_string(result), nullptr);
+ else afb_request_success(r, result, nullptr);
+ afb_request_unref(r);
+ },
+ afb_request_addref(r));
+}
+
+void role_t::interrupt(afb_request* r, json_object* o)
+{
+}
diff --git a/ahl-binding/role.hpp b/ahl-binding/role.hpp
new file mode 100644
index 0000000..1469b68
--- /dev/null
+++ b/ahl-binding/role.hpp
@@ -0,0 +1,79 @@
+#pragma once
+
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Loïc Collignon <loic.collignon@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+#include "interrupt.hpp"
+
+struct afb_request;
+
+class role_t
+{
+private:
+ // Members filled by config
+ std::string uid_;
+ std::string description_;
+ std::string hal_;
+ std::string stream_;
+ int priority_;
+ std::vector<interrupt_t> interrupts_;
+
+ std::string device_uri_;
+ bool opened_;
+
+ int apply_policy(afb_request* req);
+
+public:
+ explicit role_t() = default;
+ explicit role_t(const role_t&) = default;
+ explicit role_t(role_t&&) = default;
+
+ ~role_t() = default;
+
+ role_t& operator=(const role_t&) = default;
+ role_t& operator=(role_t&&) = default;
+
+ static role_t from_json(json_object* o);
+
+ explicit role_t(json_object* j);
+
+ role_t& operator<<(json_object* j);
+
+ std::string uid() const;
+ std::string description() const;
+ std::string hal() const;
+ std::string stream() const;
+ int priority() const;
+ const std::vector<interrupt_t>& interrupts() const;
+ std::string device_uri() const;
+ bool opened() const;
+
+ void uid(std::string v);
+ void description(std::string v);
+ void hal(std::string v);
+ void stream(std::string v);
+ void device_uri(std::string v);
+ void priority(int v);
+
+ void invoke(afb_request* r);
+
+ void open(afb_request* r, json_object* o);
+ void close(afb_request* r, json_object* o);
+ void volume(afb_request* r, json_object* o);
+ void interrupt(afb_request* r, json_object* o);
+};