diff options
Diffstat (limited to 'PluginRoutingInterfacePulse/src/RoutingSenderPULSE.cpp')
-rw-r--r-- | PluginRoutingInterfacePulse/src/RoutingSenderPULSE.cpp | 914 |
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 + } +} |