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

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

Issue 1815983003: Remove deprecated QTKit Video Capture Support (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebased Created 4 years, 8 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 #import "media/capture/video/mac/video_capture_device_qtkit_mac.h"
6
7 #import <QTKit/QTKit.h>
8 #include <stddef.h>
9
10 #include "base/debug/crash_logging.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "media/base/timestamp_constants.h"
14 #include "media/base/video_capture_types.h"
15 #include "media/capture/video/mac/video_capture_device_mac.h"
16 #include "media/capture/video/video_capture_device.h"
17 #include "ui/gfx/geometry/size.h"
18
19 @implementation VideoCaptureDeviceQTKit
20
21 #pragma mark Class methods
22
23 + (void)getDeviceNames:(NSMutableDictionary*)deviceNames {
24 // Third-party drivers often throw exceptions. The following catches any
25 // exceptions and continues in an orderly fashion with no devices detected.
26 NSArray* captureDevices = nil;
27 @try {
28 captureDevices =
29 [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
30 } @catch (id exception) {
31 }
32
33 for (QTCaptureDevice* device in captureDevices) {
34 if ([[device attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue])
35 continue;
36 DeviceNameAndTransportType* nameAndTransportType = [[
37 [DeviceNameAndTransportType alloc]
38 initWithName:[device localizedDisplayName]
39 transportType:media::kIOAudioDeviceTransportTypeUnknown] autorelease];
40 [deviceNames setObject:nameAndTransportType forKey:[device uniqueID]];
41 }
42 }
43
44 + (NSDictionary*)deviceNames {
45 NSMutableDictionary* deviceNames =
46 [[[NSMutableDictionary alloc] init] autorelease];
47
48 // TODO(shess): Post to the main thread to see if that helps
49 // http://crbug.com/139164
50 [self performSelectorOnMainThread:@selector(getDeviceNames:)
51 withObject:deviceNames
52 waitUntilDone:YES];
53 return deviceNames;
54 }
55
56 #pragma mark Public methods
57
58 - (id)initWithFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
59 self = [super init];
60 if (self) {
61 frameReceiver_ = frameReceiver;
62 lock_ = [[NSLock alloc] init];
63 }
64 return self;
65 }
66
67 - (void)dealloc {
68 [captureSession_ release];
69 [captureDeviceInput_ release];
70 [super dealloc];
71 }
72
73 - (void)setFrameReceiver:(media::VideoCaptureDeviceMac*)frameReceiver {
74 [lock_ lock];
75 frameReceiver_ = frameReceiver;
76 [lock_ unlock];
77 }
78
79 - (BOOL)setCaptureDevice:(NSString*)deviceId {
80 if (deviceId) {
81 // Set the capture device.
82 if (captureDeviceInput_) {
83 DLOG(ERROR) << "Video capture device already set.";
84 return NO;
85 }
86
87 // TODO(mcasas): Consider using [QTCaptureDevice deviceWithUniqueID] instead
88 // of explicitly forcing reenumeration of devices.
89 NSArray* captureDevices =
90 [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
91 NSArray* captureDevicesNames = [captureDevices valueForKey:@"uniqueID"];
92 NSUInteger index = [captureDevicesNames indexOfObject:deviceId];
93 if (index == NSNotFound) {
94 [self sendErrorString:[NSString stringWithUTF8String:
95 "Video capture device not found."]];
96 return NO;
97 }
98 QTCaptureDevice* device = [captureDevices objectAtIndex:index];
99 if ([[device
100 attributeForKey:QTCaptureDeviceSuspendedAttribute] boolValue]) {
101 [self sendErrorString:
102 [NSString stringWithUTF8String:
103 "Cannot open suspended video capture device."]];
104 return NO;
105 }
106 NSError* error;
107 if (![device open:&error]) {
108 [self sendErrorString:
109 [NSString stringWithFormat:
110 @"Could not open video capture device (%@): %@",
111 [error localizedDescription],
112 [error localizedFailureReason]]];
113 return NO;
114 }
115 captureDeviceInput_ = [[QTCaptureDeviceInput alloc] initWithDevice:device];
116 captureSession_ = [[QTCaptureSession alloc] init];
117
118 QTCaptureDecompressedVideoOutput* captureDecompressedOutput =
119 [[[QTCaptureDecompressedVideoOutput alloc] init] autorelease];
120 [captureDecompressedOutput setDelegate:self];
121 [captureDecompressedOutput setAutomaticallyDropsLateVideoFrames:YES];
122 if (![captureSession_ addOutput:captureDecompressedOutput error:&error]) {
123 [self
124 sendErrorString:
125 [NSString stringWithFormat:
126 @"Could not connect video capture output (%@): %@",
127 [error localizedDescription],
128 [error localizedFailureReason]]];
129 return NO;
130 }
131
132 // This key can be used to check if video capture code was related to a
133 // particular crash.
134 base::debug::SetCrashKeyValue("VideoCaptureDeviceQTKit", "OpenedDevice");
135
136 // Set the video pixel format to 2VUY (a.k.a UYVY, packed 4:2:2).
137 NSDictionary* captureDictionary = [NSDictionary
138 dictionaryWithObject:
139 [NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8]
140 forKey:(id)kCVPixelBufferPixelFormatTypeKey];
141 [captureDecompressedOutput setPixelBufferAttributes:captureDictionary];
142
143 return YES;
144 } else {
145 // Remove the previously set capture device.
146 if (!captureDeviceInput_) {
147 // Being here means stopping a device that never started OK in the first
148 // place, log it.
149 [self sendLogString:[NSString
150 stringWithUTF8String:
151 "No video capture device set, on removal."]];
152 return YES;
153 }
154 // Tear down input and output, stop the capture and deregister observers.
155 [self stopCapture];
156 [captureSession_ release];
157 captureSession_ = nil;
158 [captureDeviceInput_ release];
159 captureDeviceInput_ = nil;
160 return YES;
161 }
162 }
163
164 - (BOOL)setCaptureHeight:(int)height
165 width:(int)width
166 frameRate:(float)frameRate {
167 if (!captureDeviceInput_) {
168 [self sendErrorString:
169 [NSString stringWithUTF8String:"No video capture device set."]];
170 return NO;
171 }
172 if ([[captureSession_ outputs] count] != 1) {
173 [self sendErrorString:[NSString
174 stringWithUTF8String:
175 "Video capture capabilities already set."]];
176 return NO;
177 }
178 if (frameRate <= 0.0f) {
179 [self sendErrorString:[NSString stringWithUTF8String:"Wrong frame rate."]];
180 return NO;
181 }
182
183 frameRate_ = frameRate;
184
185 QTCaptureDecompressedVideoOutput* output =
186 [[captureSession_ outputs] objectAtIndex:0];
187
188 // Set up desired output properties. The old capture dictionary is used to
189 // retrieve the initial pixel format, which must be maintained.
190 NSDictionary* videoSettingsDictionary = @{
191 (id)kCVPixelBufferWidthKey : @(width), (id)
192 kCVPixelBufferHeightKey : @(height), (id)
193 kCVPixelBufferPixelFormatTypeKey : [[output pixelBufferAttributes]
194 valueForKey:(id)kCVPixelBufferPixelFormatTypeKey]
195 };
196 [output setPixelBufferAttributes:videoSettingsDictionary];
197
198 [output setMinimumVideoFrameInterval:(NSTimeInterval)1 / frameRate];
199 return YES;
200 }
201
202 - (BOOL)startCapture {
203 if ([[captureSession_ outputs] count] == 0) {
204 // Capture properties not set.
205 [self
206 sendErrorString:[NSString stringWithUTF8String:
207 "Video capture device not initialized."]];
208 return NO;
209 }
210 if ([[captureSession_ inputs] count] == 0) {
211 NSError* error;
212 if (![captureSession_ addInput:captureDeviceInput_ error:&error]) {
213 [self
214 sendErrorString:
215 [NSString stringWithFormat:
216 @"Could not connect video capture device (%@): %@",
217 [error localizedDescription],
218 [error localizedFailureReason]]];
219
220 return NO;
221 }
222 NSNotificationCenter* notificationCenter =
223 [NSNotificationCenter defaultCenter];
224 [notificationCenter addObserver:self
225 selector:@selector(handleNotification:)
226 name:QTCaptureSessionRuntimeErrorNotification
227 object:captureSession_];
228 [captureSession_ startRunning];
229 }
230 return YES;
231 }
232
233 - (void)stopCapture {
234 // QTKit achieves thread safety and asynchronous execution by posting messages
235 // to the main thread, e.g. -addOutput:. Both -removeOutput: and -removeInput:
236 // post a message to the main thread while holding a lock that the
237 // notification handler might need. To avoid a deadlock, we perform those
238 // tasks in the main thread. See bugs http://crbug.com/152757 and
239 // http://crbug.com/399792.
240 [self performSelectorOnMainThread:@selector(stopCaptureOnUIThread:)
241 withObject:nil
242 waitUntilDone:YES];
243 [[NSNotificationCenter defaultCenter] removeObserver:self];
244 }
245
246 - (void)stopCaptureOnUIThread:(id)dummy {
247 if ([[captureSession_ inputs] count] > 0) {
248 DCHECK_EQ([[captureSession_ inputs] count], 1u);
249 [captureSession_ removeInput:captureDeviceInput_];
250 [captureSession_ stopRunning];
251 }
252 if ([[captureSession_ outputs] count] > 0) {
253 DCHECK_EQ([[captureSession_ outputs] count], 1u);
254 id output = [[captureSession_ outputs] objectAtIndex:0];
255 [output setDelegate:nil];
256 [captureSession_ removeOutput:output];
257 }
258 }
259
260 // |captureOutput| is called by the capture device to deliver a new frame.
261 - (void)captureOutput:(QTCaptureOutput*)captureOutput
262 didOutputVideoFrame:(CVImageBufferRef)videoFrame
263 withSampleBuffer:(QTSampleBuffer*)sampleBuffer
264 fromConnection:(QTCaptureConnection*)connection {
265 [lock_ lock];
266 if (!frameReceiver_) {
267 [lock_ unlock];
268 return;
269 }
270
271 // Lock the frame and calculate frame size.
272 const int kLockFlags = 0;
273 if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags) ==
274 kCVReturnSuccess) {
275 void* baseAddress = CVPixelBufferGetBaseAddress(videoFrame);
276 size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame);
277 size_t frameWidth = CVPixelBufferGetWidth(videoFrame);
278 size_t frameHeight = CVPixelBufferGetHeight(videoFrame);
279 size_t frameSize = bytesPerRow * frameHeight;
280
281 // TODO(shess): bytesPerRow may not correspond to frameWidth_*2,
282 // but VideoCaptureController::OnIncomingCapturedData() requires
283 // it to do so. Plumbing things through is intrusive, for now
284 // just deliver an adjusted buffer.
285 // TODO(nick): This workaround could probably be eliminated by using
286 // VideoCaptureController::OnIncomingCapturedVideoFrame, which supports
287 // pitches.
288 UInt8* addressToPass = static_cast<UInt8*>(baseAddress);
289 // UYVY is 2 bytes per pixel.
290 size_t expectedBytesPerRow = frameWidth * 2;
291 if (bytesPerRow > expectedBytesPerRow) {
292 // TODO(shess): frameHeight and frameHeight_ are not the same,
293 // try to do what the surrounding code seems to assume.
294 // Ironically, captureCapability and frameSize are ignored
295 // anyhow.
296 adjustedFrame_.resize(expectedBytesPerRow * frameHeight);
297 // std::vector is contiguous according to standard.
298 UInt8* adjustedAddress = &adjustedFrame_[0];
299
300 for (size_t y = 0; y < frameHeight; ++y) {
301 memcpy(adjustedAddress + y * expectedBytesPerRow,
302 addressToPass + y * bytesPerRow, expectedBytesPerRow);
303 }
304
305 addressToPass = adjustedAddress;
306 frameSize = frameHeight * expectedBytesPerRow;
307 }
308
309 media::VideoCaptureFormat captureFormat(
310 gfx::Size(frameWidth, frameHeight), frameRate_,
311 media::PIXEL_FORMAT_UYVY);
312
313 // The aspect ratio dictionary is often missing, in which case we report
314 // a pixel aspect ratio of 0:0.
315 int aspectNumerator = 0, aspectDenominator = 0;
316 CFDictionaryRef aspectRatioDict = (CFDictionaryRef)CVBufferGetAttachment(
317 videoFrame, kCVImageBufferPixelAspectRatioKey, NULL);
318 if (aspectRatioDict) {
319 CFNumberRef aspectNumeratorRef = (CFNumberRef)CFDictionaryGetValue(
320 aspectRatioDict, kCVImageBufferPixelAspectRatioHorizontalSpacingKey);
321 CFNumberRef aspectDenominatorRef = (CFNumberRef)CFDictionaryGetValue(
322 aspectRatioDict, kCVImageBufferPixelAspectRatioVerticalSpacingKey);
323 DCHECK(aspectNumeratorRef && aspectDenominatorRef)
324 << "Aspect Ratio dictionary missing its entries.";
325 CFNumberGetValue(aspectNumeratorRef, kCFNumberIntType, &aspectNumerator);
326 CFNumberGetValue(aspectDenominatorRef, kCFNumberIntType,
327 &aspectDenominator);
328 }
329
330 // Deliver the captured video frame.
331 const QTTime qt_timestamp = [sampleBuffer presentationTime];
332 base::TimeDelta timestamp;
333 if (!(qt_timestamp.flags & kQTTimeIsIndefinite) && qt_timestamp.timeScale) {
334 timestamp = base::TimeDelta::FromMicroseconds(
335 qt_timestamp.timeValue * base::TimeTicks::kMicrosecondsPerSecond /
336 qt_timestamp.timeScale);
337 } else {
338 timestamp = media::kNoTimestamp();
339 }
340 frameReceiver_->ReceiveFrame(addressToPass, frameSize, captureFormat,
341 aspectNumerator, aspectDenominator, timestamp);
342
343 CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags);
344 }
345 [lock_ unlock];
346 }
347
348 - (void)handleNotification:(NSNotification*)errorNotification {
349 NSError* error = (NSError*)
350 [[errorNotification userInfo] objectForKey:QTCaptureSessionErrorKey];
351 [self sendErrorString:
352 [NSString stringWithFormat:@"%@: %@", [error localizedDescription],
353 [error localizedFailureReason]]];
354 }
355
356 - (void)sendErrorString:(NSString*)error {
357 DLOG(ERROR) << [error UTF8String];
358 [lock_ lock];
359 if (frameReceiver_)
360 frameReceiver_->ReceiveError(FROM_HERE, [error UTF8String]);
361 [lock_ unlock];
362 }
363
364 - (void)sendLogString:(NSString*)message {
365 DVLOG(1) << [message UTF8String];
366 [lock_ lock];
367 if (frameReceiver_)
368 frameReceiver_->LogMessage([message UTF8String]);
369 [lock_ unlock];
370 }
371
372 @end
OLDNEW
« no previous file with comments | « media/capture/video/mac/video_capture_device_qtkit_mac.h ('k') | media/capture/video/video_capture_device.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698