/* * 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:(float)r_scale:(float)g_scale:(float)b_scale:(float)exp_comp; @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:(float)r_scale:(float)g_scale:(float)b_scale:(float)exp_comp { 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, r_scale, g_scale, b_scale, exp_comp); } 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, float r_scale, float g_scale, float b_scale, float exp_comp) { 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:r_scale:g_scale:b_scale:exp_comp]; } 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 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} }; /* 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; } }