diff options
author | perkj@google.com <perkj@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-30 08:29:43 +0000 |
---|---|---|
committer | perkj@google.com <perkj@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-30 08:29:43 +0000 |
commit | 845fa2b7c6b1ea8af251e2086a8d88204197b2f8 (patch) | |
tree | 7bcd37fd5732bda94bcb836d568f414f97deae1b /media | |
parent | 6400249fe0652f3bfe636e2f903ceb5cb9c66921 (diff) | |
download | chromium_src-845fa2b7c6b1ea8af251e2086a8d88204197b2f8.zip chromium_src-845fa2b7c6b1ea8af251e2086a8d88204197b2f8.tar.gz chromium_src-845fa2b7c6b1ea8af251e2086a8d88204197b2f8.tar.bz2 |
This is the VideoCaptureDevice implementation for windows.
DirectShow is used for controlling cameras and receiving video frames.
It implements all COM functions needed to support cameras using DirectShow and don't depend on Microsofts base classes for DirectShow filters.
It supersedes the patch http://codereview.chromium.org/6929064/.
Review URL: http://codereview.chromium.org/7229013
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@91096 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/media.gyp | 18 | ||||
-rw-r--r-- | media/video/capture/video_capture_device_unittest.cc | 261 | ||||
-rw-r--r-- | media/video/capture/win/filter_base_win.cc | 179 | ||||
-rw-r--r-- | media/video/capture/win/filter_base_win.h | 74 | ||||
-rw-r--r-- | media/video/capture/win/pin_base_win.cc | 281 | ||||
-rw-r--r-- | media/video/capture/win/pin_base_win.h | 108 | ||||
-rw-r--r-- | media/video/capture/win/sink_filter_observer_win.h | 24 | ||||
-rw-r--r-- | media/video/capture/win/sink_filter_win.cc | 53 | ||||
-rw-r--r-- | media/video/capture/win/sink_filter_win.h | 54 | ||||
-rw-r--r-- | media/video/capture/win/sink_input_pin_win.cc | 149 | ||||
-rw-r--r-- | media/video/capture/win/sink_input_pin_win.h | 49 | ||||
-rw-r--r-- | media/video/capture/win/video_capture_device_win.cc | 656 | ||||
-rw-r--r-- | media/video/capture/win/video_capture_device_win.h | 92 |
13 files changed, 1998 insertions, 0 deletions
diff --git a/media/media.gyp b/media/media.gyp index 07a622e..a7ed4ea 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -188,6 +188,17 @@ 'video/capture/linux/video_capture_device_linux.h', 'video/capture/video_capture.h', 'video/capture/video_capture_device.h', + 'video/capture/win/filter_base_win.cc', + 'video/capture/win/filter_base_win.h', + 'video/capture/win/pin_base_win.cc', + 'video/capture/win/pin_base_win.h', + 'video/capture/win/sink_filter_observer_win.h', + 'video/capture/win/sink_filter_win.cc', + 'video/capture/win/sink_filter_win.h', + 'video/capture/win/sink_input_pin_win.cc', + 'video/capture/win/sink_input_pin_win.h', + 'video/capture/win/video_capture_device_win.cc', + 'video/capture/win/video_capture_device_win.h', 'video/capture/video_capture_device_dummy.cc', 'video/capture/video_capture_device_dummy.h', 'video/capture/video_capture_types.h', @@ -251,6 +262,12 @@ ], }, }], + ['OS=="win"', { + 'sources!': [ + 'video/capture/video_capture_device_dummy.cc', + 'video/capture/video_capture_device_dummy.h', + ], + }], ], }, { @@ -425,6 +442,7 @@ 'filters/file_data_source_unittest.cc', 'filters/rtc_video_decoder_unittest.cc', 'filters/video_renderer_base_unittest.cc', + 'video/capture/video_capture_device_unittest.cc', 'video/ffmpeg_video_decode_engine_unittest.cc', 'webm/cluster_builder.cc', 'webm/cluster_builder.h', diff --git a/media/video/capture/video_capture_device_unittest.cc b/media/video/capture/video_capture_device_unittest.cc new file mode 100644 index 0000000..d856b30 --- /dev/null +++ b/media/video/capture/video_capture_device_unittest.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "media/video/capture/fake_video_capture_device.h" +#include "media/video/capture/video_capture_device.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::AtLeast; + +namespace media { +const int kWaitTime = 3000; + +class MockFrameObserver: public media::VideoCaptureDevice::EventHandler { + public: + MOCK_METHOD0(OnErr, void()); + MOCK_METHOD3(OnFrameInfo, void(int width, int height, int frame_rate)); + + explicit MockFrameObserver(base::WaitableEvent* wait_event) + : wait_event_(wait_event) {} + + virtual void OnError() OVERRIDE { + OnErr(); + } + + virtual void OnFrameInfo( + const VideoCaptureDevice::Capability& info) OVERRIDE { + OnFrameInfo(info.width, info.height, info.frame_rate); + } + + virtual void OnIncomingCapturedFrame(const uint8* data, int length, + base::Time timestamp) OVERRIDE { + wait_event_->Signal(); + } + + private: + base::WaitableEvent* wait_event_; +}; + +class VideoCaptureDeviceTest : public testing::Test { + public: + VideoCaptureDeviceTest(): wait_event_(false, false) { } + + protected: + virtual void SetUp() { + frame_observer_.reset(new MockFrameObserver(&wait_event_)); + } + + virtual void TearDown() { + } + + base::WaitableEvent wait_event_; + scoped_ptr<MockFrameObserver> frame_observer_; + VideoCaptureDevice::Names names_; +}; + +TEST_F(VideoCaptureDeviceTest, OpenInvalidDevice) { + VideoCaptureDevice::Name device_name; + device_name.device_name = "jibberish"; + device_name.unique_id = "jibberish"; + VideoCaptureDevice* device = VideoCaptureDevice::Create(device_name); + EXPECT_TRUE(device == NULL); +} + +TEST_F(VideoCaptureDeviceTest, CaptureVGA) { + VideoCaptureDevice::GetDeviceNames(&names_); + // Make sure there are more than 0 cameras. + if (!names_.size()) { + LOG(WARNING) << "No camera available. Exiting test."; + return; + } + + scoped_ptr<VideoCaptureDevice> device( + VideoCaptureDevice::Create(names_.front())); + ASSERT_FALSE(device.get() == NULL); + + // Get info about the new resolution. + EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, 30)) + .Times(1); + + EXPECT_CALL(*frame_observer_, OnErr()) + .Times(0); + + device->Allocate(640, 480, 30, frame_observer_.get()); + device->Start(); + // Wait for 3s or for captured frame. + EXPECT_TRUE(wait_event_.TimedWait( + base::TimeDelta::FromMilliseconds(kWaitTime))); + device->Stop(); + device->DeAllocate(); +} + +TEST_F(VideoCaptureDeviceTest, Capture720p) { + VideoCaptureDevice::GetDeviceNames(&names_); + // Make sure there are more than 0 cameras. + if (!names_.size()) { + LOG(WARNING) << "No camera available. Exiting test."; + return; + } + + scoped_ptr<VideoCaptureDevice> device( + VideoCaptureDevice::Create(names_.front())); + ASSERT_FALSE(device.get() == NULL); + + // Get info about the new resolution. + // We don't care about the resulting resolution or frame rate as it might + // be different from one machine to the next. + EXPECT_CALL(*frame_observer_, OnFrameInfo(_, _, _)) + .Times(1); + + EXPECT_CALL(*frame_observer_, OnErr()) + .Times(0); + + device->Allocate(1280, 720, 30, frame_observer_.get()); + device->Start(); + // Get captured video frames. + EXPECT_TRUE(wait_event_.TimedWait( + base::TimeDelta::FromMilliseconds(kWaitTime))); + device->Stop(); + device->DeAllocate(); +} + +TEST_F(VideoCaptureDeviceTest, AllocateSameCameraTwice) { + VideoCaptureDevice::GetDeviceNames(&names_); + if (!names_.size()) { + LOG(WARNING) << "No camera available. Exiting test."; + return; + } + scoped_ptr<VideoCaptureDevice> device1( + VideoCaptureDevice::Create(names_.front())); + ASSERT_TRUE(device1.get() != NULL); + + scoped_ptr<VideoCaptureDevice> device2( + VideoCaptureDevice::Create(names_.front())); + ASSERT_TRUE(device2.get() != NULL); + + // 1. Get info about the new resolution on the first allocated camera + EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, 30)); + + device1->Allocate(640, 480, 30, frame_observer_.get()); + + // 2. Error when trying to allocate the same camera again. + EXPECT_CALL(*frame_observer_, OnErr()); + device2->Allocate(640, 480, 30, frame_observer_.get()); + + device1->DeAllocate(); + device2->DeAllocate(); +} + +TEST_F(VideoCaptureDeviceTest, AllocateBadSize) { + VideoCaptureDevice::GetDeviceNames(&names_); + if (!names_.size()) { + LOG(WARNING) << "No camera available. Exiting test."; + return; + } + scoped_ptr<VideoCaptureDevice> device( + VideoCaptureDevice::Create(names_.front())); + ASSERT_TRUE(device.get() != NULL); + + EXPECT_CALL(*frame_observer_, OnErr()) + .Times(0); + + // get info about the new resolution + EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480 , _)) + .Times(AtLeast(1)); + + device->Allocate(637, 472, 35, frame_observer_.get()); + device->DeAllocate(); +} + +TEST_F(VideoCaptureDeviceTest, ReAllocateCamera) { + VideoCaptureDevice::GetDeviceNames(&names_); + if (!names_.size()) { + LOG(WARNING) << "No camera available. Exiting test."; + return; + } + scoped_ptr<VideoCaptureDevice> device( + VideoCaptureDevice::Create(names_.front())); + ASSERT_TRUE(device.get() != NULL); + EXPECT_CALL(*frame_observer_, OnErr()) + .Times(0); + // get info about the new resolution + EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, _)); + + EXPECT_CALL(*frame_observer_, OnFrameInfo(320, 240, _)); + + device->Allocate(640, 480, 30, frame_observer_.get()); + device->Start(); + // Nothing shall happen. + device->Allocate(1280, 1024, 30, frame_observer_.get()); + device->DeAllocate(); + // Allocate new size 320, 240 + device->Allocate(320, 240, 30, frame_observer_.get()); + + device->Start(); + // Get captured video frames. + EXPECT_TRUE(wait_event_.TimedWait( + base::TimeDelta::FromMilliseconds(kWaitTime))); + device->Stop(); + device->DeAllocate(); +} + +TEST_F(VideoCaptureDeviceTest, DeAllocateCameraWhileRunning) { + VideoCaptureDevice::GetDeviceNames(&names_); + if (!names_.size()) { + LOG(WARNING) << "No camera available. Exiting test."; + return; + } + scoped_ptr<VideoCaptureDevice> device( + VideoCaptureDevice::Create(names_.front())); + ASSERT_TRUE(device.get() != NULL); + + EXPECT_CALL(*frame_observer_, OnErr()) + .Times(0); + // Get info about the new resolution. + EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, 30)); + + device->Allocate(640, 480, 30, frame_observer_.get()); + + device->Start(); + // Get captured video frames. + EXPECT_TRUE(wait_event_.TimedWait( + base::TimeDelta::FromMilliseconds(kWaitTime))); + device->DeAllocate(); +} + +TEST_F(VideoCaptureDeviceTest, TestFakeCapture) { + VideoCaptureDevice::Names names; + + FakeVideoCaptureDevice::GetDeviceNames(&names); + + ASSERT_GT(static_cast<int>(names.size()), 0); + + scoped_ptr<VideoCaptureDevice> device( + FakeVideoCaptureDevice::Create(names.front())); + ASSERT_TRUE(device.get() != NULL); + + // Get info about the new resolution. + EXPECT_CALL(*frame_observer_, OnFrameInfo(640, 480, 30)) + .Times(1); + + EXPECT_CALL(*frame_observer_, OnErr()) + .Times(0); + + device->Allocate(640, 480, 30, frame_observer_.get()); + + device->Start(); + EXPECT_TRUE(wait_event_.TimedWait( + base::TimeDelta::FromMilliseconds(kWaitTime))); + device->Stop(); + device->DeAllocate(); +} + +}; // namespace media diff --git a/media/video/capture/win/filter_base_win.cc b/media/video/capture/win/filter_base_win.cc new file mode 100644 index 0000000..89309df --- /dev/null +++ b/media/video/capture/win/filter_base_win.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/video/capture/win/filter_base_win.h" + +#pragma comment(lib, "strmiids.lib") + +namespace media { + +// Implement IEnumPins. +class PinEnumerator + : public IEnumPins, + public base::RefCounted<PinEnumerator> { + public: + explicit PinEnumerator(FilterBase* filter) + : filter_(filter), + index_(0) { + } + + ~PinEnumerator() { + } + + // IUnknown implementation. + STDMETHOD(QueryInterface)(REFIID iid, void** object_ptr) { + if (iid == IID_IEnumPins || iid == IID_IUnknown) { + AddRef(); + *object_ptr = static_cast<IEnumPins*>(this); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHOD_(ULONG, AddRef)() { + base::RefCounted<PinEnumerator>::AddRef(); + return 1; + } + + STDMETHOD_(ULONG, Release)() { + base::RefCounted<PinEnumerator>::Release(); + return 1; + } + + // Implement IEnumPins. + STDMETHOD(Next)(ULONG count, IPin** pins, ULONG* fetched) { + ULONG pins_fetched = 0; + while (pins_fetched < count && filter_->NoOfPins() > index_) { + IPin* pin = filter_->GetPin(index_++); + pin->AddRef(); + pins[pins_fetched++] = pin; + } + + if (fetched) + *fetched = pins_fetched; + + return pins_fetched == count ? S_OK : S_FALSE; + } + + STDMETHOD(Skip)(ULONG count) { + if (filter_->NoOfPins()- index_ > count) { + index_ += count; + return S_OK; + } + index_ = 0; + return S_FALSE; + } + + STDMETHOD(Reset)() { + index_ = 0; + return S_OK; + } + + STDMETHOD(Clone)(IEnumPins** clone) { + PinEnumerator* pin_enum = new PinEnumerator(filter_); + if (!pin_enum) + return E_OUTOFMEMORY; + pin_enum->AddRef(); + pin_enum->index_ = index_; + *clone = pin_enum; + return S_OK; + } + + private: + scoped_refptr<FilterBase> filter_; + size_t index_; +}; + +FilterBase::FilterBase() : state_(State_Stopped) { +} + +FilterBase::~FilterBase() { +} + +STDMETHODIMP FilterBase::EnumPins(IEnumPins** enum_pins) { + *enum_pins = new PinEnumerator(this); + (*enum_pins)->AddRef(); + return S_OK; +} + +STDMETHODIMP FilterBase::FindPin(LPCWSTR id, IPin** pin) { + return E_NOTIMPL; +} + +STDMETHODIMP FilterBase::QueryFilterInfo(FILTER_INFO* info) { + info->pGraph = owning_graph_; + info->achName[0] = L'\0'; + if (info->pGraph) + info->pGraph->AddRef(); + return S_OK; +} + +STDMETHODIMP FilterBase::JoinFilterGraph(IFilterGraph* graph, LPCWSTR name) { + owning_graph_ = graph; + return S_OK; +} + +STDMETHODIMP FilterBase::QueryVendorInfo(LPWSTR *pVendorInfo) { + return S_OK; +} + +// Implement IMediaFilter. +STDMETHODIMP FilterBase::Stop() { + state_ = State_Stopped; + return S_OK; +} + +STDMETHODIMP FilterBase::Pause() { + state_ = State_Paused; + return S_OK; +} + +STDMETHODIMP FilterBase::Run(REFERENCE_TIME start) { + state_ = State_Running; + return S_OK; +} + +STDMETHODIMP FilterBase::GetState(DWORD msec_timeout, FILTER_STATE* state) { + *state = state_; + return S_OK; +} + +STDMETHODIMP FilterBase::SetSyncSource(IReferenceClock* clock) { + return S_OK; +} + +STDMETHODIMP FilterBase::GetSyncSource(IReferenceClock** clock) { + return E_NOTIMPL; +} + +// Implement from IPersistent. +STDMETHODIMP FilterBase::GetClassID(CLSID* class_id) { + NOTREACHED(); + return E_NOTIMPL; +} + +// Implement IUnknown. +STDMETHODIMP FilterBase::QueryInterface(REFIID id, void** object_ptr) { + if (id == IID_IMediaFilter || id == IID_IUnknown) { + *object_ptr = static_cast<IMediaFilter*>(this); + } else if (id == IID_IPersist) { + *object_ptr = static_cast<IPersist*>(this); + } else { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG STDMETHODCALLTYPE FilterBase::AddRef() { + base::RefCounted<FilterBase>::AddRef(); + return 1; +} + +ULONG STDMETHODCALLTYPE FilterBase::Release() { + base::RefCounted<FilterBase>::Release(); + return 1; +} + +} // namespace media diff --git a/media/video/capture/win/filter_base_win.h b/media/video/capture/win/filter_base_win.h new file mode 100644 index 0000000..3f5d465 --- /dev/null +++ b/media/video/capture/win/filter_base_win.h @@ -0,0 +1,74 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implement a simple base class for DirectShow filters. It may only be used in +// a single threaded apartment. + +#ifndef MEDIA_VIDEO_CAPTURE_WIN_FILTER_BASE_WIN_H_ +#define MEDIA_VIDEO_CAPTURE_WIN_FILTER_BASE_WIN_H_ +#pragma once + +// Avoid including strsafe.h via dshow as it will cause build warnings. +#define NO_DSHOW_STRSAFE +#include <dshow.h> + +#include "base/memory/ref_counted.h" +#include "base/win/scoped_comptr.h" + +namespace media { + +class FilterBase + : public IBaseFilter, + public base::RefCounted<FilterBase> { + public: + FilterBase(); + virtual ~FilterBase(); + + // Number of pins connected to this filter. + virtual size_t NoOfPins() = 0; + // Returns the IPin interface pin no index. + virtual IPin* GetPin(int index) = 0; + + // Inherited from IUnknown. + STDMETHOD(QueryInterface)(REFIID id, void** object_ptr); + STDMETHOD_(ULONG, AddRef)(); + STDMETHOD_(ULONG, Release)(); + + // Inherited from IBaseFilter. + STDMETHOD(EnumPins)(IEnumPins** enum_pins); + + STDMETHOD(FindPin)(LPCWSTR id, IPin** pin); + + STDMETHOD(QueryFilterInfo)(FILTER_INFO* info); + + STDMETHOD(JoinFilterGraph)(IFilterGraph* graph, LPCWSTR name); + + STDMETHOD(QueryVendorInfo)(LPWSTR* vendor_info); + + // Inherited from IMediaFilter. + STDMETHOD(Stop)(); + + STDMETHOD(Pause)(); + + STDMETHOD(Run)(REFERENCE_TIME start); + + STDMETHOD(GetState)(DWORD msec_timeout, FILTER_STATE* state); + + STDMETHOD(SetSyncSource)(IReferenceClock* clock); + + STDMETHOD(GetSyncSource)(IReferenceClock** clock); + + // Inherited from IPersistent. + STDMETHOD(GetClassID)(CLSID* class_id) = 0; + + private: + FILTER_STATE state_; + base::win::ScopedComPtr<IFilterGraph> owning_graph_; + + DISALLOW_COPY_AND_ASSIGN(FilterBase); +}; + +} // namespace media + +#endif // MEDIA_VIDEO_CAPTURE_WIN_FILTER_BASE_WIN_H_ diff --git a/media/video/capture/win/pin_base_win.cc b/media/video/capture/win/pin_base_win.cc new file mode 100644 index 0000000..bfb062b --- /dev/null +++ b/media/video/capture/win/pin_base_win.cc @@ -0,0 +1,281 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/video/capture/win/pin_base_win.h" + +#include "base/logging.h" + +namespace media { + +// Implement IEnumPins. +class TypeEnumerator + : public IEnumMediaTypes, + public base::RefCounted<TypeEnumerator> { + public: + explicit TypeEnumerator(PinBase* pin) + : pin_(pin), + index_(0) { + } + + ~TypeEnumerator() { + } + + // Implement from IUnknown. + STDMETHOD(QueryInterface)(REFIID iid, void** object_ptr) { + if (iid == IID_IEnumMediaTypes || iid == IID_IUnknown) { + AddRef(); + *object_ptr = static_cast<IEnumMediaTypes*>(this); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHOD_(ULONG, AddRef)() { + base::RefCounted<TypeEnumerator>::AddRef(); + return 1; + } + + STDMETHOD_(ULONG, Release)() { + base::RefCounted<TypeEnumerator>::Release(); + return 1; + } + + // Implement IEnumMediaTypes. + STDMETHOD(Next)(ULONG count, AM_MEDIA_TYPE** types, ULONG* fetched) { + ULONG types_fetched = 0; + + while (types_fetched < count) { + // Allocate AM_MEDIA_TYPE that we will store the media type in. + AM_MEDIA_TYPE* type = reinterpret_cast<AM_MEDIA_TYPE*>(CoTaskMemAlloc( + sizeof(AM_MEDIA_TYPE))); + if (!type) { + FreeAllocatedMediaTypes(types_fetched, types); + return E_OUTOFMEMORY; + } + ZeroMemory(type, sizeof(AM_MEDIA_TYPE)); + + // Allocate a VIDEOINFOHEADER and connect it to the AM_MEDIA_TYPE. + type->cbFormat = sizeof(VIDEOINFOHEADER); + BYTE *format = reinterpret_cast<BYTE*>(CoTaskMemAlloc( + sizeof(VIDEOINFOHEADER))); + if (!format) { + CoTaskMemFree(type); + FreeAllocatedMediaTypes(types_fetched, types); + return E_OUTOFMEMORY; + } + type->pbFormat = format; + // Get the media type from the pin. + if (pin_->GetValidMediaType(index_++, type)) { + types[types_fetched++] = type; + } else { + CoTaskMemFree(format); + CoTaskMemFree(type); + break; + } + } + + if (fetched) + *fetched = types_fetched; + + return types_fetched == count ? S_OK : S_FALSE; + } + + STDMETHOD(Skip)(ULONG count) { + index_ += count; + return S_OK; + } + + STDMETHOD(Reset)() { + index_ = 0; + return S_OK; + } + + STDMETHOD(Clone)(IEnumMediaTypes** clone) { + TypeEnumerator* type_enum = new TypeEnumerator(pin_); + if (!type_enum) + return E_OUTOFMEMORY; + type_enum->AddRef(); + type_enum->index_ = index_; + *clone = type_enum; + return S_OK; + } + + private: + void FreeAllocatedMediaTypes(ULONG allocated, AM_MEDIA_TYPE** types) { + for (ULONG i = 0; i < allocated; ++i) { + CoTaskMemFree(types[i]->pbFormat); + CoTaskMemFree(types[i]); + } + } + + scoped_refptr<PinBase> pin_; + int index_; +}; + +PinBase::PinBase(IBaseFilter* owner) + : owner_(owner) { +} + +PinBase::~PinBase() { +} + +void PinBase::SetOwner(IBaseFilter* owner) { + owner_ = owner; +} + +// Called on an output pin to and establish a +// connection. +STDMETHODIMP PinBase::Connect(IPin* receive_pin, + const AM_MEDIA_TYPE* media_type) { + if (!receive_pin || !media_type) + return E_POINTER; + + current_media_type_ = *media_type; + receive_pin->AddRef(); + connected_pin_.Attach(receive_pin); + HRESULT hr = receive_pin->ReceiveConnection(this, media_type); + + return hr; +} + +// Called from an output pin on an input pin to and establish a +// connection. +STDMETHODIMP PinBase::ReceiveConnection(IPin* connector, + const AM_MEDIA_TYPE* media_type) { + if (!IsMediaTypeValid(media_type)) + return VFW_E_TYPE_NOT_ACCEPTED; + + current_media_type_ = current_media_type_; + connector->AddRef(); + connected_pin_.Attach(connector); + return S_OK; +} + +STDMETHODIMP PinBase::Disconnect() { + if (!connected_pin_) + return S_FALSE; + + connected_pin_.Release(); + return S_OK; +} + +STDMETHODIMP PinBase::ConnectedTo(IPin** pin) { + *pin = connected_pin_; + if (!connected_pin_) + return VFW_E_NOT_CONNECTED; + + connected_pin_.get()->AddRef(); + return S_OK; +} + +STDMETHODIMP PinBase::ConnectionMediaType(AM_MEDIA_TYPE* media_type) { + if (!connected_pin_) + return VFW_E_NOT_CONNECTED; + *media_type = current_media_type_; + return S_OK; +} + +STDMETHODIMP PinBase::QueryPinInfo(PIN_INFO* info) { + info->dir = PINDIR_INPUT; + info->pFilter = owner_; + if (owner_) + owner_->AddRef(); + info->achName[0] = L'\0'; + + return S_OK; +} + +STDMETHODIMP PinBase::QueryDirection(PIN_DIRECTION* pin_dir) { + *pin_dir = PINDIR_INPUT; + return S_OK; +} + +STDMETHODIMP PinBase::QueryId(LPWSTR* id) { + NOTREACHED(); + return E_OUTOFMEMORY; +} + +STDMETHODIMP PinBase::QueryAccept(const AM_MEDIA_TYPE* media_type) { + return S_FALSE; +} + +STDMETHODIMP PinBase::EnumMediaTypes(IEnumMediaTypes** types) { + *types = new TypeEnumerator(this); + (*types)->AddRef(); + return S_OK; +} + +STDMETHODIMP PinBase::QueryInternalConnections(IPin** pins, ULONG* no_pins) { + return E_NOTIMPL; +} + +STDMETHODIMP PinBase::EndOfStream() { + return S_OK; +} + +STDMETHODIMP PinBase::BeginFlush() { + return S_OK; +} + +STDMETHODIMP PinBase::EndFlush() { + return S_OK; +} + +STDMETHODIMP PinBase::NewSegment(REFERENCE_TIME start, + REFERENCE_TIME stop, + double rate) { + NOTREACHED(); + return E_NOTIMPL; +} + +// Inherited from IMemInputPin. +STDMETHODIMP PinBase::GetAllocator(IMemAllocator** allocator) { + return VFW_E_NO_ALLOCATOR; +} + +STDMETHODIMP PinBase::NotifyAllocator(IMemAllocator* allocator, + BOOL read_only) { + return S_OK; +} + +STDMETHODIMP PinBase::GetAllocatorRequirements( + ALLOCATOR_PROPERTIES* properties) { + return E_NOTIMPL; +} + +STDMETHODIMP PinBase::ReceiveMultiple(IMediaSample** samples, + long sample_count, + long* processed) { + NOTREACHED(); + return VFW_E_INVALIDMEDIATYPE; +} + +STDMETHODIMP PinBase::ReceiveCanBlock() { + return S_FALSE; +} + +// Inherited from IUnknown. +STDMETHODIMP PinBase::QueryInterface(REFIID id, void** object_ptr) { + if (id == IID_IPin || id == IID_IUnknown) { + *object_ptr = static_cast<IPin*>(this); + } else if (id == IID_IMemInputPin) { + *object_ptr = static_cast<IMemInputPin*>(this); + } else { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) PinBase::AddRef() { + base::RefCounted<PinBase>::AddRef(); + return 1; +} + +STDMETHODIMP_(ULONG) PinBase::Release() { + base::RefCounted<PinBase>::Release(); + return 1; +} + +} // namespace media diff --git a/media/video/capture/win/pin_base_win.h b/media/video/capture/win/pin_base_win.h new file mode 100644 index 0000000..ac84f2e --- /dev/null +++ b/media/video/capture/win/pin_base_win.h @@ -0,0 +1,108 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implement a simple base class for a DirectShow input pin. It may only be +// used in a single threaded apartment. + +#ifndef MEDIA_VIDEO_CAPTURE_WIN_PIN_BASE_WIN_H_ +#define MEDIA_VIDEO_CAPTURE_WIN_PIN_BASE_WIN_H_ +#pragma once + +// Avoid including strsafe.h via dshow as it will cause build warnings. +#define NO_DSHOW_STRSAFE +#include <dshow.h> + +#include "base/memory/ref_counted.h" +#include "base/win/scoped_comptr.h" + +namespace media { + +class PinBase + : public IPin, + public IMemInputPin, + public base::RefCounted<PinBase> { + public: + explicit PinBase(IBaseFilter* owner); + virtual ~PinBase(); + + // Function used for changing the owner. + // If the owner is deleted the owner should first call this function + // with owner = NULL. + void SetOwner(IBaseFilter* owner); + + // Checks if a media type is acceptable. This is called when this pin is + // connected to an output pin. Must return true if the media type is + // acceptable, false otherwise. + virtual bool IsMediaTypeValid(const AM_MEDIA_TYPE* media_type) = 0; + + // Enumerates valid media types. + virtual bool GetValidMediaType(int index, AM_MEDIA_TYPE* media_type) = 0; + + // Called when new media is received. Note that this is not on the same + // thread as where the pin is created. + STDMETHOD(Receive)(IMediaSample* sample) = 0; + + STDMETHOD(Connect)(IPin* receive_pin, const AM_MEDIA_TYPE* media_type); + + STDMETHOD(ReceiveConnection)(IPin* connector, + const AM_MEDIA_TYPE* media_type); + + STDMETHOD(Disconnect)(); + + STDMETHOD(ConnectedTo)(IPin** pin); + + STDMETHOD(ConnectionMediaType)(AM_MEDIA_TYPE* media_type); + + STDMETHOD(QueryPinInfo)(PIN_INFO* info); + + STDMETHOD(QueryDirection)(PIN_DIRECTION* pin_dir); + + STDMETHOD(QueryId)(LPWSTR* id); + + STDMETHOD(QueryAccept)(const AM_MEDIA_TYPE* media_type); + + STDMETHOD(EnumMediaTypes)(IEnumMediaTypes** types); + + STDMETHOD(QueryInternalConnections)(IPin** pins, ULONG* no_pins); + + STDMETHOD(EndOfStream)(); + + STDMETHOD(BeginFlush)(); + + STDMETHOD(EndFlush)(); + + STDMETHOD(NewSegment)(REFERENCE_TIME start, + REFERENCE_TIME stop, + double dRate); + + // Inherited from IMemInputPin. + STDMETHOD(GetAllocator)(IMemAllocator** allocator); + + STDMETHOD(NotifyAllocator)(IMemAllocator* allocator, BOOL read_only); + + STDMETHOD(GetAllocatorRequirements)(ALLOCATOR_PROPERTIES* properties); + + STDMETHOD(ReceiveMultiple)(IMediaSample** samples, + long sample_count, + long* processed); + STDMETHOD(ReceiveCanBlock)(); + + // Inherited from IUnknown. + STDMETHOD(QueryInterface)(REFIID id, void** object_ptr); + + STDMETHOD_(ULONG, AddRef)(); + + STDMETHOD_(ULONG, Release)(); + + private: + AM_MEDIA_TYPE current_media_type_; + base::win::ScopedComPtr<IPin> connected_pin_; + // owner_ is the filter owning this pin. We don't reference count it since + // that would create a circular reference count. + IBaseFilter* owner_; +}; + +} // namespace media + +#endif // MEDIA_VIDEO_CAPTURE_WIN_PIN_BASE_WIN_H_ diff --git a/media/video/capture/win/sink_filter_observer_win.h b/media/video/capture/win/sink_filter_observer_win.h new file mode 100644 index 0000000..13451b4 --- /dev/null +++ b/media/video/capture/win/sink_filter_observer_win.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Observer class of Sinkfilter. The implementor of this class receive video +// frames from the SinkFilter DirectShow filter. + +#ifndef MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_OBSERVER_WIN_H_ +#define MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_OBSERVER_WIN_H_ + +namespace media { + +class SinkFilterObserver { + public: + // SinkFilter will call this function with all frames delivered to it. + // buffer in only valid during this function call. + virtual void FrameReceived(const uint8* buffer, int length) = 0; + protected: + virtual ~SinkFilterObserver(); +}; + +} // namespace media + +#endif // MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_OBSERVER_WIN_H_ diff --git a/media/video/capture/win/sink_filter_win.cc b/media/video/capture/win/sink_filter_win.cc new file mode 100644 index 0000000..605d557 --- /dev/null +++ b/media/video/capture/win/sink_filter_win.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/video/capture/win/sink_filter_win.h" + +#include "base/logging.h" +#include "media/video/capture/win/sink_input_pin_win.h" + +// Define GUID for I420. This is the color format we would like to support but +// it is not defined in the DirectShow SDK. +// http://msdn.microsoft.com/en-us/library/dd757532.aspx +// 30323449-0000-0010-8000-00AA00389B71. +GUID kMediaSubTypeI420 = { + 0x30323449, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71} +}; + +namespace media { + +SinkFilterObserver::~SinkFilterObserver() {} + +SinkFilter::SinkFilter(SinkFilterObserver* observer) + : input_pin_(NULL) { + input_pin_ = new SinkInputPin(this, observer); +} + +SinkFilter::~SinkFilter() { + input_pin_->SetOwner(NULL); +} + +void SinkFilter::SetRequestedMediaCapability( + const VideoCaptureDevice::Capability& capability) { + input_pin_->SetRequestedMediaCapability(capability); +} + +const VideoCaptureDevice::Capability& SinkFilter::ResultingCapability() { + return input_pin_->ResultingCapability(); +} + +size_t SinkFilter::NoOfPins() { + return 1; +} + +IPin* SinkFilter::GetPin(int index) { + return index == 0 ? input_pin_ : NULL; +} + +STDMETHODIMP SinkFilter::GetClassID(CLSID* clsid) { + *clsid = __uuidof(SinkFilter); + return S_OK; +} + +} // namespace media diff --git a/media/video/capture/win/sink_filter_win.h b/media/video/capture/win/sink_filter_win.h new file mode 100644 index 0000000..5145cd4 --- /dev/null +++ b/media/video/capture/win/sink_filter_win.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implement a DirectShow sink filter used for receiving captured frames from +// a DirectShow Capture filter. + +#ifndef MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_WIN_H_ +#define MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_WIN_H_ + +#include <windows.h> + +#include "base/memory/scoped_ptr.h" +#include "media/video/capture/video_capture_device.h" +#include "media/video/capture/win/filter_base_win.h" +#include "media/video/capture/win/sink_filter_observer_win.h" + +// Define GUID for I420. This is the color format we would like to support but +// it is not defined in the DirectShow SDK. +// http://msdn.microsoft.com/en-us/library/dd757532.aspx +// 30323449-0000-0010-8000-00AA00389B71. +extern GUID kMediaSubTypeI420; + +namespace media { + +class SinkInputPin; + +class __declspec(uuid("88cdbbdc-a73b-4afa-acbf-15d5e2ce12c3")) + SinkFilter : public FilterBase { + public: + explicit SinkFilter(SinkFilterObserver* observer); + virtual ~SinkFilter(); + + void SetRequestedMediaCapability( + const VideoCaptureDevice::Capability& capability); + // Returns the capability that is negotiated when this + // filter is connected to a media filter. + const VideoCaptureDevice::Capability& ResultingCapability(); + + // Implement FilterBase. + virtual size_t NoOfPins(); + virtual IPin* GetPin(int index); + + STDMETHOD(GetClassID)(CLSID* clsid); + + private: + scoped_refptr<SinkInputPin> input_pin_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(SinkFilter); +}; + +} // namespace media + +#endif // MEDIA_VIDEO_CAPTURE_WIN_SINK_FILTER_WIN_H_ diff --git a/media/video/capture/win/sink_input_pin_win.cc b/media/video/capture/win/sink_input_pin_win.cc new file mode 100644 index 0000000..20a49d6 --- /dev/null +++ b/media/video/capture/win/sink_input_pin_win.cc @@ -0,0 +1,149 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/video/capture/win/sink_input_pin_win.h" + +// Avoid including strsafe.h via dshow as it will cause build warnings. +#define NO_DSHOW_STRSAFE +#include <dshow.h> + +#include "base/logging.h" + +namespace media { + +const REFERENCE_TIME kSecondsToReferenceTime = 10000000; + +SinkInputPin::SinkInputPin(IBaseFilter* filter, + SinkFilterObserver* observer) + : observer_(observer), + PinBase(filter) { +} + +SinkInputPin::~SinkInputPin() {} + +bool SinkInputPin::GetValidMediaType(int index, AM_MEDIA_TYPE* media_type) { + if (media_type->cbFormat < sizeof(VIDEOINFOHEADER)) + return false; + + VIDEOINFOHEADER* pvi = + reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); + + ZeroMemory(pvi, sizeof(VIDEOINFOHEADER)); + pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pvi->bmiHeader.biPlanes = 1; + pvi->bmiHeader.biClrImportant = 0; + pvi->bmiHeader.biClrUsed = 0; + if (requested_capability_.frame_rate > 0) { + pvi->AvgTimePerFrame = kSecondsToReferenceTime / + requested_capability_.frame_rate; + } + + media_type->majortype = MEDIATYPE_Video; + media_type->formattype = FORMAT_VideoInfo; + media_type->bTemporalCompression = FALSE; + + switch (index) { + case 0: { + pvi->bmiHeader.biCompression = MAKEFOURCC('I', '4', '2', '0'); + pvi->bmiHeader.biBitCount = 12; // bit per pixel + pvi->bmiHeader.biWidth = requested_capability_.width; + pvi->bmiHeader.biHeight = requested_capability_.height; + pvi->bmiHeader.biSizeImage = 3 * requested_capability_.height * + requested_capability_.width / 2; + media_type->subtype = kMediaSubTypeI420; + break; + } + case 1: { + pvi->bmiHeader.biCompression = MAKEFOURCC('Y', 'U', 'Y', '2'); + pvi->bmiHeader.biBitCount = 16; + pvi->bmiHeader.biWidth = requested_capability_.width; + pvi->bmiHeader.biHeight = requested_capability_.height; + pvi->bmiHeader.biSizeImage = 2 * requested_capability_.width * + requested_capability_.height; + media_type->subtype = MEDIASUBTYPE_YUY2; + break; + } + case 2: { + pvi->bmiHeader.biCompression = BI_RGB; + pvi->bmiHeader.biBitCount = 24; + pvi->bmiHeader.biWidth = requested_capability_.width; + pvi->bmiHeader.biHeight = requested_capability_.height; + pvi->bmiHeader.biSizeImage = 3 * requested_capability_.height * + requested_capability_.width; + media_type->subtype = MEDIASUBTYPE_RGB24; + break; + } + default: + return false; + } + + media_type->bFixedSizeSamples = TRUE; + media_type->lSampleSize = pvi->bmiHeader.biSizeImage; + return true; +} + +bool SinkInputPin::IsMediaTypeValid(const AM_MEDIA_TYPE* media_type) { + GUID type = media_type->majortype; + if (type != MEDIATYPE_Video) + return false; + + GUID format_type = media_type->formattype; + if (format_type != FORMAT_VideoInfo) + return false; + + // Check for the sub types we support. + GUID sub_type = media_type->subtype; + VIDEOINFOHEADER* pvi = + reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); + if (pvi == NULL) + return false; + + // Store the incoming width and height. + resulting_capability_.width = pvi->bmiHeader.biWidth; + resulting_capability_.height = abs(pvi->bmiHeader.biHeight); + if (pvi->AvgTimePerFrame > 0) { + resulting_capability_.frame_rate = + static_cast<int>(kSecondsToReferenceTime / pvi->AvgTimePerFrame); + } else { + resulting_capability_.frame_rate = requested_capability_.frame_rate; + } + if (sub_type == kMediaSubTypeI420 && + pvi->bmiHeader.biCompression == MAKEFOURCC('I', '4', '2', '0')) { + resulting_capability_.color = VideoCaptureDevice::kI420; + return true; // This format is acceptable. + } + if (sub_type == MEDIASUBTYPE_YUY2 && + pvi->bmiHeader.biCompression == MAKEFOURCC('Y', 'U', 'Y', '2')) { + resulting_capability_.color = VideoCaptureDevice::kYUY2; + return true; // This format is acceptable. + } + if (sub_type == MEDIASUBTYPE_RGB24 && + pvi->bmiHeader.biCompression == BI_RGB) { + resulting_capability_.color = VideoCaptureDevice::kRGB24; + return true; // This format is acceptable. + } + return false; +} + +HRESULT SinkInputPin::Receive(IMediaSample* sample) { + const int length = sample->GetActualDataLength(); + uint8* buffer = NULL; + if (FAILED(sample->GetPointer(&buffer))) + return S_FALSE; + + observer_->FrameReceived(buffer, length); + return S_OK; +} + +void SinkInputPin::SetRequestedMediaCapability( + const VideoCaptureDevice::Capability& capability) { + requested_capability_ = capability; + resulting_capability_ = VideoCaptureDevice::Capability(); +} + +const VideoCaptureDevice::Capability& SinkInputPin::ResultingCapability() { + return resulting_capability_; +} + +} // namespace media diff --git a/media/video/capture/win/sink_input_pin_win.h b/media/video/capture/win/sink_input_pin_win.h new file mode 100644 index 0000000..839661d --- /dev/null +++ b/media/video/capture/win/sink_input_pin_win.h @@ -0,0 +1,49 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implement a DirectShow input pin used for receiving captured frames from +// a DirectShow Capture filter. + +#ifndef MEDIA_VIDEO_CAPTURE_WIN_SINK_INPUT_PIN_WIN_H_ +#define MEDIA_VIDEO_CAPTURE_WIN_SINK_INPUT_PIN_WIN_H_ +#pragma once + +#include "media/video/capture/video_capture_device.h" +#include "media/video/capture/win/pin_base_win.h" +#include "media/video/capture/win/sink_filter_win.h" + +namespace media { + +// Const used for converting Seconds to REFERENCE_TIME. +extern const REFERENCE_TIME kSecondsToReferenceTime; + +// Input pin of the SinkFilter. +class SinkInputPin : public PinBase { + public: + SinkInputPin(IBaseFilter* filter, SinkFilterObserver* observer); + virtual ~SinkInputPin(); + + void SetRequestedMediaCapability( + const VideoCaptureDevice::Capability& capability); + // Returns the capability that is negotiated when this + // pin is connected to a media filter. + const VideoCaptureDevice::Capability& ResultingCapability(); + + // Implement PinBase. + virtual bool IsMediaTypeValid(const AM_MEDIA_TYPE* media_type); + virtual bool GetValidMediaType(int index, AM_MEDIA_TYPE* media_type); + + STDMETHOD(Receive)(IMediaSample* media_sample); + + private: + VideoCaptureDevice::Capability requested_capability_; + VideoCaptureDevice::Capability resulting_capability_; + SinkFilterObserver* observer_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(SinkInputPin); +}; + +} // namespace media + +#endif // MEDIA_VIDEO_CAPTURE_WIN_SINK_INPUT_PIN_WIN_H_ diff --git a/media/video/capture/win/video_capture_device_win.cc b/media/video/capture/win/video_capture_device_win.cc new file mode 100644 index 0000000..ee28a3e --- /dev/null +++ b/media/video/capture/win/video_capture_device_win.cc @@ -0,0 +1,656 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/video/capture/win/video_capture_device_win.h" + +#include <algorithm> +#include <list> + +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/win/scoped_variant.h" + +using base::win::ScopedComPtr; +using base::win::ScopedVariant; + +namespace { + +// Finds and creates a DirectShow Video Capture filter matching the device_name. +HRESULT GetDeviceFilter(const media::VideoCaptureDevice::Name& device_name, + IBaseFilter** filter) { + DCHECK(filter); + + ScopedComPtr<ICreateDevEnum> dev_enum; + HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC); + if (FAILED(hr)) + return hr; + + ScopedComPtr<IEnumMoniker> enum_moniker; + hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, + enum_moniker.Receive(), 0); + // CreateClassEnumerator returns S_FALSE on some Windows OS + // when no camera exist. Therefore the FAILED macro can't be used. + if (hr != S_OK) + return NULL; + + ScopedComPtr<IMoniker> moniker; + ScopedComPtr<IBaseFilter> capture_filter; + DWORD fetched = 0; + while (enum_moniker->Next(1, moniker.Receive(), &fetched) == S_OK) { + ScopedComPtr<IPropertyBag> prop_bag; + hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid()); + if (FAILED(hr)) { + moniker.Release(); + continue; + } + + // Find the description or friendly name. + static const wchar_t* kPropertyNames[] = { + L"DevicePath", L"Description", L"FriendlyName" + }; + ScopedVariant name; + for (size_t i = 0; + i < arraysize(kPropertyNames) && name.type() != VT_BSTR; ++i) { + prop_bag->Read(kPropertyNames[i], name.Receive(), 0); + } + if (name.type() == VT_BSTR) { + std::string device_path(base::SysWideToUTF8(V_BSTR(&name))); + if (device_path.compare(device_name.unique_id) == 0) { + // We have found the requested device + hr = moniker->BindToObject(0, 0, IID_IBaseFilter, + capture_filter.ReceiveVoid()); + DVPLOG_IF(2, FAILED(hr)) << "Failed to bind camera filter."; + break; + } + } + moniker.Release(); + } + + *filter = capture_filter.Detach(); + if (!*filter && SUCCEEDED(hr)) + hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + + return hr; +} + +// Check if a Pin matches a category. +bool PinMatchesCategory(IPin* pin, REFGUID category) { + DCHECK(pin); + bool found = false; + ScopedComPtr<IKsPropertySet> ks_property; + HRESULT hr = ks_property.QueryFrom(pin); + if (SUCCEEDED(hr)) { + GUID pin_category; + DWORD return_value; + hr = ks_property->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0, + &pin_category, sizeof(pin_category), &return_value); + if (SUCCEEDED(hr) && (return_value == sizeof(pin_category))) { + found = (pin_category == category) ? true : false; + } + } + return found; +} + +// Finds a IPin on a IBaseFilter given the direction an category. +HRESULT GetPin(IBaseFilter* filter, PIN_DIRECTION pin_dir, REFGUID category, + IPin** pin) { + DCHECK(pin); + ScopedComPtr<IEnumPins> pin_emum; + HRESULT hr = filter->EnumPins(pin_emum.Receive()); + if (pin_emum == NULL) + return hr; + + // Get first unconnected pin. + hr = pin_emum->Reset(); // set to first pin + while ((hr = pin_emum->Next(1, pin, NULL)) == S_OK) { + PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1); + hr = (*pin)->QueryDirection(&this_pin_dir); + if (pin_dir == this_pin_dir) { + if (category == GUID_NULL || PinMatchesCategory(*pin, category)) + return S_OK; + } + (*pin)->Release(); + } + + return E_FAIL; +} + +// Release the format block for a media type. +// http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx +void FreeMediaType(AM_MEDIA_TYPE* mt) { + if (mt->cbFormat != 0) { + CoTaskMemFree(mt->pbFormat); + mt->cbFormat = 0; + mt->pbFormat = NULL; + } + if (mt->pUnk != NULL) { + NOTREACHED(); + // pUnk should not be used. + mt->pUnk->Release(); + mt->pUnk = NULL; + } +} + +// Delete a media type structure that was allocated on the heap. +// http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx +void DeleteMediaType(AM_MEDIA_TYPE* mt) { + if (mt != NULL) { + FreeMediaType(mt); + CoTaskMemFree(mt); + } +} + +// Help structure used for comparing video capture capabilities. +struct ResolutionDiff { + int capability_index; + int diff_height; + int diff_width; + int diff_frame_rate; + media::VideoCaptureDevice::Format color; +}; + +bool CompareHeight(ResolutionDiff item1, ResolutionDiff item2) { + return abs(item1.diff_height) < abs(item2.diff_height); +} + +bool CompareWidth(ResolutionDiff item1, ResolutionDiff item2) { + return abs(item1.diff_width) < abs(item2.diff_width); +} + +bool CompareFrameRate(ResolutionDiff item1, ResolutionDiff item2) { + return abs(item1.diff_frame_rate) < abs(item2.diff_frame_rate); +} + +bool CompareColor(ResolutionDiff item1, ResolutionDiff item2) { + return (item1.color < item2.color); +} + +} // namespace + +namespace media { + +// Name of a fake DirectShow filter that exist on computers with +// GTalk installed. +static const char kGoogleCameraAdapter[] = "google camera adapter"; + +// Gets the names of all video capture devices connected to this computer. +void VideoCaptureDevice::GetDeviceNames(Names* device_names) { + DCHECK(device_names); + + base::win::ScopedCOMInitializer coinit; + ScopedComPtr<ICreateDevEnum> dev_enum; + HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC); + if (FAILED(hr)) + return; + + ScopedComPtr<IEnumMoniker> enum_moniker; + hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, + enum_moniker.Receive(), 0); + // CreateClassEnumerator returns S_FALSE on some Windows OS + // when no camera exist. Therefore the FAILED macro can't be used. + if (hr != S_OK) + return; + + device_names->clear(); + + // Enumerate all video capture devices. + ScopedComPtr<IMoniker> moniker; + int index = 0; + while (enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK) { + Name device; + ScopedComPtr<IPropertyBag> prop_bag; + hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid()); + if (FAILED(hr)) { + moniker.Release(); + continue; + } + + // Find the description or friendly name. + ScopedVariant name; + hr = prop_bag->Read(L"Description", name.Receive(), 0); + if (FAILED(hr)) + hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0); + + if (SUCCEEDED(hr) && name.type() == VT_BSTR) { + // Ignore all VFW drivers and the special Google Camera Adapter. + // Google Camera Adapter is not a real DirectShow camera device. + // VFW is very old Video for Windows drivers that can not be used. + const wchar_t* str_ptr = V_BSTR(&name); + const int name_length = arraysize(kGoogleCameraAdapter) - 1; + + if ((wcsstr(str_ptr, L"(VFW)") == NULL) && + lstrlenW(str_ptr) < name_length || + (!(LowerCaseEqualsASCII(str_ptr, str_ptr + name_length, + kGoogleCameraAdapter)))) { + device.device_name = base::SysWideToUTF8(str_ptr); + name.Reset(); + hr = prop_bag->Read(L"DevicePath", name.Receive(), 0); + if (FAILED(hr)) { + device.unique_id = device.device_name; + } else if (name.type() == VT_BSTR) { + device.unique_id = base::SysWideToUTF8(V_BSTR(&name)); + } + + device_names->push_back(device); + } + } + moniker.Release(); + } +} + +VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) { + VideoCaptureDeviceWin* self = new VideoCaptureDeviceWin(device_name); + if (self && self->Init()) + return self; + + delete self; + return NULL; +} + +VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name) + : device_name_(device_name), + state_(kIdle) { +} + +VideoCaptureDeviceWin::~VideoCaptureDeviceWin() { + if (media_control_) + media_control_->Stop(); + + if (graph_builder_) { + if (sink_filter_) { + graph_builder_->RemoveFilter(sink_filter_); + sink_filter_ = NULL; + } + + if (capture_filter_) + graph_builder_->RemoveFilter(capture_filter_); + + if (mjpg_filter_) + graph_builder_->RemoveFilter(mjpg_filter_); + } +} + +bool VideoCaptureDeviceWin::Init() { + HRESULT hr = GetDeviceFilter(device_name_, capture_filter_.Receive()); + if (!capture_filter_) { + DVLOG(2) << "Failed to create capture filter."; + return false; + } + + hr = GetPin(capture_filter_, PINDIR_OUTPUT, PIN_CATEGORY_CAPTURE, + output_capture_pin_.Receive()); + if (!output_capture_pin_) { + DVLOG(2) << "Failed to get capture output pin"; + return false; + } + + // Create the sink filter used for receiving Captured frames. + sink_filter_ = new SinkFilter(this); + if (sink_filter_ == NULL) { + DVLOG(2) << "Failed to create send filter"; + return false; + } + + input_sink_pin_ = sink_filter_->GetPin(0); + + hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(hr)) { + DVLOG(2) << "Failed to create graph builder."; + return false; + } + + hr = graph_builder_.QueryInterface(media_control_.Receive()); + if (FAILED(hr)) { + DVLOG(2) << "Failed to create media control builder."; + return false; + } + + hr = graph_builder_->AddFilter(capture_filter_, NULL); + if (FAILED(hr)) { + DVLOG(2) << "Failed to add the capture device to the graph."; + return false; + } + + hr = graph_builder_->AddFilter(sink_filter_, NULL); + if (FAILED(hr)) { + DVLOG(2)<< "Failed to add the send filter to the graph."; + return false; + } + + return CreateCapabilityMap(); +} + +void VideoCaptureDeviceWin::Allocate( + int width, + int height, + int frame_rate, + VideoCaptureDevice::EventHandler* observer) { + if (state_ != kIdle) + return; + + observer_ = observer; + // Get the camera capability that best match the requested resolution. + const int capability_index = GetBestMatchedCapability(width, height, + frame_rate); + Capability capability = capabilities_[capability_index]; + + // Reduce the frame rate if the requested frame rate is lower + // than the capability. + if (capability.frame_rate > frame_rate) + capability.frame_rate = frame_rate; + + AM_MEDIA_TYPE* pmt = NULL; + VIDEO_STREAM_CONFIG_CAPS caps; + + ScopedComPtr<IAMStreamConfig> stream_config; + HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive()); + if (FAILED(hr)) { + SetErrorState("Can't get the Capture format settings"); + return; + } + + // Get the windows capability from the capture device. + hr = stream_config->GetStreamCaps(capability_index, &pmt, + reinterpret_cast<BYTE*>(&caps)); + if (SUCCEEDED(hr)) { + if (pmt->formattype == FORMAT_VideoInfo) { + VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat); + if (capability.frame_rate > 0) + h->AvgTimePerFrame = kSecondsToReferenceTime / capability.frame_rate; + } + // Set the sink filter to request this capability. + sink_filter_->SetRequestedMediaCapability(capability); + // Order the capture device to use this capability. + hr = stream_config->SetFormat(pmt); + } + + if (FAILED(hr)) + SetErrorState("Failed to set capture device output format"); + + if (capability.color == VideoCaptureDevice::kMJPEG && !mjpg_filter_.get()) { + // Create MJPG filter if we need it. + hr = mjpg_filter_.CreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC); + + if (SUCCEEDED(hr)) { + GetPin(mjpg_filter_, PINDIR_INPUT, GUID_NULL, input_mjpg_pin_.Receive()); + GetPin(mjpg_filter_, PINDIR_OUTPUT, GUID_NULL, + output_mjpg_pin_.Receive()); + hr = graph_builder_->AddFilter(mjpg_filter_, NULL); + } + + if (FAILED(hr)) { + mjpg_filter_.Release(); + input_mjpg_pin_.Release(); + output_mjpg_pin_.Release(); + } + } + + if (capability.color == VideoCaptureDevice::kMJPEG && mjpg_filter_.get()) { + // Connect the camera to the MJPEG decoder. + hr = graph_builder_->ConnectDirect(output_capture_pin_, input_mjpg_pin_, + NULL); + // Connect the MJPEG filter to the Capture filter. + hr += graph_builder_->ConnectDirect(output_mjpg_pin_, input_sink_pin_, + NULL); + } else { + hr = graph_builder_->ConnectDirect(output_capture_pin_, input_sink_pin_, + NULL); + } + + if (FAILED(hr)) { + SetErrorState("Failed to connect the Capture graph."); + return; + } + + hr = media_control_->Pause(); + if (FAILED(hr)) { + SetErrorState("Failed to Pause the Capture device. " + "Is it already occupied?"); + return; + } + + // Get the capability back from the sink filter after the filter have been + // connected. + const Capability& used_capability = sink_filter_->ResultingCapability(); + observer_->OnFrameInfo(used_capability); + + state_ = kAllocated; +} + +void VideoCaptureDeviceWin::Start() { + if (state_ != kAllocated) + return; + + HRESULT hr = media_control_->Run(); + if (FAILED(hr)) { + SetErrorState("Failed to start the Capture device."); + return; + } + + state_ = kCapturing; +} + +void VideoCaptureDeviceWin::Stop() { + if (state_ != kCapturing) + return; + + HRESULT hr = media_control_->Stop(); + if (FAILED(hr)) { + SetErrorState("Failed to stop the capture graph."); + return; + } + + state_ = kAllocated; +} + +void VideoCaptureDeviceWin::DeAllocate() { + if (state_ == kIdle) + return; + + HRESULT hr = media_control_->Stop(); + graph_builder_->Disconnect(output_capture_pin_); + graph_builder_->Disconnect(input_sink_pin_); + + // If the _mjpg filter exist disconnect it even if it has not been used. + if (mjpg_filter_) { + graph_builder_->Disconnect(input_mjpg_pin_); + graph_builder_->Disconnect(output_mjpg_pin_); + } + + if (FAILED(hr)) { + SetErrorState("Failed to Stop the Capture device"); + return; + } + + state_ = kIdle; +} + +const VideoCaptureDevice::Name& VideoCaptureDeviceWin::device_name() { + return device_name_; +} + +// Implements SinkFilterObserver::SinkFilterObserver. +void VideoCaptureDeviceWin::FrameReceived(const uint8* buffer, + int length) { + observer_->OnIncomingCapturedFrame(buffer, length, base::Time::Now()); +} + +bool VideoCaptureDeviceWin::CreateCapabilityMap() { + ScopedComPtr<IAMStreamConfig> stream_config; + HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive()); + if (FAILED(hr)) { + DVLOG(2) << "Failed to get IAMStreamConfig interface from " + "capture device"; + return false; + } + + // Get interface used for getting the frame rate. + ScopedComPtr<IAMVideoControl> video_control; + hr = capture_filter_.QueryInterface(video_control.Receive()); + DVLOG_IF(2, FAILED(hr)) << "IAMVideoControl Interface NOT SUPPORTED"; + + AM_MEDIA_TYPE* media_type = NULL; + VIDEO_STREAM_CONFIG_CAPS caps; + int count, size; + + hr = stream_config->GetNumberOfCapabilities(&count, &size); + if (FAILED(hr)) { + DVLOG(2) << "Failed to GetNumberOfCapabilities"; + return false; + } + + for (int i = 0; i < count; ++i) { + hr = stream_config->GetStreamCaps(i, &media_type, + reinterpret_cast<BYTE*>(&caps)); + if (FAILED(hr)) { + DVLOG(2) << "Failed to GetStreamCaps"; + return false; + } + + if (media_type->majortype == MEDIATYPE_Video && + media_type->formattype == FORMAT_VideoInfo) { + Capability capability; + REFERENCE_TIME time_per_frame = 0; + + VIDEOINFOHEADER* h = + reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); + capability.width = h->bmiHeader.biWidth; + capability.height = h->bmiHeader.biHeight; + time_per_frame = h->AvgTimePerFrame; + + // Try to get the max frame rate from IAMVideoControl. + if (video_control.get()) { + LONGLONG* max_fps_ptr; + LONG list_size; + SIZE size; + size.cx = capability.width; + size.cy = capability.height; + + // GetFrameRateList doesn't return max frame rate always + // eg: Logitech Notebook. This may be due to a bug in that API + // because GetFrameRateList array is reversed in the above camera. So + // a util method written. Can't assume the first value will return + // the max fps. + hr = video_control->GetFrameRateList(output_capture_pin_, i, size, + &list_size, &max_fps_ptr); + + if (SUCCEEDED(hr) && list_size > 0) { + int min_time = *std::min_element(max_fps_ptr, + max_fps_ptr + list_size); + capability.frame_rate = (min_time > 0) ? + kSecondsToReferenceTime / min_time : 0; + } else { + // Get frame rate from VIDEOINFOHEADER. + capability.frame_rate = (time_per_frame > 0) ? + static_cast<int>(kSecondsToReferenceTime / time_per_frame) : 0; + } + } else { + // Get frame rate from VIDEOINFOHEADER since IAMVideoControl is + // not supported. + capability.frame_rate = (time_per_frame > 0) ? + static_cast<int>(kSecondsToReferenceTime / time_per_frame) : 0; + } + + // We can't switch MEDIATYPE :~(. + if (media_type->subtype == kMediaSubTypeI420) { + capability.color = VideoCaptureDevice::kI420; + } else if (media_type->subtype == MEDIASUBTYPE_IYUV) { + // This is identical to kI420. + capability.color = VideoCaptureDevice::kI420; + } else if (media_type->subtype == MEDIASUBTYPE_RGB24) { + capability.color = VideoCaptureDevice::kRGB24; + } else if (media_type->subtype == MEDIASUBTYPE_YUY2) { + capability.color = VideoCaptureDevice::kYUY2; + } else if (media_type->subtype == MEDIASUBTYPE_MJPG) { + capability.color = VideoCaptureDevice::kMJPEG; + } else { + WCHAR guid_str[128]; + StringFromGUID2(media_type->subtype, guid_str, arraysize(guid_str)); + DVLOG(2) << "Device support unknown media type " << guid_str; + continue; + } + capabilities_[i] = capability; + } + DeleteMediaType(media_type); + media_type = NULL; + } + + return capabilities_.size() > 0; +} + +// Loops through the list of capabilities and returns an index of the best +// matching capability. +// The algorithm prioritize height, width, frame rate and color format in that +// order. +int VideoCaptureDeviceWin::GetBestMatchedCapability(int requested_width, + int requested_height, + int requested_frame_rate) { + std::list<ResolutionDiff> diff_list; + + // Loop through the candidates to create a list of differentials between the + // requested resolution and the camera capability. + for (CapabilityMap::iterator iterator = capabilities_.begin(); + iterator != capabilities_.end(); + ++iterator) { + Capability capability = iterator->second; + + ResolutionDiff diff; + diff.capability_index = iterator->first; + diff.diff_width = capability.width - requested_width; + diff.diff_height = capability.height - requested_height; + diff.diff_frame_rate = capability.frame_rate - requested_frame_rate; + diff.color = capability.color; + diff_list.push_back(diff); + } + + // Sort the best height candidates. + diff_list.sort(&CompareHeight); + int best_diff = diff_list.front().diff_height; + for (std::list<ResolutionDiff>::iterator it = diff_list.begin(); + it != diff_list.end(); ++it) { + if (it->diff_height != best_diff) { + // Remove all candidates but the best. + diff_list.erase(it, diff_list.end()); + break; + } + } + + // Sort the best width candidates. + diff_list.sort(&CompareWidth); + best_diff = diff_list.front().diff_width; + for (std::list<ResolutionDiff>::iterator it = diff_list.begin(); + it != diff_list.end(); ++it) { + if (it->diff_width != best_diff) { + // Remove all candidates but the best. + diff_list.erase(it, diff_list.end()); + break; + } + } + + // Sort the best frame rate candidates. + diff_list.sort(&CompareFrameRate); + best_diff = diff_list.front().diff_frame_rate; + for (std::list<ResolutionDiff>::iterator it = diff_list.begin(); + it != diff_list.end(); ++it) { + if (it->diff_frame_rate != best_diff) { + diff_list.erase(it, diff_list.end()); + break; + } + } + + // Decide the best color format. + diff_list.sort(&CompareColor); + return diff_list.front().capability_index; +} + +void VideoCaptureDeviceWin::SetErrorState(const char* reason) { + DLOG(ERROR) << reason; + state_ = kError; + observer_->OnError(); +} + +} // namespace media diff --git a/media/video/capture/win/video_capture_device_win.h b/media/video/capture/win/video_capture_device_win.h new file mode 100644 index 0000000..392edae --- /dev/null +++ b/media/video/capture/win/video_capture_device_win.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Windows specific implementation of VideoCaptureDevice. +// DirectShow is used for capturing. DirectShow provide it's own threads +// for capturing. + +#ifndef MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_WIN_H_ +#define MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_WIN_H_ +#pragma once + +// Avoid including strsafe.h via dshow as it will cause build warnings. +#define NO_DSHOW_STRSAFE +#include <dshow.h> + +#include <map> +#include <string> + +#include "base/threading/thread.h" +#include "base/win/scoped_com_initializer.h" +#include "base/win/scoped_comptr.h" +#include "media/video/capture/video_capture_device.h" +#include "media/video/capture/win/sink_filter_win.h" +#include "media/video/capture/win/sink_input_pin_win.h" + +namespace media { + +class VideoCaptureDeviceWin + : public VideoCaptureDevice, + public SinkFilterObserver { + public: + explicit VideoCaptureDeviceWin(const Name& device_name); + virtual ~VideoCaptureDeviceWin(); + // Opens the device driver for this device. + // This function is used by the static VideoCaptureDevice::Create function. + bool Init(); + + // VideoCaptureDevice implementation. + virtual void Allocate(int width, + int height, + int frame_rate, + VideoCaptureDevice::EventHandler* observer); + virtual void Start(); + virtual void Stop(); + virtual void DeAllocate(); + virtual const Name& device_name(); + + private: + enum InternalState { + kIdle, // The device driver is opened but camera is not in use. + kAllocated, // The camera has been allocated and can be started. + kCapturing, // Video is being captured. + kError // Error accessing HW functions. + // User needs to recover by destroying the object. + }; + typedef std::map<int, Capability> CapabilityMap; + + // Implements SinkFilterObserver. + virtual void FrameReceived(const uint8* buffer, int length); + + bool CreateCapabilityMap(); + int GetBestMatchedCapability(int width, int height, int frame_rate); + void SetErrorState(const char* reason); + + base::win::ScopedCOMInitializer initialize_com_; + + Name device_name_; + InternalState state_; + VideoCaptureDevice::EventHandler* observer_; + + base::win::ScopedComPtr<IBaseFilter> capture_filter_; + base::win::ScopedComPtr<IGraphBuilder> graph_builder_; + base::win::ScopedComPtr<IMediaControl> media_control_; + base::win::ScopedComPtr<IPin> input_sink_pin_; + base::win::ScopedComPtr<IPin> output_capture_pin_; + // Used when using a MJPEG decoder. + base::win::ScopedComPtr<IBaseFilter> mjpg_filter_; + base::win::ScopedComPtr<IPin> input_mjpg_pin_; + base::win::ScopedComPtr<IPin> output_mjpg_pin_; + + scoped_refptr<SinkFilter> sink_filter_; + + // Map of all capabilities this device support. + CapabilityMap capabilities_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureDeviceWin); +}; + +} // namespace media + +#endif // MEDIA_VIDEO_CAPTURE_WIN_VIDEO_CAPTURE_DEVICE_WIN_H_ |