blob: 921be3e4baf3635b2764f7f9dd535e7d25496585 [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 <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"
namespace starboard {
namespace shared {
namespace win32 {
using Microsoft::WRL::ComPtr;
using ::starboard::shared::starboard::ThreadChecker;
using ::starboard::shared::win32::CheckResult;
namespace {
// This magic number is taken directly from sample from MS. Can be further
// tuned in case if there is playback issues.
const int kSampleAllocatorFramesMax = 5;
// 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 = 10;
// CLSID_CMSVideoDecoderMFT {62CE7E72-4C71-4D20-B15D-452831A87D9D}
const GUID CLSID_VideoDecoder = {0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45,
0x28, 0x31, 0xA8, 0x7D, 0x9D};
class VideoFrameFactory {
public:
static VideoFramePtr Construct(ComPtr<IMFSample> sample,
uint32_t width,
uint32_t height) {
ComPtr<IMFMediaBuffer> media_buffer;
HRESULT hr = sample->GetBufferByIndex(0, &media_buffer);
CheckResult(hr);
LONGLONG win32_timestamp = 0;
hr = sample->GetSampleTime(&win32_timestamp);
CheckResult(hr);
VideoFramePtr out(new VideoFrame(width, height,
ConvertToMediaTime(win32_timestamp),
sample.Detach(),
nullptr, DeleteSample));
frames_in_flight_.increment();
return out;
}
static int frames_in_flight() { return frames_in_flight_.load(); }
private:
static void DeleteSample(void* context, void* sample) {
SB_UNREFERENCED_PARAMETER(context);
IMFSample* data_ptr = static_cast<IMFSample*>(sample);
data_ptr->Release();
frames_in_flight_.decrement();
}
static atomic_int32_t frames_in_flight_;
};
atomic_int32_t VideoFrameFactory::frames_in_flight_;
class AbstractWin32VideoDecoderImpl : public AbstractWin32VideoDecoder {
public:
AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec, SbDrmSystem drm_system)
: thread_checker_(ThreadChecker::kSetThreadIdOnFirstCheck),
codec_(codec),
impl_("video", CLSID_VideoDecoder, drm_system),
surface_width_(0),
surface_height_(0) {
Configure();
}
void Consume(ComPtr<IMFSample> sample) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
VideoFramePtr frame_output =
VideoFrameFactory::Construct(sample, surface_width_, surface_height_);
output_queue_.push(frame_output);
}
void OnNewOutputType(const ComPtr<IMFMediaType>& type) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
MFVideoArea aperture;
HRESULT hr = type->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
reinterpret_cast<UINT8*>(&aperture), sizeof(MFVideoArea), nullptr);
if (SUCCEEDED(hr)) {
// TODO: consider offset as well
surface_width_ = aperture.Area.cx;
surface_height_ = aperture.Area.cy;
return;
}
uint32_t width;
uint32_t height;
hr = MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE,
&width, &height);
if (SUCCEEDED(hr)) {
surface_width_ = width;
surface_height_ = height;
}
}
void Configure() {
ComPtr<IMFMediaType> input_type;
HRESULT hr = MFCreateMediaType(&input_type);
SB_DCHECK(SUCCEEDED(hr));
hr = input_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
SB_DCHECK(SUCCEEDED(hr));
GUID selected_guid = {};
// If this is updated then media_is_video_supported.cc also needs to be
// updated.
switch (codec_) {
case kSbMediaVideoCodecH264: {
selected_guid = MFVideoFormat_H264;
break;
}
case kSbMediaVideoCodecH265: {
selected_guid = MFVideoFormat_H265;
break;
}
case kSbMediaVideoCodecVp9: {
selected_guid = MFVideoFormat_VP90;
break;
}
default: { SB_NOTREACHED(); }
}
hr = input_type->SetGUID(MF_MT_SUBTYPE, selected_guid);
SB_DCHECK(SUCCEEDED(hr));
MediaTransform& decoder = impl_.GetDecoder();
decoder.SetInputType(input_type);
dx_decoder_ctx_ = GetDirectXForHardwareDecoding();
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);
decoder.SetOutputTypeBySubType(MFVideoFormat_YV12);
ComPtr<IMFAttributes> attributes = decoder.GetAttributes();
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()));
// Only allowed to set once.
SB_DCHECK(0 == surface_width_);
SB_DCHECK(0 == surface_height_);
ComPtr<IMFMediaType> output_type = decoder.GetCurrentOutputType();
SB_DCHECK(output_type);
hr = MFGetAttributeSize(output_type.Get(), MF_MT_FRAME_SIZE,
&surface_width_, &surface_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));
}
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_;
DecryptingDecoder impl_;
uint32_t surface_width_;
uint32_t surface_height_;
HardwareDecoderContext dx_decoder_ctx_;
};
} // 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