Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Side by Side Diff: media/capture/video/mac/video_capture_device_mac.mm

Issue 2214533002: move //media/capture to //device/capture (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/capture/video/mac/video_capture_device_mac.h"
6
7 #include <IOKit/IOCFPlugIn.h>
8 #include <IOKit/usb/IOUSBLib.h>
9 #include <IOKit/usb/USBSpec.h>
10 #include <stddef.h>
11 #include <stdint.h>
12
13 #include <limits>
14 #include <utility>
15
16 #include "base/bind.h"
17 #include "base/location.h"
18 #include "base/logging.h"
19 #include "base/mac/scoped_ioobject.h"
20 #include "base/mac/scoped_ioplugininterface.h"
21 #include "base/macros.h"
22 #include "base/single_thread_task_runner.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "base/time/time.h"
26 #import "media/base/mac/avfoundation_glue.h"
27 #include "media/base/timestamp_constants.h"
28 #import "media/capture/video/mac/video_capture_device_avfoundation_mac.h"
29 #include "ui/gfx/geometry/size.h"
30
31 @implementation DeviceNameAndTransportType
32
33 - (id)initWithName:(NSString*)deviceName transportType:(int32_t)transportType {
34 if (self = [super init]) {
35 deviceName_.reset([deviceName copy]);
36 transportType_ = transportType;
37 }
38 return self;
39 }
40
41 - (NSString*)deviceName {
42 return deviceName_;
43 }
44
45 - (int32_t)transportType {
46 return transportType_;
47 }
48
49 @end // @implementation DeviceNameAndTransportType
50
51 namespace media {
52
53 // Mac specific limits for minimum and maximum frame rate.
54 const float kMinFrameRate = 1.0f;
55 const float kMaxFrameRate = 30.0f;
56
57 // In device identifiers, the USB VID and PID are stored in 4 bytes each.
58 const size_t kVidPidSize = 4;
59
60 // The following constants are extracted from the specification "Universal
61 // Serial Bus Device Class Definition for Video Devices", Rev. 1.1 June 1, 2005.
62 // http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip
63 // CS_INTERFACE: Sec. A.4 "Video Class-Specific Descriptor Types".
64 const int kVcCsInterface = 0x24;
65 // VC_PROCESSING_UNIT: Sec. A.5 "Video Class-Specific VC Interface Descriptor
66 // Subtypes".
67 const int kVcProcessingUnit = 0x5;
68 // SET_CUR: Sec. A.8 "Video Class-Specific Request Codes".
69 const int kVcRequestCodeSetCur = 0x1;
70 // PU_POWER_LINE_FREQUENCY_CONTROL: Sec. A.9.5 "Processing Unit Control
71 // Selectors".
72 const int kPuPowerLineFrequencyControl = 0x5;
73 // Sec. 4.2.2.3.5 Power Line Frequency Control.
74 const int k50Hz = 1;
75 const int k60Hz = 2;
76 const int kPuPowerLineFrequencyControlCommandSize = 1;
77
78 // Addition to the IOUSB family of structures, with subtype and unit ID.
79 typedef struct IOUSBInterfaceDescriptor {
80 IOUSBDescriptorHeader header;
81 UInt8 bDescriptorSubType;
82 UInt8 bUnitID;
83 } IOUSBInterfaceDescriptor;
84
85 // Tries to create a user-side device interface for a given USB device. Returns
86 // true if interface was found and passes it back in |device_interface|. The
87 // caller should release |device_interface|.
88 static bool FindDeviceInterfaceInUsbDevice(
89 const int vendor_id,
90 const int product_id,
91 const io_service_t usb_device,
92 IOUSBDeviceInterface*** device_interface) {
93 // Create a plugin, i.e. a user-side controller to manipulate USB device.
94 IOCFPlugInInterface** plugin;
95 SInt32 score; // Unused, but required for IOCreatePlugInInterfaceForService.
96 kern_return_t kr = IOCreatePlugInInterfaceForService(
97 usb_device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin,
98 &score);
99 if (kr != kIOReturnSuccess || !plugin) {
100 DLOG(ERROR) << "IOCreatePlugInInterfaceForService";
101 return false;
102 }
103 base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_ref(plugin);
104
105 // Fetch the Device Interface from the plugin.
106 HRESULT res = (*plugin)->QueryInterface(
107 plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
108 reinterpret_cast<LPVOID*>(device_interface));
109 if (!SUCCEEDED(res) || !*device_interface) {
110 DLOG(ERROR) << "QueryInterface, couldn't create interface to USB";
111 return false;
112 }
113 return true;
114 }
115
116 // Tries to find a Video Control type interface inside a general USB device
117 // interface |device_interface|, and returns it in |video_control_interface| if
118 // found. The returned interface must be released in the caller.
119 static bool FindVideoControlInterfaceInDeviceInterface(
120 IOUSBDeviceInterface** device_interface,
121 IOCFPlugInInterface*** video_control_interface) {
122 // Create an iterator to the list of Video-AVControl interfaces of the device,
123 // then get the first interface in the list.
124 io_iterator_t interface_iterator;
125 IOUSBFindInterfaceRequest interface_request = {
126 .bInterfaceClass = kUSBVideoInterfaceClass,
127 .bInterfaceSubClass = kUSBVideoControlSubClass,
128 .bInterfaceProtocol = kIOUSBFindInterfaceDontCare,
129 .bAlternateSetting = kIOUSBFindInterfaceDontCare};
130 kern_return_t kr =
131 (*device_interface)
132 ->CreateInterfaceIterator(device_interface, &interface_request,
133 &interface_iterator);
134 if (kr != kIOReturnSuccess) {
135 DLOG(ERROR) << "Could not create an iterator to the device's interfaces.";
136 return false;
137 }
138 base::mac::ScopedIOObject<io_iterator_t> iterator_ref(interface_iterator);
139
140 // There should be just one interface matching the class-subclass desired.
141 io_service_t found_interface;
142 found_interface = IOIteratorNext(interface_iterator);
143 if (!found_interface) {
144 DLOG(ERROR) << "Could not find a Video-AVControl interface in the device.";
145 return false;
146 }
147 base::mac::ScopedIOObject<io_service_t> found_interface_ref(found_interface);
148
149 // Create a user side controller (i.e. a "plugin") for the found interface.
150 SInt32 score;
151 kr = IOCreatePlugInInterfaceForService(
152 found_interface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID,
153 video_control_interface, &score);
154 if (kr != kIOReturnSuccess || !*video_control_interface) {
155 DLOG(ERROR) << "IOCreatePlugInInterfaceForService";
156 return false;
157 }
158 return true;
159 }
160
161 // Creates a control interface for |plugin_interface| and produces a command to
162 // set the appropriate Power Line frequency for flicker removal.
163 static void SetAntiFlickerInVideoControlInterface(
164 IOCFPlugInInterface** plugin_interface,
165 const PowerLineFrequency frequency) {
166 // Create, the control interface for the found plugin, and release
167 // the intermediate plugin.
168 IOUSBInterfaceInterface** control_interface = NULL;
169 HRESULT res =
170 (*plugin_interface)
171 ->QueryInterface(plugin_interface,
172 CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
173 reinterpret_cast<LPVOID*>(&control_interface));
174 if (!SUCCEEDED(res) || !control_interface) {
175 DLOG(ERROR) << "Couldn’t create control interface";
176 return;
177 }
178 base::mac::ScopedIOPluginInterface<IOUSBInterfaceInterface>
179 control_interface_ref(control_interface);
180
181 // Find the device's unit ID presenting type 0x24 (kVcCsInterface) and
182 // subtype 0x5 (kVcProcessingUnit). Inside this unit is where we find the
183 // power line frequency removal setting, and this id is device dependent.
184 int real_unit_id = -1;
185 IOUSBDescriptorHeader* descriptor = NULL;
186 IOUSBInterfaceDescriptor* cs_descriptor = NULL;
187 IOUSBInterfaceInterface220** interface =
188 reinterpret_cast<IOUSBInterfaceInterface220**>(control_interface);
189 while ((descriptor = (*interface)
190 ->FindNextAssociatedDescriptor(interface, descriptor,
191 kUSBAnyDesc))) {
192 cs_descriptor = reinterpret_cast<IOUSBInterfaceDescriptor*>(descriptor);
193 if ((descriptor->bDescriptorType == kVcCsInterface) &&
194 (cs_descriptor->bDescriptorSubType == kVcProcessingUnit)) {
195 real_unit_id = cs_descriptor->bUnitID;
196 break;
197 }
198 }
199 DVLOG_IF(1, real_unit_id == -1)
200 << "This USB device doesn't seem to have a "
201 << " VC_PROCESSING_UNIT, anti-flicker not available";
202 if (real_unit_id == -1)
203 return;
204
205 if ((*control_interface)->USBInterfaceOpen(control_interface) !=
206 kIOReturnSuccess) {
207 DLOG(ERROR) << "Unable to open control interface";
208 return;
209 }
210
211 // Create the control request and launch it to the device's control interface.
212 // Note how the wIndex needs the interface number OR'ed in the lowest bits.
213 IOUSBDevRequest command;
214 command.bmRequestType =
215 USBmakebmRequestType(kUSBOut, kUSBClass, kUSBInterface);
216 command.bRequest = kVcRequestCodeSetCur;
217 UInt8 interface_number;
218 (*control_interface)
219 ->GetInterfaceNumber(control_interface, &interface_number);
220 command.wIndex = (real_unit_id << 8) | interface_number;
221 const int selector = kPuPowerLineFrequencyControl;
222 command.wValue = (selector << 8);
223 command.wLength = kPuPowerLineFrequencyControlCommandSize;
224 command.wLenDone = 0;
225 int power_line_flag_value =
226 (frequency == PowerLineFrequency::FREQUENCY_50HZ) ? k50Hz : k60Hz;
227 command.pData = &power_line_flag_value;
228
229 IOReturn ret =
230 (*control_interface)->ControlRequest(control_interface, 0, &command);
231 DLOG_IF(ERROR, ret != kIOReturnSuccess) << "Anti-flicker control request"
232 << " failed (0x" << std::hex << ret
233 << "), unit id: " << real_unit_id;
234 DVLOG_IF(1, ret == kIOReturnSuccess) << "Anti-flicker set to "
235 << static_cast<int>(frequency) << "Hz";
236
237 (*control_interface)->USBInterfaceClose(control_interface);
238 }
239
240 // Sets the flicker removal in a USB webcam identified by |vendor_id| and
241 // |product_id|, if available. The process includes first finding all USB
242 // devices matching the specified |vendor_id| and |product_id|; for each
243 // matching device, a device interface, and inside it a video control interface
244 // are created. The latter is used to a send a power frequency setting command.
245 static void SetAntiFlickerInUsbDevice(const int vendor_id,
246 const int product_id,
247 const PowerLineFrequency frequency) {
248 if (frequency == PowerLineFrequency::FREQUENCY_DEFAULT)
249 return;
250 DVLOG(1) << "Setting Power Line Frequency to " << static_cast<int>(frequency)
251 << " Hz, device " << std::hex << vendor_id << "-" << product_id;
252
253 // Compose a search dictionary with vendor and product ID.
254 CFMutableDictionaryRef query_dictionary =
255 IOServiceMatching(kIOUSBDeviceClassName);
256 CFDictionarySetValue(
257 query_dictionary, CFSTR(kUSBVendorName),
258 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendor_id));
259 CFDictionarySetValue(
260 query_dictionary, CFSTR(kUSBProductName),
261 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &product_id));
262
263 io_iterator_t usb_iterator;
264 kern_return_t kr = IOServiceGetMatchingServices(
265 kIOMasterPortDefault, query_dictionary, &usb_iterator);
266 if (kr != kIOReturnSuccess) {
267 DLOG(ERROR) << "No devices found with specified Vendor and Product ID.";
268 return;
269 }
270 base::mac::ScopedIOObject<io_iterator_t> usb_iterator_ref(usb_iterator);
271
272 while (io_service_t usb_device = IOIteratorNext(usb_iterator)) {
273 base::mac::ScopedIOObject<io_service_t> usb_device_ref(usb_device);
274
275 IOUSBDeviceInterface** device_interface = NULL;
276 if (!FindDeviceInterfaceInUsbDevice(vendor_id, product_id, usb_device,
277 &device_interface)) {
278 return;
279 }
280 base::mac::ScopedIOPluginInterface<IOUSBDeviceInterface>
281 device_interface_ref(device_interface);
282
283 IOCFPlugInInterface** video_control_interface = NULL;
284 if (!FindVideoControlInterfaceInDeviceInterface(device_interface,
285 &video_control_interface)) {
286 return;
287 }
288 base::mac::ScopedIOPluginInterface<IOCFPlugInInterface>
289 plugin_interface_ref(video_control_interface);
290
291 SetAntiFlickerInVideoControlInterface(video_control_interface, frequency);
292 }
293 }
294
295 VideoCaptureDeviceMac::VideoCaptureDeviceMac(
296 const VideoCaptureDeviceDescriptor& device_descriptor)
297 : device_descriptor_(device_descriptor),
298 task_runner_(base::ThreadTaskRunnerHandle::Get()),
299 state_(kNotInitialized),
300 capture_device_(nil),
301 weak_factory_(this) {}
302
303 VideoCaptureDeviceMac::~VideoCaptureDeviceMac() {
304 DCHECK(task_runner_->BelongsToCurrentThread());
305 }
306
307 void VideoCaptureDeviceMac::AllocateAndStart(
308 const VideoCaptureParams& params,
309 std::unique_ptr<VideoCaptureDevice::Client> client) {
310 DCHECK(task_runner_->BelongsToCurrentThread());
311 if (state_ != kIdle) {
312 return;
313 }
314
315 client_ = std::move(client);
316 if (device_descriptor_.capture_api == VideoCaptureApi::MACOSX_AVFOUNDATION)
317 LogMessage("Using AVFoundation for device: " +
318 device_descriptor_.display_name);
319
320 NSString* deviceId =
321 [NSString stringWithUTF8String:device_descriptor_.device_id.c_str()];
322
323 [capture_device_ setFrameReceiver:this];
324
325 if (![capture_device_ setCaptureDevice:deviceId]) {
326 SetErrorState(FROM_HERE, "Could not open capture device.");
327 return;
328 }
329
330 capture_format_.frame_size = params.requested_format.frame_size;
331 capture_format_.frame_rate =
332 std::max(kMinFrameRate,
333 std::min(params.requested_format.frame_rate, kMaxFrameRate));
334 // Leave the pixel format selection to AVFoundation. The pixel format
335 // will be passed to |ReceiveFrame|.
336 capture_format_.pixel_format = PIXEL_FORMAT_UNKNOWN;
337
338 if (!UpdateCaptureResolution())
339 return;
340
341 // Try setting the power line frequency removal (anti-flicker). The built-in
342 // cameras are normally suspended so the configuration must happen right
343 // before starting capture and during configuration.
344 const std::string device_model = GetDeviceModelId(
345 device_descriptor_.device_id, device_descriptor_.capture_api,
346 device_descriptor_.transport_type);
347 if (device_model.length() > 2 * kVidPidSize) {
348 std::string vendor_id = device_model.substr(0, kVidPidSize);
349 std::string model_id = device_model.substr(kVidPidSize + 1);
350 int vendor_id_as_int, model_id_as_int;
351 if (base::HexStringToInt(base::StringPiece(vendor_id), &vendor_id_as_int) &&
352 base::HexStringToInt(base::StringPiece(model_id), &model_id_as_int)) {
353 SetAntiFlickerInUsbDevice(vendor_id_as_int, model_id_as_int,
354 GetPowerLineFrequency(params));
355 }
356 }
357
358 if (![capture_device_ startCapture]) {
359 SetErrorState(FROM_HERE, "Could not start capture device.");
360 return;
361 }
362
363 state_ = kCapturing;
364 }
365
366 void VideoCaptureDeviceMac::StopAndDeAllocate() {
367 DCHECK(task_runner_->BelongsToCurrentThread());
368 DCHECK(state_ == kCapturing || state_ == kError) << state_;
369
370 [capture_device_ setCaptureDevice:nil];
371 [capture_device_ setFrameReceiver:nil];
372 client_.reset();
373 state_ = kIdle;
374 }
375
376 void VideoCaptureDeviceMac::TakePhoto(TakePhotoCallback callback) {
377 DCHECK(task_runner_->BelongsToCurrentThread());
378 DCHECK(state_ == kCapturing) << state_;
379
380 if (photo_callback_) // Only one picture can be in flight at a time.
381 return;
382
383 photo_callback_.reset(new TakePhotoCallback(std::move(callback)));
384 [capture_device_ takePhoto];
385 }
386
387 bool VideoCaptureDeviceMac::Init(VideoCaptureApi capture_api_type) {
388 DCHECK(task_runner_->BelongsToCurrentThread());
389 DCHECK_EQ(state_, kNotInitialized);
390
391 if (capture_api_type != VideoCaptureApi::MACOSX_AVFOUNDATION)
392 return false;
393
394 capture_device_.reset(
395 [[VideoCaptureDeviceAVFoundation alloc] initWithFrameReceiver:this]);
396
397 if (!capture_device_)
398 return false;
399
400 state_ = kIdle;
401 return true;
402 }
403
404 void VideoCaptureDeviceMac::ReceiveFrame(const uint8_t* video_frame,
405 int video_frame_length,
406 const VideoCaptureFormat& frame_format,
407 int aspect_numerator,
408 int aspect_denominator,
409 base::TimeDelta timestamp) {
410 if (capture_format_.frame_size != frame_format.frame_size) {
411 ReceiveError(FROM_HERE,
412 "Captured resolution " + frame_format.frame_size.ToString() +
413 ", and expected " + capture_format_.frame_size.ToString());
414 return;
415 }
416
417 client_->OnIncomingCapturedData(video_frame, video_frame_length, frame_format,
418 0, base::TimeTicks::Now(), timestamp);
419 }
420
421 void VideoCaptureDeviceMac::OnPhotoTaken(const uint8_t* image_data,
422 size_t image_length,
423 const std::string& mime_type) {
424 DCHECK(photo_callback_);
425 if (!image_data || !image_length) {
426 OnPhotoError();
427 return;
428 }
429
430 mojom::BlobPtr blob = mojom::Blob::New();
431 blob->data.assign(image_data, image_data + image_length);
432 blob->mime_type = mime_type;
433 photo_callback_->Run(std::move(blob));
434 photo_callback_.reset();
435 }
436
437 void VideoCaptureDeviceMac::OnPhotoError() {
438 DLOG(ERROR) << __FUNCTION__ << " error taking picture";
439 photo_callback_.reset();
440 }
441
442 void VideoCaptureDeviceMac::ReceiveError(
443 const tracked_objects::Location& from_here,
444 const std::string& reason) {
445 task_runner_->PostTask(
446 FROM_HERE, base::Bind(&VideoCaptureDeviceMac::SetErrorState,
447 weak_factory_.GetWeakPtr(), from_here, reason));
448 }
449
450 void VideoCaptureDeviceMac::LogMessage(const std::string& message) {
451 DCHECK(task_runner_->BelongsToCurrentThread());
452 if (client_)
453 client_->OnLog(message);
454 }
455
456 // static
457 std::string VideoCaptureDeviceMac::GetDeviceModelId(
458 const std::string& device_id,
459 VideoCaptureApi capture_api,
460 VideoCaptureTransportType transport_type) {
461 // Skip the AVFoundation's not USB nor built-in devices.
462 if (capture_api == VideoCaptureApi::MACOSX_AVFOUNDATION &&
463 transport_type != VideoCaptureTransportType::MACOSX_USB_OR_BUILT_IN)
464 return "";
465 if (capture_api == VideoCaptureApi::MACOSX_DECKLINK)
466 return "";
467 // Both PID and VID are 4 characters.
468 if (device_id.size() < 2 * kVidPidSize)
469 return "";
470
471 // The last characters of device id is a concatenation of VID and then PID.
472 const size_t vid_location = device_id.size() - 2 * kVidPidSize;
473 std::string id_vendor = device_id.substr(vid_location, kVidPidSize);
474 const size_t pid_location = device_id.size() - kVidPidSize;
475 std::string id_product = device_id.substr(pid_location, kVidPidSize);
476
477 return id_vendor + ":" + id_product;
478 }
479
480 void VideoCaptureDeviceMac::SetErrorState(
481 const tracked_objects::Location& from_here,
482 const std::string& reason) {
483 DCHECK(task_runner_->BelongsToCurrentThread());
484 state_ = kError;
485 client_->OnError(from_here, reason);
486 }
487
488 bool VideoCaptureDeviceMac::UpdateCaptureResolution() {
489 if (![capture_device_ setCaptureHeight:capture_format_.frame_size.height()
490 width:capture_format_.frame_size.width()
491 frameRate:capture_format_.frame_rate]) {
492 ReceiveError(FROM_HERE, "Could not configure capture device.");
493 return false;
494 }
495 return true;
496 }
497
498 } // namespace media
OLDNEW
« no previous file with comments | « media/capture/video/mac/video_capture_device_mac.h ('k') | media/capture/video/scoped_result_callback.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698