blob: 11b64de18d9e29278a0630684addf45dfb55443e [file] [log] [blame]
// 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/single_thread_task_runner.h"
#include "base/sync_socket.h"
#include "base/task/post_task.h"
#include "cc/base/math_util.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_task_traits.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 "content/renderer/media/audio/mojo_audio_output_ipc.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/audio_output_controller.h"
#include "media/audio/audio_output_device.h"
#include "media/audio/audio_system_impl.h"
#include "media/audio/audio_thread_impl.h"
#include "media/audio/mock_audio_manager.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;
const int kRenderProcessId = 42;
const int kRenderFrameId = 24;
const float kWaveFrequency = 440.f;
const int kChannels = 1;
const int kBuffers = 100;
const int kSampleFrequency = 8000;
const int kSamplesPerBuffer = kSampleFrequency / 10;
std::unique_ptr<media::AudioOutputStream::AudioSourceCallback>
GetTestAudioSource() {
return std::make_unique<media::SineWaveAudioSource>(kChannels, kWaveFrequency,
kSampleFrequency);
}
media::AudioParameters GetTestAudioParameters() {
return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO, kSampleFrequency,
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::BindOnce(&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::RunLoop(base::RunLoop::Type::kNestableTasksAllowed).RunUntilIdle();
SyncWith(base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO}));
SyncWith(media::AudioManager::Get()->GetWorkerTaskRunner());
}
}
class MockAudioOutputStream : public media::AudioOutputStream,
public base::PlatformThread::Delegate {
public:
MockAudioOutputStream()
: done_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
~MockAudioOutputStream() override {
base::PlatformThread::Join(thread_handle_);
}
void Wait() { done_.Wait(); }
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();
delete 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::IsFloatNearlyTheSame(
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_;
AudioSourceCallback* callback_;
};
class TestRenderCallback : public media::AudioRendererSink::RenderCallback {
public:
TestRenderCallback() : source_(GetTestAudioSource()) {}
~TestRenderCallback() override {}
int Render(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
int prior_frames_skipped,
media::AudioBus* dest) override {
return source_->OnMoreData(delay, delay_timestamp, prior_frames_skipped,
dest);
}
MOCK_METHOD0(OnRenderError, void());
private:
std::unique_ptr<media::AudioOutputStream::AudioSourceCallback> source_;
};
} // 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_manager_(std::make_unique<media::AudioThreadImpl>()),
audio_system_(&audio_manager_) {
media_stream_manager_ = std::make_unique<MediaStreamManager>(
&audio_system_, audio_manager_.GetTaskRunner());
}
~RendererAudioOutputStreamFactoryIntegrationTest() override {
audio_manager_.Shutdown();
}
UniqueAudioOutputStreamFactoryPtr CreateAndBindFactory(
mojom::RendererAudioOutputStreamFactoryRequest request) {
factory_context_.reset(new RendererAudioOutputStreamFactoryContextImpl(
kRenderProcessId, &audio_system_, &audio_manager_,
media_stream_manager_.get()));
return RenderFrameAudioOutputStreamFactoryHandle::CreateFactory(
factory_context_.get(), kRenderFrameId, std::move(request));
}
std::unique_ptr<MediaStreamManager> media_stream_manager_;
TestBrowserThreadBundle thread_bundle_;
media::MockAudioManager audio_manager_;
media::AudioSystemImpl audio_system_;
std::unique_ptr<RendererAudioOutputStreamFactoryContextImpl,
BrowserThread::DeleteOnIOThread>
factory_context_;
};
// It's flaky on the buildbot, http://crbug.com/761214.
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();
// Make sure the mock audio manager uses our mock stream.
bool create_stream_called = false;
audio_manager_.SetMakeOutputStreamCB(base::BindRepeating(
[](bool* create_stream_called, media::AudioOutputStream* stream,
const media::AudioParameters& params,
const std::string& name) -> media::AudioOutputStream* {
DCHECK(!*create_stream_called);
DCHECK(stream);
DCHECK_EQ(name, "default");
DCHECK_EQ(params.AsHumanReadableString(),
GetTestAudioParameters().AsHumanReadableString());
*create_stream_called = true;
return stream;
},
&create_stream_called, stream));
mojom::RendererAudioOutputStreamFactoryPtr stream_factory;
auto factory_handle =
CreateAndBindFactory(mojo::MakeRequest(&stream_factory));
base::Thread renderer_side_ipc_thread("Renderer IPC thread");
ASSERT_TRUE(renderer_side_ipc_thread.Start());
auto renderer_ipc_task_runner = renderer_side_ipc_thread.task_runner();
// Bind |stream_factory| to |renderer_ipc_task_runner|.
mojom::RendererAudioOutputStreamFactory* factory_ptr;
renderer_ipc_task_runner->PostTask(
FROM_HERE, base::BindOnce(
[](mojom::RendererAudioOutputStreamFactoryPtr* factory,
mojom::RendererAudioOutputStreamFactoryPtrInfo info,
mojom::RendererAudioOutputStreamFactory** ptr) {
factory->Bind(std::move(info));
*ptr = factory->get();
},
base::Unretained(&stream_factory),
stream_factory.PassInterface(), &factory_ptr));
// Wait for factory_ptr to be set.
SyncWith(renderer_ipc_task_runner);
auto renderer_side_ipc = std::make_unique<MojoAudioOutputIPC>(
base::BindRepeating(
[](mojom::RendererAudioOutputStreamFactory* factory_ptr) {
return factory_ptr;
},
factory_ptr),
renderer_ipc_task_runner);
auto device = base::MakeRefCounted<media::AudioOutputDevice>(
std::move(renderer_side_ipc), renderer_ipc_task_runner,
media::AudioSinkParameters(), base::TimeDelta());
StrictMock<TestRenderCallback> source;
device->Initialize(GetTestAudioParameters(), &source);
device->Start();
device->Play();
// Wait for stream to start.
SyncWithAllThreads();
// Wait for stream to finish. Verifies data.
stream->Wait();
device->Stop();
// |stream_factory| must be destroyed on the correct thread.
renderer_ipc_task_runner->PostTask(
FROM_HERE,
base::BindOnce([](mojom::RendererAudioOutputStreamFactoryPtr) {},
std::move(stream_factory)));
SyncWith(renderer_ipc_task_runner);
// Wait for any clean-up tasks.
SyncWithAllThreads();
}
} // namespace content