CAF: Working With Caf Call Legs

From TBwiki
(Difference between revisions)
Jump to: navigation, search
(Keeping re-synchronized legs that are part of a link or a mixer)
(Minor example code fix)
 
(29 intermediate revisions by 2 users not shown)
Line 18: Line 18:
  
 
=== CAF Mixer (''CTBCAFMixer''): ===
 
=== CAF Mixer (''CTBCAFMixer''): ===
CAF Call Legs (''CTBCAFCallLeg'') have a built-in function to join with another leg, making audio flow from one leg to the other directly.
+
CAF Call Legs (''CTBCAFCallLeg'') have a built-in function to join with another leg, making audio flow from one leg to another leg (or multiple destination legs).
  
However, when it is required to mix audio from multiple call legs or sources (building conferences for example), then the ''CTBCAFMixer'' object is used.
+
However, one leg can be used as a Join destination only once. And thus, when it is required for one leg to "hear" from multiple call legs or sources (conferences for example), then the ''CTBCAFMixer'' object is used.
  
 
The ''CTBCAFMixer'' (based on CTBCMCMixer base class) is the representation of a DSP resource used to mix audio from different sources (call legs, stream server, or other mixers).
 
The ''CTBCAFMixer'' (based on CTBCMCMixer base class) is the representation of a DSP resource used to mix audio from different sources (call legs, stream server, or other mixers).
Line 144: Line 144:
 
   
 
   
 
  // Create an audio mixer
 
  // Create an audio mixer
  pCallFlow->MixerCreate( ptrMixerAttribute );
+
  pCallFlow->AddMixer( ptrMixerAttribute );
 
   
 
   
 
  // Initialize the call (this actually creates the call legs)
 
  // Initialize the call (this actually creates the call legs)
Line 156: Line 156:
 
   CTBCMC_EVENT_ATTRIBUTE  Event;
 
   CTBCMC_EVENT_ATTRIBUTE  Event;
 
   Event.AddToneString( "telcofax/eof" );
 
   Event.AddToneString( "telcofax/eof" );
   GetIncomingActiveLeg()->StartEventCollection( in_EventMask, NULL, TBX_FALSE /* Don't suppress tones */ );
+
   GetIncomingActiveLeg()->StartEventCollection( Event, NULL, TBX_FALSE /* Don't suppress tones */ );
 
   
 
   
 
   // Call base class to forward event to next behavior
 
   // Call base class to forward event to next behavior
Line 199: Line 199:
 
  ptrOutgoingLegAttribute->CopyLegAttributeFrom( *(ptrIncomingLegAttribute.Get()) );
 
  ptrOutgoingLegAttribute->CopyLegAttributeFrom( *(ptrIncomingLegAttribute.Get()) );
 
* Smart pointer to empty protocol attributes that Behaviors can fill, and that will be used when creating the outgoing call (SIP invite, ISUP IAM, ISDN Setup, etc.)
 
* Smart pointer to empty protocol attributes that Behaviors can fill, and that will be used when creating the outgoing call (SIP invite, ISUP IAM, ISDN Setup, etc.)
 +
ptrOutgoingLegProtocolAttributes = tbnew CTBCMC_PROTOCOL_ATTRIBUTE();
  
 
==== Mixer attributes ====
 
==== Mixer attributes ====
Line 271: Line 272:
 
    
 
    
 
*'''Important note:''' Behaviors that implement that function MUST, as the LAST line of their function, call the base class' '''CTBCAFCallBehavior::OnMixerFreed()'''. Not calling base class' function will cause the mixer and call to leak. Calling that before the last line of the function will cause crash, as the Behavior object could destroy itself within that function upon destruction of the last leg/mixer of the call.
 
*'''Important note:''' Behaviors that implement that function MUST, as the LAST line of their function, call the base class' '''CTBCAFCallBehavior::OnMixerFreed()'''. Not calling base class' function will cause the mixer and call to leak. Calling that before the last line of the function will cause crash, as the Behavior object could destroy itself within that function upon destruction of the last leg/mixer of the call.
 +
 +
== Joining call legs ==
 +
The call legs can be "joined" together, meaning that audio from a leg is sent toward the other leg.
 +
 +
Call legs can be joined full-duplex, or half-duplex.
 +
 +
=== Join/Unjoin API ===
 +
Joining call legs is made by using the call leg's function ''Join()''
 +
TBX_RESULT CTBCMCLeg::Join
 +
(
 +
  CTBCMCLeg*            in_pDestinationLeg,  // Other leg to join with
 +
  PITBCMCErrorListener  in_pErrorListener,  // Error listener for this Join operation (generally not used)
 +
  TBX_BOOL              in_fWarnIfToneDetectionEnabled, // Prints a warning in toolpack_engine's log if tone detection still enabled upon join
 +
  CTBCMC_JOIN_ATTRIBUTE* in_pJoinAttribute    // Attributes for joining -> Half or full-duplex
 +
);
 +
 +
Upon completion of the join, call flow's function ''OnLegJoinDone()'' is called:
 +
  TBX_RESULT CTBCAFCallFlow::OnLegJoinDone
 +
  (
 +
    IN    PCTBCAFCallLeg              in_pSrcCallLeg,
 +
    IN    PCTBCAFCallLeg              in_pDstCallLeg,
 +
    IN    CTBCMC_JOIN_ATTRIBUTE*      in_pJoinAttribute
 +
  )
 +
 +
Unjoining call legs is made by using the call leg's function ''Unjoin()'':
 +
TBX_RESULT CTBCMCLeg::Unjoin
 +
(
 +
  CTBCMCLeg*            in_pDestinationLeg,  // Other leg to join with
 +
  PITBCMCErrorListener  in_pErrorListener  // Error listener for this unjoin operation (generally not used)
 +
);
 +
 +
Unjoining is also implicitly initiated when one of the two legs is terminating.
 +
 +
Upon completion of the unjoin, call flow's function ''OnLegUnjoinDone()'' is called:
 +
  TBX_RESULT CTBCAFCallFlow::OnLegUnjoinDone
 +
  (
 +
    IN    PCTBCAFCallLeg              in_pSrcCallLeg,
 +
    IN    PCTBCAFCallLeg              in_pDstCallLeg,
 +
    IN    CTBCMC_JOIN_ATTRIBUTE*      in_pJoinAttribute
 +
  )
 +
 +
 +
 +
=== Basic rules ===
 +
'''Rules''':
 +
* Each call leg can receive audio from only one source at the time: Another leg, or a mixer.
 +
* A call leg can send it's audio to any number of destinations (audio "forking"), obviously as long as these destinations don't already receive audio from other source.
 +
* A Join operation on a pair of call legs already joined together will "override" the previous join attributes
 +
** Convert half-duplex to full-duplex connection
 +
** Convert full-duplex to half-duplex connection
 +
** Invert direction of a half-duplex connection
 +
 +
'''Corollaries''':
 +
* To receive audio from more than one source, a leg must be connected to a mixer
 +
* Doing A->Join(B,hd) then B->Join(A,hd) won't make a full-duplex connection... it will INVERT the direction of the join
 +
* With TMedia, audio "forking" (one leg sending to multiple other legs) does not require DSPs. Only audio mixing requires DSPs.
 +
* Mixers can be joined to mixers, if complex scenarios require it
 +
 +
=== Scenario: Full-duplex join ===
 +
''LegA <--> LegB''<br/><br/>
 +
When joined full-duplex, each leg receives the audio from the other leg.
 +
 +
* pLegA->Join( pLegB );
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [fd] )
 +
* pLegA->Unjoin( pLegB );
 +
** ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [fd] )
 +
 +
=== Scenario: Half-duplex join ===
 +
''LegA ---> LegB''<br/><br/>
 +
When joined half-duplex, each source leg sends audio to destination leg, but not the opposite.
 +
 +
* CTBCMC_JOIN_ATTRIBUTE  HdJoin;
 +
* HdJoin.IsFullDuplex() = TBX_FALSE;
 +
* pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin );
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
 +
* pLegA->Unjoin( pLegB );
 +
** ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [hd] )
 +
 +
=== Scenario: Inverting direction of half-duplex join ===
 +
''LegA ---> LegB''  ===>  ''LegB ---> LegA''<br/><br/>
 +
Joining A--->B then B--->A is not creating a full-duplex connection, it's actually inverting the direction of the connection.
 +
* pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin );
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
 +
* pLegB->Join( pLegA, NULL, TBX_FALSE, &HdJoin );
 +
** ==> CallFlow::OnLegJoinDone( pLegB, pLegA, [hd] )  // This confirms that join direction has been inverted
 +
* pLegB->Unjoin( pLegA );
 +
** ==> CallFlow::OnLegUnjoinDone( pLegB, pLegA, [hd] )
 +
 +
=== Scenario: Changing from half-duplex to full-duplex ===
 +
''LegA ---> LegB''  ===>  ''LegA <---> LegB''<br/><br/>
 +
A ''Join'' operation on two legs already joined together will override the join attributes.
 +
* pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin);
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
 +
* pLegA->Join( pLegB );  // NULL attributes default to full-duplex
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [fd] )  // This confirms that join is now full-duplex
 +
* pLegA->Unjoin( pLegB );
 +
** ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [hd] )
 +
 +
=== Scenario: Audio forking ===
 +
LegA <---> LegB
 +
      ---> LegC
 +
      ---> LegD
 +
LegB  ---> LegE
 +
 +
There is no limit on audio forking, as long as all legs receive audio from at most one source.
 +
* pLegA->Join( pLegB );
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [fd] )
 +
* pLegA->Join( pLegC, NULL, TBX_FALSE, &HdJoin );
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegC, [hd] )
 +
* pLegA->Join( pLegD, NULL, TBX_FALSE, &HdJoin );
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegD, [hd] )
 +
* pLegB->Join( pLegE, NULL, TBX_FALSE, &HdJoin );
 +
** ==> CallFlow::OnLegJoinDone( pLegB, pLegE, [hd] )
 +
* pLegA->TerminateCall()
 +
** ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [fd] )
 +
** ==> CallFlow::OnLegUnjoinDone( pLegA, pLegC, [hd] )
 +
** ==> CallFlow::OnLegUnjoinDone( pLegA, pLegD, [hd] )
 +
** ==> CallFlow::OnCallLegTerminated( pLegA )
 +
 +
=== Scenario: Illegal join ===
 +
LegA ---> LegB
 +
LegB <--> LegC  *** Illegal
 +
 +
A join operation that would make a leg receive audio from more than one source would be illegal.
 +
* pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin );
 +
** ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
 +
* pLegB->Join( pLegC );
 +
** ==> Error!
 +
 +
== Joining with mixers ==
 +
Joining with audio mixers is similar to joining legs.  Call legs or mixers can both be joined to call legs or mixers.
 +
 +
Differences when joining with mixers:
 +
* We use the terms "speaker" or "listener", instead of "half-duplex" or "full-duplex".
 +
* Unlike a call leg, a mixer can be destination from multiple sources (that's actually the purpose of a mixer!)
 +
 +
For more information about mixers, please refer to the following pages:
 +
* [[AudioMixers]]
 +
* [[CAF:_Working_With_Cmc_Mixers]]
 +
 +
 +
=== Mixer Join/Unjoin API ===
 +
Joining with mixers is made by using the mixer's functions ''MixerJoin()''
 +
TBX_RESULT CTBCMCMixer::MixerJoin
 +
(
 +
  IN    CTBCMCLeg *                        in_pLeg,
 +
  IN    PCTBCMC_MIXER_LEG_JOIN_ATTRIBUTE    in_pJoinAttribute = NULL,
 +
  IN    PITBCMCErrorListener                in_pErrorListener = NULL
 +
);
 +
TBX_RESULT CTBCMCMixer::MixerJoin
 +
(
 +
  IN    CTBCMCMixer*                        in_pOtherMixer,
 +
  IN    PCTBCMC_MIXER_MIXER_JOIN_ATTRIBUTE  in_pJoinAttribute = NULL,
 +
  IN    PITBCMCErrorListener                in_pErrorListener = NULL
 +
);
 +
 +
Upon completion of the join, call flow's function ''OnMixerJoinDone()'' is called:
 +
  TBX_RESULT CTBCAFCallFlow::OnMixerJoinDone
 +
  (
 +
    IN    PCTBCAFMixer                in_pMixer
 +
    IN    PCTBCAFCallLeg              in_pCallLeg
 +
  )
 +
  TBX_RESULT CTBCAFCallFlow::OnMixerJoinDone
 +
  (
 +
    IN    PCTBCAFMixer                in_pMixer
 +
    IN    PCTBCAFMixer                in_pOtherMixer
 +
  )
 +
 +
 +
Unjoining mixers is made by using the mixer's function ''MixerUnjoin()'':
 +
TBX_RESULT CTBCMCMixer::MixerUnjoin
 +
(
 +
  IN    CTBCMCLeg *                      in_pLeg,
 +
  IN    PITBCMCErrorListener              in_pErrorListener
 +
);
 +
 +
Unjoining is also implicitly initiated when the mixer or call leg is terminated.
 +
 +
Upon completion of the unjoin, call flow's function ''OnMixerUnjoinDone()'' is called:
 +
  TBX_RESULT CTBCAFCallFlow::OnMixerUnjoinDone
 +
  (
 +
    IN    PCTBCAFMixer                in_pMixer
 +
    IN    PCTBCAFCallLeg              in_pCallLeg
 +
  )
 +
  TBX_RESULT CTBCAFCallFlow::OnMixerUnjoinDone
 +
  (
 +
    IN    PCTBCAFMixer                in_pMixer
 +
    IN    PCTBCAFMixer                in_pOtherMixer
 +
  )
  
 
== Re-synchronizing with [[Toolpack_Application:toolpack_engine|toolpack_engine]] ==
 
== Re-synchronizing with [[Toolpack_Application:toolpack_engine|toolpack_engine]] ==
 +
Note: You can first read the overview of re-synchronization mechanism here: [[CAF:_Call_Legs_Resync]]
 +
 
There are a couple of cases where a CAF application needs to re-synchronize with [[Toolpack_Application:toolpack_engine|toolpack_engine]]:
 
There are a couple of cases where a CAF application needs to re-synchronize with [[Toolpack_Application:toolpack_engine|toolpack_engine]]:
 
* The CAF application was restarted
 
* The CAF application was restarted
Line 302: Line 494:
 
=> Communication re-established with [[Toolpack_Application:toolpack_engine|toolpack_engine]]
 
=> Communication re-established with [[Toolpack_Application:toolpack_engine|toolpack_engine]]
 
* '''OnCallLegSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a call leg that does not exist in the CAF application.
 
* '''OnCallLegSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a call leg that does not exist in the CAF application.
* '''OnLinkSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a link between two call legs (legs are joined, audio is flowing from one to the other directly).
 
 
* '''OnMixerSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a mixer that does not exist in the CAF application.
 
* '''OnMixerSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a mixer that does not exist in the CAF application.
 +
* '''OnLinkSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a link between two call legs (legs are joined, audio is flowing from one to the other directly).
 
* '''OnMixerLinkSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a link between a leg and a mixer, or between two mixers.
 
* '''OnMixerLinkSync()''' (CAF application callback): Indicates that the [[Toolpack_Application:toolpack_engine|toolpack_engine]] knows about a link between a leg and a mixer, or between two mixers.
 
* '''OnCmcLibReady()''' (CAF application callback): Indicates that communication with [[Toolpack_Application:toolpack_engine|toolpack_engine]] has been restored, and that all legs, links and mixers have been re-synchronized
 
* '''OnCmcLibReady()''' (CAF application callback): Indicates that communication with [[Toolpack_Application:toolpack_engine|toolpack_engine]] has been restored, and that all legs, links and mixers have been re-synchronized
Line 327: Line 519:
  
 
==== Keeping re-synchronized legs that are part of a link or a mixer ====
 
==== Keeping re-synchronized legs that are part of a link or a mixer ====
The helper class ''CTBCAFCallLegResync'' is provided to help creating CTBCAFCallLeg objects for re-synchronized legs, and later retrieve them and attach them to a CTBCAFCallFlow parent, upon '''OnLinkSync()''' or '''OnMixerSync()''':
+
The helper classes ''CTBCAFCallLegResync'' and ''CTBCAFMixerResync'' are provided to help creating CTBCAFCallLeg and CTBCAFMixer objects for re-synchronized legs and mixers, and later retrieve them and attach them to a CTBCAFCallFlow parent, upon '''OnLinkSync()''' or '''OnMixerLinkSync()''':
  
