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..2a16606baa88a33b4928a3686a73851569e076e3 |
| --- /dev/null |
| +++ b/content/renderer/media/audio_renderer_sink_cache_impl.cc |
| @@ -0,0 +1,294 @@ |
| +// 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 { |
| +constexpr int kDeleteTimeoutMs = 5000; |
| +} // namespace |
| + |
| +namespace content { |
| + |
| +// 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. |
| +}; |
| + |
| +// Helper class for CacheEntry lookup. |
|
DaleCurtis
2016/05/23 18:29:08
Why choose a helper class vs a lambda?
o1ka
2016/05/23 19:21:34
It's used in two places and requires a quite lot o
DaleCurtis
2016/05/23 20:20:26
Hmm, looks like my original comment got eaten. I c
o1ka
2016/05/24 15:00:41
Yes, this is another way to implement it. I don't
|
| +class CacheEntryFinder { |
| + public: |
| + CacheEntryFinder(int source_render_frame_id, |
| + const std::string& device_id, |
| + const url::Origin& security_origin, |
| + bool unused_only) |
| + : source_render_frame_id_(source_render_frame_id), |
| + device_id_(device_id), |
| + security_origin_(security_origin), |
| + unused_only_(unused_only) {} |
| + |
| + bool operator()(const AudioRendererSinkCacheImpl::CacheEntry& s) const { |
| + if (s.used && unused_only_) |
| + return false; |
| + |
| + if (s.source_render_frame_id != source_render_frame_id_) |
| + return false; |
| + |
| + if (media::AudioDeviceDescription::IsDefaultDevice(device_id_) && |
| + media::AudioDeviceDescription::IsDefaultDevice(s.device_id)) { |
| + // Both device IDs represent the same default device => do not compare |
| + // them; the default device is always authorized => ignoring security |
| + // origin. |
| + return true; |
| + } |
| + |
| + return (s.device_id == device_id_) && |
| + (s.security_origin == security_origin_); |
| + } |
| + |
| + private: |
| + int source_render_frame_id_; |
| + std::string device_id_; |
| + url::Origin security_origin_; |
| + bool unused_only_; |
| +}; |
| + |
| +// static |
| +std::unique_ptr<AudioRendererSinkCache> AudioRendererSinkCache::Create() { |
| + return base::WrapUnique(new AudioRendererSinkCacheImpl( |
| + base::ThreadTaskRunnerHandle::Get(), |
| + base::Bind(AudioDeviceFactory::NewAudioRendererMixerSink), |
| + kDeleteTimeoutMs)); |
| +} |
| + |
| +AudioRendererSinkCacheImpl::AudioRendererSinkCacheImpl( |
| + scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| + const CreateSinkCallback& create_sink_cb, |
| + int delete_timeout_ms) |
| + : task_runner_(task_runner), |
|
DaleCurtis
2016/05/23 18:29:08
Isn't std::move necessary now whenever you pass-by
o1ka
2016/05/24 15:00:41
Done.
|
| + create_sink_cb_(create_sink_cb), |
| + delete_timeout_ms_(delete_timeout_ms), |
|
DaleCurtis
2016/05/23 18:29:08
store as timedelta, FromMilliseconds(...)
o1ka
2016/05/24 15:00:41
Done.
|
| + weak_ptr_factory_(this) {} |
|
DaleCurtis
2016/05/23 18:29:08
If you take my suggestion below to use a Repeating
o1ka
2016/05/24 15:00:41
This is a nice suggestion. The advantage of the cu
|
| + |
| +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; |
| + cache_entry.source_render_frame_id = source_render_frame_id; |
| + cache_entry.security_origin = security_origin; |
| + cache_entry.used = false; |
| + |
| + 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."; |
| + } else { |
| + // Ignore session id. |
| + { |
| + base::AutoLock auto_lock(cache_lock_); |
|
DaleCurtis
2016/05/23 18:29:08
Instead of dropping in and out of lock state (some
o1ka
2016/05/23 19:21:34
Ok will do.
o1ka
2016/05/24 15:00:41
Done.
|
| + auto cache_iter = std::find_if( |
| + cache_.begin(), cache_.end(), |
| + CacheEntryFinder(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() |
| + << " - reusing a cached sink. source_render_frame_id: " |
| + << source_render_frame_id << " session_id: " << session_id |
| + << " device_id: " << device_id |
| + << " security_origin: " << security_origin << " Device info: [" |
| + << cache_iter->sink->GetOutputDeviceInfo().AsHumanReadableString() |
| + << "] "; |
| + |
| + return cache_iter->sink->GetOutputDeviceInfo(); |
| + } // if (cache_iter != cache_.end()) |
| + } // Lock scope. |
| + |
| + // 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. |
| + base::AutoLock auto_lock(cache_lock_); |
| + cache_.push_back(cache_entry); |
| + } |
| + |
| + // Schedule it for deletion. |
|
DaleCurtis
2016/05/23 18:29:08
Why schedule them individually for deletion vs hav
o1ka
2016/05/23 19:21:34
Because it's quite a rare occasion when output par
o1ka
2016/05/23 19:26:27
(It was like that in the original draft version wh
DaleCurtis
2016/05/23 20:20:26
You wouldn't run the timer the whole time. You'd o
o1ka
2016/05/24 15:00:41
I looked into CancelableCallback before implementi
DaleCurtis
2016/05/24 20:41:42
This isn't true of WeakPtr either. You have a race
o1ka
2016/05/25 12:20:51
Oh, I've had a feeling that I might be missing som
DaleCurtis
2016/05/25 16:48:52
The multiple threads thing isn't a problem if you
o1ka
2016/05/25 17:00:56
Would't I need to use a weak pointer to post that
|
| + DeleteLaterIfUnused(cache_entry.sink.get()); |
| + |
| + DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get() |
| + << " inserted. source_render_frame_id: " << source_render_frame_id |
| + << " session_id: " << session_id << " device_id: " << device_id |
| + << " security_origin: " << security_origin << " Device info: [" |
| + << cache_entry.sink->GetOutputDeviceInfo().AsHumanReadableString() |
| + << "] "; |
| + |
| + 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_); |
|
DaleCurtis
2016/05/23 18:29:08
Ditto on lock once.
o1ka
2016/05/24 15:00:41
Done.
|
| + |
| + auto cache_iter = |
| + std::find_if(cache_.begin(), cache_.end(), |
| + CacheEntryFinder(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." |
| + << " source_render_frame_id: " << source_render_frame_id |
| + << " device_id: " << device_id |
| + << " security_origin: " << security_origin; |
| + |
| + cache_iter->used = true; |
| + return cache_iter->sink; |
| + } // for |
| + } // Lock scope. |
| + |
| + // No unused sink is found, create one, mark it used, cache it and return. |
| + CacheEntry cache_entry; |
| + cache_entry.source_render_frame_id = source_render_frame_id; |
| + cache_entry.device_id = device_id; |
| + cache_entry.security_origin = security_origin; |
| + cache_entry.used = true; |
| + |
| + cache_entry.sink = create_sink_cb_.Run( |
| + source_render_frame_id, 0 /* session_id */, device_id, security_origin); |
| + |
| + base::AutoLock auto_lock(cache_lock_); |
| + 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_ptr_factory_.GetWeakPtr(), sink_ptr, |
| + false /*do not delete if used*/), |
| + base::TimeDelta::FromMilliseconds(delete_timeout_ms_)); |
| +} |
| + |
| +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 |
| + // scheduled for deletion get aquired and released before scheduled |
| + // 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; |
| + } |
| + |
| + DVLOG(1) << "DeleteSink: address: " << sink_ptr |
| + << " force_delete_used: " << force_delete_used |
| + << " in use: " << cache_iter->used << " source_render_frame_id: " |
| + << cache_iter->source_render_frame_id |
| + << " device_id: " << cache_iter->device_id |
| + << " security_origin: " << cache_iter->security_origin; |
| + |
| + // 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."; |
| + } |
| +} |
| + |
| +int AudioRendererSinkCacheImpl::GetCacheSizeForTesting() { |
| + return cache_.size(); |
| +} |
| + |
| +} // namespace content |