tutorial_5.cpp

C++ Tutorial Step 5: Picking up incoming calls and catching voice activity notifications

In this step of the tutorial, we finally get around to voice calls. We'll write a small SkypeKit client that

Code Walkthrough

To pick up incoming calls, we will basically need to detect when a Conversation::LOCAL_LIVESTATUS property gets changed to Conversation::RINGING_FOR_ME and then call JoinLiveSession() method of that object. In reality however, it is somewhat more complicated than that.

Firstly, there is the obvious issue of possibility of another live conversation being up already. Live conversations with more than two participants will be covered in another tutorial, so in this case we will just ignore other incoming calls in such cases. It would also be nice to display the list of remote participants. It's a list - not single remote participant - because it could be the case that you were invited to an already existing voice conference.

To do all that, lets introduce our own version of Conversation::JoinLiveSession and name it PickUpCall.

void MyConversation::PickUpCall()
{
  // CallerList we keep in Conversation class - so that it won't get garbage collected
  // while the conversation object exists in the wrapper. 
  this->GetParticipants(callerList, Conversation::OTHER_CONSUMERS);
  SEString partListAsString = "";
  for (uint i = 0; i < callerList.size(); i++)
  {
    partListAsString = partListAsString 
      + " " 
      + (const char*)callerList[i]->GetProp(Participant::P_IDENTITY);
  };

  // re-loading with filter ALL, so that we get our own 
  // Participant::P_SOUND_LEVEL updates as well
  this->GetParticipants(callerList, Conversation::ALL);
  fetch(callerList);

  if (!skype->isLiveSessionUp)
  {
    printf("Incoming call from: %s \n", (const char*)partListAsString);
    this->JoinLiveSession();
  }
  else
  {
    printf("Another call is coming in from : %s \n", (const char*)partListAsString);
    printf("As we already have a live session up, we will reject it. \n");
    this->LeaveLiveSession(true);
  };
};

Now to the tricky part. To reliably catch an incoming calls, we actually need to detect two events: Skype::OnConversationListChange and Conversation::OnChange property change callback.

The reason we need both is somewhat convoluted, but bear with me. If an incoming call occurs, what really happens from the wrapper perspective is that conversation object's LOCAL_LIVESTATUS property obtains a new value - RINGING_FOR_ME. However, there is no guarantee that the conversation object where this occured is already cached in the wrapper. Even if you have called methods or accessed properties of that particular conversation before, then unless you are keeping a ConversationRef pointing to that object in your code - the object has most likely been garbage collected. If the object is not in wrapper's object cache - you will not get property update events for that object.

So, unless you have references to every single conversation object, the only foolproof place to pick up calls would be Skype::OnConversationListChange callback. This gets fired every time a conversation is added or removed from a conversation list. In this case, the object would be added to the Conversation::LIVE_CONVERSATIONS.

void MySkype::OnConversationListChange(
        const ConversationRef &conversation,
        const Conversation::LIST_TYPE &type,
        const bool &added)
{
  if (type == Conversation::LIVE_CONVERSATIONS)
  {
    Conversation::LOCAL_LIVESTATUS liveStatus;
    conversation->GetPropLocalLivestatus(liveStatus);
    SEString liveStatusAsString = tostring(liveStatus);
    printf("OnConversationListChange : %s\n", (const char*)liveStatusAsString);

    if (liveStatus == Conversation::RINGING_FOR_ME)
    {
      printf("RING RING..\n");
      printf("Picking up call from MySkype::OnConversationListChange\n");
      // Saving the currently live conversation reference..
      liveSession = conversation->ref();
      liveSession.fetch();
      liveSession->PickUpCall();
    };
  };
};

The above method works really well. Until you come up against situation where you have more than one incoming calls in quick succession, from the same caller: one call comes in - you pick it up, call ends, another incoming call occurs. Then the method described above suddenly stops working.

The reason it stops working is that by default, formerly live conversations do not get removed from the LIVE_CONVERSATIONS list immediately. Instead they will linger on in that list for several additional seconds. This means that while the previously live conversation does that lingering, and if another call in the same conversation takes place - the OnConversationListChange event will not fire - because it didn't have time to get removed from that list in the first place.

However, we can now make sure that the conversation object is in wrapper's object cache, by keeping a reference to that object. So, now we can actually use the Conversation::OnChange callback and check for RINGING_FOR_ME.

