From ec7caf8b226ce4d561579da0b85f3e67b78fb70e Mon Sep 17 00:00:00 2001 From: Stefan Jahn Date: Sat, 18 Feb 2023 23:31:26 +0100 Subject: [PATCH] Added some more documentation. Larger previews, e.g. 1280x720 seem possible. --- videodev-vfw.cc | 309 +++++++++++++++++++++++++++++++----------------- videodev-vfw.h | 11 +- 2 files changed, 208 insertions(+), 112 deletions(-) diff --git a/videodev-vfw.cc b/videodev-vfw.cc index 5ff7665..de335a8 100644 --- a/videodev-vfw.cc +++ b/videodev-vfw.cc @@ -1,5 +1,6 @@ /* - * module to read video data from vfw. + * Module to grab video data from VfW interface. The interface is + * poorly documented for which some odd comments maybe found here. */ #include "config.h" @@ -15,26 +16,36 @@ #include "convert.h" #include "videodev-vfw.h" +static char classNameVfW[] = "VFW-Class"; + +/* + * Constructor + */ VideoDev_VFW::VideoDev_VFW() { - printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - hwnd = cap = NULL; - camid = -1; - inframe_pixfmt = 0x0; - inframe_w = -1; - inframe_h = -1; - inframe = NULL; - inframe_size = 0; - vfw_size = 0; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + hwnd = cap = NULL; + camid = -1; + inframe_pixfmt = 0x0; + inframe_w = -1; + inframe_h = -1; + inframe = NULL; + inframe_size = 0; + vfw_size = 0; + hclass = NULL; + hinst = NULL; }; - +/* + * Destructor + */ VideoDev_VFW::~VideoDev_VFW() { - printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - if (running > 0) CaptureStop(); + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (running > 0) + CaptureStop(); } /* - * check for connected devices and return the result in a list. + * Check for connected devices and returns the result in a list. */ int VideoDev_VFW::GetDeviceList(std::list *list) { @@ -43,9 +54,11 @@ int VideoDev_VFW::GetDeviceList(std::list *list) { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); if (list == NULL) return VDEV_STATUS_ERROR; - + + // go through a list of 10 (maximum number defined by VfW) for(int i=0; i < 10; i++) if(capGetDriverDescription(i, name, sizeof(name), desc, sizeof(desc))) { + // create a driver for the return list std::string device; device = "VFW " + to_string(i) + " " + (string)name + " [" + (string)desc + "]"; printf ("%s:%d %s Found device '%s'\n", __FILE__, __LINE__, __FUNCTION__, device.c_str()); @@ -56,57 +69,118 @@ int VideoDev_VFW::GetDeviceList(std::list *list) { } /* - * Open Device - * prepare the buffer + * Destroy and unregister window class. */ -int VideoDev_VFW::Open() { +int VideoDev_VFW::DestroyClass() { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (hclass) { + if(!UnregisterClass(classNameVfW, hinst)) { + printf ("%s:%d %s Could not unregister VFW class\n", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + free (hclass); + hclass = NULL; + } + return 1; +} + +/* + * Create and register window class. + */ +int VideoDev_VFW::CreateClass() { + + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (hclass) return 1; + + hclass = (WNDCLASSEX *)malloc(sizeof(WNDCLASSEX)); + hclass->hInstance = hinst; + hclass->lpszClassName = classNameVfW; + hclass->lpfnWndProc = DefWindowProc; + hclass->style = CS_DBLCLKS; + hclass->cbSize = sizeof(WNDCLASSEX); + hclass->hIcon = LoadIcon(NULL,IDI_APPLICATION); + hclass->hIconSm = LoadIcon(NULL,IDI_APPLICATION); + hclass->hCursor = LoadCursor(NULL,IDC_ARROW); + hclass->lpszMenuName = NULL; + hclass->cbClsExtra = 0; + hclass->cbWndExtra = 0; + hclass->hbrBackground = (HBRUSH)COLOR_BACKGROUND; + + if(!RegisterClassEx (hclass)) { + printf ("%s:%d %s Could not register VFW class\n", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + return 1; +} + +/* + * The callback is run when an error in the capture driver occurred. + */ +LRESULT VFW_error_callback (HWND h, int nID, LPCSTR lpsz) { + printf("%s:%d %s id=%d (%s)\n", __FILE__, __LINE__, __FUNCTION__, nID, lpsz); + return TRUE; +} - static int first = 1; +/* + * The callback is run when a status update in the capture driver occurred. + */ +LRESULT VFW_status_callback (HWND h, int nID, LPCSTR lpsz) { + printf("%s:%d %s id=%d (%s)\n", __FILE__, __LINE__, __FUNCTION__, nID, lpsz); + return TRUE; +} + +#define USE_PREVIEW 1 + +/* + * Opens the device. + */ +int VideoDev_VFW::Open() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + // extract camera id from device name camid = atoi (conf_device.c_str()); printf ("%s:%d %s Opening VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid); - // create parent window - //HWND main_hwnd = (HWND)GDK_WINDOW_HWND (gtk_widget_get_window (GTK_WIDGET(main_window))); - HINSTANCE hinst = GetModuleHandle(NULL); - char classNameVfW[] = "VFW-Class"; - - if (first) { - WNDCLASSEX hclass; - hclass.hInstance=hinst; - hclass.lpszClassName=classNameVfW; - hclass.lpfnWndProc=DefWindowProc; - hclass.style=CS_DBLCLKS; - hclass.cbSize=sizeof(WNDCLASSEX); - hclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); - hclass.hIconSm=LoadIcon(NULL,IDI_APPLICATION); - hclass.hCursor=LoadCursor(NULL,IDC_ARROW); - hclass.lpszMenuName=NULL; - hclass.cbClsExtra=0; - hclass.cbWndExtra=0; - hclass.hbrBackground=(HBRUSH)COLOR_BACKGROUND; - - if(!RegisterClassEx(&hclass)) { - printf ("%s:%d %s Could not register VFW class\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_ERROR; - } - first = 0; - } + // find application instance handle for later use + hinst = GetModuleHandle(NULL); + + // create and register window class + if (!CreateClass()) + return VDEV_STATUS_ERROR; + + // now create the parent window for capture devicey + // some notes: + // - the minimum possible size of 3x3 + // - the WS_POPUPWINDOW removes window decorations + // - it does not need a parent window + // - the window MUST be visible, otherwise video grabbing does not work (no callbacks run) hwnd = CreateWindowEx(0, classNameVfW, "CameraPreview", WS_POPUPWINDOW|WS_VISIBLE, 0, 0, 3, 3, NULL, NULL, hinst, NULL); - if (hwnd == NULL) { + if (!hwnd) { printf ("%s:%d %s Could not create window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError()); return VDEV_STATUS_ERROR; } - ShowWindow(hwnd, SW_SHOW); - - // connect to driver + if(!ShowWindow(hwnd, SW_SHOW)) { + printf ("%s:%d %s Could not show window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError()); + return VDEV_STATUS_ERROR; + } + + // create capture driver window handle, also here minim size is 1x1 cap = capCreateCaptureWindow("VFW", WS_CHILD|WS_VISIBLE, 0, 0, 1, 1, hwnd, camid); if(!cap) { printf ("%s:%d %s Could not open VFW id %d window\n", __FILE__, __LINE__, __FUNCTION__, camid); return VDEV_STATUS_ERROR; } + if(!capSetCallbackOnStatus(cap, VFW_status_callback)) { + printf ("%s:%d %s Could not set status callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError()); + return VDEV_STATUS_ERROR; + } + if(!capSetCallbackOnError(cap, VFW_error_callback)) { + printf ("%s:%d %s Could not set error callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError()); + return VDEV_STATUS_ERROR; + } + + // connect to driver if(!capDriverConnect(cap, camid)) { printf ("%s:%d %s Could not connect to VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid); return VDEV_STATUS_ERROR; @@ -115,9 +189,13 @@ int VideoDev_VFW::Open() { // let the callbacks know this class pointer capSetUserData(cap, this); + // set video source, capture format and size + capDlgVideoSource(cap); + capDlgVideoFormat(cap); + CAPTUREPARMS cp; if(!capCaptureGetSetup(cap, &cp, sizeof(cp))) { - printf ("%s:%d %s Could not get VFW setup\n", __FILE__, __LINE__, __FUNCTION__); + printf ("%s:%d %s Could not get VFW capture setup\n", __FILE__, __LINE__, __FUNCTION__); return VDEV_STATUS_ERROR; } cp.dwRequestMicroSecPerFrame = 33333; @@ -131,20 +209,10 @@ int VideoDev_VFW::Open() { cp.fAbortRightMouse = 0; cp.fLimitEnabled = 0; if(!capCaptureSetSetup(cap, &cp, sizeof(cp))) { - printf ("%s:%d %s Could not set VFW setup\n", __FILE__, __LINE__, __FUNCTION__); + printf ("%s:%d %s Could not set VFW capture setup\n", __FILE__, __LINE__, __FUNCTION__); return VDEV_STATUS_ERROR; } - capOverlay(cap, 0); - capPreviewRate(cap, 10); // rate in ms - capPreviewScale(cap, 0); - capPreview(cap, TRUE); - //capCaptureSequenceNoFile(cap); - - // set video source, capture format and size - capDlgVideoSource(cap); - capDlgVideoFormat(cap); - // get current valid width/height BITMAPINFO bi; int i = 0; @@ -158,14 +226,15 @@ int VideoDev_VFW::Open() { // try to set the configured width/height if(conf_width != -1) { - bi.bmiHeader.biWidth = conf_width; - bi.bmiHeader.biHeight = conf_height; + inframe_w = bi.bmiHeader.biWidth = conf_width; + inframe_h = bi.bmiHeader.biHeight = conf_height; bi.bmiHeader.biCompression = inframe_pixfmt; if(!capSetVideoFormat(cap, &bi, sizeof(BITMAPINFO))) { printf ("%s:%d %s Could not set capture size %dx%d (%s)\n", __FILE__, __LINE__, __FUNCTION__, conf_width, conf_height, conf_format.c_str()); return VDEV_STATUS_ERROR; } } + // find out maximum resolution else { int sizes[][2] = {{320, 240}, {640, 480}, {800, 600}, {1024, 768}, {1280, 720}, {1280, 1024}, {1920, 1080}, {-1, -1}}; @@ -175,18 +244,18 @@ int VideoDev_VFW::Open() { inframe_h = bi.bmiHeader.biHeight = sizes[i][1]; bi.bmiHeader.biCompression = inframe_pixfmt; if(capSetVideoFormat(cap, &bi, sizeof(BITMAPINFO))) { - printf ("%s:%d %s Resolution %dx%d (%s) works\n", __FILE__, __LINE__, __FUNCTION__, inframe_w, inframe_h, conf_format.c_str()); - if (inframe_w >= conf_width && inframe_h >= conf_height) { - conf_width = inframe_w; - conf_height = inframe_h; - } + printf ("%s:%d %s Resolution %dx%d (%s) works\n", __FILE__, __LINE__, __FUNCTION__, inframe_w, inframe_h, conf_format.c_str()); + if (inframe_w >= conf_width && inframe_h >= conf_height) { + conf_width = inframe_w; + conf_height = inframe_h; + } } i++; } } // translate this special format to what our convert functions can work with - if(inframe_pixfmt == V4L2_PIX_FMT_YUY2) { + if (inframe_pixfmt == V4L2_PIX_FMT_YUY2) { inframe_pixfmt = V4L2_PIX_FMT_YUYV; } @@ -195,8 +264,7 @@ int VideoDev_VFW::Open() { /* - * Close Device - * Free videobuffer + * Close the device. */ int VideoDev_VFW::Close() { @@ -207,6 +275,7 @@ int VideoDev_VFW::Close() { DestroyWindow(hwnd); hwnd = cap = NULL; } + DestroyClass(); return VDEV_STATUS_OK; }; @@ -216,22 +285,21 @@ int VideoDev_VFW::Close() { * VideoGrabbing */ -LRESULT CALLBACK VFW_frame_callback(HWND h, LPVIDEOHDR v) { +/* + * The callback is run when a new frame is available. It copies the + * available data into the framebuffer of the class instance. + */ +LRESULT CALLBACK VFW_frame_callback (HWND h, LPVIDEOHDR v) { VideoDev_VFW * obj = (VideoDev_VFW *)capGetUserData(h); obj->SetFrameBufferSize(v->dwBytesUsed); memcpy(obj->GetFrameBuffer(), v->lpData, obj->GetFrameBufferSize()); return TRUE; } -LRESULT VFW_error_callback(HWND h, int nID, LPCSTR lpsz) { - printf("%s:%d %s id=%d (%s)\n", __FILE__, __LINE__, __FUNCTION__, nID, lpsz); - return TRUE; -} - /* - * prepare inframe for raw picture data, will hold a video frame with 16bit per channel or BGR32/BGR24 - * inframe size = 4*W*H - * send the start capture signal to the cam + * Prepare inframe for raw picture data, will hold a video frame with + * maximum size of inframe size = 4*W*H. Setup capture + * callbacks. Then send the start capture signal to the camera. */ int VideoDev_VFW::CaptureStart() { @@ -240,41 +308,53 @@ int VideoDev_VFW::CaptureStart() { // allocate memory for frame data if (inframe != NULL) free (inframe); inframe_size = 4 * inframe_w * inframe_h; - inframe = (unsigned char*) malloc(inframe_size); + inframe = (unsigned char *) malloc(inframe_size); pixelformat = inframe_pixfmt; - if(!capSetCallbackOnError(cap, VFW_error_callback)) { - printf ("%s:%d %s Could not set error callback to VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid); + // disable overlay + capOverlay(cap, FALSE); + +#if USE_PREVIEW + // use preview window for grabbing + capPreviewRate (cap, 10); // rate in ms + capPreviewScale (cap, FALSE); + capPreview (cap, TRUE); +#else + // otherwise use other capture + capPreview (cap, FALSE); + if(!capCaptureSequenceNoFile (cap)) { + printf ("%s:%d %s Could not start capture (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError()); + } +#endif + + if(!capSetCallbackOnFrame(cap, VFW_frame_callback)) { + printf ("%s:%d %s Could not set frame callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError()); return VDEV_STATUS_ERROR; } if(!capSetCallbackOnVideoStream(cap, VFW_frame_callback)) { - printf ("%s:%d %s Could not set videostream callback to VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid); - return VDEV_STATUS_ERROR; + printf ("%s:%d %s Could not set videostream callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError()); + //return VDEV_STATUS_ERROR; } - + if(!capSetCallbackOnYield(cap, VFW_frame_callback)) { - printf ("%s:%d %s Cmould not set yield callback to VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid); - return VDEV_STATUS_ERROR; + printf ("%s:%d %s Could not set yield callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError()); + //return VDEV_STATUS_ERROR; } - - if(!capSetCallbackOnFrame(cap, VFW_frame_callback)) { - printf ("%s:%d %s Could not set frame callback to VFW id %d\n", __FILE__, __LINE__, __FUNCTION__, camid); - return VDEV_STATUS_ERROR; - } - + return VDEV_STATUS_OK; }; /* - * free inbuffer + * Stop capture and free framebuffer. */ int VideoDev_VFW::CaptureStop() { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + capCaptureAbort(cap); capSetCallbackOnError(cap, NULL); capSetCallbackOnFrame(cap, NULL); @@ -292,35 +372,47 @@ int VideoDev_VFW::CaptureStop() { }; /* - * try to grab one frame and convert it into RGB32. - * If something goes wrong return an error code. - * Return code VDEV_STATUS_AGAIN is not an error. There was no video image ready to read. + * This function needed to continue running the capture window. */ -// FIXME: SVBGetVideoData needs to be outside of Lock/UnLockMutex - using inside thread inbuffer -int VideoDev_VFW::Grab(VideoFrameRaw *vf) { +void VideoDev_VFW::HandleMessages() { + MSG msg; + while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} - MSG msg; - while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - if (inframe == NULL) return VDEV_STATUS_ERROR; +/* + * Try to grab one frame and copy it into raw video buffer. If + * something goes wrong return an error code. Return code + * VDEV_STATUS_AGAIN is not an error. There was no video image ready + * to read. + */ +int VideoDev_VFW::Grab(VideoFrameRaw *vf) { + + HandleMessages(); + if (inframe == NULL) return VDEV_STATUS_ERROR; if (GetFrameBufferSize() > 0) { LockMutex(); vf->CopyFrom(inframe_pixfmt, inframe_w, inframe_h, GetFrameBufferSize(), inframe); UnLockMutex(); } + else { + return VDEV_STATUS_AGAIN; + } return VDEV_STATUS_OK; } - +/* + * For VfW this seems difficult / impossible to obtain the supported + * video formats, but they must be selected via capDlgVideoFormat(). + */ int VideoDev_VFW::GetDeviceFormats(string device, std::list *formats) { printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - return VDEV_STATUS_OK; } @@ -334,7 +426,6 @@ int VideoDev_VFW::GetDeviceFormats(string device, std::list *formats) { * set video control identified by id */ int VideoDev_VFW::SetDevCtrl(unsigned int id, int value) { - return VDEV_STATUS_OK; }; diff --git a/videodev-vfw.h b/videodev-vfw.h index 0ec3908..63d8152 100644 --- a/videodev-vfw.h +++ b/videodev-vfw.h @@ -7,14 +7,16 @@ class VideoDev_VFW: public VideoDev { private: unsigned char *inframe; - int inframe_size; + int inframe_size; int inframe_w, inframe_h; int inframe_pixfmt; - int vfw_size; + int vfw_size; ConvertData cdata; int camid; - HWND cap, hwnd; + HWND cap, hwnd; + WNDCLASSEX * hclass; + HINSTANCE hinst; int Grab(VideoFrameRaw *vf); int Open(); @@ -23,6 +25,9 @@ private: int CaptureStop(); int SetDevCtrl(unsigned int id, int value); int GetDevCtrl(unsigned int id, int *value); + int CreateClass(); + int DestroyClass(); + void HandleMessages(); void print_error(int err); public: