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

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: First round of comments addressed 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
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..20e883e87a086198de3dff68c15fa89c9f3b0682
--- /dev/null
+++ b/content/renderer/media/audio_renderer_sink_cache_unittest.cc
@@ -0,0 +1,313 @@
+// 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/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 {
+static const char* const kDefaultDeviceId =
+ media::AudioDeviceDescription::kDefaultDeviceId;
+static const char kAnotherDeviceId[] = "another-device-id";
+static const int kRenderFrameId = 124;
+static 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)),
+ 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();
+ }
+
+ protected:
+ int sink_count() {
+ DCHECK(message_loop_.task_runner()->BelongsToCurrentThread());
+ return cache_->sinks_.size();
+ }
+
+ 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) {
+ // The sink must be stoped before deletion.
+ EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop())
+ .Times(1);
+ }
+
+ // 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());
+ media::AudioRendererSink* sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ ExpectToStop(sink);
+ EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
+ EXPECT_EQ(1, sink_count());
+
+ // Verify that another sink with the same key is successfully created
+ media::AudioRendererSink* another_sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ ExpectToStop(another_sink);
+ 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.
+ media::AudioRendererSink* yet_another_sink =
+ cache_->GetSink(kRenderFrameId, kAnotherDeviceId, url::Origin()).get();
+ ExpectToStop(yet_another_sink);
+ 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(kRenderFrameId, kDefaultDeviceId, url::Origin(), sink);
+ 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(kRenderFrameId, kDefaultDeviceId, url::Origin(),
+ another_sink);
+ EXPECT_EQ(1, sink_count());
+ EXPECT_EQ(kAnotherDeviceId,
+ yet_another_sink->GetOutputDeviceInfo().device_id());
+
+ cache_->ReleaseSink(kRenderFrameId, kAnotherDeviceId, url::Origin(),
+ yet_another_sink);
+ 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().
+ 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.
+ 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:
+ 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:
+ media::AudioRendererSink* sink =
+ cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get();
+ ExpectToStop(sink);
+ EXPECT_EQ(1, sink_count());
+
+ // Release it:
+ cache_->ReleaseSink(kRenderFrameId, kDefaultDeviceId, url::Origin(), sink);
+ 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 responce 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 = nullptr;
+
+ PostAndRunUntilDone(
+ thread2,
+ base::Bind(&AudioRendererSinkCacheTest::GetSink, base::Unretained(this),
+ kRenderFrameId, kDefaultDeviceId, url::Origin(), &sink));
+
+ EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
+ ExpectToStop(sink);
+ 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()), kRenderFrameId,
+ kDefaultDeviceId, url::Origin(), sink));
+
+ EXPECT_EQ(0, sink_count());
+}
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698