| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "media/capture/video/mac/video_capture_device_avfoundation_mac.h" | 5 #import "media/capture/video/mac/video_capture_device_avfoundation_mac.h" |
| 6 | 6 |
| 7 #import <CoreMedia/CoreMedia.h> | 7 #import <CoreMedia/CoreMedia.h> |
| 8 #import <CoreVideo/CoreVideo.h> | 8 #import <CoreVideo/CoreVideo.h> |
| 9 #include <stddef.h> | 9 #include <stddef.h> |
| 10 #include <stdint.h> | 10 #include <stdint.h> |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 88 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.MacBook.NumberOfDevices", | 88 UMA_HISTOGRAM_COUNTS("Media.VideoCapture.MacBook.NumberOfDevices", |
| 89 number_of_devices + number_of_suspended_devices); | 89 number_of_devices + number_of_suspended_devices); |
| 90 if (number_of_devices + number_of_suspended_devices == 0) { | 90 if (number_of_devices + number_of_suspended_devices == 0) { |
| 91 UMA_HISTOGRAM_ENUMERATION( | 91 UMA_HISTOGRAM_ENUMERATION( |
| 92 "Media.VideoCapture.MacBook.HardwareVersionWhenNoCamera", | 92 "Media.VideoCapture.MacBook.HardwareVersionWhenNoCamera", |
| 93 GetMacBookModel(model), MAX_MACBOOK_VERSION + 1); | 93 GetMacBookModel(model), MAX_MACBOOK_VERSION + 1); |
| 94 } | 94 } |
| 95 } | 95 } |
| 96 } | 96 } |
| 97 | 97 |
| 98 } // anonymous namespace |
| 99 |
| 98 // This function translates Mac Core Video pixel formats to Chromium pixel | 100 // This function translates Mac Core Video pixel formats to Chromium pixel |
| 99 // formats. | 101 // formats. |
| 100 media::VideoPixelFormat FourCCToChromiumPixelFormat(FourCharCode code) { | 102 media::VideoPixelFormat FourCCToChromiumPixelFormat(FourCharCode code) { |
| 101 switch (code) { | 103 switch (code) { |
| 102 case kCVPixelFormatType_422YpCbCr8: | 104 case kCVPixelFormatType_422YpCbCr8: |
| 103 return media::PIXEL_FORMAT_UYVY; | 105 return media::PIXEL_FORMAT_UYVY; |
| 104 case CoreMediaGlue::kCMPixelFormat_422YpCbCr8_yuvs: | 106 case CoreMediaGlue::kCMPixelFormat_422YpCbCr8_yuvs: |
| 105 return media::PIXEL_FORMAT_YUY2; | 107 return media::PIXEL_FORMAT_YUY2; |
| 106 case CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML: | 108 case CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML: |
| 107 return media::PIXEL_FORMAT_MJPEG; | 109 return media::PIXEL_FORMAT_MJPEG; |
| 108 default: | 110 default: |
| 109 return media::PIXEL_FORMAT_UNKNOWN; | 111 return media::PIXEL_FORMAT_UNKNOWN; |
| 110 } | 112 } |
| 111 } | 113 } |
| 112 | 114 |
| 113 // Extracts |base_address| and |length| out of a SampleBuffer. | |
| 114 void ExtractBaseAddressAndLength( | |
| 115 char** base_address, | |
| 116 size_t* length, | |
| 117 CoreMediaGlue::CMSampleBufferRef sample_buffer) { | |
| 118 CoreMediaGlue::CMBlockBufferRef block_buffer = | |
| 119 CoreMediaGlue::CMSampleBufferGetDataBuffer(sample_buffer); | |
| 120 DCHECK(block_buffer); | |
| 121 | |
| 122 size_t length_at_offset; | |
| 123 const OSStatus status = CoreMediaGlue::CMBlockBufferGetDataPointer( | |
| 124 block_buffer, 0, &length_at_offset, length, base_address); | |
| 125 DCHECK_EQ(noErr, status); | |
| 126 // Expect the (M)JPEG data to be available as a contiguous reference, i.e. | |
| 127 // not covered by multiple memory blocks. | |
| 128 DCHECK_EQ(length_at_offset, *length); | |
| 129 } | |
| 130 | |
| 131 } // anonymous namespace | |
| 132 | |
| 133 @implementation VideoCaptureDeviceAVFoundation | 115 @implementation VideoCaptureDeviceAVFoundation |
| 134 | 116 |
| 135 #pragma mark Class methods | 117 #pragma mark Class methods |
| 136 | 118 |
| 137 + (void)getDeviceNames:(NSMutableDictionary*)deviceNames { | 119 + (void)getDeviceNames:(NSMutableDictionary*)deviceNames { |
| 138 // At this stage we already know that AVFoundation is supported and the whole | 120 // At this stage we already know that AVFoundation is supported and the whole |
| 139 // library is loaded and initialised, by the device monitoring. | 121 // library is loaded and initialised, by the device monitoring. |
| 140 NSArray* devices = [AVCaptureDeviceGlue devices]; | 122 NSArray* devices = [AVCaptureDeviceGlue devices]; |
| 141 int number_of_suspended_devices = 0; | 123 int number_of_suspended_devices = 0; |
| 142 for (CrAVCaptureDevice* device in devices) { | 124 for (CrAVCaptureDevice* device in devices) { |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 228 if (!deviceId) { | 210 if (!deviceId) { |
| 229 // First stop the capture session, if it's running. | 211 // First stop the capture session, if it's running. |
| 230 [self stopCapture]; | 212 [self stopCapture]; |
| 231 // Now remove the input and output from the capture session. | 213 // Now remove the input and output from the capture session. |
| 232 [captureSession_ removeOutput:captureVideoDataOutput_]; | 214 [captureSession_ removeOutput:captureVideoDataOutput_]; |
| 233 if (captureDeviceInput_) { | 215 if (captureDeviceInput_) { |
| 234 [captureSession_ removeInput:captureDeviceInput_]; | 216 [captureSession_ removeInput:captureDeviceInput_]; |
| 235 // No need to release |captureDeviceInput_|, is owned by the session. | 217 // No need to release |captureDeviceInput_|, is owned by the session. |
| 236 captureDeviceInput_ = nil; | 218 captureDeviceInput_ = nil; |
| 237 } | 219 } |
| 238 if (stillImageOutput_) | |
| 239 [captureSession_ removeOutput:stillImageOutput_]; | |
| 240 return YES; | 220 return YES; |
| 241 } | 221 } |
| 242 | 222 |
| 243 // Look for input device with requested name. | 223 // Look for input device with requested name. |
| 244 captureDevice_ = [AVCaptureDeviceGlue deviceWithUniqueID:deviceId]; | 224 captureDevice_ = [AVCaptureDeviceGlue deviceWithUniqueID:deviceId]; |
| 245 if (!captureDevice_) { | 225 if (!captureDevice_) { |
| 246 [self | 226 [self |
| 247 sendErrorString:[NSString stringWithUTF8String: | 227 sendErrorString:[NSString stringWithUTF8String: |
| 248 "Could not open video capture device."]]; | 228 "Could not open video capture device."]]; |
| 249 return NO; | 229 return NO; |
| (...skipping 24 matching lines...) Expand all Loading... |
| 274 [self sendErrorString:[NSString stringWithUTF8String: | 254 [self sendErrorString:[NSString stringWithUTF8String: |
| 275 "Could not create video data output."]]; | 255 "Could not create video data output."]]; |
| 276 return NO; | 256 return NO; |
| 277 } | 257 } |
| 278 [captureVideoDataOutput_ setAlwaysDiscardsLateVideoFrames:true]; | 258 [captureVideoDataOutput_ setAlwaysDiscardsLateVideoFrames:true]; |
| 279 [captureVideoDataOutput_ | 259 [captureVideoDataOutput_ |
| 280 setSampleBufferDelegate:self | 260 setSampleBufferDelegate:self |
| 281 queue:dispatch_get_global_queue( | 261 queue:dispatch_get_global_queue( |
| 282 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; | 262 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; |
| 283 [captureSession_ addOutput:captureVideoDataOutput_]; | 263 [captureSession_ addOutput:captureVideoDataOutput_]; |
| 284 | |
| 285 // Create and plug the still image capture output. This should happen in | |
| 286 // advance of the actual picture to allow for the 3A to stabilize. | |
| 287 stillImageOutput_.reset( | |
| 288 [[AVFoundationGlue::AVCaptureStillImageOutputClass() alloc] init]); | |
| 289 if ([captureSession_ canAddOutput:stillImageOutput_]) | |
| 290 [captureSession_ addOutput:stillImageOutput_]; | |
| 291 | |
| 292 return YES; | 264 return YES; |
| 293 } | 265 } |
| 294 | 266 |
| 295 - (BOOL)setCaptureHeight:(int)height | 267 - (BOOL)setCaptureHeight:(int)height |
| 296 width:(int)width | 268 width:(int)width |
| 297 frameRate:(float)frameRate { | 269 frameRate:(float)frameRate { |
| 298 DCHECK(![captureSession_ isRunning] && | 270 DCHECK(![captureSession_ isRunning] && |
| 299 main_thread_checker_.CalledOnValidThread()); | 271 main_thread_checker_.CalledOnValidThread()); |
| 300 | 272 |
| 301 frameWidth_ = width; | 273 frameWidth_ = width; |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 380 return YES; | 352 return YES; |
| 381 } | 353 } |
| 382 | 354 |
| 383 - (void)stopCapture { | 355 - (void)stopCapture { |
| 384 DCHECK(main_thread_checker_.CalledOnValidThread()); | 356 DCHECK(main_thread_checker_.CalledOnValidThread()); |
| 385 if ([captureSession_ isRunning]) | 357 if ([captureSession_ isRunning]) |
| 386 [captureSession_ stopRunning]; // Synchronous. | 358 [captureSession_ stopRunning]; // Synchronous. |
| 387 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 359 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 388 } | 360 } |
| 389 | 361 |
| 390 - (void)takePhoto { | |
| 391 DCHECK(main_thread_checker_.CalledOnValidThread()); | |
| 392 DCHECK([captureSession_ isRunning]); | |
| 393 | |
| 394 DCHECK_EQ(1u, [[stillImageOutput_ connections] count]); | |
| 395 CrAVCaptureConnection* const connection = | |
| 396 [[stillImageOutput_ connections] firstObject]; | |
| 397 if (!connection) { | |
| 398 base::AutoLock lock(lock_); | |
| 399 frameReceiver_->OnPhotoError(); | |
| 400 return; | |
| 401 } | |
| 402 | |
| 403 const auto handler = ^(CoreMediaGlue::CMSampleBufferRef sampleBuffer, | |
| 404 NSError* error) { | |
| 405 base::AutoLock lock(lock_); | |
| 406 if (!frameReceiver_) | |
| 407 return; | |
| 408 if (error != nil) { | |
| 409 frameReceiver_->OnPhotoError(); | |
| 410 return; | |
| 411 } | |
| 412 | |
| 413 // Recommended compressed pixel format is JPEG, we don't expect surprises. | |
| 414 // TODO(mcasas): Consider using [1] for merging EXIF output information: | |
| 415 // [1] +(NSData*)jpegStillImageNSDataRepresentation:jpegSampleBuffer; | |
| 416 DCHECK_EQ( | |
| 417 CoreMediaGlue::kCMVideoCodecType_JPEG, | |
| 418 CoreMediaGlue::CMFormatDescriptionGetMediaSubType( | |
| 419 CoreMediaGlue::CMSampleBufferGetFormatDescription(sampleBuffer))); | |
| 420 | |
| 421 char* baseAddress = 0; | |
| 422 size_t length = 0; | |
| 423 ExtractBaseAddressAndLength(&baseAddress, &length, sampleBuffer); | |
| 424 frameReceiver_->OnPhotoTaken(reinterpret_cast<uint8_t*>(baseAddress), | |
| 425 length, "image/jpeg"); | |
| 426 }; | |
| 427 | |
| 428 [stillImageOutput_ captureStillImageAsynchronouslyFromConnection:connection | |
| 429 completionHandler:handler]; | |
| 430 } | |
| 431 | |
| 432 #pragma mark Private methods | 362 #pragma mark Private methods |
| 433 | 363 |
| 434 // |captureOutput| is called by the capture device to deliver a new frame. | 364 // |captureOutput| is called by the capture device to deliver a new frame. |
| 435 // AVFoundation calls from a number of threads, depending on, at least, if | 365 // AVFoundation calls from a number of threads, depending on, at least, if |
| 436 // Chrome is on foreground or background. | 366 // Chrome is on foreground or background. |
| 437 - (void)captureOutput:(CrAVCaptureOutput*)captureOutput | 367 - (void)captureOutput:(CrAVCaptureOutput*)captureOutput |
| 438 didOutputSampleBuffer:(CoreMediaGlue::CMSampleBufferRef)sampleBuffer | 368 didOutputSampleBuffer:(CoreMediaGlue::CMSampleBufferRef)sampleBuffer |
| 439 fromConnection:(CrAVCaptureConnection*)connection { | 369 fromConnection:(CrAVCaptureConnection*)connection { |
| 440 const CoreMediaGlue::CMFormatDescriptionRef formatDescription = | 370 const CoreMediaGlue::CMFormatDescriptionRef formatDescription = |
| 441 CoreMediaGlue::CMSampleBufferGetFormatDescription(sampleBuffer); | 371 CoreMediaGlue::CMSampleBufferGetFormatDescription(sampleBuffer); |
| 442 const FourCharCode fourcc = | 372 const FourCharCode fourcc = |
| 443 CoreMediaGlue::CMFormatDescriptionGetMediaSubType(formatDescription); | 373 CoreMediaGlue::CMFormatDescriptionGetMediaSubType(formatDescription); |
| 444 const CoreMediaGlue::CMVideoDimensions dimensions = | 374 const CoreMediaGlue::CMVideoDimensions dimensions = |
| 445 CoreMediaGlue::CMVideoFormatDescriptionGetDimensions(formatDescription); | 375 CoreMediaGlue::CMVideoFormatDescriptionGetDimensions(formatDescription); |
| 446 const media::VideoCaptureFormat captureFormat( | 376 const media::VideoCaptureFormat captureFormat( |
| 447 gfx::Size(dimensions.width, dimensions.height), frameRate_, | 377 gfx::Size(dimensions.width, dimensions.height), frameRate_, |
| 448 FourCCToChromiumPixelFormat(fourcc)); | 378 FourCCToChromiumPixelFormat(fourcc)); |
| 449 | 379 |
| 450 char* baseAddress = 0; | 380 char* baseAddress = 0; |
| 451 size_t frameSize = 0; | 381 size_t frameSize = 0; |
| 452 CVImageBufferRef videoFrame = nil; | 382 CVImageBufferRef videoFrame = nil; |
| 453 if (fourcc == CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML) { | 383 if (fourcc == CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML) { |
| 454 ExtractBaseAddressAndLength(&baseAddress, &frameSize, sampleBuffer); | 384 // If MJPEG, use block buffer instead of pixel buffer. |
| 385 CoreMediaGlue::CMBlockBufferRef blockBuffer = |
| 386 CoreMediaGlue::CMSampleBufferGetDataBuffer(sampleBuffer); |
| 387 if (blockBuffer) { |
| 388 size_t lengthAtOffset; |
| 389 CoreMediaGlue::CMBlockBufferGetDataPointer( |
| 390 blockBuffer, 0, &lengthAtOffset, &frameSize, &baseAddress); |
| 391 // Expect the MJPEG data to be available as a contiguous reference, i.e. |
| 392 // not covered by multiple memory blocks. |
| 393 CHECK_EQ(lengthAtOffset, frameSize); |
| 394 } |
| 455 } else { | 395 } else { |
| 456 videoFrame = CoreMediaGlue::CMSampleBufferGetImageBuffer(sampleBuffer); | 396 videoFrame = CoreMediaGlue::CMSampleBufferGetImageBuffer(sampleBuffer); |
| 457 // Lock the frame and calculate frame size. | 397 // Lock the frame and calculate frame size. |
| 458 if (CVPixelBufferLockBaseAddress(videoFrame, kCVPixelBufferLock_ReadOnly) == | 398 if (CVPixelBufferLockBaseAddress(videoFrame, kCVPixelBufferLock_ReadOnly) == |
| 459 kCVReturnSuccess) { | 399 kCVReturnSuccess) { |
| 460 baseAddress = static_cast<char*>(CVPixelBufferGetBaseAddress(videoFrame)); | 400 baseAddress = static_cast<char*>(CVPixelBufferGetBaseAddress(videoFrame)); |
| 461 frameSize = CVPixelBufferGetHeight(videoFrame) * | 401 frameSize = CVPixelBufferGetHeight(videoFrame) * |
| 462 CVPixelBufferGetBytesPerRow(videoFrame); | 402 CVPixelBufferGetBytesPerRow(videoFrame); |
| 463 } else { | 403 } else { |
| 464 videoFrame = nil; | 404 videoFrame = nil; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 496 } | 436 } |
| 497 | 437 |
| 498 - (void)sendErrorString:(NSString*)error { | 438 - (void)sendErrorString:(NSString*)error { |
| 499 DLOG(ERROR) << [error UTF8String]; | 439 DLOG(ERROR) << [error UTF8String]; |
| 500 base::AutoLock lock(lock_); | 440 base::AutoLock lock(lock_); |
| 501 if (frameReceiver_) | 441 if (frameReceiver_) |
| 502 frameReceiver_->ReceiveError(FROM_HERE, [error UTF8String]); | 442 frameReceiver_->ReceiveError(FROM_HERE, [error UTF8String]); |
| 503 } | 443 } |
| 504 | 444 |
| 505 @end | 445 @end |
| OLD | NEW |