| // 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_impl.h" |
| |
| #include <d3d11_4.h> |
| |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "gpu/command_buffer/service/mailbox_manager.h" |
| #include "gpu/command_buffer/service/scheduler.h" |
| #include "gpu/command_buffer/service/texture_manager.h" |
| #include "gpu/ipc/service/gpu_channel.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/cdm_context.h" |
| #include "media/base/decoder_buffer.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 "ui/gl/gl_angle_util_win.h" |
| #include "ui/gl/gl_bindings.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| static bool MakeContextCurrent(gpu::CommandBufferStub* stub) { |
| return stub && stub->decoder_context()->MakeCurrent(); |
| } |
| |
| } // namespace |
| |
| D3D11VideoDecoderImpl::D3D11VideoDecoderImpl( |
| base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb) |
| : get_stub_cb_(get_stub_cb), weak_factory_(this) {} |
| |
| D3D11VideoDecoderImpl::~D3D11VideoDecoderImpl() { |
| // TODO(liberato): be sure to clear |picture_buffers_| on the main thread. |
| // For now, we always run on the main thread anyway. |
| |
| if (stub_ && !wait_sequence_id_.is_null()) |
| stub_->channel()->scheduler()->DestroySequence(wait_sequence_id_); |
| } |
| |
| std::string D3D11VideoDecoderImpl::GetDisplayName() const { |
| NOTREACHED() << "Nobody should ask D3D11VideoDecoderImpl for its name"; |
| return "D3D11VideoDecoderImpl"; |
| } |
| |
| void D3D11VideoDecoderImpl::Initialize( |
| const VideoDecoderConfig& config, |
| bool low_delay, |
| CdmContext* cdm_context, |
| const InitCB& init_cb, |
| const OutputCB& output_cb, |
| const WaitingForDecryptionKeyCB& waiting_for_decryption_key_cb) { |
| init_cb_ = init_cb; |
| output_cb_ = output_cb; |
| is_encrypted_ = config.is_encrypted(); |
| |
| stub_ = get_stub_cb_.Run(); |
| if (!MakeContextCurrent(stub_)) { |
| NotifyError("Failed to get decoder stub"); |
| return; |
| } |
| // TODO(liberato): see GpuVideoFrameFactory. |
| // stub_->AddDestructionObserver(this); |
| wait_sequence_id_ = stub_->channel()->scheduler()->CreateSequence( |
| gpu::SchedulingPriority::kNormal); |
| |
| // Use the ANGLE device, rather than create our own. It would be nice if we |
| // could use our own device, and run on the mojo thread, but texture sharing |
| // seems to be difficult. |
| device_ = gl::QueryD3D11DeviceObjectFromANGLE(); |
| device_->GetImmediateContext(device_context_.GetAddressOf()); |
| |
| HRESULT hr; |
| |
| // TODO(liberato): Handle cleanup better. Also consider being less chatty in |
| // the logs, since this will fall back. |
| hr = device_context_.CopyTo(video_context_.GetAddressOf()); |
| if (!SUCCEEDED(hr)) { |
| NotifyError("Failed to get device context"); |
| return; |
| } |
| |
| hr = device_.CopyTo(video_device_.GetAddressOf()); |
| if (!SUCCEEDED(hr)) { |
| NotifyError("Failed to get video device"); |
| return; |
| } |
| |
| GUID needed_guid; |
| memcpy(&needed_guid, &D3D11_DECODER_PROFILE_H264_VLD_NOFGT, |
| sizeof(needed_guid)); |
| GUID decoder_guid = {}; |
| |
| { |
| // Enumerate supported video profiles and look for the H264 profile. |
| bool found = false; |
| UINT profile_count = video_device_->GetVideoDecoderProfileCount(); |
| for (UINT profile_idx = 0; profile_idx < profile_count; profile_idx++) { |
| GUID profile_id = {}; |
| hr = video_device_->GetVideoDecoderProfile(profile_idx, &profile_id); |
| if (SUCCEEDED(hr) && (profile_id == needed_guid)) { |
| decoder_guid = profile_id; |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| 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; |
| } |
| multi_threaded->SetMultithreadProtected(TRUE); |
| |
| D3D11_VIDEO_DECODER_DESC desc = {}; |
| desc.Guid = decoder_guid; |
| // TODO(liberato): where do these numbers come from? |
| desc.SampleWidth = 1920; |
| desc.SampleHeight = 1088; |
| 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 (dec_config.ConfigBitstreamRaw == 2) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| NotifyError("Failed to find decoder config"); |
| return; |
| } |
| |
| if (is_encrypted_) |
| dec_config.guidConfigBitstreamEncryption = D3D11_DECODER_ENCRYPTION_HW_CENC; |
| |
| memcpy(&decoder_guid_, &decoder_guid, sizeof decoder_guid_); |
| |
| Microsoft::WRL::ComPtr<ID3D11VideoDecoder> video_decoder; |
| hr = video_device_->CreateVideoDecoder(&desc, &dec_config, |
| video_decoder.GetAddressOf()); |
| if (!video_decoder.Get()) { |
| NotifyError("Failed to create a video decoder"); |
| return; |
| } |
| |
| accelerated_video_decoder_ = |
| std::make_unique<H264Decoder>(std::make_unique<D3D11H264Accelerator>( |
| this, video_decoder, video_device_, video_context_)); |
| |
| // |cdm_context| could be null for clear playback. |
| if (cdm_context) { |
| new_key_callback_registration_ = |
| cdm_context->RegisterNewKeyCB(base::BindRepeating( |
| &D3D11VideoDecoderImpl::NotifyNewKey, weak_factory_.GetWeakPtr())); |
| } |
| |
| state_ = State::kRunning; |
| std::move(init_cb_).Run(true); |
| } |
| |
| void D3D11VideoDecoderImpl::Decode(scoped_refptr<DecoderBuffer> buffer, |
| const DecodeCB& decode_cb) { |
| 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::Bind(&D3D11VideoDecoderImpl::DoDecode, weak_factory_.GetWeakPtr())); |
| } |
| |
| void D3D11VideoDecoderImpl::DoDecode() { |
| if (state_ != State::kRunning) |
| 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::kNoKey) { |
| state_ = State::kWaitingForNewKey; |
| // Note that another DoDecode() task would be posted in NotifyNewKey(). |
| return; |
| } else { |
| LOG(ERROR) << "VDA Error " << result; |
| NotifyError("Accelerated decode failed"); |
| return; |
| } |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&D3D11VideoDecoderImpl::DoDecode, weak_factory_.GetWeakPtr())); |
| } |
| |
| void D3D11VideoDecoderImpl::Reset(const base::Closure& closure) { |
| 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(); |
| closure.Run(); |
| } |
| |
| bool D3D11VideoDecoderImpl::NeedsBitstreamConversion() const { |
| // This is called from multiple threads. |
| return true; |
| } |
| |
| bool D3D11VideoDecoderImpl::CanReadWithoutStalling() const { |
| // This is called from multiple threads. |
| return false; |
| } |
| |
| int D3D11VideoDecoderImpl::GetMaxDecodeRequests() const { |
| // This is called from multiple threads. |
| return 4; |
| } |
| |
| void D3D11VideoDecoderImpl::CreatePictureBuffers() { |
| // 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 (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.GetAddressOf()); |
| 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_stub_cb_, video_device_, out_texture, |
| decoder_guid_, textures_per_picture)) { |
| NotifyError("Unable to allocate PictureBuffer"); |
| return; |
| } |
| } |
| } |
| |
| D3D11PictureBuffer* D3D11VideoDecoderImpl::GetPicture() { |
| for (auto& buffer : picture_buffers_) { |
| if (!buffer->in_client_use() && !buffer->in_picture_use()) { |
| buffer->timestamp_ = current_timestamp_; |
| return buffer.get(); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| void D3D11VideoDecoderImpl::OutputResult(D3D11PictureBuffer* buffer) { |
| buffer->set_in_client_use(true); |
| |
| // Note: The pixel format doesn't matter. |
| gfx::Rect visible_rect(buffer->size()); |
| // TODO(liberato): Pixel aspect ratio should come from the VideoDecoderConfig |
| // (except when it should come from the SPS). |
| // https://crbug.com/837337 |
| double pixel_aspect_ratio = 1.0; |
| base::TimeDelta timestamp = buffer->timestamp_; |
| auto frame = VideoFrame::WrapNativeTextures( |
| PIXEL_FORMAT_NV12, buffer->mailbox_holders(), |
| VideoFrame::ReleaseMailboxCB(), visible_rect.size(), visible_rect, |
| GetNaturalSize(visible_rect, pixel_aspect_ratio), timestamp); |
| |
| frame->SetReleaseMailboxCB(media::BindToCurrentLoop(base::BindOnce( |
| &D3D11VideoDecoderImpl::OnMailboxReleased, weak_factory_.GetWeakPtr(), |
| scoped_refptr<D3D11PictureBuffer>(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 (is_encrypted_) |
| frame->metadata()->SetBoolean(VideoFrameMetadata::PROTECTED_VIDEO, true); |
| output_cb_.Run(frame); |
| } |
| |
| void D3D11VideoDecoderImpl::OnMailboxReleased( |
| scoped_refptr<D3D11PictureBuffer> buffer, |
| const gpu::SyncToken& sync_token) { |
| // Note that |buffer| might no longer be in |picture_buffers_| if we've |
| // replaced them. That's okay. |
| |
| stub_->channel()->scheduler()->ScheduleTask(gpu::Scheduler::Task( |
| wait_sequence_id_, |
| base::BindOnce(&D3D11VideoDecoderImpl::OnSyncTokenReleased, GetWeakPtr(), |
| std::move(buffer)), |
| std::vector<gpu::SyncToken>({sync_token}))); |
| } |
| |
| void D3D11VideoDecoderImpl::OnSyncTokenReleased( |
| scoped_refptr<D3D11PictureBuffer> buffer) { |
| // Note that |buffer| might no longer be in |picture_buffers_|. |
| buffer->set_in_client_use(false); |
| |
| // Also re-start decoding in case it was waiting for more pictures. |
| // TODO(liberato): there might be something pending already. we should |
| // probably check. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&D3D11VideoDecoderImpl::DoDecode, GetWeakPtr())); |
| } |
| |
| base::WeakPtr<D3D11VideoDecoderImpl> D3D11VideoDecoderImpl::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void D3D11VideoDecoderImpl::NotifyNewKey() { |
| if (state_ != State::kWaitingForNewKey) { |
| // Note that this method may be called before DoDecode() because the key |
| // acquisition stack may be running independently of the media decoding |
| // stack. So if this isn't in kWaitingForNewKey state no "resuming" is |
| // required therefore no special action taken here. |
| return; |
| } |
| |
| state_ = State::kRunning; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&D3D11VideoDecoderImpl::DoDecode, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void D3D11VideoDecoderImpl::NotifyError(const char* reason) { |
| state_ = State::kError; |
| DLOG(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); |
| } |
| |
| } // namespace media |