DirectShow 抓取图片

发布于 2021-11-30


除了可以从 PIN_CATEGORY_CAPTURE,一般摄像头专门为抓取高质量图片,额外提供了一个 PIN_CATEGORY_STILL。

软快门

为了抓拍图片,我们需要触发软件快门,开始真正的抓取。

CComPtr<IAMVideoControl> am_video_control;
  HRESULT hr = camera_filter_->QueryInterface(IID_IAMVideoControl,
                                              (void **)&am_video_control);
  if (FAILED(hr)) {
    return;
  }

  if (!capture_graph_builder2_) {
    return;
  }

  CComPtr<IPin> pin;
  hr =
      capture_graph_builder2_->FindPin(camera_filter_, // Filter.
                                       PINDIR_OUTPUT, // Look for an output pin.
                                       &PIN_CATEGORY_STILL, // Pin category.
                                       NULL,  // Media type (don't care).
                                       FALSE, // Pin must be unconnected.
                                       0,     // Get the 0'th pin.
                                       &pin   // Receives a pointer to thepin.
      );
  if (FAILED(hr)) {
    return;
  }

  hr = am_video_control->SetMode(pin, VideoControlFlag_Trigger);

抓取回调

触发软快门之后,又了图片数据,会回调到 SampleGrabberCallback::BufferCB 中,我们可以在这里保存图片数据到文件中。

完整代码

qedit.h文件:

#ifndef __qedit_h__
#define __qedit_h__

///////////////////////////////////////////////////////////////////////////////////

#pragma once

///////////////////////////////////////////////////////////////////////////////////

interface
    ISampleGrabberCB
    :
public IUnknown
{
    virtual STDMETHODIMP SampleCB( double SampleTime, IMediaSample *pSample ) = 0;
    virtual STDMETHODIMP BufferCB( double SampleTime, BYTE *pBuffer, long BufferLen ) = 0;
};

///////////////////////////////////////////////////////////////////////////////////

static
    const
    IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994, { 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };

///////////////////////////////////////////////////////////////////////////////////

interface
    ISampleGrabber
    :
public IUnknown
{
    virtual HRESULT STDMETHODCALLTYPE SetOneShot( BOOL OneShot ) = 0;
    virtual HRESULT STDMETHODCALLTYPE SetMediaType( const AM_MEDIA_TYPE *pType ) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType( AM_MEDIA_TYPE *pType ) = 0;
    virtual HRESULT STDMETHODCALLTYPE SetBufferSamples( BOOL BufferThem ) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer( long *pBufferSize, long *pBuffer ) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetCurrentSample( IMediaSample **ppSample ) = 0;
    virtual HRESULT STDMETHODCALLTYPE SetCallback( ISampleGrabberCB *pCallback, long WhichMethodToCallback ) = 0;
};

///////////////////////////////////////////////////////////////////////////////////

static
    const
    IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };

///////////////////////////////////////////////////////////////////////////////////

static
    const
    CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };

///////////////////////////////////////////////////////////////////////////////////

static
    const
    CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };

///////////////////////////////////////////////////////////////////////////////////

static
    const
    CLSID CLSID_VideoEffects1Category = { 0xcc7bfb42, 0xf175, 0x11d1, { 0xa3, 0x92, 0x0, 0xe0, 0x29, 0x1f, 0x39, 0x59 } };

///////////////////////////////////////////////////////////////////////////////////

static
    const
    CLSID CLSID_VideoEffects2Category = { 0xcc7bfb43, 0xf175, 0x11d1, { 0xa3, 0x92, 0x0, 0xe0, 0x29, 0x1f, 0x39, 0x59 } };

///////////////////////////////////////////////////////////////////////////////////

static
    const
    CLSID CLSID_AudioEffects1Category = { 0xcc7bfb44, 0xf175, 0x11d1, { 0xa3, 0x92, 0x0, 0xe0, 0x29, 0x1f, 0x39, 0x59 } };

///////////////////////////////////////////////////////////////////////////////////

