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.

887 lines
31KB

  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. interface ISampleGrabberCB : public IUnknown
  19. {
  20. virtual STDMETHODIMP SampleCB (double, IMediaSample*) = 0;
  21. virtual STDMETHODIMP BufferCB (double, BYTE*, long) = 0;
  22. };
  23. interface ISampleGrabber : public IUnknown
  24. {
  25. virtual HRESULT STDMETHODCALLTYPE SetOneShot (BOOL) = 0;
  26. virtual HRESULT STDMETHODCALLTYPE SetMediaType (const AM_MEDIA_TYPE*) = 0;
  27. virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType (AM_MEDIA_TYPE*) = 0;
  28. virtual HRESULT STDMETHODCALLTYPE SetBufferSamples (BOOL) = 0;
  29. virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer (long*, long*) = 0;
  30. virtual HRESULT STDMETHODCALLTYPE GetCurrentSample (IMediaSample**) = 0;
  31. virtual HRESULT STDMETHODCALLTYPE SetCallback (ISampleGrabberCB*, long) = 0;
  32. };
  33. static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994, { 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };
  34. static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
  35. static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
  36. static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
  37. //==============================================================================
  38. class DShowCameraDeviceInteral : public ChangeBroadcaster
  39. {
  40. public:
  41. DShowCameraDeviceInteral (CameraDevice* const owner_,
  42. const ComSmartPtr <ICaptureGraphBuilder2>& captureGraphBuilder_,
  43. const ComSmartPtr <IBaseFilter>& filter_,
  44. int minWidth, int minHeight,
  45. int maxWidth, int maxHeight)
  46. : owner (owner_),
  47. captureGraphBuilder (captureGraphBuilder_),
  48. filter (filter_),
  49. ok (false),
  50. imageNeedsFlipping (false),
  51. width (0),
  52. height (0),
  53. activeUsers (0),
  54. recordNextFrameTime (false),
  55. previewMaxFPS (60)
  56. {
  57. HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
  58. if (FAILED (hr))
  59. return;
  60. hr = captureGraphBuilder->SetFiltergraph (graphBuilder);
  61. if (FAILED (hr))
  62. return;
  63. hr = graphBuilder.QueryInterface (mediaControl);
  64. if (FAILED (hr))
  65. return;
  66. {
  67. ComSmartPtr <IAMStreamConfig> streamConfig;
  68. hr = captureGraphBuilder->FindInterface (&PIN_CATEGORY_CAPTURE, 0, filter,
  69. IID_IAMStreamConfig, (void**) streamConfig.resetAndGetPointerAddress());
  70. if (streamConfig != nullptr)
  71. {
  72. getVideoSizes (streamConfig);
  73. if (! selectVideoSize (streamConfig, minWidth, minHeight, maxWidth, maxHeight))
  74. return;
  75. }
  76. }
  77. hr = graphBuilder->AddFilter (filter, _T("Video Capture"));
  78. if (FAILED (hr))
  79. return;
  80. hr = smartTee.CoCreateInstance (CLSID_SmartTee);
  81. if (FAILED (hr))
  82. return;
  83. hr = graphBuilder->AddFilter (smartTee, _T("Smart Tee"));
  84. if (FAILED (hr))
  85. return;
  86. if (! connectFilters (filter, smartTee))
  87. return;
  88. ComSmartPtr <IBaseFilter> sampleGrabberBase;
  89. hr = sampleGrabberBase.CoCreateInstance (CLSID_SampleGrabber);
  90. if (FAILED (hr))
  91. return;
  92. hr = sampleGrabberBase.QueryInterface (IID_ISampleGrabber, sampleGrabber);
  93. if (FAILED (hr))
  94. return;
  95. {
  96. AM_MEDIA_TYPE mt = { 0 };
  97. mt.majortype = MEDIATYPE_Video;
  98. mt.subtype = MEDIASUBTYPE_RGB24;
  99. mt.formattype = FORMAT_VideoInfo;
  100. sampleGrabber->SetMediaType (&mt);
  101. }
  102. callback = new GrabberCallback (*this);
  103. hr = sampleGrabber->SetCallback (callback, 1);
  104. hr = graphBuilder->AddFilter (sampleGrabberBase, _T("Sample Grabber"));
  105. if (FAILED (hr))
  106. return;
  107. ComSmartPtr <IPin> grabberInputPin;
  108. if (! (getPin (smartTee, PINDIR_OUTPUT, smartTeeCaptureOutputPin, "capture")
  109. && getPin (smartTee, PINDIR_OUTPUT, smartTeePreviewOutputPin, "preview")
  110. && getPin (sampleGrabberBase, PINDIR_INPUT, grabberInputPin)))
  111. return;
  112. hr = graphBuilder->Connect (smartTeePreviewOutputPin, grabberInputPin);
  113. if (FAILED (hr))
  114. return;
  115. AM_MEDIA_TYPE mt = { 0 };
  116. hr = sampleGrabber->GetConnectedMediaType (&mt);
  117. VIDEOINFOHEADER* pVih = (VIDEOINFOHEADER*) (mt.pbFormat);
  118. width = pVih->bmiHeader.biWidth;
  119. height = pVih->bmiHeader.biHeight;
  120. ComSmartPtr <IBaseFilter> nullFilter;
  121. hr = nullFilter.CoCreateInstance (CLSID_NullRenderer);
  122. hr = graphBuilder->AddFilter (nullFilter, _T("Null Renderer"));
  123. if (connectFilters (sampleGrabberBase, nullFilter)
  124. && addGraphToRot())
  125. {
  126. activeImage = Image (Image::RGB, width, height, true);
  127. loadingImage = Image (Image::RGB, width, height, true);
  128. ok = true;
  129. }
  130. }
  131. ~DShowCameraDeviceInteral()
  132. {
  133. if (mediaControl != nullptr)
  134. mediaControl->Stop();
  135. removeGraphFromRot();
  136. for (int i = viewerComps.size(); --i >= 0;)
  137. viewerComps.getUnchecked(i)->ownerDeleted();
  138. if (sampleGrabber != nullptr)
  139. {
  140. sampleGrabber->SetCallback (nullptr, 0);
  141. sampleGrabber = nullptr;
  142. }
  143. callback = nullptr;
  144. graphBuilder = nullptr;
  145. mediaControl = nullptr;
  146. filter = nullptr;
  147. captureGraphBuilder = nullptr;
  148. smartTee = nullptr;
  149. smartTeePreviewOutputPin = nullptr;
  150. smartTeeCaptureOutputPin = nullptr;
  151. asfWriter = nullptr;
  152. }
  153. void addUser()
  154. {
  155. if (ok && activeUsers++ == 0)
  156. mediaControl->Run();
  157. }
  158. void removeUser()
  159. {
  160. if (ok && --activeUsers == 0)
  161. mediaControl->Stop();
  162. }
  163. int getPreviewMaxFPS() const
  164. {
  165. return previewMaxFPS;
  166. }
  167. void handleFrame (double /*time*/, BYTE* buffer, long /*bufferSize*/)
  168. {
  169. if (recordNextFrameTime)
  170. {
  171. const double defaultCameraLatency = 0.1;
  172. firstRecordedTime = Time::getCurrentTime() - RelativeTime (defaultCameraLatency);
  173. recordNextFrameTime = false;
  174. ComSmartPtr <IPin> pin;
  175. if (getPin (filter, PINDIR_OUTPUT, pin))
  176. {
  177. ComSmartPtr <IAMPushSource> pushSource;
  178. HRESULT hr = pin.QueryInterface (pushSource);
  179. if (pushSource != nullptr)
  180. {
  181. REFERENCE_TIME latency = 0;
  182. hr = pushSource->GetLatency (&latency);
  183. firstRecordedTime = firstRecordedTime - RelativeTime ((double) latency);
  184. }
  185. }
  186. }
  187. {
  188. const int lineStride = width * 3;
  189. const ScopedLock sl (imageSwapLock);
  190. {
  191. const Image::BitmapData destData (loadingImage, 0, 0, width, height, Image::BitmapData::writeOnly);
  192. for (int i = 0; i < height; ++i)
  193. memcpy (destData.getLinePointer ((height - 1) - i),
  194. buffer + lineStride * i,
  195. lineStride);
  196. }
  197. imageNeedsFlipping = true;
  198. }
  199. if (listeners.size() > 0)
  200. callListeners (loadingImage);
  201. sendChangeMessage();
  202. }
  203. void drawCurrentImage (Graphics& g, int x, int y, int w, int h)
  204. {
  205. if (imageNeedsFlipping)
  206. {
  207. const ScopedLock sl (imageSwapLock);
  208. std::swap (loadingImage, activeImage);
  209. imageNeedsFlipping = false;
  210. }
  211. RectanglePlacement rp (RectanglePlacement::centred);
  212. double dx = 0, dy = 0, dw = width, dh = height;
  213. rp.applyTo (dx, dy, dw, dh, x, y, w, h);
  214. const int rx = roundToInt (dx), ry = roundToInt (dy);
  215. const int rw = roundToInt (dw), rh = roundToInt (dh);
  216. {
  217. Graphics::ScopedSaveState ss (g);
  218. g.excludeClipRegion (Rectangle<int> (rx, ry, rw, rh));
  219. g.fillAll (Colours::black);
  220. }
  221. g.drawImage (activeImage, rx, ry, rw, rh, 0, 0, width, height);
  222. }
  223. bool createFileCaptureFilter (const File& file, int quality)
  224. {
  225. removeFileCaptureFilter();
  226. file.deleteFile();
  227. mediaControl->Stop();
  228. firstRecordedTime = Time();
  229. recordNextFrameTime = true;
  230. previewMaxFPS = 60;
  231. HRESULT hr = asfWriter.CoCreateInstance (CLSID_WMAsfWriter);
  232. if (SUCCEEDED (hr))
  233. {
  234. ComSmartPtr <IFileSinkFilter> fileSink;
  235. hr = asfWriter.QueryInterface (fileSink);
  236. if (SUCCEEDED (hr))
  237. {
  238. hr = fileSink->SetFileName (file.getFullPathName().toWideCharPointer(), 0);
  239. if (SUCCEEDED (hr))
  240. {
  241. hr = graphBuilder->AddFilter (asfWriter, _T("AsfWriter"));
  242. if (SUCCEEDED (hr))
  243. {
  244. ComSmartPtr <IConfigAsfWriter> asfConfig;
  245. hr = asfWriter.QueryInterface (asfConfig);
  246. asfConfig->SetIndexMode (true);
  247. ComSmartPtr <IWMProfileManager> profileManager;
  248. hr = WMCreateProfileManager (profileManager.resetAndGetPointerAddress());
  249. // This gibberish is the DirectShow profile for a video-only wmv file.
  250. String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\">"
  251. "<streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\" "
  252. "streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\" "
  253. "bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\">"
  254. "<videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>"
  255. "<wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\" "
  256. "btemporalcompression=\"1\" lsamplesize=\"0\">"
  257. "<videoinfoheader dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"$AVGTIMEPERFRAME\">"
  258. "<rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
  259. "<rctarget left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
  260. "<bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\" "
  261. "bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\" "
  262. "biclrused=\"0\" biclrimportant=\"0\"/>"
  263. "</videoinfoheader>"
  264. "</wmmediatype>"
  265. "</streamconfig>"
  266. "</profile>");
  267. const int fps[] = { 10, 15, 30 };
  268. int maxFramesPerSecond = fps [jlimit (0, numElementsInArray (fps) - 1, quality & 0xff)];
  269. if ((quality & 0xff000000) != 0) // (internal hacky way to pass explicit frame rates for testing)
  270. maxFramesPerSecond = (quality >> 24) & 0xff;
  271. prof = prof.replace ("$WIDTH", String (width))
  272. .replace ("$HEIGHT", String (height))
  273. .replace ("$AVGTIMEPERFRAME", String (10000000 / maxFramesPerSecond));
  274. ComSmartPtr <IWMProfile> currentProfile;
  275. hr = profileManager->LoadProfileByData (prof.toWideCharPointer(), currentProfile.resetAndGetPointerAddress());
  276. hr = asfConfig->ConfigureFilterUsingProfile (currentProfile);
  277. if (SUCCEEDED (hr))
  278. {
  279. ComSmartPtr <IPin> asfWriterInputPin;
  280. if (getPin (asfWriter, PINDIR_INPUT, asfWriterInputPin, "Video Input 01"))
  281. {
  282. hr = graphBuilder->Connect (smartTeeCaptureOutputPin, asfWriterInputPin);
  283. if (SUCCEEDED (hr) && ok && activeUsers > 0
  284. && SUCCEEDED (mediaControl->Run()))
  285. {
  286. previewMaxFPS = (quality < 2) ? 15 : 25; // throttle back the preview comps to try to leave the cpu free for encoding
  287. if ((quality & 0x00ff0000) != 0) // (internal hacky way to pass explicit frame rates for testing)
  288. previewMaxFPS = (quality >> 16) & 0xff;
  289. return true;
  290. }
  291. }
  292. }
  293. }
  294. }
  295. }
  296. }
  297. removeFileCaptureFilter();
  298. if (ok && activeUsers > 0)
  299. mediaControl->Run();
  300. return false;
  301. }
  302. void removeFileCaptureFilter()
  303. {
  304. mediaControl->Stop();
  305. if (asfWriter != nullptr)
  306. {
  307. graphBuilder->RemoveFilter (asfWriter);
  308. asfWriter = nullptr;
  309. }
  310. if (ok && activeUsers > 0)
  311. mediaControl->Run();
  312. previewMaxFPS = 60;
  313. }
  314. //==============================================================================
  315. void addListener (CameraDevice::Listener* listenerToAdd)
  316. {
  317. const ScopedLock sl (listenerLock);
  318. if (listeners.size() == 0)
  319. addUser();
  320. listeners.addIfNotAlreadyThere (listenerToAdd);
  321. }
  322. void removeListener (CameraDevice::Listener* listenerToRemove)
  323. {
  324. const ScopedLock sl (listenerLock);
  325. listeners.removeFirstMatchingValue (listenerToRemove);
  326. if (listeners.size() == 0)
  327. removeUser();
  328. }
  329. void callListeners (const Image& image)
  330. {
  331. const ScopedLock sl (listenerLock);
  332. for (int i = listeners.size(); --i >= 0;)
  333. if (CameraDevice::Listener* const l = listeners[i])
  334. l->imageReceived (image);
  335. }
  336. //==============================================================================
  337. class DShowCaptureViewerComp : public Component,
  338. public ChangeListener
  339. {
  340. public:
  341. DShowCaptureViewerComp (DShowCameraDeviceInteral* const owner_)
  342. : owner (owner_), maxFPS (15), lastRepaintTime (0)
  343. {
  344. setOpaque (true);
  345. owner->addChangeListener (this);
  346. owner->addUser();
  347. owner->viewerComps.add (this);
  348. setSize (owner->width, owner->height);
  349. }
  350. ~DShowCaptureViewerComp()
  351. {
  352. if (owner != nullptr)
  353. {
  354. owner->viewerComps.removeFirstMatchingValue (this);
  355. owner->removeUser();
  356. owner->removeChangeListener (this);
  357. }
  358. }
  359. void ownerDeleted()
  360. {
  361. owner = nullptr;
  362. }
  363. void paint (Graphics& g)
  364. {
  365. g.setColour (Colours::black);
  366. g.setImageResamplingQuality (Graphics::lowResamplingQuality);
  367. if (owner != nullptr)
  368. owner->drawCurrentImage (g, 0, 0, getWidth(), getHeight());
  369. else
  370. g.fillAll (Colours::black);
  371. }
  372. void changeListenerCallback (ChangeBroadcaster*)
  373. {
  374. const int64 now = Time::currentTimeMillis();
  375. if (now >= lastRepaintTime + (1000 / maxFPS))
  376. {
  377. lastRepaintTime = now;
  378. repaint();
  379. if (owner != nullptr)
  380. maxFPS = owner->getPreviewMaxFPS();
  381. }
  382. }
  383. private:
  384. DShowCameraDeviceInteral* owner;
  385. int maxFPS;
  386. int64 lastRepaintTime;
  387. };
  388. //==============================================================================
  389. bool ok;
  390. int width, height;
  391. Time firstRecordedTime;
  392. Array <DShowCaptureViewerComp*> viewerComps;
  393. private:
  394. CameraDevice* const owner;
  395. ComSmartPtr <ICaptureGraphBuilder2> captureGraphBuilder;
  396. ComSmartPtr <IBaseFilter> filter;
  397. ComSmartPtr <IBaseFilter> smartTee;
  398. ComSmartPtr <IGraphBuilder> graphBuilder;
  399. ComSmartPtr <ISampleGrabber> sampleGrabber;
  400. ComSmartPtr <IMediaControl> mediaControl;
  401. ComSmartPtr <IPin> smartTeePreviewOutputPin;
  402. ComSmartPtr <IPin> smartTeeCaptureOutputPin;
  403. ComSmartPtr <IBaseFilter> asfWriter;
  404. int activeUsers;
  405. Array <int> widths, heights;
  406. DWORD graphRegistrationID;
  407. CriticalSection imageSwapLock;
  408. bool imageNeedsFlipping;
  409. Image loadingImage;
  410. Image activeImage;
  411. bool recordNextFrameTime;
  412. int previewMaxFPS;
  413. void getVideoSizes (IAMStreamConfig* const streamConfig)
  414. {
  415. widths.clear();
  416. heights.clear();
  417. int count = 0, size = 0;
  418. streamConfig->GetNumberOfCapabilities (&count, &size);
  419. if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
  420. {
  421. for (int i = 0; i < count; ++i)
  422. {
  423. VIDEO_STREAM_CONFIG_CAPS scc;
  424. AM_MEDIA_TYPE* config;
  425. HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
  426. if (SUCCEEDED (hr))
  427. {
  428. const int w = scc.InputSize.cx;
  429. const int h = scc.InputSize.cy;
  430. bool duplicate = false;
  431. for (int j = widths.size(); --j >= 0;)
  432. {
  433. if (w == widths.getUnchecked (j) && h == heights.getUnchecked (j))
  434. {
  435. duplicate = true;
  436. break;
  437. }
  438. }
  439. if (! duplicate)
  440. {
  441. DBG ("Camera capture size: " + String (w) + ", " + String (h));
  442. widths.add (w);
  443. heights.add (h);
  444. }
  445. deleteMediaType (config);
  446. }
  447. }
  448. }
  449. }
  450. bool selectVideoSize (IAMStreamConfig* const streamConfig,
  451. const int minWidth, const int minHeight,
  452. const int maxWidth, const int maxHeight)
  453. {
  454. int count = 0, size = 0, bestArea = 0, bestIndex = -1;
  455. streamConfig->GetNumberOfCapabilities (&count, &size);
  456. if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
  457. {
  458. AM_MEDIA_TYPE* config;
  459. VIDEO_STREAM_CONFIG_CAPS scc;
  460. for (int i = 0; i < count; ++i)
  461. {
  462. HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
  463. if (SUCCEEDED (hr))
  464. {
  465. if (scc.InputSize.cx >= minWidth
  466. && scc.InputSize.cy >= minHeight
  467. && scc.InputSize.cx <= maxWidth
  468. && scc.InputSize.cy <= maxHeight)
  469. {
  470. int area = scc.InputSize.cx * scc.InputSize.cy;
  471. if (area > bestArea)
  472. {
  473. bestIndex = i;
  474. bestArea = area;
  475. }
  476. }
  477. deleteMediaType (config);
  478. }
  479. }
  480. if (bestIndex >= 0)
  481. {
  482. HRESULT hr = streamConfig->GetStreamCaps (bestIndex, &config, (BYTE*) &scc);
  483. hr = streamConfig->SetFormat (config);
  484. deleteMediaType (config);
  485. return SUCCEEDED (hr);
  486. }
  487. }
  488. return false;
  489. }
  490. static bool getPin (IBaseFilter* filter, const PIN_DIRECTION wantedDirection,
  491. ComSmartPtr<IPin>& result, const char* pinName = nullptr)
  492. {
  493. ComSmartPtr <IEnumPins> enumerator;
  494. ComSmartPtr <IPin> pin;
  495. filter->EnumPins (enumerator.resetAndGetPointerAddress());
  496. while (enumerator->Next (1, pin.resetAndGetPointerAddress(), 0) == S_OK)
  497. {
  498. PIN_DIRECTION dir;
  499. pin->QueryDirection (&dir);
  500. if (wantedDirection == dir)
  501. {
  502. PIN_INFO info = { 0 };
  503. pin->QueryPinInfo (&info);
  504. if (pinName == nullptr || String (pinName).equalsIgnoreCase (String (info.achName)))
  505. {
  506. result = pin;
  507. return true;
  508. }
  509. }
  510. }
  511. return false;
  512. }
  513. bool connectFilters (IBaseFilter* const first, IBaseFilter* const second) const
  514. {
  515. ComSmartPtr <IPin> in, out;
  516. return getPin (first, PINDIR_OUTPUT, out)
  517. && getPin (second, PINDIR_INPUT, in)
  518. && SUCCEEDED (graphBuilder->Connect (out, in));
  519. }
  520. bool addGraphToRot()
  521. {
  522. ComSmartPtr <IRunningObjectTable> rot;
  523. if (FAILED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
  524. return false;
  525. ComSmartPtr <IMoniker> moniker;
  526. WCHAR buffer[128];
  527. HRESULT hr = CreateItemMoniker (_T("!"), buffer, moniker.resetAndGetPointerAddress());
  528. if (FAILED (hr))
  529. return false;
  530. graphRegistrationID = 0;
  531. return SUCCEEDED (rot->Register (0, graphBuilder, moniker, &graphRegistrationID));
  532. }
  533. void removeGraphFromRot()
  534. {
  535. ComSmartPtr <IRunningObjectTable> rot;
  536. if (SUCCEEDED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
  537. rot->Revoke (graphRegistrationID);
  538. }
  539. static void deleteMediaType (AM_MEDIA_TYPE* const pmt)
  540. {
  541. if (pmt->cbFormat != 0)
  542. CoTaskMemFree ((PVOID) pmt->pbFormat);
  543. if (pmt->pUnk != nullptr)
  544. pmt->pUnk->Release();
  545. CoTaskMemFree (pmt);
  546. }
  547. //==============================================================================
  548. class GrabberCallback : public ComBaseClassHelperBase <ISampleGrabberCB>
  549. {
  550. public:
  551. GrabberCallback (DShowCameraDeviceInteral& cam)
  552. : ComBaseClassHelperBase <ISampleGrabberCB> (0), owner (cam) {}
  553. JUCE_COMRESULT QueryInterface (REFIID refId, void** result)
  554. {
  555. if (refId == IID_ISampleGrabberCB)
  556. return castToType <ISampleGrabberCB> (result);
  557. return ComBaseClassHelperBase<ISampleGrabberCB>::QueryInterface (refId, result);
  558. }
  559. STDMETHODIMP SampleCB (double, IMediaSample*) { return E_FAIL; }
  560. STDMETHODIMP BufferCB (double time, BYTE* buffer, long bufferSize)
  561. {
  562. owner.handleFrame (time, buffer, bufferSize);
  563. return S_OK;
  564. }
  565. private:
  566. DShowCameraDeviceInteral& owner;
  567. JUCE_DECLARE_NON_COPYABLE (GrabberCallback)
  568. };
  569. ComSmartPtr <GrabberCallback> callback;
  570. Array <CameraDevice::Listener*> listeners;
  571. CriticalSection listenerLock;
  572. //==============================================================================
  573. JUCE_DECLARE_NON_COPYABLE (DShowCameraDeviceInteral)
  574. };
  575. //==============================================================================
  576. CameraDevice::CameraDevice (const String& nm, int /*index*/)
  577. : name (nm)
  578. {
  579. isRecording = false;
  580. }
  581. CameraDevice::~CameraDevice()
  582. {
  583. stopRecording();
  584. delete static_cast <DShowCameraDeviceInteral*> (internal);
  585. internal = nullptr;
  586. }
  587. Component* CameraDevice::createViewerComponent()
  588. {
  589. return new DShowCameraDeviceInteral::DShowCaptureViewerComp (static_cast <DShowCameraDeviceInteral*> (internal));
  590. }
  591. String CameraDevice::getFileExtension()
  592. {
  593. return ".wmv";
  594. }
  595. void CameraDevice::startRecordingToFile (const File& file, int quality)
  596. {
  597. stopRecording();
  598. DShowCameraDeviceInteral* const d = (DShowCameraDeviceInteral*) internal;
  599. d->addUser();
  600. isRecording = d->createFileCaptureFilter (file, quality);
  601. }
  602. Time CameraDevice::getTimeOfFirstRecordedFrame() const
  603. {
  604. DShowCameraDeviceInteral* const d = (DShowCameraDeviceInteral*) internal;
  605. return d->firstRecordedTime;
  606. }
  607. void CameraDevice::stopRecording()
  608. {
  609. if (isRecording)
  610. {
  611. DShowCameraDeviceInteral* const d = (DShowCameraDeviceInteral*) internal;
  612. d->removeFileCaptureFilter();
  613. d->removeUser();
  614. isRecording = false;
  615. }
  616. }
  617. void CameraDevice::addListener (Listener* listenerToAdd)
  618. {
  619. DShowCameraDeviceInteral* const d = (DShowCameraDeviceInteral*) internal;
  620. if (listenerToAdd != nullptr)
  621. d->addListener (listenerToAdd);
  622. }
  623. void CameraDevice::removeListener (Listener* listenerToRemove)
  624. {
  625. DShowCameraDeviceInteral* const d = (DShowCameraDeviceInteral*) internal;
  626. if (listenerToRemove != nullptr)
  627. d->removeListener (listenerToRemove);
  628. }
  629. //==============================================================================
  630. namespace
  631. {
  632. ComSmartPtr <IBaseFilter> enumerateCameras (StringArray* const names,
  633. const int deviceIndexToOpen,
  634. String& name)
  635. {
  636. int index = 0;
  637. ComSmartPtr <IBaseFilter> result;
  638. ComSmartPtr <ICreateDevEnum> pDevEnum;
  639. HRESULT hr = pDevEnum.CoCreateInstance (CLSID_SystemDeviceEnum);
  640. if (SUCCEEDED (hr))
  641. {
  642. ComSmartPtr <IEnumMoniker> enumerator;
  643. hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, enumerator.resetAndGetPointerAddress(), 0);
  644. if (SUCCEEDED (hr) && enumerator != nullptr)
  645. {
  646. ComSmartPtr <IMoniker> moniker;
  647. ULONG fetched;
  648. while (enumerator->Next (1, moniker.resetAndGetPointerAddress(), &fetched) == S_OK)
  649. {
  650. ComSmartPtr <IBaseFilter> captureFilter;
  651. hr = moniker->BindToObject (0, 0, IID_IBaseFilter, (void**) captureFilter.resetAndGetPointerAddress());
  652. if (SUCCEEDED (hr))
  653. {
  654. ComSmartPtr <IPropertyBag> propertyBag;
  655. hr = moniker->BindToStorage (0, 0, IID_IPropertyBag, (void**) propertyBag.resetAndGetPointerAddress());
  656. if (SUCCEEDED (hr))
  657. {
  658. VARIANT var;
  659. var.vt = VT_BSTR;
  660. hr = propertyBag->Read (_T("FriendlyName"), &var, 0);
  661. propertyBag = nullptr;
  662. if (SUCCEEDED (hr))
  663. {
  664. if (names != nullptr)
  665. names->add (var.bstrVal);
  666. if (index == deviceIndexToOpen)
  667. {
  668. name = var.bstrVal;
  669. result = captureFilter;
  670. break;
  671. }
  672. ++index;
  673. }
  674. }
  675. }
  676. }
  677. }
  678. }
  679. return result;
  680. }
  681. }
  682. StringArray CameraDevice::getAvailableDevices()
  683. {
  684. StringArray devs;
  685. String dummy;
  686. enumerateCameras (&devs, -1, dummy);
  687. return devs;
  688. }
  689. CameraDevice* CameraDevice::openDevice (int index,
  690. int minWidth, int minHeight,
  691. int maxWidth, int maxHeight)
  692. {
  693. ComSmartPtr <ICaptureGraphBuilder2> captureGraphBuilder;
  694. HRESULT hr = captureGraphBuilder.CoCreateInstance (CLSID_CaptureGraphBuilder2);
  695. if (SUCCEEDED (hr))
  696. {
  697. String name;
  698. const ComSmartPtr <IBaseFilter> filter (enumerateCameras (0, index, name));
  699. if (filter != nullptr)
  700. {
  701. ScopedPointer <CameraDevice> cam (new CameraDevice (name, index));
  702. DShowCameraDeviceInteral* const intern
  703. = new DShowCameraDeviceInteral (cam, captureGraphBuilder, filter,
  704. minWidth, minHeight, maxWidth, maxHeight);
  705. cam->internal = intern;
  706. if (intern->ok)
  707. return cam.release();
  708. }
  709. }
  710. return nullptr;
  711. }