summaryrefslogtreecommitdiff
path: root/PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp
diff options
context:
space:
mode:
authorAdrian Scarlat <adrian.scarlat@windriver.com>2015-05-22 14:16:45 +0000
committerJames Thomas <james.thomas@codethink.co.uk>2015-05-22 14:18:47 +0000
commitba709ee7d4f9f81af638a3d3c640b7152bbe32bc (patch)
tree3d05af7d27b462dbafcea61116474c9d9da3b3fd /PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp
parent64d2ba454ecfb1ea9bef3b4b717989afa58db1c7 (diff)
downloadaudiomanager-ba709ee7d4f9f81af638a3d3c640b7152bbe32bc.tar.gz
This Routing Interface is needed for any application that will
be developed on top of AM and will use PulseAudio Sound Server to control the sources and sinks present on the system. It must be loaded by AM; The interface can be built by supplying cmake with the -DWITH_PULSE_ROUTING_PLUGIN=ON; After building one configuration file will be available: 1. libPluginRoutingInterfacePULSE.conf - configuration file for Pulse Routing Plugin; it will be loaded at runtime by the Pulse Routing Interface; Changed files: CMakeLists.txt Added new folders: PluginRoutingInterfacePulse/ Added new files: PluginRoutingInterfacePulse/CMakeLists.txt PluginRoutingInterfacePulse/README PluginRoutingInterfacePulse/data/libPluginRoutingInterfacePULSE.conf PluginRoutingInterfacePulse/include/RoutingSenderMainloopPULSE.h PluginRoutingInterfacePulse/include/RoutingSenderPULSE.h PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp PluginRoutingInterfacePulse/src/RoutingSenderPULSE.cpp Signed-off-by: Adrian Scarlat <adrian.scarlat@windriver.com>
Diffstat (limited to 'PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp')
-rw-r--r--PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp610
1 files changed, 610 insertions, 0 deletions
diff --git a/PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp b/PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp
new file mode 100644
index 0000000..5738b42
--- /dev/null
+++ b/PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp
@@ -0,0 +1,610 @@
+/**
+ * SPDX license identifier: MPL-2.0
+ *
+ * Copyright (C) 2011-2014, Wind River Systems
+ * Copyright (C) 2014, GENIVI Alliance
+ *
+ * This file is part of Pulse Audio Interface Routing Plugin.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License (MPL), v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * For further information see http://www.genivi.org/.
+ *
+ * List of changes:
+ *
+ * 21.08.2014, Adrian Scarlat, First version of the code;
+ * Porting code from AM ver1.x to AM ver3.0;
+ * Added Copyright and License information;
+ */
+
+/* Includes */
+
+#include <pthread.h>
+#include "shared/CAmDltWrapper.h"
+#include "RoutingSenderPULSE.h"
+#include "RoutingSenderMainloopPULSE.h"
+
+static pthread_t *p_thread;
+static pa_mainloop *main_loop;
+
+/* struct used for ramp_volume changing */
+typedef struct ramp_volume
+{
+ uint32_t sink_input_index;
+ uint32_t volume_ini;
+ uint32_t volume_end;
+ /* max ramp time in ms */
+ uint16_t ramp_max_time;
+ /* current delay between calls in ms */
+ uint16_t ramp_crt_elapsed;
+ /* aux used to calculate delay between calls */
+ timespec start_time;
+};
+/* map used for storing ramp_volume information for sources */
+std::map<uint32_t, ramp_volume> g_sinkInputId2rampVolume;
+
+/* Defines */
+#define QUIT_REASON_PA_DOWN 1
+#define QUIT_REASON_AMGR_DOWN 2
+
+DLT_IMPORT_CONTEXT(routingPulse)
+
+bool routing_sender_create_mainloop(void *thiz)
+{
+ if (!thiz) {
+ logError("Can not create an working thread for Pulse Audio without a Routing plugin\n");
+ return false;
+ }
+ p_thread = (pthread_t *) malloc(sizeof(pthread_t));
+ pthread_create(p_thread, NULL, routing_sender_start_mainloop, thiz);
+ return true;
+}
+
+
+void* routing_sender_start_mainloop(void *thiz)
+{
+ pa_mainloop_api *mainloop_api;
+ pa_proplist *proplist;
+ pa_context *context;
+ int ret = 0;
+
+ if (!thiz) {
+ logError("Can not create an working thread for Pulse Audio without a Routing plugin\n");
+ return NULL;
+ }
+
+ proplist = pa_proplist_new();
+ if (pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "RoutingPULSE") < 0) {
+ logError("Can not prepare Pulse Audio main loop: pa_proplist_sets\n");
+ goto end;
+ }
+
+ if (!(main_loop = pa_mainloop_new()))
+ {
+ logError("Can not prepare Pulse Audio main loop: pa_mainloop_new\n");
+ goto end;
+ }
+
+ if (!(mainloop_api = pa_mainloop_get_api(main_loop)))
+ {
+ logError("Can not prepare Pulse Audio main loop: pa_mainloop_get_api\n");
+ goto end;
+ }
+
+ if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist)))
+ {
+ logError("Can not prepare Pulse Audio main loop: pa_context_new_with_proplist\n");
+ goto end;
+ }
+
+ pa_context_set_state_callback(context, routing_sender_context_state_callback, thiz);
+
+ if (pa_context_connect(context, NULL, pa_context_flags_t(0), NULL) < 0)
+ {
+ logError("Can not prepare Pulse Audio main loop: pa_context_new_with_proplist\n");
+ goto end;
+ }
+ ((RoutingSenderPULSE *) thiz)->setPAContext(context);
+
+ pa_mainloop_run(main_loop, &ret);
+
+end:
+ if (ret == QUIT_REASON_PA_DOWN)
+ {
+ ret = 0;
+ //mail loop ended
+ if (context)
+ {
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+ context = NULL;
+ }
+
+ if (proplist)
+ {
+ pa_proplist_free(proplist);
+ proplist = NULL;
+ }
+
+ if (main_loop)
+ {
+ //pa_signal_done();
+ pa_signal_done();
+ pa_mainloop_free(main_loop);
+ main_loop = NULL;
+ }
+
+ //... pulse audio is down ... retry to connect
+ usleep(100000);
+ routing_sender_start_mainloop(thiz);
+ }
+ //TDOO: else if -check other value for "ret"
+ else
+ {
+ return thiz;
+ }
+}
+
+
+void routing_sender_pa_event_callback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *thiz)
+{
+ switch(t)
+ {
+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ {
+ pa_context_get_sink_input_info(
+ c, idx, routing_sender_get_sink_input_info_callback, thiz);
+ break;
+ }
+ case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
+ {
+ pa_context_get_sink_input_info(
+ c, idx, routing_sender_get_sink_input_info_callback, thiz);
+ break;
+ }
+ default:
+ {
+ logInfo("Pulse Audio event", t, "was ignored");
+ }
+ }
+}
+
+
+void routing_sender_get_sink_input_info_callback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata)
+{
+ (void) eol;
+
+ RoutingSenderPULSE* thiz = (RoutingSenderPULSE*) userdata;
+ if (!thiz)
+ {
+ logError("pa_context_get_sink_input_info was called with wrong params\n");
+ return;
+ }
+
+ /* init map of ramp_volumes, usefull later */
+ if (i != NULL && strcmp(i->name,"null") != 0)
+ {
+ ramp_volume source_ramp_volume;
+ source_ramp_volume.sink_input_index = i->index;
+ source_ramp_volume.ramp_crt_elapsed = 0;
+ g_sinkInputId2rampVolume.insert(std::make_pair(i->index, source_ramp_volume));
+ }
+
+ thiz->getSinkInputInfoCallback(c, i, userdata);
+}
+
+
+void routing_sender_get_source_output_info_callback(pa_context *c, const pa_source_output_info *i, int eol, void *userdata)
+{
+ (void) eol;
+
+ RoutingSenderPULSE* thiz = (RoutingSenderPULSE*) userdata;
+ if (!thiz)
+ {
+ logError("pa_context_get_surce_output_info was called with wrong params\n");
+ return;
+ }
+ thiz->getSourceOutputInfoCallback(c, i, userdata);
+}
+
+
+
+void routing_sender_get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata)
+{
+ RoutingSenderPULSE* thiz = (RoutingSenderPULSE*) userdata;
+ if (!thiz)
+ {
+ logError("pa_context_get_sink_info was called with wrong params\n");
+ return;
+ }
+ thiz->getSinkInfoCallback(c, i, is_last, userdata);
+}
+
+
+void routing_sender_get_source_info_callback(pa_context *c, const pa_source_info *i, int is_last, void *userdata)
+{
+ RoutingSenderPULSE* thiz = (RoutingSenderPULSE*) userdata;
+ if (!thiz)
+ {
+ logError("pa_context_get_source_info was called with wrong params\n");
+ return;
+ }
+ thiz->getSourceInfoCallback(c, i, is_last, userdata);
+}
+
+
+void routing_sender_subscriber_callback(pa_context *c, int success, void *thiz) {
+ if (success)
+ {
+ pa_operation *o = pa_context_get_sink_info_list(c, routing_sender_get_sink_info_callback, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_get_sink_info_list");
+ }
+ }
+ else
+ {
+ logError("routing_sender_subscriber_callback: success = false");
+ }
+}
+
+
+void routing_sender_context_state_callback(pa_context *c, void *thiz)
+ {
+
+ if (pa_context_get_state(c) == PA_CONTEXT_FAILED)
+ {
+ pa_mainloop_quit(main_loop, QUIT_REASON_PA_DOWN);
+ }
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ //when context is ready - subscriber for server events (we are mainly interested in sink inputs & source outputs)
+ pa_context_set_subscribe_callback(c, routing_sender_pa_event_callback, thiz);
+ pa_operation *o = pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_ALL, routing_sender_subscriber_callback, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_subscribe");
+ }
+ }
+ //other states are not relevant
+}
+
+
+bool routing_sender_get_source_info(pa_context *c, void *thiz) {
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ pa_operation *o = pa_context_get_source_info_list(c, routing_sender_get_source_info_callback, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_get_sink_info_list");
+ return false;
+ }
+ }
+ else
+ {
+ logError("Can not get Pulse Audio sources info - context not ready\n");
+ return false;
+ }
+
+ return true;
+}
+
+
+bool routing_sender_move_sink_input(pa_context *c, uint32_t sink_input_index, uint32_t sink_index, void *thiz)
+{
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ pa_operation *o = pa_context_move_sink_input_by_index(c, sink_input_index, sink_index, NULL, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_move_sink_input_by_index");
+ return false;
+ }
+ }
+ else
+ {
+ logError("Can not move sink input - context not ready\n");
+ return false;
+ }
+ return true;
+}
+
+
+bool routing_sender_move_source_output(pa_context *c, uint32_t source_output_index, uint32_t source_index, void *thiz)
+{
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ pa_operation *o = pa_context_move_source_output_by_index(c, source_output_index, source_index, NULL, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return false;
+ }
+ }
+ else
+ {
+ logError("Can not move source output - context not ready\n");
+ return false;
+ }
+ return true;
+}
+
+static inline uint16_t timespec2mili(const timespec & time)
+{
+ return (uint16_t)((time.tv_nsec == -1 && time.tv_sec == -1) ?
+ -1 :
+ time.tv_sec * 1000 + time.tv_nsec / 1000000);
+}
+
+/* Considers that time2 > time1 */
+static inline uint16_t timespec2DeltaMili(const timespec & time1, const timespec & time2)
+{
+ timespec l_deltaTime;
+ l_deltaTime.tv_sec = time2.tv_sec - time1.tv_sec;
+ l_deltaTime.tv_nsec = time2.tv_nsec - time1.tv_nsec;
+ return timespec2mili(l_deltaTime);
+}
+
+static void routing_sender_sink_input_volume_cb(pa_context *c, int success, void *data)
+{
+ logInfo("routing_sender_sink_input_volume_cb: success=", success, " data=", data);
+ if (success)
+ {
+ ramp_volume * l_ramp_volume = (ramp_volume *)data;
+ timespec l_endTime;
+ clock_gettime(0, &l_endTime);
+ l_ramp_volume->ramp_crt_elapsed = timespec2DeltaMili(l_ramp_volume->start_time, l_endTime);
+ if (l_ramp_volume->ramp_crt_elapsed >= l_ramp_volume->ramp_max_time)
+ {
+ return;
+ }
+
+ logInfo("routing_sender_sink_input_volume_cb: ms elapsed=", l_ramp_volume->ramp_crt_elapsed, " of ", l_ramp_volume->ramp_max_time);
+
+ /* ######## Calculate new volume with formula: ##########
+ crt_time x ( vol_end - vol_ini )
+ new_vol = vol_ini + ----------------------------------
+ max_time
+ ###################################################### */
+ uint32_t new_volume =
+ l_ramp_volume->volume_ini +
+ ( ( l_ramp_volume->ramp_crt_elapsed * ( l_ramp_volume->volume_end - l_ramp_volume->volume_ini ) ) / l_ramp_volume->ramp_max_time );
+ logInfo("routing_sender_sink_input_volume_cb: vol_ini=",l_ramp_volume->volume_ini,"vol_crt=",new_volume,"vol_end=",l_ramp_volume->volume_end);
+
+
+ /* ***** Set volume again ***** */
+ pa_cvolume *volumeCh = (pa_cvolume *) malloc(sizeof(pa_cvolume));
+ volumeCh->channels = 1;//TODO: check is stream is mono / stereo
+ volumeCh->values[0] = new_volume;
+
+ pa_operation *o = pa_context_set_sink_input_volume(c, l_ramp_volume->sink_input_index, volumeCh, NULL, NULL);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return ;
+ }
+ usleep(10000);
+
+ volumeCh->channels = 2;//TODO: check is stream is mono / stereo
+ volumeCh->values[0] = new_volume;
+ volumeCh->values[1] = new_volume;
+ logInfo("routing_sender_sink_input_volume_cb: will set vol=", new_volume);
+ o = pa_context_set_sink_input_volume(c, l_ramp_volume->sink_input_index, volumeCh, routing_sender_sink_input_volume_cb, l_ramp_volume);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return ;
+ }
+ }
+
+}
+
+bool routing_sender_sink_input_volume_ramp(pa_context *c, uint32_t sink_input_index, uint32_t crt_volume, uint32_t volume, uint16_t ramp_time, void *thiz)
+{
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ /* before everything, check to see if ramp_volume struct exists */
+ std::map<uint32_t, ramp_volume>::iterator iter = g_sinkInputId2rampVolume.find(sink_input_index);
+ std::map<uint32_t, ramp_volume>::iterator iterEnd = g_sinkInputId2rampVolume.end();
+ if (iter != iterEnd)
+ {
+ /* set test volume with only 1 unit more or less, just to see how callback responds */
+ pa_volume_t test_volume = ((crt_volume * MAX_PULSE_VOLUME) / 100);
+ test_volume += volume > crt_volume ? 1 : -1;
+
+ pa_cvolume *volumeCh = (pa_cvolume *) malloc(sizeof(pa_cvolume));
+ volumeCh->channels = 1;//TODO: check is stream is mono / stereo
+ volumeCh->values[0] = test_volume;
+
+ pa_operation *o = pa_context_set_sink_input_volume(c, sink_input_index, volumeCh, NULL, NULL);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return false;
+ }
+
+ volumeCh->channels = 2;//TODO: check is stream is mono / stereo
+ volumeCh->values[0] = test_volume;
+ volumeCh->values[1] = test_volume;
+
+ ramp_volume * l_ramp_volume = (ramp_volume *) &iter->second;
+ logInfo("routing_sender_sink_input_volume_ramp: searching ",sink_input_index,"found ramp_vlume struct with sinkInputId ",l_ramp_volume->sink_input_index," (should be equal) ");
+ l_ramp_volume->volume_ini = (crt_volume * MAX_PULSE_VOLUME) / 100;
+ l_ramp_volume->volume_end = (volume * MAX_PULSE_VOLUME) / 100;
+ l_ramp_volume->ramp_max_time = ramp_time;
+ clock_gettime(0, &l_ramp_volume->start_time);
+ l_ramp_volume->ramp_crt_elapsed = 0;
+ logInfo("routing_sender_sink_input_volume_ramp: will set vol=", test_volume);
+ o = pa_context_set_sink_input_volume(c, sink_input_index, volumeCh, routing_sender_sink_input_volume_cb, l_ramp_volume);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return false;
+ }
+ }
+ else
+ {
+ logInfo("routing_sender_sink_input_volume_ramp: didn't find struct with sinkInputId ", sink_input_index);
+ /* make-it the old traditional way */
+ return routing_sender_sink_input_volume(c, sink_input_index, volume, thiz);
+ }
+
+
+ }
+ else
+ {
+ logError("Can not set sink input volume - context not ready\n");
+ return false;
+ }
+ return true;
+}
+
+bool routing_sender_sink_input_volume(pa_context *c, uint32_t sink_input_index, uint32_t volume, void *thiz)
+{
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ pa_cvolume *volumeCh = (pa_cvolume *) malloc(sizeof(pa_cvolume));
+ volumeCh->channels = 1;//TODO: check is stream is mono / stereo
+ volumeCh->values[0] = (volume * MAX_PULSE_VOLUME) / 100;
+
+ pa_operation *o = pa_context_set_sink_input_volume(c, sink_input_index, volumeCh, NULL, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return false;
+ }
+
+ volumeCh->channels = 2;//TODO: check is stream is mono / stereo
+ volumeCh->values[0] = (volume * MAX_PULSE_VOLUME) / 100;
+ volumeCh->values[1] = (volume * MAX_PULSE_VOLUME) / 100;
+
+ o = pa_context_set_sink_input_volume(c, sink_input_index, volumeCh, NULL, NULL);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return false;
+ }
+ }
+ else
+ {
+ logError("Can not set sink input volume - context not ready\n");
+ return false;
+ }
+ return true;
+}
+
+
+bool routing_sender_sink_input_mute(pa_context *c, uint32_t sink_input_index, bool mute, void *thiz)
+{
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ pa_operation *o = pa_context_set_sink_input_mute(
+ c, sink_input_index, mute ? 1 : 0, NULL, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_mute");
+ return false;
+ }
+ }
+ else
+ {
+ logError("Can not set sink input volume - context not ready\n");
+ return false;
+ }
+ return true;
+}
+
+
+bool routing_sender_sink_volume(pa_context *c, uint32_t sink_index, uint32_t volume, void *thiz)
+{
+ if (pa_context_get_state(c) == PA_CONTEXT_READY)
+ {
+ pa_cvolume *volumeCh = (pa_cvolume *) malloc(sizeof(pa_cvolume));
+ volumeCh->channels = 2;//TODO: check is stream is mono / stereo
+ volumeCh->values[0] = (volume * MAX_PULSE_VOLUME) / 100;
+ volumeCh->values[1] = (volume * MAX_PULSE_VOLUME) / 100;
+
+ pa_operation *o = pa_context_set_sink_volume_by_index(c, sink_index, volumeCh, NULL, thiz);
+ if (o)
+ {
+ pa_operation_unref(o);
+ }
+ else
+ {
+ logError("Unable to create Pulse Audio operation:",
+ "pa_context_set_sink_input_volume");
+ return false;
+ }
+ }
+ else
+ {
+ logError("Can not set sink input volume - context not ready\n");
+ return false;
+ }
+ return true;
+}
+
+//TODO - implements mute/un-mute and sink suspend(pause)
+//TODO - IMPORTANT !! implement volume change for sink input even multiple sink inputs are created during one connection