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.

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