You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SimpleSkyCam/videodev-vfw.cc

478 lines
14 KiB

/*
* Module to grab video data from VfW interface. The interface is
* poorly documented for which some odd comments maybe found here.
*/
#include "config.h"
#include <windows.h>
#include <windowsx.h>
#include <vfw.h>
#include <aviriff.h>
#include <list>
#include <string>
#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;
hclass = NULL;
hinst = NULL;
};
/*
* Destructor
*/
VideoDev_VFW::~VideoDev_VFW() {
printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
if (running > 0)
CaptureStop();
}
/*
* Check for connected devices and returns the result in a list.
*/
int VideoDev_VFW::GetDeviceList(std::list<std::string> *list) {
char name[256];
char desc[256];
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());
list->push_back(device);
}
return VDEV_STATUS_OK;
}
/*
* Destroy and unregister window class.
*/
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;
}
/*
* Print driver capabilities.
*/
void VideoDev_VFW::GetCapabilities() {
CAPDRIVERCAPS caps;
if(!capDriverGetCaps(cap, &caps, sizeof(caps))) {
printf ("%s:%d %s Unable to get driver capabilities\n", __FILE__, __LINE__, __FUNCTION__);
}
else {
printf ("%s:%d %s Id: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.wDeviceIndex);
printf ("%s:%d %s Overlay: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasOverlay);
printf ("%s:%d %s VideoSource Dialog: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasDlgVideoSource);
printf ("%s:%d %s VideoFormat Dialog: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasDlgVideoFormat);
printf ("%s:%d %s VideoDisplay Dialog: %d\n", __FILE__, __LINE__, __FUNCTION__, caps.fHasDlgVideoDisplay);
}
}
/*
* 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;
}
/*
* 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 0
/*
* 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);
// 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)
#if USE_PREVIEW
hwnd = CreateWindowEx(0, classNameVfW, "CameraPreview", WS_POPUPWINDOW|WS_VISIBLE, 0, 0, 3, 3, NULL, NULL, hinst, NULL);
#else
hwnd = CreateWindowEx(0, classNameVfW, "CameraPreview", WS_POPUPWINDOW|WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hinst, NULL);
#endif
if (!hwnd) {
printf ("%s:%d %s Could not create window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError());
return VDEV_STATUS_ERROR;
}
#if USE_PREVIEW
if(!ShowWindow(hwnd, SW_SHOW)) {
printf ("%s:%d %s Could not show window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError());
return VDEV_STATUS_ERROR;
}
#else
if(!ShowWindow(hwnd, SW_HIDE)) {
printf ("%s:%d %s Could not hide window (%ld)\n", __FILE__, __LINE__, __FUNCTION__, GetLastError());
return VDEV_STATUS_ERROR;
}
#endif
// create capture driver window handle, also here minim size is 1x1
#if USE_PREVIEW
cap = capCreateCaptureWindow("VFW", WS_CHILD|WS_VISIBLE, 0, 0, 1, 1, hwnd, camid);
#else
cap = capCreateCaptureWindow("VFW", WS_CHILD, 0, 0, 0, 0, hwnd, camid);
#endif
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;
}
// check capabilities
GetCapabilities();
// 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 capture setup\n", __FILE__, __LINE__, __FUNCTION__);
return VDEV_STATUS_ERROR;
}
cp.dwRequestMicroSecPerFrame = 10000; // rate in us
cp.fMakeUserHitOKToCapture = 0;
cp.wPercentDropForError = 10;
cp.fYield = TRUE;
cp.wNumVideoRequested = 1;
cp.fCaptureAudio = 0;
cp.vKeyAbort = 0;
cp.fAbortLeftMouse = 0;
cp.fAbortRightMouse = 0;
cp.fLimitEnabled = 0;
if(!capCaptureSetSetup(cap, &cp, sizeof(cp))) {
printf ("%s:%d %s Could not set VFW capture setup\n", __FILE__, __LINE__, __FUNCTION__);
return VDEV_STATUS_ERROR;
}
// get current valid width/height
BITMAPINFO bi;
int i = 0;
capGetVideoFormat(cap, &bi, sizeof(BITMAPINFO));
inframe_w = bi.bmiHeader.biWidth;
inframe_h = bi.bmiHeader.biHeight;
printf ("%s:%d %s Current capture size is %dx%d\n", __FILE__, __LINE__, __FUNCTION__, inframe_w, inframe_h);
conf_format = convert_from_pixelformat(bi.bmiHeader.biCompression);
inframe_pixfmt = bi.bmiHeader.biCompression;
printf ("%s:%d %s Current capture format is %s\n", __FILE__, __LINE__, __FUNCTION__, conf_format.c_str());
// try to set the configured width/height
if(conf_width != -1) {
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}};
while(sizes[i][0] != -1) {
inframe_w = bi.bmiHeader.biWidth = sizes[i][0];
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;
}
}
i++;
}
}
// translate this special format to what our convert functions can work with
if (inframe_pixfmt == V4L2_PIX_FMT_YUY2) {
inframe_pixfmt = V4L2_PIX_FMT_YUYV;
}
return VDEV_STATUS_OK;
};
/*
* Close the device.
*/
int VideoDev_VFW::Close() {
printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
if(cap) {
capDriverDisconnect(cap);
DestroyWindow(cap);
DestroyWindow(hwnd);
hwnd = cap = NULL;
}
DestroyClass();
return VDEV_STATUS_OK;
};
/*****************************************************************************************************
* VideoGrabbing
*/
/*
* 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);
if (obj->GetFrameBuffer()) {
memcpy(obj->GetFrameBuffer(), v->lpData, obj->GetFrameBufferSize());
}
return TRUE;
}
/*
* 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() {
printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
// allocate memory for frame data
if (inframe != NULL) free (inframe);
inframe_size = 4 * inframe_w * inframe_h;
inframe = (unsigned char *) malloc(inframe_size);
pixelformat = inframe_pixfmt;
#if USE_PREVIEW
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;
}
#else
if(!capSetCallbackOnVideoStream(cap, VFW_frame_callback)) {
printf ("%s:%d %s Could not set videostream callback to VFW id %d (%ld)\n", __FILE__, __LINE__, __FUNCTION__, camid, GetLastError());
return VDEV_STATUS_ERROR;
}
#endif
// 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
return VDEV_STATUS_OK;
};
/*
* Stop capture and free framebuffer.
*/
int VideoDev_VFW::CaptureStop() {
printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
capCaptureAbort(cap);
capSetCallbackOnFrame(cap, NULL);
capSetCallbackOnVideoStream(cap, NULL);
capSetCallbackOnStatus(cap, NULL);
capSetCallbackOnError(cap, NULL);
// free inframe memory
if (inframe) {
free (inframe);
inframe_size = 0;
inframe = NULL;
}
return VDEV_STATUS_OK;
};
/*
* This function needed to continue running the capture window.
*/
void VideoDev_VFW::HandleMessages() {
MSG msg;
while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/*
* 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) {
// Do not know exactly why, but needed to translate/dispatch window message
HandleMessages();
if (inframe == NULL) return VDEV_STATUS_ERROR;
if (GetFrameBufferSize() > 0) {
LockMutex();
vf->CopyFrom(inframe_pixfmt, inframe_w, inframe_h, GetFrameBufferSize(), inframe);
SetFrameBufferSize(0);
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<string> *formats) {
printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
return VDEV_STATUS_OK;
}
/*****************************************************************************************************
* Controls
*/
/*
* set video control identified by id
*/
int VideoDev_VFW::SetDevCtrl(unsigned int id, int value) {
return VDEV_STATUS_OK;
};
/*
* get video control identified by id
*/
int VideoDev_VFW::GetDevCtrl(unsigned int id, int *value) {
return VDEV_STATUS_OK;
};