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.

836 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. if (weakRef->pictureTakenCallback != nullptr)
  193. weakRef->pictureTakenCallback (image);
  194. weakRef->pictureTakenCallback = nullptr;
  195. });
  196. }
  197. void addUser()
  198. {
  199. if (openedSuccessfully && activeUsers++ == 0)
  200. mediaControl->Run();
  201. }
  202. void removeUser()
  203. {
  204. if (openedSuccessfully && --activeUsers == 0)
  205. mediaControl->Stop();
  206. }
  207. void handleFrame (double /*time*/, BYTE* buffer, long /*bufferSize*/)
  208. {
  209. if (recordNextFrameTime)
  210. {
  211. const double defaultCameraLatency = 0.1;
  212. firstRecordedTime = Time::getCurrentTime() - RelativeTime (defaultCameraLatency);
  213. recordNextFrameTime = false;
  214. ComSmartPtr<ComTypes::IPin> pin;
  215. if (getPin (filter, ComTypes::PINDIR_OUTPUT, pin))
  216. {
  217. if (auto pushSource = pin.getInterface<ComTypes::IAMPushSource>())
  218. {
  219. ComTypes::REFERENCE_TIME latency = 0;
  220. pushSource->GetLatency (&latency);
  221. firstRecordedTime = firstRecordedTime - RelativeTime ((double) latency);
  222. }
  223. }
  224. }
  225. {
  226. const int lineStride = width * 3;
  227. const ScopedLock sl (imageSwapLock);
  228. {
  229. loadingImage.duplicateIfShared();
  230. const Image::BitmapData destData (loadingImage, 0, 0, width, height, Image::BitmapData::writeOnly);
  231. for (int i = 0; i < height; ++i)
  232. memcpy (destData.getLinePointer ((height - 1) - i),
  233. buffer + lineStride * i,
  234. (size_t) lineStride);
  235. }
  236. imageNeedsFlipping = true;
  237. }
  238. if (listeners.size() > 0)
  239. callListeners (loadingImage);
  240. notifyPictureTakenIfNeeded (loadingImage);
  241. sendChangeMessage();
  242. }
  243. void drawCurrentImage (Graphics& g, Rectangle<int> area)
  244. {
  245. if (imageNeedsFlipping)
  246. {
  247. const ScopedLock sl (imageSwapLock);
  248. std::swap (loadingImage, activeImage);
  249. imageNeedsFlipping = false;
  250. }
  251. Rectangle<int> centred (RectanglePlacement (RectanglePlacement::centred)
  252. .appliedTo (Rectangle<int> (width, height), area));
  253. RectangleList<int> borders (area);
  254. borders.subtract (centred);
  255. g.setColour (Colours::black);
  256. g.fillRectList (borders);
  257. g.drawImage (activeImage, centred.getX(), centred.getY(),
  258. centred.getWidth(), centred.getHeight(), 0, 0, width, height);
  259. }
  260. bool createFileCaptureFilter (const File& file, int quality)
  261. {
  262. removeFileCaptureFilter();
  263. file.deleteFile();
  264. mediaControl->Stop();
  265. firstRecordedTime = Time();
  266. recordNextFrameTime = true;
  267. previewMaxFPS = 60;
  268. HRESULT hr = asfWriter.CoCreateInstance (ComTypes::CLSID_WMAsfWriter);
  269. if (SUCCEEDED (hr))
  270. {
  271. if (auto fileSink = asfWriter.getInterface<ComTypes::IFileSinkFilter>())
  272. {
  273. hr = fileSink->SetFileName (file.getFullPathName().toWideCharPointer(), nullptr);
  274. if (SUCCEEDED (hr))
  275. {
  276. hr = graphBuilder->AddFilter (asfWriter, _T("AsfWriter"));
  277. if (SUCCEEDED (hr))
  278. {
  279. if (auto asfConfig = asfWriter.getInterface<ComTypes::IConfigAsfWriter>())
  280. {
  281. asfConfig->SetIndexMode (true);
  282. ComSmartPtr<IWMProfileManager> profileManager;
  283. using Fn = HRESULT (*) (IWMProfileManager**);
  284. // This function is available on Windows 2000 and up, but we load it at runtime anyway
  285. // because some versions of MinGW ship with libraries that don't include this symbol.
  286. if (auto* fn = reinterpret_cast<Fn> (wmvcoreLibrary.getFunction ("WMCreateProfileManager")))
  287. hr = fn (profileManager.resetAndGetPointerAddress());
  288. else
  289. jassertfalse;
  290. // This gibberish is the DirectShow profile for a video-only wmv file.
  291. String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\">"
  292. "<streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\" "
  293. "streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\" "
  294. "bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\">"
  295. "<videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>"
  296. "<wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\" "
  297. "btemporalcompression=\"1\" lsamplesize=\"0\">"
  298. "<videoinfoheader dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"$AVGTIMEPERFRAME\">"
  299. "<rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
  300. "<rctarget left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
  301. "<bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\" "
  302. "bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\" "
  303. "biclrused=\"0\" biclrimportant=\"0\"/>"
  304. "</videoinfoheader>"
  305. "</wmmediatype>"
  306. "</streamconfig>"
  307. "</profile>");
  308. const int fps[] = { 10, 15, 30 };
  309. int maxFramesPerSecond = fps[jlimit (0, numElementsInArray (fps) - 1, quality & 0xff)];
  310. if (((uint32_t) quality & 0xff000000) != 0) // (internal hacky way to pass explicit frame rates for testing)
  311. maxFramesPerSecond = (quality >> 24) & 0xff;
  312. prof = prof.replace ("$WIDTH", String (width))
  313. .replace ("$HEIGHT", String (height))
  314. .replace ("$AVGTIMEPERFRAME", String (10000000 / maxFramesPerSecond));
  315. ComSmartPtr<IWMProfile> currentProfile;
  316. hr = profileManager->LoadProfileByData (prof.toWideCharPointer(), currentProfile.resetAndGetPointerAddress());
  317. hr = asfConfig->ConfigureFilterUsingProfile (currentProfile);
  318. if (SUCCEEDED (hr))
  319. {
  320. ComSmartPtr<ComTypes::IPin> asfWriterInputPin;
  321. if (getPin (asfWriter, ComTypes::PINDIR_INPUT, asfWriterInputPin, "Video Input 01"))
  322. {
  323. hr = graphBuilder->Connect (smartTeeCaptureOutputPin, asfWriterInputPin);
  324. if (SUCCEEDED (hr) && openedSuccessfully && activeUsers > 0
  325. && SUCCEEDED (mediaControl->Run()))
  326. {
  327. previewMaxFPS = (quality < 2) ? 15 : 25; // throttle back the preview comps to try to leave the cpu free for encoding
  328. if ((quality & 0x00ff0000) != 0) // (internal hacky way to pass explicit frame rates for testing)
  329. previewMaxFPS = (quality >> 16) & 0xff;
  330. return true;
  331. }
  332. }
  333. }
  334. }
  335. }
  336. }
  337. }
  338. }
  339. removeFileCaptureFilter();
  340. if (openedSuccessfully && activeUsers > 0)
  341. mediaControl->Run();
  342. return false;
  343. }
  344. void removeFileCaptureFilter()
  345. {
  346. mediaControl->Stop();
  347. if (asfWriter != nullptr)
  348. {
  349. graphBuilder->RemoveFilter (asfWriter);
  350. asfWriter = nullptr;
  351. }
  352. if (openedSuccessfully && activeUsers > 0)
  353. mediaControl->Run();
  354. previewMaxFPS = 60;
  355. }
  356. static ComSmartPtr<ComTypes::IBaseFilter> enumerateCameras (StringArray* names, const int deviceIndexToOpen)
  357. {
  358. int index = 0;
  359. ComSmartPtr<ComTypes::ICreateDevEnum> pDevEnum;
  360. struct Deleter
  361. {
  362. void operator() (IUnknown* ptr) const noexcept { ptr->Release(); }
  363. };
  364. using ContextPtr = std::unique_ptr<IBindCtx, Deleter>;
  365. if (SUCCEEDED (pDevEnum.CoCreateInstance (ComTypes::CLSID_SystemDeviceEnum)))
  366. {
  367. ComSmartPtr<IEnumMoniker> enumerator;
  368. HRESULT hr = pDevEnum->CreateClassEnumerator (ComTypes::CLSID_VideoInputDeviceCategory, enumerator.resetAndGetPointerAddress(), 0);
  369. if (SUCCEEDED (hr) && enumerator != nullptr)
  370. {
  371. ComSmartPtr<IMoniker> moniker;
  372. ULONG fetched;
  373. while (enumerator->Next (1, moniker.resetAndGetPointerAddress(), &fetched) == S_OK)
  374. {
  375. auto context = []
  376. {
  377. IBindCtx* ptr = nullptr;
  378. [[maybe_unused]] const auto result = CreateBindCtx (0, &ptr);
  379. return ContextPtr (ptr);
  380. }();
  381. ComSmartPtr<ComTypes::IBaseFilter> captureFilter;
  382. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  383. hr = moniker->BindToObject (context.get(), nullptr, __uuidof (ComTypes::IBaseFilter), (void**) captureFilter.resetAndGetPointerAddress());
  384. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  385. if (SUCCEEDED (hr))
  386. {
  387. ComSmartPtr<IPropertyBag> propertyBag;
  388. hr = moniker->BindToStorage (context.get(), nullptr, IID_IPropertyBag, (void**) propertyBag.resetAndGetPointerAddress());
  389. if (SUCCEEDED (hr))
  390. {
  391. VARIANT var;
  392. var.vt = VT_BSTR;
  393. hr = propertyBag->Read (_T("FriendlyName"), &var, nullptr);
  394. propertyBag = nullptr;
  395. if (SUCCEEDED (hr))
  396. {
  397. if (names != nullptr)
  398. names->add (var.bstrVal);
  399. if (index == deviceIndexToOpen)
  400. return captureFilter;
  401. ++index;
  402. }
  403. }
  404. }
  405. }
  406. }
  407. }
  408. return nullptr;
  409. }
  410. static StringArray getAvailableDevices()
  411. {
  412. StringArray devs;
  413. enumerateCameras (&devs, -1);
  414. return devs;
  415. }
  416. struct GrabberCallback : public ComBaseClassHelperBase<ComTypes::ISampleGrabberCB>
  417. {
  418. GrabberCallback (Pimpl& p)
  419. : ComBaseClassHelperBase (0), owner (p) {}
  420. JUCE_COMRESULT QueryInterface (REFIID refId, void** result)
  421. {
  422. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
  423. if (refId == __uuidof (ComTypes::ISampleGrabberCB))
  424. return castToType<ComTypes::ISampleGrabberCB> (result);
  425. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  426. return ComBaseClassHelperBase<ComTypes::ISampleGrabberCB>::QueryInterface (refId, result);
  427. }
  428. JUCE_COMRESULT SampleCB (double, ComTypes::IMediaSample*) { return E_FAIL; }
  429. JUCE_COMRESULT BufferCB (double time, BYTE* buffer, long bufferSize)
  430. {
  431. owner.handleFrame (time, buffer, bufferSize);
  432. return S_OK;
  433. }
  434. Pimpl& owner;
  435. JUCE_DECLARE_NON_COPYABLE (GrabberCallback)
  436. };
  437. DynamicLibrary wmvcoreLibrary { "wmvcore" };
  438. CameraDevice& owner;
  439. ComSmartPtr<GrabberCallback> callback;
  440. CriticalSection listenerLock;
  441. ListenerList<Listener> listeners;
  442. CriticalSection pictureTakenCallbackLock;
  443. std::function<void (const Image&)> pictureTakenCallback;
  444. bool isRecording = false, openedSuccessfully = false;
  445. int width = 0, height = 0;
  446. Time firstRecordedTime;
  447. Array<ViewerComponent*> viewerComps;
  448. ComSmartPtr<ComTypes::ICaptureGraphBuilder2> captureGraphBuilder;
  449. ComSmartPtr<ComTypes::IBaseFilter> filter, smartTee, asfWriter;
  450. ComSmartPtr<ComTypes::IGraphBuilder> graphBuilder;
  451. ComSmartPtr<ComTypes::ISampleGrabber> sampleGrabber;
  452. ComSmartPtr<ComTypes::IMediaControl> mediaControl;
  453. ComSmartPtr<ComTypes::IPin> smartTeePreviewOutputPin, smartTeeCaptureOutputPin;
  454. int activeUsers = 0;
  455. Array<int> widths, heights;
  456. DWORD graphRegistrationID;
  457. CriticalSection imageSwapLock;
  458. bool imageNeedsFlipping = false;
  459. Image loadingImage, activeImage;
  460. bool recordNextFrameTime = false;
  461. int previewMaxFPS = 60;
  462. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  463. private:
  464. void getVideoSizes (ComTypes::IAMStreamConfig* const streamConfig)
  465. {
  466. widths.clear();
  467. heights.clear();
  468. int count = 0, size = 0;
  469. streamConfig->GetNumberOfCapabilities (&count, &size);
  470. if (size == (int) sizeof (ComTypes::VIDEO_STREAM_CONFIG_CAPS))
  471. {
  472. for (int i = 0; i < count; ++i)
  473. {
  474. ComTypes::VIDEO_STREAM_CONFIG_CAPS scc;
  475. ComTypes::AM_MEDIA_TYPE* config;
  476. HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
  477. if (SUCCEEDED (hr))
  478. {
  479. const int w = scc.InputSize.cx;
  480. const int h = scc.InputSize.cy;
  481. bool duplicate = false;
  482. for (int j = widths.size(); --j >= 0;)
  483. {
  484. if (w == widths.getUnchecked (j) && h == heights.getUnchecked (j))
  485. {
  486. duplicate = true;
  487. break;
  488. }
  489. }
  490. if (! duplicate)
  491. {
  492. widths.add (w);
  493. heights.add (h);
  494. }
  495. deleteMediaType (config);
  496. }
  497. }
  498. }
  499. }
  500. bool selectVideoSize (ComTypes::IAMStreamConfig* const streamConfig,
  501. const int minWidth, const int minHeight,
  502. const int maxWidth, const int maxHeight)
  503. {
  504. int count = 0, size = 0, bestArea = 0, bestIndex = -1;
  505. streamConfig->GetNumberOfCapabilities (&count, &size);
  506. if (size == (int) sizeof (ComTypes::VIDEO_STREAM_CONFIG_CAPS))
  507. {
  508. ComTypes::AM_MEDIA_TYPE* config;
  509. ComTypes::VIDEO_STREAM_CONFIG_CAPS scc;
  510. for (int i = 0; i < count; ++i)
  511. {
  512. HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
  513. if (SUCCEEDED (hr))
  514. {
  515. if (scc.InputSize.cx >= minWidth
  516. && scc.InputSize.cy >= minHeight
  517. && scc.InputSize.cx <= maxWidth
  518. && scc.InputSize.cy <= maxHeight)
  519. {
  520. int area = scc.InputSize.cx * scc.InputSize.cy;
  521. if (area > bestArea)
  522. {
  523. bestIndex = i;
  524. bestArea = area;
  525. }
  526. }
  527. deleteMediaType (config);
  528. }
  529. }
  530. if (bestIndex >= 0)
  531. {
  532. HRESULT hr = streamConfig->GetStreamCaps (bestIndex, &config, (BYTE*) &scc);
  533. hr = streamConfig->SetFormat (config);
  534. deleteMediaType (config);
  535. return SUCCEEDED (hr);
  536. }
  537. }
  538. return false;
  539. }
  540. static bool getPin (ComTypes::IBaseFilter* filter, const ComTypes::PIN_DIRECTION wantedDirection,
  541. ComSmartPtr<ComTypes::IPin>& result, const char* pinName = nullptr)
  542. {
  543. ComSmartPtr<ComTypes::IEnumPins> enumerator;
  544. ComSmartPtr<ComTypes::IPin> pin;
  545. filter->EnumPins (enumerator.resetAndGetPointerAddress());
  546. while (enumerator->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
  547. {
  548. ComTypes::PIN_DIRECTION dir;
  549. pin->QueryDirection (&dir);
  550. if (wantedDirection == dir)
  551. {
  552. ComTypes::PIN_INFO info = {};
  553. pin->QueryPinInfo (&info);
  554. if (pinName == nullptr || String (pinName).equalsIgnoreCase (String (info.achName)))
  555. {
  556. result = pin;
  557. return true;
  558. }
  559. }
  560. }
  561. return false;
  562. }
  563. bool connectFilters (ComTypes::IBaseFilter* const first, ComTypes::IBaseFilter* const second) const
  564. {
  565. ComSmartPtr<ComTypes::IPin> in, out;
  566. return getPin (first, ComTypes::PINDIR_OUTPUT, out)
  567. && getPin (second, ComTypes::PINDIR_INPUT, in)
  568. && SUCCEEDED (graphBuilder->Connect (out, in));
  569. }
  570. bool addGraphToRot()
  571. {
  572. ComSmartPtr<IRunningObjectTable> rot;
  573. if (FAILED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
  574. return false;
  575. ComSmartPtr<IMoniker> moniker;
  576. WCHAR buffer[128]{};
  577. HRESULT hr = CreateItemMoniker (_T("!"), buffer, moniker.resetAndGetPointerAddress());
  578. if (FAILED (hr))
  579. return false;
  580. graphRegistrationID = 0;
  581. return SUCCEEDED (rot->Register (0, graphBuilder, moniker, &graphRegistrationID));
  582. }
  583. void removeGraphFromRot()
  584. {
  585. ComSmartPtr<IRunningObjectTable> rot;
  586. if (SUCCEEDED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
  587. rot->Revoke (graphRegistrationID);
  588. }
  589. void disconnectAnyViewers();
  590. static void deleteMediaType (ComTypes::AM_MEDIA_TYPE* const pmt)
  591. {
  592. if (pmt->cbFormat != 0)
  593. CoTaskMemFree ((PVOID) pmt->pbFormat);
  594. if (pmt->pUnk != nullptr)
  595. pmt->pUnk->Release();
  596. CoTaskMemFree (pmt);
  597. }
  598. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  599. };
  600. //==============================================================================
  601. struct CameraDevice::ViewerComponent : public Component,
  602. public ChangeListener
  603. {
  604. ViewerComponent (CameraDevice& d)
  605. : owner (d.pimpl.get()), maxFPS (15), lastRepaintTime (0)
  606. {
  607. setOpaque (true);
  608. owner->addChangeListener (this);
  609. owner->addUser();
  610. owner->viewerComps.add (this);
  611. setSize (owner->width, owner->height);
  612. }
  613. ~ViewerComponent() override
  614. {
  615. if (owner != nullptr)
  616. {
  617. owner->viewerComps.removeFirstMatchingValue (this);
  618. owner->removeUser();
  619. owner->removeChangeListener (this);
  620. }
  621. }
  622. void ownerDeleted()
  623. {
  624. owner = nullptr;
  625. }
  626. void paint (Graphics& g) override
  627. {
  628. g.setColour (Colours::black);
  629. g.setImageResamplingQuality (Graphics::lowResamplingQuality);
  630. if (owner != nullptr)
  631. owner->drawCurrentImage (g, getLocalBounds());
  632. else
  633. g.fillAll();
  634. }
  635. void changeListenerCallback (ChangeBroadcaster*) override
  636. {
  637. const int64 now = Time::currentTimeMillis();
  638. if (now >= lastRepaintTime + (1000 / maxFPS))
  639. {
  640. lastRepaintTime = now;
  641. repaint();
  642. if (owner != nullptr)
  643. maxFPS = owner->previewMaxFPS;
  644. }
  645. }
  646. private:
  647. Pimpl* owner;
  648. int maxFPS;
  649. int64 lastRepaintTime;
  650. };
  651. void CameraDevice::Pimpl::disconnectAnyViewers()
  652. {
  653. for (int i = viewerComps.size(); --i >= 0;)
  654. viewerComps.getUnchecked(i)->ownerDeleted();
  655. }
  656. String CameraDevice::getFileExtension()
  657. {
  658. return ".wmv";
  659. }