Использование примера Grabber

[Функция, связанная с этой страницей DirectShow, является устаревшей функцией. Он был заменен MediaPlayer, IMFMediaEngine, и аудио/ видео захвата в Media Foundation. Эти функции оптимизированы для Windows 10 и Windows 11. Корпорация Майкрософт настоятельно рекомендует, чтобы новый код использовал MediaPlayer, IMFMediaEngine и аудио- и видеозахват в Media Foundation вместо DirectShow, когда это возможно. Корпорация Майкрософт предлагает переписать существующий код, использующий устаревшие API, чтобы по возможности использовать новые API.]

[Этот API не поддерживается и может быть изменен или недоступен в будущем.]

Фильтр Sample Grabber — это фильтр преобразования, который можно использовать для захвата примеров мультимедиа из потока при их прохождении через граф фильтра.

Если вы просто хотите получить растровое изображение из видеофайла, проще использовать объект Media Detector (MediaDet). Дополнительные сведения см. в разделе Захват рамки плаката . Однако пример Grabber является более гибким, так как он работает практически с любым типом мультимедиа (см. раздел ISampleGrabber::SetMediaType для нескольких исключений) и предоставляет больше возможностей управления для приложения.

Создание диспетчера графов фильтров

Для начала создайте диспетчер графов фильтров и запросите интерфейсы IMediaControl и IMediaEventEx .

    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEventEx *pEvent = NULL;


    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
        CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
    if (FAILED(hr))
    {
        goto done;
    }

Добавление примера Grabber в граф фильтров

Создайте экземпляр фильтра Sample Grabber и добавьте его в граф фильтра. Запросите фильтр Sample Grabber для интерфейса ISampleGrabber .

    IBaseFilter *pGrabberF = NULL;
    ISampleGrabber *pGrabber = NULL;


    // Create the Sample Grabber filter.
    hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&pGrabberF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
    if (FAILED(hr))
    {
        goto done;
    }

Установка типа носителя

При первом создании примера Grabber он не имеет предпочтительного типа мультимедиа. Это означает, что вы можете подключиться практически к любому фильтру в графе, но не будете контролировать тип полученных данных. Поэтому перед построением остальной части графа необходимо задать тип носителя для Sample Grabber, вызвав метод ISampleGrabber::SetMediaType .

Когда пример Grabber подключается, он сравнивает этот тип мультимедиа с типом мультимедиа, предлагаемым другим фильтром. Единственными полями, которые проверяются, являются основной тип, подтип и тип формата. Для любого из них значение GUID_NULL означает "принять любое значение". В большинстве случаев требуется задать основной тип и подтип. Например, следующий код указывает несжатое 24-разрядное видео RGB:

    AM_MEDIA_TYPE mt;
    ZeroMemory(&mt, sizeof(mt));
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;

    hr = pGrabber->SetMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

Создание графа фильтров

Теперь можно создать остальную часть графа фильтра. Так как пример Grabber будет подключаться только с использованием указанного типа мультимедиа, это позволяет использовать при сборке графа преимущества интеллектуального подключения диспетчера фильтров графа.

