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.

808 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. interface ISampleGrabberCB : public IUnknown
  20. {
  21. virtual STDMETHODIMP SampleCB (double, IMediaSample*) = 0;
  22. virtual STDMETHODIMP BufferCB (double, BYTE*, long) = 0;
  23. };
  24. interface ISampleGrabber : public IUnknown
  25. {
  26. virtual HRESULT STDMETHODCALLTYPE SetOneShot (BOOL) = 0;
  27. virtual HRESULT STDMETHODCALLTYPE SetMediaType (const AM_MEDIA_TYPE*) = 0;
  28. virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType (AM_MEDIA_TYPE*) = 0;
  29. virtual HRESULT STDMETHODCALLTYPE SetBufferSamples (BOOL) = 0;
  30. virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer (long*, long*) = 0;
  31. virtual HRESULT STDMETHODCALLTYPE GetCurrentSample (IMediaSample**) = 0;
  32. virtual HRESULT STDMETHODCALLTYPE SetCallback (ISampleGrabberCB*, long) = 0;
  33. };
  34. static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994, { 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };
  35. static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
  36. static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
  37. static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
  38. struct CameraDevice::Pimpl : public ChangeBroadcaster
  39. {
  40. Pimpl (CameraDevice& ownerToUse, const String&, int index,
  41. int minWidth, int minHeight, int maxWidth, int maxHeight,
  42. bool /*highQuality*/)
  43. : owner (ownerToUse),
  44. isRecording (false),
  45. openedSuccessfully (false),
  46. imageNeedsFlipping (false),
  47. width (0), height (0),
  48. activeUsers (0),
  49. recordNextFrameTime (false),
  50. previewMaxFPS (60)
  51. {
  52. HRESULT hr = captureGraphBuilder.CoCreateInstance (CLSID_CaptureGraphBuilder2);
  53. if (FAILED (hr))
  54. return;
  55. filter = enumerateCameras (nullptr, index);
  56. if (filter == nullptr)
  57. return;
  58. hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
  59. if (FAILED (hr))
  60. return;
  61. hr = captureGraphBuilder->SetFiltergraph (graphBuilder);
  62. if (FAILED (hr))
  63. return;
  64. hr = graphBuilder.QueryInterface (mediaControl);
  65. if (FAILED (hr))
  66. return;
  67. {
  68. ComSmartPtr<IAMStreamConfig> streamConfig;
  69. hr = captureGraphBuilder->FindInterface (&PIN_CATEGORY_CAPTURE, 0, filter,
  70. IID_IAMStreamConfig, (void**) streamConfig.resetAndGetPointerAddress());
  71. if (streamConfig != nullptr)
  72. {
  73. getVideoSizes (streamConfig);
  74. if (! selectVideoSize (streamConfig, minWidth, minHeight, maxWidth, maxHeight))
  75. return;
  76. }
  77. }
  78. hr = graphBuilder->AddFilter (filter, _T("Video Capture"));
  79. if (FAILED (hr))
  80. return;
  81. hr = smartTee.CoCreateInstance (CLSID_SmartTee);
  82. if (FAILED (hr))
  83. return;
  84. hr = graphBuilder->AddFilter (smartTee, _T("Smart Tee"));
  85. if (FAILED (hr))
  86. return;
  87. if (! connectFilters (filter, smartTee))
  88. return;
  89. ComSmartPtr<IBaseFilter> sampleGrabberBase;
  90. hr = sampleGrabberBase.CoCreateInstance (CLSID_SampleGrabber);
  91. if (FAILED (hr))
  92. return;
  93. hr = sampleGrabberBase.QueryInterface (IID_ISampleGrabber, sampleGrabber);
  94. if (FAILED (hr))
  95. return;
  96. {
  97. AM_MEDIA_TYPE mt = { 0 };
  98. mt.majortype = MEDIATYPE_Video;
  99. mt.subtype = MEDIASUBTYPE_RGB24;
  100. mt.formattype = FORMAT_VideoInfo;
  101. sampleGrabber->SetMediaType (&mt);
  102. }
  103. callback = new GrabberCallback (*this);
  104. hr = sampleGrabber->SetCallback (callback, 1);
  105. hr = graphBuilder->AddFilter (sampleGrabberBase, _T("Sample Grabber"));
  106. if (FAILED (hr))
  107. return;
  108. ComSmartPtr<IPin> grabberInputPin;
  109. if (! (getPin (smartTee, PINDIR_OUTPUT, smartTeeCaptureOutputPin, "capture")
  110. && getPin (smartTee, PINDIR_OUTPUT, smartTeePreviewOutputPin, "preview")
  111. && getPin (sampleGrabberBase, PINDIR_INPUT, grabberInputPin)))
  112. return;
  113. hr = graphBuilder->Connect (smartTeePreviewOutputPin, grabberInputPin);
  114. if (FAILED (hr))
  115. return;
  116. AM_MEDIA_TYPE mt = { 0 };
  117. hr = sampleGrabber->GetConnectedMediaType (&mt);
  118. VIDEOINFOHEADER* pVih = (VIDEOINFOHEADER*) (mt.pbFormat);
  119. width = pVih->bmiHeader.biWidth;
  120. height = pVih->bmiHeader.biHeight;
  121. ComSmartPtr<IBaseFilter> nullFilter;
  122. hr = nullFilter.CoCreateInstance (CLSID_NullRenderer);
  123. hr = graphBuilder->AddFilter (nullFilter, _T("Null Renderer"));
  124. if (connectFilters (sampleGrabberBase, nullFilter)
  125. && addGraphToRot())
  126. {
  127. activeImage = Image (Image::RGB, width, height, true);
  128. loadingImage = Image (Image::RGB, width, height, true);
  129. openedSuccessfully = true;
  130. }
  131. }
  132. ~Pimpl()
  133. {
  134. if (mediaControl != nullptr)
  135. mediaControl->Stop();
  136. removeGraphFromRot();
  137. disconnectAnyViewers();
  138. if (sampleGrabber != nullptr)
  139. {
  140. sampleGrabber->SetCallback (nullptr, 0);
  141. sampleGrabber = nullptr;
  142. }
  143. callback = nullptr;
  144. graphBuilder = nullptr;
  145. mediaControl = nullptr;
  146. filter = nullptr;
  147. captureGraphBuilder = nullptr;
  148. smartTee = nullptr;
  149. smartTeePreviewOutputPin = nullptr;
  150. smartTeeCaptureOutputPin = nullptr;
  151. asfWriter = nullptr;
  152. }
  153. bool openedOk() const noexcept { return openedSuccessfully; }
  154. void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse)
  155. {
  156. {
  157. const ScopedLock sl (callbackLock);
  158. jassert (pictureTakenCallbackToUse != nullptr);
  159. if (pictureTakenCallbackToUse == nullptr)
  160. return;
  161. pictureTakenCallback = static_cast<std::function<void (const Image&)>&&> (pictureTakenCallbackToUse);
  162. }
  163. addUser();
  164. }
  165. void startRecordingToFile (const File& file, int quality)
  166. {
  167. addUser();
  168. isRecording = createFileCaptureFilter (file, quality);
  169. }
  170. void stopRecording()
  171. {
  172. if (isRecording)
  173. {
  174. removeFileCaptureFilter();
  175. removeUser();
  176. isRecording = false;
  177. }
  178. }
  179. Time getTimeOfFirstRecordedFrame() const
  180. {
  181. return firstRecordedTime;
  182. }
  183. void notifyImageReceivedIfNeeded (const Image& image)
  184. {
  185. {
  186. const ScopedLock sl (callbackLock);
  187. if (pictureTakenCallback == nullptr)
  188. return;
  189. }
  190. WeakReference<Pimpl> weakRef (this);
  191. MessageManager::callAsync ([weakRef, image]() mutable
  192. {
  193. if (weakRef == nullptr)
  194. return;
  195. if (weakRef->pictureTakenCallback != nullptr)
  196. weakRef->pictureTakenCallback (image);
  197. weakRef->pictureTakenCallback = nullptr;
  198. });
  199. }
  200. void addUser()
  201. {
  202. if (openedSuccessfully && activeUsers++ == 0)
  203. mediaControl->Run();
  204. }
  205. void removeUser()
  206. {
  207. if (openedSuccessfully && --activeUsers == 0)
  208. mediaControl->Stop();
  209. }
  210. void handleFrame (double /*time*/, BYTE* buffer, long /*bufferSize*/)
  211. {
  212. if (recordNextFrameTime)
  213. {
  214. const double defaultCameraLatency = 0.1;
  215. firstRecordedTime = Time::getCurrentTime() - RelativeTime (defaultCameraLatency);
  216. recordNextFrameTime = false;
  217. ComSmartPtr<IPin> pin;
  218. if (getPin (filter, PINDIR_OUTPUT, pin))
  219. {
  220. ComSmartPtr<IAMPushSource> pushSource;
  221. HRESULT hr = pin.QueryInterface (pushSource);
  222. if (pushSource != nullptr)
  223. {
  224. REFERENCE_TIME latency = 0;
  225. hr = pushSource->GetLatency (&latency);
  226. firstRecordedTime = firstRecordedTime - RelativeTime ((double) latency);
  227. }
  228. }
  229. }
  230. {
  231. const int lineStride = width * 3;
  232. const ScopedLock sl (imageSwapLock);
  233. {
  234. loadingImage.duplicateIfShared();
  235. const Image::BitmapData destData (loadingImage, 0, 0, width, height, Image::BitmapData::writeOnly);
  236. for (int i = 0; i < height; ++i)
  237. memcpy (destData.getLinePointer ((height - 1) - i),
  238. buffer + lineStride * i,
  239. lineStride);
  240. }
  241. imageNeedsFlipping = true;
  242. }
  243. notifyImageReceivedIfNeeded (loadingImage);
  244. sendChangeMessage();
  245. }
  246. void drawCurrentImage (Graphics& g, Rectangle<int> area)
  247. {
  248. if (imageNeedsFlipping)
  249. {
  250. const ScopedLock sl (imageSwapLock);
  251. std::swap (loadingImage, activeImage);
  252. imageNeedsFlipping = false;
  253. }
  254. Rectangle<int> centred (RectanglePlacement (RectanglePlacement::centred)
  255. .appliedTo (Rectangle<int> (width, height), area));
  256. RectangleList<int> borders (area);
  257. borders.subtract (centred);
  258. g.setColour (Colours::black);
  259. g.fillRectList (borders);
  260. g.drawImage (activeImage, centred.getX(), centred.getY(),
  261. centred.getWidth(), centred.getHeight(), 0, 0, width, height);
  262. }
  263. bool createFileCaptureFilter (const File& file, int quality)
  264. {
  265. removeFileCaptureFilter();
  266. file.deleteFile();
  267. mediaControl->Stop();
  268. firstRecordedTime = Time();
  269. recordNextFrameTime = true;
  270. previewMaxFPS = 60;
  271. HRESULT hr = asfWriter.CoCreateInstance (CLSID_WMAsfWriter);
  272. if (SUCCEEDED (hr))
  273. {
  274. ComSmartPtr<IFileSinkFilter> fileSink;
  275. hr = asfWriter.QueryInterface (fileSink);
  276. if (SUCCEEDED (hr))
  277. {
  278. hr = fileSink->SetFileName (file.getFullPathName().toWideCharPointer(), 0);
  279. if (SUCCEEDED (hr))
  280. {
  281. hr = graphBuilder->AddFilter (asfWriter, _T("AsfWriter"));
  282. if (SUCCEEDED (hr))
  283. {
  284. ComSmartPtr<IConfigAsfWriter> asfConfig;
  285. hr = asfWriter.QueryInterface (asfConfig);
  286. asfConfig->SetIndexMode (true);
  287. ComSmartPtr<IWMProfileManager> profileManager;
  288. hr = WMCreateProfileManager (profileManager.resetAndGetPointerAddress());
  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 ((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<IPin> asfWriterInputPin;
  320. if (getPin (asfWriter, 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. removeFileCaptureFilter();
  338. if (openedSuccessfully && activeUsers > 0)
  339. mediaControl->Run();
  340. return false;
  341. }
  342. void removeFileCaptureFilter()
  343. {
  344. mediaControl->Stop();
  345. if (asfWriter != nullptr)
  346. {
  347. graphBuilder->RemoveFilter (asfWriter);
  348. asfWriter = nullptr;
  349. }
  350. if (openedSuccessfully && activeUsers > 0)
  351. mediaControl->Run();
  352. previewMaxFPS = 60;
  353. }
  354. static ComSmartPtr<IBaseFilter> enumerateCameras (StringArray* names, const int deviceIndexToOpen)
  355. {
  356. int index = 0;
  357. ComSmartPtr<ICreateDevEnum> pDevEnum;
  358. if (SUCCEEDED (pDevEnum.CoCreateInstance (CLSID_SystemDeviceEnum)))
  359. {
  360. ComSmartPtr<IEnumMoniker> enumerator;
  361. HRESULT hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, enumerator.resetAndGetPointerAddress(), 0);
  362. if (SUCCEEDED (hr) && enumerator != nullptr)
  363. {
  364. ComSmartPtr<IMoniker> moniker;
  365. ULONG fetched;
  366. while (enumerator->Next (1, moniker.resetAndGetPointerAddress(), &fetched) == S_OK)
  367. {
  368. ComSmartPtr<IBaseFilter> captureFilter;
  369. hr = moniker->BindToObject (0, 0, IID_IBaseFilter, (void**) captureFilter.resetAndGetPointerAddress());
  370. if (SUCCEEDED (hr))
  371. {
  372. ComSmartPtr<IPropertyBag> propertyBag;
  373. hr = moniker->BindToStorage (0, 0, IID_IPropertyBag, (void**) propertyBag.resetAndGetPointerAddress());
  374. if (SUCCEEDED (hr))
  375. {
  376. VARIANT var;
  377. var.vt = VT_BSTR;
  378. hr = propertyBag->Read (_T("FriendlyName"), &var, 0);
  379. propertyBag = nullptr;
  380. if (SUCCEEDED (hr))
  381. {
  382. if (names != nullptr)
  383. names->add (var.bstrVal);
  384. if (index == deviceIndexToOpen)
  385. return captureFilter;
  386. ++index;
  387. }
  388. }
  389. }
  390. }
  391. }
  392. }
  393. return nullptr;
  394. }
  395. static StringArray getAvailableDevices()
  396. {
  397. StringArray devs;
  398. enumerateCameras (&devs, -1);
  399. return devs;
  400. }
  401. struct GrabberCallback : public ComBaseClassHelperBase<ISampleGrabberCB>
  402. {
  403. GrabberCallback (Pimpl& p)
  404. : ComBaseClassHelperBase<ISampleGrabberCB> (0), owner (p) {}
  405. JUCE_COMRESULT QueryInterface (REFIID refId, void** result)
  406. {
  407. if (refId == IID_ISampleGrabberCB)
  408. return castToType<ISampleGrabberCB> (result);
  409. return ComBaseClassHelperBase<ISampleGrabberCB>::QueryInterface (refId, result);
  410. }
  411. STDMETHODIMP SampleCB (double, IMediaSample*) { return E_FAIL; }
  412. STDMETHODIMP BufferCB (double time, BYTE* buffer, long bufferSize)
  413. {
  414. owner.handleFrame (time, buffer, bufferSize);
  415. return S_OK;
  416. }
  417. Pimpl& owner;
  418. JUCE_DECLARE_NON_COPYABLE (GrabberCallback)
  419. };
  420. CameraDevice& owner;
  421. ComSmartPtr<GrabberCallback> callback;
  422. bool isRecording, openedSuccessfully;
  423. int width, height;
  424. Time firstRecordedTime;
  425. Array<ViewerComponent*> viewerComps;
  426. ComSmartPtr<ICaptureGraphBuilder2> captureGraphBuilder;
  427. ComSmartPtr<IBaseFilter> filter, smartTee, asfWriter;
  428. ComSmartPtr<IGraphBuilder> graphBuilder;
  429. ComSmartPtr<ISampleGrabber> sampleGrabber;
  430. ComSmartPtr<IMediaControl> mediaControl;
  431. ComSmartPtr<IPin> smartTeePreviewOutputPin, smartTeeCaptureOutputPin;
  432. int activeUsers;
  433. Array<int> widths, heights;
  434. DWORD graphRegistrationID;
  435. CriticalSection imageSwapLock;
  436. bool imageNeedsFlipping;
  437. Image loadingImage, activeImage;
  438. bool recordNextFrameTime;
  439. int previewMaxFPS;
  440. CriticalSection callbackLock;
  441. std::function<void (const Image&)> pictureTakenCallback;
  442. JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
  443. private:
  444. void getVideoSizes (IAMStreamConfig* const streamConfig)
  445. {
  446. widths.clear();
  447. heights.clear();
  448. int count = 0, size = 0;
  449. streamConfig->GetNumberOfCapabilities (&count, &size);
  450. if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
  451. {
  452. for (int i = 0; i < count; ++i)
  453. {
  454. VIDEO_STREAM_CONFIG_CAPS scc;
  455. AM_MEDIA_TYPE* config;
  456. HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
  457. if (SUCCEEDED (hr))
  458. {
  459. const int w = scc.InputSize.cx;
  460. const int h = scc.InputSize.cy;
  461. bool duplicate = false;
  462. for (int j = widths.size(); --j >= 0;)
  463. {
  464. if (w == widths.getUnchecked (j) && h == heights.getUnchecked (j))
  465. {
  466. duplicate = true;
  467. break;
  468. }
  469. }
  470. if (! duplicate)
  471. {
  472. DBG ("Camera capture size: " + String (w) + ", " + String (h));
  473. widths.add (w);
  474. heights.add (h);
  475. }
  476. deleteMediaType (config);
  477. }
  478. }
  479. }
  480. }
  481. bool selectVideoSize (IAMStreamConfig* const streamConfig,
  482. const int minWidth, const int minHeight,
  483. const int maxWidth, const int maxHeight)
  484. {
  485. int count = 0, size = 0, bestArea = 0, bestIndex = -1;
  486. streamConfig->GetNumberOfCapabilities (&count, &size);
  487. if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
  488. {
  489. AM_MEDIA_TYPE* config;
  490. VIDEO_STREAM_CONFIG_CAPS scc;
  491. for (int i = 0; i < count; ++i)
  492. {
  493. HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
  494. if (SUCCEEDED (hr))
  495. {
  496. if (scc.InputSize.cx >= minWidth
  497. && scc.InputSize.cy >= minHeight
  498. && scc.InputSize.cx <= maxWidth
  499. && scc.InputSize.cy <= maxHeight)
  500. {
  501. int area = scc.InputSize.cx * scc.InputSize.cy;
  502. if (area > bestArea)
  503. {
  504. bestIndex = i;
  505. bestArea = area;
  506. }
  507. }
  508. deleteMediaType (config);
  509. }
  510. }
  511. if (bestIndex >= 0)
  512. {
  513. HRESULT hr = streamConfig->GetStreamCaps (bestIndex, &config, (BYTE*) &scc);
  514. hr = streamConfig->SetFormat (config);
  515. deleteMediaType (config);
  516. return SUCCEEDED (hr);
  517. }
  518. }
  519. return false;
  520. }
  521. static bool getPin (IBaseFilter* filter, const PIN_DIRECTION wantedDirection,
  522. ComSmartPtr<IPin>& result, const char* pinName = nullptr)
  523. {
  524. ComSmartPtr<IEnumPins> enumerator;
  525. ComSmartPtr<IPin> pin;
  526. filter->EnumPins (enumerator.resetAndGetPointerAddress());
  527. while (enumerator->Next (1, pin.resetAndGetPointerAddress(), 0) == S_OK)
  528. {
  529. PIN_DIRECTION dir;
  530. pin->QueryDirection (&dir);
  531. if (wantedDirection == dir)
  532. {
  533. PIN_INFO info = { 0 };
  534. pin->QueryPinInfo (&info);
  535. if (pinName == nullptr || String (pinName).equalsIgnoreCase (String (info.achName)))
  536. {
  537. result = pin;
  538. return true;
  539. }
  540. }
  541. }
  542. return false;
  543. }
  544. bool connectFilters (IBaseFilter* const first, IBaseFilter* const second) const
  545. {
  546. ComSmartPtr<IPin> in, out;
  547. return getPin (first, PINDIR_OUTPUT, out)
  548. && getPin (second, PINDIR_INPUT, in)
  549. && SUCCEEDED (graphBuilder->Connect (out, in));
  550. }
  551. bool addGraphToRot()
  552. {
  553. ComSmartPtr<IRunningObjectTable> rot;
  554. if (FAILED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
  555. return false;
  556. ComSmartPtr<IMoniker> moniker;
  557. WCHAR buffer[128];
  558. HRESULT hr = CreateItemMoniker (_T("!"), buffer, moniker.resetAndGetPointerAddress());
  559. if (FAILED (hr))
  560. return false;
  561. graphRegistrationID = 0;
  562. return SUCCEEDED (rot->Register (0, graphBuilder, moniker, &graphRegistrationID));
  563. }
  564. void removeGraphFromRot()
  565. {
  566. ComSmartPtr<IRunningObjectTable> rot;
  567. if (SUCCEEDED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
  568. rot->Revoke (graphRegistrationID);
  569. }
  570. void disconnectAnyViewers();
  571. static void deleteMediaType (AM_MEDIA_TYPE* const pmt)
  572. {
  573. if (pmt->cbFormat != 0)
  574. CoTaskMemFree ((PVOID) pmt->pbFormat);
  575. if (pmt->pUnk != nullptr)
  576. pmt->pUnk->Release();
  577. CoTaskMemFree (pmt);
  578. }
  579. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  580. };
  581. //==============================================================================
  582. struct CameraDevice::ViewerComponent : public Component,
  583. public ChangeListener
  584. {
  585. ViewerComponent (CameraDevice& d)
  586. : owner (d.pimpl.get()), maxFPS (15), lastRepaintTime (0)
  587. {
  588. setOpaque (true);
  589. owner->addChangeListener (this);
  590. owner->addUser();
  591. owner->viewerComps.add (this);
  592. setSize (owner->width, owner->height);
  593. }
  594. ~ViewerComponent()
  595. {
  596. if (owner != nullptr)
  597. {
  598. owner->viewerComps.removeFirstMatchingValue (this);
  599. owner->removeUser();
  600. owner->removeChangeListener (this);
  601. }
  602. }
  603. void ownerDeleted()
  604. {
  605. owner = nullptr;
  606. }
  607. void paint (Graphics& g) override
  608. {
  609. g.setColour (Colours::black);
  610. g.setImageResamplingQuality (Graphics::lowResamplingQuality);
  611. if (owner != nullptr)
  612. owner->drawCurrentImage (g, getLocalBounds());
  613. else
  614. g.fillAll();
  615. }
  616. void changeListenerCallback (ChangeBroadcaster*) override
  617. {
  618. const int64 now = Time::currentTimeMillis();
  619. if (now >= lastRepaintTime + (1000 / maxFPS))
  620. {
  621. lastRepaintTime = now;
  622. repaint();
  623. if (owner != nullptr)
  624. maxFPS = owner->previewMaxFPS;
  625. }
  626. }
  627. private:
  628. Pimpl* owner;
  629. int maxFPS;
  630. int64 lastRepaintTime;
  631. };
  632. void CameraDevice::Pimpl::disconnectAnyViewers()
  633. {
  634. for (int i = viewerComps.size(); --i >= 0;)
  635. viewerComps.getUnchecked(i)->ownerDeleted();
  636. }
  637. String CameraDevice::getFileExtension()
  638. {
  639. return ".wmv";
  640. }