blob: 3e10cdcf1d2adf9446a482b4b51ead3d99bf27be [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/android/video_frame_factory_impl.h"
#include <memory>
#include "base/android/android_image_reader_compat.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/task_runner_util.h"
#include "base/trace_event/trace_event.h"
#include "gpu/command_buffer/service/abstract_texture.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/texture_owner.h"
#include "gpu/config/gpu_finch_features.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/media_switches.h"
#include "media/base/video_frame.h"
#include "media/gpu/android/codec_image.h"
#include "media/gpu/android/codec_image_group.h"
#include "media/gpu/android/codec_wrapper.h"
#include "media/gpu/android/maybe_render_early_manager.h"
#include "media/gpu/command_buffer_helper.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "ui/gl/scoped_make_current.h"
namespace media {
namespace {
// The frames must be copied when threaded texture mailboxes are in use
// (http://crbug.com/582170). This texture copy can be avoided if
// AImageReader/AHardwareBuffer is supported and AImageReader
// max size is not limited to 1 (crbug.com/1091945).
absl::optional<VideoFrameMetadata::CopyMode> GetVideoFrameCopyMode(
bool enable_threaded_texture_mailboxes) {
if (!enable_threaded_texture_mailboxes)
return absl::nullopt;
// If we can run thread-safe, we don't need to copy.
if (features::NeedThreadSafeAndroidMedia())
return absl::nullopt;
return features::IsWebViewZeroCopyVideoEnabled()
? VideoFrameMetadata::CopyMode::kCopyMailboxesOnly
: VideoFrameMetadata::CopyMode::kCopyToNewTexture;
}
gpu::TextureOwner::Mode GetTextureOwnerMode(
VideoFrameFactory::OverlayMode overlay_mode,
const absl::optional<VideoFrameMetadata::CopyMode>& copy_mode) {
if (copy_mode == VideoFrameMetadata::kCopyMailboxesOnly) {
DCHECK(features::IsWebViewZeroCopyVideoEnabled());
return gpu::TextureOwner::Mode::kAImageReaderInsecureMultithreaded;
}
switch (overlay_mode) {
case VideoFrameFactory::OverlayMode::kDontRequestPromotionHints:
case VideoFrameFactory::OverlayMode::kRequestPromotionHints:
return features::IsAImageReaderEnabled()
? gpu::TextureOwner::Mode::kAImageReaderInsecure
: gpu::TextureOwner::Mode::kSurfaceTextureInsecure;
case VideoFrameFactory::OverlayMode::kSurfaceControlSecure:
DCHECK(features::IsAImageReaderEnabled());
return gpu::TextureOwner::Mode::kAImageReaderSecureSurfaceControl;
case VideoFrameFactory::OverlayMode::kSurfaceControlInsecure:
DCHECK(features::IsAImageReaderEnabled());
return gpu::TextureOwner::Mode::kAImageReaderInsecureSurfaceControl;
}
NOTREACHED();
return gpu::TextureOwner::Mode::kSurfaceTextureInsecure;
}
// Run on the GPU main thread to allocate the texture owner, and return it
// via |init_cb|.
static void AllocateTextureOwnerOnGpuThread(
VideoFrameFactory::InitCB init_cb,
VideoFrameFactory::OverlayMode overlay_mode,
const absl::optional<VideoFrameMetadata::CopyMode>& copy_mode,
scoped_refptr<gpu::SharedContextState> shared_context_state) {
if (!shared_context_state) {
std::move(init_cb).Run(nullptr);
return;
}
std::move(init_cb).Run(gpu::TextureOwner::Create(
gpu::TextureOwner::CreateTexture(shared_context_state),
GetTextureOwnerMode(overlay_mode, copy_mode), shared_context_state));
}
} // namespace
using gpu::gles2::AbstractTexture;
VideoFrameFactoryImpl::VideoFrameFactoryImpl(
scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
const gpu::GpuPreferences& gpu_preferences,
std::unique_ptr<SharedImageVideoProvider> image_provider,
std::unique_ptr<MaybeRenderEarlyManager> mre_manager,
std::unique_ptr<FrameInfoHelper> frame_info_helper,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(std::move(drdc_lock)),
image_provider_(std::move(image_provider)),
gpu_task_runner_(std::move(gpu_task_runner)),
copy_mode_(GetVideoFrameCopyMode(
gpu_preferences.enable_threaded_texture_mailboxes)),
mre_manager_(std::move(mre_manager)),
frame_info_helper_(std::move(frame_info_helper)) {}
VideoFrameFactoryImpl::~VideoFrameFactoryImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void VideoFrameFactoryImpl::Initialize(OverlayMode overlay_mode,
InitCB init_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
overlay_mode_ = overlay_mode;
// On init success, create the TextureOwner and hop it back to this thread to
// call |init_cb|.
auto gpu_init_cb = base::BindOnce(&AllocateTextureOwnerOnGpuThread,
BindToCurrentLoop(std::move(init_cb)),
overlay_mode, copy_mode_);
image_provider_->Initialize(std::move(gpu_init_cb));
}
void VideoFrameFactoryImpl::SetSurfaceBundle(
scoped_refptr<CodecSurfaceBundle> surface_bundle) {
scoped_refptr<CodecImageGroup> image_group;
// Increase the generation ID used by the shared image provider, since we're
// changing the TextureOwner. This is temporary. See ImageSpec.
image_spec_.generation_id++;
if (!surface_bundle) {
// Clear everything, just so we're not holding a reference.
codec_buffer_wait_coordinator_ = nullptr;
} else {
// If |surface_bundle| is using a CodecBufferWaitCoordinator, then get it.
// Note that the only reason we need this is for legacy mailbox support; we
// send it to the SharedImageVideoProvider so that (eventually) it can get
// the service id from the owner for the legacy mailbox texture. Otherwise,
// this would be a lot simpler.
codec_buffer_wait_coordinator_ =
surface_bundle->overlay()
? nullptr
: surface_bundle->codec_buffer_wait_coordinator();
// TODO(liberato): When we enable pooling, do we need to clear the pool
// here because the CodecImageGroup has changed? It's unclear, since the
// CodecImage shouldn't be in any group once we re-use it, so maybe it's
// fine to take no action.
mre_manager_->SetSurfaceBundle(std::move(surface_bundle));
}
}
void VideoFrameFactoryImpl::CreateVideoFrame(
std::unique_ptr<CodecOutputBuffer> output_buffer,
base::TimeDelta timestamp,
gfx::Size natural_size,
PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb,
OnceOutputCB output_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
gfx::Size coded_size = output_buffer->size();
gfx::Rect visible_rect(coded_size);
auto output_buffer_renderer = std::make_unique<CodecOutputBufferRenderer>(
std::move(output_buffer), codec_buffer_wait_coordinator_, GetDrDcLock());
// The pixel format doesn't matter here as long as it's valid for texture
// frames. But SkiaRenderer wants to ensure that the format of the resource
// used here which will eventually create a promise image must match the
// format of the resource(SharedImageVideo) used to create fulfill image.
// crbug.com/1028746. Since we create all the textures/abstract textures as
// well as shared images for video to be of format RGBA, we need to use the
// pixel format as ABGR here(which corresponds to 32bpp RGBA).
VideoPixelFormat pixel_format = PIXEL_FORMAT_ABGR;
// Check that we can create a VideoFrame for this config before trying to
// create the textures for it.
if (!VideoFrame::IsValidConfig(pixel_format, VideoFrame::STORAGE_OPAQUE,
coded_size, visible_rect, natural_size)) {
LOG(ERROR) << __func__ << " unsupported video frame format";
std::move(output_cb).Run(nullptr);
return;
}
auto image_ready_cb = base::BindOnce(
&VideoFrameFactoryImpl::CreateVideoFrame_OnImageReady,
weak_factory_.GetWeakPtr(), std::move(output_cb), timestamp, natural_size,
!!codec_buffer_wait_coordinator_, std::move(promotion_hint_cb),
pixel_format, overlay_mode_, copy_mode_, gpu_task_runner_);
RequestImage(std::move(output_buffer_renderer), std::move(image_ready_cb));
}
void VideoFrameFactoryImpl::RequestImage(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
ImageWithInfoReadyCB image_ready_cb) {
auto info_cb =
base::BindOnce(&VideoFrameFactoryImpl::CreateVideoFrame_OnFrameInfoReady,
weak_factory_.GetWeakPtr(), std::move(image_ready_cb));
frame_info_helper_->GetFrameInfo(std::move(buffer_renderer),
std::move(info_cb));
}
void VideoFrameFactoryImpl::CreateVideoFrame_OnFrameInfoReady(
ImageWithInfoReadyCB image_ready_cb,
std::unique_ptr<CodecOutputBufferRenderer> output_buffer_renderer,
FrameInfoHelper::FrameInfo frame_info) {
// If we don't have output buffer here we can't rely on reply from
// FrameInfoHelper as there might be not cached value and we can't render
// nothing. But in this case call comes from RunAfterPendingVideoFrames and we
// just want to ask for the same image spec as before to order callback after
// all RequestImage, so skip updating image_spec_ in this case.
if (output_buffer_renderer) {
image_spec_.coded_size = frame_info.coded_size;
} else {
// It is possible that we come here from RunAfterPendingVideoFrames before
// CreateVideoFrame was called. In this case we don't have coded_size, but
// it also means that there was no `image_provider_->RequestImage` calls so
// we can just run callback instantly.
if (image_spec_.coded_size.IsEmpty()) {
std::move(image_ready_cb)
.Run(nullptr, FrameInfoHelper::FrameInfo(),
SharedImageVideoProvider::ImageRecord());
return;
}
}
DCHECK(!image_spec_.coded_size.IsEmpty());
auto cb = base::BindOnce(std::move(image_ready_cb),
std::move(output_buffer_renderer), frame_info);
image_provider_->RequestImage(std::move(cb), image_spec_);
}
// static
void VideoFrameFactoryImpl::CreateVideoFrame_OnImageReady(
base::WeakPtr<VideoFrameFactoryImpl> thiz,
OnceOutputCB output_cb,
base::TimeDelta timestamp,
gfx::Size natural_size,
bool is_texture_owner_backed,
PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb,
VideoPixelFormat pixel_format,
OverlayMode overlay_mode,
const absl::optional<VideoFrameMetadata::CopyMode>& copy_mode,
scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
std::unique_ptr<CodecOutputBufferRenderer> output_buffer_renderer,
FrameInfoHelper::FrameInfo frame_info,
SharedImageVideoProvider::ImageRecord record) {
TRACE_EVENT0("media", "VideoVideoFrameFactoryImpl::OnVideoFrameImageReady");
if (!thiz)
return;
// Initialize the CodecImage to use this output buffer. Note that we're not
// on the gpu main thread here, but it's okay since CodecImage is not being
// used at this point. Alternatively, we could post it, or hand it off to the
// MaybeRenderEarlyManager to save a post.
//
// When we remove the output buffer management from CodecImage, then that's
// what we'd have a reference to here rather than CodecImage.
record.codec_image_holder->codec_image_raw()->Initialize(
std::move(output_buffer_renderer), is_texture_owner_backed,
std::move(promotion_hint_cb));
// Send the CodecImage (via holder, since we can't touch the refcount here) to
// the MaybeRenderEarlyManager.
thiz->mre_manager()->AddCodecImage(record.codec_image_holder);
// In case we need to get the YCbCr info, take the image holder out of the
// record before we move it into |completion_cb|.
auto codec_image_holder = std::move(record.codec_image_holder);
gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes];
mailbox_holders[0] = gpu::MailboxHolder(record.mailbox, gpu::SyncToken(),
GL_TEXTURE_EXTERNAL_OES);
auto frame = VideoFrame::WrapNativeTextures(
pixel_format, mailbox_holders, VideoFrame::ReleaseMailboxCB(),
frame_info.coded_size, frame_info.visible_rect, natural_size, timestamp);
// For Vulkan.
frame->set_ycbcr_info(frame_info.ycbcr_info);
// If, for some reason, we failed to create a frame, then fail. Note that we
// don't need to call |release_cb|; dropping it is okay since the api says so.
if (!frame) {
LOG(ERROR) << __func__ << " failed to create video frame";
std::move(output_cb).Run(nullptr);
return;
}
frame->metadata().copy_mode = copy_mode;
const bool is_surface_control =
overlay_mode == OverlayMode::kSurfaceControlSecure ||
overlay_mode == OverlayMode::kSurfaceControlInsecure;
const bool wants_promotion_hints =
overlay_mode == OverlayMode::kRequestPromotionHints;
bool allow_overlay = false;
if (is_surface_control) {
DCHECK(is_texture_owner_backed);
allow_overlay = true;
} else {
// We unconditionally mark the picture as overlayable, even if
// |!is_texture_owner_backed|, if we want to get hints. It's
// required, else we won't get hints.
allow_overlay = !is_texture_owner_backed || wants_promotion_hints;
}
frame->metadata().allow_overlay = allow_overlay;
frame->metadata().wants_promotion_hint = wants_promotion_hints;
frame->metadata().texture_owner = is_texture_owner_backed;
// TODO(liberato): if this is run via being dropped, then it would be nice
// to find that out rather than treating the image as unused. If the renderer
// is torn down, then this will be dropped rather than run. While |provider_|
// allows this, it doesn't have enough information to understand if the image
// is free or not. The problem only really affects the pool, since the
// direct provider destroys the SharedImage which works in either case. Any
// use of the image (e.g., if viz is still using it after the renderer has
// been torn down unexpectedly), will just not draw anything. That's fine.
//
// However, the pool will try to re-use the image, so the SharedImage remains
// valid. However, it's not a good idea to draw with it until the CodecImage
// is re-initialized with a new frame. If the renderer is torn down without
// getting returns from viz, then the pool does the wrong thing. However,
// the pool really doesn't know anything about VideoFrames, and dropping the
// callback does, in fact, signal that it's unused now (as described in the
// api). So, we probably should wrap the release cb in a default invoke, and
// if the default invoke happens, do something. Unclear what, though. Can't
// move it into the CodecImage (might hold a ref to the CodecImage in the cb),
// so it's unclear. As it is, CodecImage just handles the case where it's
// used after release.
frame->SetReleaseMailboxCB(std::move(record.release_cb));
// Note that we don't want to handle the CodecImageGroup here. It needs to be
// accessed on the gpu thread. Once we move to pooling, only the initial
// create / destroy operations will affect it anyway, so it might as well stay
// on the gpu thread.
std::move(output_cb).Run(std::move(frame));
}
void VideoFrameFactoryImpl::RunAfterPendingVideoFrames(
base::OnceClosure closure) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Run |closure| after we receive an image from |image_provider_|. We don't
// need the image, but it guarantees that it's ordered after all previous
// requests have been fulfilled.
auto image_ready_cb = base::BindOnce(
[](base::OnceClosure closure,
std::unique_ptr<CodecOutputBufferRenderer> output_buffer_renderer,
FrameInfoHelper::FrameInfo frame_info,
SharedImageVideoProvider::ImageRecord record) {
// Ignore |record| since we don't actually need an image.
std::move(closure).Run();
},
std::move(closure));
RequestImage(nullptr, std::move(image_ready_cb));
}
} // namespace media