summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTai Vuong <tvuong@audiokinetic.com>2017-08-24 16:33:40 -0400
committerTai Vuong <tvuong@audiokinetic.com>2017-08-24 16:33:40 -0400
commit94b4cfd3fa2a24736808ed854903f4c3038c9e11 (patch)
tree6e543167873f513925624ee2f2d641410def3a3d /src
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt52
-rw-r--r--src/ahl-apidef.h297
-rw-r--r--src/ahl-apidef.json601
-rw-r--r--src/ahl-binding.c450
-rw-r--r--src/ahl-binding.h96
-rw-r--r--src/ahl-deviceenum.c40
6 files changed, 1536 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..cbfe5dc
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,52 @@
+###########################################################################
+# Copyright 2017 Audiokinetic.com
+#
+# author: Francois Thibault <fthibault@audiokinetic.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+# Generate API-v2 hat from OpenAPI json definition
+macro(SET_TARGET_GENSKEL TARGET_NAME API_DEF_NAME)
+ add_custom_command(OUTPUT ${API_DEF_NAME}.h
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ DEPENDS ${API_DEF_NAME}.json
+ COMMAND afb-genskel ${API_DEF_NAME}.json >${API_DEF_NAME}.h
+ )
+ add_custom_target(${API_DEF_NAME}_OPENAPI DEPENDS ${API_DEF_NAME}.h)
+ add_dependencies(${TARGET_NAME} ${API_DEF_NAME}_OPENAPI)
+
+endmacro(SET_TARGET_GENSKEL)
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(audiohighlevel-afb)
+
+ # Define project Targets
+ ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c)
+
+ # Generate API-v2 hat from OpenAPI json definition
+ SET_TARGET_GENSKEL(${TARGET_NAME} ahl-apidef)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ PREFIX "afb-"
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME}
+ afb-utilities
+ ${link_libraries}
+ )
+
diff --git a/src/ahl-apidef.h b/src/ahl-apidef.h
new file mode 100644
index 0000000..fa78681
--- /dev/null
+++ b/src/ahl-apidef.h
@@ -0,0 +1,297 @@
+
+static const char _afb_description_v2_audiohl[] =
+ "{\"openapi\":\"3.0.0\",\"$schema\":\"http:iot.bzh/download/openapi/schem"
+ "a-3.0/default-schema.json\",\"info\":{\"description\":\"Audio high level"
+ " API for AGL applications\",\"title\":\"audiohighlevel\",\"version\":\"1"
+ ".0\",\"x-binding-c-generator\":{\"api\":\"audiohl\",\"version\":2,\"pref"
+ "ix\":\"audiohlapi_\",\"postfix\":\"\",\"start\":null,\"onevent\":null,\""
+ "init\":\"AhlBindingInit\",\"scope\":\"\",\"private\":false}},\"servers\""
+ ":[{\"url\":\"ws://{host}:{port}/api/audiohl\",\"description\":\"Audio hi"
+ "gh level API for AGL applications.\",\"variables\":{\"host\":{\"default\""
+ ":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":[{\"$r"
+ "ef\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"schemas\":"
+ "{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"afb-ev"
+ "ent\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply-v2\":"
+ "{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":[\"jty"
+ "pe\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const"
+ "\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"statu"
+ "s\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"type\""
+ ":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string"
+ "\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"object\"}"
+ "}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"event"
+ "\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-event"
+ "\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}},\"e"
+ "ndpoint_info\":{\"type\":\"object\",\"required\":[\"endpoint_id\",\"type"
+ "\",\"name\"],\"properties\":{\"endpoint_id\":{\"type\":\"int\"},\"type\""
+ ":{\"type\":\"enum\"},\"name\":{\"type\":\"string\"}}},\"stream_info\":{\""
+ "type\":\"object\",\"required\":[\"stream_id\",\"pcm_name\",\"name\"],\"p"
+ "roperties\":{\"stream_id\":{\"type\":\"int\"},\"pcm_name\":{\"type\":\"s"
+ "tring\"},\"$ref\":\"#/components/schemas/endpoint_info\"}},\"routing_inf"
+ "o\":{\"type\":\"object\",\"required\":[\"routing_id\",\"source_id\",\"si"
+ "nk_id\"],\"properties\":{\"routing_id\":{\"type\":\"int\"},\"source_id\""
+ ":{\"type\":\"int\"},\"sink_id\":{\"type\":\"int\"}}}},\"x-permissions\":"
+ "{\"streamcontrol\":{\"permission\":\"urn:AGL:permission:audio:public:str"
+ "eamcontrol\"},\"routingcontrol\":{\"permission\":\"urn:AGL:permission:au"
+ "dio:public:routingcontrol\"},\"soundevent\":{\"permission\":\"urn:AGL:pe"
+ "rmission:audio:public:soundevent\"}},\"responses\":{\"200\":{\"descripti"
+ "on\":\"A complex object array response\",\"content\":{\"application/json"
+ "\":{\"schema\":{\"$ref\":\"#/components/schemas/afb-reply\"}}}},\"400\":"
+ "{\"description\":\"Invalid arguments\"}}},\"paths\":{\"/get_sources\":{\""
+ "description\":\"Retrieve array of available audio sources\",\"get\":{\"p"
+ "arameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":fals"
+ "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\"}}}},\"/get_sinks\":{\"description\":\"Retrieve array of availab"
+ "le audio sinks\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"a"
+ "udio_role\",\"required\":false,\"schema\":{\"type\":\"enum\"}}],\"respon"
+ "ses\":{\"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/streamcontrol\"},\"parameters\":[{\"in\":\"que"
+ "ry\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"en"
+ "um\"}},{\"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-perm"
+ "issions\":{\"$ref\":\"#/components/x-permissions/streamcontrol\"},\"para"
+ "meters\":[{\"in\":\"query\",\"name\":\"stream_id\",\"required\":true,\"s"
+ "chema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compon"
+ "ents/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}"
+ "}},\"/get_available_routings\":{\"description\":\"Retrieve array of avai"
+ "lable routing info structures\",\"get\":{\"parameters\":[{\"in\":\"query"
+ "\",\"name\":\"audio_role\",\"required\":false,\"schema\":{\"type\":\"enu"
+ "m\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\""
+ "response\":{\"description\":\"Array of routing info structures\",\"type\""
+ ":\"array\",\"items\":{\"description\":\"Routing info structure {routingI"
+ "D, sourceID, sinkID }\",\"type\":\"object\"}}},\"400\":{\"$ref\":\"#/com"
+ "ponents/responses/400\"}}}},\"/add_routing\":{\"description\":\"Request "
+ "a routing connection between available devices\",\"get\":{\"x-permission"
+ "s\":{\"$ref\":\"#/components/x-permissions/routingcontrol\"},\"parameter"
+ "s\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\"schem"
+ "a\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"routing_id\",\"req"
+ "uired\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\""
+ "$ref\":\"#/components/responses/200\",\"response\":{\"description\":\"Ro"
+ "uting information structure\",\"$ref\":\"#/components/schemas/routing_in"
+ "fo\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/remove_ro"
+ "uting\":{\"description\":\"Request to remove a routing connection betwee"
+ "n devices\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permis"
+ "sions/routingcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"rou"
+ "ting_id\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"responses\""
+ ":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\""
+ "#/components/responses/400\"}}}},\"/set_endpoint_volume\":{\"description"
+ "\":\"Set endpoint volume\",\"get\":{\"x-permissions\":{\"$ref\":\"#/comp"
+ "onents/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\","
+ "\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum"
+ "\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"sche"
+ "ma\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"volume\",\"require"
+ "d\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\""
+ "ramp_time_ms\",\"required\":false,\"schema\":{\"type\":\"int\"}}],\"resp"
+ "onses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$r"
+ "ef\":\"#/components/responses/400\"}}}},\"/get_endpoint_volume\":{\"desc"
+ "ription\":\"Get endpoint volume\",\"get\":{\"parameters\":[{\"in\":\"que"
+ "ry\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\""
+ "enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\""
+ "schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compo"
+ "nents/responses/200\",\"response\":{\"description\":\"Endpoint volume va"
+ "lue\",\"type\":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/4"
+ "00\"}}}},\"/set_endpoint_property\":{\"description\":\"Set endpoint prop"
+ "erty value\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permi"
+ "ssions/streamcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"end"
+ "point_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\""
+ "query\",\"name\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\""
+ ":\"int\"}},{\"in\":\"query\",\"name\":\"property_name\",\"required\":tru"
+ "e,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"value\""
+ ",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\""
+ "name\":\"ramp_time_ms\",\"required\":false,\"schema\":{\"type\":\"int\"}"
+ "}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"40"
+ "0\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_endpoint_propert"
+ "y\":{\"description\":\"Get endpoint property value\",\"get\":{\"paramete"
+ "rs\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"s"
+ "chema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\","
+ "\"required\":false,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"na"
+ "me\":\"property_name\",\"required\":true,\"schema\":{\"type\":\"string\""
+ "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"re"
+ "sponse\":{\"description\":\"Property value\",\"type\":\"double\"}},\"400"
+ "\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_endpoint_state\":"
+ "{\"description\":\"Set endpoint state\",\"get\":{\"x-permissions\":{\"$r"
+ "ef\":\"#/components/x-permissions/streamcontrol\"},\"parameters\":[{\"in"
+ "\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\""
+ "type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\""
+ ":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state_"
+ "name\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"que"
+ "ry\",\"name\":\"state_value\",\"required\":true,\"schema\":{\"type\":\"s"
+ "tring\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200"
+ "\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_endpoint"
+ "_state\":{\"description\":\"Get endpoint state value\",\"get\":{\"parame"
+ "ters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\""
+ "schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\""
+ ",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"na"
+ "me\":\"state_name\",\"required\":true,\"schema\":{\"type\":\"string\"}}]"
+ ",\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"respo"
+ "nse\":{\"description\":\"Endpoint state value\",\"type\":\"string\"}},\""
+ "400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/post_sound_event\""
+ ":{\"description\":\"Post sound event\",\"get\":{\"x-permissions\":{\"$re"
+ "f\":\"#/components/x-permissions/soundevent\"},\"parameters\":[{\"in\":\""
+ "query\",\"name\":\"event_name\",\"required\":true,\"schema\":{\"type\":\""
+ "string\"}},{\"in\":\"query\",\"name\":\"audio_role\",\"required\":false,"
+ "\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"media_name\""
+ ",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\","
+ "\"name\":\"audio_context\",\"required\":false,\"schema\":{\"type\":\"obj"
+ "ect\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\""
+ "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/subscribe\":{\""
+ "description\":\"Subscribe to audio high level events\",\"get\":{\"parame"
+ "ters\":[{\"in\":\"query\",\"name\":\"events\",\"required\":true,\"schema"
+ "\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}],\"responses\":"
+ "{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#"
+ "/components/responses/400\"}}}}}}"
+;
+
+static const struct afb_auth _afb_auths_v2_audiohl[] = {
+ { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:streamcontrol" },
+ { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:routingcontrol" },
+ { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:soundevent" }
+};
+
+ void audiohlapi_get_sources(struct afb_req req);
+ void audiohlapi_get_sinks(struct afb_req req);
+ void audiohlapi_stream_open(struct afb_req req);
+ void audiohlapi_stream_close(struct afb_req req);
+ void audiohlapi_get_available_routings(struct afb_req req);
+ void audiohlapi_add_routing(struct afb_req req);
+ void audiohlapi_remove_routing(struct afb_req req);
+ void audiohlapi_set_endpoint_volume(struct afb_req req);
+ void audiohlapi_get_endpoint_volume(struct afb_req req);
+ void audiohlapi_set_endpoint_property(struct afb_req req);
+ void audiohlapi_get_endpoint_property(struct afb_req req);
+ void audiohlapi_set_endpoint_state(struct afb_req req);
+ void audiohlapi_get_endpoint_state(struct afb_req req);
+ void audiohlapi_post_sound_event(struct afb_req req);
+ void audiohlapi_subscribe(struct afb_req req);
+
+static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = {
+ {
+ .verb = "get_sources",
+ .callback = audiohlapi_get_sources,
+ .auth = NULL,
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_sinks",
+ .callback = audiohlapi_get_sinks,
+ .auth = NULL,
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "stream_open",
+ .callback = audiohlapi_stream_open,
+ .auth = &_afb_auths_v2_audiohl[0],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "stream_close",
+ .callback = audiohlapi_stream_close,
+ .auth = &_afb_auths_v2_audiohl[0],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_available_routings",
+ .callback = audiohlapi_get_available_routings,
+ .auth = NULL,
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "add_routing",
+ .callback = audiohlapi_add_routing,
+ .auth = &_afb_auths_v2_audiohl[1],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "remove_routing",
+ .callback = audiohlapi_remove_routing,
+ .auth = &_afb_auths_v2_audiohl[1],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "set_endpoint_volume",
+ .callback = audiohlapi_set_endpoint_volume,
+ .auth = &_afb_auths_v2_audiohl[0],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_endpoint_volume",
+ .callback = audiohlapi_get_endpoint_volume,
+ .auth = NULL,
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "set_endpoint_property",
+ .callback = audiohlapi_set_endpoint_property,
+ .auth = &_afb_auths_v2_audiohl[0],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_endpoint_property",
+ .callback = audiohlapi_get_endpoint_property,
+ .auth = NULL,
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "set_endpoint_state",
+ .callback = audiohlapi_set_endpoint_state,
+ .auth = &_afb_auths_v2_audiohl[0],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "get_endpoint_state",
+ .callback = audiohlapi_get_endpoint_state,
+ .auth = NULL,
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "post_sound_event",
+ .callback = audiohlapi_post_sound_event,
+ .auth = &_afb_auths_v2_audiohl[2],
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ {
+ .verb = "subscribe",
+ .callback = audiohlapi_subscribe,
+ .auth = NULL,
+ .info = NULL,
+ .session = AFB_SESSION_NONE_V2
+ },
+ { .verb = NULL }
+};
+
+const struct afb_binding_v2 afbBindingV2 = {
+ .api = "audiohl",
+ .specification = _afb_description_v2_audiohl,
+ .info = NULL,
+ .verbs = _afb_verbs_v2_audiohl,
+ .preinit = NULL,
+ .init = AhlBindingInit,
+ .onevent = NULL,
+ .noconcurrency = 0
+};
+
diff --git a/src/ahl-apidef.json b/src/ahl-apidef.json
new file mode 100644
index 0000000..cbe25a9
--- /dev/null
+++ b/src/ahl-apidef.json
@@ -0,0 +1,601 @@
+{
+ "openapi": "3.0.0",
+ "$schema": "http:iot.bzh/download/openapi/schema-3.0/default-schema.json",
+ "info": {
+ "description": "Audio high level API for AGL applications",
+ "title": "audiohighlevel",
+ "version": "1.0",
+ "x-binding-c-generator": {
+ "api": "audiohl",
+ "version": 2,
+ "prefix": "audiohlapi_",
+ "postfix": "",
+ "start": null,
+ "onevent": null,
+ "init": "AhlBindingInit",
+ "scope": "",
+ "private": false
+ }
+ },
+ "servers": [
+ {
+ "url": "ws://{host}:{port}/api/audiohl",
+ "description": "Audio high level API for AGL applications.",
+ "variables": {
+ "host": {
+ "default": "localhost"
+ },
+ "port": {
+ "default": "1234"
+ }
+ },
+ "x-afb-events": [
+ {
+ "$ref": "#/components/schemas/afb-event"
+ }
+ ]
+ }
+ ],
+ "components": {
+ "schemas": {
+ "afb-reply": {
+ "$ref": "#/components/schemas/afb-reply-v2"
+ },
+ "afb-event": {
+ "$ref": "#/components/schemas/afb-event-v2"
+ },
+ "afb-reply-v2": {
+ "title": "Generic response.",
+ "type": "object",
+ "required": ["jtype", "request"],
+ "properties": {
+ "jtype": {
+ "type": "string",
+ "const": "afb-reply"
+ },
+ "request": {
+ "type": "object",
+ "required": ["status"],
+ "properties": {
+ "status": {
+ "type": "string"
+ },
+ "info": {
+ "type": "string"
+ },
+ "token": {
+ "type": "string"
+ },
+ "uuid": {
+ "type": "string"
+ },
+ "reqid": {
+ "type": "string"
+ }
+ }
+ },
+ "response": {
+ "type": "object"
+ }
+ }
+ },
+ "afb-event-v2": {
+ "type": "object",
+ "required": ["jtype", "event"],
+ "properties": {
+ "jtype": {
+ "type": "string",
+ "const": "afb-event"
+ },
+ "event": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object"
+ }
+ }
+ },
+ "endpoint_info": {
+ "type": "object",
+ "required": [ "endpoint_id", "type", "name" ],
+ "properties": {
+ "endpoint_id": { "type": "int" },
+ "type": { "type": "enum" },
+ "name": { "type": "string" }
+ }
+ },
+ "stream_info": {
+ "type": "object",
+ "required": [ "stream_id", "pcm_name", "name" ],
+ "properties": {
+ "stream_id": { "type": "int" },
+ "pcm_name": { "type": "string" },
+ "$ref": "#/components/schemas/endpoint_info"
+ }
+ },
+ "routing_info": {
+ "type": "object",
+ "required": [ "routing_id", "source_id", "sink_id" ],
+ "properties": {
+ "routing_id": { "type": "int" },
+ "source_id": { "type": "int" },
+ "sink_id": { "type": "int" }
+ }
+ }
+ },
+ "x-permissions": {
+ "streamcontrol": {
+ "permission": "urn:AGL:permission:audio:public:streamcontrol"
+ },
+ "routingcontrol": {
+ "permission": "urn:AGL:permission:audio:public:routingcontrol"
+ },
+ "soundevent": {
+ "permission": "urn:AGL:permission:audio:public:soundevent"
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "A complex object array response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/afb-reply"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid arguments"
+ }
+ }
+ },
+ "paths": {
+ "/get_sources": {
+ "description": "Retrieve array of available audio sources",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": false,
+ "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" }
+ }
+ }
+ },
+ "/get_sinks": {
+ "description": "Retrieve array of available audio sinks",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": false,
+ "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/streamcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "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/streamcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "stream_id",
+ "required": true,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/get_available_routings": {
+ "description": "Retrieve array of available routing info structures",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": false,
+ "schema": { "type": "enum" }
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/200",
+ "response": {
+ "description": "Array of routing info structures",
+ "type": "array",
+ "items": {
+ "description": "Routing info structure {routingID, sourceID, sinkID }",
+ "type": "object"
+ }
+ }
+ },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/add_routing": {
+ "description": "Request a routing connection between available devices",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/routingcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "routing_id",
+ "required": false,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/200",
+ "response": {
+ "description": "Routing information structure",
+ "$ref": "#/components/schemas/routing_info"
+ }
+ },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/remove_routing": {
+ "description": "Request to remove a routing connection between devices",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/routingcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "routing_id",
+ "required": true,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/set_endpoint_volume": {
+ "description": "Set endpoint volume",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": true,
+ "schema": { "type": "int" }
+ },
+ {
+ "in": "query",
+ "name": "volume",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "ramp_time_ms",
+ "required": false,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/get_endpoint_volume": {
+ "description": "Get endpoint volume",
+ "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",
+ "response": {
+ "description": "Endpoint volume value",
+ "type": "double"
+ }
+ },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/set_endpoint_property": {
+ "description": "Set endpoint property value",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": false,
+ "schema": { "type": "int" }
+ },
+ {
+ "in": "query",
+ "name": "property_name",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "value",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "ramp_time_ms",
+ "required": false,
+ "schema": { "type": "int" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/get_endpoint_property": {
+ "description": "Get endpoint property value",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": false,
+ "schema": { "type": "int" }
+ },
+ {
+ "in": "query",
+ "name": "property_name",
+ "required": true,
+ "schema": { "type": "string" }
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/200",
+ "response": {
+ "description": "Property value",
+ "type": "double"
+ }
+ },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/set_endpoint_state": {
+ "description": "Set endpoint state",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": true,
+ "schema": { "type": "int" }
+ },
+ {
+ "in": "query",
+ "name": "state_name",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "state_value",
+ "required": true,
+ "schema": { "type": "string" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/get_endpoint_state": {
+ "description": "Get endpoint state value",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "endpoint_type",
+ "required": true,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "endpoint_id",
+ "required": true,
+ "schema": { "type": "int" }
+ },
+ {
+ "in": "query",
+ "name": "state_name",
+ "required": true,
+ "schema": { "type": "string" }
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/200",
+ "response": {
+ "description": "Endpoint state value",
+ "type": "string"
+ }
+ },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/post_sound_event": {
+ "description": "Post sound event",
+ "get": {
+ "x-permissions": { "$ref": "#/components/x-permissions/soundevent" },
+ "parameters": [
+ {
+ "in": "query",
+ "name": "event_name",
+ "required": true,
+ "schema": { "type": "string" }
+ },
+ {
+ "in": "query",
+ "name": "audio_role",
+ "required": false,
+ "schema": { "type": "enum" }
+ },
+ {
+ "in": "query",
+ "name": "media_name",
+ "required": false,
+ "schema": { "type": "string"}
+ },
+ {
+ "in": "query",
+ "name": "audio_context",
+ "required": false,
+ "schema": { "type": "object" }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ },
+ "/subscribe": {
+ "description": "Subscribe to audio high level events",
+ "get": {
+ "parameters": [
+ {
+ "in": "query",
+ "name": "events",
+ "required": true,
+ "schema": { "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+ ],
+ "responses": {
+ "200": { "$ref": "#/components/responses/200" },
+ "400": { "$ref": "#/components/responses/400" }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ahl-binding.c b/src/ahl-binding.c
new file mode 100644
index 0000000..99236bd
--- /dev/null
+++ b/src/ahl-binding.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ * Author Francois Thibault <fthibault@audiokinetic.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "ahl-binding.h"
+#include "ahl-apidef.h" // Generated from JSON OpenAPI
+#include "wrap-json.h"
+
+// TODO: json_object_put to free JSON objects? potential leaks currently
+
+// Helper macros/func for packaging JSON objects from C structures
+
+#define EndpointInfoStructToJSON(__JSON_OBJECT__, __ENDPOINTINFOSTRUCT__) \
+ wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:s}", "endpoint_id", __ENDPOINTINFOSTRUCT__.endpoint_id, "type", __ENDPOINTINFOSTRUCT__.type, "name", __ENDPOINTINFOSTRUCT__.name);
+
+#define RoutingInfoStructToJSON(__JSON_OBJECT__, __ROUTINGINFOSTRUCT__) \
+ wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:i}", "routing_id", __ROUTINGINFOSTRUCT__.routing_id, "source_id", __ROUTINGINFOSTRUCT__.source_id, "sink_id", __ROUTINGINFOSTRUCT__.sink_id);
+
+static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT streamInfo)
+{
+ json_object *endpointInfoJ;
+ EndpointInfoStructToJSON(endpointInfoJ,streamInfo.endpoint_info);
+ wrap_json_pack(streamInfoJ, "{s:i,s:s}", "stream_id", streamInfo.stream_id, "pcm_name", streamInfo.pcm_name);
+ json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ);
+}
+
+// Binding initialization
+PUBLIC int AhlBindingInit()
+{
+
+ int errcount = 0;
+
+ // Initialize list of available sources/sinks using lower level services
+ errcount += EnumerateSources();
+ errcount += EnumerateSinks();
+
+ // TODO: Register for device changes from lower level services
+
+ // TODO: Parse high-level binding configuration file
+
+ // TODO: Perform other binding initialization tasks
+
+ AFB_DEBUG("Audio high-level Binding success errcount=%d", errcount);
+ return errcount;
+}
+
+PUBLIC void audiohlapi_get_sources(struct afb_req req)
+{
+ json_object *sourcesJ = NULL;
+ json_object *sourceJ = NULL;
+ json_object *queryJ = NULL;
+ AudioRoleT audioRole = AUDIOROLE_MAXVALUE;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s?i}", "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;
+ }
+
+ if (audioRole != AUDIOROLE_MAXVALUE)
+ {
+ AFB_DEBUG("Filtering according to specified audio role=%d", audioRole);
+ }
+
+ // Fake run-time data for test purposes
+ EndpointInfoT sources[3];
+ sources[0].endpoint_id = 0;
+ sources[0].type = 0;
+ sources[0].name = "Source0";
+ sources[1].endpoint_id = 1;
+ sources[1].type = 1;
+ sources[1].name = "Source1";
+ sources[2].endpoint_id = 2;
+ sources[2].type = 2;
+ sources[2].name = "Source2";
+
+ sourcesJ = json_object_new_array();
+ for ( unsigned int i = 0 ; i < 3; i++)
+ {
+ EndpointInfoStructToJSON(sourceJ, sources[i]);
+ json_object_array_add(sourcesJ, sourceJ);
+ }
+
+ afb_req_success(req, sourcesJ, "List of sources");
+}
+
+PUBLIC void audiohlapi_get_sinks(struct afb_req req)
+{
+ json_object *sinksJ = NULL;
+ json_object *sinkJ = NULL;
+ json_object *queryJ = NULL;
+ AudioRoleT audioRole = AUDIOROLE_MAXVALUE;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s?i}", "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;
+ }
+
+ if (audioRole != AUDIOROLE_MAXVALUE)
+ {
+ AFB_DEBUG("Filtering according to specified audio role=%d", audioRole);
+ }
+
+ // Fake run-time data for test purposes
+ EndpointInfoT sinks[3];
+ sinks[0].endpoint_id = 0;
+ sinks[0].type = 0;
+ sinks[0].name = "Sink0";
+ sinks[1].endpoint_id = 1;
+ sinks[1].type = 1;
+ sinks[1].name = "Sink1";
+ sinks[2].endpoint_id = 2;
+ sinks[2].type = 2;
+ sinks[2].name = "Sink2";
+
+ sinksJ = json_object_new_array();
+ for ( unsigned int i = 0 ; i < 3; i++)
+ {
+ EndpointInfoStructToJSON(sinkJ, sinks[i]);
+ json_object_array_add(sinksJ, sinkJ);
+ }
+
+ afb_req_success(req, sinksJ, "List of sinks");
+}
+
+PUBLIC void audiohlapi_stream_open(struct afb_req req)
+{
+ json_object *streamInfoJ = NULL;
+ StreamInfoT streamInfo;
+ json_object *queryJ = NULL;
+ AudioRoleT audioRole;
+ EndpointTypeT endpointType;
+ endpointID_t endpointID = UNDEFINED_ID;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s:i,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointType, "endpoint_id", &endpointID);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = audio_role:%d endpointType:%d endpointID:%d", audioRole,endpointType,endpointID);
+
+ if (endpointID == UNDEFINED_ID)
+ {
+ // TODO: Go through configuration and available devices to find best device for specified role
+ endpointID = 2;
+ }
+
+ // Fake run-time data for test purposes
+ streamInfo.stream_id = 12;
+ streamInfo.pcm_name = "plug:Entertainment";
+ streamInfo.endpoint_info.endpoint_id = endpointID;
+ streamInfo.endpoint_info.type = endpointType;
+ streamInfo.endpoint_info.name = "MainSpeakers";
+
+ StreamInfoStructToJSON(&streamInfoJ,streamInfo);
+
+ afb_req_success(req, streamInfoJ, "Stream info structure");
+}
+
+PUBLIC void audiohlapi_stream_close(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ streamID_t streamID = UNDEFINED_ID;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID);
+
+ // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing
+
+ afb_req_success(req, NULL, "Stream close completed");
+}
+
+// Routings
+PUBLIC void audiohlapi_get_available_routings(struct afb_req req)
+{
+ json_object *routingsJ;
+ json_object *routingJ;
+ json_object *queryJ = NULL;
+ AudioRoleT audioRole = AUDIOROLE_MAXVALUE;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s?i}", "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;
+ }
+
+ if (audioRole != AUDIOROLE_MAXVALUE)
+ {
+ AFB_DEBUG("Filtering according to specified audio role=%d", audioRole);
+ }
+
+ // Fake run-time data for test purposes
+ RoutingInfoT routings[3];
+ routings[0].source_id = 0;
+ routings[0].sink_id = 0;
+ routings[0].routing_id = 0;
+ routings[1].source_id = 1;
+ routings[1].sink_id = 1;
+ routings[1].routing_id = 1;
+ routings[2].source_id = 2;
+ routings[2].sink_id = 2;
+ routings[2].routing_id = 2;
+
+ routingsJ = json_object_new_array();
+ for (unsigned int i = 0; i < 3; i++)
+ {
+ RoutingInfoStructToJSON(routingJ, routings[i]);
+ json_object_array_add(routingsJ, routingJ);
+ }
+
+ afb_req_success(req, routingsJ, "List of available routings");
+}
+
+PUBLIC void audiohlapi_add_routing(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ AudioRoleT audioRole = AUDIOROLE_MAXVALUE;
+ routingID_t routingID = UNDEFINED_ID;
+ json_object *routingJ = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s?i}", "audio_role", &audioRole,"routing_id",routingID);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = audio_role:%d routing_id:%d", audioRole,routingID);
+
+ // Fake run-time data for test purposes
+ RoutingInfoT routingInfo;
+ routingInfo.routing_id = routingID;
+ routingInfo.source_id = 3;
+ routingInfo.sink_id = 4;
+
+ RoutingInfoStructToJSON(routingJ,routingInfo);
+
+ afb_req_success(req,routingJ, "Selected routing information");
+}
+
+PUBLIC void audiohlapi_remove_routing(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ routingID_t routingID = UNDEFINED_ID;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i}", "routing_id", &routingID);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = routing_id:%d", routingID);
+
+ // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing
+
+ afb_req_success(req, NULL, "Remove routing completed");
+}
+
+// Endpoints
+PUBLIC void audiohlapi_set_endpoint_volume(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = UNDEFINED_ID;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ char * volumeStr = NULL;
+ int rampTimeMS = 0;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"volume",&volumeStr,"ramp_time_ms",&rampTimeMS);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s ramp_time_ms: %d", endpointType,endpointID,volumeStr,rampTimeMS);
+
+ // TODO: Parse volume string to support increment/absolute/percent notation
+
+ afb_req_success(req, NULL, "Set volume completed");
+}
+
+PUBLIC void audiohlapi_get_endpoint_volume(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = UNDEFINED_ID;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ json_object *volumeJ;
+ double volume = 0.0;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID);
+
+ volume = 87.0; // TODO: Get actual volume value
+ volumeJ = json_object_new_double(volume);
+
+ afb_req_success(req, volumeJ, "Retrieved volume value");
+}
+
+PUBLIC void audiohlapi_set_endpoint_property(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = UNDEFINED_ID;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ char * propertyName = NULL;
+ char * propValueStr = NULL;
+ int rampTimeMS = 0;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr,"ramp_time_ms",&rampTimeMS);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s ramp_time_ms:%d", endpointType,endpointID,propertyName,propValueStr,rampTimeMS);
+
+ // TODO: Parse property value string to support increment/absolute/percent notation
+
+ afb_req_success(req, NULL, "Set property completed");
+}
+
+PUBLIC void audiohlapi_get_endpoint_property(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = UNDEFINED_ID;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ char * propertyName = NULL;
+ json_object *propertyValJ;
+ double value = 0.0;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s?i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName);
+
+ value = 93.0; // TODO: Get actual property value
+ propertyValJ = json_object_new_double(value);
+
+ afb_req_success(req, propertyValJ, "Retrieved property value");
+}
+
+PUBLIC void audiohlapi_set_endpoint_state(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = UNDEFINED_ID;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ char * stateName = NULL;
+ char * stateValue = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName,"state_value",&stateValue);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s state_value:%s", endpointType,endpointID,stateName,stateValue);
+
+ afb_req_success(req, NULL, "Set endpoint state completed");
+}
+
+PUBLIC void audiohlapi_get_endpoint_state(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ endpointID_t endpointID = UNDEFINED_ID;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ json_object *stateValJ;
+ char * stateName = NULL;
+ char * stateValue = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s", endpointType,endpointID,stateName);
+
+ stateValJ = json_object_new_string(stateValue);
+
+ afb_req_success(req, stateValJ, "Retrieved state value");
+}
+
+// Sound events
+PUBLIC void audiohlapi_post_sound_event(struct afb_req req)
+{
+ json_object *queryJ = NULL;
+ char * eventName = NULL;
+ char * mediaName = NULL;
+ AudioRoleT audioRole;
+ json_object *audioContext = NULL;
+
+ queryJ = afb_req_json(req);
+ int err = wrap_json_unpack(queryJ, "{s:s,s?i,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"audio_context",&audioContext);
+ if (err) {
+ afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ return;
+ }
+ AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%d media_name:%s", eventName,audioRole,mediaName);
+
+ // TODO: Post sound event to rendering services
+
+ afb_req_success(req, NULL, "Posted sound event");
+ }
+
+
+// Monitoring
+PUBLIC void audiohlapi_subscribe(struct afb_req req)
+{
+ // json_object *queryJ = NULL;
+
+ // queryJ = afb_req_json(req);
+
+ // TODO: Iterate through array length, parsing the string value to actual events
+ // TODO: Subscribe to appropriate events from other services
+
+ afb_req_success(req, NULL, "Subscribe to events finished");
+}
diff --git a/src/ahl-binding.h b/src/ahl-binding.h
new file mode 100644
index 0000000..7bd0653
--- /dev/null
+++ b/src/ahl-binding.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ * Author Francois Thibault <fthibault@audiokinetic.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef AHL_BINDING_INCLUDE
+#define AHL_BINDING_INCLUDE
+
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+#include <json-c/json.h>
+
+#ifndef PUBLIC
+ #define PUBLIC
+#endif
+
+#define UNDEFINED_ID -1
+
+typedef int endpointID_t;
+typedef int streamID_t;
+typedef int routingID_t;
+
+typedef enum EndpointType {
+ ENDPOINTTYPE_SOURCE = 0, // source devices
+ ENDPOINTTYPE_SINK, // sink devices
+ ENDPOINTTYPE_MAXVALUE // Enum count, keep at the end
+} EndpointTypeT;
+
+typedef enum AudioRole {
+ AUDIOROLE_WARNING = 0, // Safety-relevant or critical alerts/alarms
+ AUDIOROLE_GUIDANCE, // Important user information where user action is expected (e.g. navigation instruction)
+ AUDIOROLE_NOTIFICATION, // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...)
+ AUDIOROLE_COMMUNICATIONS, // Voice communications (e.g. handsfree, speech recognition)
+ AUDIOROLE_ENTERTAINMENT, // Multimedia content (e.g. tuner, media player, etc.)
+ AUDIOROLE_SYSTEM, // System level content
+ AUDIOROLE_DEFAULT, // No specific audio role (legacy applications)
+ AUDIOROLE_MAXVALUE // Enum count, keep at the end
+} AudioRoleT;
+
+typedef enum AudioDeviceClass {
+ AUDIODEVICE_SPEAKERMAIN = 0,
+ AUDIODEVICE_SPEAKERHEADREST,
+ AUDIODEVICE_HEADSET,
+ AUDIODEVICE_HEADPHONE,
+ AUDIODEVICE_LINEOUT,
+ AUDIODEVICE_LINEIN,
+ AUDIODEVICE_BLUETOOTH,
+ AUDIODEVICE_HANDSET,
+ AUDIODEVICE_HDMI,
+ AUDIODEVICE_USB,
+ AUDIODEVICE_TONES,
+ AUDIODEVICE_VOICE,
+ AUDIODEVICE_PHONELINK,
+ AUDIODEVICE_DEFAULT,
+ AUDIODEVICE_MAXVALUE // Enum count, keep at the end
+} AudioDeviceClassT;
+
+typedef struct EndpointInfo
+{
+ endpointID_t endpoint_id;
+ EndpointTypeT type;
+ char * name;
+ // TODO: Consider adding associated device class
+} EndpointInfoT;
+
+typedef struct StreamInfo {
+ streamID_t stream_id;
+ char * pcm_name;
+ EndpointInfoT endpoint_info;
+} StreamInfoT;
+
+typedef struct RoutingInfo {
+ routingID_t routing_id;
+ endpointID_t source_id;
+ endpointID_t sink_id;
+} RoutingInfoT;
+
+PUBLIC int AhlBindingInit();
+// ahl-deviceenum.c
+PUBLIC int EnumerateSources();
+PUBLIC int EnumerateSinks();
+
+#endif
diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c
new file mode 100644
index 0000000..6629de2
--- /dev/null
+++ b/src/ahl-deviceenum.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 "Audiokinetic Inc"
+ * Author Francois Thibault <fthibault@audiokinetic.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "ahl-binding.h"
+
+PUBLIC int EnumerateSources() {
+
+ // TODO: Use lower level services to build a list of available source devices
+
+ AFB_DEBUG ("Audio high-level - Enumerate sources done");
+ return 0;
+}
+
+PUBLIC int EnumerateSinks() {
+
+ // TODO: Use lower level services to build a list of available sink devices
+
+ AFB_DEBUG ("Audio high-level - Enumerate sinks done");
+ return 0;
+}
+