| // Copyright 2017 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/shared/win32/win32_video_decoder.h" |
| |
| #include <Codecapi.h> |
| #include <D3d11_1.h> |
| |
| #include <queue> |
| #include <utility> |
| |
| #include "starboard/shared/starboard/thread_checker.h" |
| #include "starboard/shared/win32/atomic_queue.h" |
| #include "starboard/shared/win32/decrypting_decoder.h" |
| #include "starboard/shared/win32/dx_context_video_decoder.h" |
| #include "starboard/shared/win32/error_utils.h" |
| #include "starboard/shared/win32/media_common.h" |
| #include "starboard/shared/win32/media_foundation_utils.h" |
| #include "starboard/shared/win32/video_texture.h" |
| #include "starboard/shared/win32/video_transform.h" |
| #include "starboard/window.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace win32 { |
| |
| using Microsoft::WRL::ComPtr; |
| using ::starboard::shared::starboard::ThreadChecker; |
| using ::starboard::shared::win32::CheckResult; |
| |
| namespace { |
| |
| // In MS sample it is set to 5 and we experienced issues when this is set to |
| // below 5. This can be further tuned to balance between playback smoothness |
| // and memory consumption. |
| const int kSampleAllocatorFramesMax = 12; |
| // This is the minimum allocated frames in the output ring buffer. Can be |
| // further tuned to save memory. Note that use a value that is too small leads |
| // to hang when calling ProcessOutput(). |
| const int kMinimumOutputSampleCount = kSampleAllocatorFramesMax + 5; |
| |
| // CLSID_CMSVideoDecoderMFT {62CE7E72-4C71-4D20-B15D-452831A87D9D} |
| const GUID CLSID_VideoDecoder = {0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45, |
| 0x28, 0x31, 0xA8, 0x7D, 0x9D}; |
| |
| class AbstractWin32VideoDecoderImpl : public AbstractWin32VideoDecoder { |
| public: |
| AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec, SbDrmSystem drm_system) |
| : thread_checker_(ThreadChecker::kSetThreadIdOnFirstCheck), |
| codec_(codec) { |
| SbMemorySet(&display_aperture_, 0, sizeof(RECT)); |
| Configure(drm_system); |
| } |
| |
| void Consume(ComPtr<IMFSample> sample) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| VideoFramePtr frame_output = VideoFrameFactory::Construct( |
| sample, display_aperture_, video_blt_interfaces_); |
| output_queue_.push(frame_output); |
| } |
| |
| void OnNewOutputType(const ComPtr<IMFMediaType>& type) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| RECT rect = {}; |
| MFVideoArea aperture; |
| HRESULT hr = type->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, |
| reinterpret_cast<UINT8*>(&aperture), |
| sizeof(MFVideoArea), nullptr); |
| if (SUCCEEDED(hr)) { |
| display_aperture_.left = aperture.OffsetX.value; |
| display_aperture_.right = rect.left + aperture.Area.cx; |
| display_aperture_.top = aperture.OffsetY.value; |
| display_aperture_.bottom = rect.top + aperture.Area.cy; |
| return; |
| } |
| |
| uint32_t width; |
| uint32_t height; |
| hr = MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE, &width, &height); |
| if (SUCCEEDED(hr)) { |
| display_aperture_.left = 0; |
| display_aperture_.top = 0; |
| display_aperture_.right = rect.left + width; |
| display_aperture_.bottom = rect.top + height; |
| } |
| } |
| |
| void Configure(SbDrmSystem drm_system) { |
| scoped_ptr<MediaTransform> media_transform; |
| |
| // If this is updated then media_is_video_supported.cc also needs to be |
| // updated. |
| switch (codec_) { |
| case kSbMediaVideoCodecH264: { |
| media_transform = CreateH264Transform(kVideoFormat_YV12); |
| break; |
| } |
| case kSbMediaVideoCodecVp9: { |
| // VP9 decoder needs a default resolution. |
| media_transform = TryCreateVP9Transform(kVideoFormat_YV12, 1024, 768); |
| break; |
| } |
| default: { SB_NOTREACHED(); } |
| } |
| |
| impl_.reset( |
| new DecryptingDecoder("video", media_transform.Pass(), drm_system)); |
| MediaTransform* decoder = impl_->GetDecoder(); |
| |
| dx_decoder_ctx_ = GetDirectXForHardwareDecoding(); |
| video_blt_interfaces_.dx_device_ = dx_decoder_ctx_.dx_device_out; |
| |
| DWORD input_stream_count = 0; |
| DWORD output_stream_count = 0; |
| decoder->GetStreamCount(&input_stream_count, &output_stream_count); |
| SB_DCHECK(1 == input_stream_count); |
| SB_DCHECK(1 == output_stream_count); |
| |
| ComPtr<IMFAttributes> attributes = decoder->GetAttributes(); |
| |
| HRESULT hr = attributes->SetUINT32(MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT, |
| kMinimumOutputSampleCount); |
| CheckResult(hr); |
| |
| UINT32 value = 0; |
| hr = attributes->GetUINT32(MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE, &value); |
| SB_DCHECK(hr == S_OK || hr == MF_E_ATTRIBUTENOTFOUND); |
| |
| // TODO: handle the MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE correctly. |
| // SB_DCHECK(value == TRUE); |
| |
| // Enables DirectX video acceleration for video decoding. |
| decoder->SendMessage(MFT_MESSAGE_SET_D3D_MANAGER, |
| reinterpret_cast<ULONG_PTR>( |
| dx_decoder_ctx_.dxgi_device_manager_out.Get())); |
| |
| ComPtr<IMFMediaType> output_type = decoder->GetCurrentOutputType(); |
| SB_DCHECK(output_type); |
| |
| UINT32 width; |
| UINT32 height; |
| hr = MFGetAttributeSize(output_type.Get(), MF_MT_FRAME_SIZE, &width, |
| &height); |
| |
| display_aperture_.left = 0; |
| display_aperture_.top = 0; |
| display_aperture_.right = width; |
| display_aperture_.bottom = height; |
| |
| if (FAILED(hr)) { |
| SB_NOTREACHED() << "could not get width & height, hr = " << hr; |
| return; |
| } |
| |
| ComPtr<IMFAttributes> output_attributes = |
| decoder->GetOutputStreamAttributes(); |
| // The decoder must output textures that are bound to shader resources, |
| // or we can't draw them later via ANGLE. |
| hr = output_attributes->SetUINT32( |
| MF_SA_D3D11_BINDFLAGS, D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_DECODER); |
| SB_DCHECK(SUCCEEDED(hr)); |
| |
| dx_decoder_ctx_.dx_device_out.As(&video_blt_interfaces_.video_device_); |
| |
| D3D11_VIDEO_PROCESSOR_CONTENT_DESC video_processor_desc = {}; |
| video_processor_desc.InputFrameFormat = |
| D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE; |
| // Presumably changing the input/output frame rate would |
| // allow us to use the VideoProcessor to decide which frames to drop |
| // But we already have common code that does this, so presumably |
| // by setting the input and output frame rate the same, we instruct |
| // the VideoProcessor to give us one output for every input frame. |
| video_processor_desc.InputFrameRate.Numerator = 60; |
| video_processor_desc.InputFrameRate.Denominator = 1; |
| video_processor_desc.OutputFrameRate = video_processor_desc.InputFrameRate; |
| |
| SbWindowOptions window_options; |
| SbWindowSetDefaultOptions(&window_options); |
| video_processor_desc.OutputWidth = window_options.size.width; |
| video_processor_desc.OutputHeight = window_options.size.height; |
| |
| video_processor_desc.InputWidth = video_processor_desc.OutputWidth; |
| video_processor_desc.InputHeight = video_processor_desc.OutputHeight; |
| video_processor_desc.OutputFrameRate.Numerator = 60; |
| video_processor_desc.OutputFrameRate.Denominator = 1; |
| video_processor_desc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL; |
| |
| hr = video_blt_interfaces_.video_device_->CreateVideoProcessorEnumerator( |
| &video_processor_desc, &video_blt_interfaces_.video_processor_enum_); |
| CheckResult(hr); |
| |
| hr = video_blt_interfaces_.video_device_->CreateVideoProcessor( |
| video_blt_interfaces_.video_processor_enum_.Get(), 0, |
| &video_blt_interfaces_.video_processor_); |
| CheckResult(hr); |
| |
| ComPtr<ID3D11DeviceContext> device_context; |
| dx_decoder_ctx_.dx_device_out->GetImmediateContext(&device_context); |
| |
| device_context.As(&video_blt_interfaces_.video_context_); |
| video_blt_interfaces_.video_context_->VideoProcessorSetStreamFrameFormat( |
| video_blt_interfaces_.video_processor_.Get(), 0, |
| D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE); |
| |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/hh447754(v=vs.85).aspx |
| // "for example, if you provide your own pixel shader for the video |
| // processor, you might want to disable the driver's automatic |
| // processing." |
| // We do have our own pixel shader, so we do want to disable anything |
| // like this. |
| video_blt_interfaces_.video_context_ |
| ->VideoProcessorSetStreamAutoProcessingMode( |
| video_blt_interfaces_.video_processor_.Get(), 0, false); |
| } |
| |
| bool TryWrite(const scoped_refptr<InputBuffer>& buff) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| const bool write_ok = impl_->TryWriteInputBuffer(buff, 0); |
| return write_ok; |
| } |
| |
| void WriteEndOfStream() SB_OVERRIDE { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| impl_->Drain(); |
| |
| ComPtr<IMFSample> sample; |
| ComPtr<IMFMediaType> media_type; |
| while (VideoFrameFactory::frames_in_flight() < kSampleAllocatorFramesMax && |
| impl_->ProcessAndRead(&sample, &media_type)) { |
| if (media_type) { |
| OnNewOutputType(media_type); |
| } |
| if (sample) { |
| Consume(sample); |
| } |
| } |
| } |
| |
| VideoFramePtr ProcessAndRead(bool* too_many_outstanding_frames) SB_OVERRIDE { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(too_many_outstanding_frames); |
| |
| *too_many_outstanding_frames = |
| VideoFrameFactory::frames_in_flight() >= kSampleAllocatorFramesMax; |
| |
| if (!*too_many_outstanding_frames) { |
| ComPtr<IMFSample> sample; |
| ComPtr<IMFMediaType> media_type; |
| impl_->ProcessAndRead(&sample, &media_type); |
| if (media_type) { |
| OnNewOutputType(media_type); |
| } |
| if (sample) { |
| Consume(sample); |
| } |
| } |
| if (output_queue_.empty()) { |
| return NULL; |
| } |
| VideoFramePtr output = output_queue_.front(); |
| output_queue_.pop(); |
| return output; |
| } |
| |
| void Reset() SB_OVERRIDE { |
| impl_->Reset(); |
| std::queue<VideoFramePtr> empty; |
| output_queue_.swap(empty); |
| thread_checker_.Detach(); |
| } |
| |
| // The object is single-threaded and is driven by a dedicated thread. |
| // However the thread may gets destroyed and re-created over the life time of |
| // this object. We enforce that certain member functions can only called |
| // from one thread while still allows this object to be driven by different |
| // threads by: |
| // 1. The |thread_checker_| is initially created without attaching to any |
| // thread. |
| // 2. When a thread is destroyed, Reset() will be called which in turn calls |
| // Detach() on the |thread_checker_| to allow the object to attach to a |
| // new thread. |
| ::starboard::shared::starboard::ThreadChecker thread_checker_; |
| std::queue<VideoFramePtr> output_queue_; |
| const SbMediaVideoCodec codec_; |
| scoped_ptr<DecryptingDecoder> impl_; |
| |
| RECT display_aperture_; |
| HardwareDecoderContext dx_decoder_ctx_; |
| |
| VideoBltInterfaces video_blt_interfaces_; |
| }; |
| |
| } // anonymous namespace. |
| |
| scoped_ptr<AbstractWin32VideoDecoder> AbstractWin32VideoDecoder::Create( |
| SbMediaVideoCodec codec, |
| SbDrmSystem drm_system) { |
| return scoped_ptr<AbstractWin32VideoDecoder>( |
| new AbstractWin32VideoDecoderImpl(codec, drm_system)); |
| } |
| |
| } // namespace win32 |
| } // namespace shared |
| } // namespace starboard |