From 87be9daee783c15219f960afb092fecb755ab5fc Mon Sep 17 00:00:00 2001 From: Loïc Collignon Date: Fri, 14 Dec 2018 14:42:45 +0100 Subject: WIP: adding a master volume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a master volume that binds to the master volume of the real hardware, as set up in the corresponding HAL. Change-Id: I18b02327bb42177c21ed8d9db9f7a7b8adbd87d0 Signed-off-by: Loïc Collignon --- ahl-binding/ahl-binding.cpp | 94 +++++++++++++++++++--- ahl-binding/ahl-binding.hpp | 13 ++-- ahl-binding/interrupt.cpp | 17 ++-- ahl-binding/role.cpp | 125 +++++++++++++++++++++++------- ahl-binding/role.hpp | 16 +++- conf.d/project/etc/policy-4a-sample1.json | 22 +++++- 6 files changed, 225 insertions(+), 62 deletions(-) diff --git a/ahl-binding/ahl-binding.cpp b/ahl-binding/ahl-binding.cpp index 98d36b6..1446a81 100644 --- a/ahl-binding/ahl-binding.cpp +++ b/ahl-binding/ahl-binding.cpp @@ -88,6 +88,15 @@ void ahl_api_get_roles(afb_req_t req) ahl_binding_t::instance().get_roles(req); } +/** + * @brief Callback invoked when clients call the verb 'active'. + * @param[in] req Request to handle. + */ +void ahl_api_active(afb_req_t req) +{ + ahl_binding_t::instance().active(req); +} + /** * @brief Callback invoked when clients call the verb 'subscribe'. * @param[in] req Request to handle. @@ -181,16 +190,16 @@ int ahl_binding_t::init() } AFB_API_NOTICE(handle_, "Required '%s' API found!", HAL_MGR_API); - afb_api_seal(handle_); - AFB_API_NOTICE(handle_, "API is now sealed!"); - - volume_changed_ = afb_api_make_event(handle_, "volume_changed"); + volume_changed_ = afb_api_make_event(handle_, VOLUME_CHANGED); if(!afb_event_is_valid(volume_changed_)) { - AFB_API_ERROR(handle_, "Failed to create the \"volume_changed\" event!"); + AFB_API_ERROR(handle_, "Failed to create the \"" VOLUME_CHANGED "\" event!"); return -2; } + afb_api_seal(handle_); + AFB_API_NOTICE(handle_, "API is now sealed!"); + if (update_streams()) return -1; return 0; } @@ -202,7 +211,7 @@ int ahl_binding_t::init() int ahl_binding_t::update_streams() { json_object* loaded = nullptr; - size_t hals_count = 0, streams_count = 0; + size_t hals_count = 0, streams_count = 0, playbacks_count = 0; if (afb_api_call_sync(handle_, "4a-hal-manager", "loaded", json_object_new_object(), &loaded, nullptr, nullptr)) { @@ -217,7 +226,7 @@ int ahl_binding_t::update_streams() if (!json_object_is_type(loaded, json_type_array)) { - AFB_API_ERROR(handle_, "Expected an array from '4a-hal-manager/loaded', but got something else!"); + AFB_API_ERROR(handle_, "Expected an array from '4a-hal-manager/loaded', but got something else: %s", json_object_to_json_string(loaded)); json_object_put(loaded); return -1; } @@ -260,6 +269,24 @@ int ahl_binding_t::update_streams() ); } + json_object* playbacksJ = nullptr; + json_object_object_get_ex(info, "playbacks", &playbacksJ); + playbacks_count = json_object_array_length(playbacksJ); + for(int j = 0; j < playbacks_count; ++j) + { + json_object * nameJ = nullptr, * mixerJ = nullptr; + json_object * playbackJ = json_object_array_get_idx(playbacksJ, j); + + json_object_object_get_ex(playbackJ, "name", &nameJ); + json_object_object_get_ex(playbackJ, "mixer-name", &mixerJ); + + update_stream( + halname, + json_object_get_string(nameJ), + json_object_get_string(mixerJ) + ); + } + json_object_put(info); } json_object_put(loaded); @@ -277,7 +304,7 @@ void ahl_binding_t::update_stream(std::string halname, std::string stream, std:: { for(auto& r : roles_) { - if(r.stream() == stream) + if(r.resource() == stream) { if (r.device_uri().size()) AFB_API_WARNING(handle_, "Multiple stream with same name: '%s'.", stream.c_str()); @@ -309,6 +336,18 @@ void ahl_binding_t::load_static_verbs() throw std::runtime_error("Failed to add 'get_role' verb to the API."); } + if (afb_api_add_verb( + handle_, + "active", + "Control the active role", + ahl_api_active, + nullptr, + nullptr, + AFB_SESSION_NONE_X2, 0)) + { + throw std::runtime_error("Failed to add 'get_role' verb to the API."); + } + if (afb_api_add_verb( handle_, "subscribe", @@ -450,20 +489,51 @@ void ahl_binding_t::get_roles(afb_req_t req) const afb_req_success(req, result, nullptr); } +void ahl_binding_t::active(afb_req_t req) +{ + role_t* activerole = nullptr; + for(role_t& r : roles_) + { + if (r.opened()) + { + if (activerole) + { + if (activerole->priority() < r.priority()) + activerole = &r; + } + else activerole = &r; + } + } + + if (!activerole) + { + afb_req_fail(req, "No active role!", nullptr); + return; + } + + activerole->invoke(req); + + //json_object* response = nullptr; + //char* error = nullptr; + //char* info = nullptr; + //afb_api_call_sync(handle_, HL_API_NAME, activerole->uid().c_str(), afb_req_json(req), &response, &error, &info); + //afb_req_reply(req, response, error, info); +} + void ahl_binding_t::subscribe(afb_req_t req) const { if (afb_req_subscribe(req, volume_changed_)) - afb_req_fail(req, "Failed to subscribe to \"volume_changed\" event!", nullptr); + afb_req_fail(req, "Failed to subscribe to \"" VOLUME_CHANGED "\" event!", nullptr); else - afb_req_success(req, nullptr, "Subscribed to \"volume_changed\" event!"); + afb_req_success(req, nullptr, "Subscribed to \"" VOLUME_CHANGED "\" event!"); } void ahl_binding_t::unsubscribe(afb_req_t req) const { if (afb_req_unsubscribe(req, volume_changed_)) - afb_req_fail(req, "Failed to unsubscribe from \"volume_changed\" event!", nullptr); + afb_req_fail(req, "Failed to unsubscribe from \"" VOLUME_CHANGED "\" event!", nullptr); else - afb_req_success(req, nullptr, "Unsubscribed from \"volume_changed\" event!"); + afb_req_success(req, nullptr, "Unsubscribed from \"" VOLUME_CHANGED "\" event!"); } int ahl_binding_t::emit_volume_changed(const std::string& role, int volume) const diff --git a/ahl-binding/ahl-binding.hpp b/ahl-binding/ahl-binding.hpp index 24f4acb..e5a1d05 100644 --- a/ahl-binding/ahl-binding.hpp +++ b/ahl-binding/ahl-binding.hpp @@ -17,13 +17,13 @@ * limitations under the License. */ +#include #include #include -#include -#include #include #include -#include +#include +#include #include "config_entry.hpp" #include "role.hpp" @@ -31,6 +31,8 @@ #define HL_API_NAME "ahl-4a" #define HL_API_INFO "Audio high level API for AGL applications" #define HAL_MGR_API "4a-hal-manager" +#define VOLUME_CHANGED "volume_changed" +#define MASTER_CONTROL "agl-master-playback-volume" #include "afb-binding-common.h" @@ -42,6 +44,7 @@ private: afb_api_t handle_; afb_event_t volume_changed_; std::vector roles_; + std::stack active_role_; explicit ahl_binding_t(); @@ -62,6 +65,8 @@ public: int init(); void event(std::string name, json_object* arg); void get_roles(afb_req_t req) const; + void active(afb_req_t req); + void master(afb_req_t req) const; void subscribe(afb_req_t req) const; void unsubscribe(afb_req_t req) const; int emit_volume_changed(const std::string& role, int volume) const; @@ -72,5 +77,3 @@ public: void audiorole(afb_req_t req); int parse_roles_config(json_object* o); }; - - diff --git a/ahl-binding/interrupt.cpp b/ahl-binding/interrupt.cpp index 5bf0b79..7893d99 100644 --- a/ahl-binding/interrupt.cpp +++ b/ahl-binding/interrupt.cpp @@ -65,9 +65,9 @@ int interrupt_t::apply(afb_req_t req, const role_t& role) AFB_API_DEBUG(ahl_binding_t::instance().handle(), "Call '%s'/'%s' '%s'", - r.hal().c_str(), r.stream().c_str(), json_object_to_json_string(arg)); + r.hal().c_str(), r.resource().c_str(), json_object_to_json_string(arg)); - if(afb_api_call_sync(ahl_binding_t::instance().handle(), r.hal().c_str(), r.stream().c_str(), arg, &result, nullptr, nullptr)) + if(afb_api_call_sync(ahl_binding_t::instance().handle(), r.hal().c_str(), r.resource().c_str(), arg, &result, nullptr, nullptr)) { afb_req_fail(req, "Failed to call 'ramp' action on stream", nullptr); return -1; @@ -78,7 +78,7 @@ int interrupt_t::apply(afb_req_t req, const role_t& role) applied_on_.push_back(std::make_tuple(r.uid(), json_object_get_int(jvolold))); AFB_API_DEBUG(ahl_binding_t::instance().handle(), "POLICY: Applying a ramp to '%s' stream because '%s' is opened and have higher priority!", - r.stream().c_str(), role.stream().c_str()); + r.resource().c_str(), role.resource().c_str()); } } } @@ -117,13 +117,6 @@ void interrupt_t::clear() // Create an fake-interrupt, with the old volume json_object* interrupt = json_tokener_parse(json_object_to_json_string(args_)); json_object_object_add(interrupt, "volume", json_object_new_int(vol)); // Replace the volume - /* - json_object* volume = nullptr; - if (json_object_object_get_ex(interrupt, "volume", &volume)) - { - json_object_set_int(volume, vol); - } - */ json_object* arg = json_object_new_object(); json_object_object_add(arg, "ramp", interrupt); @@ -131,9 +124,9 @@ void interrupt_t::clear() AFB_API_DEBUG(ahl_binding_t::instance().handle(), "Call '%s'/'%s' '%s", - r.hal().c_str(), r.stream().c_str(), json_object_to_json_string(arg)); + r.hal().c_str(), r.resource().c_str(), json_object_to_json_string(arg)); - if(afb_api_call_sync(ahl_binding_t::instance().handle(), r.hal().c_str(), r.stream().c_str(), arg, &result, nullptr, nullptr)) + if(afb_api_call_sync(ahl_binding_t::instance().handle(), r.hal().c_str(), r.resource().c_str(), arg, &result, nullptr, nullptr)) { AFB_API_ERROR(ahl_binding_t::instance().handle(), "Failed to call 'ramp' action on '%s'", role.c_str()); diff --git a/ahl-binding/role.cpp b/ahl-binding/role.cpp index 3a4dd0e..1a5d7ba 100644 --- a/ahl-binding/role.cpp +++ b/ahl-binding/role.cpp @@ -22,23 +22,55 @@ using session_t = std::vector; +inline restype_t fromstring(const std::string& str) +{ + if (str == "" || str == "stream") return restype_t::stream; + if (str == "playback") return restype_t::playback; + if (str == "control") return restype_t::control; + + std::stringstream ss; + ss << "Unknown resource type: " << str; + throw std::runtime_error(ss.str()); +} + role_t::role_t(json_object* j) { + rtype_ = restype_t::stream; + jcast(uid_, j, "uid"); jcast(description_, j, "description"); jcast(priority_, j, "priority"); - jcast(stream_, j, "stream"); + jcast(resource_, j, "resource"); jcast_array(interrupts_, j, "interrupts"); + + std::string type; + jcast(type, j, "type"); + rtype_ = fromstring(type); + + // device_uri_ = (rtype_ == restype_t::stream) ? "" : resource_; + if (rtype_ == restype_t::control) + { + device_uri_ = resource_; + hal_ = "4a-hal-csl-cm106-8ch-usb"; + } opened_ = false; } role_t& role_t::operator<<(json_object* j) { + rtype_ = restype_t::stream; + jcast(uid_, j, "uid"); jcast(description_, j, "description"); jcast(priority_, j, "priority"); - jcast(stream_, j, "stream"); + jcast(resource_, j, "resource"); jcast_array(interrupts_, j, "interrupts"); + + std::string type; + jcast(type, j, "type"); + rtype_ = fromstring(type); + + // device_uri_ = (rtype_ == restype_t::stream) ? "" : resource_; return *this; } @@ -57,9 +89,14 @@ std::string role_t::hal() const return hal_; } -std::string role_t::stream() const +std::string role_t::resource() const { - return stream_; + return resource_; +} + +restype_t role_t::rtype() const +{ + return rtype_; } int role_t::priority() const @@ -67,6 +104,11 @@ int role_t::priority() const return priority_; } +const std::vector& role_t::interrupts() const +{ + return interrupts_; +} + std::string role_t::device_uri() const { return device_uri_; @@ -92,9 +134,14 @@ void role_t::hal(std::string v) hal_ = v; } -void role_t::stream(std::string v) +void role_t::resource(std::string v) +{ + resource_ = v; +} + +void role_t::rtype(restype_t v) { - stream_ = v; + rtype_ = v; } void role_t::priority(int v) @@ -107,11 +154,6 @@ void role_t::device_uri(std::string v) device_uri_ = v; } -const std::vector& role_t::interrupts() const -{ - return interrupts_; -} - int role_t::apply_policy(afb_req_t req) { return interrupts_.size() ? interrupts_[0].apply(req, *this) : 0; @@ -158,7 +200,11 @@ void role_t::open(afb_req_t r, json_object* o) return; } - if (!apply_policy(r)) + if (rtype_ != restype_t::stream) + { + afb_req_fail(r, "Only stream resources can be opened!", nullptr); + } + else if (!apply_policy(r)) { afb_req_context(r, 0, // Do not replace previous context if any @@ -180,7 +226,7 @@ void role_t::open(afb_req_t r, json_object* o) afb_api_call( api, role->hal_.c_str(), - role->stream_.c_str(), + role->resource_.c_str(), a, NULL, NULL); @@ -229,11 +275,13 @@ void role_t::close(afb_req_t r, json_object* o) } void role_t::mute(afb_req_t r, json_object* o) { - do_mute(r, true); + if (rtype_ != restype_t::stream) afb_req_fail(r, "Only stream resource can be muted!", nullptr); + else do_mute(r, true); } void role_t::unmute(afb_req_t r, json_object *o) { - do_mute(r, false); + if (rtype_ != restype_t::stream) afb_req_fail(r, "Only stream resource can be unmuted!", nullptr); + else do_mute(r, false); } void role_t::do_mute(afb_req_t r, bool v) { @@ -245,7 +293,7 @@ void role_t::do_mute(afb_req_t r, bool v) { afb_api_call( api, hal_.c_str(), - stream_.c_str(), + resource_.c_str(), a, [](void* closure, json_object* result, const char* error, const char* info, afb_api_t handle) { @@ -262,7 +310,7 @@ void role_t::do_mute(afb_req_t r, bool v) { struct volumeclosure { - std::string role; + role_t* role; afb_req_t req; }; @@ -300,32 +348,57 @@ void role_t::volume(afb_req_t r, json_object* o) json_object_get(value); json_object* a = json_object_new_object(); - json_object_object_add(a, "volume", value); + json_object_object_add(a, (rtype_ == restype_t::control ? "value" : "volume"), value); + + AFB_API_DEBUG(api, "VOLUME QUERY: %s", json_object_to_json_string(a)); volumeclosure* userdata = new volumeclosure(); - userdata->role = uid_; + userdata->role = this; userdata->req = afb_req_addref(r); afb_api_call( api, hal_.c_str(), - stream_.c_str(), + resource_.c_str(), a, [](void* closure, json_object* result, const char* error, const char* info, afb_api_t handle) { AFB_API_DEBUG(handle, "Got the following answer: %s", json_object_to_json_string(result)); volumeclosure* r = reinterpret_cast(closure); - json_object_get(result); - if (error) afb_req_fail(r->req, json_object_to_json_string(result), nullptr); + if (error) + { + json_object_get(result); + afb_req_fail(r->req, json_object_to_json_string(result), nullptr); + } else { - json_object* volnew; - if (json_object_object_get_ex(result, "volnew", &volnew)) + if (r->role->rtype() == restype_t::control) + { + int average = 0; + size_t arraylen = json_object_array_length(result); + for (size_t i = 0; i < arraylen; ++i) + { + average += json_object_get_int(json_object_array_get_idx(result, i)); + } + average = average / (int)arraylen; + + json_object* response = json_object_new_object(); + json_object* newvol = json_object_new_int(average); + json_object_object_add(response, "newvol", newvol); + ahl_binding_t::instance().emit_volume_changed(r->role->uid(), average); + afb_req_success(r->req, response, nullptr); + } + else { - ahl_binding_t::instance().emit_volume_changed(r->role, json_object_get_int(volnew)); + json_object* volnew; + if (json_object_object_get_ex(result, "volnew", &volnew)) + { + ahl_binding_t::instance().emit_volume_changed(r->role->uid(), json_object_get_int(volnew)); + } + json_object_get(result); + afb_req_success(r->req, result, nullptr); } - afb_req_success(r->req, result, nullptr); } afb_req_unref(r->req); delete r; diff --git a/ahl-binding/role.hpp b/ahl-binding/role.hpp index 055a405..5623f83 100644 --- a/ahl-binding/role.hpp +++ b/ahl-binding/role.hpp @@ -21,6 +21,13 @@ #include "interrupt.hpp" #include "afb-binding-common.h" +enum class restype_t +{ + stream, + playback, + control +}; + class role_t { private: @@ -28,9 +35,10 @@ private: std::string uid_; std::string description_; std::string hal_; - std::string stream_; + std::string resource_; int priority_; std::vector interrupts_; + restype_t rtype_; std::string device_uri_; bool opened_ = false; @@ -58,7 +66,8 @@ public: std::string uid() const; std::string description() const; std::string hal() const; - std::string stream() const; + std::string resource() const; + restype_t rtype() const; int priority() const; const std::vector& interrupts() const; std::string device_uri() const; @@ -67,7 +76,8 @@ public: void uid(std::string v); void description(std::string v); void hal(std::string v); - void stream(std::string v); + void resource(std::string v); + void rtype(restype_t v); void device_uri(std::string v); void priority(int v); diff --git a/conf.d/project/etc/policy-4a-sample1.json b/conf.d/project/etc/policy-4a-sample1.json index 673caa7..2cc44a6 100644 --- a/conf.d/project/etc/policy-4a-sample1.json +++ b/conf.d/project/etc/policy-4a-sample1.json @@ -13,23 +13,37 @@ "controls": [], "events": [], "roles":[ + { + "uid": "master-control", + "description": "Master volume using control", + "priority": 0, + "resource": "agl-master-playback-volume", + "type": "control" + }, + { + "uid": "master-playback", + "description": "Master volume using playback", + "priority": 0, + "resource": "playback", + "type": "playback" + }, { "uid": "radio", "description": "Radio (tuner)", "priority": 0, - "stream": "radio_stream" + "resource": "radio_stream" }, { "uid": "multimedia", "description": "Multimedia content (e.g. media player, etc.)", "priority": 0, - "stream": "multimedia" + "resource": "multimedia" }, { "uid": "emergency", "description": "Safety-relevant or critical alerts/alarms", "priority": 100, - "stream": "emergency", + "resource": "emergency", "interrupts":[ {"type": "ramp", "args": { "uid": "ramp-slow", "volume": 30} } ] @@ -39,7 +53,7 @@ "name": "navigation", "description": "Navigation instructions (GPS, turn directions, etc...)", "priority": 25, - "stream": "navigation", + "resource": "navigation", "interrupts":[ {"type": "ramp", "args": { "uid": "ramp-slow", "volume": 30} } ] -- cgit 1.2.3-korg