Yahoo!Coder's Cookbook menu left background
Yahoo!Coder's Cookbook menu right background
YCC Cam Cap 1.0.1

Description:
YCC Cam Cap is a program that automatically saves both inbound and outbound Yahoo! Messenger webcam streams to an AVI file.
Language:
Visual Basic.NET 2005
Screenshot:
YCC Cam Cap screenshot
Virus Scan:

Introduction

YCC Cam Cap is a program that automatically saves both inbound and outbound Yahoo! Messenger webcam streams to an AVI file. YCC Cam Cap performs this by taking individual screen shots of the Yahoo! Messenger webcam control and saving them into a video file on your hard drive. YCC Cam Cap is not selective in the webcam streams it captures but saves any webcam control located on the desktop. This process is carried out automatically behind the scenes so that when a new webcam window is opened, a new file is created and when a webcam window is closed the file is finalized. As long as you have a powerful enough machine, YCC Cam Cap is capable of saving an almost unlimited number of webcam windows at the same time and in real time.

Quick Start

  1. Press Start Capture and once a Yahoo! Messenger webcam window opens, it will be captured.

Rational and Background

I was inspired to write this project from MSN Webcam Recorder which can be found at http://ml20rc.msnfanatic.com/index.html. The MSN Webcam Recorder runs as a background application that passively sniffs the network for MSN webcam traffic. When a webcam conversation is detected, a file is created and saved with that packet data. YCC Cam Cap works differently from MSN Webcam Recorder but to the user produces the same results.

Early on in the design process I had to make a decision to either capture data by packets or screen captures. Each has its advantages and disadvantages. Packet capture is the most efficient means to make sure your recoding is an accurate representation of the actual stream. In this approach you would sniff the Ethernet traffic for a Yahoo! webcam stream and then save the raw data into useful data. The downside to this is you would have to completely reverse engineer the webcam protocol and use third-party utilities to gather the data. Another drawback to this approach was only found after I started investigating webcam packets. If the user sending data (My Webcam) does not have any viewers then no actual picture data is generated. Instead, statistical data that the webcam is ready to accept viewers is sent. This means that if you have your cam on and displaying pictures, if no one is viewing that data can not be captured. 

The second approach involves taking screen shots of the webcam control and then saving this series of pictures to a file. The advantage to this approach is an easier program design as there are many examples of screen capture utilities available. The disadvantages to this include system performance, stream representation, and the many quarks of Windows. First of all is the stream representation. Without looking at the packets coming form the Yahoo! server, you can not guess the timing or rate of the stream. To compensate for this, more data than is needed is pumped into the capture file. Simply put, you make more screen captures per second than is needed. Another consideration is how Windows makes screen captures. Most screen capture APIs will simply capture an area on the screen where the control is located. If another window happens to be in the way or “clips” the control window then the other application window will also be captured. Starting with Windows XP, Microsoft introduced a new API called PrintWindow. This API captures the desired window no matter where it is on the screen and no mater if it is covered by another window. Another consideration is what happens when the control is minimized. Unfortunately there is no API that can still capture this window. When a window is minimized, the operating system stops sending PAINT messages to the window. Even if an API were to capture the window, it would only get the last image and would not update the images until the window is once again set to normal. With PrintWindow all you get is a black square where the control should be when it is minimized. This would not be a problem with the packet system. The last consideration is system performance. The majority of processing is done when the picture is converted and compressed into an AVI file. This process would occur no matter which approach is used. On the other hand, making pictures from packets is slightly more efficient than making pictures from screen captures.

In the end I decided to go with the screen capture approach. I have previously written screen capture programs so the learning curve would be easier. Even though capturing packets has many more advantages, not being able to capture frames when no was watching the webcam was a deal breaker. I feel that if the cam is displaying pictures, it is better to get those pictures than wait for someone to connect, if they connect at all. Since I am using screen capture I am still left with one major disadvantage, YCC Cam Cap will not be able to capture data when the webcam control is minimized.

Design

EnumWindows

