blob: 5479e23790f4839dca815f8713f9dc3127e17247 [file] [log] [blame]
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Contains code capturing video frames from a camera device on Windows.
19 * This code uses capXxx API, available via capCreateCaptureWindow.
20 */
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -070021
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -070022#include <vfw.h>
23#include "android/camera/camera-capture.h"
24#include "android/camera/camera-format-converters.h"
25
Vladimir Chtchetkinec68dbbe2011-09-23 07:58:34 -070026#define E(...) derror(__VA_ARGS__)
27#define W(...) dwarning(__VA_ARGS__)
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -070028#define D(...) VERBOSE_PRINT(camera,__VA_ARGS__)
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -070029#define D_ACTIVE VERBOSE_CHECK(camera)
30
31/* the T(...) macro is used to dump traffic */
32#define T_ACTIVE 0
33
34#if T_ACTIVE
35#define T(...) VERBOSE_PRINT(camera,__VA_ARGS__)
36#else
37#define T(...) ((void)0)
38#endif
39
40/* Default name for the capture window. */
41static const char* _default_window_name = "AndroidEmulatorVC";
42
43typedef struct WndCameraDevice WndCameraDevice;
44/* Windows-specific camera device descriptor. */
45struct WndCameraDevice {
46 /* Common camera device descriptor. */
47 CameraDevice header;
48 /* Capture window name. (default is AndroidEmulatorVC) */
49 char* window_name;
50 /* Input channel (video driver index). (default is 0) */
51 int input_channel;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -070052
53 /*
54 * Set when framework gets initialized.
55 */
56
57 /* Video capturing window. Null indicates that device is not connected. */
58 HWND cap_window;
59 /* DC for frame bitmap manipulation. Null indicates that frames are not
60 * being capturing. */
61 HDC dc;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -070062 /* Bitmap info for the frames obtained from the video capture driver. */
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -070063 BITMAPINFO* frame_bitmap;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -070064 /* Bitmap info to use for GetDIBits calls. We can't really use bitmap info
65 * obtained from the video capture driver, because of the two issues. First,
66 * the driver may return an incompatible 'biCompresstion' value. For instance,
67 * sometimes it returns a "fourcc' pixel format value instead of BI_XXX,
68 * which causes GetDIBits to fail. Second, the bitmap that represents a frame
69 * that has been actually obtained from the device is not necessarily matches
70 * bitmap info that capture driver has returned. Sometimes the captured bitmap
71 * is a 32-bit RGB, while bit count reported by the driver is 16. So, to
72 * address these issues we need to have another bitmap info, that can be used
73 * in GetDIBits calls. */
74 BITMAPINFO* gdi_bitmap;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -070075 /* Framebuffer large enough to fit the frame. */
76 uint8_t* framebuffer;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -070077 /* Framebuffer size. */
78 size_t framebuffer_size;
79 /* Framebuffer's pixel format. */
80 uint32_t pixel_format;
81 /* If != 0, frame bitmap is "top-down". If 0, frame bitmap is "bottom-up". */
82 int is_top_down;
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -080083 /* Flags whether frame should be captured using clipboard (1), or via frame
84 * callback (0) */
85 int use_clipboard;
86 /* Contains last frame captured via frame callback. */
87 void* last_frame;
88 /* Byte size of the 'last_frame' buffer. */
89 uint32_t last_frame_size;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -070090};
91
92/*******************************************************************************
93 * CameraDevice routines
94 ******************************************************************************/
95
96/* Allocates an instance of WndCameraDevice structure.
97 * Return:
98 * Allocated instance of WndCameraDevice structure. Note that this routine
99 * also sets 'opaque' field in the 'header' structure to point back to the
100 * containing WndCameraDevice instance.
101 */
102static WndCameraDevice*
103_camera_device_alloc(void)
104{
105 WndCameraDevice* cd = (WndCameraDevice*)malloc(sizeof(WndCameraDevice));
106 if (cd != NULL) {
107 memset(cd, 0, sizeof(WndCameraDevice));
108 cd->header.opaque = cd;
109 } else {
110 E("%s: Unable to allocate WndCameraDevice instance", __FUNCTION__);
111 }
112 return cd;
113}
114
115/* Uninitializes and frees WndCameraDevice descriptor.
116 * Note that upon return from this routine memory allocated for the descriptor
117 * will be freed.
118 */
119static void
120_camera_device_free(WndCameraDevice* cd)
121{
122 if (cd != NULL) {
123 if (cd->cap_window != NULL) {
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700124 /* Disconnect from the driver. */
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700125 capDriverDisconnect(cd->cap_window);
126
127 if (cd->dc != NULL) {
128 W("%s: Frames should not be capturing at this point",
129 __FUNCTION__);
130 ReleaseDC(cd->cap_window, cd->dc);
131 cd->dc = NULL;
132 }
133 /* Destroy the capturing window. */
134 DestroyWindow(cd->cap_window);
135 cd->cap_window = NULL;
136 }
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700137 if (cd->gdi_bitmap != NULL) {
138 free(cd->gdi_bitmap);
139 }
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700140 if (cd->frame_bitmap != NULL) {
141 free(cd->frame_bitmap);
142 }
143 if (cd->window_name != NULL) {
144 free(cd->window_name);
145 }
146 if (cd->framebuffer != NULL) {
147 free(cd->framebuffer);
148 }
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800149 if (cd->last_frame != NULL) {
150 free(cd->last_frame);
151 }
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700152 AFREE(cd);
153 } else {
154 W("%s: No descriptor", __FUNCTION__);
155 }
156}
157
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700158/* Resets camera device after capturing.
159 * Since new capture request may require different frame dimensions we must
160 * reset frame info cached in the capture window. The only way to do that would
161 * be closing, and reopening it again. */
162static void
163_camera_device_reset(WndCameraDevice* cd)
164{
165 if (cd != NULL && cd->cap_window != NULL) {
166 capDriverDisconnect(cd->cap_window);
167 if (cd->dc != NULL) {
168 ReleaseDC(cd->cap_window, cd->dc);
169 cd->dc = NULL;
170 }
171 if (cd->gdi_bitmap != NULL) {
172 free(cd->gdi_bitmap);
173 cd->gdi_bitmap = NULL;
174 }
175 if (cd->frame_bitmap != NULL) {
176 free(cd->frame_bitmap);
177 cd->frame_bitmap = NULL;
178 }
179 if (cd->framebuffer != NULL) {
180 free(cd->framebuffer);
181 cd->framebuffer = NULL;
182 }
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800183 if (cd->last_frame != NULL) {
184 free(cd->last_frame);
185 cd->last_frame = NULL;
186 }
187 cd->last_frame_size = 0;
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700188
189 /* Recreate the capturing window. */
190 DestroyWindow(cd->cap_window);
191 cd->cap_window = capCreateCaptureWindow(cd->window_name, WS_CHILD, 0, 0,
192 0, 0, HWND_MESSAGE, 1);
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800193 if (cd->cap_window != NULL) {
194 /* Save capture window descriptor as window's user data. */
195 capSetUserData(cd->cap_window, cd);
196 }
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700197 }
198}
199
200/* Gets an absolute value out of a signed integer. */
201static __inline__ int
202_abs(int val)
203{
204 return (val < 0) ? -val : val;
205}
206
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800207/* Callback that is invoked when a frame gets captured in capGrabFrameNoStop */
208static LRESULT CALLBACK
209_on_captured_frame(HWND hwnd, LPVIDEOHDR hdr)
210{
211 /* Capture window descriptor is saved in window's user data. */
212 WndCameraDevice* wcd = (WndCameraDevice*)capGetUserData(hwnd);
213
214 /* Reallocate frame buffer (if needed) */
215 if (wcd->last_frame_size < hdr->dwBytesUsed) {
216 wcd->last_frame_size = hdr->dwBytesUsed;
217 if (wcd->last_frame != NULL) {
218 free(wcd->last_frame);
219 }
220 wcd->last_frame = malloc(wcd->last_frame_size);
221 }
222
223 /* Copy captured frame. */
224 memcpy(wcd->last_frame, hdr->lpData, hdr->dwBytesUsed);
225
226 return (LRESULT)0;
227}
228
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700229/*******************************************************************************
230 * CameraDevice API
231 ******************************************************************************/
232
233CameraDevice*
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700234camera_device_open(const char* name, int inp_channel)
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700235{
236 WndCameraDevice* wcd;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700237
238 /* Allocate descriptor and initialize windows-specific fields. */
239 wcd = _camera_device_alloc();
240 if (wcd == NULL) {
241 E("%s: Unable to allocate WndCameraDevice instance", __FUNCTION__);
242 return NULL;
243 }
244 wcd->window_name = (name != NULL) ? ASTRDUP(name) :
245 ASTRDUP(_default_window_name);
246 if (wcd->window_name == NULL) {
247 E("%s: Unable to save window name", __FUNCTION__);
248 _camera_device_free(wcd);
249 return NULL;
250 }
251 wcd->input_channel = inp_channel;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700252
253 /* Create capture window that is a child of HWND_MESSAGE window.
254 * We make it invisible, so it doesn't mess with the UI. Also
255 * note that we supply standard HWND_MESSAGE window handle as
256 * the parent window, since we don't want video capturing
257 * machinery to be dependent on the details of our UI. */
258 wcd->cap_window = capCreateCaptureWindow(wcd->window_name, WS_CHILD, 0, 0,
259 0, 0, HWND_MESSAGE, 1);
260 if (wcd->cap_window == NULL) {
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700261 E("%s: Unable to create video capturing window '%s': %d",
262 __FUNCTION__, wcd->window_name, GetLastError());
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700263 _camera_device_free(wcd);
264 return NULL;
265 }
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800266 /* Save capture window descriptor as window's user data. */
267 capSetUserData(wcd->cap_window, wcd);
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700268
269 return &wcd->header;
270}
271
272int
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700273camera_device_start_capturing(CameraDevice* cd,
274 uint32_t pixel_format,
275 int frame_width,
276 int frame_height)
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700277{
278 WndCameraDevice* wcd;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700279 HBITMAP bm_handle;
280 BITMAP bitmap;
281 size_t format_info_size;
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800282 CAPTUREPARMS cap_param;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700283
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700284 if (cd == NULL || cd->opaque == NULL) {
285 E("%s: Invalid camera device descriptor", __FUNCTION__);
286 return -1;
287 }
288 wcd = (WndCameraDevice*)cd->opaque;
289
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700290 /* wcd->dc is an indicator of capturing: !NULL - capturing, NULL - not */
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700291 if (wcd->dc != NULL) {
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700292 W("%s: Capturing is already on on device '%s'",
293 __FUNCTION__, wcd->window_name);
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700294 return 0;
295 }
296
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700297 /* Connect capture window to the video capture driver. */
298 if (!capDriverConnect(wcd->cap_window, wcd->input_channel)) {
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700299 return -1;
300 }
301
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700302 /* Get current frame information from the driver. */
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700303 format_info_size = capGetVideoFormatSize(wcd->cap_window);
304 if (format_info_size == 0) {
305 E("%s: Unable to get video format size: %d",
306 __FUNCTION__, GetLastError());
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700307 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700308 return -1;
309 }
310 wcd->frame_bitmap = (BITMAPINFO*)malloc(format_info_size);
311 if (wcd->frame_bitmap == NULL) {
312 E("%s: Unable to allocate frame bitmap info buffer", __FUNCTION__);
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700313 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700314 return -1;
315 }
316 if (!capGetVideoFormat(wcd->cap_window, wcd->frame_bitmap,
317 format_info_size)) {
318 E("%s: Unable to obtain video format: %d", __FUNCTION__, GetLastError());
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700319 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700320 return -1;
321 }
322
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700323 /* Lets see if we need to set different frame dimensions */
324 if (wcd->frame_bitmap->bmiHeader.biWidth != frame_width ||
325 abs(wcd->frame_bitmap->bmiHeader.biHeight) != frame_height) {
326 /* Dimensions don't match. Set new frame info. */
327 wcd->frame_bitmap->bmiHeader.biWidth = frame_width;
328 wcd->frame_bitmap->bmiHeader.biHeight = frame_height;
329 /* We need to recalculate image size, since the capture window / driver
330 * will use image size provided by us. */
331 if (wcd->frame_bitmap->bmiHeader.biBitCount == 24) {
332 /* Special case that may require WORD boundary alignment. */
333 uint32_t bpl = (frame_width * 3 + 1) & ~1;
334 wcd->frame_bitmap->bmiHeader.biSizeImage = bpl * frame_height;
335 } else {
336 wcd->frame_bitmap->bmiHeader.biSizeImage =
337 (frame_width * frame_height * wcd->frame_bitmap->bmiHeader.biBitCount) / 8;
338 }
339 if (!capSetVideoFormat(wcd->cap_window, wcd->frame_bitmap,
340 format_info_size)) {
341 E("%s: Unable to set video format: %d", __FUNCTION__, GetLastError());
342 _camera_device_reset(wcd);
343 return -1;
344 }
345 }
346
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700347 if (wcd->frame_bitmap->bmiHeader.biCompression > BI_PNG) {
348 D("%s: Video capturing driver has reported pixel format %.4s",
349 __FUNCTION__, (const char*)&wcd->frame_bitmap->bmiHeader.biCompression);
350 }
351
352 /* Most of the time frame bitmaps come in "bottom-up" form, where its origin
353 * is the lower-left corner. However, it could be in the normal "top-down"
354 * form with the origin in the upper-left corner. So, we must adjust the
355 * biHeight field, since the way "top-down" form is reported here is by
356 * setting biHeight to a negative value. */
357 if (wcd->frame_bitmap->bmiHeader.biHeight < 0) {
358 wcd->frame_bitmap->bmiHeader.biHeight =
359 -wcd->frame_bitmap->bmiHeader.biHeight;
360 wcd->is_top_down = 1;
361 } else {
362 wcd->is_top_down = 0;
363 }
364
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700365 /* Get DC for the capturing window that will be used when we deal with
366 * bitmaps obtained from the camera device during frame capturing. */
367 wcd->dc = GetDC(wcd->cap_window);
368 if (wcd->dc == NULL) {
369 E("%s: Unable to obtain DC for %s: %d",
370 __FUNCTION__, wcd->window_name, GetLastError());
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700371 _camera_device_reset(wcd);
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700372 return -1;
373 }
374
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800375 /* Setup some capture parameters. */
376 if (capCaptureGetSetup(wcd->cap_window, &cap_param, sizeof(cap_param))) {
377 /* Use separate thread to capture video stream. */
378 cap_param.fYield = TRUE;
379 /* Don't show any dialogs. */
380 cap_param.fMakeUserHitOKToCapture = FALSE;
381 capCaptureSetSetup(wcd->cap_window, &cap_param, sizeof(cap_param));
382 }
383
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700384 /*
385 * At this point we need to grab a frame to properly setup framebuffer, and
386 * calculate pixel format. The problem is that bitmap information obtained
387 * from the driver doesn't necessarily match the actual bitmap we're going to
388 * obtain via capGrabFrame / capEditCopy / GetClipboardData
389 */
390
391 /* Grab a frame, and post it to the clipboard. Not very effective, but this
392 * is how capXxx API is operating. */
393 if (!capGrabFrameNoStop(wcd->cap_window) ||
394 !capEditCopy(wcd->cap_window) ||
395 !OpenClipboard(wcd->cap_window)) {
396 E("%s: Device '%s' is unable to save frame to the clipboard: %d",
397 __FUNCTION__, wcd->window_name, GetLastError());
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700398 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700399 return -1;
400 }
401
402 /* Get bitmap handle saved into clipboard. Note that bitmap is still
403 * owned by the clipboard here! */
404 bm_handle = (HBITMAP)GetClipboardData(CF_BITMAP);
405 if (bm_handle == NULL) {
406 E("%s: Device '%s' is unable to obtain frame from the clipboard: %d",
407 __FUNCTION__, wcd->window_name, GetLastError());
408 CloseClipboard();
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700409 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700410 return -1;
411 }
412
413 /* Get bitmap object that is initialized with the actual bitmap info. */
414 if (!GetObject(bm_handle, sizeof(BITMAP), &bitmap)) {
415 E("%s: Device '%s' is unable to obtain frame's bitmap: %d",
416 __FUNCTION__, wcd->window_name, GetLastError());
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800417 EmptyClipboard();
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700418 CloseClipboard();
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700419 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700420 return -1;
421 }
422
423 /* Now that we have all we need in 'bitmap' */
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800424 EmptyClipboard();
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700425 CloseClipboard();
426
427 /* Make sure that dimensions match. Othewise - fail. */
428 if (wcd->frame_bitmap->bmiHeader.biWidth != bitmap.bmWidth ||
429 wcd->frame_bitmap->bmiHeader.biHeight != bitmap.bmHeight ) {
430 E("%s: Requested dimensions %dx%d do not match the actual %dx%d",
431 __FUNCTION__, frame_width, frame_height,
432 wcd->frame_bitmap->bmiHeader.biWidth,
433 wcd->frame_bitmap->bmiHeader.biHeight);
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700434 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700435 return -1;
436 }
437
438 /* Create bitmap info that will be used with GetDIBits. */
439 wcd->gdi_bitmap = (BITMAPINFO*)malloc(wcd->frame_bitmap->bmiHeader.biSize);
440 if (wcd->gdi_bitmap == NULL) {
441 E("%s: Unable to allocate gdi bitmap info", __FUNCTION__);
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700442 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700443 return -1;
444 }
445 memcpy(wcd->gdi_bitmap, wcd->frame_bitmap,
446 wcd->frame_bitmap->bmiHeader.biSize);
447 wcd->gdi_bitmap->bmiHeader.biCompression = BI_RGB;
448 wcd->gdi_bitmap->bmiHeader.biBitCount = bitmap.bmBitsPixel;
449 wcd->gdi_bitmap->bmiHeader.biSizeImage = bitmap.bmWidthBytes * bitmap.bmWidth;
450 /* Adjust GDI's bitmap biHeight for proper frame direction ("top-down", or
451 * "bottom-up") We do this trick in order to simplify pixel format conversion
452 * routines, where we always assume "top-down" frames. The trick he is to
453 * have negative biHeight in 'gdi_bitmap' if driver provides "bottom-up"
454 * frames, and positive biHeight in 'gdi_bitmap' if driver provides "top-down"
455 * frames. This way GetGDIBits will always return "top-down" frames. */
456 if (wcd->is_top_down) {
457 wcd->gdi_bitmap->bmiHeader.biHeight =
458 wcd->frame_bitmap->bmiHeader.biHeight;
459 } else {
460 wcd->gdi_bitmap->bmiHeader.biHeight =
461 -wcd->frame_bitmap->bmiHeader.biHeight;
462 }
463
464 /* Allocate framebuffer. */
465 wcd->framebuffer = (uint8_t*)malloc(wcd->gdi_bitmap->bmiHeader.biSizeImage);
466 if (wcd->framebuffer == NULL) {
467 E("%s: Unable to allocate %d bytes for framebuffer",
468 __FUNCTION__, wcd->gdi_bitmap->bmiHeader.biSizeImage);
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700469 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700470 return -1;
471 }
472
473 /* Lets see what pixel format we will use. */
474 if (wcd->gdi_bitmap->bmiHeader.biBitCount == 16) {
475 wcd->pixel_format = V4L2_PIX_FMT_RGB565;
476 } else if (wcd->gdi_bitmap->bmiHeader.biBitCount == 24) {
Vladimir Chtchetkine6ef999d2011-09-21 16:22:40 -0700477 wcd->pixel_format = V4L2_PIX_FMT_BGR24;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700478 } else if (wcd->gdi_bitmap->bmiHeader.biBitCount == 32) {
Vladimir Chtchetkine6ef999d2011-09-21 16:22:40 -0700479 wcd->pixel_format = V4L2_PIX_FMT_BGR32;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700480 } else {
481 E("%s: Unsupported number of bits per pixel %d",
482 __FUNCTION__, wcd->gdi_bitmap->bmiHeader.biBitCount);
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700483 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700484 return -1;
485 }
486
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700487 D("%s: Capturing device '%s': %d bits per pixel in %.4s [%dx%d] frame",
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700488 __FUNCTION__, wcd->window_name, wcd->gdi_bitmap->bmiHeader.biBitCount,
489 (const char*)&wcd->pixel_format, wcd->frame_bitmap->bmiHeader.biWidth,
490 wcd->frame_bitmap->bmiHeader.biHeight);
491
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800492 /* Try to setup capture frame callback. */
493 wcd->use_clipboard = 1;
494 if (capSetCallbackOnFrame(wcd->cap_window, _on_captured_frame)) {
495 /* Callback is set. Don't use clipboard when capturing frames. */
496 wcd->use_clipboard = 0;
497 }
498
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700499 return 0;
500}
501
502int
503camera_device_stop_capturing(CameraDevice* cd)
504{
505 WndCameraDevice* wcd;
506 if (cd == NULL || cd->opaque == NULL) {
507 E("%s: Invalid camera device descriptor", __FUNCTION__);
508 return -1;
509 }
510 wcd = (WndCameraDevice*)cd->opaque;
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700511
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800512 /* Disable frame callback. */
513 capSetCallbackOnFrame(wcd->cap_window, NULL);
514
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700515 /* wcd->dc is the indicator of capture. */
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700516 if (wcd->dc == NULL) {
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700517 W("%s: Device '%s' is not capturing video",
518 __FUNCTION__, wcd->window_name);
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700519 return 0;
520 }
521 ReleaseDC(wcd->cap_window, wcd->dc);
522 wcd->dc = NULL;
523
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700524 /* Reset the device in preparation for the next capture. */
525 _camera_device_reset(wcd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700526
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700527 return 0;
528}
529
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800530/* Capture frame using frame callback.
531 * Parameters and return value for this routine matches _camera_device_read_frame
532 */
533static int
534_camera_device_read_frame_callback(WndCameraDevice* wcd,
535 ClientFrameBuffer* framebuffers,
536 int fbs_num,
537 float r_scale,
538 float g_scale,
539 float b_scale,
540 float exp_comp)
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700541{
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800542 /* Grab the frame. Note that this call will cause frame callback to be
543 * invoked before capGrabFrameNoStop returns. */
544 if (!capGrabFrameNoStop(wcd->cap_window) || wcd->last_frame == NULL) {
545 E("%s: Device '%s' is unable to grab a frame: %d",
546 __FUNCTION__, wcd->window_name, GetLastError());
547 return -1;
548 }
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700549
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800550 /* Convert framebuffer. */
551 return convert_frame(wcd->last_frame,
552 wcd->frame_bitmap->bmiHeader.biCompression,
553 wcd->frame_bitmap->bmiHeader.biSizeImage,
554 wcd->frame_bitmap->bmiHeader.biWidth,
555 wcd->frame_bitmap->bmiHeader.biHeight,
556 framebuffers, fbs_num,
557 r_scale, g_scale, b_scale, exp_comp);
558}
559
560/* Capture frame using clipboard.
561 * Parameters and return value for this routine matches _camera_device_read_frame
562 */
563static int
564_camera_device_read_frame_clipboard(WndCameraDevice* wcd,
565 ClientFrameBuffer* framebuffers,
566 int fbs_num,
567 float r_scale,
568 float g_scale,
569 float b_scale,
570 float exp_comp)
571{
572 HBITMAP bm_handle;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700573
574 /* Grab a frame, and post it to the clipboard. Not very effective, but this
575 * is how capXxx API is operating. */
576 if (!capGrabFrameNoStop(wcd->cap_window) ||
577 !capEditCopy(wcd->cap_window) ||
578 !OpenClipboard(wcd->cap_window)) {
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700579 E("%s: Device '%s' is unable to save frame to the clipboard: %d",
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700580 __FUNCTION__, wcd->window_name, GetLastError());
581 return -1;
582 }
583
584 /* Get bitmap handle saved into clipboard. Note that bitmap is still
585 * owned by the clipboard here! */
586 bm_handle = (HBITMAP)GetClipboardData(CF_BITMAP);
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700587 if (bm_handle == NULL) {
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700588 E("%s: Device '%s' is unable to obtain frame from the clipboard: %d",
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700589 __FUNCTION__, wcd->window_name, GetLastError());
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800590 EmptyClipboard();
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700591 CloseClipboard();
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700592 return -1;
593 }
594
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700595 /* Get bitmap buffer. */
596 if (wcd->gdi_bitmap->bmiHeader.biHeight > 0) {
597 wcd->gdi_bitmap->bmiHeader.biHeight = -wcd->gdi_bitmap->bmiHeader.biHeight;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700598 }
599
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700600 if (!GetDIBits(wcd->dc, bm_handle, 0, wcd->frame_bitmap->bmiHeader.biHeight,
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700601 wcd->framebuffer, wcd->gdi_bitmap, DIB_RGB_COLORS)) {
602 E("%s: Device '%s' is unable to transfer frame to the framebuffer: %d",
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700603 __FUNCTION__, wcd->window_name, GetLastError());
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800604 EmptyClipboard();
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700605 CloseClipboard();
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700606 return -1;
607 }
608
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700609 if (wcd->gdi_bitmap->bmiHeader.biHeight < 0) {
610 wcd->gdi_bitmap->bmiHeader.biHeight = -wcd->gdi_bitmap->bmiHeader.biHeight;
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700611 }
612
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800613 EmptyClipboard();
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700614 CloseClipboard();
615
616 /* Convert framebuffer. */
617 return convert_frame(wcd->framebuffer,
618 wcd->pixel_format,
619 wcd->gdi_bitmap->bmiHeader.biSizeImage,
620 wcd->frame_bitmap->bmiHeader.biWidth,
621 wcd->frame_bitmap->bmiHeader.biHeight,
Vladimir Chtchetkine37fb84f2011-11-23 13:03:37 -0800622 framebuffers, fbs_num,
623 r_scale, g_scale, b_scale, exp_comp);
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700624}
625
Vladimir Chtchetkine9d5d3452012-02-02 09:19:08 -0800626int
627camera_device_read_frame(CameraDevice* cd,
628 ClientFrameBuffer* framebuffers,
629 int fbs_num,
630 float r_scale,
631 float g_scale,
632 float b_scale,
633 float exp_comp)
634{
635 WndCameraDevice* wcd;
636
637 /* Sanity checks. */
638 if (cd == NULL || cd->opaque == NULL) {
639 E("%s: Invalid camera device descriptor", __FUNCTION__);
640 return -1;
641 }
642 wcd = (WndCameraDevice*)cd->opaque;
643 if (wcd->dc == NULL) {
644 W("%s: Device '%s' is not captuing video",
645 __FUNCTION__, wcd->window_name);
646 return -1;
647 }
648
649 /* Dispatch the call to an appropriate routine: grabbing a frame using
650 * clipboard, or using a frame callback. */
651 return wcd->use_clipboard ?
652 _camera_device_read_frame_clipboard(wcd, framebuffers, fbs_num, r_scale,
653 g_scale, b_scale, exp_comp) :
654 _camera_device_read_frame_callback(wcd, framebuffers, fbs_num, r_scale,
655 g_scale, b_scale, exp_comp);
656}
657
Vladimir Chtchetkine4ed09fd2011-08-18 09:42:40 -0700658void
659camera_device_close(CameraDevice* cd)
660{
661 /* Sanity checks. */
662 if (cd == NULL || cd->opaque == NULL) {
663 E("%s: Invalid camera device descriptor", __FUNCTION__);
664 } else {
665 WndCameraDevice* wcd = (WndCameraDevice*)cd->opaque;
666 _camera_device_free(wcd);
667 }
668}
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700669
670int
671enumerate_camera_devices(CameraInfo* cis, int max)
672{
Vladimir Chtchetkine8d9a7a52011-11-15 09:17:29 -0800673/* Array containing emulated webcam frame dimensions.
674 * capXxx API provides device independent frame dimensions, by scaling frames
675 * received from the device to whatever dimensions were requested by the user.
676 * So, we can just use a small set of frame dimensions to emulate.
677 */
678static const CameraFrameDim _emulate_dims[] =
679{
680 /* Emulates 640x480 frame. */
681 {640, 480},
682 /* Emulates 352x288 frame (required by camera framework). */
683 {352, 288},
Vladimir Chtchetkinee080a452011-11-15 13:47:39 -0800684 /* Emulates 320x240 frame (required by camera framework). */
685 {320, 240},
Vladimir Chtchetkine8d9a7a52011-11-15 09:17:29 -0800686 /* Emulates 176x144 frame (required by camera framework). */
687 {176, 144}
688};
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700689 int inp_channel, found = 0;
690
691 for (inp_channel = 0; inp_channel < 10 && found < max; inp_channel++) {
692 char name[256];
693 CameraDevice* cd;
694
695 snprintf(name, sizeof(name), "%s%d", _default_window_name, found);
696 cd = camera_device_open(name, inp_channel);
697 if (cd != NULL) {
698 WndCameraDevice* wcd = (WndCameraDevice*)cd->opaque;
699
700 /* Unfortunately, on Windows we have to start capturing in order to get the
Vladimir Chtchetkine8d9a7a52011-11-15 09:17:29 -0800701 * actual frame properties. */
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700702 if (!camera_device_start_capturing(cd, V4L2_PIX_FMT_RGB32, 640, 480)) {
Vladimir Chtchetkine8d9a7a52011-11-15 09:17:29 -0800703 cis[found].frame_sizes = (CameraFrameDim*)malloc(sizeof(_emulate_dims));
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700704 if (cis[found].frame_sizes != NULL) {
Vladimir Chtchetkineb8dcaff2011-09-17 11:15:47 -0700705 char disp_name[24];
706 sprintf(disp_name, "webcam%d", found);
707 cis[found].display_name = ASTRDUP(disp_name);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700708 cis[found].device_name = ASTRDUP(name);
Vladimir Chtchetkineb8dcaff2011-09-17 11:15:47 -0700709 cis[found].direction = ASTRDUP("front");
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700710 cis[found].inp_channel = inp_channel;
Vladimir Chtchetkine8d9a7a52011-11-15 09:17:29 -0800711 cis[found].frame_sizes_num = sizeof(_emulate_dims) / sizeof(*_emulate_dims);
712 memcpy(cis[found].frame_sizes, _emulate_dims, sizeof(_emulate_dims));
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700713 cis[found].pixel_format = wcd->pixel_format;
714 cis[found].in_use = 0;
715 found++;
716 } else {
717 E("%s: Unable to allocate dimensions", __FUNCTION__);
718 }
Vladimir Chtchetkinea83cf902011-09-22 08:51:59 -0700719 camera_device_stop_capturing(cd);
Vladimir Chtchetkinecf1c2c72011-09-04 09:51:40 -0700720 } else {
721 /* No more cameras. */
722 camera_device_close(cd);
723 break;
724 }
725 camera_device_close(cd);
726 } else {
727 /* No more cameras. */
728 break;
729 }
730 }
731
732 return found;
733}