Recording Skype calls. Part 1. Skype API and sound processing

Skype logoRecording Skype calls may be very useful, especially when we discuss some business questions or, for example, approaches related to the project issues. Also listening of records may help to improve knowledge of English language, correct mistakes.

I decided to try to develop application that will use Skype API and record conversation to the MP3-file.

I spent few evenings and figured out one interesting thing about Skype: it is able to record sound without any additions! All we need is to send Skype API command that will redirect sound channels to the files! Of course, there are some limitations:

  • the format of redirected sound is WAV,
  • Skype redirect channels separately from microphone and from speakers.

So on the output we have two huge files (since WAV doesn’t have compression), which is inconvenient to store and listen.

To create Skype recorder we should write some wrapper for Skype API and “class-handler” that will send and receive messages from Skype.

It is also possible to use official Skype4COM library instead of implementing own wrapper, but it was interesting for me to do it myself.

Here is the small tutorial how to use Skype API.

 
Connecting to Skype and “subscribing” to events

First of all, we need to register discover and attach Windows API messages since Skype uses them for communication:

[DllImport("user32.dll")]
public static extern uint RegisterWindowMessage(string message);

skypeApiDiscover = RegisterWindowMessage("SkypeControlAPIDiscover");
skypeApiAttach = RegisterWindowMessage("SkypeControlAPIAttach");

Now, to attach to the Skype, we need to send attachment request via Windows API broadcast message. As a parameter we must provide discover message ID skypeApiDiscover and window handle that will receive further messages from the Skype.

[DllImport("user32.dll")]
private static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint msg, IntPtr wParam,
    ref CopyDataStruct lParam, uint flags, uint timeout, out IntPtr result);

SendMessageTimeout(new IntPtr(-1), skypeApiDiscover, ourWindowHandle,
    IntPtr.Zero, IntPtr.Zero, 100, out result)

Skype will show prompt for attachment request:
Attachment request

After clicking one of the buttons Skype sends Window API message to the window which handle we have specified. The window must contain WndProc in order to process message.

public IntPtr WndProc(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    // ...
}

As an attachment result Skype sends message with message == skypeApiAttach and lParam == 0 (in case of success; see other codes in the Skype API documentation). Parameter wParam contains Skype window handle that we will use to send commands.
If attachment was done then WndProc will start receiving messages for all Skype events. To filter them we can check that message is WM_COPYDATA and wParam == skypeWindowHandle. The information about event is stored in the lParam (COPYDATASTRUCT structure). In case of using COPYDATASTRUCT to receive messages from Skype, we can implement it in such way:

[StructLayout(LayoutKind.Sequential)]
internal struct CopyDataStruct
{
    public string Id;
    public int Size;
    public string Data;
}

Field Data contains Skype message data. For example:

CALL 1234 STATUS INPROGRESS

where 1234 – is the unique ID of the call. It must be used in the other Skype commands to have control over this conversation.

 
Sending commands to Skype

For sending certain Skype command we need to prepare CopyDataStruct and send it by using SendMessageTimeout Windows API function:

[DllImport("user32.dll")]
private static extern IntPtr SendMessageTimeout(IntPtr windowHandle, uint message,
    IntPtr wParam, ref CopyDataStruct lParam, SendMessageTimeoutFlags flags,
    uint timeout, out IntPtr result);

private void sendSkypeCommand(string command)
{
    var data = new CopyDataStruct { Id = "1", Size = command.Length + 1, Data = command };
    IntPtr result;
    SendMessageTimeout(skypeWindowHandle, WM_COPYDATA, ourWindowHandle, ref data,
        SendMessageTimeoutFlags.Normal, 100, out result);
}

For example, if we want to redirect current conversation sound to the file, we should use two Skype commands:

  • ALTER CALL {0} SET_OUTPUT FILE="{1}" – redirecting speakers sound, that we hear from our conversation partner (in Skype terms);
  • ALTER CALL {0} SET_CAPTURE_MIC FILE="{1}" – redirecting our microphone, what we say to our conversation partner.

The fragments of the code that send these commands:

public void RedirectSoundToFile(string inFileName, string outFileName)
{
    var recordInCommand = string.Format("ALTER CALL {0} SET_OUTPUT FILE=\"{1}\"",
        currentCallNumber, inFileName);
    var recordOutCommand = string.Format("ALTER CALL {0} SET_CAPTURE_MIC FILE=\"{1}\"",
        currentCallNumber, outFileName);

    sendSkypeCommand(recordInCommand);
    sendSkypeCommand(recordOutCommand);
}

Note, that after redirection we will continue hearing conversation partner and microphone will work. Furthermore, different application can send redirection simultaneously and Skype will handle each separately.

 
Skype API issues

I figured out that in order to develop application that will automatically detect Skype presence and have an ability to reconnect, we need to invent some workarounds for the few Skype API issues:

  1. We don’t know when Skype starts since we don’t get API messages without connection. As a workaround we can wait for Skype.exe process, but here is the second issue.
  2. Skype API works only on the main screen, when we are logged in. The first login screen will not “answer” to our requests. So, to get to know when exactly we can try to connect, we should wait definite window (for example, check its presence by window class name via FindWindow or set global shell hook).
  3. Skype will not send any API messages if we simply close it (not logged out) or kill the process. In that case our application will still think that it is connected. Furthermore, if Skype will be started again, we will not receive any messages. We must explicitly reconnect by sending new request.

 
The main steps to write a simple recorder

  • Implement wrapper for Skype API that will allow sending and receiving messages, or use official library.
  • Implement some kind of connector that will process received messages and watch for Skype presence.
  • Implement application settings (filters, black-list, etc.).
  • Implement some logic that will make a decision how to react on Skype events depends on settings.
  • Implement post-processing for redirected sound files.

 
Post-processing for redirected sound files

Skype saves sound “channels” separately in WAV format. To get MP3 file we need to:

  1. Merge files into one file.
  2. Convert WAV to MP3.

The easiest way is to use some external tool. For example, we can use such free open-source solutions like SoX and LAME.

SoX allows merging, converting and applying various effects to sound files.
To merge two WAV files we can simply create a process with next parameters:

sox.exe -m FirstInputWavFile SecondInputWavFile OutputWavFile

LAME allows very fast and high-quality converting of WAV-file to MP3. To convert merged WAV-file to MP3 we should execute LAME with parameters:

lame.exe -V2 InputWavFile OutputMp3File

Switch -V sets encoding quality.

 
That’s all. In the second part I will tell about SkypeAutoRecorder – my open-source application for recording Skype calls. All code examples here were taken from it. Full code is available at CodePlex.

3 thoughts on “Recording Skype calls. Part 1. Skype API and sound processing

  1. I am attempting to record the Skype incoming video from a call. Skype doesn’t use Direct Show, so there is no way to gain access though those means. I believe the API doesn’t give video access. So what approach would be possible?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s