void MyConversation::OnChange(int prop)
{
  if (prop == Conversation::P_LOCAL_LIVESTATUS)
  {
    Conversation::LOCAL_LIVESTATUS liveStatus;
    this->GetPropLocalLivestatus(liveStatus);
    if (liveStatus == Conversation::RINGING_FOR_ME)
    {
      printf("RING RING..\n");
      printf("Picking up call from MyConversation::OnChange\n");
      this->PickUpCall();
    };

    if (liveStatus == Conversation::IM_LIVE)
    {
      // Saving the currently live conversation reference..
      skype->liveSession = this->ref();
      printf("Live session is up.\n");
    };

    if ((liveStatus == Conversation::RECENTLY_LIVE) || (liveStatus == Conversation::NONE))
    {
      printf("Call has ended..\n");
      skype->isLiveSessionUp = false;
    };
  };
};

Alternative and somewhat simpler way to do all this would be to set the period that formerly live conversations stay in the recently live list to zero. This can be controlled modifying SETUPKEY_RECENTLY_LIVE_TIMEOUT like this:

skype->SetInt(SETUPKEY_RECENTLY_LIVE_TIMEOUT, 0);

Note that this is an account-based setup key, you can only access it after successful login.

We have now completed our first objective - the client is picking up incoming calls, refusing calls when one is already up, and displaying a notification when the call ends.

Our second objective was displaying voice activity events. For this we need to derive our own Participant class and catch changes to the Participant::P_SOUND_LEVEL property. This property can have integer values ranging from 0 to 10.

The customized Participant class would looks like this:

class MyParticipant : public Participant
{
public:
  typedef DRef<MyParticipant, Participant> Ref;
  typedef DRefs<MyParticipant, Participant> Refs;
  MyParticipant(unsigned int oid, SERootObject* root) : Participant(oid, root) {};
  void OnChange(int prop);
};

void MyParticipant::OnChange(int prop)
{
  if (prop == Participant::P_SOUND_LEVEL)
  {
    SEString name;
    uint soundLevel;
    GetPropIdentity(name);
    GetPropSoundLevel(soundLevel);
    printf("%s sound level = %d\n", (const char*)name, soundLevel);
  };
};

Note that this property change event fires only for remote participants - you will not get those events for your own voice activities.

To keep the MyParticipant::OnChange event firing reliably - you will need to have a list of references for your live conversation. Having just a MyConversation::Ref in your code for your live conversation is not enough. Participants objects in that conversation object may still get garbage collected. In case of this particular example, we are lucky - we are already retrieving the list of participants in our MyConversation::PickUpCall - to have it nicely printed out on screen. The only thing we need there is to keep that list of participants for entire duration of the call. Hence having the MyParticipant::Refs callerList - placed within MyConversation class.

Now onto selecting audio devices. Note that this part is not actually mandatory for picking up calls. However, it may happen that the SkypeKit audio engine fails to pick correct audio devices for you, so it makes sense to cover this topic here as well.

Firstly, there are three audio devices that the SkypeKit audio engine is interested in - output (speakers/headsets), input (microphone) and the PCM (or notification device). That one is basically an output device as well. It is used for various notification sounds, such as ringtones and other audible notification sounds. Having it separately enables you to have phone ringing on a different device from normal headset.

On your machine, there are two sorts of audio devices: audio in (microphones) and audio out (playback).

To query lists of those devices, there are two Skype class methods: GetAvailableOutputDevices and GetAvailableRecordingDevices. Both these return three SEStringLists.

After you have these lists, you can set all active audio devices for SkypeKit with just one command: Skype::SelectSoundDevices-

In code, this would look approximately like this:

SEStringList outHandles, outNames, outProductIDs;
SEStringList inHandles, inNames, inProductIDs;

printf("** Playback devices:\n");
skype->GetAvailableOutputDevices (outHandles, outNames, outProductIDs);
for (uint i = 0; i < outHandles.size(); i++)
{
  printf("%4d. %s\n", i, (const char*)outNames[i]);
};

// Getting a list of audio input devices.
printf("\n** Recording devices:\n");
skype->GetAvailableRecordingDevices (inHandles, inNames, inProductIDs);
for (uint i = 0; i < inHandles.size(); i++)
{
    printf("%4d. %s\n", i, (const char*)inNames[i]);
};

uint inputDeviceIdx   = 0;
uint outputDeviceIdx  = 0;

