Tutorial – Project 4

In this tutorial we will see how to create a simple recorder for our calls. The project’s base is very similar to project three , we just need to make a few changes.

Once again we will have a softphone that allows to start and to stop an audio call, and in addiction we will be able to record our conversations, choosing among 3 options: in the first case we want to save what we hear during the conversation, in the second what we say, in the third both.

First of all we have to change the graphical interface of our phone, so it will become as follow:

screenshot

It contains three buttons, a text box where to put the SIP destination address and an option button with three possible choices. Only one of these radio buttons can be checked at one time, since they are mutually exclusive. Names of the buttons are btnCall, btnHangup, btnRec. Radio buttons are called rbtn_IN, rbtn_OUT, rbtn_BOTH, and they are used to indicates which audio flow the user wants to record.

So, let’s start to examine the constructor method, which is very similar to that shown in the last project we have developed

Recorder::Recorder(QWidget *parent, Qt::WFlags flags)
    : QWidget(parent, flags), call_on(false), rec_on(false)
{
    setupUi(this);

    vdk = new VDKQtEngine();
    vdk->init();
    vdk->setActiveSipAccount(1);

    micId = vdk->getDefaultAudioInputDevice();
    spkId = vdk->getDefaultAudioOutputDevice();

    connect(vdk,SIGNAL(sigCallEstablished(int)),this,SLOT(onCallEstablished(int)));
    connect(vdk,SIGNAL(sigCallFinished(int)),this,SLOT(onCallFinished(int)));

    connect(btnCall,SIGNAL(clicked()),this,SLOT(btnCallClicked()));
    connect(btnHangup,SIGNAL(clicked()),this,SLOT(btnHangupClicked()));
    connect(btnRec,SIGNAL(clicked()),this,SLOT(btnRecClicked()));

    btnCall->setEnabled(true);
    btnHangup->setEnabled(false);
    btnRec->setEnabled(false);

    rbtn_IN->setChecked(true);

}

This time we use two flags, call_on and rec_on, respectively indicating that a call is established and that a recording is in progress.

Like in previous examples, we setup the graphical interface, then we declare and initialize a new VDKQtEngine object, and activate the first account of our vdk.ini configuration; after that, we store the identifiers for input and output audio devices in micId and spkId variables, then we make the necessary connections between signals and corresponding slots.

Finally we set the initial GUI state, by enabling only btnCall and checking the rbtn_IN, as the default choice for recording will be to save only the input audio stream.

Let’s see how the btnCallClick() method is implemented:

void Recorder::btnCallClicked()
{

	voipLineId = vdk->startAudioCall(destAddress->text().toAscii().data());

	call_on=true;

	btnHangup->setEnabled(true);
	btnCall->setEnabled(false);
}

When the user clicks on the button, the softphone starts a new audio call, directed to the destination SIP address specified inside the destAddress text box, and stores the value of the line that will be kept busy during the call in the voipLineId variable; this value, as already seen in the previous tutorials, is returned by vdk’s method startAudioCall. After that we set the call_on flag and we disable btnCall, enabling btnHangup instead.

When the called user accepts to start the conversation, vdk emits sigCallEstablished and the slot onCallEstablished is executed. The method takes as its argument the value of the network line on which the call is established.

void
Recorder::onCallEstablished(int line)
{
	vdk->connectDeviceToLine(micId,line);
	vdk->connectLineToDevice(line,spkId);
	btnRec->setEnabled(true);
}

With this method we start the media streams: first we connect the microphone to the line, letting our voice reach the remote party, then we do the same for remote party’s voice. After that we also enable btnRec, because from this moment on we will be able to record a conversation.

Let’s examine what btnRecClicked() does when invoked from user’s click on btnRec.

void Recorder::btnRecClicked(){

    if((!rec_on)){
        qDebug("Starting a new recording");
        fileOutId=vdk->getFileOutputDevice("rec_file.wav");

        if(rbtn_IN->isChecked())
            vdk->connectLineToDevice(voipLineId,fileOutId);

        else if(rbtn_OUT->isChecked())
            vdk->connectDevices(micId,fileOutId);

        else if(rbtn_BOTH->isChecked()){
            vdk->connectDevices(micId,fileOutId);
            vdk->connectLineToDevice(voipLineId,fileOutId);
        }

        rbtn_IN->setEnabled(false);
        rbtn_OUT->setEnabled(false);
        rbtn_BOTH->setEnabled(false);

        btnRec->setText("Stop");
        rec_on=true;
    }
    else if(rec_on){
        qDebug("Stopping recording");

        if(rbtn_IN->isChecked())
            vdk->disconnectLineFromDevice(voipLineId,fileOutId);

        else if (rbtn_OUT->isChecked())
            vdk->disconnectDevices(micId,fileOutId);

        else if (rbtn_BOTH->isChecked())
        {
            vdk->disconnectLineFromDevice(voipLineId,fileOutId);
            vdk->disconnectDevices(micId,fileOutId);
        }

        btnRec->setText("Record");

        rbtn_IN->setEnabled(true);
        rbtn_OUT->setEnabled(true);
        rbtn_BOTH->setEnabled(true);

        rbtn_IN->setChecked(true);
        rec_on=false;
    }

}

