summaryrefslogtreecommitdiff
path: root/PluginRoutingInterfacePulse/src/RoutingSenderMainloopPULSE.cpp
diff options
context:
space:
mode:
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