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.

886 lines
30KB

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