1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/video/capture/win/video_capture_device_mf_win.h"
6
7 #include <mfapi.h>
8 #include <mferror.h>
9
10 #include "base/memory/ref_counted.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/synchronization/waitable_event.h"
14 #include "base/win/scoped_co_mem.h"
15 #include "base/win/windows_version.h"
16 #include "media/video/capture/win/capability_list_win.h"
17
18 using base::win::ScopedCoMem;
19 using base::win::ScopedComPtr;
20
21 namespace media {
22
23 // In Windows device identifiers, the USB VID and PID are preceded by the string
24 // "vid_" or "pid_". The identifiers are each 4 bytes long.
25 const char kVidPrefix[] = "vid_"; // Also contains '\0'.
26 const char kPidPrefix[] = "pid_"; // Also contains '\0'.
27 const size_t kVidPidSize = 4;
28
GetFrameSize(IMFMediaType * type,gfx::Size * frame_size)29 static bool GetFrameSize(IMFMediaType* type, gfx::Size* frame_size) {
30 UINT32 width32, height32;
31 if (FAILED(MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width32, &height32)))
32 return false;
33 frame_size->SetSize(width32, height32);
34 return true;
35 }
36
GetFrameRate(IMFMediaType * type,int * frame_rate_numerator,int * frame_rate_denominator)37 static bool GetFrameRate(IMFMediaType* type,
38 int* frame_rate_numerator,
39 int* frame_rate_denominator) {
40 UINT32 numerator, denominator;
41 if (FAILED(MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &numerator,
42 &denominator))||
43 !denominator) {
44 return false;
45 }
46 *frame_rate_numerator = numerator;
47 *frame_rate_denominator = denominator;
48 return true;
49 }
50
FillCapabilitiesFromType(IMFMediaType * type,VideoCaptureCapabilityWin * capability)51 static bool FillCapabilitiesFromType(IMFMediaType* type,
52 VideoCaptureCapabilityWin* capability) {
53 GUID type_guid;
54 if (FAILED(type->GetGUID(MF_MT_SUBTYPE, &type_guid)) ||
55 !GetFrameSize(type, &capability->supported_format.frame_size) ||
56 !GetFrameRate(type,
57 &capability->frame_rate_numerator,
58 &capability->frame_rate_denominator) ||
59 !VideoCaptureDeviceMFWin::FormatFromGuid(type_guid,
60 &capability->supported_format.pixel_format)) {
61 return false;
62 }
63 capability->supported_format.frame_rate =
64 capability->frame_rate_numerator / capability->frame_rate_denominator;
65
66 return true;
67 }
68
FillCapabilities(IMFSourceReader * source,CapabilityList * capabilities)69 HRESULT FillCapabilities(IMFSourceReader* source,
70 CapabilityList* capabilities) {
71 DWORD stream_index = 0;
72 ScopedComPtr<IMFMediaType> type;
73 HRESULT hr;
74 while (SUCCEEDED(hr = source->GetNativeMediaType(
75 MF_SOURCE_READER_FIRST_VIDEO_STREAM, stream_index, type.Receive()))) {
76 VideoCaptureCapabilityWin capability(stream_index++);
77 if (FillCapabilitiesFromType(type, &capability))
78 capabilities->Add(capability);
79 type.Release();
80 }
81
82 if (capabilities->empty() && (SUCCEEDED(hr) || hr == MF_E_NO_MORE_TYPES))
83 hr = HRESULT_FROM_WIN32(ERROR_EMPTY);
84
85 return (hr == MF_E_NO_MORE_TYPES) ? S_OK : hr;
86 }
87
88
89 class MFReaderCallback FINAL
90 : public base::RefCountedThreadSafe<MFReaderCallback>,
91 public IMFSourceReaderCallback {
92 public:
MFReaderCallback(VideoCaptureDeviceMFWin * observer)93 MFReaderCallback(VideoCaptureDeviceMFWin* observer)
94 : observer_(observer), wait_event_(NULL) {
95 }
96
SetSignalOnFlush(base::WaitableEvent * event)97 void SetSignalOnFlush(base::WaitableEvent* event) {
98 wait_event_ = event;
99 }
100
STDMETHOD(QueryInterface)101 STDMETHOD(QueryInterface)(REFIID riid, void** object) {
102 if (riid != IID_IUnknown && riid != IID_IMFSourceReaderCallback)
103 return E_NOINTERFACE;
104 *object = static_cast<IMFSourceReaderCallback*>(this);
105 AddRef();
106 return S_OK;
107 }
108
STDMETHOD_(ULONG,AddRef)109 STDMETHOD_(ULONG, AddRef)() {
110 base::RefCountedThreadSafe<MFReaderCallback>::AddRef();
111 return 1U;
112 }
113
STDMETHOD_(ULONG,Release)114 STDMETHOD_(ULONG, Release)() {
115 base::RefCountedThreadSafe<MFReaderCallback>::Release();
116 return 1U;
117 }
118
STDMETHOD(OnReadSample)119 STDMETHOD(OnReadSample)(HRESULT status, DWORD stream_index,
120 DWORD stream_flags, LONGLONG time_stamp, IMFSample* sample) {
121 base::TimeTicks stamp(base::TimeTicks::Now());
122 if (!sample) {
123 observer_->OnIncomingCapturedData(NULL, 0, 0, stamp);
124 return S_OK;
125 }
126
127 DWORD count = 0;
128 sample->GetBufferCount(&count);
129
130 for (DWORD i = 0; i < count; ++i) {
131 ScopedComPtr<IMFMediaBuffer> buffer;
132 sample->GetBufferByIndex(i, buffer.Receive());
133 if (buffer) {
134 DWORD length = 0, max_length = 0;
135 BYTE* data = NULL;
136 buffer->Lock(&data, &max_length, &length);
137 observer_->OnIncomingCapturedData(data, length, 0, stamp);
138 buffer->Unlock();
139 }
140 }
141 return S_OK;
142 }
143
STDMETHOD(OnFlush)144 STDMETHOD(OnFlush)(DWORD stream_index) {
145 if (wait_event_) {
146 wait_event_->Signal();
147 wait_event_ = NULL;
148 }
149 return S_OK;
150 }
151
STDMETHOD(OnEvent)152 STDMETHOD(OnEvent)(DWORD stream_index, IMFMediaEvent* event) {
153 NOTIMPLEMENTED();
154 return S_OK;
155 }
156
157 private:
158 friend class base::RefCountedThreadSafe<MFReaderCallback>;
~MFReaderCallback()159 ~MFReaderCallback() {}
160
161 VideoCaptureDeviceMFWin* observer_;
162 base::WaitableEvent* wait_event_;
163 };
164
165 // static
FormatFromGuid(const GUID & guid,VideoPixelFormat * format)166 bool VideoCaptureDeviceMFWin::FormatFromGuid(const GUID& guid,
167 VideoPixelFormat* format) {
168 struct {
169 const GUID& guid;
170 const VideoPixelFormat format;
171 } static const kFormatMap[] = {
172 { MFVideoFormat_I420, PIXEL_FORMAT_I420 },
173 { MFVideoFormat_YUY2, PIXEL_FORMAT_YUY2 },
174 { MFVideoFormat_UYVY, PIXEL_FORMAT_UYVY },
175 { MFVideoFormat_RGB24, PIXEL_FORMAT_RGB24 },
176 { MFVideoFormat_ARGB32, PIXEL_FORMAT_ARGB },
177 { MFVideoFormat_MJPG, PIXEL_FORMAT_MJPEG },
178 { MFVideoFormat_YV12, PIXEL_FORMAT_YV12 },
179 };
180
181 for (int i = 0; i < arraysize(kFormatMap); ++i) {
182 if (kFormatMap[i].guid == guid) {
183 *format = kFormatMap[i].format;
184 return true;
185 }
186 }
187
188 return false;
189 }
190
GetModel() const191 const std::string VideoCaptureDevice::Name::GetModel() const {
192 const size_t vid_prefix_size = sizeof(kVidPrefix) - 1;
193 const size_t pid_prefix_size = sizeof(kPidPrefix) - 1;
194 const size_t vid_location = unique_id_.find(kVidPrefix);
195 if (vid_location == std::string::npos ||
196 vid_location + vid_prefix_size + kVidPidSize > unique_id_.size()) {
197 return "";
198 }
199 const size_t pid_location = unique_id_.find(kPidPrefix);
200 if (pid_location == std::string::npos ||
201 pid_location + pid_prefix_size + kVidPidSize > unique_id_.size()) {
202 return "";
203 }
204 std::string id_vendor =
205 unique_id_.substr(vid_location + vid_prefix_size, kVidPidSize);
206 std::string id_product =
207 unique_id_.substr(pid_location + pid_prefix_size, kVidPidSize);
208 return id_vendor + ":" + id_product;
209 }
210
VideoCaptureDeviceMFWin(const Name & device_name)211 VideoCaptureDeviceMFWin::VideoCaptureDeviceMFWin(const Name& device_name)
212 : name_(device_name), capture_(0) {
213 DetachFromThread();
214 }
215
~VideoCaptureDeviceMFWin()216 VideoCaptureDeviceMFWin::~VideoCaptureDeviceMFWin() {
217 DCHECK(CalledOnValidThread());
218 }
219
Init(const base::win::ScopedComPtr<IMFMediaSource> & source)220 bool VideoCaptureDeviceMFWin::Init(
221 const base::win::ScopedComPtr<IMFMediaSource>& source) {
222 DCHECK(CalledOnValidThread());
223 DCHECK(!reader_);
224
225 ScopedComPtr<IMFAttributes> attributes;
226 MFCreateAttributes(attributes.Receive(), 1);
227 DCHECK(attributes);
228
229 callback_ = new MFReaderCallback(this);
230 attributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback_.get());
231
232 return SUCCEEDED(MFCreateSourceReaderFromMediaSource(source, attributes,
233 reader_.Receive()));
234 }
235
AllocateAndStart(const VideoCaptureParams & params,scoped_ptr<VideoCaptureDevice::Client> client)236 void VideoCaptureDeviceMFWin::AllocateAndStart(
237 const VideoCaptureParams& params,
238 scoped_ptr<VideoCaptureDevice::Client> client) {
239 DCHECK(CalledOnValidThread());
240
241 base::AutoLock lock(lock_);
242
243 client_ = client.Pass();
244 DCHECK_EQ(capture_, false);
245
246 CapabilityList capabilities;
247 HRESULT hr = S_OK;
248 if (!reader_ || FAILED(hr = FillCapabilities(reader_, &capabilities))) {
249 OnError(hr);
250 return;
251 }
252
253 VideoCaptureCapabilityWin found_capability =
254 capabilities.GetBestMatchedFormat(
255 params.requested_format.frame_size.width(),
256 params.requested_format.frame_size.height(),
257 params.requested_format.frame_rate);
258
259 ScopedComPtr<IMFMediaType> type;
260 if (FAILED(hr = reader_->GetNativeMediaType(
261 MF_SOURCE_READER_FIRST_VIDEO_STREAM, found_capability.stream_index,
262 type.Receive())) ||
263 FAILED(hr = reader_->SetCurrentMediaType(
264 MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, type))) {
265 OnError(hr);
266 return;
267 }
268
269 if (FAILED(hr = reader_->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0,
270 NULL, NULL, NULL, NULL))) {
271 OnError(hr);
272 return;
273 }
274 capture_format_ = found_capability.supported_format;
275 capture_ = true;
276 }
277
StopAndDeAllocate()278 void VideoCaptureDeviceMFWin::StopAndDeAllocate() {
279 DCHECK(CalledOnValidThread());
280 base::WaitableEvent flushed(false, false);
281 const int kFlushTimeOutInMs = 1000;
282 bool wait = false;
283 {
284 base::AutoLock lock(lock_);
285 if (capture_) {
286 capture_ = false;
287 callback_->SetSignalOnFlush(&flushed);
288 HRESULT hr = reader_->Flush(MF_SOURCE_READER_ALL_STREAMS);
289 wait = SUCCEEDED(hr);
290 if (!wait) {
291 callback_->SetSignalOnFlush(NULL);
292 }
293 }
294 client_.reset();
295 }
296
297 // If the device has been unplugged, the Flush() won't trigger the event
298 // and a timeout will happen.
299 // TODO(tommi): Hook up the IMFMediaEventGenerator notifications API and
300 // do not wait at all after getting MEVideoCaptureDeviceRemoved event.
301 // See issue/226396.
302 if (wait)
303 flushed.TimedWait(base::TimeDelta::FromMilliseconds(kFlushTimeOutInMs));
304 }
305
OnIncomingCapturedData(const uint8 * data,int length,int rotation,const base::TimeTicks & time_stamp)306 void VideoCaptureDeviceMFWin::OnIncomingCapturedData(
307 const uint8* data,
308 int length,
309 int rotation,
310 const base::TimeTicks& time_stamp) {
311 base::AutoLock lock(lock_);
312 if (data && client_.get()) {
313 client_->OnIncomingCapturedData(
314 data, length, capture_format_, rotation, time_stamp);
315 }
316
317 if (capture_) {
318 HRESULT hr = reader_->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0,
319 NULL, NULL, NULL, NULL);
320 if (FAILED(hr)) {
321 // If running the *VideoCap* unit tests on repeat, this can sometimes
322 // fail with HRESULT_FROM_WINHRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION).
323 // It's not clear to me why this is, but it is possible that it has
324 // something to do with this bug:
325 // http://support.microsoft.com/kb/979567
326 OnError(hr);
327 }
328 }
329 }
330
OnError(HRESULT hr)331 void VideoCaptureDeviceMFWin::OnError(HRESULT hr) {
332 std::string log_msg = base::StringPrintf("VideoCaptureDeviceMFWin: %x", hr);
333 DLOG(ERROR) << log_msg;
334 if (client_.get())
335 client_->OnError(log_msg);
336 }
337
338 } // namespace media
339