Upon '''OnLegSync()''', legs should be allocated inside the class ''CTBCAFCallLegResync'' using function '''AllocateCallLeg()'''. Legs don't yet need to be attached to a parent CTBCAFCallFlow, that's normally done upon '''OnLinkSync()''' or '''OnMixerSync()'''.
+
Upon '''OnLegSync()''':
 +
* Call function '''AllocateCallLeg()''' of object ''CTBCAFCallLegResync'', to allocate a temporary call leg object (that will be used later upon '''OnLinkSync''').
 
   
 
   
Upon '''OnLinkSync()''', the LegId of the two joined call legs are provided. Both legs must be retrieved from the ''CTBCAFCallLegResync'' (using functions '''FindCallLeg()''' and '''RemoveCallLeg()'''), then added to a CTBCAFCallFlow object that will resume the call flow of these two joined call legs.
+
Upon '''OnMixerSync()''':
 
+
* Call function '''OnMixerSync''' of object ''CTBCAFMixerResync'', to allocate temporary mixer object (that will be used later upon '''OnMixerLinkSync''').
Upon '''OnMixerSync()''', the LegId of all legs in that mixer (along with Join Attributes) are provided. A new CTBCAFMixer object must be allocated, and all call legs from that mixer must be retrieved from the ''CTBCAFCallLegResync'' object (using functions '''FindCallLeg()''' and '''RemoveCallLeg()'''). The allocated mixer and found legs must then be assigned to a CTBCAFCallFlow object that will resume the call flow.
+
 
   
 
   
Finally, upon '''OnCmcLibReady()''', the application can destroy the CTBCAFCallLegResync object, which will take care of refusing (dropping) all call legs that it still references (that have not been removed by '''CTBCAFCallLegResync::RemoveCallLeg''')
+
Upon '''OnLinkSync()''':
 +
* This function provides the LegId of the two joined call legs.
 +
* Retrieve both call legs by calling function '''FindCallLeg()''' of object ''CTBCAFCallLegResync''.
 +
* Call function '''LinkSyncBindToExistingCallFlow''' of object ''CTBCAFCallLegResync''. This function will see if one of these two joined call legs is already assigned to an existing call flow, and will bind the other leg to the same call flow if appropriate
 +
* If function '''LinkSyncBindToExistingCallFlow''' has not found an existing call flow to bind legs to, create (allocate) a new call flow that owns these two call legs.
 +
* Call function'''MarkCallLegUsed''' of object ''CTBCAFCallLegResync'' to indicate that these legs are in use and must not be destroyed upon termination of the object ''CTBCAFCallLegResync''.
 +
 
 +
Upon '''OnMixerLinkSync()''':
 +
* This function provides the LegId or MixerId of the two joined entities (Mixer/Leg, or Mixer/Mixer).
 +
* Call function '''OnMixerLinkSync''' of object ''CTBCAFMixerResync''.
 +
* This function will retrieve the existing call flow that already owns the source or destination of this mixer link, and make sure to bind both source and destination of this link to that call flow.
 +
 
 +
Finally, upon '''OnCmcLibReady()''', the application can destroy the ''CTBCAFCallLegResync'' and ''CTBCAFMixerResync'' objects, which will take care of refusing (dropping) all call legs and mixers that have not been assigned to a call flow.
  
 
Simplified example code:
 
Simplified example code:
Line 362: Line 566:
 
     );
 
     );
 
  }
 
  }
 +
 +
TBX_VOID CTBMyApp::OnMixerSync
 +
(
 +
  IN    TBCMC_MIXER_ID                      in_MixerId,
 +
  IN CTBCMC_MIXER_ATTRIBUTE &       in_MixerAttribute
 +
)
 +
{
 +
    // Upon first re-synchronized mixer, allocate utility class to contain re-synchronized mixers
 +
    if( !mpMixerResync)
 +
    {
 +
        mpMixerResync= new TBCAF::CTBCAFCallLegResync( MY_APP_MAX_MIXERS );
 +
    }
 
   
 
   
 +
    // Re-create a CTBCAFMixer object for this re-synchronized leg
 +
    mpMixerResync->AllocateMixer
 +
    (
 +
        NULL,                            // Not yet bound to a parent call flow
 +
        in_MixerId,                      // Use the mixer Id of this re-synchronized mixer
 +
        in_MixerAttribute,              // Store the attributes of this re-synchronized mixer
 +
        this                            // Until it has a parent call flow, set ourself as free listener
 +
    );
 +
}
 +
 
  TBX_VOID CTBMyApp::OnLinkSync
 
  TBX_VOID CTBMyApp::OnLinkSync
 
  (
 
  (
Line 386: Line 612:
 
     {
 
     {
 
         // Need to allocate a new call flow, using the constructor that also attaches two legs to the call flow
 
         // Need to allocate a new call flow, using the constructor that also attaches two legs to the call flow
         pMyCallFlow = tbnew CTBCAFMyCallFlow
+
         pMyCallFlow = tbnew CTBCAFMyCallFlow( this );
        (
+
            this,                // Set current application as free listener for my call flow
+
            in_LinkId,          // Id of the link between these two joined legs
+
            in_JoinAttribute,    // Attributes of the link between these two joined legs
+
            ptrSrcLeg,
+
            ptrDstLeg
+
        );
+
 
      
 
      
 
         // Re-attach behaviors to this call flow
 
         // Re-attach behaviors to this call flow
Line 401: Line 620:
 
         // Initialize the newly created call flow, to be able to start working with it
 
         // Initialize the newly created call flow, to be able to start working with it
 
         pMyCallFlow->InitCall( &pMyCallFlow );
 
         pMyCallFlow->InitCall( &pMyCallFlow );
 +
 +
        // Bind the legs to the call flow
 +
        pMyCallFlow->BindCallLeg( ptrSrcLeg );
 +
        pMyCallFlow->BindCallLeg( ptrDstLeg );
 
     }
 
     }
 
      
 
      
Line 408: Line 631:
 
     mpCallLegResync->MarkCallLegUsed( in_DstLegId );
 
     mpCallLegResync->MarkCallLegUsed( in_DstLegId );
 
  }
 
  }
+
 
