blob: 93bc487f5f7155c82f86161cd508c7bd8901ed9c [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 <utility>
#include "starboard/shared/win32/atomic_queue.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/win32_decoder_impl.h"
namespace starboard {
namespace shared {
namespace win32 {
using Microsoft::WRL::ComPtr;
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;
// CLSID_CMSVideoDecoderMFT {62CE7E72-4C71-4D20-B15D-452831A87D9D}
const GUID CLSID_VideoDecoder = {0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45,
0x28, 0x31, 0xA8, 0x7D, 0x9D};
ComPtr<ID3D11Texture2D> GetTexture2D(ComPtr<IMFMediaBuffer> media_buffer) {
ComPtr<IMFDXGIBuffer> dxgi_buffer;
HRESULT hr = media_buffer.As(&dxgi_buffer);
CheckResult(hr);
SB_DCHECK(dxgi_buffer.Get());
ComPtr<ID3D11Texture2D> dx_texture;
hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&dx_texture));
CheckResult(hr);
return dx_texture;
}
class VideoFrameFactory {
public:
static VideoFramePtr Construct(SbMediaTime timestamp,
ComPtr<IMFMediaBuffer> media_buffer) {
ComPtr<ID3D11Texture2D> texture = GetTexture2D(media_buffer);
D3D11_TEXTURE2D_DESC tex_desc;
texture->GetDesc(&tex_desc);
VideoFramePtr out(new VideoFrame(tex_desc.Width, tex_desc.Height,
timestamp, media_buffer.Detach(),
nullptr, DeleteTextureFn));
frames_in_flight_.increment();
return out;
}
static int frames_in_flight() { return frames_in_flight_.load(); }
private:
static void DeleteTextureFn(void* context, void* texture) {
SB_UNREFERENCED_PARAMETER(context);
IMFMediaBuffer* data_ptr = static_cast<IMFMediaBuffer*>(texture);
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 MediaBufferConsumerInterface {
public:
explicit AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec)
: codec_(codec), surface_width_(0), surface_height_(0) {
MediaBufferConsumerInterface* media_cb = this;
impl_.reset(new DecoderImpl("video", media_cb));
EnsureVideoDecoderCreated();
}
virtual void Consume(ComPtr<IMFMediaBuffer> media_buffer,
int64_t win32_timestamp) {
const SbMediaTime media_timestamp = ConvertToMediaTime(win32_timestamp);
VideoFramePtr frame_output =
VideoFrameFactory::Construct(media_timestamp, media_buffer);
output_queue_.PushBack(frame_output);
}
ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
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));
hr = decoder->SetInputType(0, input_type.Get(), 0);
return input_type;
}
void EnsureVideoDecoderCreated() SB_OVERRIDE {
if (impl_->has_decoder()) {
return;
}
ComPtr<IMFTransform> decoder =
DecoderImpl::CreateDecoder(CLSID_VideoDecoder);
ComPtr<IMFMediaType> media_type = Configure(decoder.Get());
SB_DCHECK(decoder);
impl_->set_decoder(decoder);
dx_decoder_ctx_ = GetDirectXForHardwareDecoding();
SB_UNREFERENCED_PARAMETER(dx_decoder_ctx_);
HRESULT hr = S_OK;
DWORD input_stream_count = 0;
DWORD output_stream_count = 0;
hr = decoder->GetStreamCount(&input_stream_count, &output_stream_count);
CheckResult(hr);
SB_DCHECK(1 == input_stream_count);
SB_DCHECK(1 == output_stream_count);
impl_->ActivateDecryptor(media_type);
ComPtr<IMFMediaType> output_type =
FindMediaType(MFVideoFormat_YV12, decoder.Get());
SB_DCHECK(output_type);
hr = decoder->SetOutputType(0, output_type.Get(), 0);
CheckResult(hr);
ComPtr<IMFAttributes> attributes;
hr = decoder->GetAttributes(attributes.GetAddressOf());
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.
hr = decoder->ProcessMessage(
MFT_MESSAGE_SET_D3D_MANAGER,
ULONG_PTR(dx_decoder_ctx_.dxgi_device_manager_out.Get()));
CheckResult(hr);
// Only allowed to set once.
SB_DCHECK(0 == surface_width_);
SB_DCHECK(0 == surface_height_);
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;
hr = decoder->GetOutputStreamAttributes(0, &output_attributes);
SB_DCHECK(SUCCEEDED(hr));
// 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));
decoder->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
decoder->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
}
bool TryWrite(const scoped_refptr<InputBuffer>& buff) {
std::pair<int, int> width_height(buff->video_sample_info()->frame_width,
buff->video_sample_info()->frame_height);
EnsureVideoDecoderCreated();
if (!impl_->has_decoder()) {
SB_NOTREACHED();
return false; // TODO: Signal an error.
}
if (!output_queue_.IsEmpty()) {
return false; // Wait for more data.
}
// This would be used for decrypting content.
// For now this is empty.
std::vector<uint8_t> key_id;
std::vector<uint8_t> iv;
std::vector<Subsample> empty_subsample;
const SbMediaTime media_timestamp = buff->pts();
const int64_t win32_timestamp = ConvertToWin32Time(media_timestamp);
const bool write_ok = impl_->TryWriteInputBuffer(
buff->data(), buff->size(), win32_timestamp,
key_id, iv, empty_subsample);
return write_ok;
}
void WriteEndOfStream() SB_OVERRIDE {
if (impl_->has_decoder()) {
impl_->DrainDecoder();
while (VideoFrameFactory::frames_in_flight() <
kSampleAllocatorFramesMax &&
impl_->DeliverOneOutputOnAllTransforms()) {
}
} else {
// Don't call DrainDecoder() if input data is never queued.
// TODO: Send EOS.
}
}
VideoFramePtr ProcessAndRead() SB_OVERRIDE {
if (VideoFrameFactory::frames_in_flight() < kSampleAllocatorFramesMax) {
impl_->DeliverOneOutputOnAllTransforms();
}
VideoFramePtr output = output_queue_.PopFront();
return output;
}
AtomicQueue<VideoFramePtr> output_queue_;
SbMediaVideoCodec codec_;
scoped_ptr<DecoderImpl> impl_;
uint32_t surface_width_;
uint32_t surface_height_;
HardwareDecoderContext dx_decoder_ctx_;
};
} // anonymous namespace.
scoped_ptr<AbstractWin32VideoDecoder> AbstractWin32VideoDecoder::Create(
SbMediaVideoCodec codec) {
return scoped_ptr<AbstractWin32VideoDecoder>(
new AbstractWin32VideoDecoderImpl(codec));
}
} // namespace win32
} // namespace shared
} // namespace starboard