다음을 통해 공유


4단계: Video Renderer 추가

[이 페이지와 연결된 기능인 DirectShow는 레거시 기능입니다. MediaPlayer, IMFMediaEngineMedia Foundation의 오디오/비디오 캡처로 대체되었습니다. 이러한 기능은 Windows 10 및 Windows 11 최적화되었습니다. 가능한 경우 새 코드에서 DirectShow 대신 MediaPlayer, IMFMediaEngine오디오/비디오 캡처를 사용하는 것이 좋습니다. 가능한 경우 레거시 API를 사용하는 기존 코드를 다시 작성하여 새 API를 사용하도록 제안합니다.]

이 항목은 DirectShow의 오디오/비디오 재생 자습서의 4단계입니다. 전체 코드는 DirectShow 재생 예제 항목에 나와 있습니다.

DirectShow는 비디오를 렌더링하는 여러 가지 필터를 제공합니다.

이러한 필터 간의 차이점에 대한 자세한 내용은 올바른 비디오 렌더러 선택을 참조하세요.

이 자습서에서 각 비디오 렌더러 필터는 이들 간의 차이점 중 일부를 추상화하는 클래스에 의해 래핑됩니다. 이러한 클래스는 모두 라는 CVideoRenderer추상 기본 클래스에서 파생됩니다. 선언 CVideoRenderer2단계: CVideoRenderer 선언 및 파생 클래스에 표시됩니다.

다음 메서드는 EVR, VMR-9 및 마지막으로 VMR-7부터 차례로 각 비디오 렌더러를 만들려고 시도합니다.

HRESULT DShowPlayer::CreateVideoRenderer()
{
    HRESULT hr = E_FAIL;

    enum { Try_EVR, Try_VMR9, Try_VMR7 };

    for (DWORD i = Try_EVR; i <= Try_VMR7; i++)
    {
        switch (i)
        {
        case Try_EVR:
            m_pVideo = new (std::nothrow) CEVR();
            break;

        case Try_VMR9:
            m_pVideo = new (std::nothrow) CVMR9();
            break;

        case Try_VMR7:
            m_pVideo = new (std::nothrow) CVMR7();
            break;
        }

        if (m_pVideo == NULL)
        {
            hr = E_OUTOFMEMORY;
            break;
        }

        hr = m_pVideo->AddToGraph(m_pGraph, m_hwnd);
        if (SUCCEEDED(hr))
        {
            break;
        }

        delete m_pVideo;
        m_pVideo = NULL;
    }
    return hr;
}

EVR 필터

다음 코드는 EVR 필터를 만들고 필터 그래프에 추가합니다. 이 예제에 사용된 함수 AddFilterByCLSIDCLSID로 필터 추가 항목에 나와 있습니다.

HRESULT CEVR::AddToGraph(IGraphBuilder *pGraph, HWND hwnd)
{
    IBaseFilter *pEVR = NULL;

    HRESULT hr = AddFilterByCLSID(pGraph, CLSID_EnhancedVideoRenderer, 
        &pEVR, L"EVR");

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

    hr = InitializeEVR(pEVR, hwnd, &m_pVideoDisplay);
    if (FAILED(hr))
    {
        goto done;
    }

    // Note: Because IMFVideoDisplayControl is a service interface,
    // you cannot QI the pointer to get back the IBaseFilter pointer.
    // Therefore, we need to cache the IBaseFilter pointer.

    m_pEVR = pEVR;
    m_pEVR->AddRef();

done:
    SafeRelease(&pEVR);
    return hr;
}

함수는 InitializeEVR EVR 필터를 초기화합니다. 이 함수는 다음 단계를 수행합니다.

  1. IMFGetService 인터페이스에 대한 필터를 쿼리합니다.
  2. IMFGetService::GetService를 호출하여 IMFVideoDisplayControl 인터페이스에 대한 포인터를 가져옵니다.
  3. IMFVideoDisplayControl::SetVideoWindow를 호출하여 비디오 창을 설정합니다.
  4. IMFVideoDisplayControl::SetAspectRatioMode를 호출하여 비디오 가로 세로 비율을 유지하도록 EVR을 구성합니다.

다음 코드는 함수를 InitializeEVR 보여줍니다.

HRESULT InitializeEVR( 
    IBaseFilter *pEVR,              // Pointer to the EVR
    HWND hwnd,                      // Clipping window
    IMFVideoDisplayControl** ppDisplayControl
    ) 
{ 
    IMFGetService *pGS = NULL;
    IMFVideoDisplayControl *pDisplay = NULL;

    HRESULT hr = pEVR->QueryInterface(IID_PPV_ARGS(&pGS)); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGS->GetService(MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&pDisplay));
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the clipping window.
    hr = pDisplay->SetVideoWindow(hwnd);
    if (FAILED(hr))
    {
        goto done;
    }

    // Preserve aspect ratio by letter-boxing
    hr = pDisplay->SetAspectRatioMode(MFVideoARMode_PreservePicture);
    if (FAILED(hr))
    {
        goto done;
    }

    // Return the IMFVideoDisplayControl pointer to the caller.
    *ppDisplayControl = pDisplay;
    (*ppDisplayControl)->AddRef();

