Write C++ program to make DirectShow capture video

  • 2020-05-09 18:58:01
  • OfStack

Video captures the construction of Graph
An graph diagram that can capture audio or video is called a capture graph diagram. Capturing an graph diagram is much more complex than playing back an graph diagram with a 1-like file, and dshow provides an Capture Graph Builder COM component to make capturing an graph diagram easier to generate. Capture Graph Builder provides an ICaptureGraphBuilder2 interface that provides methods to build and control the capture of graph.
First create 1 Capture Graph Builder object and 1 graph manger object, then use filter graph manager as the parameter and call ICaptureGraphBuilder2::SetFiltergraph to initialize Capture Graph Builder. Take a look at the following code:


HRESULT InitCaptureGraphBuilder(IGraphBuilder **ppGraph,      //Receives the pointer 
                ICaptureGraphBuilder2 **ppBuilder)           //Receives the pointer 
{ 
  if(!ppGraph || !ppBuilder) 
  { 
    return E_POINTER; 
  } 
 
  IGraphBuilder *pGraph = NULL; 
  ICaptureGraphBuilder2 *pBuild = NULL; 
  //Create the Capture Graph Builder 
  HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,  
                    CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2,  
                    (void**)&pGraph); 
 
  if(SECCEEDED(hr)) 
  { 
    //Create the Filter Graph Manager 
    hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,  
                    IID_IGraphBuilder, (void**)&pGraph); 
    if(SECCEEDED(hr)) 
    { 
      //Initialize the Capture Graph Builder 
      pBuild->SetFiltergraph(pGraph); 
      //Return both interface pointers to the caller 
      *ppBuild = pBuild; 
      *ppGraph = pGraph;   //The caller must release both interface 
      return S_OK; 
    } 
    else  
    { 
      pBuild->Release(); 
    } 
  } 
  return hr;     //Failed 
} 

 
Video capture device
Many new video capture devices now use the WDM driver method. In the WDM mechanism, Microsoft provides a driver independent of the hardware device, called a class driver. The driver provided by the driver vendor is called minidrivers. Minidrivers provides functions that deal directly with the hardware, in which the class driver is called.
In directshow's filter diagram, any WDM capture device appears as an WDM Video Capture filter (Filter). The WDM Video Capture filter builds its own filter based on the characteristics of the driver
 

Direcshow video capture of Filter Pin types

Capture Filter1 generally has two or more output pin, and they all output the same media type, such as preview pin and capture pin, so it is not very good to distinguish these pin according to the media type. Now you have to distinguish each pin according to the function of pin, and each pin has an GUID, which is called pin.
For a closer look at the types of pin, see Working with Pin Categories below. For most applications, ICaptureGraphBuilder2 provides functions that automatically determine the type of pin.
Preview pin and capture pin

Video capture Filter both provide preview and capture output pin, preview pin to display the video stream on screen, and capture pin to write the video stream to a file.

The differences between preview pin and output pin are as follows:
1 to ensure that pin is captured on video frame traffic, preview pin can be stopped when necessary.
2 the video frames captured by pin are timestamped, but the video streams previewed by pin are not.

The reason for the lack of timestamp is that the filter chart manager adds a small latency to the video stream. If the capture time is considered to be render time, the video renderFilter will consider the video stream to have a small delay. If render filter tries to play continuously, it will lose its frame. Remove the timestamp to ensure that the video frame can be played when it comes, without waiting or losing the frame.

Preview the pin class GUID for PIN_CATEGORY_PREVIEW The catch pin species GUID is PIN_CATEGORY_CAPTURE

Video Port pin
Video Port is a hardware device between a video device (TV) and a video card. With Video Port, video data can be sent directly to the image card, and video can be displayed directly on the screen through hardware overlays. Video Port connects two devices.
The biggest benefit of using Video Port is that the video stream is written directly into memory without any of CPU's work.
If the capture device USES Video Port, capture Filter with one video port pin instead of preview pin.

The type of video port pin GUID is PIN_CATEGORY_VIDEOPORT

One capture filter has at least one Capture pin, in addition, it may have one preview pin and one video port pin, or neither. Maybe filter has a lot of capture pin, and one preview pin. Each pin represents one media type, so one filter can have one video capture pin, video preview pin, Audio capture pin, audio preview pin.

Upstream WDM Filters
On top of the capture Filter, the WDM device may require an additional filters, and here are the filter

TV Tuner Filter TV Audio Filter. Analog Video Crossbar Filter

Although these are separate filter, they may represent the same hardware device. Each filter controls a different function of the device. These filter are connected through pin, but there is no data flow in pin. Therefore, these pin connections are independent of the media type. They use one GUID value to define minidriver for a given device, for example: TV tuner Filter and video capture filter both support the same medium.

