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

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

Issue 2697663003: Add mojo interface+impl creation of audio streams. (Closed)
Patch Set: Use mojo::ReportBadMessage and add a thread check. Created 3 years, 9 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/renderer_audio_output_stream_factory_context_impl_unittest.cc
diff --git a/content/browser/renderer_host/media/renderer_audio_output_stream_factory_context_impl_unittest.cc b/content/browser/renderer_host/media/renderer_audio_output_stream_factory_context_impl_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4a81273b7d168dc542e0543b494062594dbdf772
--- /dev/null
+++ b/content/browser/renderer_host/media/renderer_audio_output_stream_factory_context_impl_unittest.cc
@@ -0,0 +1,383 @@
+// 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/renderer_audio_output_stream_factory_context_impl.h"
+
+#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/renderer_audio_output_stream_factory.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/audio_system_impl.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/audio/simple_sources.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 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 kRenderProcessId = 42;
+const int kRenderFrameId = 24;
+const int kNoSessionId = 0;
+const float kWaveFrequency = 440.f;
+const int kChannels = 1;
+const int kBuffers = 1000;
+const int kSampleFrequency = 44100;
+const int kBitsPerSample = 16;
+const int kSamplesPerBuffer = kSampleFrequency / 100;
+const char kSalt[] = "salt";
+
+std::unique_ptr<media::AudioOutputStream::AudioSourceCallback>
+GetTestAudioSource() {
+ return base::MakeUnique<media::SineWaveAudioSource>(kChannels, kWaveFrequency,
+ kSampleFrequency);
+}
+
+media::AudioParameters GetTestAudioParameters() {
+ return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_MONO, kSampleFrequency,
+ kBitsPerSample, kSamplesPerBuffer);
+}
+
+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) {
+ ON_CALL(*this, HasAudioOutputDevices()).WillByDefault(Return(true));
+ }
+
+ ~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)
+ : done_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ 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 {
+ done_.Wait();
+ callback_ = nullptr;
+ }
+
+ bool Open() override { return true; }
+ void SetVolume(double volume) override {}
+ void GetVolume(double* volume) override { *volume = 1; }
+ void Close() override {
+ Stop();
+ audio_manager_->ReleaseOutputStream(this);
+ }
+
+ void ThreadMain() override {
+ std::unique_ptr<media::AudioOutputStream::AudioSourceCallback>
+ expected_audio = GetTestAudioSource();
+ media::AudioParameters params = GetTestAudioParameters();
+ std::unique_ptr<media::AudioBus> dest = media::AudioBus::Create(params);
+ std::unique_ptr<media::AudioBus> expected_buffer =
+ media::AudioBus::Create(params);
+ for (int i = 0; i < kBuffers; ++i) {
+ expected_audio->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0,
+ expected_buffer.get());
+ 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(
+ expected_buffer->channel(0)[frame], dest->channel(0)[frame]))
+ << "Got " << dest->channel(0)[frame] << ", expected "
+ << expected_buffer->channel(0)[frame];
+ }
+ }
+ done_.Signal();
+ }
+
+ private:
+ base::OnceClosure sync_closure_;
+ base::PlatformThreadHandle thread_handle_;
+ base::WaitableEvent done_;
+ MockAudioManager* audio_manager_;
+ AudioSourceCallback* callback_;
+};
+
+void AuthCallback(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. Provides the signal given by
+// GetTestAudioSource() from a dedicated thread when given sync socket and
+// shared memory.
+// TODO(maxmorin): Replace with an instance of the real client, when it exists.
+class TestIPCClient : public base::PlatformThread::Delegate {
+ public:
+ TestIPCClient() {}
+
+ ~TestIPCClient() override { base::PlatformThread::Join(thread_handle_); }
+
+ // Starts thread, sets up IPC primitives and sends signal on thread.
+ void Start(mojo::ScopedSharedBufferHandle shared_buffer,
+ mojo::ScopedHandle socket_handle) {
+ 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(
+ GetTestAudioParameters()));
+ 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 {
+ std::unique_ptr<media::AudioOutputStream::AudioSourceCallback>
+ audio_source = GetTestAudioSource();
+
+ media::AudioOutputBuffer* buffer =
+ reinterpret_cast<media::AudioOutputBuffer*>(memory_->memory());
+ std::unique_ptr<media::AudioBus> output_bus =
+ media::AudioBus::WrapMemory(GetTestAudioParameters(), buffer->audio);
+
+ // Send s.
+ for (uint32_t i = 0; i < kBuffers;) {
+ uint32_t pending_data = 0;
+ size_t bytes_read = socket_->Receive(&pending_data, sizeof(pending_data));
+ // Use check here, since there's a risk of hangs in case of a bug.
+ PCHECK(sizeof(pending_data) == bytes_read)
+ << "Tried to read " << sizeof(pending_data) << " bytes but only read "
+ << bytes_read << " bytes";
+ CHECK_EQ(0u, pending_data);
+
+ ++i;
+ audio_source->OnMoreData(base::TimeDelta(), base::TimeTicks(), 0,
+ output_bus.get());
+
+ size_t bytes_written = socket_->Send(&i, sizeof(i));
+ PCHECK(sizeof(pending_data) == bytes_written)
+ << "Tried to write " << sizeof(pending_data)
+ << " bytes but only wrote " << bytes_written << " bytes";
+ }
+ }
+
+ private:
+ base::PlatformThreadHandle thread_handle_;
+ std::unique_ptr<base::CancelableSyncSocket> socket_;
+ std::unique_ptr<base::SharedMemory> memory_;
+};
+
+} // namespace
+
+// TODO(maxmorin): Add test for play, pause and set volume.
+class RendererAudioOutputStreamFactoryIntegrationTest : public Test {
+ public:
+ RendererAudioOutputStreamFactoryIntegrationTest()
+ : media_stream_manager_(),
+ thread_bundle_(TestBrowserThreadBundle::Options::REAL_IO_THREAD),
+ audio_thread_(),
+ log_factory_(),
+ audio_manager_(new MockAudioManager(audio_thread_.task_runner(),
+ audio_thread_.worker_task_runner(),
+ &log_factory_)),
+ audio_system_(media::AudioSystemImpl::Create(audio_manager_.get())) {
+ media_stream_manager_ =
+ base::MakeUnique<MediaStreamManager>(audio_system_.get());
+ }
+
+ void CreateAndBindFactory(AudioOutputStreamFactoryRequest request) {
+ factory_context_.reset(new RendererAudioOutputStreamFactoryContextImpl(
+ kRenderProcessId, audio_system_.get(), audio_manager_.get(),
+ media_stream_manager_.get(), kSalt));
+ factory_context_->CreateFactory(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<media::AudioSystem> audio_system_;
+ std::unique_ptr<RendererAudioOutputStreamFactoryContextImpl,
+ BrowserThread::DeleteOnIOThread>
+ factory_context_;
+};
+
+TEST_F(RendererAudioOutputStreamFactoryIntegrationTest, StreamIntegrationTest) {
+ // Sets up the factory 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(GetTestAudioParameters()));
+
+ AudioOutputStreamFactoryPtr factory_ptr;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(&RendererAudioOutputStreamFactoryIntegrationTest::
+ CreateAndBindFactory,
+ base::Unretained(this),
+ base::Passed(mojo::MakeRequest(&factory_ptr))));
+
+ AudioOutputStreamProviderPtr provider_ptr;
+ base::RunLoop loop;
+ media::OutputDeviceStatus status;
+ media::AudioParameters params;
+ std::string id;
+ factory_ptr->RequestDeviceAuthorization(
+ mojo::MakeRequest(&provider_ptr), kNoSessionId, "default",
+ base::Bind(&AuthCallback, base::Passed(loop.QuitWhenIdleClosure()),
+ base::Unretained(&status), base::Unretained(&params),
+ base::Unretained(&id)));
+ loop.Run();
+ ASSERT_EQ(status, media::OUTPUT_DEVICE_STATUS_OK);
+ ASSERT_EQ(GetTestAudioParameters().AsHumanReadableString(),
+ params.AsHumanReadableString());
+ ASSERT_TRUE(id.empty());
+
+ AudioOutputStreamPtr stream_ptr;
+ {
+ TestIPCClient client;
+ provider_ptr->Acquire(
+ mojo::MakeRequest(&stream_ptr), params,
+ base::Bind(&TestIPCClient::Start, base::Unretained(&client)));
+ SyncWithAllThreads();
+ stream_ptr->Play();
+ SyncWithAllThreads();
+ } // Joining client thread.
+ stream_ptr.reset();
+ SyncWithAllThreads();
+}
+
+} // namespace content
« no previous file with comments | « content/browser/renderer_host/media/renderer_audio_output_stream_factory_context_impl.cc ('k') | content/common/BUILD.gn » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698