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.

604 lines
20KB

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