Vuo  2.3.2
VuoVideoCapture.mm
Go to the documentation of this file.
1 
10 #include "module.h"
11 #include <time.h>
12 #include "VuoApp.h"
13 #include "VuoVideoCapture.h"
14 #include "VuoTriggerSet.hh"
15 #include "VuoOsStatus.h"
16 #include "../vuo.hid/VuoUsbVendor.h"
17 
18 #include "VuoMacOSSDKWorkaround.h"
19 #include <IOKit/IOKitLib.h>
20 #include <CoreMediaIO/CMIOHardware.h>
21 #include <AVFoundation/AVFoundation.h>
22 #import <OpenGL/gl.h>
23 
24 #ifdef VUO_COMPILER
26  "title" : "VuoVideoCapture",
27  "dependencies" : [
28  "VuoImage",
29  "VuoOsStatus",
30  "VuoUsbVendor",
31  "VuoVideoInputDevice",
32  "VuoList_VuoVideoInputDevice",
33  "AVFoundation.framework",
34  "CoreMedia.framework",
35  "CoreMediaIO.framework",
36  "CoreVideo.framework",
37  "IOKit.framework",
38  "AppKit.framework",
39  ]
40  });
41 #endif
42 
43 extern "C"
44 {
45 
50 {
51  static dispatch_once_t enableOnce = 0;
52  dispatch_once(&enableOnce, ^{
54  CMIOObjectPropertyAddress property = { kCMIOHardwarePropertyAllowScreenCaptureDevices, kCMIOObjectPropertyScopeGlobal, kCMIOObjectPropertyElementMaster };
55  double t0 = VuoLogGetTime();
56  bool hasProperty = CMIOObjectHasProperty(kCMIOObjectSystemObject, &property);
57  double t1 = VuoLogGetTime();
58  if (t1 - t0 > 1)
59  VUserLog("Warning: Apple CoreMediaIO took %.1f seconds to initialize its 3rd-party plugins.", t1 - t0);
60 
61  if (hasProperty)
62  {
63  UInt32 yes = 1;
64  // This call sometimes blocks the main thread for about 5 seconds (!).
65  OSStatus ret = CMIOObjectSetPropertyData(kCMIOObjectSystemObject, &property, 0, NULL, sizeof(yes), &yes);
66  if (ret != kCMIOHardwareNoError)
67  {
68  char *errorText = VuoOsStatus_getText(ret);
69  VUserLog("Warning: Couldn't enable tethered iOS device support: %s", errorText);
70  free(errorText);
71  }
72  }
73  });
74  });
75 }
76 
77 }
78 
80 
84 typedef struct
85 {
86  CVOpenGLTextureCacheRef textureCache;
87  dispatch_queue_t queue;
89  AVCaptureDevice *device;
90  AVCaptureDeviceInput *input;
91  AVCaptureSession *session;
93  void (*receivedFrameTrigger)(VuoVideoFrame image);
97  AVCaptureDeviceFormat *preferredFormat;
99  double firstFrameTime;
101 
102 static dispatch_queue_t VuoVideoCapture_pendingDevicesQueue;
103 static std::set<VuoVideoCaptureInternal *> VuoVideoCapture_pendingDevices;
104 
106 
113 {
114  for (auto dev = VuoVideoCapture_pendingDevices.begin(); dev != VuoVideoCapture_pendingDevices.end();)
115  {
117  if ((*dev)->device)
118  VuoVideoCapture_pendingDevices.erase(*dev++);
119  else
120  ++dev;
121  }
122 }
123 
127 @interface VuoVideoCaptureDeviceListener : NSObject
128 {
130 }
131 
132 - (id) init;
133 - (void) devicesDidChange:(NSNotification*)notif;
134 - (void) addCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger;
135 - (void) removeCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger;
136 @end
137 
138 @implementation VuoVideoCaptureDeviceListener
139 
140 - (id) init
141 {
142  if (self = [super init])
143  {
144  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devicesDidChange:) name:AVCaptureDeviceWasConnectedNotification object:nil];
145  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devicesDidChange:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
146  }
147 
148  return self;
149 }
150 
151 - (void)dealloc
152 {
153  [NSNotificationCenter.defaultCenter removeObserver:self];
154  [super dealloc];
155 }
156 
157 - (void) addCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger
158 {
159  callbacks.addTrigger(outputTrigger);
160  outputTrigger(VuoVideoCapture_getInputDevices());
161 }
162 
163 - (void) removeCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger
164 {
165  callbacks.removeTrigger(outputTrigger);
166 }
167 
168 - (void) devicesDidChange:(NSNotification*)notif
169 {
172 }
173 
174 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(AVCaptureDevice *)object change:(NSDictionary *)change context:(void *)p
175 {
177  if ([keyPath isEqualToString:@"connected"]
178  && !object.connected)
179  {
180  VUserLog("Warning: %s was disconnected. I'll keep looking for it.", VuoVideoInputDevice_getSummary(vci->vdevice));
181 
182  [vci->device removeObserver:self forKeyPath:@"connected" context:vci];
183 
184  [vci->session removeInput:vci->input];
185  vci->input = nil;
186  dispatch_async(VuoVideoCapture_pendingDevicesQueue, ^{
187  VuoVideoCapture_pendingDevices.insert(vci);
188  });
189  }
190 }
191 @end
192 
194 
199 {
201 
202  static dispatch_once_t init = 0;
203  dispatch_once(&init, ^{
205  VuoVideoCapture_pendingDevicesQueue = dispatch_queue_create("org.vuo.video.pendingDevicesQueue", NULL);
206  });
207 }
208 
213 {
214  if(deviceListener != nil)
215  [deviceListener removeCallback:devicesDidChange];
216 }
217 
222 {
224  [deviceListener addCallback:devicesDidChange];
225 }
226 
234 NSString *VuoVideoCapture_getVendorNameForUniqueID(NSString *uniqueID)
235 {
236  CFMutableDictionaryRef match_dictionary = IOServiceMatching("IOFireWireDevice");
237  io_iterator_t entry_iterator;
238  if (IOServiceGetMatchingServices(kIOMasterPortDefault, match_dictionary, &entry_iterator) == kIOReturnSuccess)
239  {
240  io_registry_entry_t serviceObject;
241  while ((serviceObject = IOIteratorNext(entry_iterator)))
242  {
243  CFMutableDictionaryRef serviceDictionary;
244  if (IORegistryEntryCreateCFProperties(serviceObject, &serviceDictionary, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
245  {
246  IOObjectRelease(serviceObject);
247  continue;
248  }
249 
250  NSString *guidAsHexString = [NSString stringWithFormat:@"%llx",[(NSNumber *)CFDictionaryGetValue(serviceDictionary, @"GUID") longLongValue]];
251  if ([uniqueID rangeOfString:guidAsHexString].location != NSNotFound)
252  {
253  NSString *vendorName = [(NSString *)CFDictionaryGetValue(serviceDictionary, @"FireWire Vendor Name") retain];
254  CFRelease(serviceDictionary);
255  IOObjectRelease(serviceObject);
256  IOObjectRelease(entry_iterator);
257  return vendorName;
258  }
259 
260  CFRelease(serviceDictionary);
261  IOObjectRelease(serviceObject);
262  }
263  IOObjectRelease(entry_iterator);
264  }
265 
266  match_dictionary = IOServiceMatching("IOUSBDevice");
267  if (IOServiceGetMatchingServices(kIOMasterPortDefault, match_dictionary, &entry_iterator) == kIOReturnSuccess)
268  {
269  io_registry_entry_t serviceObject;
270  while ((serviceObject = IOIteratorNext(entry_iterator)))
271  {
272  CFMutableDictionaryRef serviceDictionary;
273  if (IORegistryEntryCreateCFProperties(serviceObject, &serviceDictionary, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
274  {
275  IOObjectRelease(serviceObject);
276  continue;
277  }
278 
279  NSString *guidAsHexString = [NSString stringWithFormat:@"%llx",[(NSNumber *)CFDictionaryGetValue(serviceDictionary, @"locationID") longLongValue]];
280  if ([uniqueID rangeOfString:guidAsHexString].location != NSNotFound)
281  {
282  NSString *vendorName = [(NSString *)CFDictionaryGetValue(serviceDictionary, @"USB Vendor Name") retain];
283  if (!vendorName.length)
284  {
285  char *vendor = VuoUsbVendor_getText(((NSNumber *)CFDictionaryGetValue(serviceDictionary, @"idVendor")).intValue);
286  vendorName = [[NSString stringWithUTF8String:vendor] retain];
287  free(vendor);
288  }
289 
290  CFRelease(serviceDictionary);
291  IOObjectRelease(serviceObject);
292  IOObjectRelease(entry_iterator);
293  return vendorName;
294  }
295 
296  CFRelease(serviceDictionary);
297  IOObjectRelease(serviceObject);
298  }
299  IOObjectRelease(entry_iterator);
300  }
301 
302  return @"";
303 }
304 
309 {
310  NSString *deviceName = dev.localizedName;
311 
312  NSString *vendorName = VuoVideoCapture_getVendorNameForUniqueID([dev uniqueID]);
313 
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];
321 
322  if (![dev.manufacturer isEqualToString:vendorName]
323  && ![dev.manufacturer isEqualToString:@"Unknown"])
324  deviceName = [NSString stringWithFormat:@"%@ %@", dev.manufacturer, deviceName];
325 
326  if ([vendorName length])
327  deviceName = [NSString stringWithFormat:@"%@ %@", vendorName, deviceName];
328 
329  // manufacturer=0x00000001 LLC. IOReg= modelID= name=VirtualEyez HD result=[0x00000001 LLC. VirtualEyez HD]
330  // manufacturer=Allocinit.com IOReg= modelID=CamTwist name=CamTwist result=[Allocinit.com CamTwist CamTwist]
331  // manufacturer=Apple Inc. IOReg= modelID=Apple Camera VendorID_0x106B ProductID_0x1570 name=FaceTime HD Camera result=[Apple Inc. FaceTime HD Camera]
332  // manufacturer=Apple Inc. IOReg= modelID=iOS Device name=smokris iPhone result=[Apple Inc. iOS Device smokris iPhone]
333  // manufacturer=Canon IOReg=Canon modelID= name=VIXIA HV30 result=[Canon VIXIA HV30]
334  // manufacturer=Kinoni IOReg= modelID=EpocCamDevice:0:0 name=EpocCam result=[Kinoni EpocCamDevice:0:0 EpocCam]
335  // manufacturer=SnapVendor IOReg= modelID=SnapCamera name=Snap Camera result=[SnapVendor SnapCamera Snap Camera]
336  // manufacturer=Telestream IOReg= modelID=WCVC-0.4.0 name=Wirecast Virtual Camera result=[Telestream WCVC-0.4.0 Wirecast Virtual Camera]
337  // manufacturer=Unknown IOReg=Logitech Inc. modelID=UVC Camera VendorID_1133 ProductID_2053 name=USB Camera #2 result=[Logitech Inc. UVC USB Camera #2]
338  // manufacturer=Unknown IOReg=Logitech Inc. modelID=UVC Camera VendorID_1133 ProductID_2081 name=USB Camera result=[Logitech Inc. UVC USB Camera]
339  // manufacturer=Unknown IOReg=Logitech Inc. modelID=UVC Camera VendorID_1133 ProductID_2093 name=HD Pro Webcam C920 result=[Logitech Inc. UVC HD Pro Webcam C920]
340  // manufacturer=Visicom Inc. IOReg= modelID= name=ManyCam Virtual Webcam result=[Visicom Inc. ManyCam Virtual Webcam]
341 // NSLog(@"manufacturer=%@ IOReg=%@ modelID=%@ name=%@ result=[%@]", dev.manufacturer, vendorName, dev.modelID, dev.localizedName, deviceName);
342 
343  [vendorName release];
344  return VuoText_make([deviceName UTF8String]);
345 }
346 
351 {
353 
354  // Use +devices instead of +devicesWithMediaType:AVMediaTypeVideo,
355  // since the latter doesn't include tethered iOS devices.
356 #pragma clang diagnostic push
357 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
358  // The replacement, AVCaptureDeviceDiscoverySession, isn't available until macOS 10.15.
359  NSArray *inputDevices = [AVCaptureDevice devices];
360 #pragma clang diagnostic pop
361 
363 
364  for (AVCaptureDevice *dev in inputDevices)
365  {
366  if (![dev hasMediaType:AVMediaTypeVideo]
367  && ![dev hasMediaType:AVMediaTypeMuxed])
368  continue;
369 
370  VuoText uniqueID = VuoText_make([[dev uniqueID] UTF8String]);
371 
373  }
374 
375  return devices;
376 }
377 
379 
383 static void VuoVideoCapture_freeCallback(VuoImage imageToFree)
384 {
385 }
386 
390 @interface VuoVideoCaptureDelegate : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
392 @end
393 
394 @implementation VuoVideoCaptureDelegate
400 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
401 {
402  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
403  if (!pixelBuffer)
404  {
405  VUserLog("Warning: AV Foundation gave us a sampleBuffer without a pixelBuffer.");
406  return;
407  }
408 
409  size_t pixelBufferWidth = CVPixelBufferGetWidth(pixelBuffer);
410  size_t pixelBufferHeight = CVPixelBufferGetHeight(pixelBuffer);
411 
412  // Work around AV Foundation bug where it sometimes ignores format changes on some cameras (e.g., 640x480@60FPS Logitech C910).
413  CMVideoDimensions preferredDim = CMVideoFormatDescriptionGetDimensions(_vci->preferredFormat.formatDescription);
414  if ((preferredDim.width && pixelBufferWidth != preferredDim.width)
415  || (preferredDim.height && pixelBufferHeight != preferredDim.height))
416  {
417  const int maxSetSizeAttempts = 10;
418  if (_vci->attemptedSetSizes < maxSetSizeAttempts)
419  {
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);
425  VuoVideoCapture_setSize(_vci, _vci->preferredWidth, _vci->preferredHeight);
426  return;
427  }
428  else if (_vci->attemptedSetSizes == maxSetSizeAttempts)
429  {
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);
432  }
433  }
434 
435 #pragma clang diagnostic push
436 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
437  __block CVOpenGLTextureRef texture;
438  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
439  CVOpenGLTextureCacheCreateTextureFromImage(NULL, _vci->textureCache, pixelBuffer, NULL, &texture);
440  });
441 
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;
446 
448  CVOpenGLTextureGetName(texture),
449  GL_RGB,
450  pixelBufferWidth,
451  pixelBufferHeight,
453  VuoRetain(rectImage);
454  VuoImage image = VuoImage_makeCopy(rectImage, CVOpenGLTextureIsFlipped(texture), stretchedWidth, 0, false);
455  if (_vci->firstFrameTime == -INFINITY)
456  _vci->firstFrameTime = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer));
457  if (_vci->receivedFrameTrigger)
458  _vci->receivedFrameTrigger(VuoVideoFrame_make(image,
459  CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) - _vci->firstFrameTime,
460  CMTimeGetSeconds(CMSampleBufferGetDuration(sampleBuffer))));
461 
462  CVOpenGLTextureRelease(texture);
463  VuoRelease(rectImage);
464 
465  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
466  CVOpenGLTextureCacheFlush(_vci->textureCache, 0);
467  });
468 #pragma clang diagnostic pop
469 }
470 @end
471 
476 {
478  vci->device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
479  else
480  {
481  vci->device = [AVCaptureDevice deviceWithUniqueID:[NSString stringWithUTF8String:vci->vdevice.id]];
482 
483  // If that fails, try to match the device name
484  if (vci->vdevice.matchType == VuoVideoInputDevice_MatchIdThenName
486  && !vci->device)
487  {
488 #pragma clang diagnostic push
489 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
490  for (AVCaptureDevice *dev in AVCaptureDevice.devices)
491 #pragma clang diagnostic pop
492  {
493  if (![dev hasMediaType:AVMediaTypeVideo]
494  && ![dev hasMediaType:AVMediaTypeMuxed])
495  continue;
496 
497  if (strstr(VuoVideoCapture_getDeviceName(dev), vci->vdevice.name))
498  {
499  vci->device = dev;
500  break;
501  }
502  }
503  }
504  }
505 
506  if (vci->device)
507  {
508  [vci->device addObserver:deviceListener forKeyPath:@"connected" options:0 context:vci];
509 
511  if (vci->preferredListening)
513 
514  NSError *e;
515  vci->input = [AVCaptureDeviceInput deviceInputWithDevice:vci->device error:&e];
516  if (vci->input)
517  {
518  if ([vci->session canAddInput:vci->input])
519  [vci->session addInput:vci->input];
520  else
521  VUserLog("Error: -[AVCaptureSession canAddInput:] said no.");
522  }
523  else
524  VUserLog("Error: Couldn't create AVCaptureDeviceInput: %s", e.localizedDescription.UTF8String);
525 
526  vci->firstFrameTime = -INFINITY;
527  }
528 }
529 
534 {
536 
537  if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)])
538  {
539  long status = (long)[AVCaptureDevice performSelector:@selector(authorizationStatusForMediaType:) withObject:@"vide"];
540  if (status == 0 /* AVAuthorizationStatusNotDetermined */)
541  VUserLog("Warning: Video input may be unavailable due to system restrictions. Check System Preferences > Security & Privacy > Privacy > Camera.");
542  else if (status == 1 /* AVAuthorizationStatusRestricted */
543  || status == 2 /* AVAuthorizationStatusDenied */)
544  VUserLog("Error: Video input is unavailable due to system restrictions. Check System Preferences > Security & Privacy > Privacy > Camera.");
545  }
546 
549 
550  vci->receivedFrameTrigger = receivedFrame;
551 
552  vci->queue = dispatch_queue_create("org.vuo.VuoVideoCapture", NULL);
553 
554  VUOLOG_PROFILE_BEGIN(mainQueue);
555  dispatch_sync(dispatch_get_main_queue(), ^{
556  VUOLOG_PROFILE_END(mainQueue);
558  vci->delegate.vci = vci;
559 
560  vci->session = [AVCaptureSession new];
561  vci->session.sessionPreset = AVCaptureSessionPresetPhoto;
562 
563  vci->vdevice = inputDevice;
565 
566  {
568  if (!vci->device)
569  {
570  VUserLog("Warning: %s isn't currently available. I'll keep trying.", VuoVideoInputDevice_getSummary(inputDevice));
571  dispatch_async(VuoVideoCapture_pendingDevicesQueue, ^{
573  });
574  }
575  }
576 
577  {
578  AVCaptureVideoDataOutput *output = [AVCaptureVideoDataOutput new];
579  [output setAlwaysDiscardsLateVideoFrames:YES];
580  [output setSampleBufferDelegate:vci->delegate queue:vci->queue];
581  [vci->session addOutput:output];
582  [output release];
583  }
584  });
585 
586  {
587 #pragma clang diagnostic push
588 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
589  CGLPixelFormatObj pf = (CGLPixelFormatObj)VuoGlContext_makePlatformPixelFormat(false, false, -1);
590  __block CVReturn ret;
591  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
592  ret = CVOpenGLTextureCacheCreate(NULL,
593  (CFDictionaryRef)@{
594  (NSString *)kCVOpenGLTextureCacheChromaSamplingModeKey: (NSString *)kCVOpenGLTextureCacheChromaSamplingModeBestPerformance
595  }, cgl_ctx, pf, NULL, &vci->textureCache);
596  });
597  CGLReleasePixelFormat(pf);
598 #pragma clang diagnostic pop
599 
600  if (ret != kCVReturnSuccess)
601  {
602  VUserLog("Error: Couldn't create texture cache: %d", ret);
604  return NULL;
605  }
606  }
607 
608  return (VuoVideoCapture)vci;
609 }
610 
615 {
617  if (!vci)
618  return;
619 
620  vci->preferredWidth = width;
621  vci->preferredHeight = height;
622 
623  if (!vci->device)
624  return;
625 
626  AVCaptureDeviceFormat *bestFormat = nil;
627  AVFrameRateRange *bestFrameRateRange = nil;
628  if (width == 0 && height == 0)
629  {
630  // By default, pick the format with the highest framerate.
631  for (AVCaptureDeviceFormat *format in vci->device.formats)
632  for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
633  if (range.maxFrameRate > bestFrameRateRange.maxFrameRate)
634  {
635  bestFormat = format;
636  bestFrameRateRange = range;
637  }
638 
639  // Then, if there are multiple resolutions with that framerate, pick the highest resolution.
640  for (AVCaptureDeviceFormat *format in vci->device.formats)
641  {
642  CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
643  for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
644  {
645  CMVideoDimensions bestDim = CMVideoFormatDescriptionGetDimensions(bestFormat.formatDescription);
646  if (VuoReal_areEqual(range.maxFrameRate, bestFrameRateRange.maxFrameRate)
647  && dim.width > bestDim.width
648  && dim.height > bestDim.height)
649  {
650  bestFormat = format;
651  bestFrameRateRange = range;
652  }
653  }
654  }
655  }
656  else
657  {
658  // Pick the format with the closest resolution.
659  float bestDist = INFINITY;
660  for (AVCaptureDeviceFormat *format in vci->device.formats)
661  {
662  CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
663  float dist = pow(dim.width - width, 2) + pow(dim.height - height, 2);
664  if (dist < bestDist)
665  {
666  bestDist = dist;
667  bestFormat = format;
668  bestFrameRateRange = nil;
669  for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
670  if (range.maxFrameRate > bestFrameRateRange.maxFrameRate)
671  bestFrameRateRange = range;
672  }
673  }
674  }
675 
676  if (bestFormat)
677  {
678  if ([vci->device lockForConfiguration:NULL] == YES)
679  {
680  vci->device.activeFormat = bestFormat;
681 
682  [vci->preferredFormat release];
683  vci->preferredFormat = [bestFormat retain];
684  if (bestFrameRateRange)
685  {
686  vci->device.activeVideoMinFrameDuration = bestFrameRateRange.minFrameDuration;
687  vci->device.activeVideoMaxFrameDuration = bestFrameRateRange.minFrameDuration;
688  }
689 
690  [vci->device unlockForConfiguration];
691  }
692  else
693  VUserLog("Warning: Couldn't lock the video input device for configuration; ignoring the specified resolution.");
694  }
695  else
696  {
697  [vci->preferredFormat release];
698  vci->preferredFormat = nil;
699  VUserLog("Warning: This device didn't report any supported formats.");
700  }
701 }
702 
707 {
709  if (!vci)
710  return;
711 
712  vci->preferredListening = true;
713  VUOLOG_PROFILE_BEGIN(mainQueue);
714  dispatch_sync(dispatch_get_main_queue(), ^{
715  VUOLOG_PROFILE_END(mainQueue);
716  [vci->session startRunning];
717  });
718 }
719 
724 {
726  if (!vci)
727  return;
728 
729  vci->preferredListening = false;
730  VUOLOG_PROFILE_BEGIN(mainQueue);
731  dispatch_sync(dispatch_get_main_queue(), ^{
732  VUOLOG_PROFILE_END(mainQueue);
733  [vci->session stopRunning];
734  });
735 }
736 
741 {
743  if (!vci)
744  return;
745 
746  vci->receivedFrameTrigger = receivedFrame;
747 }
748 
752 void VuoVideoCapture_free(void *p)
753 {
755 
756  dispatch_sync(VuoVideoCapture_pendingDevicesQueue, ^{
758  });
759 
760  [vci->device removeObserver:deviceListener forKeyPath:@"connected" context:vci];
761 
762  VUOLOG_PROFILE_BEGIN(mainQueue);
763  dispatch_sync(dispatch_get_main_queue(), ^{
764  VUOLOG_PROFILE_END(mainQueue);
765  [vci->session stopRunning];
766  });
767  dispatch_sync(vci->queue, ^{});
768 
769  [vci->session release];
770  [vci->delegate release];
771 
772  if (vci->textureCache)
773  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
774 #pragma clang diagnostic push
775 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
776  CVOpenGLTextureCacheRelease(vci->textureCache);
777 #pragma clang diagnostic pop
778  });
779 
780  dispatch_release(vci->queue);
781 
783 }