Например, если вы указали несжатое видео, можно подключить исходный фильтр к примеру Grabber, и диспетчер графа фильтров автоматически добавит средство синтаксического анализа файлов и декодер. В следующем примере используется вспомогающая функция ConnectFilters, которая указана в разделе Подключить два фильтра:

    IBaseFilter *pSourceF = NULL;
    IEnumPins *pEnum = NULL;
    IPin *pPin = NULL;


    hr = pGraph->AddSourceFilter(pszVideoFile, L"Source", &pSourceF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSourceF->EnumPins(&pEnum);
    if (FAILED(hr))
    {
        goto done;
    }

    while (S_OK == pEnum->Next(1, &pPin, NULL))
    {
        hr = ConnectFilters(pGraph, pPin, pGrabberF);
        SafeRelease(&pPin);
        if (SUCCEEDED(hr))
        {
            break;
        }
    }

    if (FAILED(hr))
    {
        goto done;
    }

Пример Grabber является фильтром преобразования, поэтому выходной контакт должен быть подключен к другому фильтру. Часто вы можете просто отменить выборки после завершения работы с ними. В этом случае подключите sample Grabber к фильтру отрисовщика NULL, который отменяет получаемые данные.

В следующем примере выполняется подключение sample Grabber к фильтру отрисовщика null:

    IBaseFilter *pNullF = NULL;


    hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, 
        IID_PPV_ARGS(&pNullF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pNullF, L"Null Filter");
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConnectFilters(pGraph, pGrabberF, pNullF);
    if (FAILED(hr))
    {
        goto done;
    }

Имейте в виду, что размещение примера Grabber между декодером видео и отрисовщиком видео может значительно снизить производительность отрисовки. Пример Grabber является фильтром на месте, что означает, что выходной буфер совпадает с входным буфером. Для отрисовки видео выходной буфер, скорее всего, будет расположен на графическом карта, где операции чтения выполняются гораздо медленнее по сравнению с операциями чтения в main памяти.

Запуск графа

Пример Grabber работает в одном из двух режимов:

  • Режим буферизации создает копию каждого образца перед доставкой нижестоящего примера.
  • Режим обратного вызова вызывает определяемую приложением функцию обратного вызова для каждого примера.

В этой статье описывается режим буферизации. (Прежде чем использовать режим обратного вызова, имейте в виду, что функция обратного вызова должна быть довольно ограничена. В противном случае это может значительно снизить производительность или даже привести к взаимоблокировкам. Дополнительные сведения см. в разделе ISampleGrabber::SetCallback.) Чтобы активировать режим буферизации, вызовите метод ISampleGrabber::SetBufferSamples со значением TRUE.

При необходимости вызовите метод ISampleGrabber::SetOneShot со значением TRUE. Это приводит к остановке sample Grabber после получения первого примера носителя, что полезно, если вы хотите получить один кадр из потока. Найдите нужное время, запустите граф и дождитесь события EC_COMPLETE . Обратите внимание, что уровень точности кадра зависит от источника. Например, поиск mpeg-файла часто не является точным кадром.

Чтобы запустить граф как можно быстрее, отключите графовые часы, как описано в разделе Настройка часов графа.

В следующем примере включается однократный режим и режим буферизации, выполняется граф фильтра и ожидается завершение.

    hr = pGrabber->SetOneShot(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetBufferSamples(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pControl->Run();
    if (FAILED(hr))
    {
        goto done;
    }

    long evCode;
    hr = pEvent->WaitForCompletion(INFINITE, &evCode);

Захват примера

В режиме буферизации sample Grabber хранит копию каждого образца. Метод ISampleGrabber::GetCurrentBuffer копирует буфер в массив, выделенный вызывающим объектом. Чтобы определить необходимый размер массива, сначала вызовите Метод GetCurrentBuffer с указателем NULL для адреса массива. Затем выделите массив и вызовите метод во второй раз, чтобы скопировать буфер. Следующий пример показывает эти шаги.

    // Find the required buffer size.
    long cbBuffer;
    hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
    if (!pBuffer) 
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

Необходимо знать точный формат данных в буфере. Чтобы получить эти сведения, вызовите метод ISampleGrabber::GetConnectedMediaType . Этот метод заполняет структуру AM_MEDIA_TYPE форматом .

Для несжатого видеопотока сведения о формате содержатся в структуре VIDEOINFOHEADER . В следующем примере показано, как получить сведения о формате для несжатого видеопотока.

    // Examine the format block.
    if ((mt.formattype == FORMAT_VideoInfo) && 
        (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
        (mt.pbFormat != NULL)) 
    {
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;

        hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader, 
            mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
    }
    else 
    {
        // Invalid format.
        hr = VFW_E_INVALIDMEDIATYPE; 
    }

Примечание

Пример Grabber не поддерживает VIDEOINFOHEADER2.

 

Пример кода

Ниже приведен полный код для предыдущих примеров.

Примечание

В этом примере функция SafeRelease используется для освобождения указателей интерфейса.

 

#include <windows.h>
#include <dshow.h>
#include "qedit.h"

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}



HRESULT WriteBitmap(PCWSTR, BITMAPINFOHEADER*, size_t, BYTE*, size_t);

HRESULT GrabVideoBitmap(PCWSTR pszVideoFile, PCWSTR pszBitmapFile)
{
    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEventEx *pEvent = NULL;
    IBaseFilter *pGrabberF = NULL;
    ISampleGrabber *pGrabber = NULL;
    IBaseFilter *pSourceF = NULL;
    IEnumPins *pEnum = NULL;
    IPin *pPin = NULL;
    IBaseFilter *pNullF = NULL;

    BYTE *pBuffer = NULL;

    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
        CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the Sample Grabber filter.
    hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&pGrabberF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pGrabberF, L&quot;Sample Grabber&quot;);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
    if (FAILED(hr))
    {
        goto done;
    }

    AM_MEDIA_TYPE mt;
    ZeroMemory(&mt, sizeof(mt));
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;

    hr = pGrabber->SetMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddSourceFilter(pszVideoFile, L&quot;Source&quot;, &pSourceF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSourceF->EnumPins(&pEnum);
    if (FAILED(hr))
    {
        goto done;
    }

    while (S_OK == pEnum->Next(1, &pPin, NULL))
    {
        hr = ConnectFilters(pGraph, pPin, pGrabberF);
        SafeRelease(&pPin);
        if (SUCCEEDED(hr))
        {
            break;
        }
    }

    if (FAILED(hr))
    {
        goto done;
    }

    hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, 
        IID_PPV_ARGS(&pNullF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pNullF, L&quot;Null Filter&quot;);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConnectFilters(pGraph, pGrabberF, pNullF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetOneShot(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetBufferSamples(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pControl->Run();
    if (FAILED(hr))
    {
        goto done;
    }

    long evCode;
    hr = pEvent->WaitForCompletion(INFINITE, &evCode);

    // Find the required buffer size.
    long cbBuffer;
    hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
    if (!pBuffer) 
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->GetConnectedMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

    // Examine the format block.
    if ((mt.formattype == FORMAT_VideoInfo) && 
        (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
        (mt.pbFormat != NULL)) 
    {
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;

        hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader, 
            mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
    }
    else 
    {
        // Invalid format.
        hr = VFW_E_INVALIDMEDIATYPE; 
    }

    FreeMediaType(mt);

done:
    CoTaskMemFree(pBuffer);
    SafeRelease(&pPin);
    SafeRelease(&pEnum);
    SafeRelease(&pNullF);
    SafeRelease(&pSourceF);
    SafeRelease(&pGrabber);
    SafeRelease(&pGrabberF);
    SafeRelease(&pControl);
    SafeRelease(&pEvent);
    SafeRelease(&pGraph);
    return hr;
};

// Writes a bitmap file
//  pszFileName:  Output file name.
//  pBMI:         Bitmap format information (including pallete).
//  cbBMI:        Size of the BITMAPINFOHEADER, including palette, if present.
//  pData:        Pointer to the bitmap bits.
//  cbData        Size of the bitmap, in bytes.

HRESULT WriteBitmap(PCWSTR pszFileName, BITMAPINFOHEADER *pBMI, size_t cbBMI,
    BYTE *pData, size_t cbData)
{
    HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL, 
        CREATE_ALWAYS, 0, NULL);
    if (hFile == NULL)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    BITMAPFILEHEADER bmf = { };

    bmf.bfType = &#39;MB&#39;;
    bmf.bfSize = cbBMI+ cbData + sizeof(bmf); 
    bmf.bfOffBits = sizeof(bmf) + cbBMI; 

    DWORD cbWritten = 0;
    BOOL result = WriteFile(hFile, &bmf, sizeof(bmf), &cbWritten, NULL);
    if (result)
    {
        result = WriteFile(hFile, pBMI, cbBMI, &cbWritten, NULL);
    }
    if (result)
    {
        result = WriteFile(hFile, pData, cbData, &cbWritten, NULL);
    }

    HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32(GetLastError());

    CloseHandle(hFile);

    return hr;
}

Использование служб редактирования DirectShow