6#include <bnb/utils/defs.hpp>
28#pragma comment(lib,"Mfplat.lib")
29#pragma comment(lib,"Mf.lib")
30#pragma comment(lib,"Mfreadwrite.lib")
31#pragma comment(lib,"mfuuid.lib")
32#pragma comment(lib,"shlwapi.lib")
34using Microsoft::WRL::ComPtr;
36#include "mfcaptureengine.h"
39template <
class T>
void SafeRelease(T** ppT)
48enum VideoPixelFormat {
49 PIXEL_FORMAT_UNKNOWN = 0,
58PIXEL_FORMAT_I420A = 4,
68PIXEL_FORMAT_ARGB = 10,
69PIXEL_FORMAT_XRGB = 11,
70PIXEL_FORMAT_RGB24 = 12,
73PIXEL_FORMAT_MJPEG = 14,
79PIXEL_FORMAT_YUV420P9 = 16,
80PIXEL_FORMAT_YUV420P10 = 17,
81PIXEL_FORMAT_YUV422P9 = 18,
82PIXEL_FORMAT_YUV422P10 = 19,
83PIXEL_FORMAT_YUV444P9 = 20,
84PIXEL_FORMAT_YUV444P10 = 21,
85PIXEL_FORMAT_YUV420P12 = 22,
86PIXEL_FORMAT_YUV422P12 = 23,
87PIXEL_FORMAT_YUV444P12 = 24,
92PIXEL_FORMAT_ABGR = 27,
93PIXEL_FORMAT_XBGR = 28,
95PIXEL_FORMAT_P016LE = 29,
102PIXEL_FORMAT_BGRA = 32,
110static VideoPixelFormat
const kSupportedCapturePixelFormats[] = {
111 PIXEL_FORMAT_NV12, PIXEL_FORMAT_I420, PIXEL_FORMAT_YV12,
112 PIXEL_FORMAT_NV21, PIXEL_FORMAT_UYVY, PIXEL_FORMAT_YUY2,
113 PIXEL_FORMAT_RGB24, PIXEL_FORMAT_ARGB, PIXEL_FORMAT_MJPEG,
121 void SetWidthAndHeight(
int w,
int h) {
125 int width()
const {
return width_; }
126 int height()
const {
return height_; }
132struct VideoCaptureFormat {
133 gfx::Size frame_size;
134 float frame_rate = 0;
135 VideoPixelFormat pixel_format = PIXEL_FORMAT_UNKNOWN;
138HRESULT CreateCaptureEngine(IMFCaptureEngine** engine) {
139 ComPtr<IMFCaptureEngineClassFactory> capture_engine_class_factory;
140 HRESULT hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory,
nullptr,
141 CLSCTX_INPROC_SERVER,
142 IID_PPV_ARGS(&capture_engine_class_factory));
146 return capture_engine_class_factory->CreateInstance(CLSID_MFCaptureEngine,
147 IID_PPV_ARGS(engine));
150HRESULT CopyAttribute(IMFAttributes* source_attributes,
151 IMFAttributes* destination_attributes,
154 PropVariantInit(&var);
155 HRESULT hr = source_attributes->GetItem(key, &var);
159 hr = destination_attributes->SetItem(key, var);
160 PropVariantClear(&var);
164HRESULT CreateVideoDeviceSource(IMFMediaSource** ppSource,
size_t index)
170 IMFMediaSource* pSource = NULL;
171 IMFAttributes* pAttributes = NULL;
172 IMFActivate** ppDevices = NULL;
175 HRESULT hr = MFCreateAttributes(&pAttributes, 1);
182 hr = pAttributes->SetGUID(
183 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
184 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
192 hr = MFEnumDeviceSources(pAttributes, &ppDevices, &count);
198 if (count == 0 || index >= count)
205 hr = ppDevices[index]->ActivateObject(IID_PPV_ARGS(&pSource));
212 (*ppSource)->AddRef();
215 SafeRelease(&pAttributes);
217 for (DWORD i = 0; i < count; i++)
219 SafeRelease(&ppDevices[i]);
221 CoTaskMemFree(ppDevices);
222 SafeRelease(&pSource);
227bool GetFrameSizeFromMediaType(IMFMediaType* type, gfx::Size* frame_size) {
228 UINT32 width32, height32;
229 if (FAILED(MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width32, &height32)))
231 frame_size->SetWidthAndHeight(width32, height32);
235struct MediaFormatConfiguration {
236 GUID mf_source_media_subtype;
237 GUID mf_sink_media_subtype;
238 VideoPixelFormat pixel_format;
241bool GetMediaFormatConfigurationFromMFSourceMediaSubtype(
242 const GUID& mf_source_media_subtype,
243 MediaFormatConfiguration* media_format_configuration) {
244 static const MediaFormatConfiguration kMediaFormatConfigurationMap[] = {
251 {MFVideoFormat_I420, MFVideoFormat_I420, PIXEL_FORMAT_I420},
252 {MFVideoFormat_YUY2, MFVideoFormat_I420, PIXEL_FORMAT_I420},
253 {MFVideoFormat_UYVY, MFVideoFormat_I420, PIXEL_FORMAT_I420},
254 {MFVideoFormat_RGB24, MFVideoFormat_I420, PIXEL_FORMAT_I420},
255 {MFVideoFormat_RGB32, MFVideoFormat_I420, PIXEL_FORMAT_I420},
256 {MFVideoFormat_ARGB32, MFVideoFormat_I420, PIXEL_FORMAT_I420},
257 {MFVideoFormat_MJPG, MFVideoFormat_I420, PIXEL_FORMAT_I420},
258 {MFVideoFormat_NV12, MFVideoFormat_NV12, PIXEL_FORMAT_NV12},
259 {MFVideoFormat_YV12, MFVideoFormat_I420, PIXEL_FORMAT_I420},
272 {GUID_ContainerFormatJpeg, GUID_ContainerFormatJpeg, PIXEL_FORMAT_MJPEG} };
274 for (
const auto& kMediaFormatConfiguration : kMediaFormatConfigurationMap) {
275 if (IsEqualGUID(kMediaFormatConfiguration.mf_source_media_subtype,
276 mf_source_media_subtype)) {
277 *media_format_configuration = kMediaFormatConfiguration;
286bool GetPixelFormatFromMFSourceMediaSubtype(
const GUID& mf_source_media_subtype, VideoPixelFormat* pixel_format) {
287 MediaFormatConfiguration media_format_configuration;
288 if (!GetMediaFormatConfigurationFromMFSourceMediaSubtype(
289 mf_source_media_subtype, &media_format_configuration))
291 *pixel_format = media_format_configuration.pixel_format;
298HRESULT GetMFSinkMediaSubtype(IMFMediaType* source_media_type,
299 GUID* mf_sink_media_subtype,
302 HRESULT hr = source_media_type->GetGUID(MF_MT_SUBTYPE, &source_subtype);
305 MediaFormatConfiguration media_format_configuration;
306 if (!GetMediaFormatConfigurationFromMFSourceMediaSubtype(
307 source_subtype, &media_format_configuration))
309 *mf_sink_media_subtype = media_format_configuration.mf_sink_media_subtype;
311 IsEqualGUID(media_format_configuration.mf_sink_media_subtype, source_subtype);
315HRESULT ConvertToVideoSinkMediaType(IMFMediaType* source_media_type,
316 IMFMediaType* sink_media_type, UINT32 frame_rate) {
317 HRESULT hr = sink_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
321 bool passthrough =
false;
322 GUID mf_sink_media_subtype;
323 hr = GetMFSinkMediaSubtype(source_media_type, &mf_sink_media_subtype,
328 hr = sink_media_type->SetGUID(MF_MT_SUBTYPE, mf_sink_media_subtype);
334 hr = CopyAttribute(source_media_type, sink_media_type, MF_MT_FRAME_SIZE);
339 hr = MFSetAttributeRatio(sink_media_type, MF_MT_FRAME_RATE, frame_rate, 1);
344 hr = CopyAttribute(source_media_type, sink_media_type,
345 MF_MT_PIXEL_ASPECT_RATIO);
349 return CopyAttribute(source_media_type, sink_media_type,
350 MF_MT_INTERLACE_MODE);
354struct CapabilityWin {
355 CapabilityWin(
int media_type_index,
const VideoCaptureFormat& format)
356 : media_type_index(media_type_index),
357 supported_format(format),
362 CapabilityWin(
int media_type_index,
363 const VideoCaptureFormat& format,
364 const BITMAPINFOHEADER& info_header)
365 : media_type_index(media_type_index),
366 supported_format(format),
367 info_header(info_header),
371 CapabilityWin(
int media_type_index,
372 const VideoCaptureFormat& format,
374 : media_type_index(media_type_index),
375 supported_format(format),
377 stream_index(stream_index) {}
379 const int media_type_index;
380 const VideoCaptureFormat supported_format;
383 const BITMAPINFOHEADER info_header;
386 const int stream_index;
389typedef std::list<CapabilityWin> CapabilityList;
393bool ComparePixelFormatPreference(
394 const VideoPixelFormat& lhs,
395 const VideoPixelFormat& rhs) {
396 auto* format_lhs = std::find(
397 kSupportedCapturePixelFormats,
398 kSupportedCapturePixelFormats + std::size(kSupportedCapturePixelFormats),
400 auto* format_rhs = std::find(
401 kSupportedCapturePixelFormats,
402 kSupportedCapturePixelFormats + std::size(kSupportedCapturePixelFormats),
404 return format_lhs < format_rhs;
410bool CompareCapability(
const VideoCaptureFormat& requested,
411 const VideoCaptureFormat& lhs,
412 const VideoCaptureFormat& rhs) {
416 const bool use_requested =
417 (requested.pixel_format == PIXEL_FORMAT_Y16);
418 if (use_requested && lhs.pixel_format != rhs.pixel_format) {
419 if (lhs.pixel_format == requested.pixel_format)
421 if (rhs.pixel_format == requested.pixel_format)
424 const int diff_height_lhs =
425 std::abs(lhs.frame_size.height() - requested.frame_size.height());
426 const int diff_height_rhs =
427 std::abs(rhs.frame_size.height() - requested.frame_size.height());
428 if (diff_height_lhs != diff_height_rhs)
429 return diff_height_lhs < diff_height_rhs;
431 const int diff_width_lhs =
432 std::abs(lhs.frame_size.width() - requested.frame_size.width());
433 const int diff_width_rhs =
434 std::abs(rhs.frame_size.width() - requested.frame_size.width());
435 if (diff_width_lhs != diff_width_rhs)
436 return diff_width_lhs < diff_width_rhs;
438 const float diff_fps_lhs = std::fabs(lhs.frame_rate - requested.frame_rate);
439 const float diff_fps_rhs = std::fabs(rhs.frame_rate - requested.frame_rate);
440 if (diff_fps_lhs != diff_fps_rhs)
441 return diff_fps_lhs < diff_fps_rhs;
443 return ComparePixelFormatPreference(lhs.pixel_format,
447const CapabilityWin& GetBestMatchedCapability(
448 const VideoCaptureFormat& requested,
449 const CapabilityList& capabilities) {
451 const CapabilityWin* best_match = &(*capabilities.begin());
452 for (
const CapabilityWin& capability : capabilities) {
453 if (CompareCapability(requested, capability.supported_format,
454 best_match->supported_format)) {
455 best_match = &capability;
466class ScopedBufferLock {
468 explicit ScopedBufferLock(ComPtr<IMFMediaBuffer> buffer)
469 : buffer_(std::move(buffer)) {
470 if (FAILED(buffer_.As(&buffer_2d_))) {
476 if (Lock2DSize() || Lock2D()) {
479 buffer_2d_->Unlock2D();
482 buffer_2d_ =
nullptr;
488 bool IsContiguous() {
491 SUCCEEDED(buffer_2d_->IsContiguousFormat(&is_contiguous)) &&
493 (length_ || SUCCEEDED(buffer_2d_->GetContiguousLength(&length_)));
497 ComPtr<IMF2DBuffer2> buffer_2d_2;
498 if (FAILED(buffer_.As(&buffer_2d_2)))
501 return SUCCEEDED(buffer_2d_2->Lock2DSize(MF2DBuffer_LockFlags_Read, &data_,
502 &pitch_, &data_start, &length_));
505 bool Lock2D() {
return SUCCEEDED(buffer_2d_->Lock2D(&data_, &pitch_)); }
508 DWORD max_length = 0;
509 buffer_->Lock(&data_, &max_length, &length_);
512 ~ScopedBufferLock() {
514 buffer_2d_->Unlock2D();
519 ScopedBufferLock(
const ScopedBufferLock&) =
delete;
520 ScopedBufferLock& operator=(
const ScopedBufferLock&) =
delete;
522 BYTE* data()
const {
return data_; }
523 DWORD length()
const {
return length_; }
524 LONG pitch()
const {
return pitch_; }
527 ComPtr<IMFMediaBuffer> buffer_;
528 ComPtr<IMF2DBuffer> buffer_2d_;
529 BYTE* data_ =
nullptr;
535class MFVideoCallback final
537 public IMFCaptureEngineOnSampleCallback,
538 public IMFCaptureEngineOnEventCallback {
540 MFVideoCallback(T* observer) : observer_(observer) {}
542 IFACEMETHODIMP QueryInterface(REFIID riid,
void**
object)
override {
543 HRESULT hr = E_NOINTERFACE;
544 if (IsEqualGUID(riid, IID_IUnknown)) {
548 else if (IsEqualGUID(riid, IID_IMFCaptureEngineOnSampleCallback)) {
549 *
object =
static_cast<IMFCaptureEngineOnSampleCallback*
>(
this);
552 else if (IsEqualGUID(riid, IID_IMFCaptureEngineOnEventCallback)) {
553 *
object =
static_cast<IMFCaptureEngineOnEventCallback*
>(
this);
562 IFACEMETHODIMP_(ULONG) AddRef()
override {
563 return InterlockedIncrement(&m_cRef);
566 IFACEMETHODIMP_(ULONG) Release()
override {
567 ULONG l = InterlockedDecrement(&m_cRef);
568 if (0 == l)
delete this;
572 IFACEMETHODIMP OnEvent(IMFMediaEvent* media_event)
override {
577 GUID capture_event_guid = GUID_NULL;
579 std::lock_guard<std::mutex> guard(lock_);
580 observer_->OnEvent(media_event);
581 if (HRESULT hr = media_event->GetExtendedType(&capture_event_guid); FAILED(hr)) {
588 IFACEMETHODIMP OnSample(IMFSample* sample)
override {
589 std::lock_guard<std::mutex> guard(lock_);
595 observer_->OnFrameDropped();
607 sample->GetBufferCount(&count);
609 for (DWORD i = 0; i < count; ++i) {
610 ComPtr<IMFMediaBuffer> buffer;
611 sample->GetBufferByIndex(i, &buffer);
613 auto locked_buffer = std::make_shared<ScopedBufferLock>(buffer);
614 if (locked_buffer->data()) {
615 observer_->OnIncomingCapturedData(std::move(locked_buffer));
618 observer_->OnFrameDropped();
622 observer_->OnFrameDropped();
629 std::lock_guard<std::mutex> guard(lock_);
635 ~MFVideoCallback() {}
640class VideoCaptureDeviceMFWin {
642 using callback_t = std::function<void(std::shared_ptr<ScopedBufferLock> lock)>;
643 VideoCaptureDeviceMFWin(ComPtr<IMFMediaSource>& source)
645 camera_stop_ = ::CreateEvent(NULL, TRUE, FALSE,
"CameraStop");
646 capture_error_ = ::CreateEvent(NULL, TRUE, FALSE,
"CaptureError");
647 capture_initialize_ = ::CreateEvent(NULL, TRUE, FALSE,
"CaptureInitialize");
650 void SetCallback(callback_t callback) {
651 std::lock_guard<std::mutex> lock(mutex_);
652 callback_ = callback;
655 ~VideoCaptureDeviceMFWin() {
656 ::CloseHandle(camera_stop_);
657 ::CloseHandle(capture_error_);
658 ::CloseHandle(capture_initialize_);
661 void OnEvent(IMFMediaEvent* media_event) {
663 GUID capture_event_guid = GUID_NULL;
665 media_event->GetStatus(&hr);
666 media_event->GetExtendedType(&capture_event_guid);
667 if (IsEqualGUID(capture_event_guid, MF_CAPTURE_ENGINE_ERROR) || FAILED(hr)) {
668 ::SetEvent(capture_error_);
670 else if (IsEqualGUID(capture_event_guid, MF_CAPTURE_ENGINE_INITIALIZED)) {
671 ::SetEvent(capture_initialize_);
673 else if (IsEqualGUID(capture_event_guid, MF_CAPTURE_ENGINE_PREVIEW_STOPPED)) {
674 ::SetEvent(camera_stop_);
678 void OnFrameDropped() {
683 void OnIncomingCapturedData(std::shared_ptr<ScopedBufferLock> buffer_lock) {
684 std::lock_guard<std::mutex> lock(mutex_);
686 callback_(std::move(buffer_lock));
694 hr = CreateCaptureEngine(&engine_);
701 ComPtr<IMFAttributes> attributes;
702 hr = MFCreateAttributes(&attributes, 1);
708 hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY, TRUE);
715 video_callback_ =
new MFVideoCallback<VideoCaptureDeviceMFWin>(
this);
718 hr = engine_->Initialize(video_callback_.Get(), attributes.Get(),
nullptr, source_.Get());
724 HANDLE events[] = { capture_initialize_, capture_error_ };
726 DWORD wait_result = ::WaitForMultipleObjects(2, events, FALSE, INFINITE);
727 switch (wait_result) {
731 hr = HRESULT_FROM_WIN32(::GetLastError());
742 bool GetFrameRateFromMediaType(IMFMediaType* type,
float* frame_rate) {
743 UINT32 numerator, denominator;
744 if (FAILED(MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &numerator,
749 *frame_rate =
static_cast<float>(numerator) / denominator;
753 bool GetFormatFromSourceMediaType(IMFMediaType* source_media_type,
bool photo, VideoCaptureFormat* format) {
754 GUID major_type_guid;
755 if (FAILED(source_media_type->GetGUID(MF_MT_MAJOR_TYPE, &major_type_guid)) ||
756 (!IsEqualGUID(major_type_guid, MFMediaType_Image) &&
758 !GetFrameRateFromMediaType(source_media_type, &format->frame_rate)))) {
763 if (FAILED(source_media_type->GetGUID(MF_MT_SUBTYPE, &sub_type_guid)) ||
764 !GetFrameSizeFromMediaType(source_media_type, &format->frame_size) ||
765 !GetPixelFormatFromMFSourceMediaSubtype(
766 sub_type_guid, &format->pixel_format)) {
774 HRESULT FillCapabilities(IMFCaptureSource* source, CapabilityList* capabilities) {
775 DWORD stream_count = 0;
776 HRESULT hr = source->GetDeviceStreamCount(&stream_count);
777 if (FAILED(hr)) { assert(0); }
779 for (DWORD stream_index = 0; stream_index < stream_count; stream_index++) {
780 MF_CAPTURE_ENGINE_STREAM_CATEGORY stream_category;
781 hr = source->GetDeviceStreamCategory(stream_index, &stream_category);
782 if (FAILED(hr)) { assert(0); }
783 if (stream_category != MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_PREVIEW &&
784 stream_category != MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_CAPTURE) {
788 DWORD media_type_index = 0;
789 ComPtr<IMFMediaType> type;
791 while (SUCCEEDED(source->GetAvailableDeviceMediaType(stream_index, media_type_index, &type))) {
792 VideoCaptureFormat format;
793 if (GetFormatFromSourceMediaType(type.Get(),
false, &format))
794 capabilities->emplace_back(media_type_index, format, stream_index);
798 if (hr == MF_E_NO_MORE_TYPES) {
808 HRESULT AllocateAndStart(
int width,
int height, UINT32 frame_rate, VideoPixelFormat& pixel_format) {
809 ComPtr<IMFCaptureSource> source;
810 HRESULT hr = engine_->GetSource(&source);
811 if (FAILED(hr)) {
return hr; }
813 CapabilityList video_capabilities;
814 FillCapabilities(source.Get(), &video_capabilities);
816 VideoCaptureFormat requested;
817 requested.frame_size.SetWidthAndHeight(width, height);
818 requested.frame_rate =
static_cast<float>(frame_rate);
819 requested.pixel_format = pixel_format;
821 const CapabilityWin best_match_video_capability =
822 GetBestMatchedCapability(requested, video_capabilities);
823 if (best_match_video_capability.supported_format.frame_size.width() != width ||
824 best_match_video_capability.supported_format.frame_size.height() != height) {
828 pixel_format = best_match_video_capability.supported_format.pixel_format;
830 ComPtr<IMFMediaType> source_video_media_type;
831 hr = source->GetAvailableDeviceMediaType(best_match_video_capability.stream_index,
832 best_match_video_capability.media_type_index, &source_video_media_type);
833 if (FAILED(hr)) {
return hr; }
835 hr = source->SetCurrentDeviceMediaType(
836 best_match_video_capability.stream_index, source_video_media_type.Get());
837 if (FAILED(hr)) {
return hr; }
839 ComPtr<IMFCaptureSink> sink;
840 hr = engine_->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, &sink);
841 if (FAILED(hr)) {
return hr; }
843 ComPtr<IMFCapturePreviewSink> preview_sink;
844 hr = sink->QueryInterface(IID_PPV_ARGS(&preview_sink));
845 if (FAILED(hr)) {
return hr; }
847 hr = preview_sink->RemoveAllStreams();
848 if (FAILED(hr)) {
return hr; }
850 ComPtr<IMFMediaType> sink_video_media_type;
851 hr = MFCreateMediaType(&sink_video_media_type);
852 if (FAILED(hr)) {
return hr; }
854 hr = ConvertToVideoSinkMediaType(source_video_media_type.Get(), sink_video_media_type.Get(), frame_rate);
855 if (FAILED(hr)) {
return hr; }
857 DWORD dw_sink_stream_index = 0;
858 hr = preview_sink->AddStream(best_match_video_capability.stream_index,
859 sink_video_media_type.Get(),
nullptr,
860 &dw_sink_stream_index);
861 if (FAILED(hr)) {
return hr; }
863 hr = preview_sink->SetSampleCallback(dw_sink_stream_index,
864 video_callback_.Get());
865 if (FAILED(hr)) {
return hr; }
875 hr = engine_->StartPreview();
876 if (FAILED(hr)) {
return hr; }
882 HRESULT hr = engine_->StopPreview();
887 HANDLE events[] = { camera_stop_, capture_error_ };
889 DWORD wait_result = ::WaitForMultipleObjects(2, events, FALSE, INFINITE);
890 if(wait_result == WAIT_OBJECT_0) {
898 HANDLE capture_error_;
899 HANDLE capture_initialize_;
900 ComPtr<MFVideoCallback<VideoCaptureDeviceMFWin>> video_callback_;
901 ComPtr<IMFMediaSource> source_;
902 ComPtr<IMFCaptureEngine> engine_;
904 callback_t callback_ =
nullptr;