Image and AVI Capture This programming example shows how to capture AVI files and JPEG images simultaneously using IC Imaging Control.
Language: | Visual C++
Version: | 3.3
Author: | IC Imaging Control Support Department
Requirements: |
Software: | IC Imaging Control 3.3, Visual Studio™ 2010
Hardware: | Camera, converter or grabber with WDM Stream Class drivers.
Download sample | Image and AVI Capture - VC++

This problem is solved using a special frame filter that saves images to JPEG files. The frame filter is included in the sample's setup file. Following functionality is implemented in the sample:

- Open and setup a video capture device using IC Imaging Control's built-in dialogs.
- Save the currently used video capture and load the last used video capture device at program start.
- Resize the live video to IC Imaging Control's window size.
- List all available codecs in a combo box.
- If available, then show the codec's property dialog.
- Pause and continue AVI Capture.
- Snap and save images while AVI files are captured.

Prerequisites

For displaying live video streams and handling video capture devices, the application needs a Grabber object.

    DShowLib::Grabber        m_cGrabber;

For enabling AVI capture, a Media Stream Sink is needed in the application.

    DShowLib::tMediaStreamSinkPtr m_pAVISink;

Resizing the live video display

The live video should be displayed in a CStatic object named m_cStaticVideoWindow. It should also be resized to the size of m_cStaticVideoWindow. Thus the size of m_cStaticVideoWindow must be queried first by a call to m_cStaticVideoWindow.GetClientRect(&rect). To allow the Grabber to resize the live video display, the property setDefaultWindowPosition must be set to false. Then the width and hight of the previously querried rect are passed to the Grabber's method setWindowSize.

    CRect rect;
    m_cStaticVideoWindow.GetClientRect( &rect);
    m_cGrabber.setDefaultWindowPosition(false);
    m_cGrabber.setWindowSize(rect.Width(), rect.Height());

Device handling

The application tries to reload and open the last used video capture device in the OnInitDialog method of the dialog. The Grabber class provides the method loadDeviceStateFromFile, which loads a previously saved XML file. It returns true, if the video capture device, that has been saved in the file, could have been successfully loaded and opened.

    if( m_cGrabber.loadDeviceStateFromFile( "device.xml"))
    {
        // Display the device's name in the caption bar of the application.
        SetWindowText( "Image and AVI Capture " + CString(m_cGrabber.getDev().c_str()));
    }

The application contains buttons for video capture device selection, setting the device properties and start or stop the live video stream. If the video device selection button is clicked, the application stops a probably running live video first. Then the built-in video capture device selection dialog of IC Imaging Control is shown by a call to showDevicePage. After this property dialog has been closed, the application checks, whether a valid video capture device has been selected. If a valid video capture device has been selected, its current settings are saved to a file using the method saveDeviceStateToFile. Thus the current video capture device can be loaded automatically next time the application is started.

    void CImageandAVICaptureDlg::OnBnClickedButtondevice()
    {
        // If live video is running, stop it.
        if(m_cGrabber.isDevValid() && m_cGrabber.isLive())
        {
            m_cGrabber.stopLive();
        }

        m_cGrabber.showDevicePage(this->m_hWnd);

        // If we have selected a valid device, save it to the file "device.xml", so
        // the application can load it automatically when it is started the next time.
        if( m_cGrabber.isDevValid())
        {
            m_cGrabber.saveDeviceStateToFile("device.xml");
        }

        // Now display the device's name in the caption bar of the application.
        SetWindowText( "Image and AVI Capture " + CString(m_cGrabber.getDev().c_str()));

        SetButtonStates();
    }

The video capture device's properties can simply be adjusted using the IC Imaging Control built-in video capture device property dialog. A call to showVCDPropertyPage shows this dialog. After the video capture device property dialog has been closed, the current properties of the video capture device are saved again.

    void CImageandAVICaptureDlg::OnBnClickedButtonimagesettings()
    {
        if( m_cGrabber.isDevValid())
        {
            m_cGrabber.showVCDPropertyPage(this->m_hWnd);
            m_cGrabber.saveDeviceStateToFile("device.xml");
        }
    }

