464 lines
14 KiB
C++
464 lines
14 KiB
C++
/*
|
|
|
|
OpenPnp-Capture: a video capture subsystem.
|
|
|
|
Windows platform/implementation specific structures and typedefs.
|
|
|
|
The platform classes are also responsible for converting
|
|
the frames into 24-bit per pixel RGB frames.
|
|
|
|
Created by Niels Moseley on 7/6/17.
|
|
Copyright (c) 2017 Niels Moseley.
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
|
|
#include <vector>
|
|
#include <stdio.h>
|
|
|
|
#include <windows.h>
|
|
#include <mmsystem.h> // for MAKEFOURCC macro
|
|
|
|
#include "../common/logging.h"
|
|
#include "scopedcomptr.h"
|
|
#include "platformstream.h"
|
|
#include "platformcontext.h"
|
|
|
|
// a platform factory function needed by
|
|
// libmain.cpp
|
|
Context* createPlatformContext()
|
|
{
|
|
return new PlatformContext();
|
|
}
|
|
|
|
PlatformContext::PlatformContext() : Context()
|
|
{
|
|
HRESULT hr;
|
|
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
if (hr != S_OK)
|
|
{
|
|
// This might happen when another part of the program
|
|
// as already called CoInitializeEx.
|
|
// and we can carry on without problems...
|
|
LOG(LOG_WARNING, "PlatformContext::CoInitializeEx failed (HRESULT = %08X)!\n", hr);
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_DEBUG, "PlatformContext created\n");
|
|
}
|
|
|
|
enumerateDevices();
|
|
}
|
|
|
|
PlatformContext::~PlatformContext()
|
|
{
|
|
CoUninitialize();
|
|
}
|
|
|
|
|
|
bool PlatformContext::enumerateDevices()
|
|
{
|
|
ICreateDevEnum* dev_enum = nullptr;
|
|
IEnumMoniker* enum_moniker = nullptr;
|
|
IMoniker* moniker = nullptr;
|
|
IPropertyBag* pbag = nullptr;
|
|
|
|
LOG(LOG_DEBUG, "Enumerating devices\n");
|
|
|
|
m_devices.clear();
|
|
|
|
//create an enumerator for video input devices
|
|
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,CLSCTX_INPROC_SERVER,IID_ICreateDevEnum,(void**) &dev_enum);
|
|
if ((hr != S_OK) || (dev_enum == nullptr))
|
|
{
|
|
LOG(LOG_CRIT, "Could not create ICreateDevEnum object\n");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_DEBUG, "ICreateDevEnum created\n");
|
|
}
|
|
|
|
ScopedComPtr<ICreateDevEnum> devEnum(dev_enum);
|
|
|
|
hr = devEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&enum_moniker, 0);
|
|
if (hr == S_FALSE)
|
|
{
|
|
// no devices found!
|
|
LOG(LOG_INFO, "No devices found\n");
|
|
return true;
|
|
}
|
|
if (hr != S_OK)
|
|
{
|
|
LOG(LOG_CRIT, "Could not create class enumerator object\n");
|
|
return false;
|
|
}
|
|
|
|
ScopedComPtr<IEnumMoniker> enumMoniker(enum_moniker);
|
|
|
|
//get devices
|
|
uint32_t num_devices = 0;
|
|
VARIANT name;
|
|
while (enumMoniker->Next(1, &moniker,0) == S_OK)
|
|
{
|
|
//get properties
|
|
hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, (void**) &pbag);
|
|
|
|
if (hr >= 0)
|
|
{
|
|
platformDeviceInfo *info = new platformDeviceInfo();
|
|
VariantInit(&name);
|
|
|
|
//get the description
|
|
hr = pbag->Read(L"Description", &name, 0);
|
|
if (hr < 0) hr = pbag->Read(L"FriendlyName", &name, 0);
|
|
if (hr >= 0)
|
|
{
|
|
BSTR BStringPtr = name.bstrVal;
|
|
|
|
if (BStringPtr)
|
|
{
|
|
// copy wchar device name into info structure so we can reference the
|
|
// device later
|
|
info->m_filterName = std::wstring(BStringPtr);
|
|
|
|
// convert wchar string to UTF-8 to pass to JAVA
|
|
info->m_name = wcharPtrToString(BStringPtr);
|
|
info->m_uniqueID = info->m_name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_ERR, "Could not generate device name for device!\n");
|
|
}
|
|
|
|
hr = pbag->Read(L"DevicePath", &name, 0);
|
|
if (hr >= 0 && name.bstrVal)
|
|
{
|
|
info->m_devicePath = std::wstring(name.bstrVal);
|
|
}
|
|
else {
|
|
LOG(LOG_WARNING, " device path not found! fallback to using device index...\n");
|
|
info->m_devicePath = std::to_wstring(num_devices);
|
|
}
|
|
|
|
info->m_uniqueID.append(" ");
|
|
info->m_uniqueID.append(wstringToString(info->m_devicePath));
|
|
LOG(LOG_INFO, " -> PATH %s\n", wstringToString(info->m_devicePath).c_str());
|
|
|
|
enumerateFrameInfo(moniker, info);
|
|
m_devices.push_back(info);
|
|
|
|
LOG(LOG_INFO, "ID %d -> %s\n", num_devices, info->m_name.c_str());
|
|
|
|
VariantClear(&name);
|
|
|
|
pbag->Release();
|
|
pbag = nullptr;
|
|
|
|
moniker->Release();
|
|
moniker = nullptr;
|
|
|
|
num_devices++;
|
|
}
|
|
else{
|
|
moniker->Release();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
std::string PlatformContext::wstringToString(const std::wstring &wstr)
|
|
{
|
|
return wcharPtrToString(wstr.c_str());
|
|
}
|
|
|
|
std::string PlatformContext::wcharPtrToString(const wchar_t *sstr)
|
|
{
|
|
std::vector<char> buffer;
|
|
int32_t chars = WideCharToMultiByte(CP_UTF8, 0, sstr, -1, nullptr, 0, nullptr, nullptr);
|
|
if (chars == 0) return std::string("");
|
|
|
|
buffer.resize(chars);
|
|
WideCharToMultiByte(CP_UTF8, 0, sstr, -1, &buffer[0], chars, nullptr, nullptr);
|
|
return std::string(&buffer[0]);
|
|
}
|
|
|
|
|
|
// Release the format block for a media type.
|
|
void _FreeMediaType(AM_MEDIA_TYPE& mt)
|
|
{
|
|
if (mt.cbFormat != 0)
|
|
{
|
|
CoTaskMemFree((PVOID)mt.pbFormat);
|
|
mt.cbFormat = 0;
|
|
mt.pbFormat = NULL;
|
|
}
|
|
if (mt.pUnk != NULL)
|
|
{
|
|
// pUnk should not be used.
|
|
mt.pUnk->Release();
|
|
mt.pUnk = NULL;
|
|
}
|
|
}
|
|
|
|
// Delete a media type structure that was allocated on the heap.
|
|
void _DeleteMediaType(AM_MEDIA_TYPE *pmt)
|
|
{
|
|
if (pmt != NULL)
|
|
{
|
|
_FreeMediaType(*pmt);
|
|
CoTaskMemFree(pmt);
|
|
}
|
|
}
|
|
|
|
bool PinMatchesCategory(IPin *pPin, REFGUID Category)
|
|
{
|
|
bool bFound = FALSE;
|
|
|
|
IKsPropertySet *pKs = NULL;
|
|
HRESULT hr = pPin->QueryInterface(IID_PPV_ARGS(&pKs));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
GUID PinCategory;
|
|
DWORD cbReturned;
|
|
hr = pKs->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0,
|
|
&PinCategory, sizeof(GUID), &cbReturned);
|
|
if (SUCCEEDED(hr) && (cbReturned == sizeof(GUID)))
|
|
{
|
|
bFound = (PinCategory == Category);
|
|
}
|
|
pKs->Release();
|
|
}
|
|
return bFound;
|
|
}
|
|
|
|
HRESULT FindPinByCategory(
|
|
IBaseFilter *pFilter, // Pointer to the filter to search.
|
|
PIN_DIRECTION PinDir, // Direction of the pin.
|
|
REFGUID Category, // Pin category.
|
|
IPin **ppPin) // Receives a pointer to the pin.
|
|
{
|
|
*ppPin = 0;
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL bFound = FALSE;
|
|
|
|
IEnumPins *pEnum = 0;
|
|
IPin *pPin = 0;
|
|
|
|
hr = pFilter->EnumPins(&pEnum);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
ScopedComPtr<IEnumPins> pinEnum(pEnum);
|
|
|
|
while (hr = pinEnum->Next(1, &pPin, 0), hr == S_OK)
|
|
{
|
|
PIN_DIRECTION ThisPinDir;
|
|
hr = pPin->QueryDirection(&ThisPinDir);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
ScopedComPtr<IPin> myPin(pPin);
|
|
|
|
if ((ThisPinDir == PinDir) &&
|
|
PinMatchesCategory(pPin, Category))
|
|
{
|
|
*ppPin = pPin;
|
|
(*ppPin)->AddRef(); // The caller must release the interface.
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
bool PlatformContext::enumerateFrameInfo(IMoniker *moniker, platformDeviceInfo *info)
|
|
{
|
|
IBaseFilter *pCap = NULL;
|
|
IEnumPins *pEnum = NULL;
|
|
IPin *pPin = NULL;
|
|
|
|
LOG(LOG_DEBUG, "enumerateFrameInfo() called\n");
|
|
|
|
HRESULT hr = moniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
|
|
if (!SUCCEEDED(hr))
|
|
{
|
|
LOG(LOG_ERR, "No frame information: BindToObject failed.\n");
|
|
return false;
|
|
}
|
|
|
|
ScopedComPtr<IBaseFilter> baseFilter(pCap);
|
|
|
|
hr = baseFilter->EnumPins(&pEnum);
|
|
if (FAILED(hr))
|
|
{
|
|
LOG(LOG_ERR, "No frame information: EnumPins failed.\n");
|
|
return false;
|
|
}
|
|
ScopedComPtr<IEnumPins> pinEnum(pEnum);
|
|
if (FindPinByCategory(pCap, PINDIR_OUTPUT, PIN_CATEGORY_CAPTURE, &pPin) == S_OK)
|
|
{
|
|
LOG(LOG_INFO, "Capture pin found!\n");
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_ERR, "Could not find capture pin!\n");
|
|
return false;
|
|
}
|
|
|
|
ScopedComPtr<IPin> capturePin(pPin);
|
|
|
|
// retrieve an IAMStreamConfig interface
|
|
IAMStreamConfig *pConfig = NULL;
|
|
if (capturePin->QueryInterface(IID_IAMStreamConfig, (void**)&pConfig) != S_OK)
|
|
{
|
|
LOG(LOG_ERR, "Could not create IAMStreamConfig interface!\n");
|
|
return false;
|
|
}
|
|
|
|
ScopedComPtr<IAMStreamConfig> streamConfig(pConfig);
|
|
|
|
int iCount = 0, iSize = 0;
|
|
hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);
|
|
|
|
LOG(LOG_INFO,"Stream has %d capabilities.\n", iCount);
|
|
|
|
// Check the size to make sure we pass in the correct structure.
|
|
if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS))
|
|
{
|
|
// Use the video capabilities structure.
|
|
|
|
for (int32_t iFormat = 0; iFormat < iCount; iFormat++)
|
|
{
|
|
VIDEO_STREAM_CONFIG_CAPS scc;
|
|
AM_MEDIA_TYPE *pmtConfig;
|
|
hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
/* Examine the format, and possibly use it. */
|
|
if (pmtConfig->formattype == FORMAT_VideoInfo)
|
|
{
|
|
// Check the buffer size.
|
|
if (pmtConfig->cbFormat >= sizeof(VIDEOINFOHEADER))
|
|
{
|
|
VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>(pmtConfig->pbFormat);
|
|
CapFormatInfo newFrameInfo;
|
|
if (pVih != nullptr)
|
|
{
|
|
newFrameInfo.bpp = pVih->bmiHeader.biBitCount;
|
|
if (pVih->bmiHeader.biCompression == BI_RGB)
|
|
{
|
|
newFrameInfo.fourcc = MAKEFOURCC('R', 'G', 'B', ' ');
|
|
}
|
|
else if (pVih->bmiHeader.biCompression == BI_BITFIELDS)
|
|
{
|
|
newFrameInfo.fourcc = MAKEFOURCC(' ', ' ', ' ', ' ');
|
|
}
|
|
else
|
|
{
|
|
newFrameInfo.fourcc = pVih->bmiHeader.biCompression;
|
|
}
|
|
|
|
newFrameInfo.width = pVih->bmiHeader.biWidth;
|
|
newFrameInfo.height = pVih->bmiHeader.biHeight;
|
|
|
|
if (pVih->AvgTimePerFrame != 0)
|
|
{
|
|
// pVih->AvgTimePerFrame is in units of 100ns
|
|
newFrameInfo.fps = static_cast<uint32_t>(10.0e6f/static_cast<float>(pVih->AvgTimePerFrame));
|
|
}
|
|
else
|
|
{
|
|
newFrameInfo.fps = 0;
|
|
}
|
|
|
|
std::string fourCCString = fourCCToString(newFrameInfo.fourcc);
|
|
|
|
LOG(LOG_INFO, "%d x %d %d fps %d bpp FOURCC=%s\n", newFrameInfo.width, newFrameInfo.height,
|
|
newFrameInfo.fps, newFrameInfo.bpp, fourCCString.c_str());
|
|
|
|
info->m_formats.push_back(newFrameInfo);
|
|
}
|
|
}
|
|
}
|
|
// Delete the media type when you are done.
|
|
_DeleteMediaType(pmtConfig);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
HRESULT FindCaptureDevice(IBaseFilter** ppSrcFilter, const wchar_t* devicePath)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
std::wstring strDevicePath = devicePath;
|
|
|
|
ICreateDevEnum* pDevEnum = nullptr;
|
|
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
ScopedComPtr<ICreateDevEnum> devEnum(pDevEnum);
|
|
|
|
IEnumMoniker* pEnumMoniker = nullptr;
|
|
hr = devEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumMoniker, 0);
|
|
if(FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
ScopedComPtr<IEnumMoniker> enumMoniker(pEnumMoniker);
|
|
|
|
IMoniker* pMoniker = nullptr;
|
|
for(int num_devices = 0; enumMoniker->Next(1, &pMoniker, 0) == S_OK; ++num_devices)
|
|
{
|
|
ScopedComPtr<IMoniker> moniker(pMoniker);
|
|
|
|
IPropertyBag* pbag = nullptr;
|
|
hr = moniker->BindToStorage(0, 0, IID_PPV_ARGS(&pbag));
|
|
if(FAILED(hr)) {
|
|
continue; // Skip this one, maybe the next one will work.
|
|
}
|
|
ScopedComPtr<IPropertyBag> propertyBag(pbag);
|
|
|
|
VARIANT varName;
|
|
VariantInit(&varName);
|
|
hr = pbag->Read(L"DevicePath", &varName, 0);
|
|
if ((SUCCEEDED(hr) && strDevicePath == varName.bstrVal) ||
|
|
(FAILED(hr) && strDevicePath == std::to_wstring(num_devices))) {
|
|
VariantClear(&varName);
|
|
hr = pMoniker->BindToObject(0, 0, IID_PPV_ARGS(ppSrcFilter));
|
|
return hr;
|
|
}
|
|
VariantClear(&varName);
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|