The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1327 lines
59KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. struct CameraDevice::Pimpl
  14. {
  15. using InternalOpenCameraResultCallback = std::function<void(const String& /*cameraId*/, const String& /*error*/)>;
  16. Pimpl (CameraDevice& ownerToUse, const String& cameraIdToUse, int /*index*/,
  17. int /*minWidth*/, int /*minHeight*/, int /*maxWidth*/, int /*maxHeight*/,
  18. bool useHighQuality)
  19. : owner (ownerToUse),
  20. cameraId (cameraIdToUse),
  21. captureSession (*this, useHighQuality)
  22. {
  23. }
  24. String getCameraId() const noexcept { return cameraId; }
  25. void open (InternalOpenCameraResultCallback cameraOpenCallbackToUse)
  26. {
  27. cameraOpenCallback = std::move (cameraOpenCallbackToUse);
  28. if (cameraOpenCallback == nullptr)
  29. {
  30. // A valid camera open callback must be passed.
  31. jassertfalse;
  32. return;
  33. }
  34. [AVCaptureDevice requestAccessForMediaType: AVMediaTypeVideo
  35. completionHandler: ^(BOOL granted)
  36. {
  37. // Access to video is required for camera to work,
  38. // black images will be produced otherwise!
  39. jassert (granted);
  40. ignoreUnused (granted);
  41. }];
  42. [AVCaptureDevice requestAccessForMediaType: AVMediaTypeAudio
  43. completionHandler: ^(BOOL granted)
  44. {
  45. // Access to audio is required for camera to work,
  46. // silence will be produced otherwise!
  47. jassert (granted);
  48. ignoreUnused (granted);
  49. }];
  50. captureSession.startSessionForDeviceWithId (cameraId);
  51. }
  52. bool openedOk() const noexcept { return captureSession.openedOk(); }
  53. void takeStillPicture (std::function<void(const Image&)> pictureTakenCallbackToUse)
  54. {
  55. if (pictureTakenCallbackToUse == nullptr)
  56. {
  57. jassertfalse;
  58. return;
  59. }
  60. pictureTakenCallback = std::move (pictureTakenCallbackToUse);
  61. triggerStillPictureCapture();
  62. }
  63. void startRecordingToFile (const File& file, int /*quality*/)
  64. {
  65. file.deleteFile();
  66. captureSession.startRecording (file);
  67. }
  68. void stopRecording()
  69. {
  70. captureSession.stopRecording();
  71. }
  72. Time getTimeOfFirstRecordedFrame() const
  73. {
  74. return captureSession.getTimeOfFirstRecordedFrame();
  75. }
  76. static StringArray getAvailableDevices()
  77. {
  78. StringArray results;
  79. JUCE_CAMERA_LOG ("Available camera devices: ");
  80. for (AVCaptureDevice* device in getDevices())
  81. {
  82. JUCE_CAMERA_LOG ("Device start----------------------------------");
  83. printDebugCameraInfo (device);
  84. JUCE_CAMERA_LOG ("Device end----------------------------------");
  85. results.add (nsStringToJuce (device.uniqueID));
  86. }
  87. return results;
  88. }
  89. void addListener (CameraDevice::Listener* listenerToAdd)
  90. {
  91. const ScopedLock sl (listenerLock);
  92. listeners.add (listenerToAdd);
  93. if (listeners.size() == 1)
  94. triggerStillPictureCapture();
  95. }
  96. void removeListener (CameraDevice::Listener* listenerToRemove)
  97. {
  98. const ScopedLock sl (listenerLock);
  99. listeners.remove (listenerToRemove);
  100. }
  101. private:
  102. static NSArray<AVCaptureDevice*>* getDevices()
  103. {
  104. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  105. if (iosVersion.major >= 10)
  106. {
  107. std::unique_ptr<NSMutableArray<AVCaptureDeviceType>, NSObjectDeleter> deviceTypes ([[NSMutableArray alloc] initWithCapacity: 2]);
  108. [deviceTypes.get() addObject: AVCaptureDeviceTypeBuiltInWideAngleCamera];
  109. [deviceTypes.get() addObject: AVCaptureDeviceTypeBuiltInTelephotoCamera];
  110. if ((iosVersion.major == 10 && iosVersion.minor >= 2) || iosVersion.major >= 11)
  111. [deviceTypes.get() addObject: AVCaptureDeviceTypeBuiltInDualCamera];
  112. if ((iosVersion.major == 11 && iosVersion.minor >= 1) || iosVersion.major >= 12)
  113. [deviceTypes.get() addObject: AVCaptureDeviceTypeBuiltInTrueDepthCamera];
  114. auto discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: deviceTypes.get()
  115. mediaType: AVMediaTypeVideo
  116. position: AVCaptureDevicePositionUnspecified];
  117. return [discoverySession devices];
  118. }
  119. #endif
  120. return [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo];
  121. }
  122. //==============================================================================
  123. static void printDebugCameraInfo (AVCaptureDevice* device)
  124. {
  125. auto position = device.position;
  126. String positionString = position == AVCaptureDevicePositionBack
  127. ? "Back"
  128. : position == AVCaptureDevicePositionFront
  129. ? "Front"
  130. : "Unspecified";
  131. JUCE_CAMERA_LOG ("Position: " + positionString);
  132. JUCE_CAMERA_LOG ("Model ID: " + nsStringToJuce (device.modelID));
  133. JUCE_CAMERA_LOG ("Localized name: " + nsStringToJuce (device.localizedName));
  134. JUCE_CAMERA_LOG ("Unique ID: " + nsStringToJuce (device.uniqueID));
  135. JUCE_CAMERA_LOG ("Lens aperture: " + String (device.lensAperture));
  136. JUCE_CAMERA_LOG ("Has flash: " + String ((int)device.hasFlash));
  137. JUCE_CAMERA_LOG ("Supports flash always on: " + String ((int)[device isFlashModeSupported: AVCaptureFlashModeOn]));
  138. JUCE_CAMERA_LOG ("Supports auto flash: " + String ((int)[device isFlashModeSupported: AVCaptureFlashModeAuto]));
  139. JUCE_CAMERA_LOG ("Has torch: " + String ((int)device.hasTorch));
  140. JUCE_CAMERA_LOG ("Supports torch always on: " + String ((int)[device isTorchModeSupported: AVCaptureTorchModeOn]));
  141. JUCE_CAMERA_LOG ("Supports auto torch: " + String ((int)[device isTorchModeSupported: AVCaptureTorchModeAuto]));
  142. JUCE_CAMERA_LOG ("Low light boost supported: " + String ((int)device.lowLightBoostEnabled));
  143. JUCE_CAMERA_LOG ("Supports auto white balance: " + String ((int)[device isWhiteBalanceModeSupported: AVCaptureWhiteBalanceModeAutoWhiteBalance]));
  144. JUCE_CAMERA_LOG ("Supports continuous auto white balance: " + String ((int)[device isWhiteBalanceModeSupported: AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]));
  145. JUCE_CAMERA_LOG ("Supports auto focus: " + String ((int)[device isFocusModeSupported: AVCaptureFocusModeAutoFocus]));
  146. JUCE_CAMERA_LOG ("Supports continuous auto focus: " + String ((int)[device isFocusModeSupported: AVCaptureFocusModeContinuousAutoFocus]));
  147. JUCE_CAMERA_LOG ("Supports point of interest focus: " + String ((int)device.focusPointOfInterestSupported));
  148. JUCE_CAMERA_LOG ("Smooth auto focus supported: " + String ((int)device.smoothAutoFocusSupported));
  149. JUCE_CAMERA_LOG ("Auto focus range restriction supported: " + String ((int)device.autoFocusRangeRestrictionSupported));
  150. JUCE_CAMERA_LOG ("Supports auto exposure: " + String ((int)[device isExposureModeSupported: AVCaptureExposureModeAutoExpose]));
  151. JUCE_CAMERA_LOG ("Supports continuous auto exposure: " + String ((int)[device isExposureModeSupported: AVCaptureExposureModeContinuousAutoExposure]));
  152. JUCE_CAMERA_LOG ("Supports custom exposure: " + String ((int)[device isExposureModeSupported: AVCaptureExposureModeCustom]));
  153. JUCE_CAMERA_LOG ("Supports point of interest exposure: " + String ((int)device.exposurePointOfInterestSupported));
  154. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  155. if (iosVersion.major >= 10)
  156. {
  157. JUCE_CAMERA_LOG ("Device type: " + nsStringToJuce (device.deviceType));
  158. JUCE_CAMERA_LOG ("Locking focus with custom lens position supported: " + String ((int)device.lockingFocusWithCustomLensPositionSupported));
  159. }
  160. #endif
  161. #if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
  162. if (iosVersion.major >= 11)
  163. {
  164. JUCE_CAMERA_LOG ("Min available video zoom factor: " + String (device.minAvailableVideoZoomFactor));
  165. JUCE_CAMERA_LOG ("Max available video zoom factor: " + String (device.maxAvailableVideoZoomFactor));
  166. JUCE_CAMERA_LOG ("Dual camera switch over video zoom factor: " + String (device.dualCameraSwitchOverVideoZoomFactor));
  167. }
  168. #endif
  169. JUCE_CAMERA_LOG ("Capture formats start-------------------");
  170. for (AVCaptureDeviceFormat* format in device.formats)
  171. {
  172. JUCE_CAMERA_LOG ("Capture format start------");
  173. printDebugCameraFormatInfo (format);
  174. JUCE_CAMERA_LOG ("Capture format end------");
  175. }
  176. JUCE_CAMERA_LOG ("Capture formats end-------------------");
  177. }
  178. static void printDebugCameraFormatInfo (AVCaptureDeviceFormat* format)
  179. {
  180. JUCE_CAMERA_LOG ("Media type: " + nsStringToJuce (format.mediaType));
  181. String colourSpaces;
  182. for (NSNumber* number in format.supportedColorSpaces)
  183. {
  184. switch ([number intValue])
  185. {
  186. case AVCaptureColorSpace_sRGB: colourSpaces << "sRGB "; break;
  187. case AVCaptureColorSpace_P3_D65: colourSpaces << "P3_D65 "; break;
  188. default: break;
  189. }
  190. }
  191. JUCE_CAMERA_LOG ("Supported colour spaces: " + colourSpaces);
  192. JUCE_CAMERA_LOG ("Video field of view: " + String (format.videoFieldOfView));
  193. JUCE_CAMERA_LOG ("Video max zoom factor: " + String (format.videoMaxZoomFactor));
  194. JUCE_CAMERA_LOG ("Video zoom factor upscale threshold: " + String (format.videoZoomFactorUpscaleThreshold));
  195. String videoFrameRateRangesString = "Video supported frame rate ranges: ";
  196. for (AVFrameRateRange* range in format.videoSupportedFrameRateRanges)
  197. videoFrameRateRangesString << frameRateRangeToString (range);
  198. JUCE_CAMERA_LOG (videoFrameRateRangesString);
  199. JUCE_CAMERA_LOG ("Video binned: " + String (int(format.videoBinned)));
  200. #if defined (__IPHONE_8_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0
  201. if (iosVersion.major >= 8)
  202. {
  203. JUCE_CAMERA_LOG ("Video HDR supported: " + String (int (format.videoHDRSupported)));
  204. JUCE_CAMERA_LOG ("High resolution still image dimensions: " + getHighResStillImgDimensionsString (format.highResolutionStillImageDimensions));
  205. JUCE_CAMERA_LOG ("Min ISO: " + String (format.minISO));
  206. JUCE_CAMERA_LOG ("Max ISO: " + String (format.maxISO));
  207. JUCE_CAMERA_LOG ("Min exposure duration: " + cmTimeToString (format.minExposureDuration));
  208. String autoFocusSystemString;
  209. switch (format.autoFocusSystem)
  210. {
  211. case AVCaptureAutoFocusSystemPhaseDetection: autoFocusSystemString = "PhaseDetection"; break;
  212. case AVCaptureAutoFocusSystemContrastDetection: autoFocusSystemString = "ContrastDetection"; break;
  213. case AVCaptureAutoFocusSystemNone:
  214. default: autoFocusSystemString = "None";
  215. }
  216. JUCE_CAMERA_LOG ("Auto focus system: " + autoFocusSystemString);
  217. JUCE_CAMERA_LOG ("Standard (iOS 5.0) video stabilization supported: " + String ((int) [format isVideoStabilizationModeSupported: AVCaptureVideoStabilizationModeStandard]));
  218. JUCE_CAMERA_LOG ("Cinematic video stabilization supported: " + String ((int) [format isVideoStabilizationModeSupported: AVCaptureVideoStabilizationModeCinematic]));
  219. JUCE_CAMERA_LOG ("Auto video stabilization supported: " + String ((int) [format isVideoStabilizationModeSupported: AVCaptureVideoStabilizationModeAuto]));
  220. }
  221. #endif
  222. #if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
  223. if (iosVersion.major >= 11)
  224. {
  225. JUCE_CAMERA_LOG ("Min zoom factor for depth data delivery: " + String (format.videoMinZoomFactorForDepthDataDelivery));
  226. JUCE_CAMERA_LOG ("Max zoom factor for depth data delivery: " + String (format.videoMaxZoomFactorForDepthDataDelivery));
  227. }
  228. #endif
  229. }
  230. static String getHighResStillImgDimensionsString (CMVideoDimensions d)
  231. {
  232. return "[" + String (d.width) + " " + String (d.height) + "]";
  233. }
  234. static String cmTimeToString (CMTime time)
  235. {
  236. CFStringRef timeDesc = CMTimeCopyDescription (nullptr, time);
  237. String result = String::fromCFString (timeDesc);
  238. CFRelease (timeDesc);
  239. return result;
  240. }
  241. static String frameRateRangeToString (AVFrameRateRange* range)
  242. {
  243. String result;
  244. result << "[minFrameDuration: " + cmTimeToString (range.minFrameDuration);
  245. result << " maxFrameDuration: " + cmTimeToString (range.maxFrameDuration);
  246. result << " minFrameRate: " + String (range.minFrameRate);
  247. result << " maxFrameRate: " + String (range.maxFrameRate) << "] ";
  248. return result;
  249. }
  250. //==============================================================================
  251. class CaptureSession
  252. {
  253. public:
  254. CaptureSession (Pimpl& ownerToUse, bool useHighQuality)
  255. : owner (ownerToUse),
  256. captureSessionQueue (dispatch_queue_create ("JuceCameraDeviceBackgroundDispatchQueue", DISPATCH_QUEUE_SERIAL)),
  257. captureSession ([[AVCaptureSession alloc] init]),
  258. delegate (nullptr),
  259. stillPictureTaker (*this),
  260. videoRecorder (*this)
  261. {
  262. static SessionDelegateClass cls;
  263. delegate.reset ([cls.createInstance() init]);
  264. SessionDelegateClass::setOwner (delegate.get(), this);
  265. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  266. [[NSNotificationCenter defaultCenter] addObserver: delegate.get()
  267. selector: @selector (sessionDidStartRunning:)
  268. name: AVCaptureSessionDidStartRunningNotification
  269. object: captureSession.get()];
  270. [[NSNotificationCenter defaultCenter] addObserver: delegate.get()
  271. selector: @selector (sessionDidStopRunning:)
  272. name: AVCaptureSessionDidStopRunningNotification
  273. object: captureSession.get()];
  274. [[NSNotificationCenter defaultCenter] addObserver: delegate.get()
  275. selector: @selector (sessionRuntimeError:)
  276. name: AVCaptureSessionRuntimeErrorNotification
  277. object: captureSession.get()];
  278. [[NSNotificationCenter defaultCenter] addObserver: delegate.get()
  279. selector: @selector (sessionWasInterrupted:)
  280. name: AVCaptureSessionWasInterruptedNotification
  281. object: captureSession.get()];
  282. [[NSNotificationCenter defaultCenter] addObserver: delegate.get()
  283. selector: @selector (sessionInterruptionEnded:)
  284. name: AVCaptureSessionInterruptionEndedNotification
  285. object: captureSession.get()];
  286. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  287. dispatch_async (captureSessionQueue,^
  288. {
  289. [captureSession.get() setSessionPreset: useHighQuality ? AVCaptureSessionPresetHigh
  290. : AVCaptureSessionPresetMedium];
  291. });
  292. ++numCaptureSessions;
  293. }
  294. ~CaptureSession()
  295. {
  296. [[NSNotificationCenter defaultCenter] removeObserver: delegate.get()];
  297. stopRecording();
  298. if (--numCaptureSessions == 0)
  299. {
  300. dispatch_async (captureSessionQueue, ^
  301. {
  302. if (captureSession.get().running)
  303. [captureSession.get() stopRunning];
  304. sessionClosedEvent.signal();
  305. });
  306. sessionClosedEvent.wait (-1);
  307. }
  308. }
  309. bool openedOk() const noexcept { return sessionStarted; }
  310. void startSessionForDeviceWithId (const String& cameraIdToUse)
  311. {
  312. dispatch_async (captureSessionQueue,^
  313. {
  314. cameraDevice = [AVCaptureDevice deviceWithUniqueID: juceStringToNS (cameraIdToUse)];
  315. auto audioDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeAudio];
  316. [captureSession.get() beginConfiguration];
  317. // This will add just video...
  318. auto error = addInputToDevice (cameraDevice);
  319. if (error.isNotEmpty())
  320. {
  321. WeakReference<CaptureSession> weakRef (this);
  322. MessageManager::callAsync ([weakRef, error]() mutable
  323. {
  324. if (weakRef != nullptr)
  325. weakRef->owner.cameraOpenCallback ({}, error);
  326. });
  327. return;
  328. }
  329. // ... so add audio explicitly here
  330. error = addInputToDevice (audioDevice);
  331. if (error.isNotEmpty())
  332. {
  333. WeakReference<CaptureSession> weakRef (this);
  334. MessageManager::callAsync ([weakRef, error]() mutable
  335. {
  336. if (weakRef != nullptr)
  337. weakRef->owner.cameraOpenCallback ({}, error);
  338. });
  339. return;
  340. }
  341. [captureSession.get() commitConfiguration];
  342. if (! captureSession.get().running)
  343. [captureSession.get() startRunning];
  344. });
  345. }
  346. AVCaptureVideoPreviewLayer* createPreviewLayer()
  347. {
  348. if (! openedOk())
  349. {
  350. // A session must be started first!
  351. jassertfalse;
  352. return nullptr;
  353. }
  354. previewLayer = [AVCaptureVideoPreviewLayer layerWithSession: captureSession.get()];
  355. return previewLayer;
  356. }
  357. void takeStillPicture()
  358. {
  359. if (! openedOk())
  360. {
  361. // A session must be started first!
  362. jassert (openedOk());
  363. return;
  364. }
  365. stillPictureTaker.takePicture (previewLayer.connection.videoOrientation);
  366. }
  367. void startRecording (const File& file)
  368. {
  369. if (! openedOk())
  370. {
  371. // A session must be started first!
  372. jassertfalse;
  373. return;
  374. }
  375. if (file.existsAsFile())
  376. {
  377. // File overwriting is not supported by iOS video recorder, the target
  378. // file must not exist.
  379. jassertfalse;
  380. return;
  381. }
  382. videoRecorder.startRecording (file, previewLayer.connection.videoOrientation);
  383. }
  384. void stopRecording()
  385. {
  386. videoRecorder.stopRecording();
  387. }
  388. Time getTimeOfFirstRecordedFrame() const
  389. {
  390. return videoRecorder.getTimeOfFirstRecordedFrame();
  391. }
  392. JUCE_DECLARE_WEAK_REFERENCEABLE (CaptureSession)
  393. private:
  394. String addInputToDevice (AVCaptureDevice* device)
  395. {
  396. NSError* error = nil;
  397. auto input = [AVCaptureDeviceInput deviceInputWithDevice: device
  398. error: &error];
  399. if (error != nil)
  400. return nsStringToJuce (error.localizedDescription);
  401. if (! [captureSession.get() canAddInput: input])
  402. return "Could not add input to camera session.";
  403. [captureSession.get() addInput: input];
  404. return {};
  405. }
  406. //==============================================================================
  407. struct SessionDelegateClass : public ObjCClass<NSObject>
  408. {
  409. SessionDelegateClass() : ObjCClass<NSObject> ("SessionDelegateClass_")
  410. {
  411. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  412. addMethod (@selector (sessionDidStartRunning:), started, "v@:@");
  413. addMethod (@selector (sessionDidStopRunning:), stopped, "v@:@");
  414. addMethod (@selector (sessionRuntimeError:), runtimeError, "v@:@");
  415. addMethod (@selector (sessionWasInterrupted:), interrupted, "v@:@");
  416. addMethod (@selector (sessionInterruptionEnded:), interruptionEnded, "v@:@");
  417. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  418. addIvar<CaptureSession*> ("owner");
  419. registerClass();
  420. }
  421. //==============================================================================
  422. static CaptureSession& getOwner (id self) { return *getIvar<CaptureSession*> (self, "owner"); }
  423. static void setOwner (id self, CaptureSession* s) { object_setInstanceVariable (self, "owner", s); }
  424. private:
  425. //==============================================================================
  426. static void started (id self, SEL, NSNotification* notification)
  427. {
  428. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  429. ignoreUnused (notification);
  430. dispatch_async (dispatch_get_main_queue(),
  431. ^{
  432. getOwner (self).cameraSessionStarted();
  433. });
  434. }
  435. static void stopped (id, SEL, NSNotification* notification)
  436. {
  437. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  438. ignoreUnused (notification);
  439. }
  440. static void runtimeError (id self, SEL, NSNotification* notification)
  441. {
  442. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  443. dispatch_async (dispatch_get_main_queue(),
  444. ^{
  445. NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
  446. auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  447. getOwner (self).cameraSessionRuntimeError (errorString);
  448. });
  449. }
  450. static void interrupted (id, SEL, NSNotification* notification)
  451. {
  452. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  453. ignoreUnused (notification);
  454. }
  455. static void interruptionEnded (id, SEL, NSNotification* notification)
  456. {
  457. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  458. ignoreUnused (notification);
  459. }
  460. };
  461. //==============================================================================
  462. class StillPictureTaker
  463. {
  464. public:
  465. StillPictureTaker (CaptureSession& cs)
  466. : captureSession (cs),
  467. captureOutput (createCaptureOutput()),
  468. photoOutputDelegate (nullptr)
  469. {
  470. if (Pimpl::getIOSVersion().major >= 10)
  471. {
  472. static PhotoOutputDelegateClass cls;
  473. photoOutputDelegate.reset ([cls.createInstance() init]);
  474. PhotoOutputDelegateClass::setOwner (photoOutputDelegate.get(), this);
  475. }
  476. captureSession.addOutputIfPossible (captureOutput);
  477. }
  478. void takePicture (AVCaptureVideoOrientation orientationToUse)
  479. {
  480. if (takingPicture)
  481. {
  482. // Picture taking already in progress!
  483. jassertfalse;
  484. return;
  485. }
  486. takingPicture = true;
  487. printImageOutputDebugInfo (captureOutput);
  488. if (auto* connection = findVideoConnection (captureOutput))
  489. {
  490. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  491. if (Pimpl::getIOSVersion().major >= 10 && [captureOutput isKindOfClass: [AVCapturePhotoOutput class]])
  492. {
  493. auto* photoOutput = (AVCapturePhotoOutput*) captureOutput;
  494. auto outputConnection = [photoOutput connectionWithMediaType: AVMediaTypeVideo];
  495. outputConnection.videoOrientation = orientationToUse;
  496. [photoOutput capturePhotoWithSettings: [AVCapturePhotoSettings photoSettings]
  497. delegate: id<AVCapturePhotoCaptureDelegate> (photoOutputDelegate.get())];
  498. return;
  499. }
  500. #endif
  501. auto* stillImageOutput = (AVCaptureStillImageOutput*) captureOutput;
  502. auto outputConnection = [stillImageOutput connectionWithMediaType: AVMediaTypeVideo];
  503. outputConnection.videoOrientation = orientationToUse;
  504. [stillImageOutput captureStillImageAsynchronouslyFromConnection: connection completionHandler:
  505. ^(CMSampleBufferRef imageSampleBuffer, NSError* error)
  506. {
  507. if (error != nil)
  508. {
  509. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  510. jassertfalse;
  511. return;
  512. }
  513. NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: imageSampleBuffer];
  514. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  515. callListeners (image);
  516. MessageManager::callAsync ([this, image]() { notifyPictureTaken (image); });
  517. }];
  518. }
  519. else
  520. {
  521. // Could not find a connection of video type
  522. jassertfalse;
  523. }
  524. }
  525. private:
  526. static AVCaptureOutput* createCaptureOutput()
  527. {
  528. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  529. if (Pimpl::getIOSVersion().major >= 10)
  530. return [AVCapturePhotoOutput new];
  531. #endif
  532. return [AVCaptureStillImageOutput new];
  533. }
  534. static void printImageOutputDebugInfo (AVCaptureOutput* captureOutput)
  535. {
  536. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  537. if (Pimpl::getIOSVersion().major >= 10 && [captureOutput isKindOfClass: [AVCapturePhotoOutput class]])
  538. {
  539. auto* photoOutput = (AVCapturePhotoOutput*) captureOutput;
  540. String typesString;
  541. for (AVVideoCodecType type in photoOutput.availablePhotoCodecTypes)
  542. typesString << nsStringToJuce (type) << " ";
  543. JUCE_CAMERA_LOG ("Available image codec types: " + typesString);
  544. JUCE_CAMERA_LOG ("Still image stabilization supported: " + String ((int) photoOutput.stillImageStabilizationSupported));
  545. JUCE_CAMERA_LOG ("Dual camera fusion supported: " + String ((int) photoOutput.dualCameraFusionSupported));
  546. JUCE_CAMERA_LOG ("Supports flash: " + String ((int) [photoOutput.supportedFlashModes containsObject: @(AVCaptureFlashModeOn)]));
  547. JUCE_CAMERA_LOG ("Supports auto flash: " + String ((int) [photoOutput.supportedFlashModes containsObject: @(AVCaptureFlashModeAuto)]));
  548. JUCE_CAMERA_LOG ("Max bracketed photo count: " + String (photoOutput.maxBracketedCapturePhotoCount));
  549. JUCE_CAMERA_LOG ("Lens stabilization during bracketed capture supported: " + String ((int) photoOutput.lensStabilizationDuringBracketedCaptureSupported));
  550. JUCE_CAMERA_LOG ("Live photo capture supported: " + String ((int) photoOutput.livePhotoCaptureSupported));
  551. #if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
  552. if (Pimpl::getIOSVersion().major >= 11)
  553. {
  554. typesString.clear();
  555. for (AVFileType type in photoOutput.availablePhotoFileTypes)
  556. typesString << nsStringToJuce (type) << " ";
  557. JUCE_CAMERA_LOG ("Available photo file types: " + typesString);
  558. typesString.clear();
  559. for (AVFileType type in photoOutput.availableRawPhotoFileTypes)
  560. typesString << nsStringToJuce (type) << " ";
  561. JUCE_CAMERA_LOG ("Available RAW photo file types: " + typesString);
  562. typesString.clear();
  563. for (AVFileType type in photoOutput.availableLivePhotoVideoCodecTypes)
  564. typesString << nsStringToJuce (type) << " ";
  565. JUCE_CAMERA_LOG ("Available live photo video codec types: " + typesString);
  566. JUCE_CAMERA_LOG ("Dual camera dual photo delivery supported: " + String ((int) photoOutput.dualCameraDualPhotoDeliverySupported));
  567. JUCE_CAMERA_LOG ("Camera calibration data delivery supported: " + String ((int) photoOutput.cameraCalibrationDataDeliverySupported));
  568. JUCE_CAMERA_LOG ("Depth data delivery supported: " + String ((int) photoOutput.depthDataDeliverySupported));
  569. }
  570. #endif
  571. return;
  572. }
  573. #endif
  574. auto* stillImageOutput = (AVCaptureStillImageOutput*) captureOutput;
  575. String typesString;
  576. for (AVVideoCodecType type in stillImageOutput.availableImageDataCodecTypes)
  577. typesString << nsStringToJuce (type) << " ";
  578. JUCE_CAMERA_LOG ("Available image codec types: " + typesString);
  579. JUCE_CAMERA_LOG ("Still image stabilization supported: " + String ((int) stillImageOutput.stillImageStabilizationSupported));
  580. JUCE_CAMERA_LOG ("Automatically enables still image stabilization when available: " + String ((int) stillImageOutput.automaticallyEnablesStillImageStabilizationWhenAvailable));
  581. JUCE_CAMERA_LOG ("Output settings for image output: " + nsStringToJuce ([stillImageOutput.outputSettings description]));
  582. }
  583. //==============================================================================
  584. static AVCaptureConnection* findVideoConnection (AVCaptureOutput* output)
  585. {
  586. for (AVCaptureConnection* connection in output.connections)
  587. for (AVCaptureInputPort* port in connection.inputPorts)
  588. if ([port.mediaType isEqual: AVMediaTypeVideo])
  589. return connection;
  590. return nullptr;
  591. }
  592. //==============================================================================
  593. class PhotoOutputDelegateClass : public ObjCClass<NSObject>
  594. {
  595. public:
  596. PhotoOutputDelegateClass() : ObjCClass<NSObject> ("PhotoOutputDelegateClass_")
  597. {
  598. addMethod (@selector (captureOutput:willBeginCaptureForResolvedSettings:), willBeginCaptureForSettings, "v@:@@");
  599. addMethod (@selector (captureOutput:willCapturePhotoForResolvedSettings:), willCaptureForSettings, "v@:@@");
  600. addMethod (@selector (captureOutput:didCapturePhotoForResolvedSettings:), didCaptureForSettings, "v@:@@");
  601. addMethod (@selector (captureOutput:didFinishCaptureForResolvedSettings:error:), didFinishCaptureForSettings, "v@:@@@");
  602. if (Pimpl::getIOSVersion().major >= 11)
  603. addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), didFinishProcessingPhoto, "v@:@@@");
  604. else
  605. addMethod (@selector (captureOutput:didFinishProcessingPhotoSampleBuffer:previewPhotoSampleBuffer:resolvedSettings:bracketSettings:error:), didFinishProcessingPhotoSampleBuffer, "v@:@@@@@@");
  606. addIvar<StillPictureTaker*> ("owner");
  607. registerClass();
  608. }
  609. //==============================================================================
  610. static StillPictureTaker& getOwner (id self) { return *getIvar<StillPictureTaker*> (self, "owner"); }
  611. static void setOwner (id self, StillPictureTaker* t) { object_setInstanceVariable (self, "owner", t); }
  612. private:
  613. static void willBeginCaptureForSettings (id, SEL, AVCapturePhotoOutput*, AVCaptureResolvedPhotoSettings*)
  614. {
  615. JUCE_CAMERA_LOG ("willBeginCaptureForSettings()");
  616. }
  617. static void willCaptureForSettings (id, SEL, AVCapturePhotoOutput*, AVCaptureResolvedPhotoSettings*)
  618. {
  619. JUCE_CAMERA_LOG ("willCaptureForSettings()");
  620. }
  621. static void didCaptureForSettings (id, SEL, AVCapturePhotoOutput*, AVCaptureResolvedPhotoSettings*)
  622. {
  623. JUCE_CAMERA_LOG ("didCaptureForSettings()");
  624. }
  625. static void didFinishCaptureForSettings (id, SEL, AVCapturePhotoOutput*, AVCaptureResolvedPhotoSettings*, NSError* error)
  626. {
  627. String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  628. ignoreUnused (errorString);
  629. JUCE_CAMERA_LOG ("didFinishCaptureForSettings(), error = " + errorString);
  630. }
  631. static void didFinishProcessingPhoto (id self, SEL, AVCapturePhotoOutput*, AVCapturePhoto* capturePhoto, NSError* error)
  632. {
  633. String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  634. ignoreUnused (errorString);
  635. JUCE_CAMERA_LOG ("didFinishProcessingPhoto(), error = " + errorString);
  636. if (error != nil)
  637. {
  638. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  639. jassertfalse;
  640. return;
  641. }
  642. auto* imageOrientation = (NSNumber *) capturePhoto.metadata[(NSString*) kCGImagePropertyOrientation];
  643. auto* uiImage = getImageWithCorrectOrientation ((CGImagePropertyOrientation) imageOrientation.unsignedIntValue,
  644. [capturePhoto CGImageRepresentation]);
  645. auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f);
  646. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  647. getOwner (self).callListeners (image);
  648. MessageManager::callAsync ([self, image]() { getOwner (self).notifyPictureTaken (image); });
  649. }
  650. static UIImage* getImageWithCorrectOrientation (CGImagePropertyOrientation imageOrientation,
  651. CGImageRef imageData)
  652. {
  653. auto origWidth = CGImageGetWidth (imageData);
  654. auto origHeight = CGImageGetHeight (imageData);
  655. auto targetSize = getTargetImageDimensionFor (imageOrientation, imageData);
  656. UIGraphicsBeginImageContext (targetSize);
  657. CGContextRef context = UIGraphicsGetCurrentContext();
  658. switch (imageOrientation)
  659. {
  660. case kCGImagePropertyOrientationUp:
  661. CGContextScaleCTM (context, 1.0, -1.0);
  662. CGContextTranslateCTM (context, 0.0, -targetSize.height);
  663. break;
  664. case kCGImagePropertyOrientationRight:
  665. CGContextRotateCTM (context, 90 * MathConstants<CGFloat>::pi / 180);
  666. CGContextScaleCTM (context, targetSize.height / origHeight, -targetSize.width / origWidth);
  667. break;
  668. case kCGImagePropertyOrientationDown:
  669. CGContextTranslateCTM (context, targetSize.width, 0.0);
  670. CGContextScaleCTM (context, -1.0, 1.0);
  671. break;
  672. case kCGImagePropertyOrientationLeft:
  673. CGContextRotateCTM (context, -90 * MathConstants<CGFloat>::pi / 180);
  674. CGContextScaleCTM (context, targetSize.height / origHeight, -targetSize.width / origWidth);
  675. CGContextTranslateCTM (context, -targetSize.width, -targetSize.height);
  676. break;
  677. case kCGImagePropertyOrientationUpMirrored:
  678. case kCGImagePropertyOrientationDownMirrored:
  679. case kCGImagePropertyOrientationLeftMirrored:
  680. case kCGImagePropertyOrientationRightMirrored:
  681. default:
  682. // Not implemented.
  683. jassertfalse;
  684. break;
  685. }
  686. CGContextDrawImage (context, CGRectMake (0, 0, targetSize.width, targetSize.height), imageData);
  687. UIImage* correctedImage = UIGraphicsGetImageFromCurrentImageContext();
  688. UIGraphicsEndImageContext();
  689. return correctedImage;
  690. }
  691. static CGSize getTargetImageDimensionFor (CGImagePropertyOrientation imageOrientation,
  692. CGImageRef imageData)
  693. {
  694. auto width = CGImageGetWidth (imageData);
  695. auto height = CGImageGetHeight (imageData);
  696. switch (imageOrientation)
  697. {
  698. case kCGImagePropertyOrientationUp:
  699. case kCGImagePropertyOrientationUpMirrored:
  700. case kCGImagePropertyOrientationDown:
  701. case kCGImagePropertyOrientationDownMirrored:
  702. return CGSizeMake ((CGFloat) width, (CGFloat) height);
  703. case kCGImagePropertyOrientationRight:
  704. case kCGImagePropertyOrientationRightMirrored:
  705. case kCGImagePropertyOrientationLeft:
  706. case kCGImagePropertyOrientationLeftMirrored:
  707. return CGSizeMake ((CGFloat) height, (CGFloat) width);
  708. }
  709. jassertfalse;
  710. return CGSizeMake ((CGFloat) width, (CGFloat) height);
  711. }
  712. static void didFinishProcessingPhotoSampleBuffer (id self, SEL, AVCapturePhotoOutput*,
  713. CMSampleBufferRef imageBuffer, CMSampleBufferRef imagePreviewBuffer,
  714. AVCaptureResolvedPhotoSettings*, AVCaptureBracketedStillImageSettings*,
  715. NSError* error)
  716. {
  717. String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  718. ignoreUnused (errorString);
  719. JUCE_CAMERA_LOG ("didFinishProcessingPhotoSampleBuffer(), error = " + errorString);
  720. if (error != nil)
  721. {
  722. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  723. jassertfalse;
  724. return;
  725. }
  726. NSData* origImageData = [AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer: imageBuffer previewPhotoSampleBuffer: imagePreviewBuffer];
  727. auto origImage = [UIImage imageWithData: origImageData];
  728. auto imageOrientation = uiImageOrientationToCGImageOrientation (origImage.imageOrientation);
  729. auto* uiImage = getImageWithCorrectOrientation (imageOrientation, origImage.CGImage);
  730. auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f);
  731. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  732. getOwner (self).callListeners (image);
  733. MessageManager::callAsync ([self, image]() { getOwner (self).notifyPictureTaken (image); });
  734. }
  735. static CGImagePropertyOrientation uiImageOrientationToCGImageOrientation (UIImageOrientation orientation)
  736. {
  737. switch (orientation)
  738. {
  739. case UIImageOrientationUp: return kCGImagePropertyOrientationUp;
  740. case UIImageOrientationDown: return kCGImagePropertyOrientationDown;
  741. case UIImageOrientationLeft: return kCGImagePropertyOrientationLeft;
  742. case UIImageOrientationRight: return kCGImagePropertyOrientationRight;
  743. case UIImageOrientationUpMirrored: return kCGImagePropertyOrientationUpMirrored;
  744. case UIImageOrientationDownMirrored: return kCGImagePropertyOrientationDownMirrored;
  745. case UIImageOrientationLeftMirrored: return kCGImagePropertyOrientationLeftMirrored;
  746. case UIImageOrientationRightMirrored: return kCGImagePropertyOrientationRightMirrored;
  747. }
  748. }
  749. };
  750. //==============================================================================
  751. void callListeners (const Image& image)
  752. {
  753. captureSession.callListeners (image);
  754. }
  755. void notifyPictureTaken (const Image& image)
  756. {
  757. takingPicture = false;
  758. captureSession.notifyPictureTaken (image);
  759. }
  760. CaptureSession& captureSession;
  761. AVCaptureOutput* captureOutput;
  762. std::unique_ptr<NSObject, NSObjectDeleter> photoOutputDelegate;
  763. bool takingPicture = false;
  764. };
  765. //==============================================================================
  766. // NB: FileOutputRecordingDelegateClass callbacks can be called from any thread (incl.
  767. // the message thread), so waiting for an event when stopping recording is not an
  768. // option and VideoRecorder must be alive at all times in order to get stopped
  769. // recording callback.
  770. class VideoRecorder
  771. {
  772. public:
  773. VideoRecorder (CaptureSession& session)
  774. : movieFileOutput ([AVCaptureMovieFileOutput new]),
  775. delegate (nullptr)
  776. {
  777. static FileOutputRecordingDelegateClass cls;
  778. delegate.reset ([cls.createInstance() init]);
  779. FileOutputRecordingDelegateClass::setOwner (delegate.get(), this);
  780. session.addOutputIfPossible (movieFileOutput);
  781. }
  782. ~VideoRecorder()
  783. {
  784. stopRecording();
  785. // Shutting down a device while recording will stop the recording
  786. // abruptly and the recording will be lost.
  787. jassert (! recordingInProgress);
  788. }
  789. void startRecording (const File& file, AVCaptureVideoOrientation orientationToUse)
  790. {
  791. if (Pimpl::getIOSVersion().major >= 10)
  792. printVideoOutputDebugInfo (movieFileOutput);
  793. auto url = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())
  794. isDirectory: NO];
  795. auto outputConnection = [movieFileOutput connectionWithMediaType: AVMediaTypeVideo];
  796. outputConnection.videoOrientation = orientationToUse;
  797. [movieFileOutput startRecordingToOutputFileURL: url recordingDelegate: delegate.get()];
  798. }
  799. void stopRecording()
  800. {
  801. [movieFileOutput stopRecording];
  802. }
  803. Time getTimeOfFirstRecordedFrame() const
  804. {
  805. return Time (firstRecordedFrameTimeMs.get());
  806. }
  807. private:
  808. static void printVideoOutputDebugInfo (AVCaptureMovieFileOutput* output)
  809. {
  810. ignoreUnused (output);
  811. JUCE_CAMERA_LOG ("Available video codec types:");
  812. #if JUCE_CAMERA_LOG_ENABLED
  813. for (AVVideoCodecType type in output.availableVideoCodecTypes)
  814. JUCE_CAMERA_LOG (nsStringToJuce (type));
  815. #endif
  816. JUCE_CAMERA_LOG ("Output settings per video connection:");
  817. #if JUCE_CAMERA_LOG_ENABLED
  818. for (AVCaptureConnection* connection in output.connections)
  819. JUCE_CAMERA_LOG (nsStringToJuce ([[output outputSettingsForConnection: connection] description]));
  820. #endif
  821. }
  822. //==============================================================================
  823. struct FileOutputRecordingDelegateClass : public ObjCClass<NSObject<AVCaptureFileOutputRecordingDelegate>>
  824. {
  825. FileOutputRecordingDelegateClass() : ObjCClass<NSObject<AVCaptureFileOutputRecordingDelegate>> ("FileOutputRecordingDelegateClass_")
  826. {
  827. addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL:fromConnections:), started, "v@:@@@");
  828. addMethod (@selector (captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:), stopped, "v@:@@@@");
  829. addIvar<VideoRecorder*> ("owner");
  830. registerClass();
  831. }
  832. //==============================================================================
  833. static VideoRecorder& getOwner (id self) { return *getIvar<VideoRecorder*> (self, "owner"); }
  834. static void setOwner (id self, VideoRecorder* r) { object_setInstanceVariable (self, "owner", r); }
  835. private:
  836. static void started (id self, SEL, AVCaptureFileOutput*, NSURL*, NSArray<AVCaptureConnection*>*)
  837. {
  838. JUCE_CAMERA_LOG ("Started recording");
  839. getOwner (self).firstRecordedFrameTimeMs.set (Time::getCurrentTime().toMilliseconds());
  840. getOwner (self).recordingInProgress = true;
  841. }
  842. static void stopped (id self, SEL, AVCaptureFileOutput*, NSURL*, NSArray<AVCaptureConnection*>*, NSError* error)
  843. {
  844. String errorString;
  845. bool recordingPlayable = true;
  846. // There might have been an error in the recording, yet there may be a playable file...
  847. if ([error code] != noErr)
  848. {
  849. id value = [[error userInfo] objectForKey: AVErrorRecordingSuccessfullyFinishedKey];
  850. if (value != nil && ! [value boolValue])
  851. recordingPlayable = false;
  852. errorString = nsStringToJuce (error.localizedDescription) + ", playable: " + String ((int) recordingPlayable);
  853. }
  854. JUCE_CAMERA_LOG ("Stopped recording, error = " + errorString);
  855. getOwner (self).recordingInProgress = false;
  856. }
  857. };
  858. AVCaptureMovieFileOutput* movieFileOutput;
  859. std::unique_ptr<NSObject<AVCaptureFileOutputRecordingDelegate>, NSObjectDeleter> delegate;
  860. bool recordingInProgress = false;
  861. Atomic<int64> firstRecordedFrameTimeMs { 0 };
  862. };
  863. //==============================================================================
  864. void addOutputIfPossible (AVCaptureOutput* output)
  865. {
  866. dispatch_async (captureSessionQueue,^
  867. {
  868. if ([captureSession.get() canAddOutput: output])
  869. {
  870. [captureSession.get() beginConfiguration];
  871. [captureSession.get() addOutput: output];
  872. [captureSession.get() commitConfiguration];
  873. return;
  874. }
  875. // Can't add output to camera session!
  876. jassertfalse;
  877. });
  878. }
  879. //==============================================================================
  880. void cameraSessionStarted()
  881. {
  882. sessionStarted = true;
  883. owner.cameraSessionStarted();
  884. }
  885. void cameraSessionRuntimeError (const String& error)
  886. {
  887. owner.cameraSessionRuntimeError (error);
  888. }
  889. void callListeners (const Image& image)
  890. {
  891. owner.callListeners (image);
  892. }
  893. void notifyPictureTaken (const Image& image)
  894. {
  895. owner.notifyPictureTaken (image);
  896. }
  897. Pimpl& owner;
  898. dispatch_queue_t captureSessionQueue;
  899. std::unique_ptr<AVCaptureSession, NSObjectDeleter> captureSession;
  900. std::unique_ptr<NSObject, NSObjectDeleter> delegate;
  901. StillPictureTaker stillPictureTaker;
  902. VideoRecorder videoRecorder;
  903. AVCaptureDevice* cameraDevice = nil;
  904. AVCaptureVideoPreviewLayer* previewLayer = nil;
  905. bool sessionStarted = false;
  906. WaitableEvent sessionClosedEvent;
  907. static int numCaptureSessions;
  908. };
  909. //==============================================================================
  910. void cameraSessionStarted()
  911. {
  912. JUCE_CAMERA_LOG ("cameraSessionStarted()");
  913. cameraOpenCallback (cameraId, {});
  914. }
  915. void cameraSessionRuntimeError (const String& error)
  916. {
  917. JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
  918. if (! notifiedOfCameraOpening)
  919. {
  920. cameraOpenCallback ({}, error);
  921. }
  922. else
  923. {
  924. if (owner.onErrorOccurred != nullptr)
  925. owner.onErrorOccurred (error);
  926. }
  927. }
  928. void callListeners (const Image& image)
  929. {
  930. const ScopedLock sl (listenerLock);
  931. listeners.call ([=] (Listener& l) { l.imageReceived (image); });
  932. }
  933. void notifyPictureTaken (const Image& image)
  934. {
  935. JUCE_CAMERA_LOG ("notifyPictureTaken()");
  936. if (pictureTakenCallback != nullptr)
  937. pictureTakenCallback (image);
  938. }
  939. //==============================================================================
  940. void triggerStillPictureCapture()
  941. {
  942. captureSession.takeStillPicture();
  943. }
  944. //==============================================================================
  945. CameraDevice& owner;
  946. String cameraId;
  947. InternalOpenCameraResultCallback cameraOpenCallback;
  948. CriticalSection listenerLock;
  949. ListenerList<Listener> listeners;
  950. std::function<void(const Image&)> pictureTakenCallback;
  951. CaptureSession captureSession;
  952. bool notifiedOfCameraOpening = false;
  953. //==============================================================================
  954. struct IOSVersion
  955. {
  956. int major;
  957. int minor;
  958. };
  959. static IOSVersion getIOSVersion()
  960. {
  961. auto processInfo = [NSProcessInfo processInfo];
  962. if (! [processInfo respondsToSelector: @selector (operatingSystemVersion)])
  963. return {7, 0}; // Below 8.0 in fact, but only care that it's below 8
  964. return { (int)[processInfo operatingSystemVersion].majorVersion,
  965. (int)[processInfo operatingSystemVersion].minorVersion };
  966. }
  967. static IOSVersion iosVersion;
  968. friend struct CameraDevice::ViewerComponent;
  969. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  970. };
  971. CameraDevice::Pimpl::IOSVersion CameraDevice::Pimpl::iosVersion = CameraDevice::Pimpl::getIOSVersion();
  972. int CameraDevice::Pimpl::CaptureSession::numCaptureSessions = 0;
  973. //==============================================================================
  974. struct CameraDevice::ViewerComponent : public UIViewComponent
  975. {
  976. //==============================================================================
  977. struct JuceCameraDeviceViewerClass : public ObjCClass<UIView>
  978. {
  979. JuceCameraDeviceViewerClass() : ObjCClass<UIView> ("JuceCameraDeviceViewerClass_")
  980. {
  981. addMethod (@selector (layoutSubviews), layoutSubviews, "v@:");
  982. registerClass();
  983. }
  984. private:
  985. static void layoutSubviews (id self, SEL)
  986. {
  987. sendSuperclassMessage (self, @selector (layoutSubviews));
  988. UIView* asUIView = (UIView*) self;
  989. updateOrientation (self);
  990. if (auto* previewLayer = getPreviewLayer (self))
  991. previewLayer.frame = asUIView.bounds;
  992. }
  993. static AVCaptureVideoPreviewLayer* getPreviewLayer (id self)
  994. {
  995. UIView* asUIView = (UIView*) self;
  996. if (asUIView.layer.sublayers != nil && [asUIView.layer.sublayers count] > 0)
  997. if ([asUIView.layer.sublayers[0] isKindOfClass: [AVCaptureVideoPreviewLayer class]])
  998. return (AVCaptureVideoPreviewLayer*) asUIView.layer.sublayers[0];
  999. return nil;
  1000. }
  1001. static void updateOrientation (id self)
  1002. {
  1003. if (auto* previewLayer = getPreviewLayer (self))
  1004. {
  1005. UIDeviceOrientation o = [UIDevice currentDevice].orientation;
  1006. if (UIDeviceOrientationIsPortrait (o) || UIDeviceOrientationIsLandscape (o))
  1007. {
  1008. if (previewLayer.connection != nil)
  1009. previewLayer.connection.videoOrientation = (AVCaptureVideoOrientation) o;
  1010. }
  1011. }
  1012. }
  1013. };
  1014. ViewerComponent (CameraDevice& device)
  1015. {
  1016. static JuceCameraDeviceViewerClass cls;
  1017. // Initial size that can be overriden later.
  1018. setSize (640, 480);
  1019. auto view = [cls.createInstance() init];
  1020. setView (view);
  1021. auto* previewLayer = device.pimpl->captureSession.createPreviewLayer();
  1022. previewLayer.frame = view.bounds;
  1023. UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
  1024. AVCaptureVideoOrientation videoOrientation = statusBarOrientation != UIInterfaceOrientationUnknown
  1025. ? (AVCaptureVideoOrientation) statusBarOrientation
  1026. : AVCaptureVideoOrientationPortrait;
  1027. previewLayer.connection.videoOrientation = videoOrientation;
  1028. [view.layer addSublayer: previewLayer];
  1029. }
  1030. };
  1031. //==============================================================================
  1032. String CameraDevice::getFileExtension()
  1033. {
  1034. return ".mov";
  1035. }