blob: 86710d558c1e3894f8fc7d9f4dd17577d8274e6c [file] [log] [blame]
// Copyright 2014 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/renderer/media/webrtc_audio_renderer.h"
#include <string>
#include <utility>
#include <vector>
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "build/build_config.h"
#include "content/public/renderer/media_stream_audio_renderer.h"
#include "content/renderer/media/audio_device_factory.h"
#include "content/renderer/media/audio_message_filter.h"
#include "content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h"
#include "content/renderer/media/webrtc_audio_device_impl.h"
#include "media/audio/audio_output_device.h"
#include "media/audio/audio_output_ipc.h"
#include "media/base/audio_bus.h"
#include "media/base/mock_audio_renderer_sink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebMediaStream.h"
#include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
#include "third_party/WebKit/public/web/WebHeap.h"
#include "third_party/libjingle/source/talk/app/webrtc/mediastreaminterface.h"
using testing::Return;
using testing::_;
namespace content {
namespace {
const int kHardwareSampleRate = 44100;
const int kHardwareBufferSize = 512;
const char kDefaultOutputDeviceId[] = "";
const char kOtherOutputDeviceId[] = "other-output-device";
const char kInvalidOutputDeviceId[] = "invalid-device";
class MockAudioOutputIPC : public media::AudioOutputIPC {
public:
MockAudioOutputIPC() {}
virtual ~MockAudioOutputIPC() {}
MOCK_METHOD4(RequestDeviceAuthorization,
void(media::AudioOutputIPCDelegate* delegate,
int session_id,
const std::string& device_id,
const url::Origin& security_origin));
MOCK_METHOD2(CreateStream,
void(media::AudioOutputIPCDelegate* delegate,
const media::AudioParameters& params));
MOCK_METHOD0(PlayStream, void());
MOCK_METHOD0(PauseStream, void());
MOCK_METHOD0(CloseStream, void());
MOCK_METHOD1(SetVolume, void(double volume));
MOCK_METHOD2(SwitchOutputDevice,
void(const std::string& device_id,
const url::Origin& security_origin));
};
class FakeAudioOutputDevice
: NON_EXPORTED_BASE(public media::AudioOutputDevice) {
public:
FakeAudioOutputDevice(
scoped_ptr<media::AudioOutputIPC> ipc,
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
const std::string& device_id)
: AudioOutputDevice(std::move(ipc),
io_task_runner,
0,
std::string(),
url::Origin()),
device_id_(device_id) {}
MOCK_METHOD0(Start, void());
MOCK_METHOD0(Stop, void());
MOCK_METHOD0(Pause, void());
MOCK_METHOD0(Play, void());
MOCK_METHOD1(SetVolume, bool(double volume));
media::OutputDeviceStatus GetDeviceStatus() override {
return device_id_ == kInvalidOutputDeviceId
? media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL
: media::OUTPUT_DEVICE_STATUS_OK;
}
std::string GetDeviceId() const { return device_id_; }
media::AudioParameters GetOutputParameters() override {
return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_STEREO,
kHardwareSampleRate, 16, kHardwareBufferSize);
}
protected:
virtual ~FakeAudioOutputDevice() {}
private:
const std::string device_id_;
};
class MockAudioRendererSource : public WebRtcAudioRendererSource {
public:
MockAudioRendererSource() {}
virtual ~MockAudioRendererSource() {}
MOCK_METHOD4(RenderData, void(media::AudioBus* audio_bus,
int sample_rate,
int audio_delay_milliseconds,
base::TimeDelta* current_time));
MOCK_METHOD1(RemoveAudioRenderer, void(WebRtcAudioRenderer* renderer));
MOCK_METHOD0(AudioRendererThreadStopped, void());
};
} // namespace
class WebRtcAudioRendererTest : public testing::Test,
public AudioDeviceFactory {
public:
MOCK_METHOD1(MockSwitchDeviceCallback, void(media::OutputDeviceStatus));
void SwitchDeviceCallback(base::RunLoop* loop,
media::OutputDeviceStatus result) {
MockSwitchDeviceCallback(result);
loop->Quit();
}
protected:
WebRtcAudioRendererTest()
: message_loop_(new base::MessageLoopForIO),
mock_ipc_(nullptr),
source_(new MockAudioRendererSource()) {
blink::WebVector<blink::WebMediaStreamTrack> dummy_tracks;
stream_.initialize("new stream", dummy_tracks, dummy_tracks);
}
void SetupRenderer(const std::string& device_id) {
renderer_ = new WebRtcAudioRenderer(message_loop_->task_runner(), stream_,
1, 1, device_id, url::Origin());
EXPECT_CALL(*this, MockCreateOutputDevice(1, _, device_id, _));
EXPECT_TRUE(renderer_->Initialize(source_.get()));
renderer_proxy_ = renderer_->CreateSharedAudioRendererProxy(stream_);
}
MOCK_METHOD1(CreateInputDevice, media::AudioInputDevice*(int));
MOCK_METHOD4(MockCreateOutputDevice,
media::AudioOutputDevice*(int,
int,
const std::string&,
const url::Origin&));
media::AudioOutputDevice* CreateOutputDevice(
int render_frame_id,
int session_id,
const std::string& device_id,
const url::Origin& security_origin) {
MockAudioOutputIPC* fake_ipc = new MockAudioOutputIPC();
FakeAudioOutputDevice* fake_device =
new FakeAudioOutputDevice(scoped_ptr<media::AudioOutputIPC>(fake_ipc),
message_loop_->task_runner(), device_id);
if (device_id != kInvalidOutputDeviceId) {
mock_output_device_ = fake_device;
mock_ipc_ = fake_ipc;
EXPECT_CALL(*mock_output_device_.get(), Start());
}
MockCreateOutputDevice(render_frame_id, session_id, device_id,
security_origin);
return fake_device;
}
void TearDown() override {
renderer_proxy_ = nullptr;
renderer_ = nullptr;
stream_.reset();
source_.reset();
mock_output_device_ = nullptr;
blink::WebHeap::collectAllGarbageForTesting();
}
// Used to construct |mock_output_device_|.
scoped_ptr<base::MessageLoopForIO> message_loop_;
MockAudioOutputIPC* mock_ipc_; // Owned by AudioOuputDevice.
scoped_refptr<FakeAudioOutputDevice> mock_output_device_;
scoped_ptr<MockAudioRendererSource> source_;
blink::WebMediaStream stream_;
scoped_refptr<WebRtcAudioRenderer> renderer_;
scoped_refptr<MediaStreamAudioRenderer> renderer_proxy_;
};
// Verify that the renderer will be stopped if the only proxy is stopped.
TEST_F(WebRtcAudioRendererTest, StopRenderer) {
SetupRenderer(kDefaultOutputDeviceId);
renderer_proxy_->Start();
// |renderer_| has only one proxy, stopping the proxy should stop the sink of
// |renderer_|.
EXPECT_CALL(*mock_output_device_.get(), Stop());
EXPECT_CALL(*source_.get(), RemoveAudioRenderer(renderer_.get()));
renderer_proxy_->Stop();
}
// Verify that the renderer will not be stopped unless the last proxy is
// stopped.
TEST_F(WebRtcAudioRendererTest, MultipleRenderers) {
SetupRenderer(kDefaultOutputDeviceId);
renderer_proxy_->Start();
// Create a vector of renderer proxies from the |renderer_|.
std::vector<scoped_refptr<MediaStreamAudioRenderer> > renderer_proxies_;
static const int kNumberOfRendererProxy = 5;
for (int i = 0; i < kNumberOfRendererProxy; ++i) {
scoped_refptr<MediaStreamAudioRenderer> renderer_proxy(
renderer_->CreateSharedAudioRendererProxy(stream_));
renderer_proxy->Start();
renderer_proxies_.push_back(renderer_proxy);
}
// Stop the |renderer_proxy_| should not stop the sink since it is used by
// other proxies.
EXPECT_CALL(*mock_output_device_.get(), Stop()).Times(0);
renderer_proxy_->Stop();
for (int i = 0; i < kNumberOfRendererProxy; ++i) {
if (i != kNumberOfRendererProxy -1) {
EXPECT_CALL(*mock_output_device_.get(), Stop()).Times(0);
} else {
// When the last proxy is stopped, the sink will stop.
EXPECT_CALL(*source_.get(), RemoveAudioRenderer(renderer_.get()));
EXPECT_CALL(*mock_output_device_.get(), Stop());
}
renderer_proxies_[i]->Stop();
}
}
// Verify that the sink of the renderer is using the expected sample rate and
// buffer size.
TEST_F(WebRtcAudioRendererTest, VerifySinkParameters) {
SetupRenderer(kDefaultOutputDeviceId);
renderer_proxy_->Start();
#if defined(OS_LINUX) || defined(OS_MACOSX)
static const int kExpectedBufferSize = kHardwareSampleRate / 100;
#elif defined(OS_ANDROID)
static const int kExpectedBufferSize = 2 * kHardwareSampleRate / 100;
#else
// Windows.
static const int kExpectedBufferSize = kHardwareBufferSize;
#endif
EXPECT_EQ(kExpectedBufferSize, renderer_->frames_per_buffer());
EXPECT_EQ(kHardwareSampleRate, renderer_->sample_rate());
EXPECT_EQ(2, renderer_->channels());
EXPECT_CALL(*mock_output_device_.get(), Stop());
EXPECT_CALL(*source_.get(), RemoveAudioRenderer(renderer_.get()));
renderer_proxy_->Stop();
}
TEST_F(WebRtcAudioRendererTest, NonDefaultDevice) {
SetupRenderer(kDefaultOutputDeviceId);
EXPECT_EQ(mock_output_device_->GetDeviceId(), kDefaultOutputDeviceId);
renderer_proxy_->Start();
EXPECT_CALL(*mock_output_device_.get(), Stop());
EXPECT_CALL(*source_.get(), RemoveAudioRenderer(renderer_.get()));
renderer_proxy_->Stop();
SetupRenderer(kOtherOutputDeviceId);
EXPECT_EQ(mock_output_device_->GetDeviceId(), kOtherOutputDeviceId);
renderer_proxy_->Start();
EXPECT_CALL(*mock_output_device_.get(), Stop());
EXPECT_CALL(*source_.get(), RemoveAudioRenderer(renderer_.get()));
renderer_proxy_->Stop();
}
TEST_F(WebRtcAudioRendererTest, SwitchOutputDevice) {
SetupRenderer(kDefaultOutputDeviceId);
EXPECT_EQ(kDefaultOutputDeviceId, mock_output_device_->GetDeviceId());
renderer_proxy_->Start();
EXPECT_CALL(*mock_output_device_.get(), Stop());
EXPECT_CALL(*this, MockCreateOutputDevice(_, _, kOtherOutputDeviceId, _));
EXPECT_CALL(*source_.get(), AudioRendererThreadStopped());
EXPECT_CALL(*this, MockSwitchDeviceCallback(media::OUTPUT_DEVICE_STATUS_OK));
base::RunLoop loop;
renderer_proxy_->GetOutputDevice()->SwitchOutputDevice(
kOtherOutputDeviceId, url::Origin(),
base::Bind(&WebRtcAudioRendererTest::SwitchDeviceCallback,
base::Unretained(this), &loop));
loop.Run();
EXPECT_EQ(kOtherOutputDeviceId, mock_output_device_->GetDeviceId());
EXPECT_CALL(*mock_output_device_.get(), Stop());
EXPECT_CALL(*source_.get(), RemoveAudioRenderer(renderer_.get()));
renderer_proxy_->Stop();
}
TEST_F(WebRtcAudioRendererTest, SwitchOutputDeviceInvalidDevice) {
SetupRenderer(kDefaultOutputDeviceId);
EXPECT_EQ(kDefaultOutputDeviceId, mock_output_device_->GetDeviceId());
renderer_proxy_->Start();
EXPECT_CALL(*this, MockCreateOutputDevice(_, _, kInvalidOutputDeviceId, _));
EXPECT_CALL(*this, MockSwitchDeviceCallback(
media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL));
base::RunLoop loop;
renderer_proxy_->GetOutputDevice()->SwitchOutputDevice(
kInvalidOutputDeviceId, url::Origin(),
base::Bind(&WebRtcAudioRendererTest::SwitchDeviceCallback,
base::Unretained(this), &loop));
loop.Run();
EXPECT_EQ(kDefaultOutputDeviceId, mock_output_device_->GetDeviceId());
EXPECT_CALL(*mock_output_device_.get(), Stop());
EXPECT_CALL(*source_.get(), RemoveAudioRenderer(renderer_.get()));
renderer_proxy_->Stop();
}
} // namespace content