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..81a3a1e7dd3eb521264003f133c795129994d0d5 |
--- /dev/null |
+++ b/content/renderer/media/audio_renderer_sink_cache_impl.cc |
@@ -0,0 +1,246 @@ |
+// 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 "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 "url/origin.h" |
+ |
+namespace { |
+enum { kDeleteTimeoutMs = 5000 }; |
+} // namespace |
+ |
+namespace content { |
+ |
+// 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) |
+ : delete_timeout_ms_(delete_timeout_ms), |
+ task_runner_(task_runner), |
+ create_sink_cb_(create_sink_cb), |
+ weak_ptr_factory_(this) {} |
+ |
+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) { |
+ 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, security_origin); |
+ |
+ const media::OutputDeviceInfo& device_info = sink->GetOutputDeviceInfo(); |
+ DVLOG(1) << "GetSinkInfo: address: " << sink.get() |
+ << " - used session to create new sink. source_render_frame_id: " |
+ << source_render_frame_id << " session_id: " << session_id |
+ << " device_id: " << device_id |
+ << " security_origin: " << security_origin << " Device info: [" |
+ << device_info.AsHumanReadableString() << "] "; |
+ |
+ const SinkKey key(source_render_frame_id, device_info.device_id(), |
+ security_origin); |
+ |
+ base::AutoLock auto_lock(sinks_lock_); |
+ // Cache a newly-created sink, using device id obtained from the sink. |
+ sinks_.insert(std::make_pair( |
+ key, AudioRendererSinkReference(sink, false /*not in use*/))); |
+ // And schedule it for deletion. |
+ DeleteLaterIfUnused(key, sink.get()); |
+ return device_info; |
+ } |
+ |
+ // We are provided with the actual device id. |
+ const SinkKey key(source_render_frame_id, device_id, security_origin); |
+ base::AutoLock auto_lock(sinks_lock_); |
+ |
+ { |
+ auto sink_iter = sinks_.find(key); |
+ if (sink_iter != sinks_.end()) { |
+ // A matching cached sink is found. |
+ DVLOG(1) << "GetSinkInfo: address: " << sink_iter->second.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: [" |
+ << sink_iter->second.sink->GetOutputDeviceInfo() |
+ .AsHumanReadableString() |
+ << "] "; |
+ return sink_iter->second.sink->GetOutputDeviceInfo(); |
+ } |
+ } |
+ |
+ // No matching sink is found, create one and cache it - still under the lock, |
+ // see the comment to InsertNewSinkWhileLockHeld(). |
+ scoped_refptr<media::AudioRendererSink> sink = |
+ InsertNewSinkWhileLockHeld(key, false /*not in use*/); |
+ DVLOG(1) << "GetSinkInfo: address: " << sink |
+ << " - no matching cached sink found, created a new one." |
+ << " source_render_frame_id: " << source_render_frame_id |
+ << " session_id: " << session_id << " device_id: " << device_id |
+ << " security_origin: " << security_origin << " Device info: [" |
+ << sink->GetOutputDeviceInfo().AsHumanReadableString() << "] "; |
+ // Schedule it for deletion. |
+ DeleteLaterIfUnused(key, sink.get()); |
+ return sink->GetOutputDeviceInfo(); |
+} |
+ |
+scoped_refptr<media::AudioRendererSink> AudioRendererSinkCacheImpl::GetSink( |
+ int source_render_frame_id, |
+ const std::string& device_id, |
+ const url::Origin& security_origin) { |
+ const SinkKey key(source_render_frame_id, device_id, security_origin); |
+ base::AutoLock auto_lock(sinks_lock_); |
+ |
+ const auto range = sinks_.equal_range(key); |
+ for (auto iter = range.first; iter != range.second; ++iter) { |
+ if (iter->second.used) |
+ continue; |
+ |
+ // Found unused sink; mark it as used and return. |
+ DVLOG(1) << "GetSink: address: " << iter->second.sink.get() |
+ << " - found unused cached sink, reusing it." |
+ << " source_render_frame_id: " << source_render_frame_id |
+ << " device_id: " << device_id |
+ << " security_origin: " << security_origin; |
+ |
+ iter->second.used = true; |
+ return iter->second.sink; |
+ } |
+ |
+ // No unused sink is found, create one, mark it used, cache it and return. |
+ scoped_refptr<media::AudioRendererSink> sink = |
+ InsertNewSinkWhileLockHeld(key, true); |
+ DVLOG(1) << "GetSink: address: " << 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 sink; |
+} |
+ |
+void AudioRendererSinkCacheImpl::ReleaseSink( |
+ int source_render_frame_id, |
+ const std::string& device_id, |
+ const url::Origin& security_origin, |
+ const media::AudioRendererSink* sink) { |
+ // We don't know the sink state, so won't reused it. Delete it immediately. |
+ DeleteSink(SinkKey(source_render_frame_id, device_id, security_origin), sink, |
+ true); |
+} |
+ |
+void AudioRendererSinkCacheImpl::DeleteLaterIfUnused( |
+ const SinkKey& key, |
+ const media::AudioRendererSink* sink) { |
+ DVLOG(1) << "DeleteLaterIfUnused: address: " << sink; |
+ |
+ task_runner_->PostDelayedTask( |
+ FROM_HERE, base::Bind(&AudioRendererSinkCacheImpl::DeleteSink, |
+ weak_ptr_factory_.GetWeakPtr(), key, sink, |
+ false /*do not delete if used*/), |
+ base::TimeDelta::FromMilliseconds(delete_timeout_ms_)); |
+} |
+ |
+void AudioRendererSinkCacheImpl::DeleteSink( |
+ const SinkKey& key, |
+ const media::AudioRendererSink* sink, |
+ bool force_delete_used) { |
+ DCHECK(sink); |
+ base::AutoLock auto_lock(sinks_lock_); |
+ |
+ const auto range = sinks_.equal_range(key); |
+ for (auto iter = range.first; iter != range.second; ++iter) { |
+ if (iter->second.sink.get() != sink) |
+ continue; |
+ |
+ const AudioRendererSinkReference& sink_ref(iter->second); |
+ |
+ DVLOG(1) << "DeleteSink: address: " << sink |
+ << " force_delete_used: " << force_delete_used |
+ << " in use: " << sink_ref.used |
+ << " source_render_frame_id: " << key.source_render_frame_id |
+ << " device_id: " << key.device_id |
+ << " security_origin: " << key.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 && sink_ref.used)) |
+ << "Attempt to delete a non-aquired sink."; |
+ |
+ if (force_delete_used || !sink_ref.used) { // We can delete it. |
+ sink_ref.sink->Stop(); // Sink must be stopped before deletion. |
+ sinks_.erase(iter); |
+ DVLOG(1) << "DeleteSink: address: " << sink << " deleted. "; |
+ } else { |
+ DVLOG(1) << "DeleteSink: address: " << sink |
+ << " sink in use, skipping deletion."; |
+ } |
+ |
+ return; |
+ } // for |
+ |
+ // 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 |
+ << " could not find a sink which is supposed to be" |
+ << " in use. Wrong key? source_render_frame_id: " |
+ << key.source_render_frame_id |
+ << " device_id: " << key.device_id |
+ << " security_origin: " << key.security_origin; |
+} |
+ |
+scoped_refptr<media::AudioRendererSink> |
+AudioRendererSinkCacheImpl::InsertNewSinkWhileLockHeld(const SinkKey& key, |
+ bool used) { |
+ sinks_lock_.AssertAcquired(); |
+ const AudioRendererSinkMap::iterator& iter = sinks_.insert(std::make_pair( |
+ key, AudioRendererSinkReference( |
+ create_sink_cb_.Run(key.source_render_frame_id, 0, key.device_id, |
+ key.security_origin), |
+ used))); |
+ return iter->second.sink; |
+} |
+ |
+AudioRendererSinkCacheImpl::SinkKey::SinkKey(int source_render_frame_id, |
+ const std::string& device_id, |
+ const url::Origin& security_origin) |
+ : source_render_frame_id(source_render_frame_id), |
+ device_id(device_id), |
+ security_origin(security_origin) {} |
+ |
+AudioRendererSinkCacheImpl::SinkKey::SinkKey(const SinkKey& other) = default; |
+ |
+AudioRendererSinkCacheImpl::AudioRendererSinkReference:: |
+ AudioRendererSinkReference(scoped_refptr<media::AudioRendererSink> sink, |
+ bool used) |
+ : sink(sink), used(used) {} |
+ |
+AudioRendererSinkCacheImpl::AudioRendererSinkReference:: |
+ AudioRendererSinkReference(const AudioRendererSinkReference& other) = |
+ default; |
+ |
+AudioRendererSinkCacheImpl::AudioRendererSinkReference:: |
+ ~AudioRendererSinkReference() {} |
+ |
+} // namespace content |