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.

410 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-7 by Raw Material Software ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the
  7. GNU General Public License, as published by the Free Software Foundation;
  8. either version 2 of the License, or (at your option) any later version.
  9. JUCE is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with JUCE; if not, visit www.gnu.org/licenses or write to the
  15. Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  16. Boston, MA 02111-1307 USA
  17. ------------------------------------------------------------------------------
  18. If you'd like to release a closed-source product which uses JUCE, commercial
  19. licenses are also available: visit www.rawmaterialsoftware.com/juce for
  20. more information.
  21. ==============================================================================
  22. */
  23. // (This file gets included by juce_mac_NativeCode.mm, rather than being
  24. // compiled on its own).
  25. #if JUCE_INCLUDED_FILE && JUCE_QUICKTIME && JUCE_USE_CAMERA
  26. //==============================================================================
  27. #define QTCaptureCallbackDelegate MakeObjCClassName(QTCaptureCallbackDelegate)
  28. class QTCameraDeviceInteral;
  29. END_JUCE_NAMESPACE
  30. @interface QTCaptureCallbackDelegate : NSObject
  31. {
  32. @public
  33. CameraDevice* owner;
  34. QTCameraDeviceInteral* internal;
  35. Time* firstRecordedTime;
  36. }
  37. - (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner internalDev: (QTCameraDeviceInteral*) d;
  38. - (void) dealloc;
  39. - (void) captureOutput: (QTCaptureOutput*) captureOutput
  40. didOutputVideoFrame: (CVImageBufferRef) videoFrame
  41. withSampleBuffer: (QTSampleBuffer*) sampleBuffer
  42. fromConnection: (QTCaptureConnection*) connection;
  43. - (void) captureOutput: (QTCaptureFileOutput*) captureOutput
  44. didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
  45. fromConnection: (QTCaptureConnection*) connection;
  46. @end
  47. BEGIN_JUCE_NAMESPACE
  48. //==============================================================================
  49. class QTCameraDeviceInteral
  50. {
  51. public:
  52. QTCameraDeviceInteral (CameraDevice* owner, int index)
  53. {
  54. const ScopedAutoReleasePool pool;
  55. session = [[QTCaptureSession alloc] init];
  56. NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo];
  57. device = (QTCaptureDevice*) [devs objectAtIndex: index];
  58. input = 0;
  59. fileOutput = 0;
  60. imageOutput = 0;
  61. callbackDelegate = [[QTCaptureCallbackDelegate alloc] initWithOwner: owner
  62. internalDev: this];
  63. NSError* err = 0;
  64. [device retain];
  65. [device open: &err];
  66. if (err == 0)
  67. {
  68. input = [[QTCaptureDeviceInput alloc] initWithDevice: device];
  69. [session addInput: input error: &err];
  70. if (err == 0)
  71. {
  72. resetFile();
  73. imageOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
  74. [imageOutput setDelegate: callbackDelegate];
  75. if (err == 0)
  76. {
  77. [session startRunning];
  78. return;
  79. }
  80. }
  81. }
  82. openingError = nsStringToJuce ([err description]);
  83. DBG (openingError);
  84. }
  85. ~QTCameraDeviceInteral()
  86. {
  87. [session stopRunning];
  88. [session removeOutput: imageOutput];
  89. [session release];
  90. [input release];
  91. [device release];
  92. [fileOutput release];
  93. [imageOutput release];
  94. [callbackDelegate release];
  95. }
  96. void resetFile()
  97. {
  98. [session removeOutput: fileOutput];
  99. [fileOutput release];
  100. fileOutput = [[QTCaptureMovieFileOutput alloc] init];
  101. [fileOutput setDelegate: callbackDelegate];
  102. }
  103. void addListener (CameraImageListener* listenerToAdd)
  104. {
  105. const ScopedLock sl (listenerLock);
  106. if (listeners.size() == 0)
  107. [session addOutput: imageOutput error: nil];
  108. listeners.addIfNotAlreadyThere (listenerToAdd);
  109. }
  110. void removeListener (CameraImageListener* listenerToRemove)
  111. {
  112. const ScopedLock sl (listenerLock);
  113. listeners.removeValue (listenerToRemove);
  114. if (listeners.size() == 0)
  115. [session removeOutput: imageOutput];
  116. }
  117. static void drawNSBitmapIntoJuceImage (Image& dest, NSBitmapImageRep* source)
  118. {
  119. const ScopedAutoReleasePool pool;
  120. int lineStride, pixelStride;
  121. uint8* pixels = dest.lockPixelDataReadWrite (0, 0, dest.getWidth(), dest.getHeight(),
  122. lineStride, pixelStride);
  123. NSBitmapImageRep* rep = [[NSBitmapImageRep alloc]
  124. initWithBitmapDataPlanes: &pixels
  125. pixelsWide: dest.getWidth()
  126. pixelsHigh: dest.getHeight()
  127. bitsPerSample: 8
  128. samplesPerPixel: pixelStride
  129. hasAlpha: dest.hasAlphaChannel()
  130. isPlanar: NO
  131. colorSpaceName: NSCalibratedRGBColorSpace
  132. bitmapFormat: (NSBitmapFormat) 0
  133. bytesPerRow: lineStride
  134. bitsPerPixel: pixelStride * 8];
  135. [NSGraphicsContext saveGraphicsState];
  136. [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithBitmapImageRep: rep]];
  137. [source drawAtPoint: NSZeroPoint];
  138. [[NSGraphicsContext currentContext] flushGraphics];
  139. [NSGraphicsContext restoreGraphicsState];
  140. uint8* start = pixels;
  141. for (int h = dest.getHeight(); --h >= 0;)
  142. {
  143. uint8* p = start;
  144. start += lineStride;
  145. for (int i = dest.getWidth(); --i >= 0;)
  146. {
  147. #if JUCE_BIG_ENDIAN
  148. const uint8 oldp3 = p[3];
  149. const uint8 oldp1 = p[1];
  150. p[3] = p[0];
  151. p[0] = oldp1;
  152. p[1] = p[2];
  153. p[2] = oldp3;
  154. #else
  155. const uint8 oldp0 = p[0];
  156. p[0] = p[2];
  157. p[2] = oldp0;
  158. #endif
  159. p += pixelStride;
  160. }
  161. }
  162. dest.releasePixelDataReadWrite (pixels);
  163. }
  164. void callListeners (NSBitmapImageRep* bitmap)
  165. {
  166. Image image (Image::ARGB, [bitmap size].width, [bitmap size].height, false);
  167. drawNSBitmapIntoJuceImage (image, bitmap);
  168. const ScopedLock sl (listenerLock);
  169. for (int i = listeners.size(); --i >= 0;)
  170. {
  171. CameraImageListener* l = (CameraImageListener*) listeners[i];
  172. if (l != 0)
  173. l->imageReceived (image);
  174. }
  175. }
  176. QTCaptureDevice* device;
  177. QTCaptureDeviceInput* input;
  178. QTCaptureSession* session;
  179. QTCaptureMovieFileOutput* fileOutput;
  180. QTCaptureDecompressedVideoOutput* imageOutput;
  181. QTCaptureCallbackDelegate* callbackDelegate;
  182. String openingError;
  183. VoidArray listeners;
  184. CriticalSection listenerLock;
  185. };
  186. END_JUCE_NAMESPACE
  187. @implementation QTCaptureCallbackDelegate
  188. - (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner_
  189. internalDev: (QTCameraDeviceInteral*) d
  190. {
  191. [super init];
  192. owner = owner_;
  193. internal = d;
  194. firstRecordedTime = 0;
  195. return self;
  196. }
  197. - (void) dealloc
  198. {
  199. delete firstRecordedTime;
  200. [super dealloc];
  201. }
  202. - (void) captureOutput: (QTCaptureOutput*) captureOutput
  203. didOutputVideoFrame: (CVImageBufferRef) videoFrame
  204. withSampleBuffer: (QTSampleBuffer*) sampleBuffer
  205. fromConnection: (QTCaptureConnection*) connection
  206. {
  207. const ScopedAutoReleasePool pool;
  208. CIImage* image = [CIImage imageWithCVImageBuffer: videoFrame];
  209. NSBitmapImageRep* bitmap = [[[NSBitmapImageRep alloc] initWithCIImage: image] autorelease];
  210. internal->callListeners (bitmap);
  211. }
  212. - (void) captureOutput: (QTCaptureFileOutput*) captureOutput
  213. didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer
  214. fromConnection: (QTCaptureConnection*) connection
  215. {
  216. if (firstRecordedTime == 0)
  217. firstRecordedTime = new Time (Time::getCurrentTime());
  218. }
  219. @end
  220. BEGIN_JUCE_NAMESPACE
  221. //==============================================================================
  222. class QTCaptureViewerComp : public NSViewComponent
  223. {
  224. public:
  225. QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal)
  226. {
  227. const ScopedAutoReleasePool pool;
  228. captureView = [[QTCaptureView alloc] init];
  229. [captureView setCaptureSession: internal->session];
  230. setSize (640, 480); // xxx need to somehow get the movie size - how?
  231. setView (captureView);
  232. }
  233. ~QTCaptureViewerComp()
  234. {
  235. setView (0);
  236. [captureView setCaptureSession: nil];
  237. [captureView release];
  238. }
  239. QTCaptureView* captureView;
  240. };
  241. //==============================================================================
  242. CameraDevice::CameraDevice (const String& name_, int index)
  243. : name (name_)
  244. {
  245. isRecording = false;
  246. QTCameraDeviceInteral* d = new QTCameraDeviceInteral (this, index);
  247. internal = d;
  248. }
  249. CameraDevice::~CameraDevice()
  250. {
  251. stopRecording();
  252. delete (QTCameraDeviceInteral*) internal;
  253. internal = 0;
  254. }
  255. Component* CameraDevice::createViewerComponent()
  256. {
  257. return new QTCaptureViewerComp (this, (QTCameraDeviceInteral*) internal);
  258. }
  259. const String CameraDevice::getFileExtension()
  260. {
  261. return ".mov";
  262. }
  263. void CameraDevice::startRecordingToFile (const File& file)
  264. {
  265. stopRecording();
  266. QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal;
  267. deleteAndZero (d->callbackDelegate->firstRecordedTime);
  268. file.deleteFile();
  269. [d->fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]];
  270. [d->session addOutput: d->fileOutput error: nil];
  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 < [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. CameraDevice* d = new CameraDevice (getAvailableDevices() [index], index);
  319. if (((QTCameraDeviceInteral*) (d->internal))->openingError.isEmpty())
  320. return d;
  321. delete d;
  322. return 0;
  323. }
  324. #endif