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.

1331 lines
58KB

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