diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ahl-apidef.h | 339 | ||||
-rw-r--r-- | src/ahl-apidef.json | 204 | ||||
-rw-r--r-- | src/ahl-binding.c | 730 | ||||
-rw-r--r-- | src/ahl-binding.h | 106 | ||||
-rw-r--r-- | src/ahl-config.c | 66 | ||||
-rw-r--r-- | src/ahl-deviceenum.c | 154 | ||||
-rw-r--r-- | src/ahl-interface.h | 112 | ||||
-rw-r--r-- | src/ahl-policy.c | 123 |
8 files changed, 1233 insertions, 601 deletions
diff --git a/src/ahl-apidef.h b/src/ahl-apidef.h index 9a39470..e23d1e0 100644 --- a/src/ahl-apidef.h +++ b/src/ahl-apidef.h @@ -1,138 +1,155 @@ static const char _afb_description_v2_audiohl[] = - "{\"openapi\":\"3.0.0\",\"$schema\":\"http:iot.bzh/download/openapi/schem" - "a-3.0/default-schema.json\",\"info\":{\"description\":\"Audio high level" - " API for AGL applications\",\"title\":\"audiohighlevel\",\"version\":\"1" - ".0\",\"x-binding-c-generator\":{\"api\":\"audiohl\",\"version\":2,\"pref" - "ix\":\"audiohlapi_\",\"postfix\":\"\",\"start\":null,\"onevent\":null,\"" - "init\":\"AhlBindingInit\",\"scope\":\"\",\"private\":false}},\"servers\"" - ":[{\"url\":\"ws://{host}:{port}/api/audiohl\",\"description\":\"Audio hi" - "gh level API for AGL applications.\",\"variables\":{\"host\":{\"default\"" - ":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":[{\"$r" - "ef\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"schemas\":" - "{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"afb-ev" - "ent\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply-v2\":" - "{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":[\"jty" - "pe\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const" - "\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"statu" - "s\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"type\"" - ":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string" - "\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"object\"}" - "}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"event" - "\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-event" - "\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}},\"e" - "ndpoint_info\":{\"type\":\"object\",\"required\":[\"endpoint_id\",\"type" - "\",\"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\":\"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\"" - ":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\":\"" + "{\"openapi\":\"3.0.0\",\"info\":{\"description\":\"Audio high level API " + "for AGL applications\",\"title\":\"audiohighlevel\",\"version\":\"1.0\"," + "\"x-binding-c-generator\":{\"api\":\"audiohl\",\"version\":2,\"prefix\":" + "\"audiohlapi_\",\"postfix\":\"\",\"start\":null,\"onevent\":\"AhlOnEvent" + "\",\"init\":\"AhlBindingInit\",\"scope\":\"\",\"private\":false}},\"serv" + "ers\":[{\"url\":\"ws://{host}:{port}/api/audiohl\",\"description\":\"Aud" + "io high level API for AGL applications.\",\"variables\":{\"host\":{\"def" + "ault\":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":" + "[{\"$ref\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"sche" + "mas\":{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"" + "afb-event\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply" + "-v2\":{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":" + "[\"jtype\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"" + "const\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"" + "status\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"t" + "ype\":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"s" + "tring\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"obje" + "ct\"}}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"" + "event\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-" + "event\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}" + "},\"endpoint_info\":{\"type\":\"object\",\"required\":[\"endpoint_id\",\"" + "type\",\"device_name\",\"device_uri\"],\"properties\":{\"endpoint_id\":{" + "\"type\":\"int\"},\"type\":{\"type\":\"enum\"},\"device_name\":{\"type\"" + ":\"string\"},\"device_uri_type\":{\"type\":\"string\"}}},\"stream_info\"" + ":{\"type\":\"object\",\"required\":[\"stream_id\",\"state\",\"mute\",\"e" + "ndpoint_info\"],\"properties\":{\"stream_id\":{\"type\":\"int\"},\"state" + "\":{\"type\":\"int\"},\"mute\":{\"type\":\"int\"},\"device_uri\":{\"type" + "\":\"string\"},\"$ref\":\"#/components/schemas/endpoint_info\"}}},\"x-pe" + "rmissions\":{\"streamcontrol\":{\"permission\":\"urn:AGL:permission:audi" + "o:public:streamcontrol\"},\"routingcontrol\":{\"permission\":\"urn:AGL:p" + "ermission:audio:public:routingcontrol\"},\"audiostream\":{\"permission\"" + ":\"urn:AGL:permission:audio:public:audiostream\"},\"prioritysignal\":{\"" + "permission\":\"urn:AGL:permission:audio:public:prioritysignal\"},\"sound" + "event\":{\"permission\":\"urn:AGL:permission:audio:public:soundevent\"}," + "\"streammonitor\":{\"permission\":\"urn:AGL:permission:audio:public:stre" + "ammonitor\"}},\"responses\":{\"200\":{\"description\":\"A complex object" + " array response\",\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/afb-reply\"}}}},\"400\":{\"description\":\"Inval" + "id arguments\"}}},\"paths\":{\"/get_sources\":{\"description\":\"Retriev" + "e array of available audio sources\",\"get\":{\"parameters\":[{\"in\":\"" + "query\",\"name\":\"audio_role\",\"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\"}}}},\"/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\"}}}}}}" + "0\",\"response\":{\"description\":\"Array of endpoint info structures\"," + "\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/endpoint_i" + "nfo\"}}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_sin" + "ks\":{\"description\":\"Retrieve array of available audio sinks\",\"get\"" + ":{\"parameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\"" + ":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\"" + ":\"#/components/responses/200\",\"response\":{\"description\":\"Array of" + " endpoint info structures\",\"type\":\"array\",\"items\":{\"$ref\":\"#/c" + "omponents/schemas/endpoint_info\"}}},\"400\":{\"$ref\":\"#/components/re" + "sponses/400\"}}}},\"/stream_open\":{\"description\":\"Request opening a " + "stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissio" + "ns/audiostream\"},\"parameters\":[{\"in\":\"query\",\"name\":\"audio_rol" + "e\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\"" + ",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enu" + "m\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":false,\"sc" + "hema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compone" + "nts/responses/200\",\"response\":{\"description\":\"Stream information s" + "tructure\",\"$ref\":\"#/components/schemas/stream_info\"}},\"400\":{\"$r" + "ef\":\"#/components/responses/400\"}}}},\"/stream_close\":{\"description" + "\":\"Request closing a stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#" + "/components/x-permissions/audiostream\"},\"parameters\":[{\"in\":\"query" + "\",\"name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"int\"" + "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"4" + "00\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_stream_state\":" + "{\"description\":\"Change stream active state\",\"get\":{\"x-permissions" + "\":{\"$ref\":\"#/components/x-permissions/streamcontrol\"},\"parameters\"" + ":[{\"in\":\"query\",\"name\":\"stream_id\",\"required\":true,\"schema\":" + "{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state\",\"required\":tr" + "ue,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/" + "components/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/4" + "00\"}}}},\"/set_stream_mute\":{\"description\":\"Change stream mute stat" + "e\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/st" + "reamcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"stream_id\"," + "\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"nam" + "e\":\"mute\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"respons" + "es\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\"" + ":\"#/components/responses/400\"}}}},\"/get_stream_info\":{\"description\"" + ":\"Retrieve stream information\",\"get\":{\"x-permissions\":{\"$ref\":\"" + "#/components/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"qu" + "ery\",\"name\":\"stream_id\",\"required\":true,\"schema\":{\"type\":\"in" + "t\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"" + "response\":{\"description\":\"Stream information structure\",\"$ref\":\"" + "#/components/schemas/stream_info\"}},\"400\":{\"$ref\":\"#/components/re" + "sponses/400\"}}}},\"/set_volume\":{\"description\":\"Set volume\",\"get\"" + ":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/streamcontrol" + "\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"requi" + "red\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"" + "endpoint_id\",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"" + "query\",\"name\":\"volume\",\"required\":true,\"schema\":{\"type\":\"str" + "ing\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" + "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_volume\":{" + "\"description\":\"Get volume\",\"get\":{\"parameters\":[{\"in\":\"query\"" + ",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enu" + "m\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"sch" + "ema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/componen" + "ts/responses/200\",\"response\":{\"description\":\"Endpoint volume value" + "\",\"type\":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"" + "}}}},\"/get_list_properties\":{\"description\":\"Retrieve a list of supp" + "orted properties for a particular endpoint\",\"get\":{\"parameters\":[{\"" + "in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{" + "\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"require" + "d\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref" + "\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/resp" + "onses/400\"}}}},\"/set_property\":{\"description\":\"Set property value\"" + ",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions/strea" + "mcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\"" + ",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"n" + "ame\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"}}," + "{\"in\":\"query\",\"name\":\"property_name\",\"required\":true,\"schema\"" + ":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"value\",\"required\"" + ":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\"" + ":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/components/respon" + "ses/400\"}}}},\"/get_property\":{\"description\":\"Get property value\"," + "\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"" + "required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name" + "\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\":\"int\"}},{\"" + "in\":\"query\",\"name\":\"property_name\",\"required\":true,\"schema\":{" + "\"type\":\"string\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/r" + "esponses/200\",\"response\":{\"description\":\"Property value\",\"type\"" + ":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/ge" + "t_list_events\":{\"description\":\"Retrieve a list of supported events f" + "or a particular audio role\",\"get\":{\"parameters\":[{\"in\":\"query\"," + "\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"" + "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"4" + "00\":{\"$ref\":\"#/components/responses/400\"}}}},\"/post_event\":{\"des" + "cription\":\"Post sound or audio device related event (extendable mechan" + "ism)\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permissions" + "/soundevent\"},\"parameters\":[{\"in\":\"query\",\"name\":\"event_name\"" + ",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"" + "name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"string\"}" + "},{\"in\":\"query\",\"name\":\"media_name\",\"required\":false,\"schema\"" + ":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"event_context\",\"r" + "equired\":false,\"schema\":{\"type\":\"object\"}}],\"responses\":{\"200\"" + ":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#/compone" + "nts/responses/400\"}}}},\"/subscribe\":{\"description\":\"Subscribe to a" + "udio high level events\",\"get\":{\"parameters\":[{\"in\":\"query\",\"na" + "me\":\"events\",\"required\":true,\"schema\":{\"type\":\"array\",\"items" + "\":{\"type\":\"string\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/compone" + "nts/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}" + "},\"/unsubscribe\":{\"description\":\"Unubscribe to audio high level eve" + "nts\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"events\",\"r" + "equired\":true,\"schema\":{\"type\":\"array\",\"items\":{\"type\":\"stri" + "ng\"}}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" + "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}}}}" ; static const struct afb_auth _afb_auths_v2_audiohl[] = { + { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:audiostream" }, { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:streamcontrol" }, { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:soundevent" } }; @@ -141,13 +158,16 @@ 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_set_stream_state(struct afb_req req); + void audiohlapi_set_stream_mute(struct afb_req req); + void audiohlapi_get_stream_info(struct afb_req req); void audiohlapi_set_volume(struct afb_req req); void audiohlapi_get_volume(struct afb_req req); + void audiohlapi_get_list_properties(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_get_list_events(struct afb_req req); + void audiohlapi_post_event(struct afb_req req); void audiohlapi_subscribe(struct afb_req req); void audiohlapi_unsubscribe(struct afb_req req); @@ -181,9 +201,30 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { .session = AFB_SESSION_NONE_V2 }, { + .verb = "set_stream_state", + .callback = audiohlapi_set_stream_state, + .auth = &_afb_auths_v2_audiohl[1], + .info = "Change stream active state", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "set_stream_mute", + .callback = audiohlapi_set_stream_mute, + .auth = &_afb_auths_v2_audiohl[1], + .info = "Change stream mute state", + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get_stream_info", + .callback = audiohlapi_get_stream_info, + .auth = &_afb_auths_v2_audiohl[1], + .info = "Retrieve stream information", + .session = AFB_SESSION_NONE_V2 + }, + { .verb = "set_volume", .callback = audiohlapi_set_volume, - .auth = &_afb_auths_v2_audiohl[0], + .auth = &_afb_auths_v2_audiohl[1], .info = "Set volume", .session = AFB_SESSION_NONE_V2 }, @@ -195,9 +236,16 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { .session = AFB_SESSION_NONE_V2 }, { + .verb = "get_list_properties", + .callback = audiohlapi_get_list_properties, + .auth = NULL, + .info = "Retrieve a list of supported properties for a particular endpoint", + .session = AFB_SESSION_NONE_V2 + }, + { .verb = "set_property", .callback = audiohlapi_set_property, - .auth = &_afb_auths_v2_audiohl[0], + .auth = &_afb_auths_v2_audiohl[1], .info = "Set property value", .session = AFB_SESSION_NONE_V2 }, @@ -209,24 +257,17 @@ static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { .session = AFB_SESSION_NONE_V2 }, { - .verb = "set_state", - .callback = audiohlapi_set_state, - .auth = &_afb_auths_v2_audiohl[0], - .info = "Set state", - .session = AFB_SESSION_NONE_V2 - }, - { - .verb = "get_state", - .callback = audiohlapi_get_state, + .verb = "get_list_events", + .callback = audiohlapi_get_list_events, .auth = NULL, - .info = "Get state value", + .info = "Retrieve a list of supported events for a particular audio role", .session = AFB_SESSION_NONE_V2 }, { - .verb = "post_sound_event", - .callback = audiohlapi_post_sound_event, - .auth = &_afb_auths_v2_audiohl[1], - .info = "Post sound event", + .verb = "post_event", + .callback = audiohlapi_post_event, + .auth = &_afb_auths_v2_audiohl[2], + .info = "Post sound or audio device related event (extendable mechanism)", .session = AFB_SESSION_NONE_V2 }, { @@ -259,7 +300,7 @@ const struct afb_binding_v2 afbBindingV2 = { .verbs = _afb_verbs_v2_audiohl, .preinit = NULL, .init = AhlBindingInit, - .onevent = NULL, + .onevent = AhlOnEvent, .noconcurrency = 0 }; diff --git a/src/ahl-apidef.json b/src/ahl-apidef.json index 45067ea..d486337 100644 --- a/src/ahl-apidef.json +++ b/src/ahl-apidef.json @@ -1,6 +1,5 @@ { "openapi": "3.0.0", - "$schema": "http:iot.bzh/download/openapi/schema-3.0/default-schema.json", "info": { "description": "Audio high level API for AGL applications", "title": "audiohighlevel", @@ -11,7 +10,7 @@ "prefix": "audiohlapi_", "postfix": "", "start": null, - "onevent": null, + "onevent": "AhlOnEvent", "init": "AhlBindingInit", "scope": "", "private": false @@ -107,23 +106,23 @@ }, "stream_info": { "type": "object", - "required": [ "stream_id", "endpoint_info" ], + "required": [ "stream_id", "state", "mute", "endpoint_info" ], "properties": { "stream_id": { "type": "int" }, + "state": { "type": "int" }, + "mute": { "type": "int" }, + "device_uri": { "type": "string" }, "$ref": "#/components/schemas/endpoint_info" } } }, "x-permissions": { - "streamcontrol": { - "permission": "urn:AGL:permission:audio:public:streamcontrol" - }, - "routingcontrol": { - "permission": "urn:AGL:permission:audio:public:routingcontrol" - }, - "soundevent": { - "permission": "urn:AGL:permission:audio:public:soundevent" - } + "streamcontrol": { "permission": "urn:AGL:permission:audio:public:streamcontrol"}, + "routingcontrol": { "permission": "urn:AGL:permission:audio:public:routingcontrol"}, + "audiostream": { "permission": "urn:AGL:permission:audio:public:audiostream"}, + "prioritysignal": { "permission": "urn:AGL:permission:audio:public:prioritysignal"}, + "soundevent": {"permission": "urn:AGL:permission:audio:public:soundevent"}, + "streammonitor": {"permission": "urn:AGL:permission:audio:public:streammonitor"} }, "responses": { "200": { @@ -194,7 +193,7 @@ "/stream_open": { "description": "Request opening a stream", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { "$ref": "#/components/x-permissions/audiostream" }, "parameters": [ { "in": "query", @@ -230,7 +229,7 @@ "/stream_close": { "description": "Request closing a stream", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { "$ref": "#/components/x-permissions/audiostream" }, "parameters": [ { "in": "query", @@ -245,34 +244,54 @@ } } }, - "/set_volume": { - "description": "Set volume", + "/set_stream_state": { + "description": "Change stream active state", "get": { - "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "x-permissions": { + "$ref": "#/components/x-permissions/streamcontrol" + }, "parameters": [ { "in": "query", - "name": "endpoint_type", + "name": "stream_id", "required": true, - "schema": { "type": "enum" } + "schema": {"type": "int"} }, { "in": "query", - "name": "endpoint_id", + "name": "state", "required": true, - "schema": { "type": "int" } + "schema": {"type": "int"} + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200" }, + "400": { + "$ref": "#/components/responses/400" + } + } + } + }, + "/set_stream_mute": { + "description": "Change stream mute state", + "get": { + "x-permissions": { + "$ref": "#/components/x-permissions/streamcontrol" + }, + "parameters": [ { "in": "query", - "name": "volume", + "name": "stream_id", "required": true, - "schema": { "type": "string" } + "schema": {"type": "int"} }, { "in": "query", - "name": "ramp_time_ms", - "required": false, - "schema": { "type": "int" } + "name": "mute", + "required": true, + "schema": {"type": "int"} } ], "responses": { @@ -281,37 +300,34 @@ } } }, - "/get_volume": { - "description": "Get volume", + "/get_stream_info": { + "description": "Retrieve stream information", "get": { + "x-permissions": { + "$ref": "#/components/x-permissions/streamcontrol" + }, "parameters": [ { "in": "query", - "name": "endpoint_type", - "required": true, - "schema": { "type": "enum" } - }, - { - "in": "query", - "name": "endpoint_id", + "name": "stream_id", "required": true, - "schema": { "type": "int" } + "schema": {"type": "int"} } ], "responses": { "200": { "$ref": "#/components/responses/200", "response": { - "description": "Endpoint volume value", - "type": "double" + "description": "Stream information structure", + "$ref": "#/components/schemas/stream_info" } }, "400": { "$ref": "#/components/responses/400" } } } }, - "/set_property": { - "description": "Set property value", + "/set_volume": { + "description": "Set volume", "get": { "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, "parameters": [ @@ -324,36 +340,53 @@ { "in": "query", "name": "endpoint_id", - "required": false, + "required": true, "schema": { "type": "int" } }, { "in": "query", - "name": "property_name", + "name": "volume", "required": true, "schema": { "type": "string" } - }, + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/get_volume": { + "description": "Get volume", + "get": { + "parameters": [ { "in": "query", - "name": "value", + "name": "endpoint_type", "required": true, - "schema": { "type": "string" } + "schema": { "type": "enum" } }, { "in": "query", - "name": "ramp_time_ms", - "required": false, + "name": "endpoint_id", + "required": true, "schema": { "type": "int" } } ], "responses": { - "200": { "$ref": "#/components/responses/200" }, + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Endpoint volume value", + "type": "double" + } + }, "400": { "$ref": "#/components/responses/400" } } } }, - "/get_property": { - "description": "Get property value", + "/get_list_properties": { + "description": "Retrieve a list of supported properties for a particular endpoint", "get": { "parameters": [ { @@ -367,28 +400,16 @@ "name": "endpoint_id", "required": false, "schema": { "type": "int" } - }, - { - "in": "query", - "name": "property_name", - "required": true, - "schema": { "type": "string" } } ], "responses": { - "200": { - "$ref": "#/components/responses/200", - "response": { - "description": "Property value", - "type": "double" - } - }, + "200": { "$ref": "#/components/responses/200" }, "400": { "$ref": "#/components/responses/400" } } } }, - "/set_state": { - "description": "Set state", + "/set_property": { + "description": "Set property value", "get": { "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, "parameters": [ @@ -401,18 +422,18 @@ { "in": "query", "name": "endpoint_id", - "required": true, + "required": false, "schema": { "type": "int" } }, { "in": "query", - "name": "state_name", + "name": "property_name", "required": true, "schema": { "type": "string" } }, { "in": "query", - "name": "state_value", + "name": "value", "required": true, "schema": { "type": "string" } } @@ -423,8 +444,8 @@ } } }, - "/get_state": { - "description": "Get state value", + "/get_property": { + "description": "Get property value", "get": { "parameters": [ { @@ -436,12 +457,12 @@ { "in": "query", "name": "endpoint_id", - "required": true, + "required": false, "schema": { "type": "int" } }, { "in": "query", - "name": "state_name", + "name": "property_name", "required": true, "schema": { "type": "string" } } @@ -450,16 +471,37 @@ "200": { "$ref": "#/components/responses/200", "response": { - "description": "Endpoint state value", - "type": "string" + "description": "Property value", + "type": "double" } }, "400": { "$ref": "#/components/responses/400" } } } }, - "/post_sound_event": { - "description": "Post sound event", + "/get_list_events": { + "description": "Retrieve a list of supported events for a particular audio role", + "get": { + "parameters": [ + { + "in": "query", + "name": "audio_role", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200" + }, + "400": { + "$ref": "#/components/responses/400" + } + } + } + }, + "/post_event": { + "description": "Post sound or audio device related event (extendable mechanism)", "get": { "x-permissions": { "$ref": "#/components/x-permissions/soundevent" }, "parameters": [ @@ -483,7 +525,7 @@ }, { "in": "query", - "name": "audio_context", + "name": "event_context", "required": false, "schema": { "type": "object" } } @@ -530,12 +572,8 @@ } ], "responses": { - "200": { - "$ref": "#/components/responses/200" - }, - "400": { - "$ref": "#/components/responses/400" - } + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } } } } diff --git a/src/ahl-binding.c b/src/ahl-binding.c index 4b075c0..3f8d71d 100644 --- a/src/ahl-binding.c +++ b/src/ahl-binding.c @@ -26,61 +26,44 @@ // 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) +//afb_req_context_set ?? +// usr = afb_req_context_get(req); +// afb_req_context_clear(req); - -// 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,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) +static void AudioFormatStructToJSON(json_object **audioFormatJ, AudioFormatT * pAudioFormat) { - json_object *endpointInfoJ; - EndpointInfoStructToJSON(endpointInfoJ,streamInfo.endpointInfo); - wrap_json_pack(streamInfoJ, "{s:i}", "stream_id", streamInfo.streamID); - json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); + wrap_json_pack(audioFormatJ, "{s:i,s:i,s:i}", + "sample_rate", pAudioFormat->sampleRate, + "num_channels", pAudioFormat->numChannels, + "sample_type", pAudioFormat->sampleType); } -static streamID_t CreateNewStreamID() +// Helper macros/func for packaging JSON objects from C structures +static void EndpointInfoStructToJSON(json_object **endpointInfoJ, EndpointInfoT * pEndpointInfo) { - streamID_t newID = g_AHLCtx.nextStreamID; - g_AHLCtx.nextStreamID++; - return newID; + json_object *formatInfoJ = NULL; + wrap_json_pack(endpointInfoJ, "{s:i,s:i,s:s,s:i}", + "endpoint_id", pEndpointInfo->endpointID, + "endpoint_type", pEndpointInfo->type, + "device_name", pEndpointInfo->gsDeviceName->str, + "device_uri_type", pEndpointInfo->deviceURIType); + AudioFormatStructToJSON(&formatInfoJ,&pEndpointInfo->format); + json_object_object_add(*endpointInfoJ,"format",formatInfoJ); } - -static int FindRoleIndex( const char * in_pAudioRole) + +static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT * pStreamInfo) { - 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; + json_object *endpointInfoJ = NULL; + EndpointInfoStructToJSON(&endpointInfoJ,pStreamInfo->pEndpointInfo); + wrap_json_pack(streamInfoJ, "{s:i,s:i,s:i,s:s}", + "stream_id", pStreamInfo->streamID, + "state", pStreamInfo->streamState, + "mute", pStreamInfo->streamMute, + "device_uri",pStreamInfo->pEndpointInfo->gsDeviceURI->str); + json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); } + static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT in_endpointType) { EndpointInfoT * pEndpointInfo = NULL; @@ -104,6 +87,104 @@ static EndpointInfoT * GetEndpointInfo(endpointID_t in_endpointID, EndpointTypeT return pEndpointInfo; } +static StreamInfoT * GetActiveStream(streamID_t in_streamID) +{ + int iNumActiveStreams = g_AHLCtx.policyCtx.pActiveStreams->len; + StreamInfoT * pStreamInfo = NULL; + for ( int i = 0; i < iNumActiveStreams ; i++ ) { + StreamInfoT * pCurStreamInfo = &g_array_index(g_AHLCtx.policyCtx.pActiveStreams,StreamInfoT,i); + if (pCurStreamInfo->streamID == in_streamID){ + pStreamInfo = pCurStreamInfo; + break; + } + } + return pStreamInfo; +} + +static int CreateEvents() +{ + int err = 0; + + g_AHLCtx.policyCtx.propertyEvent = afb_daemon_make_event(AHL_ENDPOINT_PROPERTY_EVENT); + err = !afb_event_is_valid(g_AHLCtx.policyCtx.propertyEvent); + if (err) { + AFB_ERROR("Could not create endpoint property change event"); + err++; + } + + g_AHLCtx.policyCtx.volumeEvent = afb_daemon_make_event(AHL_ENDPOINT_VOLUME_EVENT); + err = !afb_event_is_valid(g_AHLCtx.policyCtx.volumeEvent); + if (err) { + AFB_ERROR("Could not create endpoint volume change event"); + err++; + } + + g_AHLCtx.policyCtx.postEvent = afb_daemon_make_event(AHL_POST_EVENT); + err = !afb_event_is_valid(g_AHLCtx.policyCtx.postEvent); + if (err) { + AFB_ERROR("Could not create post event call event"); + err++; + } + + return err; +} + +static void AhlBindingTerm() +{ + // Policy termination + Policy_Term(); + + // Events + for (int i = 0; i < g_AHLCtx.policyCtx.pEventList->len; i++) + { + // For each event within the role + GArray * pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, i ); + for (int j = 0 ; j < pRoleEventArray->len; j++) + { + GString * gsEventName = &g_array_index(pRoleEventArray,GString,j); + g_string_free(gsEventName,TRUE); + } + g_array_free(pRoleEventArray,TRUE); + pRoleEventArray = NULL; + } + g_ptr_array_free(g_AHLCtx.policyCtx.pEventList,TRUE); + g_AHLCtx.policyCtx.pEventList = NULL; + + // Endpoints + TermEndpoints(); + + // TODO: Need to free g_strings in HAL list + g_array_free(g_AHLCtx.pHALList,TRUE); + g_hash_table_remove_all(g_AHLCtx.policyCtx.pRolePriority); + g_hash_table_destroy(g_AHLCtx.policyCtx.pRolePriority); + // TODO: Need to free g_strings in audio roles list + g_array_free(g_AHLCtx.policyCtx.pAudioRoles,TRUE); + g_array_free(g_AHLCtx.policyCtx.pInterruptBehavior,TRUE); + g_array_free(g_AHLCtx.policyCtx.pActiveStreams,TRUE); + + AFB_INFO("Audio high-level Binding succesTermination"); +} + +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; +} + + // Binding initialization PUBLIC int AhlBindingInit() { @@ -121,12 +202,28 @@ PUBLIC int AhlBindingInit() // Parse high-level binding JSON configuration file (will build device lists) errcount += ParseHLBConfig(); + atexit(AhlBindingTerm); + + // This API uses services from low level audio + errcount = afb_daemon_require_api_v2("alsacore",1) ; + if( errcount != 0 ) + { + AFB_ERROR("Audio high level API requires alsacore API to be available"); + return 1; + } + + errcount += CreateEvents(); + + // Parse high-level binding JSON configuration file (will build device lists) + errcount += ParseHLBConfig(); + + // Policy initialization + errcount += Policy_Init(); + // Initialize list of active streams g_AHLCtx.policyCtx.pActiveStreams = g_array_new(FALSE,TRUE,sizeof(StreamInfoT)); - // TODO: Register for device changes ALSA low level audio service - - // TODO: Perform other binding initialization tasks (e.g. broadcast service ready event?) + // TODO: Register for device changes ALSA low level audio service or HAL // TODO: Use AGL persistence framework to retrieve and set inital state/volumes/properties @@ -134,11 +231,15 @@ PUBLIC int AhlBindingInit() return errcount; } -// TODO: AhlBindingTerm()? -// // TODO: Use AGL persistence framework to retrieve and set inital state/volumes +PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ) +{ + // TODO: Implement handling events from HAL... + AFB_DEBUG("AHL received event %s", evtname); +} -// TODO: OnEventFunction -// if ALSA device availability change -> update source / sink list +// TODO: OnEventFunction when it actually subscribe to other binding events +// TODO: Dynamic device handling +// if HAL 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) @@ -156,26 +257,23 @@ PUBLIC void audiohlapi_get_sources(struct afb_req req) return; } - sourcesJ = json_object_new_array(); - 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) + // List device only for current audio role and device type (pick the role device list) + int iRoleIndex = FindRoleIndex(audioRole); + if ( iRoleIndex < 0) { 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) + else { + sourcesJ = json_object_new_array(); 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); + EndpointInfoStructToJSON(&sourceJ, &sourceInfo); json_object_array_add(sourcesJ, sourceJ); } } @@ -197,26 +295,23 @@ PUBLIC void audiohlapi_get_sinks(struct afb_req req) return; } - sinksJ = json_object_new_array(); - 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) + // List device only for current audio role and device type (pick the role device list) + int iRoleIndex = FindRoleIndex(audioRole); + if ( iRoleIndex < 0) { 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) + else { + sinksJ = json_object_new_array(); 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); + EndpointInfoStructToJSON(&sinkJ, &sinkInfo); json_object_array_add(sinksJ, sinkJ); } } @@ -230,10 +325,10 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) StreamInfoT streamInfo; json_object *queryJ = NULL; char * audioRole = NULL; - EndpointTypeT endpointType; - endpointID_t endpointID = UNDEFINED_ID; - int policyAllowed = 0; - EndpointInfoT endpointInfo; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + endpointID_t endpointID = AHL_UNDEFINED; + int policyAllowed = AHL_POLICY_REJECT; + EndpointInfoT * pEndpointInfo = NULL; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:s,s:i,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointType, "endpoint_id", &endpointID); @@ -261,32 +356,32 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) return; } - if (endpointID == UNDEFINED_ID) + if (endpointID == AHL_UNDEFINED) { // Assign a device based on configuration priority (first in the list for requested role and endpoint type) - endpointInfo = g_array_index(pRoleDeviceArray,EndpointInfoT,0); + pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,0); + streamInfo.endpointSelMode = AHL_ENDPOINTSELMODE_AUTO; } else{ + streamInfo.endpointSelMode = AHL_ENDPOINTSELMODE_MANUAL; // 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; + pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + if (pEndpointInfo->endpointID == endpointID) { break; } } - if (iEndpointFound == 0) { + if (pEndpointInfo == NULL) { afb_req_fail_f(req, "Endpoint not available", "Specified endpoint not available for role:%s and device type:%d endpoint id %d",audioRole,endpointType,endpointID); return; } } // 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) + policyAllowed = Policy_OpenStream(audioRole, endpointType, pEndpointInfo->endpointID); + if (policyAllowed == AHL_POLICY_REJECT) { afb_req_fail(req, "Audio policy violation", "Open stream not allowed in current context"); return; @@ -294,12 +389,30 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) // Create stream streamInfo.streamID = CreateNewStreamID(); // create new ID - streamInfo.endpointInfo = endpointInfo; + streamInfo.streamState = STREAM_STATUS_READY; + streamInfo.streamMute = STREAM_UNMUTED; + streamInfo.pEndpointInfo = pEndpointInfo; + + char streamEventName[128]; + snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); + + streamInfo.streamStateEvent = afb_daemon_make_event(streamEventName); + err = !afb_event_is_valid(streamInfo.streamStateEvent); + if (err) { + afb_req_fail(req, "Stream event creation failure", "Could not create stream specific state change event"); + return; + } + + err = afb_req_subscribe(req,streamInfo.streamStateEvent); + if (err) { + afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event"); + return; + } // Push stream on active stream list g_array_append_val( g_AHLCtx.policyCtx.pActiveStreams, streamInfo ); - StreamInfoStructToJSON(&streamInfoJ,streamInfo); + StreamInfoStructToJSON(&streamInfoJ,&streamInfo); afb_req_success(req, streamInfoJ, "Stream info structure"); } @@ -307,7 +420,8 @@ PUBLIC void audiohlapi_stream_open(struct afb_req req) PUBLIC void audiohlapi_stream_close(struct afb_req req) { json_object *queryJ = NULL; - streamID_t streamID = UNDEFINED_ID; + streamID_t streamID = AHL_UNDEFINED; + int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID); @@ -319,12 +433,36 @@ 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 + // Call policy to verify whether creating a new audio stream is allowed in current context and possibly take other actions + policyAllowed = Policy_CloseStream(streamID); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Close stream not allowed in current context"); + return; + } + // 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){ + // Unsubscribe client from stream events + char streamEventName[128]; + snprintf(streamEventName,128,"ahl_streamstate_%d",streamInfo.streamID); + int iValid = afb_event_is_valid(streamInfo.streamStateEvent); + if (iValid) { + err = afb_req_unsubscribe(req,streamInfo.streamStateEvent); + if (err) { + afb_req_fail(req, "Stream event subscription failure", "Could not subscribe to stream specific state change event"); + return; + } + } + else{ + AFB_WARNING("Stream event no longer valid and therefore not unsubscribed"); + break; + } + g_array_remove_index(g_AHLCtx.policyCtx.pActiveStreams,i); iStreamFound = 1; break; @@ -339,35 +477,142 @@ PUBLIC void audiohlapi_stream_close(struct afb_req req) afb_req_success(req, NULL, "Stream close completed"); } -// Endpoints + PUBLIC void audiohlapi_set_stream_state(struct afb_req req) + { + json_object *queryJ = NULL; + streamID_t streamID = AHL_UNDEFINED; + StreamStateT streamState = STREAM_STATUS_MAXVALUE; + int policyAllowed = AHL_POLICY_REJECT; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "stream_id", &streamID,"state",&streamState); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d, state:%d", streamID,streamState); + + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + policyAllowed = Policy_SetStreamState(streamID,streamState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Change stream state not allowed in current context"); + return; + } + + pStreamInfo->streamState = streamState; + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",pStreamInfo->streamMute,"state",streamState); + if (err) { + afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + return; + } + afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); + + afb_req_success(req, NULL, "Set stream state"); + } + + PUBLIC void audiohlapi_set_stream_mute(struct afb_req req) + { + json_object *queryJ = NULL; + streamID_t streamID = AHL_UNDEFINED; + StreamMuteT muteState = STREAM_MUTE_MAXVALUE; + int policyAllowed = AHL_POLICY_REJECT; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "stream_id", &streamID,"mute",&muteState); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d, mute:%d", streamID,muteState); + + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + policyAllowed = Policy_SetStreamMute(streamID,muteState); + if (policyAllowed == AHL_POLICY_REJECT) + { + afb_req_fail(req, "Audio policy violation", "Mute stream not allowed in current context"); + return; + } + + pStreamInfo->streamMute = muteState; + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i}","mute",muteState,"state",pStreamInfo->streamState); + if (err) { + afb_req_fail_f(req, "Invalid event data for stream state event", "Invalid event data for stream state event: %s",json_object_to_json_string(eventDataJ)); + return; + } + afb_event_push(pStreamInfo->streamStateEvent,eventDataJ); + + afb_req_success(req, NULL, "Set stream mute completed"); + } + +PUBLIC void audiohlapi_get_stream_info(struct afb_req req) + { + json_object *queryJ = NULL; + streamID_t streamID = AHL_UNDEFINED; + json_object * streamInfoJ = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); + + StreamInfoT * pStreamInfo = GetActiveStream(streamID); + if (pStreamInfo == NULL) { + afb_req_fail_f(req, "Stream not found", "Specified stream not currently active stream_id -> %d",streamID); + return; + } + + StreamInfoStructToJSON(&streamInfoJ,pStreamInfo); + + afb_req_success(req, streamInfoJ, "Get stream info completed"); + } + PUBLIC void audiohlapi_set_volume(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * volumeStr = NULL; - int rampTimeMS = 0; - int policyAllowed = 0; + int policyAllowed = AHL_POLICY_REJECT; 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); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"volume",&volumeStr); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - 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("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s", endpointType,endpointID,volumeStr); + + // TODO: Parse volume string to support increment/absolute/percent notation (or delegate to action / policy layer to interpret) int vol = atoi(volumeStr); - policyAllowed = Policy_SetVolume(endpointType, endpointID, volumeStr, rampTimeMS); // TODO: Potentially retrieve modified value by policy (e.g. volume limit) + // TODO: Policy needs way to set cached endpoint volume value (eg.) + policyAllowed = Policy_SetVolume(endpointType, endpointID, volumeStr); // TODO: Potentially retrieve modified value by policy (e.g. volume limit) if (!policyAllowed) { afb_req_fail(req, "Audio policy violation", "Set volume not allowed in current context"); return; } - // TODO: Cache during device enumeration for efficiency + // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -376,27 +621,36 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) } // 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) + GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Ramp"); // Or _Vol for direct control (no ramping) // 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); + err = wrap_json_pack(&j_query,"{s:s,s:i}","label",gsHALControlName->str, "val",vol); if (err) { afb_req_fail_f(req, "Invalid query for HAL ctlset", "Invalid query for HAL ctlset: %s",json_object_to_json_string(j_query)); return; } + // TODO: Move this to ref implmentation policy // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->halAPIName, "ctlset", j_query, &j_response); + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlset", j_query, &j_response); if (err) { - afb_req_fail_f(req, "HAL ctlset failure", "Could not ctlset on HAL: %s",pEndpointInfo->halAPIName); + afb_req_fail_f(req, "HAL ctlset failure", "Could not ctlset on HAL: %s",pEndpointInfo->gsHALAPIName->str); return; } AFB_DEBUG("HAL ctlset response=%s", json_object_to_json_string(j_response)); + + // Package event data + json_object * eventDataJ = NULL; + err = wrap_json_pack(&eventDataJ,"{s:i,s:i,s:i}","endpoint_id",endpointID,"endpoint_type",endpointType,"value",vol); + if (err) { + afb_req_fail_f(req, "Invalid event data for volume event", "Invalid event data for volume event: %s",json_object_to_json_string(eventDataJ)); + return; + } + afb_event_push(g_AHLCtx.policyCtx.volumeEvent,eventDataJ); afb_req_success(req, NULL, "Set volume completed"); } @@ -404,9 +658,9 @@ PUBLIC void audiohlapi_set_volume(struct afb_req req) PUBLIC void audiohlapi_get_volume(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; - json_object *volumeJ; + json_object * volumeJ = NULL; queryJ = afb_req_json(req); int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); @@ -416,7 +670,7 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); - // TODO: Cache during device enumeration for efficiency + // TODO: Cache HAL control name during device enumeration for efficiency EndpointInfoT * pEndpointInfo = GetEndpointInfo(endpointID,endpointType); if (pEndpointInfo == NULL) { @@ -425,24 +679,25 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) } // 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 + GString * gsHALControlName = g_string_new(pEndpointInfo->gsAudioRole->str); + g_string_append(gsHALControlName,"_Vol"); // Use current value, not ramp target // Set endpoint volume using HAL services (leveraging ramps etc.) json_object *j_response, *j_query = NULL; + // TODO: Returned cached endpoint volume value (controlled by policy) // Package query - err = wrap_json_pack(&j_query,"{s:s}","label",halControlName); + err = wrap_json_pack(&j_query,"{s:s}","label",gsHALControlName->str); 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; } + // TODO: Return cached value or move to policy // Set the volume using the HAL - err = afb_service_call_sync(pEndpointInfo->halAPIName, "ctlget", j_query, &j_response); + err = afb_service_call_sync(pEndpointInfo->gsHALAPIName->str, "ctlget", j_query, &j_response); if (err) { - afb_req_fail_f(req, "HAL ctlget failure", "Could not ctlget on HAL: %s",pEndpointInfo->halAPIName); + afb_req_fail_f(req, "HAL ctlget failure", "Could not ctlget on HAL: %s",pEndpointInfo->gsHALAPIName->str); return; } AFB_INFO("HAL ctlget response=%s", json_object_to_json_string(j_response)); @@ -464,38 +719,77 @@ PUBLIC void audiohlapi_get_volume(struct afb_req req) afb_req_success(req, volumeJ, "Retrieved volume value"); } +// Properties +PUBLIC void audiohlapi_get_list_properties(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = AHL_UNDEFINED; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + json_object * endpointPropsJ = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); + + 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; + } + + // Build and return list of properties for specific endpoint + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, pEndpointInfo->pPropTable); + endpointPropsJ = json_object_new_array(); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + json_object * propJ = json_object_new_string(key); + json_object_array_add(endpointPropsJ, propJ); + } + + afb_req_success(req, endpointPropsJ, "Retrieved property list for endpoint"); +} + PUBLIC void audiohlapi_set_property(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; char * propValueStr = NULL; - int rampTimeMS = 0; - int policyAllowed = 0; + int policyAllowed = AHL_POLICY_REJECT; + + // TODO: object type detection (string = state, numeric = property) 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); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s ramp_time_ms:%d", endpointType,endpointID,propertyName,propValueStr,rampTimeMS); + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s", endpointType,endpointID,propertyName,propValueStr); // 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) + policyAllowed = Policy_SetProperty(endpointType, endpointID, propertyName, propValueStr); // 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 + // TODO: Policy to dispatch on right service target + // TODO: Policy tp cache value in property list + + afb_event_push(g_AHLCtx.policyCtx.propertyEvent,queryJ); afb_req_success(req, NULL, "Set property completed"); } @@ -503,21 +797,22 @@ PUBLIC void audiohlapi_set_property(struct afb_req req) PUBLIC void audiohlapi_get_property(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; + endpointID_t endpointID = AHL_UNDEFINED; EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; char * propertyName = NULL; json_object *propertyValJ; double value = 0.0; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s?i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); - // TODO: Retrieve cached property value + // TODO: Retriev189e cached property value + // TODO Account for properties with string types value = 93.0; // TODO: Get actual property value propertyValJ = json_object_new_double(value); @@ -525,94 +820,97 @@ PUBLIC void audiohlapi_get_property(struct afb_req req) afb_req_success(req, propertyValJ, "Retrieved property value"); } -PUBLIC void audiohlapi_set_state(struct afb_req req) +// Audio related events + +PUBLIC void audiohlapi_get_list_events(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; + char * audioRole = NULL; + json_object * roleEventsJ = NULL; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName,"state_value",&stateValue); + 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; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s state_value:%s", endpointType,endpointID,stateName,stateValue); + AFB_DEBUG("Parsed input arguments = audio_role:%s",audioRole); - // 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); + // Build and return list of events for specific audio role + 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; } - // 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) + GArray * pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, iRoleIndex ); + roleEventsJ = json_object_new_array(); + int iNumberEvents = pRoleEventArray->len; + for ( int i = 0 ; i < iNumberEvents; i++) { - afb_req_fail(req, "Audio policy violation", "Set endpoint state not allowed in current context"); - return; + GString gsEventName = g_array_index(pRoleEventArray,GString,i); + json_object * eventJ = json_object_new_string(gsEventName.str); + json_object_array_add(roleEventsJ, eventJ); } - - // Change the state of the endpoint as requested - - afb_req_success(req, NULL, "Set endpoint state completed"); + + afb_req_success(req, roleEventsJ, "Retrieved event list for audio role"); } -PUBLIC void audiohlapi_get_state(struct afb_req req) +PUBLIC void audiohlapi_post_event(struct afb_req req) { json_object *queryJ = NULL; - endpointID_t endpointID = UNDEFINED_ID; - EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; - json_object *stateValJ; - char * stateName = NULL; - char * stateValue = NULL; + char * eventName = NULL; + char * audioRole = NULL; + char * mediaName = NULL; + json_object *eventContext = NULL; + int policyAllowed = AHL_POLICY_REJECT; queryJ = afb_req_json(req); - int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName); + int err = wrap_json_unpack(queryJ, "{s:s,s:s,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"event_context",&eventContext); if (err) { afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); return; } - AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s", endpointType,endpointID,stateName); - - stateValJ = json_object_new_string(stateValue); - // return cached value + AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%s", eventName,audioRole); - afb_req_success(req, stateValJ, "Retrieved state value"); -} + // Verify if known event for audio role + 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; + } -// Sound events -PUBLIC void audiohlapi_post_sound_event(struct afb_req req) -{ - json_object *queryJ = NULL; - char * eventName = NULL; - char * mediaName = NULL; - char * audioRole = NULL; - json_object *audioContext = NULL; - int policyAllowed = 0; - - queryJ = afb_req_json(req); - 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)); + GArray * pRoleEventArray = NULL; + pRoleEventArray = g_ptr_array_index( g_AHLCtx.policyCtx.pEventList, iRoleIndex ); + if (pRoleEventArray->len == 0) { + afb_req_fail_f(req, "No available events", "No available events for role:%s",audioRole); + return; + } + // Check to find specific event + int iEventFound = 0; + for (int i = 0; i < pRoleEventArray->len; i++) + { + GString gs = g_array_index( pRoleEventArray, GString, i ); + if ( strcasecmp(gs.str,eventName) == 0 ) + { + iEventFound = 1; + break; + } + } + if (!iEventFound) { + afb_req_fail_f(req, "Event not found for audio role", "Event not found for roke:%s",audioRole); return; } - AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%s media_name:%s", eventName,audioRole,mediaName); // 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) + policyAllowed = Policy_PostEvent(eventName, audioRole, mediaName, (void*)eventContext); 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_event_push(g_AHLCtx.policyCtx.postEvent,queryJ); afb_req_success(req, NULL, "Posted sound event"); } @@ -621,24 +919,78 @@ PUBLIC void audiohlapi_post_sound_event(struct afb_req req) // Monitoring PUBLIC void audiohlapi_subscribe(struct afb_req req) { - // json_object *queryJ = NULL; - - // queryJ = afb_req_json(req); + json_object *queryJ = NULL; + json_object * eventArrayJ = NULL; - // TODO: Iterate through array length, parsing the string value to actual events - // TODO: Subscribe to appropriate events from other services + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:o}", "events", &eventArrayJ); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + int iNumEvents = json_object_array_length(eventArrayJ); + for (int i = 0; i < iNumEvents; i++) + { + char * pEventName = NULL; + json_object * jEvent = json_object_array_get_idx(eventArrayJ,i); + pEventName = (char *)json_object_get_string(jEvent); + if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { + afb_req_subscribe(req, g_AHLCtx.policyCtx.propertyEvent); + AFB_DEBUG("Client subscribed to endpoint property events"); + } + else if(!strcasecmp(pEventName, AHL_ENDPOINT_VOLUME_EVENT)) { + afb_req_subscribe(req, g_AHLCtx.policyCtx.volumeEvent); + AFB_DEBUG("Client subscribed to endpoint volume events"); + } + else if(!strcasecmp(pEventName, AHL_POST_EVENT)) { + afb_req_subscribe(req, g_AHLCtx.policyCtx.postEvent); + AFB_DEBUG("Client subscribed to post event calls events"); + } + else { + afb_req_fail(req, "failed", "Invalid event"); + return; + } + } 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 + json_object *queryJ = NULL; + json_object * eventArrayJ = NULL; - afb_req_success(req, NULL, "Subscribe to events finished"); + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:o}", "events", &eventArrayJ); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + int iNumEvents = json_object_array_length(eventArrayJ); + for (int i = 0; i < iNumEvents; i++) + { + char * pEventName = NULL; + json_object * jEvent = json_object_array_get_idx(eventArrayJ,i); + pEventName = (char *)json_object_get_string(jEvent); + if(!strcasecmp(pEventName, AHL_ENDPOINT_PROPERTY_EVENT)) { + afb_req_unsubscribe(req, g_AHLCtx.policyCtx.propertyEvent); + AFB_DEBUG("Client unsubscribed to endpoint property events"); + } + else if(!strcasecmp(pEventName, AHL_ENDPOINT_VOLUME_EVENT)) { + afb_req_unsubscribe(req, g_AHLCtx.policyCtx.volumeEvent); + AFB_DEBUG("Client unsubscribed to endpoint volume events"); + } + else if(!strcasecmp(pEventName, AHL_POST_EVENT)) { + afb_req_unsubscribe(req, g_AHLCtx.policyCtx.postEvent); + AFB_DEBUG("Client unsubscribed to post event calls events"); + } + else { + afb_req_fail(req, "failed", "Invalid event"); + return; + } + } + + afb_req_success(req, NULL, "Unsubscribe to events finished"); } diff --git a/src/ahl-binding.h b/src/ahl-binding.h index 2422963..508ca0a 100644 --- a/src/ahl-binding.h +++ b/src/ahl-binding.h @@ -29,45 +29,71 @@ #define PUBLIC #endif -/////////////// Binding private information ////////////////// +#define AHL_POLICY_ACCEPT 1 +#define AHL_POLICY_REJECT 0 -#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 +#define AHL_UNDEFINED -1 + +typedef int endpointID_t; +typedef int streamID_t; + +typedef enum EndpointSelectionMode { + AHL_ENDPOINTSELMODE_AUTO = 0, // Automatic endpoint selection based on config priority + AHL_ENDPOINTSELMODE_MANUAL, // Explicit endpoint selection + AHL_ENDPOINTSELMODEMAXVALUE, // Enum count, keep at the end +} EndpointSelectionModeT; + +typedef struct AudioFormat { + int sampleRate; // Sample rate + int numChannels; // Number of channels + SampleTypeT sampleType; // Sample type + // TODO: Interleaving? + // TODO: Sample sub format? +} AudioFormatT; + +typedef struct AlsaDeviceInfo { + int cardNum; // HW card number + int deviceNum; // HW device number + int subDeviceNum; // HW sub device number +} AlsaDeviceInfoT; typedef struct EndpointInfo { - 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 + endpointID_t endpointID; // Unique endpoint ID (per type) + EndpointTypeT type; // Source or sink device + GString * gsDeviceName; // Device name for applications to display + GString * gsDeviceURI; // Associated URI + DeviceURITypeT deviceURIType; // Device URI type (includes audio domain information) + GString * gsAudioRole; // Audio role that registered this endpoint + GString * gsHALAPIName; // HAL associated with the device (for volume control) + AlsaDeviceInfoT alsaInfo; // ALSA specific device information + AudioFormatT format; // Preferred audio format supported (later could be array of supported formats) + int iVolume; // Storage for current endpoint volume (policy effected). Target volume during ramping? + GHashTable * pPropTable; // Storage for array of properties (policy effected) } EndpointInfoT; typedef struct StreamInfo { - streamID_t streamID; - EndpointInfoT endpointInfo; + streamID_t streamID; // Stream unique ID + EndpointInfoT * pEndpointInfo; // Associated endpoint information + StreamStateT streamState; // Stream activity state + StreamMuteT streamMute; // Stream mute state + struct afb_event streamStateEvent; // Stream specific event for stream state changes + EndpointSelectionModeT endpointSelMode; // Automatic (priority based) or manual endpoint selection } StreamInfoT; // 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 + GPtrArray * pEventList; // Event list per audio roles (GArray*) + GHashTable * pRolePriority; // List of role priorities (int). + GArray * pInterruptBehavior; // List of interrupt behavior per audio role (int/enum). 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 + int iNumberRoles; // Number of audio roles from configuration + struct afb_event propertyEvent; // AGL event used when property changes + struct afb_event volumeEvent; // AGL event used when volume changes + struct afb_event postEvent; // AGL event used on post event call } AHLPolicyCtxT; // Global binding context @@ -77,23 +103,31 @@ typedef struct AHLCtx { 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; +// ahl-binding.c PUBLIC int AhlBindingInit(); +PUBLIC void AhlOnEvent(const char *evtname, json_object *eventJ); + // ahl-deviceenum.c -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); +int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName); +int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName); +void TermEndpoints(); // ahl-config.c -PUBLIC int ParseHLBConfig(); +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(); - -#define AUDIOHL_MAXHALCONTROLNAME_LENGTH 128 +int Policy_Endpoint_Property_Init(EndpointInfoT * io_pEndpointInfo); +int Policy_Init(); +void Policy_Term(); +int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID); +int Policy_CloseStream(streamID_t streamID); +int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ); +int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute); +int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr); +int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr); +int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext); +int Policy_AudioDeviceChange(); + + #endif // AHL_BINDING_INCLUDE diff --git a/src/ahl-config.c b/src/ahl-config.c index 432910f..dcf828f 100644 --- a/src/ahl-config.c +++ b/src/ahl-config.c @@ -23,15 +23,12 @@ #include "ahl-binding.h" -// TODO: Term() to free all allocs upon exit... - extern AHLCtxT g_AHLCtx; -PUBLIC int ParseHLBConfig() { +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]; @@ -46,40 +43,27 @@ PUBLIC int ParseHLBConfig() { 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); + int err = wrap_json_unpack(config_JFile, "{s:s,s:o,s:o}", "version", &versionStr,"audio_roles",&jAudioRoles,"hal_list",&jHALList); 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); + g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles; + // 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.pRolePriority = g_hash_table_new(g_str_hash, g_str_equal); g_AHLCtx.policyCtx.pAudioRoles = g_array_sized_new(FALSE, TRUE, sizeof(GString), iNumberOfRoles); + g_AHLCtx.policyCtx.pInterruptBehavior = g_array_sized_new(FALSE, TRUE, sizeof(int), iNumberOfRoles); g_AHLCtx.policyCtx.pSourceEndpoints = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.pSinkEndpoints = g_ptr_array_sized_new(iNumberOfRoles); + g_AHLCtx.policyCtx.pEventList = g_ptr_array_sized_new(iNumberOfRoles); g_AHLCtx.policyCtx.iNumberRoles = iNumberOfRoles; - for (unsigned int i = 0; i < iHALListLength; i++) + for (int i = 0; i < iHALListLength; i++) { char * pHAL = NULL; json_object * jHAL = json_object_array_get_idx(jHALList,i); @@ -96,17 +80,27 @@ PUBLIC int ParseHLBConfig() { } } - for (unsigned int i = 0; i < iNumberOfRoles; i++) + for (int i = 0; i < iNumberOfRoles; i++) { int priority = 0; json_object * jAudioRole = json_object_array_get_idx(jAudioRoles,i); json_object * jOutputDevices = NULL; json_object * jInputDevices = NULL; + json_object * jEvents = NULL; char * pRoleName = NULL; + InterruptedBehaviorT interupBehavior = AHL_INTERRUPTEDBEHAVIOR_CONTINUE; //Default + 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); + int iNumEvents = 0; + + err = wrap_json_unpack(jAudioRole, "{s:s,s:i,s?o,s?o,s?o,s?i}", + "name", &pRoleName, + "priority",&priority, + "output",&jOutputDevices, + "input",&jInputDevices, + "events",&jEvents, + "interupt_behavior",&interupBehavior); if (err) { AFB_ERROR("Invalid audio role configuration : %s", json_object_to_json_string(jAudioRole)); return 1; @@ -116,10 +110,14 @@ PUBLIC int ParseHLBConfig() { iNumOutDevices = json_object_array_length(jOutputDevices); if (jInputDevices) iNumInDevices = json_object_array_length(jInputDevices); + if (jEvents) + iNumEvents = json_object_array_length(jEvents); GString * gRoleName = g_string_new( pRoleName ); g_array_append_val( g_AHLCtx.policyCtx.pAudioRoles, *gRoleName ); - g_array_append_val( g_AHLCtx.policyCtx.pRolePriority, priority ); + g_hash_table_insert(g_AHLCtx.policyCtx.pRolePriority, pRoleName, &priority); + + g_array_append_val(g_AHLCtx.policyCtx.pInterruptBehavior, interupBehavior); // Sources GArray * pRoleSourceDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); @@ -131,6 +129,7 @@ PUBLIC int ParseHLBConfig() { return 1; } } + // Sinks GArray * pRoleSinkDeviceArray = g_array_new(FALSE, TRUE, sizeof(EndpointInfoT)); g_ptr_array_add(g_AHLCtx.policyCtx.pSinkEndpoints,pRoleSinkDeviceArray); if (iNumOutDevices) { @@ -140,6 +139,17 @@ PUBLIC int ParseHLBConfig() { return 1; } } + // Events + GArray * pEventsArray = g_array_new(FALSE, TRUE, sizeof(GString)); + g_ptr_array_add(g_AHLCtx.policyCtx.pEventList,pEventsArray); + // Parse and validate list of available events + for (int i = 0; i < iNumEvents; i++) + { + json_object * jEvent = json_object_array_get_idx(jEvents,i); + char * pEventName = (char *)json_object_get_string(jEvent); + GString * gsEventName = g_string_new(pEventName); + g_array_append_val(pEventsArray, *gsEventName); + } } // Build lists of all device URI referenced in config file (input/output) diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c index b866d52..c69108e 100644 --- a/src/ahl-deviceenum.c +++ b/src/ahl-deviceenum.c @@ -62,22 +62,22 @@ static int SeparateDomainFromDeviceURI( char * in_pDeviceURI, char ** out_pDomai static int IsAlsaDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_ALSA) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_ALSA) == 0); } static int IsPulseDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_PULSE) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_PULSE) == 0); } static int IsGStreamerDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_GSTREAMER) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_GSTREAMER) == 0); } static int IsExternalDomain(const char * in_pDomainStr) { - return (int) (strcasecmp(in_pDomainStr,AUDIOHL_DOMAIN_EXTERNAL) == 0); + return (int) (strcasecmp(in_pDomainStr,AHL_DOMAIN_EXTERNAL) == 0); } static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndpointInfo ) @@ -130,11 +130,11 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp 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 + g_string_assign(out_pEndpointInfo->gsDeviceName,pDeviceName); // Overwritten by HAL name if available // get card number - out_pEndpointInfo->cardNum = snd_pcm_info_get_card(pPcmInfo); - if ( out_pEndpointInfo->cardNum < 0 ) + out_pEndpointInfo->alsaInfo.cardNum = snd_pcm_info_get_card(pPcmInfo); + if ( out_pEndpointInfo->alsaInfo.cardNum < 0 ) { AFB_WARNING("No Alsa card number available"); retVal = 1; @@ -142,8 +142,8 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp } // get device number - out_pEndpointInfo->deviceNum = snd_pcm_info_get_device(pPcmInfo); - if ( out_pEndpointInfo->deviceNum < 0 ) + out_pEndpointInfo->alsaInfo.deviceNum = snd_pcm_info_get_device(pPcmInfo); + if ( out_pEndpointInfo->alsaInfo.deviceNum < 0 ) { AFB_WARNING("No Alsa device number available"); retVal = 1; @@ -151,8 +151,8 @@ static int FillALSAPCMInfo( snd_pcm_t * in_pPcmHandle, EndpointInfoT * out_pEndp } // get sub-device number - out_pEndpointInfo->subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo); - if ( out_pEndpointInfo->subDeviceNum < 0 ) + out_pEndpointInfo->alsaInfo.subDeviceNum = snd_pcm_info_get_subdevice(pPcmInfo); + if ( out_pEndpointInfo->alsaInfo.subDeviceNum < 0 ) { AFB_WARNING("No Alsa subdevice number available"); retVal = 1; @@ -199,9 +199,9 @@ static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) // 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); + if (iCardNum == io_pEndpointInfo->alsaInfo.cardNum) { + g_string_assign(io_pEndpointInfo->gsHALAPIName,pAPIName); + g_string_assign(io_pEndpointInfo->gsDeviceName,pShortName); found = 1; break; } @@ -209,20 +209,79 @@ static int RetrieveAssociatedHALAPIName(EndpointInfoT * io_pEndpointInfo) return !found; } -static int InitializeEndpointStates( EndpointInfoT * out_pEndpointInfo ) +static void InitEndpointInfo( EndpointInfoT * out_pEndpointInfo ) { - //out_pEndpointInfo = g_array_sized_new(FALSE,TRUE,sizeof) - // for list of known states - return 0; + out_pEndpointInfo->endpointID = AHL_UNDEFINED; + out_pEndpointInfo->type = ENDPOINTTYPE_MAXVALUE; + out_pEndpointInfo->gsDeviceName = g_string_new("Unassigned"); + out_pEndpointInfo->gsDeviceURI = g_string_new("Unassigned"); + out_pEndpointInfo->deviceURIType = DEVICEURITYPE_MAXVALUE; + out_pEndpointInfo->gsAudioRole = g_string_new("Unassigned"); + out_pEndpointInfo->gsHALAPIName = g_string_new("Unassigned"); + out_pEndpointInfo->alsaInfo.cardNum = AHL_UNDEFINED; + out_pEndpointInfo->alsaInfo.deviceNum = AHL_UNDEFINED; + out_pEndpointInfo->alsaInfo.subDeviceNum = AHL_UNDEFINED; + out_pEndpointInfo->format.sampleRate = AHL_UNDEFINED; + out_pEndpointInfo->format.numChannels = AHL_UNDEFINED; + out_pEndpointInfo->format.sampleType = AHL_FORMAT_UNKNOWN; + out_pEndpointInfo->pPropTable = g_hash_table_new(g_str_hash, g_str_equal); +} + +static void TermEndpointInfo( EndpointInfoT * out_pEndpointInfo ) +{ + g_string_free(out_pEndpointInfo->gsDeviceName,TRUE); + g_string_free(out_pEndpointInfo->gsDeviceURI,TRUE); + g_string_free(out_pEndpointInfo->gsAudioRole,TRUE); + g_string_free(out_pEndpointInfo->gsHALAPIName,TRUE); + // TODO: Free json_object for all property values + g_hash_table_remove_all(out_pEndpointInfo->pPropTable); + g_hash_table_destroy(out_pEndpointInfo->pPropTable); +} + +void TermEndpoints() +{ + // Sources for each role + for (int i = 0; i < g_AHLCtx.policyCtx.pSourceEndpoints->len; i++) + { + // For each endpoint within the role + GArray * pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, i ); + for (int j = 0 ; j < pRoleDeviceArray->len; j++) + { + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + TermEndpointInfo(pEndpointInfo); + } + g_array_free(pRoleDeviceArray,TRUE); + pRoleDeviceArray = NULL; + } + g_ptr_array_free(g_AHLCtx.policyCtx.pSourceEndpoints,TRUE); + g_AHLCtx.policyCtx.pSourceEndpoints = NULL; + + // Sinks for each role + for (int i = 0; i < g_AHLCtx.policyCtx.pSinkEndpoints->len; i++) + { + // For each endpoint within the role + GArray * pRoleDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, i ); + for (int j = 0 ; j < pRoleDeviceArray->len; j++) + { + EndpointInfoT * pEndpointInfo = &g_array_index(pRoleDeviceArray,EndpointInfoT,j); + TermEndpointInfo(pEndpointInfo); + } + g_array_free(pRoleDeviceArray,TRUE); + pRoleDeviceArray = NULL; + } + g_ptr_array_free(g_AHLCtx.policyCtx.pSinkEndpoints,TRUE); + g_AHLCtx.policyCtx.pSinkEndpoints = NULL; } +#define AUDIOHL_MAX_DEVICE_URI_LENGTH 128 + // For a given audio role -PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, char * in_pRoleName) { +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++) + for (int i = 0; i < iNumberDevices; i++) { char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive char * pDeviceURIDomain = NULL; @@ -242,13 +301,12 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch AFB_WARNING("Invalid device URI string -> %s",fullDeviceURI); continue; } + + InitEndpointInfo(&endpointInfo); // 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 + g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); // Overwritten by HAL name if available if (IsAlsaDomain(pDeviceURIDomain)) { @@ -275,10 +333,11 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch snd_pcm_close(pPcmHandle); + // TODO: Consider policy call to determine URI for set volume execution for a particular role (hw?) // Retrieve HAL API name err = RetrieveAssociatedHALAPIName(&endpointInfo); if (err) { - AFB_WARNING("SetVolume will fail without HAL association ->%s",endpointInfo.deviceURI); + AFB_WARNING("SetVolume will fail without HAL association ->%s",endpointInfo.gsDeviceURI->str); // Choose not to skip anyhow... } } @@ -302,16 +361,15 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch 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); + g_string_assign(endpointInfo.gsDeviceURI,pDeviceURIPCM); + g_string_assign(endpointInfo.gsAudioRole,in_pRoleName); endpointInfo.endpointID = CreateNewSourceID(); endpointInfo.type = ENDPOINTTYPE_SOURCE; + err = Policy_Endpoint_Property_Init(&endpointInfo); + if (err) { + AFB_WARNING("Policy endpoint properties initalization failed for endpointid :%d type:%d",endpointInfo.endpointID, endpointInfo.type); + // Choose not to skip anyhow... + } // add to structure to list of available source devices GArray * pRoleSourceDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSourceEndpoints, in_iRoleIndex ); @@ -324,12 +382,12 @@ PUBLIC int EnumerateSources(json_object * in_jSourceArray, int in_iRoleIndex, ch } // For a given audio role -PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * in_pRoleName) { +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++) + for (int i = 0; i < iNumberDevices; i++) { char fullDeviceURI[AUDIOHL_MAX_DEVICE_URI_LENGTH]; // strtok is destructive char * pDeviceURIDomain = NULL; @@ -353,10 +411,12 @@ PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * // 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); + InitEndpointInfo(&endpointInfo); + + endpointInfo.alsaInfo.cardNum = -1; + endpointInfo.alsaInfo.deviceNum = -1; + endpointInfo.alsaInfo.cardNum = -1; + g_string_assign(endpointInfo.gsDeviceName,pDeviceURIPCM); if (IsAlsaDomain(pDeviceURIDomain)) { @@ -383,6 +443,7 @@ PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * snd_pcm_close(pPcmHandle); + // TODO: Consider policy call to determine URI for set volume execution for a particular role (hw?) // Retrieve HAL API name err = RetrieveAssociatedHALAPIName(&endpointInfo); if (err) { @@ -412,16 +473,15 @@ PUBLIC int EnumerateSinks(json_object * in_jSinkArray, int in_iRoleIndex, char * 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); + g_string_assign(endpointInfo.gsDeviceURI,pDeviceURIPCM); + g_string_assign(endpointInfo.gsAudioRole,in_pRoleName); endpointInfo.endpointID = CreateNewSinkID(); endpointInfo.type = ENDPOINTTYPE_SINK; + err = Policy_Endpoint_Property_Init(&endpointInfo); + if (err) { + AFB_WARNING("Policy endpoint properties initalization failed for endpointid :%d type:%d",endpointInfo.endpointID, endpointInfo.type); + // Choose not to skip anyhow... + } // add to structure to list of available source devices GArray * pRoleSinkDeviceArray = g_ptr_array_index( g_AHLCtx.policyCtx.pSinkEndpoints, in_iRoleIndex ); diff --git a/src/ahl-interface.h b/src/ahl-interface.h index 781bb05..edead36 100644 --- a/src/ahl-interface.h +++ b/src/ahl-interface.h @@ -18,28 +18,12 @@ #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) @@ -52,23 +36,87 @@ typedef enum DeviceURIType { 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" +typedef enum StreamState { + STREAM_STATUS_READY = 0, // Stream is inactive + STREAM_STATUS_RUNNING, // Stream is running + STREAM_STATUS_MAXVALUE, // Enum count, keep at the end +} StreamStateT; + +typedef enum StreamMute { + STREAM_UNMUTED = 0, // Stream is not muted + STREAM_MUTED, // Stream is muted + STREAM_MUTE_MAXVALUE, // Enum count, keep at the end +} StreamMuteT; + +// Define default behavior of audio role when interrupted by higher priority sources +typedef enum InterruptedBehavior { + AHL_INTERRUPTEDBEHAVIOR_CONTINUE = 0, // Continue to play when interrupted (e.g. media may be ducked) + AHL_INTERRUPTEDBEHAVIOR_CANCEL, // Abort playback when interrupted (e.g. non-important HMI feedback that does not make sense later) + AHL_INTERRUPTEDBEHAVIOR_PAUSE, // Pause source when interrupted, to be resumed afterwards (e.g. non-temporal guidance) + AHL_INTERRUPTEDBEHAVIOR_MAXVALUE, // Enum count, keep at the end +} InterruptedBehaviorT; + +#define AHL_ENDPOINT_PROPERTY_EVENT "ahl_endpoint_property_event" +#define AHL_ENDPOINT_VOLUME_EVENT "ahl_endpoint_volume_event" +#define AHL_POST_EVENT "ahl_post_event" + +// CPU endianness assumed in all formats +typedef enum SampleType { + AHL_FORMAT_UNKNOWN = -1, // Unknown + AHL_FORMAT_U8 = 0, // Unsigned 8 bit + AHL_FORMAT_S16, // Signed 16 bit Little Endian + AHL_FORMAT_S24, // Signed 24 bit Little Endian using low three bytes in 32-bit word + AHL_FORMAT_S32, // Signed 32 bit Little Endian + AHL_FORMAT_FLOAT, // Float 32 bit Little Endian, Range -1.0 to 1.0 + AHL_FORMAT_FLOAT64, // Float 64 bit Little Endian, Range -1.0 to 1.0 + AHL_FORMAT_IEC958, // IEC-958 Little Endian (SPDIF) + AHL_FORMAT_MU_LAW, // Mu-Law + AHL_FORMAT_A_LAW, // A-Law + AHL_FORMAT_IMA_ADPCM, // Ima-ADPCM + AHL_FORMAT_MPEG, // MPEG + AHL_FORMAT_GSM, // GSM + AHL_FORMAT_G723, // G723 + AHL_FORMAT_DSD, // Direct stream digital + AHL_FORMAT_MAXVALUE, // Enum count, keep at the end +} SampleTypeT; + +// Stream event types +typedef enum StreamEventType { + AHL_STREAMEVENT_START = 0, // Audio streaming should start + AHL_STREAMEVENT_STOP, // Audio streaming should stop + AHL_STREAMEVENT_UNMUTE, // Audio stream unmuted + AHL_STREAMEVENT_MUTE, // Audio stream muted + AHL_STREAMEVENT_MAXVALUE, // Enum count, keep at the end +} StreamEventTypeT; + +// Known audio domain string definitions (for configuration file format and device URI interpretation) +#define AHL_DOMAIN_ALSA "alsa" +#define AHL_DOMAIN_PULSE "pulse" +#define AHL_DOMAIN_GSTREAMER "gstreamer" +#define AHL_DOMAIN_EXTERNAL "external" + +// Standardized name for common audio roles (not enforced in any way, just helps compatibility) +#define AHL_ROLE_WARNING "warning" // Safety-relevant or critical alerts/alarms +#define AHL_ROLE_GUIDANCE "guidance" // Important user information where user action is expected (e.g. navigation instruction) +#define AHL_ROLE_NOTIFICATION "notification" // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...) +#define AHL_ROLE_COMMUNICATION "communications" // Voice communications (e.g. handsfree, speech recognition) +#define AHL_ROLE_ENTERTAINMENT "entertainment" // Multimedia content (e.g. tuner, media player, etc.) +#define AHL_ROLE_SYSTEM "system" // System level content or development +#define AHL_ROLE_STARTUP "startup" // Early (startup) sound +#define AHL_ROLE_SHUTDOWN "shutdown" // Late (shutdown) sound +#define AHL_ROLE_NONE "none" // Non-assigned / legacy applications + +// Standardized list of properties (not enforced in any way, just helps compatibility) +#define AHL_PROPERTY_BALANCE "balance" +#define AHL_PROPERTY_FADE "fade" +#define AHL_PROPERTY_EQ_LOW "eq_bass" +#define AHL_PROPERTY_EQ_MID "eq_mid" +#define AHL_PROPERTY_EQ_HIGH "eq_treble" -// 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" +// Standardized list of events +#define AHL_EVENTS_PLAYSOUND "play_sound" +#define AHL_EVENTS_ECHOCANCEL_ENABLE "echocancel_enable" +#define AHL_EVENTS_ECHOCANCEL_DISABLE "echocancel_disable" -// 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 index 7741afd..fa050af 100644 --- a/src/ahl-policy.c +++ b/src/ahl-policy.c @@ -21,70 +21,119 @@ #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) +// TODO: Currently only isolated in separate source file. Objective is to make this to at least a shared lib plug-in (shared C context) +// Going a step further would be to implement this within a distinct policy binding (requires to switch to JSON interface) 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) +static void Add_Endpoint_Property_Numeric( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, int in_iPropertyValue) { - return 1; // Policy allowed + json_object * propValueJ = json_object_new_int(in_iPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -PUBLIC int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr, int rampTimeMS) +static void Add_Endpoint_Property_String( EndpointInfoT * io_pEndpointInfo, char * in_pPropertyName, char * in_pPropertyValue) { - return 1; // Policy allowed + json_object * propValueJ = json_object_new_string(in_pPropertyValue); + g_hash_table_insert(io_pEndpointInfo->pPropTable, in_pPropertyName, propValueJ); } -PUBLIC int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr, int rampTimeMS) +int Policy_OpenStream(char *pAudioRole, EndpointTypeT endpointType, endpointID_t endpointID) { - return 1; // Policy allowed + // TODO: Example rule -> when system is in shutdown or low power mode, no audio stream can be opened (return AHL_POLICY_REJECT) + + return AHL_POLICY_ACCEPT; } +int Policy_CloseStream(streamID_t streamID) +{ + // For completeness, unlikely to have any policy rules here -PUBLIC int Policy_SetState(EndpointTypeT endpointType, endpointID_t endpointID, char *pStateName, char *pStateValue) + return AHL_POLICY_ACCEPT; +} + +int Policy_SetStreamState(streamID_t streamID, StreamStateT streamState ) { + // If higher priority audio role stream requires audio ducking (and un-ducking) of other streams (e.g. navigation ducks entertainment) + // TODO: Could potentially provide a fairly generic system using interupt behavior information and audio role priority (implemented and can be customized here) + // Specific exception can also be + + // Source exclusion. E.g. When a source (e.g tuner) with same audio role as already active stream (e.g. media player) with same endpoint target, + // the former source is stopped (i.e. raise streamstate stop event) + + // If source on communication role is active (e.g. handsfree call), activating entertainment sources is prohibited + + // If handsfree or speech recognition (communication role) is started during entertainment playback, mute all entertainment streams (any endpoints except RSE) + + // Startup or Shutdown audio stream mute entertainment (unmut when stream no longer active) + + // Optional: Mute endpoint before activation, unmute afterwards (after a delay?) to avoid noises + + + return AHL_POLICY_ACCEPT; +} +int Policy_SetStreamMute(streamID_t streamID, StreamMuteT streamMute) +{ + return AHL_POLICY_ACCEPT; +} - //Mute - if(strcmp(pStateName, AUDIOHL_STATE_NAME_MUTE) == 0) - { - if(strcmp(pStateName, AUDIOHL_STATE_NAME_MUTE) == 0) +int Policy_SetVolume(EndpointTypeT endpointType, endpointID_t endpointID, char *volumeStr) +{ + return AHL_POLICY_ACCEPT; +} +int Policy_SetProperty(EndpointTypeT endpointType, endpointID_t endpointID, char *propertyName, char *propValueStr) +{ + return AHL_POLICY_ACCEPT; +} - return AUDIOHL_POLICY_ACCEPT; - } +int Policy_PostEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext) +{ + // TODO: Any event with media specified should trigger action on provided rendering services (e.g. Wwise binding, gstreamer file player wrapper, MPDC? simple ALSA player (aplay)?) - //Retrieve global context + // Example (when the policy is hooked to CAN events). Post audio playback events other than safety during reverse gear engaged declined + // Example post HMI audio role playback events declined when higher priority streams are active - //Active rule check - - //Ducking rule settings + return AHL_POLICY_ACCEPT; +} +int Policy_AudioDeviceChange() +{ + // Allow or disallow a new audio endpoint to be used by the system + // TODO: Policy assigns audio role(s) for device (or default) + // TODO: Raise events to engage device switching if active stream in audio role assigned to the new endpoint + + return AHL_POLICY_ACCEPT; +} - return AUDIOHL_POLICY_ACCEPT; +int Policy_Endpoint_Property_Init(EndpointInfoT * io_EndpointInfo) +{ + // Populate list of supported properties for specified endpoint (use helper functions) + // Setup initial values for all properties + + // TODO: Switch on different known endpoints to populate different properties + + // Test example + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_LOW,3); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_MID,0); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_EQ_HIGH,6); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_BALANCE,0); + Add_Endpoint_Property_Numeric(io_EndpointInfo,AHL_PROPERTY_FADE,30); + Add_Endpoint_Property_String(io_EndpointInfo,"preset_name","flat"); + + return 0; // No errors } -PUBLIC int Policy_PostSoundEvent(char *eventName, char *audioRole, char *mediaName, void *audioContext) +int Policy_Init() { - return 1; // Policy allowed + // Other policy specific initialization + return 0; // No errors } -PUBLIC int Policy_AudioDeviceChange() +void Policy_Term() { - // 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 + // Policy termination +} |