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.

610 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #if defined (MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
  19. #define JUCE_USE_NEW_CAMERA_API 1
  20. #endif
  21. struct CameraDevice::Pimpl
  22. {
  23. Pimpl (CameraDevice& ownerToUse, const String& deviceNameToUse, int /*index*/,
  24. int /*minWidth*/, int /*minHeight*/,
  25. int /*maxWidth*/, int /*maxHeight*/,
  26. bool useHighQuality)
  27. : owner (ownerToUse),
  28. deviceName (deviceNameToUse)
  29. {
  30. imageOutput = []() -> std::unique_ptr<ImageOutputBase>
  31. {
  32. #if JUCE_USE_NEW_CAMERA_API
  33. if (@available (macOS 10.15, *))
  34. return std::make_unique<PostCatalinaPhotoOutput>();
  35. #endif
  36. return std::make_unique<PreCatalinaStillImageOutput>();
  37. }();
  38. session = [[AVCaptureSession alloc] init];
  39. session.sessionPreset = useHighQuality ? AVCaptureSessionPresetHigh
  40. : AVCaptureSessionPresetMedium;
  41. refreshConnections();
  42. static DelegateClass cls;
  43. callbackDelegate = (id<AVCaptureFileOutputRecordingDelegate>) [cls.createInstance() init];
  44. DelegateClass::setOwner (callbackDelegate, this);
  45. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  46. [[NSNotificationCenter defaultCenter] addObserver: callbackDelegate
  47. selector: @selector (captureSessionRuntimeError:)
  48. name: AVCaptureSessionRuntimeErrorNotification
  49. object: session];
  50. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  51. }
  52. ~Pimpl()
  53. {
  54. [[NSNotificationCenter defaultCenter] removeObserver: callbackDelegate];
  55. [session stopRunning];
  56. removeInput();
  57. removeImageCapture();
  58. removeMovieCapture();
  59. [session release];
  60. [callbackDelegate release];
  61. }
  62. //==============================================================================
  63. bool openedOk() const noexcept { return openingError.isEmpty(); }
  64. void startSession()
  65. {
  66. if (! [session isRunning])
  67. [session startRunning];
  68. }
  69. void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse)
  70. {
  71. if (pictureTakenCallbackToUse == nullptr)
  72. {
  73. jassertfalse;
  74. return;
  75. }
  76. pictureTakenCallback = std::move (pictureTakenCallbackToUse);
  77. triggerImageCapture();
  78. }
  79. void startRecordingToFile (const File& file, int /*quality*/)
  80. {
  81. stopRecording();
  82. refreshIfNeeded();
  83. firstPresentationTime = Time::getCurrentTime();
  84. file.deleteFile();
  85. startSession();
  86. isRecording = true;
  87. [fileOutput startRecordingToOutputFileURL: createNSURLFromFile (file)
  88. recordingDelegate: callbackDelegate];
  89. }
  90. void stopRecording()
  91. {
  92. if (isRecording)
  93. {
  94. [fileOutput stopRecording];
  95. isRecording = false;
  96. }
  97. }
  98. Time getTimeOfFirstRecordedFrame() const
  99. {
  100. return firstPresentationTime;
  101. }
  102. void addListener (CameraDevice::Listener* listenerToAdd)
  103. {
  104. const ScopedLock sl (listenerLock);
  105. listeners.add (listenerToAdd);
  106. if (listeners.size() == 1)
  107. triggerImageCapture();
  108. }
  109. void removeListener (CameraDevice::Listener* listenerToRemove)
  110. {
  111. const ScopedLock sl (listenerLock);
  112. listeners.remove (listenerToRemove);
  113. }
  114. static NSArray* getCaptureDevices()
  115. {
  116. #if JUCE_USE_NEW_CAMERA_API
  117. if (@available (macOS 10.15, *))
  118. {
  119. const auto deviceType = [&]
  120. {
  121. #if defined (MAC_OS_VERSION_14_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
  122. if (@available (macOS 14.0, *))
  123. return AVCaptureDeviceTypeExternal;
  124. #endif
  125. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", "-Wunguarded-availability-new")
  126. return AVCaptureDeviceTypeExternalUnknown;
  127. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  128. }();
  129. auto* discovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInWideAngleCamera, deviceType]
  130. mediaType: AVMediaTypeVideo
  131. position: AVCaptureDevicePositionUnspecified];
  132. return [discovery devices];
  133. }
  134. #endif
  135. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  136. return [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo];
  137. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  138. }
  139. static StringArray getAvailableDevices()
  140. {
  141. StringArray results;
  142. for (AVCaptureDevice* device : getCaptureDevices())
  143. results.add (nsStringToJuce ([device localizedName]));
  144. return results;
  145. }
  146. AVCaptureSession* getCaptureSession()
  147. {
  148. return session;
  149. }
  150. NSView* createVideoCapturePreview()
  151. {
  152. // The video preview must be created before the capture session is
  153. // started. Make sure you haven't called `addListener`,
  154. // `startRecordingToFile`, or `takeStillPicture` before calling this
  155. // function.
  156. jassert (! [session isRunning]);
  157. startSession();
  158. JUCE_AUTORELEASEPOOL
  159. {
  160. NSView* view = [[NSView alloc] init];
  161. [view setLayer: [AVCaptureVideoPreviewLayer layerWithSession: getCaptureSession()]];
  162. return view;
  163. }
  164. }
  165. private:
  166. //==============================================================================
  167. struct DelegateClass : public ObjCClass<NSObject>
  168. {
  169. DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
  170. {
  171. addIvar<Pimpl*> ("owner");
  172. addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
  173. addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL);
  174. addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL);
  175. addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL);
  176. addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL);
  177. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  178. addMethod (@selector (captureSessionRuntimeError:), sessionRuntimeError);
  179. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  180. registerClass();
  181. }
  182. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  183. static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
  184. private:
  185. static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  186. static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  187. static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  188. static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
  189. static void sessionRuntimeError (id self, SEL, NSNotification* notification)
  190. {
  191. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  192. NSError* error = [notification.userInfo objectForKey: AVCaptureSessionErrorKey];
  193. auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  194. getOwner (self).cameraSessionRuntimeError (errorString);
  195. }
  196. };
  197. //==============================================================================
  198. struct ImageOutputBase
  199. {
  200. virtual ~ImageOutputBase() = default;
  201. virtual void addImageCapture (AVCaptureSession*) = 0;
  202. virtual void removeImageCapture (AVCaptureSession*) = 0;
  203. virtual NSArray<AVCaptureConnection*>* getConnections() const = 0;
  204. virtual void triggerImageCapture (Pimpl& p) = 0;
  205. };
  206. #if JUCE_USE_NEW_CAMERA_API
  207. class API_AVAILABLE (macos (10.15)) PostCatalinaPhotoOutput : public ImageOutputBase
  208. {
  209. public:
  210. PostCatalinaPhotoOutput()
  211. {
  212. static PhotoOutputDelegateClass cls;
  213. delegate.reset ([cls.createInstance() init]);
  214. }
  215. void addImageCapture (AVCaptureSession* s) override
  216. {
  217. if (imageOutput != nil)
  218. return;
  219. imageOutput = [[AVCapturePhotoOutput alloc] init];
  220. [s addOutput: imageOutput];
  221. }
  222. void removeImageCapture (AVCaptureSession* s) override
  223. {
  224. if (imageOutput == nil)
  225. return;
  226. [s removeOutput: imageOutput];
  227. [imageOutput release];
  228. imageOutput = nil;
  229. }
  230. NSArray<AVCaptureConnection*>* getConnections() const override
  231. {
  232. if (imageOutput != nil)
  233. return imageOutput.connections;
  234. return nil;
  235. }
  236. void triggerImageCapture (Pimpl& p) override
  237. {
  238. if (imageOutput == nil)
  239. return;
  240. PhotoOutputDelegateClass::setOwner (delegate.get(), &p);
  241. [imageOutput capturePhotoWithSettings: [AVCapturePhotoSettings photoSettings]
  242. delegate: delegate.get()];
  243. }
  244. private:
  245. class PhotoOutputDelegateClass : public ObjCClass<NSObject<AVCapturePhotoCaptureDelegate>>
  246. {
  247. public:
  248. PhotoOutputDelegateClass()
  249. : ObjCClass ("PhotoOutputDelegateClass_")
  250. {
  251. addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), [] (id self, SEL, AVCapturePhotoOutput*, AVCapturePhoto* photo, NSError* error)
  252. {
  253. if (error != nil)
  254. {
  255. [[maybe_unused]] String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  256. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + errorString);
  257. jassertfalse;
  258. return;
  259. }
  260. auto* imageData = [photo fileDataRepresentation];
  261. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  262. getOwner (self).imageCaptureFinished (image);
  263. });
  264. addIvar<Pimpl*> ("owner");
  265. registerClass();
  266. }
  267. static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
  268. static void setOwner (id self, Pimpl* t) { object_setInstanceVariable (self, "owner", t); }
  269. };
  270. AVCapturePhotoOutput* imageOutput = nil;
  271. NSUniquePtr<NSObject<AVCapturePhotoCaptureDelegate>> delegate;
  272. };
  273. #endif
  274. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  275. class PreCatalinaStillImageOutput : public ImageOutputBase
  276. {
  277. public:
  278. void addImageCapture (AVCaptureSession* s) override
  279. {
  280. if (imageOutput != nil)
  281. return;
  282. const auto codecType = []
  283. {
  284. if (@available (macOS 10.13, *))
  285. return AVVideoCodecTypeJPEG;
  286. return AVVideoCodecJPEG;
  287. }();
  288. imageOutput = [[AVCaptureStillImageOutput alloc] init];
  289. auto imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: codecType, AVVideoCodecKey, nil];
  290. [imageOutput setOutputSettings: imageSettings];
  291. [imageSettings release];
  292. [s addOutput: imageOutput];
  293. }
  294. void removeImageCapture (AVCaptureSession* s) override
  295. {
  296. if (imageOutput == nil)
  297. return;
  298. [s removeOutput: imageOutput];
  299. [imageOutput release];
  300. imageOutput = nil;
  301. }
  302. NSArray<AVCaptureConnection*>* getConnections() const override
  303. {
  304. if (imageOutput != nil)
  305. return imageOutput.connections;
  306. return nil;
  307. }
  308. void triggerImageCapture (Pimpl& p) override
  309. {
  310. if (auto* videoConnection = p.getVideoConnection())
  311. {
  312. [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
  313. completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
  314. {
  315. if (error != nil)
  316. {
  317. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  318. jassertfalse;
  319. return;
  320. }
  321. auto* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer];
  322. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  323. p.imageCaptureFinished (image);
  324. }];
  325. }
  326. }
  327. private:
  328. AVCaptureStillImageOutput* imageOutput = nil;
  329. };
  330. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  331. //==============================================================================
  332. void addImageCapture()
  333. {
  334. imageOutput->addImageCapture (session);
  335. }
  336. void addMovieCapture()
  337. {
  338. if (fileOutput == nil)
  339. {
  340. fileOutput = [[AVCaptureMovieFileOutput alloc] init];
  341. [session addOutput: fileOutput];
  342. }
  343. }
  344. void removeImageCapture()
  345. {
  346. imageOutput->removeImageCapture (session);
  347. }
  348. void removeMovieCapture()
  349. {
  350. if (fileOutput != nil)
  351. {
  352. [session removeOutput: fileOutput];
  353. [fileOutput release];
  354. fileOutput = nil;
  355. }
  356. }
  357. void removeCurrentSessionVideoInputs()
  358. {
  359. if (session != nil)
  360. {
  361. NSArray<AVCaptureDeviceInput*>* inputs = session.inputs;
  362. for (AVCaptureDeviceInput* input : inputs)
  363. if ([input.device hasMediaType: AVMediaTypeVideo])
  364. [session removeInput:input];
  365. }
  366. }
  367. void addInput()
  368. {
  369. if (currentInput == nil)
  370. {
  371. for (AVCaptureDevice* device : getCaptureDevices())
  372. {
  373. if (deviceName == nsStringToJuce ([device localizedName]))
  374. {
  375. removeCurrentSessionVideoInputs();
  376. NSError* err = nil;
  377. AVCaptureDeviceInput* inputDevice = [[AVCaptureDeviceInput alloc] initWithDevice: device
  378. error: &err];
  379. jassert (err == nil);
  380. if ([session canAddInput: inputDevice])
  381. {
  382. [session addInput: inputDevice];
  383. currentInput = inputDevice;
  384. }
  385. else
  386. {
  387. jassertfalse;
  388. [inputDevice release];
  389. }
  390. return;
  391. }
  392. }
  393. }
  394. }
  395. void removeInput()
  396. {
  397. if (currentInput != nil)
  398. {
  399. [session removeInput: currentInput];
  400. [currentInput release];
  401. currentInput = nil;
  402. }
  403. }
  404. void refreshConnections()
  405. {
  406. [session beginConfiguration];
  407. removeInput();
  408. removeImageCapture();
  409. removeMovieCapture();
  410. addInput();
  411. addImageCapture();
  412. addMovieCapture();
  413. [session commitConfiguration];
  414. }
  415. void refreshIfNeeded()
  416. {
  417. if (getVideoConnection() == nullptr)
  418. refreshConnections();
  419. }
  420. AVCaptureConnection* getVideoConnection() const
  421. {
  422. auto* connections = imageOutput->getConnections();
  423. if (connections != nil)
  424. for (AVCaptureConnection* connection in connections)
  425. if ([connection isActive] && [connection isEnabled])
  426. for (AVCaptureInputPort* port in [connection inputPorts])
  427. if ([[port mediaType] isEqual: AVMediaTypeVideo])
  428. return connection;
  429. return nil;
  430. }
  431. void imageCaptureFinished (const Image& image)
  432. {
  433. handleImageCapture (image);
  434. MessageManager::callAsync ([weakRef = WeakReference<Pimpl> { this }, image]() mutable
  435. {
  436. if (weakRef != nullptr && weakRef->pictureTakenCallback != nullptr)
  437. weakRef->pictureTakenCallback (image);
  438. });
  439. }
  440. void handleImageCapture (const Image& image)
  441. {
  442. const ScopedLock sl (listenerLock);
  443. listeners.call ([=] (Listener& l) { l.imageReceived (image); });
  444. if (! listeners.isEmpty())
  445. triggerImageCapture();
  446. }
  447. void triggerImageCapture()
  448. {
  449. refreshIfNeeded();
  450. startSession();
  451. if (auto* videoConnection = getVideoConnection())
  452. imageOutput->triggerImageCapture (*this);
  453. }
  454. void cameraSessionRuntimeError (const String& error)
  455. {
  456. JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
  457. if (owner.onErrorOccurred != nullptr)
  458. owner.onErrorOccurred (error);
  459. }
  460. //==============================================================================
  461. CameraDevice& owner;
  462. String deviceName;
  463. AVCaptureSession* session = nil;
  464. AVCaptureMovieFileOutput* fileOutput = nil;
  465. std::unique_ptr<ImageOutputBase> imageOutput;
  466. AVCaptureDeviceInput* currentInput = nil;
  467. id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
  468. String openingError;
  469. Time firstPresentationTime;
  470. bool isRecording = false;
  471. CriticalSection listenerLock;
  472. ListenerList<Listener> listeners;
  473. std::function<void (const Image&)> pictureTakenCallback = nullptr;
  474. //==============================================================================
  475. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  476. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  477. };
  478. //==============================================================================
  479. struct CameraDevice::ViewerComponent : public NSViewComponent
  480. {
  481. ViewerComponent (CameraDevice& device)
  482. {
  483. setView (device.pimpl->createVideoCapturePreview());
  484. }
  485. ~ViewerComponent()
  486. {
  487. setView (nil);
  488. }
  489. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  490. };
  491. String CameraDevice::getFileExtension()
  492. {
  493. return ".mov";
  494. }