done:
    SafeRelease(&pGS);
    SafeRelease(&pDisplay);
    return hr; 
} 

그래프가 빌드된 후 는 DShowPlayer::RenderStreams 를 호출합니다 CVideoRenderer::FinalizeGraph. 이 메서드는 최종 초기화 또는 정리를 수행합니다. 다음 코드는 이 메서드의 구현을 CEVR 보여줍니다.

HRESULT CEVR::FinalizeGraph(IGraphBuilder *pGraph)
{
    if (m_pEVR == NULL)
    {
        return S_OK;
    }

    BOOL bRemoved;
    HRESULT hr = RemoveUnconnectedRenderer(pGraph, m_pEVR, &bRemoved);
    if (bRemoved)
    {
        SafeRelease(&m_pEVR);
        SafeRelease(&m_pVideoDisplay);
    }
    return hr;
}

EVR이 다른 필터에 연결되지 않은 경우 이 메서드는 그래프에서 EVR을 제거합니다. 미디어 파일에 비디오 스트림이 없는 경우 이 문제가 발생할 수 있습니다.

VMR-9 필터

다음 코드는 VMR-9 필터를 만들고 필터 그래프에 추가합니다.

HRESULT CVMR9::AddToGraph(IGraphBuilder *pGraph, HWND hwnd)
{
    IBaseFilter *pVMR = NULL;

    HRESULT hr = AddFilterByCLSID(pGraph, CLSID_VideoMixingRenderer9, 
        &pVMR, L"VMR-9");
    if (SUCCEEDED(hr))
    {
        // Set windowless mode on the VMR. This must be done before the VMR 
        // is connected.
        hr = InitWindowlessVMR9(pVMR, hwnd, &m_pWindowless);
    }
    SafeRelease(&pVMR);
    return hr;
}

함수는 InitWindowlessVMR9 창 없는 모드에 대해 VMR-9를 초기화합니다. (창 없는 모드에 대한 자세한 내용은 VMR 창 없는 모드를 참조하세요.) 이 함수는 다음 단계를 수행합니다.

  1. IVMRFilterConfig9 인터페이스에 대한 VMR-9 필터를 쿼리합니다.
  2. IVMRFilterConfig9::SetRenderingMode 메서드를 호출하여 창 없는 모드를 설정합니다.
  3. IVMRWindowlessControl9 인터페이스에 대한 VMR-9 필터를 쿼리합니다.
  4. IVMRWindowlessControl9::SetVideoClippingWindow 메서드를 호출하여 비디오 창을 설정합니다.
  5. IVMRWindowlessControl9::SetAspectRatioMode 메서드를 호출하여 비디오 가로 세로 비율을 유지합니다.

다음 코드는 함수를 InitWindowlessVMR9 보여줍니다.

HRESULT InitWindowlessVMR9( 
    IBaseFilter *pVMR,              // Pointer to the VMR
    HWND hwnd,                      // Clipping window
    IVMRWindowlessControl9** ppWC   // Receives a pointer to the VMR.
    ) 
{ 

    IVMRFilterConfig9 * pConfig = NULL; 
    IVMRWindowlessControl9 *pWC = NULL;

    // Set the rendering mode.  
    HRESULT hr = pVMR->QueryInterface(IID_PPV_ARGS(&pConfig)); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pConfig->SetRenderingMode(VMR9Mode_Windowless); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Query for the windowless control interface.
    hr = pVMR->QueryInterface(IID_PPV_ARGS(&pWC));
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the clipping window.
    hr = pWC->SetVideoClippingWindow(hwnd);
    if (FAILED(hr))
    {
        goto done;
    }

    // Preserve aspect ratio by letter-boxing
    hr = pWC->SetAspectRatioMode(VMR9ARMode_LetterBox);
    if (FAILED(hr))
    {
        goto done;
    }

    // Return the IVMRWindowlessControl pointer to the caller.
    *ppWC = pWC;
    (*ppWC)->AddRef();

done:
    SafeRelease(&pConfig);
    SafeRelease(&pWC);
    return hr; 
} 

메서드는 CVMR9::FinalizeGraph VMR-9 필터가 연결되어 있는지 확인하고, 연결되지 않은 경우 필터 그래프에서 제거합니다.

