summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AudioManagerCore/include/CAmControlReceiver.h3
-rw-r--r--AudioManagerCore/include/CAmControlSender.h3
-rw-r--r--AudioManagerCore/include/CAmDatabaseHandlerMap.h5
-rw-r--r--AudioManagerCore/include/CAmRoutingReceiver.h3
-rw-r--r--AudioManagerCore/include/CAmRoutingSender.h20
-rw-r--r--AudioManagerCore/include/IAmDatabaseHandler.h5
-rw-r--r--AudioManagerCore/src/CAmControlReceiver.cpp41
-rw-r--r--AudioManagerCore/src/CAmControlSender.cpp13
-rw-r--r--AudioManagerCore/src/CAmDatabaseHandlerMap.cpp90
-rw-r--r--AudioManagerCore/src/CAmRoutingReceiver.cpp78
-rw-r--r--AudioManagerCore/src/CAmRoutingSender.cpp25
-rw-r--r--AudioManagerCore/test/AmMapHandlerTest/CAmMapHandlerTest.cpp14
-rw-r--r--AudioManagerCore/test/AmRoutingInterfaceTest/CAmRoutingInterfaceTest.cpp118
-rw-r--r--AudioManagerCore/test/MockIAmControlSend.h4
-rw-r--r--AudioManagerCore/test/MockIAmRoutingSend.h6
-rwxr-xr-xAudioManagerUtilities/include/IAmLogger.h3
-rw-r--r--docx/10_early_audio.dox120
-rw-r--r--docx/images/early_announcement.pngbin0 -> 43711 bytes
-rw-r--r--docx/images/early_transfer.pngbin0 -> 42024 bytes
-rw-r--r--include/IAmControl.h56
-rw-r--r--include/IAmRouting.h48
-rwxr-xr-xinclude/audiomanagertypes.h1
22 files changed, 605 insertions, 51 deletions
diff --git a/AudioManagerCore/include/CAmControlReceiver.h b/AudioManagerCore/include/CAmControlReceiver.h
index bd897de..d4a75b1 100644
--- a/AudioManagerCore/include/CAmControlReceiver.h
+++ b/AudioManagerCore/include/CAmControlReceiver.h
@@ -103,6 +103,7 @@ public:
am_Error_e getGatewayInfoDB(const am_gatewayID_t gatewayID, am_Gateway_s &gatewayData) const;
am_Error_e getConverterInfoDB(const am_converterID_t converterID, am_Converter_s &converterData) const;
am_Error_e getCrossfaderInfoDB(const am_crossfaderID_t crossfaderID, am_Crossfader_s &crossfaderData) const;
+ am_Error_e getConnectionInfoDB(const am_connectionID_t connectionID, am_Connection_s &connectionData) const;
am_Error_e getMainConnectionInfoDB(const am_mainConnectionID_t mainConnectionID, am_MainConnection_s &mainConnectionData) const;
am_Error_e getListSinksOfDomain(const am_domainID_t domainID, std::vector<am_sinkID_t> &listSinkID) const;
am_Error_e getListSourcesOfDomain(const am_domainID_t domainID, std::vector<am_sourceID_t> &listSourceID) const;
@@ -127,6 +128,8 @@ public:
void setRoutingRundown();
void confirmControllerReady(const am_Error_e error);
void confirmControllerRundown(const am_Error_e error);
+ am_Error_e transferConnection(am_Handle_s &handle, am_mainConnectionID_t mainConnectionID
+ , am_domainID_t domainID) override;
am_Error_e getSocketHandler(CAmSocketHandler * &socketHandler);
void getInterfaceVersion(std::string &version) const;
am_Error_e changeSourceDB(const am_sourceID_t sourceID, const am_sourceClass_t sourceClassID, const std::vector<am_SoundProperty_s> &listSoundProperties, const std::vector<am_CustomConnectionFormat_t> &listConnectionFormats, const std::vector<am_MainSoundProperty_s> &listMainSoundProperties);
diff --git a/AudioManagerCore/include/CAmControlSender.h b/AudioManagerCore/include/CAmControlSender.h
index 77e8e2c..71220d5 100644
--- a/AudioManagerCore/include/CAmControlSender.h
+++ b/AudioManagerCore/include/CAmControlSender.h
@@ -71,6 +71,8 @@ public:
am_Error_e hookSystemDeregisterConverter(const am_converterID_t converterID);
am_Error_e hookSystemRegisterCrossfader(const am_Crossfader_s &crossfaderData, am_crossfaderID_t &crossfaderID);
am_Error_e hookSystemDeregisterCrossfader(const am_crossfaderID_t crossfaderID);
+ am_Error_e hookSystemRegisterEarlyConnection(am_domainID_t domainID
+ , const am_MainConnection_s &mainConnectionData, const am_Route_s &route);
void hookSystemSinkVolumeTick(const am_Handle_s handle, const am_sinkID_t sinkID, const am_volume_t volume);
void hookSystemSourceVolumeTick(const am_Handle_s handle, const am_sourceID_t sourceID, const am_volume_t volume);
void hookSystemInterruptStateChange(const am_sourceID_t sourceID, const am_InterruptState_e interruptState);
@@ -82,6 +84,7 @@ public:
void hookSystemTimingInformationChanged(const am_mainConnectionID_t mainConnectionID, const am_timeSync_t time);
void cbAckConnect(const am_Handle_s handle, const am_Error_e errorID);
void cbAckDisconnect(const am_Handle_s handle, const am_Error_e errorID);
+ void cbAckTransferConnection(const am_Handle_s handle, const am_Error_e errorID);
void cbAckCrossFade(const am_Handle_s handle, const am_HotSink_e hostsink, const am_Error_e error);
void cbAckSetSinkVolumeChange(const am_Handle_s handle, const am_volume_t volume, const am_Error_e error);
void cbAckSetSourceVolumeChange(const am_Handle_s handle, const am_volume_t voulme, const am_Error_e error);
diff --git a/AudioManagerCore/include/CAmDatabaseHandlerMap.h b/AudioManagerCore/include/CAmDatabaseHandlerMap.h
index 46da560..7c3f17d 100644
--- a/AudioManagerCore/include/CAmDatabaseHandlerMap.h
+++ b/AudioManagerCore/include/CAmDatabaseHandlerMap.h
@@ -123,13 +123,13 @@ public:
};
am_Error_e enterDomainDB(const am_Domain_s &domainData, am_domainID_t &domainID);
- am_Error_e enterMainConnectionDB(const am_MainConnection_s &mainConnectionData, am_mainConnectionID_t &connectionID);
+ am_Error_e enterMainConnectionDB(const am_MainConnection_s &mainConnectionData, am_mainConnectionID_t &connectionID, bool allowReserved = false) override;
am_Error_e enterSinkDB(const am_Sink_s &sinkData, am_sinkID_t &sinkID);
am_Error_e enterCrossfaderDB(const am_Crossfader_s &crossfaderData, am_crossfaderID_t &crossfaderID);
am_Error_e enterGatewayDB(const am_Gateway_s &gatewayData, am_gatewayID_t &gatewayID);
am_Error_e enterConverterDB(const am_Converter_s &converterData, am_converterID_t &converterID);
am_Error_e enterSourceDB(const am_Source_s &sourceData, am_sourceID_t &sourceID);
- am_Error_e enterConnectionDB(const am_Connection_s &connection, am_connectionID_t &connectionID);
+ am_Error_e enterConnectionDB(const am_Connection_s &connection, am_connectionID_t &connectionID, bool allowReserved = false) override;
am_Error_e enterSinkClassDB(const am_SinkClass_s &sinkClass, am_sinkClass_t &sinkClassID);
am_Error_e enterSourceClassDB(am_sourceClass_t &sourceClassID, const am_SourceClass_s &sourceClass);
am_Error_e enterSystemProperties(const std::vector<am_SystemProperty_s> &listSystemProperties);
@@ -175,6 +175,7 @@ public:
am_Error_e getSinkInfoDB(const am_sinkID_t sinkID, am_Sink_s &sinkData) const;
am_Error_e getSourceInfoDB(const am_sourceID_t sourceID, am_Source_s &sourceData) const;
am_Error_e getCrossfaderInfoDB(const am_crossfaderID_t crossfaderID, am_Crossfader_s &crossfaderData) const;
+ am_Error_e getConnectionInfoDB(const am_connectionID_t connectionID, am_Connection_s &connectionData) const;
am_Error_e getMainConnectionInfoDB(const am_mainConnectionID_t mainConnectionID, am_MainConnection_s &mainConnectionData) const;
am_Error_e getSinkMainVolume(const am_sinkID_t sinkID, am_mainVolume_t &mainVolume) const;
am_Error_e getSinkVolume(const am_sinkID_t sinkID, am_volume_t &volume) const;
diff --git a/AudioManagerCore/include/CAmRoutingReceiver.h b/AudioManagerCore/include/CAmRoutingReceiver.h
index 42f3e89..fe12ccd 100644
--- a/AudioManagerCore/include/CAmRoutingReceiver.h
+++ b/AudioManagerCore/include/CAmRoutingReceiver.h
@@ -44,6 +44,7 @@ public:
CAmRoutingReceiver(IAmDatabaseHandler *iDatabaseHandler, CAmRoutingSender *iRoutingSender, CAmControlSender *iControlSender, CAmSocketHandler *iSocketHandler);
CAmRoutingReceiver(IAmDatabaseHandler *iDatabaseHandler, CAmRoutingSender *iRoutingSender, CAmControlSender *iControlSender, CAmSocketHandler *iSocketHandler, CAmDbusWrapper *iDBusWrapper);
~CAmRoutingReceiver();
+ void ackTransferConnection(const am_Handle_s handle, const am_Error_e errorID);
void ackConnect(const am_Handle_s handle, const am_connectionID_t connectionID, const am_Error_e error);
void ackDisconnect(const am_Handle_s handle, const am_connectionID_t connectionID, const am_Error_e error);
void ackSetSinkVolumeChange(const am_Handle_s handle, const am_volume_t volume, const am_Error_e error);
@@ -59,6 +60,8 @@ public:
am_Error_e peekDomain(const std::string &name, am_domainID_t &domainID);
am_Error_e registerDomain(const am_Domain_s &domainData, am_domainID_t &domainID);
am_Error_e deregisterDomain(const am_domainID_t domainID);
+ am_Error_e registerEarlyConnection(am_domainID_t domainID, const am_Route_s &route
+ , am_ConnectionState_e state);
am_Error_e registerGateway(const am_Gateway_s &gatewayData, am_gatewayID_t &gatewayID);
am_Error_e registerConverter(const am_Converter_s &converterData, am_converterID_t &converterID);
am_Error_e deregisterGateway(const am_gatewayID_t gatewayID);
diff --git a/AudioManagerCore/include/CAmRoutingSender.h b/AudioManagerCore/include/CAmRoutingSender.h
index 39b5c23..cbaddae 100644
--- a/AudioManagerCore/include/CAmRoutingSender.h
+++ b/AudioManagerCore/include/CAmRoutingSender.h
@@ -62,6 +62,8 @@ public:
am_Error_e startupInterfaces(CAmRoutingReceiver *iRoutingReceiver);
void setRoutingReady();
void setRoutingRundown();
+ am_Error_e asyncTransferConnection(am_Handle_s &handle, am_domainID_t domainID
+ , const std::vector<std::pair<std::string, std::string>> &route, am_ConnectionState_e state);
am_Error_e asyncAbort(const am_Handle_s &handle);
am_Error_e asyncConnect(am_Handle_s &handle, am_connectionID_t &connectionID, const am_sourceID_t sourceID, const am_sinkID_t sinkID, const am_CustomConnectionFormat_t connectionFormat);
am_Error_e asyncDisconnect(am_Handle_s &handle, const am_connectionID_t connectionID);
@@ -262,6 +264,24 @@ public:
CAmRoutingSender *mRoutingSender;
};
+ class handleTransfer : public handleDataBase
+ {
+ public:
+ handleTransfer(IAmRoutingSend *interface, const std::vector<std::pair<std::string, std::string>> &route
+ , am_ConnectionState_e state, IAmDatabaseHandler *databaseHandler)
+ : handleDataBase(interface, databaseHandler)
+ , mRoute(route)
+ , mState(state)
+ , mTransferPending(true) {}
+ ~handleTransfer() { };
+ am_Error_e writeDataToDatabase() { return E_OK; };
+
+ private:
+ const std::vector<std::pair<std::string, std::string>> mRoute;
+ am_ConnectionState_e mState;
+ bool mTransferPending;
+ };
+
class handleSetVolumes : public handleDataBase
{
public:
diff --git a/AudioManagerCore/include/IAmDatabaseHandler.h b/AudioManagerCore/include/IAmDatabaseHandler.h
index 8e4118e..281a93b 100644
--- a/AudioManagerCore/include/IAmDatabaseHandler.h
+++ b/AudioManagerCore/include/IAmDatabaseHandler.h
@@ -62,13 +62,13 @@ public:
* The following interface methods must be implemented by the subclass.
*/
virtual am_Error_e enterDomainDB(const am_Domain_s &domainData, am_domainID_t &domainID) = 0;
- virtual am_Error_e enterMainConnectionDB(const am_MainConnection_s &mainConnectionData, am_mainConnectionID_t &connectionID) = 0;
+ virtual am_Error_e enterMainConnectionDB(const am_MainConnection_s &mainConnectionData, am_mainConnectionID_t &connectionID, bool allowReserved) = 0;
virtual am_Error_e enterSinkDB(const am_Sink_s &sinkData, am_sinkID_t &sinkID) = 0;
virtual am_Error_e enterCrossfaderDB(const am_Crossfader_s &crossfaderData, am_crossfaderID_t &crossfaderID) = 0;
virtual am_Error_e enterGatewayDB(const am_Gateway_s &gatewayData, am_gatewayID_t &gatewayID) = 0;
virtual am_Error_e enterConverterDB(const am_Converter_s &converteData, am_converterID_t &converterID) = 0;
virtual am_Error_e enterSourceDB(const am_Source_s &sourceData, am_sourceID_t &sourceID) = 0;
- virtual am_Error_e enterConnectionDB(const am_Connection_s &connection, am_connectionID_t &connectionID) = 0;
+ virtual am_Error_e enterConnectionDB(const am_Connection_s &connection, am_connectionID_t &connectionID, bool allowReserved) = 0;
virtual am_Error_e enterSinkClassDB(const am_SinkClass_s &sinkClass, am_sinkClass_t &sinkClassID) = 0;
virtual am_Error_e enterSourceClassDB(am_sourceClass_t &sourceClassID, const am_SourceClass_s &sourceClass) = 0;
virtual am_Error_e enterSystemProperties(const std::vector<am_SystemProperty_s> &listSystemProperties) = 0;
@@ -114,6 +114,7 @@ public:
virtual am_Error_e getSinkInfoDB(const am_sinkID_t sinkID, am_Sink_s &sinkData) const = 0;
virtual am_Error_e getSourceInfoDB(const am_sourceID_t sourceID, am_Source_s &sourceData) const = 0;
virtual am_Error_e getCrossfaderInfoDB(const am_crossfaderID_t crossfaderID, am_Crossfader_s &crossfaderData) const = 0;
+ virtual am_Error_e getConnectionInfoDB(const am_connectionID_t connectionID, am_Connection_s &connectionData) const = 0;
virtual am_Error_e getMainConnectionInfoDB(const am_mainConnectionID_t mainConnectionID, am_MainConnection_s &mainConnectionData) const = 0;
virtual am_Error_e getSinkMainVolume(const am_sinkID_t sinkID, am_mainVolume_t &mainVolume) const = 0;
virtual am_Error_e getSinkVolume(const am_sinkID_t sinkID, am_volume_t &volume) const = 0;
diff --git a/AudioManagerCore/src/CAmControlReceiver.cpp b/AudioManagerCore/src/CAmControlReceiver.cpp
index 7a6072c..7efee95 100644
--- a/AudioManagerCore/src/CAmControlReceiver.cpp
+++ b/AudioManagerCore/src/CAmControlReceiver.cpp
@@ -128,7 +128,7 @@ am_Error_e CAmControlReceiver::enterDomainDB(const am_Domain_s &domainData, am_d
am_Error_e CAmControlReceiver::enterMainConnectionDB(const am_MainConnection_s &mainConnectionData, am_mainConnectionID_t &connectionID)
{
- return (mDatabaseHandler->enterMainConnectionDB(mainConnectionData, connectionID));
+ return (mDatabaseHandler->enterMainConnectionDB(mainConnectionData, connectionID, false));
}
am_Error_e CAmControlReceiver::enterSinkDB(const am_Sink_s &sinkData, am_sinkID_t &sinkID)
@@ -291,6 +291,11 @@ am_Error_e CAmControlReceiver::getSourceInfoDB(const am_sourceID_t sourceID, am_
return (mDatabaseHandler->getSourceInfoDB(sourceID, sourceData));
}
+am_Error_e CAmControlReceiver::getConnectionInfoDB(const am_connectionID_t connectionID, am_Connection_s &connectionData) const
+{
+ return (mDatabaseHandler->getConnectionInfoDB(connectionID, connectionData));
+}
+
am_Error_e CAmControlReceiver::getMainConnectionInfoDB(const am_mainConnectionID_t mainConnectionID, am_MainConnection_s &mainConnectionData) const
{
return (mDatabaseHandler->getMainConnectionInfoDB(mainConnectionID, mainConnectionData));
@@ -451,6 +456,40 @@ void CAmControlReceiver::confirmControllerRundown(const am_Error_e error)
mSocketHandler->exit_mainloop();
}
+am_Error_e CAmControlReceiver::transferConnection(am_Handle_s &handle
+ , am_mainConnectionID_t mainConnectionID, am_domainID_t domainID)
+{
+ am_MainConnection_s mainConnectionData;
+ if (mDatabaseHandler->getMainConnectionInfoDB(mainConnectionID, mainConnectionData) != E_OK)
+ {
+ return E_DATABASE_ERROR;
+ }
+
+ std::vector<std::pair<std::string, std::string>> route;
+ route.reserve(mainConnectionData.listConnectionID.size());
+ for (auto iter : mainConnectionData.listConnectionID)
+ {
+ am_Connection_s connectionData;
+ if (mDatabaseHandler->getConnectionInfoDB(iter, connectionData) != E_OK)
+ {
+ return E_DATABASE_ERROR;
+ }
+
+ // determine source and sink name, even if they are only peeked, but not fully registered
+ am_Source_s sourceData;
+ am_Sink_s sinkData;
+ if ( (mDatabaseHandler->getSourceInfoDB(connectionData.sourceID, sourceData) == E_NON_EXISTENT)
+ || (mDatabaseHandler->getSinkInfoDB(connectionData.sinkID, sinkData) == E_NON_EXISTENT))
+ {
+ return E_DATABASE_ERROR;
+ }
+
+ route.push_back({sourceData.name, sinkData.name});
+ }
+
+ return mRoutingSender->asyncTransferConnection(handle, domainID, route, mainConnectionData.connectionState);
+}
+
am_Error_e CAmControlReceiver::getSocketHandler(CAmSocketHandler * &socketHandler)
{
socketHandler = mSocketHandler;
diff --git a/AudioManagerCore/src/CAmControlSender.cpp b/AudioManagerCore/src/CAmControlSender.cpp
index 0fa84ef..bb92198 100644
--- a/AudioManagerCore/src/CAmControlSender.cpp
+++ b/AudioManagerCore/src/CAmControlSender.cpp
@@ -319,6 +319,13 @@ am_Error_e CAmControlSender::hookSystemDeregisterCrossfader(const am_crossfaderI
return (mController->hookSystemDeregisterCrossfader(crossfaderID));
}
+am_Error_e CAmControlSender::hookSystemRegisterEarlyConnection(am_domainID_t domainID
+ , const am_MainConnection_s &mainConnectionData, const am_Route_s &route)
+{
+ assert(mController);
+ return mController->hookSystemRegisterEarlyMainConnection(domainID, mainConnectionData, route);
+}
+
void CAmControlSender::hookSystemSinkVolumeTick(const am_Handle_s handle, const am_sinkID_t sinkID, const am_volume_t volume)
{
assert(mController);
@@ -385,6 +392,12 @@ void CAmControlSender::cbAckDisconnect(const am_Handle_s handle, const am_Error_
mController->cbAckDisconnect(handle, errorID);
}
+void CAmControlSender::cbAckTransferConnection(const am_Handle_s handle, const am_Error_e errorID)
+{
+ assert(mController);
+ mController->cbAckTransferConnection(handle, errorID);
+}
+
void CAmControlSender::cbAckCrossFade(const am_Handle_s handle, const am_HotSink_e hostsink, const am_Error_e error)
{
assert(mController);
diff --git a/AudioManagerCore/src/CAmDatabaseHandlerMap.cpp b/AudioManagerCore/src/CAmDatabaseHandlerMap.cpp
index 6e6e3f9..fc539bb 100644
--- a/AudioManagerCore/src/CAmDatabaseHandlerMap.cpp
+++ b/AudioManagerCore/src/CAmDatabaseHandlerMap.cpp
@@ -585,7 +585,7 @@ int16_t CAmDatabaseHandlerMap::calculateDelayForRoute(const std::vector<am_conne
return delay;
}
-am_Error_e CAmDatabaseHandlerMap::enterMainConnectionDB(const am_MainConnection_s &mainConnectionData, am_mainConnectionID_t &connectionID)
+am_Error_e CAmDatabaseHandlerMap::enterMainConnectionDB(const am_MainConnection_s &mainConnectionData, am_mainConnectionID_t &connectionID, bool allowReserved)
{
if (mainConnectionData.mainConnectionID != 0)
{
@@ -599,18 +599,34 @@ am_Error_e CAmDatabaseHandlerMap::enterMainConnectionDB(const am_MainConnection_
return (E_NOT_POSSIBLE);
}
- if (!existSink(mainConnectionData.sinkID))
+ auto itMappedSink = mMappedData.mSinkMap.find(mainConnectionData.sinkID);
+ if ((itMappedSink == mMappedData.mSinkMap.end()) || (itMappedSink->second.reserved && !allowReserved))
{
logError(__METHOD_NAME__, "sinkID must be valid!");
return (E_NOT_POSSIBLE);
}
- if (!existSource(mainConnectionData.sourceID))
+ auto itMappedSource = mMappedData.mSourceMap.find(mainConnectionData.sourceID);
+ if ((itMappedSource == mMappedData.mSourceMap.end()) || (itMappedSource->second.reserved && !allowReserved))
{
logError(__METHOD_NAME__, "sourceID must be valid!");
return (E_NOT_POSSIBLE);
}
+ // check if we already have this connection
+ for (auto &mapped : mMappedData.mMainConnectionMap)
+ {
+ if ((mapped.second.sourceID != mainConnectionData.sourceID) || (mapped.second.sinkID != mainConnectionData.sinkID))
+ {
+ continue;
+ }
+
+ connectionID = mapped.second.mainConnectionID;
+ logWarning(__METHOD_NAME__, "main connection from source", mainConnectionData.sourceID
+ , "to sink", mainConnectionData.sinkID, "already exists with ID", connectionID);
+ return E_ALREADY_EXISTS;
+ }
+
int16_t delay = 0;
int16_t nextID = 0;
if (mMappedData.increaseMainConnectionID(nextID))
@@ -1149,7 +1165,7 @@ am_Error_e CAmDatabaseHandlerMap::enterSourceDB(const am_Source_s &sourceData, a
return (E_OK);
}
-am_Error_e CAmDatabaseHandlerMap::enterConnectionDB(const am_Connection_s &connection, am_connectionID_t &connectionID)
+am_Error_e CAmDatabaseHandlerMap::enterConnectionDB(const am_Connection_s &connection, am_connectionID_t &connectionID, bool allowReserved)
{
if (connection.connectionID != 0)
{
@@ -1157,18 +1173,34 @@ am_Error_e CAmDatabaseHandlerMap::enterConnectionDB(const am_Connection_s &conne
return (E_NOT_POSSIBLE);
}
- if (!existSink(connection.sinkID))
+ const AmMapSink::const_iterator &itMappedSink = mMappedData.mSinkMap.find(connection.sinkID);
+ if ((itMappedSink == mMappedData.mSinkMap.end()) || (itMappedSink->second.reserved && !allowReserved))
{
logError(__METHOD_NAME__, "sinkID must exist!");
return (E_NOT_POSSIBLE);
}
- if (!existSource(connection.sourceID))
+ const AmMapSource::const_iterator &itMappedSource = mMappedData.mSourceMap.find(connection.sourceID);
+ if ((itMappedSource == mMappedData.mSourceMap.end()) || (itMappedSource->second.reserved && !allowReserved))
{
logError(__METHOD_NAME__, "sourceID must exist!");
return (E_NOT_POSSIBLE);
}
+ // check if we already have this connection
+ for (auto &mapped : mMappedData.mConnectionMap)
+ {
+ if ((mapped.second.sourceID != connection.sourceID) || (mapped.second.sinkID != connection.sinkID))
+ {
+ continue;
+ }
+
+ connectionID = mapped.second.connectionID;
+ logWarning(__METHOD_NAME__, "connection from source", connection.sourceID
+ , "to sink", connection.sinkID, "already exists with ID", connectionID);
+ return E_ALREADY_EXISTS;
+ }
+
// connection format is not checked, because it's project specific
int16_t nextID = 0;
if (mMappedData.increaseConnectionID(nextID))
@@ -1917,54 +1949,66 @@ am_Error_e CAmDatabaseHandlerMap::getSourceClassInfoDB(const am_sourceID_t sourc
am_Error_e CAmDatabaseHandlerMap::getSinkInfoDB(const am_sinkID_t sinkID, am_Sink_s &sinkData) const
{
-
- if (!existSink(sinkID))
+ auto iter = mMappedData.mSinkMap.find(sinkID);
+ if (iter == mMappedData.mSinkMap.end())
{
logWarning(__METHOD_NAME__, "sinkID", sinkID, "does not exist");
return (E_NON_EXISTENT);
}
- am_Sink_Database_s mappedSink = mMappedData.mSinkMap.at(sinkID);
- if ( true == mappedSink.reserved )
+ sinkData = iter->second; // copy to output parameter even if only ID and name are valid
+ if ( iter->second.reserved )
{
- return (E_NON_EXISTENT);
+ logWarning(__METHOD_NAME__, "sinkID", sinkID, "reserved for", sinkData.name, "but details are E_UNKNOWN");
+ return E_UNKNOWN;
}
- sinkData = mappedSink;
-
return (E_OK);
}
am_Error_e CAmDatabaseHandlerMap::getSourceInfoDB(const am_sourceID_t sourceID, am_Source_s &sourceData) const
{
-
- if (!existSource(sourceID))
+ auto iter = mMappedData.mSourceMap.find(sourceID);
+ if (iter == mMappedData.mSourceMap.end())
{
logWarning(__METHOD_NAME__, "sourceID", sourceID, "does not exist");
return (E_NON_EXISTENT);
}
- am_Source_Database_s mappedSource = mMappedData.mSourceMap.at(sourceID);
- if ( true == mappedSource.reserved )
+ sourceData = iter->second; // copy to output parameter even if only ID and name are valid
+ if ( true == iter->second.reserved )
{
+ logWarning(__METHOD_NAME__, "sourceID", sourceID, "reserved for", sourceData.name, "but details are E_UNKNOWN");
+ return E_UNKNOWN;
+ }
+
+ return (E_OK);
+}
+
+am_Error_e am::CAmDatabaseHandlerMap::getConnectionInfoDB(const am_connectionID_t connectionID, am_Connection_s &connectionData) const
+{
+ auto iter = mMappedData.mConnectionMap.find(connectionID);
+ if (iter == mMappedData.mConnectionMap.end())
+ {
+ logError(__METHOD_NAME__, "connectionID", connectionID, "does not exist");
return (E_NON_EXISTENT);
}
- sourceData = mappedSource;
+ connectionData = iter->second;
- return (E_OK);
+ return E_OK;
}
am_Error_e am::CAmDatabaseHandlerMap::getMainConnectionInfoDB(const am_mainConnectionID_t mainConnectionID, am_MainConnection_s &mainConnectionData) const
{
- if (!existMainConnection(mainConnectionID))
+ auto iter = mMappedData.mMainConnectionMap.find(mainConnectionID);
+ if (iter == mMappedData.mMainConnectionMap.end())
{
- logError(__METHOD_NAME__, "mainConnectionID must exist");
+ logError(__METHOD_NAME__, "mainConnectionID", mainConnectionID, "does not exist");
return (E_NON_EXISTENT);
}
- am_MainConnection_s temp = mMappedData.mMainConnectionMap.at(mainConnectionID);
- mainConnectionData = temp;
+ mainConnectionData = iter->second;
return (E_OK);
}
diff --git a/AudioManagerCore/src/CAmRoutingReceiver.cpp b/AudioManagerCore/src/CAmRoutingReceiver.cpp
index be13474..56fb5e2 100644
--- a/AudioManagerCore/src/CAmRoutingReceiver.cpp
+++ b/AudioManagerCore/src/CAmRoutingReceiver.cpp
@@ -124,6 +124,28 @@ void CAmRoutingReceiver::ackDisconnect(const am_Handle_s handle, const am_connec
mpControlSender->cbAckDisconnect(handle, error);
}
+/**
+ * Support hand-over acknowledgment of connections surviving shutdown of the AM
+ *
+ * @param handle: composite identifier used in the request
+ * @param errorID:success indicator (E_OK if application takes over,
+ * E_NOT_POSSIBLE if the routing adapter is not prepared to take over
+ * full responsibility for all involved sources and sinks)
+ */
+void CAmRoutingReceiver::ackTransferConnection(const am_Handle_s handle, const am_Error_e error)
+{
+ if (error == E_OK)
+ {
+ mpRoutingSender->writeToDatabaseAndRemove(handle);
+ }
+ else
+ {
+ mpRoutingSender->removeHandle(handle);
+ }
+
+ mpControlSender->cbAckTransferConnection(handle, error);
+}
+
void CAmRoutingReceiver::ackSetSinkVolumeChange(const am_Handle_s handle, const am_volume_t volume, const am_Error_e error)
{
logInfo(__METHOD_NAME__, "handle=", handle, "volume=", volume, "error=", error);
@@ -233,6 +255,62 @@ am_Error_e CAmRoutingReceiver::deregisterDomain(const am_domainID_t domainID)
return (mpControlSender->hookSystemDeregisterDomain(domainID));
}
+am_Error_e CAmRoutingReceiver::registerEarlyConnection(am_domainID_t domainID
+ , const am_Route_s &route, am_ConnectionState_e state)
+{
+ const auto &segmentList = route.route;
+ if (segmentList.size() < 1)
+ {
+ logError(__METHOD_NAME__, "empty route from domain", domainID);
+ return E_NOT_POSSIBLE;
+ }
+
+ am_MainConnection_s mainConnectionData;
+ mainConnectionData.mainConnectionID = 0;
+ mainConnectionData.sourceID = segmentList.front().sourceID;
+ mainConnectionData.sinkID = segmentList.back().sinkID;
+ mainConnectionData.connectionState = state;
+ mainConnectionData.listConnectionID.reserve(segmentList.size());
+ for (const auto &segment : segmentList)
+ {
+ am_Connection_s conn;
+ conn.sourceID = segment.sourceID;
+ conn.sinkID = segment.sinkID;
+ conn.connectionFormat = segment.connectionFormat;
+ conn.connectionID = 0;
+ am_Error_e success = mpDatabaseHandler->enterConnectionDB(conn, conn.connectionID, true);
+ switch (success)
+ {
+ case E_OK:
+ case E_ALREADY_EXISTS:
+ case E_NO_CHANGE:
+ mainConnectionData.listConnectionID.push_back(conn.connectionID);
+ break;
+
+ default:
+ logError(__METHOD_NAME__, "failed to enter connection segment", conn.sourceID
+ , "to", conn.sinkID, "from domain", domainID, "error=", success);
+ return success;
+ }
+ }
+
+ am_Error_e success = mpDatabaseHandler->enterMainConnectionDB(mainConnectionData, mainConnectionData.mainConnectionID, true);
+ switch (success)
+ {
+ case E_OK:
+ case E_ALREADY_EXISTS:
+ case E_NO_CHANGE:
+ break;
+
+ default:
+ logError(__METHOD_NAME__, "failed to enter main connection", mainConnectionData.sourceID
+ , "to", mainConnectionData.sinkID, "from domain", domainID, "error=", success);
+ return success;
+ }
+
+ return mpControlSender->hookSystemRegisterEarlyConnection(domainID, mainConnectionData, route);
+}
+
am_Error_e CAmRoutingReceiver::registerGateway(const am_Gateway_s &gatewayData, am_gatewayID_t &gatewayID)
{
return (mpControlSender->hookSystemRegisterGateway(gatewayData, gatewayID));
diff --git a/AudioManagerCore/src/CAmRoutingSender.cpp b/AudioManagerCore/src/CAmRoutingSender.cpp
index 36f5c1a..fc9f879 100644
--- a/AudioManagerCore/src/CAmRoutingSender.cpp
+++ b/AudioManagerCore/src/CAmRoutingSender.cpp
@@ -285,7 +285,7 @@ am_Error_e CAmRoutingSender::asyncConnect(am_Handle_s &handle, am_connectionID_t
tempConnection.connectionID = 0;
tempConnection.delay = -1;
- am_Error_e connError(mpDatabaseHandler->enterConnectionDB(tempConnection, connectionID));
+ am_Error_e connError(mpDatabaseHandler->enterConnectionDB(tempConnection, connectionID, false));
if (connError)
{
return(connError);
@@ -921,6 +921,29 @@ void CAmRoutingSender::setRoutingRundown()
}
}
+am_Error_e CAmRoutingSender::asyncTransferConnection(am_Handle_s &handle, am_domainID_t domainID
+ , const std::vector<std::pair<std::string, std::string>> &route, am_ConnectionState_e state)
+{
+ auto iter = mMapDomainInterface.find(domainID);
+ if (iter != mMapDomainInterface.end() && iter->second)
+ {
+ auto handleData = std::make_shared<handleTransfer>(iter->second, route, state, mpDatabaseHandler);
+ handle = createHandle(handleData, H_TRANSFERCONNECTION);
+
+ logInfo(__METHOD_NAME__, "handle=", handle);
+
+ am_Error_e success = iter->second->asyncTransferConnection(handle, domainID, route, state);
+ if (success != E_OK)
+ {
+ removeHandle(handle);
+ }
+ return success;
+ }
+
+ // given domain not found in map
+ return E_NON_EXISTENT;
+}
+
am_Error_e CAmRoutingSender::asyncSetVolumes(am_Handle_s &handle, const std::vector<am_Volumes_s> &listVolumes)
{
IAmRoutingSend *pRoutingInterface(NULL);
diff --git a/AudioManagerCore/test/AmMapHandlerTest/CAmMapHandlerTest.cpp b/AudioManagerCore/test/AmMapHandlerTest/CAmMapHandlerTest.cpp
index 6e10d19..cbbfc06 100644
--- a/AudioManagerCore/test/AmMapHandlerTest/CAmMapHandlerTest.cpp
+++ b/AudioManagerCore/test/AmMapHandlerTest/CAmMapHandlerTest.cpp
@@ -2817,8 +2817,8 @@ TEST_F(CAmMapHandlerTest, connectionIDBoundary)
ASSERT_EQ(E_OK, pDatabaseHandler.enterSourceDB(source, forgetSource));
ASSERT_EQ(E_OK, pDatabaseHandler.enterConnectionDB(connection,connectionID));
ASSERT_EQ(12, connectionID);
- ASSERT_EQ(E_UNKNOWN, pDatabaseHandler.enterConnectionDB(connection,connectionID));
- ASSERT_EQ(0, connectionID);
+ ASSERT_EQ(E_ALREADY_EXISTS, pDatabaseHandler.enterConnectionDB(connection,connectionID));
+ ASSERT_EQ(12, connectionID);
}
TEST_F(CAmMapHandlerTest, mainConnectionIDBoundary)
@@ -2892,10 +2892,12 @@ TEST_F(CAmMapHandlerTest, mainConnectionIDBoundary)
#else
EXPECT_CALL(*MockDatabaseObserver::getMockObserverObject(), timingInformationChanged(_, _)).Times(1);
#endif
- EXPECT_CALL(*MockDatabaseObserver::getMockObserverObject(), removedMainConnection(_)).Times(2);
- EXPECT_CALL(*MockDatabaseObserver::getMockObserverObject(), mainConnectionStateChanged(_, _)).Times(3);
+ EXPECT_CALL(*MockDatabaseObserver::getMockObserverObject(), removedMainConnection(_)).Times(3);
+ EXPECT_CALL(*MockDatabaseObserver::getMockObserverObject(), mainConnectionStateChanged(_, _)).Times(4);
ASSERT_EQ(E_OK, pDatabaseHandler.removeMainConnectionDB(10));
ASSERT_EQ(E_OK, pDatabaseHandler.removeMainConnectionDB(12));
+ // drop also last dynamic connection before entering again
+ ASSERT_EQ(E_OK, pDatabaseHandler.removeMainConnectionDB(mainConnectionID));
ASSERT_EQ(E_OK, pDatabaseHandler.enterMainConnectionDB(mainConnection,mainConnectionID));
ASSERT_EQ(10, mainConnectionID);
mainConnection.sinkID = 77;
@@ -2917,8 +2919,8 @@ TEST_F(CAmMapHandlerTest, mainConnectionIDBoundary)
ASSERT_EQ(E_OK, pDatabaseHandler.enterSourceDB(source, forgetSource));
ASSERT_EQ(E_OK, pDatabaseHandler.enterMainConnectionDB(mainConnection,mainConnectionID));
ASSERT_EQ(12, mainConnectionID);
- ASSERT_EQ(E_UNKNOWN, pDatabaseHandler.enterMainConnectionDB(mainConnection,mainConnectionID));
- ASSERT_EQ(0, mainConnectionID);
+ ASSERT_EQ(E_ALREADY_EXISTS, pDatabaseHandler.enterMainConnectionDB(mainConnection,mainConnectionID));
+ ASSERT_EQ(12, mainConnectionID);
}
TEST_F(CAmMapHandlerTest, increaseID)
diff --git a/AudioManagerCore/test/AmRoutingInterfaceTest/CAmRoutingInterfaceTest.cpp b/AudioManagerCore/test/AmRoutingInterfaceTest/CAmRoutingInterfaceTest.cpp
index e5e9636..e2df6e7 100644
--- a/AudioManagerCore/test/AmRoutingInterfaceTest/CAmRoutingInterfaceTest.cpp
+++ b/AudioManagerCore/test/AmRoutingInterfaceTest/CAmRoutingInterfaceTest.cpp
@@ -643,6 +643,124 @@ TEST_F(CAmRoutingInterfaceTest,handleOverflowAbsolute)
ASSERT_EQ(handleOverflowCheck1.handle,0);
}
+/**
+ * Validate handling of early connection announcement and transfer
+ */
+TEST_F(CAmRoutingInterfaceTest, registerEarlyConnection)
+{
+ // prepare additional domain
+ am_Domain_s domain;
+ pCF.createDomain(domain);
+ domain.name = "mock";
+ domain.busname = "mock";
+
+ am_SourceClass_s sourceclass;
+ sourceclass.name="sClass";
+ sourceclass.sourceClassID=5;
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterSourceClassDB(sourceclass.sourceClassID,sourceclass));
+ am_Source_s source;
+ pCF.createSource(source);
+ source.sourceID=1;
+
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterDomainDB(domain, domain.domainID));
+ ASSERT_EQ(E_OK,pRoutingSender.addDomainLookup(domain));
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterSourceDB(source, source.sourceID));
+
+ am_sinkID_t sinkID;
+ ASSERT_EQ(E_OK, pRoutingReceiver.peekSink("AnySink", sinkID));
+
+ am_Route_s route;
+ route.sourceID = source.sourceID;
+ route.sinkID = sinkID;
+ route.route.push_back({source.sourceID, sinkID, domain.domainID, CF_GENIVI_STEREO});
+ EXPECT_CALL(pMockControlInterface, hookSystemRegisterEarlyMainConnection(_, _, _))
+ .WillOnce(Return(E_OK));
+ ASSERT_EQ(E_OK, pRoutingReceiver.registerEarlyConnection(domain.domainID, route, CS_CONNECTED));
+
+ std::vector<am::am_MainConnection_s> listMainConnections;
+ ASSERT_EQ(E_OK, pControlReceiver.getListMainConnections(listMainConnections));
+
+ ASSERT_EQ(1, listMainConnections.size());
+ EXPECT_EQ(CS_CONNECTED, listMainConnections[0].connectionState);
+}
+
+TEST_F(CAmRoutingInterfaceTest, transferConnection)
+{
+ // prepare additional domain
+ am_Domain_s domain;
+ pCF.createDomain(domain);
+ domain.name = "mock";
+ domain.busname = "mock";
+
+ am_SinkClass_s sinkclass;
+ sinkclass.sinkClassID=5;
+ sinkclass.name="sname";
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterSinkClassDB(sinkclass,sinkclass.sinkClassID));
+
+ am_Sink_s sink;
+ pCF.createSink(sink);
+ sink.sinkID = 2;
+ sink.domainID = DYNAMIC_ID_BOUNDARY;
+
+ am_SourceClass_s sourceclass;
+ sourceclass.name="sClass";
+ sourceclass.sourceClassID=5;
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterSourceClassDB(sourceclass.sourceClassID,sourceclass));
+ am_Source_s source;
+ pCF.createSource(source);
+ source.sourceID=1;
+
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterDomainDB(domain, domain.domainID));
+ ASSERT_EQ(E_OK,pRoutingSender.addDomainLookup(domain));
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterSinkDB(sink, sink.sinkID));
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterSourceDB(source,source.sourceID));
+
+ am_Connection_s con;
+ con.connectionID = 0;
+ con.sourceID = source.sourceID;
+ con.sinkID = sink.sinkID;
+ con.delay = 0;
+ con.connectionFormat = CF_GENIVI_STEREO;
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterConnectionDB(con, con.connectionID));
+
+ am_MainConnection_s mainConnection;
+ mainConnection.mainConnectionID = 0;
+ mainConnection.connectionState = CS_CONNECTED;
+ mainConnection.sinkID = sink.sinkID;
+ mainConnection.sourceID = source.sourceID;
+ mainConnection.delay = 0;
+ mainConnection.listConnectionID.push_back(con.connectionID);
+ ASSERT_EQ(E_OK, pDatabaseHandler.enterMainConnectionDB(mainConnection, mainConnection.mainConnectionID));
+
+ // execute
+ EXPECT_CALL(pMockInterface, asyncTransferConnection(_, _, _, CS_CONNECTED))
+ .WillOnce(Invoke(&pMockInterface, &MockIAmRoutingSend::defaultAsyncTransferConnection));
+ am_Handle_s handle;
+ EXPECT_EQ(E_NOT_POSSIBLE, pControlReceiver.transferConnection(handle, mainConnection.mainConnectionID, domain.domainID));
+
+ // validate that all handles have vanished
+ std::vector<am_Handle_s> listHandles;
+ ASSERT_EQ(E_OK, pControlReceiver.getListHandles(listHandles));
+ EXPECT_TRUE(listHandles.empty());
+
+ // prepare routing side to accept the transfer and try again
+ EXPECT_CALL(pMockInterface, asyncTransferConnection(_, _, _, CS_CONNECTED))
+ .WillOnce(Return(E_OK));
+ EXPECT_EQ(E_OK, pControlReceiver.transferConnection(handle, mainConnection.mainConnectionID, domain.domainID));
+
+ // validate that a proper handle is memorized
+ EXPECT_EQ(E_OK, pControlReceiver.getListHandles(listHandles));
+ EXPECT_EQ(1, listHandles.size());
+ EXPECT_EQ(H_TRANSFERCONNECTION, listHandles[0].handleType);
+
+ // inject acknowledgment
+ EXPECT_CALL(pMockControlInterface, cbAckTransferConnection(_, E_OK));
+ pRoutingReceiver.ackTransferConnection(listHandles[0], E_OK);
+
+ // validate that all handles have vanished
+ ASSERT_EQ(E_OK, pControlReceiver.getListHandles(listHandles));
+ ASSERT_TRUE(listHandles.empty());
+}
int main(int argc, char **argv)
diff --git a/AudioManagerCore/test/MockIAmControlSend.h b/AudioManagerCore/test/MockIAmControlSend.h
index 9115d00..74ccc4f 100644
--- a/AudioManagerCore/test/MockIAmControlSend.h
+++ b/AudioManagerCore/test/MockIAmControlSend.h
@@ -105,10 +105,14 @@ class MockIAmControlSend : public IAmControlSend {
void(const am_speed_t speed));
MOCK_METHOD2(hookSystemTimingInformationChanged,
void(const am_mainConnectionID_t mainConnectionID, const am_timeSync_t time));
+ MOCK_METHOD3(hookSystemRegisterEarlyMainConnection,
+ am_Error_e(am_domainID_t domainID, const am_MainConnection_s &mainConnectionData, const am_Route_s &route));
MOCK_METHOD2(cbAckConnect,
void(const am_Handle_s handle, const am_Error_e errorID));
MOCK_METHOD2(cbAckDisconnect,
void(const am_Handle_s handle, const am_Error_e errorID));
+ MOCK_METHOD2(cbAckTransferConnection,
+ void(const am_Handle_s handle, const am_Error_e errorID));
MOCK_METHOD3(cbAckCrossFade,
void(const am_Handle_s handle, const am_HotSink_e hostsink, const am_Error_e error));
MOCK_METHOD3(cbAckSetSinkVolumeChange,
diff --git a/AudioManagerCore/test/MockIAmRoutingSend.h b/AudioManagerCore/test/MockIAmRoutingSend.h
index 748e64e..21ea8f1 100644
--- a/AudioManagerCore/test/MockIAmRoutingSend.h
+++ b/AudioManagerCore/test/MockIAmRoutingSend.h
@@ -45,6 +45,12 @@ class MockIAmRoutingSend : public IAmRoutingSend {
am_Error_e(const am_Handle_s handle, const am_connectionID_t connectionID, const am_sourceID_t sourceID, const am_sinkID_t sinkID, const am_CustomAvailabilityReason_t connectionFormat));
MOCK_METHOD2(asyncDisconnect,
am_Error_e(const am_Handle_s handle, const am_connectionID_t connectionID));
+ MOCK_METHOD4(asyncTransferConnection,
+ am_Error_e(const am_Handle_s handle, am_domainID_t domainID, const std::vector<std::pair<std::string, std::string>> &route, am_ConnectionState_e state));
+ am_Error_e defaultAsyncTransferConnection(const am_Handle_s handle, am_domainID_t domainID
+ , const std::vector<std::pair<std::string, std::string>> &route
+ , am_ConnectionState_e state) // allow usage of default implementation from IAmRoutingSend
+ { return IAmRoutingSend::asyncTransferConnection(handle, domainID, route, state); }
MOCK_METHOD5(asyncSetSinkVolume,
am_Error_e(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));
MOCK_METHOD5(asyncSetSourceVolume,
diff --git a/AudioManagerUtilities/include/IAmLogger.h b/AudioManagerUtilities/include/IAmLogger.h
index 4517657..b98614b 100755
--- a/AudioManagerUtilities/include/IAmLogger.h
+++ b/AudioManagerUtilities/include/IAmLogger.h
@@ -279,7 +279,8 @@ private:
"H_CROSSFADE",
"H_SETVOLUMES",
"H_SETSINKNOTIFICATION",
- "H_SETSOURCENOTIFICATION"
+ "H_SETSOURCENOTIFICATION",
+ "H_TRANSFERCONNECTION"
}
);
}
diff --git a/docx/10_early_audio.dox b/docx/10_early_audio.dox
index bdd72f5..fc2cf0c 100644
--- a/docx/10_early_audio.dox
+++ b/docx/10_early_audio.dox
@@ -1,4 +1,4 @@
- /*
+/*
* Copyright (C) 2012, BMW AG
*
* This file is part of GENIVI Project AudioManager.
@@ -6,26 +6,116 @@
* Contributions are licensed to the GENIVI Alliance under one or more
* Contribution License Agreements.
*
- * \copyright
+ * @copyright
* This Source Code Form is subject to the terms of the
* Mozilla Public License, 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/.
*
- * \\author Christian Linke (christian.linke@bmw.de)
+ * @authors Christian Linke, 2012 <christian.linke@bmw.de>,\n
+ * Martin Koch, 2020 <mkoch@martin.koch@ese.de>
*
*/
- /*!
-\page early Early Audio
-\section req The Requirement
-The requirement reviews showed that one very important aspect of managing audio within GENIVI is the early/late phase where the Linux part of the system is
-not available. This feature is addressed via special domains: EarlyDomains. These domains are acting "unmanaged" - meaning without the interaction with the
-AudioManager - providing a simpler set of features until the AudioManager is up and running. When the Linux system is then fully operable a handover is done
-from the EarlyDomains towards the AudioManager. In order to be able to operate without the Linux up and running, the EarlyDomains must be implemented on a
-second fast-boot or always-on controller, e.g. the so called vehicle processor.\n
-\section earlys Early Startup
+
+ /**************************************************************************//**
+@page early Early Audio
+
+@section req The Requirement
+The requirement reviews showed that one very important aspect of managing audio within GENIVI is the
+early/late phase where the AudioManager daemon (possibly along with the entire Linux part of the system)
+is not available. This feature is addressed via special domains: EarlyDomains.
+In normal operation these domains are slave to the routing side of the audio-manager
+daemon and optionally also interact with its command side. Here they are acting "unmanaged",
+that is without the control through the AudioManager, and usually provide only a limited set
+of features until the AudioManager is up and running. At minimum, they need to be capable of
+establishing the audio or video connection autonomously and start streaming their information.
+Scenarios requiring such capabilities comprise:
+ - System alerts
+ + Door open
+ + Seat belt missing
+ + Engine faults
+ - Parking assistant warnings
+ + Rear view camera screen
+ + Beeps if distance goes below limit
+
+In order to handle above situations, such applications at least need to be prioritized during startup.
+Furthermore, if they need to be operational before the entire Linux is available, the EarlyDomains can
+be implemented on a second fast-boot or always-on controller, e.g. the so called vehicle processor.
+
+Later, once the Linux system is fully operable and the audio-manager process is also started,
+a hand-over is done from the EarlyDomains towards the AudioManager so the latter can synchronize
+its internal state.
+
+Similarly, on system shutdown, the audiomanager process may already be stopped and unloaded
+while above information is still required to be presented to the user.
+
+@section earlys Early Startup
This picture shows the principle of the early startup:
-\image html early.png
-\section late Late Rundown
+@image html early.png
+
+Once a routing adapter is connected to the audio-manager and has received the setRoutingReady()
+signal, it registers (announces) the topology elements (sources, sinks and gateways) accessible
+through it to the daemon, which assigns numerical IDs to each of them. Details are given along
+with the registration, such as
+ - element name
+ - domain membership
+ - class (grouping category) membership
+ - supported connection formats
+ - availability
+ - streaming state (ON/OFF, sources only)
+ - audible volume
+ - additional element-specific properties
+
+For announcing a pre-established audio connection above information is not sufficient,
+as no connection information can be extracted from it. Here the function @ref am::IAmRoutingReceive::registerEarlyConnection
+"registerEarlyConnection()" is used from the routing side. The daemon shall then transparently
+forward this notification to the controller, which in turn registers the new connection to
+the daemons database.
+
+> A special case is given if the connection spans multiple domains. Although controlling the entire
+> route during startup and knowing that all involved elements do exist and are up and running,
+> the routing adapter cannot reliably assume that their registration to the audio-manager is
+> already completed. This can be overcome by using the existing functions @ref am::IAmRoutingReceive::peekSource "peekSource()"
+> and @ref am::IAmRoutingReceive::peekSink "peekSink()", which will return a valid numeric
+> identifier for the requested element specified with its unique name only. These functions
+> determine the IDs from either
+> - assigned IDs for actually registered elements
+> - or statically configured data
+> - or they reserve an ID which will be assigned to the element during later registration
+>
+> So also in this case the proper hand-over of early connections should be possible.
+
+Below sequence chart illustrates the details:
+@image html early_announcement.png
+
+Once the connection is announced, the controller takes over full responsibility, including
+ - notifying the command side on its existence through function
+ @ref am::IAmCommandSend::cbNewMainConnection "cbNewMainConnection()"
+ - suspending or disconnecting it on command-side request according to business logic
+
+Thus a matching function __unregisterEarlyConnection()__ triggered by the routing adapter shall
+__not__ be necessary.
+
+@section late Late Rundown
+
+Connections which are still active (in state CS_CONNECTED or CS_SUSPENDED) and shall be kept
+alive beyond termination of the audio-manager process need to be handed over to the routing
+adapter before the daemon process terminates.
+
This picture shows the principle of the late rundown:
-\image html late.png
+@image html late.png
+
+Selectable for handover are all domains marked with status flag DS_INDEPENDENT_RUNDOWN indicating
+their capability of taking care of late connections autonomously. They shall be considered for the
+hand-over offering through asynchronous, non-blocking function @ref am::IAmControlReceive::transferConnection
+"transferConnection()". Below sequence chart shows the usage of this function and its forwarding
+and acknowledging siblings:
+
+@image html early_transfer.png
+
+After positive acknowledgment through the routing adapter the AM is no longer responsible
+for the connection and its elements, so the subsequent deregisterSource/Sink/Domain()
+requests do not harm.
+
+@note In case the audio-manager is restarted and said connection still exists under responsibility
+ of a routing adapter, above function registerEarlyConnection() can be invoked again.
*/ \ No newline at end of file
diff --git a/docx/images/early_announcement.png b/docx/images/early_announcement.png
new file mode 100644
index 0000000..b955f1f
--- /dev/null
+++ b/docx/images/early_announcement.png
Binary files differ
diff --git a/docx/images/early_transfer.png b/docx/images/early_transfer.png
new file mode 100644
index 0000000..f5ddb36
--- /dev/null
+++ b/docx/images/early_transfer.png
Binary files differ
diff --git a/include/IAmControl.h b/include/IAmControl.h
index 32008a0..0dd46e5 100644
--- a/include/IAmControl.h
+++ b/include/IAmControl.h
@@ -439,6 +439,12 @@ public:
*/
virtual am_Error_e getCrossfaderInfoDB(const am_crossfaderID_t crossfaderID, am_Crossfader_s& crossfaderData) const =0;
/**
+ * returns details of a connection, including involved sources and sinks
+ * @return E_OK on success, E_DATABASE_ERROR on error, E_NON_EXISTENT if
+ * crossfader was not found
+ */
+ virtual am_Error_e getConnectionInfoDB(const am_connectionID_t connectionID, am_Connection_s& connectionData) const =0;
+ /**
* returns sources and the sink of a crossfader
* @return E_OK on success, E_DATABASE_ERROR on error, E_NON_EXISTENT if
* crossfader was not found
@@ -554,6 +560,24 @@ public:
* confirmRoutingRundown.
*/
virtual void setRoutingRundown() =0;
+
+ /**
+ * Hand-over to routing-side application any connection meant to survive AM shutdown
+ * (see page @ref early)
+ *
+ * @param handle: composite identifier used to map the response
+ * @param domainID: target domain which shall take over
+ * @param mainConnectionID: subject of this request
+ *
+ * @return E_OK if command was forwarded to routing adapter successfully,
+ * E_COMMUNICATION or other meaningful value otherwise
+ *
+ * @note Success of the responsibility transfer itself is acknowledged through corresponding
+ * function @ref am::IAmControlSend::cbAckTransferConnection "cbAckTransferConnection()".
+ */
+ virtual am_Error_e transferConnection(am_Handle_s &handle
+ , am_mainConnectionID_t mainConnectionID, am_domainID_t domainID) = 0;
+
/**
* acknowledges the setControllerReady call.
*/
@@ -858,6 +882,25 @@ public:
* @return E_OK on success, E_UNKNOWN on error, E_NON_EXISTENT if not found
*/
virtual am_Error_e hookSystemDeregisterCrossfader(const am_crossfaderID_t crossfaderID) =0;
+
+ /**
+ * Support announcement of audio connections already active at AM startup
+ *
+ * @param domainID: home domain announcing this early connection
+ * @param mainConnectionData: details of main connection
+ * @param route: route details as requested from routing side
+ *
+ * @return success indicator. Controller should use E_OK on success,
+ * E_ALREADY_EXISTS or E_NO_CHANGE if given connection is already registered,
+ * E_DATABASE_ERROR if any of the listed sources or sinks does not exist in the data base,
+ * E_NOT_POSSIBLE if feature is not supported by the controller
+ */
+ virtual am_Error_e hookSystemRegisterEarlyMainConnection(am_domainID_t domainID
+ , const am_MainConnection_s &mainConnectionData, const am_Route_s &route)
+ {
+ return E_NOT_POSSIBLE; // empty default implementation
+ }
+
/**
* volumeticks. therse are used to indicate volumechanges during a ramp
*/
@@ -905,6 +948,19 @@ public:
* ack for disconnect
*/
virtual void cbAckDisconnect(const am_Handle_s handle, const am_Error_e errorID) =0;
+
+ /**
+ * Hand-over acknowledgment of connections surviving shutdown of the AM,
+ * forwarded from routing side (see @ref IAmRoutingReceive::ackTransferConnection)
+ *
+ * @param handle: composite identifier mirrored from request
+ * @param errorID: success indicator as obtained from routing side application
+ */
+ virtual void cbAckTransferConnection(const am_Handle_s /* handle */, const am_Error_e /* errorID */)
+ {
+ // empty default implementation
+ }
+
/**
* ack for crossfading
*/
diff --git a/include/IAmRouting.h b/include/IAmRouting.h
index 1acaac6..b86e782 100644
--- a/include/IAmRouting.h
+++ b/include/IAmRouting.h
@@ -139,6 +139,34 @@ public:
* @return E_OK on succes, E_NON_EXISTENT if not found E_UNKOWN on error
*/
virtual am_Error_e deregisterDomain(const am_domainID_t domainID) =0;
+
+ /**
+ * Support announcement of audio connections already active at AM startup
+ *
+ * @param domainID: home domain announcing this early connection
+ * @param route: list of connection segments
+ * @param state: either stable CS_CONNECTED, CS_DISCONNECTED, CS_SUSPENDED
+ * or transient CS_CONNECTING, CS_DISCONNECTING
+ *
+ * @return success indicator as obtained from the controller
+ *
+ * @note If the connection is announced with one of the transient states
+ * CS_CONNECTING or CS_DISCONNECTING, a secondary registerEarlyConnection()
+ * call is expected once a stable state is reached
+ */
+ virtual am_Error_e registerEarlyConnection(am_domainID_t domainID, const am_Route_s &route
+ , am_ConnectionState_e state) = 0;
+
+ /**
+ * Notify hand-over acknowledgment of connections surviving shutdown of the AM
+ *
+ * @param handle: composite identifier used in the request
+ * @param errorID: success indicator as obtained from the routing-side application,
+ * e.g. E_OK if the application assumes full responsibility,
+ * any meaningful error condition otherwise
+ */
+ virtual void ackTransferConnection(const am_Handle_s handle, const am_Error_e errorID) = 0;
+
/**
* registers a converter. @return E_OK on succes, E_ALREADY_EXISTENT if already
* registered E_UNKOWN on error
@@ -385,6 +413,26 @@ public:
* or be ready again.
*/
virtual void setRoutingRundown(const uint16_t handle) =0;
+
+ /**
+ * Forward hand-over of a connection meant to survive AM shutdown
+ * in routing-side application
+ *
+ * @param handle: composite identifier used to map the response
+ * @param domainID: target domain for this offering
+ * @param route: names of involved sources and sinks including intermediate gateways
+ * @param state: either stable CS_CONNECTED, CS_DISCONNECTED, CS_SUSPENDED
+ * or transient CS_CONNECTING, CS_DISCONNECTING
+ *
+ * @return success indicator as obtained from the plugins, e.g E_OK or E_COMMUNICATION
+ */
+ virtual am_Error_e asyncTransferConnection(const am_Handle_s handle, am_domainID_t domainID
+ , const std::vector<std::pair<std::string, std::string>> &route
+ , am_ConnectionState_e state)
+ {
+ return E_NOT_POSSIBLE; // default response if not supported by the plugin
+ }
+
/**
* aborts an asynchronous action.
* @return E_OK on success, E_UNKNOWN on error, E_NON_EXISTENT if handle was not
diff --git a/include/audiomanagertypes.h b/include/audiomanagertypes.h
index 99e01e2..469fcd5 100755
--- a/include/audiomanagertypes.h
+++ b/include/audiomanagertypes.h
@@ -451,6 +451,7 @@ enum am_Handle_e
H_SETVOLUMES = 11,
H_SETSINKNOTIFICATION = 12,
H_SETSOURCENOTIFICATION = 13,
+ H_TRANSFERCONNECTION = 14,
H_MAX
};