// 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