Menggunakan Pembaca Sumber untuk Memproses Data Media

Topik ini menjelaskan cara menggunakan Pembaca Sumber untuk memproses data media.

Untuk menggunakan Pembaca Sumber, ikuti langkah-langkah dasar berikut:

  1. Buat instans Pembaca Sumber.
  2. Menghitung kemungkinan format output.
  3. Atur format output aktual untuk setiap aliran.
  4. Memproses data dari sumber.

Sisa topik ini menjelaskan langkah-langkah ini secara rinci.

Membuat Pembaca Sumber

Untuk membuat instans Pembaca Sumber, panggil salah satu fungsi berikut:

Fungsi Deskripsi
MFCreateSourceReaderFromURL
Mengambil URL sebagai input. Fungsi ini menggunakan Source Resolver untuk membuat sumber media dari URL.
MFCreateSourceReaderFromByteStream
Mengambil penunjuk ke aliran byte. Fungsi ini juga menggunakan Pemecah Masalah Sumber untuk membuat sumber media.
MFCreateSourceReaderFromMediaSource
Membawa penunjuk ke sumber media yang telah dibuat. Fungsi ini berguna untuk sumber media yang tidak dapat dibuat oleh Pemecah Masalah Sumber, seperti menangkap perangkat atau sumber media kustom.

 

Biasanya, untuk file media, gunakan MFCreateSourceReaderFromURL. Untuk perangkat, seperti webcam, gunakan MFCreateSourceReaderFromMediaSource. (Untuk informasi selengkapnya tentang perangkat pengambilan di Microsoft Media Foundation, lihat Pengambilan Audio/Video.)

Masing-masing fungsi ini menerima pointer IMFAttributes opsional, yang digunakan untuk mengatur berbagai opsi pada Source Reader, seperti yang dijelaskan dalam topik referensi untuk fungsi-fungsi ini. Untuk mendapatkan perilaku default, atur parameter ini ke NULL. Setiap fungsi mengembalikan penunjuk IMFSourceReader sebagai parameter keluaran. Anda harus memanggil fungsi CoInitialize(Ex) dan MFStartup sebelum memanggil salah satu fungsi ini.

Kode berikut membuat Pembaca Sumber dari URL.

int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
    if (argc < 2)
    {
        return 1;
    }

    const WCHAR *pszURL = argv[1];

    // Initialize the COM runtime.
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr))
    {
        // Initialize the Media Foundation platform.
        hr = MFStartup(MF_VERSION);
        if (SUCCEEDED(hr))
        {
            // Create the source reader.
            IMFSourceReader *pReader;
            hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
            if (SUCCEEDED(hr))
            {
                ReadMediaFile(pReader);
                pReader->Release();
            }
            // Shut down Media Foundation.
            MFShutdown();
        }
        CoUninitialize();
    }
}

Menghitung Format Output

Setiap sumber media memiliki setidaknya satu aliran. Misalnya, file video mungkin berisi aliran video dan aliran audio. Format setiap aliran dijelaskan menggunakan jenis media, yang diwakili oleh antarmuka IMFMediaType. Untuk informasi selengkapnya tentang jenis media, lihat Jenis Media. Anda harus memeriksa jenis media untuk memahami format data yang Anda dapatkan dari Pembaca Sumber.

Awalnya, setiap aliran memiliki format default, yang dapat Anda temukan dengan memanggil metode IMFSourceReader::GetCurrentMediaType:

Untuk setiap aliran, sumber media menawarkan daftar kemungkinan jenis media untuk aliran tersebut. Jumlah jenis tergantung pada sumbernya. Jika sumber mewakili file media, biasanya hanya ada satu jenis per aliran. Webcam, di sisi lain, mungkin dapat melakukan streaming video dalam beberapa format yang berbeda. Dalam hal ini, aplikasi dapat memilih format mana yang akan digunakan dari daftar jenis media.

