diff options
Diffstat (limited to 'chromium/content/renderer/media/audio/audio_renderer_sink_cache_impl.cc')
-rw-r--r-- | chromium/content/renderer/media/audio/audio_renderer_sink_cache_impl.cc | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/chromium/content/renderer/media/audio/audio_renderer_sink_cache_impl.cc b/chromium/content/renderer/media/audio/audio_renderer_sink_cache_impl.cc new file mode 100644 index 00000000000..a28bcf9eed6 --- /dev/null +++ b/chromium/content/renderer/media/audio/audio_renderer_sink_cache_impl.cc @@ -0,0 +1,352 @@ +// 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 "content/renderer/media/audio/audio_renderer_sink_cache_impl.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/trace_event/trace_event.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/render_frame_observer.h" +#include "content/renderer/media/audio/audio_device_factory.h" +#include "media/audio/audio_device_description.h" +#include "media/base/audio_renderer_sink.h" + +namespace content { + +AudioRendererSinkCacheImpl* AudioRendererSinkCacheImpl::instance_ = nullptr; +constexpr int kDeleteTimeoutMs = 5000; + +class AudioRendererSinkCacheImpl::FrameObserver : public RenderFrameObserver { + public: + explicit FrameObserver(content::RenderFrame* render_frame) + : RenderFrameObserver(render_frame) {} + ~FrameObserver() override{}; + + private: + // content::RenderFrameObserver implementation: + void DidCommitProvisionalLoad(bool is_new_navigation, + bool is_same_document_navigation) override { + if (!is_same_document_navigation) + DropFrameCache(); + } + + void OnDestruct() override { + DropFrameCache(); + delete this; + } + + void DropFrameCache() { + if (AudioRendererSinkCacheImpl::instance_) + AudioRendererSinkCacheImpl::instance_->DropSinksForFrame(routing_id()); + } + + DISALLOW_COPY_AND_ASSIGN(FrameObserver); +}; + +namespace { + +enum GetOutputDeviceInfoCacheUtilization { + // No cached sink found. + SINK_CACHE_MISS_NO_SINK = 0, + + // If session id is used to specify a device, we always have to create and + // cache a new sink. + SINK_CACHE_MISS_CANNOT_LOOKUP_BY_SESSION_ID = 1, + + // Output parmeters for an already-cached sink are requested. + SINK_CACHE_HIT = 2, + + // For UMA. + SINK_CACHE_LAST_ENTRY +}; + +bool SinkIsHealthy(media::AudioRendererSink* sink) { + return sink->GetOutputDeviceInfo().device_status() == + media::OUTPUT_DEVICE_STATUS_OK; +} + +} // namespace + +// Cached sink data. +struct AudioRendererSinkCacheImpl::CacheEntry { + int source_render_frame_id; + std::string device_id; + scoped_refptr<media::AudioRendererSink> sink; // Sink instance + bool used; // True if in use by a client. +}; + +// static +std::unique_ptr<AudioRendererSinkCache> AudioRendererSinkCache::Create() { + return std::make_unique<AudioRendererSinkCacheImpl>( + base::ThreadTaskRunnerHandle::Get(), + base::Bind(&AudioDeviceFactory::NewAudioRendererMixerSink), + base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs)); +} + +// static +void AudioRendererSinkCache::ObserveFrame(RenderFrame* frame) { + new AudioRendererSinkCacheImpl::FrameObserver(frame); +} + +AudioRendererSinkCacheImpl::AudioRendererSinkCacheImpl( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + CreateSinkCallback create_sink_cb, + base::TimeDelta delete_timeout) + : task_runner_(std::move(task_runner)), + create_sink_cb_(std::move(create_sink_cb)), + delete_timeout_(delete_timeout), + weak_ptr_factory_(this) { + weak_this_ = weak_ptr_factory_.GetWeakPtr(); + if (instance_) + LOG(ERROR) << "More that one AudioRendererSinkCache instance created. " + "Allowed in tests only."; + instance_ = this; +} + +AudioRendererSinkCacheImpl::~AudioRendererSinkCacheImpl() { + DCHECK(task_runner_->BelongsToCurrentThread()); + // We just release all the cached sinks here. Stop them first. + // We can stop all the sinks, no matter they are used or not, since everything + // is being destroyed anyways. + for (auto& entry : cache_) + entry.sink->Stop(); + + if (instance_ == this) + instance_ = nullptr; +} + +media::OutputDeviceInfo AudioRendererSinkCacheImpl::GetSinkInfo( + int source_render_frame_id, + int session_id, + const std::string& device_id) { + TRACE_EVENT_BEGIN2("audio", "AudioRendererSinkCacheImpl::GetSinkInfo", + "frame_id", source_render_frame_id, "device id", + device_id); + + if (media::AudioDeviceDescription::UseSessionIdToSelectDevice(session_id, + device_id)) { + // We are provided with session id instead of device id. Session id is + // unique, so we can't find any matching sink. Creating a new one. + scoped_refptr<media::AudioRendererSink> sink = + create_sink_cb_.Run(source_render_frame_id, session_id, device_id); + + CacheOrStopUnusedSink(source_render_frame_id, + sink->GetOutputDeviceInfo().device_id(), sink); + + UMA_HISTOGRAM_ENUMERATION( + "Media.Audio.Render.SinkCache.GetOutputDeviceInfoCacheUtilization", + SINK_CACHE_MISS_CANNOT_LOOKUP_BY_SESSION_ID, SINK_CACHE_LAST_ENTRY); + TRACE_EVENT_END1("audio", "AudioRendererSinkCacheImpl::GetSinkInfo", + "result", "Cache not used due to using |session_id|"); + + return sink->GetOutputDeviceInfo(); + } + // Ignore session id. + { + base::AutoLock auto_lock(cache_lock_); + auto cache_iter = FindCacheEntry_Locked(source_render_frame_id, device_id, + false /* unused_only */); + if (cache_iter != cache_.end()) { + // A matching cached sink is found. + UMA_HISTOGRAM_ENUMERATION( + "Media.Audio.Render.SinkCache.GetOutputDeviceInfoCacheUtilization", + SINK_CACHE_HIT, SINK_CACHE_LAST_ENTRY); + TRACE_EVENT_END1("audio", "AudioRendererSinkCacheImpl::GetSinkInfo", + "result", "Cache hit"); + return cache_iter->sink->GetOutputDeviceInfo(); + } + } + + // No matching sink found, create a new one. + scoped_refptr<media::AudioRendererSink> sink = create_sink_cb_.Run( + source_render_frame_id, 0 /* session_id */, device_id); + + CacheOrStopUnusedSink(source_render_frame_id, device_id, sink); + + UMA_HISTOGRAM_ENUMERATION( + "Media.Audio.Render.SinkCache.GetOutputDeviceInfoCacheUtilization", + SINK_CACHE_MISS_NO_SINK, SINK_CACHE_LAST_ENTRY); + + TRACE_EVENT_END1("audio", "AudioRendererSinkCacheImpl::GetSinkInfo", "result", + "Cache miss"); + // |sink| is ref-counted, so it's ok if it is removed from cache before we get + // here. + return sink->GetOutputDeviceInfo(); +} + +scoped_refptr<media::AudioRendererSink> AudioRendererSinkCacheImpl::GetSink( + int source_render_frame_id, + const std::string& device_id) { + UMA_HISTOGRAM_BOOLEAN("Media.Audio.Render.SinkCache.UsedForSinkCreation", + true); + TRACE_EVENT_BEGIN2("audio", "AudioRendererSinkCacheImpl::GetSink", "frame_id", + source_render_frame_id, "device id", device_id); + + base::AutoLock auto_lock(cache_lock_); + + auto cache_iter = FindCacheEntry_Locked(source_render_frame_id, device_id, + true /* unused sink only */); + + if (cache_iter != cache_.end()) { + // Found unused sink; mark it as used and return. + cache_iter->used = true; + UMA_HISTOGRAM_BOOLEAN( + "Media.Audio.Render.SinkCache.InfoSinkReusedForOutput", true); + TRACE_EVENT_END1("audio", "AudioRendererSinkCacheImpl::GetSink", "result", + "Cache hit"); + return cache_iter->sink; + } + + // No unused sink is found, create one, mark it used, cache it and return. + CacheEntry cache_entry = {source_render_frame_id, device_id, + create_sink_cb_.Run(source_render_frame_id, + 0 /* session_id */, device_id), + true /* used */}; + + if (SinkIsHealthy(cache_entry.sink.get())) { + TRACE_EVENT_INSTANT0( + "audio", "AudioRendererSinkCacheImpl::GetSink: caching new sink", + TRACE_EVENT_SCOPE_THREAD); + cache_.push_back(cache_entry); + } + + TRACE_EVENT_END1("audio", "AudioRendererSinkCacheImpl::GetSink", "result", + "Cache miss"); + return cache_entry.sink; +} + +void AudioRendererSinkCacheImpl::ReleaseSink( + const media::AudioRendererSink* sink_ptr) { + // We don't know the sink state, so won't reuse it. Delete it immediately. + DeleteSink(sink_ptr, true); +} + +void AudioRendererSinkCacheImpl::DeleteLaterIfUnused( + const media::AudioRendererSink* sink_ptr) { + task_runner_->PostDelayedTask( + FROM_HERE, + base::BindOnce(&AudioRendererSinkCacheImpl::DeleteSink, weak_this_, + base::RetainedRef(sink_ptr), + false /*do not delete if used*/), + delete_timeout_); +} + +void AudioRendererSinkCacheImpl::DeleteSink( + const media::AudioRendererSink* sink_ptr, + bool force_delete_used) { + DCHECK(sink_ptr); + + scoped_refptr<media::AudioRendererSink> sink_to_stop; + + { + base::AutoLock auto_lock(cache_lock_); + + // Looking up the sink by its pointer. + auto cache_iter = std::find_if(cache_.begin(), cache_.end(), + [sink_ptr](const CacheEntry& val) { + return val.sink.get() == sink_ptr; + }); + + if (cache_iter == cache_.end()) + return; + + // When |force_delete_used| is set, it's expected that we are deleting a + // used sink. + DCHECK((!force_delete_used) || (force_delete_used && cache_iter->used)) + << "Attempt to delete a non-acquired sink."; + + if (!force_delete_used && cache_iter->used) + return; + + // To stop the sink before deletion if it's not used, we need to hold + // a ref to it. + if (!cache_iter->used) { + sink_to_stop = cache_iter->sink; + UMA_HISTOGRAM_BOOLEAN( + "Media.Audio.Render.SinkCache.InfoSinkReusedForOutput", false); + } + + cache_.erase(cache_iter); + } // Lock scope; + + // Stop the sink out of the lock scope. + if (sink_to_stop.get()) { + DCHECK_EQ(sink_ptr, sink_to_stop.get()); + sink_to_stop->Stop(); + } +} + +AudioRendererSinkCacheImpl::CacheContainer::iterator +AudioRendererSinkCacheImpl::FindCacheEntry_Locked( + int source_render_frame_id, + const std::string& device_id, + bool unused_only) { + return std::find_if( + cache_.begin(), cache_.end(), + [source_render_frame_id, &device_id, unused_only](const CacheEntry& val) { + if (val.used && unused_only) + return false; + if (val.source_render_frame_id != source_render_frame_id) + return false; + if (media::AudioDeviceDescription::IsDefaultDevice(device_id) && + media::AudioDeviceDescription::IsDefaultDevice(val.device_id)) { + // Both device IDs represent the same default device => do not compare + // them; + return true; + } + return val.device_id == device_id; + }); +} + +void AudioRendererSinkCacheImpl::CacheOrStopUnusedSink( + int source_render_frame_id, + const std::string& device_id, + scoped_refptr<media::AudioRendererSink> sink) { + if (!SinkIsHealthy(sink.get())) { + TRACE_EVENT_INSTANT0("audio", "CacheOrStopUnusedSink: Unhealthy sink", + TRACE_EVENT_SCOPE_THREAD); + // Since |sink| is not cached, we must make sure to Stop it now. + sink->Stop(); + return; + } + + CacheEntry cache_entry = {source_render_frame_id, device_id, std::move(sink), + false /* not used */}; + + { + base::AutoLock auto_lock(cache_lock_); + cache_.push_back(cache_entry); + } + + DeleteLaterIfUnused(cache_entry.sink.get()); +} + +void AudioRendererSinkCacheImpl::DropSinksForFrame(int source_render_frame_id) { + base::AutoLock auto_lock(cache_lock_); + cache_.erase(std::remove_if(cache_.begin(), cache_.end(), + [source_render_frame_id](const CacheEntry& val) { + if (val.source_render_frame_id == + source_render_frame_id) { + val.sink->Stop(); + return true; + } + return false; + }), + cache_.end()); +} + +int AudioRendererSinkCacheImpl::GetCacheSizeForTesting() { + return cache_.size(); +} + +} // namespace content |