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.

403 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software 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 "On the Mac, cameras use Quicktime, so if you turn on JUCE_USE_CAMERA, you also need to enable JUCE_QUICKTIME"
  19. #endif
  20. extern Image juce_createImageFromCIImage (CIImage* im, int w, int h);
  21. //==============================================================================
  22. class QTCameraDeviceInternal
  23. {
  24. public:
  25. QTCameraDeviceInternal (CameraDevice*, const int index)
  26. : input (nil),
  27. audioDevice (nil),
  28. audioInput (nil),
  29. session (nil),
  30. fileOutput (nil),
  31. imageOutput (nil),
  32. firstPresentationTime (0),
  33. averageTimeOffset (0)
  34. {
  35. JUCE_AUTORELEASEPOOL
  36. {
  37. session = [[QTCaptureSession alloc] init];
  38. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  39. device = (QTCaptureDevice*) [devs objectAtIndex: index];
  40. static DelegateClass cls;
  41. callbackDelegate = [cls.createInstance() init];
  42. DelegateClass::setOwner (callbackDelegate, this);
  43. NSError* err = nil;
  44. [device retain];
  45. [device open: &err];
  46. if (err == nil)
  47. {
  48. input = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  49. audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  50. [session addInput: input error: &err];
  51. if (err == nil)
  52. {
  53. resetFile();
  54. imageOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
  55. [imageOutput setDelegate: callbackDelegate];
  56. if (err == nil)
  57. {
  58. [session startRunning];
  59. return;
  60. }
  61. }
  62. }
  63. openingError = nsStringToJuce ([err description]);
  64. DBG (openingError);
  65. }
  66. }
  67. ~QTCameraDeviceInternal()
  68. {
  69. [session stopRunning];
  70. [session removeOutput: imageOutput];
  71. [session release];
  72. [input release];
  73. [device release];
  74. [audioDevice release];
  75. [audioInput release];
  76. [fileOutput release];
  77. [imageOutput release];
  78. [callbackDelegate release];
  79. }
  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 addListener (CameraDevice::Listener* listenerToAdd)
  108. {
  109. const ScopedLock sl (listenerLock);
  110. if (listeners.size() == 0)
  111. [session addOutput: imageOutput error: nil];
  112. listeners.addIfNotAlreadyThere (listenerToAdd);
  113. }
  114. void removeListener (CameraDevice::Listener* listenerToRemove)
  115. {
  116. const ScopedLock sl (listenerLock);
  117. listeners.removeFirstMatchingValue (listenerToRemove);
  118. if (listeners.size() == 0)
  119. [session removeOutput: imageOutput];
  120. }
  121. void callListeners (CIImage* frame, int w, int h)
  122. {
  123. Image image (juce_createImageFromCIImage (frame, w, h));
  124. const ScopedLock sl (listenerLock);
  125. for (int i = listeners.size(); --i >= 0;)
  126. {
  127. CameraDevice::Listener* const l = listeners[i];
  128. if (l != nullptr)
  129. l->imageReceived (image);
  130. }
  131. }
  132. void captureBuffer (QTSampleBuffer* sampleBuffer)
  133. {
  134. const Time now (Time::getCurrentTime());
  135. #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
  136. NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute];
  137. #else
  138. NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: nsStringLiteral ("hostTime")];
  139. #endif
  140. int64 presentationTime = (hosttime != nil)
  141. ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40)
  142. : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50);
  143. const int64 timeDiff = now.toMilliseconds() - presentationTime;
  144. if (firstPresentationTime == 0)
  145. {
  146. firstPresentationTime = presentationTime;
  147. averageTimeOffset = timeDiff;
  148. }
  149. else
  150. {
  151. averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128;
  152. }
  153. }
  154. QTCaptureDevice* device;
  155. QTCaptureDeviceInput* input;
  156. QTCaptureDevice* audioDevice;
  157. QTCaptureDeviceInput* audioInput;
  158. QTCaptureSession* session;
  159. QTCaptureMovieFileOutput* fileOutput;
  160. QTCaptureDecompressedVideoOutput* imageOutput;
  161. NSObject* callbackDelegate;
  162. String openingError;
  163. int64 firstPresentationTime;
  164. int64 averageTimeOffset;
  165. Array<CameraDevice::Listener*> listeners;
  166. CriticalSection listenerLock;
  167. private:
  168. //==============================================================================
  169. struct DelegateClass : public ObjCClass <NSObject>
  170. {
  171. DelegateClass() : ObjCClass <NSObject> ("JUCEAppDelegate_")
  172. {
  173. addIvar<QTCameraDeviceInternal*> ("owner");
  174. addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:),
  175. didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@");
  176. addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:),
  177. didOutputVideoFrame, "v@:@@@");
  178. registerClass();
  179. }
  180. static void setOwner (id self, QTCameraDeviceInternal* owner) { object_setInstanceVariable (self, "owner", owner); }
  181. static QTCameraDeviceInternal* getOwner (id self) { return getIvar<QTCameraDeviceInternal*> (self, "owner"); }
  182. private:
  183. static void didOutputVideoFrame (id self, SEL, QTCaptureOutput*, CVImageBufferRef videoFrame,
  184. QTSampleBuffer*, QTCaptureConnection*)
  185. {
  186. QTCameraDeviceInternal* const internal = getOwner (self);
  187. if (internal->listeners.size() > 0)
  188. {
  189. JUCE_AUTORELEASEPOOL
  190. {
  191. internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame],
  192. (int) CVPixelBufferGetWidth (videoFrame),
  193. (int) CVPixelBufferGetHeight (videoFrame));
  194. }
  195. }
  196. }
  197. static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*)
  198. {
  199. getOwner (self)->captureBuffer (sampleBuffer);
  200. }
  201. };
  202. };
  203. //==============================================================================
  204. class QTCaptureViewerComp : public NSViewComponent
  205. {
  206. public:
  207. QTCaptureViewerComp (CameraDevice*, QTCameraDeviceInternal* internal)
  208. {
  209. JUCE_AUTORELEASEPOOL
  210. {
  211. captureView = [[QTCaptureView alloc] init];
  212. [captureView setCaptureSession: internal->session];
  213. setSize (640, 480); // xxx need to somehow get the movie size - how?
  214. setView (captureView);
  215. }
  216. }
  217. ~QTCaptureViewerComp()
  218. {
  219. setView (0);
  220. [captureView setCaptureSession: nil];
  221. [captureView release];
  222. }
  223. QTCaptureView* captureView;
  224. };
  225. //==============================================================================
  226. CameraDevice::CameraDevice (const String& name_, int index)
  227. : name (name_)
  228. {
  229. isRecording = false;
  230. internal = new QTCameraDeviceInternal (this, index);
  231. }
  232. CameraDevice::~CameraDevice()
  233. {
  234. stopRecording();
  235. delete static_cast <QTCameraDeviceInternal*> (internal);
  236. internal = nullptr;
  237. }
  238. Component* CameraDevice::createViewerComponent()
  239. {
  240. return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInternal*> (internal));
  241. }
  242. String CameraDevice::getFileExtension()
  243. {
  244. return ".mov";
  245. }
  246. void CameraDevice::startRecordingToFile (const File& file, int quality)
  247. {
  248. stopRecording();
  249. QTCameraDeviceInternal* const d = static_cast <QTCameraDeviceInternal*> (internal);
  250. d->firstPresentationTime = 0;
  251. file.deleteFile();
  252. // In some versions of QT (e.g. on 10.5), if you record video without audio, the speed comes
  253. // out wrong, so we'll put some audio in there too..,
  254. d->addDefaultAudioInput();
  255. [d->session addOutput: d->fileOutput error: nil];
  256. NSEnumerator* connectionEnumerator = [[d->fileOutput connections] objectEnumerator];
  257. for (;;)
  258. {
  259. QTCaptureConnection* connection = [connectionEnumerator nextObject];
  260. if (connection == nil)
  261. break;
  262. QTCompressionOptions* options = nil;
  263. NSString* mediaType = [connection mediaType];
  264. if ([mediaType isEqualToString: QTMediaTypeVideo])
  265. options = [QTCompressionOptions compressionOptionsWithIdentifier:
  266. quality >= 1 ? nsStringLiteral ("QTCompressionOptionsSD480SizeH264Video")
  267. : nsStringLiteral ("QTCompressionOptions240SizeH264Video")];
  268. else if ([mediaType isEqualToString: QTMediaTypeSound])
  269. options = [QTCompressionOptions compressionOptionsWithIdentifier: nsStringLiteral ("QTCompressionOptionsHighQualityAACAudio")];
  270. [d->fileOutput setCompressionOptions: options forConnection: connection];
  271. }
  272. [d->fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]];
  273. isRecording = true;
  274. }
  275. Time CameraDevice::getTimeOfFirstRecordedFrame() const
  276. {
  277. QTCameraDeviceInternal* const d = static_cast <QTCameraDeviceInternal*> (internal);
  278. if (d->firstPresentationTime != 0)
  279. return Time (d->firstPresentationTime + d->averageTimeOffset);
  280. return Time();
  281. }
  282. void CameraDevice::stopRecording()
  283. {
  284. if (isRecording)
  285. {
  286. static_cast <QTCameraDeviceInternal*> (internal)->resetFile();
  287. isRecording = false;
  288. }
  289. }
  290. void CameraDevice::addListener (Listener* listenerToAdd)
  291. {
  292. if (listenerToAdd != nullptr)
  293. static_cast <QTCameraDeviceInternal*> (internal)->addListener (listenerToAdd);
  294. }
  295. void CameraDevice::removeListener (Listener* listenerToRemove)
  296. {
  297. if (listenerToRemove != nullptr)
  298. static_cast <QTCameraDeviceInternal*> (internal)->removeListener (listenerToRemove);
  299. }
  300. //==============================================================================
  301. StringArray CameraDevice::getAvailableDevices()
  302. {
  303. JUCE_AUTORELEASEPOOL
  304. {
  305. StringArray results;
  306. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  307. for (int i = 0; i < (int) [devs count]; ++i)
  308. {
  309. QTCaptureDevice* dev = (QTCaptureDevice*) [devs objectAtIndex: i];
  310. results.add (nsStringToJuce ([dev localizedDisplayName]));
  311. }
  312. return results;
  313. }
  314. }
  315. CameraDevice* CameraDevice::openDevice (int index,
  316. int /*minWidth*/, int /*minHeight*/,
  317. int /*maxWidth*/, int /*maxHeight*/)
  318. {
  319. ScopedPointer <CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index));
  320. if (static_cast <QTCameraDeviceInternal*> (d->internal)->openingError.isEmpty())
  321. return d.release();
  322. return nullptr;
  323. }