mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
- New Media Foundation grabber
- JsonAPI available grabber fix - commented json config removed
This commit is contained in:
16
libsrc/grabber/mediafoundation/CMakeLists.txt
Normal file
16
libsrc/grabber/mediafoundation/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
# Define the current source locations
|
||||
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber)
|
||||
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/mediafoundation)
|
||||
|
||||
FILE ( GLOB MF_SOURCES "${CURRENT_HEADER_DIR}/MF*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
|
||||
|
||||
add_library(mf-grabber ${MF_SOURCES} )
|
||||
|
||||
target_link_libraries(mf-grabber
|
||||
hyperion
|
||||
${QT_LIBRARIES}
|
||||
)
|
||||
|
||||
if(TURBOJPEG_FOUND)
|
||||
target_link_libraries(mf-grabber ${TurboJPEG_LIBRARY})
|
||||
endif(TURBOJPEG_FOUND)
|
868
libsrc/grabber/mediafoundation/MFGrabber.cpp
Normal file
868
libsrc/grabber/mediafoundation/MFGrabber.cpp
Normal file
@@ -0,0 +1,868 @@
|
||||
#include "MFSourceReaderCB.h"
|
||||
#include "grabber/MFGrabber.h"
|
||||
|
||||
static PixelFormat GetPixelFormatForGuid(const GUID guid)
|
||||
{
|
||||
if (IsEqualGUID(guid, MFVideoFormat_RGB32)) return PixelFormat::RGB32;
|
||||
if (IsEqualGUID(guid, MFVideoFormat_YUY2)) return PixelFormat::YUYV;
|
||||
if (IsEqualGUID(guid, MFVideoFormat_UYVY)) return PixelFormat::UYVY;
|
||||
if (IsEqualGUID(guid, MFVideoFormat_MJPG)) return PixelFormat::MJPEG;
|
||||
return PixelFormat::NO_CHANGE;
|
||||
};
|
||||
|
||||
MFGrabber::MFGrabber(const QString & device, unsigned width, unsigned height, unsigned fps, unsigned input, int pixelDecimation)
|
||||
: Grabber("V4L2:"+device)
|
||||
, _deviceName(device)
|
||||
, _buffers()
|
||||
, _hr(S_FALSE)
|
||||
, _sourceReader(nullptr)
|
||||
, _pixelDecimation(pixelDecimation)
|
||||
, _lineLength(-1)
|
||||
, _frameByteSize(-1)
|
||||
, _noSignalCounterThreshold(40)
|
||||
, _noSignalCounter(0)
|
||||
, _fpsSoftwareDecimation(1)
|
||||
, _brightness(0)
|
||||
, _contrast(0)
|
||||
, _saturation(0)
|
||||
, _hue(0)
|
||||
, _currentFrame(0)
|
||||
, _noSignalThresholdColor(ColorRgb{0,0,0})
|
||||
, _signalDetectionEnabled(true)
|
||||
, _cecDetectionEnabled(true)
|
||||
, _noSignalDetected(false)
|
||||
, _initialized(false)
|
||||
, _x_frac_min(0.25)
|
||||
, _y_frac_min(0.25)
|
||||
, _x_frac_max(0.75)
|
||||
, _y_frac_max(0.75)
|
||||
{
|
||||
setInput(input);
|
||||
setWidthHeight(width, height);
|
||||
setFramerate(fps);
|
||||
// setDeviceVideoStandard(device, videoStandard);
|
||||
|
||||
CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
_hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
|
||||
if (FAILED(_hr))
|
||||
CoUninitialize();
|
||||
else
|
||||
_sourceReaderCB = new SourceReaderCB(this);
|
||||
}
|
||||
|
||||
MFGrabber::~MFGrabber()
|
||||
{
|
||||
uninit();
|
||||
|
||||
SAFE_RELEASE(_sourceReader);
|
||||
SAFE_RELEASE(_sourceReaderCB);
|
||||
|
||||
if (SUCCEEDED(_hr) && SUCCEEDED(MFShutdown()))
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
bool MFGrabber::init()
|
||||
{
|
||||
if (!_initialized && SUCCEEDED(_hr))
|
||||
{
|
||||
QString foundDevice = "";
|
||||
int foundIndex = -1, bestGuess = -1, bestGuessMinX = INT_MAX, bestGuessMinFPS = INT_MAX;
|
||||
bool autoDiscovery = (QString::compare(_deviceName, "auto", Qt::CaseInsensitive) == 0 );
|
||||
|
||||
// enumerate the video capture devices on the user's system
|
||||
enumVideoCaptureDevices();
|
||||
|
||||
if (!autoDiscovery && !_deviceProperties.contains(_deviceName))
|
||||
{
|
||||
Debug(_log, "Device '%s' is not available. Changing to auto.", QSTRING_CSTR(_deviceName));
|
||||
autoDiscovery = true;
|
||||
}
|
||||
|
||||
if (autoDiscovery)
|
||||
{
|
||||
Debug(_log, "Forcing auto discovery device");
|
||||
if (_deviceProperties.count()>0)
|
||||
{
|
||||
foundDevice = _deviceProperties.firstKey();
|
||||
_deviceName = foundDevice;
|
||||
Debug(_log, "Auto discovery set to %s", QSTRING_CSTR(_deviceName));
|
||||
}
|
||||
}
|
||||
else
|
||||
foundDevice = _deviceName;
|
||||
|
||||
if (foundDevice.isNull() || foundDevice.isEmpty() || !_deviceProperties.contains(foundDevice))
|
||||
{
|
||||
Error(_log, "Could not find any capture device");
|
||||
return false;
|
||||
}
|
||||
|
||||
MFGrabber::DeviceProperties dev = _deviceProperties[foundDevice];
|
||||
|
||||
Debug(_log, "Searching for %s %d x %d @ %d fps (%s)", QSTRING_CSTR(foundDevice), _width, _height,_fps, QSTRING_CSTR(pixelFormatToString(_pixelFormat)));
|
||||
|
||||
for( int i = 0; i < dev.valid.count() && foundIndex < 0; ++i )
|
||||
{
|
||||
bool strict = false;
|
||||
const auto& val = dev.valid[i];
|
||||
|
||||
if (bestGuess == -1 || (val.x <= bestGuessMinX && val.x >= 640 && val.fps <= bestGuessMinFPS && val.fps >= 10))
|
||||
{
|
||||
bestGuess = i;
|
||||
bestGuessMinFPS = val.fps;
|
||||
bestGuessMinX = val.x;
|
||||
}
|
||||
|
||||
if(_width && _height)
|
||||
{
|
||||
strict = true;
|
||||
if (val.x != _width || val.y != _height)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_fps && _fps!=15)
|
||||
{
|
||||
strict = true;
|
||||
if (val.fps != _fps)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_pixelFormat != PixelFormat::NO_CHANGE)
|
||||
{
|
||||
strict = true;
|
||||
if (val.pf != _pixelFormat)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strict)
|
||||
foundIndex = i;
|
||||
}
|
||||
|
||||
if (foundIndex < 0 && bestGuess >= 0)
|
||||
{
|
||||
if (!autoDiscovery)
|
||||
Warning(_log, "Selected resolution not found in supported modes. Forcing best resolution");
|
||||
else
|
||||
Debug(_log, "Forcing best resolution");
|
||||
|
||||
foundIndex = bestGuess;
|
||||
}
|
||||
|
||||
if (foundIndex>=0)
|
||||
{
|
||||
if (init_device(foundDevice, dev.valid[foundIndex]))
|
||||
_initialized = true;
|
||||
}
|
||||
else
|
||||
Error(_log, "Could not find any capture device settings");
|
||||
}
|
||||
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
void MFGrabber::uninit()
|
||||
{
|
||||
// stop if the grabber was not stopped
|
||||
if (_initialized)
|
||||
{
|
||||
Debug(_log,"uninit grabber: %s", QSTRING_CSTR(_deviceName));
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool MFGrabber::init_device(QString deviceName, DevicePropertiesItem props)
|
||||
{
|
||||
bool setStreamParamOK = false;
|
||||
PixelFormat pixelformat = GetPixelFormatForGuid(props.guid);
|
||||
QString error, guid = _deviceProperties[deviceName].name;
|
||||
HRESULT hr,hr1,hr2;
|
||||
|
||||
Debug(_log, "Init %s, %d x %d @ %d fps (%s) => %s", QSTRING_CSTR(deviceName), props.x, props.y, props.fps, QSTRING_CSTR(pixelFormatToString(pixelformat)), QSTRING_CSTR(guid));
|
||||
|
||||
IMFMediaSource* device = nullptr;
|
||||
IMFAttributes* attr;
|
||||
hr = MFCreateAttributes(&attr, 2);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = attr->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
int size = guid.length() + 1024;
|
||||
wchar_t *name = new wchar_t[size];
|
||||
memset(name, 0, size);
|
||||
guid.toWCharArray(name);
|
||||
|
||||
if (SUCCEEDED(attr->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, (LPCWSTR)name)) && _sourceReaderCB)
|
||||
{
|
||||
hr = MFCreateDeviceSource(attr, &device);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAFE_RELEASE(device);;
|
||||
error = QString("MFCreateDeviceSource %1").arg(hr);
|
||||
}
|
||||
}
|
||||
else
|
||||
error = QString("IMFAttributes_SetString_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK %1").arg(hr);
|
||||
|
||||
delete[] name;
|
||||
}
|
||||
SAFE_RELEASE(attr);
|
||||
}
|
||||
else
|
||||
{
|
||||
SAFE_RELEASE(attr);
|
||||
error = QString("MFCreateAttributes_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE %1").arg(hr);
|
||||
}
|
||||
|
||||
if (device)
|
||||
{
|
||||
Debug(_log, "Device opened");
|
||||
if (_brightness != 0 || _contrast != 0 || _saturation != 0 || _hue != 0)
|
||||
{
|
||||
IAMVideoProcAmp *pProcAmp = NULL;
|
||||
if (SUCCEEDED(device->QueryInterface(IID_PPV_ARGS(&pProcAmp))))
|
||||
{
|
||||
long lMin, lMax, lStep, lDefault, lCaps, Val;
|
||||
if (_brightness != 0)
|
||||
{
|
||||
if (SUCCEEDED(pProcAmp->GetRange(VideoProcAmp_Brightness, &lMin, &lMax, &lStep, &lDefault, &lCaps)))
|
||||
{
|
||||
Debug(_log, "Brightness: min=%i, max=%i, default=%i", lMin, lMax, lDefault);
|
||||
|
||||
if (SUCCEEDED(pProcAmp->Get(VideoProcAmp_Brightness, &Val, &lCaps)))
|
||||
Debug(_log, "Current brightness set to: %i",Val);
|
||||
|
||||
if (SUCCEEDED(pProcAmp->Set(VideoProcAmp_Brightness, _brightness, VideoProcAmp_Flags_Manual)))
|
||||
Debug(_log, "Brightness set to: %i",_brightness);
|
||||
else
|
||||
Error(_log, "Could not set brightness");
|
||||
}
|
||||
else
|
||||
Error(_log, "Brightness is not supported by the grabber");
|
||||
}
|
||||
|
||||
if (_contrast != 0)
|
||||
{
|
||||
if (SUCCEEDED(pProcAmp->GetRange(VideoProcAmp_Contrast, &lMin, &lMax, &lStep, &lDefault, &lCaps)))
|
||||
{
|
||||
Debug(_log, "Contrast: min=%i, max=%i, default=%i", lMin, lMax, lDefault);
|
||||
|
||||
if (SUCCEEDED(pProcAmp->Get(VideoProcAmp_Contrast, &Val, &lCaps)))
|
||||
Debug(_log, "Current contrast set to: %i",Val);
|
||||
|
||||
if (SUCCEEDED(pProcAmp->Set(VideoProcAmp_Contrast, _contrast, VideoProcAmp_Flags_Manual)))
|
||||
Debug(_log, "Contrast set to: %i",_contrast);
|
||||
else
|
||||
Error(_log, "Could not set contrast");
|
||||
}
|
||||
else
|
||||
Error(_log, "Contrast is not supported by the grabber");
|
||||
}
|
||||
|
||||
if (_saturation != 0)
|
||||
{
|
||||
if (SUCCEEDED(pProcAmp->GetRange(VideoProcAmp_Saturation, &lMin, &lMax, &lStep, &lDefault, &lCaps)))
|
||||
{
|
||||
Debug(_log, "Saturation: min=%i, max=%i, default=%i", lMin, lMax, lDefault);
|
||||
|
||||
if (SUCCEEDED(pProcAmp->Get(VideoProcAmp_Saturation, &Val, &lCaps)))
|
||||
Debug(_log, "Current saturation set to: %i",Val);
|
||||
|
||||
if (SUCCEEDED(pProcAmp->Set(VideoProcAmp_Saturation, _saturation, VideoProcAmp_Flags_Manual)))
|
||||
Debug(_log, "Saturation set to: %i",_saturation);
|
||||
else
|
||||
Error(_log, "Could not set saturation");
|
||||
}
|
||||
else
|
||||
Error(_log, "Saturation is not supported by the grabber");
|
||||
}
|
||||
|
||||
if (_hue != 0)
|
||||
{
|
||||
hr = pProcAmp->GetRange(VideoProcAmp_Hue, &lMin, &lMax, &lStep, &lDefault, &lCaps);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
Debug(_log, "Hue: min=%i, max=%i, default=%i", lMin, lMax, lDefault);
|
||||
|
||||
hr = pProcAmp->Get(VideoProcAmp_Hue, &Val, &lCaps);
|
||||
if (SUCCEEDED(hr))
|
||||
Debug(_log, "Current hue set to: %i",Val);
|
||||
|
||||
hr = pProcAmp->Set(VideoProcAmp_Hue, _hue, VideoProcAmp_Flags_Manual);
|
||||
if (SUCCEEDED(hr))
|
||||
Debug(_log, "Hue set to: %i",_hue);
|
||||
else
|
||||
Error(_log, "Could not set hue");
|
||||
}
|
||||
else
|
||||
Error(_log, "Hue is not supported by the grabber");
|
||||
}
|
||||
|
||||
pProcAmp->Release();
|
||||
}
|
||||
}
|
||||
|
||||
IMFAttributes* pAttributes;
|
||||
hr1 = MFCreateAttributes(&pAttributes, 1);
|
||||
|
||||
if (SUCCEEDED(hr1))
|
||||
hr2 = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, (IMFSourceReaderCallback *)_sourceReaderCB);
|
||||
|
||||
if (SUCCEEDED(hr1) && SUCCEEDED(hr2))
|
||||
hr = MFCreateSourceReaderFromMediaSource(device, pAttributes, &_sourceReader);
|
||||
else
|
||||
hr = E_INVALIDARG;
|
||||
|
||||
if (SUCCEEDED(hr1))
|
||||
pAttributes->Release();
|
||||
|
||||
device->Release();
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
IMFMediaType* type;
|
||||
|
||||
hr = MFCreateMediaType(&type);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = type->SetGUID(MF_MT_SUBTYPE, props.guid);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = MFSetAttributeSize(type, MF_MT_FRAME_SIZE, props.x, props.y);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = MFSetAttributeSize(type, MF_MT_FRAME_RATE, props.fps_a, props.fps_b);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
MFSetAttributeRatio(type, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
|
||||
|
||||
hr = _sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, type);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
setStreamParamOK = true;
|
||||
}
|
||||
else
|
||||
error = QString("SetCurrentMediaType %1").arg(hr);
|
||||
}
|
||||
else
|
||||
error = QString("MFSetAttributeSize_MF_MT_FRAME_RATE %1").arg(hr);
|
||||
}
|
||||
else
|
||||
error = QString("SMFSetAttributeSize_MF_MT_FRAME_SIZE %1").arg(hr);
|
||||
}
|
||||
else
|
||||
error = QString("SetGUID_MF_MT_SUBTYPE %1").arg(hr);
|
||||
}
|
||||
else
|
||||
error = QString("SetGUID_MF_MT_MAJOR_TYPE %1").arg(hr);
|
||||
|
||||
type->Release();
|
||||
}
|
||||
else
|
||||
error = QString("IMFAttributes_SetString %1").arg(hr);
|
||||
|
||||
if (!setStreamParamOK)
|
||||
Error(_log, "Could not stream set params (%s)", QSTRING_CSTR(error));
|
||||
}
|
||||
else
|
||||
Error(_log, "MFCreateSourceReaderFromMediaSource (%i)", hr);
|
||||
}
|
||||
else
|
||||
Error(_log, "Could not open device (%s)", QSTRING_CSTR(error));
|
||||
|
||||
if (!setStreamParamOK)
|
||||
{
|
||||
SAFE_RELEASE(_sourceReader);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pixelFormat = props.pf;
|
||||
_width = props.x;
|
||||
_height = props.y;
|
||||
|
||||
switch (_pixelFormat)
|
||||
{
|
||||
case PixelFormat::UYVY:
|
||||
case PixelFormat::YUYV:
|
||||
{
|
||||
_frameByteSize = props.x * props.y * 2;
|
||||
_lineLength = props.x * 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case PixelFormat::RGB32:
|
||||
{
|
||||
_frameByteSize = props.x * props.y * 4;
|
||||
_lineLength = props.x * 3;
|
||||
}
|
||||
break;
|
||||
|
||||
case PixelFormat::MJPEG:
|
||||
{
|
||||
_lineLength = props.x * 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return setStreamParamOK;
|
||||
}
|
||||
|
||||
void MFGrabber::uninit_device()
|
||||
{
|
||||
SAFE_RELEASE(_sourceReader);
|
||||
}
|
||||
|
||||
void MFGrabber::enumVideoCaptureDevices()
|
||||
{
|
||||
if (FAILED(_hr))
|
||||
{
|
||||
Error(_log, "enumVideoCaptureDevices(): Media Foundation not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
_deviceProperties.clear();
|
||||
|
||||
IMFAttributes* attr;
|
||||
if(SUCCEEDED(MFCreateAttributes(&attr, 1)))
|
||||
{
|
||||
if(SUCCEEDED(attr->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)))
|
||||
{
|
||||
UINT32 count;
|
||||
IMFActivate** devices;
|
||||
if(SUCCEEDED(MFEnumDeviceSources(attr, &devices, &count)))
|
||||
{
|
||||
Debug(_log, "Detected devices: %u", count);
|
||||
for (UINT32 i = 0; i < count; i++)
|
||||
{
|
||||
UINT32 length;
|
||||
LPWSTR name;
|
||||
LPWSTR symlink;
|
||||
|
||||
if(SUCCEEDED(devices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &length)))
|
||||
{
|
||||
if(SUCCEEDED(devices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &symlink, &length)))
|
||||
{
|
||||
QString dev = QString::fromUtf16((const ushort*)name);
|
||||
MFGrabber::DeviceProperties properties;
|
||||
properties.name = QString::fromUtf16((const ushort*)symlink);
|
||||
|
||||
Info(_log, "Found capture device: %s", QSTRING_CSTR(dev));
|
||||
IMFMediaSource *pSource = nullptr;
|
||||
if(SUCCEEDED(devices[i]->ActivateObject(IID_PPV_ARGS(&pSource))))
|
||||
{
|
||||
IMFMediaType *pType = nullptr;
|
||||
IMFSourceReader* reader;
|
||||
if(SUCCEEDED(MFCreateSourceReaderFromMediaSource(pSource, NULL, &reader)))
|
||||
{
|
||||
for (DWORD i = 0; ; i++)
|
||||
{
|
||||
if (FAILED(reader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, i, &pType)))
|
||||
break;
|
||||
|
||||
GUID format;
|
||||
UINT64 frame_size;
|
||||
UINT64 frame_rate;
|
||||
|
||||
if( SUCCEEDED(pType->GetGUID(MF_MT_SUBTYPE, &format)) &&
|
||||
SUCCEEDED(pType->GetUINT64(MF_MT_FRAME_SIZE, &frame_size)) &&
|
||||
SUCCEEDED(pType->GetUINT64(MF_MT_FRAME_RATE, &frame_rate)) &&
|
||||
frame_rate > 0)
|
||||
{
|
||||
PixelFormat pixelformat = GetPixelFormatForGuid(format);
|
||||
DWORD w = frame_size >> 32;
|
||||
DWORD h = (DWORD) frame_size;
|
||||
DWORD fr1 = frame_rate >> 32;
|
||||
DWORD fr2 = (DWORD) frame_rate;
|
||||
if (pixelformat != PixelFormat::NO_CHANGE)
|
||||
{
|
||||
int framerate = fr1/fr2;
|
||||
QString sFrame = QString::number(framerate).rightJustified(2,' ');
|
||||
QString displayResolutions = QString::number(w).rightJustified(4,' ') +"x"+ QString::number(h).rightJustified(4,' ');
|
||||
|
||||
if (!properties.displayResolutions.contains(displayResolutions))
|
||||
properties.displayResolutions << displayResolutions;
|
||||
|
||||
if (!properties.framerates.contains(sFrame))
|
||||
properties.framerates << sFrame;
|
||||
|
||||
DevicePropertiesItem di;
|
||||
di.x = w;
|
||||
di.y = h;
|
||||
di.fps = framerate;
|
||||
di.fps_a = fr1;
|
||||
di.fps_b = fr2;
|
||||
di.pf = pixelformat;
|
||||
di.guid = format;
|
||||
properties.valid.append(di);
|
||||
}
|
||||
}
|
||||
|
||||
pType->Release();
|
||||
}
|
||||
reader->Release();
|
||||
}
|
||||
pSource->Release();
|
||||
}
|
||||
properties.displayResolutions.sort();
|
||||
properties.framerates.sort();
|
||||
_deviceProperties.insert(dev, properties);
|
||||
}
|
||||
CoTaskMemFree(symlink);
|
||||
}
|
||||
CoTaskMemFree(name);
|
||||
devices[i]->Release();
|
||||
}
|
||||
|
||||
CoTaskMemFree(devices);
|
||||
}
|
||||
attr->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MFGrabber::start_capturing()
|
||||
{
|
||||
if (_sourceReader)
|
||||
{
|
||||
HRESULT hr = _sourceReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||
0, NULL, NULL, NULL, NULL);
|
||||
if (!SUCCEEDED(hr))
|
||||
Error(_log, "ReadSample (%i)", hr);
|
||||
}
|
||||
}
|
||||
|
||||
bool MFGrabber::process_image(const void *frameImageBuffer, int size)
|
||||
{
|
||||
bool frameSend = false;
|
||||
|
||||
unsigned int processFrameIndex = _currentFrame++;
|
||||
|
||||
// frame skipping
|
||||
if ( (processFrameIndex % _fpsSoftwareDecimation != 0) && (_fpsSoftwareDecimation > 1))
|
||||
return frameSend;
|
||||
|
||||
// CEC detection
|
||||
if (_cecDetectionEnabled)
|
||||
return frameSend;
|
||||
|
||||
// We do want a new frame...
|
||||
if (size < _frameByteSize && _pixelFormat != PixelFormat::MJPEG)
|
||||
Error(_log, "Frame too small: %d != %d", size, _frameByteSize);
|
||||
else
|
||||
{
|
||||
if (_threadManager.isActive())
|
||||
{
|
||||
if (_threadManager._threads == nullptr)
|
||||
{
|
||||
_threadManager.initThreads();
|
||||
Debug(_log, "Max thread count = %d", _threadManager._maxThreads);
|
||||
|
||||
for (unsigned int i=0; i < _threadManager._maxThreads && _threadManager._threads != nullptr; i++)
|
||||
{
|
||||
MFThread* _thread=_threadManager._threads[i];
|
||||
connect(_thread, SIGNAL(newFrame(unsigned int, const Image<ColorRgb> &,unsigned int)), this , SLOT(newThreadFrame(unsigned int, const Image<ColorRgb> &, unsigned int)));
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i=0;_threadManager.isActive() && i < _threadManager._maxThreads && _threadManager._threads != nullptr; i++)
|
||||
{
|
||||
if ((_threadManager._threads[i]->isFinished() || !_threadManager._threads[i]->isRunning()))
|
||||
// aquire lock
|
||||
if ( _threadManager._threads[i]->isBusy() == false)
|
||||
{
|
||||
MFThread* _thread = _threadManager._threads[i];
|
||||
_thread->setup(i, _pixelFormat, (uint8_t *)frameImageBuffer, size, _width, _height, _lineLength, _subsamp, _cropLeft, _cropTop, _cropBottom, _cropRight, _videoMode, processFrameIndex, _pixelDecimation);
|
||||
|
||||
if (_threadManager._maxThreads > 1)
|
||||
_threadManager._threads[i]->start();
|
||||
|
||||
frameSend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frameSend;
|
||||
}
|
||||
|
||||
void MFGrabber::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, int noSignalCounterThreshold)
|
||||
{
|
||||
_noSignalThresholdColor.red = uint8_t(255*redSignalThreshold);
|
||||
_noSignalThresholdColor.green = uint8_t(255*greenSignalThreshold);
|
||||
_noSignalThresholdColor.blue = uint8_t(255*blueSignalThreshold);
|
||||
_noSignalCounterThreshold = qMax(1, noSignalCounterThreshold);
|
||||
|
||||
Info(_log, "Signal threshold set to: {%d, %d, %d} and frames: %d", _noSignalThresholdColor.red, _noSignalThresholdColor.green, _noSignalThresholdColor.blue, _noSignalCounterThreshold );
|
||||
}
|
||||
|
||||
void MFGrabber::setSignalDetectionOffset(double horizontalMin, double verticalMin, double horizontalMax, double verticalMax)
|
||||
{
|
||||
// rainbow 16 stripes 0.47 0.2 0.49 0.8
|
||||
// unicolor: 0.25 0.25 0.75 0.75
|
||||
|
||||
_x_frac_min = horizontalMin;
|
||||
_y_frac_min = verticalMin;
|
||||
_x_frac_max = horizontalMax;
|
||||
_y_frac_max = verticalMax;
|
||||
|
||||
Info(_log, "Signal detection area set to: %f,%f x %f,%f", _x_frac_min, _y_frac_min, _x_frac_max, _y_frac_max );
|
||||
}
|
||||
|
||||
bool MFGrabber::start()
|
||||
{
|
||||
try
|
||||
{
|
||||
_threadManager.start();
|
||||
Info(_log, "Decoding threads: %d",_threadManager._maxThreads );
|
||||
|
||||
if (init())
|
||||
{
|
||||
start_capturing();
|
||||
Info(_log, "Started");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
Error(_log, "Start failed (%s)", e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MFGrabber::stop()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
_threadManager.stop();
|
||||
uninit_device();
|
||||
_deviceProperties.clear();
|
||||
_initialized = false;
|
||||
Info(_log, "Stopped");
|
||||
}
|
||||
}
|
||||
|
||||
void MFGrabber::receive_image(const void *frameImageBuffer, int size, QString message)
|
||||
{
|
||||
if (frameImageBuffer == NULL || size ==0)
|
||||
Error(_log, "Received empty image frame: %s", QSTRING_CSTR(message));
|
||||
else
|
||||
{
|
||||
if (!message.isEmpty())
|
||||
Debug(_log, "Received image frame: %s", QSTRING_CSTR(message));
|
||||
process_image(frameImageBuffer, size);
|
||||
}
|
||||
|
||||
start_capturing();
|
||||
}
|
||||
|
||||
void MFGrabber::newThreadFrame(unsigned int threadIndex, const Image<ColorRgb>& image, unsigned int sourceCount)
|
||||
{
|
||||
checkSignalDetectionEnabled(image);
|
||||
|
||||
// get next frame
|
||||
if (threadIndex >_threadManager._maxThreads)
|
||||
Error(_log, "Frame index %d out of range", sourceCount);
|
||||
|
||||
if (threadIndex <= _threadManager._maxThreads)
|
||||
_threadManager._threads[threadIndex]->noBusy();
|
||||
}
|
||||
|
||||
void MFGrabber::checkSignalDetectionEnabled(Image<ColorRgb> image)
|
||||
{
|
||||
if (_signalDetectionEnabled)
|
||||
{
|
||||
// check signal (only in center of the resulting image, because some grabbers have noise values along the borders)
|
||||
bool noSignal = true;
|
||||
|
||||
// top left
|
||||
unsigned xOffset = image.width() * _x_frac_min;
|
||||
unsigned yOffset = image.height() * _y_frac_min;
|
||||
|
||||
// bottom right
|
||||
unsigned xMax = image.width() * _x_frac_max;
|
||||
unsigned yMax = image.height() * _y_frac_max;
|
||||
|
||||
for (unsigned x = xOffset; noSignal && x < xMax; ++x)
|
||||
for (unsigned y = yOffset; noSignal && y < yMax; ++y)
|
||||
noSignal &= (ColorRgb&)image(x, y) <= _noSignalThresholdColor;
|
||||
|
||||
if (noSignal)
|
||||
++_noSignalCounter;
|
||||
else
|
||||
{
|
||||
if (_noSignalCounter >= _noSignalCounterThreshold)
|
||||
{
|
||||
_noSignalDetected = true;
|
||||
Info(_log, "Signal detected");
|
||||
}
|
||||
|
||||
_noSignalCounter = 0;
|
||||
}
|
||||
|
||||
if ( _noSignalCounter < _noSignalCounterThreshold)
|
||||
{
|
||||
emit newFrame(image);
|
||||
}
|
||||
else if (_noSignalCounter == _noSignalCounterThreshold)
|
||||
{
|
||||
_noSignalDetected = false;
|
||||
Info(_log, "Signal lost");
|
||||
}
|
||||
}
|
||||
else
|
||||
emit newFrame(image);
|
||||
}
|
||||
|
||||
QStringList MFGrabber::getV4L2devices() const
|
||||
{
|
||||
QStringList result = QStringList();
|
||||
for (auto it = _deviceProperties.begin(); it != _deviceProperties.end(); ++it)
|
||||
result << it.key();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList MFGrabber::getV4L2EncodingFormats(const QString& devicePath) const
|
||||
{
|
||||
QStringList result = QStringList();
|
||||
|
||||
for(int i = 0; i < _deviceProperties[devicePath].valid.count(); ++i )
|
||||
if (!result.contains(pixelFormatToString(_deviceProperties[devicePath].valid[i].pf), Qt::CaseInsensitive))
|
||||
result << pixelFormatToString(_deviceProperties[devicePath].valid[i].pf).toLower();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MFGrabber::setSignalDetectionEnable(bool enable)
|
||||
{
|
||||
if (_signalDetectionEnabled != enable)
|
||||
{
|
||||
_signalDetectionEnabled = enable;
|
||||
Info(_log, "Signal detection is now %s", enable ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
void MFGrabber::setCecDetectionEnable(bool enable)
|
||||
{
|
||||
if (_cecDetectionEnabled != enable)
|
||||
{
|
||||
_cecDetectionEnabled = enable;
|
||||
Info(_log, QString("CEC detection is now %1").arg(enable ? "enabled" : "disabled").toLocal8Bit());
|
||||
}
|
||||
}
|
||||
|
||||
void MFGrabber::setPixelDecimation(int pixelDecimation)
|
||||
{
|
||||
if (_pixelDecimation != pixelDecimation)
|
||||
_pixelDecimation = pixelDecimation;
|
||||
}
|
||||
|
||||
void MFGrabber::setDeviceVideoStandard(QString device, VideoStandard videoStandard)
|
||||
{
|
||||
if (_deviceName != device)
|
||||
{
|
||||
_deviceName = device;
|
||||
if (_initialized && !device.isEmpty())
|
||||
{
|
||||
Debug(_log,"Restarting Media Foundation grabber");
|
||||
uninit();
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MFGrabber::setInput(int input)
|
||||
{
|
||||
if(Grabber::setInput(input))
|
||||
{
|
||||
bool started = _initialized;
|
||||
uninit();
|
||||
if(started)
|
||||
start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MFGrabber::setWidthHeight(int width, int height)
|
||||
{
|
||||
if(Grabber::setWidthHeight(width,height))
|
||||
{
|
||||
Debug(_log,"Set width:height to: %i:&i", width, height);
|
||||
if (_initialized)
|
||||
{
|
||||
Debug(_log,"Restarting Media Foundation grabber");
|
||||
uninit();
|
||||
start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MFGrabber::setFramerate(int fps)
|
||||
{
|
||||
if(Grabber::setFramerate(fps))
|
||||
{
|
||||
Debug(_log,"Set fps to: %i", fps);
|
||||
if (_initialized)
|
||||
{
|
||||
Debug(_log,"Restarting Media Foundation grabber");
|
||||
uninit();
|
||||
start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MFGrabber::setFpsSoftwareDecimation(int decimation)
|
||||
{
|
||||
_fpsSoftwareDecimation = decimation;
|
||||
if (decimation > 1)
|
||||
Debug(_log,"Every %ith image per second are processed", decimation);
|
||||
}
|
||||
|
||||
void MFGrabber::setEncoding(QString enc)
|
||||
{
|
||||
if (_pixelFormat != parsePixelFormat(enc))
|
||||
{
|
||||
Debug(_log,"Set encoding to: %s", QSTRING_CSTR(enc));
|
||||
_pixelFormat = parsePixelFormat(enc);
|
||||
if (_initialized)
|
||||
{
|
||||
Debug(_log,"Restarting Media Foundation Grabber");
|
||||
uninit();
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MFGrabber::setBrightnessContrastSaturationHue(int brightness, int contrast, int saturation, int hue)
|
||||
{
|
||||
if (_brightness != brightness || _contrast != contrast || _saturation != saturation || _hue != hue)
|
||||
{
|
||||
_brightness = brightness;
|
||||
_contrast = contrast;
|
||||
_saturation = saturation;
|
||||
_hue = hue;
|
||||
|
||||
Debug(_log,"Set brightness to %i, contrast to %i, saturation to %i, hue to %i", _brightness, _contrast, _saturation, _hue);
|
||||
|
||||
if (_initialized)
|
||||
{
|
||||
Debug(_log,"Restarting Media Foundation Grabber");
|
||||
uninit();
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
134
libsrc/grabber/mediafoundation/MFSourceReaderCB.h
Normal file
134
libsrc/grabber/mediafoundation/MFSourceReaderCB.h
Normal file
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
#include <shlwapi.h>
|
||||
#include <mferror.h>
|
||||
#include <strmif.h>
|
||||
|
||||
#pragma comment (lib, "ole32.lib")
|
||||
#pragma comment (lib, "mf.lib")
|
||||
#pragma comment (lib, "mfplat.lib")
|
||||
#pragma comment (lib, "mfuuid.lib")
|
||||
#pragma comment (lib, "mfreadwrite.lib")
|
||||
|
||||
#include <grabber/MFGrabber.h>
|
||||
|
||||
#define SAFE_RELEASE(x) if(x) { x->Release(); x = nullptr; }
|
||||
|
||||
class SourceReaderCB : public IMFSourceReaderCallback
|
||||
{
|
||||
public:
|
||||
SourceReaderCB(MFGrabber* grabber)
|
||||
: _nRefCount(1)
|
||||
, _grabber(grabber)
|
||||
, _bEOS(FALSE)
|
||||
, _hrStatus(S_OK)
|
||||
{
|
||||
InitializeCriticalSection(&_critsec);
|
||||
}
|
||||
|
||||
// IUnknown methods
|
||||
STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
|
||||
{
|
||||
static const QITAB qit[] =
|
||||
{
|
||||
QITABENT(SourceReaderCB, IMFSourceReaderCallback),
|
||||
{ 0 },
|
||||
};
|
||||
return QISearch(this, qit, iid, ppv);
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&_nRefCount);
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) Release()
|
||||
{
|
||||
ULONG uCount = InterlockedDecrement(&_nRefCount);
|
||||
if (uCount == 0)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
return uCount;
|
||||
}
|
||||
|
||||
// IMFSourceReaderCallback methods
|
||||
STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample)
|
||||
{
|
||||
EnterCriticalSection(&_critsec);
|
||||
|
||||
if (SUCCEEDED(hrStatus))
|
||||
{
|
||||
QString error = "";
|
||||
bool frameSend = false;
|
||||
|
||||
if (pSample)
|
||||
{
|
||||
IMFMediaBuffer* buffer;
|
||||
|
||||
hrStatus = pSample->ConvertToContiguousBuffer(&buffer);
|
||||
if (SUCCEEDED(hrStatus))
|
||||
{
|
||||
|
||||
BYTE* data = nullptr;
|
||||
DWORD maxLength = 0, currentLength = 0;
|
||||
|
||||
hrStatus = buffer->Lock(&data, &maxLength, ¤tLength);
|
||||
if(SUCCEEDED(hrStatus))
|
||||
{
|
||||
frameSend = true;
|
||||
_grabber->receive_image(data,currentLength,error);
|
||||
|
||||
buffer->Unlock();
|
||||
}
|
||||
else
|
||||
error = QString("buffer->Lock failed => %1").arg(hrStatus);
|
||||
|
||||
SAFE_RELEASE(buffer);
|
||||
}
|
||||
else
|
||||
error = QString("pSample->ConvertToContiguousBuffer failed => %1").arg(hrStatus);
|
||||
|
||||
}
|
||||
else
|
||||
error = "pSample is NULL";
|
||||
|
||||
if (!frameSend)
|
||||
_grabber->receive_image(NULL,0,error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Streaming error.
|
||||
NotifyError(hrStatus);
|
||||
}
|
||||
|
||||
if (MF_SOURCE_READERF_ENDOFSTREAM & dwStreamFlags)
|
||||
{
|
||||
// Reached the end of the stream.
|
||||
_bEOS = TRUE;
|
||||
}
|
||||
_hrStatus = hrStatus;
|
||||
|
||||
LeaveCriticalSection(&_critsec);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) { return S_OK; }
|
||||
STDMETHODIMP OnFlush(DWORD) { return S_OK; }
|
||||
|
||||
private:
|
||||
virtual ~SourceReaderCB() {}
|
||||
void NotifyError(HRESULT hr) { Error(_grabber->_log, "Source Reader error"); }
|
||||
|
||||
private:
|
||||
long _nRefCount;
|
||||
CRITICAL_SECTION _critsec;
|
||||
MFGrabber* _grabber;
|
||||
BOOL _bEOS;
|
||||
HRESULT _hrStatus;
|
||||
};
|
175
libsrc/grabber/mediafoundation/MFThread.cpp
Normal file
175
libsrc/grabber/mediafoundation/MFThread.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "grabber/MFThread.h"
|
||||
#include <QDebug>
|
||||
|
||||
volatile bool MFThread::_isActive = false;
|
||||
|
||||
MFThread::MFThread():
|
||||
_localData(nullptr),
|
||||
_localDataSize(0),
|
||||
_decompress(nullptr),
|
||||
_isBusy(false),
|
||||
_semaphore(1),
|
||||
_imageResampler()
|
||||
{
|
||||
}
|
||||
|
||||
MFThread::~MFThread()
|
||||
{
|
||||
if (_decompress == nullptr)
|
||||
tjDestroy(_decompress);
|
||||
|
||||
if (_localData != NULL)
|
||||
{
|
||||
free(_localData);
|
||||
_localData = NULL;
|
||||
_localDataSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MFThread::setup(
|
||||
unsigned int threadIndex, PixelFormat pixelFormat, uint8_t* sharedData,
|
||||
int size, int width, int height, int lineLength,
|
||||
int subsamp, unsigned cropLeft, unsigned cropTop, unsigned cropBottom, unsigned cropRight,
|
||||
VideoMode videoMode, int currentFrame, int pixelDecimation)
|
||||
{
|
||||
_workerIndex = threadIndex;
|
||||
_lineLength = lineLength;
|
||||
_pixelFormat = pixelFormat;
|
||||
_size = size;
|
||||
_width = width;
|
||||
_height = height;
|
||||
_subsamp = subsamp;
|
||||
_cropLeft = cropLeft;
|
||||
_cropTop = cropTop;
|
||||
_cropBottom = cropBottom;
|
||||
_cropRight = cropRight;
|
||||
_currentFrame = currentFrame;
|
||||
_pixelDecimation = pixelDecimation;
|
||||
|
||||
_imageResampler.setVideoMode(videoMode);
|
||||
_imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom);
|
||||
_imageResampler.setHorizontalPixelDecimation(_pixelDecimation);
|
||||
_imageResampler.setVerticalPixelDecimation(_pixelDecimation);
|
||||
|
||||
if (size > _localDataSize)
|
||||
{
|
||||
if (_localData != NULL)
|
||||
{
|
||||
free(_localData);
|
||||
_localData = NULL;
|
||||
_localDataSize = 0;
|
||||
}
|
||||
_localData = (uint8_t *) malloc(size+1);
|
||||
_localDataSize = size;
|
||||
}
|
||||
memcpy(_localData, sharedData, size);
|
||||
}
|
||||
|
||||
void MFThread::run()
|
||||
{
|
||||
if (_isActive && _width > 0 && _height > 0)
|
||||
{
|
||||
if (_pixelFormat == PixelFormat::MJPEG)
|
||||
{
|
||||
processImageMjpeg();
|
||||
}
|
||||
else
|
||||
{
|
||||
Image<ColorRgb> image = Image<ColorRgb>();
|
||||
_imageResampler.processImage(_localData, _width, _height, _lineLength, _pixelFormat, image);
|
||||
emit newFrame(_workerIndex, image, _currentFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MFThread::isBusy()
|
||||
{
|
||||
bool temp;
|
||||
_semaphore.acquire();
|
||||
if (_isBusy)
|
||||
temp = true;
|
||||
else
|
||||
{
|
||||
temp = false;
|
||||
_isBusy = true;
|
||||
}
|
||||
_semaphore.release();
|
||||
return temp;
|
||||
}
|
||||
|
||||
void MFThread::noBusy()
|
||||
{
|
||||
_semaphore.acquire();
|
||||
_isBusy = false;
|
||||
_semaphore.release();
|
||||
}
|
||||
|
||||
void MFThread::processImageMjpeg()
|
||||
{
|
||||
if (_decompress == nullptr)
|
||||
{
|
||||
_decompress = tjInitDecompress();
|
||||
_scalingFactors = tjGetScalingFactors (&_scalingFactorsCount);
|
||||
}
|
||||
|
||||
if (tjDecompressHeader2(_decompress, _localData, _size, &_width, &_height, &_subsamp) != 0)
|
||||
{
|
||||
if (tjGetErrorCode(_decompress) == TJERR_FATAL)
|
||||
return;
|
||||
}
|
||||
|
||||
int scaledWidth = _width, scaledHeight = _height;
|
||||
if(_scalingFactors != nullptr && _pixelDecimation > 1)
|
||||
{
|
||||
for (int i = 0; i < _scalingFactorsCount ; i++)
|
||||
{
|
||||
const int tempWidth = TJSCALED(_width, _scalingFactors[i]);
|
||||
const int tempHeight = TJSCALED(_height, _scalingFactors[i]);
|
||||
if (tempWidth <= _width/_pixelDecimation && tempHeight <= _height/_pixelDecimation)
|
||||
{
|
||||
scaledWidth = tempWidth;
|
||||
scaledHeight = tempHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (scaledWidth == _width && scaledHeight == _height)
|
||||
{
|
||||
scaledWidth = TJSCALED(_width, _scalingFactors[_scalingFactorsCount-1]);
|
||||
scaledHeight = TJSCALED(_height, _scalingFactors[_scalingFactorsCount-1]);
|
||||
}
|
||||
}
|
||||
|
||||
Image<ColorRgb> srcImage(scaledWidth, scaledHeight);
|
||||
|
||||
if (tjDecompress2(_decompress, _localData , _size, (unsigned char*)srcImage.memptr(), scaledWidth, 0, scaledHeight, TJPF_RGB, TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE) != 0)
|
||||
{
|
||||
if (tjGetErrorCode(_decompress) == TJERR_FATAL)
|
||||
return;
|
||||
}
|
||||
|
||||
// got image, process it
|
||||
if ( !(_cropLeft > 0 || _cropTop > 0 || _cropBottom > 0 || _cropRight > 0))
|
||||
emit newFrame(_workerIndex, srcImage, _currentFrame);
|
||||
else
|
||||
{
|
||||
// calculate the output size
|
||||
int outputWidth = (_width - _cropLeft - _cropRight);
|
||||
int outputHeight = (_height - _cropTop - _cropBottom);
|
||||
|
||||
if (outputWidth <= 0 || outputHeight <= 0)
|
||||
return;
|
||||
|
||||
Image<ColorRgb> destImage(outputWidth, outputHeight);
|
||||
|
||||
for (unsigned int y = 0; y < destImage.height(); y++)
|
||||
{
|
||||
unsigned char* source = (unsigned char*)srcImage.memptr() + (y + _cropTop)*srcImage.width()*3 + _cropLeft*3;
|
||||
unsigned char* dest = (unsigned char*)destImage.memptr() + y*destImage.width()*3;
|
||||
memcpy(dest, source, destImage.width()*3);
|
||||
}
|
||||
|
||||
// emit
|
||||
emit newFrame(_workerIndex, destImage, _currentFrame);
|
||||
}
|
||||
}
|
161
libsrc/grabber/mediafoundation/MFWrapper.cpp
Normal file
161
libsrc/grabber/mediafoundation/MFWrapper.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include <QMetaType>
|
||||
|
||||
#include <grabber/MFWrapper.h>
|
||||
|
||||
// qt
|
||||
#include <QTimer>
|
||||
|
||||
MFWrapper::MFWrapper(const QString &device, unsigned grabWidth, unsigned grabHeight, unsigned fps, unsigned input, int pixelDecimation )
|
||||
: GrabberWrapper("V4L2:"+device, &_grabber, grabWidth, grabHeight, 10)
|
||||
, _grabber(device, grabWidth, grabHeight, fps, input, pixelDecimation)
|
||||
{
|
||||
_ggrabber = &_grabber;
|
||||
|
||||
// register the image type
|
||||
qRegisterMetaType<Image<ColorRgb>>("Image<ColorRgb>");
|
||||
|
||||
// Handle the image in the captured thread using a direct connection
|
||||
connect(&_grabber, &MFGrabber::newFrame, this, &MFWrapper::newFrame, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
MFWrapper::~MFWrapper()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool MFWrapper::start()
|
||||
{
|
||||
return ( _grabber.start() && GrabberWrapper::start());
|
||||
}
|
||||
|
||||
void MFWrapper::stop()
|
||||
{
|
||||
_grabber.stop();
|
||||
GrabberWrapper::stop();
|
||||
}
|
||||
|
||||
void MFWrapper::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, int noSignalCounterThreshold)
|
||||
{
|
||||
_grabber.setSignalThreshold( redSignalThreshold, greenSignalThreshold, blueSignalThreshold, noSignalCounterThreshold);
|
||||
}
|
||||
|
||||
void MFWrapper::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom)
|
||||
{
|
||||
_grabber.setCropping(cropLeft, cropRight, cropTop, cropBottom);
|
||||
}
|
||||
|
||||
void MFWrapper::setSignalDetectionOffset(double verticalMin, double horizontalMin, double verticalMax, double horizontalMax)
|
||||
{
|
||||
_grabber.setSignalDetectionOffset(verticalMin, horizontalMin, verticalMax, horizontalMax);
|
||||
}
|
||||
|
||||
void MFWrapper::newFrame(const Image<ColorRgb> &image)
|
||||
{
|
||||
emit systemImage(_grabberName, image);
|
||||
}
|
||||
|
||||
void MFWrapper::action()
|
||||
{
|
||||
// dummy
|
||||
}
|
||||
|
||||
void MFWrapper::setSignalDetectionEnable(bool enable)
|
||||
{
|
||||
_grabber.setSignalDetectionEnable(enable);
|
||||
}
|
||||
|
||||
bool MFWrapper::getSignalDetectionEnable() const
|
||||
{
|
||||
return _grabber.getSignalDetectionEnabled();
|
||||
}
|
||||
|
||||
void MFWrapper::setCecDetectionEnable(bool enable)
|
||||
{
|
||||
_grabber.setCecDetectionEnable(enable);
|
||||
}
|
||||
|
||||
bool MFWrapper::getCecDetectionEnable() const
|
||||
{
|
||||
return _grabber.getCecDetectionEnabled();
|
||||
}
|
||||
|
||||
void MFWrapper::setDeviceVideoStandard(const QString& device, VideoStandard videoStandard)
|
||||
{
|
||||
_grabber.setDeviceVideoStandard(device, VideoStandard::NO_CHANGE);
|
||||
}
|
||||
|
||||
void MFWrapper::setFpsSoftwareDecimation(int decimation)
|
||||
{
|
||||
_grabber.setFpsSoftwareDecimation(decimation);
|
||||
}
|
||||
|
||||
void MFWrapper::setEncoding(QString enc)
|
||||
{
|
||||
_grabber.setEncoding(enc);
|
||||
}
|
||||
|
||||
void MFWrapper::setBrightnessContrastSaturationHue(int brightness, int contrast, int saturation, int hue)
|
||||
{
|
||||
_grabber.setBrightnessContrastSaturationHue(brightness, contrast, saturation, hue);
|
||||
}
|
||||
|
||||
void MFWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
|
||||
{
|
||||
if(type == settings::V4L2 && _grabberName.startsWith("V4L2"))
|
||||
{
|
||||
// extract settings
|
||||
const QJsonObject& obj = config.object();
|
||||
|
||||
// device name, video standard
|
||||
_grabber.setDeviceVideoStandard(
|
||||
obj["device"].toString("auto"),
|
||||
parseVideoStandard(obj["standard"].toString("no-change")));
|
||||
|
||||
// device input
|
||||
_grabber.setInput(obj["input"].toInt(-1));
|
||||
|
||||
// device resolution
|
||||
_grabber.setWidthHeight(obj["width"].toInt(0), obj["height"].toInt(0));
|
||||
|
||||
// device framerate
|
||||
_grabber.setFramerate(obj["fps"].toInt(15));
|
||||
|
||||
// image size decimation
|
||||
_grabber.setPixelDecimation(obj["sizeDecimation"].toInt(8));
|
||||
|
||||
// image cropping
|
||||
_grabber.setCropping(
|
||||
obj["cropLeft"].toInt(0),
|
||||
obj["cropRight"].toInt(0),
|
||||
obj["cropTop"].toInt(0),
|
||||
obj["cropBottom"].toInt(0));
|
||||
|
||||
// Brightness, Contrast, Saturation, Hue
|
||||
_grabber.setBrightnessContrastSaturationHue(obj["hardware_brightness"].toInt(0),
|
||||
obj["hardware_contrast"].toInt(0),
|
||||
obj["hardware_saturation"].toInt(0),
|
||||
obj["hardware_hue"].toInt(0));
|
||||
|
||||
// CEC Standby
|
||||
_grabber.setCecDetectionEnable(obj["cecDetection"].toBool(true));
|
||||
|
||||
// software frame skipping
|
||||
_grabber.setFpsSoftwareDecimation(obj["fpsSoftwareDecimation"].toInt(1));
|
||||
|
||||
// Signal detection
|
||||
_grabber.setSignalDetectionOffset(
|
||||
obj["sDHOffsetMin"].toDouble(0.25),
|
||||
obj["sDVOffsetMin"].toDouble(0.25),
|
||||
obj["sDHOffsetMax"].toDouble(0.75),
|
||||
obj["sDVOffsetMax"].toDouble(0.75));
|
||||
_grabber.setSignalThreshold(
|
||||
obj["redSignalThreshold"].toDouble(0.0)/100.0,
|
||||
obj["greenSignalThreshold"].toDouble(0.0)/100.0,
|
||||
obj["blueSignalThreshold"].toDouble(0.0)/100.0,
|
||||
obj["noSignalCounterThreshold"].toInt(50) );
|
||||
_grabber.setSignalDetectionEnable(obj["signalDetection"].toBool(true));
|
||||
|
||||
// Hardware encoding format
|
||||
_grabber.setEncoding(obj["encoding"].toString("NO_CHANGE"));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user