summaryrefslogtreecommitdiff
path: root/chromium/content/renderer/media/audio/audio_renderer_sink_cache_impl.cc
diff options
context:
space:
mode:
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.cc352
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