static
    const
    CLSID CLSID_AudioEffects2Category = { 0xcc7bfb45, 0xf175, 0x11d1, { 0xa3, 0x92, 0x0, 0xe0, 0x29, 0x1f, 0x39, 0x59 } };

///////////////////////////////////////////////////////////////////////////////////

#endif

///////////////////////////////////////////////////////////////////////////////////

实现文件:

#include <string>

#include <windows.h>

// 包含 CComPtr,自动管理 COM 指针
#include <atlbase.h>

// DirectShow 所需要的头文件
#include <dshow.h>

// ISampleGrabberCB 接口定义
#include "qedit.h"

// DirectShow 所需要的lib库,定义实现了相关的 COM 接口
#pragma comment(lib, "strmiids.lib")

// 实现了 AMGetErrorText 函数。不是必须。
#pragma comment(lib, "strmbase.lib")

// 自动初始化、销毁 COM
class ScopedCOMInitializer {
public:
  ScopedCOMInitializer() { hr_ = CoInitialize(NULL); }
  ~ScopedCOMInitializer() {
    if (SUCCEEDED(hr_)) {
      CoUninitialize();
    }
  }
  bool IsSucceeded() { return SUCCEEDED(hr_); }

private:
  // 禁止拷贝
  ScopedCOMInitializer(const ScopedCOMInitializer &) = delete;

  // 禁止赋值
  ScopedCOMInitializer &operator=(const ScopedCOMInitializer &) = delete;

  HRESULT hr_;
};

class SampleGrabberCallback : public ISampleGrabberCB {
public:
  // 为了简单,自己管理 COM 对象生命周期,返回假的引用技术
  STDMETHODIMP_(ULONG) AddRef() { return 1; }
  STDMETHODIMP_(ULONG) Release() { return 2; }

  STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) {
    return E_NOTIMPL;
  }

  STDMETHODIMP SampleCB(double Time, IMediaSample *pSample) {
    return E_NOTIMPL;
  }

  // 保存图片
  STDMETHODIMP BufferCB(double Time, BYTE *pBuffer, long BufferLen) {
    SYSTEMTIME st;
    GetLocalTime(&st);
    wchar_t file_name[MAX_PATH] = {0};
    swprintf_s(file_name, L"test_%4d_%2d_%2d_%2d_%2d_%2d.jpg", st.wYear,
               st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
    HANDLE file = CreateFileW(file_name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                              FILE_ATTRIBUTE_NORMAL, 0);
    if (INVALID_HANDLE_VALUE == file) {
      return E_HANDLE;
    }
    DWORD dwWrite;
    WriteFile(file, pBuffer, BufferLen, &dwWrite, NULL);
    CloseHandle(file);

    return NOERROR;
  }
};

