blob: 4210ad5e6c7fd98a22f6eaf8e9f4c2b1de8986df [file] [log] [blame]
// Copyright 2016 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/avda_codec_allocator.h"
#include <stddef.h>
#include <memory>
#include "base/logging.h"
#include "base/sys_info.h"
#include "base/task_runner_util.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_task_runner_handle.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/media.h"
#include "media/base/timestamp_constants.h"
#include "media/gpu/android_video_decode_accelerator.h"
namespace media {
namespace {
// 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::TimeDelta::FromMilliseconds(800);
// Delete |codec| and signal |done_event| if it's not null.
void DeleteMediaCodecAndSignal(std::unique_ptr<MediaCodecBridge> codec,
base::WaitableEvent* done_event) {
codec.reset();
if (done_event)
done_event->Signal();
}
void DropReferenceToSurfaceBundle(
scoped_refptr<AVDASurfaceBundle> surface_bundle) {
// Do nothing. Let |surface_bundle| go out of scope.
}
} // namespace
CodecConfig::CodecConfig() {}
CodecConfig::~CodecConfig() {}
AVDACodecAllocator::HangDetector::HangDetector(base::TickClock* tick_clock)
: tick_clock_(tick_clock) {}
void AVDACodecAllocator::HangDetector::WillProcessTask(
const base::PendingTask& pending_task) {
base::AutoLock l(lock_);
task_start_time_ = tick_clock_->NowTicks();
}
void AVDACodecAllocator::HangDetector::DidProcessTask(
const base::PendingTask& pending_task) {
base::AutoLock l(lock_);
task_start_time_ = base::TimeTicks();
}
bool AVDACodecAllocator::HangDetector::IsThreadLikelyHung() {
base::AutoLock l(lock_);
if (task_start_time_.is_null())
return false;
return (tick_clock_->NowTicks() - task_start_time_) >
kHungTaskDetectionTimeout;
}
// static
AVDACodecAllocator* AVDACodecAllocator::Instance() {
static AVDACodecAllocator* allocator = new AVDACodecAllocator();
return allocator;
}
bool AVDACodecAllocator::StartThread(AVDACodecAllocatorClient* client) {
DCHECK(thread_checker_.CalledOnValidThread());
// Cancel any pending StopThreadTask()s because we need the threads now.
weak_this_factory_.InvalidateWeakPtrs();
// Try to start the threads if they haven't been started.
for (auto* thread : threads_) {
if (thread->thread.IsRunning())
continue;
if (!thread->thread.Start())
return false;
// Register the hang detector to observe the thread's MessageLoop.
thread->thread.task_runner()->PostTask(
FROM_HERE, base::Bind(&base::MessageLoop::AddTaskObserver,
base::Unretained(thread->thread.message_loop()),
&thread->hang_detector));
}
clients_.insert(client);
return true;
}
void AVDACodecAllocator::StopThread(AVDACodecAllocatorClient* client) {
DCHECK(thread_checker_.CalledOnValidThread());
clients_.erase(client);
if (!clients_.empty()) {
// If we aren't stopping, then signal immediately.
if (stop_event_for_testing_)
stop_event_for_testing_->Signal();
return;
}
// Post a task to stop each thread through its task runner and back to this
// thread. This ensures that all pending tasks are run first. If a new AVDA
// calls StartThread() before StopThreadTask() runs, it's canceled by
// invalidating its weak pointer. As a result we're guaranteed to only call
// Thread::Stop() while there are no tasks on its queue. We don't try to stop
// hung threads. But if it recovers it will be stopped the next time a client
// calls this.
for (size_t i = 0; i < threads_.size(); i++) {
if (threads_[i]->thread.IsRunning() &&
!threads_[i]->hang_detector.IsThreadLikelyHung()) {
threads_[i]->thread.task_runner()->PostTaskAndReply(
FROM_HERE, base::Bind(&base::DoNothing),
base::Bind(&AVDACodecAllocator::StopThreadTask,
weak_this_factory_.GetWeakPtr(), i));
}
}
}
// Return the task runner for tasks of type |type|.
scoped_refptr<base::SingleThreadTaskRunner> AVDACodecAllocator::TaskRunnerFor(
TaskType task_type) {
DCHECK(thread_checker_.CalledOnValidThread());
return threads_[task_type]->thread.task_runner();
}
bool AVDACodecAllocator::AllocateSurface(AVDASurfaceAllocatorClient* client,
int surface_id) {
DVLOG(1) << __func__ << ": " << surface_id;
DCHECK(thread_checker_.CalledOnValidThread());
if (surface_id == SurfaceManager::kNoSurfaceID)
return true;
// If it's not owned or being released, |client| now owns it.
if (!surface_owners_.count(surface_id) &&
!pending_codec_releases_.count(surface_id)) {
surface_owners_[surface_id].owner = client;
return true;
}
// Otherwise |client| replaces the previous waiter (if any).
OwnerRecord& record = surface_owners_[surface_id];
if (record.waiter)
record.waiter->OnSurfaceAvailable(false);
record.waiter = client;
return false;
}
void AVDACodecAllocator::DeallocateSurface(AVDASurfaceAllocatorClient* client,
int surface_id) {
DCHECK(thread_checker_.CalledOnValidThread());
if (surface_id == SurfaceManager::kNoSurfaceID ||
!surface_owners_.count(surface_id)) {
return;
}
OwnerRecord& record = surface_owners_[surface_id];
if (record.owner == client)
record.owner = nullptr;
else if (record.waiter == client)
record.waiter = nullptr;
// Promote the waiter if possible.
if (record.waiter && !record.owner &&
!pending_codec_releases_.count(surface_id)) {
record.owner = record.waiter;
record.waiter = nullptr;
record.owner->OnSurfaceAvailable(true);
return;
}
// Remove the record if it's now unused.
if (!record.owner && !record.waiter)
surface_owners_.erase(surface_id);
}
// During surface teardown we have to handle the following cases.
// 1) No AVDA has acquired the surface, or the surface has already been
// completely released.
// 2) A MediaCodec is currently being configured with the surface on another
// thread. Whether an AVDA owns the surface or has already deallocated it,
// the MediaCodec should be dropped when configuration completes.
// 3) An AVDA owns the surface and it responds to OnSurfaceDestroyed() by:
// a) Replacing the destroyed surface by calling MediaCodec#setSurface().
// b) Releasing the MediaCodec it's attached to.
// 4) No AVDA owns the surface, but the MediaCodec it's attached to is currently
// being destroyed on another thread.
void AVDACodecAllocator::OnSurfaceDestroyed(int surface_id) {
DVLOG(1) << __func__ << ": " << surface_id;
DCHECK(thread_checker_.CalledOnValidThread());
// Notify the owner and waiter (if any).
if (surface_owners_.count(surface_id)) {
OwnerRecord& record = surface_owners_[surface_id];
if (record.waiter) {
record.waiter->OnSurfaceAvailable(false);
record.waiter = nullptr;
}
if (record.owner)
record.owner->OnSurfaceDestroyed();
surface_owners_.erase(surface_id);
}
// The codec might have been released above in OnSurfaceDestroyed(), or was
// already pending release.
if (!pending_codec_releases_.count(surface_id))
return;
// The codec is being released so we have to wait for it here. It's a
// TimedWait() because the MediaCodec release may hang due to framework bugs.
// And in that case we don't want to hang the browser UI thread. Android ANRs
// occur when the UI thread is blocked for 5 seconds, so waiting for 2 seconds
// gives us leeway to avoid an ANR. Verified no ANR on a Nexus 7.
base::WaitableEvent& released =
pending_codec_releases_.find(surface_id)->second;
released.TimedWait(base::TimeDelta::FromSeconds(2));
if (!released.IsSignaled())
DLOG(WARNING) << __func__ << ": timed out waiting for MediaCodec#release()";
}
std::unique_ptr<MediaCodecBridge> AVDACodecAllocator::CreateMediaCodecSync(
scoped_refptr<CodecConfig> codec_config) {
TRACE_EVENT0("media", "AVDA::CreateMediaCodecSync");
jobject media_crypto =
codec_config->media_crypto ? codec_config->media_crypto->obj() : nullptr;
// |needs_protected_surface| implies encrypted stream.
DCHECK(!codec_config->needs_protected_surface || media_crypto);
const bool require_software_codec = codec_config->task_type == SW_CODEC;
std::unique_ptr<MediaCodecBridge> codec(
MediaCodecBridgeImpl::CreateVideoDecoder(
codec_config->codec, codec_config->needs_protected_surface,
codec_config->initial_expected_coded_size,
codec_config->surface_bundle->j_surface().obj(), media_crypto,
codec_config->csd0, codec_config->csd1, true,
require_software_codec));
return codec;
}
void AVDACodecAllocator::CreateMediaCodecAsync(
base::WeakPtr<AVDACodecAllocatorClient> client,
scoped_refptr<CodecConfig> codec_config) {
// Allocate the codec on the appropriate thread, and reply to this one with
// the result. If |client| is gone by then, we handle cleanup.
base::PostTaskAndReplyWithResult(
TaskRunnerFor(codec_config->task_type).get(), FROM_HERE,
base::Bind(&AVDACodecAllocator::CreateMediaCodecSync,
base::Unretained(this), codec_config),
base::Bind(&AVDACodecAllocator::ForwardOrDropCodec,
base::Unretained(this), client, codec_config->task_type,
codec_config->surface_bundle));
}
void AVDACodecAllocator::ForwardOrDropCodec(
base::WeakPtr<AVDACodecAllocatorClient> client,
TaskType task_type,
scoped_refptr<AVDASurfaceBundle> surface_bundle,
std::unique_ptr<MediaCodecBridge> media_codec) {
if (!client) {
// |client| has been destroyed. Free |media_codec| on the right thread.
// Note that this also preserves |surface_bundle| until |media_codec| has
// been released, in case our ref to it is the last one.
ReleaseMediaCodec(std::move(media_codec), task_type, surface_bundle);
return;
}
client->OnCodecConfigured(std::move(media_codec));
}
void AVDACodecAllocator::ReleaseMediaCodec(
std::unique_ptr<MediaCodecBridge> media_codec,
TaskType task_type,
const scoped_refptr<AVDASurfaceBundle>& surface_bundle) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(media_codec);
// No need to track the release if it's a SurfaceTexture. We still forward
// the reference to |surface_bundle|, though, so that the SurfaceTexture
// lasts at least as long as the codec.
if (surface_bundle->surface_id == SurfaceManager::kNoSurfaceID) {
TaskRunnerFor(task_type)->PostTaskAndReply(
FROM_HERE,
base::Bind(&DeleteMediaCodecAndSignal,
base::Passed(std::move(media_codec)), nullptr),
base::Bind(&DropReferenceToSurfaceBundle, surface_bundle));
return;
}
DCHECK(!surface_bundle->surface_texture);
pending_codec_releases_.emplace(
std::piecewise_construct,
std::forward_as_tuple(surface_bundle->surface_id),
std::forward_as_tuple(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED));
base::WaitableEvent* released =
&pending_codec_releases_.find(surface_bundle->surface_id)->second;
// Note that we forward |surface_bundle|, too, so that the surface outlasts
// the codec. This doesn't matter so much for CVV surfaces, since they don't
// auto-release when they're dropped. However, for surface owners, this will
// become important, so we still handle it. Plus, it makes sense.
TaskRunnerFor(task_type)->PostTaskAndReply(
FROM_HERE,
base::Bind(&DeleteMediaCodecAndSignal,
base::Passed(std::move(media_codec)), released),
base::Bind(&AVDACodecAllocator::OnMediaCodecReleased,
base::Unretained(this), surface_bundle));
}
void AVDACodecAllocator::OnMediaCodecReleased(
scoped_refptr<AVDASurfaceBundle> surface_bundle) {
DCHECK(thread_checker_.CalledOnValidThread());
pending_codec_releases_.erase(surface_bundle->surface_id);
if (!surface_owners_.count(surface_bundle->surface_id))
return;
OwnerRecord& record = surface_owners_[surface_bundle->surface_id];
if (!record.owner && record.waiter) {
record.owner = record.waiter;
record.waiter = nullptr;
record.owner->OnSurfaceAvailable(true);
}
// Also note that |surface_bundle| lasted at least as long as the codec.
}
// Returns a hint about whether the construction thread has hung for
// |task_type|. Note that if a thread isn't started, then we'll just return
// "not hung", since it'll run on the current thread anyway. The hang
// detector will see no pending jobs in that case, so it's automatic.
bool AVDACodecAllocator::IsThreadLikelyHung(TaskType task_type) {
DCHECK(thread_checker_.CalledOnValidThread());
return threads_[task_type]->hang_detector.IsThreadLikelyHung();
}
bool AVDACodecAllocator::IsAnyRegisteredAVDA() {
return !clients_.empty();
}
base::Optional<TaskType> AVDACodecAllocator::TaskTypeForAllocation() {
if (!IsThreadLikelyHung(AUTO_CODEC))
return AUTO_CODEC;
if (!IsThreadLikelyHung(SW_CODEC))
return SW_CODEC;
return base::nullopt;
}
base::Thread& AVDACodecAllocator::GetThreadForTesting(TaskType task_type) {
return threads_[task_type]->thread;
}
AVDACodecAllocator::AVDACodecAllocator(base::TickClock* tick_clock,
base::WaitableEvent* stop_event)
: stop_event_for_testing_(stop_event), weak_this_factory_(this) {
// We leak the clock we create, but that's okay because we're a singleton.
auto* clock = tick_clock ? tick_clock : new base::DefaultTickClock();
// Create threads with names and indices that match up with TaskType.
threads_.push_back(new ThreadAndHangDetector("AVDAAutoThread", clock));
threads_.push_back(new ThreadAndHangDetector("AVDASWThread", clock));
static_assert(AUTO_CODEC == 0 && SW_CODEC == 1,
"TaskType values are not ordered correctly.");
}
AVDACodecAllocator::~AVDACodecAllocator() {
// Only tests should reach here. Shut down threads so that we guarantee that
// nothing will use the threads.
for (auto* thread : threads_)
thread->thread.Stop();
}
void AVDACodecAllocator::StopThreadTask(size_t index) {
threads_[index]->thread.Stop();
// Signal the stop event after both threads are stopped.
if (stop_event_for_testing_ && !threads_[AUTO_CODEC]->thread.IsRunning() &&
!threads_[SW_CODEC]->thread.IsRunning()) {
stop_event_for_testing_->Signal();
}
}
} // namespace media