summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTai Vuong <tvuong@audiokinetic.com>2017-09-12 16:33:39 -0400
committerTai Vuong <tvuong@audiokinetic.com>2017-09-12 16:33:39 -0400
commitd69dc9732886074e9f400961b500e70d5c8305d7 (patch)
treeec5d4bb353814d442e3e4d84430a5343fee7b0c8 /src
parent9098015429bcc87a7b624ade16732848a9d90f67 (diff)
Pre-AudioWorkshop Demo
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt21
-rw-r--r--src/ahl-apidef.h336
-rw-r--r--src/ahl-apidef.json156
-rw-r--r--src/ahl-binding.c530
-rw-r--r--src/ahl-binding.h113
-rw-r--r--src/ahl-config.c148
-rw-r--r--src/ahl-deviceenum.c412
-rw-r--r--src/ahl-interface.h74
-rw-r--r--src/ahl-policy.c90
9 files changed, 1354 insertions, 526 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cbfe5dc..c0f6ca9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -27,11 +27,19 @@ macro(SET_TARGET_GENSKEL TARGET_NAME API_DEF_NAME)
endmacro(SET_TARGET_GENSKEL)
+FIND_PACKAGE(PkgConfig REQUIRED)
+PKG_CHECK_MODULES(GLIB_PKG REQUIRED glib-2.0)
+
+# get_cmake_property(_variableNames VARIABLES)
+# foreach (_variableName ${_variableNames})
+# message(STATUS "${_variableName}=${${_variableName}}")
+# endforeach()
+
# Add target to project dependency list
-PROJECT_TARGET_ADD(audiohighlevel-afb)
+PROJECT_TARGET_ADD(audiohighlevel)
# Define project Targets
- ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c)
+ ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c ahl-config.c ahl-policy.c)
# Generate API-v2 hat from OpenAPI json definition
SET_TARGET_GENSKEL(${TARGET_NAME} ahl-apidef)
@@ -44,9 +52,16 @@ PROJECT_TARGET_ADD(audiohighlevel-afb)
OUTPUT_NAME ${TARGET_NAME}
)
+ # Define target includes
+ TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME}
+ PUBLIC ${GLIB_PKG_INCLUDE_DIRS}
+ )
+
# Library dependencies (include updates automatically)
+ # Find package for GLIB does not seem to export
TARGET_LINK_LIBRARIES(${TARGET_NAME}
- afb-utilities
+ afb-utilities
+ ${GLIB_PKG_LIBRARIES}
${link_libraries}
)
diff --git a/src/ahl-apidef.h b/src/ahl-apidef.h
index fa78681..9a39470 100644
--- a/src/ahl-apidef.h
+++ b/src/ahl-apidef.h
@@ -22,140 +22,118 @@ static const char _afb_description_v2_audiohl[] =
"\"],\"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\""
+ "\",\"device_name\",\"device_uri\"],\"properties\":{\"endpoint_id\":{\"ty"
+ "pe\":\"int\"},\"type\":{\"type\":\"enum\"},\"device_name\":{\"type\":\"s"
+ "tring\"},\"device_uri_type\":{\"type\":\"string\"}}},\"stream_info\":{\""
+ "type\":\"object\",\"required\":[\"stream_id\",\"endpoint_info\"],\"prope"
+ "rties\":{\"stream_id\":{\"type\":\"int\"},\"$ref\":\"#/components/schema"
+ "s/endpoint_info\"}}},\"x-permissions\":{\"streamcontrol\":{\"permission\""
+ ":\"urn:AGL:permission:audio:public:streamcontrol\"},\"routingcontrol\":{"
+ "\"permission\":\"urn:AGL:permission:audio:public:routingcontrol\"},\"sou"
+ "ndevent\":{\"permission\":\"urn:AGL:permission:audio:public:soundevent\""
+ "}},\"responses\":{\"200\":{\"description\":\"A complex object array resp"
+ "onse\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo"
+ "nents/schemas/afb-reply\"}}}},\"400\":{\"description\":\"Invalid argumen"
+ "ts\"}}},\"paths\":{\"/get_sources\":{\"description\":\"Retrieve array of"
+ " available audio sources\",\"get\":{\"parameters\":[{\"in\":\"query\",\""
+ "name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"}"
+ "}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"res"
+ "ponse\":{\"description\":\"Array of endpoint info structures\",\"type\":"
+ "\"array\",\"items\":{\"$ref\":\"#/components/schemas/endpoint_info\"}}},"
+ "\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_sinks\":{\"d"
+ "escription\":\"Retrieve array of available audio sinks\",\"get\":{\"para"
+ "meters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\""
+ "schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/co"
+ "mponents/responses/200\",\"response\":{\"description\":\"Array of endpoi"
+ "nt info structures\",\"type\":\"array\",\"items\":{\"$ref\":\"#/componen"
+ "ts/schemas/endpoint_info\"}}},\"400\":{\"$ref\":\"#/components/responses"
+ "/400\"}}}},\"/stream_open\":{\"description\":\"Request opening a stream\""
+ ",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/strea"
+ "mcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\""
+ "required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"na"
+ "me\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}}"
+ ",{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":false,\"schema\""
+ ":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/re"
+ "sponses/200\",\"response\":{\"description\":\"Stream information structu"
+ "re\",\"$ref\":\"#/components/schemas/stream_info\"}},\"400\":{\"$ref\":\""
+ "#/components/responses/400\"}}}},\"/stream_close\":{\"description\":\"Re"
+ "quest closing a stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#/compon"
+ "ents/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\",\""
+ "name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\""
+ "responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{"
+ "\"$ref\":\"#/components/responses/400\"}}}},\"/set_volume\":{\"descripti"
+ "on\":\"Set volume\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/"
+ "x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\""
+ ":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\""
+ "in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"schema\":{\""
+ "type\":\"int\"}},{\"in\":\"query\",\"name\":\"volume\",\"required\":true"
+ ",\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"ramp_tim"
+ "e_ms\",\"required\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":"
+ "{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#"
+ "/components/responses/400\"}}}},\"/get_volume\":{\"description\":\"Get v"
+ "olume\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_t"
+ "ype\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\""
+ ",\"name\":\"endpoint_id\",\"required\":true,\"schema\":{\"type\":\"int\""
"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"re"
- "sponse\":{\"description\":\"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\":{\""
+ "sponse\":{\"description\":\"Endpoint volume value\",\"type\":\"double\"}"
+ "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_property\""
+ ":{\"description\":\"Set property value\",\"get\":{\"x-permissions\":{\"$"
+ "ref\":\"#/components/x-permissions/streamcontrol\"},\"parameters\":[{\"i"
+ "n\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\""
"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\""
- ":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\":"
+ ":false,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"prope"
+ "rty_name\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\""
+ "query\",\"name\":\"value\",\"required\":true,\"schema\":{\"type\":\"stri"
+ "ng\"}},{\"in\":\"query\",\"name\":\"ramp_time_ms\",\"required\":false,\""
+ "schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compo"
+ "nents/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}"
+ "}}},\"/get_property\":{\"description\":\"Get property value\",\"get\":{\""
+ "parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":"
+ "true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoi"
+ "nt_id\",\"required\":false,\"schema\":{\"type\":\"int\"}},{\"in\":\"quer"
+ "y\",\"name\":\"property_name\",\"required\":true,\"schema\":{\"type\":\""
+ "string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/20"
+ "0\",\"response\":{\"description\":\"Property value\",\"type\":\"double\""
+ "}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_state\":{"
+ "\"description\":\"Set state\",\"get\":{\"x-permissions\":{\"$ref\":\"#/c"
+ "omponents/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query"
+ "\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"e"
+ "num\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"s"
+ "chema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state_name\",\""
+ "required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"na"
+ "me\":\"state_value\",\"required\":true,\"schema\":{\"type\":\"string\"}}"
+ "],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400"
+ "\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_state\":{\"descri"
+ "ption\":\"Get state value\",\"get\":{\"parameters\":[{\"in\":\"query\",\""
+ "name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum\""
+ "}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"schema"
+ "\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state_name\",\"requi"
+ "red\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\""
+ "$ref\":\"#/components/responses/200\",\"response\":{\"description\":\"En"
+ "dpoint state value\",\"type\":\"string\"}},\"400\":{\"$ref\":\"#/compone"
+ "nts/responses/400\"}}}},\"/post_sound_event\":{\"description\":\"Post so"
+ "und event\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permis"
+ "sions/soundevent\"},\"parameters\":[{\"in\":\"query\",\"name\":\"event_n"
+ "ame\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"quer"
+ "y\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"str"
+ "ing\"}},{\"in\":\"query\",\"name\":\"media_name\",\"required\":false,\"s"
+ "chema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"audio_contex"
+ "t\",\"required\":false,\"schema\":{\"type\":\"object\"}}],\"responses\":"
"{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#"
- "/components/responses/400\"}}}}}}"
+ "/components/responses/400\"}}}},\"/subscribe\":{\"description\":\"Subscr"
+ "ibe to audio high level events\",\"get\":{\"parameters\":[{\"in\":\"quer"
+ "y\",\"name\":\"events\",\"required\":true,\"schema\":{\"type\":\"array\""
+ ",\"items\":{\"type\":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#"
+ "/components/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/"
+ "400\"}}}},\"/unsubscribe\":{\"description\":\"Unubscribe to audio high l"
+ "evel events\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"even"
+ "ts\",\"required\":true,\"schema\":{\"type\":\"array\",\"items\":{\"type\""
+ ":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/components/response"
+ "s/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}}}}"
;
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" }
};
@@ -163,131 +141,121 @@ static const struct afb_auth _afb_auths_v2_audiohl[] = {
void audiohlapi_get_sinks(struct afb_req req);
void audiohlapi_stream_open(struct afb_req req);
void audiohlapi_stream_close(struct afb_req req);
- void audiohlapi_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_set_volume(struct afb_req req);
+ void audiohlapi_get_volume(struct afb_req req);
+ void audiohlapi_set_property(struct afb_req req);
+ void audiohlapi_get_property(struct afb_req req);
+ void audiohlapi_set_state(struct afb_req req);
+ void audiohlapi_get_state(struct afb_req req);
void audiohlapi_post_sound_event(struct afb_req req);
void audiohlapi_subscribe(struct afb_req req);
+ void audiohlapi_unsubscribe(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,
+ .info = "Retrieve array of available audio sources",
.session = AFB_SESSION_NONE_V2
},
{
.verb = "get_sinks",
.callback = audiohlapi_get_sinks,
.auth = NULL,
- .info = NULL,
+ .info = "Retrieve array of available audio sinks",
.session = AFB_SESSION_NONE_V2
},
{
.verb = "stream_open",
.callback = audiohlapi_stream_open,
.auth = &_afb_auths_v2_audiohl[0],
- .info = NULL,
+ .info = "Request opening a stream",
.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,
+ .info = "Request closing a stream",
.session = AFB_SESSION_NONE_V2
},
{
- .verb = "set_endpoint_volume",
- .callback = audiohlapi_set_endpoint_volume,
+ .verb = "set_volume",
+ .callback = audiohlapi_set_volume,
.auth = &_afb_auths_v2_audiohl[0],
- .info = NULL,
+ .info = "Set volume",
.session = AFB_SESSION_NONE_V2
},
{
- .verb = "get_endpoint_volume",
- .callback = audiohlapi_get_endpoint_volume,
+ .verb = "get_volume",
+ .callback = audiohlapi_get_volume,
.auth = NULL,
- .info = NULL,
+ .info = "Get volume",
.session = AFB_SESSION_NONE_V2
},
{
- .verb = "set_endpoint_property",
- .callback = audiohlapi_set_endpoint_property,
+ .verb = "set_property",
+ .callback = audiohlapi_set_property,
.auth = &_afb_auths_v2_audiohl[0],
- .info = NULL,
+ .info = "Set property value",
.session = AFB_SESSION_NONE_V2
},
{
- .verb = "get_endpoint_property",
- .callback = audiohlapi_get_endpoint_property,
+ .verb = "get_property",
+ .callback = audiohlapi_get_property,
.auth = NULL,
- .info = NULL,
+ .info = "Get property value",
.session = AFB_SESSION_NONE_V2
},
{
- .verb = "set_endpoint_state",
- .callback = audiohlapi_set_endpoint_state,
+ .verb = "set_state",
+ .callback = audiohlapi_set_state,
.auth = &_afb_auths_v2_audiohl[0],
- .info = NULL,
+ .info = "Set state",
.session = AFB_SESSION_NONE_V2
},
{
- .verb = "get_endpoint_state",
- .callback = audiohlapi_get_endpoint_state,
+ .verb = "get_state",
+ .callback = audiohlapi_get_state,
.auth = NULL,
- .info = NULL,
+ .info = "Get state value",
.session = AFB_SESSION_NONE_V2
},
{
.verb = "post_sound_event",
.callback = audiohlapi_post_sound_event,
- .auth = &_afb_auths_v2_audiohl[2],
- .info = NULL,
+ .auth = &_afb_auths_v2_audiohl[1],
+ .info = "Post sound event",
.session = AFB_SESSION_NONE_V2
},
{
.verb = "subscribe",
.callback = audiohlapi_subscribe,
.auth = NULL,
- .info = NULL,
+ .info = "Subscribe to audio high level events",
.session = AFB_SESSION_NONE_V2
},
- { .verb = NULL }
+ {
+ .verb = "unsubscribe",
+ .callback = audiohlapi_unsubscribe,
+ .auth = NULL,
+ .info = "Unubscribe 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 = "audiohl",
.specification = _afb_description_v2_audiohl,
- .info = NULL,
+ .info = "Audio high level API for AGL applications",
.verbs = _afb_verbs_v2_audiohl,
.preinit = NULL,
.init = AhlBindingInit,
diff --git a/src/ahl-apidef.json b/src/ahl-apidef.json
index cbe25a9..45067ea 100644
--- a/src/ahl-apidef.json
+++ b/src/ahl-apidef.json
@@ -97,30 +97,21 @@
},
"endpoint_info": {
"type": "object",
- "required": [ "endpoint_id", "type", "name" ],
+ "required": [ "endpoint_id", "type", "device_name", "device_uri" ],
"properties": {
"endpoint_id": { "type": "int" },
"type": { "type": "enum" },
- "name": { "type": "string" }
+ "device_name": { "type": "string" },
+ "device_uri_type": { "type": "string" }
}
},
"stream_info": {
"type": "object",
- "required": [ "stream_id", "pcm_name", "name" ],
+ "required": [ "stream_id", "endpoint_info" ],
"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": {
@@ -158,8 +149,8 @@
{
"in": "query",
"name": "audio_role",
- "required": false,
- "schema": { "type": "enum" }
+ "required": true,
+ "schema": { "type": "string" }
}
],
"responses": {
@@ -182,8 +173,8 @@
{
"in": "query",
"name": "audio_role",
- "required": false,
- "schema": { "type": "enum" }
+ "required": true,
+ "schema": { "type": "string" }
}
],
"responses": {
@@ -209,7 +200,7 @@
"in": "query",
"name": "audio_role",
"required": true,
- "schema": { "type": "enum" }
+ "schema": { "type": "string" }
},
{
"in": "query",
@@ -254,83 +245,8 @@
}
}
},
- "/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",
+ "/set_volume": {
+ "description": "Set volume",
"get": {
"x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" },
"parameters": [
@@ -365,8 +281,8 @@
}
}
},
- "/get_endpoint_volume": {
- "description": "Get endpoint volume",
+ "/get_volume": {
+ "description": "Get volume",
"get": {
"parameters": [
{
@@ -394,8 +310,8 @@
}
}
},
- "/set_endpoint_property": {
- "description": "Set endpoint property value",
+ "/set_property": {
+ "description": "Set property value",
"get": {
"x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" },
"parameters": [
@@ -436,8 +352,8 @@
}
}
},
- "/get_endpoint_property": {
- "description": "Get endpoint property value",
+ "/get_property": {
+ "description": "Get property value",
"get": {
"parameters": [
{
@@ -471,8 +387,8 @@
}
}
},
- "/set_endpoint_state": {
- "description": "Set endpoint state",
+ "/set_state": {
+ "description": "Set state",
"get": {
"x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" },
"parameters": [
@@ -507,8 +423,8 @@
}
}
},
- "/get_endpoint_state": {
- "description": "Get endpoint state value",
+ "/get_state": {
+ "description": "Get state value",
"get": {
"parameters": [
{
@@ -556,8 +472,8 @@
{
"in": "query",
"name": "audio_role",
- "required": false,
- "schema": { "type": "enum" }
+ "required": true,
+ "schema": { "type": "string" }
},
{
"in": "query",
@@ -596,6 +512,32 @@
"400": { "$ref": "#/components/responses/400" }
}
}
+ },
+ "/unsubscribe": {
+ "description": "Unubscribe 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
index 99236bd..4b075c0 100644
--- a/src/ahl-binding.c
+++ b/src/ahl-binding.c
@@ -18,87 +18,167 @@
#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
+// Global high-level binding context
+AHLCtxT g_AHLCtx;
+
+// Workshop development topics:
+// - Associating streamID with a client id of afb. Has to be done with sessions?
+// - Declaring dependencies on other binding services (examples)
+// - json_object_put to free JSON objects? potential leaks currently
+// - no events on new HAL registered to alsacore?
+// - unregistering event subscription examples?
+// - discussion how to use property and event system to dispatch external parameterization (e.g. Wwise/Fiberdyne)
+// - discussion where to best isolate policy (controller or HLB plugin)
+// - Other HLB attributes to pass through (e.g. interrupted behavior)
+// - DBScale TLV warning
+// - GLib (internal) dependency
+// - HAL registration dependency (initialization order)
+// - Binding startup arguments for config file path
+// - Can we use the same HAL for different card numbers?
+// - Example use of volume ramping in HAL?
+// - Binding termination function
+// - AGL persistence framework?
+// - How to provide API services with config.xml (secrets and all)
-// Helper macros/func for packaging JSON objects from C structures
+// 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);
-
+ wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:s,s:i}",\
+ "endpoint_id", __ENDPOINTINFOSTRUCT__.endpointID, \
+ "endpoint_type", __ENDPOINTINFOSTRUCT__.type, \
+ "device_name", __ENDPOINTINFOSTRUCT__.deviceName, \
+ "device_uri_type", __ENDPOINTINFOSTRUCT__.deviceURIType);
+
static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT streamInfo)
{
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);
+ EndpointInfoStructToJSON(endpointInfoJ,streamInfo.endpointInfo);
+ wrap_json_pack(streamInfoJ, "{s:i}", "stream_id", streamInfo.streamID);
json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ);
}
+static streamID_t CreateNewStreamID()
+{
+ streamID_t newID = g_AHLCtx.nextStreamID;
+ g_AHLCtx.nextStreamID++;
+ return newID;
+}
+
+static int FindRoleIndex( const char * in_pAudioRole)
+{
+ int index = -1; // Not found
+ for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++)
+ {
+ GString gs = g_array_index( g_AHLCtx.policyCtx.pAudioRoles, GString, i );
+ if ( strcasecmp(gs.str,in_pAudioRole) == 0 )
+ index = i;
+ }
+ return index;
+}
+
+static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT in_endpointType)
+{
+ EndpointInfoT * pEndpointInfo = NULL;
+ for (int i = 0; i < g_AHLCtx.policyCtx.iNumberRoles; i++)
+ {
+ GArray * pRoleDeviceArray = NULL;
+ if (in_endpointType == ENDPOINTTYPE_SOURCE){
+ pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, i );
+ }
+ else{
+ pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, i );
+ }
+ for (int j = 0; j < pRoleDeviceArray->len; j++) {
+ EndpointInfoT * pCurEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j);
+ if (pCurEndpointInfo->endpointID == in_endpointID) {
+ pEndpointInfo = pCurEndpointInfo;
+ break;
+ }
+ }
+ }
+ return pEndpointInfo;
+}
+
// Binding initialization
PUBLIC int AhlBindingInit()
{
-
int errcount = 0;
+ int err = 0;
+
+ // This API uses services from low level audio
+ err = afb_daemon_require_api_v2("alsacore",1) ;
+ if( err != 0 )
+ {
+ AFB_ERROR("Audio high level API requires alsacore API to be available");
+ return 1;
+ }
+
+ // Parse high-level binding JSON configuration file (will build device lists)
+ errcount += ParseHLBConfig();
- // Initialize list of available sources/sinks using lower level services
- errcount += EnumerateSources();
- errcount += EnumerateSinks();
+ // Initialize list of active streams
+ g_AHLCtx.policyCtx.pActiveStreams = g_array_new(FALSE,TRUE,sizeof(StreamInfoT));
- // TODO: Register for device changes from lower level services
+ // TODO: Register for device changes ALSA low level audio service
- // TODO: Parse high-level binding configuration file
+ // TODO: Perform other binding initialization tasks (e.g. broadcast service ready event?)
- // TODO: Perform other binding initialization tasks
+ // TODO: Use AGL persistence framework to retrieve and set inital state/volumes/properties
AFB_DEBUG("Audio high-level Binding success errcount=%d", errcount);
return errcount;
}
+// TODO: AhlBindingTerm()?
+// // TODO: Use AGL persistence framework to retrieve and set inital state/volumes
+
+// TODO: OnEventFunction
+// if ALSA device availability change -> update source / sink list
+// Call policy to attempt to assign role based on information (e.g. device name)
+// Policy should also determine priority (insert device in right spot in the list)
+
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;
-
+ char * audioRole = NULL;
+
queryJ = afb_req_json(req);
- int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole);
+ 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;
}
- 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++)
+
+ AFB_DEBUG("Filtering devices according to specified audio role=%s", audioRole);
+ // Check that the role requested exists in current configuration
+ int roleIndex = FindRoleIndex(audioRole);
+ if ( roleIndex == -1)
{
- EndpointInfoStructToJSON(sourceJ, sources[i]);
- json_object_array_add(sourcesJ, sourceJ);
+ afb_req_fail_f(req, "Invalid arguments", "Requested audio role does not exist in current configuration -> %s", json_object_get_string(queryJ));
+ return;
}
+ // List device only for current audio role and device type (pick the role device list)
+ int iRoleIndex = FindRoleIndex(audioRole);
+ if (iRoleIndex >= 0)
+ {
+ GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, iRoleIndex );
+ int iNumberDevices = pRoleSourceDeviceArray->len;
+ for ( int j = 0 ; j < iNumberDevices; j++)
+ {
+ EndpointInfoT sourceInfo = g_array_index(pRoleSourceDeviceArray,EndpointInfoT,j);
+ EndpointInfoStructToJSON(sourceJ, sourceInfo);
+ json_object_array_add(sourcesJ, sourceJ);
+ }
+ }
afb_req_success(req, sourcesJ, "List of sources");
}
@@ -108,38 +188,38 @@ 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;
+ char * audioRole = NULL;
queryJ = afb_req_json(req);
- int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole);
+ 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;
}
- 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++)
+
+ AFB_DEBUG("Filtering devices according to specified audio role=%s", audioRole);
+ // Check that the role requested exists in current configuration
+ int roleIndex = FindRoleIndex(audioRole);
+ if ( roleIndex == -1)
{
- EndpointInfoStructToJSON(sinkJ, sinks[i]);
- json_object_array_add(sinksJ, sinkJ);
+ afb_req_fail_f(req, "Invalid arguments", "Requested audio role does not exist in current configuration -> %s", json_object_get_string(queryJ));
+ return;
}
+ // List device only for current audio role and device type (pick the role device list)
+ int iRoleIndex = FindRoleIndex(audioRole);
+ if (iRoleIndex >= 0)
+ {
+ GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, iRoleIndex );
+ int iNumberDevices = pRoleSinkDeviceArray->len;
+ for ( int j = 0 ; j < iNumberDevices; j++)
+ {
+ EndpointInfoT sinkInfo = g_array_index(pRoleSinkDeviceArray,EndpointInfoT,j);
+ EndpointInfoStructToJSON(sinkJ, sinkInfo);
+ json_object_array_add(sinksJ, sinkJ);
+ }
+ }
afb_req_success(req, sinksJ, "List of sinks");
}
@@ -149,30 +229,75 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req)
json_object *streamInfoJ = NULL;
StreamInfoT streamInfo;
json_object *queryJ = NULL;
- AudioRoleT audioRole;
+ char * audioRole = NULL;
EndpointTypeT endpointType;
endpointID_t endpointID = UNDEFINED_ID;
+ int policyAllowed = 0;
+ EndpointInfoT endpointInfo;
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);
+ int err = wrap_json_unpack(queryJ, "{s:s,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);
+ AFB_DEBUG("Parsed input arguments = audio_role:%s endpoint_type:%d endpoint_id:%d", audioRole,endpointType,endpointID);
+
+ int iRoleIndex = FindRoleIndex(audioRole);
+ if (iRoleIndex < 0) {
+ afb_req_fail_f(req, "Invalid audio role", "Audio role was not found in configuration -> %s",audioRole);
+ return;
+ }
+
+ GArray * pRoleDeviceArray = NULL;
+ if (endpointType == ENDPOINTTYPE_SOURCE){
+ pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, iRoleIndex );
+ }
+ else{
+ pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, iRoleIndex );
+ }
+ if (pRoleDeviceArray->len == 0) {
+ afb_req_fail_f(req, "No available devices", "No available devices for role:%s and device type:%d",audioRole,endpointType);
+ return;
+ }
if (endpointID == UNDEFINED_ID)
{
- // TODO: Go through configuration and available devices to find best device for specified role
- endpointID = 2;
+ // Assign a device based on configuration priority (first in the list for requested role and endpoint type)
+ endpointInfo = g_array_index(pRoleDeviceArray,EndpointInfoT,0);
+ }
+ else{
+ // Find specified endpoint ID in list of devices
+ int iNumberDevices = pRoleDeviceArray->len;
+ int iEndpointFound = 0;
+ for ( int j = 0 ; j < iNumberDevices; j++)
+ {
+ endpointInfo = g_array_index(pRoleDeviceArray,EndpointInfoT,j);
+ if (endpointInfo.endpointID == endpointID) {
+ iEndpointFound = 1;
+ break;
+ }
+ }
+ if (iEndpointFound == 0) {
+ 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;
+ }
}
- // 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";
+ // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions
+ policyAllowed = Policy_OpenStream(audioRole, endpointType, endpointID);
+ if (policyAllowed == AUDIOHL_POLICY_REJECT)
+ {
+ afb_req_fail(req, "Audio policy violation", "Open stream not allowed in current context");
+ return;
+ }
+
+ // Create stream
+ streamInfo.streamID = CreateNewStreamID(); // create new ID
+ streamInfo.endpointInfo = endpointInfo;
+
+ // Push stream on active stream list
+ g_array_append_val( g_AHLCtx.policyCtx.pActiveStreams, streamInfo );
StreamInfoStructToJSON(&streamInfoJ,streamInfo);
@@ -194,124 +319,94 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req)
// TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing
+ // Remove from active stream list (if present)
+ int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len;
+ int iStreamFound = 0;
+ for ( int i = 0; i < iNumActiveStreams ; i++ ) {
+ StreamInfoT streamInfo = g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i);
+ if (streamInfo.streamID == streamID){
+ g_array_remove_index(g_AHLCtx.policyCtx.pActiveStreams,i);
+ iStreamFound = 1;
+ break;
+ }
+ }
+
+ if (iStreamFound == 0) {
+ afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID);
+ return;
+ }
+
afb_req_success(req, NULL, "Stream close completed");
}
-// Routings
-PUBLIC void audiohlapi_get_available_routings(struct afb_req req)
+// Endpoints
+PUBLIC void audiohlapi_set_volume(struct afb_req req)
{
- json_object *routingsJ;
- json_object *routingJ;
json_object *queryJ = NULL;
- AudioRoleT audioRole = AUDIOROLE_MAXVALUE;
+ endpointID_t endpointID = UNDEFINED_ID;
+ EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
+ char * volumeStr = NULL;
+ int rampTimeMS = 0;
+ int policyAllowed = 0;
queryJ = afb_req_json(req);
- int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole);
+ 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
+ int vol = atoi(volumeStr);
- if (audioRole != AUDIOROLE_MAXVALUE)
+ policyAllowed = Policy_SetVolume(endpointType, endpointID, volumeStr, rampTimeMS); // TODO: Potentially retrieve modified value by policy (e.g. volume limit)
+ if (!policyAllowed)
{
- 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_fail(req, "Audio policy violation", "Set volume not allowed in current context");
+ return;
}
- 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));
+ // TODO: Cache during device enumeration for efficiency
+ 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;
}
- 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");
-}
+ // Using audio role available from endpoint to target the right HAL control (build string based on convention)
+ char halControlName[AUDIOHL_MAXHALCONTROLNAME_LENGTH];
+ strncpy(halControlName,pEndpointInfo->audioRole,AUDIOHL_MAXHALCONTROLNAME_LENGTH);
+ strcat(halControlName,"_Ramp"); // Or _Vol for direct control (no ramping)
-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);
+ // Set endpoint volume using HAL services (leveraging ramps etc.)
+ json_object *j_response, *j_query = NULL;
+
+ // Package query
+ err = wrap_json_pack(&j_query,"{s:s,s:i}","label",halControlName, "val",vol);
if (err) {
- afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ afb_req_fail_f(req, "Invalid query for HAL ctlset", "Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query));
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);
+ // Set the volume using the HAL
+ err = afb_service_call_sync(pEndpointInfo->halAPIName, "ctlset", j_query, &j_response);
if (err) {
- afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
+ afb_req_fail_f(req, "HAL ctlset failure", "Could not ctlset on HAL: %s",pEndpointInfo->halAPIName);
return;
}
- AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s ramp_time_ms: %d", endpointType,endpointID,volumeStr,rampTimeMS);
-
- // TODO: Parse volume string to support increment/absolute/percent notation
-
+ AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response));
+
afb_req_success(req, NULL, "Set volume completed");
}
-PUBLIC void audiohlapi_get_endpoint_volume(struct afb_req req)
+PUBLIC void audiohlapi_get_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);
@@ -321,13 +416,55 @@ PUBLIC void audiohlapi_get_endpoint_volume(struct afb_req req)
}
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);
+ // TODO: Cache during device enumeration for efficiency
+ 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;
+ }
+
+ // Using audio role available from endpoint to target the right HAL control (build string based on convention)
+ char halControlName[AUDIOHL_MAXHALCONTROLNAME_LENGTH];
+ strncpy(halControlName,pEndpointInfo->audioRole,AUDIOHL_MAXHALCONTROLNAME_LENGTH);
+ strcat(halControlName,"_Vol"); // Use current value, not ramp target
+
+ // Set endpoint volume using HAL services (leveraging ramps etc.)
+ json_object *j_response, *j_query = NULL;
+
+ // Package query
+ err = wrap_json_pack(&j_query,"{s:s}","label",halControlName);
+ if (err) {
+ afb_req_fail_f(req, "Invalid query for HAL ctlget", "Invalid query for HAL ctlget: %s",json_object_to_json_string(j_query));
+ return;
+ }
+
+ // Set the volume using the HAL
+ err = afb_service_call_sync(pEndpointInfo->halAPIName, "ctlget", j_query, &j_response);
+ if (err) {
+ afb_req_fail_f(req, "HAL ctlget failure", "Could not ctlget on HAL: %s",pEndpointInfo->halAPIName);
+ return;
+ }
+ AFB_INFO("HAL ctlget response=%s", json_object_to_json_string(j_response));
+
+ // Parse response
+ json_object * jRespObj = NULL;
+ json_object_object_get_ex(j_response, "response", &jRespObj);
+ json_object * jVal = NULL;
+ json_object_object_get_ex(jRespObj, "val", &jVal);
+ int val1 = 0, val2 = 0; // Why 2 values?
+ err = wrap_json_unpack(jVal, "[ii]", &val1, &val2);
+ if (err) {
+ afb_req_fail_f(req,"Volume retrieve failed", "Could not retrieve volume value -> %s", json_object_get_string(jVal));
+ return;
+ }
+
+ volumeJ = json_object_new_double((double)val1);
afb_req_success(req, volumeJ, "Retrieved volume value");
}
-PUBLIC void audiohlapi_set_endpoint_property(struct afb_req req)
+PUBLIC void audiohlapi_set_property(struct afb_req req)
{
json_object *queryJ = NULL;
endpointID_t endpointID = UNDEFINED_ID;
@@ -335,6 +472,7 @@ PUBLIC void audiohlapi_set_endpoint_property(struct afb_req req)
char * propertyName = NULL;
char * propValueStr = NULL;
int rampTimeMS = 0;
+ int policyAllowed = 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);
@@ -346,10 +484,23 @@ PUBLIC void audiohlapi_set_endpoint_property(struct afb_req req)
// TODO: Parse property value string to support increment/absolute/percent notation
+ // Call policy to allow custom policy actions in current context
+ policyAllowed = Policy_SetProperty(endpointType, endpointID, propertyName, propValueStr, rampTimeMS); // TODO: Potentially retrieve modified value by policy (e.g. parameter limit)
+ if (!policyAllowed)
+ {
+ afb_req_fail(req, "Audio policy violation", "Set endpoint property not allowed in current context");
+ return;
+ }
+
+ // TODO: Set endpoint property (dispatch on right service target)
+ // Property targets (Internal,Wwise,Fiberdyne) (e.g. Wwise.ParamX, Fiberdyne.ParamY, Internal.ParamZ)
+ // Cache value in property list
+ // TBD
+
afb_req_success(req, NULL, "Set property completed");
}
-PUBLIC void audiohlapi_get_endpoint_property(struct afb_req req)
+PUBLIC void audiohlapi_get_property(struct afb_req req)
{
json_object *queryJ = NULL;
endpointID_t endpointID = UNDEFINED_ID;
@@ -366,19 +517,22 @@ PUBLIC void audiohlapi_get_endpoint_property(struct afb_req req)
}
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
+ // TODO: Retrieve cached property value
+
+ 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)
+PUBLIC void audiohlapi_set_state(struct afb_req req)
{
json_object *queryJ = NULL;
endpointID_t endpointID = UNDEFINED_ID;
EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE;
char * stateName = NULL;
char * stateValue = NULL;
+ int policyAllowed = 0;
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);
@@ -388,10 +542,28 @@ PUBLIC void audiohlapi_set_endpoint_state(struct afb_req req)
}
AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s state_value:%s", endpointType,endpointID,stateName,stateValue);
+ // Check that state provided is within list of known state for this config
+ char * pDefaultStateValue = g_hash_table_lookup(g_AHLCtx.pDefaultStatesHT, stateName);
+ if (pDefaultStateValue == NULL)
+ {
+ afb_req_fail_f(req, "Invalid arguments", "State provided is not known to configuration query=%s", stateName);
+ return;
+ }
+
+ // Call policy to allow custom policy actions in current context
+ policyAllowed = Policy_SetState(endpointType, endpointID, stateName, stateValue); // TODO: Potentially retrieve modified value by policy (e.g. state change)
+ if (!policyAllowed)
+ {
+ afb_req_fail(req, "Audio policy violation", "Set endpoint state not allowed in current context");
+ return;
+ }
+
+ // Change the state of the endpoint as requested
+
afb_req_success(req, NULL, "Set endpoint state completed");
}
-PUBLIC void audiohlapi_get_endpoint_state(struct afb_req req)
+PUBLIC void audiohlapi_get_state(struct afb_req req)
{
json_object *queryJ = NULL;
endpointID_t endpointID = UNDEFINED_ID;
@@ -409,6 +581,7 @@ PUBLIC void audiohlapi_get_endpoint_state(struct afb_req req)
AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s", endpointType,endpointID,stateName);
stateValJ = json_object_new_string(stateValue);
+ // return cached value
afb_req_success(req, stateValJ, "Retrieved state value");
}
@@ -419,18 +592,27 @@ PUBLIC void audiohlapi_post_sound_event(struct afb_req req)
json_object *queryJ = NULL;
char * eventName = NULL;
char * mediaName = NULL;
- AudioRoleT audioRole;
+ char * audioRole = NULL;
json_object *audioContext = NULL;
+ int policyAllowed = 0;
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);
+ int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"audio_context",&audioContext);
if (err) {
afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ));
return;
}
- AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%d media_name:%s", eventName,audioRole,mediaName);
+ AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%s media_name:%s", eventName,audioRole,mediaName);
- // TODO: Post sound event to rendering services
+ // Call policy to allow custom policy actions in current context (e.g. cancel playback)
+ policyAllowed = Policy_PostSoundEvent(eventName, audioRole, mediaName, (void*)audioContext); // TODO: Potentially retrieve modified value by policy (e.g. change media)
+ if (!policyAllowed)
+ {
+ afb_req_fail(req, "Audio policy violation", "Post sound event not allowed in current context");
+ return;
+ }
+
+ // TODO: Post sound event to rendering services (e.g. gstreamer file player wrapper or simple ALSA player)
afb_req_success(req, NULL, "Posted sound event");
}
@@ -448,3 +630,15 @@ PUBLIC void audiohlapi_subscribe(struct afb_req req)
afb_req_success(req, NULL, "Subscribe to events finished");
}
+
+PUBLIC void audiohlapi_unsubscribe(struct afb_req req)
+{
+ // json_object *queryJ = NULL;
+
+ // queryJ = afb_req_json(req);
+
+ // TODO: Iterate through array length, parsing the string value to actual events
+ // TODO: Unsubscribe to appropriate events from other services
+
+ afb_req_success(req, NULL, "Subscribe to events finished");
+}
diff --git a/src/ahl-binding.h b/src/ahl-binding.h
index 7bd0653..2422963 100644
--- a/src/ahl-binding.h
+++ b/src/ahl-binding.h
@@ -15,82 +15,85 @@
* 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>
+#include <glib.h>
+
+#include "ahl-interface.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;
+/////////////// Binding private information //////////////////
-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;
+#define AUDIOHL_MAX_DEVICE_URI_LENGTH 256
+#define AUDIOHL_MAX_DEVICE_NAME_LENGTH 256
+#define AUDIOHL_MAX_AUDIOROLE_LENGTH 128
+#define AUDIOHL_MAX_HALAPINAME_LENGTH 64
+#define AUDIOHL_POLICY_ACCEPT 1
+#define AUDIOHL_POLICY_REJECT 0
typedef struct EndpointInfo
{
- endpointID_t endpoint_id;
- EndpointTypeT type;
- char * name;
- // TODO: Consider adding associated device class
+ endpointID_t endpointID; // Unique endpoint ID (per type)
+ EndpointTypeT type; // Source or sink device
+ char deviceName[AUDIOHL_MAX_DEVICE_NAME_LENGTH]; // Device name for applications to display
+ char deviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // Associated URI
+ DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information)
+ char audioRole[AUDIOHL_MAX_AUDIOROLE_LENGTH]; // Audio role that registered this endpoint -> private
+ char halAPIName[AUDIOHL_MAX_AUDIOROLE_LENGTH]; // HAL associated with the device (for volume control) -> private
+ int cardNum; // HW card number -> private
+ int deviceNum; // HW device number -> private
+ int subDeviceNum; // HW sub device number -> private
+ // Cached endpoint properties
+ GHashTable * pStatesHT; // Keep all known states in key value pairs
} EndpointInfoT;
typedef struct StreamInfo {
- streamID_t stream_id;
- char * pcm_name;
- EndpointInfoT endpoint_info;
+ streamID_t streamID;
+ EndpointInfoT endpointInfo;
} StreamInfoT;
-typedef struct RoutingInfo {
- routingID_t routing_id;
- endpointID_t source_id;
- endpointID_t sink_id;
-} RoutingInfoT;
+// Parts of the context that are visible to the policy (for state based decisions)
+typedef struct AHLPolicyCtx {
+ GPtrArray * pSourceEndpoints; // Array of source end points for each audio role (GArray*)
+ GPtrArray * pSinkEndpoints; // Array of sink end points for each audio role (GArray*)
+ GArray * pRolePriority; // List of role priorities (int). TODO: Should be hash table with role name as key
+ GArray * pAudioRoles; // List of audio roles (GString)
+ GArray * pActiveStreams; // List of active streams (StreamInfoT)
+ int iNumberRoles; // Number of audio roles from configuration
+ // TODO: Global properties -> exposed to policy
+} 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
+ GArray * pHALList; // List of HAL dependencies
+ GHashTable * pDefaultStatesHT; // List of states and default values known to configuration
+} AHLCtxT;
PUBLIC int AhlBindingInit();
// ahl-deviceenum.c
-PUBLIC int EnumerateSources();
-PUBLIC int EnumerateSinks();
+PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName);
+PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName);
+// ahl-config.c
+PUBLIC int ParseHLBConfig();
+// ahl-policy.c
+PUBLIC int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID);
+PUBLIC int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr, int rampTimeMS);
+PUBLIC int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr, int rampTimeMS);
+PUBLIC int Policy_SetState(EndpointTypeT endpointType, endpointID_t endpointID, char *pStateName, char *pStateValue);
+PUBLIC int Policy_PostSoundEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext);
+PUBLIC int Policy_AudioDeviceChange();
-#endif
+#define AUDIOHL_MAXHALCONTROLNAME_LENGTH 128
+
+#endif // AHL_BINDING_INCLUDE
diff --git a/src/ahl-config.c b/src/ahl-config.c
new file mode 100644
index 0000000..432910f
--- /dev/null
+++ b/src/ahl-config.c
@@ -0,0 +1,148 @@
+/*
+ * 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 <json-c/json.h>
+#include "wrap-json.h"
+
+#include "ahl-binding.h"
+
+// TODO: Term() to free all allocs upon exit...
+
+extern AHLCtxT g_AHLCtx;
+
+PUBLIC int ParseHLBConfig() {
+ char * versionStr = NULL;
+ json_object * jAudioRoles = NULL;
+ json_object * jHALList = NULL;
+ json_object * jStateList = NULL;
+
+ // TODO: This should be retrieve from binding startup arguments
+ char configfile_path[256];
+ sprintf(configfile_path, "%s/opt/config/ahl-config.json", getenv("HOME"));
+ AFB_INFO("High-level config file -> %s\n", configfile_path);
+
+ // Open configuration file
+ json_object *config_JFile=json_object_from_file(configfile_path);
+ if(config_JFile == NULL)
+ {
+ AFB_ERROR("Error: Can't open configuration file -> %s",configfile_path);
+ return 1;
+ }
+
+ int err = wrap_json_unpack(config_JFile, "{s:s,s:o,s:o,s:o}", "version", &versionStr,"audio_roles",&jAudioRoles,"hal_list",&jHALList,"state_list",&jStateList);
+ if (err) {
+ AFB_ERROR("Invalid configuration file -> %s", configfile_path);
+ return 1;
+ }
+
+ // Parse and populate list of known states
+ g_AHLCtx.pDefaultStatesHT = g_hash_table_new(g_str_hash, g_str_equal);
+ int iStateListLength = json_object_array_length(jStateList);
+ for ( int i = 0; i < iStateListLength; i++)
+ {
+ char * pStateName = NULL;
+ char * pStateVal = NULL;
+ json_object * jStateobject = json_object_array_get_idx(jStateList,i);
+ err = wrap_json_unpack(jStateobject, "{s:s,s:s}", "name", &pStateName,"default_val",&pStateVal);
+ if (err) {
+ AFB_ERROR("Invalid state object -> %s", json_object_get_string(jStateobject));
+ return 1;
+ }
+
+ g_hash_table_insert(g_AHLCtx.pDefaultStatesHT, pStateName, pStateVal);
+ }
+
+ int iHALListLength = json_object_array_length(jHALList);
+ int iNumberOfRoles = json_object_array_length(jAudioRoles);
+ // Dynamically allocated based on number or roles found
+ g_AHLCtx.pHALList = g_array_sized_new(FALSE, TRUE, sizeof(GString), iHALListLength);
+ g_AHLCtx.policyCtx.pRolePriority = g_array_sized_new(FALSE, TRUE, sizeof(int), iNumberOfRoles);
+ g_AHLCtx.policyCtx.pAudioRoles = g_array_sized_new(FALSE, TRUE, sizeof(GString), iNumberOfRoles);
+ g_AHLCtx.policyCtx.pSourceEndpoints = g_ptr_array_sized_new(iNumberOfRoles);
+ g_AHLCtx.policyCtx.pSinkEndpoints = g_ptr_array_sized_new(iNumberOfRoles);
+ g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles;
+
+ for (unsigned int i = 0; i < iHALListLength; i++)
+ {
+ char * pHAL = NULL;
+ json_object * jHAL = json_object_array_get_idx(jHALList,i);
+ pHAL = (char *)json_object_get_string(jHAL);
+ GString * gHALName = g_string_new( pHAL );
+ g_array_append_val( g_AHLCtx.pHALList, *gHALName );
+
+ // Set dependency on HAL
+ err = afb_daemon_require_api_v2(pHAL,1) ;
+ if( err != 0 )
+ {
+ AFB_ERROR("Audio high level API could not set dependenvy on API: %s",pHAL);
+ return 1;
+ }
+ }
+
+ for (unsigned 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;
+ char * pRoleName = NULL;
+ int iNumOutDevices = 0;
+ int iNumInDevices = 0;
+
+ err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s?o,s?o}", "name", &pRoleName,"priority",&priority,"output",&jOutputDevices,"input",&jInputDevices);
+ if (err) {
+ AFB_ERROR("Invalid audio role configuration : %s", json_object_to_json_string(jAudioRole));
+ return 1;
+ }
+
+ if (jOutputDevices)
+ iNumOutDevices = json_object_array_length(jOutputDevices);
+ if (jInputDevices)
+ iNumInDevices = json_object_array_length(jInputDevices);
+
+ GString * gRoleName = g_string_new( pRoleName );
+ g_array_append_val( g_AHLCtx.policyCtx.pAudioRoles, *gRoleName );
+ g_array_append_val( g_AHLCtx.policyCtx.pRolePriority, priority );
+
+ // Sources
+ GArray * pRoleSourceDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT));
+ g_ptr_array_add(g_AHLCtx.policyCtx.pSourceEndpoints,pRoleSourceDeviceArray);
+ if (iNumInDevices) {
+ err = EnumerateSources(jInputDevices,i,pRoleName);
+ if (err) {
+ AFB_ERROR("Invalid input devices : %s", json_object_to_json_string(jInputDevices));
+ return 1;
+ }
+ }
+ GArray * pRoleSinkDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT));
+ g_ptr_array_add(g_AHLCtx.policyCtx.pSinkEndpoints,pRoleSinkDeviceArray);
+ if (iNumOutDevices) {
+ err = EnumerateSinks(jOutputDevices,i,pRoleName);
+ if (err) {
+ AFB_ERROR("Invalid output devices : %s", json_object_to_json_string(jOutputDevices));
+ return 1;
+ }
+ }
+ }
+
+ // Build lists of all device URI referenced in config file (input/output)
+ AFB_DEBUG ("Audio high-level - Parse high-level audio configuration done");
+ return 0;
+}
diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c
index 6629de2..b866d52 100644
--- a/src/ahl-deviceenum.c
+++ b/src/ahl-deviceenum.c
@@ -18,23 +18,417 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
-#include <time.h>
+#include <alsa/asoundlib.h>
+#include <alsa/pcm.h>
+#include <json-c/json.h>
+#include "wrap-json.h"
#include "ahl-binding.h"
-PUBLIC int EnumerateSources() {
-
- // TODO: Use lower level services to build a list of available source devices
+extern AHLCtxT g_AHLCtx;
+
+static endpointID_t CreateNewSourceID()
+{
+ endpointID_t newID = g_AHLCtx.nextSourceEndpointID;
+ g_AHLCtx.nextSourceEndpointID++;
+ return newID;
+}
+
+static endpointID_t CreateNewSinkID()
+{
+ endpointID_t newID = g_AHLCtx.nextSinkEndpointID;
+ g_AHLCtx.nextSinkEndpointID++;
+ return newID;
+}
+
+// Watchout: This function uses strtok and is destructive on the input string (use a copy)
+static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomain, char ** out_pDevice)
+{
+ *out_pDomain = strtok(in_pDeviceURI, ".");
+ if (*out_pDomain == NULL)
+ {
+ AFB_ERROR("Error tokenizing device URI -> %s",in_pDeviceURI);
+ return 1;
+ }
+ // TODO: Validate domain is known string (e.g. ALSA,Pulse,GStreamer)
+ *out_pDevice = strtok(NULL, ".");
+ if (*out_pDevice == NULL)
+ {
+ AFB_ERROR("Error tokenizing device URI -> %s",in_pDeviceURI);
+ return 1;
+ }
+ return 0;
+}
+
+static int IsAlsaDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_ALSA) == 0);
+}
+
+static int IsPulseDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_PULSE) == 0);
+}
+
+static int IsGStreamerDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_GSTREAMER) == 0);
+}
+
+static int IsExternalDomain(const char * in_pDomainStr)
+{
+ return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_EXTERNAL) == 0);
+}
+
+static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndpointInfo )
+{
+ snd_pcm_type_t pcmType = 0;
+ snd_pcm_info_t * pPcmInfo = NULL;
+ int iAlsaRet = 0;
+ const char * pDeviceName = NULL;
+ int retVal = 0;
+
+ // 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;
+ default:
+ out_pEndpointInfo->deviceURIType = DEVICEURITYPE_ALSA_OTHER;
+ break;
+ }
+
+ iAlsaRet = snd_pcm_info_malloc(&pPcmInfo);
+ if (iAlsaRet < 0)
+ {
+ AFB_WARNING("Error allocating PCM info structure");
+ retVal = 1;
+ goto End;
+ }
+
+ iAlsaRet = snd_pcm_info(in_pPcmHandle,pPcmInfo);
+ if (iAlsaRet < 0)
+ {
+ AFB_WARNING("Error retrieving PCM device info");
+ retVal = 1;
+ goto End;
+ }
+
+ // Populate target device name (for application display)
+ pDeviceName = snd_pcm_info_get_name(pPcmInfo);
+ if (pDeviceName == NULL)
+ {
+ AFB_WARNING("No Alsa device name available");
+ retVal = 1;
+ goto End;
+ // Could potentially assign a "default" name and carry on with this device
+ }
+ strncpy(out_pEndpointInfo->deviceName,pDeviceName,AUDIOHL_MAX_DEVICE_NAME_LENGTH); // Overwritten by HAL name if available
+
+ // get card number
+ out_pEndpointInfo->cardNum = snd_pcm_info_get_card(pPcmInfo);
+ if ( out_pEndpointInfo->cardNum < 0 )
+ {
+ AFB_WARNING("No Alsa card number available");
+ retVal = 1;
+ goto End;
+ }
+ // get device number
+ out_pEndpointInfo->deviceNum = snd_pcm_info_get_device(pPcmInfo);
+ if ( out_pEndpointInfo->deviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa device number available");
+ retVal = 1;
+ goto End;
+ }
+
+ // get sub-device number
+ out_pEndpointInfo->subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo);
+ if ( out_pEndpointInfo->subDeviceNum < 0 )
+ {
+ AFB_WARNING("No Alsa subdevice number available");
+ retVal = 1;
+ goto End;
+ }
+
+End:
+ if(pPcmInfo) {
+ snd_pcm_info_free(pPcmInfo);
+ pPcmInfo = NULL;
+ }
+
+ return retVal;
+}
+
+static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo)
+{
+ json_object *j_response, *j_query = NULL;
+ int err;
+ err = afb_service_call_sync("alsacore", "hallist", j_query, &j_response);
+ if (err) {
+ AFB_ERROR("Could not retrieve list of HAL from ALSA core");
+ return 1;
+ }
+ AFB_DEBUG("ALSAcore hallist response=%s", json_object_to_json_string(j_response));
+
+ // Look through returned list for matching card
+ int found = 0;
+ json_object * jRespObj = NULL;
+ json_object_object_get_ex(j_response, "response", &jRespObj);
+ int iNumHAL = json_object_array_length(jRespObj);
+ for ( int i = 0 ; i < iNumHAL; i++)
+ {
+ json_object * jHAL = json_object_array_get_idx(jRespObj,i);
+ char * pDevIDStr = NULL;
+ char * pAPIName = NULL;
+ char * pShortName = NULL;
+
+ int err = wrap_json_unpack(jHAL, "{s:s,s:s,s:s}", "devid", &pDevIDStr,"api", &pAPIName,"shortname",&pShortName);
+ if (err) {
+ AFB_ERROR("Could not retrieve devid string=%s", json_object_get_string(jHAL));
+ return 1;
+ }
+
+ // Retrieve card number (e.g. hw:0)
+ int iCardNum = atoi(pDevIDStr+3);
+ if (iCardNum == io_pEndpointInfo->cardNum) {
+ strncpy(io_pEndpointInfo->halAPIName,pAPIName,AUDIOHL_MAX_AUDIOROLE_LENGTH);
+ strncpy(io_pEndpointInfo->deviceName,pShortName,AUDIOHL_MAX_DEVICE_NAME_LENGTH);
+ found = 1;
+ break;
+ }
+ }
+ return !found;
+}
+
+static int InitializeEndpointStates( EndpointInfoT * out_pEndpointInfo )
+{
+ //out_pEndpointInfo = g_array_sized_new(FALSE,TRUE,sizeof)
+ // for list of known states
+ return 0;
+}
+
+// For a given audio role
+PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName) {
+
+ int iNumberDevices = json_object_array_length(in_jSourceArray);
+
+ // Parse and validate list of available devices
+ for (unsigned int i = 0; i < iNumberDevices; i++)
+ {
+ char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive
+ char * pDeviceURIDomain = NULL;
+ char * pFullDeviceURI = NULL;
+ char * pDeviceURIPCM = NULL;
+ int err = 0;
+ EndpointInfoT endpointInfo;
+
+ json_object * jSource = json_object_array_get_idx(in_jSourceArray,i);
+
+ // strip domain name from URI
+ pFullDeviceURI = (char *)json_object_get_string(jSource);
+ strncpy(fullDeviceURI,pFullDeviceURI,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ err = SeparateDomainFromDeviceURI(fullDeviceURI,&pDeviceURIDomain,&pDeviceURIPCM);
+ if (err)
+ {
+ AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ // non ALSA URI are simply passed to application (no validation) at this time
+ // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection
+ endpointInfo.cardNum = -1;
+ endpointInfo.deviceNum = -1;
+ endpointInfo.cardNum = -1;
+ strncpy(endpointInfo.deviceName,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_NAME_LENGTH); // Overwritten by HAL name if available
+
+ if (IsAlsaDomain(pDeviceURIDomain))
+ {
+ // 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)
+
+ snd_pcm_t * pPcmHandle = NULL;
+
+ // Get PCM handle
+ err = snd_pcm_open(&pPcmHandle, pDeviceURIPCM, SND_PCM_STREAM_CAPTURE, 0);
+ if (err < 0)
+ {
+ AFB_NOTICE("Alsa PCM device was not found -> %s", pDeviceURIPCM);
+ continue;
+ }
+
+ err = FillALSAPCMInfo(pPcmHandle,&endpointInfo);
+ if (err) {
+ AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pDeviceURIPCM);
+ snd_pcm_close(pPcmHandle);
+ continue;
+ }
+
+ snd_pcm_close(pPcmHandle);
+
+ // Retrieve HAL API name
+ err = RetrieveAssociatedHALAPIName(&endpointInfo);
+ if (err) {
+ AFB_WARNING("SetVolume will fail without HAL association ->%s",endpointInfo.deviceURI);
+ // Choose not to skip anyhow...
+ }
+ }
+ else if (IsPulseDomain(pDeviceURIDomain)) {
+ // Pulse domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_PULSE;
+ }
+ else if (IsGStreamerDomain(pDeviceURIDomain)){
+ // GStreamer domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_GSTREAMER;
+ }
+ else if (IsExternalDomain(pDeviceURIDomain)){
+ // External domain
+ endpointInfo.deviceURIType = DEVICEURITYPE_EXTERNAL;
+ }
+ else {
+ // Unknown domain
+ AFB_WARNING("Unknown domain in device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ err = InitializeEndpointStates( &endpointInfo );
+ if (err) {
+ AFB_ERROR("Cannot initialize endpoint states for URI -> %s",fullDeviceURI);
+ continue;
+ }
+
+ strncpy(endpointInfo.deviceURI,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ strncpy(endpointInfo.audioRole,in_pRoleName,AUDIOHL_MAX_AUDIOROLE_LENGTH);
+ endpointInfo.endpointID = CreateNewSourceID();
+ endpointInfo.type = ENDPOINTTYPE_SOURCE;
+
+ // add to structure to list of available source devices
+ GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, in_iRoleIndex );
+ g_array_append_val(pRoleSourceDeviceArray, endpointInfo);
+
+ } // for all 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
-
+// For a given audio role
+PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName) {
+
+ int iNumberDevices = json_object_array_length(in_jSinkArray);
+
+ // Parse and validate list of available devices
+ for (unsigned int i = 0; i < iNumberDevices; i++)
+ {
+ char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive
+ char * pDeviceURIDomain = NULL;
+ char * pFullDeviceURI = NULL;
+ char * pDeviceURIPCM = NULL;
+ int err = 0;
+ EndpointInfoT endpointInfo;
+
+ json_object * jSink = json_object_array_get_idx(in_jSinkArray,i);
+
+ // strip domain name from URI
+ pFullDeviceURI = (char*)json_object_get_string(jSink);
+ strncpy(fullDeviceURI,pFullDeviceURI,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ err = SeparateDomainFromDeviceURI(fullDeviceURI,&pDeviceURIDomain,&pDeviceURIPCM);
+ if (err)
+ {
+ AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ // non ALSA URI are simply passed to application (no validation) at this time
+ // In Non ALSA case devices in config are assumed to be available, if not application can fallback on explicit device selection
+
+ endpointInfo.cardNum = -1;
+ endpointInfo.deviceNum = -1;
+ endpointInfo.cardNum = -1;
+ strncpy(endpointInfo.deviceName,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_NAME_LENGTH);
+
+ if (IsAlsaDomain(pDeviceURIDomain))
+ {
+ // 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)
+
+ snd_pcm_t * pPcmHandle = NULL;
+
+ // get PCM handle
+ err = snd_pcm_open(&pPcmHandle, pDeviceURIPCM, SND_PCM_STREAM_PLAYBACK, 0);
+ if (err < 0)
+ {
+ AFB_NOTICE("Alsa PCM device was not found -> %s", pDeviceURIPCM);
+ continue;
+ }
+
+ err = FillALSAPCMInfo(pPcmHandle,&endpointInfo);
+ if (err) {
+ AFB_WARNING("Unable to retrieve PCM information for PCM -> %s",pDeviceURIPCM);
+ snd_pcm_close(pPcmHandle);
+ continue;
+ }
+
+ snd_pcm_close(pPcmHandle);
+
+ // Retrieve HAL API name
+ err = RetrieveAssociatedHALAPIName(&endpointInfo);
+ if (err) {
+ //AFB_WARNING("SetVolume w fail without HAL association ->%s",endpointInfo.deviceURI);
+ continue;
+ }
+ }
+ else if (IsPulseDomain(pDeviceURIDomain)) {
+ // Pulse domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins Pulse API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_PULSE;
+
+ }
+ else if (IsGStreamerDomain(pDeviceURIDomain)){
+ // GStreamer domain
+ // For now display name is device URI directly, could extrapolated using more heuristics or even usins GStreamer API later on
+ endpointInfo.deviceURIType = DEVICEURITYPE_GSTREAMER;
+ }
+ else if (IsExternalDomain(pDeviceURIDomain)){
+ // External domain
+
+ endpointInfo.deviceURIType = DEVICEURITYPE_EXTERNAL;
+ }
+ else {
+ // Unknown domain
+ AFB_WARNING("Unknown domain in device URI string -> %s",fullDeviceURI);
+ continue;
+ }
+
+ err = InitializeEndpointStates( &endpointInfo );
+ if (err) {
+ AFB_ERROR("Cannot initialize endpoint states for URI -> %s",fullDeviceURI);
+ continue;
+ }
+
+ strncpy(endpointInfo.deviceURI,pDeviceURIPCM,AUDIOHL_MAX_DEVICE_URI_LENGTH);
+ strncpy(endpointInfo.audioRole,in_pRoleName,AUDIOHL_MAX_AUDIOROLE_LENGTH);
+ endpointInfo.endpointID = CreateNewSinkID();
+ endpointInfo.type = ENDPOINTTYPE_SINK;
+
+ // add to structure to list of available source devices
+ GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, in_iRoleIndex );
+ g_array_append_val(pRoleSinkDeviceArray, endpointInfo);
+
+ } // for all devices
+
AFB_DEBUG ("Audio high-level - Enumerate sinks done");
return 0;
}
-
diff --git a/src/ahl-interface.h b/src/ahl-interface.h
new file mode 100644
index 0000000..781bb05
--- /dev/null
+++ b/src/ahl-interface.h
@@ -0,0 +1,74 @@
+/*
+ * 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_INTERFACE_INCLUDE
+#define AHL_INTERFACE_INCLUDE
+
+#define UNDEFINED_ID -1
+
+typedef int endpointID_t;
+typedef int streamID_t;
+
+typedef enum EndpointType {
+ ENDPOINTTYPE_SOURCE = 0, // source devices
+ ENDPOINTTYPE_SINK, // sink devices
+ ENDPOINTTYPE_MAXVALUE // Enum count, keep at the end
+} EndpointTypeT;
+
+// Standardized name for common audio roles (not enforced in any way, just helps system being more compatible)
+#define AUDIOROLE_WARNING "warning" // Safety-relevant or critical alerts/alarms
+#define AUDIOROLE_GUIDANCE "guidance" // Important user information where user action is expected (e.g. navigation instruction)
+#define AUDIOROLE_NOTIFICATION "notification" // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...)
+#define AUDIOROLE_COMMUNICATION "communications" // Voice communications (e.g. handsfree, speech recognition)
+#define AUDIOROLE_ENTERTAINMENT "entertainment" // Multimedia content (e.g. tuner, media player, etc.)
+#define AUDIOROLE_SYSTEM "system" // System level content or development
+#define AUDIOROLE_STARTUP "startup" // Early (startup) sound
+#define AUDIOROLE_SHUTDOWN "shutdown" // Late (shutdown) sound
+#define AUDIOROLE_NONE "none" // Non-assigned / legacy applications
+
+typedef enum DeviceURIType {
+ DEVICEURITYPE_ALSA_HW = 0, // Alsa hardware device URI
+ DEVICEURITYPE_ALSA_DMIX, // Alsa Dmix device URI (only for playback devices)
+ DEVICEURITYPE_ALSA_DSNOOP, // Alsa DSnoop device URI (only for capture devices)
+ DEVICEURITYPE_ALSA_SOFTVOL, // Alsa softvol device URI
+ DEVICEURITYPE_ALSA_OTHER, // Alsa domain URI device of unspecified type
+ DEVICEURITYPE_PULSE, // Pulse device URI
+ DEVICEURITYPE_GSTREAMER, // GStreamer device URI
+ DEVICEURITYPE_EXTERNAL, // Device URI for external ECU device
+ DEVICEURITYPE_MAXVALUE // Enum count, keep at the end
+} DeviceURITypeT;
+
+// Standardized list of properties (string used for extensibility)
+#define AUDIOHL_PROPERTY_BALANCE "balance"
+#define AUDIOHL_PROPERTY_FADE "fade"
+#define AUDIOHL_PROPERTY_EQ_LOW "eq_low"
+#define AUDIOHL_PROPERTY_EQ_MID "eq_mid"
+#define AUDIOHL_PROPERTY_EQ_HIGH "eq_high"
+
+// Standardized list of state names/values (string used for extensibility)
+#define AUDIOHL_STATE_NAME_ACTIVE "active"
+#define AUDIOHL_STATE_NAME_MUTE "mute"
+#define AUDIOHL_STATE_VALUE_ON "on"
+#define AUDIOHL_STATE_VALUE_OFF "off"
+
+// Known audio domain string definitions (for configuration file format)
+#define AUDIOHL_DOMAIN_ALSA "Alsa"
+#define AUDIOHL_DOMAIN_PULSE "Pulse"
+#define AUDIOHL_DOMAIN_GSTREAMER "GStreamer"
+#define AUDIOHL_DOMAIN_EXTERNAL "External"
+
+#endif // AHL_INTERFACE_INCLUDE
diff --git a/src/ahl-policy.c b/src/ahl-policy.c
new file mode 100644
index 0000000..7741afd
--- /dev/null
+++ b/src/ahl-policy.c
@@ -0,0 +1,90 @@
+/*
+ * 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 "ahl-binding.h"
+
+
+// This file provides example of custom, business logic driven policy actions that can affect behavior of the high level
+// TODO: Currently only isolated in separate source file. Objective is to make this to at least a shared lib plug-in
+// Going a step further would be to implement this within afb-controler policy plug-in (would require bi-directional access to HLB context)
+
+extern AHLCtxT g_AHLCtx; // TODO: Cannot stay if moved to external module
+
+// Attribute of high-level binding (parsed), policy enforced
+typedef enum InterruptedBehavior {
+ INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media will be ducked)
+ INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later)
+ INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance)
+ INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end
+} InterruptedBehaviorT;
+
+PUBLIC int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID)
+{
+ return 1; // Policy allowed
+}
+
+PUBLIC int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr, int rampTimeMS)
+{
+ return 1; // Policy allowed
+}
+
+PUBLIC int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr, int rampTimeMS)
+{
+ return 1; // Policy allowed
+}
+
+
+PUBLIC int Policy_SetState(EndpointTypeT endpointType, endpointID_t endpointID, char *pStateName, char *pStateValue)
+{
+
+
+ //Mute
+ if(strcmp(pStateName, AUDIOHL_STATE_NAME_MUTE) == 0)
+ {
+ if(strcmp(pStateName, AUDIOHL_STATE_NAME_MUTE) == 0)
+
+
+ return AUDIOHL_POLICY_ACCEPT;
+ }
+
+ //Retrieve global context
+
+
+ //Active rule check
+
+ //Ducking rule settings
+
+
+ return AUDIOHL_POLICY_ACCEPT;
+}
+
+PUBLIC int Policy_PostSoundEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext)
+{
+ return 1; // Policy allowed
+}
+
+PUBLIC int Policy_AudioDeviceChange()
+{
+ // Allow or disallow a new audio device
+ // Raise events to engage device switching
+ // Policy can also assign audio role(s) for device
+ return 1; // Policy allowed
+} \ No newline at end of file