summaryrefslogtreecommitdiffstats
path: root/libsoundmanager/libsoundmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libsoundmanager/libsoundmanager.cpp')
-rw-r--r--libsoundmanager/libsoundmanager.cpp676
1 files changed, 676 insertions, 0 deletions
diff --git a/libsoundmanager/libsoundmanager.cpp b/libsoundmanager/libsoundmanager.cpp
new file mode 100644
index 0000000..c61f9d2
--- /dev/null
+++ b/libsoundmanager/libsoundmanager.cpp
@@ -0,0 +1,676 @@
+/*
+ * Copyright (c) 2017 TOYOTA MOTOR CORPORATION
+ *
+ * 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.
+ */
+
+#include <stdarg.h>
+#include <sys/socket.h>
+#include <iostream>
+#include <algorithm>
+#include <thread>
+#include <errno.h>
+#include <libsoundmanager.hpp>
+
+#define ELOG(args,...) _ELOG(__FUNCTION__,__LINE__,args,##__VA_ARGS__)
+#ifdef DEBUGMODE
+ #define DLOG(args,...) _DLOG(__FUNCTION__,__LINE__,args,##__VA_ARGS__)
+#else
+ #define DLOG(args,...)
+#endif
+static void _DLOG(const char* func, const int line, const char* log, ...);
+static void _ELOG(const char* func, const int line, const char* log, ...);
+
+using namespace std;
+
+static bool has_verb(const std::string& verb);
+static const char API[] = "soundmanager";
+
+static const std::vector<std::string> api_list{
+ std::string("connect"),
+ std::string("disconnect"),
+ std::string("setVolume"),
+ std::string("volumeStep"),
+ std::string("setSinkMuteState"),
+ std::string("getListMainConnections"),
+ std::string("ackConnect"),
+ std::string("ackDisconnect"),
+ std::string("ackSetSourceState"),
+ std::string("registerSource"),
+ std::string("deregisterSource"),
+ std::string("subscribe"),
+ std::string("unsubscribe")
+};
+
+static const std::vector<std::string> event_list{
+ std::string("newMainConnection"),
+ std::string("volumeChanged"),
+ std::string("removedMainConnection"),
+ std::string("sinkMuteStateChanged"),
+ std::string("mainConnectionStateChanged"),
+ std::string("setRoutingReady"),
+ std::string("setRoutingRundown"),
+ std::string("asyncConnect"),
+ std::string("asyncSetSourceState")
+};
+
+static void _on_hangup_static(void *closure, struct afb_wsj1 *wsj)
+{
+ static_cast<LibSoundmanager*>(closure)->on_hangup(NULL,wsj);
+}
+
+static void _on_call_static(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg)
+{
+ /* LibSoundmanager is not called from other process */
+}
+
+static void _on_event_static(void* closure, const char* event, struct afb_wsj1_msg *msg)
+{
+ static_cast<LibSoundmanager*>(closure)->on_event(NULL,event,msg);
+}
+
+static void _on_reply_static(void *closure, struct afb_wsj1_msg *msg)
+{
+ static_cast<LibSoundmanager*>(closure)->on_reply(NULL,msg);
+}
+
+static void *event_loop_run(void *args)
+{
+ struct sd_event* loop = (struct sd_event*)(args);
+ DLOG("start eventloop");
+ for(;;)
+ sd_event_run(loop, 30000000);
+}
+
+LibSoundmanager::LibSoundmanager()
+{
+}
+
+LibSoundmanager::~LibSoundmanager()
+{
+ if(mploop)
+ {
+ sd_event_unref(mploop);
+ }
+ if(sp_websock != NULL)
+ {
+ afb_wsj1_unref(sp_websock);
+ }
+}
+
+
+/**
+ * This function is initialization function
+ *
+ * #### Parameters
+ * - port [in] : This argument should be specified to the port number to be used for websocket
+ * - token [in] : This argument should be specified to the token to be used for websocket
+ *
+ * #### Rreturn
+ * Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ *
+ */
+int LibSoundmanager::init(int port, const string& token)
+{
+ int ret;
+ if(port > 0 && token.size() > 0)
+ {
+ mport = port;
+ mtoken = token;
+ }
+ else
+ {
+ ELOG("port and token should be > 0, Initial port and token uses.");
+ return -1;
+ }
+
+ ret = initialize_websocket();
+ if(ret != 0 )
+ {
+ ELOG("Failed to initialize websocket");
+ return -1;
+ }
+ ret = init_event();
+ if(ret != 0 )
+ {
+ ELOG("Failed to initialize websocket");
+ return -1;
+ }
+ ret = run_eventloop();
+ if(ret == -1){
+ ELOG("Failed to create thread");
+ return -1;
+ }
+ return 0;
+}
+
+int LibSoundmanager::initialize_websocket()
+{
+ mploop = NULL;
+ onEvent = nullptr;
+ onReply = nullptr;
+ int ret = sd_event_default(&mploop);
+ if(ret < 0)
+ {
+ ELOG("Failed to create event loop");
+ goto END;
+ }
+ /* Initialize interface from websocket */
+ {
+ minterface.on_hangup = _on_hangup_static;
+ minterface.on_call = _on_call_static;
+ minterface.on_event = _on_event_static;
+ string muri = "ws://localhost:" + to_string(mport) + "/api?token=" + mtoken;
+ sp_websock = afb_ws_client_connect_wsj1(mploop, muri.c_str(), &minterface, this);
+ }
+ if(sp_websock == NULL)
+ {
+ ELOG("Failed to create websocket connection");
+ goto END;
+ }
+
+ return 0;
+END:
+ if(mploop)
+ {
+ sd_event_unref(mploop);
+ }
+ return -1;
+}
+
+int LibSoundmanager::init_event(){
+ /* subscribe most important event for sound right */
+ return subscribe(string("asyncSetSourceState"));
+}
+
+/**
+ * This function register callback function for reply/event message from sound manager
+ *
+ * #### Parameters
+ * - event_cb [in] : This argument should be specified to the callback for subscribed event
+ * - reply_cb [in] : This argument should be specified to the reply callback for call function
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * Event callback is invoked by sound manager for event you subscribed.
+ * If you would like to get event, please call subscribe function before/after this function
+ */
+void LibSoundmanager::register_callback(
+ void (*event_cb)(const string& event, struct json_object* event_contents),
+ void (*reply_cb)(struct json_object* reply_contents),
+ void (*hangup_cb)(void))
+{
+ onEvent = event_cb;
+ onReply = reply_cb;
+ onHangup = hangup_cb;
+}
+
+void LibSoundmanager::register_callback(
+ void (*reply_cb)(struct json_object* reply_contents),
+ void (*hangup_cb)(void))
+{
+ onReply = reply_cb;
+ onHangup = hangup_cb;
+}
+
+int LibSoundmanager::run_eventloop()
+{
+ if(mploop && sp_websock)
+ {
+ pthread_t thread_id;
+ int ret = pthread_create(&thread_id, NULL, event_loop_run, mploop);
+ if(ret != 0)
+ {
+ ELOG("Cannot run eventloop due to error:%d", errno);
+ return -1;
+ }
+ else
+ return 0;
+ }
+ else
+ {
+ ELOG("Connecting is not established yet");
+ return -1;
+ }
+}
+
+/**
+ * This function calls registerSource of Audio Manager via WebSocket
+ * registerSource is registration as source for policy management
+ *
+ * #### Parameters
+ * - sourceName [in] : This argument should be specified to the source name (e.g. "MediaPlayer")
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * This function must be called to get source ID
+ * mainConnectionID is returned by async reply function
+ *
+ */
+int LibSoundmanager::registerSource(const string& sourceName)
+{
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ struct json_object* j_obj = json_object_new_object();
+ struct json_object* jsn = json_object_new_string(sourceName.c_str());
+ json_object_object_add(j_obj, "appname", jsn);
+ return this->call(__FUNCTION__, j_obj);
+}
+
+/**
+ * This function calls connect of Audio Manager via WebSocket
+ * connect is to get sound right
+ *
+ * #### Parameters
+ * - sourceID [in] : This argument should be specified to the sourceID as int. This parameter is returned value of registerSource
+ * - sinkID [in] : This argument should be specified to the sinkID as int. ID is specified by AudioManager
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * This function must be called to get source right
+ * connectionID is
+ *
+ */
+int LibSoundmanager::connect(int sourceID, int sinkID)
+{
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ struct json_object* j_obj = json_object_new_object();
+ struct json_object* jsource = json_object_new_int(sourceID);
+ struct json_object* jsink = json_object_new_int(sinkID);
+ json_object_object_add(j_obj, "sourceID", jsource);
+ json_object_object_add(j_obj, "sinkID", jsink);
+ return this->call(__FUNCTION__, j_obj);
+}
+
+/**
+ * This function calls the connect of Audio Manager via WebSocket
+ *
+ * #### Parameters
+ * - sourceID [in] : This argument should be specified to the sourceID as int. This parameter is returned value of registerSource
+ * - sinkName [in] : This argument should be specified to the sinkID as int. ID is aliased by SoundManager (e.g: "default")
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * For now, aliase(hardware abstraction) like "DriverZone:Speaker" is undier hard consideration
+ * Just "default" is usable. "default" parameter is set by soundmanager
+ *
+ */
+int LibSoundmanager::connect(int sourceID, const string& sinkName)
+{
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ struct json_object* j_obj = json_object_new_object();
+ struct json_object* jsource = json_object_new_int(sourceID);
+ //struct json_object* jsink = json_object_new_int(1);
+ struct json_object* jsink = json_object_new_string(sinkName.c_str());
+ json_object_object_add(j_obj, "sourceID", jsource);
+ json_object_object_add(j_obj, "sinkID", jsink);
+ return this->call(__FUNCTION__, j_obj);
+}
+
+/**
+ * This function calls the disconnect of Audio Manager via WebSocket
+ *
+ * #### Parameters
+ * - connectionID [in] : This parameter is returned value of connect
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ *
+ *
+ */
+int LibSoundmanager::disconnect(int connectionID)
+{
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ struct json_object* j_obj = json_object_new_object();
+ struct json_object* jconnection = json_object_new_int(connectionID);
+ json_object_object_add(j_obj, "mainConnectionID", jconnection);
+ return this->call(__FUNCTION__, j_obj);
+}
+
+/**
+ * This function calls the ackSetSourceState of Audio Manager via WebSocket
+ *
+ * #### Parameters
+ * - sourceID [in] : This parameter is returned value of ackSetSourceState
+ * - handle [in] : This parameter is returned value of ackSetSourceState
+ * - errno [in] : If you have some errors, input ohter than 0. 0 means acknowledge
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * This function must be called when application get asyncSetSourceState event
+ * Input handle number attached in asyncSetSourceState and error number(0 is acknowledge)
+ */
+int LibSoundmanager::ackSetSourceState(int handle, int error)
+{
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ struct json_object* j_obj = json_object_new_object();
+ struct json_object* jhandle = json_object_new_int(handle);
+ struct json_object* jerrno = json_object_new_int(error);
+ json_object_object_add(j_obj, "handle", jhandle);
+ json_object_object_add(j_obj, "error", jerrno);
+ return this->call(__FUNCTION__, j_obj);
+}
+
+/**
+ * This function calls the API of Audio Manager via WebSocket
+ *
+ * #### Parameters
+ * - verb [in] : This argument should be specified to the API name (e.g. "connect")
+ * - arg [in] : This argument should be specified to the argument of API. And this argument expects JSON object
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * To call Audio Manager's APIs, the application should set its function name, arguments to JSON format.
+ *
+ */
+int LibSoundmanager::call(const string& verb, struct json_object* arg)
+{
+ int ret;
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ if (!has_verb(verb))
+ {
+ ELOG("verb doesn't exit");
+ return -1;
+ }
+ ret = afb_wsj1_call_j(sp_websock, API, verb.c_str(), arg, _on_reply_static, this);
+ if (ret < 0) {
+ ELOG("Failed to call verb:%s",verb.c_str());
+ }
+ return ret;
+}
+
+/**
+ * This function calls the API of Audio Manager via WebSocket
+ * This function is overload function of "call"
+ *
+ * #### Parameters
+ * - verb [in] : This argument should be specified to the API name (e.g. "connect")
+ * - arg [in] : This argument should be specified to the argument of API. And this argument expects JSON object
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * To call Audio Manager's APIs, the application should set its function name, arguments to JSON format.
+ *
+ */
+int LibSoundmanager::call(const char* verb, struct json_object* arg)
+{
+ int ret;
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ if (!has_verb(string(verb)))
+ {
+ ELOG("verb doesn't exit");
+ return -1;
+ }
+ ret = afb_wsj1_call_j(sp_websock, API, verb, arg, _on_reply_static, this);
+ if (ret < 0) {
+ ELOG("Failed to call verb:%s",verb);
+ }
+ return ret;
+}
+
+/**
+ * Register callback function for each event
+ *
+ * #### Parameters
+ * - event_name [in] : This argument should be specified to the event name
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * This function enables to get an event to your callback function.
+ * Regarding the list of event name, please refer to CommandSender API and RountingSender API.
+ *
+ */
+int LibSoundmanager::subscribe(const string& event_name)
+{
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ struct json_object* j_obj = json_object_new_object();
+ json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str()));
+
+ int ret = afb_wsj1_call_j(sp_websock, API, "subscribe", j_obj, _on_reply_static, this);
+ if (ret < 0) {
+ ELOG("Failed to call verb:%s",__FUNCTION__);
+ }
+ return ret;
+}
+
+/**
+ * Unregister callback function for each event
+ *
+ * #### Parameters
+ * - event_name [in] : This argument should be specified to the event name
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * This function disables to get an event to your callback function.
+ *
+ */
+int LibSoundmanager::unsubscribe(const string& event_name)
+{
+ if(!sp_websock)
+ {
+ return -1;
+ }
+ struct json_object* j_obj = json_object_new_object();
+ json_object_object_add(j_obj, "event", json_object_new_string(event_name.c_str()));
+
+ int ret = afb_wsj1_call_j(sp_websock, API, "unsubscribe", j_obj, _on_reply_static, this);
+ if (ret < 0) {
+ ELOG("Failed to call verb:%s",__FUNCTION__);
+ }
+ return ret;
+}
+
+/**
+ * This function calls the ackSetSourceState of Audio Manager via WebSocket
+ *
+ * #### Parameters
+ * - EventType_AsyncSetSourceState [in] : This parameter is EventType of soundmanager
+ * - handler_func [in] : This parameter is callback function
+ *
+ * #### Rreturn
+ * - Returns 0 on success or -1 in case of error.
+ *
+ * #### Note
+ * This function must be called when application get asyncSetSourceState event
+ * Input handle number attached in asyncSetSourceState and error number(0 is acknowledge)
+ */
+void LibSoundmanager::set_event_handler(enum EventType_AsyncSetSourceState et, handler_asyncSetSourceState f)
+{
+ if (et >= 1 && et <= 3) {
+ this->handlers[et] = std::move(f);
+ }
+}
+
+
+/************* Callback Function *************/
+
+void LibSoundmanager::on_hangup(void *closure, struct afb_wsj1 *wsj)
+{
+ DLOG("%s called", __FUNCTION__);
+ if(onHangup != nullptr)
+ {
+ onHangup();
+ }
+}
+
+void LibSoundmanager::on_call(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg)
+{
+}
+
+/*
+* event is like "soundmanager/newMainConnection"
+* msg is like {"event":"soundmanager\/newMainConnection","data":{"mainConnectionID":3,"sourceID":101,"sinkID":100,"delay":0,"connectionState":4},"jtype":"afb-event"})}
+* ^key^ ^^^^^^^^^^^^ value ^^^^^^^^^^^^
+* so you can get
+ event name : struct json_object obj = json_object_object_get(msg,"event")
+*/
+void LibSoundmanager::on_event(void *closure, const char *event, struct afb_wsj1_msg *msg)
+{
+ /* check event is for us */
+ string ev = string(event);
+ if (ev.find(API) == string::npos) {
+ /* It's not us */
+ return;
+ }
+ struct json_object* ev_contents = afb_wsj1_msg_object_j(msg);
+ if((onEvent != nullptr))
+ {
+ onEvent(ev, ev_contents);
+ }
+
+ if(ev.find("asyncSetSourceState") != string::npos){
+ // check sourceID is indicated to me
+ struct json_object *json_data, *jSourceID, *jsourceState, *jhandle;
+ json_object_object_get_ex(ev_contents, "data", &json_data);
+ json_object_object_get_ex(json_data, "sourceID", &jSourceID);
+ int sourceID = json_object_get_int(jSourceID);
+ /* This is not supported yet because application may have some sourceID.
+ Application has to check sourceID
+ if(find( msourceIDs.begin(),msourceIDs.end(), sourceID) != msourceIDs.end()){
+ json_object_put(ev_contents);
+ return;
+ }*/
+ json_object_object_get_ex(json_data, "sourceState", &jsourceState);
+ json_object_object_get_ex(json_data, "handle", &jhandle);
+ string sourceState = string(json_object_get_string(jsourceState));
+ int handle = json_object_get_int(jhandle);
+ dispatch_asyncSetSourceState(sourceID, handle, sourceState);
+ json_object_put(json_data);
+ json_object_put(jSourceID);
+ json_object_put(jsourceState);
+ json_object_put(jhandle);
+ }
+ json_object_put(ev_contents);
+}
+
+void LibSoundmanager::on_reply(void *closure, struct afb_wsj1_msg *msg)
+{
+ struct json_object* reply = afb_wsj1_msg_object_j(msg);
+ /*struct json_object *json_data = json_object_object_get(reply, "response");
+ struct json_object *jverb = json_object_object_get(json_data, "verb");
+ const char* cverb = json_object_get_string(jverb);
+ DLOG("cverb is %s",cverb);
+ string verb = string(cverb);
+ DLOG("verb is %s",verb.c_str());
+
+ if(verb == "registerSource"){
+ struct json_object *jsourceID = json_object_object_get(json_data, "sourceID");
+ int sourceID = json_object_get_int(jsourceID);
+ msourceIDs.push_back(sourceID);
+ DLOG("my sourceID is created: %d", sourceID);
+ }*/
+ if(onReply != nullptr)
+ {
+ onReply(reply);
+ }
+ json_object_put(reply);
+}
+
+int LibSoundmanager::dispatch_asyncSetSourceState(int sourceID, int handle, const string& sourceState){
+ //dipatch event
+ EventType_AsyncSetSourceState x;
+ if(sourceState == "on"){
+ x = Event_AsyncSetSourceState_On;
+ }else if(sourceState == "off"){
+ x = Event_AsyncSetSourceState_Off;
+ }else if(sourceState == "pause"){
+ x = Event_AsyncSetSourceState_Pause;
+ }
+ auto i = this->handlers.find(x);
+ if(i != handlers.end()){
+ i->second(sourceID, handle);
+ return 0;
+ }
+ else{
+ return -1;
+ }
+}
+
+/* Internal Function in libsoundmanager */
+
+static void _ELOG(const char* func, const int line, const char* log, ...)
+{
+ char *message;
+ va_list args;
+ va_start(args, log);
+ if (log == NULL || vasprintf(&message, log, args) < 0)
+ message = NULL;
+ cout << "[ERROR: soundmanager]" << func << "(" << line << "):" << message << endl;
+ va_end(args);
+ free(message);
+}
+
+static void _DLOG(const char* func, const int line, const char* log, ...)
+{
+ char *message;
+ va_list args;
+ va_start(args, log);
+ if (log == NULL || vasprintf(&message, log, args) < 0)
+ message = NULL;
+ cout << "[DEBUG: soundmanager]" << func << "(" << line << "):" << message << endl;
+ va_end(args);
+ free(message);
+}
+
+static bool has_verb(const string& verb)
+{
+ if(find(api_list.begin(), api_list.end(), verb) != api_list.end())
+ return true;
+ else
+ return false;
+}