16 #include "../vuo.hid/VuoUsbVendor.h"
19 #include <IOKit/IOKitLib.h>
20 #include <CoreMediaIO/CMIOHardware.h>
21 #include <AVFoundation/AVFoundation.h>
26 "title" :
"VuoVideoCapture",
31 "VuoVideoInputDevice",
32 "VuoList_VuoVideoInputDevice",
33 "AVFoundation.framework",
34 "CoreMedia.framework",
35 "CoreMediaIO.framework",
36 "CoreVideo.framework",
51 static dispatch_once_t enableOnce = 0;
52 dispatch_once(&enableOnce, ^{
54 CMIOObjectPropertyAddress
property = { kCMIOHardwarePropertyAllowScreenCaptureDevices, kCMIOObjectPropertyScopeGlobal, kCMIOObjectPropertyElementMaster };
56 bool hasProperty = CMIOObjectHasProperty(kCMIOObjectSystemObject, &property);
59 VUserLog(
"Warning: Apple CoreMediaIO took %.1f seconds to initialize its 3rd-party plugins.", t1 - t0);
65 OSStatus ret = CMIOObjectSetPropertyData(kCMIOObjectSystemObject, &property, 0, NULL,
sizeof(yes), &yes);
66 if (ret != kCMIOHardwareNoError)
69 VUserLog(
"Warning: Couldn't enable tethered iOS device support: %s", errorText);
133 - (void) devicesDidChange:(NSNotification*)notif;
142 if (
self = [super
init])
144 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devicesDidChange:) name:AVCaptureDeviceWasConnectedNotification object:nil];
145 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devicesDidChange:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
153 [NSNotificationCenter.defaultCenter removeObserver:self];
168 - (void) devicesDidChange:(NSNotification*)notif
174 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(AVCaptureDevice *)object change:(NSDictionary *)change context:(
void *)p
177 if ([keyPath isEqualToString:
@"connected"]
178 && !
object.connected)
182 [vci->device removeObserver:self forKeyPath:@"connected" context:vci];
184 [vci->session removeInput:vci->input];
202 static dispatch_once_t
init = 0;
203 dispatch_once(&
init, ^{
236 CFMutableDictionaryRef match_dictionary = IOServiceMatching(
"IOFireWireDevice");
237 io_iterator_t entry_iterator;
238 if (IOServiceGetMatchingServices(kIOMasterPortDefault, match_dictionary, &entry_iterator) == kIOReturnSuccess)
240 io_registry_entry_t serviceObject;
241 while ((serviceObject = IOIteratorNext(entry_iterator)))
243 CFMutableDictionaryRef serviceDictionary;
244 if (IORegistryEntryCreateCFProperties(serviceObject, &serviceDictionary, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
246 IOObjectRelease(serviceObject);
250 NSString *guidAsHexString = [NSString stringWithFormat:@"%llx",[(NSNumber *)CFDictionaryGetValue(serviceDictionary, @"GUID") longLongValue]];
251 if ([uniqueID rangeOfString:guidAsHexString].location != NSNotFound)
253 NSString *vendorName = [(NSString *)CFDictionaryGetValue(serviceDictionary, @"FireWire Vendor Name") retain];
254 CFRelease(serviceDictionary);
255 IOObjectRelease(serviceObject);
256 IOObjectRelease(entry_iterator);
260 CFRelease(serviceDictionary);
261 IOObjectRelease(serviceObject);
263 IOObjectRelease(entry_iterator);
266 match_dictionary = IOServiceMatching(
"IOUSBDevice");
267 if (IOServiceGetMatchingServices(kIOMasterPortDefault, match_dictionary, &entry_iterator) == kIOReturnSuccess)
269 io_registry_entry_t serviceObject;
270 while ((serviceObject = IOIteratorNext(entry_iterator)))
272 CFMutableDictionaryRef serviceDictionary;
273 if (IORegistryEntryCreateCFProperties(serviceObject, &serviceDictionary, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
275 IOObjectRelease(serviceObject);
279 NSString *guidAsHexString = [NSString stringWithFormat:@"%llx",[(NSNumber *)CFDictionaryGetValue(serviceDictionary, @"locationID") longLongValue]];
280 if ([uniqueID rangeOfString:guidAsHexString].location != NSNotFound)
282 NSString *vendorName = [(NSString *)CFDictionaryGetValue(serviceDictionary, @"USB Vendor Name") retain];
283 if (!vendorName.length)
285 char *vendor =
VuoUsbVendor_getText(((NSNumber *)CFDictionaryGetValue(serviceDictionary,
@"idVendor")).intValue);
286 vendorName = [[NSString stringWithUTF8String:vendor] retain];
290 CFRelease(serviceDictionary);
291 IOObjectRelease(serviceObject);
292 IOObjectRelease(entry_iterator);
296 CFRelease(serviceDictionary);
297 IOObjectRelease(serviceObject);
299 IOObjectRelease(entry_iterator);
310 NSString *deviceName = dev.localizedName;
314 NSString *modelWithoutVendor = dev.modelID;
315 NSUInteger tail = [modelWithoutVendor rangeOfString:@" Camera VendorID_"].location;
316 if (tail != NSNotFound)
317 modelWithoutVendor = [modelWithoutVendor substringToIndex:tail];
318 if (modelWithoutVendor.length
319 && [dev.manufacturer rangeOfString:modelWithoutVendor].location == NSNotFound)
320 deviceName = [NSString stringWithFormat:@"%@ %@", modelWithoutVendor, deviceName];
322 if (![dev.manufacturer isEqualToString:vendorName]
323 && ![dev.manufacturer isEqualToString:
@"Unknown"])
324 deviceName = [NSString stringWithFormat:@"%@ %@", dev.manufacturer, deviceName];
326 if ([vendorName length])
327 deviceName = [NSString stringWithFormat:@"%@ %@", vendorName, deviceName];
343 [vendorName release];
356 #pragma clang diagnostic push
357 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
359 NSArray *inputDevices = [AVCaptureDevice devices];
360 #pragma clang diagnostic pop
364 for (AVCaptureDevice *dev in inputDevices)
366 if (![dev hasMediaType:AVMediaTypeVideo]
367 && ![dev hasMediaType:AVMediaTypeMuxed])
400 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
402 CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
405 VUserLog(
"Warning: AV Foundation gave us a sampleBuffer without a pixelBuffer.");
409 size_t pixelBufferWidth = CVPixelBufferGetWidth(pixelBuffer);
410 size_t pixelBufferHeight = CVPixelBufferGetHeight(pixelBuffer);
413 CMVideoDimensions preferredDim = CMVideoFormatDescriptionGetDimensions(_vci->preferredFormat.formatDescription);
414 if ((preferredDim.width && pixelBufferWidth != preferredDim.width)
415 || (preferredDim.height && pixelBufferHeight != preferredDim.height))
417 const int maxSetSizeAttempts = 10;
418 if (_vci->attemptedSetSizes < maxSetSizeAttempts)
420 ++_vci->attemptedSetSizes;
421 VUserLog(
"Warning: We requested %dx%d, but the camera gave us %zux%zu. Trying again (attempt %d of %d)…",
422 preferredDim.width, preferredDim.height,
423 pixelBufferWidth, pixelBufferHeight,
424 _vci->attemptedSetSizes, maxSetSizeAttempts);
428 else if (_vci->attemptedSetSizes == maxSetSizeAttempts)
430 ++_vci->attemptedSetSizes;
431 VUserLog(
"Warning: The camera didn't get the hint after %d attempts, so we'll just let it do what it wants…", maxSetSizeAttempts);
435 #pragma clang diagnostic push
436 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
437 __block CVOpenGLTextureRef texture;
439 CVOpenGLTextureCacheCreateTextureFromImage(NULL, _vci->textureCache, pixelBuffer, NULL, &texture);
442 NSDictionary *pixelAspectRatio = (NSDictionary *)CVBufferGetAttachment(pixelBuffer, CFSTR(
"CVPixelAspectRatio"),
nullptr);
443 unsigned int stretchedWidth = 0;
444 if (pixelAspectRatio)
445 stretchedWidth = pixelBufferWidth * ((NSNumber *)pixelAspectRatio[
@"HorizontalSpacing"]).intValue / ((NSNumber *)pixelAspectRatio[
@"VerticalSpacing"]).intValue;
448 CVOpenGLTextureGetName(texture),
455 if (_vci->firstFrameTime == -INFINITY)
456 _vci->firstFrameTime = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer));
457 if (_vci->receivedFrameTrigger)
459 CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) - _vci->firstFrameTime,
460 CMTimeGetSeconds(CMSampleBufferGetDuration(sampleBuffer))));
462 CVOpenGLTextureRelease(texture);
466 CVOpenGLTextureCacheFlush(_vci->textureCache, 0);
468 #pragma clang diagnostic pop
478 vci->
device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
481 vci->
device = [AVCaptureDevice deviceWithUniqueID:[NSString stringWithUTF8String:vci->vdevice.id]];
488 #pragma clang diagnostic push
489 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
490 for (AVCaptureDevice *dev in AVCaptureDevice.devices)
491 #pragma clang diagnostic pop
493 if (![dev hasMediaType:AVMediaTypeVideo]
494 && ![dev hasMediaType:AVMediaTypeMuxed])
508 [vci->device addObserver:deviceListener forKeyPath:@"connected" options:0 context:vci];
515 vci->
input = [AVCaptureDeviceInput deviceInputWithDevice:vci->device error:&e];
519 [vci->session addInput:vci->input];
521 VUserLog(
"Error: -[AVCaptureSession canAddInput:] said no.");
524 VUserLog(
"Error: Couldn't create AVCaptureDeviceInput: %s", e.localizedDescription.UTF8String);
537 if ([AVCaptureDevice respondsToSelector:
@selector(authorizationStatusForMediaType:)])
539 long status = (long)[AVCaptureDevice performSelector:
@selector(authorizationStatusForMediaType:) withObject:
@"vide"];
541 VUserLog(
"Warning: Video input may be unavailable due to system restrictions. Check System Preferences > Security & Privacy > Privacy > Camera.");
544 VUserLog(
"Error: Video input is unavailable due to system restrictions. Check System Preferences > Security & Privacy > Privacy > Camera.");
552 vci->
queue = dispatch_queue_create(
"org.vuo.VuoVideoCapture", NULL);
555 dispatch_sync(dispatch_get_main_queue(), ^{
561 vci->
session.sessionPreset = AVCaptureSessionPresetPhoto;
578 AVCaptureVideoDataOutput *output = [AVCaptureVideoDataOutput new];
579 [output setAlwaysDiscardsLateVideoFrames:YES];
580 [output setSampleBufferDelegate:vci->delegate queue:vci->queue];
581 [vci->session addOutput:output];
587 #pragma clang diagnostic push
588 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
590 __block CVReturn ret;
592 ret = CVOpenGLTextureCacheCreate(NULL,
594 (NSString *)kCVOpenGLTextureCacheChromaSamplingModeKey: (NSString *)kCVOpenGLTextureCacheChromaSamplingModeBestPerformance
597 CGLReleasePixelFormat(pf);
598 #pragma clang diagnostic pop
600 if (ret != kCVReturnSuccess)
602 VUserLog(
"Error: Couldn't create texture cache: %d", ret);
626 AVCaptureDeviceFormat *bestFormat = nil;
627 AVFrameRateRange *bestFrameRateRange = nil;
628 if (width == 0 && height == 0)
631 for (AVCaptureDeviceFormat *format in
vci->
device.formats)
632 for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
633 if (range.maxFrameRate > bestFrameRateRange.maxFrameRate)
636 bestFrameRateRange = range;
640 for (AVCaptureDeviceFormat *format in
vci->
device.formats)
642 CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
643 for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
645 CMVideoDimensions bestDim = CMVideoFormatDescriptionGetDimensions(bestFormat.formatDescription);
647 && dim.width > bestDim.width
648 && dim.height > bestDim.height)
651 bestFrameRateRange = range;
659 float bestDist = INFINITY;
660 for (AVCaptureDeviceFormat *format in
vci->
device.formats)
662 CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
663 float dist = pow(dim.width - width, 2) + pow(dim.height - height, 2);
668 bestFrameRateRange = nil;
669 for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
670 if (range.maxFrameRate > bestFrameRateRange.maxFrameRate)
671 bestFrameRateRange = range;
678 if ([
vci->
device lockForConfiguration:NULL] == YES)
682 [vci->preferredFormat release];
684 if (bestFrameRateRange)
686 vci->
device.activeVideoMinFrameDuration = bestFrameRateRange.minFrameDuration;
687 vci->
device.activeVideoMaxFrameDuration = bestFrameRateRange.minFrameDuration;
690 [vci->device unlockForConfiguration];
693 VUserLog(
"Warning: Couldn't lock the video input device for configuration; ignoring the specified resolution.");
697 [vci->preferredFormat release];
699 VUserLog(
"Warning: This device didn't report any supported formats.");
714 dispatch_sync(dispatch_get_main_queue(), ^{
716 [vci->session startRunning];
731 dispatch_sync(dispatch_get_main_queue(), ^{
733 [vci->session stopRunning];
760 [vci->device removeObserver:deviceListener forKeyPath:@"connected" context:vci];
763 dispatch_sync(dispatch_get_main_queue(), ^{
765 [vci->session stopRunning];
769 [vci->session release];
770 [vci->delegate release];
774 #pragma clang diagnostic push
775 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
777 #pragma clang diagnostic pop