diff options
Diffstat (limited to 'ahl-binding')
-rw-r--r-- | ahl-binding/CMakeLists.txt | 26 | ||||
-rw-r--r-- | ahl-binding/ahl-apidef.h | 237 | ||||
-rw-r--r-- | ahl-binding/ahl-apidef.json | 448 | ||||
-rw-r--r-- | ahl-binding/ahl-binding.c | 1276 | ||||
-rw-r--r-- | ahl-binding/ahl-binding.cpp | 389 | ||||
-rw-r--r-- | ahl-binding/ahl-binding.h | 119 | ||||
-rw-r--r-- | ahl-binding/ahl-binding.hpp | 78 | ||||
-rw-r--r-- | ahl-binding/ahl-config.c | 251 | ||||
-rw-r--r-- | ahl-binding/ahl-deviceenum.c | 336 | ||||
-rw-r--r-- | ahl-binding/ahl-json.c | 296 | ||||
-rw-r--r-- | ahl-binding/ahl-json.h | 26 | ||||
-rw-r--r-- | ahl-binding/config_entry.cpp | 62 | ||||
-rw-r--r-- | ahl-binding/config_entry.hpp | 49 | ||||
-rw-r--r-- | ahl-binding/interrupt.cpp | 34 | ||||
-rw-r--r-- | ahl-binding/interrupt.hpp | 28 | ||||
-rw-r--r-- | ahl-binding/jsonc_utils.hpp | 91 | ||||
-rw-r--r-- | ahl-binding/role.cpp | 283 | ||||
-rw-r--r-- | ahl-binding/role.hpp | 79 |
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); +}; |