Chromium Code Reviews| 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..c363d9603aa787059a385df92efe47c76a12c648 100644 |
| --- a/media/gpu/avda_codec_allocator.cc |
| +++ b/media/gpu/avda_codec_allocator.cc |
| @@ -17,24 +17,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 +77,13 @@ bool AVDACodecAllocator::HangDetector::IsThreadLikelyHung() { |
| kHungTaskDetectionTimeout; |
| } |
| -// Make sure the construction threads are started for |avda|. |
| -bool AVDACodecAllocator::StartThread(AndroidVideoDecodeAccelerator* avda) { |
| +// static |
| +AVDACodecAllocator& AVDACodecAllocator::Get() { |
| + return g_avda_codec_allocator.Get(); |
| +} |
| + |
| +// 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 +113,16 @@ 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(), |
| + clients_.insert(client); |
| + UMA_HISTOGRAM_ENUMERATION("Media.AVDA.NumAVDAInstances", clients_.size(), |
| 31); // PRESUBMIT_IGNORE_UMA_MAX |
|
watk
2016/11/22 02:03:26
This was already removed and deprecated but made i
|
| 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 +133,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 +169,177 @@ 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 (!surface_owners_.count(surface_id) && |
| + !pending_codec_releases_.count(surface_id)) { |
| + surface_owners_[surface_id].owner = client; |
| + return true; |
| + } |
| + |
| + // The surface is already owned or being released. |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; |
| + |
| + if (record.waiter && !record.owner && |
| + !pending_codec_releases_.count(surface_id)) { |
| + record.owner = record.waiter; |
| + record.waiter = nullptr; |
| + record.owner->OnSurfaceAvailable(true); |
| + return; |
| + } |
| + |
| + 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()); |
| + |
| + if (surface_owners_.count(surface_id)) { |
|
DaleCurtis
2016/11/22 01:10:25
count will find all instances, so you probably wan
|
| + 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); |
| + } |
| + |
| + if (!pending_codec_releases_.count(surface_id)) |
| + return; |
| + |
| + // The codec is being released so we have to wait for it here. It's a |
|
DaleCurtis
2016/11/22 01:10:25
Sad face. Wish there was a way to avoid this.
|
| + // TimedWait() because the MediaCodec release may hang, 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 4 seconds gives us some |
| + // leeway to avoid an ANR. Tested on a Nexus 7. |
| + base::WaitableEvent& released = |
| + pending_codec_releases_.find(surface_id)->second; |
| + released.TimedWait(base::TimeDelta::FromSeconds(4)); |
| + 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 +350,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; |