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.

422 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #if ! JUCE_QUICKTIME
  19. #error "On the Mac, cameras use Quicktime, so if you turn on JUCE_USE_CAMERA, you also need to enable JUCE_QUICKTIME"
  20. #endif
  21. //==============================================================================
  22. #define QTCaptureCallbackDelegate MakeObjCClassName(QTCaptureCallbackDelegate)
  23. class QTCameraDeviceInteral;
  24. END_JUCE_NAMESPACE
  25. @interface QTCaptureCallbackDelegate : NSObject
  26. {
  27. @public
  28. CameraDevice* owner;
  29. QTCameraDeviceInteral* internal;
  30. int64 firstPresentationTime;
  31. int64 averageTimeOffset;
  32. }
  33. - (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner internalDev: (QTCameraDeviceInteral*) d;
  34. - (void) dealloc;
  35. - (void) captureOutput: (QTCaptureOutput*) captureOutput
  36. didOutputVideoFrame: (CVImageBufferRef) videoFrame
  37. withSampleBuffer: (QTSampleBuffer*) sampleBuffer
  38. fromConnection: (QTCaptureConnection*) connection;
  39. - (void) captureOutput: (QTCaptureFileOutput*) captureOutput
  40. didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
  41. fromConnection: (QTCaptureConnection*) connection;
  42. @end
  43. BEGIN_JUCE_NAMESPACE
  44. extern Image juce_createImageFromCIImage (CIImage* im, int w, int h);
  45. //==============================================================================
  46. class QTCameraDeviceInteral
  47. {
  48. public:
  49. QTCameraDeviceInteral (CameraDevice* owner, const int index)
  50. : input (nil),
  51. audioDevice (nil),
  52. audioInput (nil),
  53. session (nil),
  54. fileOutput (nil),
  55. imageOutput (nil)
  56. {
  57. JUCE_AUTORELEASEPOOL
  58. session = [[QTCaptureSession alloc] init];
  59. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  60. device = (QTCaptureDevice*) [devs objectAtIndex: index];
  61. callbackDelegate = [[QTCaptureCallbackDelegate alloc] initWithOwner: owner
  62. internalDev: this];
  63. NSError* err = nil;
  64. [device retain];
  65. [device open: &err];
  66. if (err == nil)
  67. {
  68. input = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  69. audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  70. [session addInput: input error: &err];
  71. if (err == nil)
  72. {
  73. resetFile();
  74. imageOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
  75. [imageOutput setDelegate: callbackDelegate];
  76. if (err == nil)
  77. {
  78. [session startRunning];
  79. return;
  80. }
  81. }
  82. }
  83. openingError = nsStringToJuce ([err description]);
  84. DBG (openingError);
  85. }
  86. ~QTCameraDeviceInteral()
  87. {
  88. [session stopRunning];
  89. [session removeOutput: imageOutput];
  90. [session release];
  91. [input release];
  92. [device release];
  93. [audioDevice release];
  94. [audioInput release];
  95. [fileOutput release];
  96. [imageOutput release];
  97. [callbackDelegate release];
  98. }
  99. void resetFile()
  100. {
  101. [fileOutput recordToOutputFileURL: nil];
  102. [session removeOutput: fileOutput];
  103. [fileOutput release];
  104. fileOutput = [[QTCaptureMovieFileOutput alloc] init];
  105. [session removeInput: audioInput];
  106. [audioInput release];
  107. audioInput = nil;
  108. [audioDevice release];
  109. audioDevice = nil;
  110. [fileOutput setDelegate: callbackDelegate];
  111. }
  112. void addDefaultAudioInput()
  113. {
  114. NSError* err = nil;
  115. audioDevice = [QTCaptureDevice defaultInputDeviceWithMediaType: QTMediaTypeSound];
  116. if ([audioDevice open: &err])
  117. [audioDevice retain];
  118. else
  119. audioDevice = nil;
  120. if (audioDevice != nil)
  121. {
  122. audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: audioDevice];
  123. [session addInput: audioInput error: &err];
  124. }
  125. }
  126. void addListener (CameraDevice::Listener* listenerToAdd)
  127. {
  128. const ScopedLock sl (listenerLock);
  129. if (listeners.size() == 0)
  130. [session addOutput: imageOutput error: nil];
  131. listeners.addIfNotAlreadyThere (listenerToAdd);
  132. }
  133. void removeListener (CameraDevice::Listener* listenerToRemove)
  134. {
  135. const ScopedLock sl (listenerLock);
  136. listeners.removeValue (listenerToRemove);
  137. if (listeners.size() == 0)
  138. [session removeOutput: imageOutput];
  139. }
  140. void callListeners (CIImage* frame, int w, int h)
  141. {
  142. Image image (juce_createImageFromCIImage (frame, w, h));
  143. const ScopedLock sl (listenerLock);
  144. for (int i = listeners.size(); --i >= 0;)
  145. {
  146. CameraDevice::Listener* const l = listeners[i];
  147. if (l != nullptr)
  148. l->imageReceived (image);
  149. }
  150. }
  151. QTCaptureDevice* device;
  152. QTCaptureDeviceInput* input;
  153. QTCaptureDevice* audioDevice;
  154. QTCaptureDeviceInput* audioInput;
  155. QTCaptureSession* session;
  156. QTCaptureMovieFileOutput* fileOutput;
  157. QTCaptureDecompressedVideoOutput* imageOutput;
  158. QTCaptureCallbackDelegate* callbackDelegate;
  159. String openingError;
  160. Array<CameraDevice::Listener*> listeners;
  161. CriticalSection listenerLock;
  162. };
  163. END_JUCE_NAMESPACE
  164. @implementation QTCaptureCallbackDelegate
  165. - (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner_
  166. internalDev: (QTCameraDeviceInteral*) d
  167. {
  168. [super init];
  169. owner = owner_;
  170. internal = d;
  171. firstPresentationTime = 0;
  172. averageTimeOffset = 0;
  173. return self;
  174. }
  175. - (void) dealloc
  176. {
  177. [super dealloc];
  178. }
  179. - (void) captureOutput: (QTCaptureOutput*) captureOutput
  180. didOutputVideoFrame: (CVImageBufferRef) videoFrame
  181. withSampleBuffer: (QTSampleBuffer*) sampleBuffer
  182. fromConnection: (QTCaptureConnection*) connection
  183. {
  184. if (internal->listeners.size() > 0)
  185. {
  186. JUCE_AUTORELEASEPOOL
  187. internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame],
  188. CVPixelBufferGetWidth (videoFrame),
  189. CVPixelBufferGetHeight (videoFrame));
  190. }
  191. }
  192. - (void) captureOutput: (QTCaptureFileOutput*) captureOutput
  193. didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
  194. fromConnection: (QTCaptureConnection*) connection
  195. {
  196. const Time now (Time::getCurrentTime());
  197. #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
  198. NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute];
  199. #else
  200. NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: nsStringLiteral ("hostTime")];
  201. #endif
  202. int64 presentationTime = (hosttime != nil)
  203. ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40)
  204. : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50);
  205. const int64 timeDiff = now.toMilliseconds() - presentationTime;
  206. if (firstPresentationTime == 0)
  207. {
  208. firstPresentationTime = presentationTime;
  209. averageTimeOffset = timeDiff;
  210. }
  211. else
  212. {
  213. averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128;
  214. }
  215. }
  216. @end
  217. BEGIN_JUCE_NAMESPACE
  218. //==============================================================================
  219. class QTCaptureViewerComp : public NSViewComponent
  220. {
  221. public:
  222. QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal)
  223. {
  224. JUCE_AUTORELEASEPOOL
  225. captureView = [[QTCaptureView alloc] init];
  226. [captureView setCaptureSession: internal->session];
  227. setSize (640, 480); // xxx need to somehow get the movie size - how?
  228. setView (captureView);
  229. }
  230. ~QTCaptureViewerComp()
  231. {
  232. setView (0);
  233. [captureView setCaptureSession: nil];
  234. [captureView release];
  235. }
  236. QTCaptureView* captureView;
  237. };
  238. //==============================================================================
  239. CameraDevice::CameraDevice (const String& name_, int index)
  240. : name (name_)
  241. {
  242. isRecording = false;
  243. internal = new QTCameraDeviceInteral (this, index);
  244. }
  245. CameraDevice::~CameraDevice()
  246. {
  247. stopRecording();
  248. delete static_cast <QTCameraDeviceInteral*> (internal);
  249. internal = nullptr;
  250. }
  251. Component* CameraDevice::createViewerComponent()
  252. {
  253. return new QTCaptureViewerComp (this, static_cast <QTCameraDeviceInteral*> (internal));
  254. }
  255. String CameraDevice::getFileExtension()
  256. {
  257. return ".mov";
  258. }
  259. void CameraDevice::startRecordingToFile (const File& file, int quality)
  260. {
  261. stopRecording();
  262. QTCameraDeviceInteral* const d = static_cast <QTCameraDeviceInteral*> (internal);
  263. d->callbackDelegate->firstPresentationTime = 0;
  264. file.deleteFile();
  265. // In some versions of QT (e.g. on 10.5), if you record video without audio, the speed comes
  266. // out wrong, so we'll put some audio in there too..,
  267. d->addDefaultAudioInput();
  268. [d->session addOutput: d->fileOutput error: nil];
  269. NSEnumerator* connectionEnumerator = [[d->fileOutput connections] objectEnumerator];
  270. for (;;)
  271. {
  272. QTCaptureConnection* connection = [connectionEnumerator nextObject];
  273. if (connection == nil)
  274. break;
  275. QTCompressionOptions* options = nil;
  276. NSString* mediaType = [connection mediaType];
  277. if ([mediaType isEqualToString: QTMediaTypeVideo])
  278. options = [QTCompressionOptions compressionOptionsWithIdentifier:
  279. quality >= 1 ? nsStringLiteral ("QTCompressionOptionsSD480SizeH264Video")
  280. : nsStringLiteral ("QTCompressionOptions240SizeH264Video")];
  281. else if ([mediaType isEqualToString: QTMediaTypeSound])
  282. options = [QTCompressionOptions compressionOptionsWithIdentifier: nsStringLiteral ("QTCompressionOptionsHighQualityAACAudio")];
  283. [d->fileOutput setCompressionOptions: options forConnection: connection];
  284. }
  285. [d->fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]];
  286. isRecording = true;
  287. }
  288. Time CameraDevice::getTimeOfFirstRecordedFrame() const
  289. {
  290. QTCameraDeviceInteral* const d = static_cast <QTCameraDeviceInteral*> (internal);
  291. if (d->callbackDelegate->firstPresentationTime != 0)
  292. return Time (d->callbackDelegate->firstPresentationTime + d->callbackDelegate->averageTimeOffset);
  293. return Time();
  294. }
  295. void CameraDevice::stopRecording()
  296. {
  297. if (isRecording)
  298. {
  299. static_cast <QTCameraDeviceInteral*> (internal)->resetFile();
  300. isRecording = false;
  301. }
  302. }
  303. void CameraDevice::addListener (Listener* listenerToAdd)
  304. {
  305. if (listenerToAdd != nullptr)
  306. static_cast <QTCameraDeviceInteral*> (internal)->addListener (listenerToAdd);
  307. }
  308. void CameraDevice::removeListener (Listener* listenerToRemove)
  309. {
  310. if (listenerToRemove != nullptr)
  311. static_cast <QTCameraDeviceInteral*> (internal)->removeListener (listenerToRemove);
  312. }
  313. //==============================================================================
  314. StringArray CameraDevice::getAvailableDevices()
  315. {
  316. JUCE_AUTORELEASEPOOL
  317. StringArray results;
  318. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  319. for (int i = 0; i < (int) [devs count]; ++i)
  320. {
  321. QTCaptureDevice* dev = (QTCaptureDevice*) [devs objectAtIndex: i];
  322. results.add (nsStringToJuce ([dev localizedDisplayName]));
  323. }
  324. return results;
  325. }
  326. CameraDevice* CameraDevice::openDevice (int index,
  327. int minWidth, int minHeight,
  328. int maxWidth, int maxHeight)
  329. {
  330. ScopedPointer <CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index));
  331. if (static_cast <QTCameraDeviceInteral*> (d->internal)->openingError.isEmpty())
  332. return d.release();
  333. return nullptr;
  334. }