| // Copyright (c) 2012 The WebM 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 "encoder/win/media_source_dshow.h" |
| |
| #include <initguid.h> // MUST be included before VorbisTypes.h to avoid |
| // undefined external error for |
| // IID_VorbisEncodeSettings due to behavior of |
| // DEFINE_GUID macro. |
| #include <vfwmsgs.h> |
| |
| #include <memory> |
| #include <sstream> |
| |
| #include "encoder/video_encoder.h" |
| #include "encoder/webm_encoder.h" |
| #include "encoder/win/audio_sink_filter.h" |
| #include "encoder/win/dshow_util.h" |
| #include "encoder/win/media_type_dshow.h" |
| #include "encoder/win/video_sink_filter.h" |
| #include "encoder/win/webm_guids.h" |
| #include "glog/logging.h" |
| |
| namespace webmlive { |
| |
| namespace { |
| // DirectShow Filter name constants. |
| const wchar_t* const kAudioSourceName = L"AudioSource"; |
| const wchar_t* const kAudioSinkName = L"AudioSink"; |
| const wchar_t* const kVideoSourceName = L"VideoSource"; |
| const wchar_t* const kVideoSinkName = L"VideoSink"; |
| |
| |
| // Converts a std::string to std::wstring. |
| std::wstring string_to_wstring(const std::string& str) { |
| std::wostringstream wstr; |
| wstr << str.c_str(); |
| return wstr.str(); |
| } |
| |
| // Converts |wstr| to a multi-byte string and returns result std::string. |
| std::string wstring_to_string(const std::wstring& wstr) { |
| // Conversion buffer for |wcstombs| calls. |
| const size_t buf_size = wstr.length() + 1; |
| std::unique_ptr<char[]> temp_str( |
| new (std::nothrow) char[buf_size]); // NOLINT |
| if (!temp_str) { |
| LOG(ERROR) << "can't convert wstring of length=" << wstr.length(); |
| return std::string("<empty>"); |
| } |
| memset(temp_str.get(), 0, buf_size); |
| size_t num_converted = 0; |
| wcstombs_s(&num_converted, temp_str.get(), buf_size, wstr.c_str(), |
| wstr.length()*sizeof(wchar_t)); |
| std::string str = temp_str.get(); |
| return str; |
| } |
| |
| // Attempts to show configuration dialogs for |filter| and |pin|. Stores |
| // resulting media type in |ptr_type| and returns |kSuccess| when everything |
| // goes as planned. |
| int DoManualFilterConfiguration(const IBaseFilterPtr& filter, |
| const IPinPtr& pin, |
| bool show_pin_dialog, |
| webmlive::MediaTypePtr* ptr_type) { |
| bool filter_config_ok = false; |
| HRESULT hr = ShowFilterPropertyPage(filter); |
| if (FAILED(hr)) { |
| LOG(WARNING) << "Unable to show filter property page." << HRLOG(hr); |
| } else { |
| filter_config_ok = true; |
| } |
| |
| // Try showing the pin property page. |
| bool pin_config_ok = false; |
| if (show_pin_dialog) { |
| hr = ShowPinPropertyPage(pin); |
| if (FAILED(hr)) { |
| LOG(WARNING) << "Unable to show pin property page." << HRLOG(hr); |
| } else { |
| pin_config_ok = true; |
| } |
| } |
| |
| // Pass the media type back when either configuration attempt is successful. |
| using webmlive::MediaSourceImpl; |
| if (filter_config_ok || pin_config_ok) { |
| PinFormat formatter(pin); |
| const int status = ptr_type->Attach(formatter.format()); |
| if (status) { |
| LOG(WARNING) << "Manual filter config failed: pin has no media type."; |
| } else { |
| LOG(INFO) << "Manual filter configuration successful."; |
| return MediaSourceImpl::kSuccess; |
| } |
| } |
| return MediaSourceImpl::kManualConfigurationFailure; |
| } |
| |
| } // anonymous namespace |
| |
| // Converts media time (100 nanosecond ticks) to milliseconds. |
| int64 media_time_to_milliseconds(REFERENCE_TIME media_time) { |
| return media_time / 10000; |
| } |
| |
| // Converts media time (100 nanosecond ticks) to seconds. |
| double media_time_to_seconds(REFERENCE_TIME media_time) { |
| return media_time / 10000000.0; |
| } |
| |
| // Converts seconds to media time (100 nanosecond ticks). |
| REFERENCE_TIME seconds_to_media_time(double seconds) { |
| return static_cast<REFERENCE_TIME>(seconds * 10000000); |
| } |
| |
| MediaSourceImpl::MediaSourceImpl() |
| : audio_from_video_source_(false), |
| media_event_handle_(INVALID_HANDLE_VALUE), |
| ptr_audio_callback_(NULL), |
| ptr_video_callback_(NULL), |
| audio_device_index_(0), |
| video_device_index_(0) { |
| } |
| |
| MediaSourceImpl::~MediaSourceImpl() { |
| // Manually release directshow interfaces to avoid problems related to |
| // destruction order of com_ptr_t members. |
| audio_source_ = 0; |
| audio_sink_ = 0; |
| video_source_ = 0; |
| video_sink_ = 0; |
| media_event_handle_ = INVALID_HANDLE_VALUE; |
| media_control_ = 0; |
| media_event_ = 0; |
| capture_graph_builder_ = 0; |
| graph_builder_ = 0; |
| CoUninitialize(); |
| } |
| |
| // Builds a DirectShow filter graph that looks like this: |
| // video source -> video sink |
| int MediaSourceImpl::Init(const WebmEncoderConfig& config, |
| AudioSamplesCallbackInterface* ptr_audio_callback, |
| VideoFrameCallbackInterface* ptr_video_callback) { |
| if (!config.disable_audio && !ptr_audio_callback) { |
| LOG(ERROR) << "Null AudioSamplesCallbackInterface."; |
| return kInvalidArg; |
| } |
| if (!config.disable_video && !ptr_video_callback) { |
| LOG(ERROR) << "Null VideoFrameCallbackInterface."; |
| return kInvalidArg; |
| } |
| if (config.disable_audio && config.disable_video) { |
| LOG(ERROR) << "Audio and video are disabled."; |
| return kInvalidArg; |
| } |
| ptr_audio_callback_ = ptr_audio_callback; |
| ptr_video_callback_ = ptr_video_callback; |
| requested_audio_config_ = config.requested_audio_config; |
| requested_video_config_ = config.requested_video_config; |
| ui_opts_ = config.ui_opts; |
| const HRESULT hr = CoInitialize(NULL); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "CoInitialize failed: " << HRLOG(hr); |
| return WebmEncoder::kInitFailed; |
| } |
| int status = CreateGraph(); |
| if (status) { |
| LOG(ERROR) << "CreateGraphInterfaces failed: " << status; |
| return WebmEncoder::kInitFailed; |
| } |
| if (config.disable_video == false) { |
| if (!config.video_device_name.empty()) { |
| video_device_name_ = string_to_wstring(config.video_device_name); |
| } |
| if (config.video_device_index != kUseDefaultDevice) { |
| video_device_index_ = config.video_device_index; |
| } |
| status = CreateVideoSource(); |
| if (status) { |
| LOG(ERROR) << "CreateVideoSource failed: " << status; |
| return WebmEncoder::kNoVideoSource; |
| } |
| status = CreateVideoSink(); |
| if (status) { |
| LOG(ERROR) << "CreateVideoSink failed: " << status; |
| return WebmEncoder::kNoVideoSource; |
| } |
| status = ConnectVideoSourceToVideoSink(); |
| if (status) { |
| LOG(ERROR) << "ConnectVideoSourceToVideoSink failed: " << status; |
| return WebmEncoder::kVideoSinkError; |
| } |
| } |
| if (config.disable_audio == false) { |
| if (!config.audio_device_name.empty()) { |
| audio_device_name_ = string_to_wstring(config.audio_device_name); |
| } |
| if (config.audio_device_index != kUseDefaultDevice) { |
| audio_device_index_ = config.audio_device_index; |
| } |
| status = CreateAudioSource(); |
| if (status) { |
| LOG(ERROR) << "CreateAudioSource failed: " << status; |
| return WebmEncoder::kNoAudioSource; |
| } |
| status = CreateAudioSink(); |
| if (status) { |
| LOG(ERROR) << "CreateAudioSink failed: " << status; |
| return WebmEncoder::kNoAudioSource; |
| } |
| status = ConnectAudioSourceToAudioSink(); |
| if (status) { |
| LOG(ERROR) << "ConnectAudioSourceToAudioSink failed: " << status; |
| return WebmEncoder::kAudioSinkError; |
| } |
| } |
| status = InitGraphControl(); |
| if (status) { |
| LOG(ERROR) << "InitGraphControl failed: " << status; |
| return WebmEncoder::kEncodeMonitorError; |
| } |
| return kSuccess; |
| } |
| |
| // Runs the filter graph via |IMediaControl::Run|. Note that the Run call is |
| // asynchronous, and typically returns S_FALSE to report that the run request |
| // is in progress but has not completed. |
| int MediaSourceImpl::Run() { |
| CoInitialize(NULL); |
| HRESULT hr = media_control_->Run(); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "media control Run failed, cannot run capture!" << HRLOG(hr); |
| return WebmEncoder::kRunFailed; |
| } |
| return kSuccess; |
| } |
| |
| // Confirms that the filter graph is running via use of |HandleMediaEvent| to |
| // check for abort and completion events. Actual FILTER_STATE (as reported by |
| // |IMediaControl::GetState|) is ignored to avoid complicating |CheckStatus| |
| // with code that waits for the transition from |State_Stopped| to |
| // |State_Running|. |
| int MediaSourceImpl::CheckStatus() { |
| int status = HandleMediaEvent(); |
| if (status == kGraphAborted || status == kGraphCompleted) { |
| LOG(ERROR) << "Capture graph stopped!"; |
| return WebmEncoder::kAVCaptureStopped; |
| } |
| return kSuccess; |
| } |
| |
| // Stops the filter graph via call to |IMediaControl::Stop|. |
| void MediaSourceImpl::Stop() { |
| const HRESULT hr = media_control_->Stop(); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "media control Stop failed! error=" << HRLOG(hr); |
| } else { |
| LOG(INFO) << "graph stopping. status=" << HRLOG(hr); |
| } |
| CoUninitialize(); |
| } |
| |
| // Creates the graph builder, |graph_builder_|, and capture graph builder, |
| // |capture_graph_builder_|, and passes |graph_builder_| to |
| // |capture_graph_builder_|. |
| int MediaSourceImpl::CreateGraph() { |
| HRESULT hr = graph_builder_.CreateInstance(CLSID_FilterGraph); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "graph builder creation failed." << HRLOG(hr); |
| return kCannotCreateGraph; |
| } |
| hr = capture_graph_builder_.CreateInstance(CLSID_CaptureGraphBuilder2); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "capture graph builder creation failed." << HRLOG(hr); |
| return kCannotCreateGraph; |
| } |
| hr = capture_graph_builder_->SetFiltergraph(graph_builder_); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "could not set capture builder graph." << HRLOG(hr); |
| return kGraphConfigureError; |
| } |
| return kSuccess; |
| } |
| |
| // Uses |CaptureSourceLoader| to find a video capture source. If successful |
| // an instance of the source filter is created and added to the filter graph. |
| // Note: the first device found is used unconditionally. |
| int MediaSourceImpl::CreateVideoSource() { |
| CaptureSourceLoader loader; |
| int status = loader.Init(CLSID_VideoInputDeviceCategory); |
| if (status) { |
| LOG(ERROR) << "no video source!"; |
| return WebmEncoder::kNoVideoSource; |
| } |
| for (int i = 0; i < loader.GetNumSources(); ++i) { |
| LOG(INFO) << "vdev" << i << ": " |
| << wstring_to_string(loader.GetSourceName(i).c_str()); |
| } |
| if (video_device_name_.empty()) { |
| video_device_name_ = loader.GetSourceName(video_device_index_); |
| } |
| video_source_ = loader.GetSource(video_device_name_); |
| LOG(INFO) << "Using vdev: " << wstring_to_string(video_device_name_); |
| if (!video_source_) { |
| LOG(ERROR) << "cannot create video source!"; |
| return WebmEncoder::kNoVideoSource; |
| } |
| const HRESULT hr = graph_builder_->AddFilter(video_source_, |
| kVideoSourceName); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "cannot add video source to graph." << HRLOG(hr); |
| return kCannotAddFilter; |
| } |
| // TODO(tomfinegan): set video format instead of hoping for sane defaults. |
| return kSuccess; |
| } |
| |
| int MediaSourceImpl::CreateVideoSink() { |
| HRESULT status = E_FAIL; |
| const std::string filter_name = wstring_to_string(kVideoSinkName); |
| VideoSinkFilter* const ptr_filter = |
| new (std::nothrow) VideoSinkFilter(filter_name.c_str(), // NOLINT |
| NULL, |
| ptr_video_callback_, |
| &status); |
| if (!ptr_filter || FAILED(status)) { |
| delete ptr_filter; |
| LOG(ERROR) << "VideoSinkFilter construction failed" << HRLOG(status); |
| return kVideoSinkCreateError; |
| } |
| video_sink_ = ptr_filter; |
| status = graph_builder_->AddFilter(video_sink_, kVideoSinkName); |
| if (FAILED(status)) { |
| LOG(ERROR) << "cannot add video sink to graph" << HRLOG(status); |
| return kCannotAddFilter; |
| } |
| return kSuccess; |
| } |
| |
| int MediaSourceImpl::ConnectVideoSourceToVideoSink() { |
| PinFinder pin_finder; |
| int status = pin_finder.Init(video_source_); |
| if (status) { |
| LOG(ERROR) << "cannot look for pins on video source!"; |
| return kVideoConnectError; |
| } |
| IPinPtr video_source_pin = pin_finder.FindVideoOutputPin(0); |
| if (!video_source_pin) { |
| LOG(ERROR) << "cannot find output pin on video source!"; |
| return kVideoConnectError; |
| } |
| status = pin_finder.Init(video_sink_); |
| if (status) { |
| LOG(ERROR) << "cannot look for pins on video sink!"; |
| return kVideoConnectError; |
| } |
| IPinPtr sink_input_pin = pin_finder.FindVideoInputPin(0); |
| if (!sink_input_pin) { |
| LOG(ERROR) << "cannot find video input pin on video sink filter!"; |
| return kVideoConnectError; |
| } |
| status = kVideoConnectError; |
| HRESULT hr = E_FAIL; |
| for (int i = 0; i < kVideoFormatCount && hr != S_OK; ++i) { |
| MediaTypePtr accepted_type; |
| status = ConfigureVideoSource(video_source_pin, i, &accepted_type); |
| if (status == kSuccess) { |
| LOG(INFO) << "Format " << i << " configuration OK."; |
| } else { |
| continue; |
| } |
| hr = graph_builder_->ConnectDirect(video_source_pin, sink_input_pin, |
| accepted_type.get()); |
| LOG(INFO) << "Format " << i << ((hr == S_OK) ? " connected." : " failed."); |
| } |
| if (status || hr != S_OK) { |
| // All previous connection attempts failed. Try one last time using |
| // |video_source_pin|'s default format. |
| PinFormat formatter(video_source_pin); |
| status = formatter.set_format(NULL); |
| if (status == kSuccess) { |
| hr = graph_builder_->ConnectDirect(video_source_pin, sink_input_pin, |
| NULL); |
| if (hr == S_OK) { |
| LOG(INFO) << "Connected with video source pin default format."; |
| } else { |
| LOG(ERROR) << "Cannot connect video source to video sink."; |
| status = kVideoConnectError; |
| } |
| } |
| } |
| if (status == kSuccess && hr == S_OK) { |
| AM_MEDIA_TYPE media_type = {0}; |
| // Store the actual width/height/frame rate. |
| hr = video_source_pin->ConnectionMediaType(&media_type); |
| if (hr == S_OK) { |
| VideoMediaType video_format; |
| if (video_format.Init(media_type) == kSuccess) { |
| LOG(INFO) << "ConnectVideoSourceToVideoSink actual settings\n" |
| << " width=" << video_format.width() << "\n" |
| << " height=" << video_format.height() << "\n" |
| << " stride=" << video_format.stride() << "\n" |
| << " frame_rate=" << video_format.frame_rate() << "\n"; |
| actual_video_config_.width = video_format.width(); |
| actual_video_config_.height = video_format.height(); |
| actual_video_config_.stride = video_format.stride(); |
| actual_video_config_.frame_rate = video_format.frame_rate(); |
| } |
| } |
| MediaType::FreeMediaTypeData(&media_type); |
| } |
| return status; |
| } |
| |
| // Attempts to configure |video_source_pin| media type through use of user |
| // settings stored in |config_.requested_video_config| with |VideoMediaType| |
| // to produce an AM_MEDIA_TYPE struct suitable for use with |
| // IAMStreamConfig::SetFormat. Returns |kSuccess| upon successful |
| // configuration. |
| int MediaSourceImpl::ConfigureVideoSource(const IPinPtr& pin, |
| int sub_type, |
| MediaTypePtr* ptr_type) { |
| if (!ptr_type) { |
| LOG(ERROR) << "NULL media type output pointer (video)!"; |
| return kInvalidArg; |
| } |
| if (ui_opts_.manual_video_config) { |
| // Always disable manual configuration. |
| // |ConfigureVideoSource| is called in a loop, so this avoids making the |
| // user mess with the dialog repeatedly if/when manual settings disagree |
| // with the video sink filter. |
| ui_opts_.manual_video_config = false; |
| const int status = |
| DoManualFilterConfiguration(video_source_, pin, true, ptr_type); |
| if (status == kSuccess) { |
| LOG(INFO) << "Manual video configuration successful."; |
| return kSuccess; |
| } |
| |
| // Fall through and use settings in |requested_video_config_| when property |
| // page stuff fails. |
| LOG(WARNING) << "Manual video configuration failed."; |
| } |
| VideoMediaType video_format; |
| int status = video_format.Init(MEDIATYPE_Video, FORMAT_VideoInfo); |
| if (status) { |
| LOG(ERROR) << "video media type init failed."; |
| return kVideoConfigureError; |
| } |
| const VideoFormat video_sub_type = static_cast<VideoFormat>(sub_type); |
| const VideoConfig& video_config = requested_video_config_; |
| if (video_config.width != 0 || |
| video_config.height != 0 || |
| video_config.frame_rate != 0) { |
| // User specified video settings are present: build a complete media type |
| // and attempt configuration of the source device. |
| status = video_format.ConfigureSubType(video_sub_type, video_config); |
| if (status) { |
| LOG(ERROR) << "video sub type configuration failed."; |
| return kVideoConfigureError; |
| } |
| PinFormat formatter(pin); |
| status = formatter.set_format( |
| const_cast<AM_MEDIA_TYPE*>(video_format.get())); |
| if (status) { |
| LOG(WARNING) << "cannot set pin format: " << status; |
| return kVideoConfigureError; |
| } |
| status = ptr_type->Attach(formatter.format()); |
| if (status) { |
| LOG(ERROR) << "Cannot attach to pin format: " << status; |
| return kVideoConfigureError; |
| } |
| } else { |
| // No user settings: configure a partial media type. |
| status = video_format.ConfigurePartialType(video_sub_type); |
| if (status) { |
| LOG(ERROR) << "cannot set partial pin format: " << status; |
| return kVideoConfigureError; |
| } |
| status = ptr_type->Copy(video_format.get()); |
| if (status) { |
| LOG(ERROR) << "Cannot copy partial type: " << status; |
| return kVideoConfigureError; |
| } |
| } |
| return WebmEncoder::kSuccess; |
| } |
| |
| int MediaSourceImpl::InitGraphControl() { |
| media_control_ = IMediaControlPtr(graph_builder_); |
| if (!media_control_) { |
| LOG(ERROR) << "cannot create media control."; |
| return WebmEncoder::kEncodeControlError; |
| } |
| media_event_ = IMediaEventPtr(graph_builder_); |
| if (!media_event_) { |
| LOG(ERROR) << "cannot create media event."; |
| return WebmEncoder::kEncodeMonitorError; |
| } |
| OAEVENT* const ptr_handle = reinterpret_cast<OAEVENT*>(&media_event_handle_); |
| const HRESULT hr = media_event_->GetEventHandle(ptr_handle); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "could not media event handle!" << HRLOG(hr); |
| return WebmEncoder::kEncodeMonitorError; |
| } |
| return kSuccess; |
| } |
| |
| // Checks for an audio output pin on |video_source_|. If one exists |
| // |video_source_| is copied to |audio_source_| and |kSuccess| is returned. |
| // If there is no audio output pin |CaptureSourceLoader| is used to find an |
| // audio capture source. If successful an instance of the source filter is |
| // created and added to the filter graph. |
| // Note: in the |CaptureSourceLoader| case, the first device found is used |
| // unconditionally. |
| int MediaSourceImpl::CreateAudioSource() { |
| // Check for an audio pin on the video source. |
| // TODO(tomfinegan): We assume that the user wants to use the audio feed |
| // exposed by the video capture source. This behavior |
| // should be configurable. |
| int status; |
| if (video_source_) { |
| PinFinder pin_finder; |
| status = pin_finder.Init(video_source_); |
| if (status) { |
| LOG(ERROR) << "cannot check video source for audio pins!"; |
| return WebmEncoder::kInitFailed; |
| } |
| if (pin_finder.FindAudioOutputPin(0)) { |
| // Use the video source filter audio output pin. |
| LOG(INFO) << "Using video source filter audio output pin."; |
| audio_source_ = video_source_; |
| audio_from_video_source_ = true; |
| return kSuccess; |
| } |
| } |
| |
| // The video source doesn't have an audio output pin. Find an audio |
| // capture source. |
| CaptureSourceLoader loader; |
| status = loader.Init(CLSID_AudioInputDeviceCategory); |
| if (status) { |
| LOG(ERROR) << "no audio source!"; |
| return WebmEncoder::kNoAudioSource; |
| } |
| for (int i = 0; i < loader.GetNumSources(); ++i) { |
| LOG(INFO) << "adev" << i << ": " |
| << wstring_to_string(loader.GetSourceName(i).c_str()); |
| } |
| if (audio_device_name_.empty()) { |
| audio_device_name_ = loader.GetSourceName(audio_device_index_); |
| } |
| audio_source_ = loader.GetSource(audio_device_name_); |
| LOG(INFO) << "Using adev: " << wstring_to_string(audio_device_name_); |
| if (!audio_source_) { |
| LOG(ERROR) << "cannot create audio source!"; |
| return WebmEncoder::kNoAudioSource; |
| } |
| const HRESULT hr = graph_builder_->AddFilter(audio_source_, |
| kAudioSourceName); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "cannot add audio source to graph." << HRLOG(hr); |
| return kCannotAddFilter; |
| } |
| |
| return kSuccess; |
| } |
| |
| // Configures audio source pin to match user settings. Attempts to find a |
| // matching media type, and uses it if successful. Constructs |AudioMediaType| |
| // to configure pin with an AM_MEDIA_TYPE matching the user's settings if no |
| // match is found. Returns kSuccess upon successful configuration. |
| int MediaSourceImpl::ConfigureAudioSource(const IPinPtr& pin, |
| MediaTypePtr* ptr_type) { |
| if (!ptr_type) { |
| LOG(ERROR) << "NULL media type output pointer (audio)!"; |
| return kInvalidArg; |
| } |
| if (ui_opts_.manual_audio_config) { |
| // Manual audio configuration is enabled; try showing |audio_source_|'s |
| // property page, but only if the audio source is not a pin on |
| // |video_source_|. This is done to avoid any potential problems within |
| // the source filter that could be created by making filter level |
| // configuration changes through the property page after the video pin |
| // has been connected to the video sink filter. |
| if (!audio_from_video_source_) { |
| const int status = |
| DoManualFilterConfiguration(audio_source_, pin, false, ptr_type); |
| if (status == kSuccess) { |
| LOG(INFO) << "Manual audio configuration successful."; |
| return kSuccess; |
| } |
| |
| // Fall through and use settings in |requested_audio_config_| when |
| // property page stuff fails. |
| LOG(WARNING) << "Manual audio configuration failed."; |
| } |
| } |
| PinFormat formatter(pin); |
| MediaTypePtr audio_format; |
| int status = audio_format.Attach( |
| formatter.FindMatchingFormat(requested_audio_config_)); |
| if (status) { |
| // Try directly configuring the pin with user settings. |
| LOG(WARNING) << "no format matching requested audio settings."; |
| AudioMediaType user_audio_format; |
| status = user_audio_format.Init(); |
| if (status) { |
| LOG(ERROR) << "audio media type init failed, status=" << status; |
| return WebmEncoder::kAudioConfigureError; |
| } |
| status = user_audio_format.Configure(requested_audio_config_); |
| if (status) { |
| LOG(ERROR) << "audio media type configuration failed, status=" << status; |
| return WebmEncoder::kAudioConfigureError; |
| } |
| status = formatter.set_format(user_audio_format.get()); |
| if (status) { |
| LOG(ERROR) << "pin did not accept user audio format, status=" << status; |
| return WebmEncoder::kAudioConfigureError; |
| } |
| } else { |
| // Pin lists a format matching user settings; use it. |
| status = formatter.set_format(audio_format.get()); |
| if (status) { |
| LOG(ERROR) << "pin did not accept user audio format, status=" << status; |
| return WebmEncoder::kAudioConfigureError; |
| } |
| } |
| return ptr_type->Attach(formatter.format()); |
| } |
| |
| int MediaSourceImpl::CreateAudioSink() { |
| HRESULT status = E_FAIL; |
| const std::string filter_name = wstring_to_string(kAudioSinkName); |
| AudioSinkFilter* const ptr_filter = |
| new (std::nothrow) AudioSinkFilter(filter_name.c_str(), // NOLINT |
| NULL, |
| ptr_audio_callback_, |
| &status); |
| if (!ptr_filter || FAILED(status)) { |
| delete ptr_filter; |
| LOG(ERROR) << "AudioSinkFilter construction failed" << HRLOG(status); |
| return kAudioSinkCreateError; |
| } |
| audio_sink_ = ptr_filter; |
| status = graph_builder_->AddFilter(audio_sink_, kAudioSinkName); |
| if (FAILED(status)) { |
| LOG(ERROR) << "cannot add audio sink to graph" << HRLOG(status); |
| return kCannotAddFilter; |
| } |
| return kSuccess; |
| } |
| |
| int MediaSourceImpl::ConnectAudioSourceToAudioSink() { |
| PinFinder pin_finder; |
| int status = pin_finder.Init(audio_source_); |
| if (status) { |
| LOG(ERROR) << "cannot look for pins on audio source!"; |
| return kAudioConnectError; |
| } |
| IPinPtr audio_source_pin = pin_finder.FindAudioOutputPin(0); |
| if (!audio_source_pin) { |
| LOG(ERROR) << "cannot find output pin on audio source!"; |
| return kAudioConnectError; |
| } |
| status = pin_finder.Init(audio_sink_); |
| if (status) { |
| LOG(ERROR) << "cannot look for pins on audio sink!"; |
| return kAudioConnectError; |
| } |
| IPinPtr sink_input_pin = pin_finder.FindAudioInputPin(0); |
| if (!sink_input_pin) { |
| LOG(ERROR) << "cannot find audio input pin on audio sink filter!"; |
| return kAudioConnectError; |
| } |
| |
| // Try connecting |audio_source_| to |audio_sink_| using settings |
| // in |requested_audio_config_|. |
| HRESULT hr = E_FAIL; |
| MediaTypePtr accepted_type; |
| status = ConfigureAudioSource(audio_source_pin, &accepted_type); |
| if (status == kSuccess) { |
| LOG(INFO) << "audio source configuration OK."; |
| hr = graph_builder_->ConnectDirect(audio_source_pin, sink_input_pin, |
| accepted_type.get()); |
| if (hr == S_OK) { |
| LOG(INFO) << "configured source connected."; |
| } |
| } |
| |
| if (status || hr != S_OK) { |
| // All previous connection attempts failed. Try one last time using |
| // |audio_source_pin|'s default format. |
| PinFormat formatter(audio_source_pin); |
| status = formatter.set_format(NULL); |
| if (status == kSuccess) { |
| hr = graph_builder_->ConnectDirect(audio_source_pin, sink_input_pin, |
| NULL); |
| if (hr == S_OK) { |
| LOG(INFO) << "Connected with audio source pin default format."; |
| } else { |
| LOG(ERROR) << "Cannot connect audio source to audio sink."; |
| status = kAudioConnectError; |
| } |
| } |
| } |
| if (status == kSuccess && hr == S_OK) { |
| AM_MEDIA_TYPE media_type = {0}; |
| // Store the actual audio capture settings. |
| hr = audio_source_pin->ConnectionMediaType(&media_type); |
| if (hr == S_OK) { |
| AudioMediaType audio_format; |
| if (audio_format.Init(media_type) == kSuccess) { |
| LOG(INFO) << "ConnectAudioSourceToAudioSink actual settings\n" |
| << " block_align=" << audio_format.block_align() << "\n" |
| << " bytes_per_second=" << audio_format.bytes_per_second() |
| << "\n" |
| << " channels=" << audio_format.channels() << "\n" |
| << " format_tag=" << audio_format.format_tag() << "\n" |
| << " sample_rate=" << audio_format.sample_rate() << "\n" |
| << " bits_per_sample=" << audio_format.bits_per_sample() |
| << "\n"; |
| if (audio_format.format_tag() == WAVE_FORMAT_EXTENSIBLE) { |
| LOG(INFO) << " valid_bits_per_sample=" |
| << audio_format.valid_bits_per_sample() << "\n" |
| << " channel_mask=" << std::hex |
| << audio_format.channel_mask() << "\n"; |
| actual_audio_config_.valid_bits_per_sample = |
| audio_format.valid_bits_per_sample(); |
| actual_audio_config_.channel_mask = |
| audio_format.channel_mask(); |
| } |
| actual_audio_config_.block_align = audio_format.block_align(); |
| actual_audio_config_.bytes_per_second = |
| audio_format.bytes_per_second(); |
| actual_audio_config_.channels = audio_format.channels(); |
| actual_audio_config_.format_tag = audio_format.format_tag(); |
| actual_audio_config_.sample_rate = audio_format.sample_rate(); |
| actual_audio_config_.bits_per_sample = audio_format.bits_per_sample(); |
| } |
| } |
| MediaType::FreeMediaTypeData(&media_type); |
| } |
| return status; |
| } |
| |
| // Checks |media_event_handle_| and reads the event from |media_event_| when |
| // signaled. Responds only to completion and error events. |
| int MediaSourceImpl::HandleMediaEvent() { |
| int status = kSuccess; |
| const DWORD wait_status = WaitForSingleObject(media_event_handle_, 0); |
| const bool media_event_recvd = (wait_status == WAIT_OBJECT_0); |
| if (media_event_recvd) { |
| long code = 0; // NOLINT |
| long param1 = 0; // NOLINT |
| long param2 = 0; // NOLINT |
| const HRESULT hr = media_event_->GetEvent(&code, ¶m1, ¶m2, 0); |
| media_event_->FreeEventParams(code, param1, param2); |
| if (SUCCEEDED(hr)) { |
| if (code == EC_ERRORABORT) { |
| LOG(ERROR) << "EC_ERRORABORT"; |
| status = kGraphAborted; |
| } else if (code == EC_ERRORABORTEX) { |
| LOG(ERROR) << "EC_ERRORABORTEX"; |
| status = kGraphAborted; |
| } else if (code == EC_USERABORT) { |
| LOG(ERROR) << "EC_USERABORT"; |
| status = kGraphAborted; |
| } else if (code == EC_COMPLETE) { |
| LOG(INFO) << "EC_COMPLETE"; |
| status = kGraphCompleted; |
| } |
| } else { |
| // Couldn't get the event; tell caller to abort. |
| LOG(ERROR) << "GetEvent failed: " << HRLOG(hr); |
| status = kGraphAborted; |
| } |
| } |
| return status; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // CaptureSourceLoader |
| // |
| |
| CaptureSourceLoader::CaptureSourceLoader() |
| : source_type_(GUID_NULL) { |
| } |
| |
| CaptureSourceLoader::~CaptureSourceLoader() { |
| } |
| |
| // Verifies that |source_type| is known and calls |FindAllSources|. |
| int CaptureSourceLoader::Init(CLSID source_type) { |
| if (source_type != CLSID_AudioInputDeviceCategory && |
| source_type != CLSID_VideoInputDeviceCategory) { |
| LOG(ERROR) << "unknown device category!"; |
| return WebmEncoder::kInvalidArg; |
| } |
| source_type_ = source_type; |
| return FindAllSources(); |
| } |
| |
| // Enumerates input devices of type |source_type_| and adds them to the map of |
| // sources, |sources_|. |
| int CaptureSourceLoader::FindAllSources() { |
| ICreateDevEnumPtr sys_enum; |
| HRESULT hr = sys_enum.CreateInstance(CLSID_SystemDeviceEnum); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "source enumerator creation failed." << HRLOG(hr); |
| return kNoDeviceFound; |
| } |
| const DWORD kNoEnumFlags = 0; |
| hr = sys_enum->CreateClassEnumerator(source_type_, &source_enum_, |
| kNoEnumFlags); |
| if (FAILED(hr) || hr == S_FALSE) { |
| LOG(ERROR) << "moniker creation failed (no devices)." << HRLOG(hr); |
| return kNoDeviceFound; |
| } |
| int source_index = 0; |
| for (;;) { |
| IMonikerPtr source_moniker; |
| hr = source_enum_->Next(1, &source_moniker, NULL); |
| if (FAILED(hr) || hr == S_FALSE || !source_moniker) { |
| LOG(INFO) << "Done enumerating sources, found " << source_index |
| << " sources."; |
| break; |
| } |
| std::wstring name = GetMonikerFriendlyName(source_moniker); |
| if (name.empty()) { |
| LOG(WARNING) << "source=" << source_index << " has no name, skipping."; |
| continue; |
| } |
| VLOG(4) << "source=" << source_index << " name=" |
| << wstring_to_string(name.c_str()); |
| sources_[source_index] = name; |
| ++source_index; |
| } |
| if (sources_.size() == 0) { |
| LOG(ERROR) << "No devices found!"; |
| return kNoDeviceFound; |
| } |
| return kSuccess; |
| } |
| |
| // Obtains source name for |index| and returns result of |
| // |GetSource(std::wstring)|. |
| IBaseFilterPtr CaptureSourceLoader::GetSource(int index) { |
| if (static_cast<size_t>(index) >= sources_.size()) { |
| LOG(ERROR) << "" << index << " is not a valid source index"; |
| return NULL; |
| } |
| return GetSource(GetSourceName(index)); |
| } |
| |
| // Resets |source_enum_| and enumerates video input sources until one matching |
| // |name| is found. Then creates an instance of the filter by calling |
| // |BindToObject| on the device moniker (|source_moniker|) returned by the |
| // enumerator. |
| IBaseFilterPtr CaptureSourceLoader::GetSource(const std::wstring name) { |
| if (name.empty()) { |
| LOG(ERROR) << "empty source name."; |
| return NULL; |
| } |
| HRESULT hr = source_enum_->Reset(); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "cannot reset source enumerator!" << HRLOG(hr); |
| return NULL; |
| } |
| IMonikerPtr source_moniker; |
| for (;;) { |
| hr = source_enum_->Next(1, &source_moniker, NULL); |
| if (FAILED(hr) || hr == S_FALSE || !source_moniker) { |
| LOG(ERROR) << "device not found!"; |
| return NULL; |
| } |
| std::wstring source_name = GetMonikerFriendlyName(source_moniker); |
| if (source_name == name) { |
| break; |
| } |
| } |
| IBaseFilterPtr filter = NULL; |
| hr = source_moniker->BindToObject(NULL, NULL, IID_IBaseFilter, |
| reinterpret_cast<void**>(&filter)); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "cannot bind filter!" << HRLOG(hr); |
| } |
| return filter; |
| } |
| |
| // Extracts a string value from a |VARIANT|. Returns emptry string on failure. |
| std::wstring CaptureSourceLoader::GetStringProperty( |
| const IPropertyBagPtr &prop_bag, |
| std::wstring prop_name) { |
| VARIANT var; |
| VariantInit(&var); |
| const HRESULT hr = prop_bag->Read(prop_name.c_str(), &var, NULL); |
| std::wstring name; |
| if (SUCCEEDED(hr)) { |
| name = V_BSTR(&var); |
| } |
| VariantClear(&var); |
| return name; |
| } |
| |
| // Returns the value of |moniker|'s friendly name property. Returns an empty |
| // std::wstring on failure. |
| std::wstring CaptureSourceLoader::GetMonikerFriendlyName( |
| const IMonikerPtr& moniker) { |
| std::wstring name; |
| if (moniker) { |
| IPropertyBagPtr props; |
| HRESULT hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, |
| reinterpret_cast<void**>(&props)); |
| if (hr == S_OK) { |
| const wchar_t* const kFriendlyName = L"FriendlyName"; |
| name = GetStringProperty(props, kFriendlyName); |
| if (name.empty()) { |
| LOG(WARNING) << "moniker friendly name property missing or empty."; |
| } |
| } |
| } |
| return name; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // PinFinder |
| // |
| |
| PinFinder::PinFinder() { |
| } |
| |
| PinFinder::~PinFinder() { |
| } |
| |
| // Creates the pin enumerator, |pin_enum_| |
| int PinFinder::Init(const IBaseFilterPtr& filter) { |
| if (!filter) { |
| LOG(ERROR) << "NULL filter."; |
| return WebmEncoder::kInvalidArg; |
| } |
| const HRESULT hr = filter->EnumPins(&pin_enum_); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "cannot enum filter pins!" << HRLOG(hr); |
| return WebmEncoder::kInitFailed; |
| } |
| return WebmEncoder::kSuccess; |
| } |
| |
| // Enumerates pins and uses |PinInfo| to find audio input pins while |
| // incrementing |num_found| until |index| is reached. Returns empty |IPinPtr| |
| // on failure. |
| IPinPtr PinFinder::FindAudioInputPin(int index) const { |
| IPinPtr pin; |
| int num_found = 0; |
| for (;;) { |
| const HRESULT hr = pin_enum_->Next(1, &pin, NULL); |
| if (hr != S_OK) { |
| break; |
| } |
| PinInfo pin_info(pin); |
| if (pin_info.IsInput() && pin_info.IsAudio()) { |
| ++num_found; |
| if (num_found == index+1) { |
| break; |
| } |
| } |
| } |
| return pin; |
| } |
| |
| // Enumerates pins and uses |PinInfo| to find audio output pins while |
| // incrementing |num_found| until |index| is reached. Returns empty |IPinPtr| |
| // on failure. |
| IPinPtr PinFinder::FindAudioOutputPin(int index) const { |
| IPinPtr pin; |
| int num_found = 0; |
| for (;;) { |
| const HRESULT hr = pin_enum_->Next(1, &pin, NULL); |
| if (hr != S_OK) { |
| break; |
| } |
| PinInfo pin_info(pin); |
| if (pin_info.IsOutput() && pin_info.IsAudio()) { |
| ++num_found; |
| if (num_found == index+1) { |
| break; |
| } |
| } |
| } |
| return pin; |
| } |
| |
| // Enumerates pins and uses |PinInfo| to find video input pins while |
| // incrementing |num_found| until |index| is reached. Returns empty |IPinPtr| |
| // on failure. |
| IPinPtr PinFinder::FindVideoInputPin(int index) const { |
| IPinPtr pin; |
| int num_found = 0; |
| for (;;) { |
| const HRESULT hr = pin_enum_->Next(1, &pin, NULL); |
| if (hr != S_OK) { |
| break; |
| } |
| PinInfo pin_info(pin); |
| if (pin_info.IsInput() && pin_info.IsVideo()) { |
| ++num_found; |
| if (num_found == index+1) { |
| break; |
| } |
| } |
| } |
| return pin; |
| } |
| |
| // Enumerates pins and uses |PinInfo| to find video output pins while |
| // incrementing |num_found| until |index| is reached. Returns empty |IPinPtr| |
| // on failure. |
| IPinPtr PinFinder::FindVideoOutputPin(int index) const { |
| IPinPtr pin; |
| int num_found = 0; |
| for (;;) { |
| const HRESULT hr = pin_enum_->Next(1, &pin, NULL); |
| if (hr != S_OK) { |
| break; |
| } |
| PinInfo pin_info(pin); |
| if (pin_info.IsOutput() && pin_info.IsVideo()) { |
| ++num_found; |
| if (num_found == index+1) { |
| break; |
| } |
| } |
| } |
| return pin; |
| } |
| |
| // Enumerates pins and uses |PinInfo| to find stream input pins while |
| // incrementing |num_found| until |index| is reached. Returns empty |IPinPtr| |
| // on failure. |
| IPinPtr PinFinder::FindStreamInputPin(int index) const { |
| IPinPtr pin; |
| int num_found = 0; |
| for (;;) { |
| const HRESULT hr = pin_enum_->Next(1, &pin, NULL); |
| if (hr != S_OK) { |
| break; |
| } |
| PinInfo pin_info(pin); |
| if (pin_info.IsInput() && pin_info.IsStream()) { |
| ++num_found; |
| if (num_found == index+1) { |
| break; |
| } |
| } |
| } |
| return pin; |
| } |
| |
| // Enumerates pins and uses |PinInfo| to find stream output pins while |
| // incrementing |num_found| until |index| is reached. Returns empty |IPinPtr| |
| // on failure. |
| IPinPtr PinFinder::FindStreamOutputPin(int index) const { |
| IPinPtr pin; |
| int num_found = 0; |
| for (;;) { |
| const HRESULT hr = pin_enum_->Next(1, &pin, NULL); |
| if (hr != S_OK) { |
| break; |
| } |
| PinInfo pin_info(pin); |
| if (pin_info.IsOutput() && pin_info.IsStream()) { |
| ++num_found; |
| if (num_found == index+1) { |
| break; |
| } |
| } |
| } |
| return pin; |
| } |
| |
| // Enumerates pins and uses |PinInfo| to find input pins while incrementing |
| // |num_found| until |index| is reached. Returns empty |IPinPtr| on failure. |
| IPinPtr PinFinder::FindInputPin(int index) const { |
| IPinPtr pin; |
| int num_found = 0; |
| for (;;) { |
| const HRESULT hr = pin_enum_->Next(1, &pin, NULL); |
| if (hr != S_OK) { |
| break; |
| } |
| PinInfo pin_info(pin); |
| if (pin_info.IsInput()) { |
| ++num_found; |
| if (num_found == index+1) { |
| break; |
| } |
| } |
| } |
| return pin; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // PinInfo |
| // |
| |
| // Construct |PinInfo| for |pin|. |
| PinInfo::PinInfo(const IPinPtr& pin) |
| : pin_(pin) { |
| } |
| |
| PinInfo::~PinInfo() { |
| } |
| |
| // Enumerate media types on |pin_| until match for |major_type| is found or |
| // types are exhausted. |
| bool PinInfo::HasMajorType(GUID major_type) const { |
| bool has_type = false; |
| if (pin_) { |
| IEnumMediaTypesPtr mediatype_enum; |
| HRESULT hr = pin_->EnumMediaTypes(&mediatype_enum); |
| if (SUCCEEDED(hr)) { |
| for (;;) { |
| AM_MEDIA_TYPE* ptr_media_type = NULL; |
| hr = mediatype_enum->Next(1, &ptr_media_type, 0); |
| if (hr != S_OK) { |
| break; |
| } |
| has_type = (ptr_media_type && ptr_media_type->majortype == major_type); |
| MediaType::FreeMediaTypeData(ptr_media_type); |
| if (has_type) { |
| break; |
| } |
| } |
| } |
| } |
| return has_type; |
| } |
| |
| // Returns true if |HasMajorType| returns true for |MEDIATYPE_Audio|. |
| bool PinInfo::IsAudio() const { |
| bool is_audio_pin = false; |
| if (pin_) { |
| is_audio_pin = HasMajorType(MEDIATYPE_Audio); |
| } |
| return is_audio_pin; |
| } |
| |
| // Returns true if |pin_->QueryDirection| succeeds and sets |direction| to |
| // |PINDIR_INPUT|. |
| bool PinInfo::IsInput() const { |
| bool is_input = false; |
| if (pin_) { |
| PIN_DIRECTION direction; |
| const HRESULT hr = pin_->QueryDirection(&direction); |
| is_input = (hr == S_OK && direction == PINDIR_INPUT); |
| } |
| return is_input; |
| } |
| |
| // Returns true if |pin_->QueryDirection| succeeds and sets |direction| to |
| // |PINDIR_OUTPUT|. |
| bool PinInfo::IsOutput() const { |
| bool is_output = false; |
| if (pin_) { |
| PIN_DIRECTION direction; |
| const HRESULT hr = pin_->QueryDirection(&direction); |
| is_output = (hr == S_OK && direction == PINDIR_OUTPUT); |
| } |
| return is_output; |
| } |
| |
| // Returns true if |HasMajorType| returns true for |MEDIATYPE_Video|. |
| bool PinInfo::IsVideo() const { |
| bool is_video_pin = false; |
| if (pin_) { |
| is_video_pin = HasMajorType(MEDIATYPE_Video); |
| } |
| return is_video_pin; |
| } |
| |
| // Returns true if |HasMajorType| returns true for |MEDIATYPE_Stream|. |
| bool PinInfo::IsStream() const { |
| bool is_stream_pin = false; |
| if (pin_) { |
| is_stream_pin = HasMajorType(MEDIATYPE_Stream); |
| } |
| return is_stream_pin; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // VideoPinInfo |
| // |
| |
| VideoPinInfo::VideoPinInfo() { |
| } |
| |
| VideoPinInfo::~VideoPinInfo() { |
| } |
| |
| // Copies |pin| to |pin_| (results in an AddRef), and: |
| // - confirms that |pin| is a video pin using |PinInfo::IsVideo|, and |
| // - confirms that |pin| is connected. |
| int VideoPinInfo::Init(const IPinPtr& pin) { |
| if (!pin) { |
| LOG(ERROR) << "empty pin."; |
| return kInvalidArg; |
| } |
| PinInfo info(pin); |
| if (info.IsVideo() == false) { |
| LOG(ERROR) << "Not a video pin."; |
| return kNotVideo; |
| } |
| // Confirm that |pin| is connected. |
| IPinPtr pin_peer; |
| HRESULT hr = pin->ConnectedTo(&pin_peer); |
| if (hr != S_OK || !pin_peer) { |
| LOG(ERROR) << "pin not connected."; |
| return kNotConnected; |
| } |
| pin_ = pin; |
| return kSuccess; |
| } |
| |
| // Grabs the pin media type, extracts the |VIDEOINFOHEADER|, calculates frame |
| // rate using |AvgTimePerFrame|, and returns the value. Returns a rate <0 on |
| // failure. Note that an |AvgTimePerFrame| of 0 is interpreted as an error. |
| double VideoPinInfo::frame_rate() const { |
| double frames_per_second = -1.0; |
| // Validate |pin_| again; |_com_ptr_t| throws when empty. |
| if (pin_) { |
| AM_MEDIA_TYPE media_type; |
| const HRESULT hr = pin_->ConnectionMediaType(&media_type); |
| if (hr == S_OK) { |
| VideoMediaType video_format; |
| const int status = video_format.Init(media_type); |
| if (status == kSuccess) { |
| frames_per_second = video_format.frame_rate(); |
| } |
| MediaType::FreeMediaTypeData(&media_type); |
| } |
| } |
| return frames_per_second; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // PinFormat |
| // |
| |
| // Construct |PinFormat| for |pin|. |
| PinFormat::PinFormat(const IPinPtr& pin) |
| : pin_(pin) { |
| } |
| |
| PinFormat::~PinFormat() { |
| } |
| |
| // Returns |pin_| format via use of IAMStreamConfig::GetFormat, or returns NULL |
| // on failure. |
| AM_MEDIA_TYPE* PinFormat::format() const { |
| const IAMStreamConfigPtr config(pin_); |
| if (!config) { |
| LOG(ERROR) << "pin_ has no IAMStreamConfig interface."; |
| return NULL; |
| } |
| AM_MEDIA_TYPE* ptr_current_format = NULL; |
| const HRESULT hr = config->GetFormat(&ptr_current_format); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "IAMStreamConfig::GetFormat failed: " << HRLOG(hr); |
| } |
| return ptr_current_format; |
| } |
| |
| // Sets |pin_| format via use of IAMStreamConfig::SetFormat. Returns |
| // |kCannotSetFormat| on failure. |
| int PinFormat::set_format(const AM_MEDIA_TYPE* ptr_format) { |
| // Note: NULL |ptr_format| is OK-- some filters treat a NULL format as a |
| // request to reset to the pin's default format. |
| const IAMStreamConfigPtr config(pin_); |
| if (!config) { |
| LOG(ERROR) << "pin_ has no IAMStreamConfig interface."; |
| return kCannotSetFormat; |
| } |
| const HRESULT hr = config->SetFormat(const_cast<AM_MEDIA_TYPE*>(ptr_format)); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "cannot set pin_ format: " << HRLOG(hr); |
| return kCannotSetFormat; |
| } |
| return kSuccess; |
| } |
| |
| // Returns AM_MEDIA_TYPE pointer with settings matching those requested, or |
| // NULL. |
| AM_MEDIA_TYPE* PinFormat::FindMatchingFormat(const AudioConfig& config) { |
| IEnumMediaTypesPtr media_types; |
| HRESULT hr = pin_->EnumMediaTypes(&media_types); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "pin cannot enumerate media types."; |
| return NULL; |
| } |
| MediaTypePtr format; |
| for (;;) { |
| hr = media_types->Next(1, format.GetPtr(), NULL); |
| if (hr != S_OK) { |
| LOG(INFO) << "exhausted audio media types without finding a match."; |
| // be certain |format| is empty. |
| format.Free(); |
| break; |
| } |
| AudioMediaType audio_format; |
| const int status = audio_format.Init(*format.get()); |
| if (status) { |
| LOG(INFO) << "skipping unsupported audio media type."; |
| continue; |
| } |
| if (audio_format.channels() == config.channels && |
| audio_format.sample_rate() == config.sample_rate && |
| audio_format.bits_per_sample() == config.bits_per_sample) { |
| LOG(INFO) << "Found matching audio media type."; |
| break; |
| } |
| } |
| return format.Detach(); |
| } |
| |
| // Returns AM_MEDIA_TYPE pointer with settings matching those requested, or |
| // NULL. |
| AM_MEDIA_TYPE* PinFormat::FindMatchingFormat(const VideoConfig& config) { |
| IEnumMediaTypesPtr media_types; |
| HRESULT hr = pin_->EnumMediaTypes(&media_types); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "pin cannot enumerate media types."; |
| return NULL; |
| } |
| MediaTypePtr format; |
| for (;;) { |
| hr = media_types->Next(1, format.GetPtr(), NULL); |
| if (hr != S_OK) { |
| LOG(INFO) << "exhausted video media types without finding a match."; |
| // be certain |format| is empty. |
| format.Free(); |
| break; |
| } |
| VideoMediaType video_format; |
| const int status = video_format.Init(*format.get()); |
| if (status) { |
| LOG(INFO) << "skipping unsupported video media type."; |
| continue; |
| } |
| if (video_format.width() == config.width && |
| video_format.height() == config.height && |
| video_format.frame_rate() == config.frame_rate) { |
| LOG(INFO) << "Found matching video media type."; |
| break; |
| } |
| } |
| return format.Detach(); |
| } |
| |
| // Displays |filter|'s property page. |
| HRESULT ShowFilterPropertyPage(const IBaseFilterPtr& filter) { |
| if (!filter) { |
| LOG(ERROR) << "empty IBaseFilterPtr."; |
| return E_POINTER; |
| } |
| // Get |filter|'s IUnknown pointer. |
| IUnknownPtr iunknown(filter); |
| if (!iunknown) { |
| LOG(ERROR) << "filter does not support IUnknown?!"; |
| return E_NOINTERFACE; |
| } |
| return ShowPropertyPage(iunknown.GetInterfacePtr()); |
| } |
| |
| // Displays |pin|'s property page. |
| HRESULT ShowPinPropertyPage(const IPinPtr& pin) { |
| if (!pin) { |
| LOG(ERROR) << "empty IPinPtr."; |
| return E_POINTER; |
| } |
| // Get |pin|'s IUnknown pointer. |
| IUnknownPtr iunknown(pin); |
| if (!iunknown) { |
| LOG(ERROR) << "pin does not support IUnknown?!"; |
| return E_NOINTERFACE; |
| } |
| return ShowPropertyPage(iunknown.GetInterfacePtr()); |
| } |
| |
| // Displays property page for |ptr_iunknown|, and returns HRESULT code from |
| // the windows COM API call. |
| HRESULT ShowPropertyPage(IUnknown* ptr_iunknown) { |
| if (!ptr_iunknown) { |
| LOG(ERROR) << "Null IUnknown pointer."; |
| return E_POINTER; |
| } |
| ISpecifyPropertyPagesPtr prop(ptr_iunknown); |
| if (!prop) { |
| LOG(ERROR) << "ISpecifyPropertyPages not supported."; |
| return E_NOINTERFACE; |
| } |
| // Show |ptr_iunknown|'s page. |
| CAUUID caGUID; |
| HRESULT hr = prop->GetPages(&caGUID); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "filter has no property pages." << HRLOG(hr); |
| return hr; |
| } |
| // Release |prop| because this is what the MSDN sample does. |
| prop = 0; |
| hr = OleCreatePropertyFrame(NULL, // Parent window |
| 0, 0, // Reserved |
| NULL, // Caption for the dialog. |
| 1, // Number of objects. |
| &ptr_iunknown, |
| caGUID.cElems, // Number of prop pages. |
| caGUID.pElems, // Array of page CLSIDs. |
| 0, // Locale identifier. |
| 0, NULL); // Reserved |
| // Note: |OleCreatePropertyFrame| returns S_OK as long as the prop dialog is |
| // shown. It does not mean that the user did anything in particular. |
| // Log the return value if it isn't |S_OK|, because that would be |
| // weird. |
| if (hr != S_OK) { |
| LOG(INFO) << "OleCreatePropertyFrame returned: " << HRLOG(hr); |
| } |
| if (caGUID.pElems) { |
| CoTaskMemFree(caGUID.pElems); |
| } |
| return hr; |
| } |
| |
| } // namespace webmlive |