Vuo  2.0.0
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 #ifndef NS_RETURNS_INNER_POINTER
19 #define NS_RETURNS_INNER_POINTER
20 #endif
21 #include <IOKit/IOKitLib.h>
22 #include <CoreMediaIO/CMIOHardware.h>
23 #include <AVFoundation/AVFoundation.h>
24 #import <OpenGL/gl.h>
25 
26 #ifdef VUO_COMPILER
28  "title" : "VuoVideoCapture",
29  "dependencies" : [
30  "VuoImage",
31  "VuoOsStatus",
32  "VuoUsbVendor",
33  "VuoVideoInputDevice",
34  "VuoList_VuoVideoInputDevice",
35  "AVFoundation.framework",
36  "CoreMedia.framework",
37  "CoreMediaIO.framework",
38  "CoreVideo.framework",
39  "IOKit.framework",
40  "AppKit.framework",
41  ]
42  });
43 #endif
44 
45 extern "C"
46 {
47 
52 {
53  static dispatch_once_t enableOnce = 0;
54  dispatch_once(&enableOnce, ^{
56  CMIOObjectPropertyAddress property = { kCMIOHardwarePropertyAllowScreenCaptureDevices, kCMIOObjectPropertyScopeGlobal, kCMIOObjectPropertyElementMaster };
57  double t0 = VuoLogGetTime();
58  bool hasProperty = CMIOObjectHasProperty(kCMIOObjectSystemObject, &property);
59  double t1 = VuoLogGetTime();
60  if (t1 - t0 > 1)
61  VUserLog("Warning: Apple CoreMediaIO took %.1f seconds to initialize its 3rd-party plugins.", t1 - t0);
62 
63  if (hasProperty)
64  {
65  UInt32 yes = 1;
66  OSStatus ret = CMIOObjectSetPropertyData(kCMIOObjectSystemObject, &property, 0, NULL, sizeof(yes), &yes);
67  if (ret != kCMIOHardwareNoError)
68  {
69  char *errorText = VuoOsStatus_getText(ret);
70  VUserLog("Warning: Couldn't enable tethered iOS device support: %s", errorText);
71  free(errorText);
72  }
73  }
74  });
75  });
76 }
77 
78 }
79 
81 
85 typedef struct
86 {
87  CVOpenGLTextureCacheRef textureCache;
88  dispatch_queue_t queue;
90  AVCaptureDevice *device;
91  AVCaptureDeviceInput *input;
92  AVCaptureSession *session;
94  void (*receivedFrameTrigger)(VuoVideoFrame image);
98  AVCaptureDeviceFormat *preferredFormat;
100  double firstFrameTime;
102 
103 static dispatch_queue_t VuoVideoCapture_pendingDevicesQueue;
104 static std::set<VuoVideoCaptureInternal *> VuoVideoCapture_pendingDevices;
105 
107 
114 {
115  for (auto dev = VuoVideoCapture_pendingDevices.begin(); dev != VuoVideoCapture_pendingDevices.end();)
116  {
118  if ((*dev)->device)
119  VuoVideoCapture_pendingDevices.erase(*dev++);
120  else
121  ++dev;
122  }
123 }
124 
128 @interface VuoVideoCaptureDeviceListener : NSObject
129 {
131 }
132 
133 - (id) init;
134 - (void) devicesDidChange:(NSNotification*)notif;
135 - (void) addCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger;
136 - (void) removeCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger;
137 @end
138 
139 @implementation VuoVideoCaptureDeviceListener
140 
141 - (id) init
142 {
143  if (self = [super init])
144  {
145  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devicesDidChange:) name:AVCaptureDeviceWasConnectedNotification object:nil];
146  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devicesDidChange:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
147  }
148 
149  return self;
150 }
151 
152 - (void)dealloc
153 {
154  [NSNotificationCenter.defaultCenter removeObserver:self];
155  [super dealloc];
156 }
157 
158 - (void) addCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger
159 {
160  callbacks.addTrigger(outputTrigger);
161  outputTrigger(VuoVideoCapture_getInputDevices());
162 }
163 
164 - (void) removeCallback:(void(*)(VuoList_VuoVideoInputDevice)) outputTrigger
165 {
166  callbacks.removeTrigger(outputTrigger);
167 }
168 
169 - (void) devicesDidChange:(NSNotification*)notif
170 {
173 }
174 
175 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(AVCaptureDevice *)object change:(NSDictionary *)change context:(void *)p
176 {
178  if ([keyPath isEqualToString:@"connected"]
179  && !object.connected)
180  {
181  VUserLog("Warning: %s was disconnected. I'll keep looking for it.", VuoVideoInputDevice_getSummary(vci->vdevice));
182 
183  [vci->device removeObserver:self forKeyPath:@"connected" context:vci];
184 
185  [vci->session removeInput:vci->input];
186  vci->input = nil;
187  dispatch_async(VuoVideoCapture_pendingDevicesQueue, ^{
188  VuoVideoCapture_pendingDevices.insert(vci);
189  });
190  }
191 }
192 @end
193 
195 
200 {
202 
203  static dispatch_once_t init = 0;
204  dispatch_once(&init, ^{
206  VuoVideoCapture_pendingDevicesQueue = dispatch_queue_create("org.vuo.video.pendingDevicesQueue", NULL);
207  });
208 }
209 
214 {
215  if(deviceListener != nil)
216  [deviceListener removeCallback:devicesDidChange];
217 }
218 
223 {
225  [deviceListener addCallback:devicesDidChange];
226 }
227 
235 NSString *VuoVideoCapture_getVendorNameForUniqueID(NSString *uniqueID)
236 {
237  CFMutableDictionaryRef match_dictionary = IOServiceMatching("IOFireWireDevice");
238  io_iterator_t entry_iterator;
239  if (IOServiceGetMatchingServices(kIOMasterPortDefault, match_dictionary, &entry_iterator) == kIOReturnSuccess)
240  {
241  io_registry_entry_t serviceObject;
242  while ((serviceObject = IOIteratorNext(entry_iterator)))
243  {
244  CFMutableDictionaryRef serviceDictionary;
245  if (IORegistryEntryCreateCFProperties(serviceObject, &serviceDictionary, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
246  {
247  IOObjectRelease(serviceObject);
248  continue;
249  }
250 
251  NSString *guidAsHexString = [NSString stringWithFormat:@"%llx",[(NSNumber *)CFDictionaryGetValue(serviceDictionary, @"GUID") longLongValue]];
252  if ([uniqueID rangeOfString:guidAsHexString].location != NSNotFound)
253  {
254  NSString *vendorName = [(NSString *)CFDictionaryGetValue(serviceDictionary, @"FireWire Vendor Name") retain];
255  CFRelease(serviceDictionary);
256  IOObjectRelease(serviceObject);
257  IOObjectRelease(entry_iterator);
258  return vendorName;
259  }
260 
261  CFRelease(serviceDictionary);
262  IOObjectRelease(serviceObject);
263  }
264  IOObjectRelease(entry_iterator);
265  }
266 
267  match_dictionary = IOServiceMatching("IOUSBDevice");
268  if (IOServiceGetMatchingServices(kIOMasterPortDefault, match_dictionary, &entry_iterator) == kIOReturnSuccess)
269  {
270  io_registry_entry_t serviceObject;
271  while ((serviceObject = IOIteratorNext(entry_iterator)))
272  {
273  CFMutableDictionaryRef serviceDictionary;
274  if (IORegistryEntryCreateCFProperties(serviceObject, &serviceDictionary, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
275  {
276  IOObjectRelease(serviceObject);
277  continue;
278  }
279 
280  NSString *guidAsHexString = [NSString stringWithFormat:@"%llx",[(NSNumber *)CFDictionaryGetValue(serviceDictionary, @"locationID") longLongValue]];
281  if ([uniqueID rangeOfString:guidAsHexString].location != NSNotFound)
282  {
283  NSString *vendorName = [(NSString *)CFDictionaryGetValue(serviceDictionary, @"USB Vendor Name") retain];
284  if (!vendorName.length)
285  {
286  char *vendor = VuoUsbVendor_getText(((NSNumber *)CFDictionaryGetValue(serviceDictionary, @"idVendor")).intValue);
287  vendorName = [[NSString stringWithUTF8String:vendor] retain];
288  free(vendor);
289  }
290 
291  CFRelease(serviceDictionary);
292  IOObjectRelease(serviceObject);
293  IOObjectRelease(entry_iterator);
294  return vendorName;
295  }
296 
297  CFRelease(serviceDictionary);
298  IOObjectRelease(serviceObject);
299  }
300  IOObjectRelease(entry_iterator);
301  }
302 
303  return @"";
304 }
305 
310 {
311  NSString *deviceName = dev.localizedName;
312 
313  NSString *vendorName = VuoVideoCapture_getVendorNameForUniqueID([dev uniqueID]);
314 
315  NSString *modelWithoutVendor = dev.modelID;
316  NSUInteger tail = [modelWithoutVendor rangeOfString:@" Camera VendorID_"].location;
317  if (tail != NSNotFound)
318  modelWithoutVendor = [modelWithoutVendor substringToIndex:tail];
319  if (modelWithoutVendor.length
320  && [dev.manufacturer rangeOfString:modelWithoutVendor].location == NSNotFound)
321  deviceName = [NSString stringWithFormat:@"%@ %@", modelWithoutVendor, deviceName];
322 
323  if (![dev.manufacturer isEqualToString:vendorName]
324  && ![dev.manufacturer isEqualToString:@"Unknown"])
325  deviceName = [NSString stringWithFormat:@"%@ %@", dev.manufacturer, deviceName];
326 
327  if ([vendorName length])
328  deviceName = [NSString stringWithFormat:@"%@ %@", vendorName, deviceName];
329 
330  // manufacturer=0x00000001 LLC. IOReg= modelID= name=VirtualEyez HD result=[0x00000001 LLC. VirtualEyez HD]
331  // manufacturer=Allocinit.com IOReg= modelID=CamTwist name=CamTwist result=[Allocinit.com CamTwist CamTwist]
332  // manufacturer=Apple Inc. IOReg= modelID=Apple Camera VendorID_0x106B ProductID_0x1570 name=FaceTime HD Camera result=[Apple Inc. FaceTime HD Camera]
333  // manufacturer=Apple Inc. IOReg= modelID=iOS Device name=smokris iPhone result=[Apple Inc. iOS Device smokris iPhone]
334  // manufacturer=Canon IOReg=Canon modelID= name=VIXIA HV30 result=[Canon VIXIA HV30]
335  // manufacturer=Kinoni IOReg= modelID=EpocCamDevice:0:0 name=EpocCam result=[Kinoni EpocCamDevice:0:0 EpocCam]
336  // manufacturer=SnapVendor IOReg= modelID=SnapCamera name=Snap Camera result=[SnapVendor SnapCamera Snap Camera]
337  // manufacturer=Telestream IOReg= modelID=WCVC-0.4.0 name=Wirecast Virtual Camera result=[Telestream WCVC-0.4.0 Wirecast Virtual Camera]
338  // manufacturer=Unknown IOReg=Logitech Inc. modelID=UVC Camera VendorID_1133 ProductID_2053 name=USB Camera #2 result=[Logitech Inc. UVC USB Camera #2]
339  // manufacturer=Unknown IOReg=Logitech Inc. modelID=UVC Camera VendorID_1133 ProductID_2081 name=USB Camera result=[Logitech Inc. UVC USB Camera]
340  // 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]
341  // manufacturer=Visicom Inc. IOReg= modelID= name=ManyCam Virtual Webcam result=[Visicom Inc. ManyCam Virtual Webcam]
342 // NSLog(@"manufacturer=%@ IOReg=%@ modelID=%@ name=%@ result=[%@]", dev.manufacturer, vendorName, dev.modelID, dev.localizedName, deviceName);
343 
344  [vendorName release];
345  return VuoText_make([deviceName UTF8String]);
346 }
347 
352 {
354 
355  // Use +devices instead of +devicesWithMediaType:AVMediaTypeVideo,
356  // since the latter doesn't include tethered iOS devices.
357  NSArray *inputDevices = [AVCaptureDevice devices];
358 
360 
361  for (AVCaptureDevice *dev in inputDevices)
362  {
363  if (![dev hasMediaType:AVMediaTypeVideo]
364  && ![dev hasMediaType:AVMediaTypeMuxed])
365  continue;
366 
367  VuoText uniqueID = VuoText_make([[dev uniqueID] UTF8String]);
368 
370  }
371 
372  return devices;
373 }
374 
376 
380 static void VuoVideoCapture_freeCallback(VuoImage imageToFree)
381 {
382 }
383 
387 @interface VuoVideoCaptureDelegate : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
389 @end
390 
391 @implementation VuoVideoCaptureDelegate
397 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
398 {
399  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
400  if (!pixelBuffer)
401  {
402  VUserLog("Warning: AV Foundation gave us a sampleBuffer without a pixelBuffer.");
403  return;
404  }
405 
406  size_t pixelBufferWidth = CVPixelBufferGetWidth(pixelBuffer);
407  size_t pixelBufferHeight = CVPixelBufferGetHeight(pixelBuffer);
408 
409  // Work around AV Foundation bug where it sometimes ignores format changes on some cameras (e.g., 640x480@60FPS Logitech C910).
410  CMVideoDimensions preferredDim = CMVideoFormatDescriptionGetDimensions(_vci->preferredFormat.formatDescription);
411  if ((preferredDim.width && pixelBufferWidth != preferredDim.width)
412  || (preferredDim.height && pixelBufferHeight != preferredDim.height))
413  {
414  const int maxSetSizeAttempts = 10;
415  if (_vci->attemptedSetSizes < maxSetSizeAttempts)
416  {
417  ++_vci->attemptedSetSizes;
418  VUserLog("Warning: We requested %dx%d, but the camera gave us %zux%zu. Trying again (attempt %d of %d)…",
419  preferredDim.width, preferredDim.height,
420  pixelBufferWidth, pixelBufferHeight,
421  _vci->attemptedSetSizes, maxSetSizeAttempts);
422  VuoVideoCapture_setSize(_vci, _vci->preferredWidth, _vci->preferredHeight);
423  return;
424  }
425  else if (_vci->attemptedSetSizes == maxSetSizeAttempts)
426  {
427  ++_vci->attemptedSetSizes;
428  VUserLog("Warning: The camera didn't get the hint after %d attempts, so we'll just let it do what it wants…", maxSetSizeAttempts);
429  }
430  }
431 
432  __block CVOpenGLTextureRef texture;
433  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
434  CVOpenGLTextureCacheCreateTextureFromImage(NULL, _vci->textureCache, pixelBuffer, NULL, &texture);
435  });
436 
437  NSDictionary *pixelAspectRatio = (NSDictionary *)CVBufferGetAttachment(pixelBuffer, CFSTR("CVPixelAspectRatio"), nullptr);
438  unsigned int stretchedWidth = 0;
439  if (pixelAspectRatio)
440  stretchedWidth = pixelBufferWidth * ((NSNumber *)pixelAspectRatio[@"HorizontalSpacing"]).intValue / ((NSNumber *)pixelAspectRatio[@"VerticalSpacing"]).intValue;
441 
443  CVOpenGLTextureGetName(texture),
444  GL_RGB,
445  pixelBufferWidth,
446  pixelBufferHeight,
448  VuoRetain(rectImage);
449  VuoImage image = VuoImage_makeCopy(rectImage, CVOpenGLTextureIsFlipped(texture), stretchedWidth, 0, false);
450  if (_vci->firstFrameTime == -INFINITY)
451  _vci->firstFrameTime = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer));
452  if (_vci->receivedFrameTrigger)
453  _vci->receivedFrameTrigger(VuoVideoFrame_make(image,
454  CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) - _vci->firstFrameTime,
455  CMTimeGetSeconds(CMSampleBufferGetDuration(sampleBuffer))));
456 
457  CVOpenGLTextureRelease(texture);
458  VuoRelease(rectImage);
459 
460  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
461  CVOpenGLTextureCacheFlush(_vci->textureCache, 0);
462  });
463 }
464 @end
465 
470 {
472  vci->device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
473  else
474  {
475  vci->device = [AVCaptureDevice deviceWithUniqueID:[NSString stringWithUTF8String:vci->vdevice.id]];
476 
477  // If that fails, try to match the device name
478  if (vci->vdevice.matchType == VuoVideoInputDevice_MatchIdThenName
480  && !vci->device)
481  {
482  for (AVCaptureDevice *dev in AVCaptureDevice.devices)
483  {
484  if (![dev hasMediaType:AVMediaTypeVideo]
485  && ![dev hasMediaType:AVMediaTypeMuxed])
486  continue;
487 
488  if (strstr(VuoVideoCapture_getDeviceName(dev), vci->vdevice.name))
489  {
490  vci->device = dev;
491  break;
492  }
493  }
494  }
495  }
496 
497  if (vci->device)
498  {
499  [vci->device addObserver:deviceListener forKeyPath:@"connected" options:0 context:vci];
500 
502  if (vci->preferredListening)
504 
505  NSError *e;
506  vci->input = [AVCaptureDeviceInput deviceInputWithDevice:vci->device error:&e];
507  if (vci->input)
508  {
509  if ([vci->session canAddInput:vci->input])
510  [vci->session addInput:vci->input];
511  else
512  VUserLog("Error: -[AVCaptureSession canAddInput:] said no.");
513  }
514  else
515  VUserLog("Error: Couldn't create AVCaptureDeviceInput: %s", e.localizedDescription.UTF8String);
516 
517  vci->firstFrameTime = -INFINITY;
518  }
519 }
520 
525 {
527 
528  if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)])
529  {
530  long status = (long)[AVCaptureDevice performSelector:@selector(authorizationStatusForMediaType:) withObject:@"vide"];
531  if (status == 0 /* AVAuthorizationStatusNotDetermined */)
532  VUserLog("Warning: Video input may be unavailable due to system restrictions. Check System Preferences > Security & Privacy > Privacy > Camera.");
533  else if (status == 1 /* AVAuthorizationStatusRestricted */
534  || status == 2 /* AVAuthorizationStatusDenied */)
535  VUserLog("Error: Video input is unavailable due to system restrictions. Check System Preferences > Security & Privacy > Privacy > Camera.");
536  }
537 
540 
541  vci->receivedFrameTrigger = receivedFrame;
542 
543  vci->queue = dispatch_queue_create("org.vuo.VuoVideoCapture", NULL);
544 
545  VUOLOG_PROFILE_BEGIN(mainQueue);
546  dispatch_sync(dispatch_get_main_queue(), ^{
547  VUOLOG_PROFILE_END(mainQueue);
549  vci->delegate.vci = vci;
550 
551  vci->session = [AVCaptureSession new];
552  vci->session.sessionPreset = AVCaptureSessionPresetPhoto;
553 
554  vci->vdevice = inputDevice;
556 
557  {
559  if (!vci->device)
560  {
561  VUserLog("Warning: %s isn't currently available. I'll keep trying.", VuoVideoInputDevice_getSummary(inputDevice));
562  dispatch_async(VuoVideoCapture_pendingDevicesQueue, ^{
564  });
565  }
566  }
567 
568  {
569  AVCaptureVideoDataOutput *output = [AVCaptureVideoDataOutput new];
570  [output setAlwaysDiscardsLateVideoFrames:YES];
571  [output setSampleBufferDelegate:vci->delegate queue:vci->queue];
572  [vci->session addOutput:output];
573  [output release];
574  }
575  });
576 
577  {
578  CGLPixelFormatObj pf = (CGLPixelFormatObj)VuoGlContext_makePlatformPixelFormat(false, false, -1);
579  __block CVReturn ret;
580  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
581  ret = CVOpenGLTextureCacheCreate(NULL, NULL, cgl_ctx, pf, NULL, &vci->textureCache);
582  });
583  CGLReleasePixelFormat(pf);
584 
585  if (ret != kCVReturnSuccess)
586  {
587  VUserLog("Error: Couldn't create texture cache: %d", ret);
589  return NULL;
590  }
591  }
592 
593  return (VuoVideoCapture)vci;
594 }
595 
600 {
602  if (!vci)
603  return;
604 
605  vci->preferredWidth = width;
606  vci->preferredHeight = height;
607 
608  if (!vci->device)
609  return;
610 
611  AVCaptureDeviceFormat *bestFormat = nil;
612  AVFrameRateRange *bestFrameRateRange = nil;
613  if (width == 0 && height == 0)
614  {
615  // By default, pick the format with the highest framerate.
616  for (AVCaptureDeviceFormat *format in vci->device.formats)
617  for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
618  if (range.maxFrameRate > bestFrameRateRange.maxFrameRate)
619  {
620  bestFormat = format;
621  bestFrameRateRange = range;
622  }
623 
624  // Then, if there are multiple resolutions with that framerate, pick the highest resolution.
625  for (AVCaptureDeviceFormat *format in vci->device.formats)
626  {
627  CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
628  for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
629  {
630  CMVideoDimensions bestDim = CMVideoFormatDescriptionGetDimensions(bestFormat.formatDescription);
631  if (VuoReal_areEqual(range.maxFrameRate, bestFrameRateRange.maxFrameRate)
632  && dim.width > bestDim.width
633  && dim.height > bestDim.height)
634  {
635  bestFormat = format;
636  bestFrameRateRange = range;
637  }
638  }
639  }
640  }
641  else
642  {
643  // Pick the format with the closest resolution.
644  float bestDist = INFINITY;
645  for (AVCaptureDeviceFormat *format in vci->device.formats)
646  {
647  CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
648  float dist = pow(dim.width - width, 2) + pow(dim.height - height, 2);
649  if (dist < bestDist)
650  {
651  bestDist = dist;
652  bestFormat = format;
653  bestFrameRateRange = nil;
654  for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges)
655  if (range.maxFrameRate > bestFrameRateRange.maxFrameRate)
656  bestFrameRateRange = range;
657  }
658  }
659  }
660 
661  if (bestFormat)
662  {
663  if ([vci->device lockForConfiguration:NULL] == YES)
664  {
665  vci->device.activeFormat = bestFormat;
666 
667  [vci->preferredFormat release];
668  vci->preferredFormat = [bestFormat retain];
669  if (bestFrameRateRange)
670  {
671  vci->device.activeVideoMinFrameDuration = bestFrameRateRange.minFrameDuration;
672  vci->device.activeVideoMaxFrameDuration = bestFrameRateRange.minFrameDuration;
673  }
674 
675  [vci->device unlockForConfiguration];
676  }
677  else
678  VUserLog("Warning: Couldn't lock the video input device for configuration; ignoring the specified resolution.");
679  }
680  else
681  {
682  [vci->preferredFormat release];
683  vci->preferredFormat = nil;
684  VUserLog("Warning: This device didn't report any supported formats.");
685  }
686 }
687 
692 {
694  if (!vci)
695  return;
696 
697  vci->preferredListening = true;
698  VUOLOG_PROFILE_BEGIN(mainQueue);
699  dispatch_sync(dispatch_get_main_queue(), ^{
700  VUOLOG_PROFILE_END(mainQueue);
701  [vci->session startRunning];
702  });
703 }
704 
709 {
711  if (!vci)
712  return;
713 
714  vci->preferredListening = false;
715  VUOLOG_PROFILE_BEGIN(mainQueue);
716  dispatch_sync(dispatch_get_main_queue(), ^{
717  VUOLOG_PROFILE_END(mainQueue);
718  [vci->session stopRunning];
719  });
720 }
721 
726 {
728  if (!vci)
729  return;
730 
731  vci->receivedFrameTrigger = receivedFrame;
732 }
733 
737 void VuoVideoCapture_free(void *p)
738 {
740 
741  dispatch_sync(VuoVideoCapture_pendingDevicesQueue, ^{
743  });
744 
745  [vci->device removeObserver:deviceListener forKeyPath:@"connected" context:vci];
746 
747  VUOLOG_PROFILE_BEGIN(mainQueue);
748  dispatch_sync(dispatch_get_main_queue(), ^{
749  VUOLOG_PROFILE_END(mainQueue);
750  [vci->session stopRunning];
751  });
752  dispatch_sync(vci->queue, ^{});
753 
754  [vci->session release];
755  [vci->delegate release];
756 
757  if (vci->textureCache)
758  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
759  CVOpenGLTextureCacheRelease(vci->textureCache);
760  });
761 
762  dispatch_release(vci->queue);
763 
765 }