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.

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