TBX_VOID CTBMyApp::OnMixerSync
+
(
+
  IN    TBCMC_MIXER_ID                      in_MixerId,
+
  IN CTBCMC_MIXER_ATTRIBUTE &       in_MixerAttribute
+
)
+
{
+
    // Upon first re-synchronized mixer, allocate utility class to contain re-synchronized mixers
+
    if( !mpMixerResync)
+
    {
+
        mpMixerResync= new TBCAF::CTBCAFCallLegResync( MY_APP_MAX_MIXERS );
+
    }
+
+
    // Re-create a CTBCAFMixer object for this re-synchronized leg
+
    mpMixerResync->AllocateMixer
+
    (
+
        NULL,                            // Not yet bound to a parent call flow
+
        in_MixerId,                      // Use the mixer Id of this re-synchronized mixer
+
        in_MixerAttribute,              // Store the attributes of this re-synchronized mixer
+
        this                            // Until it has a parent call flow, set ourself as free listener
+
    );
+
}
+
+
 
  TBX_VOID CTBMyApp::OnMixerLinkSync
 
  TBX_VOID CTBMyApp::OnMixerLinkSync
 
  (
 
  (
Line 439: Line 640:
 
  )
 
  )
 
  {
 
  {
    PTRCTBCAFMixer    ptrMixer;
+
     // Use the mixer re-sync helper to automatically create mixer and bind to an existing call flow
+
     mpMixerResync->OnMixerLinkSync( in_MixerId, in_EntityId, in_pLegJoinAttribute, in_pMixerJoinAttribute );
     // Find the mixer
+
  }
     ptrMixer = mpMixerResync->FindMixer( in_MixerId );
+
 
   
+
TBX_VOID CTBMyApp::OnCmcLibReady()
    if ( in_pLegJoinAttribute ) // We're re-synchornizing a link between mixer and leg
+
{
 +
    // Free all call legs that have not been bound to a call flow during the re-sync process
 +
    if( mpCallLegResync )
 
     {
 
     {
         // Find leg joined to this mixer
+
         // Deleting mpCallLegResync will automatically reject legs that have not been bound to a call flow
         PTRCTBCAFCallLeg ptrOtherLeg = mpCallLegResync->FindCallLeg( in_EntityId );
+
         delete mpCallLegResync;
 +
        mpCallLegResync = NULL;
 +
    }
 
   
 
   
        // Try to bind the mixer and the entity to an already existing call flow
+
    // Free all mixers that have not been bound to a call flow during the re-sync process
        PITBCAFCallFlow pMyCallFlow = mpMixerResync->LinkSyncBindToExistingCallFlow( ptrMixer, ptrOtherLeg, in_pLegJoinAttribute );
+
     if( mpMixerResync)
        if( !pMyCallFlow )
+
        {
+
            // Need to allocate a new call flow
+
            pMyCallFlow = tbnew CTBCAFMyCallFlow( this );
+
     
+
            // Re-attach behaviors to this call flow
+
            pMyCallFlow = tbnew CTBCAFMyFirstBehavior( pMyCallFlow, ... );
+
            pMyCallFlow = tbnew CTBCAFMySecondBehavior( pMyCallFlow, ... );
+
+
            // Initialize the newly created call flow, to be able to start working with it
+
            pMyCallFlow->InitCall( &pMyCallFlow );
+
+
            // Bind the mixer and leg to the call flow
+
            pMyCallFlow ->BindMixer( ptrMixer );
+
            pMyCallFlow ->BindCallLeg( ptrOtherLeg );
+
        }
+
+
        // Mark the mixer and leg as 'in use' in the resync pool, to avoid them being freed upon destruction of mpMixerResync
+
        mpMixerResync->MarkMixerUsed( in_MixerId );
+
        mpCallLegResync->MarkCallLegUsed( in_EntityId );
+
     }
+
    else if ( in_pMixerJoinAttribute )
+
 
     {
 
     {
         // Find mixer joined to this mixer
+
         // Deleting mpMixerResyncwill automatically destroy all mixers that have not been bound to a call flow
        PTRCTBCAFMixer ptrOtherMixer = mpMixerResync->FindMixer( in_EntityId );
+
         delete mpMixerResync;
+
         mpMixerResync= NULL;
        // Try to bind the mixer and the entity to an already existing call flow
+
    }
        PITBCAFCallFlow pMyCallFlow = mpMixerResync->LinkSyncBindToExistingCallFlow( ptrMixer, ptrOtherMixer, in_pMixerJoinAttribute );
+
        if( !pMyCallFlow )
+
        {
+
            // Need to allocate a new call flow
+
            pMyCallFlow = tbnew CTBCAFMyCallFlow( this );
+
     
+
            // Re-attach behaviors to this call flow
+
            pMyCallFlow = tbnew CTBCAFMyFirstBehavior( pMyCallFlow, ... );
+
            pMyCallFlow = tbnew CTBCAFMySecondBehavior( pMyCallFlow, ... );
+
+
            // Initialize the newly created call flow, to be able to start working with it
+
            pMyCallFlow->InitCall( &pMyCallFlow );
+
+
            // Bind the mixers to the call flow
+
            pMyCallFlow ->BindMixer( ptrMixer );
+
            pMyCallFlow ->BindMixer( ptrOtherMixer );
+
        }
+
+
        // Mark the mixers as 'in use' in the resync pool, to avoid them being freed upon destruction of mpMixerResync
+
         mpMixerResync->MarkMixerUsed( in_MixerId );
+
         mpMixerResync->MarkMixerUsed( in_EntityId );
+
    }
+
 
  }
 
  }
+
 
  TBX_VOID CTBMyApp::OnCmcLibReady()
+
  TBX_VOID CTBMyApp::OnCmcLibNotReady()
 
  {
 
  {
     // Free all single call legs that remain
+
     // Free all call legs that have not been bound to a call flow during the re-sync process
     // Deleting mpCallLegResync will automatically reject legs still in that object
+
     if( mpCallLegResync )
    delete mpCallLegResync;
+
    {
    mpCallLegResync = NULL;
+
        // Deleting mpCallLegResync will automatically reject legs that have not been bound to a call flow
}
+
        delete mpCallLegResync;
 +
        mpCallLegResync = NULL;
 +
    }
 
   
 
   
TBX_VOID CTBMyApp::OnCmcLibNotReady()
+
     // Free all mixers that have not been bound to a call flow during the re-sync process
{
+
     if( mpMixerResync)
     // Free all single call legs that remain
+
    {
     // Deleting mpCallLegResync will automatically reject legs still in that object
+
        // Deleting mpMixerResyncwill automatically destroy all mixers that have not been bound to a call flow
    delete mpCallLegResync;
+
        delete mpMixerResync;
    mpCallLegResync = NULL;
+
        mpMixerResync= NULL;
 +
    }
 
  }
 
  }
  
  
For a more complete code example, please refer to the simple_call application, in file '''CTBS2GWSimpleCall.cpp'''.
+
For a more complete code example, please refer to:
 +
* The simple_call application, in file '''CTBS2GWSimpleCall.cpp''', for examples of call legs re-synchronization.
 +
* The simple_conf application, in file '''CTBSimpleConf.cpp''', for examples of mixers re-synchronization.
 +
* The gateway application, in file '''CTBCAFGateway.cpp''', for complete example of call legs, and mixers re-synchronization.

Latest revision as of 15:03, 4 July 2016

Contents

Overview of CAF layer

TelcoBridges provides multiple API layers for managing calls. The current page refers the the CAF API layer.

CAFCallFlow architecture.png

Overview of CAF call architecture

CAFCallFlow ownership.png

CAF Call leg (CTBCAFCallLeg):

The CTBCAFCallLeg (based on CTBCMCLeg base class) is the representation of communication with one peer, either through one protocol (SS7, ISDN, SIP, CAS) or media-only.

CTBCAFCallLeg objects can be created either after the toolpack_engine has notified the CAF application that a new call has been received, or else when the CAF application asks toolpack_engine to create a new outgoing call. When creating a new outgoing call, attributes are provided to indicate toolpack_engine which NAP to make the call to (which also defines the signaling protocol), which calling/called numbers to use, etc.

From the C++ point of view, the CTBCMCLeg contains most of the APIs require to manipulate call legs, while the CTBCAFCallLeg class is providing the interface to attach call legs to a parent CTBCAFCallFlow object

CAF Mixer (CTBCAFMixer):

CAF Call Legs (CTBCAFCallLeg) have a built-in function to join with another leg, making audio flow from one leg to another leg (or multiple destination legs).

However, one leg can be used as a Join destination only once. And thus, when it is required for one leg to "hear" from multiple call legs or sources (conferences for example), then the CTBCAFMixer object is used.

The CTBCAFMixer (based on CTBCMCMixer base class) is the representation of a DSP resource used to mix audio from different sources (call legs, stream server, or other mixers).

From the C++ point of view, the CTBCMCMixer contains most of the APIs require to manipulate mixers, while the CTBCAFMixer class is providing the interface to attach mixers to a parent CTBCAFCallFlow object.

The CTBCAFMixer object can be used for:

  • Conferences
  • Background music playing on a call
  • Recording both call legs of a call into one mixed audio file
  • etc.

See more information on mixers here: Audio Mixers

CAF Call Flow (CTBCAFCallFlow):

The CTBCAFCallFlow is a base class for implementing call flows.

  • It contains one or multiple CTBCAFCallLeg objects, and optionally one or multiple CTBCAFMixer objects.
  • Developers must create their own call flow class(es), based on CTBCAFCallFlow.
  • The role of that class is to implement a call flow that involves actions on legs and mixers that have a relation with each other.

Example call flows available in TelcoBridges source code:

  • CTBCAFBridge: A complete call flow which goal is to manage calls with two legs (one incoming, and one outgoing)
    • Accept/Alert/Answer incoming call with outgoing is Accepted/Alerted/Answered
    • Terminate both of them when one leg hangups.
    • Note: This is the call flow used by TelcoBridges' Gateway application
  • CTBSimpleCall: A simplified call flow with two legs (one incoming, one outgoing)
    • Similar to CTBCAFBridge, but over-simplified to be sample code
  • CTBSimpleConf: A relatively simple call flow that provide sample code to implement a conference call flow
    • Deals with multiple call legs, incoming or outgoing
    • Joins all the call legs to an audio mixer
    • Offers some options to change the way call legs are joined to the mixer


CAF Call Behavior (CTBCAFCallBehavior):

CTBCAFCallBehavior is a base class for implementing "behaviors". Developers must create their own behavior class(es), based on CTBCAFCallBehavior.

