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.

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