| 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/video/capture/mac/video_capture_device_avfoundation_mac.h" | 5 #import "media/video/capture/mac/video_capture_device_avfoundation_mac.h" |
| 6 | 6 |
| 7 #import <CoreVideo/CoreVideo.h> | 7 #import <CoreVideo/CoreVideo.h> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/mac/foundation_util.h" | 10 #include "base/mac/foundation_util.h" |
| 11 #include "media/video/capture/mac/video_capture_device_mac.h" | 11 #include "media/video/capture/mac/video_capture_device_mac.h" |
| 12 #include "ui/gfx/geometry/size.h" | 12 #include "ui/gfx/geometry/size.h" |
| 13 | 13 |
| 14 // Prefer MJPEG if frame width or height is larger than this. | |
| 15 static const int kMjpegWidthThreshold = 640; | |
| 16 static const int kMjpegHeightThreshold = 480; | |
| 17 | |
| 18 // This function translates Mac Core Video pixel formats to Chromium pixel | |
| 19 // formats. Chromium pixel formats are sorted in order of preference. | |
| 20 media::VideoPixelFormat FourCCToChromiumPixelFormat(FourCharCode code) { | |
| 21 switch (code) { | |
| 22 case kCVPixelFormatType_422YpCbCr8: | |
| 23 return media::PIXEL_FORMAT_UYVY; | |
| 24 case CoreMediaGlue::kCMPixelFormat_422YpCbCr8_yuvs: | |
| 25 return media::PIXEL_FORMAT_YUY2; | |
| 26 case CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML: | |
| 27 return media::PIXEL_FORMAT_MJPEG; | |
| 28 case CoreMediaGlue::kCMPixelFormat_420YpCbCr8BiPlanarVideoRange: | |
| 29 return media::PIXEL_FORMAT_NV12; | |
| 30 default: | |
| 31 return media::PIXEL_FORMAT_UNKNOWN; | |
| 32 } | |
| 33 } | |
| 34 | |
| 35 @implementation VideoCaptureDeviceAVFoundation | 14 @implementation VideoCaptureDeviceAVFoundation |
| 36 | 15 |
| 37 #pragma mark Class methods | 16 #pragma mark Class methods |
| 38 | 17 |
| 39 + (void)getDeviceNames:(NSMutableDictionary*)deviceNames { | 18 + (void)getDeviceNames:(NSMutableDictionary*)deviceNames { |
| 40 // At this stage we already know that AVFoundation is supported and the whole | 19 // At this stage we already know that AVFoundation is supported and the whole |
| 41 // library is loaded and initialised, by the device monitoring. | 20 // library is loaded and initialised, by the device monitoring. |
| 42 NSArray* devices = [AVCaptureDeviceGlue devices]; | 21 NSArray* devices = [AVCaptureDeviceGlue devices]; |
| 43 for (CrAVCaptureDevice* device in devices) { | 22 for (CrAVCaptureDevice* device in devices) { |
| 44 if (([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()] || | 23 if (([device hasMediaType:AVFoundationGlue::AVMediaTypeVideo()] || |
| (...skipping 24 matching lines...) Expand all Loading... |
| 69 CrAVCaptureDevice* device = nil; | 48 CrAVCaptureDevice* device = nil; |
| 70 for (device in devices) { | 49 for (device in devices) { |
| 71 if ([[device uniqueID] UTF8String] == name.id()) | 50 if ([[device uniqueID] UTF8String] == name.id()) |
| 72 break; | 51 break; |
| 73 } | 52 } |
| 74 if (device == nil) | 53 if (device == nil) |
| 75 return; | 54 return; |
| 76 for (CrAVCaptureDeviceFormat* format in device.formats) { | 55 for (CrAVCaptureDeviceFormat* format in device.formats) { |
| 77 // MediaSubType is a CMPixelFormatType but can be used as CVPixelFormatType | 56 // MediaSubType is a CMPixelFormatType but can be used as CVPixelFormatType |
| 78 // as well according to CMFormatDescription.h | 57 // as well according to CMFormatDescription.h |
| 79 const media::VideoPixelFormat pixelFormat = FourCCToChromiumPixelFormat( | 58 media::VideoPixelFormat pixelFormat = media::PIXEL_FORMAT_UNKNOWN; |
| 80 CoreMediaGlue::CMFormatDescriptionGetMediaSubType( | 59 switch (CoreMediaGlue::CMFormatDescriptionGetMediaSubType( |
| 81 [format formatDescription])); | 60 [format formatDescription])) { |
| 61 case kCVPixelFormatType_422YpCbCr8: // Typical. |
| 62 pixelFormat = media::PIXEL_FORMAT_UYVY; |
| 63 break; |
| 64 case CoreMediaGlue::kCMPixelFormat_422YpCbCr8_yuvs: |
| 65 pixelFormat = media::PIXEL_FORMAT_YUY2; |
| 66 break; |
| 67 case CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML: |
| 68 pixelFormat = media::PIXEL_FORMAT_MJPEG; |
| 69 default: |
| 70 break; |
| 71 } |
| 82 | 72 |
| 83 CoreMediaGlue::CMVideoDimensions dimensions = | 73 CoreMediaGlue::CMVideoDimensions dimensions = |
| 84 CoreMediaGlue::CMVideoFormatDescriptionGetDimensions( | 74 CoreMediaGlue::CMVideoFormatDescriptionGetDimensions( |
| 85 [format formatDescription]); | 75 [format formatDescription]); |
| 86 | 76 |
| 87 for (CrAVFrameRateRange* frameRate in | 77 for (CrAVFrameRateRange* frameRate in |
| 88 [format videoSupportedFrameRateRanges]) { | 78 [format videoSupportedFrameRateRanges]) { |
| 89 media::VideoCaptureFormat format( | 79 media::VideoCaptureFormat format( |
| 90 gfx::Size(dimensions.width, dimensions.height), | 80 gfx::Size(dimensions.width, dimensions.height), |
| 91 frameRate.maxFrameRate, | 81 frameRate.maxFrameRate, |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 186 // VideoCaptureDeviceMac::ReceiveFrame() is calling here, depending on the | 176 // VideoCaptureDeviceMac::ReceiveFrame() is calling here, depending on the |
| 187 // running state. VCDM::ReceiveFrame() calls here to change aspect ratio. | 177 // running state. VCDM::ReceiveFrame() calls here to change aspect ratio. |
| 188 DCHECK((![captureSession_ isRunning] && | 178 DCHECK((![captureSession_ isRunning] && |
| 189 main_thread_checker_.CalledOnValidThread()) || | 179 main_thread_checker_.CalledOnValidThread()) || |
| 190 callback_thread_checker_.CalledOnValidThread()); | 180 callback_thread_checker_.CalledOnValidThread()); |
| 191 | 181 |
| 192 frameWidth_ = width; | 182 frameWidth_ = width; |
| 193 frameHeight_ = height; | 183 frameHeight_ = height; |
| 194 frameRate_ = frameRate; | 184 frameRate_ = frameRate; |
| 195 | 185 |
| 196 FourCharCode best_fourcc = kCVPixelFormatType_422YpCbCr8; | |
| 197 const bool prefer_mjpeg = | |
| 198 width > kMjpegWidthThreshold || height > kMjpegHeightThreshold; | |
| 199 for (CrAVCaptureDeviceFormat* format in captureDevice_.formats) { | |
| 200 const FourCharCode fourcc = | |
| 201 CoreMediaGlue::CMFormatDescriptionGetMediaSubType( | |
| 202 [format formatDescription]); | |
| 203 if (prefer_mjpeg && | |
| 204 fourcc == CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML) { | |
| 205 best_fourcc = fourcc; | |
| 206 break; | |
| 207 } | |
| 208 // Compare according to Chromium preference. | |
| 209 if (FourCCToChromiumPixelFormat(fourcc) < | |
| 210 FourCCToChromiumPixelFormat(best_fourcc)) { | |
| 211 best_fourcc = fourcc; | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 // The capture output has to be configured, despite Mac documentation | 186 // The capture output has to be configured, despite Mac documentation |
| 216 // detailing that setting the sessionPreset would be enough. The reason for | 187 // detailing that setting the sessionPreset would be enough. The reason for |
| 217 // this mismatch is probably because most of the AVFoundation docs are written | 188 // this mismatch is probably because most of the AVFoundation docs are written |
| 218 // for iOS and not for MacOsX. AVVideoScalingModeKey() refers to letterboxing | 189 // for iOS and not for MacOsX. AVVideoScalingModeKey() refers to letterboxing |
| 219 // yes/no and preserve aspect ratio yes/no when scaling. Currently we set | 190 // yes/no and preserve aspect ratio yes/no when scaling. Currently we set |
| 220 // cropping and preservation. | 191 // cropping and preservation. |
| 221 NSDictionary* videoSettingsDictionary = @{ | 192 NSDictionary* videoSettingsDictionary = @{ |
| 222 (id)kCVPixelBufferWidthKey : @(width), | 193 (id)kCVPixelBufferWidthKey : @(width), |
| 223 (id)kCVPixelBufferHeightKey : @(height), | 194 (id)kCVPixelBufferHeightKey : @(height), |
| 224 (id)kCVPixelBufferPixelFormatTypeKey : @(best_fourcc), | 195 (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_422YpCbCr8), |
| 225 AVFoundationGlue::AVVideoScalingModeKey() : | 196 AVFoundationGlue::AVVideoScalingModeKey() : |
| 226 AVFoundationGlue::AVVideoScalingModeResizeAspectFill() | 197 AVFoundationGlue::AVVideoScalingModeResizeAspectFill() |
| 227 }; | 198 }; |
| 228 [captureVideoDataOutput_ setVideoSettings:videoSettingsDictionary]; | 199 [captureVideoDataOutput_ setVideoSettings:videoSettingsDictionary]; |
| 229 | 200 |
| 230 CrAVCaptureConnection* captureConnection = [captureVideoDataOutput_ | 201 CrAVCaptureConnection* captureConnection = [captureVideoDataOutput_ |
| 231 connectionWithMediaType:AVFoundationGlue::AVMediaTypeVideo()]; | 202 connectionWithMediaType:AVFoundationGlue::AVMediaTypeVideo()]; |
| 232 // Check selector existence, related to bugs http://crbug.com/327532 and | 203 // Check selector existence, related to bugs http://crbug.com/327532 and |
| 233 // http://crbug.com/328096. | 204 // http://crbug.com/328096. |
| 234 // CMTimeMake accepts integer argumenst but |frameRate| is float, round it. | 205 // CMTimeMake accepts integer argumenst but |frameRate| is float, round it. |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 275 #pragma mark Private methods | 246 #pragma mark Private methods |
| 276 | 247 |
| 277 // |captureOutput| is called by the capture device to deliver a new frame. | 248 // |captureOutput| is called by the capture device to deliver a new frame. |
| 278 - (void)captureOutput:(CrAVCaptureOutput*)captureOutput | 249 - (void)captureOutput:(CrAVCaptureOutput*)captureOutput |
| 279 didOutputSampleBuffer:(CoreMediaGlue::CMSampleBufferRef)sampleBuffer | 250 didOutputSampleBuffer:(CoreMediaGlue::CMSampleBufferRef)sampleBuffer |
| 280 fromConnection:(CrAVCaptureConnection*)connection { | 251 fromConnection:(CrAVCaptureConnection*)connection { |
| 281 // AVFoundation calls from a number of threads, depending on, at least, if | 252 // AVFoundation calls from a number of threads, depending on, at least, if |
| 282 // Chrome is on foreground or background. Sample the actual thread here. | 253 // Chrome is on foreground or background. Sample the actual thread here. |
| 283 callback_thread_checker_.DetachFromThread(); | 254 callback_thread_checker_.DetachFromThread(); |
| 284 callback_thread_checker_.CalledOnValidThread(); | 255 callback_thread_checker_.CalledOnValidThread(); |
| 256 CVImageBufferRef videoFrame = |
| 257 CoreMediaGlue::CMSampleBufferGetImageBuffer(sampleBuffer); |
| 258 // Lock the frame and calculate frame size. |
| 259 const int kLockFlags = kCVPixelBufferLock_ReadOnly; |
| 260 if (CVPixelBufferLockBaseAddress(videoFrame, kLockFlags) == |
| 261 kCVReturnSuccess) { |
| 262 void* baseAddress = CVPixelBufferGetBaseAddress(videoFrame); |
| 263 size_t bytesPerRow = CVPixelBufferGetBytesPerRow(videoFrame); |
| 264 size_t frameWidth = CVPixelBufferGetWidth(videoFrame); |
| 265 size_t frameHeight = CVPixelBufferGetHeight(videoFrame); |
| 266 size_t frameSize = bytesPerRow * frameHeight; |
| 267 UInt8* addressToPass = reinterpret_cast<UInt8*>(baseAddress); |
| 285 | 268 |
| 286 const CoreMediaGlue::CMFormatDescriptionRef formatDescription = | 269 media::VideoCaptureFormat captureFormat( |
| 287 CoreMediaGlue::CMSampleBufferGetFormatDescription(sampleBuffer); | 270 gfx::Size(frameWidth, frameHeight), |
| 288 const FourCharCode fourcc = | 271 frameRate_, |
| 289 CoreMediaGlue::CMFormatDescriptionGetMediaSubType(formatDescription); | 272 media::PIXEL_FORMAT_UYVY); |
| 290 const CoreMediaGlue::CMVideoDimensions dimensions = | 273 { |
| 291 CoreMediaGlue::CMVideoFormatDescriptionGetDimensions(formatDescription); | 274 base::AutoLock lock(lock_); |
| 292 const media::VideoCaptureFormat captureFormat( | 275 if (frameReceiver_) { |
| 293 gfx::Size(dimensions.width, dimensions.height), | 276 frameReceiver_->ReceiveFrame( |
| 294 frameRate_, | 277 addressToPass, frameSize, captureFormat, 0, 0); |
| 295 FourCCToChromiumPixelFormat(fourcc)); | 278 } |
| 279 } |
| 296 | 280 |
| 297 char* baseAddress = 0; | 281 CVPixelBufferUnlockBaseAddress(videoFrame, kLockFlags); |
| 298 size_t frameSize = 0; | |
| 299 CVImageBufferRef videoFrame = nil; | |
| 300 if (fourcc == CoreMediaGlue::kCMVideoCodecType_JPEG_OpenDML) { | |
| 301 // If MJPEG, use block buffer instead of pixel buffer. | |
| 302 CoreMediaGlue::CMBlockBufferRef blockBuffer = | |
| 303 CoreMediaGlue::CMSampleBufferGetDataBuffer(sampleBuffer); | |
| 304 if (CoreMediaGlue::CMBlockBufferGetDataLength(blockBuffer) > 0) { | |
| 305 size_t length; | |
| 306 CoreMediaGlue::CMBlockBufferGetDataPointer(blockBuffer, 0, &length, | |
| 307 &frameSize, | |
| 308 &baseAddress); | |
| 309 } | |
| 310 } else { | |
| 311 videoFrame = CoreMediaGlue::CMSampleBufferGetImageBuffer(sampleBuffer); | |
| 312 // Lock the frame and calculate frame size. | |
| 313 if (CVPixelBufferLockBaseAddress(videoFrame, kCVPixelBufferLock_ReadOnly) == | |
| 314 kCVReturnSuccess) { | |
| 315 baseAddress = static_cast<char*>(CVPixelBufferGetBaseAddress(videoFrame)); | |
| 316 frameSize = CVPixelBufferGetHeight(videoFrame) * | |
| 317 CVPixelBufferGetBytesPerRow(videoFrame); | |
| 318 } else { | |
| 319 videoFrame = nil; | |
| 320 } | |
| 321 } | 282 } |
| 322 | |
| 323 { | |
| 324 base::AutoLock lock(lock_); | |
| 325 if (frameReceiver_ && baseAddress) { | |
| 326 frameReceiver_->ReceiveFrame(reinterpret_cast<uint8_t*>(baseAddress), | |
| 327 frameSize, captureFormat, 0, 0); | |
| 328 } | |
| 329 } | |
| 330 | |
| 331 if (videoFrame) | |
| 332 CVPixelBufferUnlockBaseAddress(videoFrame, kCVPixelBufferLock_ReadOnly); | |
| 333 } | 283 } |
| 334 | 284 |
| 335 - (void)onVideoError:(NSNotification*)errorNotification { | 285 - (void)onVideoError:(NSNotification*)errorNotification { |
| 336 NSError* error = base::mac::ObjCCast<NSError>([[errorNotification userInfo] | 286 NSError* error = base::mac::ObjCCast<NSError>([[errorNotification userInfo] |
| 337 objectForKey:AVFoundationGlue::AVCaptureSessionErrorKey()]); | 287 objectForKey:AVFoundationGlue::AVCaptureSessionErrorKey()]); |
| 338 [self sendErrorString:[NSString | 288 [self sendErrorString:[NSString |
| 339 stringWithFormat:@"%@: %@", | 289 stringWithFormat:@"%@: %@", |
| 340 [error localizedDescription], | 290 [error localizedDescription], |
| 341 [error localizedFailureReason]]]; | 291 [error localizedFailureReason]]]; |
| 342 } | 292 } |
| 343 | 293 |
| 344 - (void)sendErrorString:(NSString*)error { | 294 - (void)sendErrorString:(NSString*)error { |
| 345 DLOG(ERROR) << [error UTF8String]; | 295 DLOG(ERROR) << [error UTF8String]; |
| 346 base::AutoLock lock(lock_); | 296 base::AutoLock lock(lock_); |
| 347 if (frameReceiver_) | 297 if (frameReceiver_) |
| 348 frameReceiver_->ReceiveError([error UTF8String]); | 298 frameReceiver_->ReceiveError([error UTF8String]); |
| 349 } | 299 } |
| 350 | 300 |
| 351 @end | 301 @end |
| OLD | NEW |