EVR 발표자를 작성하는 방법

[이 페이지에 설명된 구성 요소, 향상된 Video Renderer는 레거시 기능입니다. MediaPlayer 및 IMFMediaEngine 구성 요소를 통해 노출된 SVR(Simple Video Renderer)으로 대체되었습니다. 비디오 콘텐츠를 재생하려면 이러한 구성 요소 중 하나로 데이터를 보내고 새 비디오 렌더러를 인스턴스화하도록 허용해야 합니다. 이러한 구성 요소는 Windows 10 및 Windows 11에 최적화되었습니다. 가능한 경우 새 코드에서 MediaPlayer 또는 하위 수준 IMFMediaEngine API를 사용하여 EVR 대신 Windows에서 비디오 미디어를 재생하는 것이 좋습니다. 가능한 경우 레거시 API를 사용하는 기존 코드를 다시 작성하여 새 API를 사용하도록 제안합니다.]

이 문서에서는 향상된 EVR(비디오 렌더러)에 대한 사용자 지정 발표자를 작성하는 방법을 설명합니다. 사용자 지정 발표자는 DirectShow와 Media Foundation 모두에서 사용할 수 있습니다. 인터페이스와 개체 모델은 두 기술에 대해 동일하지만 정확한 작업 순서는 다를 수 있습니다.

이 항목의 예제 코드는 Windows SDK에 제공되는 EVRPresenter 샘플에서 조정됩니다.

이 항목에는 다음과 같은 섹션이 포함되어 있습니다.

필수 조건

사용자 지정 발표자를 작성하기 전에 다음 기술을 잘 알고 있어야 합니다.

  • 향상된 비디오 렌더러입니다. 향상된 비디오 렌더러를 참조하세요.
  • Direct3D 그래픽. 발표자를 작성하기 위해 3차원 그래픽을 이해할 필요는 없지만 Direct3D 디바이스를 만들고 Direct3D 화면을 관리하는 방법을 알아야 합니다. Direct3D에 익숙하지 않은 경우 DirectX 그래픽 SDK 설명서에서 "Direct3D 디바이스" 및 "Direct3D 리소스" 섹션을 읽어보세요.
  • DirectShow 필터 그래프 또는 Media Foundation 파이프라인은 애플리케이션이 비디오를 렌더링하는 데 사용할 기술에 따라 달라집니다.
  • 미디어 파운데이션 변환 EVR 믹서는 Media Foundation 변환이며 발표자는 믹서에서 직접 메서드를 호출합니다.
  • COM 개체 구현 발표자는 진행 중인 자유 스레드 COM 개체입니다.

발표자 개체 모델

이 섹션에는 발표자 개체 모델 및 인터페이스에 대한 개요가 포함되어 있습니다.

EVR 내의 데이터 흐름

EVR은 두 개의 플러그 인 구성 요소를 사용하여 비디오를 렌더링합니다. 믹서발표자. 믹서는 비디오 스트림을 혼합하고 필요한 경우 비디오를 디인터레이스합니다. 발표자는 비디오를 디스플레이에 그리거나 프레젠테이션하며 각 프레임이 그려질 때 일정을 예약합니다. 애플리케이션은 이러한 개체 중 하나를 사용자 지정 구현으로 바꿀 수 있습니다.

EVR에는 하나 이상의 입력 스트림이 있으며 믹서에는 해당 수의 입력 스트림이 있습니다. 스트림 0은 항상 참조 스트림입니다. 다른 스트림은 하위 스트림이며, 믹서는 참조 스트림에 알파 혼합됩니다. 참조 스트림은 합성 비디오의 마스터 프레임 속도를 결정합니다. 각 참조 프레임에 대해 믹서는 각 하위 스트림에서 가장 최근의 프레임을 가져와서 참조 프레임에 알파 혼합하고 단일 복합 프레임을 출력합니다. 또한 믹서는 필요한 경우 YUV에서 RGB로 디인터레이싱 및 색 변환을 수행합니다. EVR은 입력 스트림 수 또는 비디오 형식에 관계없이 항상 믹서가 비디오 파이프라인에 삽입됩니다. 다음 이미지는 이 프로세스를 보여 줍니다.

diagram showing the reference stream and substream pointing to the mixer, which points to the presenter, which points to the display

발표자는 다음 작업을 수행합니다.

  • 믹서의 출력 형식을 설정합니다. 스트리밍을 시작하기 전에 발표자는 믹서의 출력 스트림에 미디어 형식을 설정합니다. 이 미디어 형식은 복합 이미지의 형식을 정의합니다.
  • Direct3D 디바이스를 만듭니다.
  • Direct3D 표면을 할당합니다. 믹서는 복합 프레임을 이러한 표면에 블릿합니다.
  • 믹서에서 출력을 가져옵니다.
  • 프레임이 표시되는 시기를 예약합니다. EVR은 프레젠테이션 시계를 제공하고 발표자는 이 시계에 따라 프레임을 예약합니다.
  • Direct3D를 사용하여 각 프레임을 표시합니다.
  • 프레임 스테핑 및 스크러빙을 수행합니다.

발표자 상태

언제든지 발표자는 다음 상태 중 하나입니다.

  • 시작했습니다. EVR의 프레젠테이션 시계가 실행 중입니다. 발표자는 프레젠테이션이 도착할 때 비디오 프레임을 예약합니다.
  • 일시 중지되었습니다. 프레젠테이션 시계가 일시 중단되었습니다. 발표자는 새 샘플을 제공하지 않지만 예약된 샘플의 큐에 기본. 새 샘플을 받으면 발표자가 큐에 추가합니다.
  • 중지됨. 프레젠테이션 시계가 중지되었습니다. 발표자는 예약된 샘플을 카드 않습니다.
  • 종료합니다. 발표자는 Direct3D 표면과 같은 스트리밍과 관련된 모든 리소스를 해제합니다. 이 상태는 발표자의 초기 상태이며 발표자가 제거되기 전의 최종 상태입니다.

이 항목의 예제 코드에서 발표자 상태는 열거형으로 표시됩니다.

enum RENDER_STATE
{
    RENDER_STATE_STARTED = 1,
    RENDER_STATE_STOPPED,
    RENDER_STATE_PAUSED,
    RENDER_STATE_SHUTDOWN,  // Initial state.
};

발표자가 종료 상태에 있는 동안에는 일부 작업이 유효하지 않습니다. 예제 코드는 도우미 메서드를 호출하여 이 상태에 대한 검사.

    HRESULT CheckShutdown() const
    {
        if (m_RenderState == RENDER_STATE_SHUTDOWN)
        {
            return MF_E_SHUTDOWN;
        }
        else
        {
            return S_OK;
        }
    }

발표자 인터페이스

발표자는 다음 인터페이스를 구현해야 합니다.

인터페이스 설명
IMFClockStateSink EVR의 클록 상태가 변경되면 발표자에게 알릴 수 있습니다. IMFClockStateSink 구현을 참조하세요.
IMFGetService 애플리케이션 및 파이프라인의 다른 구성 요소가 발표자로부터 인터페이스를 가져오는 방법을 제공합니다.
IMFTopologyServiceLookupClient 발표자가 EVR 또는 믹서에서 인터페이스를 가져올 수 있도록 합니다. IMFTopologyServiceLookupClient 구현을 참조 하세요.
IMFVideoDeviceID 발표자와 믹서가 호환되는 기술을 사용하도록 합니다. IMFVideoDeviceID 구현을 참조하세요.
IMFVideoPresenter EVR에서 메시지를 처리합니다. IMFVideoPresenter 구현을 참조하세요.

 

다음 인터페이스는 선택 사항입니다.

인터페이스 설명
IEVRTrustedVideoPlugin 발표자가 보호된 미디어를 사용할 수 있도록 합니다. 발표자가 PMP(보호된 미디어 경로)에서 작동하도록 설계된 신뢰할 수 있는 구성 요소인 경우 이 인터페이스를 구현합니다.
IMFRateSupport 발표자가 지원하는 재생 속도의 범위를 보고합니다. IMFRateSupport 구현을 참조하세요.
IMFVideoPositionMapper 출력 비디오 프레임의 좌표를 입력 비디오 프레임의 좌표로 지도.
IQualProp 성능 정보를 보고합니다. EVR은 품질 관리 관리에 이 정보를 사용합니다. 이 인터페이스는 DirectShow SDK에 설명되어 있습니다.

 

애플리케이션이 발표자와 통신할 수 있는 인터페이스를 제공할 수도 있습니다. 표준 발표자는 이 목적을 위해 IMFVideoDisplayControl 인터페이스를 구현합니다. 이 인터페이스를 구현하거나 직접 정의할 수 있습니다. 애플리케이션은 EVR에서 IMFGetService::GetService를 호출하여 발표자로부터 인터페이스를 가져옵니다. 서비스 GUID가 MR_VIDEO_RENDER_SERVICE 경우 EVR은 GetService 요청을 발표자에게 전달합니다.

IMFVideoDeviceID 구현

IMFVideoDeviceID 인터페이스에는 디바이스 GUID를 반환하는 GetDeviceID라는 하나의 메서드가 포함되어 있습니다. 디바이스 GUID를 사용하면 발표자와 믹서가 호환되는 기술을 사용할 수 있습니다. 디바이스 GUID가 일치하지 않으면 EVR이 초기화되지 않습니다.

표준 믹서와 발표자는 모두 장치 GUID가 IID_IDirect3DDevice9 동일한 Direct3D 9를 사용합니다. 표준 믹서에서 사용자 지정 발표자를 사용하려는 경우 발표자의 디바이스 GUID는 IID_IDirect3DDevice9 합니다. 두 구성 요소를 모두 바꾸면 새 디바이스 GUID를 정의할 수 있습니다. 이 문서의 다시 기본 경우 발표자가 Direct3D 9를 사용하는 것으로 가정합니다. GetDeviceID표준 구현은 다음과 같습니다.

HRESULT EVRCustomPresenter::GetDeviceID(IID* pDeviceID)
{
    if (pDeviceID == NULL)
    {
        return E_POINTER;
    }

    *pDeviceID = __uuidof(IDirect3DDevice9);
    return S_OK;
}

