| /* | |
| * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "modules/video_capture/windows/video_capture_ds.h" | |
| #include <dvdmedia.h> // VIDEOINFOHEADER2 | |
| #include "modules/video_capture/video_capture_config.h" | |
| #include "modules/video_capture/video_capture_impl.h" | |
| #include "modules/video_capture/windows/help_functions_ds.h" | |
| #include "modules/video_capture/windows/sink_filter_ds.h" | |
| #include "rtc_base/logging.h" | |
| #include "system_wrappers/include/clock.h" | |
| namespace webrtc { | |
| namespace videocapturemodule { | |
| VideoCaptureDS::VideoCaptureDS(Clock* clock) | |
| : VideoCaptureImpl(clock), | |
| _captureFilter(NULL), | |
| _graphBuilder(NULL), | |
| _mediaControl(NULL), | |
| _inputSendPin(NULL), | |
| _outputCapturePin(NULL), | |
| _dvFilter(NULL), | |
| _inputDvPin(NULL), | |
| _outputDvPin(NULL) {} | |
| VideoCaptureDS::~VideoCaptureDS() { | |
| if (_mediaControl) { | |
| _mediaControl->Stop(); | |
| } | |
| if (_graphBuilder) { | |
| if (sink_filter_) | |
| _graphBuilder->RemoveFilter(sink_filter_.get()); | |
| if (_captureFilter) | |
| _graphBuilder->RemoveFilter(_captureFilter); | |
| if (_dvFilter) | |
| _graphBuilder->RemoveFilter(_dvFilter); | |
| } | |
| RELEASE_AND_CLEAR(_inputSendPin); | |
| RELEASE_AND_CLEAR(_outputCapturePin); | |
| RELEASE_AND_CLEAR(_captureFilter); // release the capture device | |
| RELEASE_AND_CLEAR(_dvFilter); | |
| RELEASE_AND_CLEAR(_mediaControl); | |
| RELEASE_AND_CLEAR(_inputDvPin); | |
| RELEASE_AND_CLEAR(_outputDvPin); | |
| RELEASE_AND_CLEAR(_graphBuilder); | |
| } | |
| int32_t VideoCaptureDS::Init(const char* deviceUniqueIdUTF8) { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| const int32_t nameLength = (int32_t)strlen((char*)deviceUniqueIdUTF8); | |
| if (nameLength >= kVideoCaptureUniqueNameLength) | |
| return -1; | |
| // Store the device name | |
| _deviceUniqueId = new (std::nothrow) char[nameLength + 1]; | |
| memcpy(_deviceUniqueId, deviceUniqueIdUTF8, nameLength + 1); | |
| if (_dsInfo.Init() != 0) | |
| return -1; | |
| _captureFilter = _dsInfo.GetDeviceFilter(deviceUniqueIdUTF8); | |
| if (!_captureFilter) { | |
| RTC_LOG(LS_INFO) << "Failed to create capture filter."; | |
| return -1; | |
| } | |
| // Get the interface for DirectShow's GraphBuilder | |
| HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, | |
| IID_IGraphBuilder, (void**)&_graphBuilder); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) << "Failed to create graph builder."; | |
| return -1; | |
| } | |
| hr = _graphBuilder->QueryInterface(IID_IMediaControl, (void**)&_mediaControl); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) << "Failed to create media control builder."; | |
| return -1; | |
| } | |
| hr = _graphBuilder->AddFilter(_captureFilter, CAPTURE_FILTER_NAME); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) << "Failed to add the capture device to the graph."; | |
| return -1; | |
| } | |
| _outputCapturePin = GetOutputPin(_captureFilter, PIN_CATEGORY_CAPTURE); | |
| if (!_outputCapturePin) { | |
| RTC_LOG(LS_INFO) << "Failed to get output capture pin"; | |
| return -1; | |
| } | |
| // Create the sink filte used for receiving Captured frames. | |
| sink_filter_ = new ComRefCount<CaptureSinkFilter>(this); | |
| hr = _graphBuilder->AddFilter(sink_filter_.get(), SINK_FILTER_NAME); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) << "Failed to add the send filter to the graph."; | |
| return -1; | |
| } | |
| _inputSendPin = GetInputPin(sink_filter_.get()); | |
| if (!_inputSendPin) { | |
| RTC_LOG(LS_INFO) << "Failed to get input send pin"; | |
| return -1; | |
| } | |
| if (SetCameraOutput(_requestedCapability) != 0) { | |
| return -1; | |
| } | |
| RTC_LOG(LS_INFO) << "Capture device '" << deviceUniqueIdUTF8 | |
| << "' initialized."; | |
| return 0; | |
| } | |
| int32_t VideoCaptureDS::StartCapture(const VideoCaptureCapability& capability) { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| if (capability != _requestedCapability) { | |
| DisconnectGraph(); | |
| if (SetCameraOutput(capability) != 0) { | |
| return -1; | |
| } | |
| } | |
| HRESULT hr = _mediaControl->Pause(); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) | |
| << "Failed to Pause the Capture device. Is it already occupied? " << hr; | |
| return -1; | |
| } | |
| hr = _mediaControl->Run(); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) << "Failed to start the Capture device."; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int32_t VideoCaptureDS::StopCapture() { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| HRESULT hr = _mediaControl->StopWhenReady(); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) << "Failed to stop the capture graph. " << hr; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| bool VideoCaptureDS::CaptureStarted() { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| OAFilterState state = 0; | |
| HRESULT hr = _mediaControl->GetState(1000, &state); | |
| if (hr != S_OK && hr != VFW_S_CANT_CUE) { | |
| RTC_LOG(LS_INFO) << "Failed to get the CaptureStarted status"; | |
| } | |
| RTC_LOG(LS_INFO) << "CaptureStarted " << state; | |
| return state == State_Running; | |
| } | |
| int32_t VideoCaptureDS::CaptureSettings(VideoCaptureCapability& settings) { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| settings = _requestedCapability; | |
| return 0; | |
| } | |
| int32_t VideoCaptureDS::SetCameraOutput( | |
| const VideoCaptureCapability& requestedCapability) { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| // Get the best matching capability | |
| VideoCaptureCapability capability; | |
| int32_t capabilityIndex; | |
| // Store the new requested size | |
| _requestedCapability = requestedCapability; | |
| // Match the requested capability with the supported. | |
| if ((capabilityIndex = _dsInfo.GetBestMatchedCapability( | |
| _deviceUniqueId, _requestedCapability, capability)) < 0) { | |
| return -1; | |
| } | |
| // Reduce the frame rate if possible. | |
| if (capability.maxFPS > requestedCapability.maxFPS) { | |
| capability.maxFPS = requestedCapability.maxFPS; | |
| } else if (capability.maxFPS <= 0) { | |
| capability.maxFPS = 30; | |
| } | |
| // Convert it to the windows capability index since they are not nexessary | |
| // the same | |
| VideoCaptureCapabilityWindows windowsCapability; | |
| if (_dsInfo.GetWindowsCapability(capabilityIndex, windowsCapability) != 0) { | |
| return -1; | |
| } | |
| IAMStreamConfig* streamConfig = NULL; | |
| AM_MEDIA_TYPE* pmt = NULL; | |
| VIDEO_STREAM_CONFIG_CAPS caps; | |
| HRESULT hr = _outputCapturePin->QueryInterface(IID_IAMStreamConfig, | |
| (void**)&streamConfig); | |
| if (hr) { | |
| RTC_LOG(LS_INFO) << "Can't get the Capture format settings."; | |
| return -1; | |
| } | |
| // Get the windows capability from the capture device | |
| bool isDVCamera = false; | |
| hr = streamConfig->GetStreamCaps(windowsCapability.directShowCapabilityIndex, | |
| &pmt, reinterpret_cast<BYTE*>(&caps)); | |
| if (hr == S_OK) { | |
| if (pmt->formattype == FORMAT_VideoInfo2) { | |
| VIDEOINFOHEADER2* h = reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat); | |
| if (capability.maxFPS > 0 && windowsCapability.supportFrameRateControl) { | |
| h->AvgTimePerFrame = REFERENCE_TIME(10000000.0 / capability.maxFPS); | |
| } | |
| } else { | |
| VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat); | |
| if (capability.maxFPS > 0 && windowsCapability.supportFrameRateControl) { | |
| h->AvgTimePerFrame = REFERENCE_TIME(10000000.0 / capability.maxFPS); | |
| } | |
| } | |
| // Set the sink filter to request this capability | |
| sink_filter_->SetRequestedCapability(capability); | |
| // Order the capture device to use this capability | |
| hr += streamConfig->SetFormat(pmt); | |
| // Check if this is a DV camera and we need to add MS DV Filter | |
| if (pmt->subtype == MEDIASUBTYPE_dvsl || | |
| pmt->subtype == MEDIASUBTYPE_dvsd || | |
| pmt->subtype == MEDIASUBTYPE_dvhd) { | |
| isDVCamera = true; // This is a DV camera. Use MS DV filter | |
| } | |
| FreeMediaType(pmt); | |
| pmt = NULL; | |
| } | |
| RELEASE_AND_CLEAR(streamConfig); | |
| if (FAILED(hr)) { | |
| RTC_LOG(LS_INFO) << "Failed to set capture device output format"; | |
| return -1; | |
| } | |
| if (isDVCamera) { | |
| hr = ConnectDVCamera(); | |
| } else { | |
| hr = _graphBuilder->ConnectDirect(_outputCapturePin, _inputSendPin, NULL); | |
| } | |
| if (hr != S_OK) { | |
| RTC_LOG(LS_INFO) << "Failed to connect the Capture graph " << hr; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| int32_t VideoCaptureDS::DisconnectGraph() { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| HRESULT hr = _mediaControl->Stop(); | |
| hr += _graphBuilder->Disconnect(_outputCapturePin); | |
| hr += _graphBuilder->Disconnect(_inputSendPin); | |
| // if the DV camera filter exist | |
| if (_dvFilter) { | |
| _graphBuilder->Disconnect(_inputDvPin); | |
| _graphBuilder->Disconnect(_outputDvPin); | |
| } | |
| if (hr != S_OK) { | |
| RTC_LOG(LS_ERROR) | |
| << "Failed to Stop the Capture device for reconfiguration " << hr; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| HRESULT VideoCaptureDS::ConnectDVCamera() { | |
| RTC_DCHECK_RUN_ON(&api_checker_); | |
| HRESULT hr = S_OK; | |
| if (!_dvFilter) { | |
| hr = CoCreateInstance(CLSID_DVVideoCodec, NULL, CLSCTX_INPROC, | |
| IID_IBaseFilter, (void**)&_dvFilter); | |
| if (hr != S_OK) { | |
| RTC_LOG(LS_INFO) << "Failed to create the dv decoder: " << hr; | |
| return hr; | |
| } | |
| hr = _graphBuilder->AddFilter(_dvFilter, L"VideoDecoderDV"); | |
| if (hr != S_OK) { | |
| RTC_LOG(LS_INFO) << "Failed to add the dv decoder to the graph: " << hr; | |
| return hr; | |
| } | |
| _inputDvPin = GetInputPin(_dvFilter); | |
| if (_inputDvPin == NULL) { | |
| RTC_LOG(LS_INFO) << "Failed to get input pin from DV decoder"; | |
| return -1; | |
| } | |
| _outputDvPin = GetOutputPin(_dvFilter, PIN_CATEGORY_CAPTURE); | |
| if (_outputDvPin == NULL) { | |
| RTC_LOG(LS_INFO) << "Failed to get output pin from DV decoder"; | |
| return -1; | |
| } | |
| } | |
| hr = _graphBuilder->ConnectDirect(_outputCapturePin, _inputDvPin, NULL); | |
| if (hr != S_OK) { | |
| RTC_LOG(LS_INFO) << "Failed to connect capture device to the dv devoder: " | |
| << hr; | |
| return hr; | |
| } | |
| hr = _graphBuilder->ConnectDirect(_outputDvPin, _inputSendPin, NULL); | |
| if (hr != S_OK) { | |
| if (hr == HRESULT_FROM_WIN32(ERROR_TOO_MANY_OPEN_FILES)) { | |
| RTC_LOG(LS_INFO) << "Failed to connect the capture device, busy"; | |
| } else { | |
| RTC_LOG(LS_INFO) << "Failed to connect capture device to the send graph: " | |
| << hr; | |
| } | |
| } | |
| return hr; | |
| } | |
| } // namespace videocapturemodule | |
| } // namespace webrtc |