In practice, if you use ICaptureGraphBuilder2 to create your capture graphs, these filters will be automatically added to your graph. For more details, see WDM Class Driver Filters.

Select 1 video capture device (Select capture device)

To select a video capture device, system equipment enumeration can be used. Please refer to Using the System Device Enumerator for details. enumerator can return the monikers of a device according to the type of filter. Moniker is an com object, see SDK for IMoniker.

For capture devices, the following two categories are related.

CLSID_AudioInputDeviceCategory audio equipment CLSID_VideoInputDeviceCategory video device

The following code demonstrates how to enumerate a video capture device


ICreateDevEnum *pDevEnum = NULL; 
IEnumMoniker *pEnum = NULL; 
 
//Create the system device enumerator 
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
               CLSCT_INPROC_SERVER, IID_ICreateDevEnum,  
               reinterpret_cast<void**>(&pDevEnum)); 
 
if(SUCCEEDED(hr)) 
{ 
  // create 1 Enumerator, enumerate video devices  
  hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,  
                    &pEnum, 0); 
} 

 
IEnumMoniker interface pEnum returns a list of 1 IMoniker interfaces, representing series 1 moniker. You can display all the devices and let the user select one.
Using the IMoniker::BindToStorage method, an IPropertyBag interface pointer is returned. Then call IPropertyBag::Read to read moniker's properties. Here's a look at what properties are included:

1 FriendlyName is the name of the device
2 the Description property applies only to the DV and D-VHS /MPEG cameras. If this property is available, this property describes the device in more detail
The property 3DevicePath is unreadable, but each device has a unique, 1, and no 2. You can use this property to distinguish between different instances of a device

The following code demonstrates how to display the name of the traversal device, following the code


HWND hList;     //Handle to the list box 
IMoniker *pMoniker = NULL; 
while(pEnum->Next(1, &pMoniker, NULL) == S_OK) 
{ 
  IPropertyBag *pPropBag; 
  hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)(&pPropBag)); 
  if(FAILED(hr)) 
  { 
    pMoniker->Release(); 
    continue;    //Skip this one, maybe the next one will work 
  } 
  VARIANT varName; 
  hr = pPropBag->Read(L"Description", &varName, 0); 
  if(FAILED(hr)) 
  { 
    hr = pPropBag->Read(L"FriendlyName", &varName, 0); 
  } 
  if(SECCEEDED(hr)) 
  { 
    //Add it to the application's list box 
    USES_CONVERSION; 
    (long)SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)OLE2T(varName.bstrVal)); 
    VariantClear(&varName); 
  } 
 
  pPropBag->Release(); 
  pMoniker->Release(); 
} 

 
If the user selects a device and calls IMoniker::BindToObject to generate filter for the device, then adds filter to graph.


IBaseFilter *pCap = NULL; 
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap); 
if(SECCEEDED(hr)) 
{ 
  hr = m_pGraph->AddFilter(pCap, L"Capture Filter"); 


To create graph that can preview the video, call the following code:


ICaptureGraphBuilder2 *pBuild;   //Capture Graph Builder 
//Initialize pBuild(not shown) 
... 
IBaseFilter *pCap;                 //Video capture filter 
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, 
                        pCap, NULL, NULL); 
} 

How to capture a video stream and save it to a file (Capture video to File)

Save the video stream to the AVI file

AVI Mux filter receives video streams from capture pin and packages them into AVI streams. Audio streams can also be connected to AVI Mux Filter, so mux filter combines video streams and video streams into AVI streams. File writer writes the AVI stream to a file.
You can build the graph diagram as follows


IBaseFilter *pMux; 
hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi,      //Specifies AVI for the target file 
                L"C:\\Example.avi",       //File name 
                &pMux,                  //Receives a pointer to the mux 
                NULL);    //(Optional)Receives a pointer to the file sink 

 
 
The first parameter indicates the type of file, in this case AVI, and the second parameter specifies the name of the file. For the AVI file, the SetOutputFileName function creates one AVI mux Filter and one File writer Filter, and adds two filter to the graph diagram. In this function, request the IFileSinkFilter interface through File Writer Filter, then call the IFileSinkFilter::SetFileName method, and set the name of the file. Then connect the two filter. The third parameter returns a pointer to AVI Mux, and it also returns an IFileSinkFilter parameter through the fourth parameter. If you don't need this parameter, you can set it to NULL.
Then, you should call the following function to connect capture filter and AVI Mux.


hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE,     //Pin category 
             &MEDIATYPE_Video,     //Media type 
             pCap,     //Capture filter 
             NULL,     //Intermediate filter(optional) 
             pMux);     //Mux or file sink filter 
//Release the mux filter 
pMux->Release(); 

