// Copyright 2013 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 <list>
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/speech/google_streaming_remote_engine.h"
#include "content/browser/speech/speech_recognition_manager_impl.h"
#include "content/browser/speech/speech_recognizer_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test.h"
#include "content/test/content_browser_test_utils.h"
#include "content/test/mock_google_streaming_server.h"
#include "media/audio/mock_audio_manager.h"
#include "media/audio/test_audio_input_controller_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::RunLoop;
namespace content {
class SpeechRecognitionBrowserTest :
public ContentBrowserTest,
public MockGoogleStreamingServer::Delegate,
public media::TestAudioInputControllerDelegate {
public:
enum StreamingServerState {
kIdle,
kTestAudioControllerOpened,
kClientConnected,
kClientAudioUpload,
kClientAudioUploadComplete,
kTestAudioControllerClosed,
kClientDisconnected
};
// MockGoogleStreamingServerDelegate methods.
virtual void OnClientConnected() OVERRIDE {
ASSERT_EQ(kTestAudioControllerOpened, streaming_server_state_);
streaming_server_state_ = kClientConnected;
}
virtual void OnClientAudioUpload() OVERRIDE {
if (streaming_server_state_ == kClientConnected)
streaming_server_state_ = kClientAudioUpload;
}
virtual void OnClientAudioUploadComplete() OVERRIDE {
ASSERT_EQ(kTestAudioControllerClosed, streaming_server_state_);
streaming_server_state_ = kClientAudioUploadComplete;
}
virtual void OnClientDisconnected() OVERRIDE {
ASSERT_EQ(kClientAudioUploadComplete, streaming_server_state_);
streaming_server_state_ = kClientDisconnected;
}
// media::TestAudioInputControllerDelegate methods.
virtual void TestAudioControllerOpened(
media::TestAudioInputController* controller) OVERRIDE {
ASSERT_EQ(kIdle, streaming_server_state_);
streaming_server_state_ = kTestAudioControllerOpened;
const int capture_packet_interval_ms =
(1000 * controller->audio_parameters().frames_per_buffer()) /
controller->audio_parameters().sample_rate();
ASSERT_EQ(GoogleStreamingRemoteEngine::kAudioPacketIntervalMs,
capture_packet_interval_ms);
FeedAudioController(500 /* ms */, /*noise=*/ false);
FeedAudioController(1000 /* ms */, /*noise=*/ true);
FeedAudioController(1000 /* ms */, /*noise=*/ false);
}
virtual void TestAudioControllerClosed(
media::TestAudioInputController* controller) OVERRIDE {
ASSERT_EQ(kClientAudioUpload, streaming_server_state_);
streaming_server_state_ = kTestAudioControllerClosed;
mock_streaming_server_->MockGoogleStreamingServer::SimulateResult(
GetGoodSpeechResult());
}
// Helper methods used by test fixtures.
GURL GetTestUrlFromFragment(const std::string fragment) {
return GURL(GetTestUrl("speech", "web_speech_recognition.html").spec() +
"#" + fragment);
}
std::string GetPageFragment() {
return shell()->web_contents()->GetURL().ref();
}
const StreamingServerState &streaming_server_state() {
return streaming_server_state_;
}
protected:
// ContentBrowserTest methods.
virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
test_audio_input_controller_factory_.set_delegate(this);
media::AudioInputController::set_factory_for_testing(
&test_audio_input_controller_factory_);
mock_streaming_server_.reset(new MockGoogleStreamingServer(this));
streaming_server_state_ = kIdle;
}
virtual void SetUpOnMainThread() OVERRIDE {
ASSERT_TRUE(SpeechRecognitionManagerImpl::GetInstance());
SpeechRecognizerImpl::SetAudioManagerForTesting(
new media::MockAudioManager(BrowserThread::GetMessageLoopProxyForThread(
BrowserThread::IO)));
}
virtual void TearDownOnMainThread() OVERRIDE {
SpeechRecognizerImpl::SetAudioManagerForTesting(NULL);
}
virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
test_audio_input_controller_factory_.set_delegate(NULL);
mock_streaming_server_.reset();
}
private:
static void FeedSingleBufferToAudioController(
scoped_refptr<media::TestAudioInputController> controller,
size_t buffer_size,
bool fill_with_noise) {
DCHECK(controller.get());
scoped_ptr<uint8[]> audio_buffer(new uint8[buffer_size]);
if (fill_with_noise) {
for (size_t i = 0; i < buffer_size; ++i)
audio_buffer[i] = static_cast<uint8>(127 * sin(i * 3.14F /
(16 * buffer_size)));
} else {
memset(audio_buffer.get(), 0, buffer_size);
}
controller->event_handler()->OnData(controller,
audio_buffer.get(),
buffer_size);
}
void FeedAudioController(int duration_ms, bool feed_with_noise) {
media::TestAudioInputController* controller =
test_audio_input_controller_factory_.controller();
ASSERT_TRUE(controller);
const media::AudioParameters& audio_params = controller->audio_parameters();
const size_t buffer_size = audio_params.GetBytesPerBuffer();
const int ms_per_buffer = audio_params.frames_per_buffer() * 1000 /
audio_params.sample_rate();
// We can only simulate durations that are integer multiples of the
// buffer size. In this regard see
// SpeechRecognitionEngine::GetDesiredAudioChunkDurationMs().
ASSERT_EQ(0, duration_ms % ms_per_buffer);
const int n_buffers = duration_ms / ms_per_buffer;
for (int i = 0; i < n_buffers; ++i) {
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
&FeedSingleBufferToAudioController,
scoped_refptr<media::TestAudioInputController>(controller),
buffer_size,
feed_with_noise));
}
}
SpeechRecognitionResult GetGoodSpeechResult() {
SpeechRecognitionResult result;
result.hypotheses.push_back(SpeechRecognitionHypothesis(
UTF8ToUTF16("Pictures of the moon"), 1.0F));
return result;
}
StreamingServerState streaming_server_state_;
scoped_ptr<MockGoogleStreamingServer> mock_streaming_server_;
media::TestAudioInputControllerFactory test_audio_input_controller_factory_;
};
// Simply loads the test page and checks if it was able to create a Speech
// Recognition object in JavaScript, to make sure the Web Speech API is enabled.
IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, Precheck) {
NavigateToURLBlockUntilNavigationsComplete(
shell(), GetTestUrlFromFragment("precheck"), 2);
EXPECT_EQ(kIdle, streaming_server_state());
EXPECT_EQ("success", GetPageFragment());
}
IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, OneShotRecognition) {
NavigateToURLBlockUntilNavigationsComplete(
shell(), GetTestUrlFromFragment("oneshot"), 2);
EXPECT_EQ(kClientDisconnected, streaming_server_state());
EXPECT_EQ("goodresult1", GetPageFragment());
}
} // namespace content