발표자가 종료된 경우에도 메서드가 성공해야 합니다.

IMFTopologyServiceLookupClient 구현

IMFTopologyServiceLookupClient 인터페이스를 사용하면 발표자가 다음과 같이 EVR 및 믹서에서 인터페이스 포인터를 가져올 수 있습니다.

  1. EVR이 발표자를 초기화하면 발표자의 IMFTopologyServiceLookupClient::InitServicePointers 메서드를 호출합니다. 인수는 EVR의 IMFTopologyServiceLookup 인터페이스에 대한 포인터입니다.
  2. 발표자는 IMFTopologyServiceLookup::LookupService를 호출하여 EVR 또는 믹서에서 인터페이스 포인터를 가져옵니다.

LookupService 메서드는 IMFGetService::GetService 메서드와 유사합니다. 두 메서드 모두 서비스 GUID와 IID(인터페이스 식별자)를 입력으로 사용하지만 LookupService는 인터페이스 포인터 배열을 반환하고 GetService단일 포인터를 반환합니다. 그러나 실제로는 항상 배열 크기를 1로 설정할 수 있습니다. 쿼리된 개체는 서비스 GUID에 따라 달라집니다.

  • 서비스 GUID가 MR_VIDEO_RENDER_SERVICE 경우 EVR이 쿼리됩니다.
  • 서비스 GUID가 MR_VIDEO_MIXER_SERVICE 경우 믹서가 쿼리됩니다.

InitServicePointers 구현에서 EVR에서 다음 인터페이스를 가져옵니다.

EVR 인터페이스 설명
IMediaEventSink 발표자가 EVR에 메시지를 보낼 수 있는 방법을 제공합니다. 이 인터페이스는 DirectShow SDK에 정의되어 있으므로 메시지는 Media Foundation 이벤트가 아닌 DirectShow 이벤트의 패턴을 따릅니다.
IMFClock EVR의 시계를 나타냅니다. 발표자는 이 인터페이스를 사용하여 프레젠테이션 샘플을 예약합니다. EVR은 클록 없이 실행할 수 있으므로 이 인터페이스를 사용할 수 없습니다. 그렇지 않은 경우 LookupService에서 오류 코드를 무시합니다.
또한 클록은 IMFTimer 인터페이스를 구현합니다. Media Foundation 파이프라인에서 클록은 IMFPresentationClock 인터페이스를 구현합니다. DirectShow에서는 이 인터페이스를 구현하지 않습니다.

 

믹서에서 다음 인터페이스를 가져옵니다.

믹서 인터페이스 설명
IMFTransform 발표자가 믹서와 통신할 수 있도록 합니다.
IMFVideoDeviceID 발표자가 믹서의 디바이스 GUID의 유효성을 검사할 수 있도록 합니다.

 

다음 코드는 InitServicePointers 메서드를 구현합니다.

HRESULT EVRCustomPresenter::InitServicePointers(
    IMFTopologyServiceLookup *pLookup
    )
{
    if (pLookup == NULL)
    {
        return E_POINTER;
    }

    HRESULT             hr = S_OK;
    DWORD               dwObjectCount = 0;

    EnterCriticalSection(&m_ObjectLock);

    // Do not allow initializing when playing or paused.
    if (IsActive())
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    // Ask for the clock. Optional, because the EVR might not have a clock.
    dwObjectCount = 1;

    (void)pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL,   // Not used.
        0,                          // Reserved.
        MR_VIDEO_RENDER_SERVICE,    // Service to look up.
        IID_PPV_ARGS(&m_pClock),    // Interface to retrieve.
        &dwObjectCount              // Number of elements retrieved.
        );

    // Ask for the mixer. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_pMixer), &dwObjectCount
        );

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

    // Make sure that we can work with this mixer.
    hr = ConfigureMixer(m_pMixer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Ask for the EVR's event-sink interface. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_pMediaEventSink),
        &dwObjectCount
        );

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

    // Successfully initialized. Set the state to "stopped."
    m_RenderState = RENDER_STATE_STOPPED;

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

LookupService에서 가져온 인터페이스 포인터가 더 이상 유효하지 않으면 EVR은 IMFTopologyServiceLookupClient::ReleaseServicePointers를 호출합니다. 이 메서드 내에서 모든 인터페이스 포인터를 해제하고 발표자 상태를 종료하도록 설정합니다.

HRESULT EVRCustomPresenter::ReleaseServicePointers()
{
    // Enter the shut-down state.
    EnterCriticalSection(&m_ObjectLock);

    m_RenderState = RENDER_STATE_SHUTDOWN;

    LeaveCriticalSection(&m_ObjectLock);

    // Flush any samples that were scheduled.
    Flush();

    // Clear the media type and release related resources.
    SetMediaType(NULL);

    // Release all services that were acquired from InitServicePointers.
    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    return S_OK;
}

EVR은 다음과 같은 다양한 이유로 ReleaseServicePointers를 호출합니다.

  • 핀 연결 끊기 또는 다시 연결(DirectShow) 또는 스트림 싱크 추가 또는 제거(Media Foundation).
  • 형식을 변경합니다.
  • 새 시계 설정
  • EVR의 최종 종료입니다.

발표자의 수명 동안 EVR은 InitServicePointersReleaseServicePointers를 여러 번 호출할 수 있습니다.

IMFVideoPresenter 구현

IMFVideoPresenter 인터페이스는 IMFClockStateSink상속하고 두 가지 메서드를 추가합니다.

메서드 설명
GetCurrentMediaType 합성된 비디오 프레임의 미디어 형식을 반환합니다.
ProcessMessage 발표자에게 다양한 작업을 수행하도록 알릴 수 있습니다.

 

GetCurrentMediaType 메서드는 발표자의 미디어 형식을 반환합니다. (미디어 형식 설정에 대한 자세한 내용은 를 참조하세요.협상 형식.) 미디어 형식은 IMFVideoMediaType 인터페이스 포인터로 반환됩니다. 다음 예제에서는 발표자가 미디어 형식을 IMFMediaType 포인터로 저장한다고 가정합니다. 미디어 형식에서 IMFVideoMediaType 인터페이스를 얻으려면 QueryInterface를 호출합니다.

