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.

834 lines
29KB

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