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

Unified 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: Make WebAudioSourceProvider to always return real sink info reguardless the client - to avoid behavior change. Created 4 years, 7 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « content/renderer/media/audio_renderer_sink_cache_impl.cc ('k') | content/renderer/render_frame_impl.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: content/renderer/media/audio_renderer_sink_cache_unittest.cc
diff --git a/content/renderer/media/audio_renderer_sink_cache_unittest.cc b/content/renderer/media/audio_renderer_sink_cache_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..78eddecd8225db77904f485fab4e58a8debecdff
--- /dev/null
+++ b/content/renderer/media/audio_renderer_sink_cache_unittest.cc
@@ -0,0 +1,372 @@
+// 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 <array>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/test/test_simple_task_runner.h"
+#include "content/renderer/media/audio_renderer_sink_cache_impl.h"
+#include "media/audio/audio_device_description.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/mock_audio_renderer_sink.h"
+#include "media/base/test_helpers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+const char* const kDefaultDeviceId =
+ media::AudioDeviceDescription::kDefaultDeviceId;
+const char kAnotherDeviceId[] = "another-device-id";
+const int kRenderFrameId = 124;
+const int kDeleteTimeoutMs = 1000;
+} // namespace
+
+class AudioRendererSinkCacheTest : public testing::Test {
+ public:
+ AudioRendererSinkCacheTest()
+ : cache_(new AudioRendererSinkCacheImpl(
+ message_loop_.task_runner(),
+ base::Bind(&AudioRendererSinkCacheTest::CreateSink,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs))) {}
+
+ void GetSink(int render_frame_id,
+ const std::string& device_id,
+ const url::Origin& security_origin,
+ media::AudioRendererSink** sink) {
+ *sink = cache_->GetSink(render_frame_id, device_id, security_origin).get();
+ }
+
+ void GetRandomSinkInfo(int frame) {
+ // Get info and check if memory is not corrupted.
+ EXPECT_EQ(kDefaultDeviceId,
+ cache_->GetSinkInfo(frame, 0, kDefaultDeviceId, url::Origin())
+ .device_id());
+ }
+
+ void GetRandomSink(int frame, base::TimeDelta sleep_timeout) {
+ scoped_refptr<media::AudioRendererSink> sink =
+ cache_->GetSink(frame, kDefaultDeviceId, url::Origin()).get();
+ ExpectToStop(sink.get());
+ base::PlatformThread::Sleep(sleep_timeout);
+ cache_->ReleaseSink(sink.get());
+ sink->Stop(); // Call a method to make the object is not corrupted.
+ }
+
+ protected:
+ int sink_count() {
+ DCHECK(message_loop_.task_runner()->BelongsToCurrentThread());
+ return cache_->GetCacheSizeForTesting();
+ }
+
+ scoped_refptr<media::AudioRendererSink> CreateSink(
+ int render_frame_id,
+ int session_id,
+ const std::string& device_id,
+ const url::Origin& security_origin) {
+ return new media::MockAudioRendererSink(device_id,
+ media::OUTPUT_DEVICE_STATUS_OK);
+ }
+
+ void ExpectToStop(media::AudioRendererSink* sink) {
+ // Sink must be stoped before deletion.
+ EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop())
+ .Times(1);
+ }
+
+ void ExpectNotToStop(media::AudioRendererSink* sink) {
+ // The sink must be stoped before deletion.
+ EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop())
+ .Times(0);
+ }
+
+ // Posts the task to the specified thread and runs current message loop until
+ // the task is completed.
+ void PostAndRunUntilDone(const base::Thread& thread,
+ const base::Closure& task) {
+ media::WaitableMessageLoopEvent event;
+ thread.task_runner()->PostTaskAndReply(FROM_HERE, task, event.GetClosure());
+ // Runs the loop and waits for the thread to call event's closure.
+ event.RunAndWait();
+ }
+
+ void WaitOnAnotherThread(const base::Thread& thread, int timeout_ms) {
+ PostAndRunUntilDone(
+ thread, base::Bind(base::IgnoreResult(&base::PlatformThread::Sleep),
+ base::TimeDelta::FromMilliseconds(timeout_ms)));
+ }
+
+ base::MessageLoop message_loop_;
+ std::unique_ptr<AudioRendererSinkCacheImpl> cache_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AudioRendererSinkCacheTest);
+};
+
+// Verify that normal get/release sink sequence works.
+TEST_F(AudioRendererSinkCacheTest, GetReleaseSink) {
+ // Verify that a new sink is successfully created.
+ EXPECT_EQ(0, sink_count());
+ scoped_refptr<media::AudioRendererSink> sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ ExpectNotToStop(sink.get()); // Cache should not stop sinks marked as used.
+ EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
+ EXPECT_EQ(1, sink_count());
+
+ // Verify that another sink with the same key is successfully created
+ scoped_refptr<media::AudioRendererSink> another_sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ ExpectNotToStop(another_sink.get());
+ EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
+ EXPECT_EQ(2, sink_count());
+ EXPECT_NE(sink, another_sink);
+
+ // Verify that another sink with a different kay is successfully created.
+ scoped_refptr<media::AudioRendererSink> yet_another_sink =
+ cache_->GetSink(kRenderFrameId, kAnotherDeviceId, url::Origin()).get();
+ ExpectNotToStop(yet_another_sink.get());
+ EXPECT_EQ(kAnotherDeviceId,
+ yet_another_sink->GetOutputDeviceInfo().device_id());
+ EXPECT_EQ(3, sink_count());
+ EXPECT_NE(sink, yet_another_sink);
+ EXPECT_NE(another_sink, yet_another_sink);
+
+ // Verify that the first sink is successfully deleted.
+ cache_->ReleaseSink(sink.get());
+ EXPECT_EQ(2, sink_count());
+ sink = nullptr;
+
+ // Make sure we deleted the right sink, and the memory for the rest is not
+ // corrupted.
+ EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
+ EXPECT_EQ(kAnotherDeviceId,
+ yet_another_sink->GetOutputDeviceInfo().device_id());
+
+ // Verify that the second sink is successfully deleted.
+ cache_->ReleaseSink(another_sink.get());
+ EXPECT_EQ(1, sink_count());
+ EXPECT_EQ(kAnotherDeviceId,
+ yet_another_sink->GetOutputDeviceInfo().device_id());
+
+ cache_->ReleaseSink(yet_another_sink.get());
+ EXPECT_EQ(0, sink_count());
+}
+
+// Verify that the sink created with GetSinkInfo() is reused when possible.
+TEST_F(AudioRendererSinkCacheTest, GetDeviceInfo) {
+ EXPECT_EQ(0, sink_count());
+ media::OutputDeviceInfo device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
+ EXPECT_EQ(1, sink_count());
+
+ // The info on the same device is requested, so no new sink is created.
+ media::OutputDeviceInfo one_more_device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
+ EXPECT_EQ(1, sink_count());
+ EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
+
+ // Aquire the sink that was created on GetSinkInfo().
+ scoped_refptr<media::AudioRendererSink> sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ EXPECT_EQ(1, sink_count());
+ EXPECT_EQ(device_info.device_id(), sink->GetOutputDeviceInfo().device_id());
+
+ // Now the sink is in used, but we can still get the device info out of it, no
+ // new sink is created.
+ one_more_device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
+ EXPECT_EQ(1, sink_count());
+ EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
+
+ // Request sink for the same device. The first sink is in use, so a new one
+ // should be created.
+ scoped_refptr<media::AudioRendererSink> another_sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ EXPECT_EQ(2, sink_count());
+ EXPECT_EQ(device_info.device_id(),
+ another_sink->GetOutputDeviceInfo().device_id());
+}
+
+// Verify that the sink created with GetSinkInfo() is deleted if unused.
+// The test produces 2 "Uninteresting mock" warnings for
+// MockAudioRendererSink::Stop().
+TEST_F(AudioRendererSinkCacheTest, GarbageCollection) {
+ EXPECT_EQ(0, sink_count());
+ media::OutputDeviceInfo device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
+ EXPECT_EQ(1, sink_count());
+
+ media::OutputDeviceInfo another_device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin());
+ EXPECT_EQ(2, sink_count());
+
+ base::Thread thread("timeout_thread");
+ thread.Start();
+
+ // 100 ms more than garbage collection timeout.
+ WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100);
+
+ // All the sinks should be garbage-collected by now.
+ EXPECT_EQ(0, sink_count());
+}
+
+// Verify that the sink created with GetSinkInfo() is not deleted if used within
+// the timeout.
+TEST_F(AudioRendererSinkCacheTest, NoGarbageCollectionForUsedSink) {
+ EXPECT_EQ(0, sink_count());
+ media::OutputDeviceInfo device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
+ EXPECT_EQ(1, sink_count());
+
+ base::Thread thread("timeout_thread");
+ thread.Start();
+
+ // Wait significantly less than grabage collection timeout.
+ int wait_a_bit = 100;
+ DCHECK_GT(kDeleteTimeoutMs, wait_a_bit * 2);
+ WaitOnAnotherThread(thread, wait_a_bit);
+
+ // Sink is not deleted yet.
+ EXPECT_EQ(1, sink_count());
+
+ // Request it:
+ scoped_refptr<media::AudioRendererSink> sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
+ EXPECT_EQ(1, sink_count());
+
+ // Wait more to hit garbage collection timeout.
+ WaitOnAnotherThread(thread, kDeleteTimeoutMs);
+
+ // The sink is still in place.
+ EXPECT_EQ(1, sink_count());
+}
+
+// Verify that cache works fine if a sink scheduled for delettion is aquired and
+// released before deletion timeout elapses.
+// The test produces one "Uninteresting mock" warning for
+// MockAudioRendererSink::Stop().
+TEST_F(AudioRendererSinkCacheTest, ReleaseSinkBeforeScheduledDeletion) {
+ EXPECT_EQ(0, sink_count());
+
+ media::OutputDeviceInfo device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin());
+ EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now.
+
+ // Request it:
+ scoped_refptr<media::AudioRendererSink> sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ ExpectNotToStop(sink.get());
+ EXPECT_EQ(1, sink_count());
+
+ // Release it:
+ cache_->ReleaseSink(sink.get());
+ EXPECT_EQ(0, sink_count());
+
+ media::OutputDeviceInfo another_device_info =
+ cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin());
+ EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now.
+
+ base::Thread thread("timeout_thread");
+ thread.Start();
+
+ // 100 ms more than garbage collection timeout.
+ WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100);
+
+ // Nothing crashed and the second sink deleted on schedule.
+ EXPECT_EQ(0, sink_count());
+}
+
+// Check that a sink created on one thread in response to GetSinkInfo can be
+// used on another thread.
+TEST_F(AudioRendererSinkCacheTest, MultithreadedAccess) {
+ EXPECT_EQ(0, sink_count());
+
+ base::Thread thread1("thread1");
+ thread1.Start();
+
+ base::Thread thread2("thread2");
+ thread2.Start();
+
+ // Request device information on the first thread.
+ PostAndRunUntilDone(
+ thread1,
+ base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo),
+ base::Unretained(cache_.get()), kRenderFrameId, 0,
+ kDefaultDeviceId, url::Origin()));
+
+ EXPECT_EQ(1, sink_count());
+
+ // Request the sink on the second thread.
+ media::AudioRendererSink* sink;
+
+ PostAndRunUntilDone(
+ thread2,
+ base::Bind(&AudioRendererSinkCacheTest::GetSink, base::Unretained(this),
+ kRenderFrameId, kDefaultDeviceId, url::Origin(), &sink));
+
+ EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
+ EXPECT_EQ(1, sink_count());
+
+ // Request device information on the first thread again.
+ PostAndRunUntilDone(
+ thread1,
+ base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo),
+ base::Unretained(cache_.get()), kRenderFrameId, 0,
+ kDefaultDeviceId, url::Origin()));
+ EXPECT_EQ(1, sink_count());
+
+ // Release the sink on the second thread.
+ PostAndRunUntilDone(thread2,
+ base::Bind(&AudioRendererSinkCache::ReleaseSink,
+ base::Unretained(cache_.get()), sink));
+
+ EXPECT_EQ(0, sink_count());
+}
+
+// Intensive parallell access to the cache. Produces a ton of "Uninteresting
+// mock" warnings for Stop() calls - this is fine.
+TEST_F(AudioRendererSinkCacheTest, SmokeTest) {
+ const int kExperimentSize = 1000;
+ const int kSinkCount = 10;
+ const int kThreadCount = 3;
+
+ // Sleep no more than (kDeleteTimeoutMs * 3) in total per thread.
+ const base::TimeDelta kSleepTimeout =
+ base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs * 3 / kExperimentSize);
+
+ srand(42); // Does not matter.
+
+ std::array<std::unique_ptr<base::Thread>, kThreadCount> threads;
+ for (int i = 0; i < kThreadCount; ++i) {
+ threads[i].reset(new base::Thread(std::to_string(i)));
+ threads[i]->Start();
+ }
+
+ for (int i = 0; i < kExperimentSize; ++i) {
+ for (auto& thread : threads) {
+ thread->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&AudioRendererSinkCacheTest::GetRandomSinkInfo,
+ base::Unretained(this), rand() % kSinkCount));
+ thread->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&AudioRendererSinkCacheTest::GetRandomSink,
+ base::Unretained(this), rand() % kSinkCount,
+ kSleepTimeout));
+ }
+ }
+
+ // Wait for completion of all the tasks posted to at least one thread.
+ media::WaitableMessageLoopEvent loop_event;
+ threads[kThreadCount - 1]->task_runner()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&base::DoNothing), loop_event.GetClosure());
+ // Runs the loop and waits for the thread to call event's closure.
+ loop_event.RunAndWait();
+}
+
+} // namespace content
« no previous file with comments | « content/renderer/media/audio_renderer_sink_cache_impl.cc ('k') | content/renderer/render_frame_impl.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698