Untuk mendapatkan salah satu tipe media untuk aliran, panggil metode IMFSourceReader::GetNativeMediaType. Metode ini menerima dua parameter: satu indeks untuk aliran, dan satu indeks yang mengacu pada daftar jenis media untuk aliran tersebut. Untuk menghitung semua jenis untuk aliran, tambahkan indeks daftar sambil menjaga indeks aliran tetap konstan. Ketika indeks daftar keluar dari batas, GetNativeMediaType mengembalikan MF_E_NO_MORE_TYPES.

HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    HRESULT hr = S_OK;
    DWORD dwMediaTypeIndex = 0;

    while (SUCCEEDED(hr))
    {
        IMFMediaType *pType = NULL;
        hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);
        if (hr == MF_E_NO_MORE_TYPES)
        {
            hr = S_OK;
            break;
        }
        else if (SUCCEEDED(hr))
        {
            // Examine the media type. (Not shown.)

            pType->Release();
        }
        ++dwMediaTypeIndex;
    }
    return hr;
}

Untuk menghitung jenis media untuk setiap aliran, tingkatkan indeks aliran. Ketika indeks aliran keluar dari batas, GetNativeMediaType mengembalikan MF_E_INVALIDSTREAMNUMBER.

HRESULT EnumerateMediaTypes(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    DWORD dwStreamIndex = 0;

    while (SUCCEEDED(hr))
    {
        hr = EnumerateTypesForStream(pReader, dwStreamIndex);
        if (hr == MF_E_INVALIDSTREAMNUMBER)
        {
            hr = S_OK;
            break;
        }
        ++dwStreamIndex;
    }
    return hr;
}

Mengatur Format Output

Untuk mengubah format output, panggil metode IMFSourceReader::SetCurrentMediaType. Metode ini mengambil indeks aliran dan jenis media:

hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);

Untuk jenis media, itu tergantung pada apakah Anda ingin menyisipkan dekoder.

  • Untuk mendapatkan data langsung dari sumber tanpa mendekodenya, gunakan salah satu jenis yang dikembalikan oleh GetNativeMediaType.
  • Untuk mendekode aliran, buat jenis media baru yang menjelaskan format yang tidak dikompresi yang diinginkan.

Dalam kasus dekoder, buat jenis media sebagai berikut:

  1. Panggil MFCreateMediaType untuk membuat jenis media baru.
  2. Atur atribut MF_MT_MAJOR_TYPE untuk menentukan audio atau video.
  3. Atur atribut MF_MT_SUBTYPE untuk menentukan subjenis format decoding. (Lihat GUID Subjenis Audio dan GUID Subjenis Video .)
  4. Panggil IMFSourceReader::SetCurrentMediaType.

Pembaca Sumber akan secara otomatis memuat dekoder. Untuk mendapatkan detail lengkap format yang didekodekan, panggil IMFSourceReader::GetCurrentMediaType setelah panggilan ke SetCurrentMediaType

Kode berikut mengonfigurasi aliran video untuk RGB-32 dan aliran audio untuk audio PCM.

HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    IMFMediaType *pNativeType = NULL;
    IMFMediaType *pType = NULL;

    // Find the native format of the stream.
    HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);
    if (FAILED(hr))
    {
        return hr;
    }

    GUID majorType, subtype;

    // Find the major type.
    hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Define the output type.
    hr = MFCreateMediaType(&pType);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Select a subtype.
    if (majorType == MFMediaType_Video)
    {
        subtype= MFVideoFormat_RGB32;
    }
    else if (majorType == MFMediaType_Audio)
    {
        subtype = MFAudioFormat_PCM;
    }
    else
    {
        // Unrecognized type. Skip.
        goto done;
    }

    hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the uncompressed format.
    hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);
    if (FAILED(hr))
    {
        goto done;
    }

done:
    SafeRelease(&pNativeType);
    SafeRelease(&pType);
    return hr;
}

Memproses Data Media