The button for starting and stopping the live video stream calls the Grabber's method isLive. If this methods returns true the live video stream is to be stopped, otherwise it is to be started.

However, if this button is clicked, the sink mode of the Media Stream Sink m_pAVISink is set to ePAUSE. If the live video stream is started, the video can be seen, but it will not be captured until the sink mode is set to eRUN. For this issue, the application contains an extra checkbox.

    void CImageandAVICaptureDlg::OnBnClickedButtonlivevideo()
    {
        if( m_cGrabber.isDevValid())
        {
            if (m_cGrabber.isLive())
            {
                m_cGrabber.stopLive();
            }
            else
            {
                m_cGrabber.startLive();
            }
            SetButtonStates();
            m_cCheckAVIPause.SetCheck(1);
            m_pAVISink->setSinkMode( GrabberSinkType::ePAUSE );
        }
    }

Loading the frame filter

The dialog class of the application contains a private member m_pSaveImageFilter:

    smart_com<DShowLib::IFrameFilter> m_pSaveImageFilter;

This member holds the object instance that will be used to access the "Save Image" frame filter. In the OnInitDialog method of the dialog the frame filter is loaded using a call to FilterLoader::createFilter("Save Image");. If the frame filter was loaded successfully, it is passed to the Grabber object in the device path position.

    // Try to load the "Save Image" frame filter.
    m_pSaveImageFilter = FilterLoader::createFilter("Save Image");
    // Check wether the filter could be loaded successfully.
    if( m_pSaveImageFilter == NULL)
    {
        MessageBox("Failed to load the \"Save Image\" frame filter!","Error",MB_OK|MB_ICONEXCLAMATION);
    }
    else
    {
        // The frame filter has been loaded successfully. Now it should be added
        // to the Grabber object.
        if( !m_cGrabber.setDeviceFrameFilters( m_pSaveImageFilter.get()) )
        {
            MessageBox("Failed to add the \"Save Image\" frame filter to the grabber!","Error",MB_OK|MB_ICONEXCLAMATION);
        }
    }

While the live video runs, the "Save Image" frame filter can be used to snap images and save them into a JPEG file. Since the application runs in another thread than the live video stream, the methods beginParamTransfer and endParamTransfer must be called. In between these two calls parameters of the frame filter can be set.

The "Save Image" frame filter exposes a parameter "ImageName" which can be set by a call to setParameter. If an image file name is passed to this parameter, the "Save Image" frame filter will snap the next incoming frame and save it.

    void CImageandAVICaptureDlg::OnBnClickedButtonsnapimage()
    {
        if( m_pSaveImageFilter != NULL && m_cGrabber.isDevValid() && m_cGrabber.isLive())
        {
            char szImageFileName[MAX_PATH+1];
            m_iImageCounter++;
            sprintf(szImageFileName,"Image%03d.jpg",m_iImageCounter);

            m_pSaveImageFilter->beginParamTransfer();
            // Setting the parameter "ImageName" of the filter causes the filter to
            // snap the next incoming image and save it to a file.
            m_pSaveImageFilter->setParameter("ImageName",std::string(szImageFileName));
            m_pSaveImageFilter->endParamTransfer();
        }
    }

Listing the codecs and setup the Media Stream Sink

Since this demo application should capture AVI files, codec handling must be added. The dialog class contains a member m_CodecListPtr that will point to the list of currently available codecs:

    DShowLib::tCodecListPtr m_CodecListPtr;

