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.

303 lines
10KB

  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. if (pictureTakenCallback != nullptr)
  147. pictureTakenCallback (image);
  148. }
  149. void triggerImageCapture()
  150. {
  151. refreshIfNeeded();
  152. if (auto* videoConnection = getVideoConnection())
  153. {
  154. [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
  155. completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
  156. {
  157. if (error != nil)
  158. {
  159. JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
  160. jassertfalse;
  161. return;
  162. }
  163. NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer];
  164. auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
  165. WeakReference<Pimpl> weakRef (this);
  166. MessageManager::callAsync ([weakRef, image]() mutable { if (weakRef != nullptr) weakRef->handleImageCapture (image); });
  167. }];
  168. }
  169. }
  170. static StringArray getAvailableDevices()
  171. {
  172. StringArray results;
  173. results.add ("default");
  174. return results;
  175. }
  176. void cameraSessionRuntimeError (const String& error)
  177. {
  178. JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
  179. if (owner.onErrorOccurred != nullptr)
  180. owner.onErrorOccurred (error);
  181. }
  182. CameraDevice& owner;
  183. AVCaptureView* captureView = nil;
  184. AVCaptureSession* session = nil;
  185. AVCaptureMovieFileOutput* fileOutput = nil;
  186. AVCaptureStillImageOutput* imageOutput = nil;
  187. id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
  188. String openingError;
  189. Time firstPresentationTime;
  190. bool isRecording = false;
  191. std::function<void (const Image&)> pictureTakenCallback;
  192. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  193. private:
  194. //==============================================================================
  195. struct DelegateClass : public ObjCClass<NSObject>
  196. {
  197. DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
  198. {
  199. addIvar<Pimpl*> ("owner");
  200. addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
  201. addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL, "v@:@@@");
  202. addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL, "v@:@@@");
  203. addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL, "v@:@@@");
  204. addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL, "v@:@@@@");
  205. SEL runtimeErrorSel = NSSelectorFromString (nsStringLiteral ("captureSessionRuntimeError:"));
  206. addMethod (runtimeErrorSel, sessionRuntimeError, "v@:@");
  207. registerClass();
  208. }
  209. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  210. static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
  211. private:
  212. static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  213. static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  214. static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  215. static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
  216. static void sessionRuntimeError (id self, SEL, NSNotification* notification)
  217. {
  218. JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
  219. NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
  220. auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
  221. getOwner (self).cameraSessionRuntimeError (errorString);
  222. }
  223. };
  224. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  225. };
  226. struct CameraDevice::ViewerComponent : public NSViewComponent
  227. {
  228. ViewerComponent (CameraDevice& d)
  229. {
  230. JUCE_AUTORELEASEPOOL
  231. {
  232. setSize (640, 480);
  233. setView (d.pimpl->captureView);
  234. }
  235. }
  236. ~ViewerComponent()
  237. {
  238. setView (nil);
  239. }
  240. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  241. };
  242. String CameraDevice::getFileExtension()
  243. {
  244. return ".mov";
  245. }