#include "media/gpu/android/frame_info_helper.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "gpu/command_buffer/service/shared_image/android_video_image_backing.h"
#include "gpu/ipc/service/command_buffer_stub.h"
#include "gpu/ipc/service/gpu_channel.h"
#include "gpu/ipc/service/gpu_channel_manager.h"
#include "media/base/media_switches.h"
#include "media/gpu/android/codec_output_buffer_renderer.h"
namespace media {
FrameInfoHelper::FrameInfo::FrameInfo() = default;
FrameInfoHelper::FrameInfo::~FrameInfo() = default;
FrameInfoHelper::FrameInfo::FrameInfo(FrameInfo&&) = default;
FrameInfoHelper::FrameInfo::FrameInfo(const FrameInfoHelper::FrameInfo&) =
default;
FrameInfoHelper::FrameInfo& FrameInfoHelper::FrameInfo::operator=(
const FrameInfoHelper::FrameInfo&) = default;
class FrameInfoHelperImpl : public FrameInfoHelper,
public gpu::RefCountedLockHelperDrDc {
public:
FrameInfoHelperImpl(scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(drdc_lock) {
on_gpu_ = base::SequenceBound<OnGpu>(std::move(gpu_task_runner),
std::move(get_stub_cb),
std::move(drdc_lock));
}
~FrameInfoHelperImpl() override = default;
void GetFrameInfo(std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
FrameInfoReadyCB callback) override {
Request request = {.buffer_renderer = std::move(buffer_renderer),
.callback = std::move(callback)};
requests_.push(std::move(request));
if (requests_.size() == 1)
ProcessRequestsQueue();
}
bool IsStalled() const override { return waiting_for_real_frame_info_; }
private:
struct Request {
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer;
FrameInfoReadyCB callback;
};
class OnGpu : public gpu::RefCountedLockHelperDrDc {
public:
OnGpu(SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(std::move(drdc_lock)),
frame_info_helper_holder_(
base::MakeRefCounted<FrameInfoHelperHolder>(this)) {
auto* stub = get_stub_cb.Run();
if (stub) {
gpu::ContextResult result;
shared_context_ =
stub->channel()->gpu_channel_manager()->GetSharedContextState(
&result);
if (result == gpu::ContextResult::kSuccess) {
DCHECK(shared_context_);
if (shared_context_->GrContextIsVulkan()) {
vulkan_context_provider_ = shared_context_->vk_context_provider();
} else if (shared_context_->IsGraphiteDawnVulkan()) {
dawn_context_provider_ = shared_context_->dawn_context_provider();
}
}
}
}
~OnGpu() {
DCHECK(frame_info_helper_holder_);
frame_info_helper_holder_->SetFrameInfoHelperOnGpuToNull();
}
void GetFrameInfoImpl(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
AssertAcquiredDrDcLock();
DCHECK(buffer_renderer);
auto texture_owner = buffer_renderer->texture_owner();
DCHECK(texture_owner);
std::optional<FrameInfo> info;
if (buffer_renderer->RenderToTextureOwnerFrontBuffer()) {
gfx::Size coded_size;
gfx::Rect visible_rect;
if (texture_owner->GetCodedSizeAndVisibleRect(
buffer_renderer->size(), &coded_size, &visible_rect)) {
info.emplace();
info->coded_size = coded_size;
info->visible_rect = visible_rect;
info->ycbcr_info = gpu::AndroidVideoImageBacking::GetYcbcrInfo(
texture_owner.get(), vulkan_context_provider_,
dawn_context_provider_);
}
}
std::move(cb).Run(std::move(buffer_renderer), info);
}
void GetFrameInfo(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
base::AutoLockMaybe auto_lock(GetDrDcLockPtr());
DCHECK(buffer_renderer);
auto texture_owner = buffer_renderer->texture_owner();
DCHECK(texture_owner);
auto buffer_available_cb =
base::BindOnce(&FrameInfoHelperHolder::GetFrameInfoImpl,
base::RetainedRef(frame_info_helper_holder_),
std::move(buffer_renderer), std::move(cb));
texture_owner->RunWhenBufferIsAvailable(std::move(buffer_available_cb));
}
private:
class FrameInfoHelperHolder
: public base::RefCountedThreadSafe<FrameInfoHelperHolder> {
public:
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
explicit FrameInfoHelperHolder(raw_ptr<OnGpu> frame_info_helper_on_gpu)
: frame_info_helper_on_gpu_(frame_info_helper_on_gpu) {
DCHECK(frame_info_helper_on_gpu_);
}
void GetFrameInfoImpl(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
base::AutoLock l(lock_);
if (frame_info_helper_on_gpu_) {
frame_info_helper_on_gpu_->GetFrameInfoImpl(
std::move(buffer_renderer), std::move(cb));
}
}
void SetFrameInfoHelperOnGpuToNull() {
base::AutoLock l(lock_);
frame_info_helper_on_gpu_ = nullptr;
}
private:
friend class base::RefCountedThreadSafe<FrameInfoHelperHolder>;
~FrameInfoHelperHolder() = default;
base::Lock lock_;
raw_ptr<OnGpu> frame_info_helper_on_gpu_ GUARDED_BY(lock_) = nullptr;
};
scoped_refptr<gpu::SharedContextState> shared_context_;
raw_ptr<viz::VulkanContextProvider> vulkan_context_provider_ = nullptr;
raw_ptr<gpu::DawnContextProvider> dawn_context_provider_ = nullptr;
scoped_refptr<FrameInfoHelperHolder> frame_info_helper_holder_;
};
bool CanGuessCodedSize(
const CodecOutputBufferRenderer& buffer_renderer) const {
if (!frame_info_) {
return false;
}
if (disable_coded_size_guessing_) {
return false;
}
if (!frame_info_->visible_rect.origin().IsOrigin()) {
return false;
}
return buffer_renderer.CanGuessCodedSize();
}
FrameInfo GuessFrameInfo(
const CodecOutputBufferRenderer& buffer_renderer) const {
FrameInfo info;
info.coded_size = CanGuessCodedSize(buffer_renderer)
? buffer_renderer.GuessCodedSize()
: buffer_renderer.size();
info.visible_rect = gfx::Rect(buffer_renderer.size());
return info;
}
void DisableCodedSizeGuessing(const gfx::Size& guessed_coded_size,
const gfx::Size& actual_coded_size) {
disable_coded_size_guessing_ = true;
LOG(ERROR) << "Guessed coded size incorrectly. Expected "
<< guessed_coded_size.ToString() << ", got "
<< actual_coded_size.ToString();
}
void OnRealFrameInfoAvailable(gfx::Size visible_size,
gfx::Size guessed_coded_size,
std::optional<gfx::Size> coded_size,
std::optional<gfx::Rect> visible_rect) {
DVLOG(1) << __func__
<< ": coded_size=" << (coded_size ? coded_size->ToString() : "")
<< ", visible_rect="
<< (visible_rect ? visible_rect->ToString() : "");
DCHECK(waiting_for_real_frame_info_);
waiting_for_real_frame_info_ = false;
if (coded_size && visible_rect) {
if (guessed_coded_size != *coded_size) {
DisableCodedSizeGuessing(guessed_coded_size, frame_info_->coded_size);
}
frame_info_->coded_size = *coded_size;
frame_info_->visible_rect = *visible_rect;
visible_size_ = visible_size;
} else {
}
ProcessRequestsQueue();
}
void OnFrameInfoReady(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
std::optional<FrameInfo> frame_info) {
DCHECK(buffer_renderer);
DCHECK(!requests_.empty());
auto& request = requests_.front();
if (frame_info) {
const bool is_first_frame = !frame_info_;
visible_size_ = buffer_renderer->size();
frame_info_ = *frame_info;
if (is_first_frame && CanGuessCodedSize(*buffer_renderer)) {
const auto guessed_coded_size = buffer_renderer->GuessCodedSize();
const bool guessed_coded_size_correctly =
frame_info_->coded_size == guessed_coded_size;
base::UmaHistogramBoolean(
"Media.FrameInfo.GuessedInitialCodedSizeSuccess",
guessed_coded_size_correctly);
if (!guessed_coded_size_correctly) {
DisableCodedSizeGuessing(guessed_coded_size, frame_info_->coded_size);
}
}
std::move(request.callback).Run(std::move(buffer_renderer), *frame_info_);
} else {
auto info = GuessFrameInfo(*buffer_renderer);
std::move(request.callback)
.Run(std::move(buffer_renderer), std::move(info));
}
requests_.pop();
ProcessRequestsQueue();
}
void ProcessRequestsQueue() {
while (!requests_.empty()) {
auto& request = requests_.front();
if (!request.buffer_renderer) {
std::move(request.callback).Run(nullptr, FrameInfo());
} else if (!request.buffer_renderer->texture_owner()) {
auto info = GuessFrameInfo(*request.buffer_renderer);
std::move(request.callback)
.Run(std::move(request.buffer_renderer), std::move(info));
} else if (waiting_for_real_frame_info_) {
break;
} else if (visible_size_ == request.buffer_renderer->size()) {
std::move(request.callback)
.Run(std::move(request.buffer_renderer), *frame_info_);
} else if (CanGuessCodedSize(*request.buffer_renderer)) {
DCHECK(frame_info_);
auto info = GuessFrameInfo(*request.buffer_renderer);
info.ycbcr_info = frame_info_->ycbcr_info;
waiting_for_real_frame_info_ = true;
request.buffer_renderer->set_frame_info_callback(
base::BindPostTaskToCurrentDefault(base::BindOnce(
&FrameInfoHelperImpl::OnRealFrameInfoAvailable,
weak_factory_.GetWeakPtr(), request.buffer_renderer->size(),
info.coded_size)));
std::move(request.callback)
.Run(std::move(request.buffer_renderer), info);
} else {
auto cb = base::BindPostTaskToCurrentDefault(
base::BindOnce(&FrameInfoHelperImpl::OnFrameInfoReady,
weak_factory_.GetWeakPtr()));
on_gpu_.AsyncCall(&OnGpu::GetFrameInfo)
.WithArgs(std::move(request.buffer_renderer), std::move(cb));
break;
}
requests_.pop();
}
}
base::SequenceBound<OnGpu> on_gpu_;
base::queue<Request> requests_;
std::optional<FrameInfo> frame_info_;
gfx::Size visible_size_;
bool waiting_for_real_frame_info_ = false;
bool disable_coded_size_guessing_ = false;
base::WeakPtrFactory<FrameInfoHelperImpl> weak_factory_{this};
};
std::unique_ptr<FrameInfoHelper> FrameInfoHelper::Create(
scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock) {
return std::make_unique<FrameInfoHelperImpl>(
std::move(gpu_task_runner), std::move(get_stub_cb), std::move(drdc_lock));
}
}