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.

409 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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. // (This file gets included by juce_mac_NativeCode.mm, rather than being
  19. // compiled on its own).
  20. #if JUCE_INCLUDED_FILE && JUCE_QUICKTIME && JUCE_USE_CAMERA
  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. Time* firstRecordedTime;
  31. }
  32. - (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner internalDev: (QTCameraDeviceInteral*) d;
  33. - (void) dealloc;
  34. - (void) captureOutput: (QTCaptureOutput*) captureOutput
  35. didOutputVideoFrame: (CVImageBufferRef) videoFrame
  36. withSampleBuffer: (QTSampleBuffer*) sampleBuffer
  37. fromConnection: (QTCaptureConnection*) connection;
  38. - (void) captureOutput: (QTCaptureFileOutput*) captureOutput
  39. didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
  40. fromConnection: (QTCaptureConnection*) connection;
  41. @end
  42. BEGIN_JUCE_NAMESPACE
  43. //==============================================================================
  44. class QTCameraDeviceInteral
  45. {
  46. public:
  47. QTCameraDeviceInteral (CameraDevice* owner, int index)
  48. {
  49. const ScopedAutoReleasePool pool;
  50. session = [[QTCaptureSession alloc] init];
  51. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  52. device = (QTCaptureDevice*) [devs objectAtIndex: index];
  53. input = 0;
  54. audioInput = 0;
  55. audioDevice = 0;
  56. fileOutput = 0;
  57. imageOutput = 0;
  58. callbackDelegate = [[QTCaptureCallbackDelegate alloc] initWithOwner: owner
  59. internalDev: this];
  60. NSError* err = 0;
  61. [device retain];
  62. [device open: &err];
  63. if (err == 0)
  64. {
  65. input = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  66. audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  67. [session addInput: input error: &err];
  68. if (err == 0)
  69. {
  70. resetFile();
  71. imageOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
  72. [imageOutput setDelegate: callbackDelegate];
  73. if (err == 0)
  74. {
  75. [session startRunning];
  76. return;
  77. }
  78. }
  79. }
  80. openingError = nsStringToJuce ([err description]);
  81. DBG (openingError);
  82. }
  83. ~QTCameraDeviceInteral()
  84. {
  85. [session stopRunning];
  86. [session removeOutput: imageOutput];
  87. [session release];
  88. [input release];
  89. [device release];
  90. [audioDevice release];
  91. [audioInput release];
  92. [fileOutput release];
  93. [imageOutput release];
  94. [callbackDelegate release];
  95. }
  96. void resetFile()
  97. {
  98. [fileOutput recordToOutputFileURL: nil];
  99. [session removeOutput: fileOutput];
  100. [fileOutput release];
  101. fileOutput = [[QTCaptureMovieFileOutput alloc] init];
  102. [session removeInput: audioInput];
  103. [audioInput release];
  104. audioInput = 0;
  105. [audioDevice release];
  106. audioDevice = 0;
  107. [fileOutput setDelegate: callbackDelegate];
  108. }
  109. void addDefaultAudioInput()
  110. {
  111. NSError* err = nil;
  112. audioDevice = [QTCaptureDevice defaultInputDeviceWithMediaType: QTMediaTypeSound];
  113. if ([audioDevice open: &err])
  114. [audioDevice retain];
  115. else
  116. audioDevice = nil;
  117. if (audioDevice != 0)
  118. {
  119. audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: audioDevice];
  120. [session addInput: audioInput error: &err];
  121. }
  122. }
  123. void addListener (CameraImageListener* listenerToAdd)
  124. {
  125. const ScopedLock sl (listenerLock);
  126. if (listeners.size() == 0)
  127. [session addOutput: imageOutput error: nil];
  128. listeners.addIfNotAlreadyThere (listenerToAdd);
  129. }
  130. void removeListener (CameraImageListener* listenerToRemove)
  131. {
  132. const ScopedLock sl (listenerLock);
  133. listeners.removeValue (listenerToRemove);
  134. if (listeners.size() == 0)
  135. [session removeOutput: imageOutput];
  136. }
  137. void callListeners (CIImage* frame, int w, int h)
  138. {
  139. CoreGraphicsImage image (Image::ARGB, w, h, false);
  140. CIContext* cic = [CIContext contextWithCGContext: image.context options: nil];
  141. [cic drawImage: frame inRect: CGRectMake (0, 0, w, h) fromRect: CGRectMake (0, 0, w, h)];
  142. CGContextFlush (image.context);
  143. const ScopedLock sl (listenerLock);
  144. for (int i = listeners.size(); --i >= 0;)
  145. {
  146. CameraImageListener* l = (CameraImageListener*) listeners[i];
  147. if (l != 0)
  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. VoidArray 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. firstRecordedTime = 0;
  172. return self;
  173. }
  174. - (void) dealloc
  175. {
  176. delete firstRecordedTime;
  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. const ScopedAutoReleasePool pool;
  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. if (firstRecordedTime == 0)
  197. {
  198. const Time now (Time::getCurrentTime());
  199. firstRecordedTime = new Time (now - RelativeTime (0.1));
  200. }
  201. }
  202. @end
  203. BEGIN_JUCE_NAMESPACE
  204. //==============================================================================
  205. class QTCaptureViewerComp : public NSViewComponent
  206. {
  207. public:
  208. QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal)
  209. {
  210. const ScopedAutoReleasePool pool;
  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. ~QTCaptureViewerComp()
  217. {
  218. setView (0);
  219. [captureView setCaptureSession: nil];
  220. [captureView release];
  221. }
  222. QTCaptureView* captureView;
  223. };
  224. //==============================================================================
  225. CameraDevice::CameraDevice (const String& name_, int index)
  226. : name (name_)
  227. {
  228. isRecording = false;
  229. QTCameraDeviceInteral* d = new QTCameraDeviceInteral (this, index);
  230. internal = d;
  231. }
  232. CameraDevice::~CameraDevice()
  233. {
  234. stopRecording();
  235. delete (QTCameraDeviceInteral*) internal;
  236. internal = 0;
  237. }
  238. Component* CameraDevice::createViewerComponent()
  239. {
  240. return new QTCaptureViewerComp (this, (QTCameraDeviceInteral*) internal);
  241. }
  242. const String CameraDevice::getFileExtension()
  243. {
  244. return ".mov";
  245. }
  246. void CameraDevice::startRecordingToFile (const File& file)
  247. {
  248. stopRecording();
  249. QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
  250. deleteAndZero (d->callbackDelegate->firstRecordedTime);
  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 == 0)
  261. break;
  262. QTCompressionOptions* options = 0;
  263. NSString* mediaType = [connection mediaType];
  264. if ([mediaType isEqualToString: QTMediaTypeVideo])
  265. options = [QTCompressionOptions compressionOptionsWithIdentifier: @"QTCompressionOptionsSD480SizeH264Video"];
  266. else if ([mediaType isEqualToString: QTMediaTypeSound])
  267. options = [QTCompressionOptions compressionOptionsWithIdentifier: @"QTCompressionOptionsHighQualityAACAudio"];
  268. [d->fileOutput setCompressionOptions: options forConnection: connection];
  269. }
  270. [d->fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]];
  271. isRecording = true;
  272. }
  273. const Time CameraDevice::getTimeOfFirstRecordedFrame() const
  274. {
  275. QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
  276. if (d->callbackDelegate->firstRecordedTime != 0)
  277. return *d->callbackDelegate->firstRecordedTime;
  278. return Time();
  279. }
  280. void CameraDevice::stopRecording()
  281. {
  282. if (isRecording)
  283. {
  284. QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
  285. d->resetFile();
  286. isRecording = false;
  287. }
  288. }
  289. void CameraDevice::addListener (CameraImageListener* listenerToAdd)
  290. {
  291. QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
  292. if (listenerToAdd != 0)
  293. d->addListener (listenerToAdd);
  294. }
  295. void CameraDevice::removeListener (CameraImageListener* listenerToRemove)
  296. {
  297. QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
  298. if (listenerToRemove != 0)
  299. d->removeListener (listenerToRemove);
  300. }
  301. //==============================================================================
  302. const StringArray CameraDevice::getAvailableDevices()
  303. {
  304. const ScopedAutoReleasePool pool;
  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. CameraDevice* CameraDevice::openDevice (int index,
  315. int minWidth, int minHeight,
  316. int maxWidth, int maxHeight)
  317. {
  318. ScopedPointer <CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index));
  319. if (((QTCameraDeviceInteral*) (d->internal))->openingError.isEmpty())
  320. return d.release();
  321. return 0;
  322. }
  323. #endif