mirror of
				https://github.com/pion/mediadevices.git
				synced 2025-10-31 20:02:36 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			515 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			515 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <iostream>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include <dshow.h>
 | |
| #include <qedit.h>
 | |
| #include <mmsystem.h>
 | |
| 
 | |
| #include "camera_windows.hpp"
 | |
| #include "_cgo_export.h"
 | |
| 
 | |
| 
 | |
| imageProp* getProp(camera* cam, int i)
 | |
| {
 | |
|   return &cam->props[i];
 | |
| }
 | |
| 
 | |
| char* getName(cameraList* list, int i)
 | |
| {
 | |
|   return list->name[i];
 | |
| }
 | |
| 
 | |
| 
 | |
| // printErr shows string representation of HRESULT.
 | |
| // This is for debugging.
 | |
| void printErr(HRESULT hr)
 | |
| {
 | |
|   char buf[128];
 | |
|   AMGetErrorTextA(hr, buf, 128);
 | |
|   fprintf(stderr, "%s\n", buf);
 | |
| }
 | |
| 
 | |
| // getCameraName returns name of the device.
 | |
| // returned pointer must be released by free() after use.
 | |
| char* getCameraName(IMoniker* moniker)
 | |
| {
 | |
|   LPOLESTR name;
 | |
|   if (FAILED(moniker->GetDisplayName(nullptr, nullptr, &name)))
 | |
|     return nullptr;
 | |
| 
 | |
|   std::string nameStr = utf16Decode(name);
 | |
|   char* ret = (char*)malloc(nameStr.size() + 1);
 | |
|   memcpy(ret, nameStr.c_str(), nameStr.size() + 1);
 | |
| 
 | |
|   LPMALLOC comalloc;
 | |
|   CoGetMalloc(1, &comalloc);
 | |
|   comalloc->Free(name);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| // listCamera stores information of the devices to cameraList*.
 | |
| int listCamera(cameraList* list, const char** errstr)
 | |
| {
 | |
|   ICreateDevEnum* sysDevEnum = nullptr;
 | |
|   IEnumMoniker* enumMon = nullptr;
 | |
| 
 | |
|   if (FAILED(CoCreateInstance(
 | |
|           CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC,
 | |
|           IID_ICreateDevEnum, (void**)&sysDevEnum)))
 | |
|   {
 | |
|     *errstr = errEnumDevice;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(sysDevEnum->CreateClassEnumerator(
 | |
|           CLSID_VideoInputDeviceCategory, &enumMon, 0)))
 | |
|   {
 | |
|     *errstr = errEnumDevice;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   safeRelease(&sysDevEnum);
 | |
| 
 | |
|   {
 | |
|     IMoniker* moniker;
 | |
|     list->num = 0;
 | |
|     while (enumMon->Next(1, &moniker, nullptr) == S_OK)
 | |
|     {
 | |
|       moniker->Release();
 | |
|       list->num++;
 | |
|     }
 | |
| 
 | |
|     enumMon->Reset();
 | |
|     list->name = new char*[list->num];
 | |
| 
 | |
|     int i = 0;
 | |
|     while (enumMon->Next(1, &moniker, nullptr) == S_OK)
 | |
|     {
 | |
|       list->name[i] = getCameraName(moniker);
 | |
|       moniker->Release();
 | |
|       i++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   safeRelease(&enumMon);
 | |
|   return 0;
 | |
| 
 | |
| fail:
 | |
|   safeRelease(&sysDevEnum);
 | |
|   safeRelease(&enumMon);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| // freeCameraList frees all resources stored in cameraList*.
 | |
| int freeCameraList(cameraList* list, const char** errstr)
 | |
| {
 | |
|   if (list->name != nullptr)
 | |
|   {
 | |
|     for (int i = 0; i < list->num; ++i)
 | |
|     {
 | |
|       delete list->name[i];
 | |
|     }
 | |
|     delete list->name;
 | |
|   }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| // selectCamera stores pointer to the selected device IMoniker* according to the configs in camera*.
 | |
| int selectCamera(camera* cam, IMoniker** monikerSelected, const char** errstr)
 | |
| {
 | |
|   ICreateDevEnum* sysDevEnum = nullptr;
 | |
|   IEnumMoniker* enumMon = nullptr;
 | |
| 
 | |
|   if (FAILED(CoCreateInstance(
 | |
|           CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC,
 | |
|           IID_ICreateDevEnum, (void**)&sysDevEnum)))
 | |
|   {
 | |
|     *errstr = errEnumDevice;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(sysDevEnum->CreateClassEnumerator(
 | |
|           CLSID_VideoInputDeviceCategory, &enumMon, 0)))
 | |
|   {
 | |
|     *errstr = errEnumDevice;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   safeRelease(&sysDevEnum);
 | |
| 
 | |
|   {
 | |
|     IMoniker* moniker;
 | |
|     while (enumMon->Next(1, &moniker, nullptr) == S_OK)
 | |
|     {
 | |
|       char* name = getCameraName(moniker);
 | |
|       if (strcmp(cam->name, name) != 0)
 | |
|       {
 | |
|         free(name);
 | |
|         safeRelease(&moniker);
 | |
|         continue;
 | |
|       }
 | |
|       free(name);
 | |
|       *monikerSelected = moniker;
 | |
|       safeRelease(&enumMon);
 | |
|       return 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   safeRelease(&enumMon);
 | |
|   return 0;
 | |
| 
 | |
| fail:
 | |
|   safeRelease(&sysDevEnum);
 | |
|   safeRelease(&enumMon);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| // listResolution stores list of the device to camera*.
 | |
| int listResolution(camera* cam, const char** errstr)
 | |
| {
 | |
|   cam->props = nullptr;
 | |
| 
 | |
|   IMoniker* moniker = nullptr;
 | |
|   IBaseFilter* captureFilter = nullptr;
 | |
|   ICaptureGraphBuilder2* captureGraph = nullptr;
 | |
|   IAMStreamConfig* config = nullptr;
 | |
|   IPin* src = nullptr;
 | |
|   LPOLESTR name;
 | |
| 
 | |
|   if (!selectCamera(cam, &moniker, errstr))
 | |
|   {
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   moniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&captureFilter);
 | |
|   safeRelease(&moniker);
 | |
| 
 | |
|   src = getPin(captureFilter, PINDIR_OUTPUT);
 | |
|   if (src == nullptr)
 | |
|   {
 | |
|     *errstr = errGetConfig;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   // Getting IAMStreamConfig is stub on Wine. Requires real Windows.
 | |
|   if (FAILED(src->QueryInterface(
 | |
|           IID_IAMStreamConfig, (void**)&config)))
 | |
|   {
 | |
|     *errstr = errGetConfig;
 | |
|     goto fail;
 | |
|   }
 | |
|   safeRelease(&src);
 | |
| 
 | |
|   {
 | |
|     int count = 0, size = 0;
 | |
|     if (FAILED(config->GetNumberOfCapabilities(&count, &size)))
 | |
|     {
 | |
|       *errstr = errGetConfig;
 | |
|       goto fail;
 | |
|     }
 | |
|     cam->props = new imageProp[count];
 | |
| 
 | |
|     int iProp = 0;
 | |
|     for (int i = 0; i < count; ++i)
 | |
|     {
 | |
|       VIDEO_STREAM_CONFIG_CAPS caps;
 | |
|       AM_MEDIA_TYPE* mediaType;
 | |
|       if (FAILED(config->GetStreamCaps(i, &mediaType, (BYTE*)&caps)))
 | |
|         continue;
 | |
| 
 | |
|       if (mediaType->majortype != MEDIATYPE_Video ||
 | |
|           mediaType->formattype != FORMAT_VideoInfo ||
 | |
|           mediaType->pbFormat == nullptr)
 | |
|         continue;
 | |
| 
 | |
|       VIDEOINFOHEADER* videoInfoHdr = (VIDEOINFOHEADER*)mediaType->pbFormat;
 | |
|       cam->props[iProp].width = videoInfoHdr->bmiHeader.biWidth;
 | |
|       cam->props[iProp].height = videoInfoHdr->bmiHeader.biHeight;
 | |
|       cam->props[iProp].fcc = videoInfoHdr->bmiHeader.biCompression;
 | |
|       iProp++;
 | |
|     }
 | |
|     cam->numProps = iProp;
 | |
|   }
 | |
|   safeRelease(&config);
 | |
|   safeRelease(&captureGraph);
 | |
|   safeRelease(&captureFilter);
 | |
|   safeRelease(&moniker);
 | |
|   return 0;
 | |
| 
 | |
| fail:
 | |
|   safeRelease(&src);
 | |
|   safeRelease(&config);
 | |
|   safeRelease(&captureGraph);
 | |
|   safeRelease(&captureFilter);
 | |
|   safeRelease(&moniker);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| // openCamera opens a camera and stores interface handler to camera*.
 | |
| // camera* should be freed by freeCamera() after use.
 | |
| int openCamera(camera* cam, const char** errstr)
 | |
| {
 | |
|   cam->grabber = nullptr;
 | |
|   cam->mediaControl = nullptr;
 | |
|   cam->callback = nullptr;
 | |
| 
 | |
|   IMoniker* moniker = nullptr;
 | |
|   IGraphBuilder* graphBuilder = nullptr;
 | |
|   IBaseFilter* captureFilter = nullptr;
 | |
|   IMediaControl* mediaControl = nullptr;
 | |
|   IBaseFilter* grabberFilter = nullptr;
 | |
|   ISampleGrabber* grabber = nullptr;
 | |
|   IBaseFilter* nullFilter = nullptr;
 | |
|   IPin* src = nullptr;
 | |
|   IPin* dst = nullptr;
 | |
|   IPin* end = nullptr;
 | |
|   IPin* nul = nullptr;
 | |
| 
 | |
|   if (!selectCamera(cam, &moniker, errstr))
 | |
|   {
 | |
|     goto fail;
 | |
|   }
 | |
|   moniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&captureFilter);
 | |
|   safeRelease(&moniker);
 | |
| 
 | |
|   if (FAILED(CoCreateInstance(
 | |
|           CLSID_FilterGraph, nullptr, CLSCTX_INPROC,
 | |
|           IID_IGraphBuilder, (void**)&graphBuilder)))
 | |
|   {
 | |
|     *errstr = errGraphBuilder;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(graphBuilder->QueryInterface(
 | |
|           IID_IMediaControl, (void**)&mediaControl)))
 | |
|   {
 | |
|     *errstr = errNoControl;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(graphBuilder->AddFilter(captureFilter, L"capture")))
 | |
|   {
 | |
|     *errstr = errAddFilter;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(CoCreateInstance(
 | |
|           CLSID_SampleGrabber, nullptr, CLSCTX_INPROC,
 | |
|           IID_IBaseFilter, (void**)&grabberFilter)))
 | |
|   {
 | |
|     *errstr = errGrabber;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(grabberFilter->QueryInterface(IID_ISampleGrabber, (void**)&grabber)))
 | |
|   {
 | |
|     *errstr = errGrabber;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     AM_MEDIA_TYPE mediaType;
 | |
|     memset(&mediaType, 0, sizeof(mediaType));
 | |
|     mediaType.majortype = MEDIATYPE_Video;
 | |
|     mediaType.subtype = MEDIASUBTYPE_YUY2;
 | |
|     mediaType.formattype = FORMAT_VideoInfo;
 | |
|     mediaType.bFixedSizeSamples = 1;
 | |
|     mediaType.cbFormat = sizeof(VIDEOINFOHEADER);
 | |
| 
 | |
|     VIDEOINFOHEADER videoInfoHdr;
 | |
|     memset(&videoInfoHdr, 0, sizeof(VIDEOINFOHEADER));
 | |
|     videoInfoHdr.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 | |
|     videoInfoHdr.bmiHeader.biWidth = cam->width;
 | |
|     videoInfoHdr.bmiHeader.biHeight = cam->height;
 | |
|     videoInfoHdr.bmiHeader.biPlanes = 1;
 | |
|     videoInfoHdr.bmiHeader.biBitCount = 16;
 | |
|     videoInfoHdr.bmiHeader.biCompression = MAKEFOURCC('Y', 'U', 'Y', '2');
 | |
|     mediaType.pbFormat = (BYTE*)&videoInfoHdr;
 | |
|     if (FAILED(grabber->SetMediaType(&mediaType)))
 | |
|     {
 | |
|       *errstr = errGrabber;
 | |
|       goto fail;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (FAILED(graphBuilder->AddFilter(grabberFilter, L"grabber")))
 | |
|   {
 | |
|     *errstr = errAddFilter;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(CoCreateInstance(
 | |
|           CLSID_NullRenderer, nullptr, CLSCTX_INPROC,
 | |
|           IID_IBaseFilter, (void**)&nullFilter)))
 | |
|   {
 | |
|     *errstr = errTerminator;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   if (FAILED(graphBuilder->AddFilter(nullFilter, L"bull")))
 | |
|   {
 | |
|     *errstr = errAddFilter;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   HRESULT hr;
 | |
|   src = getPin(captureFilter, PINDIR_OUTPUT);
 | |
|   dst = getPin(grabberFilter, PINDIR_INPUT);
 | |
|   if (src == nullptr || dst == nullptr ||
 | |
|       FAILED(hr = graphBuilder->Connect(src, dst)))
 | |
|   {
 | |
|     *errstr = errConnectFilters;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   safeRelease(&src);
 | |
|   safeRelease(&dst);
 | |
| 
 | |
|   end = getPin(grabberFilter, PINDIR_OUTPUT);
 | |
|   nul = getPin(nullFilter, PINDIR_INPUT);
 | |
|   if (end == nullptr || nul == nullptr ||
 | |
|       FAILED(hr = graphBuilder->Connect(end, nul)))
 | |
|   {
 | |
|     *errstr = errConnectFilters;
 | |
|     goto fail;
 | |
|   }
 | |
| 
 | |
|   safeRelease(&end);
 | |
|   safeRelease(&nul);
 | |
| 
 | |
|   safeRelease(&nullFilter);
 | |
|   safeRelease(&captureFilter);
 | |
|   safeRelease(&grabberFilter);
 | |
|   safeRelease(&graphBuilder);
 | |
| 
 | |
|   {
 | |
|     SampleGrabberCallback* cb = new SampleGrabberCallback(cam);
 | |
|     grabber->SetCallback(cb, 1);
 | |
|     cam->grabber = (void*)grabber;
 | |
|     cam->mediaControl = (void*)mediaControl;
 | |
|     cam->callback = (void*)cb;
 | |
| 
 | |
|     grabber->SetBufferSamples(true);
 | |
|     mediaControl->Run();
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| 
 | |
| fail:
 | |
|   safeRelease(&src);
 | |
|   safeRelease(&dst);
 | |
|   safeRelease(&end);
 | |
|   safeRelease(&nul);
 | |
|   safeRelease(&nullFilter);
 | |
|   safeRelease(&grabber);
 | |
|   safeRelease(&grabberFilter);
 | |
|   safeRelease(&mediaControl);
 | |
|   safeRelease(&captureFilter);
 | |
|   safeRelease(&graphBuilder);
 | |
|   safeRelease(&moniker);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| // SampleCB is not used in this app.
 | |
| HRESULT SampleGrabberCallback::SampleCB(double sampleTime, IMediaSample* sample)
 | |
| {
 | |
|   return S_OK;
 | |
| }
 | |
| 
 | |
| // BufferCB receives image from DirectShow.
 | |
| HRESULT SampleGrabberCallback::BufferCB(double sampleTime, BYTE* buf, LONG len)
 | |
| {
 | |
|   BYTE* gobuf = (BYTE*)cam_->buf;
 | |
|   const int nPix = cam_->width * cam_->height;
 | |
|   if (len > nPix * 2)
 | |
|   {
 | |
|     fprintf(stderr, "Wrong frame buffer size: %d > %d\n", len, nPix * 2);
 | |
|     return S_OK;
 | |
|   }
 | |
|   int yi = 0;
 | |
|   int cbi = cam_->width * cam_->height;
 | |
|   int cri = cbi + cbi / 2;
 | |
|   // Pack as I422
 | |
|   for (int y = 0; y < cam_->height; ++y)
 | |
|   {
 | |
|     int j = y * cam_->width * 2;
 | |
|     for (int x = 0; x < cam_->width / 2; ++x)
 | |
|     {
 | |
|       gobuf[yi] = buf[j];
 | |
|       gobuf[cbi] = buf[j + 1];
 | |
|       gobuf[yi + 1] = buf[j + 2];
 | |
|       gobuf[cri] = buf[j + 3];
 | |
|       j += 4;
 | |
|       yi += 2;
 | |
|       cbi++;
 | |
|       cri++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   imageCallback((size_t)cam_);
 | |
|   return S_OK;
 | |
| }
 | |
| 
 | |
| // freeCamera closes device and frees all resources allocated by openCamera().
 | |
| void freeCamera(camera* cam)
 | |
| {
 | |
|   if (cam->mediaControl)
 | |
|     ((IMediaControl*)cam->mediaControl)->Stop();
 | |
| 
 | |
|   safeRelease((ISampleGrabber**)&cam->grabber);
 | |
|   safeRelease((IMediaControl**)&cam->mediaControl);
 | |
| 
 | |
|   if (cam->callback)
 | |
|   {
 | |
|     ((SampleGrabberCallback*)cam->callback)->Release();
 | |
|     delete ((SampleGrabberCallback*)cam->callback);
 | |
|     cam->callback = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (cam->props)
 | |
|   {
 | |
|     delete cam->props;
 | |
|     cam->props = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // utf16Decode returns UTF-8 string from UTF-16 string.
 | |
| std::string utf16Decode(LPOLESTR olestr)
 | |
| {
 | |
|   std::wstring wstr(olestr);
 | |
|   const int len = WideCharToMultiByte(
 | |
|       CP_UTF8, 0,
 | |
|       wstr.data(), (int)wstr.size(),
 | |
|       nullptr, 0, nullptr, nullptr);
 | |
|   std::string str(len, 0);
 | |
|   WideCharToMultiByte(
 | |
|       CP_UTF8, 0,
 | |
|       wstr.data(), (int)wstr.size(),
 | |
|       (LPSTR)str.data(), len, nullptr, nullptr);
 | |
|   return str;
 | |
| }
 | |
| 
 | |
| // getPin is a helper to get I/O pin of DirectShow filter.
 | |
| IPin* getPin(IBaseFilter* filter, PIN_DIRECTION dir)
 | |
| {
 | |
|   IEnumPins* enumPins;
 | |
|   if (FAILED(filter->EnumPins(&enumPins)))
 | |
|     return nullptr;
 | |
| 
 | |
|   IPin* pin;
 | |
|   while (enumPins->Next(1, &pin, nullptr) == S_OK)
 | |
|   {
 | |
|     PIN_DIRECTION d;
 | |
|     pin->QueryDirection(&d);
 | |
|     if (d == dir)
 | |
|     {
 | |
|       enumPins->Release();
 | |
|       return pin;
 | |
|     }
 | |
|     pin->Release();
 | |
|   }
 | |
|   enumPins->Release();
 | |
|   return nullptr;
 | |
| }
 | 