Untuk mendapatkan data media dari sumbernya, panggil metode IMFSourceReader::ReadSample, seperti yang ditunjukkan dalam kode berikut.

        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

Parameter pertama adalah indeks aliran yang ingin Anda dapatkan datanya. Anda juga dapat menentukan MF_SOURCE_READER_ANY_STREAM untuk mendapatkan data berikutnya yang tersedia dari aliran apa pun. Parameter kedua berisi bendera opsional; lihat MF_SOURCE_READER_CONTROL_FLAG untuk daftar ini. Parameter ketiga menerima indeks aliran yang benar-benar menghasilkan data. Anda akan memerlukan informasi ini jika Anda mengatur parameter pertama ke MF_SOURCE_READER_ANY_STREAM. Parameter keempat menerima bendera status, menunjukkan berbagai peristiwa yang dapat terjadi saat membaca data, seperti perubahan format dalam aliran. Untuk daftar bendera status, lihat MF_SOURCE_READER_FLAG.

Jika sumber media dapat menghasilkan data untuk aliran yang diminta, parameter terakhir ReadSample menerima pointer ke antarmuka IMFSample dari objek sampel media . Gunakan sampel media untuk:

  • Dapatkan penunjuk ke data media.
  • Dapatkan waktu presentasi dan durasi sampel.
  • Dapatkan atribut yang menjelaskan interlacing, dominasi bidang, dan aspek sampel lainnya.

Konten data media bergantung pada format aliran. Untuk aliran video yang tidak dikompresi, setiap sampel media berisi satu bingkai video. Untuk aliran audio yang tidak dikompresi, setiap sampel media berisi urutan bingkai audio.

MetodeReadSample dapat mengembalikan S_OK dan belum mengembalikan sampel media dalam parameter pSample. Misalnya, ketika Anda mencapai akhir file, ReadSample menetapkan penanda MF_SOURCE_READERF_ENDOFSTREAM di dwFlags dan menyetel pSample ke NULL. Dalam hal ini, metode ReadSample mengembalikan S_OK karena tidak ada kesalahan yang terjadi, meskipun parameter pSample diatur ke NULL. Oleh karena itu, selalu periksa nilai pSample sebelum Anda mendereferensikannya.

Kode berikut menunjukkan cara memanggil ReadSample dalam perulangan dan memeriksa informasi yang dikembalikan oleh metode, hingga akhir file media tercapai.

HRESULT ProcessSamples(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    IMFSample *pSample = NULL;
    size_t  cSamples = 0;

    bool quit = false;
    while (!quit)
    {
        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

        if (FAILED(hr))
        {
            break;
        }

        wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);
        if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            wprintf(L"\tEnd of stream\n");
            quit = true;
        }
        if (flags & MF_SOURCE_READERF_NEWSTREAM)
        {
            wprintf(L"\tNew stream\n");
        }
        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            wprintf(L"\tNative type changed\n");
        }
        if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
        {
            wprintf(L"\tCurrent type changed\n");
        }
        if (flags & MF_SOURCE_READERF_STREAMTICK)
        {
            wprintf(L"\tStream tick\n");
        }

        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            // The format changed. Reconfigure the decoder.
            hr = ConfigureDecoder(pReader, streamIndex);
            if (FAILED(hr))
            {
                break;
            }
        }

        if (pSample)
        {
            ++cSamples;
        }

        SafeRelease(&pSample);
    }

    if (FAILED(hr))
    {
        wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);
    }
    else
    {
        wprintf(L"Processed %d samples\n", cSamples);
    }
    SafeRelease(&pSample);
    return hr;
}

Menguras Alur Data

Selama pemrosesan data, dekoder atau transformasi lainnya mungkin buffer sampel input. Dalam diagram berikut, aplikasi memanggil ReadSample dan menerima sampel dengan waktu presentasi sama dengan t1. Dekoder memegang sampel untuk t2 dan t3.