int main() {
  ScopedCOMInitializer com_initializer;
  if (!com_initializer.IsSucceeded()) {
    return 0;
  }

  // 创建 Capture Graph Builder
  CComPtr<ICaptureGraphBuilder2> capture_graph_builder2;
  HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
                                CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2,
                                (void **)&capture_graph_builder2);
  if (FAILED(hr)) {
    return 0;
  }

  // 创建 Filter Graph Manager
  CComPtr<IGraphBuilder> graph_builder;
  hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
                        IID_IGraphBuilder, (void **)&graph_builder);
  if (FAILED(hr)) {
    return 0;
  }

  // 初始化 Capture Graph Builder
  capture_graph_builder2->SetFiltergraph(graph_builder);

  CComPtr<IMediaControl> media_control;
  hr =
      graph_builder->QueryInterface(IID_IMediaControl, (void **)&media_control);
  if (FAILED(hr)) {
    return 0;
  }

  // 创建 System Device Enumerator
  CComPtr<ICreateDevEnum> system_device_enum;
  hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
                        IID_ICreateDevEnum, (void **)&system_device_enum);
  if (FAILED(hr)) {
    return 0;
  }

  // 创建视频输入设备枚举器
  CComPtr<IEnumMoniker> video_input_device_category;
  hr = system_device_enum->CreateClassEnumerator(
      CLSID_VideoInputDeviceCategory, &video_input_device_category, 0);
  if (FAILED(hr)) {
    return 0;
  }

  // 查找 FriendlyName 为 KS2MR01 的视频输入设备
  std::wstring camera_name(L"KS2MR01");
  CComPtr<IBaseFilter> camera_filter;
  CComPtr<IMoniker> moniker;
  ULONG cFetched;
  while (video_input_device_category->Next(1, &moniker, &cFetched) == S_OK) {
    CComPtr<IPropertyBag> property_bag;
    hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&property_bag);
    if (FAILED(hr)) {
      continue;
    }

    // 获得 FriendlyName
    CComVariant friendly_name;
    hr = property_bag->Read(L"FriendlyName", &friendly_name, 0);
    if (FAILED(hr)) {
      continue;
    }

    if (camera_name == std::wstring(friendly_name.bstrVal)) {
      // 把 IMoniker 转换成对应的 Filter
      hr = moniker->BindToObject(NULL, NULL, IID_IBaseFilter,
                                 (void **)&camera_filter);
      if (SUCCEEDED(hr)) {
        if (graph_builder) {
          hr = graph_builder->AddFilter(camera_filter, L"Video Capture");
          if (SUCCEEDED(hr)) {
            break;
          }
        }
      }
    }

    moniker = nullptr;
  }

  // 实时预览视频
  hr = capture_graph_builder2->RenderStream(
      &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, camera_filter, NULL, NULL);

  // 找到 PIN_CATEGORY_STILL
  CComPtr<IPin> still_pin;
  hr = capture_graph_builder2->FindPin(
      camera_filter,       // Filter.
      PINDIR_OUTPUT,       // Look for an output pin.
      &PIN_CATEGORY_STILL, // Pin category.
      NULL,                // Media type (don't care).
      FALSE,               // Pin must be unconnected.
      0,                   // Get the 0'th pin.
      &still_pin           // Receives a pointer to thepin.
  );
  if (FAILED(hr)) {
    return 0;
  }

  // 创建 SampleGrabber filter,用于抓取图片
  CComPtr<IBaseFilter> sample_grabber_filter;
  hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
                        IID_IBaseFilter, (void **)&sample_grabber_filter);
  if (FAILED(hr)) {
    return 0;
  }
  hr = graph_builder->AddFilter(sample_grabber_filter, L"SampleGrab");
  if (FAILED(hr)) {
    return 0;
  }

  // 创建 NullRenderer,用于最终接收数据并丢弃
  CComPtr<IBaseFilter> null_renderer_filter;
  hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
                        IID_IBaseFilter, (void **)&null_renderer_filter);
  if (FAILED(hr)) {
    return 0;
  }
  hr = graph_builder->AddFilter(null_renderer_filter, L"NullRenderer");
  if (FAILED(hr)) {
    return 0;
  }

  hr = capture_graph_builder2->RenderStream(
      &PIN_CATEGORY_STILL,   // Connect this pin ...
      &MEDIATYPE_Video,      // with this media type ...
      camera_filter,         // on this filter ...
      sample_grabber_filter, // to the Sample Grabber ...
      null_renderer_filter); // ... and finally to the Null Renderer.

  CComPtr<ISampleGrabber> sample_grabber;
  hr = sample_grabber_filter->QueryInterface(IID_ISampleGrabber,
                                             (void **)&sample_grabber);
  if (FAILED(hr)) {
    return 0;
  }
  hr = sample_grabber->SetOneShot(FALSE);
  hr = sample_grabber->SetBufferSamples(TRUE);

  SampleGrabberCallback sg_callback;
  hr = sample_grabber->SetCallback(&sg_callback, 1);

  media_control->Run();

  // 获得 IAMVideoControl 接口
  CComPtr<IAMVideoControl> video_control;
  hr = camera_filter->QueryInterface(IID_IAMVideoControl,
                                     (void **)&video_control);
  if (FAILED(hr)) {
    return 0;
  }

  ::Sleep(3 * 1000);

  // 触发软快门
  hr = video_control->SetMode(still_pin, VideoControlFlag_Trigger);

  ::Sleep(3 * 1000);
  return 0;
}

参考