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 content { | |
| 19 | |
| 20 constexpr int kDeleteTimeoutMs = 5000; | |
| 21 | |
| 22 // Cached sink data. | |
| 23 struct AudioRendererSinkCacheImpl::CacheEntry { | |
| 24 int source_render_frame_id; | |
| 25 std::string device_id; | |
| 26 url::Origin security_origin; | |
| 27 scoped_refptr<media::AudioRendererSink> sink; // Sink instance | |
| 28 bool used; // True if in use by a client. | |
| 29 }; | |
| 30 | |
| 31 // Helper class for CacheEntry lookup. | |
| 32 class CacheEntryFinder { | |
| 33 public: | |
| 34 CacheEntryFinder(int source_render_frame_id, | |
| 35 const std::string& device_id, | |
| 36 const url::Origin& security_origin, | |
| 37 bool unused_only) | |
| 38 : source_render_frame_id_(source_render_frame_id), | |
| 39 device_id_(device_id), | |
| 40 security_origin_(security_origin), | |
| 41 unused_only_(unused_only) {} | |
| 42 | |
| 43 bool operator()(const AudioRendererSinkCacheImpl::CacheEntry& s) const { | |
| 44 if (s.used && unused_only_) | |
| 45 return false; | |
| 46 | |
| 47 if (s.source_render_frame_id != source_render_frame_id_) | |
| 48 return false; | |
| 49 | |
| 50 if (media::AudioDeviceDescription::IsDefaultDevice(device_id_) && | |
| 51 media::AudioDeviceDescription::IsDefaultDevice(s.device_id)) { | |
| 52 // Both device IDs represent the same default device => do not compare | |
| 53 // them; the default device is always authorized => ignoring security | |
| 54 // origin. | |
| 55 return true; | |
| 56 } | |
| 57 | |
| 58 return (s.device_id == device_id_) && | |
|
DaleCurtis
2016/05/24 20:41:42
Avoid unnecessary parens.
o1ka
2016/05/25 12:20:52
Done.
This is something interesting though - I hav
| |
| 59 (s.security_origin == security_origin_); | |
| 60 } | |
| 61 | |
| 62 private: | |
| 63 int source_render_frame_id_; | |
|
DaleCurtis
2016/05/24 20:41:42
const all these
o1ka
2016/05/25 12:20:52
Done. (And then I removed the code)
| |
| 64 std::string device_id_; | |
| 65 url::Origin security_origin_; | |
| 66 bool unused_only_; | |
| 67 }; | |
|
DaleCurtis
2016/05/24 20:41:42
DISALLOW_COPY_AND_ASSIGN
o1ka
2016/05/25 12:20:52
It's copyable (and needs to be so for find_if), so
| |
| 68 | |
| 69 // static | |
| 70 std::unique_ptr<AudioRendererSinkCache> AudioRendererSinkCache::Create() { | |
| 71 return base::WrapUnique(new AudioRendererSinkCacheImpl( | |
| 72 base::ThreadTaskRunnerHandle::Get(), | |
| 73 base::Bind(AudioDeviceFactory::NewAudioRendererMixerSink), | |
| 74 base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs))); | |
| 75 } | |
| 76 | |
| 77 AudioRendererSinkCacheImpl::AudioRendererSinkCacheImpl( | |
| 78 scoped_refptr<base::SingleThreadTaskRunner> task_runner, | |
| 79 const CreateSinkCallback& create_sink_cb, | |
| 80 const base::TimeDelta& delete_timeout) | |
|
DaleCurtis
2016/05/24 20:41:42
Pass base::TimeDelta by value (see time.h)
o1ka
2016/05/25 12:20:52
Done.
| |
| 81 : task_runner_(std::move(task_runner)), | |
| 82 create_sink_cb_(create_sink_cb), | |
| 83 delete_timeout_(delete_timeout), | |
| 84 weak_ptr_factory_(this) {} | |
| 85 | |
| 86 AudioRendererSinkCacheImpl::~AudioRendererSinkCacheImpl() { | |
| 87 // We just release all the cached sinks here. | |
| 88 } | |
| 89 | |
| 90 media::OutputDeviceInfo AudioRendererSinkCacheImpl::GetSinkInfo( | |
| 91 int source_render_frame_id, | |
| 92 int session_id, | |
| 93 const std::string& device_id, | |
| 94 const url::Origin& security_origin) { | |
| 95 CacheEntry cache_entry; | |
|
DaleCurtis
2016/05/24 20:41:42
Worth adding a constructor to CacheEntry with some
miu
2016/05/25 01:23:20
IIRC, the uniform initializer syntax is now approv
o1ka
2016/05/25 12:20:52
True, but assignment syntax is recommended for thi
| |
| 96 cache_entry.source_render_frame_id = source_render_frame_id; | |
| 97 cache_entry.security_origin = security_origin; | |
| 98 cache_entry.used = false; | |
| 99 | |
| 100 if (media::AudioDeviceDescription::UseSessionIdToSelectDevice(session_id, | |
| 101 device_id)) { | |
| 102 // We are provided with session id instead of device id. Session id is | |
| 103 // unique, so we can't find any matching sink. Creating a new one. | |
| 104 cache_entry.sink = create_sink_cb_.Run(source_render_frame_id, session_id, | |
| 105 device_id, security_origin); | |
| 106 cache_entry.device_id = cache_entry.sink->GetOutputDeviceInfo().device_id(); | |
| 107 | |
| 108 DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get() | |
| 109 << " - used session to create new sink."; | |
| 110 | |
| 111 // Cache a newly-created sink. | |
| 112 base::AutoLock auto_lock(cache_lock_); | |
| 113 cache_.push_back(cache_entry); | |
| 114 | |
| 115 } else { | |
| 116 // Ignore session id. | |
| 117 base::AutoLock auto_lock(cache_lock_); | |
| 118 | |
| 119 auto cache_iter = std::find_if( | |
| 120 cache_.begin(), cache_.end(), | |
| 121 CacheEntryFinder(source_render_frame_id, device_id, security_origin, | |
| 122 false /* unused_only */)); | |
| 123 | |
| 124 if (cache_iter != cache_.end()) { | |
| 125 // A matching cached sink is found. | |
| 126 DVLOG(1) | |
| 127 << "GetSinkInfo: address: " << cache_iter->sink.get() | |
| 128 << " - reusing a cached sink. source_render_frame_id: " | |
| 129 << source_render_frame_id << " session_id: " << session_id | |
| 130 << " device_id: " << device_id | |
| 131 << " security_origin: " << security_origin << " Device info: [" | |
| 132 << cache_iter->sink->GetOutputDeviceInfo().AsHumanReadableString() | |
| 133 << "] "; | |
| 134 | |
| 135 return cache_iter->sink->GetOutputDeviceInfo(); | |
| 136 } // if (cache_iter != cache_.end()) | |
|
DaleCurtis
2016/05/24 20:41:42
It's not chrome style to annotate trailing } in co
o1ka
2016/05/25 12:20:52
Done.
| |
| 137 | |
| 138 // No matching sink found, create a new one. | |
| 139 cache_entry.device_id = device_id; | |
| 140 cache_entry.sink = create_sink_cb_.Run( | |
| 141 source_render_frame_id, 0 /* session_id */, device_id, security_origin); | |
| 142 | |
| 143 DVLOG(1) << "GetSinkInfo: address: " << cache_entry.sink.get() | |
| 144 << " - no matching cached sink found, created a new one."; | |
| 145 | |
| 146 // Cache a newly-created sink. | |
| 147 cache_.push_back(cache_entry); | |
| 148 } // else | |
| 149 | |
| 150 // Schedule it for deletion. | |
| 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 base::AutoLock auto_lock(cache_lock_); | |
| 168 | |
| 169 auto cache_iter = | |
| 170 std::find_if(cache_.begin(), cache_.end(), | |
| 171 CacheEntryFinder(source_render_frame_id, device_id, | |
| 172 security_origin, true /* unused_only */)); | |
| 173 | |
| 174 if (cache_iter != cache_.end()) { | |
| 175 // Found unused sink; mark it as used and return. | |
| 176 DVLOG(1) << "GetSink: address: " << cache_iter->sink.get() | |
| 177 << " - found unused cached sink, reusing it." | |
| 178 << " source_render_frame_id: " << source_render_frame_id | |
| 179 << " device_id: " << device_id | |
| 180 << " security_origin: " << security_origin; | |
| 181 | |
| 182 cache_iter->used = true; | |
| 183 return cache_iter->sink; | |
| 184 } // if | |
| 185 | |
| 186 // No unused sink is found, create one, mark it used, cache it and return. | |
| 187 CacheEntry cache_entry; | |
|
miu
2016/05/25 01:23:20
ditto here: You could just use the uniform initial
o1ka
2016/05/25 12:20:52
Done, same as above.
| |
| 188 cache_entry.source_render_frame_id = source_render_frame_id; | |
| 189 cache_entry.device_id = device_id; | |
| 190 cache_entry.security_origin = security_origin; | |
| 191 cache_entry.used = true; | |
| 192 | |
| 193 cache_entry.sink = create_sink_cb_.Run( | |
| 194 source_render_frame_id, 0 /* session_id */, device_id, security_origin); | |
| 195 | |
| 196 cache_.push_back(cache_entry); | |
| 197 | |
| 198 DVLOG(1) << "GetSink: address: " << cache_entry.sink.get() | |
|
DaleCurtis
2016/05/24 20:41:42
I'd drop some of these DVLOGs from the production
o1ka
2016/05/25 12:20:52
Yes, they hurt readability. Reduced a bit.
| |
| 199 << " - no unused cached sink found, created a new one." | |
| 200 << " source_render_frame_id: " << source_render_frame_id | |
| 201 << " device_id: " << device_id | |
| 202 << " security_origin: " << security_origin; | |
| 203 return cache_entry.sink; | |
| 204 } | |
| 205 | |
| 206 void AudioRendererSinkCacheImpl::ReleaseSink( | |
| 207 const media::AudioRendererSink* sink_ptr) { | |
| 208 // We don't know the sink state, so won't reuse it. Delete it immediately. | |
|
DaleCurtis
2016/05/24 20:41:42
Aren't we always taking this path when tearing dow
o1ka
2016/05/25 12:20:52
Do you mean reusing a sink after it was returned b
| |
| 209 DeleteSink(sink_ptr, true); | |
| 210 } | |
| 211 | |
| 212 void AudioRendererSinkCacheImpl::DeleteLaterIfUnused( | |
| 213 const media::AudioRendererSink* sink_ptr) { | |
| 214 DVLOG(1) << "DeleteLaterIfUnused: address: " << sink_ptr; | |
| 215 | |
| 216 task_runner_->PostDelayedTask( | |
| 217 FROM_HERE, base::Bind(&AudioRendererSinkCacheImpl::DeleteSink, | |
| 218 weak_ptr_factory_.GetWeakPtr(), sink_ptr, | |
| 219 false /*do not delete if used*/), | |
| 220 delete_timeout_); | |
| 221 } | |
| 222 | |
| 223 void AudioRendererSinkCacheImpl::DeleteSink( | |
| 224 const media::AudioRendererSink* sink_ptr, | |
| 225 bool force_delete_used) { | |
| 226 DCHECK(sink_ptr); | |
| 227 | |
| 228 scoped_refptr<media::AudioRendererSink> sink_to_stop; | |
| 229 | |
| 230 { | |
| 231 base::AutoLock auto_lock(cache_lock_); | |
| 232 | |
| 233 // Looking up the sink by its pointer. | |
| 234 auto cache_iter = std::find_if(cache_.begin(), cache_.end(), | |
| 235 [&sink_ptr](const CacheEntry& val) { | |
| 236 return val.sink.get() == sink_ptr; | |
| 237 }); | |
| 238 | |
| 239 if (cache_iter == cache_.end()) { | |
| 240 // If we got here and |force_delete_used| is not set it means the sink | |
| 241 // scheduled for deletion get aquired and released before scheduled | |
| 242 // deletion - this is ok. | |
| 243 DCHECK(!force_delete_used) | |
| 244 << "DeleteSink: address: " << sink_ptr | |
| 245 << " could not find a sink which is supposed to be in use"; | |
| 246 | |
| 247 DVLOG(1) << "DeleteSink: address: " << sink_ptr | |
| 248 << " force_delete_used = true - already deleted."; | |
| 249 return; | |
| 250 } | |
| 251 | |
| 252 DVLOG(1) << "DeleteSink: address: " << sink_ptr | |
| 253 << " force_delete_used: " << force_delete_used | |
| 254 << " in use: " << cache_iter->used << " source_render_frame_id: " | |
| 255 << cache_iter->source_render_frame_id | |
| 256 << " device_id: " << cache_iter->device_id | |
| 257 << " security_origin: " << cache_iter->security_origin; | |
| 258 | |
| 259 // When |force_delete_used| is set, it's expected that we are deleting a | |
| 260 // used sink. | |
| 261 DCHECK((!force_delete_used) || (force_delete_used && cache_iter->used)) | |
| 262 << "Attempt to delete a non-aquired sink."; | |
| 263 | |
| 264 if (!force_delete_used && cache_iter->used) { | |
| 265 DVLOG(1) << "DeleteSink: address: " << sink_ptr | |
| 266 << " sink in use, skipping deletion."; | |
| 267 return; | |
| 268 } | |
| 269 | |
| 270 // To stop the sink before deletion if it's not used, we need to hold | |
| 271 // a ref to it. | |
| 272 if (!cache_iter->used) | |
| 273 sink_to_stop = cache_iter->sink; | |
| 274 | |
| 275 cache_.erase(cache_iter); | |
| 276 DVLOG(1) << "DeleteSink: address: " << sink_ptr; | |
| 277 } // Lock scope; | |
| 278 | |
| 279 // Stop the sink out of the lock scope. | |
| 280 if (sink_to_stop.get()) { | |
| 281 DCHECK_EQ(sink_ptr, sink_to_stop.get()); | |
| 282 sink_to_stop->Stop(); | |
| 283 DVLOG(1) << "DeleteSink: address: " << sink_ptr << " stopped."; | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 int AudioRendererSinkCacheImpl::GetCacheSizeForTesting() { | |
| 288 return cache_.size(); | |
| 289 } | |
| 290 | |
| 291 } // namespace content | |
| OLD | NEW |