blob: 098b156530bfd097d4823c4e0cafc7fe57430bfd [file] [log] [blame]
// 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