| // 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/video_sink_filter.h" |
| |
| #include <dvdmedia.h> |
| #include <vfwmsgs.h> |
| |
| #include "encoder/win/dshow_util.h" |
| #include "encoder/win/media_source_dshow.h" |
| #include "encoder/win/media_type_dshow.h" |
| #include "encoder/win/webm_guids.h" |
| #include "glog/logging.h" |
| |
| namespace webmlive { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // VideoSinkPin |
| // |
| |
| VideoSinkPin::VideoSinkPin(TCHAR* ptr_object_name, |
| VideoSinkFilter* ptr_filter, |
| CCritSec* ptr_filter_lock, |
| HRESULT* ptr_result, |
| LPCWSTR ptr_pin_name) |
| : CBaseInputPin(ptr_object_name, |
| ptr_filter, |
| ptr_filter_lock, |
| ptr_result, |
| ptr_pin_name) { |
| } |
| |
| VideoSinkPin::~VideoSinkPin() { |
| } |
| |
| // Returns preferred media type. |
| HRESULT VideoSinkPin::GetMediaType(int32 type_index, |
| CMediaType* ptr_media_type) { |
| if (type_index < 0 || !ptr_media_type) { |
| return E_INVALIDARG; |
| } |
| if (type_index > 1) { |
| return VFW_S_NO_MORE_ITEMS; |
| } |
| VIDEOINFOHEADER* const ptr_video_info = |
| reinterpret_cast<VIDEOINFOHEADER*>( |
| ptr_media_type->AllocFormatBuffer(sizeof(VIDEOINFOHEADER))); |
| if (!ptr_video_info) { |
| LOG(ERROR) << "VIDEOINFOHEADER alloc failed."; |
| return E_OUTOFMEMORY; |
| } |
| ZeroMemory(ptr_video_info, sizeof(VIDEOINFOHEADER)); |
| ptr_video_info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); |
| |
| // Use empty source/dest rectangle-- the entire image is needed, and there is |
| // no target subrect. |
| SetRectEmpty(&ptr_video_info->rcSource); |
| SetRectEmpty(&ptr_video_info->rcTarget); |
| |
| // Set values for all input types supported. |
| ptr_media_type->SetType(&MEDIATYPE_Video); |
| ptr_media_type->SetFormatType(&FORMAT_VideoInfo); |
| ptr_media_type->SetTemporalCompression(FALSE); |
| ptr_video_info->bmiHeader.biPlanes = 1; |
| |
| if (requested_config_.width != 0) |
| ptr_video_info->bmiHeader.biWidth = requested_config_.width; |
| if (requested_config_.height != 0) |
| ptr_video_info->bmiHeader.biHeight = requested_config_.height; |
| |
| if (type_index == 0) { |
| // Set sub type and format data for I420. |
| ptr_video_info->bmiHeader.biCompression = MAKEFOURCC('I', '4', '2', '0'); |
| ptr_video_info->bmiHeader.biBitCount = kI420BitCount; |
| ptr_media_type->SetSubtype(&MEDIASUBTYPE_I420); |
| } else { |
| // Set sub type and format data for YV12. |
| ptr_video_info->bmiHeader.biCompression = MAKEFOURCC('Y', 'V', '1', '2'); |
| ptr_video_info->bmiHeader.biBitCount = kYV12BitCount; |
| ptr_media_type->SetSubtype(&MEDIASUBTYPE_YV12); |
| } |
| |
| // Set sample size. |
| ptr_video_info->bmiHeader.biSizeImage = DIBSIZE(ptr_video_info->bmiHeader); |
| ptr_media_type->SetSampleSize(ptr_video_info->bmiHeader.biSizeImage); |
| LOG(INFO) << "\n GetMediaType type_index=" << type_index << "\n" |
| << " width=" << ptr_video_info->bmiHeader.biWidth << "\n" |
| << " height=" << ptr_video_info->bmiHeader.biHeight << "\n" |
| << std::hex << " biCompression=" |
| << ptr_video_info->bmiHeader.biCompression; |
| return S_OK; |
| } |
| |
| const BITMAPINFOHEADER* BitmapInfo(const GUID& format_guid, |
| const uint8* ptr_format_blob, |
| uint32 format_length) { |
| const BITMAPINFOHEADER* ptr_header = NULL; |
| if (ptr_format_blob) { |
| if (format_guid == FORMAT_VideoInfo && |
| format_length >= sizeof(VIDEOINFOHEADER)) { |
| const VIDEOINFOHEADER* ptr_video_info = |
| reinterpret_cast<const VIDEOINFOHEADER*>(ptr_format_blob); |
| ptr_header = &ptr_video_info->bmiHeader; |
| } else if (format_guid == FORMAT_VideoInfo2 && |
| format_length >= sizeof(VIDEOINFOHEADER2)) { |
| const VIDEOINFOHEADER2* ptr_video_info = |
| reinterpret_cast<const VIDEOINFOHEADER2*>(ptr_format_blob); |
| ptr_header = &ptr_video_info->bmiHeader; |
| } |
| } |
| return ptr_header; |
| } |
| |
| // Confirms that |ptr_media_type| is VIDEOINFOHEADER or VIDEOINFOHEADER2 and |
| // has a subtype of MEDIASUBTYPE_I420. |
| HRESULT VideoSinkPin::CheckMediaType(const CMediaType* ptr_media_type) { |
| // Confirm media type is acceptable. |
| const GUID* const ptr_type_guid = ptr_media_type->Type(); |
| if (!ptr_type_guid || *ptr_type_guid != MEDIATYPE_Video) { |
| return VFW_E_TYPE_NOT_ACCEPTED; |
| } |
| |
| // Confirm that subtype and formattype GUIDs can be obtained. |
| const GUID* const ptr_subtype_guid = ptr_media_type->Subtype(); |
| const GUID* const ptr_format_guid = ptr_media_type->FormatType(); |
| if (!ptr_subtype_guid || !ptr_format_guid) { |
| return E_INVALIDARG; |
| } |
| |
| const GUID& format_guid = *ptr_format_guid; |
| const GUID& subtype_guid = *ptr_subtype_guid; |
| |
| // Confirm that the subtype is acceptable. |
| if (!AcceptableSubType(subtype_guid)) { |
| return VFW_E_TYPE_NOT_ACCEPTED; |
| } |
| |
| // Obtain access to the BITMAPINFOHEADER, and confirm that the video format |
| // is acceptable. |
| const uint8* ptr_format = ptr_media_type->Format(); |
| const uint32 format_length = ptr_media_type->FormatLength(); |
| const BITMAPINFOHEADER* ptr_header = |
| BitmapInfo(format_guid, ptr_format, format_length); |
| if (ptr_header) { |
| if (FourCCToVideoFormat(ptr_header->biCompression, |
| ptr_header->biBitCount, |
| &actual_config_.format)) { |
| // |ptr_media_type| contains a video frame with a pixel format that is |
| // acceptable-- store format information. |
| actual_config_.width = ptr_header->biWidth; |
| actual_config_.height = ptr_header->biHeight; |
| |
| // Store the stride for use with |VideoFrame::Init()|-- it's needed for |
| // format conversion. |
| actual_config_.stride = DIBWIDTHBYTES(*ptr_header); |
| } |
| } |
| |
| LOG(INFO) << "\n CheckMediaType actual video settings\n" |
| << " width=" << actual_config_.width << "\n" |
| << " height=" << actual_config_.height << "\n" |
| << " stride=" << actual_config_.stride << "\n" |
| << " format=" << actual_config_.format; |
| return S_OK; |
| } |
| |
| // Calls CBaseInputPin::Receive and then passes |ptr_sample| to |
| // |VideoSinkFilter::OnFrameReceived|. |
| HRESULT VideoSinkPin::Receive(IMediaSample* ptr_sample) { |
| CHECK_NOTNULL(m_pFilter); |
| CHECK_NOTNULL(ptr_sample); |
| VideoSinkFilter* ptr_filter = reinterpret_cast<VideoSinkFilter*>(m_pFilter); |
| CAutoLock lock(&ptr_filter->filter_lock_); |
| HRESULT hr = CBaseInputPin::Receive(ptr_sample); |
| if (FAILED(hr)) { |
| if (hr != VFW_E_WRONG_STATE) { |
| // Log the error only when it is not |VFW_E_WRONG_STATE|. The filter |
| // graph appears to always call |Receive()| once after |Stop()|. |
| LOG(ERROR) << "CBaseInputPin::Receive failed. " << HRLOG(hr); |
| } |
| return hr; |
| } |
| hr = ptr_filter->OnFrameReceived(ptr_sample); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "OnFrameReceived failed. " << HRLOG(hr); |
| } |
| return S_OK; |
| } |
| |
| bool VideoSinkPin::AcceptableSubType(const GUID& media_sub_type) { |
| return (media_sub_type == MEDIASUBTYPE_I420 || |
| media_sub_type == MEDIASUBTYPE_YV12 || |
| media_sub_type == MEDIASUBTYPE_YUY2 || |
| media_sub_type == MEDIASUBTYPE_YUYV || |
| media_sub_type == MEDIASUBTYPE_UYVY || |
| media_sub_type == MEDIASUBTYPE_RGB24 || |
| media_sub_type == MEDIASUBTYPE_RGB32); |
| } |
| |
| // Copies |actual_config_| to |ptr_config|. Note that the filter lock is always |
| // held by caller, |VideoSinkFilter::config|. |
| HRESULT VideoSinkPin::config(VideoConfig* ptr_config) const { |
| if (!ptr_config) { |
| return E_POINTER; |
| } |
| *ptr_config = actual_config_; |
| return S_OK; |
| } |
| |
| // Sets |requested_config_| and resets |actual_config_|. Filter lock always |
| // held by caller, |VideoSinkFilter::set_config|. |
| HRESULT VideoSinkPin::set_config(const VideoConfig& config) { |
| requested_config_ = config; |
| actual_config_ = VideoConfig(); |
| return S_OK; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // VideoSinkFilter |
| // |
| VideoSinkFilter::VideoSinkFilter( |
| const TCHAR* ptr_filter_name, |
| LPUNKNOWN ptr_iunknown, |
| VideoFrameCallbackInterface* ptr_frame_callback, |
| HRESULT* ptr_result) |
| : CBaseFilter(ptr_filter_name, |
| ptr_iunknown, |
| &filter_lock_, |
| CLSID_VideoSinkFilter) { |
| if (!ptr_frame_callback) { |
| *ptr_result = E_INVALIDARG; |
| return; |
| } |
| ptr_frame_callback_ = ptr_frame_callback; |
| sink_pin_.reset( |
| new (std::nothrow) VideoSinkPin(NAME("VideoSinkInputPin"), // NOLINT |
| this, &filter_lock_, ptr_result, |
| L"VideoSink")); |
| if (!sink_pin_) { |
| *ptr_result = E_OUTOFMEMORY; |
| } else { |
| *ptr_result = S_OK; |
| } |
| } |
| |
| VideoSinkFilter::~VideoSinkFilter() { |
| } |
| |
| // Locks filter and returns |VideoSinkPin::config|. |
| HRESULT VideoSinkFilter::config(VideoConfig* ptr_config) const { |
| CAutoLock lock(&filter_lock_); |
| return sink_pin_->config(ptr_config); |
| } |
| |
| // Locks filter and returns |VideoSinkPin::set_config|. |
| HRESULT VideoSinkFilter::set_config(const VideoConfig& config) { |
| if (m_State != State_Stopped) { |
| return VFW_E_NOT_STOPPED; |
| } |
| CAutoLock lock(&filter_lock_); |
| return sink_pin_->set_config(config); |
| } |
| |
| // Locks filter and returns VideoSinkPin pointer wrapped by |sink_pin_|. |
| CBasePin* VideoSinkFilter::GetPin(int index) { |
| CBasePin* ptr_pin = NULL; |
| CAutoLock lock(&filter_lock_); |
| if (index == 0) { |
| ptr_pin = sink_pin_.get(); |
| } |
| return ptr_pin; |
| } |
| |
| // Lock owned by |VideoSinkPin::Receive|. Copies buffer from |ptr_sample| into |
| // |frame_|, and then passes |frame_| to |
| // |VideoFrameCallbackInterface::OnVideoFrameReceived|. |
| HRESULT VideoSinkFilter::OnFrameReceived(IMediaSample* ptr_sample) { |
| if (!ptr_sample) { |
| return E_POINTER; |
| } |
| BYTE* ptr_sample_buffer = NULL; |
| HRESULT hr = ptr_sample->GetPointer(&ptr_sample_buffer); |
| if (FAILED(hr) || !ptr_sample_buffer) { |
| LOG(ERROR) << "OnFrameReceived called with empty sample."; |
| hr = (hr == S_OK) ? E_FAIL : hr; |
| return hr; |
| } |
| int64 timestamp = 0; |
| int64 duration = 0; |
| REFERENCE_TIME start_time = 0; |
| REFERENCE_TIME end_time = 0; |
| hr = ptr_sample->GetTime(&start_time, &end_time); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "OnFrameReceived cannot get media time(s)." << HRLOG(hr); |
| return hr; |
| } |
| timestamp = media_time_to_milliseconds(start_time); |
| if (hr != VFW_S_NO_STOP_TIME) { |
| duration = media_time_to_milliseconds(end_time) - timestamp; |
| } else { |
| LOG(WARNING) << "OnFrameReceived using time per frame for duration."; |
| AM_MEDIA_TYPE media_type = {0}; |
| hr = sink_pin_->ConnectionMediaType(&media_type); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "OnFrameReceived pin has no media type: " << HRLOG(hr); |
| return hr; |
| } |
| VideoMediaType video_format; |
| if (video_format.Init(media_type)) { |
| LOG(ERROR) << "OnFrameReceived cannot Init VideoMediaType."; |
| return E_FAIL; |
| } |
| duration = media_time_to_milliseconds(video_format.avg_time_per_frame()); |
| } |
| |
| // TODO(tomfinegan): Write an allocator that retrieves frames from |
| // |WebmEncoder::EncoderThread| and avoid this extra copy. |
| |
| const int status = frame_.Init(sink_pin_->actual_config_, |
| true, // always "keyframes" |
| timestamp, |
| duration, |
| ptr_sample_buffer, |
| ptr_sample->GetActualDataLength()); |
| if (status) { |
| LOG(ERROR) << "OnFrameReceived frame init failed: " << status; |
| return E_FAIL; |
| } |
| LOG(INFO) << "OnFrameReceived received a frame:" |
| << " width=" << sink_pin_->actual_config_.width |
| << " height=" << sink_pin_->actual_config_.height |
| << " format=" << sink_pin_->actual_config_.format |
| << " stride=" << sink_pin_->actual_config_.stride |
| << " timestamp(sec)=" << (timestamp / 1000.0) |
| << " timestamp=" << timestamp |
| << " duration(sec)= " << (duration / 1000.0) |
| << " duration= " << duration |
| << " size=" << frame_.buffer_length(); |
| int frame_status = ptr_frame_callback_->OnVideoFrameReceived(&frame_); |
| if (frame_status && frame_status != VideoFrameCallbackInterface::kDropped) { |
| LOG(ERROR) << "OnVideoFrameReceived failed, status=" << frame_status; |
| } |
| return S_OK; |
| } |
| |
| } // namespace webmlive |