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 Pemecah Masalah Sumber 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 menangkap perangkat di Microsoft Media Foundation, lihat Pengambilan Audio/Video.)

Masing-masing fungsi ini mengambil penunjuk IMFAttributes opsional, yang digunakan untuk mengatur berbagai opsi pada Pembaca Sumber, 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 output. 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 jenis media untuk aliran, panggil metode IMFSourceReader::GetNativeMediaType . Metode ini mengambil dua parameter indeks: Indeks aliran, dan indeks ke dalam daftar jenis media untuk aliran. Untuk menghitung semua jenis aliran, tambahkan indeks daftar sambil menjaga indeks aliran tetap konstan. Saat 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. Saat 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 decoder.

  • 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 pendekodean. (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 penunjuk 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.

Metode ReadSample dapat mengembalikan S_OK dan belum mengembalikan sampel media dalam parameter pSample . Misalnya, ketika Anda mencapai akhir file, ReadSample mengatur bendera MF_SOURCE_READERF_ENDOFSTREAM di dwFlags dan mengatur 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 menyangga sampel input. Dalam diagram berikut, aplikasi memanggil ReadSample dan menerima sampel dengan waktu presentasi yang sama dengan t1. Dekoder menyimpan sampel untuk t2 dan t3.

ilustrasi yang memperlihatkan buffering dalam dekoder.

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

Jika Anda ingin mendekode semua sampel yang saat ini di-buffer dalam dekoder, tanpa meneruskan sampel baru ke dekoder, atur bendera MF_SOURCE_READER_CONTROLF_DRAIN di parameter dwControlFlagsreadSample. Lanjutkan untuk melakukan ini dalam perulangan hingga ReadSample mengembalikan penunjuk sampel NULL . Bergantung pada bagaimana buffer dekoder mengambil sampel, yang 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 unit 100 nanodetik. Bagi dengan 10.000.000 untuk mendapatkan durasi dalam detik.

Mencari

Sumber media yang mendapatkan data dari file lokal biasanya dapat mencari posisi arbitrer dalam file. Menangkap perangkat seperti webcam umumnya tidak dapat mencari, karena data aktif. Sumber yang mengalirkan data melalui jaringan mungkin dapat mencari, 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 serangkaian bendera kemampuan dari sumbernya. Bendera ini didefinisikan dalam enumerasi MFMEDIASOURCE_CHARACTERISTICS . Dua bendera terkait dengan mencari:

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

 

Kode berikut menguji bendera 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 datanya adalah LONGLONG.

Ketahuilah bahwa tidak setiap sumber media menyediakan pencarian yang akurat bingkai. Akurasi pencarian tergantung pada beberapa faktor, seperti interval bingkai kunci, apakah file media berisi indeks, dan apakah data memiliki laju bit konstanta 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, tindakan 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 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 antarmuka IMFRateSupport dan IMFRateControl dari sumber media.

Akselerasi Perangkat Keras

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

  1. Membuat perangkat Microsoft Direct3D.
  2. Panggil fungsi DXVA2CreateDirect3DDeviceManager9 untuk membuat manajer perangkat Direct3D. Fungsi ini mendapatkan penunjuk ke antarmuka IDirect3DDeviceManager9 .
  3. Panggil metode IDirect3DDeviceManager9::ResetDevice dengan pointer ke perangkat Direct3D.
  4. Buat penyimpanan atribut dengan memanggil fungsi MFCreateAttributes .
  5. Buat Pembaca Sumber. Teruskan penyimpanan atribut dalam 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 Pemrosesan Video DXVA. Selain itu, jika dekoder mendukung DXVA 2.0, dekoder akan menggunakan perangkat Direct3D untuk melakukan pendekodean yang dipercepat perangkat keras.

Penting

Dimulai di Windows 8, IMFDXGIDeviceManager dapat digunakan alih-alih IDirect3DDeviceManager9. Untuk aplikasi Windows Store, Anda harus menggunakan IMFDXGIDeviceManager. Untuk informasi selengkapnya, lihat API Video Direct3D 11.

 

Pembaca Sumber