Bagikan melalui


Menggunakan Pengambil Sampel

[Fitur yang terkait dengan halaman ini, DirectShow, adalah fitur warisan. Ini telah digantikan oleh MediaPlayer, IMFMediaEngine, dan Perekaman Audio/Video di Media Foundation. Fitur-fitur tersebut telah dioptimalkan untuk Windows 10 dan Windows 11. Microsoft sangat menyarankan agar kode baru menggunakan MediaPlayer, IMFMediaEngine dan Pengambilan Audio/Video di Media Foundation alih-alih DirectShow, jika memungkinkan. Microsoft menyarankan agar kode yang ada yang menggunakan API warisan ditulis ulang untuk menggunakan API baru jika memungkinkan.]

[API ini tidak didukung dan mungkin diubah atau tidak tersedia di masa mendatang.]

Filter Sample Grabber adalah filter transform yang dapat digunakan untuk mengambil sampel media dari aliran ketika melewati grafik filter.

Jika Anda hanya ingin mengambil bitmap dari file video, lebih mudah untuk menggunakan Media Detector (MediaDet) objek. Lihat Mengambil Bingkai Poster untuk detailnya. Sampel Grabber lebih fleksibel, namun, karena bekerja dengan hampir semua jenis media (lihat ISampleGrabber::SetMediaType untuk beberapa pengecualian), dan menawarkan kontrol lebih besar kepada aplikasi.

Membuat Manajer Grafik Filter

Untuk memulai, buatlah Filter Graph Manager, kemudian kueri untuk antarmuka IMediaControl dan IMediaEventEx.

    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEventEx *pEvent = NULL;


    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
        CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
    if (FAILED(hr))
    {
        goto done;
    }

Menambahkan Sampel Grabber ke Grafik Filter

Buat instans filter Sampel Grabber dan tambahkan ke grafik filter. Kueri filter Sample Grabber untuk antarmuka ISampleGrabber .

    IBaseFilter *pGrabberF = NULL;
    ISampleGrabber *pGrabber = NULL;


    // Create the Sample Grabber filter.
    hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&pGrabberF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
    if (FAILED(hr))
    {
        goto done;
    }

Mengatur Jenis Media

Ketika Anda pertama kali membuat Sampel Grabber, ia tidak memiliki jenis media pilihan. Ini berarti Anda dapat terhubung ke hampir semua filter dalam grafik, tetapi Anda tidak akan memiliki kontrol atas jenis data yang diterimanya. Sebelum membangun grafik lainnya, oleh karena itu, Anda harus mengatur jenis media untuk Sampel Grabber, dengan memanggil metode ISampleGrabber::SetMediaType.

Ketika Grabber Sampel terhubung, itu akan membandingkan jenis media ini dengan jenis media yang ditawarkan oleh filter lain. Satu-satunya bidang yang diperiksa adalah jenis utama, subjenis, dan jenis format. Untuk salah satu dari nilai-nilai ini, nilai GUID_NULL berarti "terima nilai apa pun." Umumnya, Anda ingin mengatur jenis dan subjenis utama. Misalnya, kode berikut menentukan video RGB 24-bit yang tidak dikompresi:

    AM_MEDIA_TYPE mt;
    ZeroMemory(&mt, sizeof(mt));
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;

    hr = pGrabber->SetMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

Membangun Graf Filter

Sekarang Anda dapat membangun grafik filter lainnya. Karena Sample Grabber hanya akan terhubung menggunakan jenis media yang telah Anda tentukan, ini memungkinkan Anda memanfaatkan mekanisme Intelligent Connect Filter Graph Manager saat Anda membuat grafik.

