diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn index 7554d56d6f..f126e2f798 100644 --- a/modules/video_capture/BUILD.gn +++ b/modules/video_capture/BUILD.gn @@ -107,8 +107,6 @@ if (!build_with_chromium) { "windows/BasePin.cpp", "windows/MediaType.cpp", ] - } else { - deps += [ "//third_party/winsdk_samples" ] } } diff --git a/modules/video_capture/windows/device_info_ds.cc b/modules/video_capture/windows/device_info_ds.cc index 3c25f76075..0f5ba2b3f8 100644 --- a/modules/video_capture/windows/device_info_ds.cc +++ b/modules/video_capture/windows/device_info_ds.cc @@ -16,7 +16,6 @@ #include "rtc_base/string_utils.h" #include -#include namespace webrtc { namespace videocapturemodule { @@ -519,7 +518,7 @@ int32_t DeviceInfoDS::CreateCapabilityMap(const char* deviceUniqueIdUTF8) << " type:" << static_cast(capability.videoType) << " fps:" << capability.maxFPS; } - DeleteMediaType(pmt); + FreeMediaType(pmt); pmt = NULL; } RELEASE_AND_CLEAR(streamConfig); diff --git a/modules/video_capture/windows/help_functions_ds.cc b/modules/video_capture/windows/help_functions_ds.cc index ab85da9488..b1bc7af8e8 100644 --- a/modules/video_capture/windows/help_functions_ds.cc +++ b/modules/video_capture/windows/help_functions_ds.cc @@ -14,6 +14,8 @@ #include "modules/video_capture/windows/help_functions_ds.h" +#include "rtc_base/logging.h" + #include namespace webrtc { @@ -101,5 +103,56 @@ BOOL PinMatchesCategory(IPin* pPin, REFGUID Category) { } return bFound; } + +void ResetMediaType(AM_MEDIA_TYPE* media_type) { + if (!media_type) + return; + if (media_type->cbFormat != 0) { + CoTaskMemFree(media_type->pbFormat); + media_type->cbFormat = 0; + media_type->pbFormat = nullptr; + } + if (media_type->pUnk) { + media_type->pUnk->Release(); + media_type->pUnk = nullptr; + } +} + +void FreeMediaType(AM_MEDIA_TYPE* media_type) { + if (!media_type) + return; + ResetMediaType(media_type); + CoTaskMemFree(media_type); +} + +HRESULT CopyMediaType(AM_MEDIA_TYPE* target, const AM_MEDIA_TYPE* source) { + RTC_DCHECK_NE(source, target); + *target = *source; + if (source->cbFormat != 0) { + RTC_DCHECK(source->pbFormat); + target->pbFormat = + reinterpret_cast(CoTaskMemAlloc(source->cbFormat)); + if (target->pbFormat == nullptr) { + target->cbFormat = 0; + return E_OUTOFMEMORY; + } else { + CopyMemory(target->pbFormat, source->pbFormat, target->cbFormat); + } + } + + if (target->pUnk != nullptr) + target->pUnk->AddRef(); + + return S_OK; +} + +wchar_t* DuplicateWideString(const wchar_t* str) { + size_t len = lstrlenW(str); + wchar_t* ret = + reinterpret_cast(CoTaskMemAlloc((len + 1) * sizeof(wchar_t))); + lstrcpyW(ret, str); + return ret; +} + } // namespace videocapturemodule } // namespace webrtc diff --git a/modules/video_capture/windows/help_functions_ds.h b/modules/video_capture/windows/help_functions_ds.h index e77efd1237..29479157a8 100644 --- a/modules/video_capture/windows/help_functions_ds.h +++ b/modules/video_capture/windows/help_functions_ds.h @@ -13,6 +13,12 @@ #include +#include +#include + +#include "api/scoped_refptr.h" +#include "rtc_base/ref_counter.h" + DEFINE_GUID(MEDIASUBTYPE_I420, 0x30323449, 0x0000, @@ -51,6 +57,61 @@ LONGLONG GetMaxOfFrameArray(LONGLONG* maxFps, long size); IPin* GetInputPin(IBaseFilter* filter); IPin* GetOutputPin(IBaseFilter* filter, REFGUID Category); BOOL PinMatchesCategory(IPin* pPin, REFGUID Category); +void ResetMediaType(AM_MEDIA_TYPE* media_type); +void FreeMediaType(AM_MEDIA_TYPE* media_type); +HRESULT CopyMediaType(AM_MEDIA_TYPE* target, const AM_MEDIA_TYPE* source); + +// Helper function to make using scoped_refptr with COM interface pointers +// a little less awkward. rtc::scoped_refptr doesn't support the & operator +// or a way to receive values via an out ptr. +// The function is intentionally not called QueryInterface to make things less +// confusing for the compiler to figure out what the caller wants to do when +// called from within the context of a class that also implements COM +// interfaces. +template +HRESULT GetComInterface(IUnknown* object, rtc::scoped_refptr* ptr) { + // This helper function is not meant to magically free ptr. If we do that + // we add code bloat to most places where it's not needed and make the code + // less readable since it's not clear at the call site that the pointer + // would get freed even inf QI() fails. + RTC_DCHECK(!ptr->get()); + void* new_ptr = nullptr; + HRESULT hr = object->QueryInterface(__uuidof(T), &new_ptr); + if (SUCCEEDED(hr)) + ptr->swap(reinterpret_cast(&new_ptr)); + return hr; +} + +// Provides a reference count implementation for COM (IUnknown derived) classes. +// The implementation uses atomics for managing the ref count. +template +class ComRefCount : public T { + public: + ComRefCount() {} + + template + explicit ComRefCount(P0&& p0) : T(std::forward(p0)) {} + + STDMETHOD_(ULONG, AddRef)() override { + ref_count_.IncRef(); + return 1; + } + + STDMETHOD_(ULONG, Release)() override { + const auto status = ref_count_.DecRef(); + if (status == rtc::RefCountReleaseStatus::kDroppedLastRef) { + delete this; + return 0; + } + return 1; + } + + protected: + ~ComRefCount() {} + + private: + webrtc::webrtc_impl::RefCounter ref_count_{0}; +}; } // namespace videocapturemodule } // namespace webrtc diff --git a/modules/video_capture/windows/sink_filter_ds.cc b/modules/video_capture/windows/sink_filter_ds.cc index 1d16ad520e..16a156b6a8 100644 --- a/modules/video_capture/windows/sink_filter_ds.cc +++ b/modules/video_capture/windows/sink_filter_ds.cc @@ -10,7 +10,7 @@ #include "modules/video_capture/windows/sink_filter_ds.h" -#include "modules/video_capture/windows/help_functions_ds.h" +#include "rtc_base/arraysize.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/platform_thread.h" @@ -19,11 +19,7 @@ #include // VIDEOINFOHEADER2 #include -#define DELETE_RESET(p) \ - { \ - delete (p); \ - (p) = NULL; \ - } +#include DEFINE_GUID(CLSID_SINKFILTER, 0x88cdbbdc, @@ -40,419 +36,878 @@ DEFINE_GUID(CLSID_SINKFILTER, namespace webrtc { namespace videocapturemodule { +namespace { -typedef struct tagTHREADNAME_INFO { - DWORD dwType; // must be 0x1000 - LPCSTR szName; // pointer to name (in user addr space) - DWORD dwThreadID; // thread ID (-1=caller thread) - DWORD dwFlags; // reserved for future use, must be zero -} THREADNAME_INFO; +// Simple enumeration implementation that enumerates over a single pin :-/ +class EnumPins : public IEnumPins { + public: + EnumPins(IPin* pin) : pin_(pin) {} -CaptureInputPin::CaptureInputPin(IN TCHAR* szName, - IN CaptureSinkFilter* pFilter, - IN CCritSec* pLock, - OUT HRESULT* pHr, - IN LPCWSTR pszName) - : CBaseInputPin(szName, pFilter, pLock, pHr, pszName), - _requestedCapability(), - _resultingCapability() { - _threadHandle = NULL; -} + protected: + virtual ~EnumPins() {} -CaptureInputPin::~CaptureInputPin() {} - -HRESULT -CaptureInputPin::GetMediaType(IN int iPosition, OUT CMediaType* pmt) { - // reset the thread handle - _threadHandle = NULL; - - if (iPosition < 0) - return E_INVALIDARG; - - VIDEOINFOHEADER* pvi = - (VIDEOINFOHEADER*)pmt->AllocFormatBuffer(sizeof(VIDEOINFOHEADER)); - if (NULL == pvi) { - RTC_LOG(LS_INFO) << "CheckMediaType VIDEOINFOHEADER is NULL. Returning."; - return (E_OUTOFMEMORY); + private: + STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override { + if (riid == IID_IUnknown || riid == IID_IEnumPins) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + return E_NOINTERFACE; } - ZeroMemory(pvi, sizeof(VIDEOINFOHEADER)); - pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - pvi->bmiHeader.biPlanes = 1; - pvi->bmiHeader.biClrImportant = 0; - pvi->bmiHeader.biClrUsed = 0; - if (_requestedCapability.maxFPS != 0) { - pvi->AvgTimePerFrame = 10000000 / _requestedCapability.maxFPS; + STDMETHOD(Clone)(IEnumPins** pins) { + RTC_DCHECK(false); + return E_NOTIMPL; } - SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered. - SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle + STDMETHOD(Next)(ULONG count, IPin** pins, ULONG* fetched) { + RTC_DCHECK(count > 0); + RTC_DCHECK(pins); + // fetched may be NULL. - pmt->SetType(&MEDIATYPE_Video); - pmt->SetFormatType(&FORMAT_VideoInfo); - pmt->SetTemporalCompression(FALSE); - - int32_t positionOffset = 1; - switch (iPosition + positionOffset) { - case 0: { - pvi->bmiHeader.biCompression = MAKEFOURCC('I', '4', '2', '0'); - pvi->bmiHeader.biBitCount = 12; // bit per pixel - pvi->bmiHeader.biWidth = _requestedCapability.width; - pvi->bmiHeader.biHeight = _requestedCapability.height; - pvi->bmiHeader.biSizeImage = - 3 * _requestedCapability.height * _requestedCapability.width / 2; - pmt->SetSubtype(&MEDIASUBTYPE_I420); - } break; - case 1: { - pvi->bmiHeader.biCompression = MAKEFOURCC('Y', 'U', 'Y', '2'); - ; - pvi->bmiHeader.biBitCount = 16; // bit per pixel - pvi->bmiHeader.biWidth = _requestedCapability.width; - pvi->bmiHeader.biHeight = _requestedCapability.height; - pvi->bmiHeader.biSizeImage = - 2 * _requestedCapability.width * _requestedCapability.height; - pmt->SetSubtype(&MEDIASUBTYPE_YUY2); - } break; - case 2: { - pvi->bmiHeader.biCompression = BI_RGB; - pvi->bmiHeader.biBitCount = 24; // bit per pixel - pvi->bmiHeader.biWidth = _requestedCapability.width; - pvi->bmiHeader.biHeight = _requestedCapability.height; - pvi->bmiHeader.biSizeImage = - 3 * _requestedCapability.height * _requestedCapability.width; - pmt->SetSubtype(&MEDIASUBTYPE_RGB24); - } break; - case 3: { - pvi->bmiHeader.biCompression = MAKEFOURCC('U', 'Y', 'V', 'Y'); - pvi->bmiHeader.biBitCount = 16; // bit per pixel - pvi->bmiHeader.biWidth = _requestedCapability.width; - pvi->bmiHeader.biHeight = _requestedCapability.height; - pvi->bmiHeader.biSizeImage = - 2 * _requestedCapability.height * _requestedCapability.width; - pmt->SetSubtype(&MEDIASUBTYPE_UYVY); - } break; - case 4: { - pvi->bmiHeader.biCompression = MAKEFOURCC('M', 'J', 'P', 'G'); - pvi->bmiHeader.biBitCount = 12; // bit per pixel - pvi->bmiHeader.biWidth = _requestedCapability.width; - pvi->bmiHeader.biHeight = _requestedCapability.height; - pvi->bmiHeader.biSizeImage = - 3 * _requestedCapability.height * _requestedCapability.width / 2; - pmt->SetSubtype(&MEDIASUBTYPE_MJPG); - } break; - default: - return VFW_S_NO_MORE_ITEMS; - } - pmt->SetSampleSize(pvi->bmiHeader.biSizeImage); - RTC_LOG(LS_INFO) << "GetMediaType position " << iPosition << ", width " - << _requestedCapability.width << ", height " - << _requestedCapability.height << ", biCompression 0x" - << rtc::ToHex(pvi->bmiHeader.biCompression); - return NOERROR; -} - -HRESULT -CaptureInputPin::CheckMediaType(IN const CMediaType* pMediaType) { - // reset the thread handle - _threadHandle = NULL; - - const GUID* type = pMediaType->Type(); - if (*type != MEDIATYPE_Video) - return E_INVALIDARG; - - const GUID* formatType = pMediaType->FormatType(); - - // Check for the subtypes we support - const GUID* SubType = pMediaType->Subtype(); - if (SubType == NULL) { - return E_INVALIDARG; - } - - if (*formatType == FORMAT_VideoInfo) { - VIDEOINFOHEADER* pvi = (VIDEOINFOHEADER*)pMediaType->Format(); - if (pvi == NULL) { - return E_INVALIDARG; - } - - // Store the incoming width and height - _resultingCapability.width = pvi->bmiHeader.biWidth; - - // Store the incoming height, - // for RGB24 we assume the frame to be upside down - if (*SubType == MEDIASUBTYPE_RGB24 && pvi->bmiHeader.biHeight > 0) { - _resultingCapability.height = -(pvi->bmiHeader.biHeight); - } else { - _resultingCapability.height = abs(pvi->bmiHeader.biHeight); - } - - RTC_LOG(LS_INFO) << "CheckMediaType width:" << pvi->bmiHeader.biWidth - << " height:" << pvi->bmiHeader.biHeight - << " Compression:0x" - << rtc::ToHex(pvi->bmiHeader.biCompression); - - if (*SubType == MEDIASUBTYPE_MJPG && - pvi->bmiHeader.biCompression == MAKEFOURCC('M', 'J', 'P', 'G')) { - _resultingCapability.videoType = VideoType::kMJPEG; - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_I420 && - pvi->bmiHeader.biCompression == MAKEFOURCC('I', '4', '2', '0')) { - _resultingCapability.videoType = VideoType::kI420; - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_YUY2 && - pvi->bmiHeader.biCompression == MAKEFOURCC('Y', 'U', 'Y', '2')) { - _resultingCapability.videoType = VideoType::kYUY2; - ::Sleep(60); // workaround for bad driver - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_UYVY && - pvi->bmiHeader.biCompression == MAKEFOURCC('U', 'Y', 'V', 'Y')) { - _resultingCapability.videoType = VideoType::kUYVY; - return S_OK; // This format is acceptable. - } - - if (*SubType == MEDIASUBTYPE_HDYC) { - _resultingCapability.videoType = VideoType::kUYVY; - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_RGB24 && - pvi->bmiHeader.biCompression == BI_RGB) { - _resultingCapability.videoType = VideoType::kRGB24; - return S_OK; // This format is acceptable. - } - } - if (*formatType == FORMAT_VideoInfo2) { - // VIDEOINFOHEADER2 that has dwInterlaceFlags - VIDEOINFOHEADER2* pvi = (VIDEOINFOHEADER2*)pMediaType->Format(); - - if (pvi == NULL) { - return E_INVALIDARG; - } - - RTC_LOG(LS_INFO) << "CheckMediaType width:" << pvi->bmiHeader.biWidth - << " height:" << pvi->bmiHeader.biHeight - << " Compression:0x" - << rtc::ToHex(pvi->bmiHeader.biCompression); - - _resultingCapability.width = pvi->bmiHeader.biWidth; - - // Store the incoming height, - // for RGB24 we assume the frame to be upside down - if (*SubType == MEDIASUBTYPE_RGB24 && pvi->bmiHeader.biHeight > 0) { - _resultingCapability.height = -(pvi->bmiHeader.biHeight); - } else { - _resultingCapability.height = abs(pvi->bmiHeader.biHeight); - } - - if (*SubType == MEDIASUBTYPE_MJPG && - pvi->bmiHeader.biCompression == MAKEFOURCC('M', 'J', 'P', 'G')) { - _resultingCapability.videoType = VideoType::kMJPEG; - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_I420 && - pvi->bmiHeader.biCompression == MAKEFOURCC('I', '4', '2', '0')) { - _resultingCapability.videoType = VideoType::kI420; - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_YUY2 && - pvi->bmiHeader.biCompression == MAKEFOURCC('Y', 'U', 'Y', '2')) { - _resultingCapability.videoType = VideoType::kYUY2; - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_UYVY && - pvi->bmiHeader.biCompression == MAKEFOURCC('U', 'Y', 'V', 'Y')) { - _resultingCapability.videoType = VideoType::kUYVY; - return S_OK; // This format is acceptable. - } - - if (*SubType == MEDIASUBTYPE_HDYC) { - _resultingCapability.videoType = VideoType::kUYVY; - return S_OK; // This format is acceptable. - } - if (*SubType == MEDIASUBTYPE_RGB24 && - pvi->bmiHeader.biCompression == BI_RGB) { - _resultingCapability.videoType = VideoType::kRGB24; - return S_OK; // This format is acceptable. - } - } - return E_INVALIDARG; -} - -HRESULT -CaptureInputPin::Receive(IN IMediaSample* pIMediaSample) { - HRESULT hr = S_OK; - - RTC_DCHECK(m_pFilter); - RTC_DCHECK(pIMediaSample); - - // get the thread handle of the delivering thread inc its priority - if (_threadHandle == NULL) { - HANDLE handle = GetCurrentThread(); - SetThreadPriority(handle, THREAD_PRIORITY_HIGHEST); - _threadHandle = handle; - - rtc::SetCurrentThreadName("webrtc_video_capture"); - } - - reinterpret_cast(m_pFilter)->LockReceive(); - hr = CBaseInputPin::Receive(pIMediaSample); - - if (SUCCEEDED(hr)) { - const LONG length = pIMediaSample->GetActualDataLength(); - RTC_DCHECK(length >= 0); - - unsigned char* pBuffer = NULL; - if (S_OK != pIMediaSample->GetPointer(&pBuffer)) { - reinterpret_cast(m_pFilter)->UnlockReceive(); + if (pos_ > 0) { + if (fetched) + *fetched = 0; return S_FALSE; } - // NOTE: filter unlocked within Send call - reinterpret_cast(m_pFilter)->ProcessCapturedFrame( - pBuffer, static_cast(length), _resultingCapability); - } else { - reinterpret_cast(m_pFilter)->UnlockReceive(); + ++pos_; + pins[0] = pin_.get(); + pins[0]->AddRef(); + if (fetched) + *fetched = 1; + + return count == 1 ? S_OK : S_FALSE; } + STDMETHOD(Skip)(ULONG count) { + RTC_DCHECK(false); + return E_NOTIMPL; + } + + STDMETHOD(Reset)() { + pos_ = 0; + return S_OK; + } + + rtc::scoped_refptr pin_; + int pos_ = 0; +}; + +bool IsMediaTypePartialMatch(const AM_MEDIA_TYPE& a, const AM_MEDIA_TYPE& b) { + if (b.majortype != GUID_NULL && a.majortype != b.majortype) + return false; + + if (b.subtype != GUID_NULL && a.subtype != b.subtype) + return false; + + if (b.formattype != GUID_NULL) { + // if the format block is specified then it must match exactly + if (a.formattype != b.formattype) + return false; + + if (a.cbFormat != b.cbFormat) + return false; + + if (a.cbFormat != 0 && memcmp(a.pbFormat, b.pbFormat, a.cbFormat) != 0) + return false; + } + + return true; +} + +bool IsMediaTypeFullySpecified(const AM_MEDIA_TYPE& type) { + return type.majortype != GUID_NULL && type.formattype != GUID_NULL; +} + +BYTE* AllocMediaTypeFormatBuffer(AM_MEDIA_TYPE* media_type, ULONG length) { + RTC_DCHECK(length); + if (media_type->cbFormat == length) + return media_type->pbFormat; + + BYTE* buffer = static_cast(CoTaskMemAlloc(length)); + if (!buffer) + return nullptr; + + if (media_type->pbFormat) { + RTC_DCHECK(media_type->cbFormat); + CoTaskMemFree(media_type->pbFormat); + media_type->pbFormat = nullptr; + } + + media_type->cbFormat = length; + media_type->pbFormat = buffer; + return buffer; +} + +void GetSampleProperties(IMediaSample* sample, AM_SAMPLE2_PROPERTIES* props) { + rtc::scoped_refptr sample2; + if (SUCCEEDED(GetComInterface(sample, &sample2))) { + sample2->GetProperties(sizeof(*props), reinterpret_cast(props)); + return; + } + + // Get the properties the hard way. + props->cbData = sizeof(*props); + props->dwTypeSpecificFlags = 0; + props->dwStreamId = AM_STREAM_MEDIA; + props->dwSampleFlags = 0; + + if (sample->IsDiscontinuity() == S_OK) + props->dwSampleFlags |= AM_SAMPLE_DATADISCONTINUITY; + + if (sample->IsPreroll() == S_OK) + props->dwSampleFlags |= AM_SAMPLE_PREROLL; + + if (sample->IsSyncPoint() == S_OK) + props->dwSampleFlags |= AM_SAMPLE_SPLICEPOINT; + + if (SUCCEEDED(sample->GetTime(&props->tStart, &props->tStop))) + props->dwSampleFlags |= AM_SAMPLE_TIMEVALID | AM_SAMPLE_STOPVALID; + + if (sample->GetMediaType(&props->pMediaType) == S_OK) + props->dwSampleFlags |= AM_SAMPLE_TYPECHANGED; + + sample->GetPointer(&props->pbBuffer); + props->lActual = sample->GetActualDataLength(); + props->cbBuffer = sample->GetSize(); +} + +// Returns true if the media type is supported, false otherwise. +// For supported types, the |capability| will be populated accordingly. +bool TranslateMediaTypeToVideoCaptureCapability( + const AM_MEDIA_TYPE* media_type, + VideoCaptureCapability* capability) { + RTC_DCHECK(capability); + if (!media_type || media_type->majortype != MEDIATYPE_Video || + !media_type->pbFormat) { + return false; + } + + const BITMAPINFOHEADER* bih = nullptr; + if (media_type->formattype == FORMAT_VideoInfo) { + bih = &reinterpret_cast(media_type->pbFormat)->bmiHeader; + } else if (media_type->formattype != FORMAT_VideoInfo2) { + bih = &reinterpret_cast(media_type->pbFormat)->bmiHeader; + } else { + return false; + } + + RTC_LOG(LS_INFO) << "TranslateMediaTypeToVideoCaptureCapability width:" + << bih->biWidth << " height:" << bih->biHeight + << " Compression:0x" << rtc::ToHex(bih->biCompression); + + const GUID& sub_type = media_type->subtype; + if (sub_type == MEDIASUBTYPE_MJPG && + bih->biCompression == MAKEFOURCC('M', 'J', 'P', 'G')) { + capability->videoType = VideoType::kMJPEG; + } else if (sub_type == MEDIASUBTYPE_I420 && + bih->biCompression == MAKEFOURCC('I', '4', '2', '0')) { + capability->videoType = VideoType::kI420; + } else if (sub_type == MEDIASUBTYPE_YUY2 && + bih->biCompression == MAKEFOURCC('Y', 'U', 'Y', '2')) { + capability->videoType = VideoType::kYUY2; + } else if (sub_type == MEDIASUBTYPE_UYVY && + bih->biCompression == MAKEFOURCC('U', 'Y', 'V', 'Y')) { + capability->videoType = VideoType::kUYVY; + } else if (sub_type == MEDIASUBTYPE_HDYC) { + capability->videoType = VideoType::kUYVY; + } else if (sub_type == MEDIASUBTYPE_RGB24 && bih->biCompression == BI_RGB) { + capability->videoType = VideoType::kRGB24; + } else { + return false; + } + + // Store the incoming width and height + capability->width = bih->biWidth; + + // Store the incoming height, + // for RGB24 we assume the frame to be upside down + if (sub_type == MEDIASUBTYPE_RGB24 && bih->biHeight > 0) { + capability->height = -(bih->biHeight); + } else { + capability->height = abs(bih->biHeight); + } + + return true; +} + +class MediaTypesEnum : public IEnumMediaTypes { + public: + MediaTypesEnum(const VideoCaptureCapability& capability) + : capability_(capability) {} + + protected: + virtual ~MediaTypesEnum() {} + + private: + STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override { + if (riid == IID_IUnknown || riid == IID_IEnumMediaTypes) { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + // IEnumMediaTypes + STDMETHOD(Clone)(IEnumMediaTypes** pins) { + RTC_DCHECK(false); + return E_NOTIMPL; + } + + STDMETHOD(Next)(ULONG count, AM_MEDIA_TYPE** types, ULONG* fetched) { + RTC_DCHECK(count > 0); + RTC_DCHECK(types); + // fetched may be NULL. + if (fetched) + *fetched = 0; + + // Note, must match switch statement below. + constexpr int kNumTypes = 5; + + for (ULONG i = 0; i < count && pos_ < kNumTypes; ++i) { + AM_MEDIA_TYPE* media_type = reinterpret_cast( + CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE))); + ZeroMemory(media_type, sizeof(*media_type)); + types[i] = media_type; + VIDEOINFOHEADER* vih = reinterpret_cast( + AllocMediaTypeFormatBuffer(media_type, sizeof(VIDEOINFOHEADER))); + ZeroMemory(vih, sizeof(*vih)); + vih->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + vih->bmiHeader.biPlanes = 1; + vih->bmiHeader.biClrImportant = 0; + vih->bmiHeader.biClrUsed = 0; + if (capability_.maxFPS != 0) + vih->AvgTimePerFrame = 10000000 / capability_.maxFPS; + + SetRectEmpty(&vih->rcSource); // we want the whole image area rendered. + SetRectEmpty(&vih->rcTarget); // no particular destination rectangle + + media_type->majortype = MEDIATYPE_Video; + media_type->formattype = FORMAT_VideoInfo; + media_type->bTemporalCompression = FALSE; + + switch (pos_++) { + case 0: + // Note: The previous implementation had a bug that caused this + // format to always be omitted. Perhaps including it now will have + // a sideffect. + vih->bmiHeader.biCompression = MAKEFOURCC('I', '4', '2', '0'); + vih->bmiHeader.biBitCount = 12; // bit per pixel + media_type->subtype = MEDIASUBTYPE_I420; + break; + case 1: + vih->bmiHeader.biCompression = MAKEFOURCC('Y', 'U', 'Y', '2'); + vih->bmiHeader.biBitCount = 16; // bit per pixel + media_type->subtype = MEDIASUBTYPE_YUY2; + break; + case 2: + vih->bmiHeader.biCompression = BI_RGB; + vih->bmiHeader.biBitCount = 24; // bit per pixel + media_type->subtype = MEDIASUBTYPE_RGB24; + break; + case 3: + vih->bmiHeader.biCompression = MAKEFOURCC('U', 'Y', 'V', 'Y'); + vih->bmiHeader.biBitCount = 16; // bit per pixel + media_type->subtype = MEDIASUBTYPE_UYVY; + break; + case 4: + vih->bmiHeader.biCompression = MAKEFOURCC('M', 'J', 'P', 'G'); + vih->bmiHeader.biBitCount = 12; // bit per pixel + media_type->subtype = MEDIASUBTYPE_MJPG; + break; + default: + RTC_NOTREACHED(); + break; + } + + vih->bmiHeader.biWidth = capability_.width; + vih->bmiHeader.biHeight = capability_.height; + vih->bmiHeader.biSizeImage = ((vih->bmiHeader.biBitCount / 4) * + capability_.height * capability_.width) / + 2; + + RTC_DCHECK(vih->bmiHeader.biSizeImage); + media_type->lSampleSize = vih->bmiHeader.biSizeImage; + media_type->bFixedSizeSamples = true; + if (fetched) + ++(*fetched); + } + RTC_DCHECK(pos_ <= kNumTypes); + return pos_ == kNumTypes ? S_FALSE : S_OK; + } + + STDMETHOD(Skip)(ULONG count) { + RTC_DCHECK(false); + return E_NOTIMPL; + } + + STDMETHOD(Reset)() { + pos_ = 0; + return S_OK; + } + + int pos_ = 0; + const VideoCaptureCapability capability_; +}; + +} // namespace + +CaptureInputPin::CaptureInputPin(CaptureSinkFilter* filter) { + capture_checker_.DetachFromThread(); + // No reference held to avoid circular references. + info_.pFilter = filter; + info_.dir = PINDIR_INPUT; +} + +CaptureInputPin::~CaptureInputPin() { + RTC_DCHECK_RUN_ON(&main_checker_); + ResetMediaType(&media_type_); +} + +HRESULT CaptureInputPin::SetRequestedCapability( + const VideoCaptureCapability& capability) { + RTC_DCHECK_RUN_ON(&main_checker_); + RTC_DCHECK(Filter()->IsStopped()); + requested_capability_ = capability; + resulting_capability_ = VideoCaptureCapability(); + return S_OK; +} + +void CaptureInputPin::OnFilterActivated() { + RTC_DCHECK_RUN_ON(&main_checker_); + runtime_error_ = false; + flushing_ = false; + capture_checker_.DetachFromThread(); + capture_thread_id_ = 0; +} + +void CaptureInputPin::OnFilterDeactivated() { + RTC_DCHECK_RUN_ON(&main_checker_); + // Expedite shutdown by raising the flushing flag so no further processing + // on the capture thread occurs. When the graph is stopped and all filters + // have been told to stop, the media controller (graph) will wait for the + // capture thread to stop. + flushing_ = true; + if (allocator_) + allocator_->Decommit(); +} + +CaptureSinkFilter* CaptureInputPin::Filter() const { + return static_cast(info_.pFilter); +} + +HRESULT CaptureInputPin::AttemptConnection(IPin* receive_pin, + const AM_MEDIA_TYPE* media_type) { + RTC_DCHECK_RUN_ON(&main_checker_); + RTC_DCHECK(Filter()->IsStopped()); + + // Check that the connection is valid -- need to do this for every + // connect attempt since BreakConnect will undo it. + HRESULT hr = CheckDirection(receive_pin); + if (FAILED(hr)) + return hr; + + if (!TranslateMediaTypeToVideoCaptureCapability(media_type, + &resulting_capability_)) { + ClearAllocator(true); + return VFW_E_TYPE_NOT_ACCEPTED; + } + + // See if the other pin will accept this type. + hr = receive_pin->ReceiveConnection(static_cast(this), media_type); + if (FAILED(hr)) { + receive_pin_ = nullptr; // Should already be null, but just in case. + return hr; + } + + // Should have been set as part of the connect process. + RTC_DCHECK_EQ(receive_pin_, receive_pin); + + ResetMediaType(&media_type_); + CopyMediaType(&media_type_, media_type); + + return S_OK; +} + +std::vector CaptureInputPin::DetermineCandidateFormats( + IPin* receive_pin, + const AM_MEDIA_TYPE* media_type) { + RTC_DCHECK_RUN_ON(&main_checker_); + RTC_DCHECK(receive_pin); + RTC_DCHECK(media_type); + + std::vector ret; + + for (int i = 0; i < 2; i++) { + IEnumMediaTypes* types = nullptr; + if (i == 0) { + // First time around, try types from receive_pin. + receive_pin->EnumMediaTypes(&types); + } else { + // Then try ours. + EnumMediaTypes(&types); + } + + if (types) { + while (true) { + ULONG fetched = 0; + AM_MEDIA_TYPE* this_type = nullptr; + if (types->Next(1, &this_type, &fetched) != S_OK) + break; + + if (IsMediaTypePartialMatch(*this_type, *media_type)) { + ret.push_back(this_type); + } else { + FreeMediaType(this_type); + } + } + types->Release(); + } + } + + return ret; +} + +void CaptureInputPin::ClearAllocator(bool decommit) { + RTC_DCHECK_RUN_ON(&main_checker_); + if (!allocator_) + return; + if (decommit) + allocator_->Decommit(); + allocator_ = nullptr; +} + +HRESULT CaptureInputPin::CheckDirection(IPin* pin) const { + RTC_DCHECK_RUN_ON(&main_checker_); + PIN_DIRECTION pd; + pin->QueryDirection(&pd); + // Fairly basic check, make sure we don't pair input with input etc. + return pd == info_.dir ? VFW_E_INVALID_DIRECTION : S_OK; +} + +STDMETHODIMP CaptureInputPin::QueryInterface(REFIID riid, void** ppv) { + (*ppv) = nullptr; + if (riid == IID_IUnknown || riid == IID_IMemInputPin) { + *ppv = static_cast(this); + } else if (riid == IID_IPin) { + *ppv = static_cast(this); + } + + if (!(*ppv)) + return E_NOINTERFACE; + + static_cast(this)->AddRef(); + return S_OK; +} + +STDMETHODIMP CaptureInputPin::Connect(IPin* receive_pin, + const AM_MEDIA_TYPE* media_type) { + RTC_DCHECK_RUN_ON(&main_checker_); + if (!media_type || !receive_pin) + return E_POINTER; + + if (!Filter()->IsStopped()) + return VFW_E_NOT_STOPPED; + + if (receive_pin_) { + RTC_DCHECK(false); + return VFW_E_ALREADY_CONNECTED; + } + + if (IsMediaTypeFullySpecified(*media_type)) + return AttemptConnection(receive_pin, media_type); + + auto types = DetermineCandidateFormats(receive_pin, media_type); + bool connected = false; + for (auto* type : types) { + if (!connected && AttemptConnection(receive_pin, media_type) == S_OK) + connected = true; + + FreeMediaType(type); + } + + return connected ? S_OK : VFW_E_NO_ACCEPTABLE_TYPES; +} + +STDMETHODIMP CaptureInputPin::ReceiveConnection( + IPin* connector, + const AM_MEDIA_TYPE* media_type) { + RTC_DCHECK_RUN_ON(&main_checker_); + RTC_DCHECK(Filter()->IsStopped()); + + if (receive_pin_) { + RTC_DCHECK(false); + return VFW_E_ALREADY_CONNECTED; + } + + HRESULT hr = CheckDirection(connector); + if (FAILED(hr)) + return hr; + + if (!TranslateMediaTypeToVideoCaptureCapability(media_type, + &resulting_capability_)) + return VFW_E_TYPE_NOT_ACCEPTED; + + // Complete the connection + + receive_pin_ = connector; + ResetMediaType(&media_type_); + CopyMediaType(&media_type_, media_type); + + return S_OK; +} + +STDMETHODIMP CaptureInputPin::Disconnect() { + RTC_DCHECK_RUN_ON(&main_checker_); + if (!Filter()->IsStopped()) + return VFW_E_NOT_STOPPED; + + if (!receive_pin_) + return S_FALSE; + + ClearAllocator(true); + receive_pin_ = nullptr; + + return S_OK; +} + +STDMETHODIMP CaptureInputPin::ConnectedTo(IPin** pin) { + RTC_DCHECK_RUN_ON(&main_checker_); + + if (!receive_pin_) + return VFW_E_NOT_CONNECTED; + + *pin = receive_pin_.get(); + receive_pin_->AddRef(); + + return S_OK; +} + +STDMETHODIMP CaptureInputPin::ConnectionMediaType(AM_MEDIA_TYPE* media_type) { + RTC_DCHECK_RUN_ON(&main_checker_); + + if (!receive_pin_) + return VFW_E_NOT_CONNECTED; + + CopyMediaType(media_type, &media_type_); + + return S_OK; +} + +STDMETHODIMP CaptureInputPin::QueryPinInfo(PIN_INFO* info) { + RTC_DCHECK_RUN_ON(&main_checker_); + *info = info_; + if (info_.pFilter) + info_.pFilter->AddRef(); + return S_OK; +} + +STDMETHODIMP CaptureInputPin::QueryDirection(PIN_DIRECTION* pin_dir) { + RTC_DCHECK_RUN_ON(&main_checker_); + *pin_dir = info_.dir; + return S_OK; +} + +STDMETHODIMP CaptureInputPin::QueryId(LPWSTR* id) { + RTC_DCHECK_RUN_ON(&main_checker_); + size_t len = lstrlenW(info_.achName); + *id = reinterpret_cast(CoTaskMemAlloc((len + 1) * sizeof(wchar_t))); + lstrcpyW(*id, info_.achName); + return S_OK; +} + +STDMETHODIMP CaptureInputPin::QueryAccept(const AM_MEDIA_TYPE* media_type) { + RTC_DCHECK_RUN_ON(&main_checker_); + RTC_DCHECK(Filter()->IsStopped()); + VideoCaptureCapability capability(resulting_capability_); + return TranslateMediaTypeToVideoCaptureCapability(media_type, &capability) + ? S_FALSE + : S_OK; +} + +STDMETHODIMP CaptureInputPin::EnumMediaTypes(IEnumMediaTypes** types) { + RTC_DCHECK_RUN_ON(&main_checker_); + *types = new ComRefCount(requested_capability_); + (*types)->AddRef(); + return S_OK; +} + +STDMETHODIMP CaptureInputPin::QueryInternalConnections(IPin** pins, + ULONG* count) { + return E_NOTIMPL; +} + +STDMETHODIMP CaptureInputPin::EndOfStream() { + return S_OK; +} + +STDMETHODIMP CaptureInputPin::BeginFlush() { + RTC_DCHECK_RUN_ON(&main_checker_); + flushing_ = true; + return S_OK; +} + +STDMETHODIMP CaptureInputPin::EndFlush() { + RTC_DCHECK_RUN_ON(&main_checker_); + flushing_ = false; + runtime_error_ = false; + return S_OK; +} + +STDMETHODIMP CaptureInputPin::NewSegment(REFERENCE_TIME start, + REFERENCE_TIME stop, + double rate) { + RTC_DCHECK_RUN_ON(&main_checker_); + return S_OK; +} + +STDMETHODIMP CaptureInputPin::GetAllocator(IMemAllocator** allocator) { + RTC_DCHECK_RUN_ON(&main_checker_); + if (allocator_ == nullptr) { + HRESULT hr = CoCreateInstance(CLSID_MemoryAllocator, 0, + CLSCTX_INPROC_SERVER, IID_IMemAllocator, + reinterpret_cast(allocator)); + if (FAILED(hr)) + return hr; + allocator_.swap(allocator); + } + *allocator = allocator_; + allocator_->AddRef(); + return S_OK; +} + +STDMETHODIMP CaptureInputPin::NotifyAllocator(IMemAllocator* allocator, + BOOL read_only) { + RTC_DCHECK_RUN_ON(&main_checker_); + allocator_.swap(&allocator); + if (allocator_) + allocator_->AddRef(); + if (allocator) + allocator->Release(); + return S_OK; +} + +STDMETHODIMP CaptureInputPin::GetAllocatorRequirements( + ALLOCATOR_PROPERTIES* props) { + return E_NOTIMPL; +} + +STDMETHODIMP CaptureInputPin::Receive(IMediaSample* media_sample) { + RTC_DCHECK_RUN_ON(&capture_checker_); + + CaptureSinkFilter* const filter = static_cast(Filter()); + + if (flushing_.load(std::memory_order_relaxed)) + return S_FALSE; + + if (runtime_error_.load(std::memory_order_relaxed)) + return VFW_E_RUNTIME_ERROR; + + if (!capture_thread_id_) { + // Make sure we set the thread name only once. + capture_thread_id_ = GetCurrentThreadId(); + rtc::SetCurrentThreadName("webrtc_video_capture"); + } + + AM_SAMPLE2_PROPERTIES sample_props = {}; + GetSampleProperties(media_sample, &sample_props); + // Has the format changed in this sample? + if (sample_props.dwSampleFlags & AM_SAMPLE_TYPECHANGED) { + // Check the derived class accepts the new format. + // This shouldn't fail as the source must call QueryAccept first. + + // Note: This will modify resulting_capability_. + // That should be OK as long as resulting_capability_ is only modified + // on this thread while it is running (filter is not stopped), and only + // modified on the main thread when the filter is stopped (i.e. this thread + // is not running). + if (!TranslateMediaTypeToVideoCaptureCapability(sample_props.pMediaType, + &resulting_capability_)) { + // Raise a runtime error if we fail the media type + runtime_error_ = true; + EndOfStream(); + Filter()->NotifyEvent(EC_ERRORABORT, VFW_E_TYPE_NOT_ACCEPTED, 0); + return VFW_E_INVALIDMEDIATYPE; + } + } + + filter->ProcessCapturedFrame(sample_props.pbBuffer, sample_props.lActual, + resulting_capability_); + + return S_OK; +} + +STDMETHODIMP CaptureInputPin::ReceiveMultiple(IMediaSample** samples, + long count, + long* processed) { + HRESULT hr = S_OK; + *processed = 0; + while (count-- > 0) { + hr = Receive(samples[*processed]); + if (hr != S_OK) + break; + ++(*processed); + } return hr; } -// called under LockReceive -HRESULT CaptureInputPin::SetMatchingMediaType( - const VideoCaptureCapability& capability) { - _requestedCapability = capability; - _resultingCapability = VideoCaptureCapability(); - return S_OK; +STDMETHODIMP CaptureInputPin::ReceiveCanBlock() { + return S_FALSE; } + // ---------------------------------------------------------------------------- -CaptureSinkFilter::CaptureSinkFilter(const IN TCHAR* tszName, - IN LPUNKNOWN punk, - OUT HRESULT* phr, - VideoCaptureExternal& captureObserver) - : CBaseFilter(tszName, punk, &m_crtFilter, CLSID_SINKFILTER), - m_pInput(NULL), - _captureObserver(captureObserver) { - (*phr) = S_OK; - TCHAR inputPinName[] = L"VideoCaptureInputPin"; - m_pInput = new CaptureInputPin(inputPinName, this, &m_crtFilter, phr, - L"VideoCapture"); - if (m_pInput == NULL || FAILED(*phr)) { - (*phr) = FAILED(*phr) ? (*phr) : E_OUTOFMEMORY; - goto cleanup; - } -cleanup: - return; -} + +CaptureSinkFilter::CaptureSinkFilter(VideoCaptureExternal* capture_observer) + : input_pin_(new ComRefCount(this)), + capture_observer_(capture_observer) {} CaptureSinkFilter::~CaptureSinkFilter() { - delete m_pInput; + RTC_DCHECK_RUN_ON(&main_checker_); } -int CaptureSinkFilter::GetPinCount() { - return 1; +HRESULT CaptureSinkFilter::SetRequestedCapability( + const VideoCaptureCapability& capability) { + RTC_DCHECK_RUN_ON(&main_checker_); + // Called on the same thread as capture is started on. + return input_pin_->SetRequestedCapability(capability); } -CBasePin* CaptureSinkFilter::GetPin(IN int Index) { - CBasePin* pPin; - LockFilter(); - if (Index == 0) { - pPin = m_pInput; - } else { - pPin = NULL; - } - UnlockFilter(); - return pPin; +STDMETHODIMP CaptureSinkFilter::GetState(DWORD msecs, FILTER_STATE* state) { + RTC_DCHECK_RUN_ON(&main_checker_); + *state = state_; + return S_OK; +} + +STDMETHODIMP CaptureSinkFilter::SetSyncSource(IReferenceClock* clock) { + RTC_DCHECK_RUN_ON(&main_checker_); + return S_OK; +} + +STDMETHODIMP CaptureSinkFilter::GetSyncSource(IReferenceClock** clock) { + RTC_DCHECK_RUN_ON(&main_checker_); + return E_NOTIMPL; } STDMETHODIMP CaptureSinkFilter::Pause() { - LockReceive(); - LockFilter(); - if (m_State == State_Stopped) { - // change the state, THEN activate the input pin - m_State = State_Paused; - if (m_pInput && m_pInput->IsConnected()) { - m_pInput->Active(); - } - if (m_pInput && !m_pInput->IsConnected()) { - m_State = State_Running; - } - } else if (m_State == State_Running) { - m_State = State_Paused; - } - UnlockFilter(); - UnlockReceive(); + RTC_DCHECK_RUN_ON(&main_checker_); + state_ = State_Paused; + return S_OK; +} + +STDMETHODIMP CaptureSinkFilter::Run(REFERENCE_TIME tStart) { + RTC_DCHECK_RUN_ON(&main_checker_); + if (state_ == State_Stopped) + Pause(); + + state_ = State_Running; + input_pin_->OnFilterActivated(); + return S_OK; } STDMETHODIMP CaptureSinkFilter::Stop() { - LockReceive(); - LockFilter(); + RTC_DCHECK_RUN_ON(&main_checker_); + if (state_ == State_Stopped) + return S_OK; - // set the state - m_State = State_Stopped; + state_ = State_Stopped; + input_pin_->OnFilterDeactivated(); - // inactivate the pins - if (m_pInput) - m_pInput->Inactive(); - - UnlockFilter(); - UnlockReceive(); return S_OK; } -void CaptureSinkFilter::SetFilterGraph(IGraphBuilder* graph) { - LockFilter(); - m_pGraph = graph; - UnlockFilter(); +STDMETHODIMP CaptureSinkFilter::EnumPins(IEnumPins** pins) { + RTC_DCHECK_RUN_ON(&main_checker_); + *pins = new ComRefCount(input_pin_.get()); + (*pins)->AddRef(); + return S_OK; +} + +STDMETHODIMP CaptureSinkFilter::FindPin(LPCWSTR id, IPin** pin) { + RTC_DCHECK_RUN_ON(&main_checker_); + // There's no ID assigned to our input pin, so looking it up based on one + // is pointless (and in practice, this method isn't being used). + return VFW_E_NOT_FOUND; +} + +STDMETHODIMP CaptureSinkFilter::QueryFilterInfo(FILTER_INFO* info) { + RTC_DCHECK_RUN_ON(&main_checker_); + *info = info_; + if (info->pGraph) + info->pGraph->AddRef(); + return S_OK; +} + +STDMETHODIMP CaptureSinkFilter::JoinFilterGraph(IFilterGraph* graph, + LPCWSTR name) { + RTC_DCHECK_RUN_ON(&main_checker_); + RTC_DCHECK(IsStopped()); + + // Note, since a reference to the filter is held by the graph manager, + // filters must not hold a reference to the graph. If they would, we'd have + // a circular reference. Instead, a pointer to the graph can be held without + // reference. See documentation for IBaseFilter::JoinFilterGraph for more. + info_.pGraph = graph; // No AddRef(). + sink_ = nullptr; + + if (info_.pGraph) { + // make sure we don't hold on to the reference we may receive. + // Note that this assumes the same object identity, but so be it. + rtc::scoped_refptr sink; + GetComInterface(info_.pGraph, &sink); + sink_ = sink.get(); + } + + info_.achName[0] = L'\0'; + if (name) + lstrcpynW(info_.achName, name, arraysize(info_.achName)); + + return S_OK; +} + +STDMETHODIMP CaptureSinkFilter::QueryVendorInfo(LPWSTR* vendor_info) { + return E_NOTIMPL; } void CaptureSinkFilter::ProcessCapturedFrame( - unsigned char* pBuffer, + unsigned char* buffer, size_t length, - const VideoCaptureCapability& frameInfo) { - // we have the receiver lock - if (m_State == State_Running) { - _captureObserver.IncomingFrame(pBuffer, length, frameInfo); + const VideoCaptureCapability& frame_info) { + // Called on the capture thread. + capture_observer_->IncomingFrame(buffer, length, frame_info); +} - // trying to hold it since it's only a memcpy - // IMPROVEMENT if this work move critsect - UnlockReceive(); +void CaptureSinkFilter::NotifyEvent(long code, + LONG_PTR param1, + LONG_PTR param2) { + // Called on the capture thread. + if (!sink_) return; - } - UnlockReceive(); - return; + + if (EC_COMPLETE == code) + param2 = reinterpret_cast(static_cast(this)); + sink_->Notify(code, param1, param2); +} + +bool CaptureSinkFilter::IsStopped() const { + RTC_DCHECK_RUN_ON(&main_checker_); + return state_ == State_Stopped; } STDMETHODIMP CaptureSinkFilter::QueryInterface(REFIID riid, void** ppv) { - return GetOwner()->QueryInterface(riid, ppv); -} - -STDMETHODIMP_(ULONG) CaptureSinkFilter::AddRef() { - return GetOwner()->AddRef(); -} - -STDMETHODIMP_(ULONG) CaptureSinkFilter::Release() { - return GetOwner()->Release(); -} - -STDMETHODIMP CaptureSinkFilter::SetMatchingMediaType( - const VideoCaptureCapability& capability) { - LockReceive(); - LockFilter(); - HRESULT hr; - if (m_pInput) { - hr = m_pInput->SetMatchingMediaType(capability); - } else { - hr = E_UNEXPECTED; + if (riid == IID_IUnknown || riid == IID_IPersist || riid == IID_IBaseFilter) { + *ppv = static_cast(this); + AddRef(); + return S_OK; } - UnlockFilter(); - UnlockReceive(); - return hr; + return E_NOINTERFACE; } -STDMETHODIMP CaptureSinkFilter::GetClassID(OUT CLSID* pCLSID) { - (*pCLSID) = CLSID_SINKFILTER; +STDMETHODIMP CaptureSinkFilter::GetClassID(CLSID* clsid) { + *clsid = CLSID_SINKFILTER; return S_OK; } diff --git a/modules/video_capture/windows/sink_filter_ds.h b/modules/video_capture/windows/sink_filter_ds.h index 7bd94389aa..d9896f1c36 100644 --- a/modules/video_capture/windows/sink_filter_ds.h +++ b/modules/video_capture/windows/sink_filter_ds.h @@ -11,84 +11,151 @@ #ifndef MODULES_VIDEO_CAPTURE_MAIN_SOURCE_WINDOWS_SINK_FILTER_DS_H_ #define MODULES_VIDEO_CAPTURE_MAIN_SOURCE_WINDOWS_SINK_FILTER_DS_H_ -#include // Include base DS filter header files +#include + +#include +#include +#include #include "modules/video_capture/video_capture_defines.h" +#include "modules/video_capture/windows/help_functions_ds.h" +#include "rtc_base/thread_annotations.h" +#include "rtc_base/thread_checker.h" namespace webrtc { namespace videocapturemodule { -// forward declaration - +// forward declarations class CaptureSinkFilter; -/** - * input pin for camera input - * - */ -class CaptureInputPin : public CBaseInputPin { + +// Input pin for camera input +// Implements IMemInputPin, IPin. +class CaptureInputPin : public IMemInputPin, public IPin { public: - VideoCaptureCapability _requestedCapability; - VideoCaptureCapability _resultingCapability; - HANDLE _threadHandle; + CaptureInputPin(CaptureSinkFilter* filter); - CaptureInputPin(IN TCHAR* szName, - IN CaptureSinkFilter* pFilter, - IN CCritSec* pLock, - OUT HRESULT* pHr, - IN LPCWSTR pszName); - ~CaptureInputPin() override; + HRESULT SetRequestedCapability(const VideoCaptureCapability& capability); - HRESULT GetMediaType(IN int iPos, OUT CMediaType* pmt) override; - HRESULT CheckMediaType(IN const CMediaType* pmt) override; - STDMETHODIMP Receive(IN IMediaSample*) override; - HRESULT SetMatchingMediaType(const VideoCaptureCapability& capability); -}; + // Notifications from the filter. + void OnFilterActivated(); + void OnFilterDeactivated(); -class CaptureSinkFilter : public CBaseFilter { - public: - CaptureSinkFilter(const IN TCHAR* tszName, - IN LPUNKNOWN punk, - OUT HRESULT* phr, - VideoCaptureExternal& captureObserver); - ~CaptureSinkFilter() override; - - // -------------------------------------------------------------------- - // class methods - - void ProcessCapturedFrame(unsigned char* pBuffer, - size_t length, - const VideoCaptureCapability& frameInfo); - // explicit receiver lock aquisition and release - void LockReceive() { m_crtRecv.Lock(); } - void UnlockReceive() { m_crtRecv.Unlock(); } - // explicit filter lock aquisition and release - void LockFilter() { m_crtFilter.Lock(); } - void UnlockFilter() { m_crtFilter.Unlock(); } - void SetFilterGraph(IGraphBuilder* graph); // Used if EVR - - // -------------------------------------------------------------------- - // COM interfaces - STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override; - STDMETHOD_(ULONG, AddRef)() override; - STDMETHOD_(ULONG, Release)() override; - - STDMETHOD(SetMatchingMediaType)(const VideoCaptureCapability& capability); - - // -------------------------------------------------------------------- - // CBaseFilter methods - int GetPinCount() override; - CBasePin* GetPin(IN int Index) override; - STDMETHOD(Pause)() override; - STDMETHOD(Stop)() override; - STDMETHOD(GetClassID)(OUT CLSID* pCLSID) override; - // -------------------------------------------------------------------- - // class factory calls this - static CUnknown* CreateInstance(IN LPUNKNOWN punk, OUT HRESULT* phr); + protected: + virtual ~CaptureInputPin(); private: - CCritSec m_crtFilter; // filter lock - CCritSec m_crtRecv; // receiver lock; always acquire before filter lock - CaptureInputPin* m_pInput; - VideoCaptureExternal& _captureObserver; + CaptureSinkFilter* Filter() const; + + HRESULT AttemptConnection(IPin* receive_pin, const AM_MEDIA_TYPE* media_type); + std::vector DetermineCandidateFormats( + IPin* receive_pin, + const AM_MEDIA_TYPE* media_type); + void ClearAllocator(bool decommit); + HRESULT CheckDirection(IPin* pin) const; + + // IUnknown + STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override; + + // clang-format off + // clang isn't sure what to do with the longer STDMETHOD() function + // declarations. + + // IPin + STDMETHOD(Connect)(IPin* receive_pin, + const AM_MEDIA_TYPE* media_type) override; + STDMETHOD(ReceiveConnection)(IPin* connector, + const AM_MEDIA_TYPE* media_type) override; + STDMETHOD(Disconnect)() override; + STDMETHOD(ConnectedTo)(IPin** pin) override; + STDMETHOD(ConnectionMediaType)(AM_MEDIA_TYPE* media_type) override; + STDMETHOD(QueryPinInfo)(PIN_INFO* info) override; + STDMETHOD(QueryDirection)(PIN_DIRECTION* pin_dir) override; + STDMETHOD(QueryId)(LPWSTR* id) override; + STDMETHOD(QueryAccept)(const AM_MEDIA_TYPE* media_type) override; + STDMETHOD(EnumMediaTypes)(IEnumMediaTypes** types) override; + STDMETHOD(QueryInternalConnections)(IPin** pins, ULONG* count) override; + STDMETHOD(EndOfStream)() override; + STDMETHOD(BeginFlush)() override; + STDMETHOD(EndFlush)() override; + STDMETHOD(NewSegment)(REFERENCE_TIME start, REFERENCE_TIME stop, + double rate) override; + + // IMemInputPin + STDMETHOD(GetAllocator)(IMemAllocator** allocator) override; + STDMETHOD(NotifyAllocator)(IMemAllocator* allocator, BOOL read_only) override; + STDMETHOD(GetAllocatorRequirements)(ALLOCATOR_PROPERTIES* props) override; + STDMETHOD(Receive)(IMediaSample* sample) override; + STDMETHOD(ReceiveMultiple)(IMediaSample** samples, long count, + long* processed) override; + STDMETHOD(ReceiveCanBlock)() override; + // clang-format on + + rtc::ThreadChecker main_checker_; + rtc::ThreadChecker capture_checker_; + + VideoCaptureCapability requested_capability_ RTC_GUARDED_BY(main_checker_); + // Accessed on the main thread when Filter()->IsStopped() (capture thread not + // running), otherwise accessed on the capture thread. + VideoCaptureCapability resulting_capability_; + DWORD capture_thread_id_ = 0; + rtc::scoped_refptr allocator_ RTC_GUARDED_BY(main_checker_); + rtc::scoped_refptr receive_pin_ RTC_GUARDED_BY(main_checker_); + std::atomic_bool flushing_{false}; + std::atomic_bool runtime_error_{false}; + // Holds a referenceless pointer to the owning filter, the name and + // direction of the pin. The filter pointer can be considered const. + PIN_INFO info_ = {}; + AM_MEDIA_TYPE media_type_ RTC_GUARDED_BY(main_checker_) = {}; +}; + +// Implement IBaseFilter (including IPersist and IMediaFilter). +class CaptureSinkFilter : public IBaseFilter { + public: + CaptureSinkFilter(VideoCaptureExternal* capture_observer); + + HRESULT SetRequestedCapability(const VideoCaptureCapability& capability); + + // Called on the capture thread. + void ProcessCapturedFrame(unsigned char* buffer, + size_t length, + const VideoCaptureCapability& frame_info); + + void NotifyEvent(long code, LONG_PTR param1, LONG_PTR param2); + bool IsStopped() const; + + // IUnknown + STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override; + + // IPersist + STDMETHOD(GetClassID)(CLSID* clsid) override; + + // IMediaFilter. + STDMETHOD(GetState)(DWORD msecs, FILTER_STATE* state) override; + STDMETHOD(SetSyncSource)(IReferenceClock* clock) override; + STDMETHOD(GetSyncSource)(IReferenceClock** clock) override; + STDMETHOD(Pause)() override; + STDMETHOD(Run)(REFERENCE_TIME start) override; + STDMETHOD(Stop)() override; + + // IBaseFilter + STDMETHOD(EnumPins)(IEnumPins** pins) override; + STDMETHOD(FindPin)(LPCWSTR id, IPin** pin) override; + STDMETHOD(QueryFilterInfo)(FILTER_INFO* info) override; + STDMETHOD(JoinFilterGraph)(IFilterGraph* graph, LPCWSTR name) override; + STDMETHOD(QueryVendorInfo)(LPWSTR* vendor_info) override; + + protected: + virtual ~CaptureSinkFilter(); + + private: + rtc::ThreadChecker main_checker_; + const rtc::scoped_refptr> input_pin_; + VideoCaptureExternal* const capture_observer_; + FILTER_INFO info_ RTC_GUARDED_BY(main_checker_) = {}; + // Set/cleared in JoinFilterGraph. The filter must be stopped (no capture) + // at that time, so no lock is required. While the state is not stopped, + // the sink will be used from the capture thread. + IMediaEventSink* sink_ = nullptr; + FILTER_STATE state_ RTC_GUARDED_BY(main_checker_) = State_Stopped; }; } // namespace videocapturemodule } // namespace webrtc diff --git a/modules/video_capture/windows/video_capture_ds.cc b/modules/video_capture/windows/video_capture_ds.cc index e56dee81e5..b9d03858ff 100644 --- a/modules/video_capture/windows/video_capture_ds.cc +++ b/modules/video_capture/windows/video_capture_ds.cc @@ -23,7 +23,6 @@ VideoCaptureDS::VideoCaptureDS() : _captureFilter(NULL), _graphBuilder(NULL), _mediaControl(NULL), - _sinkFilter(NULL), _inputSendPin(NULL), _outputCapturePin(NULL), _dvFilter(NULL), @@ -35,8 +34,8 @@ VideoCaptureDS::~VideoCaptureDS() { _mediaControl->Stop(); } if (_graphBuilder) { - if (_sinkFilter) - _graphBuilder->RemoveFilter(_sinkFilter); + if (sink_filter_) + _graphBuilder->RemoveFilter(sink_filter_); if (_captureFilter) _graphBuilder->RemoveFilter(_captureFilter); if (_dvFilter) @@ -46,7 +45,6 @@ VideoCaptureDS::~VideoCaptureDS() { RELEASE_AND_CLEAR(_outputCapturePin); RELEASE_AND_CLEAR(_captureFilter); // release the capture device - RELEASE_AND_CLEAR(_sinkFilter); RELEASE_AND_CLEAR(_dvFilter); RELEASE_AND_CLEAR(_mediaControl); @@ -101,20 +99,15 @@ int32_t VideoCaptureDS::Init(const char* deviceUniqueIdUTF8) { } // Create the sink filte used for receiving Captured frames. - _sinkFilter = new CaptureSinkFilter(SINK_FILTER_NAME, NULL, &hr, *this); - if (hr != S_OK) { - RTC_LOG(LS_INFO) << "Failed to create send filter"; - return -1; - } - _sinkFilter->AddRef(); + sink_filter_ = new ComRefCount(this); - hr = _graphBuilder->AddFilter(_sinkFilter, SINK_FILTER_NAME); + hr = _graphBuilder->AddFilter(sink_filter_, SINK_FILTER_NAME); if (FAILED(hr)) { RTC_LOG(LS_INFO) << "Failed to add the send filter to the graph."; return -1; } - _inputSendPin = GetInputPin(_sinkFilter); + _inputSendPin = GetInputPin(sink_filter_); if (!_inputSendPin) { RTC_LOG(LS_INFO) << "Failed to get input send pin"; return -1; @@ -164,6 +157,7 @@ int32_t VideoCaptureDS::StopCapture() { } return 0; } + bool VideoCaptureDS::CaptureStarted() { OAFilterState state = 0; HRESULT hr = _mediaControl->GetState(1000, &state); @@ -173,6 +167,7 @@ bool VideoCaptureDS::CaptureStarted() { RTC_LOG(LS_INFO) << "CaptureStarted " << state; return state == State_Running; } + int32_t VideoCaptureDS::CaptureSettings(VideoCaptureCapability& settings) { settings = _requestedCapability; return 0; @@ -234,7 +229,7 @@ int32_t VideoCaptureDS::SetCameraOutput( } // Set the sink filter to request this capability - _sinkFilter->SetMatchingMediaType(capability); + sink_filter_->SetRequestedCapability(capability); // Order the capture device to use this capability hr += streamConfig->SetFormat(pmt); @@ -279,6 +274,7 @@ int32_t VideoCaptureDS::DisconnectGraph() { } return 0; } + HRESULT VideoCaptureDS::ConnectDVCamera() { HRESULT hr = S_OK; @@ -320,7 +316,6 @@ HRESULT VideoCaptureDS::ConnectDVCamera() { RTC_LOG(LS_INFO) << "Failed to connect capture device to the send graph: " << hr; } - return hr; } return hr; } diff --git a/modules/video_capture/windows/video_capture_ds.h b/modules/video_capture/windows/video_capture_ds.h index fef9ecd110..0f01cfaf67 100644 --- a/modules/video_capture/windows/video_capture_ds.h +++ b/modules/video_capture/windows/video_capture_ds.h @@ -11,6 +11,7 @@ #ifndef MODULES_VIDEO_CAPTURE_MAIN_SOURCE_WINDOWS_VIDEO_CAPTURE_DS_H_ #define MODULES_VIDEO_CAPTURE_MAIN_SOURCE_WINDOWS_VIDEO_CAPTURE_DS_H_ +#include "api/scoped_refptr.h" #include "modules/video_capture/video_capture_impl.h" #include "modules/video_capture/windows/device_info_ds.h" @@ -59,7 +60,7 @@ class VideoCaptureDS : public VideoCaptureImpl { IBaseFilter* _captureFilter; IGraphBuilder* _graphBuilder; IMediaControl* _mediaControl; - CaptureSinkFilter* _sinkFilter; + rtc::scoped_refptr sink_filter_; IPin* _inputSendPin; IPin* _outputCapturePin;