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.

277 lines
8.9KB

  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 (const String&, int /*index*/, int /*minWidth*/, int /*minHeight*/,
  22. int /*maxWidth*/, int /*maxHeight*/, bool useHighQuality)
  23. {
  24. JUCE_AUTORELEASEPOOL
  25. {
  26. captureView = [[AVCaptureView alloc] init];
  27. session = captureView.session;
  28. session.sessionPreset = useHighQuality ? AVCaptureSessionPresetHigh
  29. : AVCaptureSessionPresetMedium;
  30. refreshConnections();
  31. static DelegateClass cls;
  32. callbackDelegate = (id<AVCaptureFileOutputRecordingDelegate>) [cls.createInstance() init];
  33. DelegateClass::setOwner (callbackDelegate, this);
  34. }
  35. }
  36. ~Pimpl()
  37. {
  38. [session stopRunning];
  39. removeImageCapture();
  40. removeMovieCapture();
  41. [session release];
  42. [callbackDelegate release];
  43. }
  44. bool openedOk() const noexcept { return openingError.isEmpty(); }
  45. void addImageCapture()
  46. {
  47. if (imageOutput == nil)
  48. {
  49. imageOutput = [[AVCaptureStillImageOutput alloc] init];
  50. auto* imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: AVVideoCodecJPEG, AVVideoCodecKey, nil];
  51. [imageOutput setOutputSettings: imageSettings];
  52. [imageSettings release];
  53. [session addOutput: imageOutput];
  54. }
  55. }
  56. void addMovieCapture()
  57. {
  58. if (fileOutput == nil)
  59. {
  60. fileOutput = [[AVCaptureMovieFileOutput alloc] init];
  61. [session addOutput: fileOutput];
  62. }
  63. }
  64. void removeImageCapture()
  65. {
  66. if (imageOutput != nil)
  67. {
  68. [session removeOutput: imageOutput];
  69. [imageOutput release];
  70. imageOutput = nil;
  71. }
  72. }
  73. void removeMovieCapture()
  74. {
  75. if (fileOutput != nil)
  76. {
  77. [session removeOutput: fileOutput];
  78. [fileOutput release];
  79. fileOutput = nil;
  80. }
  81. }
  82. void refreshConnections()
  83. {
  84. [session beginConfiguration];
  85. removeImageCapture();
  86. removeMovieCapture();
  87. addImageCapture();
  88. addMovieCapture();
  89. [session commitConfiguration];
  90. }
  91. void refreshIfNeeded()
  92. {
  93. if (getVideoConnection() == nullptr)
  94. refreshConnections();
  95. }
  96. void startRecordingToFile (const File& file, int /*quality*/)
  97. {
  98. stopRecording();
  99. refreshIfNeeded();
  100. firstPresentationTime = Time::getCurrentTime();
  101. file.deleteFile();
  102. [fileOutput startRecordingToOutputFileURL: createNSURLFromFile (file)
  103. recordingDelegate: callbackDelegate];
  104. }
  105. void stopRecording()
  106. {
  107. if (isRecording)
  108. {
  109. [fileOutput stopRecording];
  110. isRecording = false;
  111. }
  112. }
  113. Time getTimeOfFirstRecordedFrame() const
  114. {
  115. return firstPresentationTime;
  116. }
  117. AVCaptureConnection* getVideoConnection() const
  118. {
  119. if (imageOutput != nil)
  120. for (AVCaptureConnection* connection in imageOutput.connections)
  121. if ([connection isActive] && [connection isEnabled])
  122. for (AVCaptureInputPort* port in [connection inputPorts])
  123. if ([[port mediaType] isEqual: AVMediaTypeVideo])
  124. return connection;
  125. return nil;
  126. }
  127. void handleImageCapture (const void* data, size_t size)
  128. {
  129. auto image = ImageFileFormat::loadFrom (data, size);
  130. const ScopedLock sl (listenerLock);
  131. if (! listeners.isEmpty())
  132. {
  133. for (int i = listeners.size(); --i >= 0;)
  134. if (auto* l = listeners[i])
  135. l->imageReceived (image);
  136. if (! listeners.isEmpty())
  137. triggerImageCapture();
  138. }
  139. }
  140. void triggerImageCapture()
  141. {
  142. refreshIfNeeded();
  143. if (auto* videoConnection = getVideoConnection())
  144. {
  145. [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
  146. completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError*)
  147. {
  148. auto buffer = CMSampleBufferGetDataBuffer (sampleBuffer);
  149. size_t size = CMBlockBufferGetDataLength (buffer);
  150. jassert (CMBlockBufferIsRangeContiguous (buffer, 0, size)); // TODO: need to add code to handle this if it happens
  151. char* data = nullptr;
  152. CMBlockBufferGetDataPointer (buffer, 0, &size, nullptr, &data);
  153. handleImageCapture (data, size);
  154. }];
  155. }
  156. }
  157. void addListener (CameraDevice::Listener* listenerToAdd)
  158. {
  159. const ScopedLock sl (listenerLock);
  160. listeners.addIfNotAlreadyThere (listenerToAdd);
  161. if (listeners.size() == 1)
  162. triggerImageCapture();
  163. }
  164. void removeListener (CameraDevice::Listener* listenerToRemove)
  165. {
  166. const ScopedLock sl (listenerLock);
  167. listeners.removeFirstMatchingValue (listenerToRemove);
  168. }
  169. static StringArray getAvailableDevices()
  170. {
  171. StringArray results;
  172. results.add ("default");
  173. return results;
  174. }
  175. AVCaptureView* captureView = nil;
  176. AVCaptureSession* session = nil;
  177. AVCaptureMovieFileOutput* fileOutput = nil;
  178. AVCaptureStillImageOutput* imageOutput = nil;
  179. id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
  180. String openingError;
  181. Time firstPresentationTime;
  182. bool isRecording = false;
  183. Array<CameraDevice::Listener*> listeners;
  184. CriticalSection listenerLock;
  185. private:
  186. //==============================================================================
  187. struct DelegateClass : public ObjCClass<NSObject>
  188. {
  189. DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
  190. {
  191. addIvar<Pimpl*> ("owner");
  192. addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
  193. addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL, "v@:@@@");
  194. addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL, "v@:@@@");
  195. addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL, "v@:@@@");
  196. addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL, "v@:@@@@");
  197. registerClass();
  198. }
  199. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  200. static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
  201. private:
  202. static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  203. static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  204. static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
  205. static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
  206. };
  207. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  208. };
  209. struct CameraDevice::ViewerComponent : public NSViewComponent
  210. {
  211. ViewerComponent (CameraDevice& d)
  212. {
  213. JUCE_AUTORELEASEPOOL
  214. {
  215. setSize (640, 480);
  216. setView (d.pimpl->captureView);
  217. }
  218. }
  219. ~ViewerComponent()
  220. {
  221. setView (nil);
  222. }
  223. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  224. };
  225. String CameraDevice::getFileExtension()
  226. {
  227. return ".mov";
  228. }