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.

842 lines
28KB

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