Behaviors are:

  • Attached to a call flow (CTBCAFCallFlow)
  • Modify the basic call flow, decorating it with optional functionality (CDR logging, FAX detection, Ring tone playback, etc.)

In order to implement their functionality, behavior objects, like their parent call flow object, can:

  • Receive events from all call legs and mixers of the call flow
  • Block events (hiding them from other behaviors, and from the call flow itself)
  • Manipulate call legs and mixers.

Performing actions on call legs and mixers

Actions on call legs

The call flow (and it's behaviors) can perform various actions on each of it's call legs. Here is a list of the actions available on the CTBCAFCallLeg objects (please refer to the header file CTBCMCLeg.hpp for more information on each of these functions):

Actions on CTBCAFCallLeg

Actions on mixers

The call flow (and it's behaviors) can perform various actions on each of it's mixers. Here is a list of the actions available on the CTBCAFMixer objects (please refer to the header file CTBCMCMixer.hpp for more information on each of these functions):

Actions on CTBCAFMixer

Receiving events from call legs and mixers

CAFCallFlow event.png

Events received from call legs by the CTBCAFCallFlow object

The call flow object (as well as all it's behaviors) receive the following events from it's call legs (please refer to the header file ITBCAFCallFlow.hpp for more information on each of these functions):

Call flow events from it's call legs

Events received from mixers by the CTBCAFCallFlow object

The call flow object (as well as all it's behaviors) receive the following events from it's mixers (please refer to the header file ITBCAFCallFlowMixer.hpp for more information on each of these functions):

Call flow events from it's mixers

Events behavior chain

All the events describe above are called consecutively on each behavior attached to the call flow, and ultimately, called on the call flow itself.

Each behavior in the chain can

  • Pass the event to the next behavior in the chain
  • Consume the event so other behaviors in the chain and the call flow are unaware that this event occurred

The CTBCAFCallBehavior default implementation is to forward the event to the next event in the chain.

When you implement your own behaviors, you can override only a couple of events (like OnCallLegAnswered() for example) to perform specific tasks, and leave all other events to the base class, so you don't have to write a lot of code to create a new behavior.

Example call flows

Example call flow where an incoming call is received, accepted, then terminated from network side:

Example call flow.png

Example conference call flow with two incoming call legs, one outgoing call leg, and one audio mixer:

Example call flow conf.png

Creating the CTBCAFCallFlow object, its legs and mixers

Steps to create a new call flow are:

  • Create a new object (using a class derived from CTBCAFCallFlow class)
  • Attach behaviors to the call flow
  • Add attributes of incoming and outgoing legs to create
  • Add attributes for mixers to create
  • Tell the call flow object to initialize itself, and create the call legs and mixers as requested
  • Join legs together, join legs to mixers, or join mixers to other mixers

Here is a (over-simplified) example:

ITBCAFCallFlow* pCallFlow;
ITBCAFCallFlow* pParentCallOrBehavior;

// Create the call flow object (here using the CTBCAFBridge class, based on CTBCAFCallFlow)
pCallFlow = tbnew CTBCAFBridge( 0, this);

// Optionally, attach behaviors
pParentCallOrBehavior = pCallFlow;
pParentCallOrBehavior = tbnew CTBCAFCallBehaviorBusyTone( pParentCallOrBehavior, ... );
pParentCallOrBehavior = tbnew CTBCAFCallBehaviorBridgeCdr( pParentCallOrBehavior, ... );

// Add an incoming call leg to that call flow (CTBCAFCallLeg object will get created based on provided attributes)
pCallFlow->AddIncoming( in_LegId, ptrIncomingLegAttribute, ptrIncomingLegProtocolAttributes, ptrAcceptCallProtAttribute );

// Add an outgoing call leg
pCallFlow->AddOutgoing( ptrOutgoingLegAttribute, ptrOutgoingLegProtocolAttributes );

// Create an audio mixer
pCallFlow->AddMixer( ptrMixerAttribute );

// Initialize the call (this actually creates the call legs)
pCallFlow->InitCall( &pCallFlow );


During the call flow (upon reception of an event on a call leg for example), more actions can be taken. For example:

MyCallFlowOrBehavior::OnCallLegAnswered()
{
  // Ask to detect FAX tones
  CTBCMC_EVENT_ATTRIBUTE  Event;
  Event.AddToneString( "telcofax/eof" );
  GetIncomingActiveLeg()->StartEventCollection( Event, NULL, TBX_FALSE /* Don't suppress tones */ );

  // Call base class to forward event to next behavior
  CTBCAFCallBehavior::OnCallLegAnswered();
}

During the call flow (upon reception of an event on a mixer for example), more actions can be taken. For example:

MyCallFlowOrBehavior::OnMixerCreated()
{
  // Immediately add call legs to the mixer
  GetFirstMixer()->MixerJoin( GetIncomingActiveLeg().Get(), ptrJoinAttributes );
  GetFirstMixer()->MixerJoin( GetOutgoingActiveLeg().Get(), ptrJoinAttributes );

  // Answer the incoming leg
  GetIncomingActiveLeg()->AnswerCall();

  // Play background music on the conference
  GetFirstMixer()->MixerPlayStream( PlayAttr );

  // Call base class to forward event to next behavior
  CTBCAFCallBehavior::OnMixerCreated();
}

For a more complete code example, please refer to the simple_call application, in file CTBS2GWSimpleCall.cpp, or here:

CTBCAFBridge creation

Constructing leg attributes

Incoming call leg attributes

To create the incoming call leg, the following information is required:

  • Smart pointer to incoming call leg attributes, received from toolpack_engine through OnCallLegPresent().
ptrIncomingLegAttribute = tbnew CTBCMC_CALL_LEG_ATTRIBUTE( *(in_CallLegAttribute.GetCallLegAttribute()) );
  • Smart pointer to incoming call leg protocol attributes, received from toolpack_engine through OnCallLegPresent().
ptrIncomingLegProtocolAttributes = tbnew CTBCMC_PROTOCOL_ATTRIBUTE( in_ProtocolAttribute, TBX_TRUE );
  • Smart pointer to empty protocol attributes that Behaviors can fill, and that will be used when accepting the call.
ptrAcceptCallProtAttribute = tbnew CTBCMC_PROTOCOL_ATTRIBUTE;

Outgoing call leg attributes

To create the outgoing call leg, the following information is required:

  • Smart pointer to outgoing call leg attributes to use to create the call, generally derived from the incoming call attributes
ptrOutgoingLegAttribute = tbnew CTBCMC_CALL_LEG_ATTRIBUTE();
ptrOutgoingLegAttribute->CopyLegAttributeFrom( *(ptrIncomingLegAttribute.Get()) );
  • Smart pointer to empty protocol attributes that Behaviors can fill, and that will be used when creating the outgoing call (SIP invite, ISUP IAM, ISDN Setup, etc.)
ptrOutgoingLegProtocolAttributes = tbnew CTBCMC_PROTOCOL_ATTRIBUTE();

Mixer attributes

To create the mixer, the following information is required:

  • Smart pointer to mixer attributes to use to create the mixer
ptrMixerAttribute = tbnew CTBCMC_MIXER_ATTRIBUTE();
ptrMixerAttribute->SomeParameter = SomeValue;


Call leg initialization callbacks

All behaviors attached to the call flow will be notified of the creation of new call legs through callback OnInitIncomingCallLeg() or OnInitOutgoingCallLeg().

OnInitIncomingCallLeg()

In this callback, each behavior may consult the incoming call attributes or protocol attributes, and can modify the protocol attributes that will be used to accept the call (actually providing protocol-specific information, like SS7 IEs, or SIP headers).

OnInitOutgoingCallLeg()

In this callback, each behavior can modify the protocol attributes that will be used to create the call (actually providing protocol-specific information, like SS7 IEs, or SIP headers that will be used in the initial signaling message, as SIP Invite, ISUP IAM, or ISDN Setup).

OnMixerInit()

In this callback, each behavior can modify the mixer attributes that will be used to create the mixer.

Destruction the CTBCAFCallFlow object, its legs and mixers

Self-destructing call legs, mixers, behaviors, and call flow

The CTBCAFCallFlow object contains smart pointers to it's call legs and mixers. Destruction of these will thus occur automatically when the last smart pointer reference is removed (that last reference can be either the smart pointer reference in the CTBCAFCallFlow object, or anywhere else in the application).

Upon destruction of the last call leg or last mixer (when no more leg or mixer are in the call flow), the objects based on CTBCAFCallFlow or CTBCAFCallBehavior will delete themselves automatically, by calling the attached "free listener" (that was provided in the constructor of the call flow object).

Call leg termination callbacks and actions

OnCallLegTerminatingIndication()

This callback means that toolpack_engine has received a termination request from a call leg (SIP Bye, ISUP REL, for example). A typical use of that event is to call TerminateCall() on both incoming and outgoing legs of a call flow, to hangup both sides.

TerminateCall()

This function is used for the application to request the termination of a call leg. It is also used to confirm that the application is done with a call leg, after OnCallLegTerminatingIndication() event was received (in that case, TerminateCall() can be called within the event callback, or later if the application has some more asynchronous cleanup to do).

OnCallLegTerminated()

This callback confirms that toolpack_engine has finished closing the data path and signaling for this call leg. It will also be called when a leg is destroyed because of a lost of communication with toolpack_engine (restart, network failure, crash).

FreeLeg()

It is MANDATORY that this function is called for each call leg after OnCallLegTerminated().

It's up to the application to call the function immediately (within OnCallLegTerminated() for example) or later after doing some more job (like asynchronous database updates, for example)

OnLegFreed()

This callback confirms that the leg is being freed.

It's the last chance for a call flow or a behavior to release things related to a call leg, like canceling timers for example.

  • Important note: Behaviors that implement that function MUST, as the LAST line of their function, call the base class' CTBCAFCallBehavior::OnLegFreed. Not calling base class' function will cause the call to leak. Calling that before the last line of the function will cause crash, as the Behavior object could destroy itself within that function upon destruction of the last leg/mixer of the call.


Mixer termination callbacks and actions

MixerTerminate()

This function is used for the application to request the termination of a mixer.

OnMixerTerminated()

This callback confirms that toolpack_engine has finished destroying resources related to this mixer. It will also be called when a leg is destroyed because of a lost of communication with toolpack_engine (restart, network failure, crash).

FreeMixer()

It is MANDATORY that this function is called for each mixer after OnMixerTerminated().

It's up to the application to call the function immediately (within OnMixerTerminated() for example) or later after doing some more job (like asynchronous database updates, for example)

OnMixerFreed()

This callback confirms that the mixer is being freed.

It's the last chance for a call flow or a behavior to release things related to a mixer, like canceling timers for example.

  • Important note: Behaviors that implement that function MUST, as the LAST line of their function, call the base class' CTBCAFCallBehavior::OnMixerFreed(). Not calling base class' function will cause the mixer and call to leak. Calling that before the last line of the function will cause crash, as the Behavior object could destroy itself within that function upon destruction of the last leg/mixer of the call.

Joining call legs

The call legs can be "joined" together, meaning that audio from a leg is sent toward the other leg.

Call legs can be joined full-duplex, or half-duplex.

Join/Unjoin API

Joining call legs is made by using the call leg's function Join()

TBX_RESULT CTBCMCLeg::Join
(
  CTBCMCLeg*             in_pDestinationLeg,  // Other leg to join with
  PITBCMCErrorListener   in_pErrorListener,   // Error listener for this Join operation (generally not used)
  TBX_BOOL               in_fWarnIfToneDetectionEnabled, // Prints a warning in toolpack_engine's log if tone detection still enabled upon join
  CTBCMC_JOIN_ATTRIBUTE* in_pJoinAttribute    // Attributes for joining -> Half or full-duplex
);

Upon completion of the join, call flow's function OnLegJoinDone() is called:

 TBX_RESULT CTBCAFCallFlow::OnLegJoinDone
 ( 
   IN    PCTBCAFCallLeg              in_pSrcCallLeg,
   IN    PCTBCAFCallLeg              in_pDstCallLeg,
   IN    CTBCMC_JOIN_ATTRIBUTE*      in_pJoinAttribute
 )

Unjoining call legs is made by using the call leg's function Unjoin():

TBX_RESULT CTBCMCLeg::Unjoin
(
  CTBCMCLeg*             in_pDestinationLeg,  // Other leg to join with
  PITBCMCErrorListener   in_pErrorListener   // Error listener for this unjoin operation (generally not used)
);

Unjoining is also implicitly initiated when one of the two legs is terminating.

Upon completion of the unjoin, call flow's function OnLegUnjoinDone() is called:

 TBX_RESULT CTBCAFCallFlow::OnLegUnjoinDone
 ( 
   IN    PCTBCAFCallLeg              in_pSrcCallLeg,
   IN    PCTBCAFCallLeg              in_pDstCallLeg,
   IN    CTBCMC_JOIN_ATTRIBUTE*      in_pJoinAttribute
 )


Basic rules

Rules:

  • Each call leg can receive audio from only one source at the time: Another leg, or a mixer.
  • A call leg can send it's audio to any number of destinations (audio "forking"), obviously as long as these destinations don't already receive audio from other source.
  • A Join operation on a pair of call legs already joined together will "override" the previous join attributes
    • Convert half-duplex to full-duplex connection
    • Convert full-duplex to half-duplex connection
    • Invert direction of a half-duplex connection

Corollaries:

  • To receive audio from more than one source, a leg must be connected to a mixer
  • Doing A->Join(B,hd) then B->Join(A,hd) won't make a full-duplex connection... it will INVERT the direction of the join
  • With TMedia, audio "forking" (one leg sending to multiple other legs) does not require DSPs. Only audio mixing requires DSPs.
  • Mixers can be joined to mixers, if complex scenarios require it

Scenario: Full-duplex join

LegA <--> LegB

When joined full-duplex, each leg receives the audio from the other leg.

  • pLegA->Join( pLegB );
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [fd] )
  • pLegA->Unjoin( pLegB );
    • ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [fd] )

Scenario: Half-duplex join

LegA ---> LegB

When joined half-duplex, each source leg sends audio to destination leg, but not the opposite.

  • CTBCMC_JOIN_ATTRIBUTE HdJoin;
  • HdJoin.IsFullDuplex() = TBX_FALSE;
  • pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin );
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
  • pLegA->Unjoin( pLegB );
    • ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [hd] )