ilustrasi yang memperlihatkan buffering dalam dekoder.

Pada panggilan berikutnya keReadSample , Pembaca Sumber mungkin memberikan t4 ke dekoder dan mengembalikan t2 ke aplikasi.

Jika Anda ingin mendekode semua sampel yang saat ini di-buffer di dekoder, tanpa meneruskan sampel baru ke dekoder, atur bendera MF_SOURCE_READER_CONTROLF_DRAIN di parameter dwControlFlags ReadSample. Lanjutkan melakukan ini dalam perulangan hingga ReadSample mengembalikan penunjuk sampel NULL . Tergantung pada bagaimana decoder melakukan buffering terhadap sampel, hal itu mungkin terjadi segera atau setelah beberapa panggilan ke ReadSample.

Mendapatkan Durasi File

Untuk mendapatkan durasi file media, panggil metode IMFSourceReader::GetPresentationAttribute dan minta atribut MF_PD_DURATION, seperti yang ditunjukkan dalam kode berikut.

HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)
{
    PROPVARIANT var;
    HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, 
        MF_PD_DURATION, &var);
    if (SUCCEEDED(hr))
    {
        hr = PropVariantToInt64(var, phnsDuration);
        PropVariantClear(&var);
    }
    return hr;
}

Fungsi yang ditunjukkan di sini mendapatkan durasi dalam 100 unit nanodetik. Bagi dengan 10.000.000 untuk mendapatkan durasi dalam detik.

Mencari

Sumber media yang mendapatkan data dari file lokal biasanya dapat mengakses atau menempatkan pada posisi sebarang dalam file. Perangkat penangkap seperti webcam umumnya tidak dapat melakukan penelusuran, karena data bersifat langsung. Sumber yang mengalirkan data melalui jaringan mungkin dapat dicari, tergantung pada protokol streaming jaringan.

Untuk mengetahui apakah sumber media dapat mencari, panggil IMFSourceReader::GetPresentationAttribute dan minta atribut MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, seperti yang ditunjukkan dalam kode berikut:

HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)
{
    ULONG flags = 0;

    PROPVARIANT var;
    PropVariantInit(&var);

    HRESULT hr = pReader->GetPresentationAttribute(
        MF_SOURCE_READER_MEDIASOURCE, 
        MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, 
        &var);

    if (SUCCEEDED(hr))
    {
        hr = PropVariantToUInt32(var, &flags);
    }
    if (SUCCEEDED(hr))
    {
        *pulFlags = flags;
    }

    PropVariantClear(&var);
    return hr;
}

Fungsi ini mendapatkan setelan bendera kemampuan dari sumber tersebut. Bendera ini didefinisikan dalam enumerasi MFMEDIASOURCE_CHARACTERISTICS. Dua bendera berhubungan dengan pencarian:

Bendera Deskripsi
MFMEDIASOURCE_CAN_SEEK
Sumber dapat mencari.
MFMEDIASOURCE_HAS_SLOW_SEEK
Mencari mungkin membutuhkan waktu lama untuk selesai. Misalnya, sumber mungkin perlu mengunduh seluruh file sebelum dapat mencari. (Tidak ada kriteria ketat bagi sumber untuk mengembalikan bendera ini.)

 

Kode berikut menguji penanda MFMEDIASOURCE_CAN_SEEK.

BOOL SourceCanSeek(IMFSourceReader *pReader)
{
    BOOL bCanSeek = FALSE;
    ULONG flags;
    if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
    {
        bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
    }
    return bCanSeek;
}

Untuk mencari, panggil metode IMFSourceReader::SetCurrentPosition, seperti yang ditunjukkan dalam kode berikut.

HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)
{
    PROPVARIANT var;
    HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);
    if (SUCCEEDED(hr))
    {
        hr = pReader->SetCurrentPosition(GUID_NULL, var);
        PropVariantClear(&var);
    }
    return hr;
}

