diff options
author | mflodman@chromium.org <mflodman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-18 06:09:45 +0000 |
---|---|---|
committer | mflodman@chromium.org <mflodman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-18 06:09:45 +0000 |
commit | 58dffbf95cddeb596a0e059df95441772cbd1a5f (patch) | |
tree | 22ab6ff479d3b4c2fc21251d2c712ad473e2968f /media | |
parent | 45051c3efbb736afe330144c1decfdffbbe26fae (diff) | |
download | chromium_src-58dffbf95cddeb596a0e059df95441772cbd1a5f.zip chromium_src-58dffbf95cddeb596a0e059df95441772cbd1a5f.tar.gz chromium_src-58dffbf95cddeb596a0e059df95441772cbd1a5f.tar.bz2 |
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
Diffstat (limited to 'media')
-rw-r--r-- | media/media.gyp | 8 | ||||
-rw-r--r-- | media/video/capture/mac/video_capture_device_mac.h | 65 | ||||
-rw-r--r-- | media/video/capture/mac/video_capture_device_mac.mm | 146 | ||||
-rw-r--r-- | media/video/capture/mac/video_capture_device_qtkit_mac.h | 55 | ||||
-rw-r--r-- | media/video/capture/mac/video_capture_device_qtkit_mac.mm | 176 | ||||
-rw-r--r-- | media/video/capture/video_capture_device_unittest.cc | 35 |
6 files changed, 480 insertions, 5 deletions
diff --git a/media/media.gyp b/media/media.gyp index 983705a..ae6e396 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -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', ], }, }], diff --git a/media/video/capture/mac/video_capture_device_mac.h b/media/video/capture/mac/video_capture_device_mac.h new file mode 100644 index 0000000..c15fcec --- /dev/null +++ b/media/video/capture/mac/video_capture_device_mac.h @@ -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_ diff --git a/media/video/capture/mac/video_capture_device_mac.mm b/media/video/capture/mac/video_capture_device_mac.mm new file mode 100644 index 0000000..b15a810 --- /dev/null +++ b/media/video/capture/mac/video_capture_device_mac.mm @@ -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 diff --git a/media/video/capture/mac/video_capture_device_qtkit_mac.h b/media/video/capture/mac/video_capture_device_qtkit_mac.h new file mode 100644 index 0000000..c5378ae --- /dev/null +++ b/media/video/capture/mac/video_capture_device_qtkit_mac.h @@ -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_ diff --git a/media/video/capture/mac/video_capture_device_qtkit_mac.mm b/media/video/capture/mac/video_capture_device_qtkit_mac.mm new file mode 100644 index 0000000..efbee0f --- /dev/null +++ b/media/video/capture/mac/video_capture_device_qtkit_mac.mm @@ -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 diff --git a/media/video/capture/video_capture_device_unittest.cc b/media/video/capture/video_capture_device_unittest.cc index 7cb40bc..ae8ca12 100644 --- a/media/video/capture/video_capture_device_unittest.cc +++ b/media/video/capture/video_capture_device_unittest.cc @@ -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(); |