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.

342 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #if JUCE_CLANG && ! (defined (MAC_OS_X_VERSION_10_16) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_16)
  20. #pragma clang diagnostic push
  21. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  22. #define JUCE_DEPRECATION_IGNORED 1
  23. #endif
  24. struct CameraDevice::Pimpl
  25. {
  26. Pimpl (CameraDevice& ownerToUse, const String&, int /*index*/, int /*minWidth*/, int /*minHeight*/,
  27. int /*maxWidth*/, int /*maxHeight*/, bool useHighQuality)
  28. : owner (ownerToUse)
  29. {
  30. JUCE_AUTORELEASEPOOL
  31. {
  32. captureView = [[AVCaptureView alloc] init];
  33. session = captureView.session;
  34. session.sessionPreset = useHighQuality ? AVCaptureSessionPresetHigh
  35. : AVCaptureSessionPresetMedium;
  36. refreshConnections();
  37. static DelegateClass cls;
  38. callbackDelegate = (id<AVCaptureFileOutputRecordingDelegate>) [cls.createInstance() init];
  39. DelegateClass::setOwner (callbackDelegate, this);
  40. SEL runtimeErrorSel = NSSelectorFromString (nsStringLiteral ("captureSessionRuntimeError:"));
  41. [[NSNotificationCenter defaultCenter] addObserver: callbackDelegate
  42. selector: runtimeErrorSel
  43. name: AVCaptureSessionRuntimeErrorNotification
  44. object: session];
  45. }
  46. }
  47. ~Pimpl()
  48. {
  49. [[NSNotificationCenter defaultCenter] removeObserver: callbackDelegate];
  50. [session stopRunning];
  51. removeImageCapture();
  52. removeMovieCapture();
  53. [session release];
  54. [callbackDelegate release];
  55. }
  56. bool openedOk() const noexcept { return openingError.isEmpty(); }
  57. void addImageCapture()
  58. {
  59. if (imageOutput == nil)
  60. {
  61. imageOutput = [[AVCaptureStillImageOutput alloc] init];
  62. auto imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: AVVideoCodecJPEG, AVVideoCodecKey, nil];
  63. [imageOutput setOutputSettings: imageSettings];
  64. [imageSettings release];
  65. [session addOutput: imageOutput];
  66. }
  67. }
  68. void addMovieCapture()
  69. {
  70. if (fileOutput == nil)
  71. {
  72. fileOutput = [[AVCaptureMovieFileOutput alloc] init];
  73. [session addOutput: fileOutput];
  74. }
  75. }
  76. void removeImageCapture()
  77. {
  78. if (imageOutput != nil)
  79. {
  80. [session removeOutput: imageOutput];
  81. [imageOutput release];
  82. imageOutput = nil;
  83. }
  84. }
  85. void removeMovieCapture()
  86. {
  87. if (fileOutput != nil)
  88. {
  89. [session removeOutput: fileOutput];
  90. [fileOutput release];
  91. fileOutput = nil;
  92. }
  93. }
  94. void refreshConnections()
  95. {
  96. [session beginConfiguration];
  97. removeImageCapture();
  98. removeMovieCapture();
  99. addImageCapture();
  100. addMovieCapture();
  101. [session commitConfiguration];
  102. }
  103. void refreshIfNeeded()
  104. {
  105. if (getVideoConnection() == nullptr)
  106. refreshConnections();
  107. }
  108. void takeStillPicture (std::function<void(const Image&)> pictureTakenCallbackToUse)
  109. {
  110. if (pictureTakenCallbackToUse == nullptr)
  111. {
  112. jassertfalse;
  113. return;
  114. }
  115. pictureTakenCallback = std::move (pictureTakenCallbackToUse);
  116. triggerImageCapture();
  117. }
  118. void startRecordingToFile (const File& file, int /*quality*/)
  119. {
  120. stopRecording();
  121. refreshIfNeeded();
  122. firstPresentationTime = Time::getCurrentTime();
  123. file.deleteFile();
  124. isRecording = true;
  125. [fileOutput startRecordingToOutputFileURL: createNSURLFromFile (file)
  126. recordingDelegate: callbackDelegate];
  127. }
  128. void stopRecording()
  129. {
  130. if (isRecording)
  131. {
  132. [fileOutput stopRecording];
  133. isRecording = false;
  134. }
  135. }
  136. Time getTimeOfFirstRecordedFrame() const
  137. {
  138. return firstPresentationTime;
  139. }
  140. AVCaptureConnection* getVideoConnection() const
  141. {
  142. if (imageOutput != nil)
  143. for (AVCaptureConnection* connection in imageOutput.connections)
  144. if ([connection isActive] && [connection isEnabled])
  145. for (AVCaptureInputPort* port in [connection inputPorts])
  146. if ([[port mediaType] isEqual: AVMediaTypeVideo])
  147. return connection;
  148. return nil;
  149. }
  150. void handleImageCapture (const Image& image)
  151. {
  152. const ScopedLock sl (listenerLock);
  153. listeners.call ([=] (Listener& l) { l.imageReceived (image); });
  154. if (! listeners.isEmpty())
  155. triggerImageCapture();
  156. }
  157. void triggerImageCapture()
  158. {
  159. refreshIfNeeded();
  160. if (auto* videoConnection = getVideoConnection())
  161. {
  162. [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
  163. completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
  164. {
  165. if (error != nil)
  166. {
  167. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  168. jassertfalse;
  169. return;
  170. }
  171. NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer];
  172. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  173. handleImageCapture (image);
  174. WeakReference<Pimpl> weakRef (this);
  175. MessageManager::callAsync ([weakRef, image]() mutable
  176. {
  177. if (weakRef != nullptr && weakRef->pictureTakenCallback != nullptr)
  178. weakRef->pictureTakenCallback (image);
  179. });
  180. }];
  181. }
  182. }
  183. void addListener (CameraDevice::Listener* listenerToAdd)
  184. {
  185. const ScopedLock sl (listenerLock);
  186. listeners.add (listenerToAdd);
  187. if (listeners.size() == 1)
  188. triggerImageCapture();
  189. }
  190. void removeListener (CameraDevice::Listener* listenerToRemove)
  191. {
  192. const ScopedLock sl (listenerLock);
  193. listeners.remove (listenerToRemove);
  194. }
  195. static StringArray getAvailableDevices()
  196. {
  197. StringArray results;
  198. results.add ("default");
  199. return results;
  200. }
  201. void cameraSessionRuntimeError (const String& error)
  202. {
  203. JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
  204. if (owner.onErrorOccurred != nullptr)
  205. owner.onErrorOccurred (error);
  206. }
  207. CameraDevice& owner;
  208. AVCaptureView* captureView = nil;
  209. AVCaptureSession* session = nil;
  210. AVCaptureMovieFileOutput* fileOutput = nil;
  211. AVCaptureStillImageOutput* imageOutput = nil;
  212. id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
  213. String openingError;
  214. Time firstPresentationTime;
  215. bool isRecording = false;
  216. CriticalSection listenerLock;
  217. ListenerList<Listener> listeners;
  218. std::function<void(const Image&)> pictureTakenCallback;
  219. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  220. private:
  221. //==============================================================================
  222. struct DelegateClass : public ObjCClass<NSObject>
  223. {
  224. DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
  225. {
  226. addIvar<Pimpl*> ("owner");
  227. addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
  228. addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL, "v@:@@@");
  229. addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL, "v@:@@@");
  230. addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL, "v@:@@@");
  231. addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL, "v@:@@@@");
  232. SEL runtimeErrorSel = NSSelectorFromString (nsStringLiteral ("captureSessionRuntimeError:"));
  233. addMethod (runtimeErrorSel, sessionRuntimeError, "v@:@");
  234. registerClass();
  235. }
  236. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  237. static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
  238. private:
  239. static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  240. static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  241. static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  242. static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
  243. static void sessionRuntimeError (id self, SEL, NSNotification* notification)
  244. {
  245. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  246. NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
  247. auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  248. getOwner (self).cameraSessionRuntimeError (errorString);
  249. }
  250. };
  251. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  252. };
  253. struct CameraDevice::ViewerComponent : public NSViewComponent
  254. {
  255. ViewerComponent (CameraDevice& d)
  256. {
  257. JUCE_AUTORELEASEPOOL
  258. {
  259. setSize (640, 480);
  260. setView (d.pimpl->captureView);
  261. }
  262. }
  263. ~ViewerComponent()
  264. {
  265. setView (nil);
  266. }
  267. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  268. };
  269. String CameraDevice::getFileExtension()
  270. {
  271. return ".mov";
  272. }
  273. #if JUCE_DEPRECATION_IGNORED
  274. #pragma clang diagnostic pop
  275. #undef JUCE_DEPRECATION_IGNORED
  276. #endif