blob: f0fb2701c7d2281fa26a217b627b9aa9f6d6ccb2 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/android/codec_allocator.h"
#include <stddef.h>
#include <memory>
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "media/base/android/media_codec_bridge_impl.h"
#include "media/base/limits.h"
#include "media/base/timestamp_constants.h"
namespace media {
namespace {
// This must be safe to call on any thread. Returns nullptr on failure.
std::unique_ptr<MediaCodecBridge> CreateMediaCodecInternal(
CodecAllocator::CodecFactoryCB factory_cb,
std::unique_ptr<VideoCodecConfig> codec_config) {
TRACE_EVENT0("media", "CodecAllocator::CreateMediaCodec");
base::ScopedBlockingCall scoped_block(FROM_HERE,
base::BlockingType::MAY_BLOCK);
return factory_cb.Run(*codec_config);
}
// Delete |codec| and signal |done_event| if it's not null.
void ReleaseMediaCodecInternal(std::unique_ptr<MediaCodecBridge> codec) {
TRACE_EVENT0("media", "CodecAllocator::ReleaseMediaCodec");
base::ScopedBlockingCall scoped_block(FROM_HERE,
base::BlockingType::MAY_BLOCK);
codec.reset();
}
scoped_refptr<base::SequencedTaskRunner> CreateCodecTaskRunner() {
return base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
}
} // namespace
// static
constexpr gfx::Size CodecAllocator::kMinHardwareResolution;
// static
CodecAllocator* CodecAllocator::GetInstance(
scoped_refptr<base::SequencedTaskRunner> task_runner) {
static base::NoDestructor<CodecAllocator> allocator(
base::BindRepeating(&MediaCodecBridgeImpl::CreateVideoDecoder),
task_runner);
// Verify that this caller agrees on the task runner, if one was specified.
DCHECK(!task_runner || allocator->task_runner_ == task_runner);
return allocator.get();
}
void CodecAllocator::CreateMediaCodecAsync(
CodecCreatedCB codec_created_cb,
std::unique_ptr<VideoCodecConfig> codec_config) {
DCHECK(codec_created_cb);
DCHECK(codec_config);
if (!task_runner_->RunsTasksInCurrentSequence()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&CodecAllocator::CreateMediaCodecAsync, base::Unretained(this),
base::BindPostTaskToCurrentDefault(std::move(codec_created_cb)),
std::move(codec_config)));
return;
}
// Select the task runner before adding the PendingOperation and before
// querying the |force_sw_codecs_| state.
auto* task_runner = SelectCodecTaskRunner();
// If we can't satisfy the request, fail the creation.
if (codec_config->codec_type == CodecType::kSecure && force_sw_codecs_) {
DLOG(ERROR) << "Secure software codec doesn't exist.";
std::move(codec_created_cb).Run(nullptr);
return;
}
if (force_sw_codecs_)
codec_config->codec_type = CodecType::kSoftware;
// If we're still allowed to pick any type we want, then limit to software for
// low resolution. https://crbug.com/1166833
if (codec_config->codec_type == CodecType::kAny &&
(codec_config->initial_expected_coded_size.width() <
kMinHardwareResolution.width() ||
codec_config->initial_expected_coded_size.height() <
kMinHardwareResolution.height())) {
codec_config->codec_type = CodecType::kSoftware;
}
const auto start_time = tick_clock_->NowTicks();
pending_operations_.push_back(start_time);
// Post creation to the task runner. This may hang on broken platforms; if it
// hangs, we will detect it on the next creation request, and future creations
// will fallback to software.
std::move(task_runner)
->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&CreateMediaCodecInternal, factory_cb_,
std::move(codec_config)),
base::BindOnce(&CodecAllocator::OnCodecCreated,
base::Unretained(this), start_time,
std::move(codec_created_cb)));
}
void CodecAllocator::ReleaseMediaCodec(std::unique_ptr<MediaCodecBridge> codec,
base::OnceClosure codec_released_cb) {
DCHECK(codec);
DCHECK(codec_released_cb);
if (!task_runner_->RunsTasksInCurrentSequence()) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CodecAllocator::ReleaseMediaCodec,
base::Unretained(this), std::move(codec),
base::BindPostTaskToCurrentDefault(
std::move(codec_released_cb))));
return;
}
// Update |force_sw_codecs_| status.
auto* task_runner = SelectCodecTaskRunner();
// We always return non-software codecs to the primary task runner regardless
// of whether it's hung or not. We don't want any non-sw codecs to hang the
// the secondary task runner upon release.
//
// It's okay to release a software codec back to the primary task runner, we
// just don't want to release non-sw codecs to the secondary task runner.
if (codec->GetCodecType() != CodecType::kSoftware)
task_runner = primary_task_runner_.get();
const auto start_time = tick_clock_->NowTicks();
pending_operations_.push_back(start_time);
task_runner->PostTaskAndReply(
FROM_HERE, base::BindOnce(&ReleaseMediaCodecInternal, std::move(codec)),
base::BindOnce(&CodecAllocator::OnCodecReleased, base::Unretained(this),
start_time, std::move(codec_released_cb)));
}
CodecAllocator::CodecAllocator(
CodecFactoryCB factory_cb,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: task_runner_(std::move(task_runner)),
factory_cb_(std::move(factory_cb)),
tick_clock_(base::DefaultTickClock::GetInstance()) {}
CodecAllocator::~CodecAllocator() = default;
void CodecAllocator::OnCodecCreated(base::TimeTicks start_time,
CodecCreatedCB codec_created_cb,
std::unique_ptr<MediaCodecBridge> codec) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
CompletePendingOperation(start_time);
std::move(codec_created_cb).Run(std::move(codec));
}
void CodecAllocator::OnCodecReleased(base::TimeTicks start_time,
base::OnceClosure codec_released_cb) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
CompletePendingOperation(start_time);
std::move(codec_released_cb).Run();
}
bool CodecAllocator::IsPrimaryTaskRunnerLikelyHung() const {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
// Give tasks 800ms before considering them hung. MediaCodec.configure() calls
// typically take 100-200ms on a N5, so 800ms is expected to very rarely
// result in false positives. Also, false positives have low impact because we
// resume using the thread when the task completes.
constexpr base::TimeDelta kHungTaskDetectionTimeout = base::Milliseconds(800);
return !pending_operations_.empty() &&
tick_clock_->NowTicks() - *pending_operations_.begin() >
kHungTaskDetectionTimeout;
}
base::SequencedTaskRunner* CodecAllocator::SelectCodecTaskRunner() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (IsPrimaryTaskRunnerLikelyHung()) {
force_sw_codecs_ = true;
if (!secondary_task_runner_)
secondary_task_runner_ = CreateCodecTaskRunner();
return secondary_task_runner_.get();
}
if (!primary_task_runner_)
primary_task_runner_ = CreateCodecTaskRunner();
force_sw_codecs_ = false;
return primary_task_runner_.get();
}
void CodecAllocator::CompletePendingOperation(base::TimeTicks start_time) {
// Note: This intentionally only erases the first instance, since there may be
// multiple instances of the same value.
pending_operations_.erase(
base::ranges::find(pending_operations_, start_time));
}
} // namespace media