| Index: content/renderer/media/audio_renderer_sink_cache_impl.cc
|
| diff --git a/content/renderer/media/audio_renderer_sink_cache_impl.cc b/content/renderer/media/audio_renderer_sink_cache_impl.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9fef7d0937b1dc594ee067d45f10a1ef6363b2ae
|
| --- /dev/null
|
| +++ b/content/renderer/media/audio_renderer_sink_cache_impl.cc
|
| @@ -0,0 +1,253 @@
|
| +// 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 <algorithm>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/location.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/synchronization/lock.h"
|
| +#include "base/threading/thread_task_runner_handle.h"
|
| +#include "content/renderer/media/audio_device_factory.h"
|
| +#include "content/renderer/media/audio_renderer_sink_cache_impl.h"
|
| +#include "media/audio/audio_device_description.h"
|
| +#include "media/base/audio_renderer_sink.h"
|
| +#include "url/origin.h"
|
| +
|
| +namespace content {
|
| +
|
| +constexpr int kDeleteTimeoutMs = 5000;
|
| +
|
| +// Cached sink data.
|
| +struct AudioRendererSinkCacheImpl::CacheEntry {
|
| + int source_render_frame_id;
|
| + std::string device_id;
|
| + url::Origin security_origin;
|
| + scoped_refptr<media::AudioRendererSink> sink; // Sink instance
|
| + bool used; // True if in use by a client.
|
| +};
|
| +
|
| +// static
|
| +std::unique_ptr<AudioRendererSinkCache> AudioRendererSinkCache::Create() {
|
| + return base::WrapUnique(new AudioRendererSinkCacheImpl(
|
| + base::ThreadTaskRunnerHandle::Get(),
|
| + base::Bind(&AudioDeviceFactory::NewAudioRendererMixerSink),
|
| + base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs)));
|
| +}
|
| +
|
| +AudioRendererSinkCacheImpl::AudioRendererSinkCacheImpl(
|
| + scoped_refptr<base::SingleThreadTaskRunner> task_runner,
|
| + const CreateSinkCallback& create_sink_cb,
|
| + base::TimeDelta delete_timeout)
|
| + : task_runner_(std::move(task_runner)),
|
| + create_sink_cb_(create_sink_cb),
|
| + delete_timeout_(delete_timeout),
|
| + weak_ptr_factory_(this) {
|
| + weak_this_ = weak_ptr_factory_.GetWeakPtr();
|
| +}
|
| +
|
| +AudioRendererSinkCacheImpl::~AudioRendererSinkCacheImpl() {
|
| + // We just release all the cached sinks here.
|
| +}
|
| +
|
| +media::OutputDeviceInfo AudioRendererSinkCacheImpl::GetSinkInfo(
|
| + int source_render_frame_id,
|
| + int session_id,
|
| + const std::string& device_id,
|
| + const url::Origin& security_origin) {
|
| + CacheEntry cache_entry = {source_render_frame_id,
|
| + std::string() /* device_id */, security_origin,
|
| + nullptr /* sink */, false /* not used */};
|
| +
|
| + 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.
|
| + cache_entry.sink = create_sink_cb_.Run(source_render_frame_id, session_id,
|
| + device_id, security_origin);
|
| + cache_entry.device_id = cache_entry.sink->GetOutputDeviceInfo().device_id();
|
| +
|
| + DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get()
|
| + << " - used session to create new sink.";
|
| +
|
| + // Cache a newly-created sink.
|
| + base::AutoLock auto_lock(cache_lock_);
|
| + cache_.push_back(cache_entry);
|
| +
|
| + } else {
|
| + // Ignore session id.
|
| + base::AutoLock auto_lock(cache_lock_);
|
| +
|
| + auto cache_iter =
|
| + FindCacheEntry_Locked(source_render_frame_id, device_id,
|
| + security_origin, false /* unused_only */);
|
| +
|
| + if (cache_iter != cache_.end()) {
|
| + // A matching cached sink is found.
|
| + DVLOG(1) << "GetSinkInfo: address: " << cache_iter->sink.get()
|
| + << " - reused a cached sink.";
|
| +
|
| + return cache_iter->sink->GetOutputDeviceInfo();
|
| + }
|
| +
|
| + // No matching sink found, create a new one.
|
| + cache_entry.device_id = device_id;
|
| + cache_entry.sink = create_sink_cb_.Run(
|
| + source_render_frame_id, 0 /* session_id */, device_id, security_origin);
|
| +
|
| + DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get()
|
| + << " - no matching cached sink found, created a new one.";
|
| +
|
| + // Cache a newly-created sink.
|
| + cache_.push_back(cache_entry);
|
| + }
|
| +
|
| + // Schedule it for deletion.
|
| + DeleteLaterIfUnused(cache_entry.sink.get());
|
| +
|
| + DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get()
|
| + << " created. source_render_frame_id: " << source_render_frame_id
|
| + << " session_id: " << session_id << " device_id: " << device_id
|
| + << " security_origin: " << security_origin;
|
| +
|
| + return cache_entry.sink->GetOutputDeviceInfo();
|
| +}
|
| +
|
| +scoped_refptr<media::AudioRendererSink> AudioRendererSinkCacheImpl::GetSink(
|
| + int source_render_frame_id,
|
| + const std::string& device_id,
|
| + const url::Origin& security_origin) {
|
| + base::AutoLock auto_lock(cache_lock_);
|
| +
|
| + auto cache_iter =
|
| + FindCacheEntry_Locked(source_render_frame_id, device_id, security_origin,
|
| + true /* unused_only */);
|
| +
|
| + if (cache_iter != cache_.end()) {
|
| + // Found unused sink; mark it as used and return.
|
| + DVLOG(1) << "GetSink: address: " << cache_iter->sink.get()
|
| + << " - found unused cached sink, reusing it.";
|
| +
|
| + cache_iter->used = true;
|
| + 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, security_origin,
|
| + create_sink_cb_.Run(source_render_frame_id, 0 /* session_id */, device_id,
|
| + security_origin),
|
| + true /* used */};
|
| +
|
| + cache_.push_back(cache_entry);
|
| +
|
| + DVLOG(1) << "GetSink: address: " << cache_entry.sink.get()
|
| + << " - no unused cached sink found, created a new one."
|
| + << " source_render_frame_id: " << source_render_frame_id
|
| + << " device_id: " << device_id
|
| + << " security_origin: " << security_origin;
|
| + 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::Bind(&AudioRendererSinkCacheImpl::DeleteSink, weak_this_,
|
| + 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()) {
|
| + // If |force_delete_used| is not set it means the sink scheduled for
|
| + // deletion got aquired and released before scheduled deletion - it's ok.
|
| + DCHECK(!force_delete_used)
|
| + << "DeleteSink: address: " << sink_ptr
|
| + << " could not find a sink which is supposed to be in use";
|
| +
|
| + DVLOG(1) << "DeleteSink: address: " << sink_ptr
|
| + << " force_delete_used = false - already deleted.";
|
| + 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-aquired sink.";
|
| +
|
| + if (!force_delete_used && cache_iter->used) {
|
| + DVLOG(1) << "DeleteSink: address: " << sink_ptr
|
| + << " sink in use, skipping deletion.";
|
| + 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;
|
| +
|
| + cache_.erase(cache_iter);
|
| + DVLOG(1) << "DeleteSink: address: " << sink_ptr;
|
| + } // 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();
|
| + DVLOG(1) << "DeleteSink: address: " << sink_ptr << " stopped.";
|
| + }
|
| +}
|
| +
|
| +AudioRendererSinkCacheImpl::CacheContainer::iterator
|
| +AudioRendererSinkCacheImpl::FindCacheEntry_Locked(
|
| + int source_render_frame_id,
|
| + const std::string& device_id,
|
| + const url::Origin& security_origin,
|
| + bool unused_only) {
|
| + return std::find_if(
|
| + cache_.begin(), cache_.end(),
|
| + [source_render_frame_id, &device_id, &security_origin,
|
| + 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; the default device is always authorized => ignore security
|
| + // origin.
|
| + return true;
|
| + }
|
| + return val.device_id == device_id &&
|
| + val.security_origin == security_origin;
|
| + });
|
| +};
|
| +
|
| +int AudioRendererSinkCacheImpl::GetCacheSizeForTesting() {
|
| + return cache_.size();
|
| +}
|
| +
|
| +} // namespace content
|
|
|