skype->SelectSoundDevices(
  inHandles[inputDeviceIdx], 
  outHandles[outputDeviceIdx], 
  outHandles[outputDeviceIdx]);

Full code of this example

/****************************************************************************

Getting Started With SkypeKit. Tutorial Application, Step 5.

In this step of the tutorial, we finally get around to voice calls. We'll write a small SkypeKit client that
  * waits for incoming calls
  * picks them up
  * if there's audio coming in from any of the remote parties, prints audio volume levels

**/

#include "skype-embedded_2.h"
#include "keypair.h"
#include "tutorial_common.h"

using namespace Sid;

SEString myAccountName;
SEString myAccountPsw;

//----------------------------------------------------------------------------
// Interface section

class MyParticipant : public Participant
{
public:
  typedef DRef<MyParticipant, Participant> Ref;
  typedef DRefs<MyParticipant, Participant> Refs;
  MyParticipant(unsigned int oid, SERootObject* root) : Participant(oid, root) {};

  void OnChange(int prop);
};

class MyConversation : public Conversation
{
public:
  typedef DRef<MyConversation, Conversation> Ref;
  typedef DRefs<MyConversation, Conversation> Refs;  
  MyConversation(unsigned int oid, SERootObject* root) : Conversation(oid, root) {};

  MyParticipant::Refs callerList;

  void OnChange(int prop);
  void PickUpCall();
};

class MySkype : public Skype
{
public:
  bool isLiveSessionUp;
  MyConversation::Ref liveSession;
  MySkype();

  Account*      newAccount(int oid) { return new MyAccount(oid, this); };
  Participant*  newParticipant(int oid) { return new MyParticipant(oid, this); };
  Conversation* newConversation(int oid) { return new MyConversation(oid, this); };

    void OnConversationListChange(
        const ConversationRef &conversation,
        const Conversation::LIST_TYPE &type,
        const bool &added);
};

MySkype* skype;

//----------------------------------------------------------------------------
// Implementation section

void MyParticipant::OnChange(int prop)
{
  if (prop == Participant::P_SOUND_LEVEL)
  {
    SEString name;
    uint soundLevel;
    GetPropIdentity(name);
    GetPropSoundLevel(soundLevel);
    printf("%s sound level = %d\n", (const char*)name, soundLevel);
  };
};

void MyConversation::PickUpCall()
{
  // CallerList we keep in Conversation class - so that it won't get garbage collected
  // while the conversation object exists in the wrapper. 
  this->GetParticipants(callerList, Conversation::OTHER_CONSUMERS);
  SEString partListAsString = "";
  for (uint i = 0; i < callerList.size(); i++)
  {
    partListAsString = partListAsString 
      + " " 
      + (const char*)callerList[i]->GetProp(Participant::P_IDENTITY);
  };

  // re-loading with filter ALL, so that we get our own 
  // Participant::P_SOUND_LEVEL updates as well
  this->GetParticipants(callerList, Conversation::ALL);
  fetch(callerList);


  if (!skype->isLiveSessionUp)
  {
    printf("Incoming call from: %s \n", (const char*)partListAsString);
    this->JoinLiveSession();
  }
  else
  {
    printf("Another call is coming in from : %s \n", (const char*)partListAsString);
    printf("As we already have a live session up, we will reject it. \n");
    this->LeaveLiveSession(true);
  };
};

void MyConversation::OnChange(int prop)
{
  if (prop == Conversation::P_LOCAL_LIVESTATUS)
  {
    Conversation::LOCAL_LIVESTATUS liveStatus;
    this->GetPropLocalLivestatus(liveStatus);
    if (liveStatus == Conversation::RINGING_FOR_ME)
    {
      printf("RING RING..\n");
      printf("Picking up call from MyConversation::OnChange\n");
      this->PickUpCall();
    };

    if (liveStatus == Conversation::IM_LIVE)
    {
      // Saving the currently live conversation reference..
      skype->liveSession = this->ref();
      printf("Live session is up.\n");
    };

    if ((liveStatus == Conversation::RECENTLY_LIVE) || (liveStatus == Conversation::NONE))
    {
      printf("Call has ended..\n");
      skype->isLiveSessionUp = false;
    };
  };
};

//----------------------------------------------------------------------------
// Subclassing Skype

MySkype::MySkype() : Skype() 
{ 
  isLiveSessionUp   = false;
  liveSession       = 0;
};

void MySkype::OnConversationListChange(
        const ConversationRef &conversation,
        const Conversation::LIST_TYPE &type,
        const bool &added)
{
  if (type == Conversation::LIVE_CONVERSATIONS)
  {
    Conversation::LOCAL_LIVESTATUS liveStatus;
    conversation->GetPropLocalLivestatus(liveStatus);
    SEString liveStatusAsString = tostring(liveStatus);
    printf("OnConversationListChange : %s\n", (const char*)liveStatusAsString);

    if (liveStatus == Conversation::RINGING_FOR_ME)
    {
      printf("RING RING..\n");
      printf("Picking up call from MySkype::OnConversationListChange\n");
      // Saving the currently live conversation reference..
      liveSession = conversation->ref();
      liveSession.fetch();
      liveSession->PickUpCall();
    };
  };
};

//----------------------------------------------------------------------------
// Main

int main(int argc, char * argv[])
{
  printf("*****************************************************************\n");
  printf(" SkypeKit Tutorial, Step 5. - Answering incoming audio calls.\n");
  printf("*****************************************************************\n");

  if (argc < 3)
  {
    printf("usage: tutorial_5 <skypename> <password> [mic idx] [playback idx]\n");
    return 0;
  };

  myAccountName   = argv[1];
  myAccountPsw    = argv[2];

  uint inputDeviceIdx   = 0;
  uint outputDeviceIdx  = 0;
  if (argc > 3) { inputDeviceIdx = atoi(argv[3]); };
  if (argc > 4) { outputDeviceIdx = atoi(argv[4]); };

  printf("Creating skype ..\n");
  skype = new MySkype();

  printf("Submitting application token..\n");
  getKeyPair ();
  skype->init(keyBuf, inetAddr, portNum, "streamlog.txt");
  skype->start();

  printf("Retrieving account ..\n");
  MyAccount::Ref account;

  if (skype->GetAccount(myAccountName, account))
  {
    printf("Logging in..\n");
    account->LoginWithPassword(myAccountPsw, false, true);
    account->BlockWhileLoggingIn();

    if (account->loggedIn)
    {
      printf("Loggin complete.\n");

      int recentlyLiveTimeout;
      skype->GetInt(SETUPKEY_RECENTLY_LIVE_TIMEOUT, recentlyLiveTimeout);
      printf("Conversation recently live timeout: %d seconds.\n", recentlyLiveTimeout);
    
      // Alternative way to get around having to pick up calls 
      // from two different callbacks:
      //skype->SetInt(SETUPKEY_RECENTLY_LIVE_TIMEOUT, 0);
    
      // ** Setting up audio devices **
      // First, getting a list of audio devices. There are two separate commands:
      // Skype::GetAvailableOutputDevices and Skype->GetAvailableRecordingDevices
      // Both retrurn three SEStringLists.
      // 1. First of them is list of device handles. These can be used as arguments
      // to other audio related commands, such as Skype::SelectSoundDevices
      // 2. Second list will contain descriptive device name.
      // 3. Third list contains product IDs, which can be in most cases safely ignored.

      SEStringList outHandles, outNames, outProductIDs;
      SEStringList inHandles, inNames, inProductIDs;

      printf("** Playback devices:\n");
      skype->GetAvailableOutputDevices (outHandles, outNames, outProductIDs);
      for (uint i = 0; i < outHandles.size(); i++)
      {
        printf("%4d. %s\n", i, (const char*)outNames[i]);
      };

      // Getting a list of audio input devices.
      printf("\n** Recording devices:\n");
      skype->GetAvailableRecordingDevices (inHandles, inNames, inProductIDs);
      for (uint i = 0; i < inHandles.size(); i++)
      {
          printf("%4d. %s\n", i, (const char*)inNames[i]);
      };

      skype->SelectSoundDevices(
        inHandles[inputDeviceIdx], 
        outHandles[outputDeviceIdx], 
        outHandles[outputDeviceIdx]);

      skype->SetSpeakerVolume(100);

      printf("Now accepting incoming calls..\n");
      printf("Press ENTER to quit.\n");
      getchar();

      printf("Logging out..\n");
      account->Logout(false);
      account->BlockWhileLoggingOut();
      printf("Logout complete.\n");
    };
  }
  else
  {
    printf("Account does not exist\n");
  };

  printf("Cleaning up.\n");
  skype->stop();
  delete skype;
  printf("Done.\n");
  return 0;
};
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

(c) Skype Technologies S.A. Confidential/Proprietary

Last updated: Fri Jan 27 2012