0

Adding VideoCaptureDevice for Mac.

Adding dependency on QTKit and CoreVideo.

BUG=
TEST=

Review URL: http://codereview.chromium.org/8177008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@106036 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
mflodman@chromium.org
2011-10-18 06:09:45 +00:00
parent 45051c3efb
commit 58dffbf95c
6 changed files with 480 additions and 5 deletions

@ -204,6 +204,10 @@
'video/capture/fake_video_capture_device.h',
'video/capture/linux/video_capture_device_linux.cc',
'video/capture/linux/video_capture_device_linux.h',
'video/capture/mac/video_capture_device_mac.h',
'video/capture/mac/video_capture_device_mac.mm',
'video/capture/mac/video_capture_device_qtkit_mac.h',
'video/capture/mac/video_capture_device_qtkit_mac.mm',
'video/capture/video_capture.h',
'video/capture/video_capture_device.h',
'video/capture/video_capture_device_dummy.cc',
@ -325,7 +329,7 @@
'audio/openbsd/audio_manager_openbsd.h',
],
}],
['os_posix == 1 and OS != "mac"', {
['os_posix == 1', {
'sources!': [
'video/capture/video_capture_device_dummy.cc',
'video/capture/video_capture_device_dummy.h',
@ -337,6 +341,8 @@
'$(SDKROOT)/System/Library/Frameworks/AudioUnit.framework',
'$(SDKROOT)/System/Library/Frameworks/AudioToolbox.framework',
'$(SDKROOT)/System/Library/Frameworks/CoreAudio.framework',
'$(SDKROOT)/System/Library/Frameworks/CoreVideo.framework',
'$(SDKROOT)/System/Library/Frameworks/QTKit.framework',
],
},
}],

@ -0,0 +1,65 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// OS X implementation of VideoCaptureDevice, using QTKit as native capture API.
#ifndef MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_H_
#define MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_H_
#include <string>
#include "base/compiler_specific.h"
#include "media/video/capture/video_capture_device.h"
@class VideoCaptureDeviceQTKit;
namespace media {
// Called by VideoCaptureManager to open, close and start, stop video capture
// devices.
class VideoCaptureDeviceMac : public VideoCaptureDevice {
public:
explicit VideoCaptureDeviceMac(const Name& device_name);
virtual ~VideoCaptureDeviceMac();
// VideoCaptureDevice implementation.
virtual void Allocate(int width,
int height,
int frame_rate,
VideoCaptureDevice::EventHandler* observer) OVERRIDE;
virtual void Start() OVERRIDE;
virtual void Stop() OVERRIDE;
virtual void DeAllocate() OVERRIDE;
virtual const Name& device_name() OVERRIDE;
bool Init();
// Called to deliver captured video frames.
void ReceiveFrame(const uint8* video_frame, int video_frame_length,
const Capability& frame_info);
private:
void SetErrorState(const std::string& reason);
// Flag indicating the internal state.
enum InternalState {
kNotInitialized,
kIdle,
kAllocated,
kCapturing,
kError
};
VideoCaptureDevice::Name device_name_;
VideoCaptureDevice::EventHandler* observer_;
InternalState state_;
VideoCaptureDeviceQTKit* capture_device_;
DISALLOW_COPY_AND_ASSIGN(VideoCaptureDeviceMac);
};
} // namespace media
#endif // MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_H_

@ -0,0 +1,146 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/video/capture/mac/video_capture_device_mac.h"
#import <QTKit/QTKit.h>
#include "base/logging.h"
#include "base/time.h"
#include "media/video/capture/mac/video_capture_device_qtkit_mac.h"
namespace media {
void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
// Loop through all available devices and add to |device_names|.
device_names->clear();
// TODO(mflodman) Return name and id as NSArray* instead of QTCaptureDevice*.
for (QTCaptureDevice* device in [VideoCaptureDeviceQTKit deviceNames]) {
Name name;
NSString* qt_device_name = [device localizedDisplayName];
name.device_name = [qt_device_name UTF8String];
NSString* qt_unique_id = [device uniqueID];
name.unique_id = [qt_unique_id UTF8String];
device_names->push_back(name);
}
}
VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
VideoCaptureDeviceMac* capture_device =
new VideoCaptureDeviceMac(device_name);
if (!capture_device->Init()) {
LOG(ERROR) << "Could not initialize VideoCaptureDevice.";
delete capture_device;
capture_device = NULL;
}
return capture_device;
}
VideoCaptureDeviceMac::VideoCaptureDeviceMac(const Name& device_name)
: device_name_(device_name),
observer_(NULL),
state_(kNotInitialized),
capture_device_(nil) {
}
VideoCaptureDeviceMac::~VideoCaptureDeviceMac() {
[capture_device_ release];
}
void VideoCaptureDeviceMac::Allocate(int width, int height, int frame_rate,
EventHandler* observer) {
if (state_ != kIdle) {
return;
}
observer_ = observer;
NSString* deviceId =
[NSString stringWithUTF8String:device_name_.unique_id.c_str()];
if (![capture_device_ setCaptureDevice:deviceId]) {
SetErrorState("Could not open capture device.");
return;
}
if (![capture_device_ setCaptureHeight:height
width:width
frameRate:frame_rate]) {
SetErrorState("Could not configure capture device.");
return;
}
state_ = kAllocated;
Capability current_settings;
current_settings.color = kARGB;
current_settings.width = width;
current_settings.height = height;
current_settings.frame_rate = frame_rate;
observer_->OnFrameInfo(current_settings);
}
void VideoCaptureDeviceMac::Start() {
DCHECK_EQ(state_, kAllocated);
if (![capture_device_ startCapture]) {
SetErrorState("Could not start capture device.");
return;
}
state_ = kCapturing;
}
void VideoCaptureDeviceMac::Stop() {
DCHECK_EQ(state_, kCapturing);
[capture_device_ stopCapture];
state_ = kAllocated;
}
void VideoCaptureDeviceMac::DeAllocate() {
if (state_ != kAllocated && state_ != kCapturing) {
return;
}
if (state_ == kCapturing) {
[capture_device_ stopCapture];
}
[capture_device_ setCaptureDevice:nil];
state_ = kIdle;
}
const VideoCaptureDevice::Name& VideoCaptureDeviceMac::device_name() {
return device_name_;
}
bool VideoCaptureDeviceMac::Init() {
DCHECK_EQ(state_, kNotInitialized);
Names device_names;
GetDeviceNames(&device_names);
for (Names::iterator it = device_names.begin();
it != device_names.end();
++it) {
if (device_name_.unique_id == it->unique_id) {
capture_device_ =
[[VideoCaptureDeviceQTKit alloc] initWithFrameReceiver:this];
if (!capture_device_) {
return false;
}
state_ = kIdle;
return true;
}
}
return false;
}
void VideoCaptureDeviceMac::ReceiveFrame(const uint8* video_frame,
int video_frame_length,
const Capability& frame_info) {
observer_->OnIncomingCapturedFrame(video_frame, video_frame_length,
base::Time::Now());
}
void VideoCaptureDeviceMac::SetErrorState(const std::string& reason) {
DLOG(ERROR) << reason;
state_ = kError;
observer_->OnError();
}
} // namespace media

