Menggunakan Pembaca Sumber dalam Mode Asinkron

Topik ini menjelaskan cara menggunakan Pembaca Sumber dalam mode asinkron. Dalam mode asinkron, aplikasi menyediakan antarmuka panggilan balik, yang digunakan untuk memberi tahu aplikasi bahwa data tersedia.

Topik ini mengasumsikan bahwa Anda telah membaca topik Menggunakan Pembaca Sumber untuk Memproses Data Media.

Menggunakan Mode Asinkron

Pembaca Sumber beroperasi baik dalam mode sinkron atau mode asinkron. Contoh kode yang ditampilkan di bagian sebelumnya mengasumsikan bahwa Pembaca Sumber menggunakan mode sinkron, yang merupakan default. Dalam mode sinkron, metode IMFSourceReader::ReadSample memblokir sementara sumber media menghasilkan sampel berikutnya. Sumber media biasanya memperoleh data dari beberapa sumber eksternal (seperti file lokal atau koneksi jaringan), sehingga metode dapat memblokir utas panggilan untuk waktu yang nyata.

Dalam mode asinkron, ReadSample segera kembali dan pekerjaan dilakukan pada utas lain. Setelah operasi selesai, Pembaca Sumber memanggil aplikasi melalui antarmuka panggilan balik IMFSourceReaderCallback . Untuk menggunakan mode asinkron, Anda harus menyediakan penunjuk panggilan balik saat pertama kali membuat Pembaca Sumber, sebagai berikut:

  1. Buat penyimpanan atribut dengan memanggil fungsi MFCreateAttributes .
  2. Atur atribut MF_SOURCE_READER_ASYNC_CALLBACK di penyimpanan atribut. Nilai atribut adalah penunjuk ke objek panggilan balik Anda.
  3. Saat Anda membuat Pembaca Sumber, teruskan penyimpanan atribut ke fungsi pembuatan di parameter pAttributes . Semua fungsi untuk membuat Pembaca Sumber memiliki parameter ini.

Contoh berikut menunjukkan langkah-langkah ini.

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

Setelah membuat Pembaca Sumber, Anda tidak dapat beralih mode antara sinkron dan asinkron.

Untuk mendapatkan data dalam mode asinkron, panggil metode ReadSample tetapi atur empat parameter terakhir ke NULL, seperti yang ditunjukkan dalam contoh berikut.

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

Ketika metode ReadSample selesai secara asinkron, Pembaca Sumber memanggil metode IMFSourceReaderCallback::OnReadSample Anda. Metode ini memiliki lima parameter:

  • hrStatus: Berisi nilai HRESULT . Ini adalah nilai yang sama dengan yang akan dikembalikan ReadSample dalam mode sinkron. Jika hrStatus berisi kode kesalahan, Anda dapat mengabaikan parameter yang tersisa.
  • dwStreamIndex, dwStreamFlags, llTimestamp, dan pSample: Ketiga parameter ini setara dengan tiga parameter terakhir di ReadSample. Mereka masing-masing berisi nomor aliran, bendera status, dan penunjuk IMFSample .
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

Selain itu, antarmuka panggilan balik mendefinisikan dua metode lain:

  • OnEvent. Memberi tahu aplikasi ketika peristiwa tertentu terjadi di sumber media, seperti buffering atau peristiwa koneksi jaringan.
  • OnFlush. Dipanggil ketika metode Flush selesai.

Menerapkan Antarmuka Panggilan Balik

Antarmuka panggilan balik harus aman untuk utas, karena OnReadSample dan metode panggilan balik lainnya dipanggil dari utas pekerja.

Ada beberapa pendekatan berbeda yang dapat Anda lakukan saat menerapkan panggilan balik. Misalnya, Anda dapat melakukan semua pekerjaan di dalam panggilan balik, atau Anda dapat menggunakan panggilan balik untuk memberi tahu aplikasi (misalnya, dengan memberi sinyal handel peristiwa) dan kemudian melakukan pekerjaan dari utas aplikasi.

Metode OnReadSample akan dipanggil sekali untuk setiap panggilan yang Anda lakukan ke metode IMFSourceReader::ReadSample . Untuk mendapatkan sampel berikutnya, hubungi ReadSample lagi. Jika terjadi kesalahan, OnReadSample dipanggil dengan kode kesalahan untuk parameter hrStatus .

Contoh berikut menunjukkan implementasi minimal antarmuka panggilan balik. Pertama, berikut adalah deklarasi kelas yang mengimplementasikan antarmuka.

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

};

Dalam contoh ini, kami tidak tertarik dengan metode OnEvent dan OnFlush , sehingga mereka hanya mengembalikan S_OK. Kelas menggunakan handel peristiwa untuk memberi sinyal aplikasi; handel ini disediakan melalui konstruktor.

Dalam contoh minimal ini, metode OnReadSample hanya mencetak stempel waktu ke jendela konsol. Kemudian menyimpan kode status dan bendera akhir aliran, dan memberi sinyal handel peristiwa:

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

Kode berikut menunjukkan aplikasi akan menggunakan kelas panggilan balik ini untuk membaca semua bingkai video dari file media:

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

Pembaca Sumber