summaryrefslogtreecommitdiff
path: root/PluginRoutingInterfacePulse/src/RoutingSenderPULSE.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/RoutingSenderPULSE.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/RoutingSenderPULSE.cpp')
-rw-r--r--PluginRoutingInterfacePulse/src/RoutingSenderPULSE.cpp914
1 files changed, 914 insertions, 0 deletions
diff --git a/PluginRoutingInterfacePulse/src/RoutingSenderPULSE.cpp b/PluginRoutingInterfacePulse/src/RoutingSenderPULSE.cpp
new file mode 100644
index 0000000..17422e9
--- /dev/null
+++ b/PluginRoutingInterfacePulse/src/RoutingSenderPULSE.cpp
@@ -0,0 +1,914 @@
+/**
+ * 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;
+ *
+ *
+ * DESCRIPTION
+ *
+ * This module is handling requests form AudioManager daemon and redirect them to Pulse Audio server.
+ * It keeps track of existing audio sink, sources, sink-input, sources-input and performs connection/disconnection
+ * and other operations upon AudioManager daemon request.
+ *
+ * The modul is configured with a static list of sink and sources.
+ * Sinks are: audio output(e.g. speakers) or recording applications.
+ * Sources are: playback applications or audio input(e.g. microphone)
+ *
+ * In PulseAudio server those are classified as follows:
+ * - audio output - sinks: identified by name or properties: device.api (e.g = "alsa") & device.class(e.g. = "sound")
+ * - audio input - sources: identified by name or properties: device.api (e.g = "alsa") & device.class(e.g. = "sound")
+ * - playback applications - sink inputs: identified by application.name (e.g. "ALSA plug-in [chromium-browser]",
+ * - recording applications - source outputs: identified by application.name (e.g. "ALSA plug-in [chromium-browser]",
+ * application.process.user = "popai", application.process.binary = "chromium-browser")
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+
+#include "shared/CAmDltWrapper.h"
+
+#include "RoutingSenderPULSE.h"
+#include "RoutingSenderMainloopPULSE.h"
+
+
+
+#define LIBNAME "libPluginRoutingInterfacePULSE.so"
+#define CFGNAME "libPluginRoutingInterfacePULSE.conf"
+
+/* Globals */
+
+
+/* Defines */
+DLT_DECLARE_CONTEXT(routingPulse)
+/* Maximum source volume measured in percentage. Minimum value is 0% */
+#define MAX_SOURCE_VOLUME (100)
+
+
+/**
+ * Factory function for the plug-in to be used by audio manager daemon with dlopen & dlsym functions.
+ * Pattern "libraryName"FACTORY
+ *
+ * @author Ionut Popa (ionut.popa@windriver.com)
+ * @return an instance of RoutingSendInterface or type RoutingSenderPULSE.
+ */
+extern "C" IAmRoutingSend* PluginRoutingInterfacePULSEFactory()
+{
+
+ return (new RoutingSenderPULSE(NULL));
+}
+
+/**
+ * Destructor function for the plug-in to be used by audio manager daemon with dl_open & dl_sym functions.
+ *
+ * @param routingSendInterface - the instance created by PluginRoutingInterfaceDbusFactory
+ * @author Ionut Popa (ionut.popa@windriver.com)
+ */
+extern "C" void destroyPluginRoutingInterfacePULSE(IAmRoutingSend* routingSendInterface)
+{
+ delete routingSendInterface;//virtual destructor -> our constructor will be called too
+}
+
+
+/**
+ * Constructor.
+ * @param p_paContext - reference to PulseAudio context
+ */
+RoutingSenderPULSE::RoutingSenderPULSE(pa_context *p_paContext)
+{
+ this->m_paSinkNullIndex = -1;
+ this->m_paSourceNullIndex = -1;
+ this->m_paContext = p_paContext;
+}
+
+
+void RoutingSenderPULSE::loadConfig()
+{
+ //get current library path - search: /proc/< getpid() >/maps
+ char proc_maps_file_name[256];
+ char line[256];
+ char lib_name[256];
+ char *tmp;
+ pid_t pid = getpid();
+ snprintf(proc_maps_file_name, 256, "/proc/%d/maps", pid);
+ FILE *proc_maps = fopen(proc_maps_file_name, "r");
+
+ while (!feof(proc_maps))
+ {
+ char *cnt = fgets(line, 256, proc_maps);
+ if (strlen(line) == 0 || line[0] == '#')
+ {
+ continue;
+ }
+ if (cnt == NULL) continue;
+ //tmp0 tmp1 tmp2 tmp3 tmp4 lib_name);
+ tmp = strtok(line, " ");//address-interval
+ if(tmp == NULL) continue;
+
+ tmp = strtok(NULL, " ");//rights
+ if(tmp == NULL) continue;
+
+ tmp = strtok(NULL, " ");//offset
+ if(tmp == NULL) continue;
+
+ strtok(NULL, " ");//dev
+ if(tmp == NULL) continue;
+
+ tmp = strtok(NULL, " \n");//inode
+ if(tmp == NULL) continue;
+
+ tmp = strtok(NULL, " \n");
+ if(tmp == NULL) continue;
+
+ strcpy(lib_name, tmp);
+ if ((lib_name != NULL) && (strstr(lib_name, LIBNAME) >= lib_name))
+ {
+ strcpy(strrchr(lib_name, '/') + 1, CFGNAME);
+ logInfo("PULSE - config file name: %s\n", lib_name);
+
+ FILE *config = fopen(lib_name, "r");
+
+ while (config && !feof(config))
+ {
+ char *cnt = fgets(line, 256, config);
+ if (!line || strlen(line) == 0) continue;
+ //config format line: TYPE|PULSE TYPE|NAME|CLASS|PROPERTY_NAME|PROPERTY_VALUE
+ //TYPE="Source" or "Sink"
+
+ char *tmp = strtok(line, "|");//type
+ if (strcmp("Sink", tmp) == 0)
+ {
+ //add sink config
+ RoutingSenderPULSESourceSinkConfig sinkConfig;
+
+ tmp = strtok(NULL, "|");//pulse type - not used for the moment
+
+ tmp = strtok(NULL, "|");//class
+ sinkConfig.clazz = std::string(tmp);
+
+ tmp = strtok(NULL, "|");//name
+ sinkConfig.name = std::string(tmp);
+
+ tmp = strtok(NULL, "|");//property name
+ sinkConfig.propertyName = std::string(tmp);
+
+ tmp = strtok(NULL, "|\n");//property value
+ sinkConfig.propertyValue = std::string(tmp);
+
+ m_sinks.push_back(sinkConfig);
+ logInfo("sinkConfig: sinkConfig.clazz=", sinkConfig.clazz, " sinkConfig.name=", sinkConfig.name, " sinkConfig.propertyName=", sinkConfig.propertyName, " sinkConfig.propertyValue=", sinkConfig.propertyValue);
+ }
+ if (strcmp("Source", tmp) == 0)
+ {
+ //add source config
+ RoutingSenderPULSESourceSinkConfig sourceConfig;
+
+ tmp = strtok(NULL, "|");//pulse type - not used for the moment
+
+ tmp = strtok(NULL, "|");//class
+ sourceConfig.clazz = std::string(tmp);
+
+ tmp = strtok(NULL, "|");//name
+ sourceConfig.name = std::string(tmp);
+
+ tmp = strtok(NULL, "|");//property name
+ sourceConfig.propertyName = std::string(tmp);
+
+ tmp = strtok(NULL, "|\n");//property value
+ sourceConfig.propertyValue = std::string(tmp);
+
+ m_sources.push_back(sourceConfig);
+ logInfo("sourceConfig: sourceConfig.clazz=", sourceConfig.clazz, " sourceConfig.name=", sourceConfig.name, " sourceConfig.propertyName=", sourceConfig.propertyName, " sourceConfig.propertyValue=", sourceConfig.propertyValue);
+ }
+ }
+
+ if (config)
+ fclose(config);
+ break;
+ }
+ }
+
+ fclose(proc_maps);
+}
+
+
+/**
+ * Destructor.
+ */
+RoutingSenderPULSE::~RoutingSenderPULSE()
+{
+ //TODO: Disconnect from pulse: quit main loop and free the context and stuff
+}
+
+/**
+ * Connecting sender & receiver
+ * @author Ionut Popa (ionut.popa@windriver.com)
+ */
+am_Error_e RoutingSenderPULSE::startupInterface(am::IAmRoutingReceive *p_routingReceiver)
+{
+ this->m_routingReceiver = p_routingReceiver;
+ return am::E_OK;
+}
+
+void RoutingSenderPULSE::setRoutingReady(uint16_t handle)
+{
+ //TODO: do not register sinks with the same name
+
+ int i;
+ this->loadConfig();
+ //first register Domain = PulseAudio
+ this->m_domain.name = "PulseAudio";
+ this->returnBusName(this->m_domain.busname);//set domain bus name = current interface bus name
+ this->m_domain.nodename = "PulseAudio";
+ this->m_domain.early = false;
+ this->m_domain.complete = true;
+ this->m_domain.state = am::DS_CONTROLLED;
+
+ this->m_domain.domainID = 0;
+ this->m_routingReceiver->registerDomain(this->m_domain, this->m_domain.domainID);
+
+ am_SoundProperty_s l_spTreble;
+ l_spTreble.type = SP_GENIVI_BASS;
+ l_spTreble.value = 0;
+
+ am_SoundProperty_s l_spMid;
+ l_spMid.type = SP_GENIVI_MID;
+ l_spMid.value = 0;
+
+ am_SoundProperty_s l_spBass;
+ l_spBass.type = SP_GENIVI_BASS;
+ l_spBass.value = 0;
+
+ //register sources (sink inputs & sinks)
+ for (i = 0; i < m_sources.size(); i++)
+ {
+ am_sourceID_t l_newSourceID = 0;
+ this->m_sources[i].source.sourceID = l_newSourceID;
+ this->m_sources[i].source.name = m_sources[i].name;
+ this->m_sources[i].source.sourceState = am::SS_ON;
+ this->m_sources[i].source.domainID = this->m_domain.domainID;
+ this->m_sources[i].source.visible = true;
+ this->m_sources[i].source.volume = MAX_SOURCE_VOLUME; /* initialize source volume to 100% */
+
+ this->m_sources[i].source.listConnectionFormats.push_back(am::CF_GENIVI_STEREO);
+ this->m_routingReceiver->peekSourceClassID(
+ this->m_sources[i].clazz,
+ this->m_sources[i].source.sourceClassID);
+
+ this->m_routingReceiver->registerSource(this->m_sources[i].source, l_newSourceID);
+
+ this->m_sources[i].source.sourceID = l_newSourceID;
+ m_sourceToPASinkInput[l_newSourceID] = -1;
+ m_sourceToPASource[l_newSourceID] = -1;
+
+ logInfo("PULSE - register source:"
+ ,m_sources[i].name
+ , "(", m_sources[i].propertyName , ", ", m_sources[i].propertyValue, ")");
+ m_sourceToVolume[l_newSourceID] = MAX_SOURCE_VOLUME;//initially all the sources are at 100%
+ }
+
+ //register sinks (source outputs & sources)
+ for (i = 0; i < m_sinks.size(); i++)
+ {
+ am_sinkID_t l_newsinkID = 0;
+ this->m_sinks[i].sink.sinkID = l_newsinkID;
+ this->m_sinks[i].sink.name = this->m_sinks[i].name;
+ this->m_sinks[i].sink.muteState = am::MS_MUTED;
+ this->m_sinks[i].sink.domainID = this->m_domain.domainID;
+ this->m_sinks[i].sink.visible = true;
+
+ this->m_sinks[i].sink.listSoundProperties.push_back(l_spTreble);
+ this->m_sinks[i].sink.listSoundProperties.push_back(l_spMid);
+ this->m_sinks[i].sink.listSoundProperties.push_back(l_spBass);
+ this->m_sinks[i].sink.listConnectionFormats.push_back(am::CF_GENIVI_STEREO);
+
+ this->m_routingReceiver->peekSinkClassID(
+ this->m_sinks[i].clazz,
+ this->m_sinks[i].sink.sinkClassID);
+ this->m_routingReceiver->registerSink(this->m_sinks[i].sink, l_newsinkID);
+ this->m_sinks[i].sink.sinkID = l_newsinkID;
+ m_sinkToPASourceOutput[l_newsinkID] = -1;
+ m_sinkToPASink[l_newsinkID] = -1;
+
+ logInfo("PULSE - register sink:"
+ ,m_sinks[i].name
+ , "(", m_sinks[i].propertyName , ", ", m_sinks[i].propertyValue, ")");
+ }
+
+ logInfo("PULSE - routingInterfacesReady");
+ this->m_routingReceiver->confirmRoutingReady(handle, am::E_OK);
+
+ //register pulse sink & sources, sink inputs & source outputs - > start the main PA loop
+ routing_sender_create_mainloop((void *) this);
+}
+
+void RoutingSenderPULSE::setRoutingRundown(uint16_t handle)
+{
+ this->m_routingReceiver->confirmRoutingRundown(handle, am::E_OK);
+ //TODO: implement this
+}
+
+am_Error_e RoutingSenderPULSE::asyncAbort(const am_Handle_s handle)
+{
+ (void) handle;
+ return E_NOT_USED;
+}
+
+
+am_Error_e RoutingSenderPULSE::asyncConnect(
+ const am_Handle_s handle,
+ const am_connectionID_t connectionID,
+ const am_sourceID_t sourceID,
+ const am_sinkID_t sinkID,
+ const am_CustomConnectionFormat_t connectionFormat)
+{
+ //TODO: check stuff like connectionFormat
+ logInfo("PULSE - asyncConnect() - start");
+ //add source,sink & connectionID to a list of connections maintained by Routing Pulse Engine
+ RoutingSenderPULSEConnection l_newConnection;
+ l_newConnection.sinkID = sinkID;
+ l_newConnection.sourceID = sourceID;
+ l_newConnection.connectionID = connectionID;
+ l_newConnection.handle = handle;
+
+ //by default - sources ar connected at 100% -> controller is responsible to setSourcevolume if needed
+
+ m_sourceToVolume[sourceID] = MAX_SOURCE_VOLUME;
+
+
+ if (m_sinkToPASink[sinkID] != -1)
+ {
+ if (m_sourceToPASinkInput[sourceID] != -1)
+ {
+ if (routing_sender_move_sink_input(
+ this->m_paContext,
+ m_sourceToPASinkInput[sourceID],
+ m_sinkToPASink[sinkID],
+ this))
+ {
+ //TODO: add callback for pulse move sink input -> to send confirmation; for the moment directly send confirmation
+
+ logInfo("PULSE - asyncConnect() - connectionID:", connectionID,
+ "move sinkInputIndex:", m_sourceToPASinkInput[sourceID], "to sinkIndex:", m_sinkToPASink[sinkID]);
+ }
+ else
+ {
+ this->m_routingReceiver->ackConnect(handle, connectionID, am::E_NOT_POSSIBLE);
+ return am::E_NOT_POSSIBLE;
+ }
+ }//else move_sink_input will be called later
+ }
+ else if (m_sourceToPASource[sourceID] != -1)
+ {
+ if (m_sinkToPASourceOutput[sinkID] != -1)
+ {
+ if (routing_sender_move_source_output(
+ this->m_paContext,
+ m_sinkToPASourceOutput[sinkID],
+ m_sourceToPASource[sourceID],
+ this))
+ {
+ //TODO: add callback for pulse move sink input -> to send confirmation; for the moment directly send confirmation
+
+ logInfo("PULSE - asyncConnect() - connectionID:", connectionID,
+ "move sourceOutputIndex:", m_sinkToPASourceOutput[sinkID], "to sourceIndex:", m_sourceToPASource[sourceID]);
+
+ }
+ else
+ {
+ this->m_routingReceiver->ackConnect(handle, connectionID, am::E_NOT_POSSIBLE);
+ return am::E_NOT_POSSIBLE;
+ }
+ }//else move_sink_input will be called later
+ }
+ else
+ {
+ logError("Sink and source for connection not identified:",
+ sinkID, sourceID, connectionID);
+ return am::E_NOT_POSSIBLE;
+ }
+
+ m_activeConnections.push_back(l_newConnection);
+
+ this->m_routingReceiver->ackConnect(handle, connectionID, am::E_OK);
+
+/**
+ * TODO: connection is always possible ? check that
+*/
+
+ return am::E_OK;
+}
+
+
+am_Error_e RoutingSenderPULSE::asyncDisconnect(const am_Handle_s handle, const am_connectionID_t connectionID)
+{
+ //get connection by ID ... not to many connections, therefore linear search is fast enough
+ std::vector<RoutingSenderPULSEConnection>::iterator iter = m_activeConnections.begin();
+ std::vector<RoutingSenderPULSEConnection>::iterator iterEnd = m_activeConnections.end();
+ for (; iter < iterEnd; ++iter)
+ {
+ if (iter->connectionID == connectionID)
+ {
+ if (m_sourceToPASinkInput[iter->sourceID] != -1)
+ {
+ if (this->m_paSinkNullIndex >= 0)
+ {
+ //if null sink is defined - disconnect = move sink input to null
+ logInfo("PULSE - asyncDisconnect() - connection found - move sinkInputIndex:",
+ m_sourceToPASinkInput[iter->sourceID], "to NULL sinkIndex:", this->m_paSinkNullIndex);
+
+ routing_sender_move_sink_input(
+ this->m_paContext,
+ this->m_sourceToPASinkInput[iter->sourceID],
+ this->m_paSinkNullIndex,
+ this);
+ //TODO: add callback for pulse move sink input -> to send confirmation; for the moment directly send confirmation
+ this->m_routingReceiver->ackDisconnect(handle, connectionID, am::E_OK);
+
+ }
+ }
+ else if (m_sinkToPASourceOutput[iter->sinkID] != -1)
+ {
+ if (this->m_paSourceNullIndex >= 0)
+ {
+ //if null source is defined - disconnect = move source output to null
+ logInfo("PULSE - asyncDisconnect() - connection found - move sourceOutputIndex:",
+ m_sinkToPASourceOutput[iter->sinkID], "to NULL sourceIndex:", this->m_paSourceNullIndex);
+
+ routing_sender_move_source_output(
+ this->m_paContext,
+ m_sourceToPASinkInput[iter->sourceID],
+ this->m_paSinkNullIndex,
+ this);
+ //TODO: add callback for pulse move sink input -> to send confirmation; for the moment directly send confirmation
+ this->m_routingReceiver->ackDisconnect(handle, connectionID, am::E_OK);
+
+ //remove connection from the list of active connections
+ iter = m_activeConnections.erase(iter);
+
+ break;
+ }
+ }
+ else
+ {
+ logInfo("PULSE - asyncDisconnect() - connection found - but no sink input or source");
+ this->m_routingReceiver->ackDisconnect(handle, connectionID, am::E_OK);
+ }
+ //remove connection from the list of active connections
+ iter = m_activeConnections.erase(iter);
+
+ break;
+ }
+ }
+ return am::E_OK;
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSinkVolume(
+ const am_Handle_s handle,
+ const am_sinkID_t sinkID,
+ const am_volume_t volume,
+ const am_CustomRampType_t ramp,
+ const am_time_t time)
+{
+ (void) ramp;
+ (void) time;
+
+ logInfo("PULSE - asyncSetSinkVolume() - volume:", volume, "sink index:", this->m_sinkToPASink[sinkID]);
+
+ routing_sender_sink_volume(
+ this->m_paContext,
+ this->m_sinkToPASink[sinkID],
+ volume,
+ this);
+ this->m_routingReceiver->ackSetSinkVolumeChange(handle, volume, E_OK);
+ return E_OK;
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSourceVolume(
+ const am_Handle_s handle,
+ const am_sourceID_t sourceID,
+ const am_volume_t volume,
+ const am_CustomRampType_t ramp,
+ const am_time_t time)
+{
+ (void) ramp;
+ (void) time;
+
+ am_volume_t crt_volume = this->m_sourceToVolume[sourceID];
+ this->m_sourceToVolume[sourceID] = volume;
+
+ logInfo("PULSE - asyncSetSourceVolume() - volume:", volume, "sink input index:", this->m_sourceToPASinkInput[sourceID]);
+ if (m_sourceToPASinkInput[sourceID] != -1)
+ {
+ if (time == 0)
+ {/* without ramp time */
+ routing_sender_sink_input_volume(
+ this->m_paContext,
+ this->m_sourceToPASinkInput[sourceID],
+ volume,
+ this);
+ }
+ else
+ {/* with ramp time */
+ routing_sender_sink_input_volume_ramp(
+ this->m_paContext,
+ this->m_sourceToPASinkInput[sourceID],
+ crt_volume,
+ volume,
+ (uint16_t)time,
+ this);
+ }
+ }
+ else
+ {
+ logInfo("PULSE - sink input not registered yet - should wait for registration before update the volume");
+ }
+ this->m_routingReceiver->ackSetSourceVolumeChange(handle, volume, E_OK);
+
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSourceState(
+ const am_Handle_s handle,
+ const am_sourceID_t sourceID,
+ const am_SourceState_e state)
+{
+ logInfo("PULSE - asyncSetSourceState", state);
+ switch (state)
+ {
+ case SS_ON:
+ {
+ routing_sender_sink_input_mute(
+ this->m_paContext,
+ this->m_sourceToPASinkInput[sourceID],
+ false,
+ this
+ );
+ break;
+ }
+ case SS_OFF:
+ case SS_PAUSED:
+ {
+ //TODO: mute source in case of PAUSE or OFF - is there a better way to pause ? maybe suspending the associated sink?
+ routing_sender_sink_input_mute(
+ this->m_paContext,
+ this->m_sourceToPASinkInput[sourceID],
+ true,
+ this
+ );
+ break;
+ }
+ default:
+ {
+ logError("RoutingSenderPULSE::asyncSetSourceState - wrong source state\n");
+ this->m_routingReceiver->ackSetSourceState(handle, E_NOT_POSSIBLE);
+ return E_NOT_POSSIBLE;
+ }
+ }
+ this->m_routingReceiver->ackSetSourceState(handle, E_OK);
+ return E_OK;
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSinkSoundProperties(
+ const am_Handle_s handle,
+ const am_sinkID_t sinkID,
+ const std::vector<am_SoundProperty_s>& listSoundProperties)
+{
+ (void) handle;
+ (void) sinkID;
+ (void) listSoundProperties;
+ return E_NOT_USED;
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSinkSoundProperty(
+ const am_Handle_s handle,
+ const am_sinkID_t sinkID,
+ const am_SoundProperty_s& soundProperty)
+{
+ (void) handle;
+ (void) sinkID;
+ (void) soundProperty;
+ return E_NOT_USED;
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSourceSoundProperties(
+ const am_Handle_s handle,
+ const am_sourceID_t sourceID,
+ const std::vector<am_SoundProperty_s>& listSoundProperties)
+{
+ (void) handle;
+ (void) sourceID;
+ (void) listSoundProperties;
+ return E_NOT_USED;
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSourceSoundProperty(
+ const am_Handle_s handle,
+ const am_sourceID_t sourceID,
+ const am_SoundProperty_s& soundProperty)
+{
+ (void) handle;
+ (void) sourceID;
+ (void) soundProperty;
+ return E_NOT_USED;
+}
+
+am_Error_e RoutingSenderPULSE::asyncCrossFade(
+ const am_Handle_s handle,
+ const am_crossfaderID_t crossfaderID,
+ const am_HotSink_e hotSink,
+ const am_CustomRampType_t rampType,
+ const am_time_t time)
+{
+ (void) handle;
+ (void) crossfaderID;
+ (void) hotSink;
+ (void) rampType;
+ (void) time;
+ return E_NOT_USED;
+}
+
+am_Error_e RoutingSenderPULSE::setDomainState(
+ const am_domainID_t domainID,
+ const am_DomainState_e domainState)
+{
+ (void) domainID;
+ (void) domainState;
+ return E_NOT_USED;
+}
+
+am_Error_e RoutingSenderPULSE::returnBusName(std::string& BusName) const {
+ BusName = "RoutingPULSE";
+ return E_OK;
+}
+
+void RoutingSenderPULSE::getInterfaceVersion(std::string& out_ver) const
+{
+ out_ver = "3.0";
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetVolumes(const am_Handle_s handle, const std::vector<am_Volumes_s>& listVolumes)
+{
+ (void) handle;
+ (void) listVolumes;
+ //todo: implement asyncSetVolumes;
+ return (E_NOT_USED);
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSinkNotificationConfiguration(const am_Handle_s handle, const am_sinkID_t sinkID, const am_NotificationConfiguration_s& notificationConfiguration)
+{
+ (void) handle;
+ (void) sinkID;
+ (void) notificationConfiguration;
+ //todo: implement asyncSetSinkNotificationConfiguration;
+ return (E_NOT_USED);
+}
+
+am_Error_e RoutingSenderPULSE::asyncSetSourceNotificationConfiguration(const am_Handle_s handle, const am_sourceID_t sourceID, const am_NotificationConfiguration_s& notificationConfiguration)
+{
+ (void) handle;
+ (void) sourceID;
+ (void) notificationConfiguration;
+ //todo: implement asyncSetSourceNotificationConfiguration;
+ return (E_NOT_USED);
+}
+/*******************************************************************************
+ * Private methods
+ ******************************************************************************/
+
+
+void RoutingSenderPULSE::getSinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, void *userdata)
+{
+ if (i == NULL)
+ {
+ return;
+ }
+
+ //search for corresponding Source
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iter = m_sources.begin();
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iterEnd = m_sources.end();
+ for (; iter < iterEnd; ++iter)
+ {
+ //try to match source PulseAudio properties against config properties
+ const char *property_value = pa_proplist_gets(i->proplist, iter->propertyName.c_str());
+
+ if (property_value &&
+ ( std::string::npos != std::string(property_value).find(iter->propertyValue) ||
+ std::string::npos != iter->propertyValue.find(property_value)) )
+ {
+ logInfo("PULSE - sink input registered:"
+ , " sinkInputIndex:", i->index, "sourceID:", iter->source.sourceID);
+
+ logInfo("PULSE - sink input details:"
+ , " prop_val: ", property_value, " iter->prop_val: ", iter->propertyValue);
+
+ m_sourceToPASinkInput[iter->source.sourceID] = i->index;
+
+ //iterate pending connection request
+ // -> if there is a connection pending such that sink input "i" matches source from Connect() - create the connection in pulse
+ std::vector<RoutingSenderPULSEConnection>::iterator iterConn = m_activeConnections.begin();
+ std::vector<RoutingSenderPULSEConnection>::iterator iterConnEnd = m_activeConnections.end();
+ for (; iterConn < iterConnEnd; ++iterConn)
+ {
+ if (iterConn->sourceID == iter->source.sourceID)
+ {
+ logInfo("PULSE - asyncConnect() - connectionID:", iterConn->connectionID,
+ "move sinkInputIndex:", m_sourceToPASinkInput[iterConn->sourceID], "to sinkIndex:", m_sinkToPASink[iterConn->sinkID]);
+
+ routing_sender_move_sink_input(
+ this->m_paContext,
+ m_sourceToPASinkInput[iterConn->sourceID],
+ m_sinkToPASink[iterConn->sinkID],
+ this);
+
+ //TODO: add callback for pulse move sink input -> to send confirmation; for the moment directly send confirmation
+ this->m_routingReceiver->ackConnect(iterConn->handle, iterConn->connectionID, am::E_OK);
+ }
+ }
+ //check of controller already requested vol adjustment for this source
+ bool requiresVolUpdate = false;
+ for (int j = 0; j < i->volume.channels; j++)
+ {
+ if ((i->volume.values[j]*MAX_SOURCE_VOLUME / MAX_PULSE_VOLUME) != m_sourceToVolume[iter->source.sourceID])
+ {
+ requiresVolUpdate = true;
+ logInfo("PULSE - sink registerd with vol:", (i->volume.values[j]*MAX_SOURCE_VOLUME / MAX_PULSE_VOLUME),
+ "; should be changed to:",
+ m_sourceToVolume[iter->source.sourceID]);
+ break;
+ }
+ }
+ if (requiresVolUpdate)
+ {
+ routing_sender_sink_input_volume(
+ this->m_paContext,
+ m_sourceToPASinkInput[iter->source.sourceID],
+ m_sourceToVolume[iter->source.sourceID],
+ this);
+ }
+ //TODO: check mute state was requested by controller.
+ break;
+ }
+ }
+}
+
+
+void RoutingSenderPULSE::getSourceOutputInfoCallback(pa_context *c, const pa_source_output_info *i, void *userdata)
+{
+ if (i == NULL)
+ {
+ return;
+ }
+
+ //search for corresponding Source
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iter = m_sinks.begin();
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iterEnd = m_sinks.end();
+ for (; iter < iterEnd; ++iter)
+ {
+ //try to match source PulseAudio properties agains config properties
+ const char *property_value = pa_proplist_gets(i->proplist, iter->propertyName.c_str());
+
+ if (property_value &&
+ ( std::string::npos != std::string(property_value).find(iter->propertyValue) ||
+ std::string::npos != iter->propertyValue.find(property_value)) )
+ {
+ logInfo("PULSE - source output registered:"
+ , " sourceOutputIndex:", i->index, "sinkID:", iter->sink.sinkID);
+
+ m_sinkToPASourceOutput[iter->sink.sinkID] = i->index;
+
+ //iterate pending connection request
+ // -> if there is a connection pending such that sink input "i" matches source from Connect() - create the connection in pulse
+ std::vector<RoutingSenderPULSEConnection>::iterator iterConn = m_activeConnections.begin();
+ std::vector<RoutingSenderPULSEConnection>::iterator iterConnEnd = m_activeConnections.end();
+ for (; iterConn < iterConnEnd; ++iterConn)
+ {
+ if (iterConn->sinkID == iter->sink.sinkID)
+ {
+ logInfo("PULSE - asyncConnect() - connectionID:", iterConn->connectionID,
+ "move sourceOutputIndex:", m_sinkToPASourceOutput[iterConn->sinkID], "to sourceIndex:", m_sourceToPASource[iterConn->sourceID]);
+
+ routing_sender_move_source_output(
+ this->m_paContext,
+ m_sinkToPASourceOutput[iterConn->sinkID],
+ m_sourceToPASource[iterConn->sourceID],
+ this);
+
+ //TODO: add callback for pulse move source output -> to send confirmation; for the moment directly send confirmation
+ this->m_routingReceiver->ackConnect(iterConn->handle, iterConn->connectionID, am::E_OK);
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+
+void RoutingSenderPULSE::getSinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata)
+{
+ if (i != NULL)
+ {
+ if (strcmp("null", i->name) == 0)
+ {
+ this->m_paSinkNullIndex = i->index;
+ }
+
+ //search for corresponding (already registered) Sink
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iter = m_sinks.begin();
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iterEnd = m_sinks.end();
+ for (; iter < iterEnd; ++iter)
+ {
+ //first try to match the sink name from pulse audio sink name
+ if (iter->sink.name == std::string(i->name))
+ {
+ logInfo("PULSE sink name PA:", i->name, "config name:" ,iter->sink.name);
+ logInfo("PULSE - PA sink:", i->index,
+ "corresponding to AMGR sink:", iter->sink.sinkID, " - found");
+ m_sinkToPASink[iter->sink.sinkID] = i->index;
+ }
+ else
+ {
+ //try to match sink PulseAudio properties against config properties
+ const char *property_value = pa_proplist_gets(i->proplist, iter->propertyName.c_str());
+
+ if (!property_value) continue;
+
+ if (std::string::npos != iter->propertyValue.find(property_value))
+ {
+ logInfo("PULSE - PA sink:", i->index,
+ "corresponding to AMGR sink:", iter->sink.sinkID, " - found");
+
+ m_sinkToPASink[iter->sink.sinkID] = i->index;
+ }
+ }
+ }
+ }
+ else if (is_last)
+ {
+ routing_sender_get_source_info(this->m_paContext, this);
+ logInfo("PULSE - PA sinks registration completed");
+ }
+}
+
+void RoutingSenderPULSE::getSourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *userdata)
+{
+ if (i != NULL)
+ {
+ if (strcmp("null", i->name) == 0)
+ {
+ this->m_paSourceNullIndex = i->index;
+ }
+
+ //search for corresponding (already registered) Source
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iter = m_sources.begin();
+ std::vector<RoutingSenderPULSESourceSinkConfig>::iterator iterEnd = m_sources.end();
+ for (; iter < iterEnd; ++iter)
+ {
+ //first try to match the sink name from pulse audio sink name
+ if (iter->sink.name == std::string(i->name))
+ {
+ logInfo("PULSE - PA source:", i->index,
+ "corresponding to AMGR source:", iter->source.sourceID, " - found");
+ m_sourceToPASource[iter->source.sourceID] = i->index;
+ }
+ else
+ {
+ //try to match source PulseAudio properties against config properties
+ const char *property_value = pa_proplist_gets(i->proplist, iter->propertyName.c_str());
+
+ if (!property_value) continue;
+
+ if (std::string::npos != iter->propertyValue.find(property_value))
+ {
+ logInfo("PULSE - PA source:", i->index,
+ "corresponding to AMGR source:", iter->source.sourceID, " - found");
+
+ m_sourceToPASource[iter->source.sourceID] = i->index;
+ }
+ }
+
+ }
+ }
+ else if (is_last)
+ {
+ this->m_routingReceiver->hookDomainRegistrationComplete(this->m_domain.domainID);
+ logInfo("PULSE - PA sinks and source registration completed");
+ //TODO: - search for existing sink inputs & sources outputs
+ }
+}