Misalnya, jika Anda menentukan video yang tidak dikompresi, Anda dapat menyambungkan filter sumber ke Sampel Grabber, dan Filter Graph Manager akan secara otomatis menambahkan pengurai file dan dekoder. Contoh berikut menggunakan fungsi pembantu ConnectFilters, yang tercantum dalam Sambungkan Dua Filter:

    IBaseFilter *pSourceF = NULL;
    IEnumPins *pEnum = NULL;
    IPin *pPin = NULL;


    hr = pGraph->AddSourceFilter(pszVideoFile, L"Source", &pSourceF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSourceF->EnumPins(&pEnum);
    if (FAILED(hr))
    {
        goto done;
    }

    while (S_OK == pEnum->Next(1, &pPin, NULL))
    {
        hr = ConnectFilters(pGraph, pPin, pGrabberF);
        SafeRelease(&pPin);
        if (SUCCEEDED(hr))
        {
            break;
        }
    }

    if (FAILED(hr))
    {
        goto done;
    }

Sampel Grabber adalah filter transformasi, sehingga pin output harus terhubung ke filter lain. Seringkali, Anda mungkin hanya ingin membuang sampel setelah Anda selesai dengan sampel tersebut. Dalam hal ini, sambungkan Sample Grabber ke Penyaring Perender Null , yang membuang data yang diterimanya.

Contoh berikut menghubungkan Sampel Grabber ke filter Null Renderer:

    IBaseFilter *pNullF = NULL;


    hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, 
        IID_PPV_ARGS(&pNullF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pNullF, L"Null Filter");
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConnectFilters(pGraph, pGrabberF, pNullF);
    if (FAILED(hr))
    {
        goto done;
    }

Ketahuilah bahwa menempatkan Sampel Grabber antara dekoder video dan perender video dapat secara signifikan merusak performa penyajian. Sample Grabber adalah filter trans-in-place, yang berarti buffer output sama dengan buffer input. Untuk penyajian video, buffer output kemungkinan terletak pada kartu grafis, di mana operasi baca jauh lebih lambat, dibandingkan dengan operasi baca di memori utama.

Jalankan Grafik

Sample Grabber beroperasi dalam salah satu dari dua mode:

  • Mode buffering membuat salinan setiap sampel sebelum mengirimkan sampel di hilir.
  • Mode panggilan balik memanggil fungsi panggilan balik yang ditentukan aplikasi pada setiap sampel.

Artikel ini menjelaskan mode buffering. (Sebelum menggunakan mode panggilan balik, ketahuilah bahwa fungsi panggilan balik harus cukup terbatas. Jika tidak, itu dapat secara drastis mengurangi performa atau bahkan menyebabkan kebuntuan. Untuk informasi selengkapnya, lihat ISampleGrabber::SetCallback.) Untuk mengaktifkan mode buffering, panggil metode ISampleGrabber::SetBufferSamples dengan nilai TRUE.

Secara opsional, panggil metode ISampleGrabber::SetOneShot dengan nilai TRUE. Hal ini menyebabkan Sample Grabber berhenti setelah menerima sampel media pertama, yang bermanfaat jika Anda ingin mengambil satu bingkai dari aliran. Cari ke waktu yang diinginkan, jalankan grafik, dan tunggu peristiwa EC_COMPLETE. Perhatikan bahwa tingkat akurasi bingkai tergantung pada sumbernya. Misalnya, mencari file MPEG sering kali tidak akurat secara frame.

Untuk menjalankan grafik secepat mungkin, matikan jam grafik seperti yang dijelaskan di Mengatur jam grafik.

Contoh berikut memungkinkan mode satu bidikan dan mode buffering, menjalankan grafik filter, dan menunggu penyelesaian.

    hr = pGrabber->SetOneShot(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetBufferSamples(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pControl->Run();
    if (FAILED(hr))
    {
        goto done;
    }

    long evCode;
    hr = pEvent->WaitForCompletion(INFINITE, &evCode);

Ambil Sampel

Dalam mode buffering, Sample Grabber menyimpan salinan setiap sampel. Metode ISampleGrabber::GetCurrentBuffer menyalin buffer ke dalam array yang dialokasikan pemanggil. Untuk menentukan ukuran array yang diperlukan, pertama-tama panggil GetCurrentBuffer dengan penunjuk NULL untuk alamat array. Kemudian alokasikan array dan panggil metode untuk kedua kalinya untuk menyalin buffer. Contoh berikut menunjukkan langkah-langkah ini.

    // Find the required buffer size.
    long cbBuffer;
    hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
    if (!pBuffer) 
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

Anda perlu mengetahui format data yang tepat dalam buffer. Untuk mendapatkan informasi ini, panggil metode ISampleGrabber::GetConnectedMediaType. Metode ini mengisi format ke dalam struktur AM_MEDIA_TYPE.

Untuk aliran video yang tidak dikompresi, informasi format terkandung dalam strukturVIDEOINFOHEADER. Contoh berikut menunjukkan cara mendapatkan informasi format untuk aliran video yang tidak dikompresi.

    // Examine the format block.
    if ((mt.formattype == FORMAT_VideoInfo) && 
        (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
        (mt.pbFormat != NULL)) 
    {
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;

        hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader, 
            mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
    }
    else 
    {
        // Invalid format.
        hr = VFW_E_INVALIDMEDIATYPE; 
    }

Nota

Grabber Sampel tidak mendukung VIDEOINFOHEADER2.

 

Contoh Kode

Berikut adalah kode lengkap untuk contoh sebelumnya.

Nota

Contoh ini menggunakan fungsi SafeRelease untuk merilis pointer antarmuka.

 

#include <windows.h>
#include <dshow.h>
#include "qedit.h"

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}



HRESULT WriteBitmap(PCWSTR, BITMAPINFOHEADER*, size_t, BYTE*, size_t);

HRESULT GrabVideoBitmap(PCWSTR pszVideoFile, PCWSTR pszBitmapFile)
{
    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEventEx *pEvent = NULL;
    IBaseFilter *pGrabberF = NULL;
    ISampleGrabber *pGrabber = NULL;
    IBaseFilter *pSourceF = NULL;
    IEnumPins *pEnum = NULL;
    IPin *pPin = NULL;
    IBaseFilter *pNullF = NULL;

    BYTE *pBuffer = NULL;

    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
        CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the Sample Grabber filter.
    hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&pGrabberF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
    if (FAILED(hr))
    {
        goto done;
    }

    AM_MEDIA_TYPE mt;
    ZeroMemory(&mt, sizeof(mt));
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;

    hr = pGrabber->SetMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddSourceFilter(pszVideoFile, L"Source", &pSourceF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSourceF->EnumPins(&pEnum);
    if (FAILED(hr))
    {
        goto done;
    }

    while (S_OK == pEnum->Next(1, &pPin, NULL))
    {
        hr = ConnectFilters(pGraph, pPin, pGrabberF);
        SafeRelease(&pPin);
        if (SUCCEEDED(hr))
        {
            break;
        }
    }

    if (FAILED(hr))
    {
        goto done;
    }

    hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, 
        IID_PPV_ARGS(&pNullF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pNullF, L"Null Filter");
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConnectFilters(pGraph, pGrabberF, pNullF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetOneShot(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetBufferSamples(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pControl->Run();
    if (FAILED(hr))
    {
        goto done;
    }

    long evCode;
    hr = pEvent->WaitForCompletion(INFINITE, &evCode);

    // Find the required buffer size.
    long cbBuffer;
    hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
    if (!pBuffer) 
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->GetConnectedMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

    // Examine the format block.
    if ((mt.formattype == FORMAT_VideoInfo) && 
        (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
        (mt.pbFormat != NULL)) 
    {
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;

        hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader, 
            mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
    }
    else 
    {
        // Invalid format.
        hr = VFW_E_INVALIDMEDIATYPE; 
    }

    FreeMediaType(mt);

done:
    CoTaskMemFree(pBuffer);
    SafeRelease(&pPin);
    SafeRelease(&pEnum);
    SafeRelease(&pNullF);
    SafeRelease(&pSourceF);
    SafeRelease(&pGrabber);
    SafeRelease(&pGrabberF);
    SafeRelease(&pControl);
    SafeRelease(&pEvent);
    SafeRelease(&pGraph);
    return hr;
};

// Writes a bitmap file
//  pszFileName:  Output file name.
//  pBMI:         Bitmap format information (including pallete).
//  cbBMI:        Size of the BITMAPINFOHEADER, including palette, if present.
//  pData:        Pointer to the bitmap bits.
//  cbData        Size of the bitmap, in bytes.

HRESULT WriteBitmap(PCWSTR pszFileName, BITMAPINFOHEADER *pBMI, size_t cbBMI,
    BYTE *pData, size_t cbData)
{
    HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL, 
        CREATE_ALWAYS, 0, NULL);
    if (hFile == NULL)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    BITMAPFILEHEADER bmf = { };

    bmf.bfType = &#39;MB&#39;;
    bmf.bfSize = cbBMI+ cbData + sizeof(bmf); 
    bmf.bfOffBits = sizeof(bmf) + cbBMI; 

    DWORD cbWritten = 0;
    BOOL result = WriteFile(hFile, &bmf, sizeof(bmf), &cbWritten, NULL);
    if (result)
    {
        result = WriteFile(hFile, pBMI, cbBMI, &cbWritten, NULL);
    }
    if (result)
    {
        result = WriteFile(hFile, pData, cbData, &cbWritten, NULL);
    }

    HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32(GetLastError());

    CloseHandle(hFile);

    return hr;
}

Menggunakan Layanan Pengeditan DirectShow