1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <list> 6 7 #include "base/bind.h" 8 #include "base/memory/scoped_ptr.h" 9 #include "base/run_loop.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "content/browser/speech/google_streaming_remote_engine.h" 12 #include "content/browser/speech/speech_recognition_manager_impl.h" 13 #include "content/browser/speech/speech_recognizer_impl.h" 14 #include "content/public/browser/browser_thread.h" 15 #include "content/public/browser/notification_types.h" 16 #include "content/public/browser/web_contents.h" 17 #include "content/public/test/browser_test_utils.h" 18 #include "content/public/test/content_browser_test.h" 19 #include "content/public/test/content_browser_test_utils.h" 20 #include "content/public/test/test_utils.h" 21 #include "content/shell/browser/shell.h" 22 #include "content/test/mock_google_streaming_server.h" 23 #include "media/audio/mock_audio_manager.h" 24 #include "media/audio/test_audio_input_controller_factory.h" 25 #include "testing/gtest/include/gtest/gtest.h" 26 27 using base::RunLoop; 28 29 namespace content { 30 31 class SpeechRecognitionBrowserTest : 32 public ContentBrowserTest, 33 public MockGoogleStreamingServer::Delegate, 34 public media::TestAudioInputControllerDelegate { 35 public: 36 enum StreamingServerState { 37 kIdle, 38 kTestAudioControllerOpened, 39 kClientConnected, 40 kClientAudioUpload, 41 kClientAudioUploadComplete, 42 kTestAudioControllerClosed, 43 kClientDisconnected 44 }; 45 46 // MockGoogleStreamingServerDelegate methods. 47 virtual void OnClientConnected() OVERRIDE { 48 ASSERT_EQ(kTestAudioControllerOpened, streaming_server_state_); 49 streaming_server_state_ = kClientConnected; 50 } 51 52 virtual void OnClientAudioUpload() OVERRIDE { 53 if (streaming_server_state_ == kClientConnected) 54 streaming_server_state_ = kClientAudioUpload; 55 } 56 57 virtual void OnClientAudioUploadComplete() OVERRIDE { 58 ASSERT_EQ(kTestAudioControllerClosed, streaming_server_state_); 59 streaming_server_state_ = kClientAudioUploadComplete; 60 } 61 62 virtual void OnClientDisconnected() OVERRIDE { 63 ASSERT_EQ(kClientAudioUploadComplete, streaming_server_state_); 64 streaming_server_state_ = kClientDisconnected; 65 } 66 67 // media::TestAudioInputControllerDelegate methods. 68 virtual void TestAudioControllerOpened( 69 media::TestAudioInputController* controller) OVERRIDE { 70 ASSERT_EQ(kIdle, streaming_server_state_); 71 streaming_server_state_ = kTestAudioControllerOpened; 72 const int capture_packet_interval_ms = 73 (1000 * controller->audio_parameters().frames_per_buffer()) / 74 controller->audio_parameters().sample_rate(); 75 ASSERT_EQ(GoogleStreamingRemoteEngine::kAudioPacketIntervalMs, 76 capture_packet_interval_ms); 77 FeedAudioController(500 /* ms */, /*noise=*/ false); 78 FeedAudioController(1000 /* ms */, /*noise=*/ true); 79 FeedAudioController(1000 /* ms */, /*noise=*/ false); 80 } 81 82 virtual void TestAudioControllerClosed( 83 media::TestAudioInputController* controller) OVERRIDE { 84 ASSERT_EQ(kClientAudioUpload, streaming_server_state_); 85 streaming_server_state_ = kTestAudioControllerClosed; 86 mock_streaming_server_->MockGoogleStreamingServer::SimulateResult( 87 GetGoodSpeechResult()); 88 } 89 90 // Helper methods used by test fixtures. 91 GURL GetTestUrlFromFragment(const std::string fragment) { 92 return GURL(GetTestUrl("speech", "web_speech_recognition.html").spec() + 93 "#" + fragment); 94 } 95 96 std::string GetPageFragment() { 97 return shell()->web_contents()->GetURL().ref(); 98 } 99 100 const StreamingServerState &streaming_server_state() { 101 return streaming_server_state_; 102 } 103 104 protected: 105 // ContentBrowserTest methods. 106 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 107 test_audio_input_controller_factory_.set_delegate(this); 108 media::AudioInputController::set_factory_for_testing( 109 &test_audio_input_controller_factory_); 110 mock_streaming_server_.reset(new MockGoogleStreamingServer(this)); 111 streaming_server_state_ = kIdle; 112 } 113 114 virtual void SetUpOnMainThread() OVERRIDE { 115 ASSERT_TRUE(SpeechRecognitionManagerImpl::GetInstance()); 116 SpeechRecognizerImpl::SetAudioManagerForTesting( 117 new media::MockAudioManager(BrowserThread::GetMessageLoopProxyForThread( 118 BrowserThread::IO))); 119 } 120 121 virtual void TearDownOnMainThread() OVERRIDE { 122 SpeechRecognizerImpl::SetAudioManagerForTesting(NULL); 123 } 124 125 virtual void TearDownInProcessBrowserTestFixture() OVERRIDE { 126 test_audio_input_controller_factory_.set_delegate(NULL); 127 mock_streaming_server_.reset(); 128 } 129 130 private: 131 static void FeedSingleBufferToAudioController( 132 scoped_refptr<media::TestAudioInputController> controller, 133 size_t buffer_size, 134 bool fill_with_noise) { 135 DCHECK(controller.get()); 136 const media::AudioParameters& audio_params = controller->audio_parameters(); 137 scoped_ptr<uint8[]> audio_buffer(new uint8[buffer_size]); 138 if (fill_with_noise) { 139 for (size_t i = 0; i < buffer_size; ++i) 140 audio_buffer[i] = static_cast<uint8>(127 * sin(i * 3.14F / 141 (16 * buffer_size))); 142 } else { 143 memset(audio_buffer.get(), 0, buffer_size); 144 } 145 146 scoped_ptr<media::AudioBus> audio_bus = 147 media::AudioBus::Create(audio_params); 148 audio_bus->FromInterleaved(&audio_buffer.get()[0], 149 audio_bus->frames(), 150 audio_params.bits_per_sample() / 8); 151 controller->event_handler()->OnData(controller, audio_bus.get()); 152 } 153 154 void FeedAudioController(int duration_ms, bool feed_with_noise) { 155 media::TestAudioInputController* controller = 156 test_audio_input_controller_factory_.controller(); 157 ASSERT_TRUE(controller); 158 const media::AudioParameters& audio_params = controller->audio_parameters(); 159 const size_t buffer_size = audio_params.GetBytesPerBuffer(); 160 const int ms_per_buffer = audio_params.frames_per_buffer() * 1000 / 161 audio_params.sample_rate(); 162 // We can only simulate durations that are integer multiples of the 163 // buffer size. In this regard see 164 // SpeechRecognitionEngine::GetDesiredAudioChunkDurationMs(). 165 ASSERT_EQ(0, duration_ms % ms_per_buffer); 166 167 const int n_buffers = duration_ms / ms_per_buffer; 168 for (int i = 0; i < n_buffers; ++i) { 169 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( 170 &FeedSingleBufferToAudioController, 171 scoped_refptr<media::TestAudioInputController>(controller), 172 buffer_size, 173 feed_with_noise)); 174 } 175 } 176 177 SpeechRecognitionResult GetGoodSpeechResult() { 178 SpeechRecognitionResult result; 179 result.hypotheses.push_back(SpeechRecognitionHypothesis( 180 base::UTF8ToUTF16("Pictures of the moon"), 1.0F)); 181 return result; 182 } 183 184 StreamingServerState streaming_server_state_; 185 scoped_ptr<MockGoogleStreamingServer> mock_streaming_server_; 186 media::TestAudioInputControllerFactory test_audio_input_controller_factory_; 187 }; 188 189 // Simply loads the test page and checks if it was able to create a Speech 190 // Recognition object in JavaScript, to make sure the Web Speech API is enabled. 191 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, Precheck) { 192 NavigateToURLBlockUntilNavigationsComplete( 193 shell(), GetTestUrlFromFragment("precheck"), 2); 194 195 EXPECT_EQ(kIdle, streaming_server_state()); 196 EXPECT_EQ("success", GetPageFragment()); 197 } 198 199 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, OneShotRecognition) { 200 NavigateToURLBlockUntilNavigationsComplete( 201 shell(), GetTestUrlFromFragment("oneshot"), 2); 202 203 EXPECT_EQ(kClientDisconnected, streaming_server_state()); 204 EXPECT_EQ("goodresult1", GetPageFragment()); 205 } 206 207 } // namespace content 208