Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(204)

Side by Side Diff: content/renderer/media/audio_renderer_sink_cache_unittest.cc

Issue 1942803002: Caching AudioOutputDevice instances in mixer manager (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase cache smoke test added minor unit test cleanup Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 <array>
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/test/test_simple_task_runner.h"
11 #include "content/renderer/media/audio_renderer_sink_cache_impl.h"
12 #include "media/audio/audio_device_description.h"
13 #include "media/base/audio_parameters.h"
14 #include "media/base/mock_audio_renderer_sink.h"
15 #include "media/base/test_helpers.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "url/gurl.h"
19
20 namespace content {
21
22 namespace {
DaleCurtis 2016/05/26 19:18:09 If you convert to an anonymous namespace, drop the
o1ka 2016/05/27 13:48:52 Done.
23 static const char* const kDefaultDeviceId =
24 media::AudioDeviceDescription::kDefaultDeviceId;
25 static const char kAnotherDeviceId[] = "another-device-id";
26 static const int kRenderFrameId = 124;
27 static const int kDeleteTimeoutMs = 1000;
28 } // namespace
29
30 class AudioRendererSinkCacheTest : public testing::Test {
31 public:
32 AudioRendererSinkCacheTest()
33 : cache_(new AudioRendererSinkCacheImpl(
34 message_loop_.task_runner(),
35 base::Bind(&AudioRendererSinkCacheTest::CreateSink,
36 base::Unretained(this)),
37 base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs))) {}
38
39 void GetSink(int render_frame_id,
40 const std::string& device_id,
41 const url::Origin& security_origin,
42 media::AudioRendererSink** sink) {
43 *sink = cache_->GetSink(render_frame_id, device_id, security_origin).get();
44 }
45
46 void GetRandomSinkInfo(int frame) {
47 // Get info and check if memory is not corrupted.
48 EXPECT_EQ(kDefaultDeviceId,
49 cache_->GetSinkInfo(frame, 0, kDefaultDeviceId, url::Origin())
50 .device_id());
51 }
52
53 void GetRandomSink(int frame, base::TimeDelta sleep_timeout) {
54 scoped_refptr<media::AudioRendererSink> sink =
55 cache_->GetSink(frame, kDefaultDeviceId, url::Origin()).get();
56 ExpectToStop(sink.get());
57 base::PlatformThread::Sleep(sleep_timeout);
58 cache_->ReleaseSink(sink.get());
59 sink->Stop(); // Call a method to make the object is not corrupted.
60 }
61
62 protected:
63 int sink_count() {
64 DCHECK(message_loop_.task_runner()->BelongsToCurrentThread());
65 return cache_->GetCacheSizeForTesting();
66 }
67
68 scoped_refptr<media::AudioRendererSink> CreateSink(
69 int render_frame_id,
70 int session_id,
71 const std::string& device_id,
72 const url::Origin& security_origin) {
73 return new media::MockAudioRendererSink(device_id,
74 media::OUTPUT_DEVICE_STATUS_OK);
75 }
76
77 void ExpectToStop(media::AudioRendererSink* sink) {
78 // Sink must be stoped before deletion.
79 EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop())
80 .Times(1);
81 }
82
83 void ExpectNotToStop(media::AudioRendererSink* sink) {
84 // The sink must be stoped before deletion.
85 EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop())
86 .Times(0);
87 }
88
89 // Posts the task to the specified thread and runs current message loop until
90 // the task is completed.
91 void PostAndRunUntilDone(const base::Thread& thread,
92 const base::Closure& task) {
93 media::WaitableMessageLoopEvent event;
94 thread.task_runner()->PostTaskAndReply(FROM_HERE, task, event.GetClosure());
95 // Runs the loop and waits for the thread to call event's closure.
96 event.RunAndWait();
97 }
98
99 void WaitOnAnotherThread(const base::Thread& thread, int timeout_ms) {
100 PostAndRunUntilDone(
101 thread, base::Bind(base::IgnoreResult(&base::PlatformThread::Sleep),
102 base::TimeDelta::FromMilliseconds(timeout_ms)));
103 }
104
105 base::MessageLoop message_loop_;
106 std::unique_ptr<AudioRendererSinkCacheImpl> cache_;
107
108 private:
109 DISALLOW_COPY_AND_ASSIGN(AudioRendererSinkCacheTest);
110 };
111
112 // Verify that normal get/release sink sequence works.
113 TEST_F(AudioRendererSinkCacheTest, GetReleaseSink) {
114 // Verify that a new sink is successfully created.
115 EXPECT_EQ(0, sink_count());
116 scoped_refptr<media::AudioRendererSink> sink =
117 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
118 ExpectNotToStop(sink.get()); // Cache should not stop sinks marked as used.
119 EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
120 EXPECT_EQ(1, sink_count());
121
122 // Verify that another sink with the same key is successfully created
123 scoped_refptr<media::AudioRendererSink> another_sink =
124 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
125 ExpectNotToStop(another_sink.get());
126 EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
127 EXPECT_EQ(2, sink_count());
128 EXPECT_NE(sink, another_sink);
129
130 // Verify that another sink with a different kay is successfully created.
131 scoped_refptr<media::AudioRendererSink> yet_another_sink =
132 cache_->GetSink(kRenderFrameId, kAnotherDeviceId, url::Origin()).get();
133 ExpectNotToStop(yet_another_sink.get());
134 EXPECT_EQ(kAnotherDeviceId,
135 yet_another_sink->GetOutputDeviceInfo().device_id());
136 EXPECT_EQ(3, sink_count());
137 EXPECT_NE(sink, yet_another_sink);
138 EXPECT_NE(another_sink, yet_another_sink);
139
140 // Verify that the first sink is successfully deleted.
141 cache_->ReleaseSink(sink.get());
142 EXPECT_EQ(2, sink_count());
143 sink = nullptr;
144
145 // Make sure we deleted the right sink, and the memory for the rest is not
146 // corrupted.
147 EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
148 EXPECT_EQ(kAnotherDeviceId,
149 yet_another_sink->GetOutputDeviceInfo().device_id());
150
151 // Verify that the second sink is successfully deleted.
152 cache_->ReleaseSink(another_sink.get());
153 EXPECT_EQ(1, sink_count());
154 EXPECT_EQ(kAnotherDeviceId,
155 yet_another_sink->GetOutputDeviceInfo().device_id());
156
157 cache_->ReleaseSink(yet_another_sink.get());
158 EXPECT_EQ(0, sink_count());
159 }
160
161 // Verify that the sink created with GetSinkInfo() is reused when possible.
162 TEST_F(AudioRendererSinkCacheTest, GetDeviceInfo) {
163 EXPECT_EQ(0, sink_count());
164 media::OutputDeviceInfo device_info =
165 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
166 EXPECT_EQ(1, sink_count());
167
168 // The info on the same device is requested, so no new sink is created.
169 media::OutputDeviceInfo one_more_device_info =
170 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
171 EXPECT_EQ(1, sink_count());
172 EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
173
174 // Aquire the sink that was created on GetSinkInfo().
175 scoped_refptr<media::AudioRendererSink> sink =
176 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
177 EXPECT_EQ(1, sink_count());
178 EXPECT_EQ(device_info.device_id(), sink->GetOutputDeviceInfo().device_id());
179
180 // Now the sink is in used, but we can still get the device info out of it, no
181 // new sink is created.
182 one_more_device_info =
183 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
184 EXPECT_EQ(1, sink_count());
185 EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
186
187 // Request sink for the same device. The first sink is in use, so a new one
188 // should be created.
189 scoped_refptr<media::AudioRendererSink> another_sink =
190 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
191 EXPECT_EQ(2, sink_count());
192 EXPECT_EQ(device_info.device_id(),
193 another_sink->GetOutputDeviceInfo().device_id());
194 }
195
196 // Verify that the sink created with GetSinkInfo() is deleted if unused.
197 // The test produces 2 "Uninteresting mock" warnings for
198 // MockAudioRendererSink::Stop().
199 TEST_F(AudioRendererSinkCacheTest, GarbageCollection) {
200 EXPECT_EQ(0, sink_count());
201 media::OutputDeviceInfo device_info =
202 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
203 EXPECT_EQ(1, sink_count());
204
205 media::OutputDeviceInfo another_device_info =
206 cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin());
207 EXPECT_EQ(2, sink_count());
208
209 base::Thread thread("timeout_thread");
210 thread.Start();
211
212 // 100 ms more than garbage collection timeout.
213 WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100);
214
215 // All the sinks should be garbage-collected by now.
216 EXPECT_EQ(0, sink_count());
217 }
218
219 // Verify that the sink created with GetSinkInfo() is not deleted if used within
220 // the timeout.
221 TEST_F(AudioRendererSinkCacheTest, NoGarbageCollectionForUsedSink) {
222 EXPECT_EQ(0, sink_count());
223 media::OutputDeviceInfo device_info =
224 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
225 EXPECT_EQ(1, sink_count());
226
227 base::Thread thread("timeout_thread");
228 thread.Start();
229
230 // Wait significantly less than grabage collection timeout.
231 int wait_a_bit = 100;
232 DCHECK_GT(kDeleteTimeoutMs, wait_a_bit * 2);
233 WaitOnAnotherThread(thread, wait_a_bit);
234
235 // Sink is not deleted yet.
236 EXPECT_EQ(1, sink_count());
237
238 // Request it:
239 scoped_refptr<media::AudioRendererSink> sink =
240 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
241 EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
242 EXPECT_EQ(1, sink_count());
243
244 // Wait more to hit garbage collection timeout.
245 WaitOnAnotherThread(thread, kDeleteTimeoutMs);
246
247 // The sink is still in place.
248 EXPECT_EQ(1, sink_count());
249 }
250
251 // Verify that cache works fine if a sink scheduled for delettion is aquired and
252 // released before deletion timeout elapses.
253 // The test produces one "Uninteresting mock" warning for
254 // MockAudioRendererSink::Stop().
255 TEST_F(AudioRendererSinkCacheTest, ReleaseSinkBeforeScheduledDeletion) {
256 EXPECT_EQ(0, sink_count());
257
258 media::OutputDeviceInfo device_info =
259 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
260 EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now.
261
262 // Request it:
263 scoped_refptr<media::AudioRendererSink> sink =
264 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
265 ExpectNotToStop(sink.get());
266 EXPECT_EQ(1, sink_count());
267
268 // Release it:
269 cache_->ReleaseSink(sink.get());
270 EXPECT_EQ(0, sink_count());
271
272 media::OutputDeviceInfo another_device_info =
273 cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin());
274 EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now.
275
276 base::Thread thread("timeout_thread");
277 thread.Start();
278
279 // 100 ms more than garbage collection timeout.
280 WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100);
281
282 // Nothing crashed and the second sink deleted on schedule.
283 EXPECT_EQ(0, sink_count());
284 }
285
286 // Check that a sink created on one thread in response to GetSinkInfo can be
287 // used on another thread.
288 TEST_F(AudioRendererSinkCacheTest, MultithreadedAccess) {
289 EXPECT_EQ(0, sink_count());
290
291 base::Thread thread1("thread1");
292 thread1.Start();
293
294 base::Thread thread2("thread2");
295 thread2.Start();
296
297 // Request device information on the first thread.
298 PostAndRunUntilDone(
299 thread1,
300 base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo),
301 base::Unretained(cache_.get()), kRenderFrameId, 0,
302 kDefaultDeviceId, url::Origin()));
303
304 EXPECT_EQ(1, sink_count());
305
306 // Request the sink on the second thread.
307 media::AudioRendererSink* sink;
308
309 PostAndRunUntilDone(
310 thread2,
311 base::Bind(&AudioRendererSinkCacheTest::GetSink, base::Unretained(this),
312 kRenderFrameId, kDefaultDeviceId, url::Origin(), &sink));
313
314 EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
315 EXPECT_EQ(1, sink_count());
316
317 // Request device information on the first thread again.
318 PostAndRunUntilDone(
319 thread1,
320 base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo),
321 base::Unretained(cache_.get()), kRenderFrameId, 0,
322 kDefaultDeviceId, url::Origin()));
323 EXPECT_EQ(1, sink_count());
324
325 // Release the sink on the second thread.
326 PostAndRunUntilDone(thread2,
327 base::Bind(&AudioRendererSinkCache::ReleaseSink,
328 base::Unretained(cache_.get()), sink));
329
330 EXPECT_EQ(0, sink_count());
331 }
332
333 // Intensive parallell access to the cache. Produces a ton of "Uninteresting
334 // mock" warnings for Stop() calls - this is fine.
335 TEST_F(AudioRendererSinkCacheTest, SmokeTest) {
336 const int kExperimentSize = 1000;
337 const int kSinkCount = 10;
338 const int kThreadCount = 3;
339
340 // Sleep no more than (kDeleteTimeoutMs * 3) in total per thread.
341 const base::TimeDelta kSleepTimeout =
342 base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs * 3 / kExperimentSize);
343
344 srand(42); // Does not matter.
345
346 std::array<std::unique_ptr<base::Thread>, kThreadCount> threads;
347 for (int i = 0; i < kThreadCount; ++i) {
348 threads[i].reset(new base::Thread(std::to_string(i)));
349 threads[i]->Start();
350 }
351
352 for (int i = 0; i < kExperimentSize; ++i) {
353 for (auto& thread : threads) {
354 thread->task_runner()->PostTask(
355 FROM_HERE, base::Bind(&AudioRendererSinkCacheTest::GetRandomSinkInfo,
356 base::Unretained(this), rand() % kSinkCount));
357 thread->task_runner()->PostTask(
358 FROM_HERE, base::Bind(&AudioRendererSinkCacheTest::GetRandomSink,
359 base::Unretained(this), rand() % kSinkCount,
360 kSleepTimeout));
361 }
362 }
363
364 // Wait for completion of all the tasks posted to at least one thread.
365 media::WaitableMessageLoopEvent loop_event;
366 threads[kThreadCount - 1]->task_runner()->PostTaskAndReply(
367 FROM_HERE, base::Bind(&base::DoNothing), loop_event.GetClosure());
368 // Runs the loop and waits for the thread to call event's closure.
369 loop_event.RunAndWait();
370 }
371
372 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698