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
27using base::RunLoop;
28
29namespace content {
30
31class 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.get(), 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// http://crbug.com/396414
192#if defined(OS_WIN) || defined(OS_MACOSX)
193#define MAYBE_Precheck DISABLED_Precheck
194#else
195#define MAYBE_Precheck Precheck
196#endif
197IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, MAYBE_Precheck) {
198  NavigateToURLBlockUntilNavigationsComplete(
199      shell(), GetTestUrlFromFragment("precheck"), 2);
200
201  EXPECT_EQ(kIdle, streaming_server_state());
202  EXPECT_EQ("success", GetPageFragment());
203}
204
205IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, OneShotRecognition) {
206  NavigateToURLBlockUntilNavigationsComplete(
207      shell(), GetTestUrlFromFragment("oneshot"), 2);
208
209  EXPECT_EQ(kClientDisconnected, streaming_server_state());
210  EXPECT_EQ("goodresult1", GetPageFragment());
211}
212
213}  // namespace content
214