Parameter pertama memberikan format waktu yang Anda gunakan untuk menentukan posisi pencarian. Semua sumber media di Media Foundation diperlukan untuk mendukung unit 100 nanodetik, yang ditunjukkan oleh nilai GUID_NULL. Parameter kedua adalah PROPVARIANT yang berisi posisi pencarian. Untuk unit waktu 100 nanodetik, jenis data LONGLONG.

Ketahuilah bahwa tidak setiap sumber media menyediakan pencarian akurat hingga tingkat frame. Akurasi pencarian tergantung pada beberapa faktor, seperti interval bingkai kunci, apakah file media berisi indeks, dan apakah data memiliki laju bit konstan atau variabel. Oleh karena itu, setelah Anda mencari posisi dalam file, tidak ada jaminan bahwa stempel waktu pada sampel berikutnya akan sama persis dengan posisi yang diminta. Umumnya, posisi aktual tidak akan lebih lambat dari posisi yang diminta, sehingga Anda dapat membuang sampel sampai Anda mencapai titik yang diinginkan dalam aliran.

Laju Pemutaran

Meskipun Anda dapat mengatur laju pemutaran menggunakan Pembaca Sumber, melakukan biasanya tidak terlalu berguna, karena alasan berikut:

  • Pembaca Sumber tidak mendukung pemutaran terbalik, bahkan jika sumber media melakukannya.
  • Aplikasi mengontrol waktu presentasi, sehingga aplikasi dapat menerapkan putar cepat atau lambat tanpa mengatur laju pada sumbernya.
  • Beberapa sumber media mendukung mode yang menipis, di mana sumber memberikan lebih sedikit sampel—biasanya hanya bingkai kunci. Namun, jika Anda ingin menghilangkan bingkai non-kunci, Anda dapat memeriksa setiap sampel untuk atribut MFSampleExtension_CleanPoint.

Untuk mengatur laju pemutaran menggunakan Pembaca Sumber, panggil metode IMFSourceReader::GetServiceForStream untuk mendapatkan IMFRateSupport dan antarmuka IMFRateControl dari sumber media.

Akselerasi Perangkat Keras

Pembaca Sumber kompatibel dengan Microsoft DirectX Video Acceleration (DXVA) 2.0 untuk decoding video yang dipercepat perangkat keras. Untuk menggunakan DXVA dengan Pembaca Sumber, lakukan langkah-langkah berikut.

  1. Buat perangkat Microsoft Direct3D.
  2. Panggil fungsi DXVA2CreateDirect3DDeviceManager9 untuk membuat manajer perangkat Direct3D. Fungsi ini memperoleh pointer ke antarmuka IDirect3DDeviceManager9.
  3. Panggil metodeIDirect3DDeviceManager9::ResetDevice dengan pointer ke perangkat Direct3D.
  4. Buat penyimpanan atribut dengan memanggil fungsiMFCreateAttributes.
  5. Buat Pembaca Sumber. Berikan penyimpanan atribut pada parameter pAttributes dari fungsi pembuatan.

Saat Anda menyediakan perangkat Direct3D, Pembaca Sumber mengalokasikan sampel video yang kompatibel dengan API prosesor video DXVA. Anda dapat menggunakan pemrosesan video DXVA untuk melakukan deinterlacing perangkat keras atau pencampuran video. Untuk informasi selengkapnya, lihat DXVA Video Processing. Selain itu, jika dekoder mendukung DXVA 2.0, dekoder akan menggunakan perangkat Direct3D untuk melakukan decoding yang dipercepat perangkat keras.

Penting

Mulai dari Windows 8, IMFDXGIDeviceManager dapat digunakan alih-alihIDirect3DDeviceManager9 . Untuk aplikasi Windows Store, Anda harus menggunakan IMFDXGIDeviceManager. Untuk informasi selengkapnya, lihat API Video Direct3D 11.

 

Pembaca Sumber