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