The fifth argument is the pMux pointer returned by the above function.
When capturing audio, the media type should be set to MEDIATYPE_Audio. If you're capturing video and audio from two different devices, you'd better set the audio to the main stream. This will prevent drift from streaming between the two streams, since avi mux filter will synchronize the audio and adjust the speed of the video. To set the master stream, call the IConfigAviMux::SetMasterStream method with the following code:


IConfigAviMux *pConfigMux = NULL; 
hr = pMux->QueryInterface(IID_IConfigAviMux, (void**)&pConfigMux); 
if(SUCCEEDED(hr)) 
{ 
  pConfigMux->SetMasterStream(1); 
  pConfigMux->Release(); 
} 

The parameters of SetMasterStream refer to the number of data streams, which is determined by the order in which RenderStream is called. For example, if you call RenderStream first for video streaming and then for audio, then the video streaming is 0 and the audio streaming is 1.
Add the code filter


IBaseFilter *pEncoder; 
//Add it to the filter graph 
pGraph->AddFilter(pEncoder, L"Encode"); 
//Render the stream 
hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,  
             pCap, pEncoder, pMux); 
pEncoder->Release(); 

Save the video stream to an wmv file

To save and encode the video stream into a file in windows media video (WMV) format, connect capture pin to WM ASF Writer filter.

The easiest way to build an graph diagram is to specify MEDIASUBTYPE_Asf filter in the ICaptureGraphBuilder2::SetOutputFileName method. The following


IBaseFilter *pASFWriter = 0; 
hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Asf,    //Create a windows media file 
                L"C:\\VidCap.wmv",        //File name 
                &pASFWriter,       //Receives a pointer to the filter 
                NULL);        //Receives an IFileSinkFilter interface pointer(optional)

The parameter MEDIASUBTYPE_Asf tells graph builder to use wm asf writer as the file receiver, so pbuild creates this filter, adds it to the graph diagram, and then calls IFileSinkFilter::SetFileName to set the name of the output file. The third parameter is used to return an ASF writer pointer, and the fourth parameter is used to return a pointer to the file.

Before connecting any pin to WM ASF Writer, 1 must set WM ASF Writer 1. You can do this with WM ASF Writer IConfigAsfWriter interface pointer.


ICreateDevEnum *pDevEnum = NULL; 
IEnumMoniker *pEnum = NULL; 
 
//Create the system device enumerator 
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
               CLSCT_INPROC_SERVER, IID_ICreateDevEnum,  
               reinterpret_cast<void**>(&pDevEnum)); 
 
if(SUCCEEDED(hr)) 
{ 
  // create 1 Enumerator, enumerate video devices  
  hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,  
                    &pEnum, 0); 
} 
0 Then call ICaptureGraphBuilder2::RenderStream to connect capture Filter and ASF writer:

ICreateDevEnum *pDevEnum = NULL; 
IEnumMoniker *pEnum = NULL; 
 
//Create the system device enumerator 
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
               CLSCT_INPROC_SERVER, IID_ICreateDevEnum,  
               reinterpret_cast<void**>(&pDevEnum)); 
 
if(SUCCEEDED(hr)) 
{ 
  // create 1 Enumerator, enumerate video devices  
  hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,  
                    &pEnum, 0); 
} 
1

 
Save to a custom file format
If you want to save the file in your own format, you must have your own file writer. Look at the following code:


ICreateDevEnum *pDevEnum = NULL; 
IEnumMoniker *pEnum = NULL; 
 
//Create the system device enumerator 
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
               CLSCT_INPROC_SERVER, IID_ICreateDevEnum,  
               reinterpret_cast<void**>(&pDevEnum)); 
 
if(SUCCEEDED(hr)) 
{ 
  // create 1 Enumerator, enumerate video devices  
  hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,  
                    &pEnum, 0); 
} 
2

How to save video stream into multiple files
If you want to start saving the second file after saving the video stream to a file, you should first stop graph and then change the file name of File Writer via IFileSinkFilter::SetFileName. Note that the IFileSinkFilter pointer can be returned by the fourth parameter at SetOutputFileName.
Look at the code for saving multiple files:


IBaseFilter *pMux = 0; 
IFileSinkFilter *pSink = 0; 
hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi,  
                L"C:\\YourFileName.avi", &pMux, &pSink); 
if(SUCCEEDED(hr)) 
{ 
  hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, 
                        pCap, NULL, pMux); 
  if(SUCCEEDED(hr)) 
  { 
    pControl->Run(); 
    pControl->Stop(); 
    //Change the file name and run the graph again 
    pSink->SetFileName(L"YourFileName02.avi", 0); 
    pControl->Run(); 
  } 
 
  pMux->Release(); 
  pSink->Release(); 
} 


Related articles: