aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.android2
-rw-r--r--Makefile.common2
-rw-r--r--android/camera/camera-capture-mac.c86
-rw-r--r--android/camera/camera-capture-mac.m549
4 files changed, 551 insertions, 88 deletions
diff --git a/Makefile.android b/Makefile.android
index 6888173..e58f984 100644
--- a/Makefile.android
+++ b/Makefile.android
@@ -190,7 +190,7 @@ else
endif
ifeq ($(HOST_OS),darwin)
- QEMU_SYSTEM_LDLIBS += -Wl,-framework,Cocoa
+ QEMU_SYSTEM_LDLIBS += -Wl,-framework,Cocoa,-framework,QTKit,-framework,CoreVideo
endif
include $(LOCAL_PATH)/Makefile.common
diff --git a/Makefile.common b/Makefile.common
index a816a8a..71100f3 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -457,7 +457,7 @@ else
endif
ifeq ($(HOST_OS),darwin)
- CORE_MISC_SOURCES += android/camera/camera-capture-mac.c
+ CORE_MISC_SOURCES += android/camera/camera-capture-mac.m
endif
LOCAL_SRC_FILES += $(CORE_MISC_SOURCES)
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 <Cocoa/Cocoa.h>
+#import <QTKit/QTkit.h>
+#import <CoreAudio/CoreAudio.h>
+#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;
+ }
+}