@ -0,0 +1,55 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// VideoCaptureDeviceQTKit implements all QTKit related code for
// communicating with a QTKit capture device.
#ifndef MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_QTKIT_H_
#define MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_QTKIT_H_
#import <Foundation/Foundation.h>
namespace media {
class VideoCaptureDeviceMac;
}
@class QTCaptureDeviceInput;
@class QTCaptureSession;
@interface VideoCaptureDeviceQTKit : NSObject {
@private
// Settings.
int frameRate_;
int frameWidth_;
int frameHeight_;
media::VideoCaptureDeviceMac *frameReceiver_;
// QTKit variables.
QTCaptureSession *captureSession_;
QTCaptureDeviceInput *captureDeviceInput_;
}
// Returns an array of QTCaptureDevices.
// TODO(mflodman) Return arrays of friendly name and unique id instead.
+ (NSArray *)deviceNames;
// Initializes the instance and registers the frame receiver.
- (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver;
// Sets which capture device to use. Returns YES on sucess, NO otherwise.
- (BOOL)setCaptureDevice:(NSString *)deviceId;
// Configures the capture properties.
- (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate;
// Start video capturing. Returns YES on sucess, NO otherwise.
- (BOOL)startCapture;
// Stops video capturing.
- (void)stopCapture;
@end
#endif // MEDIA_VIDEO_CAPTURE_MAC_VIDEO_CAPTURE_DEVICE_MAC_QTKIT_H_

@ -0,0 +1,176 @@
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "media/video/capture/mac/video_capture_device_qtkit_mac.h"
#import <QTKit/QTKit.h>
#include "base/logging.h"
#include "media/video/capture/mac/video_capture_device_mac.h"
#include "media/video/capture/video_capture_device.h"
@implementation VideoCaptureDeviceQTKit
#pragma mark Class methods
+ (NSArray *)deviceNames {
return [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
}
#pragma mark Public methods
- (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac *)frameReceiver {
self = [super init];
if (self) {
frameReceiver_ = frameReceiver;
}
return self;
}
- (void)dealloc {
[captureSession_ release];
[captureDeviceInput_ release];
[super dealloc];
}
- (BOOL)setCaptureDevice:(NSString *)deviceId {
if (deviceId) {
// Set the capture device.
if (captureDeviceInput_) {
DLOG(ERROR) << "Video capture device already set.";
return NO;
}
NSArray *captureDevices =
[QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
NSArray *captureDevicesNames =
[captureDevices valueForKey:@"uniqueID"];
NSUInteger index = [captureDevicesNames indexOfObject:deviceId];
if (index == NSNotFound) {
DLOG(ERROR) << "Video capture device not found.";
return NO;
}
QTCaptureDevice *device = [captureDevices objectAtIndex:index];
NSError *error;
if (![device open:&error]) {
DLOG(ERROR) << "Could not open video capture device."
<< [[error localizedDescription] UTF8String];
return NO;
}
captureDeviceInput_ = [[QTCaptureDeviceInput alloc] initWithDevice:device];
captureSession_ = [[QTCaptureSession alloc] init];
QTCaptureDecompressedVideoOutput *captureDecompressedOutput =
[[[QTCaptureDecompressedVideoOutput alloc] init] autorelease];
[captureDecompressedOutput setDelegate:self];
if (![captureSession_ addOutput:captureDecompressedOutput error:&error]) {
DLOG(ERROR) << "Could not connect video capture output."
<< [[error localizedDescription] UTF8String];
return NO;
}
return YES;
} else {
// Remove the previously set capture device.
if (!captureDeviceInput_) {
DLOG(ERROR) << "No video capture device set.";
return YES;
}
if ([[captureSession_ inputs] count] > 0) {
// The device is still running.
[self stopCapture];
}
[captureSession_ release];
captureSession_ = nil;
[captureDeviceInput_ release];
captureDeviceInput_ = nil;
return YES;
}
}
- (BOOL)setCaptureHeight:(int)height width:(int)width frameRate:(int)frameRate {
if (!captureDeviceInput_) {
DLOG(ERROR) << "No video capture device set.";
return NO;
}
if ([[captureSession_ outputs] count] != 1) {
DLOG(ERROR) << "Video capture capabilities already set.";
return NO;
}
frameWidth_ = width;
frameHeight_ = height;
frameRate_ = frameRate;
// Set up desired output properties.
NSDictionary *captureDictionary =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:frameWidth_],
(id)kCVPixelBufferWidthKey,
[NSNumber numberWithDouble:frameHeight_],
(id)kCVPixelBufferHeightKey,
[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA],
(id)kCVPixelBufferPixelFormatTypeKey,
nil];
[[[captureSession_ outputs] objectAtIndex:0]
setPixelBufferAttributes:captureDictionary];
return YES;
}
- (BOOL)startCapture {
if ([[captureSession_ outputs] count] == 0) {
// Capture properties not set.
DLOG(ERROR) << "Video capture device not initialized.";
return NO;
}
if ([[captureSession_ inputs] count] == 0) {
NSError *error;
if (![captureSession_ addInput:captureDeviceInput_ error:&error]) {
DLOG(ERROR) << "Could not connect video capture device."
<< [[error localizedDescription] UTF8String];
return NO;
}
[captureSession_ startRunning];
}
return YES;
}
- (void)stopCapture {
if ([[captureSession_ inputs] count] == 1) {
[captureSession_ removeInput:captureDeviceInput_];
[captureSession_ stopRunning];
}
}
// |captureOutput| is called by the capture device to deliver a new frame.
- (void)captureOutput:(QTCaptureOutput *)captureOutput
didOutputVideoFrame:(CVImageBufferRef)videoFrame
withSampleBuffer:(QTSampleBuffer *)sampleBuffer
fromConnection:(QTCaptureConnection *)connection {
if(!frameReceiver_) {
return;
}
// Lock the frame and calculate frame size.
const int kLockFlags = 0;
if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags)
== kCVReturnSuccess) {
void *baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
int frameHeight = CVPixelBufferGetHeight(videoFrame);
int frameSize = bytesPerRow * frameHeight;
media::VideoCaptureDevice::Capability captureCapability;
captureCapability.width = frameWidth_;
captureCapability.height = frameHeight_;
captureCapability.frame_rate = frameRate_;
captureCapability.color = media::VideoCaptureDevice::kARGB;
// Deliver the captured video frame.
frameReceiver_->ReceiveFrame(static_cast<UInt8*>(baseAddress), frameSize,
captureCapability);
CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
}
}
@end

