diff options
author | Tai Vuong <tvuong@audiokinetic.com> | 2017-09-12 16:33:39 -0400 |
---|---|---|
committer | Tai Vuong <tvuong@audiokinetic.com> | 2017-09-12 16:33:39 -0400 |
commit | d69dc9732886074e9f400961b500e70d5c8305d7 (patch) | |
tree | ec5d4bb353814d442e3e4d84430a5343fee7b0c8 /src | |
parent | 9098015429bcc87a7b624ade16732848a9d90f67 (diff) |
Pre-AudioWorkshop Demo
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 21 | ||||
-rw-r--r-- | src/ahl-apidef.h | 336 | ||||
-rw-r--r-- | src/ahl-apidef.json | 156 | ||||
-rw-r--r-- | src/ahl-binding.c | 530 | ||||
-rw-r--r-- | src/ahl-binding.h | 113 | ||||
-rw-r--r-- | src/ahl-config.c | 148 | ||||
-rw-r--r-- | src/ahl-deviceenum.c | 412 | ||||
-rw-r--r-- | src/ahl-interface.h | 74 | ||||
-rw-r--r-- | src/ahl-policy.c | 90 |
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 |