1/*
2 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/modules/video_capture/windows/video_capture_ds.h"
12
13#include "webrtc/modules/video_capture/video_capture_config.h"
14#include "webrtc/modules/video_capture/windows/help_functions_ds.h"
15#include "webrtc/modules/video_capture/windows/sink_filter_ds.h"
16#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
17#include "webrtc/system_wrappers/include/trace.h"
18
19#include <Dvdmedia.h> // VIDEOINFOHEADER2
20
21namespace webrtc
22{
23namespace videocapturemodule
24{
25VideoCaptureDS::VideoCaptureDS(const int32_t id)
26    : VideoCaptureImpl(id), _dsInfo(id), _captureFilter(NULL),
27      _graphBuilder(NULL), _mediaControl(NULL), _sinkFilter(NULL),
28      _inputSendPin(NULL), _outputCapturePin(NULL), _dvFilter(NULL),
29      _inputDvPin(NULL), _outputDvPin(NULL)
30{
31}
32
33VideoCaptureDS::~VideoCaptureDS()
34{
35    if (_mediaControl)
36    {
37        _mediaControl->Stop();
38    }
39    if (_graphBuilder)
40    {
41        if (_sinkFilter)
42            _graphBuilder->RemoveFilter(_sinkFilter);
43        if (_captureFilter)
44            _graphBuilder->RemoveFilter(_captureFilter);
45        if (_dvFilter)
46            _graphBuilder->RemoveFilter(_dvFilter);
47    }
48    RELEASE_AND_CLEAR(_inputSendPin);
49    RELEASE_AND_CLEAR(_outputCapturePin);
50
51    RELEASE_AND_CLEAR(_captureFilter); // release the capture device
52    RELEASE_AND_CLEAR(_sinkFilter);
53    RELEASE_AND_CLEAR(_dvFilter);
54
55    RELEASE_AND_CLEAR(_mediaControl);
56
57    RELEASE_AND_CLEAR(_inputDvPin);
58    RELEASE_AND_CLEAR(_outputDvPin);
59
60    RELEASE_AND_CLEAR(_graphBuilder);
61}
62
63int32_t VideoCaptureDS::Init(const int32_t id, const char* deviceUniqueIdUTF8)
64{
65    const int32_t nameLength =
66        (int32_t) strlen((char*) deviceUniqueIdUTF8);
67    if (nameLength > kVideoCaptureUniqueNameLength)
68        return -1;
69
70    // Store the device name
71    _deviceUniqueId = new (std::nothrow) char[nameLength + 1];
72    memcpy(_deviceUniqueId, deviceUniqueIdUTF8, nameLength + 1);
73
74    if (_dsInfo.Init() != 0)
75        return -1;
76
77    _captureFilter = _dsInfo.GetDeviceFilter(deviceUniqueIdUTF8);
78    if (!_captureFilter)
79    {
80        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
81                     "Failed to create capture filter.");
82        return -1;
83    }
84
85    // Get the interface for DirectShow's GraphBuilder
86    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
87                                  CLSCTX_INPROC_SERVER, IID_IGraphBuilder,
88                                  (void **) &_graphBuilder);
89    if (FAILED(hr))
90    {
91        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
92                     "Failed to create graph builder.");
93        return -1;
94    }
95
96    hr = _graphBuilder->QueryInterface(IID_IMediaControl,
97                                       (void **) &_mediaControl);
98    if (FAILED(hr))
99    {
100        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
101                     "Failed to create media control builder.");
102        return -1;
103    }
104    hr = _graphBuilder->AddFilter(_captureFilter, CAPTURE_FILTER_NAME);
105    if (FAILED(hr))
106    {
107        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
108                     "Failed to add the capture device to the graph.");
109        return -1;
110    }
111
112    _outputCapturePin = GetOutputPin(_captureFilter, PIN_CATEGORY_CAPTURE);
113
114    // Create the sink filte used for receiving Captured frames.
115    _sinkFilter = new CaptureSinkFilter(SINK_FILTER_NAME, NULL, &hr,
116                                        *this, _id);
117    if (hr != S_OK)
118    {
119        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
120                     "Failed to create send filter");
121        return -1;
122    }
123    _sinkFilter->AddRef();
124
125    hr = _graphBuilder->AddFilter(_sinkFilter, SINK_FILTER_NAME);
126    if (FAILED(hr))
127    {
128        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
129                     "Failed to add the send filter to the graph.");
130        return -1;
131    }
132    _inputSendPin = GetInputPin(_sinkFilter);
133
134    // Temporary connect here.
135    // This is done so that no one else can use the capture device.
136    if (SetCameraOutput(_requestedCapability) != 0)
137    {
138        return -1;
139    }
140    hr = _mediaControl->Pause();
141    if (FAILED(hr))
142    {
143        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
144                     "Failed to Pause the Capture device. Is it already occupied? %d.",
145                     hr);
146        return -1;
147    }
148    WEBRTC_TRACE(webrtc::kTraceStateInfo, webrtc::kTraceVideoCapture, _id,
149                 "Capture device '%s' initialized.", deviceUniqueIdUTF8);
150    return 0;
151}
152
153int32_t VideoCaptureDS::StartCapture(
154                                      const VideoCaptureCapability& capability)
155{
156    CriticalSectionScoped cs(&_apiCs);
157
158    if (capability != _requestedCapability)
159    {
160        DisconnectGraph();
161
162        if (SetCameraOutput(capability) != 0)
163        {
164            return -1;
165        }
166    }
167    HRESULT hr = _mediaControl->Run();
168    if (FAILED(hr))
169    {
170        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
171                     "Failed to start the Capture device.");
172        return -1;
173    }
174    return 0;
175}
176
177int32_t VideoCaptureDS::StopCapture()
178{
179    CriticalSectionScoped cs(&_apiCs);
180
181    HRESULT hr = _mediaControl->Pause();
182    if (FAILED(hr))
183    {
184        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
185                     "Failed to stop the capture graph. %d", hr);
186        return -1;
187    }
188    return 0;
189}
190bool VideoCaptureDS::CaptureStarted()
191{
192    OAFilterState state = 0;
193    HRESULT hr = _mediaControl->GetState(1000, &state);
194    if (hr != S_OK && hr != VFW_S_CANT_CUE)
195    {
196        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
197                     "Failed to get the CaptureStarted status");
198    }
199    WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
200                 "CaptureStarted %d", state);
201    return state == State_Running;
202
203}
204int32_t VideoCaptureDS::CaptureSettings(
205                                             VideoCaptureCapability& settings)
206{
207    settings = _requestedCapability;
208    return 0;
209}
210
211int32_t VideoCaptureDS::SetCameraOutput(
212                             const VideoCaptureCapability& requestedCapability)
213{
214
215    // Get the best matching capability
216    VideoCaptureCapability capability;
217    int32_t capabilityIndex;
218
219    // Store the new requested size
220    _requestedCapability = requestedCapability;
221    // Match the requested capability with the supported.
222    if ((capabilityIndex = _dsInfo.GetBestMatchedCapability(_deviceUniqueId,
223                                                            _requestedCapability,
224                                                            capability)) < 0)
225    {
226        return -1;
227    }
228    //Reduce the frame rate if possible.
229    if (capability.maxFPS > requestedCapability.maxFPS)
230    {
231        capability.maxFPS = requestedCapability.maxFPS;
232    } else if (capability.maxFPS <= 0)
233    {
234        capability.maxFPS = 30;
235    }
236    // Store the new expected capture delay
237    _captureDelay = capability.expectedCaptureDelay;
238
239    // Convert it to the windows capability index since they are not nexessary
240    // the same
241    VideoCaptureCapabilityWindows windowsCapability;
242    if (_dsInfo.GetWindowsCapability(capabilityIndex, windowsCapability) != 0)
243    {
244        return -1;
245    }
246
247    IAMStreamConfig* streamConfig = NULL;
248    AM_MEDIA_TYPE *pmt = NULL;
249    VIDEO_STREAM_CONFIG_CAPS caps;
250
251    HRESULT hr = _outputCapturePin->QueryInterface(IID_IAMStreamConfig,
252                                                   (void**) &streamConfig);
253    if (hr)
254    {
255        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
256                     "Can't get the Capture format settings.");
257        return -1;
258    }
259
260    //Get the windows capability from the capture device
261    bool isDVCamera = false;
262    hr = streamConfig->GetStreamCaps(
263                                    windowsCapability.directShowCapabilityIndex,
264                                    &pmt, reinterpret_cast<BYTE*> (&caps));
265    if (!FAILED(hr))
266    {
267        if (pmt->formattype == FORMAT_VideoInfo2)
268        {
269            VIDEOINFOHEADER2* h =
270                reinterpret_cast<VIDEOINFOHEADER2*> (pmt->pbFormat);
271            if (capability.maxFPS > 0
272                && windowsCapability.supportFrameRateControl)
273            {
274                h->AvgTimePerFrame = REFERENCE_TIME(10000000.0
275                                                    / capability.maxFPS);
276            }
277        }
278        else
279        {
280            VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>
281                                (pmt->pbFormat);
282            if (capability.maxFPS > 0
283                && windowsCapability.supportFrameRateControl)
284            {
285                h->AvgTimePerFrame = REFERENCE_TIME(10000000.0
286                                                    / capability.maxFPS);
287            }
288
289        }
290
291        // Set the sink filter to request this capability
292        _sinkFilter->SetMatchingMediaType(capability);
293        //Order the capture device to use this capability
294        hr += streamConfig->SetFormat(pmt);
295
296        //Check if this is a DV camera and we need to add MS DV Filter
297        if (pmt->subtype == MEDIASUBTYPE_dvsl
298           || pmt->subtype == MEDIASUBTYPE_dvsd
299           || pmt->subtype == MEDIASUBTYPE_dvhd)
300            isDVCamera = true; // This is a DV camera. Use MS DV filter
301    }
302    RELEASE_AND_CLEAR(streamConfig);
303
304    if (FAILED(hr))
305    {
306        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
307                     "Failed to set capture device output format");
308        return -1;
309    }
310
311    if (isDVCamera)
312    {
313        hr = ConnectDVCamera();
314    }
315    else
316    {
317        hr = _graphBuilder->ConnectDirect(_outputCapturePin, _inputSendPin,
318                                          NULL);
319    }
320    if (hr != S_OK)
321    {
322        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
323                     "Failed to connect the Capture graph %d", hr);
324        return -1;
325    }
326    return 0;
327}
328
329int32_t VideoCaptureDS::DisconnectGraph()
330{
331    HRESULT hr = _mediaControl->Stop();
332    hr += _graphBuilder->Disconnect(_outputCapturePin);
333    hr += _graphBuilder->Disconnect(_inputSendPin);
334
335    //if the DV camera filter exist
336    if (_dvFilter)
337    {
338        _graphBuilder->Disconnect(_inputDvPin);
339        _graphBuilder->Disconnect(_outputDvPin);
340    }
341    if (hr != S_OK)
342    {
343        WEBRTC_TRACE( webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
344                     "Failed to Stop the Capture device for reconfiguration %d",
345                     hr);
346        return -1;
347    }
348    return 0;
349}
350HRESULT VideoCaptureDS::ConnectDVCamera()
351{
352    HRESULT hr = S_OK;
353
354    if (!_dvFilter)
355    {
356        hr = CoCreateInstance(CLSID_DVVideoCodec, NULL, CLSCTX_INPROC,
357                              IID_IBaseFilter, (void **) &_dvFilter);
358        if (hr != S_OK)
359        {
360            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
361                         "Failed to create the dv decoder: %x", hr);
362            return hr;
363        }
364        hr = _graphBuilder->AddFilter(_dvFilter, L"VideoDecoderDV");
365        if (hr != S_OK)
366        {
367            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
368                         "Failed to add the dv decoder to the graph: %x", hr);
369            return hr;
370        }
371        _inputDvPin = GetInputPin(_dvFilter);
372        if (_inputDvPin == NULL)
373        {
374            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
375                         "Failed to get input pin from DV decoder");
376            return -1;
377        }
378        _outputDvPin = GetOutputPin(_dvFilter, GUID_NULL);
379        if (_outputDvPin == NULL)
380        {
381            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
382                         "Failed to get output pin from DV decoder");
383            return -1;
384        }
385    }
386    hr = _graphBuilder->ConnectDirect(_outputCapturePin, _inputDvPin, NULL);
387    if (hr != S_OK)
388    {
389        WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
390                     "Failed to connect capture device to the dv devoder: %x",
391                     hr);
392        return hr;
393    }
394
395    hr = _graphBuilder->ConnectDirect(_outputDvPin, _inputSendPin, NULL);
396    if (hr != S_OK)
397    {
398        if (hr == 0x80070004)
399        {
400            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
401                         "Failed to connect the capture device, busy");
402        }
403        else
404        {
405            WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
406                         "Failed to connect capture device to the send graph: 0x%x",
407                         hr);
408        }
409        return hr;
410    }
411    return hr;
412}
413}  // namespace videocapturemodule
414}  // namespace webrtc
415