From 955a99781dd7af82e7a55525a4e51ce5d0814021 Mon Sep 17 00:00:00 2001 From: Vladimir Chtchetkine Date: Wed, 12 Oct 2011 15:40:17 -0700 Subject: Implemented Mac camera emulation Change-Id: I01609e8bfcdd8ee5ef876344d0a334ecd69a2b7a --- android/camera/camera-capture-mac.c | 86 ------ android/camera/camera-capture-mac.m | 549 ++++++++++++++++++++++++++++++++++++ 2 files changed, 549 insertions(+), 86 deletions(-) delete mode 100644 android/camera/camera-capture-mac.c create mode 100644 android/camera/camera-capture-mac.m (limited to 'android/camera') diff --git a/android/camera/camera-capture-mac.c b/android/camera/camera-capture-mac.c deleted file mode 100644 index 764a055..0000000 --- a/android/camera/camera-capture-mac.c +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 Mac. - * This code uses capXxx API, available via capCreateCaptureWindow. - */ - -#include "android/camera/camera-capture.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 - -/******************************************************************************* - * CameraDevice API - ******************************************************************************/ - -CameraDevice* -camera_device_open(const char* name, int inp_channel) -{ - /* TODO: Implement */ - return NULL; -} - -int -camera_device_start_capturing(CameraDevice* cd, - uint32_t pixel_format, - int frame_width, - int frame_height) -{ - /* TODO: Implement */ - return -1; -} - -int -camera_device_stop_capturing(CameraDevice* cd) -{ - /* TODO: Implement */ - return -1; -} - -int -camera_device_read_frame(CameraDevice* cd, - ClientFrameBuffer* framebuffers, - int fbs_num) -{ - /* TODO: Implement */ - return -1; -} - -void -camera_device_close(CameraDevice* cd) -{ - /* TODO: Implement */ -} - -int -enumerate_camera_devices(CameraInfo* cis, int max) -{ - /* TODO: Implement */ - return 0; -} diff --git a/android/camera/camera-capture-mac.m b/android/camera/camera-capture-mac.m new file mode 100644 index 0000000..1c1c3d5 --- /dev/null +++ b/android/camera/camera-capture-mac.m @@ -0,0 +1,549 @@ +/* + * 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 that is used to capture video frames from a camera device + * on Mac. This code uses QTKit API to work with camera devices, and requires + * Mac OS at least 10.5 + */ + +#import +#import +#import +#include "android/camera/camera-capture.h" +#include "android/camera/camera-format-converters.h" + +#define E(...) derror(__VA_ARGS__) +#define W(...) dwarning(__VA_ARGS__) +#define D(...) VERBOSE_PRINT(camera,__VA_ARGS__) + +/******************************************************************************* + * Helper routines + ******************************************************************************/ + +/* Converts internal QT pixel format to a FOURCC value. */ +static uint32_t +_QTtoFOURCC(uint32_t qt_pix_format) +{ + switch (qt_pix_format) { + case kCVPixelFormatType_24RGB: + return V4L2_PIX_FMT_RGB24; + + case kCVPixelFormatType_24BGR: + return V4L2_PIX_FMT_BGR32; + + case kCVPixelFormatType_32ARGB: + case kCVPixelFormatType_32RGBA: + return V4L2_PIX_FMT_RGB32; + + case kCVPixelFormatType_32BGRA: + case kCVPixelFormatType_32ABGR: + return V4L2_PIX_FMT_BGR32; + + case kCVPixelFormatType_422YpCbCr8: + return V4L2_PIX_FMT_UYVY; + + case kCVPixelFormatType_420YpCbCr8Planar: + return V4L2_PIX_FMT_YVU420; + + case 'yuvs': // kCVPixelFormatType_422YpCbCr8_yuvs - undeclared? + return V4L2_PIX_FMT_YUYV; + + default: + E("Unrecognized pixel format '%.4s'", (const char*)&qt_pix_format); + return 0; + } +} + +/******************************************************************************* + * MacCamera implementation + ******************************************************************************/ + +/* Encapsulates a camera device on MacOS */ +@interface MacCamera : NSObject { + /* Capture session. */ + QTCaptureSession* capture_session; + /* Camera capture device. */ + QTCaptureDevice* capture_device; + /* Input device registered with the capture session. */ + QTCaptureDeviceInput* input_device; + /* Output device registered with the capture session. */ + QTCaptureVideoPreviewOutput* output_device; + /* Current framebuffer. */ + CVImageBufferRef current_frame; + /* Desired frame width */ + int desired_width; + /* Desired frame height */ + int desired_height; +} + +/* Initializes MacCamera instance. + * Return: + * Pointer to initialized instance on success, or nil on failure. + */ +- (MacCamera*)init; + +/* Undoes 'init' */ +- (void)free; + +/* Starts capturing video frames. + * Param: + * width, height - Requested dimensions for the captured video frames. + * Return: + * 0 on success, or !=0 on failure. + */ +- (int)start_capturing:(int)width:(int)height; + +/* Captures a frame from the camera device. + * Param: + * framebuffers - Array of framebuffers where to read the frame. Size of this + * array is defined by the 'fbs_num' parameter. Note that the caller must + * make sure that buffers are large enough to contain entire frame captured + * from the device. + * fbs_num - Number of entries in the 'framebuffers' array. + * Return: + * 0 on success, or non-zero value on failure. There is a special vaule 1 + * returned from this routine which indicates that frames are not yet available + * in the device. The client should respond to this value by repeating the + * read, rather than reporting an error. + */ +- (int)read_frame:(ClientFrameBuffer*)framebuffers:(int)fbs_num; + +@end + +@implementation MacCamera + +- (MacCamera*)init +{ + NSError *error; + BOOL success; + + /* Obtain the capture device, make sure it's not used by another + * application, and open it. */ + capture_device = + [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo]; + if (capture_device == nil) { + E("There are no available video devices found."); + [self release]; + return nil; + } + if ([capture_device isInUseByAnotherApplication]) { + E("Default camera device is in use by another application."); + [capture_device release]; + capture_device = nil; + [self release]; + return nil; + } + success = [capture_device open:&error]; + if (!success) { + E("Unable to open camera device: '%s'", + [[error localizedDescription] UTF8String]); + [self free]; + [self release]; + return nil; + } + + /* Create capture session. */ + capture_session = [[QTCaptureSession alloc] init]; + if (capture_session == nil) { + E("Unable to create capure session."); + [self free]; + [self release]; + return nil; + } + + /* Create an input device and register it with the capture session. */ + input_device = [[QTCaptureDeviceInput alloc] initWithDevice:capture_device]; + success = [capture_session addInput:input_device error:&error]; + if (!success) { + E("Unable to initialize input device: '%s'", + [[error localizedDescription] UTF8String]); + [input_device release]; + input_device = nil; + [self free]; + [self release]; + return nil; + } + + /* Create an output device and register it with the capture session. */ + output_device = [[QTCaptureVideoPreviewOutput alloc] init]; + success = [capture_session addOutput:output_device error:&error]; + if (!success) { + E("Unable to initialize output device: '%s'", + [[error localizedDescription] UTF8String]); + [output_device release]; + output_device = nil; + [self free]; + [self release]; + return nil; + } + [output_device setDelegate:self]; + + return self; +} + +- (void)free +{ + /* Uninitialize capture session. */ + if (capture_session != nil) { + /* Make sure that capturing is stopped. */ + if ([capture_session isRunning]) { + [capture_session stopRunning]; + } + /* Detach input and output devices from the session. */ + if (input_device != nil) { + [capture_session removeInput:input_device]; + [input_device release]; + input_device = nil; + } + if (output_device != nil) { + [capture_session removeOutput:output_device]; + [output_device release]; + output_device = nil; + } + /* Destroy capture session. */ + [capture_session release]; + capture_session = nil; + } + + /* Uninitialize capture device. */ + if (capture_device != nil) { + /* Make sure device is not opened. */ + if ([capture_device isOpen]) { + [capture_device close]; + } + [capture_device release]; + capture_device = nil; + } + + /* Release current framebuffer. */ + if (current_frame != nil) { + CVBufferRelease(current_frame); + current_frame = nil; + } +} + +- (int)start_capturing:(int)width:(int)height +{ + if (![capture_session isRunning]) { + /* Set desired frame dimensions. */ + desired_width = width; + desired_height = height; + [output_device setPixelBufferAttributes: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt: width], kCVPixelBufferWidthKey, + [NSNumber numberWithInt: height], kCVPixelBufferHeightKey, + nil]]; + [capture_session startRunning]; + return 0; + } else if (width == desired_width && height == desired_height) { + W("%s: Already capturing %dx%d frames", + __FUNCTION__, desired_width, desired_height); + return -1; + } else { + E("%s: Already capturing %dx%d frames. Requested frame dimensions are %dx%d", + __FUNCTION__, desired_width, desired_height, width, height); + return -1; + } +} + +- (int)read_frame:(ClientFrameBuffer*)framebuffers:(int)fbs_num +{ + int res = -1; + + /* Frames are pushed by QT in another thread. + * So we need a protection here. */ + @synchronized (self) + { + if (current_frame != nil) { + /* Collect frame info. */ + const uint32_t pixel_format = + _QTtoFOURCC(CVPixelBufferGetPixelFormatType(current_frame)); + const int frame_width = CVPixelBufferGetWidth(current_frame); + const int frame_height = CVPixelBufferGetHeight(current_frame); + const size_t frame_size = + CVPixelBufferGetBytesPerRow(current_frame) * frame_height; + + /* Get framebuffer pointer. */ + CVPixelBufferLockBaseAddress(current_frame, 0); + const void* pixels = CVPixelBufferGetBaseAddress(current_frame); + if (pixels != nil) { + /* Convert framebuffer. */ + res = convert_frame(pixels, pixel_format, frame_size, + frame_width, frame_height, + framebuffers, fbs_num); + } else { + E("%s: Unable to obtain framebuffer", __FUNCTION__); + res = -1; + } + CVPixelBufferUnlockBaseAddress(current_frame, 0); + } else { + /* First frame didn't come in just yet. Let the caller repeat. */ + res = 1; + } + } + + return res; +} + +- (void)captureOutput:(QTCaptureOutput*) captureOutput + didOutputVideoFrame:(CVImageBufferRef)videoFrame + withSampleBuffer:(QTSampleBuffer*) sampleBuffer + fromConnection:(QTCaptureConnection*) connection +{ + CVImageBufferRef to_release; + CVBufferRetain(videoFrame); + + /* Frames are pulled by the client in another thread. + * So we need a protection here. */ + @synchronized (self) + { + to_release = current_frame; + current_frame = videoFrame; + } + CVBufferRelease(to_release); +} + +@end + +/******************************************************************************* + * CameraDevice routines + ******************************************************************************/ + +typedef struct MacCameraDevice MacCameraDevice; +/* MacOS-specific camera device descriptor. */ +struct MacCameraDevice { + /* Common camera device descriptor. */ + CameraDevice header; + /* Actual camera device object. */ + MacCamera* device; +}; + +/* Allocates an instance of MacCameraDevice structure. + * Return: + * Allocated instance of MacCameraDevice structure. Note that this routine + * also sets 'opaque' field in the 'header' structure to point back to the + * containing MacCameraDevice instance. + */ +static MacCameraDevice* +_camera_device_alloc(void) +{ + MacCameraDevice* cd = (MacCameraDevice*)malloc(sizeof(MacCameraDevice)); + if (cd != NULL) { + memset(cd, 0, sizeof(MacCameraDevice)); + cd->header.opaque = cd; + } else { + E("%s: Unable to allocate MacCameraDevice instance", __FUNCTION__); + } + return cd; +} + +/* Uninitializes and frees MacCameraDevice descriptor. + * Note that upon return from this routine memory allocated for the descriptor + * will be freed. + */ +static void +_camera_device_free(MacCameraDevice* cd) +{ + if (cd != NULL) { + if (cd->device != NULL) { + [cd->device free]; + [cd->device release]; + cd->device = nil; + } + 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(MacCameraDevice* cd) +{ + if (cd != NULL && cd->device) { + [cd->device free]; + cd->device = [cd->device init]; + } +} + +/******************************************************************************* + * CameraDevice API + ******************************************************************************/ + +CameraDevice* +camera_device_open(const char* name, int inp_channel) +{ + MacCameraDevice* mcd; + + mcd = _camera_device_alloc(); + if (mcd == NULL) { + E("%s: Unable to allocate MacCameraDevice instance", __FUNCTION__); + return NULL; + } + mcd->device = [[MacCamera alloc] init]; + if (mcd->device == nil) { + E("%s: Unable to initialize camera device.", __FUNCTION__); + return NULL; + } + return &mcd->header; +} + +int +camera_device_start_capturing(CameraDevice* cd, + uint32_t pixel_format, + int frame_width, + int frame_height) +{ + MacCameraDevice* mcd; + + /* Sanity checks. */ + if (cd == NULL || cd->opaque == NULL) { + E("%s: Invalid camera device descriptor", __FUNCTION__); + return -1; + } + mcd = (MacCameraDevice*)cd->opaque; + if (mcd->device == nil) { + E("%s: Camera device is not opened", __FUNCTION__); + return -1; + } + + return [mcd->device start_capturing:frame_width:frame_height]; +} + +int +camera_device_stop_capturing(CameraDevice* cd) +{ + MacCameraDevice* mcd; + + /* Sanity checks. */ + if (cd == NULL || cd->opaque == NULL) { + E("%s: Invalid camera device descriptor", __FUNCTION__); + return -1; + } + mcd = (MacCameraDevice*)cd->opaque; + if (mcd->device == nil) { + E("%s: Camera device is not opened", __FUNCTION__); + return -1; + } + + /* Reset capture settings, so next call to capture can set its own. */ + _camera_device_reset(mcd); + + return 0; +} + +int +camera_device_read_frame(CameraDevice* cd, + ClientFrameBuffer* framebuffers, + int fbs_num) +{ + MacCameraDevice* mcd; + + /* Sanity checks. */ + if (cd == NULL || cd->opaque == NULL) { + E("%s: Invalid camera device descriptor", __FUNCTION__); + return -1; + } + mcd = (MacCameraDevice*)cd->opaque; + if (mcd->device == nil) { + E("%s: Camera device is not opened", __FUNCTION__); + return -1; + } + + return [mcd->device read_frame:framebuffers:fbs_num]; +} + +void +camera_device_close(CameraDevice* cd) +{ + /* Sanity checks. */ + if (cd == NULL || cd->opaque == NULL) { + E("%s: Invalid camera device descriptor", __FUNCTION__); + } else { + _camera_device_free((MacCameraDevice*)cd->opaque); + } +} + +int +enumerate_camera_devices(CameraInfo* cis, int max) +{ +/* Array containing emulated webcam frame dimensions. + * QT API provides device independent frame dimensions, by scaling frames + * received from the device to whatever dimensions were requested for the + * output device. So, we can just use a small set of frame dimensions to + * emulate. + */ +static const CameraFrameDim _emulate_dims[] = +{ + /* Emulates 640x480 frame. */ + {640, 480}, + /* Emulates 320x240 frame. */ + {320, 240}, +}; + + /* Obtain default video device. QT API doesn't really provide a reliable + * way to identify camera devices. There is a QTCaptureDevice::uniqueId + * method that supposedly does that, but in some cases it just doesn't + * work. Until we figure out a reliable device identification, we will + * stick to using only one (default) camera for emulation. */ + QTCaptureDevice* video_dev = + [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo]; + if (video_dev == nil) { + D("No web cameras are connected to the host."); + return 0; + } + + /* Obtain pixel format for the device. */ + NSArray* pix_formats = [video_dev formatDescriptions]; + if (pix_formats == nil || [pix_formats count] == 0) { + E("Unable to obtain pixel format for the default camera device."); + [video_dev release]; + return 0; + } + const uint32_t qt_pix_format = [[pix_formats objectAtIndex:0] formatType]; + [pix_formats release]; + + /* Obtain FOURCC pixel format for the device. */ + cis[0].pixel_format = _QTtoFOURCC(qt_pix_format); + if (cis[0].pixel_format == 0) { + /* Unsupported pixel format. */ + E("Pixel format '%.4s' reported by the camera device is unsupported", + (const char*)&qt_pix_format); + [video_dev release]; + return 0; + } + + /* Initialize camera info structure. */ + cis[0].frame_sizes = (CameraFrameDim*)malloc(sizeof(_emulate_dims)); + if (cis[0].frame_sizes != NULL) { + cis[0].frame_sizes_num = sizeof(_emulate_dims) / sizeof(*_emulate_dims); + memcpy(cis[0].frame_sizes, _emulate_dims, sizeof(_emulate_dims)); + cis[0].device_name = ASTRDUP("webcam0"); + cis[0].inp_channel = 0; + cis[0].display_name = ASTRDUP("webcam0"); + cis[0].in_use = 0; + [video_dev release]; + return 1; + } else { + E("Unable to allocate memory for camera information."); + [video_dev release]; + return 0; + } +} -- cgit v1.1