步骤 3:实现Frame-Grabbing函数

[与此页面关联的功能 DirectShow 是一项旧功能。 它已被 MediaPlayerIMFMediaEngine媒体基金会中的音频/视频捕获取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayerIMFMediaEngineMedia Foundation 中的音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]

[此 API 不受支持,将来可能会更改或不可用。]

本主题是 抓取海报框架的步骤 3。

下一步是实现 GetBitmap 函数,该函数使用媒体检测器抓取海报帧。 在此函数中,执行以下步骤:

  1. 创建媒体检测器。
  2. 指定媒体文件。
  3. 检查文件中的每个流。 如果是视频流,请获取视频的本机尺寸。
  4. 获取海报框架,指定寻道时间和目标尺寸。

通过调用 CoCreateInstance 创建媒体检测器对象:

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

使用 IMediaDet::p ut_Filename 方法指定文件名。 此方法采用 BSTR 参数。

hr = pDet->put_Filename(bstrFilename);

获取流的数量,并循环访问检查媒体类型的每个流。 IMediaDet::get_OutputStreams 方法检索流的数量,IMediaDet::p ut_CurrentStream 方法指定要检查的流。 退出第一个视频流的循环。

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;

如果未找到视频流,则函数将退出。

在前面的代码中, IMediaDet::get_StreamType 方法仅返回主类型 GUID。 如果不需要检查完整的媒体类型,这很方便。 但是,若要获取视频尺寸,必须检查格式块,因此需要完整的媒体类型。 可以通过调用 IMediaDet::get_StreamMediaType 方法进行检索,该方法将填充 AM_MEDIA_TYPE 结构。 媒体检测器使用 VIDEOINFOHEADER 格式块将所有视频流转换为未压缩格式。

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);

get_StreamMediaType 方法分配调用方必须释放的格式块。 此示例使用基类库中的 FreeMediaType 函数。

现在,你已准备好获取海报框架。 首先使用缓冲区的 NULL 指针调用 IMediaDet::GetBitmapBits 方法:

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

此调用返回 lSize 参数中所需的缓冲区大小。 第一个参数指定要查找的流时间;此示例使用时间零。 对于宽度和高度,此示例使用之前获取的本机视频尺寸。 如果指定其他值,媒体检测器将拉伸位图以匹配。

如果方法成功,请分配缓冲区并再次调用 GetBitmapBits

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;
}

下面是完整的函数,错误处理效果稍好一些。

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;
}

下一 步:步骤 4:在工作区上绘制位图

抓取海报框架