If we didn’t start any recording, rec_on variable is still unset, and the first part of the code is executed. First of all we open a new WAV audio file, we call it rec_file.wav and we associate to it the deviceId_t fileOutId with getFileOutputDevice method.

Then there are three different possibilities, based on which radio button has been chosen by the user. In particular:

  • If rbtn_IN is checked, we call the connectLineToDevice method with arguments the network line on which the conversation is taking place, and rec_file.wav. In this way we begin to record the input audio stream coming from the line on selected file.
  • If rbtn_OUT is checked, connectDevices method is called, using as arguments the microphone device and the device associated to output file rec_file.wav.
  • if rbtn_BOTH was selected, the application records input and output audio on the same WAV file.

We then change btnRec’s text in “Stop” to make our GUI coherent with the application state and we set the rec_on flag.

We also disable all the radio buttons, because we do not want that an user accidentally check them during a recording.

Now let’s see what happens when an user clicks on btnRec while a recording is going on: in this case rec_on flag has already been set, so the second part of method is executed. In this case we are stopping a recording that was previously started.

Again, we have to consider three different situations, based on which audio flow we were recording with our application.

  • If rbtn_IN is checked, we were recording the input from the line, so we have to stop the recording by calling vdk’s disconnectLineFromDevice method, with arguments voipLineId and the deviceId of the file fileOutId.
  • If rbtn_OUT is checked we have to make the disconnection between the microphone device and the device associated to rec_file.wav.
  • If rbtn_BOTH is checked we have to make both the disconnections described above.

Before exiting, we set again btnRec’s text to “Record” and we unset rec_on variable to come back to the original state; moreover, we check the rbtn_IN radio button, because this is the default choice for recording.

Our application is almost done, we only need to analyze what happens when an established call is stopped. Once again, there are more than one possible scenario: in fact we can choose to stop an established audio call by clicking on btnHangup, or the remote client could decide to interrupt the call before us.

When we decide to stop the call btnHangupClicked() slot is executed.

void Recorder::btnHangupClicked()
{
    vdk->abortCall(voipLineId);

    vdk->disconnectDeviceFromLine(micId,voipLineId);
    vdk->disconnectLineFromDevice(voipLineId,spkId);

    btnCall->setEnabled(true);
    btnHangup->setEnabled(false);
    btnRec->setEnabled(false);

    if(rec_on)
        btnRecClicked();

    call_on=false;
}

First of all we stop the call using vdk’s abortCall method, with argument the value of the network line on which the call was placed. We use abortCall method because we do not know if the call on line voipLineId was already established. After that we disconnect our audio input from the line, by calling the disconnectDeviceFromLine method, and in the opposite direction, the line from our speakers, by invoking the disconnectLineFromDevice method.

We enable the btnCall button, disabling the others and we unset the call_on flag. Anything else? Yes, we must consider if we are in the middle of a recording: in this case we have also to stop it and to disconnect the file device. This is done in the last lines of code:

    if(rec_on)
	btnRecClicked();

In this way, we execute the second part of the btnRecClicked() method, disconnecting the convenient devices and unsetting rec_on flag.

Let’s finally consider what happens when the the client on the opposite side of line stops the call. In this case vdk emits sigCallFinished and slot onCallFinished is invoked.

void
        Recorder::onCallFinished(int line)
{

    vdk->disconnectDeviceFromLine(micId,line);
    vdk->disconnectLineFromDevice(line,spkId);

    btnCall->setEnabled(true);
    btnHangup->setEnabled(false);
    btnRec->setEnabled(false);

    if(rec_on)
        btnRecClicked();

    call_on=false;
}

As previously seen, we disconnect devices from the network line, then we enable btnCall button while disabling other buttons and we unset call_on flag. If rec_on flag is set, we also execute btnRecClicked() method, as shown above.

We are now ready to start our recordings!