다음을 통해 공유


비동기 모드에서 원본 판독기 사용

이 항목에서는 비동기 모드에서 원본 판독 기를 사용하는 방법을 설명합니다. 비동기 모드에서 애플리케이션은 데이터를 사용할 수 있음을 애플리케이션에 알리는 데 사용되는 콜백 인터페이스를 제공합니다.

이 항목에서는 원본 판독기를 사용하여 미디어 데이터 처리 항목을 이미 읽었다고 가정합니다.

비동기 모드 사용

원본 판독기는 동기 모드 또는 비동기 모드에서 작동합니다. 이전 섹션에 표시된 코드 예제에서는 원본 판독기가 기본값인 동기 모드를 사용하고 있다고 가정합니다. 동기 모드에서 IMFSourceReader::ReadSample 메서드는 차단하고 미디어 소스는 다음 샘플을 생성합니다. 미디어 원본은 일반적으로 일부 외부 원본(예: 로컬 파일 또는 네트워크 연결)에서 데이터를 획득하므로 메서드는 상당한 시간 동안 호출 스레드를 차단할 수 있습니다.

비동기 모드에서는 ReadSample 이 즉시 반환되고 다른 스레드에서 작업이 수행됩니다. 작업이 완료되면 원본 판독기는 IMFSourceReaderCallback 콜백 인터페이스를 통해 애플리케이션을 호출합니다. 비동기 모드를 사용하려면 다음과 같이 원본 판독기를 처음 만들 때 콜백 포인터를 제공해야 합니다.

  1. MFCreateAttributes 함수를 호출하여 특성 저장소를 만듭니다.
  2. 특성 저장소에서 MF_SOURCE_READER_ASYNC_CALLBACK 특성을 설정합니다. 특성 값은 콜백 개체에 대한 포인터입니다.
  3. 원본 판독기를 만들 때 pAttributes 매개 변수의 생성 함수에 특성 저장소를 전달합니다. 원본 판독기를 만드는 모든 함수에는 이 매개 변수가 있습니다.

다음 예에서는 이 단계를 보여 줍니다.

HRESULT CreateSourceReaderAsync(
    PCWSTR pszURL, 
    IMFSourceReaderCallback *pCallback, 
    IMFSourceReader **ppReader)
{
    HRESULT hr = S_OK;
    IMFAttributes *pAttributes = NULL;

    hr = MFCreateAttributes(&pAttributes, 1);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = MFCreateSourceReaderFromURL(pszURL, pAttributes, ppReader);

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

원본 판독기를 만든 후에는 동기 모드와 비동기 간에 모드를 전환할 수 없습니다.

비동기 모드에서 데이터를 얻으려면 다음 예제와 같이 ReadSample 메서드를 호출하지만 마지막 네 개의 매개 변수를 NULL로 설정합니다.

    // Request the first sample.
    hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
        0, NULL, NULL, NULL, NULL);

ReadSample 메서드가 비동기적으로 완료되면 원본 판독기는 IMFSourceReaderCallback::OnReadSample 메서드를 호출합니다. 이 메서드에는 5개의 매개 변수가 있습니다.

  • hrStatus: HRESULT 값을 포함합니다. 이는 ReadSample 이 동기 모드에서 반환하는 것과 동일한 값입니다. hrStatus에 오류 코드가 포함된 경우 나머지 매개 변수를 무시할 수 있습니다.
  • dwStreamIndex, dwStreamFlags, llTimestamp 및 pSample: 이러한 세 매개 변수는 ReadSample의 마지막 세 매개 변수와 동일합니다. 스트림 번호, 상태 플래그 및 IMFSample 포인터가 각각 포함됩니다.
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

또한 콜백 인터페이스는 두 가지 다른 메서드를 정의합니다.

  • OnEvent. 버퍼링 또는 네트워크 연결 이벤트와 같은 특정 이벤트가 미디어 원본에서 발생하는 경우 애플리케이션에 알림
  • 온플루시. Flush 메서드가 완료되면 호출됩니다.

콜백 인터페이스 구현

OnReadSample 및 다른 콜백 메서드는 작업자 스레드에서 호출되므로 콜백 인터페이스는 스레드로부터 안전해야 합니다.

콜백을 구현할 때 수행할 수 있는 여러 가지 방법이 있습니다. 예를 들어 콜백 내에서 모든 작업을 수행하거나 콜백을 사용하여 애플리케이션에 알리고(예: 이벤트 핸들을 신호로 표시하여) 애플리케이션 스레드에서 작업을 수행할 수 있습니다.

IMFSourceReader::ReadSample 메서드를 호출할 때마다 OnReadSample 메서드가 한 번 호출됩니다. 다음 샘플을 얻으려면 ReadSample을 다시 호출합니다. 오류가 발생하면 hrStatus 매개 변수에 대한 오류 코드와 함께 OnReadSample이 호출됩니다.

다음 예제에서는 콜백 인터페이스의 최소 구현을 보여줍니다. 먼저 인터페이스를 구현하는 클래스의 선언은 다음과 같습니다.

#include <shlwapi.h>

class SourceReaderCB : public IMFSourceReaderCallback
{
public:
    SourceReaderCB(HANDLE hEvent) : 
      m_nRefCount(1), m_hEvent(hEvent), m_bEOS(FALSE), m_hrStatus(S_OK)
    {
        InitializeCriticalSection(&m_critsec);
    }

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
    {
        static const QITAB qit[] =
        {
            QITABENT(SourceReaderCB, IMFSourceReaderCallback),
            { 0 },
        };
        return QISearch(this, qit, iid, ppv);
    }
    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_nRefCount);
    }
    STDMETHODIMP_(ULONG) Release()
    {
        ULONG uCount = InterlockedDecrement(&m_nRefCount);
        if (uCount == 0)
        {
            delete this;
        }
        return uCount;
    }

    // IMFSourceReaderCallback methods
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

    STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *)
    {
        return S_OK;
    }

    STDMETHODIMP OnFlush(DWORD)
    {
        return S_OK;
    }

public:
    HRESULT Wait(DWORD dwMilliseconds, BOOL *pbEOS)
    {
        *pbEOS = FALSE;

        DWORD dwResult = WaitForSingleObject(m_hEvent, dwMilliseconds);
        if (dwResult == WAIT_TIMEOUT)
        {
            return E_PENDING;
        }
        else if (dwResult != WAIT_OBJECT_0)
        {
            return HRESULT_FROM_WIN32(GetLastError());
        }

        *pbEOS = m_bEOS;
        return m_hrStatus;
    }
    
private:
    
    // Destructor is private. Caller should call Release.
    virtual ~SourceReaderCB() 
    {
    }

    void NotifyError(HRESULT hr)
    {
        wprintf(L"Source Reader error: 0x%X\n", hr);
    }

private:
    long                m_nRefCount;        // Reference count.
    CRITICAL_SECTION    m_critsec;
    HANDLE              m_hEvent;
    BOOL                m_bEOS;
    HRESULT             m_hrStatus;

};

이 예제에서는 OnEventOnFlush 메서드에 관심이 없으므로 단순히 S_OK 반환합니다. 클래스는 이벤트 핸들을 사용하여 애플리케이션에 신호를 보냅니다. 이 핸들은 생성자를 통해 제공됩니다.

이 최소 예제에서 OnReadSample 메서드는 타임스탬프를 콘솔 창에 출력합니다. 그런 다음, 상태 코드와 스트림 끝 플래그를 저장하고 이벤트 핸들에 신호를 보냅니다.

HRESULT SourceReaderCB::OnReadSample(
    HRESULT hrStatus,
    DWORD /* dwStreamIndex */,
    DWORD dwStreamFlags,
    LONGLONG llTimestamp,
    IMFSample *pSample      // Can be NULL
    )
{
    EnterCriticalSection(&m_critsec);

    if (SUCCEEDED(hrStatus))
    {
        if (pSample)
        {
            // Do something with the sample.
            wprintf(L"Frame @ %I64d\n", llTimestamp);
        }
    }
    else
    {
        // Streaming error.
        NotifyError(hrStatus);
    }

    if (MF_SOURCE_READERF_ENDOFSTREAM & dwStreamFlags)
    {
        // Reached the end of the stream.
        m_bEOS = TRUE;
    }
    m_hrStatus = hrStatus;

    LeaveCriticalSection(&m_critsec);
    SetEvent(m_hEvent);
    return S_OK;
}

다음 코드는 애플리케이션이 이 콜백 클래스를 사용하여 미디어 파일에서 모든 비디오 프레임을 읽는 것을 보여 줍니다.

HRESULT ReadMediaFile(PCWSTR pszURL)
{
    HRESULT hr = S_OK;

    IMFSourceReader *pReader = NULL;
    SourceReaderCB *pCallback = NULL;

    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (hEvent == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto done;
    }

    // Create an instance of the callback object.
    pCallback = new (std::nothrow) SourceReaderCB(hEvent);
    if (pCallback == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    // Create the Source Reader.
    hr = CreateSourceReaderAsync(pszURL, pCallback, &pReader);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConfigureDecoder(pReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM);
    if (FAILED(hr))
    {
        goto done;
    }

    // Request the first sample.
    hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
        0, NULL, NULL, NULL, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    while (SUCCEEDED(hr))
    {
        BOOL bEOS;
        hr = pCallback->Wait(INFINITE, &bEOS);
        if (FAILED(hr) || bEOS)
        {
            break;
        }
        hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,
            0, NULL, NULL, NULL, NULL);
    }

done:
    SafeRelease(&pReader);
    SafeRelease(&pCallback);
    return hr;
}

원본 판독기