The dialog class has a method FillCodecComboBox that queries the available codecs and lists them in the combo box m_ComboCodecs. In this method the first available codec is selected and the Media Stream Sink is created with this codec. If this codec has a property page, the button m_cButtonCodecProperties will be enabled. This allows the user to call the property dialog of the codec. At the end of the method FillCodecComboBox the strings for uncompressed AVI capture are added to the combo box m_ComboCodecs.

    void CImageandAVICaptureDlg::FillCodecComboBox()
    {
        m_CodecListPtr = Codec::getAvailableCodecs();
        unsigned int i = 0;
        for(  i = 0; i < m_CodecListPtr->size();i++ )
        {
            m_ComboCodecs.AddString( m_CodecListPtr->at(i)->getName().c_str());
            if( i ==  0 ) // Select the first codec.
            {
                m_ComboCodecs.SetCurSel(i);
                m_pAVISink = MediaStreamSink::create(     MediaStreamContainer::create( MSC_AviContainer ), m_CodecListPtr->at(i));

                // If the currently selected codec has a dialog, the codec properties button will be enabled.
                if(m_CodecListPtr->at(i)->hasDialog())
                    m_cButtonCodecProperties.EnableWindow(true);
                else
                    m_cButtonCodecProperties.EnableWindow(true);
            }
        }

        // Add the uncompressed formats.
        m_ComboCodecs.AddString("Y800");
        m_ComboCodecs.AddString("RGB24");
        m_ComboCodecs.AddString("RGB32");
    }

If a codec is selected in the combo box m_ComboCodecs, the Media Stream Sink m_pAVISink must be created new with this codec. First of all, a probably running live video stream must be stopped. Then the m_pAVISink's member of the application's dialog class is set to null, which deletes the current Media Stream Sink. In the next step the text of the combo box m_ComboCodecs is evaluated. If it is one of the "manually" added uncompressed video formats, the regarding media type must be passed to MediaStreamContainer::create. Otherwise the element in the list m_CodecListPtr at position iIndex is passed to MediaStreamContainer::create. iIndex is the index of the currently selected codec in the combo box m_ComboCodecs. Thus the combo box m_ComboCodecs should not sort its entries!

    void CImageandAVICaptureDlg::OnCbnSelchangeCombocodecs()
    {
        int iIndex = m_ComboCodecs.GetCurSel();
        bool bRestartLive = false;
        if( iIndex > -1 )
        {
            CString szCodec;
            m_ComboCodecs.GetLBText(iIndex,szCodec);

            if( m_cGrabber.isLive())
            {
                m_cGrabber.stopLive();
                bRestartLive = true;
            }

            m_pAVISink = NULL;
            GetDlgItem(IDC_BUTTONCODECPROPERTIES)->EnableWindow(false);
            if( szCodec == "Y800")
                m_pAVISink = MediaStreamSink::create(     MediaStreamContainer::create( MSC_AviContainer ), MEDIASUBTYPE_Y800 );
            else
                if( szCodec == "RGB24")
                    m_pAVISink = MediaStreamSink::create(     MediaStreamContainer::create( MSC_AviContainer ), MEDIASUBTYPE_RGB24 );
                else
                    if( szCodec == "RGB32")
                        m_pAVISink = MediaStreamSink::create(     MediaStreamContainer::create( MSC_AviContainer ), MEDIASUBTYPE_RGB32 );
                    else
                    {
                        m_pAVISink = MediaStreamSink::create(     MediaStreamContainer::create( MSC_AviContainer ), m_CodecListPtr->at(iIndex));
                        if(m_pAVISink->getCodec()->hasDialog())
                        {
                            GetDlgItem(IDC_BUTTONCODECPROPERTIES)->EnableWindow(true);
                        }
                    }

            CString FileName;
            m_cEditAVIName.GetWindowText(FileName);
            m_pAVISink->setFilename((LPCSTR)FileName);

            m_pAVISink->setSinkMode( GrabberSinkType::ePAUSE );
            m_cCheckAVIPause.SetCheck(1);

            m_cGrabber.setSinkType( m_pAVISink );

            if( bRestartLive)
            {
                m_cGrabber.startLive();
            }
        }
    }

The new created Media Stream Sink m_pAVISink must be passed to the Grabber object by a call to m_cGrabber.setSinkType( m_pAVISink );. If this is missing, only the live video will be shown and image capture is possible, but no AVI file would be captured. At least the OnCbnSelchangeCombocodecs performs some more functions like passing the file name of the AVI file to the Media Stream Sink and restarting the live video stream with paused AVI capture.