S>Итак, задачка старая — нужно сделать захват и encode видео в MPEG4 и звука в MP3. Все это нужно делать в риалтайме. Как это например сделано во FlyCap. Но теперь задача сделать это все через DirectShow.

Нужно построить граф и вставить в него mpeg4encoder, ну DivX, например.

Что бы говорить о конкретных деталях реализации, нужно увидеть как ты сейчас строишь граф, поэтому приведи код или скажи какой из примеров DS SDK ты используешь?

S>По правде сказать я бы не хотел использовать имеющиеся примеры, потому как не люблю реализации интерфейсов на чистом API, да и вообще организация кода примеров PSDK мне не нравится. Я скорее воспользуюсь WTL, но это не важно, так что будем считать что я использую PlayCap sample из DS SDK как самый простой. Теперь мне нужно кодировать видео через Microsoft MPEG4 V1/2 Codec, а аудио соотв MPEG3 и писать это в файл.

S>Нет, правда покажи как добавить этот самый граф и на звук тоже, а?

Ну ладно, сам напросился

Ниже будет приведен код создания графа в общем виде, он содержат вспомогательные функции:

// Функция находит фильтр по имени в system device enumerator и возвращает его.
// Параметры: [in]  CComBSTR& bstrFilterName      - имя фильтра
//            [out] CComPtr<IBaseFilter>& pFilter - найденный фильтр
//            [in]  REFCLSID clsidDeviceClass     - CLSID категории фильтра (см. DS SDK)
HRESULT GetFilter(CComBSTR& bstrFilterName, CComPtr<IBaseFilter>& pFilter, REFCLSID clsidDeviceClass)
{
    HRESULT hr;

    CComQIPtr<ICreateDevEnum, &IID_ICreateDevEnum> spSysDevEnum;
    hr = spSysDevEnum.CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC);
    if (SUCCEEDED(hr))
    {
        CComPtr<IEnumMoniker> pEnum;

        hr = spSysDevEnum->CreateClassEnumerator(clsidDeviceClass, &pEnum, 0);
        if (S_OK == hr)) // было неправильно - if ( SUCCEEDED(hr) ), см. примечание Snax
        {
            CComPtr<IMoniker> pMoniker;
            ULONG cFetched;
            // Enumerate over every category
            ATLASSERT(pEnum);
            while (SUCCEEDED(pEnum->Next(1, &pMoniker, &cFetched)))
            {
                if (!pMoniker)
                {
                    hr = E_FAIL;
                    break;
                }
                CComQIPtr<IPropertyBag> pPropBag;
                // Associate moniker with a file
                hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
                if SUCCEEDED(hr)
                {
                    CComVariant varCurFilterName;
                    varCurFilterName.vt = VT_BSTR;
                    ATLASSERT(pPropBag);
                    // Read FriendlyName from property bag
                    pPropBag->Read(L"FriendlyName", &varCurFilterName, 0);
                    if (bstrFilterName == varCurFilterName.bstrVal) // is this our filter?
                    {
                        // Create filter using IMoniker
                        pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**) &pFilter);
#ifdef DEBUG
                        FILTER_INFO fiRetrieved;
                        pFilter->QueryFilterInfo(&fiRetrieved);
                        ATLTRACE("SUCCEED: (GetFilter) The %s filter have been retreived\n", bstrFilterName);
#endif // DEBUG
                        return S_OK;
                    }
                }
                pMoniker = NULL;
            }
            ATLTRACE("FAILED: (GetFilter) The %s filter have not been retreived\n", bstrFilterName);
        }
    }

    return hr;
}


// Функция находит неподключенный пин у фильтра и возвращает его.
// Параметры: [in]  PIN_INFO* pPinInfo  - информация по которой ищется пин (см. DS SDK)
//            [out] CComPtr<IPin>& pPin - найденный пин
HRESULT GetPin(PIN_INFO* pPinInfo, CComPtr<IPin>& pPin)
{
    HRESULT hr;

    if (!pPinInfo) return E_POINTER;

    if (pPinInfo->pFilter)
    {
        CComPtr<IEnumPins> pEnumPins;
        ULONG cFetched;
        hr = pPinInfo->pFilter->EnumPins(&pEnumPins);
        ATLASSERT(pEnumPins);
        // Enumerate all pins of the filter
        while(pEnumPins->Next(1, &pPin, &cFetched) == S_OK)
        {
            PIN_INFO piPinInfo;
            // We need to get pin information to compare it with our PinInfo
            hr = pPin->QueryPinInfo(&piPinInfo);
            if (SUCCEEDED(hr))
            {
                // We compare direction of the pin if it is the same it is our
                // pin.
                // We do not need a pin which have '~' first symbol because
                // GraphBuilder do not connect pins which have this.
                if (piPinInfo.dir == pPinInfo->dir && piPinInfo.achName[0] != L'~')
                {
                    // We are interesting in disconnected pins therefore we
                    // need to check the connection using ConnectedTo().
                    CComPtr<IPin> pConnectedPin;
                    hr = pPin->ConnectedTo(&pConnectedPin);
                    if (hr == VFW_E_NOT_CONNECTED || !pConnectedPin)
                    {
                        hr = S_OK;
                        break;
                    }
                }
            }
            pPin = NULL;    // release this pin to get next
        }
    }
    else
    {
        hr = E_INVALIDARG;
    }

    return hr;
}


