Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include <algorithm> | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/location.h" | |
| 9 #include "base/memory/ptr_util.h" | |
| 10 #include "base/synchronization/lock.h" | |
| 11 #include "base/threading/thread_task_runner_handle.h" | |
| 12 #include "content/renderer/media/audio_device_factory.h" | |
| 13 #include "content/renderer/media/audio_renderer_sink_cache_impl.h" | |
| 14 #include "media/audio/audio_device_description.h" | |
| 15 #include "media/base/audio_renderer_sink.h" | |
| 16 #include "url/origin.h" | |
| 17 | |
| 18 namespace { | |
| 19 constexpr int kDeleteTimeoutMs = 5000; | |
| 20 } // namespace | |
| 21 | |
| 22 namespace content { | |
| 23 | |
| 24 // Cached sink data. | |
| 25 struct AudioRendererSinkCacheImpl::CacheEntry { | |
| 26 int source_render_frame_id; | |
| 27 std::string device_id; | |
| 28 url::Origin security_origin; | |
| 29 scoped_refptr<media::AudioRendererSink> sink; // Sink instance | |
| 30 bool used; // True if in use by a client. | |
| 31 }; | |
| 32 | |
| 33 // 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
| |
| 34 class CacheEntryFinder { | |
| 35 public: | |
| 36 CacheEntryFinder(int source_render_frame_id, | |
| 37 const std::string& device_id, | |
| 38 const url::Origin& security_origin, | |
| 39 bool unused_only) | |
| 40 : source_render_frame_id_(source_render_frame_id), | |
| 41 device_id_(device_id), | |
| 42 security_origin_(security_origin), | |
| 43 unused_only_(unused_only) {} | |
| 44 | |
| 45 bool operator()(const AudioRendererSinkCacheImpl::CacheEntry& s) const { | |
| 46 if (s.used && unused_only_) | |
| 47 return false; | |
| 48 | |
| 49 if (s.source_render_frame_id != source_render_frame_id_) | |
| 50 return false; | |
| 51 | |
| 52 if (media::AudioDeviceDescription::IsDefaultDevice(device_id_) && | |
| 53 media::AudioDeviceDescription::IsDefaultDevice(s.device_id)) { | |
| 54 // Both device IDs represent the same default device => do not compare | |
| 55 // them; the default device is always authorized => ignoring security | |
| 56 // origin. | |
| 57 return true; | |
| 58 } | |
| 59 | |
| 60 return (s.device_id == device_id_) && | |
| 61 (s.security_origin == security_origin_); | |
| 62 } | |
| 63 | |
| 64 private: | |
| 65 int source_render_frame_id_; | |
| 66 std::string device_id_; | |
| 67 url::Origin security_origin_; | |
| 68 bool unused_only_; | |
| 69 }; | |
| 70 | |
| 71 // static | |
| 72 std::unique_ptr<AudioRendererSinkCache> AudioRendererSinkCache::Create() { | |
| 73 return base::WrapUnique(new AudioRendererSinkCacheImpl( | |
| 74 base::ThreadTaskRunnerHandle::Get(), | |
| 75 base::Bind(AudioDeviceFactory::NewAudioRendererMixerSink), | |
| 76 kDeleteTimeoutMs)); | |
| 77 } | |
| 78 | |
| 79 AudioRendererSinkCacheImpl::AudioRendererSinkCacheImpl( | |
| 80 scoped_refptr<base::SingleThreadTaskRunner> task_runner, | |
| 81 const CreateSinkCallback& create_sink_cb, | |
| 82 int delete_timeout_ms) | |
| 83 : 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.
| |
| 84 create_sink_cb_(create_sink_cb), | |
| 85 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.
| |
| 86 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
| |
| 87 | |
| 88 AudioRendererSinkCacheImpl::~AudioRendererSinkCacheImpl() { | |
| 89 // We just release all the cached sinks here. | |
| 90 } | |
| 91 | |
| 92 media::OutputDeviceInfo AudioRendererSinkCacheImpl::GetSinkInfo( | |
| 93 int source_render_frame_id, | |
| 94 int session_id, | |
| 95 const std::string& device_id, | |
| 96 const url::Origin& security_origin) { | |
| 97 CacheEntry cache_entry; | |
| 98 cache_entry.source_render_frame_id = source_render_frame_id; | |
| 99 cache_entry.security_origin = security_origin; | |
| 100 cache_entry.used = false; | |
| 101 | |
| 102 if (media::AudioDeviceDescription::UseSessionIdToSelectDevice(session_id, | |
| 103 device_id)) { | |
| 104 // We are provided with session id instead of device id. Session id is | |
| 105 // unique, so we can't find any matching sink. Creating a new one. | |
| 106 cache_entry.sink = create_sink_cb_.Run(source_render_frame_id, session_id, | |
| 107 device_id, security_origin); | |
| 108 cache_entry.device_id = cache_entry.sink->GetOutputDeviceInfo().device_id(); | |
| 109 | |
| 110 DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get() | |
| 111 << " - used session to create new sink."; | |
| 112 } else { | |
| 113 // Ignore session id. | |
| 114 { | |
| 115 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.
| |
| 116 auto cache_iter = std::find_if( | |
| 117 cache_.begin(), cache_.end(), | |
| 118 CacheEntryFinder(source_render_frame_id, device_id, security_origin, | |
| 119 false /* unused_only */)); | |
| 120 if (cache_iter != cache_.end()) { | |
| 121 // A matching cached sink is found. | |
| 122 DVLOG(1) | |
| 123 << "GetSinkInfo: address: " << cache_iter->sink.get() | |
| 124 << " - reusing a cached sink. source_render_frame_id: " | |
| 125 << source_render_frame_id << " session_id: " << session_id | |
| 126 << " device_id: " << device_id | |
| 127 << " security_origin: " << security_origin << " Device info: [" | |
| 128 << cache_iter->sink->GetOutputDeviceInfo().AsHumanReadableString() | |
| 129 << "] "; | |
| 130 | |
| 131 return cache_iter->sink->GetOutputDeviceInfo(); | |
| 132 } // if (cache_iter != cache_.end()) | |
| 133 } // Lock scope. | |
| 134 | |
| 135 // No matching sink found, create a new one. | |
| 136 cache_entry.device_id = device_id; | |
| 137 cache_entry.sink = create_sink_cb_.Run( | |
| 138 source_render_frame_id, 0 /* session_id */, device_id, security_origin); | |
| 139 | |
| 140 DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get() | |
| 141 << " - no matching cached sink found, created a new one."; | |
| 142 } | |
| 143 | |
| 144 { | |
| 145 // Cache a newly-created sink. | |
| 146 base::AutoLock auto_lock(cache_lock_); | |
| 147 cache_.push_back(cache_entry); | |
| 148 } | |
| 149 | |
| 150 // 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
| |
| 151 DeleteLaterIfUnused(cache_entry.sink.get()); | |
| 152 | |
| 153 DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get() | |
| 154 << " inserted. source_render_frame_id: " << source_render_frame_id | |
| 155 << " session_id: " << session_id << " device_id: " << device_id | |
| 156 << " security_origin: " << security_origin << " Device info: [" | |
| 157 << cache_entry.sink->GetOutputDeviceInfo().AsHumanReadableString() | |
| 158 << "] "; | |
| 159 | |
| 160 return cache_entry.sink->GetOutputDeviceInfo(); | |
| 161 } | |
| 162 | |
| 163 scoped_refptr<media::AudioRendererSink> AudioRendererSinkCacheImpl::GetSink( | |
| 164 int source_render_frame_id, | |
| 165 const std::string& device_id, | |
| 166 const url::Origin& security_origin) { | |
| 167 { | |
| 168 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.
| |
| 169 | |
| 170 auto cache_iter = | |
| 171 std::find_if(cache_.begin(), cache_.end(), | |
| 172 CacheEntryFinder(source_render_frame_id, device_id, | |
| 173 security_origin, true /* unused_only */)); | |
| 174 | |
| 175 if (cache_iter != cache_.end()) { | |
| 176 // Found unused sink; mark it as used and return. | |
| 177 DVLOG(1) << "GetSink: address: " << cache_iter->sink.get() | |
| 178 << " - found unused cached sink, reusing it." | |
| 179 << " source_render_frame_id: " << source_render_frame_id | |
| 180 << " device_id: " << device_id | |
| 181 << " security_origin: " << security_origin; | |
| 182 | |
| 183 cache_iter->used = true; | |
| 184 return cache_iter->sink; | |
| 185 } // for | |
| 186 } // Lock scope. | |
| 187 | |
| 188 // No unused sink is found, create one, mark it used, cache it and return. | |
| 189 CacheEntry cache_entry; | |
| 190 cache_entry.source_render_frame_id = source_render_frame_id; | |
| 191 cache_entry.device_id = device_id; | |
| 192 cache_entry.security_origin = security_origin; | |
| 193 cache_entry.used = true; | |
| 194 | |
| 195 cache_entry.sink = create_sink_cb_.Run( | |
| 196 source_render_frame_id, 0 /* session_id */, device_id, security_origin); | |
| 197 | |
| 198 base::AutoLock auto_lock(cache_lock_); | |
| 199 cache_.push_back(cache_entry); | |
| 200 | |
| 201 DVLOG(1) << "GetSink: address: " << cache_entry.sink.get() | |
| 202 << " - no unused cached sink found, created a new one." | |
| 203 << " source_render_frame_id: " << source_render_frame_id | |
| 204 << " device_id: " << device_id | |
| 205 << " security_origin: " << security_origin; | |
| 206 return cache_entry.sink; | |
| 207 } | |
| 208 | |
| 209 void AudioRendererSinkCacheImpl::ReleaseSink( | |
| 210 const media::AudioRendererSink* sink_ptr) { | |
| 211 // We don't know the sink state, so won't reuse it. Delete it immediately. | |
| 212 DeleteSink(sink_ptr, true); | |
| 213 } | |
| 214 | |
| 215 void AudioRendererSinkCacheImpl::DeleteLaterIfUnused( | |
| 216 const media::AudioRendererSink* sink_ptr) { | |
| 217 DVLOG(1) << "DeleteLaterIfUnused: address: " << sink_ptr; | |
| 218 | |
| 219 task_runner_->PostDelayedTask( | |
| 220 FROM_HERE, base::Bind(&AudioRendererSinkCacheImpl::DeleteSink, | |
| 221 weak_ptr_factory_.GetWeakPtr(), sink_ptr, | |
| 222 false /*do not delete if used*/), | |
| 223 base::TimeDelta::FromMilliseconds(delete_timeout_ms_)); | |
| 224 } | |
| 225 | |
| 226 void AudioRendererSinkCacheImpl::DeleteSink( | |
| 227 const media::AudioRendererSink* sink_ptr, | |
| 228 bool force_delete_used) { | |
| 229 DCHECK(sink_ptr); | |
| 230 | |
| 231 scoped_refptr<media::AudioRendererSink> sink_to_stop; | |
| 232 | |
| 233 { | |
| 234 base::AutoLock auto_lock(cache_lock_); | |
| 235 | |
| 236 // Looking up the sink by its pointer. | |
| 237 auto cache_iter = std::find_if(cache_.begin(), cache_.end(), | |
| 238 [&sink_ptr](const CacheEntry& val) { | |
| 239 return val.sink.get() == sink_ptr; | |
| 240 }); | |
| 241 | |
| 242 if (cache_iter == cache_.end()) { | |
| 243 // If we got here and |force_delete_used| is not set it means the sink | |
| 244 // scheduled for deletion get aquired and released before scheduled | |
| 245 // deletion - this is ok. | |
| 246 DCHECK(!force_delete_used) | |
| 247 << "DeleteSink: address: " << sink_ptr | |
| 248 << " could not find a sink which is supposed to be in use"; | |
| 249 | |
| 250 DVLOG(1) << "DeleteSink: address: " << sink_ptr | |
| 251 << " force_delete_used = true - already deleted."; | |
| 252 return; | |
| 253 } | |
| 254 | |
| 255 DVLOG(1) << "DeleteSink: address: " << sink_ptr | |
| 256 << " force_delete_used: " << force_delete_used | |
| 257 << " in use: " << cache_iter->used << " source_render_frame_id: " | |
| 258 << cache_iter->source_render_frame_id | |
| 259 << " device_id: " << cache_iter->device_id | |
| 260 << " security_origin: " << cache_iter->security_origin; | |
| 261 | |
| 262 // When |force_delete_used| is set, it's expected that we are deleting a | |
| 263 // used sink. | |
| 264 DCHECK((!force_delete_used) || (force_delete_used && cache_iter->used)) | |
| 265 << "Attempt to delete a non-aquired sink."; | |
| 266 | |
| 267 if (!force_delete_used && cache_iter->used) { | |
| 268 DVLOG(1) << "DeleteSink: address: " << sink_ptr | |
| 269 << " sink in use, skipping deletion."; | |
| 270 return; | |
| 271 } | |
| 272 | |
| 273 // To stop the sink before deletion if it's not used, we need to hold | |
| 274 // a ref to it. | |
| 275 if (!cache_iter->used) | |
| 276 sink_to_stop = cache_iter->sink; | |
| 277 | |
| 278 cache_.erase(cache_iter); | |
| 279 DVLOG(1) << "DeleteSink: address: " << sink_ptr; | |
| 280 } // Lock scope; | |
| 281 | |
| 282 // Stop the sink out of the lock scope. | |
| 283 if (sink_to_stop.get()) { | |
| 284 DCHECK_EQ(sink_ptr, sink_to_stop.get()); | |
| 285 sink_to_stop->Stop(); | |
| 286 DVLOG(1) << "DeleteSink: address: " << sink_ptr << " stopped."; | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 int AudioRendererSinkCacheImpl::GetCacheSizeForTesting() { | |
| 291 return cache_.size(); | |
| 292 } | |
| 293 | |
| 294 } // namespace content | |
| OLD | NEW |