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

Unified Diff: content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc

Issue 2792293002: Reland of Add mojo interface+impl creation of audio streams. (Closed)
Patch Set: Created 3 years, 8 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/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
diff --git a/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2e3f864930138de9d28b287cdf73af6fd3b902f6
--- /dev/null
+++ b/content/browser/renderer_host/media/render_frame_audio_output_stream_factory_unittest.cc
@@ -0,0 +1,371 @@
+// Copyright 2017 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 "content/browser/renderer_host/media/render_frame_audio_output_stream_factory.h"
+
+#include <limits>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_handle.h"
+#include "base/run_loop.h"
+#include "base/sync_socket.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/browser/renderer_host/media/renderer_audio_output_stream_factory_context.h"
+#include "content/common/media/renderer_audio_output_stream_factory.mojom.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "media/audio/audio_output_controller.h"
+#include "media/base/audio_parameters.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+using testing::Test;
+using AudioOutputStreamFactory = mojom::RendererAudioOutputStreamFactory;
+using AudioOutputStreamFactoryPtr =
+ mojo::InterfacePtr<AudioOutputStreamFactory>;
+using AudioOutputStreamFactoryRequest =
+ mojo::InterfaceRequest<AudioOutputStreamFactory>;
+using AudioOutputStream = media::mojom::AudioOutputStream;
+using AudioOutputStreamPtr = mojo::InterfacePtr<AudioOutputStream>;
+using AudioOutputStreamRequest = mojo::InterfaceRequest<AudioOutputStream>;
+using AudioOutputStreamProvider = media::mojom::AudioOutputStreamProvider;
+using AudioOutputStreamProviderPtr =
+ mojo::InterfacePtr<AudioOutputStreamProvider>;
+using AudioOutputStreamProviderRequest =
+ mojo::InterfaceRequest<AudioOutputStreamProvider>;
+
+const int kStreamId = 0;
+const int kNoSessionId = 0;
+const int kRenderProcessId = 42;
+const int kRenderFrameId = 24;
+const int kSampleFrequency = 44100;
+const int kBitsPerSample = 16;
+const int kSamplesPerBuffer = kSampleFrequency / 100;
+const char kSalt[] = "salt";
+
+media::AudioParameters GetTestAudioParameters() {
+ return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_MONO, kSampleFrequency,
+ kBitsPerSample, kSamplesPerBuffer);
+}
+
+class MockAudioOutputDelegate : public media::AudioOutputDelegate {
+ public:
+ // |on_destruction| can be used to observe the destruction of the delegate.
+ explicit MockAudioOutputDelegate(
+ base::OnceClosure on_destruction = base::OnceClosure())
+ : on_destruction_(std::move(on_destruction)) {}
+
+ ~MockAudioOutputDelegate() {
+ if (on_destruction_)
+ std::move(on_destruction_).Run();
+ }
+
+ MOCK_CONST_METHOD0(GetController,
+ scoped_refptr<media::AudioOutputController>());
+ MOCK_CONST_METHOD0(GetStreamId, int());
+ MOCK_METHOD0(OnPlayStream, void());
+ MOCK_METHOD0(OnPauseStream, void());
+ MOCK_METHOD1(OnSetVolume, void(double));
+
+ private:
+ base::OnceClosure on_destruction_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockAudioOutputDelegate);
+};
+
+class MockContext : public RendererAudioOutputStreamFactoryContext {
+ public:
+ explicit MockContext(bool auth_ok) : salt_(kSalt), auth_ok_(auth_ok) {}
+
+ ~MockContext() override { EXPECT_EQ(nullptr, delegate_); }
+
+ int GetRenderProcessId() const override { return kRenderProcessId; }
+
+ std::string GetHMACForDeviceId(
+ const url::Origin& origin,
+ const std::string& raw_device_id) const override {
+ return MediaStreamManager::GetHMACForMediaDeviceID(salt_, origin,
+ raw_device_id);
+ }
+
+ void RequestDeviceAuthorization(
+ int render_frame_id,
+ int session_id,
+ const std::string& device_id,
+ const url::Origin& security_origin,
+ AuthorizationCompletedCallback cb) const override {
+ EXPECT_EQ(render_frame_id, kRenderFrameId);
+ EXPECT_EQ(session_id, 0);
+ if (auth_ok_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(cb, media::OutputDeviceStatus::OUTPUT_DEVICE_STATUS_OK,
+ false, GetTestAudioParameters(), "default"));
+ return;
+ }
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(cb,
+ media::OutputDeviceStatus::
+ OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED,
+ false, media::AudioParameters::UnavailableDeviceParams(),
+ ""));
+ }
+
+ // The event handler for the delegate will be stored at
+ // |*event_handler_location| when the delegate is created.
+ void PrepareDelegateForCreation(
+ std::unique_ptr<media::AudioOutputDelegate> delegate,
+ media::AudioOutputDelegate::EventHandler** event_handler_location) {
+ EXPECT_EQ(nullptr, delegate_);
+ EXPECT_EQ(nullptr, delegate_event_handler_location_);
+ delegate_ = std::move(delegate);
+ delegate_event_handler_location_ = event_handler_location;
+ }
+
+ std::unique_ptr<media::AudioOutputDelegate> CreateDelegate(
+ const std::string& unique_device_id,
+ int render_frame_id,
+ const media::AudioParameters& params,
+ media::AudioOutputDelegate::EventHandler* handler) override {
+ EXPECT_NE(nullptr, delegate_);
+ EXPECT_NE(nullptr, delegate_event_handler_location_);
+ *delegate_event_handler_location_ = handler;
+ delegate_event_handler_location_ = nullptr;
+ return std::move(delegate_);
+ }
+
+ AudioOutputStreamFactoryPtr CreateFactory() {
+ DCHECK(!factory_);
+ AudioOutputStreamFactoryPtr ret;
+ factory_ = base::MakeUnique<RenderFrameAudioOutputStreamFactory>(
+ kRenderFrameId, this);
+ factory_binding_ = base::MakeUnique<
+ mojo::Binding<mojom::RendererAudioOutputStreamFactory>>(factory_.get(),
+ &ret);
+ return ret;
+ }
+
+ private:
+ const std::string salt_;
+ const bool auth_ok_;
+ std::unique_ptr<RenderFrameAudioOutputStreamFactory> factory_;
+ std::unique_ptr<mojo::Binding<mojom::RendererAudioOutputStreamFactory>>
+ factory_binding_;
+ std::unique_ptr<media::AudioOutputDelegate> delegate_;
+ media::AudioOutputDelegate::EventHandler** delegate_event_handler_location_ =
+ nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(MockContext);
+};
+
+class MockClient {
+ public:
+ MockClient() {}
+ ~MockClient() {}
+
+ void StreamCreated(mojo::ScopedSharedBufferHandle handle1,
+ mojo::ScopedHandle handle2) {
+ was_called_ = true;
+ }
+
+ bool was_called() { return was_called_; }
+
+ private:
+ bool was_called_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(MockClient);
+};
+
+void AuthCallback(media::OutputDeviceStatus* status_out,
+ media::AudioParameters* params_out,
+ std::string* id_out,
+ media::OutputDeviceStatus status,
+ const media::AudioParameters& params,
+ const std::string& id) {
+ *status_out = status;
+ *params_out = params;
+ *id_out = id;
+}
+
+} // namespace
+
+// This test authorizes and creates a stream, and checks that
+// 1. the authorization callback is called with appropriate parameters.
+// 2. the AudioOutputDelegate is created.
+// 3. when the delegate calls OnStreamCreated, this is propagated to the client.
+TEST(RenderFrameAudioOutputStreamFactoryTest, CreateStream) {
+ content::TestBrowserThreadBundle thread_bundle;
+ AudioOutputStreamProviderPtr provider;
+ AudioOutputStreamPtr output_stream;
+ MockClient client;
+ media::AudioOutputDelegate::EventHandler* event_handler = nullptr;
+ auto factory_context = base::MakeUnique<MockContext>(true);
+ factory_context->PrepareDelegateForCreation(
+ base::MakeUnique<MockAudioOutputDelegate>(), &event_handler);
+ AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory();
+
+ media::OutputDeviceStatus status;
+ media::AudioParameters params;
+ std::string id;
+ factory_ptr->RequestDeviceAuthorization(
+ mojo::MakeRequest(&provider), kNoSessionId, "default",
+ base::Bind(&AuthCallback, base::Unretained(&status),
+ base::Unretained(&params), base::Unretained(&id)));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK);
+ EXPECT_EQ(params.AsHumanReadableString(),
+ GetTestAudioParameters().AsHumanReadableString());
+ EXPECT_TRUE(id.empty());
+
+ provider->Acquire(
+ mojo::MakeRequest<AudioOutputStream>(&output_stream), params,
+ base::Bind(&MockClient::StreamCreated, base::Unretained(&client)));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_NE(event_handler, nullptr);
+
+ base::SharedMemory shared_memory;
+ ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(100));
+
+ base::CancelableSyncSocket local, remote;
+ ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&local, &remote));
+ event_handler->OnStreamCreated(kStreamId, &shared_memory, &remote);
+
+ base::RunLoop().RunUntilIdle();
+ // Make sure we got the callback from creating stream.
+ EXPECT_TRUE(client.was_called());
+}
+
+TEST(RenderFrameAudioOutputStreamFactoryTest, NotAuthorized_Denied) {
+ content::TestBrowserThreadBundle thread_bundle;
+ AudioOutputStreamProviderPtr output_provider;
+ auto factory_context = base::MakeUnique<MockContext>(false);
+ AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory();
+
+ media::OutputDeviceStatus status;
+ media::AudioParameters params;
+ std::string id;
+ factory_ptr->RequestDeviceAuthorization(
+ mojo::MakeRequest(&output_provider), kNoSessionId, "default",
+ base::Bind(&AuthCallback, base::Unretained(&status),
+ base::Unretained(&params), base::Unretained(&id)));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(status, media::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED);
+ EXPECT_TRUE(id.empty());
+}
+
+TEST(RenderFrameAudioOutputStreamFactoryTest, ConnectionError_DeletesStream) {
+ content::TestBrowserThreadBundle thread_bundle;
+ AudioOutputStreamProviderPtr provider;
+ AudioOutputStreamPtr output_stream;
+ MockClient client;
+ bool delegate_is_destructed = false;
+ media::AudioOutputDelegate::EventHandler* event_handler = nullptr;
+ auto factory_context = base::MakeUnique<MockContext>(true);
+ factory_context->PrepareDelegateForCreation(
+ base::MakeUnique<MockAudioOutputDelegate>(
+ base::BindOnce([](bool* destructed) { *destructed = true; },
+ &delegate_is_destructed)),
+ &event_handler);
+ AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory();
+
+ factory_ptr->RequestDeviceAuthorization(
+ mojo::MakeRequest(&provider), kNoSessionId, "default",
+ base::Bind([](media::OutputDeviceStatus status,
+ const media::AudioParameters& params,
+ const std::string& id) {}));
+ base::RunLoop().RunUntilIdle();
+
+ provider->Acquire(
+ mojo::MakeRequest<AudioOutputStream>(&output_stream),
+ GetTestAudioParameters(),
+ base::Bind(&MockClient::StreamCreated, base::Unretained(&client)));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_NE(event_handler, nullptr);
+ EXPECT_FALSE(delegate_is_destructed);
+ output_stream.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(delegate_is_destructed);
+}
+
+TEST(RenderFrameAudioOutputStreamFactoryTest, DelegateError_DeletesStream) {
+ content::TestBrowserThreadBundle thread_bundle;
+ AudioOutputStreamProviderPtr provider;
+ AudioOutputStreamPtr output_stream;
+ MockClient client;
+ bool delegate_is_destructed = false;
+ media::AudioOutputDelegate::EventHandler* event_handler = nullptr;
+ auto factory_context = base::MakeUnique<MockContext>(true);
+ factory_context->PrepareDelegateForCreation(
+ base::MakeUnique<MockAudioOutputDelegate>(
+ base::BindOnce([](bool* destructed) { *destructed = true; },
+ &delegate_is_destructed)),
+ &event_handler);
+ AudioOutputStreamFactoryPtr factory_ptr = factory_context->CreateFactory();
+
+ factory_ptr->RequestDeviceAuthorization(
+ mojo::MakeRequest(&provider), kNoSessionId, "default",
+ base::Bind([](media::OutputDeviceStatus status,
+ const media::AudioParameters& params,
+ const std::string& id) {}));
+ base::RunLoop().RunUntilIdle();
+
+ provider->Acquire(
+ mojo::MakeRequest<AudioOutputStream>(&output_stream),
+ GetTestAudioParameters(),
+ base::Bind(&MockClient::StreamCreated, base::Unretained(&client)));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_NE(event_handler, nullptr);
+ EXPECT_FALSE(delegate_is_destructed);
+ event_handler->OnStreamError(kStreamId);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(delegate_is_destructed);
+}
+
+TEST(RenderFrameAudioOutputStreamFactoryTest, OutOfRangeSessionId_BadMessage) {
+ // This test checks that we get a bad message if session_id is too large
+ // to fit in an integer. This ensures that we don't overflow when casting the
+ // int64_t to an int
+ if (sizeof(int) >= sizeof(int64_t)) {
+ // In this case, any int64_t would fit in an int, and the case we are
+ // checking for is impossible.
+ return;
+ }
+
+ bool got_bad_message = false;
+ mojo::edk::SetDefaultProcessErrorCallback(
+ base::Bind([](bool* got_bad_message,
+ const std::string& s) { *got_bad_message = true; },
+ &got_bad_message));
+
+ TestBrowserThreadBundle thread_bundle;
+
+ AudioOutputStreamProviderPtr output_provider;
+ auto factory_context = base::MakeUnique<MockContext>(true);
+ auto factory_ptr = factory_context->CreateFactory();
+
+ int64_t session_id = std::numeric_limits<int>::max();
+ ++session_id;
+
+ EXPECT_FALSE(got_bad_message);
+ factory_ptr->RequestDeviceAuthorization(
+ mojo::MakeRequest(&output_provider), session_id, "default",
+ base::Bind([](media::OutputDeviceStatus, const media::AudioParameters&,
+ const std::string&) {}));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(got_bad_message);
+}
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698