Microsoft DirectX 9.0

Step 3: Implement the Frame-Grabbing Function

The next step is to implement the GetBitmap function, which uses the Media Detector to grab a poster frame. Inside this function, perform the following steps:

  1. Create the Media Detector.
  2. Specify a media file.
  3. Examine each stream in the file. If it is a video stream, get the native dimensions of the video.
  4. Obtain a poster frame, specifying the seek time and the target dimension. 

Create the Media Detector object by calling CoCreateInstance:

CComPtr<IMediaDet> pDet;
hr = pDet.CoCreateInstance(__uuidof(MediaDet));

Specify a file name, using the IMediaDet::put_Filename method. This method takes a BSTR parameter.

hr = pDet->put_Filename(bstrFilename);

Get the number of streams, and loop through each stream checking the media type. The IMediaDet::get_OutputStreams method retrieves the number of streams, and the IMediaDet::put_CurrentStream method specifies which stream to examine. Exit the loop on the first video stream.

long lStreams;
bool bFound = false;
hr = pDet->get_OutputStreams(&lStreams);
for (long i = 0; i < lStreams; i++)
{
    GUID major_type;
    hr = pDet->put_CurrentStream(i);
    hr = pDet->get_StreamType(&major_type);
    if (major_type == MEDIATYPE_Video)  // Found a video stream.
    {
        bFound = true;
        break;
    }
}
if (!bFound) return VFW_E_INVALIDMEDIATYPE;

If no video stream was found, the function exits.

In the previous code, the IMediaDet::get_StreamType method returns just the major type GUID. This is convenient if you do not need to examine the complete media type. To get the video dimensions, however, it is necessary to examine the format block, so the full media type is needed. You can retrieve that by calling the IMediaDet::get_StreamMediaType method, which fills in an AM_MEDIA_TYPE structure. The Media Detector converts all video streams into uncompressed format, with a VIDEOINFOHEADER format block.

long width = 0, height = 0; 
AM_MEDIA_TYPE mt;
hr = pDet->get_StreamMediaType(&mt);
if (mt.formattype == FORMAT_VideoInfo) 
{
    VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)(mt.pbFormat);
    width = pVih->bmiHeader.biWidth;
    height = pVih->bmiHeader.biHeight;
    
    // We want the absolute height, and don't care about up-down orientation.
    if (height < 0) height *= -1;
}
else {
    return VFW_E_INVALIDMEDIATYPE; // This should not happen, in theory.
}
FreeMediaType(mt);

The get_StreamMediaType method allocates the format block, which the caller must free. This example uses the FreeMediaType function from the base class library.

Now you are ready to get the poster frame. First call the IMediaDet::GetBitmapBits method with a NULL pointer for the buffer:

long lSize;
hr = pDet->GetBitmapBits(0, &lSize, NULL, width, height);

This call returns the required buffer size in the lSize parameter. The first parameter specifies the stream time to seek to; this example uses time zero. For the width and height, this example uses the native video dimensions obtained earlier. If you specify other values, the Media Detector will stretch the bitmap to match.

If the method succeeds, allocate the buffer and call GetBitmapBits again:

if (SUCCEEDED(hr)) 
{
    char *pBuffer = new char[lSize];
    if (!pBuffer) return E_OUTOFMEMORY;
    hr = pDet->GetBitmapBits(0, NULL, pBuffer, width, height);
    if (FAILED(hr))
        delete [] pBuffer;
}

Here is the complete function, with somewhat better error handling.

HRESULT GetBitmap(LPTSTR pszFileName, BITMAPINFOHEADER** ppbmih)
{
    HRESULT hr;
    CComPtr<IMediaDet> pDet;
    hr = pDet.CoCreateInstance(__uuidof(MediaDet));
    if (FAILED(hr)) return hr;

    // Convert the file name to a BSTR.
    CComBSTR bstrFilename(pszFileName);
    hr = pDet->put_Filename(bstrFilename);
    if (FAILED(hr)) return hr;

    long lStreams;
    bool bFound = false;
    hr = pDet->get_OutputStreams(&lStreams);
    if (FAILED(hr)) return hr;

    for (long i = 0; i < lStreams; i++)
    {
        GUID major_type;
        hr = pDet->put_CurrentStream(i);
        if (SUCCEEDED(hr))
        {
            hr = pDet->get_StreamType(&major_type);
        }
        if (FAILED(hr)) break;

        if (major_type == MEDIATYPE_Video)
        {
            bFound = true;
            break;
        }
    }
    if (!bFound) return VFW_E_INVALIDMEDIATYPE;

    long width = 0, height = 0; 
    AM_MEDIA_TYPE mt;
    hr = pDet->get_StreamMediaType(&mt);
    if (SUCCEEDED(hr)) 
    {
        if ((mt.formattype == FORMAT_VideoInfo) && 
            (mt.cbFormat >= sizeof(VIDEOINFOHEADER)))
        {
            VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)(mt.pbFormat);
            width = pVih->bmiHeader.biWidth;
            height = pVih->bmiHeader.biHeight;
        
            // We want the absolute height, don't care about orientation.
            if (height < 0) height *= -1;
        }
        else
        {
            hr = VFW_E_INVALIDMEDIATYPE; // Should not happen, in theory.
        }
        FreeMediaType(mt);
    }
    if (FAILED(hr)) return hr;
    
    // Find the required buffer size.
    long size;
    hr = pDet->GetBitmapBits(0, &size, NULL, width, height);
    if (SUCCEEDED(hr)) 
    {
        char *pBuffer = new char[size];
        if (!pBuffer) return E_OUTOFMEMORY;
        try {
            hr = pDet->GetBitmapBits(0, NULL, pBuffer, width, height);
            if (SUCCEEDED(hr))
            {
                // Delete the old image, if any.
                if (*ppbmih) delete[] (*ppbmih);
                (*ppbmih) = (BITMAPINFOHEADER*)pBuffer;
            }
            else
            {
                delete [] pBuffer;
            }
        }
        catch (...) {
            delete [] pBuffer;
            return E_OUTOFMEMORY;
        }
    }
    return hr;
}