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.

354 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. #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
  177. NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute];
  178. #else
  179. NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: nsStringLiteral ("hostTime")];
  180. #endif
  181. int64 presentationTime = (hosttime != nil)
  182. ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40)
  183. : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50);
  184. const int64 timeDiff = now.toMilliseconds() - presentationTime;
  185. if (firstPresentationTime == 0)
  186. {
  187. firstPresentationTime = presentationTime;
  188. averageTimeOffset = timeDiff;
  189. }
  190. else
  191. {
  192. averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128;
  193. }
  194. }
  195. static StringArray getAvailableDevices()
  196. {
  197. StringArray results;
  198. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  199. for (int i = 0; i < (int) [devs count]; ++i)
  200. {
  201. QTCaptureDevice* dev = (QTCaptureDevice*) [devs objectAtIndex: i];
  202. results.add (nsStringToJuce ([dev localizedDisplayName]));
  203. }
  204. return results;
  205. }
  206. QTCaptureDevice* device;
  207. QTCaptureDevice* audioDevice;
  208. QTCaptureDeviceInput* input;
  209. QTCaptureDeviceInput* audioInput;
  210. QTCaptureSession* session;
  211. QTCaptureMovieFileOutput* fileOutput;
  212. QTCaptureDecompressedVideoOutput* imageOutput;
  213. NSObject* callbackDelegate;
  214. String openingError;
  215. int64 firstPresentationTime, averageTimeOffset;
  216. bool isRecording;
  217. Array<CameraDevice::Listener*> listeners;
  218. CriticalSection listenerLock;
  219. private:
  220. //==============================================================================
  221. struct DelegateClass : public ObjCClass<NSObject>
  222. {
  223. DelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_")
  224. {
  225. addIvar<Pimpl*> ("owner");
  226. addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:),
  227. didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@");
  228. addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:),
  229. didOutputVideoFrame, "v@:@@@");
  230. registerClass();
  231. }
  232. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  233. static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
  234. private:
  235. static void didOutputVideoFrame (id self, SEL, QTCaptureOutput*, CVImageBufferRef videoFrame,
  236. QTSampleBuffer*, QTCaptureConnection*)
  237. {
  238. Pimpl* const internal = getOwner (self);
  239. if (internal->listeners.size() > 0)
  240. {
  241. JUCE_AUTORELEASEPOOL
  242. {
  243. internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame],
  244. (int) CVPixelBufferGetWidth (videoFrame),
  245. (int) CVPixelBufferGetHeight (videoFrame));
  246. }
  247. }
  248. }
  249. static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*)
  250. {
  251. getOwner (self)->captureBuffer (sampleBuffer);
  252. }
  253. };
  254. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  255. };
  256. struct CameraDevice::ViewerComponent : public NSViewComponent
  257. {
  258. ViewerComponent (CameraDevice& d)
  259. {
  260. JUCE_AUTORELEASEPOOL
  261. {
  262. captureView = [[QTCaptureView alloc] init];
  263. [captureView setCaptureSession: d.pimpl->session];
  264. setSize (640, 480);
  265. setView (captureView);
  266. }
  267. }
  268. ~ViewerComponent()
  269. {
  270. setView (nil);
  271. [captureView setCaptureSession: nil];
  272. [captureView release];
  273. }
  274. QTCaptureView* captureView;
  275. JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
  276. };
  277. String CameraDevice::getFileExtension()
  278. {
  279. return ".mov";
  280. }