Chromium Code Reviews| 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..22a89efad368be28f4074528afd3b5eb042091ce |
| --- /dev/null |
| +++ b/content/renderer/media/audio_renderer_sink_cache_impl.cc |
| @@ -0,0 +1,256 @@ |
| +// 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() |
|
Guido Urdaneta
2016/05/25 13:33:14
Consider removing these DVLOG statements.
o1ka
2016/05/25 14:52:11
You mean - ALL??? :) If reviewers insist I'll do i
Guido Urdaneta
2016/05/25 15:44:09
No big deal, really. It's up to you :)
o1ka
2016/05/26 09:00:27
Acknowledged.
|
| + << " - 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) { |
| + DVLOG(1) << "DeleteLaterIfUnused: address: " << 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 we got here and |force_delete_used| is not set it means the sink |
|
Guido Urdaneta
2016/05/25 13:33:14
remove "we got here and"
o1ka
2016/05/25 14:52:11
Done.
|
| + // scheduled for deletion get aquired and released before scheduled |
|
Guido Urdaneta
2016/05/25 13:33:13
typo: s/get/got
o1ka
2016/05/25 14:52:11
Done.
|
| + // deletion - this is 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 = true - 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 |