Scenario: Inverting direction of half-duplex join

LegA ---> LegB ===> LegB ---> LegA

Joining A--->B then B--->A is not creating a full-duplex connection, it's actually inverting the direction of the connection.

  • pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin );
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
  • pLegB->Join( pLegA, NULL, TBX_FALSE, &HdJoin );
    • ==> CallFlow::OnLegJoinDone( pLegB, pLegA, [hd] ) // This confirms that join direction has been inverted
  • pLegB->Unjoin( pLegA );
    • ==> CallFlow::OnLegUnjoinDone( pLegB, pLegA, [hd] )

Scenario: Changing from half-duplex to full-duplex

LegA ---> LegB ===> LegA <---> LegB

A Join operation on two legs already joined together will override the join attributes.

  • pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin);
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
  • pLegA->Join( pLegB ); // NULL attributes default to full-duplex
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [fd] ) // This confirms that join is now full-duplex
  • pLegA->Unjoin( pLegB );
    • ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [hd] )

Scenario: Audio forking

LegA <---> LegB
      ---> LegC
      ---> LegD
LegB  ---> LegE

There is no limit on audio forking, as long as all legs receive audio from at most one source.

  • pLegA->Join( pLegB );
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [fd] )
  • pLegA->Join( pLegC, NULL, TBX_FALSE, &HdJoin );
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegC, [hd] )
  • pLegA->Join( pLegD, NULL, TBX_FALSE, &HdJoin );
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegD, [hd] )
  • pLegB->Join( pLegE, NULL, TBX_FALSE, &HdJoin );
    • ==> CallFlow::OnLegJoinDone( pLegB, pLegE, [hd] )
  • pLegA->TerminateCall()
    • ==> CallFlow::OnLegUnjoinDone( pLegA, pLegB, [fd] )
    • ==> CallFlow::OnLegUnjoinDone( pLegA, pLegC, [hd] )
    • ==> CallFlow::OnLegUnjoinDone( pLegA, pLegD, [hd] )
    • ==> CallFlow::OnCallLegTerminated( pLegA )

Scenario: Illegal join

LegA ---> LegB
LegB <--> LegC  *** Illegal

