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.

350 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #if ! JUCE_QUICKTIME
  18. #error "To support cameras in OSX you'll need to enable the JUCE_QUICKTIME flag"
  19. #endif
  20. extern Image juce_createImageFromCIImage (CIImage*, int w, int h);
  21. struct CameraDevice::Pimpl
  22. {
  23. Pimpl (const String&, const int index, int /*minWidth*/, int /*minHeight*/, int /*maxWidth*/, int /*maxHeight*/)
  24. : input (nil),
  25. audioDevice (nil),
  26. audioInput (nil),
  27. session (nil),
  28. fileOutput (nil),
  29. imageOutput (nil),
  30. firstPresentationTime (0),
  31. averageTimeOffset (0),
  32. isRecording (false)
  33. {
  34. JUCE_AUTORELEASEPOOL
  35. {
  36. session = [[QTCaptureSession alloc] init];
  37. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  38. device = (QTCaptureDevice*) [devs objectAtIndex: index];
  39. static DelegateClass cls;
  40. callbackDelegate = [cls.createInstance() init];
  41. DelegateClass::setOwner (callbackDelegate, this);
  42. NSError* err = nil;
  43. [device retain];
  44. [device open: &err];
  45. if (err == nil)
  46. {
  47. input = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  48. audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  49. [session addInput: input error: &err];
  50. if (err == nil)
  51. {
  52. resetFile();
  53. imageOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
  54. [imageOutput setDelegate: callbackDelegate];
  55. if (err == nil)
  56. {
  57. [session startRunning];
  58. return;
  59. }
  60. }
  61. }
  62. openingError = nsStringToJuce ([err description]);
  63. DBG (openingError);
  64. }
  65. }
  66. ~Pimpl()
  67. {
  68. [session stopRunning];
  69. [session removeOutput: imageOutput];
  70. [session release];
  71. [input release];
  72. [device release];
  73. [audioDevice release];
  74. [audioInput release];
  75. [fileOutput release];
  76. [imageOutput release];
  77. [callbackDelegate release];
  78. }
  79. bool openedOk() const noexcept { return openingError.isEmpty(); }
  80. void resetFile()
  81. {
  82. [fileOutput recordToOutputFileURL: nil];
  83. [session removeOutput: fileOutput];
  84. [fileOutput release];
  85. fileOutput = [[QTCaptureMovieFileOutput alloc] init];
  86. [session removeInput: audioInput];
  87. [audioInput release];
  88. audioInput = nil;
  89. [audioDevice release];
  90. audioDevice = nil;
  91. [fileOutput setDelegate: callbackDelegate];
  92. }
  93. void addDefaultAudioInput()
  94. {
  95. NSError* err = nil;
  96. audioDevice = [QTCaptureDevice defaultInputDeviceWithMediaType: QTMediaTypeSound];
  97. if ([audioDevice open: &err])
  98. [audioDevice retain];
  99. else
  100. audioDevice = nil;
  101. if (audioDevice != nil)
  102. {
  103. audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: audioDevice];
  104. [session addInput: audioInput error: &err];
  105. }
  106. }
  107. void startRecordingToFile (const File& file, int quality)
  108. {
  109. stopRecording();
  110. firstPresentationTime = 0;
  111. file.deleteFile();
  112. // In some versions of QT (e.g. on 10.5), if you record video without audio, the speed comes
  113. // out wrong, so we'll put some audio in there too..,
  114. addDefaultAudioInput();
  115. [session addOutput: fileOutput error: nil];
  116. NSEnumerator* connectionEnumerator = [[fileOutput connections] objectEnumerator];
  117. for (;;)
  118. {
  119. QTCaptureConnection* connection = [connectionEnumerator nextObject];
  120. if (connection == nil)
  121. break;
  122. QTCompressionOptions* options = nil;
  123. NSString* mediaType = [connection mediaType];
  124. if ([mediaType isEqualToString: QTMediaTypeVideo])
  125. options = [QTCompressionOptions compressionOptionsWithIdentifier:
  126. quality >= 1 ? nsStringLiteral ("QTCompressionOptionsSD480SizeH264Video")
  127. : nsStringLiteral ("QTCompressionOptions240SizeH264Video")];
  128. else if ([mediaType isEqualToString: QTMediaTypeSound])
  129. options = [QTCompressionOptions compressionOptionsWithIdentifier: nsStringLiteral ("QTCompressionOptionsHighQualityAACAudio")];
  130. [fileOutput setCompressionOptions: options forConnection: connection];
  131. }
  132. [fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]];
  133. isRecording = true;
  134. }
  135. void stopRecording()
  136. {
  137. if (isRecording)
  138. {
  139. resetFile();
  140. isRecording = false;
  141. }
  142. }
  143. Time getTimeOfFirstRecordedFrame() const
  144. {
  145. return firstPresentationTime != 0 ? Time (firstPresentationTime + averageTimeOffset)
  146. : Time();
  147. }
  148. void addListener (CameraDevice::Listener* listenerToAdd)
  149. {
  150. const ScopedLock sl (listenerLock);
  151. if (listeners.size() == 0)
  152. [session addOutput: imageOutput error: nil];
  153. listeners.addIfNotAlreadyThere (listenerToAdd);
  154. }
  155. void removeListener (CameraDevice::Listener* listenerToRemove)
  156. {
  157. const ScopedLock sl (listenerLock);
  158. listeners.removeFirstMatchingValue (listenerToRemove);
  159. if (listeners.size() == 0)
  160. [session removeOutput: imageOutput];
  161. }
  162. void callListeners (CIImage* frame, int w, int h)
  163. {
  164. Image image (juce_createImageFromCIImage (frame, w, h));
  165. const ScopedLock sl (listenerLock);
  166. for (int i = listeners.size(); --i >= 0;)
  167. {
  168. CameraDevice::Listener* const l = listeners[i];
  169. if (l != nullptr)
  170. l->imageReceived (image);
  171. }
  172. }
  173. void captureBuffer (QTSampleBuffer* sampleBuffer)
  174. {
  175. const Time now (Time::getCurrentTime());
  176. NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute];
  177. int64 presentationTime = (hosttime != nil)
  178. ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40)
  179. : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50);
  180. const int64 timeDiff = now.toMilliseconds() - presentationTime;
  181. if (firstPresentationTime == 0)
  182. {
  183. firstPresentationTime = presentationTime;
  184. averageTimeOffset = timeDiff;
  185. }
  186. else
  187. {
  188. averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128;
  189. }
  190. }
  191. static StringArray getAvailableDevices()
  192. {
  193. StringArray results;
  194. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  195. for (int i = 0; i < (int) [devs count]; ++i)
  196. {
  197. QTCaptureDevice* dev = (QTCaptureDevice*) [devs objectAtIndex: i];
  198. results.add (nsStringToJuce ([dev localizedDisplayName]));
  199. }
  200. return results;
  201. }
  202. QTCaptureDevice* device;
  203. QTCaptureDevice* audioDevice;
  204. QTCaptureDeviceInput* input;
  205. QTCaptureDeviceInput* audioInput;
  206. QTCaptureSession* session;
  207. QTCaptureMovieFileOutput* fileOutput;
  208. QTCaptureDecompressedVideoOutput* imageOutput;
  209. NSObject* callbackDelegate;
  210. String openingError;
  211. int64 firstPresentationTime, averageTimeOffset;
  212. bool isRecording;
  213. Array<CameraDevice::Listener*> listeners;
  214. CriticalSection listenerLock;
  215. private:
  216. //==============================================================================
  217. struct DelegateClass : public ObjCClass<NSObject>
  218. {
  219. DelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_")
  220. {
  221. addIvar<Pimpl*> ("owner");
  222. addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:),
  223. didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@");
  224. addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:),
  225. didOutputVideoFrame, "v@:@@@");
  226. registerClass();
  227. }
  228. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  229. static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
  230. private:
  231. static void didOutputVideoFrame (id self, SEL, QTCaptureOutput*, CVImageBufferRef videoFrame,
  232. QTSampleBuffer*, QTCaptureConnection*)
  233. {
  234. Pimpl* const internal = getOwner (self);
  235. if (internal->listeners.size() > 0)
  236. {
  237. JUCE_AUTORELEASEPOOL
  238. {
  239. internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame],
  240. (int) CVPixelBufferGetWidth (videoFrame),
  241. (int) CVPixelBufferGetHeight (videoFrame));
  242. }
  243. }
  244. }
  245. static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*)
  246. {
  247. getOwner (self)->captureBuffer (sampleBuffer);
  248. }
  249. };
  250. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  251. };
  252. struct CameraDevice::ViewerComponent : public NSViewComponent
  253. {
  254. ViewerComponent (CameraDevice& d)
  255. {
  256. JUCE_AUTORELEASEPOOL
  257. {
  258. captureView = [[QTCaptureView alloc] init];
  259. [captureView setCaptureSession: d.pimpl->session];
  260. setSize (640, 480);
  261. setView (captureView);
  262. }
  263. }
  264. ~ViewerComponent()
  265. {
  266. setView (nil);
  267. [captureView setCaptureSession: nil];
  268. [captureView release];
  269. }
  270. QTCaptureView* captureView;
  271. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  272. };
  273. String CameraDevice::getFileExtension()
  274. {
  275. return ".mov";
  276. }