HRESULT EVRCustomPresenter::GetCurrentMediaType(
    IMFVideoMediaType** ppMediaType
    )
{
    HRESULT hr = S_OK;

    if (ppMediaType == NULL)
    {
        return E_POINTER;
    }

    *ppMediaType = NULL;

    EnterCriticalSection(&m_ObjectLock);

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

    if (m_pMediaType == NULL)
    {
        hr = MF_E_NOT_INITIALIZED;
        goto done;
    }

    hr = m_pMediaType->QueryInterface(IID_PPV_ARGS(ppMediaType));

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

ProcessMessage 메서드는 EVR이 발표자와 통신하는 기본 메커니즘입니다. 다음 메시지가 정의됩니다. 각 메시지를 구현하는 세부 정보는 이 항목의 re기본der에 제공됩니다.

메시지 설명
MFVP_MESSAGE_INVALIDATEMEDIATYPE 믹서의 출력 미디어 형식이 잘못되었습니다. 발표자는 믹서와 새 미디어 형식을 협상해야 합니다. 협상 형식을 참조 하세요.
MFVP_MESSAGE_BEGINSTREAMING 스트리밍이 시작되었습니다. 이 메시지에는 특별한 작업이 필요하지 않지만 리소스를 할당하는 데 사용할 수 있습니다.
MFVP_MESSAGE_ENDSTREAMING 스트리밍이 종료되었습니다. MFVP_MESSAGE_BEGINSTREAMING 메시지에 대한 응답으로 할당한 리소스를 해제합니다.
MFVP_MESSAGE_PROCESSINPUTNOTIFY 믹서는 새 입력 샘플을 받았으며 새 출력 프레임을 생성할 수 있습니다. 발표자는 믹서에서 IMFTransform::P rocessOutput을 호출해야 합니다. 출력 처리를 참조하세요.
MFVP_MESSAGE_ENDOFSTREAM 프레젠테이션이 종료되었습니다. 스트림의 끝을 참조하세요.
MFVP_MESSAGE_FLUSH EVR은 렌더링 파이프라인의 데이터를 플러시합니다. 발표자는 프레젠테이션으로 예약된 모든 비디오 프레임을 카드 않습니다.
MFVP_MESSAGE_STEP 발표자에게 N 프레임 앞으로 나아갈 것을 요청합니다. 발표자는 다음 N-1 프레임을 카드 N번째 프레임을 표시해야 합니다. 프레임 스테핑을 참조하세요.
MFVP_MESSAGE_CANCELSTEP 프레임 스테핑을 취소합니다.

 

IMFClockStateSink 구현

발표자는 IMFClockStateSink를 상속하는 IMFVideoPresenter 구현의 일부로 IMFClockStateSink 인터페이스를 구현해야 합니다. EVR은 이 인터페이스를 사용하여 EVR의 클록 상태가 변경되면 발표자에게 알립니다. 시계 상태에 대한 자세한 내용은 프레젠테이션 시계를 참조 하세요.

이 인터페이스에서 메서드를 구현하기 위한 몇 가지 지침은 다음과 같습니다. 발표자가 종료되면 모든 메서드가 실패합니다.

메서드 설명
OnClockStart
  1. 발표자 상태를 시작하도록 설정합니다.
  2. llClockStartOffset이 PRESENTATION_CURRENT_POSITION 않으면 발표자의 샘플 큐를 플러시합니다. (이는 받는 것과 같습니다. MFVP_MESSAGE_FLUSH 메시지입니다.)
  3. 이전 프레임 단계 요청이 아직 보류 중인 경우 요청을 처리합니다(프레임 단계별 실행 참조). 그렇지 않으면 믹서에서 출력을 처리합니다(처리 출력 참조).
OnClockStop
  1. 발표자 상태를 중지되도록 설정합니다.
  2. 발표자의 샘플 큐를 플러시합니다.
  3. 보류 중인 프레임 단계 작업을 취소합니다.
OnClockPause 발표자 상태를 일시 중지되도록 설정합니다.
OnClockRestart OnClockStart와 동일하게 처리하지만 샘플 큐를 플러시하지 않습니다.
OnClockSetRate
  1. 속도가 0에서 0이 아닌 값으로 변경되면 프레임 스테이핑을 취소합니다.
  2. 새 클록 속도를 저장합니다. 클록 속도는 샘플이 표시될 때 영향을 줍니다. 자세한 내용은 샘플 예약을 참조 하세요.

 

IMFRateSupport 구현

1× 속도 이외의 재생 속도를 지원하려면 발표자가 IMFRateSupport 인터페이스를 구현해야 합니다. 이 인터페이스에서 메서드를 구현하기 위한 몇 가지 지침은 다음과 같습니다. 발표자가 종료된 후 모든 메서드가 실패해야 합니다. 이 인터페이스에 대한 자세한 내용은 Rate Control을 참조 하세요.

설명
GetSlowestRate 최소 재생 속도를 나타내려면 0을 반환합니다.
GetFastestRate 씬되지 않은 재생의 경우 재생 속도가 모니터의 새로 고침 속도를 초과해서는 안 됩니다. 최대 새로 고침 속도 = (Hz) / 비디오 프레임 속도(fps). 비디오 프레임 속도는 발표자의 미디어 유형에 지정됩니다.
씬 재생의 경우 재생 속도는 바인딩되지 않습니다. 는 FLT_MAX 값을 반환합니다. 실제로 원본 및 디코더는 씬 재생 중에 제한 요인이 됩니다.
역방향 재생의 경우 최대 속도의 음수를 반환합니다.
IsRateSupported flRate의 절대값이 발표자의 최대 재생 속도를 초과하는 경우 MF_E_UNSUPPORTED_RATE 반환합니다. GetFastestRate에 대해 설명된 대로 최대 재생 속도를 계산합니다.

 

다음 예제에서는 GetFastestRate 메서드를 구현하는 방법을 보여줍니다.

float EVRCustomPresenter::GetMaxRate(BOOL bThin)
{
    // Non-thinned:
    // If we have a valid frame rate and a monitor refresh rate, the maximum
    // playback rate is equal to the refresh rate. Otherwise, the maximum rate
    // is unbounded (FLT_MAX).

    // Thinned: The maximum rate is unbounded.

    float   fMaxRate = FLT_MAX;
    MFRatio fps = { 0, 0 };
    UINT    MonitorRateHz = 0;

    if (!bThin && (m_pMediaType != NULL))
    {
        GetFrameRate(m_pMediaType, &fps);
        MonitorRateHz = m_pD3DPresentEngine->RefreshRate();

        if (fps.Denominator && fps.Numerator && MonitorRateHz)
        {
            // Max Rate = Refresh Rate / Frame Rate
            fMaxRate = (float)MulDiv(
                MonitorRateHz, fps.Denominator, fps.Numerator);
        }
    }

    return fMaxRate;
}

이전 예제에서는 도우미 메서드인 GetMaxRate를 호출하여 최대 정방향 재생 속도를 계산합니다.

다음 예제에서는 IsRateSupported 메서드를 구현하는 방법을 보여 줍니다.

HRESULT EVRCustomPresenter::IsRateSupported(
    BOOL bThin,
    float fRate,
    float *pfNearestSupportedRate
    )
{
    EnterCriticalSection(&m_ObjectLock);

    float   fMaxRate = 0.0f;
    float   fNearestRate = fRate;  // If we support fRate, that is the nearest.

    HRESULT hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    // Find the maximum forward rate.
    // Note: We have no minimum rate (that is, we support anything down to 0).
    fMaxRate = GetMaxRate(bThin);

    if (fabsf(fRate) > fMaxRate)
    {
        // The (absolute) requested rate exceeds the maximum rate.
        hr = MF_E_UNSUPPORTED_RATE;

        // The nearest supported rate is fMaxRate.
        fNearestRate = fMaxRate;
        if (fRate < 0)
        {
            // Negative for reverse playback.
            fNearestRate = -fNearestRate;
        }
    }

    // Return the nearest supported rate.
    if (pfNearestSupportedRate != NULL)
    {
        *pfNearestSupportedRate = fNearestRate;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

EVR에 이벤트 보내기

발표자는 EVR에 다양한 이벤트를 알려야 합니다. 이렇게 하려면 EVR이 발표자의 IMFTopologyServiceLookupClient::InitServicePointers 메서드를 호출할 때 가져온 EVR의 IMediaEventSink 인터페이스를 사용합니다. (다음 항목IMediaEventSink 인터페이스는 원래 DirectShow 인터페이스이지만 DirectShow EVR과 Media Foundation 모두에서 사용됩니다.) 다음 코드는 EVR에 이벤트를 보내는 방법을 보여줍니다.

    // NotifyEvent: Send an event to the EVR through its IMediaEventSink interface.
    void NotifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
    {
        if (m_pMediaEventSink)
        {
            m_pMediaEventSink->Notify(EventCode, Param1, Param2);
        }
    }

다음 표에서는 이벤트 매개 변수와 함께 발표자가 보내는 이벤트를 나열합니다.

이벤트 설명
EC_COMPLETE 발표자가 MFVP_MESSAGE_ENDOFSTREAM 메시지 이후의 모든 프레임 렌더링을 완료했습니다.
  • Param1: 작업의 상태 나타내는 HRESULT입니다.
  • Param2: 사용되지 않습니다.
자세한 내용은 스트림의 끝을 참조 하세요.
EC_DISPLAY_CHANGED Direct3D 디바이스가 변경되었습니다.
  • Param1: 사용되지 않습니다.
  • Param2: 사용되지 않습니다.
자세한 내용은 Direct3D 디바이스 관리를 참조 하세요.
EC_ERRORABORT 스트리밍을 중지해야 하는 오류가 발생했습니다.
  • Param1: 발생한 오류를 나타내는 HRESULT 입니다.
  • Param2: 사용되지 않습니다.
EC_PROCESSING_LATENCY 발표자가 각 프레임을 렌더링하는 데 걸리는 시간을 지정합니다. (선택 사항)
  • Param1: 프레임을 처리하는 시간을 100나노초 단위로 포함하는 상수 LONGLONG 값에 대한 포인터입니다.
  • Param2: 사용되지 않습니다.
자세한 내용은 출력 처리를 참조 하세요.
EC_SAMPLE_LATENCY 렌더링 샘플에서 현재 지연 시간을 지정합니다. 값이 양수이면 샘플이 일정에 뒤처집니다. 값이 음수이면 샘플이 일정보다 앞서 있습니다. (선택 사항)
  • Param1: 지연 시간을 포함하는 상수 LONGLONG 값에 대한 포인터(100나노초 단위)입니다.
  • Param2: 사용되지 않습니다.
EC_SCRUB_TIME 재생 속도가 0이면 EC_STEP_COMPLETE 직후에 전송됩니다. 이 이벤트에는 표시된 프레임의 타임스탬프를 포함합니다.
  • Param1: 타임스탬프를 32비트 낮춥니다.
  • Param2: 타임스탬프를 32비트 상한 값으로 표시합니다.
자세한 내용은 프레임 스테핑을 참조하세요.
EC_STEP_COMPLETE 발표자가 프레임 단계를 완료하거나 취소했습니다.
- Param1: 사용되지 않습니다.
- Param2: 사용되지 않습니다.
자세한 내용은 프레임 스테핑을 참조하세요.
참고: 이전 버전의 설명서에서 Param1을 잘못 설명했습니다. 이 매개 변수는 이 이벤트에 사용되지 않습니다.

 

협상 형식

발표자가 EVR에서 MFVP_MESSAGE_INVALIDATEMEDIATYPE 메시지를 받을 때마다 다음과 같이 믹서에서 출력 형식을 설정해야 합니다.

  1. 가능한 출력 형식을 얻으려면 믹서에서 IMFTransform::GetOutputAvailableType을 호출합니다. 이 형식은 그래픽 디바이스의 입력 스트림 및 비디오 처리 기능을 고려할 때 믹서가 생성할 수 있는 형식을 설명합니다.

  2. 발표자가 이 미디어 형식을 렌더링 형식으로 사용할 수 있는지 확인합니다. 구현에 고유한 요구 사항이 있을 수 있지만 검사 몇 가지 사항은 다음과 같습니다.

    • 비디오의 압축을 풀어야 합니다.
    • 비디오에는 프로그레시브 프레임만 있어야 합니다. MF_MT_INTERLACE_MODE 특성이 MFVideoInterlace_Progressive 같은지 확인합니다.
    • 이 형식은 Direct3D 디바이스와 호환되어야 합니다.

    형식이 허용되지 않는 경우 1단계로 돌아가서 믹서의 다음 제안된 형식을 가져옵니다.

  3. 원래 형식의 복제본인 새 미디어 형식을 만든 다음, 다음 특성을 변경합니다.

  4. 믹서가 수정된 출력 형식을 허용할지 여부를 테스트하려면 MFT_SET_TYPE_TEST_ONLY 플래그를 사용하여 IMFTransform::SetOutputType호출합니다. 믹서가 형식을 거부하는 경우 1단계로 돌아가서 다음 형식을 가져옵니다.

  5. Direct3D Surface 할당에 설명된 대로 Direct3D 표면 풀을 할당합니다. 믹서는 합성된 비디오 프레임을 그릴 때 이러한 표면을 사용합니다.

  6. 플래그 없이 SetOutputType을 호출하여 믹서에서 출력 형식을 설정합니다. SetOutputType에 대한 첫 번째 호출이 4단계에서 성공한 경우 메서드가 다시 성공해야 합니다.

믹서 형식이 부족하면 GetOutputAvailableType 메서드는 MF_E_NO_MORE_TYPES 반환합니다. 발표자가 믹서에 적합한 출력 형식을 찾을 수 없는 경우 스트림을 렌더링할 수 없습니다. 이 경우 DirectShow 또는 Media Foundation에서 다른 스트림 형식을 시도할 수 있습니다. 따라서 발표자는 유효한 형식을 찾을 때까지 여러 MFVP_MESSAGE_INVALIDATEMEDIATYPE 메시지를 연속으로 받을 수 있습니다.

믹서는 원본 및 대상의 PAR(픽셀 가로 세로 비율)을 고려하여 비디오를 자동으로 레터박스에 넣습니다. 최상의 결과를 얻으려면 화면 너비와 높이 및 기하학적 조리개는 비디오가 디스플레이에 표시될 실제 크기와 같아야 합니다. 다음 이미지는 이 프로세스를 보여 줍니다.

diagram showing a composited fram leading to a direct3d surface, which leads to a window

다음 코드는 프로세스의 개요를 보여줍니다. 일부 단계는 도우미 함수에 배치되며 정확한 세부 정보는 발표자의 요구 사항에 따라 달라집니다.

HRESULT EVRCustomPresenter::RenegotiateMediaType()
{
    HRESULT hr = S_OK;
    BOOL bFoundMediaType = FALSE;

    IMFMediaType *pMixerType = NULL;
    IMFMediaType *pOptimalType = NULL;
    IMFVideoMediaType *pVideoType = NULL;

    if (!m_pMixer)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Loop through all of the mixer's proposed output types.
    DWORD iTypeIndex = 0;
    while (!bFoundMediaType && (hr != MF_E_NO_MORE_TYPES))
    {
        SafeRelease(&pMixerType);
        SafeRelease(&pOptimalType);

        // Step 1. Get the next media type supported by mixer.
        hr = m_pMixer->GetOutputAvailableType(0, iTypeIndex++, &pMixerType);
        if (FAILED(hr))
        {
            break;
        }

        // From now on, if anything in this loop fails, try the next type,
        // until we succeed or the mixer runs out of types.

        // Step 2. Check if we support this media type.
        if (SUCCEEDED(hr))
        {
            // Note: None of the modifications that we make later in CreateOptimalVideoType
            // will affect the suitability of the type, at least for us. (Possibly for the mixer.)
            hr = IsMediaTypeSupported(pMixerType);
        }

        // Step 3. Adjust the mixer's type to match our requirements.
        if (SUCCEEDED(hr))
        {
            hr = CreateOptimalVideoType(pMixerType, &pOptimalType);
        }

        // Step 4. Check if the mixer will accept this media type.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, MFT_SET_TYPE_TEST_ONLY);
        }

        // Step 5. Try to set the media type on ourselves.
        if (SUCCEEDED(hr))
        {
            hr = SetMediaType(pOptimalType);
        }

        // Step 6. Set output media type on mixer.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, 0);

            assert(SUCCEEDED(hr)); // This should succeed unless the MFT lied in the previous call.

            // If something went wrong, clear the media type.
            if (FAILED(hr))
            {
                SetMediaType(NULL);
            }
        }

        if (SUCCEEDED(hr))
        {
            bFoundMediaType = TRUE;
        }
    }

    SafeRelease(&pMixerType);
    SafeRelease(&pOptimalType);
    SafeRelease(&pVideoType);

    return hr;
}

비디오 미디어 유형에 대한 자세한 내용은 비디오 미디어 유형을 참조 하세요.

Direct3D 디바이스 관리

발표자는 Direct3D 디바이스를 만들고 스트리밍 중에 디바이스 손실을 처리합니다. 또한 발표자는 다른 구성 요소가 동일한 디바이스를 사용할 수 있는 방법을 제공하는 Direct3D 디바이스 관리자를 호스팅합니다. 예를 들어 믹서는 Direct3D 디바이스를 사용하여 하위 스트림을 혼합하고, 분리하고, 색 조정을 수행합니다. 디코더가 비디오 가속 디코딩에 Direct3D 디바이스를 사용할 수 있습니다. (비디오 가속에 대한 자세한 내용은 다음을 참조하세요 .DirectX 비디오 가속 2.0.)

Direct3D 디바이스를 설정하려면 다음 단계를 수행합니다.

  1. Direct3DCreate9 또는 Direct3DCreate9Ex를 호출하여 Direct3D 개체를 만듭니다.
  2. IDirect3D9::CreateDevice 또는 IDirect3D9Ex::CreateDevice를 호출하여 디바이스를 만듭니다.
  3. DXVA2CreateDirect3DDeviceManager9를 호출하여 디바이스 관리자를 만듭니다.
  4. IDirect3DDeviceManager9::ResetDevice를 호출하여 디바이스 관리자에서 디바이스를 설정합니다.

다른 파이프라인 구성 요소에 디바이스 관리자가 필요한 경우 EVR에서 IMFGetService::GetService를 호출하고 서비스 GUID에 대한 MR_VIDEO_ACCELERATION_SERVICE 지정합니다. EVR은 발표자에게 요청을 전달합니다. 개체가 IDirect3DDeviceManager9 포인터를 가져오면 IDirect3DDeviceManager9::OpenDeviceHandle을 호출하여 디바이스에 대한 핸들을 가져올 수 있습니다. 개체가 디바이스를 사용해야 하는 경우 IDirect3DDevice9 포인터를 반환하는 IDirect3DDeviceManager9::LockDevice 메서드에 디바이스 핸들을 전달합니다.

디바이스를 만든 후 발표자가 디바이스를 삭제하고 새 디바이스를 만드는 경우 발표자는 ResetDevice를 다시 호출해야 합니다. ResetDevice 메서드는 기존 디바이스 핸들을 무효화하여 LockDevice DXVA2_E_NEW_VIDEO_DEVICE 반환합니다. 이 오류 코드는 디바이스를 사용하여 다른 개체에 새 디바이스 핸들을 열어야 한다는 신호를 표시합니다. 디바이스 관리자 사용에 대한 자세한 내용은 Direct3D 장치 관리자 참조하세요.

발표자는 창 모드 또는 전체 화면 전용 모드에서 디바이스를 만들 수 있습니다. 창 모드의 경우 애플리케이션에서 비디오 창을 지정할 수 있는 방법을 제공해야 합니다. 표준 발표자는 이 목적을 위해 IMFVideoDisplayControl::SetVideoWindow 메서드를 구현합니다. 발표자를 처음 만들 때 디바이스를 만들어야 합니다. 일반적으로 현재는 창 또는 백 버퍼 형식과 같은 모든 디바이스 매개 변수를 알 수 없습니다. 임시 디바이스를 만들고 나중에 바꿀 수 있습니다.&#; 디바이스 관리자에서 ResetDevice를 호출하기만 하면 됩니다.

새 디바이스를 만들거나 기존 디바이스에서 IDirect3DDevice9::Reset 또는 IDirect3DDevice9Ex::ResetEx를 호출하는 경우 EC_DISPLAY_CHANGED 이벤트를 EVR로 보냅니다. 이 이벤트는 미디어 형식을 재협상하도록 EVR에 알릴 수 있습니다. EVR은 이 이벤트에 대한 이벤트 매개 변수를 무시합니다.

Direct3D Surface 할당

발표자가 미디어 형식을 설정한 후에는 믹서가 비디오 프레임을 작성하는 데 사용할 Direct3D 표면을 할당할 수 있습니다. 화면은 발표자의 미디어 유형과 일치해야 합니다.

  • 표면 형식은 미디어 하위 형식과 일치해야 합니다. 예를 들어 하위 형식이 MFVideoFormat_RGB24 경우 표면 형식은 D3DFMT_X8R8G8B8 합니다. 하위 형식 및 Direct3D 형식에 대한 자세한 내용은 비디오 하위 유형 GUID를 참조 하세요.
  • 표면 너비와 높이는 미디어 형식의 MF_MT_FRAME_SIZE 특성에 지정된 차원과 일치해야 합니다.

화면을 할당하는 권장 방법은 발표자가 창에서 실행되는지 전체 화면으로 실행되는지에 따라 달라집니다.

Direct3D 디바이스가 창에 있는 경우 각각 단일 백 버퍼를 사용하여 여러 스왑 체인을 만들 수 있습니다. 한 스왑 체인을 제시해도 다른 스왑 체인을 방해하지 않으므로 이 방법을 사용하여 각 표면을 독립적으로 표시할 수 있습니다. 다른 표면이 프레젠테이션을 위해 예약된 동안 믹서는 표면에 데이터를 쓸 수 있습니다.

먼저 만들 스왑 체인 수를 결정합니다. 최소 3개를 사용하는 것이 좋습니다. 각 스왑 체인에 대해 다음을 수행합니다.

  1. IDirect3DDevice9::CreateAdditionalSwapChain을 호출하여 스왑 체인을 만듭니다.
  2. IDirect3DSwapChain9::GetBackBuffer를 호출하여 스왑 체인의 백 버퍼 화면에 대한 포인터를 가져옵니다.
  3. MFCreateVideoSampleFromSurface를 호출하고 포인터를 표면에 전달합니다. 이 함수는 비디오 샘플 개체에 대한 포인터를 반환합니다. 비디오 샘플 개체는 IMFSample 인터페이스를 구현하고 발표자는 이 인터페이스를 사용하여 발표자가 믹서의 IMFTransform::P rocessOutput 메서드를 호출할 때 믹서에 표면을 전달합니다. 비디오 샘플 개체에 대한 자세한 내용은 비디오 샘플을 참조 하세요.
  4. IMFSample 포인터를 큐에 저장합니다. 발표자는 처리 출력에 설명된 대로 처리하는 동안 이 큐에서 샘플을 가져옵니다.
  5. 스왑 체인이 해제되지 않도록 IDirect3DSwapChain9 포인터에 대한 참조를 유지합니다.

전체 화면 전용 모드에서는 디바이스에 둘 이상의 스왑 체인이 있을 수 없습니다. 이 스왑 체인은 전체 화면 디바이스를 만들 때 암시적으로 만들어집니다. 스왑 체인에는 둘 이상의 백 버퍼가 있을 수 있습니다. 그러나 동일한 스왑 체인의 다른 백 버퍼에 쓰는 동안 하나의 백 버퍼를 제시하는 경우 두 작업을 조정하는 쉬운 방법은 없습니다. 이는 Direct3D가 표면 대칭 이동을 구현하는 방식 때문입니다. Present를 호출하면 그래픽 드라이버가 그래픽 메모리의 표면 포인터를 업데이트합니다. Present를 호출할 때 IDirect3DSurface9 포인터를 보유하는 경우 Present 호출이 반환된 후 다른 버퍼를 가리킵니다.

가장 간단한 옵션은 스왑 체인에 대한 하나의 비디오 샘플을 만드는 것입니다. 이 옵션을 선택하는 경우 창 모드에 지정된 것과 동일한 단계를 수행합니다. 유일한 차이점은 샘플 큐에 단일 비디오 샘플이 포함되어 있다는 것입니다. 또 다른 옵션은 오프스크린 표면을 만든 다음 백 버퍼에 블릿하는 것입니다. 만드는 표면은 믹서가 출력 프레임을 합성하는 데 사용하는 IDirectXVideoProcessor::VideoProcessBlt 메서드를 지원해야 합니다.

추적 샘플

발표자가 먼저 비디오 샘플을 할당하면 큐에 배치됩니다. 발표자는 믹서에서 새 프레임을 가져와야 할 때마다 이 큐에서 그립니다. 믹서가 프레임을 출력한 후 발표자는 샘플을 두 번째 큐로 이동합니다. 두 번째 큐는 예약된 프레젠테이션 시간을 기다리는 샘플용입니다.

각 샘플의 상태 쉽게 추적할 수 있도록 비디오 샘플 개체는 IMFTrackedSample 인터페이스를 구현합니다. 다음과 같이 이 인터페이스를 사용할 수 있습니다.

  1. 발표자에서 IMFAsyncCallback 인터페이스를 구현합니다.

  2. 예약된 큐에 샘플을 배치하기 전에 IMFTrackedSample 인터페이스에 대한 비디오 샘플 개체를 쿼리합니다.

  3. 콜백 인터페이스에 대한 포인터를 사용하여 IMFTrackedSample::SetAllocator를 호출합니다.

  4. 샘플을 프레젠테이션할 준비가 되면 예약된 큐에서 제거한 후 프레젠테이션하고 샘플에 대한 모든 참조를 해제합니다.

  5. 샘플은 콜백을 호출합니다. (이 경우 샘플 개체는 콜백이 호출될 때까지 자체에 대한 참조 횟수를 보유하기 때문에 삭제되지 않습니다.)

  6. 콜백 내에서 샘플을 사용 가능한 큐로 반환합니다.

발표자는 IMFTrackedSample을 사용하여 샘플을 추적할 필요가 없습니다. 디자인에 가장 적합한 기술을 구현할 수 있습니다. IMFTrackedSample한 가지 장점은 발표자의 일정 및 렌더링 함수를 도우미 개체로 이동할 수 있으며 샘플 개체가 해당 메커니즘을 제공하므로 비디오 샘플을 릴리스할 때 발표자에게 다시 호출하기 위한 특별한 메커니즘이 필요하지 않다는 것입니다.

다음 코드는 콜백을 설정하는 방법을 보여줍니다.

HRESULT EVRCustomPresenter::TrackSample(IMFSample *pSample)
{
    IMFTrackedSample *pTracked = NULL;

    HRESULT hr = pSample->QueryInterface(IID_PPV_ARGS(&pTracked));

    if (SUCCEEDED(hr))
    {
        hr = pTracked->SetAllocator(&m_SampleFreeCB, NULL);
    }

    SafeRelease(&pTracked);
    return hr;
}

콜백에서 비동기 결과 개체에서 IMFAsyncResult::GetObject를 호출하여 샘플에 대한 포인터를 검색합니다.

HRESULT EVRCustomPresenter::OnSampleFree(IMFAsyncResult *pResult)
{
    IUnknown *pObject = NULL;
    IMFSample *pSample = NULL;
    IUnknown *pUnk = NULL;

    // Get the sample from the async result object.
    HRESULT hr = pResult->GetObject(&pObject);
    if (FAILED(hr))
    {
        goto done;
    }

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

    // If this sample was submitted for a frame-step, the frame step operation
    // is complete.

    if (m_FrameStep.state == FRAMESTEP_SCHEDULED)
    {
        // Query the sample for IUnknown and compare it to our cached value.
        hr = pSample->QueryInterface(IID_PPV_ARGS(&pUnk));
        if (FAILED(hr))
        {
            goto done;
        }

        if (m_FrameStep.pSampleNoRef == (DWORD_PTR)pUnk)
        {
            // Notify the EVR.
            hr = CompleteFrameStep(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Note: Although pObject is also an IUnknown pointer, it is not
        // guaranteed to be the exact pointer value returned through
        // QueryInterface. Therefore, the second QueryInterface call is
        // required.
    }

    /*** Begin lock ***/

    EnterCriticalSection(&m_ObjectLock);

    UINT32 token = MFGetAttributeUINT32(
        pSample, MFSamplePresenter_SampleCounter, (UINT32)-1);

    if (token == m_TokenCounter)
    {
        // Return the sample to the sample pool.
        hr = m_SamplePool.ReturnSample(pSample);
        if (SUCCEEDED(hr))
        {
            // A free sample is available. Process more data if possible.
            (void)ProcessOutputLoop();
        }
    }

    LeaveCriticalSection(&m_ObjectLock);

    /*** End lock ***/

done:
    if (FAILED(hr))
    {
        NotifyEvent(EC_ERRORABORT, hr, 0);
    }
    SafeRelease(&pObject);
    SafeRelease(&pSample);
    SafeRelease(&pUnk);
    return hr;
}

출력 처리

믹서가 새 입력 샘플을 받을 때마다 EVR은 발표자에게 MFVP_MESSAGE_PROCESSINPUTNOTIFY 메시지를 보냅니다. 이 메시지는 믹서에 전달할 새 비디오 프레임이 있을 수 있음을 나타냅니다. 이에 대해 발표자는 믹서에서 IMFTransform::P rocessOutput을 호출합니다. 메서드가 성공하면 발표자는 프레젠테이션 샘플을 예약합니다.

믹서에서 출력을 얻으려면 다음 단계를 수행합니다.

  1. 클록 상태를 확인합니다. 시계가 일시 중지된 경우 첫 번째 비디오 프레임이 아니면 MFVP_MESSAGE_PROCESSINPUTNOTIFY 메시지를 무시합니다. 시계가 실행 중이거나 첫 번째 비디오 프레임인 경우 계속합니다.

  2. 사용 가능한 샘플의 큐에서 샘플을 가져옵니다. 큐가 비어 있으면 할당된 모든 샘플이 현재 프레젠테이션되도록 예약되어 있음을 의미합니다. 이 경우 현재 MFVP_MESSAGE_PROCESSINPUTNOTIFY 메시지를 무시합니다. 다음 샘플을 사용할 수 있게 되면 여기에 나열된 단계를 반복합니다.

  3. (선택 사항) 시계를 사용할 수 있는 경우 IMFClock::GetCorrelatedTime을 호출하여 현재 시계 시간(T1)을 가져옵니다.

  4. 믹서에서 IMFTransform::P rocessOutput을 호출합니다. ProcessOutput이 성공하면 샘플에 비디오 프레임이 포함됩니다. 메서드가 실패하면 반환 코드를 검사. ProcessOutput다음 오류 코드는 중요한 오류가 아닙니다.

    오류 코드 설명
    MF_E_TRANSFORM_NEED_MORE_INPUT 믹서는 새 출력 프레임을 생성하기 전에 더 많은 입력이 필요합니다.
    이 오류 코드가 표시되면 스트림의 끝에 도달했는지 여부를 검사 스트림의 끝에 설명된 대로 적절하게 응답합니다. 그렇지 않으면 이 MF_E_TRANSFORM_NEED_MORE_INPUT 메시지를 무시합니다. 믹서가 더 많은 입력을 받으면 EVR이 다른 EVR을 보냅니다.
    MF_E_TRANSFORM_STREAM_CHANGE 서식 변경 업스트림 인해 믹서의 출력 형식이 잘못되었습니다.
    이 오류 코드가 표시되면 발표자의 미디어 유형을 NULL설정합니다. EVR은 새 형식을 요청합니다.
    MF_E_TRANSFORM_TYPE_NOT_SET 믹서에는 새 미디어 유형이 필요합니다.
    이 오류 코드가 표시되면 협상 형식에 설명된 대로 믹서의 출력 형식을 재협상합니다.

     

    ProcessOutput이 성공하면 계속합니다.

  5. (선택 사항) 클록을 사용할 수 있는 경우 현재 시계 시간(T2)을 가져옵니다. 믹서에 의해 도입된 대기 시간의 양은 (T2 - T1)입니다. 이 값이 있는 EC_PROCESSING_LATENCY 이벤트를 EVR로 보냅니다. EVR은 품질 관리에 이 값을 사용합니다. 사용할 수 있는 클록이 없는 경우 EC_PROCESSING_LATENCY 이벤트를 보낼 이유가 없습니다.

  6. (선택 사항) 추적 샘플에 설명된 대로 IMFTrackedSample 샘플을 쿼리하고 IMFTrackedSample::SetAllocator호출합니다.

  7. 프레젠테이션 샘플을 예약합니다.

이 단계 시퀀스는 발표자가 믹서에서 출력을 가져오기 전에 종료할 수 있습니다. 요청이 삭제되지 않도록 하려면 다음이 발생할 때 동일한 단계를 반복해야 합니다.

  • 발표자의 IMFClockStateSink::OnClockStart 또는 IMFClockStateSink::OnClockStart 메서드가 호출됩니다. 클록이 일시 중지되었기 때문에 믹서가 입력을 무시하는 경우를 처리합니다(1단계).
  • IMFTrackedSample 콜백이 호출됩니다. 믹서가 입력을 수신하지만 발표자의 모든 비디오 샘플이 사용 중인 경우를 처리합니다(2단계).

다음 몇 가지 코드 예제에서는 이러한 단계를 자세히 보여 줍니다. 발표자는 MFVP_MESSAGE_PROCESSINPUTNOTIFY 메시지를 받으면 메서드를 호출 ProcessInputNotify 합니다(다음 예제 참조).

//-----------------------------------------------------------------------------
// ProcessInputNotify
//
// Attempts to get a new output sample from the mixer.
//
// This method is called when the EVR sends an MFVP_MESSAGE_PROCESSINPUTNOTIFY
// message, which indicates that the mixer has a new input sample.
//
// Note: If there are multiple input streams, the mixer might not deliver an
// output sample for every input sample.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessInputNotify()
{
    HRESULT hr = S_OK;

    // Set the flag that says the mixer has a new sample.
    m_bSampleNotify = TRUE;

    if (m_pMediaType == NULL)
    {
        // We don't have a valid media type yet.
        hr = MF_E_TRANSFORM_TYPE_NOT_SET;
    }
    else
    {
        // Try to process an output sample.
        ProcessOutputLoop();
    }
    return hr;
}

ProcessInputNotify 메서드는 믹서에 새 입력이 있다는 사실을 기록하도록 부울 플래그를 설정합니다. 그런 다음, 다음 예제와 ProcessOutputLoop 같이 메서드를 호출합니다. 이 메서드는 믹서에서 가능한 한 많은 샘플을 끌어오려고 시도합니다.

void EVRCustomPresenter::ProcessOutputLoop()
{
    HRESULT hr = S_OK;

    // Process as many samples as possible.
    while (hr == S_OK)
    {
        // If the mixer doesn't have a new input sample, break from the loop.
        if (!m_bSampleNotify)
        {
            hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
            break;
        }

        // Try to process a sample.
        hr = ProcessOutput();

        // NOTE: ProcessOutput can return S_FALSE to indicate it did not
        // process a sample. If so, break out of the loop.
    }

    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
    {
        // The mixer has run out of input data. Check for end-of-stream.
        CheckEndOfStream();
    }
}

다음 예제에 표시된 메서드는 ProcessOutput 믹서에서 단일 비디오 프레임을 얻으려고 시도합니다. 사용할 수 있는 ProcessSample 비디오 프레임이 없으면 S_FALSE 또는 오류 코드를 반환합니다. 이 코드는 루프를 ProcessOutputLoop중단합니다. 대부분의 작업은 메서드 내에서 ProcessOutput 수행됩니다.

//-----------------------------------------------------------------------------
// ProcessOutput
//
// Attempts to get a new output sample from the mixer.
//
// Called in two situations:
// (1) ProcessOutputLoop, if the mixer has a new input sample.
// (2) Repainting the last frame.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessOutput()
{
    assert(m_bSampleNotify || m_bRepaint);  // See note above.

    HRESULT     hr = S_OK;
    DWORD       dwStatus = 0;
    LONGLONG    mixerStartTime = 0, mixerEndTime = 0;
    MFTIME      systemTime = 0;
    BOOL        bRepaint = m_bRepaint; // Temporarily store this state flag.

    MFT_OUTPUT_DATA_BUFFER dataBuffer;
    ZeroMemory(&dataBuffer, sizeof(dataBuffer));

    IMFSample *pSample = NULL;

    // If the clock is not running, we present the first sample,
    // and then don't present any more until the clock starts.

    if ((m_RenderState != RENDER_STATE_STARTED) &&  // Not running.
         !m_bRepaint &&             // Not a repaint request.
         m_bPrerolled               // At least one sample has been presented.
         )
    {
        return S_FALSE;
    }

    // Make sure we have a pointer to the mixer.
    if (m_pMixer == NULL)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Try to get a free sample from the video sample pool.
    hr = m_SamplePool.GetSample(&pSample);
    if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
    {
        // No free samples. Try again when a sample is released.
        return S_FALSE;
    }
    else if (FAILED(hr))
    {
        return hr;
    }

    // From now on, we have a valid video sample pointer, where the mixer will
    // write the video data.
    assert(pSample != NULL);

    // (If the following assertion fires, it means we are not managing the sample pool correctly.)
    assert(MFGetAttributeUINT32(pSample, MFSamplePresenter_SampleCounter, (UINT32)-1) == m_TokenCounter);

    if (m_bRepaint)
    {
        // Repaint request. Ask the mixer for the most recent sample.
        SetDesiredSampleTime(
            pSample,
            m_scheduler.LastSampleTime(),
            m_scheduler.FrameDuration()
            );

        m_bRepaint = FALSE; // OK to clear this flag now.
    }
    else
    {
        // Not a repaint request. Clear the desired sample time; the mixer will
        // give us the next frame in the stream.
        ClearDesiredSampleTime(pSample);

        if (m_pClock)
        {
            // Latency: Record the starting time for ProcessOutput.
            (void)m_pClock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
        }
    }

    // Now we are ready to get an output sample from the mixer.
    dataBuffer.dwStreamID = 0;
    dataBuffer.pSample = pSample;
    dataBuffer.dwStatus = 0;

    hr = m_pMixer->ProcessOutput(0, 1, &dataBuffer, &dwStatus);

    if (FAILED(hr))
    {
        // Return the sample to the pool.
        HRESULT hr2 = m_SamplePool.ReturnSample(pSample);
        if (FAILED(hr2))
        {
            hr = hr2;
            goto done;
        }
        // Handle some known error codes from ProcessOutput.
        if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
        {
            // The mixer's format is not set. Negotiate a new format.
            hr = RenegotiateMediaType();
        }
        else if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
        {
            // There was a dynamic media type change. Clear our media type.
            SetMediaType(NULL);
        }
        else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
            // The mixer needs more input.
            // We have to wait for the mixer to get more input.
            m_bSampleNotify = FALSE;
        }
    }
    else
    {
        // We got an output sample from the mixer.

        if (m_pClock && !bRepaint)
        {
            // Latency: Record the ending time for the ProcessOutput operation,
            // and notify the EVR of the latency.

            (void)m_pClock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);

            LONGLONG latencyTime = mixerEndTime - mixerStartTime;
            NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
        }

        // Set up notification for when the sample is released.
        hr = TrackSample(pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Schedule the sample.
        if ((m_FrameStep.state == FRAMESTEP_NONE) || bRepaint)
        {
            hr = DeliverSample(pSample, bRepaint);
            if (FAILED(hr))
            {
                goto done;
            }
        }
        else
        {
            // We are frame-stepping (and this is not a repaint request).
            hr = DeliverFrameStepSample(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        m_bPrerolled = TRUE; // We have presented at least one sample now.
    }

done:
    SafeRelease(&pSample);

    // Important: Release any events returned from the ProcessOutput method.
    SafeRelease(&dataBuffer.pEvents);
    return hr;
}

이 예제에 대한 몇 가지 설명은 다음과 같습니다.

  • m_SamplePool 변수는 사용 가능한 비디오 샘플의 큐를 보유하는 컬렉션 개체로 간주됩니다. 개체의 GetSample 메서드는 큐가 비어 있으면 MF_E_SAMPLEALLOCATOR_EMPTY 반환합니다.
  • mixer의 ProcessOutput 메서드가 MF_E_TRANSFORM_NEED_MORE_INPUT 반환하면 믹서가 더 이상 출력을 생성할 수 없으므로 발표자가 m_fSampleNotify 플래그를 지웁니다.
  • TrackSample IMFTrackedSample 콜백을 설정하는 메서드는 추적 샘플 섹션에 표시됩니다.

프레임 다시 그리기

경우에 따라 발표자는 최신 비디오 프레임을 다시 그려야 할 수 있습니다. 예를 들어 표준 발표자는 다음과 같은 상황에서 프레임을 다시 칠합니다.

  • 창 모드에서 애플리케이션의 창에서 받은 WM_PAINT 메시지에 대한 응답입니다. IMFVideoDisplayControl::RepaintVideo를 참조하세요.
  • 애플리케이션이 IMFVideoDisplayControl::SetVideoPosition을 호출할 때 대상 사각형이 변경되는 경우

다음 단계를 사용하여 믹서에 최신 프레임을 다시 만들도록 요청합니다.

  1. 큐에서 비디오 샘플을 가져옵니다.
  2. IMFDesiredSample 인터페이스에 대한 샘플을 쿼리합니다.
  3. IMFDesiredSample::SetDesiredSampleTimeAndDuration을 호출 합니다. 가장 최근 비디오 프레임의 타임스탬프를 지정합니다. (이 값을 캐시하고 각 프레임에 대해 업데이트해야 합니다.)
  4. 믹서에서 ProcessOutput을 호출합니다.

프레임을 다시 칠할 때 프레젠테이션 시계를 무시하고 프레임을 즉시 표시할 수 있습니다.

샘플 예약

비디오 프레임은 언제든지 EVR에 도달할 수 있습니다. 발표자는 프레임의 타임스탬프를 기준으로 각 프레임을 올바른 시간에 프레젠테이션할 책임이 있습니다. 발표자가 믹서에서 새 샘플을 가져오면 예약된 큐에 샘플을 배치합니다. 별도의 스레드에서 발표자는 계속해서 큐의 헤드에서 첫 번째 샘플을 가져오고 다음을 수행할지 여부를 결정합니다.

  • 샘플을 제공합니다.
  • 샘플은 초기이므로 큐에 유지합니다.
  • 샘플이 늦기 때문에 카드. 가능하면 프레임을 삭제하지 않아야 하지만 발표자가 지속적으로 뒤처지는 경우 필요할 수 있습니다.

비디오 프레임에 대한 타임스탬프를 얻으려면 비디오 샘플에서 IMFSample::GetSampleTime을 호출합니다. 타임스탬프는 EVR의 프레젠테이션 시계에 상대적입니다. 현재 시계 시간을 얻으려면 IMFClock::GetCorrelatedTime을 호출 합니다. EVR에 프레젠테이션 시계가 없거나 샘플에 타임스탬프가 없는 경우 샘플을 받은 직후에 표시할 수 있습니다.

각 샘플의 기간을 얻으려면 IMFSample::GetSampleDuration을 호출 합니다. 샘플에 기간이 없는 경우 MFFrameRateToAverageTimePerFrame 함수를 사용하여 프레임 속도에서 기간을 계산할 수 있습니다.

샘플을 예약할 때는 다음 사항에 유의하세요.

  • 재생 속도가 정상 속도보다 빠르거나 느린 경우 시계는 더 빠르거나 느린 속도로 실행됩니다. 즉, 샘플의 타임스탬프는 항상 프레젠테이션 시계를 기준으로 올바른 대상 시간을 제공합니다. 그러나 프레젠테이션 시간을 다른 클록 시간(예: 고해상도 성능 카운터)으로 변환하는 경우 클록 속도에 따라 시간을 조정해야 합니다. 클록 속도가 변경되면 EVR은 발표자의 IMFClockStateSink::OnClockSetRate 메서드를 호출합니다.
  • 재생 속도는 역방향 재생에 대해 음수일 수 있습니다. 재생 속도가 음수이면 프레젠테이션 시계가 뒤로 실행됩니다. 즉, 시간 N + 1 시간 N 전에 발생 합니다.

다음 예제에서는 프레젠테이션 클록을 기준으로 샘플의 초기 또는 지연 시간을 계산합니다.

    LONGLONG hnsPresentationTime = 0;
    LONGLONG hnsTimeNow = 0;
    MFTIME   hnsSystemTime = 0;

    BOOL bPresentNow = TRUE;
    LONG lNextSleep = 0;

    if (m_pClock)
    {
        // Get the sample's time stamp. It is valid for a sample to
        // have no time stamp.
        hr = pSample->GetSampleTime(&hnsPresentationTime);

        // Get the clock time. (But if the sample does not have a time stamp,
        // we don't need the clock time.)
        if (SUCCEEDED(hr))
        {
            hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
        }

        // Calculate the time until the sample's presentation time.
        // A negative value means the sample is late.
        LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
        if (m_fRate < 0)
        {
            // For reverse playback, the clock runs backward. Therefore, the
            // delta is reversed.
            hnsDelta = - hnsDelta;
        }

프레젠테이션 시계는 일반적으로 시스템 시계 또는 오디오 렌더러에 의해 구동됩니다. (오디오 렌더러는 사운드 카드 오디오를 사용하는 속도에서 파생됩니다.) 일반적으로 프레젠테이션 시계는 모니터의 새로 고침 속도와 동기화되지 않습니다.

Direct3D 프레젠테이션 매개 변수가 프레젠테이션 간격에 대한 D3DPRESENT_INTERVAL_DEFAULT 또는 D3DPRESENT_INTERVAL_ONE 지정하는 경우 프레젠테이션 작업은 모니터의 세로 추적을 기다립니다. 이는 손쉽게 찢어지는 것을 방지하는 방법이지만 일정 알고리즘의 정확도를 줄입니다. 반대로 프레젠테이션 간격이 D3DPRESENT_INTERVAL_IMMEDIATE 경우 Present 메서드가 즉시 실행되므로 예약 알고리즘이 세로 추적 기간 동안에만 Present를 호출할 만큼 정확하지 않으면 중단이 발생합니다.

다음 함수는 정확한 타이밍 정보를 얻는 데 도움이 될 수 있습니다.

  • IDirect3DDevice9::GetRasterStatus 는 현재 검사 라인 및 래스터가 세로 빈 기간에 있는지 여부를 포함하여 래스터에 대한 정보를 반환합니다.
  • DwmGetCompositionTimingInfo 는 데스크톱 창 관리자에 대한 타이밍 정보를 반환합니다. 이 정보는 데스크톱 컴퍼지션을 사용하는 경우에 유용합니다.

샘플 발표

이 섹션에서는 Direct3D Surface 할당에 설명된 대로 각 표면에 대해 별도의 스왑 체인을 만들었다고 가정합니다. 샘플을 제공하려면 다음과 같이 비디오 샘플에서 스왑 체인을 가져옵니다.

  1. 비디오 샘플에서 IMFSample::GetBufferByIndex를 호출하여 버퍼를 가져옵니다.
  2. IMFGetService 인터페이스에 대한 버퍼를 쿼리합니다.
  3. IMFGetService::GetService를 호출하여 Direct3D 표면의 IDirect3DSurface9 인터페이스를 가져옵니다. (호출 하여 이 단계와 이전 단계를 하나로 결합할 수 있습니다.MFGetService.)
  4. 화면에서 IDirect3DSurface9::GetContainer를 호출하여 스왑 체인에 대한 포인터를 가져옵니다.
  5. 스왑 체인에서 IDirect3DSwapChain9::P resent를 호출합니다.

다음 코드는 다음 단계를 보여줍니다.

HRESULT D3DPresentEngine::PresentSample(IMFSample* pSample, LONGLONG llTarget)
{
    HRESULT hr = S_OK;

    IMFMediaBuffer* pBuffer = NULL;
    IDirect3DSurface9* pSurface = NULL;
    IDirect3DSwapChain9* pSwapChain = NULL;

    if (pSample)
    {
        // Get the buffer from the sample.
        hr = pSample->GetBufferByIndex(0, &pBuffer);
        if (FAILED(hr))
        {
            goto done;
        }

        // Get the surface from the buffer.
        hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pSurface));
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (m_pSurfaceRepaint)
    {
        // Redraw from the last surface.
        pSurface = m_pSurfaceRepaint;
        pSurface->AddRef();
    }

    if (pSurface)
    {
        // Get the swap chain from the surface.
        hr = pSurface->GetContainer(IID_PPV_ARGS(&pSwapChain));
        if (FAILED(hr))
        {
            goto done;
        }

        // Present the swap chain.
        hr = PresentSwapChain(pSwapChain, pSurface);
        if (FAILED(hr))
        {
            goto done;
        }

        // Store this pointer in case we need to repaint the surface.
        CopyComPointer(m_pSurfaceRepaint, pSurface);
    }
    else
    {
        // No surface. All we can do is paint a black rectangle.
        PaintFrameWithGDI();
    }

done:
    SafeRelease(&pSwapChain);
    SafeRelease(&pSurface);
    SafeRelease(&pBuffer);

    if (FAILED(hr))
    {
        if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG)
        {
            // We failed because the device was lost. Fill the destination rectangle.
            PaintFrameWithGDI();

            // Ignore. We need to reset or re-create the device, but this method
            // is probably being called from the scheduler thread, which is not the
            // same thread that created the device. The Reset(Ex) method must be
            // called from the thread that created the device.

            // The presenter will detect the state when it calls CheckDeviceState()
            // on the next sample.
            hr = S_OK;
        }
    }
    return hr;
}

원본 및 대상 사각형

원본 사각형은 표시할 비디오 프레임의 부분입니다. 전체 비디오 프레임이 {0, 0, 1, 1} 좌표가 있는 사각형을 차지하는 정규화된 좌표계를 기준으로 정의됩니다. 대상 사각형은 비디오 프레임이 그려지는 대상 표면 내의 영역입니다. 표준 발표자를 사용하면 애플리케이션이 IMFVideoDisplayControl::SetVideoPosition을 호출하여 이러한 사각형을 설정할 수 있습니다.

원본 및 대상 사각형을 적용하는 몇 가지 옵션이 있습니다. 첫 번째 옵션은 믹서가 적용하도록 하는 것입니다.

  • VIDEO_ZOOM_RECT 특성을 사용하여 원본 사각형을 설정합니다. 믹서는 비디오를 대상 화면에 표시할 때 원본 사각형을 적용합니다. 믹서의 기본 원본 사각형은 전체 프레임입니다.
  • 대상 사각형을 믹서의 출력 형식에서 기하학적 조리개로 설정합니다. 자세한 내용은 협상 형식을 참조 하세요.

두 번째 옵션은 Present 메서드에서 pSourceRectpDestRect 매개 변수를 지정하여 IDirect3DSwapChain9::P resent일 때 사각형을 적용하는 것입니다. 이러한 옵션을 결합할 수 있습니다. 예를 들어 믹서에서 원본 사각형을 설정하지만 Present 메서드에서 대상 사각형을 적용할 수 있습니다.

애플리케이션이 대상 사각형을 변경하거나 창의 크기를 조정하는 경우 새 표면을 할당해야 할 수 있습니다. 이 경우 이 작업을 예약 스레드와 동기화해야 합니다. 새 표면을 할당하기 전에 예약 큐를 플러시하고 이전 샘플을 카드.

스트림의 끝

EVR의 모든 입력 스트림이 종료되면 EVR은 발표자에게 MFVP_MESSAGE_ENDOFSTREAM 메시지를 보냅니다. 그러나 메시지를 받을 때 몇 가지 비디오 프레임이 다시 기본 처리될 수 있습니다. 스트림 끝 메시지에 응답하기 전에 믹서에서 모든 출력을 드레이닝하고 모든 재기본 프레임을 표시해야 합니다. 마지막 프레임이 표시되면 EC_COMPLETE 이벤트를 EVR로 보냅니다.

다음 예제에서는 다양한 조건이 충족되는 경우 EC_COMPLETE 이벤트를 보내는 메서드를 보여 줍니다. 그렇지 않으면 이벤트를 보내지 않고 S_OK 반환합니다.

HRESULT EVRCustomPresenter::CheckEndOfStream()
{
    if (!m_bEndStreaming)
    {
        // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
        return S_OK;
    }

    if (m_bSampleNotify)
    {
        // The mixer still has input.
        return S_OK;
    }

    if (m_SamplePool.AreSamplesPending())
    {
        // Samples are still scheduled for rendering.
        return S_OK;
    }

    // Everything is complete. Now we can tell the EVR that we are done.
    NotifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
    m_bEndStreaming = FALSE;
    return S_OK;
}

이 메서드는 다음 상태를 검사.

  • m_fSampleNotify 변수가 TRUE면 믹서에 아직 처리되지 않은 프레임이 하나 이상 있음을 의미합니다. (자세한 내용은 다음을 참조하세요 .출력 처리.)
  • m_fEndStreaming 변수는 초기 값 FALSE인 부울 플래그입니다. 발표자는 EVR이 MFVP_MESSAGE_ENDOFSTREAM 메시지를 보낼 때 플래그를 TRUE설정합니다.
  • AreSamplesPending 이 메서드는 하나 이상의 프레임이 예약된 큐에서 대기하는 한 TRUE를 반환하는 것으로 간주됩니다.

IMFVideoPresenter::P rocessMessage 메서드에서 m_fEndStreaming TRUE설정하고 EVR이 MFVP_MESSAGE_ENDOFSTREAM 메시지를 보낼 때 호출 CheckEndOfStream 합니다.

HRESULT EVRCustomPresenter::ProcessMessage(
    MFVP_MESSAGE_TYPE eMessage,
    ULONG_PTR ulParam
    )
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_ObjectLock);

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

    switch (eMessage)
    {
    // Flush all pending samples.
    case MFVP_MESSAGE_FLUSH:
        hr = Flush();
        break;

    // Renegotiate the media type with the mixer.
    case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
        hr = RenegotiateMediaType();
        break;

    // The mixer received a new input sample.
    case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
        hr = ProcessInputNotify();
        break;

    // Streaming is about to start.
    case MFVP_MESSAGE_BEGINSTREAMING:
        hr = BeginStreaming();
        break;

    // Streaming has ended. (The EVR has stopped.)
    case MFVP_MESSAGE_ENDSTREAMING:
        hr = EndStreaming();
        break;

    // All input streams have ended.
    case MFVP_MESSAGE_ENDOFSTREAM:
        // Set the EOS flag.
        m_bEndStreaming = TRUE;
        // Check if it's time to send the EC_COMPLETE event to the EVR.
        hr = CheckEndOfStream();
        break;

    // Frame-stepping is starting.
    case MFVP_MESSAGE_STEP:
        hr = PrepareFrameStep(LODWORD(ulParam));
        break;

    // Cancels frame-stepping.
    case MFVP_MESSAGE_CANCELSTEP:
        hr = CancelFrameStep();
        break;

    default:
        hr = E_INVALIDARG; // Unknown message. This case should never occur.
        break;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

또한 믹서의 IMFTransform::P rocessOutput 메서드가 MF_E_TRANSFORM_NEED_MORE_INPUT 반환하는지 호출 CheckEndOfStream 합니다. 이 오류 코드는 믹서에 더 이상 입력 샘플이 없음을 나타냅니다(출력 처리 참조).

프레임 스테핑

EVR은 Media Foundation에서 DirectShow의 프레임 스테핑 및 스크러빙을 지원하도록 설계되었습니다. 프레임 스테핑 및 스크러빙은 개념적으로 비슷합니다. 두 경우 모두 애플리케이션은 한 번에 하나의 비디오 프레임을 요청합니다. 내부적으로 발표자는 동일한 메커니즘을 사용하여 두 기능을 모두 구현합니다.

DirectShow의 프레임 스테핑은 다음과 같이 작동합니다.

Media Foundation에서 스크러빙은 다음과 같이 작동합니다.

  • 애플리케이션은 IMFRateControl::SetRate를 호출하여 재생 속도를 0으로 설정합니다.
  • 새 프레임을 렌더링하기 위해 애플리케이션은 IMFMediaSession::Start를 원하는 위치로 호출합니다. EVR은 ulParam1인 MFVP_MESSAGE_STEP 메시지를 보냅니다.
  • 스크러빙을 중지하기 위해 애플리케이션은 재생 속도를 0이 아닌 값으로 설정합니다. EVR은 MFVP_MESSAGE_CANCELSTEP 메시지를 보냅니다.

MFVP_MESSAGE_STEP 메시지를 받은 후 발표자는 대상 프레임이 도착할 때까지 기다립니다. 단계 수가 N이면 발표자는 다음(N - 1) 샘플을 카드 N번째 샘플을 표시합니다. 발표자가 프레임 단계를 완료하면 lParam1이 FALSE설정된 EC_STEP_COMPLETE 이벤트를 EVR로 보냅니다. 또한 재생 속도가 0이면 발표자는 EC_SCRUB_TIME 이벤트를 보냅니다. 프레임 단계 작업이 보류 중인 동안 EVR이 프레임 스테이징을 취소하면 발표자는 lParam1이 TRUE설정된 EC_STEP_COMPLETE 이벤트를 보냅니다.

애플리케이션은 단계를 프레임하거나 여러 번 스크럽할 수 있으므로 발표자는 MFVP_MESSAGE_CANCELSTEP 메시지를 받기 전에 여러 MFVP_MESSAGE_STEP 메시지를 받을 수 있습니다. 또한 발표자는 클록이 시작되기 전이나 시계가 실행되는 동안 MFVP_MESSAGE_STEP 메시지를 받을 수 있습니다.

프레임 스테핑 구현

이 섹션에서는 프레임 스테핑을 구현하는 알고리즘에 대해 설명합니다. 프레임 단계별 알고리즘은 다음 변수를 사용합니다.

  • step_count. 현재 프레임 스테핑 작업의 단계 수를 지정하는 부호 없는 정수입니다.

  • step_queue. IMFSample 포인터의 큐입니다.

  • step_state. 언제든지 발표자는 프레임 스테핑과 관련하여 다음 상태 중 하나에 있을 수 있습니다.

    시스템 상태 설명
    NOT_STEPPING 프레임 스테핑이 아닙니다.
    기다리고 발표자가 MFVP_MESSAGE_STEP 메시지를 받았지만 시계가 시작되지 않았습니다.
    PENDING 발표자가 MFVP_MESSAGE_STEP 메시지를 받았고 시계가 시작되었지만 발표자가 대상 프레임을 받기를 기다리고 있습니다.
    예약 발표자가 대상 프레임을 받고 프레젠테이션을 예약했지만 프레임이 표시되지 않았습니다.
    완료 발표자가 대상 프레임을 제시하고 EC_STEP_COMPLETE 이벤트를 보냈으며 다음 MFVP_MESSAGE_STEP 또는 MFVP_MESSAGE_CANCELSTEP 메시지를 기다리고 있습니다.

     

    이러한 상태는 발표자 상태 섹션 에 나열된 발표자 상태와 독립적입니다.

다음 절차는 프레임 단계별 알고리즘에 대해 정의됩니다.

PrepareFrameStep 프로시저

  1. 분 step_count.
  2. step_state 대기로 설정합니다.
  3. 시계가 실행 중인 경우 StartFrameStep을 호출합니다.

StartFrameStep 프로시저

  1. step_state WAITING과 같으면 step_state 보류 중으로 설정합니다. step_queue 각 샘플에 대해 DeliverFrameStepSample을 호출합니다.
  2. step_state NOT_STEPPING 같으면 step_queue 샘플을 제거하고 프레젠테이션을 예약합니다.

CompleteFrameStep 프로시저

  1. step_state COMPLETE로 설정합니다.
  2. lParam1 FALSE를 사용하여 EC_STEP_COMPLETE 이벤트를 보냅니다. =
  3. 클록 속도가 0이면 샘플 시간으로 EC_SCRUB_TIME 이벤트를 보냅니다.

DeliverFrameStepSample 프로시저

  1. 클록 속도가 0이고 샘플 시간 + 샘플 기간<클록 시간이면 샘플을 카드. 종료합니다.
  2. step_state SCHEDULED 또는 COMPLETE와 같으면 샘플을 step_queue 추가합니다. 종료합니다.
  3. step_count 감소합니다.
  4. step_count> 0이면 샘플을 카드. 종료합니다.
  5. step_state WAITING과 같으면 샘플을 step_queue 추가합니다. 종료합니다.
  6. 프레젠테이션 샘플을 예약합니다.
  7. step_state SCHEDULED로 설정합니다.

CancelFrameStep 프로시저

  1. step_state NOT_STEPPING 설정
  2. step_count 0으로 다시 설정합니다.
  3. step_state 이전 값이 WAITING, PENDING 또는 SCHEDULED인 경우 lParam1 = TRUE사용하여 EC_STEP_COMPLETE 보냅니다.

다음과 같이 다음 절차를 호출합니다.

발표자 메시지 또는 메서드 절차
MFVP_MESSAGE_STEP 메시지 PrepareFrameStep
MFVP_MESSAGE_STEP 메시지 CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
IMFTrackedSample 콜백 CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

다음 순서도에서는 프레임 단계별 프로시저를 보여 줍니다.

flow chart showing paths that start with mfvp-message-step and mfvp-message-processinputnotify and end at

EVR에서 발표자 설정

발표자를 구현한 후 다음 단계는 EVR을 사용하도록 구성하는 것입니다.

DirectShow에서 발표자 설정

DirectShow 애플리케이션에서 다음과 같이 EVR에서 발표자를 설정합니다.

  1. CoCreateInstance를 호출하여 EVR 필터를 만듭니다. CLSID가 CLSID_EnhancedVideoRenderer.
  2. 필터 그래프에 EVR을 추가합니다.
  3. 발표자의 인스턴스를 만듭니다. 발표자는 IClassFactory를 통해 표준 COM 개체 만들기를 지원할 수 있지만 필수는 아닙니다.
  4. IMFVideoRenderer 인터페이스에 대한 EVR 필터를 쿼리합니다.
  5. IMFVideoRenderer::InitializeRenderer를 호출 합니다.

Media Foundation에서 발표자 설정

Media Foundation에는 EVR 미디어 싱크 또는 EVR 활성화 개체를 만드는지 여부에 따라 몇 가지 옵션이 있습니다. 활성화 개체에 대한 자세한 내용은 활성화 개체를 참조 하세요.

EVR 미디어 싱크의 경우 다음을 수행합니다.

  1. MFCreateVideoRenderer를 호출하여 미디어 싱크를 만듭니다.
  2. 발표자의 인스턴스를 만듭니다.
  3. IMFVideoRenderer 인터페이스에 대한 EVR 미디어 싱크를 쿼리합니다.
  4. IMFVideoRenderer::InitializeRenderer를 호출 합니다.

EVR 활성화 개체의 경우 다음을 수행합니다.

  1. MFCreateVideoRendererActivate를 호출하여 활성화 개체를 만듭니다.

  2. 활성화 개체에서 다음 특성 중 하나를 설정합니다.

    attribute 설명
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE 발표자의 활성화 개체에 대한 포인터입니다.
    이 플래그를 사용하면 발표자에 대한 활성화 개체를 제공해야 합니다. 활성화 개체는 IMFActivate 인터페이스를 구현해야 합니다.
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID 발표자의 CLSID입니다.
    이 플래그를 사용하면 발표자가 IClassFactory를 통해 표준 COM 개체 만들기를 지원해야 합니다.

     

  3. 필요에 따라 활성화 개체에서 MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS 특성을 설정합니다.

향상된 비디오 렌더러

EVRPresenter 샘플