다음을 통해 공유


4단계: Video Renderer 추가

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

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

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

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

이 자습서에서 각 비디오 렌더러 필터는 차이점 중 일부를 추상화하는 클래스에 의해 래핑됩니다. 이러한 클래스는 모두 CVideoRenderer추상 기본 클래스에서 파생됩니다. CVideoRenderer의 선언은 2단계에서 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 필터를 만들고 필터 그래프에 추가합니다. 이 예제에 사용된 함수 AddFilterByCLSID CLSID 필터 추가항목에 나와 있습니다.

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::RenderStreamsCVideoRenderer::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 필터 사용

비디오 혼합 렌더러 사용