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

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

Issue 2319493002: Add mojo interface for audio rendering. (Closed)
Patch Set: ++docs Created 3 years, 10 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/audio_output_service_context_impl_unittest.cc
diff --git a/content/browser/renderer_host/media/audio_output_service_context_impl_unittest.cc b/content/browser/renderer_host/media/audio_output_service_context_impl_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c195db5103b2dd5e22e92a500a6eb40ae3b211bf
--- /dev/null
+++ b/content/browser/renderer_host/media/audio_output_service_context_impl_unittest.cc
@@ -0,0 +1,355 @@
+// 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 "content/browser/renderer_host/media/audio_output_service_context_impl.h"
+
+#include <cmath>
+#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 "cc/base/math_util.h"
+#include "content/browser/audio_manager_thread.h"
+#include "content/browser/renderer_host/media/media_stream_manager.h"
+#include "content/common/media/audio_output.mojom.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/audio_output_controller.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_switches.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::_;
+using testing::StrictMock;
+using testing::Return;
+using testing::Test;
+using Signal = std::vector<std::unique_ptr<const media::AudioBus>>;
+using AudioOutputService = mojom::RendererAudioOutputService;
+using AudioOutputServicePtr = mojo::InterfacePtr<AudioOutputService>;
+using AudioOutputServiceRequest = mojo::InterfaceRequest<AudioOutputService>;
+using AudioOutput = mojom::AudioOutput;
+using AudioOutputPtr = mojo::InterfacePtr<AudioOutput>;
+using AudioOutputRequest = mojo::InterfaceRequest<AudioOutput>;
+
+const int kRenderProcessId = 42;
+const int kRenderFrameId = 24;
+const char kSecurityOrigin[] = "http://localhost";
+const char kSalt[] = "salt";
+const double pi = std::acos(-1);
+const media::AudioParameters params(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_MONO,
+ 44100 /*sample frequency*/,
+ 16 /*bits per sample*/,
+ 441 /*10 ms buffers*/);
+
+Signal MakeSineWave() {
+ Signal s;
+ s.reserve(1000);
+ int64_t t = 0;
+ for (int i = 0; i < 1000; i++) {
+ std::unique_ptr<media::AudioBus> bus = media::AudioBus::Create(params);
+ float* channel = bus->channel(0);
+ int frames = params.frames_per_buffer();
+ for (int j = 0; j < frames; ++j) {
+ ++t;
+ channel[j] = std::sin(t * params.GetMicrosecondsPerFrame() / 1000000.0 *
+ 440 * 2 * pi);
+ }
+ s.push_back(std::move(bus));
+ }
+ return s;
+}
+
+void SyncWith(scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ CHECK(task_runner);
+ CHECK(!task_runner->BelongsToCurrentThread());
+ base::WaitableEvent e = {base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED};
+ task_runner->PostTask(FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
+ base::Unretained(&e)));
+ e.Wait();
+}
+
+void SyncWithAllThreads() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ // New tasks might be posted while we are syncing, but in every iteration at
+ // least one task will be run. 20 iterations should be enough for our code.
+ for (int i = 0; i < 20; ++i) {
+ {
+ base::MessageLoop::ScopedNestableTaskAllower allower(
+ base::MessageLoop::current());
+ base::RunLoop().RunUntilIdle();
+ }
+ SyncWith(BrowserThread::GetTaskRunnerForThread(BrowserThread::IO));
+ SyncWith(media::AudioManager::Get()->GetWorkerTaskRunner());
+ }
+}
+
+class MockAudioManager : public media::AudioManagerBase {
+ public:
+ MockAudioManager(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner,
+ media::AudioLogFactory* audio_log_factory)
+ : media::AudioManagerBase(task_runner,
+ worker_task_runner,
+ audio_log_factory) {}
+
+ ~MockAudioManager() override { Shutdown(); }
+
+ MOCK_METHOD0(HasAudioOutputDevices, bool());
+ MOCK_METHOD0(HasAudioInputDevices, bool());
+ MOCK_METHOD0(GetName, const char*());
+
+ MOCK_METHOD2(MakeLinearOutputStream,
+ media::AudioOutputStream*(const media::AudioParameters& params,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLowLatencyOutputStream,
+ media::AudioOutputStream*(const media::AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLinearInputStream,
+ media::AudioInputStream*(const media::AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLowLatencyInputStream,
+ media::AudioInputStream*(const media::AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD2(GetPreferredOutputStreamParameters,
+ media::AudioParameters(const std::string& device_id,
+ const media::AudioParameters& params));
+};
+
+class MockAudioOutputStream : public media::AudioOutputStream,
+ public base::PlatformThread::Delegate {
+ public:
+ explicit MockAudioOutputStream(MockAudioManager* audio_manager)
+ : audio_manager_(audio_manager) {}
+ ~MockAudioOutputStream() override {
+ base::PlatformThread::Join(thread_handle_);
+ }
+
+ void Start(AudioSourceCallback* callback) override {
+ callback_ = callback;
+ EXPECT_TRUE(base::PlatformThread::CreateWithPriority(
+ 0, this, &thread_handle_, base::ThreadPriority::REALTIME_AUDIO));
+ }
+
+ void Stop() override {}
+
+ bool Open() override { return true; }
+ void SetVolume(double volume) override {}
+ void GetVolume(double* volume) override { *volume = 1; }
+ void Close() override { audio_manager_->ReleaseOutputStream(this); }
+
+ void ThreadMain() override {
+ Signal expected = MakeSineWave();
+ std::unique_ptr<media::AudioBus> dest = media::AudioBus::Create(params);
+ for (const auto& bus : expected) {
+ callback_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0,
+ dest.get());
+ for (int frame = 0; frame < params.frames_per_buffer(); ++frame) {
+ // Using EXPECT here causes massive log spam in case of a broken test,
+ // and ASSERT causes it to hang, so we use CHECK.
+ CHECK(cc::MathUtil::IsNearlyTheSameForTesting(bus->channel(0)[frame],
+ dest->channel(0)[frame]))
+ << "Got " << dest->channel(0)[frame] << ", expected "
+ << bus->channel(0)[frame];
+ }
+ }
+ }
+
+ private:
+ base::OnceClosure sync_closure_;
+ base::PlatformThreadHandle thread_handle_;
+ MockAudioManager* audio_manager_;
+ AudioSourceCallback* callback_;
+};
+
+void AuthCallback2(base::OnceClosure sync_closure,
+ 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;
+ std::move(sync_closure).Run();
+}
+
+// "Renderer-side" audio client. Sends a signal from a dedicated thread.
+// TODO(maxmorin): Replace with an instance of the real client, when it exists.
+class TestClient : public base::PlatformThread::Delegate {
+ public:
+ TestClient() {}
+
+ ~TestClient() override { base::PlatformThread::Join(thread_handle_); }
+
+ // Starts thread, sets up IPC primitives and sends signal on thread.
+ void Start(Signal s,
+ mojo::ScopedSharedBufferHandle shared_buffer,
+ mojo::ScopedHandle socket_handle) {
+ s_ = std::move(s);
+
+ EXPECT_TRUE(socket_handle.is_valid());
+ // Set up socket.
+ base::PlatformFile fd;
+ mojo::UnwrapPlatformFile(std::move(socket_handle), &fd);
+ socket_ = base::MakeUnique<base::CancelableSyncSocket>(fd);
+ EXPECT_NE(socket_->handle(), base::CancelableSyncSocket::kInvalidHandle);
+
+ // Set up memory.
+ EXPECT_TRUE(shared_buffer.is_valid());
+ size_t memory_length;
+ base::SharedMemoryHandle shmem_handle;
+ bool read_only;
+ EXPECT_EQ(
+ mojo::UnwrapSharedMemoryHandle(std::move(shared_buffer), &shmem_handle,
+ &memory_length, &read_only),
+ MOJO_RESULT_OK);
+ EXPECT_EQ(memory_length, sizeof(media::AudioOutputBufferParameters) +
+ media::AudioBus::CalculateMemorySize(params));
+ EXPECT_EQ(read_only, false);
+ memory_ = base::MakeUnique<base::SharedMemory>(shmem_handle, read_only);
+ EXPECT_TRUE(memory_->Map(memory_length));
+
+ EXPECT_TRUE(base::PlatformThread::CreateWithPriority(
+ 0, this, &thread_handle_, base::ThreadPriority::REALTIME_AUDIO));
+ }
+
+ void ThreadMain() override {
+ media::AudioOutputBuffer* buffer =
+ reinterpret_cast<media::AudioOutputBuffer*>(memory_->memory());
+ std::unique_ptr<media::AudioBus> output_bus =
+ media::AudioBus::WrapMemory(params, buffer->audio);
+
+ // Send s.
+ uint32_t buffer_index = 0;
+ for (const auto& bus : s_) {
+ uint32_t pending_data = 0;
+ size_t bytes_read = socket_->Receive(&pending_data, sizeof(pending_data));
+ EXPECT_EQ(sizeof(pending_data), bytes_read);
+ EXPECT_EQ(0u, pending_data);
+
+ ++buffer_index;
+
+ bus->CopyTo(output_bus.get());
+
+ size_t bytes_written = socket_->Send(&buffer_index, sizeof(buffer_index));
+ EXPECT_EQ(sizeof(buffer_index), bytes_written);
+ }
+ }
+
+ private:
+ base::PlatformThreadHandle thread_handle_;
+ Signal s_;
+ std::unique_ptr<base::CancelableSyncSocket> socket_;
+ std::unique_ptr<base::SharedMemory> memory_;
+};
+
+} // namespace
+
+class AudioOutputServiceIntegrationTest : public Test {
+ public:
+ AudioOutputServiceIntegrationTest()
+ : media_stream_manager_(),
+ thread_bundle_(TestBrowserThreadBundle::Options::REAL_IO_THREAD),
+ audio_thread_(),
+ log_factory_(),
+ audio_manager_(
+ new StrictMock<MockAudioManager>(audio_thread_.task_runner(),
+ audio_thread_.worker_task_runner(),
+ &log_factory_)) {
+ media_stream_manager_ =
+ base::MakeUnique<MediaStreamManager>(audio_manager_.get());
+ }
+
+ ~AudioOutputServiceIntegrationTest() override { SyncWithAllThreads(); }
+
+ void CreateAndBindService(AudioOutputServiceRequest request) {
+ service_context_.reset(new AudioOutputServiceContextImpl(
+ kRenderProcessId, audio_manager_.get(), media_stream_manager_.get(),
+ kSalt));
+ service_context_->CreateService(kRenderFrameId, std::move(request));
+ }
+
+ std::unique_ptr<MediaStreamManager> media_stream_manager_;
+ TestBrowserThreadBundle thread_bundle_;
+ AudioManagerThread audio_thread_;
+ media::FakeAudioLogFactory log_factory_;
+ media::ScopedAudioManagerPtr audio_manager_;
+ std::unique_ptr<AudioOutputServiceContextImpl,
+ BrowserThread::DeleteOnIOThread>
+ service_context_;
+};
+
+TEST_F(AudioOutputServiceIntegrationTest, StreamIntegrationTest) {
+ // Sets up the service on the IO thread and runs client code on the UI thread.
+ // Send a sine wave from the client and makes sure it's received by the output
+ // stream.
+ MockAudioOutputStream* stream = new MockAudioOutputStream(
+ static_cast<MockAudioManager*>(audio_manager_.get()));
+
+ // Make sure the mock audio manager uses our mock stream.
+ EXPECT_CALL(*static_cast<MockAudioManager*>(audio_manager_.get()),
+ MakeLowLatencyOutputStream(_, "", _))
+ .WillOnce(Return(stream));
+ EXPECT_CALL(*static_cast<MockAudioManager*>(audio_manager_.get()),
+ GetPreferredOutputStreamParameters(_, _))
+ .WillRepeatedly(Return(params));
+
+ AudioOutputServicePtr service_ptr;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&AudioOutputServiceIntegrationTest::CreateAndBindService,
+ base::Unretained(this),
+ base::Passed(mojo::MakeRequest(&service_ptr))));
+
+ AudioOutputPtr output_ptr;
+ base::RunLoop loop;
+ media::OutputDeviceStatus status;
+ media::AudioParameters params2;
+ std::string id;
+ service_ptr->RequestDeviceAuthorization(
+ mojo::MakeRequest<AudioOutput>(&output_ptr), /*session_id*/ 0, "default",
+ url::Origin(GURL(kSecurityOrigin)),
+ base::Bind(&AuthCallback2, base::Passed(loop.QuitWhenIdleClosure()),
+ base::Unretained(&status), base::Unretained(&params2),
+ base::Unretained(&id)));
+ loop.Run();
+ ASSERT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK);
+ ASSERT_EQ(params.AsHumanReadableString(), params2.AsHumanReadableString());
+ ASSERT_TRUE(id.empty());
+
+ {
+ TestClient client;
+ output_ptr->Start(params,
+ base::Bind(&TestClient::Start, base::Unretained(&client),
+ base::Passed(MakeSineWave())));
+ output_ptr->Play();
+ SyncWithAllThreads();
+ } // Joining client thread.
+ output_ptr.reset();
+ SyncWithAllThreads(); // Joins stream thread.
+}
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698