diff options
Diffstat (limited to 'src/ahl-binding.c')
-rw-r--r-- | src/ahl-binding.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/src/ahl-binding.c b/src/ahl-binding.c new file mode 100644 index 0000000..99236bd --- /dev/null +++ b/src/ahl-binding.c @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2017 "Audiokinetic Inc" + * Author Francois Thibault <fthibault@audiokinetic.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "ahl-binding.h" +#include "ahl-apidef.h" // Generated from JSON OpenAPI +#include "wrap-json.h" + +// TODO: json_object_put to free JSON objects? potential leaks currently + +// Helper macros/func for packaging JSON objects from C structures + +#define EndpointInfoStructToJSON(__JSON_OBJECT__, __ENDPOINTINFOSTRUCT__) \ + wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:s}", "endpoint_id", __ENDPOINTINFOSTRUCT__.endpoint_id, "type", __ENDPOINTINFOSTRUCT__.type, "name", __ENDPOINTINFOSTRUCT__.name); + +#define RoutingInfoStructToJSON(__JSON_OBJECT__, __ROUTINGINFOSTRUCT__) \ + wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:i}", "routing_id", __ROUTINGINFOSTRUCT__.routing_id, "source_id", __ROUTINGINFOSTRUCT__.source_id, "sink_id", __ROUTINGINFOSTRUCT__.sink_id); + +static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT streamInfo) +{ + json_object *endpointInfoJ; + EndpointInfoStructToJSON(endpointInfoJ,streamInfo.endpoint_info); + wrap_json_pack(streamInfoJ, "{s:i,s:s}", "stream_id", streamInfo.stream_id, "pcm_name", streamInfo.pcm_name); + json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); +} + +// Binding initialization +PUBLIC int AhlBindingInit() +{ + + int errcount = 0; + + // Initialize list of available sources/sinks using lower level services + errcount += EnumerateSources(); + errcount += EnumerateSinks(); + + // TODO: Register for device changes from lower level services + + // TODO: Parse high-level binding configuration file + + // TODO: Perform other binding initialization tasks + + AFB_DEBUG("Audio high-level Binding success errcount=%d", errcount); + return errcount; +} + +PUBLIC void audiohlapi_get_sources(struct afb_req req) +{ + json_object *sourcesJ = NULL; + json_object *sourceJ = NULL; + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + if (audioRole != AUDIOROLE_MAXVALUE) + { + AFB_DEBUG("Filtering according to specified audio role=%d", audioRole); + } + + // Fake run-time data for test purposes + EndpointInfoT sources[3]; + sources[0].endpoint_id = 0; + sources[0].type = 0; + sources[0].name = "Source0"; + sources[1].endpoint_id = 1; + sources[1].type = 1; + sources[1].name = "Source1"; + sources[2].endpoint_id = 2; + sources[2].type = 2; + sources[2].name = "Source2"; + + sourcesJ = json_object_new_array(); + for ( unsigned int i = 0 ; i < 3; i++) + { + EndpointInfoStructToJSON(sourceJ, sources[i]); + json_object_array_add(sourcesJ, sourceJ); + } + + afb_req_success(req, sourcesJ, "List of sources"); +} + +PUBLIC void audiohlapi_get_sinks(struct afb_req req) +{ + json_object *sinksJ = NULL; + json_object *sinkJ = NULL; + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + if (audioRole != AUDIOROLE_MAXVALUE) + { + AFB_DEBUG("Filtering according to specified audio role=%d", audioRole); + } + + // Fake run-time data for test purposes + EndpointInfoT sinks[3]; + sinks[0].endpoint_id = 0; + sinks[0].type = 0; + sinks[0].name = "Sink0"; + sinks[1].endpoint_id = 1; + sinks[1].type = 1; + sinks[1].name = "Sink1"; + sinks[2].endpoint_id = 2; + sinks[2].type = 2; + sinks[2].name = "Sink2"; + + sinksJ = json_object_new_array(); + for ( unsigned int i = 0 ; i < 3; i++) + { + EndpointInfoStructToJSON(sinkJ, sinks[i]); + json_object_array_add(sinksJ, sinkJ); + } + + afb_req_success(req, sinksJ, "List of sinks"); +} + +PUBLIC void audiohlapi_stream_open(struct afb_req req) +{ + json_object *streamInfoJ = NULL; + StreamInfoT streamInfo; + json_object *queryJ = NULL; + AudioRoleT audioRole; + EndpointTypeT endpointType; + endpointID_t endpointID = UNDEFINED_ID; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointType, "endpoint_id", &endpointID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = audio_role:%d endpointType:%d endpointID:%d", audioRole,endpointType,endpointID); + + if (endpointID == UNDEFINED_ID) + { + // TODO: Go through configuration and available devices to find best device for specified role + endpointID = 2; + } + + // Fake run-time data for test purposes + streamInfo.stream_id = 12; + streamInfo.pcm_name = "plug:Entertainment"; + streamInfo.endpoint_info.endpoint_id = endpointID; + streamInfo.endpoint_info.type = endpointType; + streamInfo.endpoint_info.name = "MainSpeakers"; + + StreamInfoStructToJSON(&streamInfoJ,streamInfo); + + afb_req_success(req, streamInfoJ, "Stream info structure"); +} + +PUBLIC void audiohlapi_stream_close(struct afb_req req) +{ + json_object *queryJ = NULL; + streamID_t streamID = UNDEFINED_ID; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); + + // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing + + afb_req_success(req, NULL, "Stream close completed"); +} + +// Routings +PUBLIC void audiohlapi_get_available_routings(struct afb_req req) +{ + json_object *routingsJ; + json_object *routingJ; + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + if (audioRole != AUDIOROLE_MAXVALUE) + { + AFB_DEBUG("Filtering according to specified audio role=%d", audioRole); + } + + // Fake run-time data for test purposes + RoutingInfoT routings[3]; + routings[0].source_id = 0; + routings[0].sink_id = 0; + routings[0].routing_id = 0; + routings[1].source_id = 1; + routings[1].sink_id = 1; + routings[1].routing_id = 1; + routings[2].source_id = 2; + routings[2].sink_id = 2; + routings[2].routing_id = 2; + + routingsJ = json_object_new_array(); + for (unsigned int i = 0; i < 3; i++) + { + RoutingInfoStructToJSON(routingJ, routings[i]); + json_object_array_add(routingsJ, routingJ); + } + + afb_req_success(req, routingsJ, "List of available routings"); +} + +PUBLIC void audiohlapi_add_routing(struct afb_req req) +{ + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + routingID_t routingID = UNDEFINED_ID; + json_object *routingJ = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s?i}", "audio_role", &audioRole,"routing_id",routingID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = audio_role:%d routing_id:%d", audioRole,routingID); + + // Fake run-time data for test purposes + RoutingInfoT routingInfo; + routingInfo.routing_id = routingID; + routingInfo.source_id = 3; + routingInfo.sink_id = 4; + + RoutingInfoStructToJSON(routingJ,routingInfo); + + afb_req_success(req,routingJ, "Selected routing information"); +} + +PUBLIC void audiohlapi_remove_routing(struct afb_req req) +{ + json_object *queryJ = NULL; + routingID_t routingID = UNDEFINED_ID; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i}", "routing_id", &routingID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = routing_id:%d", routingID); + + // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing + + afb_req_success(req, NULL, "Remove routing completed"); +} + +// Endpoints +PUBLIC void audiohlapi_set_endpoint_volume(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * volumeStr = NULL; + int rampTimeMS = 0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"volume",&volumeStr,"ramp_time_ms",&rampTimeMS); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s ramp_time_ms: %d", endpointType,endpointID,volumeStr,rampTimeMS); + + // TODO: Parse volume string to support increment/absolute/percent notation + + afb_req_success(req, NULL, "Set volume completed"); +} + +PUBLIC void audiohlapi_get_endpoint_volume(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + json_object *volumeJ; + double volume = 0.0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); + + volume = 87.0; // TODO: Get actual volume value + volumeJ = json_object_new_double(volume); + + afb_req_success(req, volumeJ, "Retrieved volume value"); +} + +PUBLIC void audiohlapi_set_endpoint_property(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * propertyName = NULL; + char * propValueStr = NULL; + int rampTimeMS = 0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr,"ramp_time_ms",&rampTimeMS); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s ramp_time_ms:%d", endpointType,endpointID,propertyName,propValueStr,rampTimeMS); + + // TODO: Parse property value string to support increment/absolute/percent notation + + afb_req_success(req, NULL, "Set property completed"); +} + +PUBLIC void audiohlapi_get_endpoint_property(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * propertyName = NULL; + json_object *propertyValJ; + double value = 0.0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s?i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); + + value = 93.0; // TODO: Get actual property value + propertyValJ = json_object_new_double(value); + + afb_req_success(req, propertyValJ, "Retrieved property value"); +} + +PUBLIC void audiohlapi_set_endpoint_state(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * stateName = NULL; + char * stateValue = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName,"state_value",&stateValue); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s state_value:%s", endpointType,endpointID,stateName,stateValue); + + afb_req_success(req, NULL, "Set endpoint state completed"); +} + +PUBLIC void audiohlapi_get_endpoint_state(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + json_object *stateValJ; + char * stateName = NULL; + char * stateValue = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s", endpointType,endpointID,stateName); + + stateValJ = json_object_new_string(stateValue); + + afb_req_success(req, stateValJ, "Retrieved state value"); +} + +// Sound events +PUBLIC void audiohlapi_post_sound_event(struct afb_req req) +{ + json_object *queryJ = NULL; + char * eventName = NULL; + char * mediaName = NULL; + AudioRoleT audioRole; + json_object *audioContext = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:s,s?i,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"audio_context",&audioContext); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%d media_name:%s", eventName,audioRole,mediaName); + + // TODO: Post sound event to rendering services + + afb_req_success(req, NULL, "Posted sound event"); + } + + +// Monitoring +PUBLIC void audiohlapi_subscribe(struct afb_req req) +{ + // json_object *queryJ = NULL; + + // queryJ = afb_req_json(req); + + // TODO: Iterate through array length, parsing the string value to actual events + // TODO: Subscribe to appropriate events from other services + + afb_req_success(req, NULL, "Subscribe to events finished"); +} |