Refactored internal helpers. Simplified structure. Updated comments. Updates #41
Signed-off-by: Maxim Kostin <v-maxkos@microsoft.com>
This commit is contained in:
		| @@ -54,9 +54,12 @@ CV_EXPORTS void winrt_startMessageLoop(void callback(Args...), Args... args); | |||||||
|  |  | ||||||
| /** @brief | /** @brief | ||||||
| @note | @note | ||||||
|     Sets the reporter method for the HighguiAssist singleton. Starts the main OpenCV as |     Starts (1) frame-grabbing loop and (2) message loop | ||||||
|     an async thread in WinRT. See VideoCapture for the example of callback implementation. |     1. Function passed as an argument must implement common OCV reading frames | ||||||
|     Here is how the class can be used: |        pattern (see cv::VideoCapture documentation) AND call cv::winrt_imgshow(). | ||||||
|  |     2. Message processing loop required to overcome WinRT container and type | ||||||
|  |        conversion restrictions. OCV provides default implementation | ||||||
|  |        Here is how the class can be used: | ||||||
| @code | @code | ||||||
|     void cvMain() |     void cvMain() | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -697,7 +697,7 @@ VideoCapture& VideoCapture::operator >> (Mat& image) | |||||||
|             bridge.bIsFrameNew = false; |             bridge.bIsFrameNew = false; | ||||||
|  |  | ||||||
|             // needed here because setting Mat 'image' is not allowed by OutputArray in read() |             // needed here because setting Mat 'image' is not allowed by OutputArray in read() | ||||||
|             Mat m(bridge.height, bridge.width, CV_8UC3, p); |             Mat m(bridge.getHeight(), bridge.getWidth(), CV_8UC3, p); | ||||||
|             image = m; |             image = m; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -227,7 +227,6 @@ HRESULT MediaStreamSink::IsMediaTypeSupported(__in IMFMediaType *mediaType, __de | |||||||
|     HRESULT hr = ExceptionBoundary([this, mediaType, closestMediaType, &supported]() |     HRESULT hr = ExceptionBoundary([this, mediaType, closestMediaType, &supported]() | ||||||
|     { |     { | ||||||
|         auto lock = _lock.LockExclusive(); |         auto lock = _lock.LockExclusive(); | ||||||
|         HRESULT hr = S_OK; |  | ||||||
|  |  | ||||||
|         if (closestMediaType != nullptr) |         if (closestMediaType != nullptr) | ||||||
|         { |         { | ||||||
| @@ -281,7 +280,6 @@ HRESULT MediaStreamSink::SetCurrentMediaType(__in IMFMediaType *mediaType) | |||||||
|     return ExceptionBoundary([this, mediaType]() |     return ExceptionBoundary([this, mediaType]() | ||||||
|     { |     { | ||||||
|         auto lock = _lock.LockExclusive(); |         auto lock = _lock.LockExclusive(); | ||||||
|         HRESULT hr = S_OK; |  | ||||||
|  |  | ||||||
|         CHKNULL(mediaType); |         CHKNULL(mediaType); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ using namespace ::std; | |||||||
| /***************************** VideoioBridge class ******************************/ | /***************************** VideoioBridge class ******************************/ | ||||||
|  |  | ||||||
| // non-blocking | // non-blocking | ||||||
| void VideoioBridge::requestForUIthreadAsync(int action, int widthp, int heightp) | void VideoioBridge::requestForUIthreadAsync(int action) | ||||||
| { | { | ||||||
|     reporter.report(action); |     reporter.report(action); | ||||||
| } | } | ||||||
| @@ -80,10 +80,79 @@ void VideoioBridge::allocateOutputBuffers() | |||||||
|     backOutputBuffer = ref new WriteableBitmap(width, height); |     backOutputBuffer = ref new WriteableBitmap(width, height); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // performed on UI thread | ||||||
|  | void VideoioBridge::allocateBuffers(int width, int height) | ||||||
|  | { | ||||||
|  |     // allocate input Mats (bgra8 = CV_8UC4, RGB24 = CV_8UC3) | ||||||
|  |     frontInputMat.create(height, width, CV_8UC3); | ||||||
|  |     backInputMat.create(height, width, CV_8UC3); | ||||||
|  |  | ||||||
|  |     frontInputPtr = frontInputMat.ptr(0); | ||||||
|  |     backInputPtr = backInputMat.ptr(0); | ||||||
|  |  | ||||||
|  |     allocateOutputBuffers(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // performed on UI thread | ||||||
|  | bool VideoioBridge::openCamera() | ||||||
|  | { | ||||||
|  |     // buffers must alloc'd on UI thread | ||||||
|  |     allocateBuffers(width, height); | ||||||
|  |  | ||||||
|  |     // nb. video capture device init must be done on UI thread; | ||||||
|  |     if (!Video::getInstance().isStarted()) | ||||||
|  |     { | ||||||
|  |         Video::getInstance().initGrabber(deviceIndex, width, height); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // nb on UI thread | ||||||
|  | void VideoioBridge::updateFrameContainer() | ||||||
|  | { | ||||||
|  |     // copy output Mat to WBM | ||||||
|  |     Video::getInstance().CopyOutput(); | ||||||
|  |  | ||||||
|  |     // set XAML image element with image WBM | ||||||
|  |     cvImage->Source = backOutputBuffer; | ||||||
|  | } | ||||||
|  |  | ||||||
| void VideoioBridge::imshow() | void VideoioBridge::imshow() | ||||||
| { | { | ||||||
|     VideoioBridge::getInstance().swapOutputBuffers(); |     swapOutputBuffers(); | ||||||
|     VideoioBridge::getInstance().requestForUIthreadAsync(cv::UPDATE_IMAGE_ELEMENT); |     requestForUIthreadAsync(cv::UPDATE_IMAGE_ELEMENT); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int VideoioBridge::getDeviceIndex() | ||||||
|  | { | ||||||
|  |     return deviceIndex; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoioBridge::setDeviceIndex(int index) | ||||||
|  | { | ||||||
|  |     deviceIndex = index; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int VideoioBridge::getWidth() | ||||||
|  | { | ||||||
|  |     return width; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int VideoioBridge::getHeight() | ||||||
|  | { | ||||||
|  |     return height; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoioBridge::setWidth(int _width) | ||||||
|  | { | ||||||
|  |     width = _width; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VideoioBridge::setHeight(int _height) | ||||||
|  | { | ||||||
|  |     height = _height; | ||||||
| } | } | ||||||
|  |  | ||||||
| // end | // end | ||||||
| @@ -50,19 +50,27 @@ public: | |||||||
|     static VideoioBridge& getInstance(); |     static VideoioBridge& getInstance(); | ||||||
|  |  | ||||||
|     // call after initialization |     // call after initialization | ||||||
|     void setReporter(Concurrency::progress_reporter<int> pr) { reporter = pr; } |     void    setReporter(Concurrency::progress_reporter<int> pr) { reporter = pr; } | ||||||
|  |  | ||||||
|     // to be called from cvMain via cap_winrt on bg thread - non-blocking (async) |     // to be called from cvMain via cap_winrt on bg thread - non-blocking (async) | ||||||
|     void requestForUIthreadAsync( int action, int width=0, int height=0 ); |     void    requestForUIthreadAsync(int action); | ||||||
|  |  | ||||||
|     // TODO: modify in window.cpp: void cv::imshow( const String& winname, InputArray _img ) |     // TODO: modify in window.cpp: void cv::imshow( const String& winname, InputArray _img ) | ||||||
|     void    imshow(/*cv::InputArray matToShow*/);   // shows Mat in the cvImage element |     void    imshow(/*cv::InputArray matToShow*/);   // shows Mat in the cvImage element | ||||||
|     void    swapInputBuffers(); |     void    swapInputBuffers(); | ||||||
|     void    allocateOutputBuffers(); |     void    allocateOutputBuffers(); | ||||||
|     void    swapOutputBuffers(); |     void    swapOutputBuffers(); | ||||||
|  |     void    updateFrameContainer(); | ||||||
|  |     bool    openCamera(); | ||||||
|  |     void    allocateBuffers(int width, int height); | ||||||
|  |  | ||||||
|  |     int     getDeviceIndex(); | ||||||
|  |     void    setDeviceIndex(int index); | ||||||
|  |     int     getWidth(); | ||||||
|  |     void    setWidth(int width); | ||||||
|  |     int     getHeight(); | ||||||
|  |     void    setHeight(int height); | ||||||
|  |  | ||||||
|     int                         deviceIndex, width, height; |  | ||||||
|     std::atomic<bool>           bIsFrameNew; |     std::atomic<bool>           bIsFrameNew; | ||||||
|     std::mutex                  inputBufferMutex;   // input is double buffered |     std::mutex                  inputBufferMutex;   // input is double buffered | ||||||
|     unsigned char *             frontInputPtr;      // OpenCV reads this |     unsigned char *             frontInputPtr;      // OpenCV reads this | ||||||
| @@ -93,4 +101,17 @@ private: | |||||||
|  |  | ||||||
|     std::atomic<bool>   deviceReady; |     std::atomic<bool>   deviceReady; | ||||||
|     Concurrency::progress_reporter<int> reporter; |     Concurrency::progress_reporter<int> reporter; | ||||||
|  |  | ||||||
|  |     // Mats are wrapped with singleton class, we do not support more than one | ||||||
|  |     // capture device simultaneously with the design at this time | ||||||
|  |     // | ||||||
|  |     // nb. inputBufferMutex was not able to guarantee that OpenCV Mats were | ||||||
|  |     // ready to accept data in the UI thread (memory access exceptions were thrown | ||||||
|  |     // even though buffer address was good). | ||||||
|  |     // Therefore allocation of Mats is also done on the UI thread before the video | ||||||
|  |     // device is initialized. | ||||||
|  |     cv::Mat frontInputMat; | ||||||
|  |     cv::Mat backInputMat; | ||||||
|  |  | ||||||
|  |     int deviceIndex, width, height; | ||||||
| }; | }; | ||||||
| @@ -43,23 +43,9 @@ using namespace Microsoft::WRL; | |||||||
|  |  | ||||||
| using namespace ::std; | using namespace ::std; | ||||||
|  |  | ||||||
|  |  | ||||||
| // nb. VideoCapture_WinRT is not a singleton, so the Mats are made file statics |  | ||||||
| // we do not support more than one capture device simultaneously with the |  | ||||||
| // design at this time |  | ||||||
|  |  | ||||||
| // nb. inputBufferMutex was not able to guarantee that OpenCV Mats were |  | ||||||
| // ready to accept data in the UI thread (memory access exceptions were thrown |  | ||||||
| // even though buffer address was good). |  | ||||||
| // Therefore allocation of Mats is also done on the UI thread before the video |  | ||||||
| // device is initialized. |  | ||||||
|  |  | ||||||
| static cv::Mat frontInputMat; |  | ||||||
| static cv::Mat backInputMat; |  | ||||||
|  |  | ||||||
| namespace cv { | namespace cv { | ||||||
|  |  | ||||||
|     /***************************** exported control functions ******************************/ |     /******************************* exported API functions **************************************/ | ||||||
|  |  | ||||||
|     template <typename ...Args> |     template <typename ...Args> | ||||||
|     void winrt_startMessageLoop(std::function<void(Args...)>&& callback, Args... args) |     void winrt_startMessageLoop(std::function<void(Args...)>&& callback, Args... args) | ||||||
| @@ -80,13 +66,13 @@ namespace cv { | |||||||
|             switch (action) |             switch (action) | ||||||
|             { |             { | ||||||
|             case OPEN_CAMERA: |             case OPEN_CAMERA: | ||||||
|                 winrt_openCamera(); |                 VideoioBridge::getInstance().openCamera(); | ||||||
|                 break; |                 break; | ||||||
|             case CLOSE_CAMERA: |             case CLOSE_CAMERA: | ||||||
|                 winrt_closeGrabber(); |                 Video::getInstance().closeGrabber(); | ||||||
|                 break; |                 break; | ||||||
|             case UPDATE_IMAGE_ELEMENT: |             case UPDATE_IMAGE_ELEMENT: | ||||||
|                 winrt_updateFrameContainer(); |                 VideoioBridge::getInstance().updateFrameContainer(); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| @@ -98,7 +84,8 @@ namespace cv { | |||||||
|         winrt_startMessageLoop(std::function<void(Args...)>(callback), args...); |         winrt_startMessageLoop(std::function<void(Args...)>(callback), args...); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void winrt_onVisibilityChanged(bool visible) { |     void winrt_onVisibilityChanged(bool visible) | ||||||
|  |     { | ||||||
|         if (visible) |         if (visible) | ||||||
|         { |         { | ||||||
|             VideoioBridge& bridge = VideoioBridge::getInstance(); |             VideoioBridge& bridge = VideoioBridge::getInstance(); | ||||||
| @@ -108,99 +95,34 @@ namespace cv { | |||||||
|             { |             { | ||||||
|                 if (Video::getInstance().isStarted()) return; |                 if (Video::getInstance().isStarted()) return; | ||||||
|  |  | ||||||
|                 int device = bridge.deviceIndex; |                 int device = bridge.getDeviceIndex(); | ||||||
|                 int width = bridge.width; |                 int width = bridge.getWidth(); | ||||||
|                 int height = bridge.height; |                 int height = bridge.getHeight(); | ||||||
|  |  | ||||||
|                 winrt_initGrabber(device, width, height); |                 Video::getInstance().initGrabber(device, width, height); | ||||||
|             } |             } | ||||||
|         } else |         } else | ||||||
|         { |         { | ||||||
|             //grabberStarted = false; |             //grabberStarted = false; | ||||||
|             winrt_closeGrabber(); |             Video::getInstance().closeGrabber(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void winrt_imshow() { |     void winrt_imshow() | ||||||
|  |     { | ||||||
|         VideoioBridge::getInstance().imshow(); |         VideoioBridge::getInstance().imshow(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void winrt_setFrameContainer(::Windows::UI::Xaml::Controls::Image^ image) { |     void winrt_setFrameContainer(::Windows::UI::Xaml::Controls::Image^ image) | ||||||
|  |     { | ||||||
|         VideoioBridge::getInstance().cvImage = image; |         VideoioBridge::getInstance().cvImage = image; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /********************************* Internal helpers ************************************/ |  | ||||||
|  |  | ||||||
|     void winrt_updateFrameContainer() |  | ||||||
|     { |  | ||||||
|         // copy output Mat to WBM |  | ||||||
|         winrt_copyOutput(); |  | ||||||
|  |  | ||||||
|         // set XAML image element with image WBM |  | ||||||
|         VideoioBridge::getInstance().cvImage->Source = VideoioBridge::getInstance().backOutputBuffer; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // performed on UI thread |  | ||||||
|     bool winrt_openCamera() |  | ||||||
|     { |  | ||||||
|         VideoioBridge& bridge = VideoioBridge::getInstance(); |  | ||||||
|  |  | ||||||
|         int device = bridge.deviceIndex; |  | ||||||
|         int width = bridge.width; |  | ||||||
|         int height = bridge.height; |  | ||||||
|  |  | ||||||
|         // buffers must alloc'd on UI thread |  | ||||||
|         winrt_allocateBuffers(width, height); |  | ||||||
|  |  | ||||||
|         // nb. video capture device init must be done on UI thread; |  | ||||||
|         if (!Video::getInstance().isStarted()) |  | ||||||
|         { |  | ||||||
|             winrt_initGrabber(device, width, height); |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // performed on UI thread |  | ||||||
|     void winrt_allocateBuffers(int width, int height) |  | ||||||
|     { |  | ||||||
|         VideoioBridge& bridge = VideoioBridge::getInstance(); |  | ||||||
|  |  | ||||||
|         // allocate input Mats (bgra8 = CV_8UC4, RGB24 = CV_8UC3) |  | ||||||
|         frontInputMat.create(height, width, CV_8UC3); |  | ||||||
|         backInputMat.create(height, width, CV_8UC3); |  | ||||||
|  |  | ||||||
|         bridge.frontInputPtr = frontInputMat.ptr(0); |  | ||||||
|         bridge.backInputPtr = backInputMat.ptr(0); |  | ||||||
|  |  | ||||||
|         bridge.allocateOutputBuffers(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // non-blocking |  | ||||||
|     bool winrt_initGrabber(int device, int w, int h) { |  | ||||||
|         // nb. Video class is not exported outside of this DLL |  | ||||||
|         // due to complexities in the CaptureFrameGrabber ref class |  | ||||||
|         // as written in the header not mixing well with pure C++ classes |  | ||||||
|         return Video::getInstance().initGrabber(device, w, h); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void winrt_closeGrabber() { |  | ||||||
|         Video::getInstance().closeGrabber(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // nb on UI thread |  | ||||||
|     void winrt_copyOutput() { |  | ||||||
|         Video::getInstance().CopyOutput(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     /********************************* VideoCapture_WinRT class ****************************/ |     /********************************* VideoCapture_WinRT class ****************************/ | ||||||
|  |  | ||||||
|     VideoCapture_WinRT::VideoCapture_WinRT(int device) : started(false) |     VideoCapture_WinRT::VideoCapture_WinRT(int device) : started(false) | ||||||
|     { |     { | ||||||
|         VideoioBridge::getInstance().deviceIndex = device; |         VideoioBridge::getInstance().setDeviceIndex(device); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool VideoCapture_WinRT::isOpened() const |     bool VideoCapture_WinRT::isOpened() const | ||||||
| @@ -240,14 +162,13 @@ namespace cv { | |||||||
|             if (width == 0) width = 640; |             if (width == 0) width = 640; | ||||||
|             if (height == 0) height = 480; |             if (height == 0) height = 480; | ||||||
|  |  | ||||||
|             VideoioBridge::getInstance().width = width; |             VideoioBridge::getInstance().setWidth(width); | ||||||
|             VideoioBridge::getInstance().height = height; |             VideoioBridge::getInstance().setHeight(height); | ||||||
|  |  | ||||||
|             // nb. Mats will be alloc'd on UI thread |             // nb. Mats will be alloc'd on UI thread | ||||||
|  |  | ||||||
|             // request device init on UI thread - this does not block, and is async |             // request device init on UI thread - this does not block, and is async | ||||||
|             VideoioBridge::getInstance().requestForUIthreadAsync(OPEN_CAMERA, |             VideoioBridge::getInstance().requestForUIthreadAsync(OPEN_CAMERA); | ||||||
|                 outArray.size().width, outArray.size().height); |  | ||||||
|  |  | ||||||
|             started = true; |             started = true; | ||||||
|             return false; |             return false; | ||||||
|   | |||||||
| @@ -42,17 +42,6 @@ | |||||||
|  |  | ||||||
| namespace cv { | namespace cv { | ||||||
|  |  | ||||||
|     /******************* Internal helpers **************************************/ |  | ||||||
|  |  | ||||||
|     void winrt_updateFrameContainer(); |  | ||||||
|     bool winrt_openCamera(); |  | ||||||
|     bool winrt_initGrabber(int device, int w, int h); |  | ||||||
|     void winrt_closeGrabber(); |  | ||||||
|     void winrt_copyOutput(); |  | ||||||
|     void winrt_allocateBuffers(int width, int height); |  | ||||||
|  |  | ||||||
|     /******************* VideoCapture_WinRT class ******************************/ |  | ||||||
|  |  | ||||||
|     class VideoCapture_WinRT : public IVideoCapture |     class VideoCapture_WinRT : public IVideoCapture | ||||||
|     { |     { | ||||||
|     public: |     public: | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ void Video::closeGrabber() { | |||||||
|     bGrabberInitInProgress = false; |     bGrabberInitInProgress = false; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // non-blocking | ||||||
| bool Video::initGrabber(int device, int w, int h) { | bool Video::initGrabber(int device, int w, int h) { | ||||||
|     // already started? |     // already started? | ||||||
|     if (bGrabberInited || bGrabberInitInProgress) return false; |     if (bGrabberInited || bGrabberInitInProgress) return false; | ||||||
| @@ -124,7 +124,7 @@ bool Video::initGrabber(int device, int w, int h) { | |||||||
|             // for 24 bpp |             // for 24 bpp | ||||||
|             props->Subtype = MediaEncodingSubtypes::Rgb24;      bytesPerPixel = 3; |             props->Subtype = MediaEncodingSubtypes::Rgb24;      bytesPerPixel = 3; | ||||||
|  |  | ||||||
|             // format used by XAML & WBM (for testing) |             // XAML & WBM use BGRA8, so it would look like | ||||||
|             // props->Subtype = MediaEncodingSubtypes::Bgra8;   bytesPerPixel = 4; |             // props->Subtype = MediaEncodingSubtypes::Bgra8;   bytesPerPixel = 4; | ||||||
|  |  | ||||||
|             props->Width = width; |             props->Width = width; | ||||||
| @@ -282,22 +282,20 @@ bool Video::listDevicesTask() { | |||||||
|  |  | ||||||
|     auto settings = ref new MediaCaptureInitializationSettings(); |     auto settings = ref new MediaCaptureInitializationSettings(); | ||||||
|  |  | ||||||
|     //vector <int> devices; |  | ||||||
|  |  | ||||||
|     create_task(DeviceInformation::FindAllAsync(DeviceClass::VideoCapture)) |     create_task(DeviceInformation::FindAllAsync(DeviceClass::VideoCapture)) | ||||||
|         .then([this, &ready](task<DeviceInformationCollection^> findTask) |         .then([this, &ready](task<DeviceInformationCollection^> findTask) | ||||||
|     { |     { | ||||||
|         m_devices = findTask.get(); |         m_devices = findTask.get(); | ||||||
|  |  | ||||||
|         for (size_t i = 0; i < m_devices->Size; i++) |         // TODO: collect device data | ||||||
|         { |         // for (size_t i = 0; i < m_devices->Size; i++) | ||||||
|             // ofVideoDevice deviceInfo; |         // { | ||||||
|             auto d = m_devices->GetAt(i); |         //   .. deviceInfo; | ||||||
|             //deviceInfo.bAvailable = true; |         //   auto d = m_devices->GetAt(i); | ||||||
|             //deviceInfo.deviceName = PlatformStringToString(d->Name); |         //   deviceInfo.bAvailable = true; | ||||||
|             //deviceInfo.hardwareName = deviceInfo.deviceName; |         //   deviceInfo.deviceName = PlatformStringToString(d->Name); | ||||||
|             // devices.push_back(deviceInfo); |         //   deviceInfo.hardwareName = deviceInfo.deviceName; | ||||||
|         } |         // } | ||||||
|  |  | ||||||
|         ready = true; |         ready = true; | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ | |||||||
| class Video { | class Video { | ||||||
| public: | public: | ||||||
|  |  | ||||||
|  |     // non-blocking | ||||||
|     bool initGrabber(int device, int w, int h); |     bool initGrabber(int device, int w, int h); | ||||||
|     void closeGrabber(); |     void closeGrabber(); | ||||||
|     bool isStarted(); |     bool isStarted(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Maxim Kostin
					Maxim Kostin