From 22c2633ac833ae78ff84beafce19cec0f05758a2 Mon Sep 17 00:00:00 2001 From: Loïc Collignon Date: Tue, 12 Jun 2018 11:00:25 +0200 Subject: Update to last softmixer and hal, plus some fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the last 4a-softmixer and 4a-hal-generic versions. Fix some potential crash and performance bottleneck. Now support sessions to disallow multiple opening or closing by not owner. Change-Id: I95b020a3fab03b1e1058812adae1d8d5986f282b Signed-off-by: Loïc Collignon --- ahl-binding/ahl-binding.cpp | 428 +++++++++++++++++++------------------------- 1 file changed, 185 insertions(+), 243 deletions(-) (limited to 'ahl-binding/ahl-binding.cpp') diff --git a/ahl-binding/ahl-binding.cpp b/ahl-binding/ahl-binding.cpp index fa4c74b..2bf1664 100644 --- a/ahl-binding/ahl-binding.cpp +++ b/ahl-binding/ahl-binding.cpp @@ -20,6 +20,16 @@ afb_dynapi* AFB_default; // BUG: Is it possible to get rid of this ? +/** + * @brief Callback invoked on new api creation. + * @param[in] handle Handle to the new api. + * @return Status code, zero if success. + */ +int ahl_api_create(void*, struct afb_dynapi* handle) +{ + return ahl_binding_t::instance().preinit(handle); +} + /** * @brief Entry point for dynamic API. * @param[in] handle Handle to start with for API creation. @@ -43,26 +53,80 @@ int afbBindingVdyn(afb_dynapi* handle) } /** - * @brief Callback to create the new api. - * @param[in] handle Handle to the new api. + * @brief Callback invoked when API enter the init phase. * @return Status code, zero if success. */ -int ahl_api_create(void*, struct afb_dynapi* handle) +int ahl_api_init(afb_dynapi*) { - return ahl_binding_t::instance().preinit(handle); + return ahl_binding_t::instance().init(); } +/** + * @brief Callback invoked when an event is received. + * @param[in] e Event's name. + * @param[in] o Event's args. + */ +void ahl_api_on_event(afb_dynapi*, const char* e, struct json_object* o) +{ + ahl_binding_t::instance().event(e, o); +} + +/** + * @brief Callback invoked when a 'roles' section is found in config file. + * @param[in] o Config section to handle. + * @return Status code, zero if success. + */ +int ahl_api_config_roles(afb_dynapi*, CtlSectionT*, json_object* o) +{ + return ahl_binding_t::instance().parse_roles_config(o); +} + +/** + * @brief Callback invoked when clients call the verb 'get_roles'. + * @param[in] req Request to handle. + */ +void ahl_api_get_roles(afb_request* req) +{ + ahl_binding_t::instance().get_roles(req); +} + +/** + * @brief Callback invoked when clients call a 'role' verb. + * @param[in] req Request to handle. + * + * Handle dynamic verbs based on role name ('multimedia', 'navigation', ...) + */ +void ahl_api_role(afb_request* req) +{ + role_t* role = (role_t*)req->vcbdata; + assert(role != nullptr); + + role->invoke(req); +} + +/** + * @brief Default constructor. + */ ahl_binding_t::ahl_binding_t() : handle_{nullptr} { } +/** + * @brief Get the singleton instance. + * @return The unique instance. + */ ahl_binding_t& ahl_binding_t::instance() { static ahl_binding_t s; return s; } +/** + * @brief This method is called during the pre-init phase of loading the binding. + * @param[in] handle Handle to the api. + * @return Status code, zero if success. + */ int ahl_binding_t::preinit(afb_dynapi* handle) { handle_ = handle; @@ -70,17 +134,13 @@ int ahl_binding_t::preinit(afb_dynapi* handle) try { load_static_verbs(); - load_controller_api(); + load_controller_configs(); - if (afb_dynapi_on_event(handle_, - [](afb_dynapi*, const char* e, struct json_object* o) - { ahl_binding_t::instance().event(e, o); } - ) - ) throw std::runtime_error("Failed to register event handler callback."); + if (afb_dynapi_on_event(handle_, ahl_api_on_event)) + throw std::runtime_error("Failed to register event handler callback."); - if (afb_dynapi_on_init(handle_, - [](afb_dynapi*) { return ahl_binding_t::instance().init(); } - )) throw std::runtime_error("Failed to register init handler callback."); + if (afb_dynapi_on_init(handle_, ahl_api_init)) + throw std::runtime_error("Failed to register init handler callback."); } catch(std::exception& e) { @@ -91,6 +151,9 @@ int ahl_binding_t::preinit(afb_dynapi* handle) return 0; } +/** + * @brief Initialize the API. + */ int ahl_binding_t::init() { using namespace std::placeholders; @@ -109,73 +172,86 @@ int ahl_binding_t::init() } AFB_DYNAPI_NOTICE(handle_, "Required 'smixer' API found!"); - // Requires corresponding API - for(const auto& h : config_.hals()) + afb_dynapi_seal(handle_); + AFB_DYNAPI_NOTICE(handle_, "API is now sealed!"); + + if (update_streams()) return -1; + return 0; +} + +/** + * @brief Update audio roles definition by binding to streams. + * @return Status code, zero if success. + */ +int ahl_binding_t::update_streams() +{ + json_object* loaded = nullptr; + json_object* response = nullptr; + size_t i = 0, j = 0; + size_t hals_count = 0, streams_count = 0; + + if (afb_dynapi_call_sync(handle_, "4a-hal-manager", "loaded", json_object_new_object(), &loaded)) + { + AFB_DYNAPI_ERROR(handle_, "Failed to call 'loaded' verb on '4a-hal-manager' API!"); + if (loaded) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(loaded)); + return -1; + } + response = json_object_object_get(loaded, "response"); + hals_count = json_object_array_length(response); + + for(i = 0; i < hals_count; ++i) { - if (afb_dynapi_require_api(handle_, h.c_str(), 1)) + json_object* info = nullptr; + json_object* streams = nullptr; + const char* halname = json_object_get_string(json_object_array_get_idx(response, i)); + AFB_DYNAPI_DEBUG(handle_, "Found an active HAL: %s", halname); + + if (afb_dynapi_call_sync(handle_, halname, "info", json_object_new_object(), &info)) { - AFB_DYNAPI_ERROR(handle_, "Failed to require '%s' API!", h.c_str()); + AFB_DYNAPI_ERROR(handle_, "Failed to call 'info' verb on '%s' API!", halname); + if (info) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(info)); return -1; } - AFB_DYNAPI_NOTICE(handle_, "Required '%s' API found!", h.c_str()); - json_object* result = nullptr; - - // if(afb_dynapi_call_sync(handle_, h.c_str(), "init-mixer", nullptr, &result)) - // { - // AFB_DYNAPI_ERROR(handle_, "Failed to call 'init-mixer' verb on '%s' API!", h.c_str()); - // if (result) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(result)); - // return -1; - // } - // AFB_DYNAPI_NOTICE(handle_, "Mixer initialized using '%s' API.", h.c_str()); - - // json_object* response = nullptr; - // json_object* verbose = json_object_new_object(); - // json_object_object_add(verbose, "verbose", json_object_new_int(1)); - // if (afb_dynapi_call_sync(handle_, h.c_str(), "list", verbose, &response)) - // { - // AFB_DYNAPI_ERROR(handle_, "Failed to call 'list' verb on '%s' API!", h.c_str()); - // if (result) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(result)); - // return -1; - // } - - json_object* response = nullptr; - json_object* arg = json_object_new_object(); - json_object_object_add(arg, "streams", json_object_new_boolean(TRUE)); - - if (afb_dynapi_call_sync(handle_, "smixer", "info", arg, &response)) + + streams = json_object_object_get(json_object_object_get(info, "response"), "streams"); + streams_count = json_object_array_length(streams); + for(j = 0; j < streams_count; ++j) { - AFB_DYNAPI_ERROR(handle_, "Failed to call 'list' verb on '%s' API!", h.c_str()); - if (result) AFB_DYNAPI_NOTICE(handle_, "%s", json_object_to_json_string(result)); - return -1; - } + update_stream( + halname, + json_object_get_string(json_object_object_get(json_object_array_get_idx(streams, j), "name")), + json_object_get_string(json_object_object_get(json_object_array_get_idx(streams, j), "cardId")) + ); + } + json_object_put(info); + } + json_object_put(loaded); + + return 0; +} - json_object* streams = json_object_object_get(response, "response"); - AFB_DYNAPI_DEBUG(handle_, "Called 'list' verb on '%s' API: %s", h.c_str(), json_object_to_json_string(streams)); - json_object* array = json_object_object_get(streams, "streams"); - size_t streams_count = json_object_array_length(array); - for(size_t i = 0; i < streams_count; ++i) +/** + * @brief Update the stream info for audio roles. + * @param[in] halname The hal on which the stream is. + * @param[in] stream The name of the stream. + * @param[in] deviceid The device ID to return when opening an audio role. + */ +void ahl_binding_t::update_stream(std::string halname, std::string stream, std::string deviceid) +{ + for(auto& r : roles_) + { + if(r.stream() == stream) { - std::string stream_name; - std::string device_uri; - json_object* item = json_object_array_get_idx(array, i); - - jcast(stream_name, item, "uid"); - jcast(device_uri, item, "alsa"); - - config_.set_device_uri(stream_name, device_uri); + if (r.device_uri().size()) + AFB_DYNAPI_WARNING(handle_, "Multiple stream with same name: '%s'.", stream.c_str()); + else + { + r.device_uri(deviceid); + r.hal(halname); + } } } - - afb_dynapi_seal(handle_); - AFB_DYNAPI_NOTICE(handle_, "API is now sealed!"); - - actions_["volume"] = std::bind(&ahl_binding_t::volume, this, _1, _2, _3, _4); - actions_["open"] = std::bind(&ahl_binding_t::open, this, _1, _2, _3, _4); - actions_["close"] = std::bind(&ahl_binding_t::close, this, _1, _2, _3, _4); - actions_["interrupt"] = std::bind(&ahl_binding_t::interrupt, this, _1, _2, _3, _4); - - return 0; } void ahl_binding_t::event(std::string name, json_object* arg) @@ -189,7 +265,7 @@ void ahl_binding_t::load_static_verbs() handle_, "get_roles", "Retrieve array of available audio roles", - [](afb_request* r) { ahl_binding_t::instance().get_roles(r); }, + ahl_api_get_roles, nullptr, nullptr, AFB_SESSION_NONE_V2)) @@ -198,7 +274,7 @@ void ahl_binding_t::load_static_verbs() } } -void ahl_binding_t::load_controller_api() +void ahl_binding_t::load_controller_configs() { char* dir_list = getenv("CONTROL_CONFIG_PATH"); if (!dir_list) dir_list = strdup(CONTROL_CONFIG_PATH); @@ -241,17 +317,8 @@ int ahl_binding_t::load_controller_config(const std::string& path) {.key = "onload", .uid = nullptr, .info = nullptr, .loadCB = OnloadConfig, .handle = nullptr, .actions = nullptr}, {.key = "controls", .uid = nullptr, .info = nullptr, .loadCB = ControlConfig, .handle = nullptr, .actions = nullptr}, {.key = "events", .uid = nullptr, .info = nullptr, .loadCB = EventConfig, .handle = nullptr, .actions = nullptr}, - { - .key = "ahl-4a", - .uid = nullptr, - .info = nullptr, - .loadCB = [](afb_dynapi*, CtlSectionT* s, json_object* o){ - return ahl_binding_t::instance().load_config(s, o); - }, - .handle = nullptr, - .actions = nullptr - }, - {.key = nullptr, .uid = nullptr, .info = nullptr, .loadCB = nullptr, .handle = nullptr, .actions = nullptr} + {.key = "roles", .uid = nullptr, .info = nullptr, .loadCB = ahl_api_config_roles, .handle = nullptr, .actions = nullptr }, + {.key = nullptr, .uid = nullptr, .info = nullptr, .loadCB = nullptr, .handle = nullptr, .actions = nullptr} }; CtlLoadSections(handle_, controller_config, controller_sections); @@ -259,187 +326,62 @@ int ahl_binding_t::load_controller_config(const std::string& path) return 0; } -int ahl_binding_t::load_config(CtlSectionT* section, json_object* o) +int ahl_binding_t::parse_roles_config(json_object* o) { - config_ << o; + assert(o != nullptr); + assert(json_object_is_type(o, json_type_array)); - // Add corresponding verbs - for(const auto& r : config_.roles()) + size_t count = json_object_array_length(o); + roles_.reserve(count); + for(size_t i = 0; i < count; ++i) { - AFB_DYNAPI_NOTICE(handle_, "New audio role: %s", r.name().c_str()); + json_object* jr = json_object_array_get_idx(o, i); + assert(jr != nullptr); - if (afb_dynapi_add_verb( - handle_, - r.name().c_str(), - r.description().c_str(), - [](afb_request* r) { ahl_binding_t::instance().audiorole(r); }, - nullptr, - nullptr, - AFB_SESSION_NONE_V2)) - { - std::stringstream ss; - ss << "Failed to add '" << r.name() << "' verb to the API."; - throw std::runtime_error(ss.str()); - } + roles_.push_back(role_t(jr)); + role_t& r = roles_[roles_.size() - 1]; + if(create_api_verb(&r)) + return -1; } - - return 0; -} -void ahl_binding_t::audiorole(afb_request* req) -{ - std::string verb = afb_request_get_verb(req); - const auto& roles = config_.roles(); - auto r = std::find_if(roles.cbegin(), roles.cend(), [&verb](const role_t& r) { return r.name() == verb; }); - - if (r == roles.cend()) - { - afb_request_fail(req, "The requested role doesn't exist!", nullptr); - return; - } - - json_object* query = json_object_get(afb_request_json(req)); - - std::string action; - jcast(action, query, "action"); - std::transform(action.begin(), action.end(), action.begin(), ::tolower); - - auto a = actions_.find(action); - if (a == actions_.end()) - { - afb_request_fail(req, "The requested action doesn't exist!", nullptr); - return; - } - a->second(req, verb, r->stream(), query); + return 0; } -void ahl_binding_t::get_roles(afb_request* req) +int ahl_binding_t::create_api_verb(role_t* r) { - json_object* result = json_object_new_array(); - for(const auto& r : config_.roles()) - json_object_array_add(result, json_object_new_string(r.name().c_str())); - afb_request_success(req, result, nullptr); -} + AFB_DYNAPI_NOTICE(handle_, "New audio role: %s", r->uid().c_str()); -void ahl_binding_t::volume(afb_request* req, std::string role, std::string stream, json_object* arg) -{ - json_object* value = json_object_object_get(arg, "value"); - json_object_get(value); - - if (!value) - { - afb_request_fail(req, "No value given!", nullptr); - return; - } - - json_object* a = json_object_new_object(); - json_object_object_add(a, "volume", value); - AFB_DYNAPI_DEBUG(handle_, "Call the HAL with the following argument: %s", json_object_to_json_string(a)); - - afb_dynapi_call( + if (afb_dynapi_add_verb( handle_, - config_.hals()[0].c_str(), // BUG: What to do if multiple hals ? - stream.c_str(), - a, - [](void* closure, int status, json_object* result, afb_dynapi* handle) - { - AFB_DYNAPI_DEBUG(handle, "Got the following answer: %s", json_object_to_json_string(result)); - afb_request* r = (afb_request*)closure; - - json_object_get(result); - if (status) afb_request_fail(r, json_object_to_json_string(result), nullptr); - else afb_request_success(r, result, nullptr); - afb_request_unref(r); - }, - afb_request_addref(req)); -} - -void ahl_binding_t::open(afb_request* req, std::string role, std::string stream, json_object* arg) -{ - for(const auto& r : config_.roles()) + r->uid().c_str(), + r->description().c_str(), + ahl_api_role, + r, + nullptr, + AFB_SESSION_NONE_V2)) { - if (r.name() == role) - { - if ( - ext::cfind_if(opened_roles_, - [&role](const role_t& r){ return r.name() == role;}) != opened_roles_.end() - ) - { - afb_request_fail(req, "This role is already opened!", nullptr); - return; - } - // Execute policy for current asked role - policy_open(req, r); - return; - } + AFB_DYNAPI_ERROR(handle_, "Failed to add '%s' verb to the API.", + r->uid().c_str()); + return -1; } - afb_request_fail(req, "Can't open the specified role!", nullptr); + + return 0; } -void ahl_binding_t::policy_open(afb_request* req, const role_t& role) +void ahl_binding_t::get_roles(afb_request* req) { - if(role.interrupts().size()) - { - const interrupt_t& i = role.interrupts()[0]; - /*if (i.type() == "mute") - { - } - else if (i.type() == "continue") - { - } - else if (i.type() == "cancel") - { - } - else */if (i.type() == "ramp") - { - for(const auto& r: opened_roles_) - { - if (role.priority() > r.priority()) - { - // { "ramp" : { "uid" : "ramp-slow", "volume" : 30 } } - json_object* arg = json_object_new_object(); - json_object_object_add(arg, "ramp", i.args()); - json_object_get(i.args()); - json_object* result = nullptr; - - AFB_DYNAPI_NOTICE(handle_, "Call 'smixer'/'%s' '%s", r.stream().c_str(), json_object_to_json_string(arg)); - - if(afb_dynapi_call_sync(handle_, "smixer", r.stream().c_str(), arg, &result)) - { - afb_request_fail(req, "Failed to call 'ramp' action on stream", nullptr); - return; - } - AFB_DYNAPI_NOTICE(handle_, "POLICY: Applying a ramp to '%s' stream because '%s' is opened and have higher priority!", r.stream().c_str(), role.stream().c_str()); - } - } - } - else - { - afb_request_fail(req, "Unkown interrupt uid!", nullptr); - return; - } - } - - json_object* result = json_object_new_object(); - json_object_object_add(result, "device_uri", json_object_new_string(role.device_uri().c_str())); + json_object* result = json_object_new_array(); + for(const auto& r : roles_) + json_object_array_add(result, json_object_new_string(r.uid().c_str())); afb_request_success(req, result, nullptr); - opened_roles_.push_back(role); } -void ahl_binding_t::close(afb_request* req, std::string role, std::string stream, json_object* arg) +const std::vector ahl_binding_t::roles() const { - AFB_DYNAPI_DEBUG(handle_, "Got this arg: %s", json_object_to_json_string(arg)); - auto it = ext::cfind_if(opened_roles_, [&role](const role_t& r) { return r.name() == role; }); - if (it == opened_roles_.cend()) - { - afb_request_fail(req, "This role is already closed!", nullptr); - return; - } - opened_roles_.erase(it); - afb_request_success(req, nullptr, "Role closed!"); + return roles_; } -void ahl_binding_t::interrupt(afb_request* req, std::string role, std::string stream, json_object* arg) +afb_dynapi* ahl_binding_t::handle() const { - afb_request_fail(req, "Not implemented yet!", nullptr); + return handle_; } -- cgit 1.2.3-korg