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.

601 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. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  120. const auto deviceType = AVCaptureDeviceTypeExternalUnknown;
  121. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  122. auto* discovery = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes: @[AVCaptureDeviceTypeBuiltInWideAngleCamera, deviceType]
  123. mediaType: AVMediaTypeVideo
  124. position: AVCaptureDevicePositionUnspecified];
  125. return [discovery devices];
  126. }
  127. #endif
  128. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  129. return [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo];
  130. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  131. }
  132. static StringArray getAvailableDevices()
  133. {
  134. StringArray results;
  135. for (AVCaptureDevice* device : getCaptureDevices())
  136. results.add (nsStringToJuce ([device localizedName]));
  137. return results;
  138. }
  139. AVCaptureSession* getCaptureSession()
  140. {
  141. return session;
  142. }
  143. NSView* createVideoCapturePreview()
  144. {
  145. // The video preview must be created before the capture session is
  146. // started. Make sure you haven't called `addListener`,
  147. // `startRecordingToFile`, or `takeStillPicture` before calling this
  148. // function.
  149. jassert (! [session isRunning]);
  150. startSession();
  151. JUCE_AUTORELEASEPOOL
  152. {
  153. NSView* view = [[NSView alloc] init];
  154. [view setLayer: [AVCaptureVideoPreviewLayer layerWithSession: getCaptureSession()]];
  155. return view;
  156. }
  157. }
  158. private:
  159. //==============================================================================
  160. struct DelegateClass : public ObjCClass<NSObject>
  161. {
  162. DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
  163. {
  164. addIvar<Pimpl*> ("owner");
  165. addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
  166. addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL);
  167. addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL);
  168. addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL);
  169. addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL);
  170. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  171. addMethod (@selector (captureSessionRuntimeError:), sessionRuntimeError);
  172. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  173. registerClass();
  174. }
  175. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  176. static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
  177. private:
  178. static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  179. static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  180. static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  181. static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
  182. static void sessionRuntimeError (id self, SEL, NSNotification* notification)
  183. {
  184. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  185. NSError* error = [notification.userInfo objectForKey: AVCaptureSessionErrorKey];
  186. auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  187. getOwner (self).cameraSessionRuntimeError (errorString);
  188. }
  189. };
  190. //==============================================================================
  191. struct ImageOutputBase
  192. {
  193. virtual ~ImageOutputBase() = default;
  194. virtual void addImageCapture (AVCaptureSession*) = 0;
  195. virtual void removeImageCapture (AVCaptureSession*) = 0;
  196. virtual NSArray<AVCaptureConnection*>* getConnections() const = 0;
  197. virtual void triggerImageCapture (Pimpl& p) = 0;
  198. };
  199. #if JUCE_USE_NEW_CAMERA_API
  200. class API_AVAILABLE (macos (10.15)) PostCatalinaPhotoOutput : public ImageOutputBase
  201. {
  202. public:
  203. PostCatalinaPhotoOutput()
  204. {
  205. static PhotoOutputDelegateClass cls;
  206. delegate.reset ([cls.createInstance() init]);
  207. }
  208. void addImageCapture (AVCaptureSession* s) override
  209. {
  210. if (imageOutput != nil)
  211. return;
  212. imageOutput = [[AVCapturePhotoOutput alloc] init];
  213. [s addOutput: imageOutput];
  214. }
  215. void removeImageCapture (AVCaptureSession* s) override
  216. {
  217. if (imageOutput == nil)
  218. return;
  219. [s removeOutput: imageOutput];
  220. [imageOutput release];
  221. imageOutput = nil;
  222. }
  223. NSArray<AVCaptureConnection*>* getConnections() const override
  224. {
  225. if (imageOutput != nil)
  226. return imageOutput.connections;
  227. return nil;
  228. }
  229. void triggerImageCapture (Pimpl& p) override
  230. {
  231. if (imageOutput == nil)
  232. return;
  233. PhotoOutputDelegateClass::setOwner (delegate.get(), &p);
  234. [imageOutput capturePhotoWithSettings: [AVCapturePhotoSettings photoSettings]
  235. delegate: delegate.get()];
  236. }
  237. private:
  238. class PhotoOutputDelegateClass : public ObjCClass<NSObject<AVCapturePhotoCaptureDelegate>>
  239. {
  240. public:
  241. PhotoOutputDelegateClass()
  242. : ObjCClass ("PhotoOutputDelegateClass_")
  243. {
  244. addMethod (@selector (captureOutput:didFinishProcessingPhoto:error:), [] (id self, SEL, AVCapturePhotoOutput*, AVCapturePhoto* photo, NSError* error)
  245. {
  246. if (error != nil)
  247. {
  248. [[maybe_unused]] String errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  249. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + errorString);
  250. jassertfalse;
  251. return;
  252. }
  253. auto* imageData = [photo fileDataRepresentation];
  254. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  255. getOwner (self).imageCaptureFinished (image);
  256. });
  257. addIvar<Pimpl*> ("owner");
  258. registerClass();
  259. }
  260. static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
  261. static void setOwner (id self, Pimpl* t) { object_setInstanceVariable (self, "owner", t); }
  262. };
  263. AVCapturePhotoOutput* imageOutput = nil;
  264. NSUniquePtr<NSObject<AVCapturePhotoCaptureDelegate>> delegate;
  265. };
  266. #endif
  267. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  268. class PreCatalinaStillImageOutput : public ImageOutputBase
  269. {
  270. public:
  271. void addImageCapture (AVCaptureSession* s) override
  272. {
  273. if (imageOutput != nil)
  274. return;
  275. const auto codecType = []
  276. {
  277. if (@available (macOS 10.13, *))
  278. return AVVideoCodecTypeJPEG;
  279. return AVVideoCodecJPEG;
  280. }();
  281. imageOutput = [[AVCaptureStillImageOutput alloc] init];
  282. auto imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: codecType, AVVideoCodecKey, nil];
  283. [imageOutput setOutputSettings: imageSettings];
  284. [imageSettings release];
  285. [s addOutput: imageOutput];
  286. }
  287. void removeImageCapture (AVCaptureSession* s) override
  288. {
  289. if (imageOutput == nil)
  290. return;
  291. [s removeOutput: imageOutput];
  292. [imageOutput release];
  293. imageOutput = nil;
  294. }
  295. NSArray<AVCaptureConnection*>* getConnections() const override
  296. {
  297. if (imageOutput != nil)
  298. return imageOutput.connections;
  299. return nil;
  300. }
  301. void triggerImageCapture (Pimpl& p) override
  302. {
  303. if (auto* videoConnection = p.getVideoConnection())
  304. {
  305. [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
  306. completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
  307. {
  308. if (error != nil)
  309. {
  310. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  311. jassertfalse;
  312. return;
  313. }
  314. auto* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer];
  315. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  316. p.imageCaptureFinished (image);
  317. }];
  318. }
  319. }
  320. private:
  321. AVCaptureStillImageOutput* imageOutput = nil;
  322. };
  323. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  324. //==============================================================================
  325. void addImageCapture()
  326. {
  327. imageOutput->addImageCapture (session);
  328. }
  329. void addMovieCapture()
  330. {
  331. if (fileOutput == nil)
  332. {
  333. fileOutput = [[AVCaptureMovieFileOutput alloc] init];
  334. [session addOutput: fileOutput];
  335. }
  336. }
  337. void removeImageCapture()
  338. {
  339. imageOutput->removeImageCapture (session);
  340. }
  341. void removeMovieCapture()
  342. {
  343. if (fileOutput != nil)
  344. {
  345. [session removeOutput: fileOutput];
  346. [fileOutput release];
  347. fileOutput = nil;
  348. }
  349. }
  350. void removeCurrentSessionVideoInputs()
  351. {
  352. if (session != nil)
  353. {
  354. NSArray<AVCaptureDeviceInput*>* inputs = session.inputs;
  355. for (AVCaptureDeviceInput* input : inputs)
  356. if ([input.device hasMediaType: AVMediaTypeVideo])
  357. [session removeInput:input];
  358. }
  359. }
  360. void addInput()
  361. {
  362. if (currentInput == nil)
  363. {
  364. for (AVCaptureDevice* device : getCaptureDevices())
  365. {
  366. if (deviceName == nsStringToJuce ([device localizedName]))
  367. {
  368. removeCurrentSessionVideoInputs();
  369. NSError* err = nil;
  370. AVCaptureDeviceInput* inputDevice = [[AVCaptureDeviceInput alloc] initWithDevice: device
  371. error: &err];
  372. jassert (err == nil);
  373. if ([session canAddInput: inputDevice])
  374. {
  375. [session addInput: inputDevice];
  376. currentInput = inputDevice;
  377. }
  378. else
  379. {
  380. jassertfalse;
  381. [inputDevice release];
  382. }
  383. return;
  384. }
  385. }
  386. }
  387. }
  388. void removeInput()
  389. {
  390. if (currentInput != nil)
  391. {
  392. [session removeInput: currentInput];
  393. [currentInput release];
  394. currentInput = nil;
  395. }
  396. }
  397. void refreshConnections()
  398. {
  399. [session beginConfiguration];
  400. removeInput();
  401. removeImageCapture();
  402. removeMovieCapture();
  403. addInput();
  404. addImageCapture();
  405. addMovieCapture();
  406. [session commitConfiguration];
  407. }
  408. void refreshIfNeeded()
  409. {
  410. if (getVideoConnection() == nullptr)
  411. refreshConnections();
  412. }
  413. AVCaptureConnection* getVideoConnection() const
  414. {
  415. auto* connections = imageOutput->getConnections();
  416. if (connections != nil)
  417. for (AVCaptureConnection* connection in connections)
  418. if ([connection isActive] && [connection isEnabled])
  419. for (AVCaptureInputPort* port in [connection inputPorts])
  420. if ([[port mediaType] isEqual: AVMediaTypeVideo])
  421. return connection;
  422. return nil;
  423. }
  424. void imageCaptureFinished (const Image& image)
  425. {
  426. handleImageCapture (image);
  427. MessageManager::callAsync ([weakRef = WeakReference<Pimpl> { this }, image]() mutable
  428. {
  429. if (weakRef != nullptr)
  430. NullCheckedInvocation::invoke (weakRef->pictureTakenCallback, image);
  431. });
  432. }
  433. void handleImageCapture (const Image& image)
  434. {
  435. const ScopedLock sl (listenerLock);
  436. listeners.call ([=] (Listener& l) { l.imageReceived (image); });
  437. if (! listeners.isEmpty())
  438. triggerImageCapture();
  439. }
  440. void triggerImageCapture()
  441. {
  442. refreshIfNeeded();
  443. startSession();
  444. if (auto* videoConnection = getVideoConnection())
  445. imageOutput->triggerImageCapture (*this);
  446. }
  447. void cameraSessionRuntimeError (const String& error)
  448. {
  449. JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
  450. NullCheckedInvocation::invoke (owner.onErrorOccurred, error);
  451. }
  452. //==============================================================================
  453. CameraDevice& owner;
  454. String deviceName;
  455. AVCaptureSession* session = nil;
  456. AVCaptureMovieFileOutput* fileOutput = nil;
  457. std::unique_ptr<ImageOutputBase> imageOutput;
  458. AVCaptureDeviceInput* currentInput = nil;
  459. id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
  460. String openingError;
  461. Time firstPresentationTime;
  462. bool isRecording = false;
  463. CriticalSection listenerLock;
  464. ListenerList<Listener> listeners;
  465. std::function<void (const Image&)> pictureTakenCallback = nullptr;
  466. //==============================================================================
  467. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  468. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  469. };
  470. //==============================================================================
  471. struct CameraDevice::ViewerComponent : public NSViewComponent
  472. {
  473. ViewerComponent (CameraDevice& device)
  474. {
  475. setView (device.pimpl->createVideoCapturePreview());
  476. }
  477. ~ViewerComponent()
  478. {
  479. setView (nil);
  480. }
  481. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  482. };
  483. String CameraDevice::getFileExtension()
  484. {
  485. return ".mov";
  486. }