// Функция соединяет два фильтра черз их пины
// Параметры: [in] IBaseFilter* pSourceFilter   - это вышестоящий фильтр
//            [in] IBaseFilter* pReceiveFilter  - это нижестоящий фильтр
//            [in] IGraphBuilder* pGraphBuilder - это GraphBuilder
HRESULT ConnectFilters(IBaseFilter* pSourceFilter, IBaseFilter* pReceiveFilter, IGraphBuilder* pGraphBuilder)
{
    HRESULT hr;
    CComPtr<IPin> pOutputPin;
    CComPtr<IPin> pInputPin;

    PIN_INFO piPinInfo = {pSourceFilter, PINDIR_OUTPUT, {0}};
    // Get output pin
    hr = GetPin(&piPinInfo, pOutputPin);
    if (SUCCEEDED(hr))
    {
        ATLASSERT(pOutputPin);
        piPinInfo.pFilter = pReceiveFilter;
        piPinInfo.dir = PINDIR_INPUT;
        // Get input pin
        hr = GetPin(&piPinInfo, pInputPin);
        // Connect two pins
        if (SUCCEEDED(hr))
            hr = pGraphBuilder->Connect(pOutputPin, pInputPin);
    }

    return hr;
}


Теперь что касается создания самого графа:

HRESULT hr;
CComPtr<IGraphBuilder> pGraphBuilder;
CComPtr<IBaseFilter> pVideoEncoderFilter;
CComPtr<IBaseFilter> pAudioEncoderFilter;
CComPtr<IBaseFilter> pAVIMuxFilter;
CComPtr<IBaseFilter> pFileWriterFilter;
CComPtr<IMediaControl> pMediaControl;

hr = pGraphBuilder.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC);

// Далее находим видео-кодировщик, ну DivX, например
if (SUCCEEDED(hr))
{
    hr = GetFilter(CComBSTR("Microsoft MPEG-4 Video Codec V3"), pVideoEncoderFilter, CLSID_VideoCompressorCategory);
    if (SUCCEEDED(hr))
    {
        // Далее находим аудио-кодировщик, ну MP3, например
        hr = GetFilter(CComBSTR("MPEG Layer-3"), pAudioEncoderFilter, CLSID_AudioCompressorCategory);
        if (SUCCEEDED(hr))
        {
            // Далее находим AVI-Mux
            hr = GetFilter(CComBSTR("AVI Mux"), pAVIMuxFilter, CLSID_LegacyAmFilterCategory);
            if (SUCCEEDED(hr))
            {
                // Далее находим FileWriter
                hr = GetFilter(CComBSTR("File writer"), pFileWriterFilter, CLSID_LegacyAmFilterCategory);
                // потом  устанавливаем имя файла, предварительно получив интерфейс IFileSinkFilter
                CComPtr<IFileSinkFilter> pFileSinkFilter;
                hr = pFilter.QueryInterface(&pFileSinkFilter);
                ATLASSERT(pFileSinkFilter);
                hr = pFileSinkFilter->SetFileName(L"test.avi", NULL);
            }
        }
    }
}

if (SUCCEEDED(hr))
{
    // теперь добавляем найденные фильтры в граф
    pGraphBuilder->AddFilter(pVideoEncoderFilter, L"Filter 1");
    pGraphBuilder->AddFilter(pAudioEncoderFilter, L"Filter 2");
    pGraphBuilder->AddFilter(pAVIMuxFilter, L"Filter 3");
    pGraphBuilder->AddFilter(pFileWriterFilter, L"Filter 4");
    // теперь все эти фильтры соединяем, попутно находя соответствующие пины
    ConnectFilters(pVideoEncoderFilter, pAVIMuxFilter, pGraphBuilder);
    ConnectFilters(pAudioEncoderFilter, pAVIMuxFilter, pGraphBuilder);
    ConnectFilters(pAVIMuxFilter, pFileWriterFilter, pGraphBuilder);

    // теперь запрашиваем интерфейс IMediaControl
    hr = pGraphBuilder.QueryInterface(&pMediaControl);
    if (SUCCEEDED(hr))
        hr = pMediaControl->Run(); // и начинаем писать наш фильм
}


Ну в общем примерно так нужно делать. Что нехватает в вышеприведенном коде: надо добавить фильтр источник аудио и видео данных. Если ты используешь CaptureDevice, то можно использовать ICaptureGraphBuilder2 интерфейс. Ну как его пользовать очень хорошо на примерах показано в DS SDK.

Да, вместо ICaptureGraphBuilder2, можно и простой граф использовать, просто для WDM capture device он удобнее. А можно с помощью SetFiltergraph упомянутого интерфейса передать ему уже готовый построенный граф.
Автор: scs    Оценить