| Index: media/gpu/avda_codec_allocator.cc
|
| diff --git a/media/gpu/avda_codec_allocator.cc b/media/gpu/avda_codec_allocator.cc
|
| index f1e2f1c69f245b9bf7de64def825c8ace83926cd..c947f40c7a3ea7f7330445fba51698b9ca73f322 100644
|
| --- a/media/gpu/avda_codec_allocator.cc
|
| +++ b/media/gpu/avda_codec_allocator.cc
|
| @@ -9,7 +9,6 @@
|
| #include <memory>
|
|
|
| #include "base/logging.h"
|
| -#include "base/metrics/histogram.h"
|
| #include "base/sys_info.h"
|
| #include "base/task_runner_util.h"
|
| #include "base/threading/thread.h"
|
| @@ -17,24 +16,39 @@
|
| #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/sdk_media_codec_bridge.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 on the construction thread 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 if its apparently hung task
|
| -// completes.
|
| +base::LazyInstance<AVDACodecAllocator>::Leaky g_avda_codec_allocator =
|
| + LAZY_INSTANCE_INITIALIZER;
|
| +
|
| +// 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<VideoCodecBridge> codec,
|
| + base::WaitableEvent* done_event) {
|
| + codec.reset();
|
| + if (done_event)
|
| + done_event->Signal();
|
| +}
|
| +
|
| } // namespace
|
|
|
| +CodecConfig::CodecConfig() {}
|
| +CodecConfig::~CodecConfig() {}
|
| +
|
| AVDACodecAllocator::TestInformation::TestInformation() {}
|
| AVDACodecAllocator::TestInformation::~TestInformation() {}
|
|
|
| @@ -62,8 +76,13 @@ bool AVDACodecAllocator::HangDetector::IsThreadLikelyHung() {
|
| kHungTaskDetectionTimeout;
|
| }
|
|
|
| -// Make sure the construction threads are started for |avda|.
|
| -bool AVDACodecAllocator::StartThread(AndroidVideoDecodeAccelerator* avda) {
|
| +// static
|
| +AVDACodecAllocator* AVDACodecAllocator::Instance() {
|
| + return g_avda_codec_allocator.Pointer();
|
| +}
|
| +
|
| +// Make sure the construction threads are started for |client|.
|
| +bool AVDACodecAllocator::StartThread(AVDACodecAllocatorClient* client) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| // Cancel any pending StopThreadTask()s because we need the threads now.
|
| @@ -93,17 +112,14 @@ bool AVDACodecAllocator::StartThread(AndroidVideoDecodeAccelerator* avda) {
|
| if (!threads_[TaskType::AUTO_CODEC]->thread.IsRunning())
|
| return false;
|
|
|
| - thread_avda_instances_.insert(avda);
|
| - UMA_HISTOGRAM_ENUMERATION("Media.AVDA.NumAVDAInstances",
|
| - thread_avda_instances_.size(),
|
| - 31); // PRESUBMIT_IGNORE_UMA_MAX
|
| + clients_.insert(client);
|
| return true;
|
| }
|
|
|
| -void AVDACodecAllocator::StopThread(AndroidVideoDecodeAccelerator* avda) {
|
| +void AVDACodecAllocator::StopThread(AVDACodecAllocatorClient* client) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| - thread_avda_instances_.erase(avda);
|
| + clients_.erase(client);
|
| // Post a task to stop the thread through the thread's task runner and back
|
| // to this thread. This ensures that all pending tasks are run first. If the
|
| // thread is hung we don't post a task to avoid leaking an unbounded number
|
| @@ -114,7 +130,7 @@ void AVDACodecAllocator::StopThread(AndroidVideoDecodeAccelerator* avda) {
|
| // be canceled by invalidating its weak pointer.
|
| base::WaitableEvent* event =
|
| (test_info_ ? test_info_->stop_event_.get() : nullptr);
|
| - if (!thread_avda_instances_.empty()) {
|
| + if (!clients_.empty()) {
|
| // If we aren't stopping, then signal immediately.
|
| if (event)
|
| event->Signal();
|
| @@ -150,6 +166,182 @@ scoped_refptr<base::SingleThreadTaskRunner> AVDACodecAllocator::TaskRunnerFor(
|
| return thread.task_runner();
|
| }
|
|
|
| +bool AVDACodecAllocator::AllocateSurface(AVDACodecAllocatorClient* 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(AVDACodecAllocatorClient* 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<VideoCodecBridge> 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_ == TaskType::SW_CODEC;
|
| +
|
| + std::unique_ptr<VideoCodecBridge> codec(VideoCodecBridge::CreateDecoder(
|
| + codec_config->codec_, codec_config->needs_protected_surface_,
|
| + codec_config->initial_expected_coded_size_,
|
| + codec_config->surface_.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) {
|
| + base::PostTaskAndReplyWithResult(
|
| + TaskRunnerFor(codec_config->task_type_).get(), FROM_HERE,
|
| + base::Bind(&AVDACodecAllocator::CreateMediaCodecSync,
|
| + base::Unretained(this), codec_config),
|
| + base::Bind(&AVDACodecAllocatorClient::OnCodecConfigured, client));
|
| +}
|
| +
|
| +void AVDACodecAllocator::ReleaseMediaCodec(
|
| + std::unique_ptr<VideoCodecBridge> media_codec,
|
| + TaskType task_type,
|
| + int surface_id) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(media_codec);
|
| +
|
| + // No need to track the release if it's a SurfaceTexture.
|
| + if (surface_id == SurfaceManager::kNoSurfaceID) {
|
| + TaskRunnerFor(task_type)->PostTask(
|
| + FROM_HERE, base::Bind(&DeleteMediaCodecAndSignal,
|
| + base::Passed(std::move(media_codec)), nullptr));
|
| + return;
|
| + }
|
| +
|
| + pending_codec_releases_.emplace(
|
| + std::piecewise_construct, std::forward_as_tuple(surface_id),
|
| + std::forward_as_tuple(base::WaitableEvent::ResetPolicy::MANUAL,
|
| + base::WaitableEvent::InitialState::NOT_SIGNALED));
|
| + base::WaitableEvent* released =
|
| + &pending_codec_releases_.find(surface_id)->second;
|
| +
|
| + // TODO(watk): Even if this is the current thread, things will work, but we
|
| + // should refactor this to not tolerate threads failing to start.
|
| + TaskRunnerFor(task_type)->PostTaskAndReply(
|
| + FROM_HERE, base::Bind(&DeleteMediaCodecAndSignal,
|
| + base::Passed(std::move(media_codec)), released),
|
| + base::Bind(&AVDACodecAllocator::OnMediaCodecAndSurfaceReleased,
|
| + base::Unretained(this), surface_id));
|
| +}
|
| +
|
| +void AVDACodecAllocator::OnMediaCodecAndSurfaceReleased(int surface_id) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + pending_codec_releases_.erase(surface_id);
|
| + if (!surface_owners_.count(surface_id))
|
| + return;
|
| +
|
| + OwnerRecord& record = surface_owners_[surface_id];
|
| + if (!record.owner && record.waiter) {
|
| + record.owner = record.waiter;
|
| + record.waiter = nullptr;
|
| + record.owner->OnSurfaceAvailable(true);
|
| + }
|
| +}
|
| +
|
| // 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
|
| @@ -160,10 +352,10 @@ bool AVDACodecAllocator::IsThreadLikelyHung(TaskType task_type) {
|
| }
|
|
|
| bool AVDACodecAllocator::IsAnyRegisteredAVDA() {
|
| - return !thread_avda_instances_.empty();
|
| + return !clients_.empty();
|
| }
|
|
|
| -AVDACodecAllocator::TaskType AVDACodecAllocator::TaskTypeForAllocation() {
|
| +TaskType AVDACodecAllocator::TaskTypeForAllocation() {
|
| if (!IsThreadLikelyHung(TaskType::AUTO_CODEC))
|
| return TaskType::AUTO_CODEC;
|
|
|
|
|