The first step in capturing screen shots is finding the window that you want to capture. The first candidate for the job was the FindWindow API (http://msdn2.microsoft.com/en-us/library/ms633499.aspx). The usage for for FindWindow is very straight forward and easy but I found that it has one fatal flaw. FindWindow will only return one window handle and if you have more than one webcam window then you are just out of luck. Another API called EnumWindows (http://msdn2.microsoft.com/en-us/library/ms633497.aspx) does fit the bill but is more complex because it requires a callback function and it returns all current window handles.

    EnumWindows(New EnumWindowsCallback(AddressOf filterEnum), 0)

In my case the callback function is filterEnum and it does as the name implies, filters the results given by EnumWindows. I check the class name and window text of each window. In the case of YCC Cam Cap, you need to capture a child window of the main webcam window so EnumChildWindows is called for each preliminary result.

    Private Function filterEnum(ByVal hWnd As IntPtr, ByVal lParam As Integer) As Boolean
         Dim sbWindowText As New StringBuilder(STRING_BUFFER_LENGTH)
         Dim sbClassName As New StringBuilder(STRING_BUFFER_LENGTH)
         GetWindowText(hWnd, sbWindowText, STRING_BUFFER_LENGTH)
         GetClassName(hWnd, sbClassName, STRING_BUFFER_LENGTH)

         Dim strWindowText As String = sbWindowText.ToString
         Dim strClassName As String = sbClassName.ToString

         'Match partial class name
         If strClassName.ToLower.IndexOf(_strClassName.ToLower) <> -1 Then
              'Match partial window title
              If strWindowText.ToLower.IndexOf(_strWindowText.ToLower) <> -1 Then
                   'Match visiable windows
                   If _bListOnlyVisable = True Then
                        If IsWindowVisible(hWnd) = True Then
                        'go to childern
                        If _bEnumChild = True Then
                             _strParentWindowText = strWindowText
                             getEnumChildWindows(hWnd)
                        Else
                             RaiseEvent ReturnProcess(hWnd, strWindowText)
                        End If

                    End If
              Else
                   If
_bEnumChild = True Then
                        _strParentWindowText = strWindowText
                        getEnumChildWindows(hWnd)
                   Else
                        RaiseEvent ReturnProcess(hWnd, strWindowText)
                   End If
              End If
         End If
    End If
    Return True
    End Function

Capture Setup

Once we know that there is a webcam window to capture, we need to create a new thread for the capture and AVI file. Since it is possible to capture muptiple windows at once I created a structure to hold the variables needed for each window. The structure is called CaptureInfo and holds such information as the window width and height, handle, AVI class, timer, and various capture objects.

    Public Structure CaptureInfo
         Dim hWindow As IntPtr
         Dim iWidth As Integer
         Dim iHeight As Integer
         Dim bWindow As Bitmap
         Dim AVIInstance As AVICapture.AVICapture
         Dim strSavePath As String
         Dim strFileName As String
         Dim uAVICompressOpt As AVICapture.AVI_APIs.AVICOMPRESSOPTIONS_CLASS
         Dim iRate As Integer
         Dim tProcess As Threading.Timer
         Dim bMyWebCam As Boolean
         Dim strWindowText As String
         Dim bLastGood As Bitmap
         Dim gWindow As Graphics
         Dim GraphicsHdc As IntPtr
         Dim bPrintResult As Integer
         Dim iFrameCount As Integer
         Dim oLock As Object
    End Structure

To keep each CaptureInfo object from being disposed of prematurly I also add it to a global collection called _cActiveProcesses. 

During setup all the variables are assigned to the proper CaptureInfo field. The width and height are calculated by the GetWindowRect API.

    'Find the dimensions of the window
    Dim rectWindow As SC_APIs.RECT
    SC_APIs.GetWindowRect(hWindow, rectWindow)
    cInfo.iWidth = rectWindow.Right - rectWindow.Left
    cInfo.iHeight = rectWindow.Bottom - rectWindow.Top

A new bitmap object, bWindow, is created to hold the screen capture. Since creating a new bitmap is very resource intensive and we will be constantly using this bitmap, it is saved in the structure.

    'Create the bitmap
    Dim bWindow As New Bitmap(cInfo.iWidth, cInfo.iHeight)
    cInfo.bWindow = bWindow

The name of the AVI is generated from the webcam name and saved to strFileName. The rate of capture saved from the main form. by default this is three frames per second (fps) because the average Yahoo! webcam usually never exceeds this rate. Increasing this rate may capture a few more frames of information over a long amount of time but the processing and file space needed is cost prohibitive. The next step is to grab a first frame for double buffering which I will explain later. The AVICapture object is then set with the desired CODEC.

    If _uAVICompressOpt Is Nothing Then
         cInfo.AVIInstance = New AVICapture.AVICapture(cInfo.strSavePath, cInfo.iRate, cInfo.iWidth, cInfo.iHeight)
    Else
         cInfo.uAVICompressOpt = _uAVICompressOpt
         cInfo.AVIInstance = New AVICapture.AVICapture(cInfo.strSavePath, cInfo.iRate, cInfo.iWidth, cInfo.iHeight, _uAVICompressOpt)
    End If

Lastly a new timer is created and started.

    Dim iTimerRate As Integer = CInt((1 / cInfo.iRate) * 1000)
    cInfo.tProcess = New System.Threading.Timer(AddressOf CaptureTimer, cInfo, 0, iTimerRate)
    _cActiveProcesses.Add(cInfo, CStr(hWindow))

Timer

By default the timer will fire three times every second. This is needed to ensure the AVI file has the proper number of frames and run time. As discussed later, AVICapture uses Video for Windows (VfW) which does not have an internal clock. This means that if the rate is set to 3 fps and you add three frames, you will only get a one second movie no matter of the rate that the frames were actually added. In other words, frame 1 is added, frame 2 added three seconds later, and frame 3 added five minutes after that, you will only get a one second movie.

PrintWindow

The rational of using PrintWindow has already been discussed in the background section so I will not take much time on it.

    Private Function PrintWindowTimer(ByVal cInfo As CaptureInfo) As Boolean
         SyncLock cInfo.oLock
              Try
                   If cInfo.hWindow <> Nothing Then
                        cInfo.gWindow = Graphics.FromImage(cInfo.bWindow)
                        cInfo.GraphicsHdc = cInfo.gWindow.GetHdc()
                        cInfo.bPrintResult = SC_APIs.PrintWindow(cInfo.hWindow, cInfo.GraphicsHdc, 1)
                        cInfo.gWindow.ReleaseHdc(cInfo.GraphicsHdc)
                        cInfo.gWindow.Flush()

                        If cInfo.bPrintResult <> 0 Then
                             Return True
                        Else
                             stopCapture(cInfo.hWindow)
                             Return False
                        End If
                   Else
                       Return False
                   End If

              Catch ex As Exception
                   Return False
              End Try
         End SyncLock
    End Function

Double Buffering

It is often said that 90% of your programming time is consumed by 10% of the code. This is my 10%. I found that the Yahoo! webcam control sometimes does not play nice with PrintWindow. I can only speculate to the cause of this (PAINT messages not being sent in time?) but the results are clear. Random frames are not properly rendered and you get either a black, white, or grey frame. When playing back the AVI this become very bothersome in a hurry and rapidly degrades the quality of the movie. To combat this effect I developed a two pronged attack. The first step is to test for the offending frame.

    Private Function DoubleBufferCalc(ByVal bBitmap As Bitmap) As Boolean
         'Get a variety of different pixels from the frame
         Dim iColor1 As Integer = bBitmap.GetPixel(20, 7).ToArgb
         Dim iColor2 As Integer = bBitmap.GetPixel(bBitmap.Width - 40, 40).ToArgb
         Dim iColor3 As Integer = bBitmap.GetPixel(bBitmap.Width - 80, bBitmap.Height - 80).ToArgb
         Dim iColor4 As Integer = bBitmap.GetPixel(160, bBitmap.Height - 160).ToArgb
         Dim iSum As Integer = iColor1 + iColor2 + iColor3 + iColor4

         'black screen
         If iSum = &HFC000000 Then
              Return True
         End If

         'white screen
         If iSum = &HFFFFFFFC Then
              Return True
         End If

         'grey screen
         If iSum = &HFF8B8F68 Then
              Return True
         End If
    End Function

DoubleBufferCalc checks each corner of the image at progressively greater distances. This is of course not a fool-proof check but getting the exact same color for each pixel checked is so rare that a false positive would be very rare. The sum of these four pixels are then checked to see if an offending frames is present.

    If DoubleBufferCalc(cInfo.bWindow) = True Then
         'Three trys
         If iBufferCount < 3 Then
              iBufferCount += 1
              GoTo BufferLoop
         Else
              'Replace with previous frame
              bTemp.RotateFlip(RotateFlipType.Rotate180FlipX)
              cInfo.AVIInstance.addFrame(bTemp)
              cInfo.iFrameCount += 1
         End If
    Else

         'Good frame
         cInfo.AVIInstance.addFrame(cInfo.bWindow)
         cInfo.iFrameCount += 1
    End If

Now that we know we have a bad frame it is given three chances to retry PrintWindow. I have found that you have a 50/50 chance of getting a successful frame here. It is possible to continuously poll PrintWindow until a good frame is found but that would take up an amazing amount of processor time and we want to write the frame before the next timer event fires. The second, last, and least desirable option is to simply write the previous frame again. Throughout the capture process bLastGood has been updated with the last valid frame. We write bLastGood to the AVI and keep going. While viewing the AVI this shows up as a brief pause in the webcam motion but is much less noticeable than having a black frame. It is necessary to write a frame every time the timer fires or you will end up with an incorrect run time.

You will also notice several odd lines.

    Dim bTemp As New Bitmap(cInfo.bLastGood)  

and

    bTemp.RotateFlip(RotateFlipType.Rotate180FlipX) 

This is because bitmaps are reference object and are passed throughout the system as pointers. I had a very hard time getting a consistent valid image out of bLastGood so I ended up making a new bitmap object to hold it. The Rotate180FlipX is needed because an identical operation is performed in AVICapture and this is used to undo it.

AVICapture

AVICapture is a specialized purpose class that I created to create, write, and compress single frames to an AVI. As stated before AVICapture uses Video for Windows (VfW) (http://msdn2.microsoft.com/en-us/library/ms713492.aspx). It is implemented with great help from “A Simple C# Wrapper for the AviFile Library” by Corinna John which can be found at http://www.codeproject.com/cs/media/avifilewrapper.asp.

AVICapture is also open to the public with source and can be found at http://ycoderscookbook.com/code/AVICapture.html.

Bugs and Gottchas

  • Unable to capture while the webcam control is minimized
  • Black frames will be recorded into the AVI file
  • Some CODECs will fail. This is due to a design glitch in the underlying AVI creation code. If you select a CODEC that is not compatible with YCC Cam Cap, you will be prompted to enter a new CODEC

Requirements

Changes

Version 1.0.1

  • Added donate in about

License Agreement

The software, YCC Yahoo! Cam Cap, is distributed under the Creative Commons GNU General Public License which can be found at http://creativecommons.org/licenses/GPL/2.0/ The major points follow:

  1. No commercial distribution without permission.
  2. You are allowed to modify the program and source, all I ask is you keep the original credit.
  3. If you do modify the program it will fall under the same license agreement.

I have chosen this license to allow users to do pretty much what they want with the program. The big thing that I ask is please don't redistribute this software without acknowledgment to YCC and a link back to http://ycoderscookbook.com. I would love to hear what you think about the program and if you have made useful changes. A forum for YCC Cam Cap can be found at http://ycoderscookbook.com/forums/viewforum.php?f=12 under the Programs directory.

As with any piece of freeware, I have made every effort to make the software useable and friendly. With that said I take no responsibility for damage created by this program or the user’s actions. You may be violating the Yahoo! Terms of Service by using this program. The user takes full responsibility for their actions.

Source and Executible

Full source code is provided. Please feel free to modify YCC Cam Cap as long as you read and understand the license agreement.

YCC Yahoo! Cam Cap Executiable (.exe)

YCC Yahoo! Cam Cap Source

Search Yahoo! Coder's Cookbook via Google search
Last Modified:
Visitors: