A forrásolvasó használata aszinkron módban

Ez a témakör azt ismerteti, hogyan használható a Forrásolvasó aszinkron módban. Aszinkron módban az alkalmazás egy visszahívási felületet biztosít, amellyel értesíti az alkalmazást arról, hogy az adatok elérhetők.

Ez a témakör feltételezi, hogy már elolvasta a témakört A forrásolvasó használata a médiaadatok feldolgozásához.

Aszinkron mód használata

A Forrásolvasó szinkron vagy aszinkron módban működik. Az előző szakaszban látható példakód azt feltételezi, hogy a Forrásolvasó szinkron módot használ, ami az alapértelmezett mód. Szinkron módban az IMFSourceReader::ReadSample metódus blokkolja, miközben a médiaforrás létrehozza a következő mintát. A médiaforrások általában valamilyen külső forrásból (például helyi fájlból vagy hálózati kapcsolatból) szereznek be adatokat, így a metódus jelentős ideig blokkolhatja a hívásláncot.

Aszinkron módban a ReadSample azonnal visszatér, és a munka egy másik szálon történik. A művelet befejezése után a Forrásolvasó meghívja az alkalmazást az IMFSourceReaderCallback visszahívási felületen keresztül. Az aszinkron mód használatához meg kell adnia egy visszahívási mutatót a Forrásolvasó első létrehozásakor, az alábbiak szerint:

  1. Hozzon létre egy attribútumtárolót az MFCreateAttributes függvény meghívásával.
  2. Állítsa be a MF_SOURCE_READER_ASYNC_CALLBACK attribútumot az attribútumtárolóban. Az attribútum értéke a visszahívási objektum mutatója.
  3. A Forrásolvasó létrehozásakor adja át az attribútumtárat a pAttributes paraméter létrehozási függvényének. A Forrásolvasó létrehozásához szükséges összes függvény rendelkezik ezzel a paraméterével.

Az alábbi példa ezeket a lépéseket mutatja be.

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;
}

A Forrásolvasó létrehozása után nem válthat a szinkron és az aszinkron mód között.

Ha aszinkron módban szeretne adatokat lekérni, hívja meg a ReadSample metódust, de az utolsó négy paramétert állítsa a NULL , ahogyan az az alábbi példában is látható.

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

Amikor a ReadSample metódus aszinkron módon fejeződik be, a forrásolvasó meghívja a IMFSourceReaderCallback::OnReadSample metódust. Ez a metódus öt paramétert használ:

  • hrStatus: Egy HRESULT értéket tartalmaz. Ez ugyanaz az érték, amelyet a ReadSampleszinkron módban ad vissza. Ha hrStatus hibakódot tartalmaz, figyelmen kívül hagyhatja a többi paramétert.
  • dwStreamIndex, dwStreamFlags, llTimestamp és pSample: Ez a három paraméter egyenértékű ReadSampleutolsó három paraméterével. Ezek tartalmazzák a stream számát, az állapotjelzőket és a IMFSample mutatót.
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

A visszahívási felület emellett két másik módszert is meghatároz:

  • OnEvent. Értesíti az alkalmazást, ha bizonyos események történnek a médiaforrásban, például pufferelés vagy hálózati kapcsolati események.
  • OnFlush. A Flush metódus befejeződésekor hívjuk.

A Visszahívási felület implementálása

A visszahívási felületnek szálbiztosnak kell lennie, mert OnReadSample és a többi visszahívási metódust munkavégző szálakból hívjuk meg.

A visszahívás implementálása során számos különböző megközelítést alkalmazhat. Elvégezheti például az összes munkát a visszahíváson belül, vagy a visszahívással értesítheti az alkalmazást (például egy eseménykezelő jelzésével), majd elvégezheti a munkát az alkalmazásszálból.

Az OnReadSample metódust a rendszer minden olyan híváshoz meghívja, amelyet az IMFSourceReader::ReadSample metódushoz intéz. A következő minta lekéréséhez hívja meg újra ReadSample. Hiba esetén a OnReadSample meghívása a hrStatus paraméter hibakódjával történik.

Az alábbi példa a visszahívási felület minimális implementációját mutatja be. Először is itt van egy osztály deklarálása, amely megvalósítja a felületet.

#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;

};

Ebben a példában minket nem érdekelnek az OnEvent és OnFlush metódusok, így egyszerűen visszatérnek S_OK. Az osztály eseménykezelőt használ az alkalmazás jelzésére; ezt a fogópontot a konstruktor biztosítja.

Ebben a minimális példában az OnReadSample metódus egyszerűen kinyomtatja az időbélyeget a konzolablakba. Ezután tárolja az állapotkódot és a stream vége jelzőt, és jelzi az eseménykezelőt:

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;
}

Az alábbi kód azt mutatja, hogy az alkalmazás ezt a visszahívási osztályt használja az összes videokép beolvasásához egy médiafájlból:

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;
}

Forrásolvasó