A join operation that would make a leg receive audio from more than one source would be illegal.

  • pLegA->Join( pLegB, NULL, TBX_FALSE, &HdJoin );
    • ==> CallFlow::OnLegJoinDone( pLegA, pLegB, [hd] )
  • pLegB->Join( pLegC );
    • ==> Error!

Joining with mixers

Joining with audio mixers is similar to joining legs. Call legs or mixers can both be joined to call legs or mixers.

Differences when joining with mixers:

  • We use the terms "speaker" or "listener", instead of "half-duplex" or "full-duplex".
  • Unlike a call leg, a mixer can be destination from multiple sources (that's actually the purpose of a mixer!)

For more information about mixers, please refer to the following pages:


Mixer Join/Unjoin API

Joining with mixers is made by using the mixer's functions MixerJoin()

TBX_RESULT CTBCMCMixer::MixerJoin
(
  IN    CTBCMCLeg *                         in_pLeg,
  IN    PCTBCMC_MIXER_LEG_JOIN_ATTRIBUTE    in_pJoinAttribute = NULL,
  IN    PITBCMCErrorListener                in_pErrorListener = NULL
);
TBX_RESULT CTBCMCMixer::MixerJoin
(
  IN    CTBCMCMixer*                        in_pOtherMixer,
  IN    PCTBCMC_MIXER_MIXER_JOIN_ATTRIBUTE  in_pJoinAttribute = NULL,
  IN    PITBCMCErrorListener                in_pErrorListener = NULL
);

Upon completion of the join, call flow's function OnMixerJoinDone() is called:

 TBX_RESULT CTBCAFCallFlow::OnMixerJoinDone
 ( 
   IN    PCTBCAFMixer                in_pMixer
   IN    PCTBCAFCallLeg              in_pCallLeg
 )
 TBX_RESULT CTBCAFCallFlow::OnMixerJoinDone
 ( 
   IN    PCTBCAFMixer                in_pMixer
   IN    PCTBCAFMixer                in_pOtherMixer
 )


Unjoining mixers is made by using the mixer's function MixerUnjoin():

TBX_RESULT CTBCMCMixer::MixerUnjoin
(
  IN    CTBCMCLeg *                       in_pLeg,
  IN    PITBCMCErrorListener              in_pErrorListener
);

Unjoining is also implicitly initiated when the mixer or call leg is terminated.

Upon completion of the unjoin, call flow's function OnMixerUnjoinDone() is called:

 TBX_RESULT CTBCAFCallFlow::OnMixerUnjoinDone
 ( 
   IN    PCTBCAFMixer                in_pMixer
   IN    PCTBCAFCallLeg              in_pCallLeg
 )
 TBX_RESULT CTBCAFCallFlow::OnMixerUnjoinDone
 ( 
   IN    PCTBCAFMixer                in_pMixer
   IN    PCTBCAFMixer                in_pOtherMixer
 )

Re-synchronizing with toolpack_engine

Note: You can first read the overview of re-synchronization mechanism here: CAF:_Call_Legs_Resync

There are a couple of cases where a CAF application needs to re-synchronize with toolpack_engine:

  • The CAF application was restarted
  • The toolpack_engine was restarted
  • Network connection between CAF application and toolpack_engine was momentarily down

When communication with toolpack_engine is lost, all call legs and mixers are notified of the disconnection through OnSyncLost() and OnMixerSyncLost(). However, they are not immediately destroyed. They are kept in the CAF framework for 10 seconds waiting for the engine (or backup engine) to reconnect. After 10 seconds, legs and mixers are terminated in the CAF framwork. This does not mean, however, that they are terminated in the toolpack_engine. Upon reconnection with engine, legs and mixers still present in the engine will be sent back to the CAF application through OnCallLegSync() and OnMixerSync().

Possible scenarios for a leg are thus:

  • Engine is reconnected within 10 seconds:
    • Some legs/mixers no more exist in toolpack_engine, and will be notified by OnCallLegTerminated() / OnMixerTerminated().
    • Legs/mixers that are still valid will be untouched (though they are notified with OnSyncLost() / OnSyncDone() / OnMixerSyncDone(), for convenience)
  • Engine has been disconnected for more than 10 seconds:
    • CAF framework will terminate call legs/mixers, and notify them through OnCallLegTerminated() / OnMixerTerminated()
    • Later, when engine is reconnected after a >10:
      • Call legs/mixers that are still available in toolpack_engine are pushed back to CAF application through OnCallLegSync(), OnLinkSync() and OnMixerSync()
      • CAF application can refuse them, or re-build it's legs/mixers/call flow contexts to resume operation on these calls


Re-synchronization sequence

In all the cases above, the re-synchronization mechanism involves the following steps:

=> Communication lost with toolpack_engine

  • OnCmcLibNotReady() (CAF application callback): Indicates that communication with toolpack_engine has been interrupted. It will not be possible to perform actions (play file, ask for digit collection) on call legs/mixers until toolpack_engine is ready again (OnCmcLibReady).
  • OnSyncLost() (CAF call flow and behavior callback): Same meaning as OnCmcLibNotReady(), passed to each call leg contexts individually.
  • OnMixerSyncLost() (CAF call flow and behavior callback): Same meaning as OnCmcLibNotReady(), passed to each call mixer contexts individually.

=> Communication re-established with toolpack_engine

  • OnCallLegSync() (CAF application callback): Indicates that the toolpack_engine knows about a call leg that does not exist in the CAF application.
  • OnMixerSync() (CAF application callback): Indicates that the toolpack_engine knows about a mixer that does not exist in the CAF application.
  • OnLinkSync() (CAF application callback): Indicates that the toolpack_engine knows about a link between two call legs (legs are joined, audio is flowing from one to the other directly).
  • OnMixerLinkSync() (CAF application callback): Indicates that the toolpack_engine knows about a link between a leg and a mixer, or between two mixers.
  • OnCmcLibReady() (CAF application callback): Indicates that communication with toolpack_engine has been restored, and that all legs, links and mixers have been re-synchronized
  • OnSyncDone() (CAF call flow and behavior callback): Indicates to a call leg (that previously received OnSyncLost()) that it is still present in the toolpack_engine, and ready to be controlled again.
  • OnMixerSyncDone() (CAF call flow and behavior callback): Indicates to a mixer (that previously received OnMixerSyncLost()) that it is still present in the toolpack_engine, and ready to be controlled again.
  • OnCallLegTerminated() (CAF call flow and behavior callback): When this function is called with Reason TBCMC_CALL_REASON_CODE_TOOLPACK_SYNC_DROP, it indicates that this call leg is no more present in toolpack_engine after re-synchronizing, and must thus be freed in the CAF application too.
  • OnMixerTerminated() (CAF call flow and behavior callback): When this function is called, it indicates that this mixer is no more present in toolpack_engine after re-synchronizing, and must thus be freed in the CAF application too.

Rebuilding new legs, mixers and call flow objects

When an application receives OnCallLegSync(), it must take the decision to either keep the call leg (create a CTBCAFCallLeg object) or refuse it (tell toolpack_engine that we don't want to re-synchronize it, and that it must be terminated).

When an application receives OnMixerSync(), it must take the decision to either keep the mixer (create a CTBCAFMixer object) or refuse it (tell toolpack_engine that we don't want to re-synchronize it, and that it must be terminated).

Dropping re-synchronized legs

The static helper function CTBCMCLeg::RefuseLeg() has been provided by the class CTBCMCLeg to terminate re-synchronized call legs without having to worry about creating any object. An application that wants to refuse a re-synchronized leg should call it.

Dropping re-synchronized links

The static helper function CTBCMCLeg::RefuseLink() has been provided by the class CTBCMCLeg to terminate re-synchronized links without having to worry about creating any object. An application that wants to refuse a re-synchronized link should call it. The static helper function CTBCMCLeg::RefuseMixerLink() has been provided by the class CTBCMCMixer to terminate re-synchronized mixer links without having to worry about creating any object. An application that wants to refuse a re-synchronized mixer link should call it.

Dropping re-synchronized mixers

The static helper function CTBCMCMixer::RefuseMixer() has been provided by the class CTBCMCMixer to terminate re-synchronized mixer without having to worry about creating any object. An application that wants to refuse a re-synchronized mixer should call it.

Keeping re-synchronized legs that are part of a link or a mixer

The helper classes CTBCAFCallLegResync and CTBCAFMixerResync are provided to help creating CTBCAFCallLeg and CTBCAFMixer objects for re-synchronized legs and mixers, and later retrieve them and attach them to a CTBCAFCallFlow parent, upon OnLinkSync() or OnMixerLinkSync():

Upon OnLegSync():

  • Call function AllocateCallLeg() of object CTBCAFCallLegResync, to allocate a temporary call leg object (that will be used later upon OnLinkSync).

Upon OnMixerSync():

  • Call function OnMixerSync of object CTBCAFMixerResync, to allocate temporary mixer object (that will be used later upon OnMixerLinkSync).

Upon OnLinkSync():

  • This function provides the LegId of the two joined call legs.
  • Retrieve both call legs by calling function FindCallLeg() of object CTBCAFCallLegResync.
  • Call function LinkSyncBindToExistingCallFlow of object CTBCAFCallLegResync. This function will see if one of these two joined call legs is already assigned to an existing call flow, and will bind the other leg to the same call flow if appropriate
  • If function LinkSyncBindToExistingCallFlow has not found an existing call flow to bind legs to, create (allocate) a new call flow that owns these two call legs.
  • Call functionMarkCallLegUsed of object CTBCAFCallLegResync to indicate that these legs are in use and must not be destroyed upon termination of the object CTBCAFCallLegResync.

Upon OnMixerLinkSync():

  • This function provides the LegId or MixerId of the two joined entities (Mixer/Leg, or Mixer/Mixer).
  • Call function OnMixerLinkSync of object CTBCAFMixerResync.
  • This function will retrieve the existing call flow that already owns the source or destination of this mixer link, and make sure to bind both source and destination of this link to that call flow.

Finally, upon OnCmcLibReady(), the application can destroy the CTBCAFCallLegResync and CTBCAFMixerResync objects, which will take care of refusing (dropping) all call legs and mixers that have not been assigned to a call flow.

Simplified example code:

TBX_VOID CTBMyApp::OnCallLegSync
(
  IN    TBCMC_LEG_ID                         in_LegId,
  IN    CTBCMC_CALL_LEG_ATTRIBUTE_COMMON&    in_CallLegAttribute
)
{
    // Upon first re-synchronized leg, allocate utility class to contain re-synchronized call legs
    if( !mpCallLegResync )
    {
        mpCallLegResync = new TBCAF::CTBCAFCallLegResync( MY_APP_MAX_CALLS );
    }

    // Re-create a CTBCAFCallLeg object for this re-synchronized leg
    mpCallLegResync->AllocateCallLeg
    (
        NULL,                            // Not yet bound to a parent call flow
        in_LegId,                        // Use the leg Id of this re-synchronized call leg
        in_CallLegAttribute,             // Store the attributes of this re-synchronized call leg
        this,                            // Until it has a parent call flow, set ourself as free listener
        TBCMC_MSG_CALL_LEG_STATE_ACTIVE  // Consider this call leg as active (answered)
                                         //  since only active calls can be re-synchronized
    );
}
TBX_VOID CTBMyApp::OnMixerSync
(
  IN    TBCMC_MIXER_ID                       in_MixerId,
  IN	 CTBCMC_MIXER_ATTRIBUTE &	      in_MixerAttribute
)
{
    // Upon first re-synchronized mixer, allocate utility class to contain re-synchronized mixers
    if( !mpMixerResync)
    {
        mpMixerResync= new TBCAF::CTBCAFCallLegResync( MY_APP_MAX_MIXERS );
    }

    // Re-create a CTBCAFMixer object for this re-synchronized leg
    mpMixerResync->AllocateMixer
    (
        NULL,                            // Not yet bound to a parent call flow
        in_MixerId,                      // Use the mixer Id of this re-synchronized mixer
        in_MixerAttribute,               // Store the attributes of this re-synchronized mixer
        this                             // Until it has a parent call flow, set ourself as free listener
    );
}
TBX_VOID CTBMyApp::OnLinkSync
(
  IN    TBCMC_LINK_ID              in_LinkId, 
  IN    TBCMC_LEG_ID               in_SrcLegId, 
  IN    TBCMC_LEG_ID               in_DstLegId, 
  IN    CTBCMC_JOIN_ATTRIBUTE &    in_JoinAttribute
)
{
    // Find both call legs in the re-sync utility mpCallLegResync
    PTRCTBCAFCallLeg ptrSrcLeg = mpCallLegResync->FindCallLeg( in_SrcLegId );
    PTRCTBCAFCallLeg ptrDstLeg = mpCallLegResync->FindCallLeg( in_DstLegId );
    
    // Try to bind these legs to an already existing call flow.
    // This happens for conference, or audio-forking call flows that deal with more than 2 call legs,
    // and where some legs are half-duplex toward multiple other legs. Function LinkSyncBindToExistingCallFlow will
    //   - Search if one of the two legs is already assigned to a call flow
    //   - attach the other leg to that call flow
    //   - Mark the two legs as joined together
    PITBCAFCallFlow pMyCallFlow = mpCallLegResync->LinkSyncBindToExistingCallFlow( ptrSrcLeg, ptrDstLeg, in_JoinAttribute );
   
    if( pMyCallFlow == NULL )
    {
        // Need to allocate a new call flow, using the constructor that also attaches two legs to the call flow
        pMyCallFlow = tbnew CTBCAFMyCallFlow( this );
    
        // Re-attach behaviors to this call flow
        pMyCallFlow = tbnew CTBCAFMyFirstBehavior( pMyCallFlow, ... );
        pMyCallFlow = tbnew CTBCAFMySecondBehavior( pMyCallFlow, ... );

        // Initialize the newly created call flow, to be able to start working with it
        pMyCallFlow->InitCall( &pMyCallFlow );

        // Bind the legs to the call flow
        pMyCallFlow->BindCallLeg( ptrSrcLeg );
        pMyCallFlow->BindCallLeg( ptrDstLeg );
    }
   
    // Mark the legs as 'in use' in the resync pool, to avoid them being freed upon destruction of mpCallLegResync,
    // now that they are owned by a call flow
    mpCallLegResync->MarkCallLegUsed( in_SrcLegId );
    mpCallLegResync->MarkCallLegUsed( in_DstLegId );
}
TBX_VOID CTBMyApp::OnMixerLinkSync
(
  IN    TBCMC_MIXER_ID                       in_MixerId,
  IN    TBCMC_ENTITY_ID                      in_EntityId,
  IN    PCTBCMC_MIXER_LEG_JOIN_ATTRIBUTE     in_pLegJoinAttribute,
  IN    PCTBCMC_MIXER_MIXER_JOIN_ATTRIBUTE   in_pMixerJoinAttribute
)
{
    // Use the mixer re-sync helper to automatically create mixer and bind to an existing call flow
    mpMixerResync->OnMixerLinkSync( in_MixerId, in_EntityId, in_pLegJoinAttribute, in_pMixerJoinAttribute );
}
TBX_VOID CTBMyApp::OnCmcLibReady()
{
    // Free all call legs that have not been bound to a call flow during the re-sync process
    if( mpCallLegResync )
    {
        // Deleting mpCallLegResync will automatically reject legs that have not been bound to a call flow
        delete mpCallLegResync;
        mpCallLegResync = NULL;
   }

    // Free all mixers that have not been bound to a call flow during the re-sync process
    if( mpMixerResync)
    {
        // Deleting mpMixerResyncwill automatically destroy all mixers that have not been bound to a call flow
        delete mpMixerResync;
        mpMixerResync= NULL;
   }
}
TBX_VOID CTBMyApp::OnCmcLibNotReady()
{
    // Free all call legs that have not been bound to a call flow during the re-sync process
    if( mpCallLegResync )
    {
        // Deleting mpCallLegResync will automatically reject legs that have not been bound to a call flow
        delete mpCallLegResync;
        mpCallLegResync = NULL;
   }

    // Free all mixers that have not been bound to a call flow during the re-sync process
    if( mpMixerResync)
    {
        // Deleting mpMixerResyncwill automatically destroy all mixers that have not been bound to a call flow
        delete mpMixerResync;
        mpMixerResync= NULL;
   }
}


For a more complete code example, please refer to:

  • The simple_call application, in file CTBS2GWSimpleCall.cpp, for examples of call legs re-synchronization.
  • The simple_conf application, in file CTBSimpleConf.cpp, for examples of mixers re-synchronization.
  • The gateway application, in file CTBCAFGateway.cpp, for complete example of call legs, and mixers re-synchronization.
Personal tools