/* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/video_capture/windows/sink_filter_ds.h" #include // VIDEOINFOHEADER2 #include #include #include #include "rtc_base/arraysize.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/platform_thread.h" #include "rtc_base/string_utils.h" DEFINE_GUID(CLSID_SINKFILTER, 0x88cdbbdc, 0xa73b, 0x4afa, 0xac, 0xbf, 0x15, 0xd5, 0xe2, 0xce, 0x12, 0xc3); namespace webrtc { namespace videocapturemodule { namespace { // Simple enumeration implementation that enumerates over a single pin :-/ class EnumPins : public IEnumPins { public: EnumPins(IPin* pin) : pin_(pin) {} protected: virtual ~EnumPins() {} 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; } STDMETHOD(Clone)(IEnumPins** pins) { RTC_DCHECK_NOTREACHED(); return E_NOTIMPL; } STDMETHOD(Next)(ULONG count, IPin** pins, ULONG* fetched) { RTC_DCHECK(count > 0); RTC_DCHECK(pins); // fetched may be NULL. if (pos_ > 0) { if (fetched) *fetched = 0; return S_FALSE; } ++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_NOTREACHED(); 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), format_preference_order_( {// Default preferences, sorted by cost-to-convert-to-i420. VideoType::kI420, VideoType::kYUY2, VideoType::kRGB24, VideoType::kUYVY, VideoType::kMJPEG}) { // Use the preferred video type, if supported. auto it = std::find(format_preference_order_.begin(), format_preference_order_.end(), capability_.videoType); if (it != format_preference_order_.end()) { RTC_LOG(LS_INFO) << "Selected video type: " << *it; // Move it to the front of the list, if it isn't already there. if (it != format_preference_order_.begin()) { format_preference_order_.splice(format_preference_order_.begin(), format_preference_order_, it, std::next(it)); } } else { RTC_LOG(LS_WARNING) << "Unsupported video type: " << rtc::ToString( static_cast(capability_.videoType)) << ", using default preference list."; } } 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_NOTREACHED(); 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; for (ULONG i = 0; i < count && pos_ < static_cast(format_preference_order_.size()); ++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; // Set format information. auto format_it = std::next(format_preference_order_.begin(), pos_++); SetMediaInfoFromVideoType(*format_it, &vih->bmiHeader, media_type); 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); } return pos_ == static_cast(format_preference_order_.size()) ? S_FALSE : S_OK; } static void SetMediaInfoFromVideoType(VideoType video_type, BITMAPINFOHEADER* bitmap_header, AM_MEDIA_TYPE* media_type) { switch (video_type) { case VideoType::kI420: bitmap_header->biCompression = MAKEFOURCC('I', '4', '2', '0'); bitmap_header->biBitCount = 12; // bit per pixel media_type->subtype = MEDIASUBTYPE_I420; break; case VideoType::kYUY2: bitmap_header->biCompression = MAKEFOURCC('Y', 'U', 'Y', '2'); bitmap_header->biBitCount = 16; // bit per pixel media_type->subtype = MEDIASUBTYPE_YUY2; break; case VideoType::kRGB24: bitmap_header->biCompression = BI_RGB; bitmap_header->biBitCount = 24; // bit per pixel media_type->subtype = MEDIASUBTYPE_RGB24; break; case VideoType::kUYVY: bitmap_header->biCompression = MAKEFOURCC('U', 'Y', 'V', 'Y'); bitmap_header->biBitCount = 16; // bit per pixel media_type->subtype = MEDIASUBTYPE_UYVY; break; case VideoType::kMJPEG: bitmap_header->biCompression = MAKEFOURCC('M', 'J', 'P', 'G'); bitmap_header->biBitCount = 12; // bit per pixel media_type->subtype = MEDIASUBTYPE_MJPG; break; default: RTC_DCHECK_NOTREACHED(); } } STDMETHOD(Skip)(ULONG count) { RTC_DCHECK_NOTREACHED(); return E_NOTIMPL; } STDMETHOD(Reset)() { pos_ = 0; return S_OK; } int pos_ = 0; const VideoCaptureCapability capability_; std::list format_preference_order_; }; } // namespace CaptureInputPin::CaptureInputPin(CaptureSinkFilter* filter) { capture_checker_.Detach(); // 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_.Detach(); 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; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW 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_NOTREACHED(); 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; } COM_DECLSPEC_NOTHROW 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_NOTREACHED(); 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; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::QueryPinInfo(PIN_INFO* info) { RTC_DCHECK_RUN_ON(&main_checker_); *info = info_; if (info_.pFilter) info_.pFilter->AddRef(); return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::QueryDirection(PIN_DIRECTION* pin_dir) { RTC_DCHECK_RUN_ON(&main_checker_); *pin_dir = info_.dir; return S_OK; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::EnumMediaTypes(IEnumMediaTypes** types) { RTC_DCHECK_RUN_ON(&main_checker_); *types = new ComRefCount(requested_capability_); (*types)->AddRef(); return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::QueryInternalConnections(IPin** pins, ULONG* count) { return E_NOTIMPL; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::EndOfStream() { return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::BeginFlush() { RTC_DCHECK_RUN_ON(&main_checker_); flushing_ = true; return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::EndFlush() { RTC_DCHECK_RUN_ON(&main_checker_); flushing_ = false; runtime_error_ = false; return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::NewSegment(REFERENCE_TIME start, REFERENCE_TIME stop, double rate) { RTC_DCHECK_RUN_ON(&main_checker_); return S_OK; } COM_DECLSPEC_NOTHROW 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_.get(); allocator_->AddRef(); return S_OK; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES* props) { return E_NOTIMPL; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureInputPin::ReceiveCanBlock() { return S_FALSE; } // ---------------------------------------------------------------------------- CaptureSinkFilter::CaptureSinkFilter(VideoCaptureImpl* capture_observer) : input_pin_(new ComRefCount(this)), capture_observer_(capture_observer) {} CaptureSinkFilter::~CaptureSinkFilter() { RTC_DCHECK_RUN_ON(&main_checker_); } 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); } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::GetState(DWORD msecs, FILTER_STATE* state) { RTC_DCHECK_RUN_ON(&main_checker_); *state = state_; return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::SetSyncSource(IReferenceClock* clock) { RTC_DCHECK_RUN_ON(&main_checker_); return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::GetSyncSource(IReferenceClock** clock) { RTC_DCHECK_RUN_ON(&main_checker_); return E_NOTIMPL; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::Pause() { RTC_DCHECK_RUN_ON(&main_checker_); state_ = State_Paused; return S_OK; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::Stop() { RTC_DCHECK_RUN_ON(&main_checker_); if (state_ == State_Stopped) return S_OK; state_ = State_Stopped; input_pin_->OnFilterDeactivated(); return S_OK; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::EnumPins(IEnumPins** pins) { RTC_DCHECK_RUN_ON(&main_checker_); *pins = new ComRefCount(input_pin_.get()); (*pins)->AddRef(); return S_OK; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::QueryFilterInfo(FILTER_INFO* info) { RTC_DCHECK_RUN_ON(&main_checker_); *info = info_; if (info->pGraph) info->pGraph->AddRef(); return S_OK; } COM_DECLSPEC_NOTHROW 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::QueryVendorInfo(LPWSTR* vendor_info) { return E_NOTIMPL; } void CaptureSinkFilter::ProcessCapturedFrame( unsigned char* buffer, size_t length, const VideoCaptureCapability& frame_info) { // Called on the capture thread. capture_observer_->IncomingFrame(buffer, length, frame_info); } void CaptureSinkFilter::NotifyEvent(long code, LONG_PTR param1, LONG_PTR param2) { // Called on the capture thread. if (!sink_) 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; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown || riid == IID_IPersist || riid == IID_IBaseFilter) { *ppv = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } COM_DECLSPEC_NOTHROW STDMETHODIMP CaptureSinkFilter::GetClassID(CLSID* clsid) { *clsid = CLSID_SINKFILTER; return S_OK; } } // namespace videocapturemodule } // namespace webrtc