| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Contains code capturing video frames from a camera device on Windows. |
| * This code uses capXxx API, available via capCreateCaptureWindow. |
| */ |
| |
| /* NOTE: On Windows, we make certain assumptions about * how many threads |
| * are accessing * the camera capture API at a time: |
| * |
| * Namely, that only ONE camera thread is accessing the camera capture API |
| * at any time. |
| * |
| * More details: |
| * |
| * First, some background. The windows camera capture library (vfw32) |
| * cannot be operated on from multiple threads, |
| * or stranges freezes/crashes will result. |
| * |
| * Therefore, all vfw32 calls will go through a single thread, |
| * and the CameraDevice API on windows |
| * merely sets arguments and signals that thread. |
| * |
| * The state machine running on that thread |
| * then assumes that only one other thread is interacting |
| * with it at a time. This is because |
| * we assume the existence of only one camera client, |
| * which results in only serial access to the underlying |
| * camera API. |
| */ |
| |
| #include "android/camera/camera-capture.h" |
| |
| #include "android/base/memory/LazyInstance.h" |
| #include "android/base/synchronization/MessageChannel.h" |
| #include "android/base/threads/Thread.h" |
| #include "android/camera/camera-format-converters.h" |
| |
| using Thread = android::base::Thread; |
| |
| #include <stdio.h> |
| #include <vfw.h> |
| #include <winsock2.h> |
| #include <windows.h> |
| |
| #define E(...) derror(__VA_ARGS__) |
| #define W(...) dwarning(__VA_ARGS__) |
| #define D(...) VERBOSE_PRINT(camera,__VA_ARGS__) |
| #define D_ACTIVE VERBOSE_CHECK(camera) |
| |
| /* the T(...) macro is used to dump traffic */ |
| #define T_ACTIVE 0 |
| |
| #if T_ACTIVE |
| #define T(...) VERBOSE_PRINT(camera,__VA_ARGS__) |
| #else |
| #define T(...) ((void)0) |
| #endif |
| |
| /* Default name for the capture window. */ |
| static const char* _default_window_name = "AndroidEmulatorVC"; |
| |
| typedef struct WndCameraDevice WndCameraDevice; |
| /* Windows-specific camera device descriptor. */ |
| struct WndCameraDevice { |
| /* Common camera device descriptor. */ |
| CameraDevice header; |
| /* Capture window name. (default is AndroidEmulatorVC) */ |
| char* window_name; |
| /* Input channel (video driver index). (default is 0) */ |
| int input_channel; |
| |
| /* |
| * Set when framework gets initialized. |
| */ |
| |
| /* Video capturing window. Null indicates that device is not connected. */ |
| HWND cap_window; |
| /* DC for frame bitmap manipulation. Null indicates that frames are not |
| * being capturing. */ |
| HDC dc; |
| /* Bitmap info for the frames obtained from the video capture driver. */ |
| BITMAPINFO* frame_bitmap; |
| /* Bitmap info to use for GetDIBits calls. We can't really use bitmap info |
| * obtained from the video capture driver, because of the two issues. First, |
| * the driver may return an incompatible 'biCompresstion' value. For instance, |
| * sometimes it returns a "fourcc' pixel format value instead of BI_XXX, |
| * which causes GetDIBits to fail. Second, the bitmap that represents a frame |
| * that has been actually obtained from the device is not necessarily matches |
| * bitmap info that capture driver has returned. Sometimes the captured bitmap |
| * is a 32-bit RGB, while bit count reported by the driver is 16. So, to |
| * address these issues we need to have another bitmap info, that can be used |
| * in GetDIBits calls. */ |
| BITMAPINFO* gdi_bitmap; |
| /* Framebuffer large enough to fit the frame. */ |
| uint8_t* framebuffer; |
| /* Framebuffer size. */ |
| size_t framebuffer_size; |
| /* Framebuffer's pixel format. */ |
| uint32_t pixel_format; |
| /* If != 0, frame bitmap is "top-down". If 0, frame bitmap is "bottom-up". */ |
| int is_top_down; |
| /* Flags whether frame should be captured using clipboard (1), or via frame |
| * callback (0) */ |
| int use_clipboard; |
| /* Contains last frame captured via frame callback. */ |
| void* last_frame; |
| /* Byte size of the 'last_frame' buffer. */ |
| uint32_t last_frame_size; |
| }; |
| |
| /******************************************************************************* |
| * CameraDevice routines |
| ******************************************************************************/ |
| |
| /* Allocates an instance of WndCameraDevice structure. |
| * Return: |
| * Allocated instance of WndCameraDevice structure. Note that this routine |
| * also sets 'opaque' field in the 'header' structure to point back to the |
| * containing WndCameraDevice instance. |
| */ |
| static WndCameraDevice* |
| _camera_device_alloc(void) |
| { |
| WndCameraDevice* cd = (WndCameraDevice*)malloc(sizeof(WndCameraDevice)); |
| if (cd != NULL) { |
| memset(cd, 0, sizeof(WndCameraDevice)); |
| cd->header.opaque = cd; |
| } else { |
| E("%s: Unable to allocate WndCameraDevice instance", __FUNCTION__); |
| } |
| return cd; |
| } |
| |
| /* Uninitializes and frees WndCameraDevice descriptor. |
| * Note that upon return from this routine memory allocated for the descriptor |
| * will be freed. |
| */ |
| static void |
| _camera_device_free(WndCameraDevice* cd) |
| { |
| if (cd != NULL) { |
| if (cd->cap_window != NULL) { |
| /* Disconnect from the driver. */ |
| capDriverDisconnect(cd->cap_window); |
| |
| if (cd->dc != NULL) { |
| W("%s: Frames should not be capturing at this point", |
| __FUNCTION__); |
| ReleaseDC(cd->cap_window, cd->dc); |
| cd->dc = NULL; |
| } |
| /* Destroy the capturing window. */ |
| DestroyWindow(cd->cap_window); |
| cd->cap_window = NULL; |
| } |
| if (cd->gdi_bitmap != NULL) { |
| free(cd->gdi_bitmap); |
| } |
| if (cd->frame_bitmap != NULL) { |
| free(cd->frame_bitmap); |
| } |
| if (cd->window_name != NULL) { |
| free(cd->window_name); |
| } |
| if (cd->framebuffer != NULL) { |
| free(cd->framebuffer); |
| } |
| if (cd->last_frame != NULL) { |
| free(cd->last_frame); |
| } |
| AFREE(cd); |
| } else { |
| W("%s: No descriptor", __FUNCTION__); |
| } |
| } |
| |
| /* Resets camera device after capturing. |
| * Since new capture request may require different frame dimensions we must |
| * reset frame info cached in the capture window. The only way to do that would |
| * be closing, and reopening it again. */ |
| static void |
| _camera_device_reset(WndCameraDevice* cd) |
| { |
| if (cd != NULL && cd->cap_window != NULL) { |
| capDriverDisconnect(cd->cap_window); |
| if (cd->dc != NULL) { |
| ReleaseDC(cd->cap_window, cd->dc); |
| cd->dc = NULL; |
| } |
| if (cd->gdi_bitmap != NULL) { |
| free(cd->gdi_bitmap); |
| cd->gdi_bitmap = NULL; |
| } |
| if (cd->frame_bitmap != NULL) { |
| free(cd->frame_bitmap); |
| cd->frame_bitmap = NULL; |
| } |
| if (cd->framebuffer != NULL) { |
| free(cd->framebuffer); |
| cd->framebuffer = NULL; |
| } |
| if (cd->last_frame != NULL) { |
| free(cd->last_frame); |
| cd->last_frame = NULL; |
| } |
| cd->last_frame_size = 0; |
| |
| /* Recreate the capturing window. */ |
| DestroyWindow(cd->cap_window); |
| cd->cap_window = capCreateCaptureWindow(cd->window_name, WS_CHILD, 0, 0, |
| 0, 0, HWND_MESSAGE, 1); |
| if (cd->cap_window != NULL) { |
| /* Save capture window descriptor as window's user data. */ |
| capSetUserData(cd->cap_window, cd); |
| } |
| } |
| } |
| |
| /* Gets an absolute value out of a signed integer. */ |
| static __inline__ int |
| _abs(int val) |
| { |
| return (val < 0) ? -val : val; |
| } |
| |
| /* Callback that is invoked when a frame gets captured in capGrabFrameNoStop */ |
| static LRESULT CALLBACK |
| _on_captured_frame(HWND hwnd, LPVIDEOHDR hdr) |
| { |
| /* Capture window descriptor is saved in window's user data. */ |
| WndCameraDevice* wcd = (WndCameraDevice*)capGetUserData(hwnd); |
| |
| /* Reallocate frame buffer (if needed) */ |
| if (wcd->last_frame_size < hdr->dwBytesUsed) { |
| wcd->last_frame_size = hdr->dwBytesUsed; |
| if (wcd->last_frame != NULL) { |
| free(wcd->last_frame); |
| } |
| wcd->last_frame = malloc(wcd->last_frame_size); |
| } |
| |
| /* Copy captured frame. */ |
| memcpy(wcd->last_frame, hdr->lpData, hdr->dwBytesUsed); |
| |
| /* If biCompression is set to default (RGB), set correct pixel format |
| * for converters. */ |
| if (wcd->frame_bitmap->bmiHeader.biCompression == BI_RGB) { |
| if (wcd->frame_bitmap->bmiHeader.biBitCount == 32) { |
| wcd->pixel_format = V4L2_PIX_FMT_BGR32; |
| } else if (wcd->frame_bitmap->bmiHeader.biBitCount == 16) { |
| wcd->pixel_format = V4L2_PIX_FMT_RGB565; |
| } else { |
| wcd->pixel_format = V4L2_PIX_FMT_BGR24; |
| } |
| } else { |
| wcd->pixel_format = wcd->frame_bitmap->bmiHeader.biCompression; |
| } |
| |
| return (LRESULT)0; |
| } |
| |
| /******************************************************************************* |
| * CameraDevice API implementation |
| ******************************************************************************/ |
| |
| static CameraDevice* |
| cmd_camera_device_open(const char* name, int inp_channel) |
| { |
| WndCameraDevice* wcd; |
| |
| /* Allocate descriptor and initialize windows-specific fields. */ |
| wcd = _camera_device_alloc(); |
| if (wcd == NULL) { |
| E("%s: Unable to allocate WndCameraDevice instance", __FUNCTION__); |
| return NULL; |
| } |
| wcd->window_name = (name != NULL) ? ASTRDUP(name) : |
| ASTRDUP(_default_window_name); |
| if (wcd->window_name == NULL) { |
| E("%s: Unable to save window name", __FUNCTION__); |
| _camera_device_free(wcd); |
| return NULL; |
| } |
| wcd->input_channel = inp_channel; |
| |
| /* Create capture window that is a child of HWND_MESSAGE window. |
| * We make it invisible, so it doesn't mess with the UI. Also |
| * note that we supply standard HWND_MESSAGE window handle as |
| * the parent window, since we don't want video capturing |
| * machinery to be dependent on the details of our UI. */ |
| wcd->cap_window = capCreateCaptureWindow(wcd->window_name, WS_CHILD, 0, 0, |
| 0, 0, HWND_MESSAGE, 1); |
| if (wcd->cap_window == NULL) { |
| E("%s: Unable to create video capturing window '%s': %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| _camera_device_free(wcd); |
| return NULL; |
| } |
| /* Save capture window descriptor as window's user data. */ |
| capSetUserData(wcd->cap_window, wcd); |
| |
| return &wcd->header; |
| } |
| |
| static int |
| cmd_camera_device_start_capturing(CameraDevice* cd, |
| uint32_t pixel_format, |
| int frame_width, |
| int frame_height) |
| { |
| WndCameraDevice* wcd; |
| HBITMAP bm_handle; |
| BITMAP bitmap; |
| size_t format_info_size; |
| CAPTUREPARMS cap_param; |
| |
| if (cd == NULL || cd->opaque == NULL) { |
| E("%s: Invalid camera device descriptor", __FUNCTION__); |
| return -1; |
| } |
| wcd = (WndCameraDevice*)cd->opaque; |
| |
| /* wcd->dc is an indicator of capturing: !NULL - capturing, NULL - not */ |
| if (wcd->dc != NULL) { |
| W("%s: Capturing is already on on device '%s'", |
| __FUNCTION__, wcd->window_name); |
| return 0; |
| } |
| |
| /* Connect capture window to the video capture driver. */ |
| if (!capDriverConnect(wcd->cap_window, wcd->input_channel)) { |
| return -1; |
| } |
| |
| /* Get current frame information from the driver. */ |
| format_info_size = capGetVideoFormatSize(wcd->cap_window); |
| if (format_info_size == 0) { |
| E("%s: Unable to get video format size: %d", |
| __FUNCTION__, GetLastError()); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| wcd->frame_bitmap = (BITMAPINFO*)malloc(format_info_size); |
| if (wcd->frame_bitmap == NULL) { |
| E("%s: Unable to allocate frame bitmap info buffer", __FUNCTION__); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| if (!capGetVideoFormat(wcd->cap_window, wcd->frame_bitmap, |
| format_info_size)) { |
| E("%s: Unable to obtain video format: %d", __FUNCTION__, GetLastError()); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| /* Lets see if we need to set different frame dimensions */ |
| if (wcd->frame_bitmap->bmiHeader.biWidth != frame_width || |
| abs(wcd->frame_bitmap->bmiHeader.biHeight) != frame_height) { |
| /* Dimensions don't match. Set new frame info. */ |
| wcd->frame_bitmap->bmiHeader.biWidth = frame_width; |
| wcd->frame_bitmap->bmiHeader.biHeight = frame_height; |
| /* We need to recalculate image size, since the capture window / driver |
| * will use image size provided by us. */ |
| if (wcd->frame_bitmap->bmiHeader.biBitCount == 24) { |
| /* Special case that may require WORD boundary alignment. */ |
| uint32_t bpl = (frame_width * 3 + 1) & ~1; |
| wcd->frame_bitmap->bmiHeader.biSizeImage = bpl * frame_height; |
| } else { |
| wcd->frame_bitmap->bmiHeader.biSizeImage = |
| (frame_width * frame_height * wcd->frame_bitmap->bmiHeader.biBitCount) / 8; |
| } |
| if (!capSetVideoFormat(wcd->cap_window, wcd->frame_bitmap, |
| format_info_size)) { |
| E("%s: Unable to set video format: %d", __FUNCTION__, GetLastError()); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| } |
| |
| if (wcd->frame_bitmap->bmiHeader.biCompression > BI_PNG) { |
| D("%s: Video capturing driver has reported pixel format %.4s", |
| __FUNCTION__, (const char*)&wcd->frame_bitmap->bmiHeader.biCompression); |
| } |
| |
| /* Most of the time frame bitmaps come in "bottom-up" form, where its origin |
| * is the lower-left corner. However, it could be in the normal "top-down" |
| * form with the origin in the upper-left corner. So, we must adjust the |
| * biHeight field, since the way "top-down" form is reported here is by |
| * setting biHeight to a negative value. */ |
| if (wcd->frame_bitmap->bmiHeader.biHeight < 0) { |
| wcd->frame_bitmap->bmiHeader.biHeight = |
| -wcd->frame_bitmap->bmiHeader.biHeight; |
| wcd->is_top_down = 1; |
| } else { |
| wcd->is_top_down = 0; |
| } |
| |
| /* Get DC for the capturing window that will be used when we deal with |
| * bitmaps obtained from the camera device during frame capturing. */ |
| wcd->dc = GetDC(wcd->cap_window); |
| if (wcd->dc == NULL) { |
| E("%s: Unable to obtain DC for %s: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| /* Setup some capture parameters. */ |
| if (capCaptureGetSetup(wcd->cap_window, &cap_param, sizeof(cap_param))) { |
| /* Use separate thread to capture video stream. */ |
| cap_param.fYield = TRUE; |
| /* Don't show any dialogs. */ |
| cap_param.fMakeUserHitOKToCapture = FALSE; |
| capCaptureSetSetup(wcd->cap_window, &cap_param, sizeof(cap_param)); |
| } |
| |
| /* |
| * At this point we need to grab a frame to properly setup framebuffer, and |
| * calculate pixel format. The problem is that bitmap information obtained |
| * from the driver doesn't necessarily match the actual bitmap we're going to |
| * obtain via capGrabFrame / capEditCopy / GetClipboardData |
| */ |
| |
| /* Grab a frame, and post it to the clipboard. Not very effective, but this |
| * is how capXxx API is operating. */ |
| if (!capGrabFrameNoStop(wcd->cap_window) || |
| !capEditCopy(wcd->cap_window) || |
| !OpenClipboard(wcd->cap_window)) { |
| E("%s: Device '%s' is unable to save frame to the clipboard: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| /* Get bitmap handle saved into clipboard. Note that bitmap is still |
| * owned by the clipboard here! */ |
| bm_handle = (HBITMAP)GetClipboardData(CF_BITMAP); |
| if (bm_handle == NULL) { |
| E("%s: Device '%s' is unable to obtain frame from the clipboard: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| CloseClipboard(); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| /* Get bitmap object that is initialized with the actual bitmap info. */ |
| if (!GetObject(bm_handle, sizeof(BITMAP), &bitmap)) { |
| E("%s: Device '%s' is unable to obtain frame's bitmap: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| EmptyClipboard(); |
| CloseClipboard(); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| /* Now that we have all we need in 'bitmap' */ |
| EmptyClipboard(); |
| CloseClipboard(); |
| |
| /* Make sure that dimensions match. Othewise - fail. */ |
| if (wcd->frame_bitmap->bmiHeader.biWidth != bitmap.bmWidth || |
| wcd->frame_bitmap->bmiHeader.biHeight != bitmap.bmHeight ) { |
| E("%s: Requested dimensions %dx%d do not match the actual %dx%d", |
| __FUNCTION__, frame_width, frame_height, |
| wcd->frame_bitmap->bmiHeader.biWidth, |
| wcd->frame_bitmap->bmiHeader.biHeight); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| /* Create bitmap info that will be used with GetDIBits. */ |
| wcd->gdi_bitmap = (BITMAPINFO*)malloc(wcd->frame_bitmap->bmiHeader.biSize); |
| if (wcd->gdi_bitmap == NULL) { |
| E("%s: Unable to allocate gdi bitmap info", __FUNCTION__); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| memcpy(wcd->gdi_bitmap, wcd->frame_bitmap, |
| wcd->frame_bitmap->bmiHeader.biSize); |
| wcd->gdi_bitmap->bmiHeader.biCompression = BI_RGB; |
| wcd->gdi_bitmap->bmiHeader.biBitCount = bitmap.bmBitsPixel; |
| wcd->gdi_bitmap->bmiHeader.biSizeImage = bitmap.bmWidthBytes * bitmap.bmWidth; |
| /* Adjust GDI's bitmap biHeight for proper frame direction ("top-down", or |
| * "bottom-up") We do this trick in order to simplify pixel format conversion |
| * routines, where we always assume "top-down" frames. The trick he is to |
| * have negative biHeight in 'gdi_bitmap' if driver provides "bottom-up" |
| * frames, and positive biHeight in 'gdi_bitmap' if driver provides "top-down" |
| * frames. This way GetGDIBits will always return "top-down" frames. */ |
| if (wcd->is_top_down) { |
| wcd->gdi_bitmap->bmiHeader.biHeight = |
| wcd->frame_bitmap->bmiHeader.biHeight; |
| } else { |
| wcd->gdi_bitmap->bmiHeader.biHeight = |
| -wcd->frame_bitmap->bmiHeader.biHeight; |
| } |
| |
| /* Allocate framebuffer. */ |
| wcd->framebuffer = (uint8_t*)malloc(wcd->gdi_bitmap->bmiHeader.biSizeImage); |
| if (wcd->framebuffer == NULL) { |
| E("%s: Unable to allocate %d bytes for framebuffer", |
| __FUNCTION__, wcd->gdi_bitmap->bmiHeader.biSizeImage); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| /* Lets see what pixel format we will use. */ |
| if (wcd->gdi_bitmap->bmiHeader.biBitCount == 16) { |
| wcd->pixel_format = V4L2_PIX_FMT_RGB565; |
| } else if (wcd->gdi_bitmap->bmiHeader.biBitCount == 24) { |
| wcd->pixel_format = V4L2_PIX_FMT_BGR24; |
| } else if (wcd->gdi_bitmap->bmiHeader.biBitCount == 32) { |
| wcd->pixel_format = V4L2_PIX_FMT_BGR32; |
| } else { |
| E("%s: Unsupported number of bits per pixel %d", |
| __FUNCTION__, wcd->gdi_bitmap->bmiHeader.biBitCount); |
| _camera_device_reset(wcd); |
| return -1; |
| } |
| |
| D("%s: Capturing device '%s': %d bits per pixel in %.4s [%dx%d] frame", |
| __FUNCTION__, wcd->window_name, wcd->gdi_bitmap->bmiHeader.biBitCount, |
| (const char*)&wcd->pixel_format, wcd->frame_bitmap->bmiHeader.biWidth, |
| wcd->frame_bitmap->bmiHeader.biHeight); |
| |
| /* Try to setup capture frame callback. */ |
| wcd->use_clipboard = 1; |
| if (capSetCallbackOnFrame(wcd->cap_window, _on_captured_frame)) { |
| /* Callback is set. Don't use clipboard when capturing frames. */ |
| wcd->use_clipboard = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| cmd_camera_device_stop_capturing(CameraDevice* cd) |
| { |
| WndCameraDevice* wcd; |
| if (cd == NULL || cd->opaque == NULL) { |
| E("%s: Invalid camera device descriptor", __FUNCTION__); |
| return -1; |
| } |
| wcd = (WndCameraDevice*)cd->opaque; |
| |
| /* Disable frame callback. */ |
| capSetCallbackOnFrame(wcd->cap_window, NULL); |
| |
| /* wcd->dc is the indicator of capture. */ |
| if (wcd->dc == NULL) { |
| W("%s: Device '%s' is not capturing video", |
| __FUNCTION__, wcd->window_name); |
| return 0; |
| } |
| ReleaseDC(wcd->cap_window, wcd->dc); |
| wcd->dc = NULL; |
| |
| /* Reset the device in preparation for the next capture. */ |
| _camera_device_reset(wcd); |
| |
| return 0; |
| } |
| |
| /* Capture frame using frame callback. |
| * Parameters and return value for this routine matches _camera_device_read_frame |
| */ |
| static int |
| _camera_device_read_frame_callback(WndCameraDevice* wcd, |
| ClientFrameBuffer* framebuffers, |
| int fbs_num, |
| float r_scale, |
| float g_scale, |
| float b_scale, |
| float exp_comp) |
| { |
| /* Grab the frame. Note that this call will cause frame callback to be |
| * invoked before capGrabFrameNoStop returns. */ |
| if (!capGrabFrameNoStop(wcd->cap_window) || wcd->last_frame == NULL) { |
| E("%s: Device '%s' is unable to grab a frame: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| return -1; |
| } |
| |
| /* Convert framebuffer. */ |
| return convert_frame(wcd->last_frame, |
| wcd->pixel_format, |
| wcd->frame_bitmap->bmiHeader.biSizeImage, |
| wcd->frame_bitmap->bmiHeader.biWidth, |
| wcd->frame_bitmap->bmiHeader.biHeight, |
| framebuffers, fbs_num, |
| r_scale, g_scale, b_scale, exp_comp); |
| } |
| |
| /* Capture frame using clipboard. |
| * Parameters and return value for this routine matches _camera_device_read_frame |
| */ |
| static int |
| _camera_device_read_frame_clipboard(WndCameraDevice* wcd, |
| ClientFrameBuffer* framebuffers, |
| int fbs_num, |
| float r_scale, |
| float g_scale, |
| float b_scale, |
| float exp_comp) |
| { |
| HBITMAP bm_handle; |
| |
| /* Grab a frame, and post it to the clipboard. Not very effective, but this |
| * is how capXxx API is operating. */ |
| if (!capGrabFrameNoStop(wcd->cap_window) || |
| !capEditCopy(wcd->cap_window) || |
| !OpenClipboard(wcd->cap_window)) { |
| E("%s: Device '%s' is unable to save frame to the clipboard: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| return -1; |
| } |
| |
| /* Get bitmap handle saved into clipboard. Note that bitmap is still |
| * owned by the clipboard here! */ |
| bm_handle = (HBITMAP)GetClipboardData(CF_BITMAP); |
| if (bm_handle == NULL) { |
| E("%s: Device '%s' is unable to obtain frame from the clipboard: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| EmptyClipboard(); |
| CloseClipboard(); |
| return -1; |
| } |
| |
| /* Get bitmap buffer. */ |
| if (wcd->gdi_bitmap->bmiHeader.biHeight > 0) { |
| wcd->gdi_bitmap->bmiHeader.biHeight = -wcd->gdi_bitmap->bmiHeader.biHeight; |
| } |
| |
| if (!GetDIBits(wcd->dc, bm_handle, 0, wcd->frame_bitmap->bmiHeader.biHeight, |
| wcd->framebuffer, wcd->gdi_bitmap, DIB_RGB_COLORS)) { |
| E("%s: Device '%s' is unable to transfer frame to the framebuffer: %d", |
| __FUNCTION__, wcd->window_name, GetLastError()); |
| EmptyClipboard(); |
| CloseClipboard(); |
| return -1; |
| } |
| |
| if (wcd->gdi_bitmap->bmiHeader.biHeight < 0) { |
| wcd->gdi_bitmap->bmiHeader.biHeight = -wcd->gdi_bitmap->bmiHeader.biHeight; |
| } |
| |
| EmptyClipboard(); |
| CloseClipboard(); |
| |
| /* Convert framebuffer. */ |
| return convert_frame(wcd->framebuffer, |
| wcd->pixel_format, |
| wcd->gdi_bitmap->bmiHeader.biSizeImage, |
| wcd->frame_bitmap->bmiHeader.biWidth, |
| wcd->frame_bitmap->bmiHeader.biHeight, |
| framebuffers, fbs_num, |
| r_scale, g_scale, b_scale, exp_comp); |
| } |
| |
| static int |
| cmd_camera_device_read_frame(CameraDevice* cd, |
| ClientFrameBuffer* framebuffers, |
| int fbs_num, |
| float r_scale, |
| float g_scale, |
| float b_scale, |
| float exp_comp) |
| { |
| WndCameraDevice* wcd; |
| |
| /* Sanity checks. */ |
| if (cd == NULL || cd->opaque == NULL) { |
| E("%s: Invalid camera device descriptor", __FUNCTION__); |
| return -1; |
| } |
| wcd = (WndCameraDevice*)cd->opaque; |
| if (wcd->dc == NULL) { |
| W("%s: Device '%s' is not captuing video", |
| __FUNCTION__, wcd->window_name); |
| return -1; |
| } |
| |
| /* Dispatch the call to an appropriate routine: grabbing a frame using |
| * clipboard, or using a frame callback. */ |
| return wcd->use_clipboard ? |
| _camera_device_read_frame_clipboard(wcd, framebuffers, fbs_num, r_scale, |
| g_scale, b_scale, exp_comp) : |
| _camera_device_read_frame_callback(wcd, framebuffers, fbs_num, r_scale, |
| g_scale, b_scale, exp_comp); |
| } |
| |
| static void |
| cmd_camera_device_close(CameraDevice* cd) |
| { |
| /* Sanity checks. */ |
| if (cd == NULL || cd->opaque == NULL) { |
| E("%s: Invalid camera device descriptor", __FUNCTION__); |
| } else { |
| WndCameraDevice* wcd = (WndCameraDevice*)cd->opaque; |
| _camera_device_free(wcd); |
| } |
| } |
| |
| static int cmd_camera_enumerate_devices(CameraInfo* cis, int max) { |
| /* Array containing emulated webcam frame dimensions. |
| * capXxx API provides device independent frame dimensions, by scaling frames |
| * received from the device to whatever dimensions were requested by the |
| * user. |
| * So, we can just use a small set of frame dimensions to emulate. |
| */ |
| static const CameraFrameDim _emulate_dims[] = { |
| /* Emulates 640x480 frame. */ |
| {640, 480}, |
| /* Emulates 352x288 frame (required by camera framework). */ |
| {352, 288}, |
| /* Emulates 320x240 frame (required by camera framework). */ |
| {320, 240}, |
| /* Emulates 176x144 frame (required by camera framework). */ |
| {176, 144}}; |
| int inp_channel, found = 0; |
| |
| for (inp_channel = 0; inp_channel < 10 && found < max; inp_channel++) { |
| char name[256]; |
| CameraDevice* cd; |
| |
| snprintf(name, sizeof(name), "%s%d", _default_window_name, found); |
| cd = cmd_camera_device_open(name, inp_channel); |
| if (cd != NULL) { |
| WndCameraDevice* wcd = (WndCameraDevice*)cd->opaque; |
| |
| /* Unfortunately, on Windows we have to start capturing in order to get the |
| * actual frame properties. */ |
| if (!cmd_camera_device_start_capturing(cd, V4L2_PIX_FMT_RGB32, 640, 480)) { |
| cis[found].frame_sizes = (CameraFrameDim*)malloc(sizeof(_emulate_dims)); |
| if (cis[found].frame_sizes != NULL) { |
| char disp_name[24]; |
| snprintf(disp_name, sizeof(disp_name), "webcam%d", found); |
| cis[found].display_name = ASTRDUP(disp_name); |
| cis[found].device_name = ASTRDUP(name); |
| cis[found].direction = ASTRDUP("front"); |
| cis[found].inp_channel = inp_channel; |
| cis[found].frame_sizes_num = sizeof(_emulate_dims) / sizeof(*_emulate_dims); |
| memcpy(cis[found].frame_sizes, _emulate_dims, sizeof(_emulate_dims)); |
| cis[found].pixel_format = wcd->pixel_format; |
| cis[found].in_use = 0; |
| found++; |
| } else { |
| E("%s: Unable to allocate dimensions", __FUNCTION__); |
| } |
| cmd_camera_device_stop_capturing(cd); |
| } else { |
| /* No more cameras. */ |
| cmd_camera_device_close(cd); |
| break; |
| } |
| cmd_camera_device_close(cd); |
| } else { |
| /* No more cameras. */ |
| break; |
| } |
| } |
| |
| return found; |
| } |
| |
| // Camera thread state |
| |
| typedef enum { |
| CAMERA_CMD_NOP, |
| CAMERA_CMD_OPEN, |
| CAMERA_CMD_START_CAPTURING, |
| CAMERA_CMD_STOP_CAPTURING, |
| CAMERA_CMD_READ_FRAME, |
| CAMERA_CMD_CLOSE, |
| CAMERA_CMD_ENUMERATE_DEVICES |
| } camera_cmd_t; |
| |
| // Structure used to model a command sent from the main thread to |
| // the camera thread. The process() method handles the command and |
| // returns a result as an intptr_t, since the result of CAMERA_CMD_OPEN |
| // is a pointer to a CameraDevice* instance. |
| struct CameraCommand { |
| camera_cmd_t cmd; |
| union { |
| // CAMERA_CMD_OPEN |
| // NOTE: The result is a CameraDevice* cast as an intptr_t |
| struct { |
| const char* name; |
| int channel; |
| } open; |
| |
| // CAMERA_CMD_START_CAPTURING |
| struct { |
| CameraDevice* device; |
| uint32_t pixel_format; |
| int frame_width; |
| int frame_height; |
| } start; |
| |
| // CAMERA_CMD_STOP_CAPTURING |
| struct { |
| CameraDevice* device; |
| } stop; |
| |
| // CAMERA_CMD_READ_FRAME |
| struct { |
| CameraDevice* device; |
| ClientFrameBuffer* framebuffers; |
| int fbs_num; |
| float r_scale; |
| float g_scale; |
| float b_scale; |
| float exp_comp; |
| } read_frame; |
| |
| // CAMERA_CMD_CLOSE |
| struct { |
| CameraDevice* device; |
| } close; |
| |
| // CAMERA_CMD_ENUMERATE_DEVICES |
| struct { |
| CameraInfo* info; |
| int max; |
| } enumerate; |
| }; |
| |
| // Process a command (i.e. execute it). Must be called in the camera |
| // thread context. |
| intptr_t process() const { |
| const CameraCommand& cmd = *this; |
| intptr_t result = 0; |
| |
| // Execute the command |
| switch (cmd.cmd) { |
| case CAMERA_CMD_NOP: |
| break; |
| |
| case CAMERA_CMD_OPEN: |
| result = reinterpret_cast<intptr_t>(cmd_camera_device_open( |
| cmd.open.name, cmd.open.channel)); |
| break; |
| |
| case CAMERA_CMD_START_CAPTURING: |
| result = cmd_camera_device_start_capturing( |
| cmd.start.device, cmd.start.pixel_format, |
| cmd.start.frame_width, cmd.start.frame_height); |
| break; |
| |
| case CAMERA_CMD_STOP_CAPTURING: |
| result = cmd_camera_device_stop_capturing(cmd.stop.device); |
| break; |
| |
| case CAMERA_CMD_READ_FRAME: |
| result = cmd_camera_device_read_frame( |
| cmd.read_frame.device, cmd.read_frame.framebuffers, |
| cmd.read_frame.fbs_num, cmd.read_frame.r_scale, |
| cmd.read_frame.g_scale, cmd.read_frame.b_scale, |
| cmd.read_frame.exp_comp); |
| break; |
| |
| case CAMERA_CMD_CLOSE: |
| cmd_camera_device_close(cmd.close.device); |
| break; |
| |
| case CAMERA_CMD_ENUMERATE_DEVICES: |
| result = cmd_camera_enumerate_devices(cmd.enumerate.info, |
| cmd.enumerate.max); |
| break; |
| } |
| return result; |
| } |
| }; |
| |
| // The camera thead instance. |
| class CameraThread : public android::base::Thread { |
| public: |
| // Constructor, called from the main thread. |
| CameraThread() : mInput(), mOutput() { |
| this->start(); |
| } |
| |
| // Main thread function, runs in the camera thread context. |
| virtual intptr_t main() override { |
| bool running = true; |
| while (running) { |
| CameraCommand cmd = {}; |
| mInput.receive(&cmd); |
| intptr_t result = cmd.process(); |
| mOutput.send(result); |
| |
| // TODO: Stop the thread at some point? |
| } |
| return 0; |
| } |
| |
| // Call from the main thread to send a command and wait for its result. |
| intptr_t sendCommandAndGetResult(const CameraCommand& cmd) { |
| mInput.send(cmd); |
| intptr_t result = 0; |
| mOutput.receive(&result); |
| return result; |
| } |
| |
| private: |
| // message channel used to send commands from the main thread |
| // to the camera thread. |
| android::base::MessageChannel<CameraCommand, 4U> mInput; |
| |
| // message channel used to send results from the camera |
| // thread to the main one. |
| android::base::MessageChannel<intptr_t, 4U> mOutput; |
| }; |
| |
| static android::base::LazyInstance<CameraThread> sCameraThread = |
| LAZY_INSTANCE_INIT; |
| |
| extern "C" int windows_camera_thread_init() { |
| // Force thread creation and start. |
| (void)sCameraThread.ptr(); |
| return 0; |
| } |
| |
| /******************************************************************************* |
| * CameraDevice API |
| ******************************************************************************/ |
| |
| CameraDevice* camera_device_open(const char* name, int channel) { |
| CameraCommand cmd = {}; |
| cmd.cmd = CAMERA_CMD_OPEN; |
| cmd.open.name = name; |
| cmd.open.channel = channel; |
| |
| return reinterpret_cast<CameraDevice*>( |
| sCameraThread->sendCommandAndGetResult(cmd)); |
| } |
| |
| int camera_device_start_capturing(CameraDevice* cd, |
| uint32_t pixel_format, |
| int frame_width, |
| int frame_height) { |
| CameraCommand cmd = {}; |
| cmd.cmd = CAMERA_CMD_START_CAPTURING; |
| cmd.start.device = cd; |
| cmd.start.pixel_format = pixel_format; |
| cmd.start.frame_width = frame_width; |
| cmd.start.frame_height = frame_height; |
| |
| return static_cast<int>(sCameraThread->sendCommandAndGetResult(cmd)); |
| } |
| |
| int camera_device_stop_capturing(CameraDevice* cd) { |
| CameraCommand cmd = {}; |
| cmd.cmd = CAMERA_CMD_STOP_CAPTURING; |
| cmd.stop.device = cd; |
| return static_cast<int>(sCameraThread->sendCommandAndGetResult(cmd)); |
| } |
| |
| int camera_device_read_frame(CameraDevice* cd, |
| ClientFrameBuffer* framebuffers, |
| int fbs_num, |
| float r_scale, |
| float g_scale, |
| float b_scale, |
| float exp_comp) { |
| CameraCommand cmd = {}; |
| cmd.cmd = CAMERA_CMD_READ_FRAME; |
| cmd.read_frame.device = cd; |
| cmd.read_frame.framebuffers = framebuffers; |
| cmd.read_frame.fbs_num = fbs_num; |
| cmd.read_frame.r_scale = r_scale; |
| cmd.read_frame.g_scale = g_scale; |
| cmd.read_frame.b_scale = b_scale; |
| cmd.read_frame.exp_comp = exp_comp; |
| |
| return static_cast<int>(sCameraThread->sendCommandAndGetResult(cmd)); |
| } |
| |
| void camera_device_close(CameraDevice* cd) { |
| CameraCommand cmd = {}; |
| cmd.cmd = CAMERA_CMD_CLOSE; |
| cmd.close.device = cd; |
| (void)sCameraThread->sendCommandAndGetResult(cmd); |
| } |
| |
| int camera_enumerate_devices(CameraInfo* cis, int max) { |
| CameraCommand cmd = {}; |
| cmd.cmd = CAMERA_CMD_ENUMERATE_DEVICES; |
| cmd.enumerate.info = cis; |
| cmd.enumerate.max = max; |
| return static_cast<int>(sCameraThread->sendCommandAndGetResult(cmd)); |
| } |