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.

331 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 = std::move (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. isRecording = true;
  120. [fileOutput startRecordingToOutputFileURL: createNSURLFromFile (file)
  121. recordingDelegate: callbackDelegate];
  122. }
  123. void stopRecording()
  124. {
  125. if (isRecording)
  126. {
  127. [fileOutput stopRecording];
  128. isRecording = false;
  129. }
  130. }
  131. Time getTimeOfFirstRecordedFrame() const
  132. {
  133. return firstPresentationTime;
  134. }
  135. AVCaptureConnection* getVideoConnection() const
  136. {
  137. if (imageOutput != nil)
  138. for (AVCaptureConnection* connection in imageOutput.connections)
  139. if ([connection isActive] && [connection isEnabled])
  140. for (AVCaptureInputPort* port in [connection inputPorts])
  141. if ([[port mediaType] isEqual: AVMediaTypeVideo])
  142. return connection;
  143. return nil;
  144. }
  145. void handleImageCapture (const Image& image)
  146. {
  147. const ScopedLock sl (listenerLock);
  148. listeners.call ([=] (Listener& l) { l.imageReceived (image); });
  149. if (! listeners.isEmpty())
  150. triggerImageCapture();
  151. }
  152. void triggerImageCapture()
  153. {
  154. refreshIfNeeded();
  155. if (auto* videoConnection = getVideoConnection())
  156. {
  157. [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
  158. completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
  159. {
  160. if (error != nil)
  161. {
  162. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  163. jassertfalse;
  164. return;
  165. }
  166. NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer];
  167. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  168. handleImageCapture (image);
  169. WeakReference<Pimpl> weakRef (this);
  170. MessageManager::callAsync ([weakRef, image]() mutable
  171. {
  172. if (weakRef != nullptr && weakRef->pictureTakenCallback != nullptr)
  173. weakRef->pictureTakenCallback (image);
  174. });
  175. }];
  176. }
  177. }
  178. void addListener (CameraDevice::Listener* listenerToAdd)
  179. {
  180. const ScopedLock sl (listenerLock);
  181. listeners.add (listenerToAdd);
  182. if (listeners.size() == 1)
  183. triggerImageCapture();
  184. }
  185. void removeListener (CameraDevice::Listener* listenerToRemove)
  186. {
  187. const ScopedLock sl (listenerLock);
  188. listeners.remove (listenerToRemove);
  189. }
  190. static StringArray getAvailableDevices()
  191. {
  192. StringArray results;
  193. results.add ("default");
  194. return results;
  195. }
  196. void cameraSessionRuntimeError (const String& error)
  197. {
  198. JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
  199. if (owner.onErrorOccurred != nullptr)
  200. owner.onErrorOccurred (error);
  201. }
  202. CameraDevice& owner;
  203. AVCaptureView* captureView = nil;
  204. AVCaptureSession* session = nil;
  205. AVCaptureMovieFileOutput* fileOutput = nil;
  206. AVCaptureStillImageOutput* imageOutput = nil;
  207. id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
  208. String openingError;
  209. Time firstPresentationTime;
  210. bool isRecording = false;
  211. CriticalSection listenerLock;
  212. ListenerList<Listener> listeners;
  213. std::function<void(const Image&)> pictureTakenCallback;
  214. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  215. private:
  216. //==============================================================================
  217. struct DelegateClass : public ObjCClass<NSObject>
  218. {
  219. DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
  220. {
  221. addIvar<Pimpl*> ("owner");
  222. addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
  223. addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL, "v@:@@@");
  224. addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL, "v@:@@@");
  225. addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL, "v@:@@@");
  226. addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL, "v@:@@@@");
  227. SEL runtimeErrorSel = NSSelectorFromString (nsStringLiteral ("captureSessionRuntimeError:"));
  228. addMethod (runtimeErrorSel, sessionRuntimeError, "v@:@");
  229. registerClass();
  230. }
  231. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  232. static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
  233. private:
  234. static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  235. static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  236. static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  237. static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
  238. static void sessionRuntimeError (id self, SEL, NSNotification* notification)
  239. {
  240. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  241. NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
  242. auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  243. getOwner (self).cameraSessionRuntimeError (errorString);
  244. }
  245. };
  246. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  247. };
  248. struct CameraDevice::ViewerComponent : public NSViewComponent
  249. {
  250. ViewerComponent (CameraDevice& d)
  251. {
  252. JUCE_AUTORELEASEPOOL
  253. {
  254. setSize (640, 480);
  255. setView (d.pimpl->captureView);
  256. }
  257. }
  258. ~ViewerComponent()
  259. {
  260. setView (nil);
  261. }
  262. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  263. };
  264. String CameraDevice::getFileExtension()
  265. {
  266. return ".mov";
  267. }