blob: 9f1d149340409cb4b2aed065bffac7a9ade63f73 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/windows/d3d11_video_decoder.h"
#include <d3d11_4.h>
#include <memory>
#include "base/bind.h"
#include "base/callback.h"
#include "base/feature_list.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/metrics/histogram_macros.h"
#include "gpu/config/gpu_finch_features.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/cdm_context.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/video_codecs.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/gpu/windows/d3d11_picture_buffer.h"
#include "media/gpu/windows/d3d11_video_context_wrapper.h"
#include "media/gpu/windows/d3d11_video_decoder_impl.h"
#include "ui/gl/gl_angle_util_win.h"
namespace media {
namespace {
#define INRANGE(_profile, codecname) \
(_profile >= codecname##PROFILE_MIN && _profile <= codecname##PROFILE_MAX)
bool IsVP9(const VideoDecoderConfig& config) {
return INRANGE(config.profile(), VP9);
}
bool IsH264(const VideoDecoderConfig& config) {
return INRANGE(config.profile(), H264);
}
bool IsUnsupportedVP9Profile(const VideoDecoderConfig& config) {
return config.profile() == VP9PROFILE_PROFILE1 ||
config.profile() == VP9PROFILE_PROFILE3;
}
#undef INRANGE
// Holder class, so that we don't keep creating CommandBufferHelpers every time
// somebody calls a callback. We can't actually create it until we're on the
// right thread.
struct CommandBufferHelperHolder
: base::RefCountedDeleteOnSequence<CommandBufferHelperHolder> {
CommandBufferHelperHolder(
scoped_refptr<base::SequencedTaskRunner> task_runner)
: base::RefCountedDeleteOnSequence<CommandBufferHelperHolder>(
std::move(task_runner)) {}
scoped_refptr<CommandBufferHelper> helper;
private:
~CommandBufferHelperHolder() = default;
friend class base::RefCountedDeleteOnSequence<CommandBufferHelperHolder>;
friend class base::DeleteHelper<CommandBufferHelperHolder>;
DISALLOW_COPY_AND_ASSIGN(CommandBufferHelperHolder);
};
scoped_refptr<CommandBufferHelper> CreateCommandBufferHelper(
base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb,
scoped_refptr<CommandBufferHelperHolder> holder) {
gpu::CommandBufferStub* stub = get_stub_cb.Run();
if (!stub)
return nullptr;
DCHECK(holder);
if (!holder->helper)
holder->helper = CommandBufferHelper::Create(stub);
return holder->helper;
}
} // namespace
std::unique_ptr<VideoDecoder> D3D11VideoDecoder::Create(
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
std::unique_ptr<MediaLog> media_log,
const gpu::GpuPreferences& gpu_preferences,
const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb) {
// We create |impl_| on the wrong thread, but we never use it here.
// Note that the output callback will hop to our thread, post the video
// frame, and along with a callback that will hop back to the impl thread
// when it's released.
// Note that we WrapUnique<VideoDecoder> rather than D3D11VideoDecoder to make
// this castable; the deleters have to match.
std::unique_ptr<MediaLog> cloned_media_log = media_log->Clone();
auto get_helper_cb =
base::BindRepeating(CreateCommandBufferHelper, std::move(get_stub_cb),
scoped_refptr<CommandBufferHelperHolder>(
new CommandBufferHelperHolder(gpu_task_runner)));
return base::WrapUnique<VideoDecoder>(
new D3D11VideoDecoder(std::move(gpu_task_runner), std::move(media_log),
gpu_preferences, gpu_workarounds,
std::make_unique<D3D11VideoDecoderImpl>(
std::move(cloned_media_log), get_helper_cb),
get_helper_cb));
}
D3D11VideoDecoder::D3D11VideoDecoder(
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
std::unique_ptr<MediaLog> media_log,
const gpu::GpuPreferences& gpu_preferences,
const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
std::unique_ptr<D3D11VideoDecoderImpl> impl,
base::RepeatingCallback<scoped_refptr<CommandBufferHelper>()> get_helper_cb)
: media_log_(std::move(media_log)),
impl_(std::move(impl)),
impl_task_runner_(std::move(gpu_task_runner)),
gpu_preferences_(gpu_preferences),
gpu_workarounds_(gpu_workarounds),
get_d3d11_device_cb_(base::BindRepeating(
[]() { return gl::QueryD3D11DeviceObjectFromANGLE(); })),
get_helper_cb_(std::move(get_helper_cb)),
weak_factory_(this) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(media_log_);
impl_weak_ = impl_->GetWeakPtr();
}
D3D11VideoDecoder::~D3D11VideoDecoder() {
// Post destruction to the main thread. When this executes, it will also
// cancel pending callbacks into |impl_| via |impl_weak_|. Callbacks out
// from |impl_| will be cancelled by |weak_factory_| when we return.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (impl_task_runner_->RunsTasksInCurrentSequence())
impl_.reset();
else
impl_task_runner_->DeleteSoon(FROM_HERE, std::move(impl_));
// Explicitly destroy the decoder, since it can reference picture buffers.
accelerated_video_decoder_.reset();
}
std::string D3D11VideoDecoder::GetDisplayName() const {
return "D3D11VideoDecoder";
}
HRESULT D3D11VideoDecoder::InitializeAcceleratedDecoder(
const VideoDecoderConfig& config,
CdmProxyContext* proxy_context,
Microsoft::WRL::ComPtr<ID3D11VideoDecoder> video_decoder) {
// If we got an 11.1 D3D11 Device, we can use a |ID3D11VideoContext1|,
// otherwise we have to make sure we only use a |ID3D11VideoContext|.
HRESULT hr;
// |device_context_| is the primary display context, but currently
// we share it for decoding purposes.
auto video_context = VideoContextWrapper::CreateWrapper(usable_feature_level_,
device_context_, &hr);
if (!SUCCEEDED(hr))
return hr;
if (IsVP9(config)) {
accelerated_video_decoder_ = std::make_unique<VP9Decoder>(
std::make_unique<D3D11VP9Accelerator>(
this, media_log_.get(), proxy_context, video_decoder, video_device_,
std::move(video_context)),
config.color_space_info());
return hr;
}
if (IsH264(config)) {
accelerated_video_decoder_ = std::make_unique<H264Decoder>(
std::make_unique<D3D11H264Accelerator>(
this, media_log_.get(), proxy_context, video_decoder, video_device_,
std::move(video_context)),
config.color_space_info());
return hr;
}
return E_FAIL;
}
bool D3D11VideoDecoder::DeviceHasDecoderID(GUID decoder_guid) {
UINT index = video_device_->GetVideoDecoderProfileCount();
while (index-- > 0) {
GUID profile = {};
if (SUCCEEDED(video_device_->GetVideoDecoderProfile(index, &profile))) {
if (profile == decoder_guid)
return true;
}
}
return false;
}
GUID D3D11VideoDecoder::GetD3D11DecoderGUID(const VideoDecoderConfig& config) {
if (IsVP9(config) && base::FeatureList::IsEnabled(kD3D11VP9Decoder))
// TODO(tmathmeyer) set up a finch experiment.
return D3D11_DECODER_PROFILE_VP9_VLD_PROFILE0;
if (IsH264(config))
return D3D11_DECODER_PROFILE_H264_VLD_NOFGT;
return {};
}
void D3D11VideoDecoder::Initialize(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* cdm_context,
const InitCB& init_cb,
const OutputCB& output_cb,
const WaitingCB& waiting_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(output_cb);
DCHECK(waiting_cb);
state_ = State::kInitializing;
if (!IsPotentiallySupported(config)) {
DVLOG(3) << "D3D11 video decoder not supported for the config.";
init_cb.Run(false);
return;
}
config_ = config;
init_cb_ = init_cb;
output_cb_ = output_cb;
waiting_cb_ = waiting_cb;
// Initialize the video decoder.
// Note that we assume that this is the ANGLE device, since we don't implement
// texture sharing properly. That also implies that this is the GPU main
// thread, since we use non-threadsafe properties of the device (e.g., we get
// the immediate context).
//
// Also note that we don't technically have a guarantee that the ANGLE device
// will use the most recent version of D3D11; it might be configured to use
// D3D9. In practice, though, it seems to use 11.1 if it's available, unless
// it's been specifically configured via switch to avoid d3d11.
//
// TODO(liberato): On re-init, we can probably re-use the device.
device_ = get_d3d11_device_cb_.Run();
if (!device_) {
// This happens if, for example, if chrome is configured to use
// D3D9 for ANGLE.
NotifyError("ANGLE did not provide D3D11 device");
return;
}
device_->GetImmediateContext(device_context_.ReleaseAndGetAddressOf());
HRESULT hr;
// TODO(liberato): Handle cleanup better. Also consider being less chatty in
// the logs, since this will fall back.
hr = device_.CopyTo(video_device_.ReleaseAndGetAddressOf());
if (!SUCCEEDED(hr)) {
NotifyError("Failed to get video device");
return;
}
GUID decoder_guid = GetD3D11DecoderGUID(config);
if (!DeviceHasDecoderID(decoder_guid)) {
NotifyError("Did not find a supported profile");
return;
}
// TODO(liberato): dxva does this. don't know if we need to.
Microsoft::WRL::ComPtr<ID3D11Multithread> multi_threaded;
hr = device_->QueryInterface(IID_PPV_ARGS(&multi_threaded));
if (!SUCCEEDED(hr)) {
NotifyError("Failed to query ID3D11Multithread");
return;
}
// TODO(liberato): This is a hack, since the unittest returns
// success without providing |multi_threaded|.
if (multi_threaded)
multi_threaded->SetMultithreadProtected(TRUE);
D3D11_VIDEO_DECODER_DESC desc = {};
desc.Guid = decoder_guid;
desc.SampleWidth = config.coded_size().width();
desc.SampleHeight = config.coded_size().height();
desc.OutputFormat = DXGI_FORMAT_NV12;
UINT config_count = 0;
hr = video_device_->GetVideoDecoderConfigCount(&desc, &config_count);
if (FAILED(hr) || config_count == 0) {
NotifyError("Failed to get video decoder config count");
return;
}
D3D11_VIDEO_DECODER_CONFIG dec_config = {};
bool found = false;
for (UINT i = 0; i < config_count; i++) {
hr = video_device_->GetVideoDecoderConfig(&desc, i, &dec_config);
if (FAILED(hr)) {
NotifyError("Failed to get decoder config");
return;
}
if (config.is_encrypted() && dec_config.guidConfigBitstreamEncryption !=
D3D11_DECODER_ENCRYPTION_HW_CENC) {
// For encrypted media, it has to use HW CENC decoder config.
continue;
}
if (IsVP9(config) && dec_config.ConfigBitstreamRaw == 1) {
// DXVA VP9 specification mentions ConfigBitstreamRaw "shall be 1".
found = true;
break;
}
if (IsH264(config) && dec_config.ConfigBitstreamRaw == 2) {
// ConfigBitstreamRaw == 2 means the decoder uses DXVA_Slice_H264_Short.
found = true;
break;
}
}
if (!found) {
NotifyError("Failed to find decoder config");
return;
}
memcpy(&decoder_guid_, &decoder_guid, sizeof decoder_guid_);
Microsoft::WRL::ComPtr<ID3D11VideoDecoder> video_decoder;
hr = video_device_->CreateVideoDecoder(
&desc, &dec_config, video_decoder.ReleaseAndGetAddressOf());
if (!video_decoder.Get()) {
NotifyError("Failed to create a video decoder");
return;
}
CdmProxyContext* proxy_context = nullptr;
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
if (cdm_context)
proxy_context = cdm_context->GetCdmProxyContext();
#endif
// Ensure that if we are encrypted, that we have a CDM.
if (config_.is_encrypted() && !proxy_context) {
NotifyError("Video stream is encrypted, but no cdm was found");
return;
}
hr = InitializeAcceleratedDecoder(config, proxy_context, video_decoder);
if (!SUCCEEDED(hr)) {
NotifyError("Failed to get device context");
return;
}
// |cdm_context| could be null for clear playback.
// TODO(liberato): On re-init, should this still happen?
if (cdm_context) {
new_key_callback_registration_ =
cdm_context->RegisterEventCB(base::BindRepeating(
&D3D11VideoDecoder::OnCdmContextEvent, weak_factory_.GetWeakPtr()));
}
auto impl_init_cb = base::BindOnce(&D3D11VideoDecoder::OnGpuInitComplete,
weak_factory_.GetWeakPtr());
auto get_picture_buffer_cb =
base::BindRepeating(&D3D11VideoDecoder::ReceivePictureBufferFromClient,
weak_factory_.GetWeakPtr());
// Initialize the gpu side. We wait until everything else is initialized,
// since we allow it to call us back re-entrantly to reduce latency. Note
// that if we're not on the same thread, then we should probably post the
// call earlier, since re-entrancy won't be an issue.
if (impl_task_runner_->RunsTasksInCurrentSequence()) {
impl_->Initialize(std::move(impl_init_cb),
std::move(get_picture_buffer_cb));
return;
}
// Bind our own init / output cb that hop to this thread, so we don't call
// the originals on some other thread.
// Important but subtle note: base::Bind will copy |config_| since it's a
// const ref.
impl_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&D3D11VideoDecoderImpl::Initialize, impl_weak_,
BindToCurrentLoop(std::move(impl_init_cb)),
BindToCurrentLoop(std::move(get_picture_buffer_cb))));
}
void D3D11VideoDecoder::ReceivePictureBufferFromClient(
scoped_refptr<D3D11PictureBuffer> buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We may decode into this buffer again.
// Note that |buffer| might no longer be in |picture_buffers_| if we've
// replaced them. That's okay.
buffer->set_in_client_use(false);
// Also re-start decoding in case it was waiting for more pictures.
DoDecode();
}
void D3D11VideoDecoder::OnGpuInitComplete(bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!init_cb_) {
// We already failed, so just do nothing.
DCHECK_EQ(state_, State::kError);
return;
}
DCHECK_EQ(state_, State::kInitializing);
if (!success) {
NotifyError("Gpu init failed");
return;
}
state_ = State::kRunning;
std::move(init_cb_).Run(true);
}
void D3D11VideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
const DecodeCB& decode_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ == State::kError) {
// TODO(liberato): consider posting, though it likely doesn't matter.
decode_cb.Run(DecodeStatus::DECODE_ERROR);
return;
}
input_buffer_queue_.push_back(std::make_pair(std::move(buffer), decode_cb));
// Post, since we're not supposed to call back before this returns. It
// probably doesn't matter since we're in the gpu process anyway.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&D3D11VideoDecoder::DoDecode, weak_factory_.GetWeakPtr()));
}
void D3D11VideoDecoder::DoDecode() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ != State::kRunning) {
DVLOG(2) << __func__ << ": Do nothing in " << static_cast<int>(state_)
<< " state.";
return;
}
if (!current_buffer_) {
if (input_buffer_queue_.empty()) {
return;
}
current_buffer_ = std::move(input_buffer_queue_.front().first);
current_decode_cb_ = input_buffer_queue_.front().second;
input_buffer_queue_.pop_front();
if (current_buffer_->end_of_stream()) {
// Flush, then signal the decode cb once all pictures have been output.
current_buffer_ = nullptr;
if (!accelerated_video_decoder_->Flush()) {
// This will also signal error |current_decode_cb_|.
NotifyError("Flush failed");
return;
}
// Pictures out output synchronously during Flush. Signal the decode
// cb now.
std::move(current_decode_cb_).Run(DecodeStatus::OK);
return;
}
// This must be after checking for EOS because there is no timestamp for an
// EOS buffer.
current_timestamp_ = current_buffer_->timestamp();
accelerated_video_decoder_->SetStream(-1, current_buffer_->data(),
current_buffer_->data_size(),
current_buffer_->decrypt_config());
}
while (true) {
// If we transition to the error state, then stop here.
if (state_ == State::kError)
return;
media::AcceleratedVideoDecoder::DecodeResult result =
accelerated_video_decoder_->Decode();
// TODO(liberato): switch + class enum.
if (result == media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
current_buffer_ = nullptr;
std::move(current_decode_cb_).Run(DecodeStatus::OK);
break;
} else if (result == media::AcceleratedVideoDecoder::kRanOutOfSurfaces) {
// At this point, we know the picture size.
// If we haven't allocated picture buffers yet, then allocate some now.
// Otherwise, stop here. We'll restart when a picture comes back.
if (picture_buffers_.size())
return;
CreatePictureBuffers();
} else if (result == media::AcceleratedVideoDecoder::kAllocateNewSurfaces) {
CreatePictureBuffers();
} else if (result == media::AcceleratedVideoDecoder::kTryAgain) {
state_ = State::kWaitingForNewKey;
waiting_cb_.Run(WaitingReason::kNoDecryptionKey);
// Another DoDecode() task would be posted in OnCdmContextEvent().
return;
} else {
LOG(ERROR) << "VDA Error " << result;
NotifyError("Accelerated decode failed");
return;
}
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&D3D11VideoDecoder::DoDecode, weak_factory_.GetWeakPtr()));
}
void D3D11VideoDecoder::Reset(const base::RepeatingClosure& closure) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_, State::kInitializing);
current_buffer_ = nullptr;
if (current_decode_cb_)
std::move(current_decode_cb_).Run(DecodeStatus::ABORTED);
for (auto& queue_pair : input_buffer_queue_)
queue_pair.second.Run(DecodeStatus::ABORTED);
input_buffer_queue_.clear();
// TODO(liberato): how do we signal an error?
accelerated_video_decoder_->Reset();
if (state_ == State::kWaitingForReset && config_.is_encrypted()) {
// On a hardware context loss event, a new swap chain has to be created (in
// the compositor). By clearing the picture buffers, next DoDecode() call
// will create a new texture. This makes the compositor to create a new swap
// chain.
// More detailed explanation at crbug.com/858286
picture_buffers_.clear();
}
// Transition out of kWaitingForNewKey since the new buffer could be clear or
// have a different key ID. Transition out of kWaitingForReset since reset
// just happened.
if (state_ == State::kWaitingForNewKey || state_ == State::kWaitingForReset)
state_ = State::kRunning;
closure.Run();
}
bool D3D11VideoDecoder::NeedsBitstreamConversion() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return true;
}
bool D3D11VideoDecoder::CanReadWithoutStalling() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return false;
}
int D3D11VideoDecoder::GetMaxDecodeRequests() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return 4;
}
void D3D11VideoDecoder::CreatePictureBuffers() {
// TODO(liberato): When we run off the gpu main thread, this call will need
// to signal success / failure asynchronously. We'll need to transition into
// a "waiting for pictures" state, since D3D11PictureBuffer will post the gpu
// thread work.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(liberato): what's the minimum that we need for the decoder?
// the VDA requests 20.
const int num_buffers = 20;
gfx::Size size = accelerated_video_decoder_->GetPicSize();
// Create an array of |num_buffers| elements to back the PictureBuffers.
D3D11_TEXTURE2D_DESC texture_desc = {};
texture_desc.Width = size.width();
texture_desc.Height = size.height();
texture_desc.MipLevels = 1;
texture_desc.ArraySize = num_buffers;
texture_desc.Format = DXGI_FORMAT_NV12;
texture_desc.SampleDesc.Count = 1;
texture_desc.Usage = D3D11_USAGE_DEFAULT;
texture_desc.BindFlags = D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE;
texture_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
if (base::FeatureList::IsEnabled(
features::kDirectCompositionUseNV12DecodeSwapChain)) {
// Decode swap chains do not support shared resources.
// TODO(sunnyps): Find a workaround for when the decoder moves to its own
// thread and D3D device. See https://crbug.com/911847
texture_desc.MiscFlags = 0;
}
if (config_.is_encrypted())
texture_desc.MiscFlags |= D3D11_RESOURCE_MISC_HW_PROTECTED;
Microsoft::WRL::ComPtr<ID3D11Texture2D> out_texture;
HRESULT hr = device_->CreateTexture2D(&texture_desc, nullptr,
out_texture.ReleaseAndGetAddressOf());
if (!SUCCEEDED(hr)) {
NotifyError("Failed to create a Texture2D for PictureBuffers");
return;
}
// Drop any old pictures.
for (auto& buffer : picture_buffers_)
DCHECK(!buffer->in_picture_use());
picture_buffers_.clear();
// Create each picture buffer.
const int textures_per_picture = 2; // From the VDA
for (size_t i = 0; i < num_buffers; i++) {
picture_buffers_.push_back(
new D3D11PictureBuffer(GL_TEXTURE_EXTERNAL_OES, size, i));
if (!picture_buffers_[i]->Init(get_helper_cb_, video_device_, out_texture,
decoder_guid_, textures_per_picture)) {
NotifyError("Unable to allocate PictureBuffer");
return;
}
}
}
D3D11PictureBuffer* D3D11VideoDecoder::GetPicture() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& buffer : picture_buffers_) {
if (!buffer->in_client_use() && !buffer->in_picture_use()) {
buffer->timestamp_ = current_timestamp_;
return buffer.get();
}
}
return nullptr;
}
void D3D11VideoDecoder::OutputResult(const CodecPicture* picture,
D3D11PictureBuffer* picture_buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
picture_buffer->set_in_client_use(true);
// Note: The pixel format doesn't matter.
gfx::Rect visible_rect = picture->visible_rect();
if (visible_rect.IsEmpty())
visible_rect = config_.visible_rect();
// TODO(https://crbug.com/843150): Use aspect ratio from decoder (SPS) if
// stream metadata doesn't overrride it.
double pixel_aspect_ratio = config_.GetPixelAspectRatio();
base::TimeDelta timestamp = picture_buffer->timestamp_;
scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
PIXEL_FORMAT_NV12, picture_buffer->mailbox_holders(),
VideoFrame::ReleaseMailboxCB(), picture_buffer->size(), visible_rect,
GetNaturalSize(visible_rect, pixel_aspect_ratio), timestamp);
// TODO(liberato): bind this to the gpu main thread.
frame->SetReleaseMailboxCB(media::BindToCurrentLoop(
base::BindOnce(&D3D11VideoDecoderImpl::OnMailboxReleased, impl_weak_,
scoped_refptr<D3D11PictureBuffer>(picture_buffer))));
frame->metadata()->SetBoolean(VideoFrameMetadata::POWER_EFFICIENT, true);
// For NV12, overlay is allowed by default. If the decoder is going to support
// non-NV12 textures, then this may have to be conditionally set. Also note
// that ALLOW_OVERLAY is required for encrypted video path.
frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY, true);
if (config_.is_encrypted()) {
frame->metadata()->SetBoolean(VideoFrameMetadata::PROTECTED_VIDEO, true);
frame->metadata()->SetBoolean(VideoFrameMetadata::HW_PROTECTED, true);
}
frame->set_color_space(picture->get_colorspace().ToGfxColorSpace());
output_cb_.Run(frame);
}
void D3D11VideoDecoder::OnCdmContextEvent(CdmContext::Event event) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << __func__ << ": event = " << static_cast<int>(event);
if (state_ == State::kInitializing || state_ == State::kError) {
DVLOG(1) << "Do nothing in " << static_cast<int>(state_) << " state.";
return;
}
switch (event) {
case CdmContext::Event::kHasAdditionalUsableKey:
// Note that this event may happen before DoDecode() because the key
// acquisition stack runs independently of the media decoding stack.
// So if this isn't in kWaitingForNewKey state no "resuming" is
// required therefore no special action taken here.
if (state_ != State::kWaitingForNewKey)
return;
state_ = State::kRunning;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&D3D11VideoDecoder::DoDecode,
weak_factory_.GetWeakPtr()));
return;
case CdmContext::Event::kHardwareContextLost:
state_ = State::kWaitingForReset;
waiting_cb_.Run(WaitingReason::kDecoderStateLost);
return;
}
}
void D3D11VideoDecoder::NotifyError(const char* reason) {
state_ = State::kError;
DLOG(ERROR) << reason;
media_log_->AddEvent(media_log_->CreateStringEvent(
MediaLogEvent::MEDIA_ERROR_LOG_ENTRY, "error", reason));
if (init_cb_)
std::move(init_cb_).Run(false);
if (current_decode_cb_)
std::move(current_decode_cb_).Run(DecodeStatus::DECODE_ERROR);
for (auto& queue_pair : input_buffer_queue_)
queue_pair.second.Run(DecodeStatus::DECODE_ERROR);
input_buffer_queue_.clear();
}
void D3D11VideoDecoder::SetCreateDeviceCallbackForTesting(
GetD3D11DeviceCB callback) {
get_d3d11_device_cb_ = std::move(callback);
}
void D3D11VideoDecoder::ReportNotSupportedReason(
NotSupportedReason enum_value) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UMA_HISTOGRAM_ENUMERATION("Media.D3D11.WasVideoSupported", enum_value);
const char* reason = nullptr;
switch (enum_value) {
case NotSupportedReason::kVideoIsSupported:
reason = "Playback is supported by D3D11VideoDecoder";
break;
case NotSupportedReason::kInsufficientD3D11FeatureLevel:
reason = "Insufficient D3D11 feature level";
break;
case NotSupportedReason::kProfileNotSupported:
reason = "Video profile is not supported by D3D11VideoDecoder";
break;
case NotSupportedReason::kCodecNotSupported:
reason = "H264 is required for D3D11VideoDecoder";
break;
case NotSupportedReason::kZeroCopyNv12Required:
reason = "Must allow zero-copy NV12 for D3D11VideoDecoder";
break;
case NotSupportedReason::kZeroCopyVideoRequired:
reason = "Must allow zero-copy video for D3D11VideoDecoder";
break;
case NotSupportedReason::kEncryptedMedia:
reason = "Encrypted media is not enabled for D3D11VideoDecoder";
break;
case NotSupportedReason::kCouldNotGetD3D11Device:
reason = "Could not get D3D11 device";
break;
}
DVLOG(2) << reason;
media_log_->AddEvent(media_log_->CreateStringEvent(
MediaLogEvent::MEDIA_INFO_LOG_ENTRY, "info", reason));
}
bool D3D11VideoDecoder::IsPotentiallySupported(
const VideoDecoderConfig& config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(liberato): All of this could be moved into MojoVideoDecoder, so that
// it could run on the client side and save the IPC hop.
// Must allow zero-copy of nv12 textures.
if (!gpu_preferences_.enable_zero_copy_dxgi_video) {
ReportNotSupportedReason(NotSupportedReason::kZeroCopyNv12Required);
return false;
}
if (gpu_workarounds_.disable_dxgi_zero_copy_video) {
ReportNotSupportedReason(NotSupportedReason::kZeroCopyVideoRequired);
return false;
}
if (config.profile() == H264PROFILE_HIGH10PROFILE) {
// H264 HIGH10 is never supported.
ReportNotSupportedReason(NotSupportedReason::kProfileNotSupported);
return false;
}
if (IsUnsupportedVP9Profile(config)) {
ReportNotSupportedReason(NotSupportedReason::kProfileNotSupported);
return false;
}
bool encrypted_stream = config.is_encrypted();
// Support of encrypted stream requires kHardwareSecureDecryption feature to
// be enabled.
if (encrypted_stream &&
!base::FeatureList::IsEnabled(kHardwareSecureDecryption)) {
ReportNotSupportedReason(NotSupportedReason::kEncryptedMedia);
return false;
}
// Converts one of chromium's VideoCodecProfile options to a dxguid value.
// If this GUID comes back empty then the profile is not supported.
GUID decoder_GUID = GetD3D11DecoderGUID(config);
// If we got the empty guid, fail.
GUID empty_guid = {};
if (decoder_GUID == empty_guid) {
ReportNotSupportedReason(NotSupportedReason::kCodecNotSupported);
return false;
}
// Remember that this might query the angle device, so this won't work if
// we're not on the GPU main thread. Also remember that devices are thread
// safe (contexts are not), so we could use the angle device from any thread
// as long as we're not calling into possible not-thread-safe things to get
// it. I.e., if this cached it, then it'd be fine.
//
// Note also that, currently, we are called from the GPU main thread only.
auto d3d11_device = get_d3d11_device_cb_.Run();
if (!d3d11_device) {
ReportNotSupportedReason(NotSupportedReason::kCouldNotGetD3D11Device);
return false;
}
usable_feature_level_ = d3d11_device->GetFeatureLevel();
if (encrypted_stream && usable_feature_level_ == D3D_FEATURE_LEVEL_11_0) {
ReportNotSupportedReason(
NotSupportedReason::kInsufficientD3D11FeatureLevel);
return false;
}
// TODO(liberato): dxva checks IsHDR() in the target colorspace, but we don't
// have the target colorspace. It's commented as being for vpx, though, so
// we skip it here for now.
ReportNotSupportedReason(NotSupportedReason::kVideoIsSupported);
return true;
}
} // namespace media