@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread.h"
@ -11,6 +12,21 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_MACOSX)
// The camera is 'locked' by the application once started on Mac OS X, not when
// allocated as for Windows and Linux, and this test case will fail.
#define MAYBE_AllocateSameCameraTwice DISABLED_AllocateSameCameraTwice
#else
#define MAYBE_AllocateSameCameraTwice AllocateSameCameraTwice
#endif
#if defined(OS_MACOSX)
// Mac/QTKit will always give you the size you ask for and this case will fail.
#define MAYBE_AllocateBadSize DISABLED_AllocateBadSize
#else
#define MAYBE_AllocateBadSize AllocateBadSize
#endif
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Return;
@ -18,7 +34,7 @@ using ::testing::AtLeast;
namespace media {
class MockFrameObserver: public media::VideoCaptureDevice::EventHandler {
class MockFrameObserver : public media::VideoCaptureDevice::EventHandler {
public:
MOCK_METHOD0(OnErr, void());
MOCK_METHOD3(OnFrameInfo, void(int width, int height, int frame_rate));
@ -48,9 +64,15 @@ class VideoCaptureDeviceTest : public testing::Test {
public:
VideoCaptureDeviceTest(): wait_event_(false, false) { }
void PostQuitTask() {
loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask);
loop_->Run();
}
protected:
virtual void SetUp() {
frame_observer_.reset(new MockFrameObserver(&wait_event_));
loop_.reset(new MessageLoopForUI());
}
virtual void TearDown() {
@ -59,6 +81,7 @@ class VideoCaptureDeviceTest : public testing::Test {
base::WaitableEvent wait_event_;
scoped_ptr<MockFrameObserver> frame_observer_;
VideoCaptureDevice::Names names_;
scoped_ptr<MessageLoop> loop_;
};
TEST_F(VideoCaptureDeviceTest, OpenInvalidDevice) {
@ -89,7 +112,8 @@ TEST_F(VideoCaptureDeviceTest, CaptureVGA) {
device->Allocate(640, 480, 30, frame_observer_.get());
device->Start();
// Wait for 3s or for captured frame.
// Get captured video frames.
PostQuitTask();
EXPECT_TRUE(wait_event_.TimedWait(base::TimeDelta::FromMilliseconds(
TestTimeouts::action_max_timeout_ms())));
device->Stop();
@ -119,13 +143,14 @@ TEST_F(VideoCaptureDeviceTest, Capture720p) {
device->Allocate(1280, 720, 30, frame_observer_.get());
device->Start();
// Get captured video frames.
PostQuitTask();
EXPECT_TRUE(wait_event_.TimedWait(base::TimeDelta::FromMilliseconds(
TestTimeouts::action_max_timeout_ms())));
device->Stop();
device->DeAllocate();
}
TEST_F(VideoCaptureDeviceTest, AllocateSameCameraTwice) {
TEST_F(VideoCaptureDeviceTest, MAYBE_AllocateSameCameraTwice) {
VideoCaptureDevice::GetDeviceNames(&names_);
if (!names_.size()) {
LOG(WARNING) << "No camera available. Exiting test.";
@ -152,7 +177,7 @@ TEST_F(VideoCaptureDeviceTest, AllocateSameCameraTwice) {
device2->DeAllocate();
}
TEST_F(VideoCaptureDeviceTest, AllocateBadSize) {
TEST_F(VideoCaptureDeviceTest, MAYBE_AllocateBadSize) {
VideoCaptureDevice::GetDeviceNames(&names_);
if (!names_.size()) {
LOG(WARNING) << "No camera available. Exiting test.";
@ -199,6 +224,7 @@ TEST_F(VideoCaptureDeviceTest, ReAllocateCamera) {
device->Start();
// Get captured video frames.
PostQuitTask();
EXPECT_TRUE(wait_event_.TimedWait(base::TimeDelta::FromMilliseconds(
TestTimeouts::action_max_timeout_ms())));
device->Stop();
@ -224,6 +250,7 @@ TEST_F(VideoCaptureDeviceTest, DeAllocateCameraWhileRunning) {
device->Start();
// Get captured video frames.
PostQuitTask();
EXPECT_TRUE(wait_event_.TimedWait(base::TimeDelta::FromMilliseconds(
TestTimeouts::action_max_timeout_ms())));
device->DeAllocate();