HRESULT CVMR9::FinalizeGraph(IGraphBuilder *pGraph)
{
    if (m_pWindowless == NULL)
    {
        return S_OK;
    }

    IBaseFilter *pFilter = NULL;

    HRESULT hr = m_pWindowless->QueryInterface(IID_PPV_ARGS(&pFilter));
    if (FAILED(hr))
    {
        goto done;
    }

    BOOL bRemoved;
    hr = RemoveUnconnectedRenderer(pGraph, pFilter, &bRemoved);

    // If we removed the VMR, then we also need to release our 
    // pointer to the VMR's windowless control interface.
    if (bRemoved)
    {
        SafeRelease(&m_pWindowless);
    }

done:
    SafeRelease(&pFilter);
    return hr;
}

VMR-7 필터

VMR-7 필터의 단계는 VMR-7 인터페이스가 대신 사용된다는 점을 제외하고 VMR-9의 단계와 거의 동일합니다. 다음 코드는 VMR-7 필터를 만들고 필터 그래프에 추가합니다.

HRESULT CVMR7::AddToGraph(IGraphBuilder *pGraph, HWND hwnd)
{
    IBaseFilter *pVMR = NULL;

    HRESULT hr = AddFilterByCLSID(pGraph, CLSID_VideoMixingRenderer, 
        &pVMR, L"VMR-7");

    if (SUCCEEDED(hr))
    {
        // Set windowless mode on the VMR. This must be done before the VMR
        // is connected.
        hr = InitWindowlessVMR(pVMR, hwnd, &m_pWindowless);
    }
    SafeRelease(&pVMR);
    return hr;
}

함수는 InitWindowlessVMR 창 없는 모드에 대해 VMR-7을 초기화합니다. 이 함수는 다음 단계를 수행합니다.

  1. IVMRFilterConfig 인터페이스에 대한 VMR-7 필터를 쿼리합니다.
  2. IVMRFilterConfig::SetRenderingMode 메서드를 호출하여 창 없는 모드를 설정합니다.
  3. IVMRWindowlessControl 인터페이스에 대한 VMR-7 필터를 쿼리합니다.
  4. IVMRWindowlessControl::SetVideoClippingWindow 메서드를 호출하여 비디오 창을 설정합니다.
  5. IVMRWindowlessControl::SetAspectRatioMode 메서드를 호출하여 비디오 가로 세로 비율을 유지합니다.

다음 코드는 함수를 InitWindowlessVMR 보여줍니다.

HRESULT InitWindowlessVMR( 
    IBaseFilter *pVMR,              // Pointer to the VMR
    HWND hwnd,                      // Clipping window
    IVMRWindowlessControl** ppWC    // Receives a pointer to the VMR.
    ) 
{ 

    IVMRFilterConfig* pConfig = NULL; 
    IVMRWindowlessControl *pWC = NULL;

    // Set the rendering mode.  
    HRESULT hr = pVMR->QueryInterface(IID_PPV_ARGS(&pConfig)); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pConfig->SetRenderingMode(VMRMode_Windowless); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Query for the windowless control interface.
    hr = pVMR->QueryInterface(IID_PPV_ARGS(&pWC));
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the clipping window.
    hr = pWC->SetVideoClippingWindow(hwnd);
    if (FAILED(hr))
    {
        goto done;
    }

    // Preserve aspect ratio by letter-boxing
    hr = pWC->SetAspectRatioMode(VMR_ARMODE_LETTER_BOX);
    if (FAILED(hr))
    {
        goto done;
    }

    // Return the IVMRWindowlessControl pointer to the caller.
    *ppWC = pWC;
    (*ppWC)->AddRef();

done:
    SafeRelease(&pConfig);
    SafeRelease(&pWC);
    return hr; 
} 

메서드는 CVMR7::FinalizeGraph VMR-7 필터가 연결되어 있는지 확인하고, 연결되지 않은 경우 필터 그래프에서 제거합니다.

HRESULT CVMR7::FinalizeGraph(IGraphBuilder *pGraph)
{
    if (m_pWindowless == NULL)
    {
        return S_OK;
    }

    IBaseFilter *pFilter = NULL;

    HRESULT hr = m_pWindowless->QueryInterface(IID_PPV_ARGS(&pFilter));
    if (FAILED(hr))
    {
        goto done;
    }

    BOOL bRemoved;
    hr = RemoveUnconnectedRenderer(pGraph, pFilter, &bRemoved);

    // If we removed the VMR, then we also need to release our 
    // pointer to the VMR's windowless control interface.
    if (bRemoved)
    {
        SafeRelease(&m_pWindowless);
    }

done:
    SafeRelease(&pFilter);
    return hr;
}

DirectShow 재생 예제

DirectShow EVR 필터 사용

비디오 혼합 렌더러 사용