blob: 25df7588bcc69ef972fed58343f398dfe372bdd7 [file] [log] [blame]
// Copyright 2014 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_factory_mac.h"
#import <IOKit/audio/IOAudioTypes.h>
#include "base/bind.h"
#include "base/location.h"
#include "base/strings/string_util.h"
#include "base/task_runner_util.h"
#import "media/base/mac/avfoundation_glue.h"
#import "media/video/capture/mac/video_capture_device_avfoundation_mac.h"
#include "media/video/capture/mac/video_capture_device_mac.h"
#import "media/video/capture/mac/video_capture_device_decklink_mac.h"
#import "media/video/capture/mac/video_capture_device_qtkit_mac.h"
namespace media {
// Some devices are known to crash if VGA is requested: http://crbug.com/396812;
// for them HD is the only supported resolution. These devices are identified by
// a characteristic trailing substring of uniqueId and by (part of) the vendor's
// name.
const struct NameAndVid {
const char* name;
const int capture_width;
const int capture_height;
const float capture_frame_rate;
} kBlacklistedCameras[] = { {"Blackmagic", 1280, 720, 60.0f } };
static scoped_ptr<media::VideoCaptureDevice::Names>
EnumerateDevicesUsingQTKit() {
scoped_ptr<VideoCaptureDevice::Names> device_names(
new VideoCaptureDevice::Names());
NSMutableDictionary* capture_devices =
[[[NSMutableDictionary alloc] init] autorelease];
[VideoCaptureDeviceQTKit getDeviceNames:capture_devices];
for (NSString* key in capture_devices) {
VideoCaptureDevice::Name name(
[[[capture_devices valueForKey:key] deviceName] UTF8String],
[key UTF8String], VideoCaptureDevice::Name::QTKIT);
for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
if (name.id().find(kBlacklistedCameras[i].name) != std::string::npos) {
DVLOG(2) << "Found blacklisted camera: " << name.id();
name.set_is_blacklisted(true);
break;
}
}
device_names->push_back(name);
}
return device_names.Pass();
}
static void RunDevicesEnumeratedCallback(
const base::Callback<void(scoped_ptr<media::VideoCaptureDevice::Names>)>&
callback,
scoped_ptr<media::VideoCaptureDevice::Names> device_names) {
callback.Run(device_names.Pass());
}
// static
bool VideoCaptureDeviceFactoryMac::PlatformSupportsAVFoundation() {
return AVFoundationGlue::IsAVFoundationSupported();
}
VideoCaptureDeviceFactoryMac::VideoCaptureDeviceFactoryMac(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
: ui_task_runner_(ui_task_runner) {
thread_checker_.DetachFromThread();
}
VideoCaptureDeviceFactoryMac::~VideoCaptureDeviceFactoryMac() {}
scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryMac::Create(
const VideoCaptureDevice::Name& device_name) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(device_name.capture_api_type(),
VideoCaptureDevice::Name::API_TYPE_UNKNOWN);
// Check device presence only for AVFoundation API, since it is too expensive
// and brittle for QTKit. The actual initialization at device level will fail
// subsequently if the device is not present.
if (AVFoundationGlue::IsAVFoundationSupported()) {
scoped_ptr<VideoCaptureDevice::Names> device_names(
new VideoCaptureDevice::Names());
GetDeviceNames(device_names.get());
VideoCaptureDevice::Names::iterator it = device_names->begin();
for (; it != device_names->end(); ++it) {
if (it->id() == device_name.id())
break;
}
if (it == device_names->end())
return scoped_ptr<VideoCaptureDevice>();
}
scoped_ptr<VideoCaptureDevice> capture_device;
if (device_name.capture_api_type() == VideoCaptureDevice::Name::DECKLINK) {
capture_device.reset(new VideoCaptureDeviceDeckLinkMac(device_name));
} else {
VideoCaptureDeviceMac* device = new VideoCaptureDeviceMac(device_name);
capture_device.reset(device);
if (!device->Init(device_name.capture_api_type())) {
LOG(ERROR) << "Could not initialize VideoCaptureDevice.";
capture_device.reset();
}
}
return scoped_ptr<VideoCaptureDevice>(capture_device.Pass());
}
void VideoCaptureDeviceFactoryMac::GetDeviceNames(
VideoCaptureDevice::Names* device_names) {
DCHECK(thread_checker_.CalledOnValidThread());
// Loop through all available devices and add to |device_names|.
NSDictionary* capture_devices;
if (AVFoundationGlue::IsAVFoundationSupported()) {
DVLOG(1) << "Enumerating video capture devices using AVFoundation";
capture_devices = [VideoCaptureDeviceAVFoundation deviceNames];
// Enumerate all devices found by AVFoundation, translate the info for each
// to class Name and add it to |device_names|.
for (NSString* key in capture_devices) {
int transport_type = [[capture_devices valueForKey:key] transportType];
// Transport types are defined for Audio devices and reused for video.
VideoCaptureDevice::Name::TransportType device_transport_type =
(transport_type == kIOAudioDeviceTransportTypeBuiltIn ||
transport_type == kIOAudioDeviceTransportTypeUSB)
? VideoCaptureDevice::Name::USB_OR_BUILT_IN
: VideoCaptureDevice::Name::OTHER_TRANSPORT;
VideoCaptureDevice::Name name(
[[[capture_devices valueForKey:key] deviceName] UTF8String],
[key UTF8String], VideoCaptureDevice::Name::AVFOUNDATION,
device_transport_type);
device_names->push_back(name);
}
// Also retrieve Blackmagic devices, if present, via DeckLink SDK API.
VideoCaptureDeviceDeckLinkMac::EnumerateDevices(device_names);
} else {
// We should not enumerate QTKit devices in Device Thread;
NOTREACHED();
}
}
void VideoCaptureDeviceFactoryMac::EnumerateDeviceNames(const base::Callback<
void(scoped_ptr<media::VideoCaptureDevice::Names>)>& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (AVFoundationGlue::IsAVFoundationSupported()) {
scoped_ptr<VideoCaptureDevice::Names> device_names(
new VideoCaptureDevice::Names());
GetDeviceNames(device_names.get());
callback.Run(device_names.Pass());
} else {
DVLOG(1) << "Enumerating video capture devices using QTKit";
base::PostTaskAndReplyWithResult(ui_task_runner_.get(), FROM_HERE,
base::Bind(&EnumerateDevicesUsingQTKit),
base::Bind(&RunDevicesEnumeratedCallback, callback));
}
}
void VideoCaptureDeviceFactoryMac::GetDeviceSupportedFormats(
const VideoCaptureDevice::Name& device,
VideoCaptureFormats* supported_formats) {
DCHECK(thread_checker_.CalledOnValidThread());
switch (device.capture_api_type()) {
case VideoCaptureDevice::Name::AVFOUNDATION:
DVLOG(1) << "Enumerating video capture capabilities, AVFoundation";
[VideoCaptureDeviceAVFoundation getDevice:device
supportedFormats:supported_formats];
break;
case VideoCaptureDevice::Name::QTKIT:
// Blacklisted cameras provide their own supported format(s), otherwise no
// such information is provided for QTKit.
if (device.is_blacklisted()) {
for (size_t i = 0; i < arraysize(kBlacklistedCameras); ++i) {
if (device.id().find(kBlacklistedCameras[i].name) !=
std::string::npos) {
supported_formats->push_back(media::VideoCaptureFormat(
gfx::Size(kBlacklistedCameras[i].capture_width,
kBlacklistedCameras[i].capture_height),
kBlacklistedCameras[i].capture_frame_rate,
media::PIXEL_FORMAT_UYVY));
break;
}
}
}
break;
case VideoCaptureDevice::Name::DECKLINK:
DVLOG(1) << "Enumerating video capture capabilities " << device.name();
VideoCaptureDeviceDeckLinkMac::EnumerateDeviceCapabilities(
device, supported_formats);
break;
default:
NOTREACHED();
}
}
} // namespace media