| Index: content/browser/speech/speech_recognition_audio_receiver_unittest.cc
|
| diff --git a/content/browser/speech/speech_recognition_audio_receiver_unittest.cc b/content/browser/speech/speech_recognition_audio_receiver_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..748c099f3b560f78c4565c68f4e9ae660f136f64
|
| --- /dev/null
|
| +++ b/content/browser/speech/speech_recognition_audio_receiver_unittest.cc
|
| @@ -0,0 +1,328 @@
|
| +// 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 "base/bind.h"
|
| +#include "base/memory/shared_memory.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/process/process_handle.h"
|
| +#include "base/time/time.h"
|
| +#include "content/browser/browser_thread_impl.h"
|
| +#include "content/browser/speech/speech_recognition_audio_receiver.h"
|
| +#include "media/audio/audio_parameters.h"
|
| +#include "media/base/audio_bus.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using testing::_;
|
| +
|
| +namespace {
|
| +
|
| +// Output audio format.
|
| +const media::AudioParameters::Format kOutputFormat =
|
| + media::AudioParameters::AUDIO_PCM_LOW_LATENCY;
|
| +const media::ChannelLayout kOutputChannelLayout = media::CHANNEL_LAYOUT_STEREO;
|
| +const int kOutputChannels = 2;
|
| +const int kOutputSampleRate = 16000;
|
| +const int kOutputBitsPerSample = 16;
|
| +const int kOutputFramesPerBuffer = 1600;
|
| +
|
| +// Timeout for socket.
|
| +const uint32 kMessages = 5;
|
| +
|
| +// Used for simulating behavior of the peer after the last socket receive was
|
| +// triggered. The test scenario consists of a decision whether to close the
|
| +// the socket, destroy the receiver and if there should be an error event
|
| +// triggered in that scenario.
|
| +enum TestScenario {
|
| + // Used for negating in a bitwise and operation. E.g.: NOT & CLOSE_SOCKET.
|
| + // Useful for readability.
|
| + NOT = 0,
|
| +
|
| + // The socket is closed after last message is received.
|
| + // The |Close()| occurs before the receiver gets destroyed.
|
| + CLOSE_SOCKET = 1,
|
| +
|
| + // The receiver is destroyed right after last message is received
|
| + // but not before the socket was closed if that's part of the scenario.
|
| + DESTROY_RECEIVER = 2,
|
| +
|
| + // Indicate the chosen test scenario should expect an error callback or not.
|
| + EXPECT_ERROR = 4
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +namespace content {
|
| +
|
| +class SpeechRecognitionAudioReceiverTestBase
|
| + : public testing::Test,
|
| + public media::AudioInputController::EventHandler {
|
| + public:
|
| + SpeechRecognitionAudioReceiverTestBase() :
|
| + io_thread_(BrowserThread::IO, &message_loop_),
|
| + num_messages_(0),
|
| + buffer_index_(0) {}
|
| +
|
| + virtual ~SpeechRecognitionAudioReceiverTestBase() {}
|
| +
|
| + void Initialize() {
|
| + media::AudioParameters audio_params;
|
| + audio_params.Reset(kOutputFormat,
|
| + kOutputChannelLayout,
|
| + kOutputChannels,
|
| + kOutputSampleRate,
|
| + kOutputBitsPerSample,
|
| + kOutputFramesPerBuffer);
|
| +
|
| + base::SharedMemoryHandle foreign_memory_handle;
|
| + base::SyncSocket::TransitDescriptor foreign_socket_descriptor;
|
| + receiver_ = SpeechRecognitionAudioReceiver::Create(
|
| + audio_params,
|
| + base::GetCurrentProcessHandle(),
|
| + this,
|
| + base::MessageLoop::current()->task_runner().get(),
|
| + &foreign_memory_handle,
|
| + &foreign_socket_descriptor);
|
| + ASSERT_TRUE(receiver_.get() != NULL);
|
| +
|
| + // Get the producer socket.
|
| + producer_socket_.reset(new base::SyncSocket(
|
| + base::SyncSocket::UnwrapHandle(foreign_socket_descriptor)));
|
| +
|
| + shared_memory_.reset(new base::SharedMemory(foreign_memory_handle, false));
|
| +
|
| + // Get the shared memory.
|
| + const size_t memory_length =
|
| + media::AudioBus::CalculateMemorySize(audio_params) +
|
| + sizeof(media::AudioInputBufferParameters);
|
| + ASSERT_TRUE(shared_memory_->Map(memory_length));
|
| +
|
| + // Peer's buffer index is accessed via |params.size| in shared memory.
|
| + uint8* ptr = static_cast<uint8*>(shared_memory_->memory());
|
| + media::AudioInputBuffer* buffer =
|
| + reinterpret_cast<media::AudioInputBuffer*>(ptr);
|
| + peer_buffer_index_ = &(buffer->params.size);
|
| +
|
| + // Local counter of sent messages.
|
| + buffer_index_ = 0;
|
| +
|
| + // The peer must manage their own counter and reset it.
|
| + ASSERT_EQ(0U, *peer_buffer_index_);
|
| + output_bus_ = media::AudioBus::WrapMemory(audio_params, buffer->audio);
|
| + }
|
| +
|
| + protected:
|
| + // Mock methods triggered by events and used for counting callbacks.
|
| + MOCK_METHOD0(SendCalled, void());
|
| + MOCK_METHOD0(OnDataCalled, void());
|
| + MOCK_METHOD0(OnErrorCalled, void());
|
| +
|
| + // Create the receiver and starts posting tasks to send |num_messages|.
|
| + void SendMessages(const uint32 num_messages) {
|
| + buffer_index_ = 0;
|
| + num_messages_ = num_messages;
|
| +
|
| + Initialize();
|
| +
|
| + // Start the sending soon on this thread.
|
| + PostSendTask();
|
| +
|
| + // We start listening after the send task was posted to catch the first
|
| + // message in the same thread message loop.
|
| + receiver_->Listen();
|
| + }
|
| +
|
| + base::SyncSocket* producer_socket() { return producer_socket_.get(); }
|
| + uint32 peer_buffer_index() { return *peer_buffer_index_; }
|
| + base::MessageLoopForIO * message_loop() { return &message_loop_; }
|
| +
|
| + void set_scenario(const TestScenario scenario) { scenario_ = scenario; }
|
| + const TestScenario& scenario() const { return scenario_; }
|
| +
|
| + base::MessageLoopForIO message_loop_;
|
| + BrowserThreadImpl io_thread_;
|
| +
|
| + private:
|
| + // AudioInputController::EventHandler methods.
|
| + void OnCreated(media::AudioInputController* controller) override {}
|
| + void OnRecording(media::AudioInputController* controller) override {}
|
| + void OnLog(media::AudioInputController* controller,
|
| + const std::string& message) override {}
|
| +
|
| + void OnError(
|
| + media::AudioInputController* controller,
|
| + media::AudioInputController::ErrorCode error_code) override {
|
| + // Trigger the mock callback for counting.
|
| + OnErrorCalled();
|
| + }
|
| +
|
| + void OnData(media::AudioInputController* controller,
|
| + const media::AudioBus* data) {
|
| + // Check if we're getting the same data as sent.
|
| + CheckAudioBus(data);
|
| +
|
| + if (buffer_index_ < num_messages_) {
|
| + // Schedule another send once this receive completes.
|
| + // The Send task should be scheduled before the Receive task.
|
| + PostSendTask();
|
| + } else {
|
| + // This path indicates we have received the last message.
|
| + // Here we apply the current test scenario.
|
| + if (scenario_ & CLOSE_SOCKET) {
|
| + producer_socket()->Close();
|
| + }
|
| +
|
| + if (scenario_ & DESTROY_RECEIVER) {
|
| + PostDestroyReceiverTask();
|
| + }
|
| + }
|
| +
|
| + // Trigger the mock callback for counting.
|
| + OnDataCalled();
|
| + }
|
| +
|
| + // Posts a task to trigger a send on the socket.
|
| + void PostSendTask() {
|
| + message_loop()->task_runner()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&SpeechRecognitionAudioReceiverTestBase::Send,
|
| + base::Unretained(this)));
|
| + }
|
| +
|
| + // Sends a buffer to the receiver over the socket and check synchronization.
|
| + void Send() {
|
| + // Check buffer index synchronization is done.
|
| + EXPECT_EQ(buffer_index_, peer_buffer_index());
|
| +
|
| + // Render different sample data for each send.
|
| + RenderSampleAudioToOutputBus(1 + static_cast<int16>(buffer_index_));
|
| +
|
| + size_t sent_bytes =
|
| + producer_socket()->Send(&buffer_index_, sizeof(buffer_index_));
|
| + EXPECT_EQ(sizeof(buffer_index_), sent_bytes);
|
| +
|
| + ++buffer_index_;
|
| +
|
| + // Trigger the mock callback for counting.
|
| + SendCalled();
|
| + }
|
| +
|
| + void DestroyReceiver() {
|
| + receiver_.reset();
|
| + }
|
| +
|
| + void PostDestroyReceiverTask() {
|
| + message_loop()->task_runner()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&SpeechRecognitionAudioReceiverTestBase::DestroyReceiver,
|
| + base::Unretained(this)));
|
| + }
|
| +
|
| + void RenderSampleAudioToOutputBus(int16 value) {
|
| + const int kFrames = kOutputFramesPerBuffer * kOutputChannels;
|
| + int16 audio_packet[kFrames];
|
| + for (int i = 0; i < kFrames; ++i) {
|
| + audio_packet[i] = value;
|
| + }
|
| +
|
| + output_bus_->FromInterleaved(audio_packet,
|
| + output_bus_->frames(),
|
| + sizeof(audio_packet[0]));
|
| + }
|
| +
|
| + // Checks that target_bus has same data as the source |output_bus_|.
|
| + void CheckAudioBus(const media::AudioBus* target_bus) {
|
| + const int kDataLength = kOutputFramesPerBuffer * kOutputChannels;
|
| + int16 source_data[kDataLength] = {0};
|
| + output_bus_->ToInterleaved(output_bus_->frames(),
|
| + kOutputBitsPerSample / 8,
|
| + source_data);
|
| + int16 target_data[kDataLength] = {0};
|
| + target_bus->ToInterleaved(target_bus->frames(),
|
| + kOutputBitsPerSample / 8,
|
| + target_data);
|
| +
|
| + for (int i = 0 ; i < kDataLength; ++i) {
|
| + EXPECT_EQ(source_data[i], target_data[i]);
|
| + }
|
| + }
|
| +
|
| + // How many sends before applying the test scenario.
|
| + uint32 num_messages_;
|
| +
|
| + // Affects behavior of the socket and receiver after the |num_messages_|
|
| + // messages were receieved.
|
| + TestScenario scenario_;
|
| +
|
| + uint32 buffer_index_;
|
| +
|
| + scoped_ptr<SpeechRecognitionAudioReceiver> receiver_;
|
| +
|
| + scoped_ptr<base::SyncSocket> producer_socket_;
|
| +
|
| + scoped_ptr<base::SharedMemory> shared_memory_;
|
| +
|
| + scoped_ptr<media::AudioBus> output_bus_;
|
| +
|
| + uint32* peer_buffer_index_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(SpeechRecognitionAudioReceiverTestBase);
|
| +};
|
| +
|
| +class SpeechRecognitionAudioReceiverTest
|
| + : public SpeechRecognitionAudioReceiverTestBase,
|
| + public testing::WithParamInterface<TestScenario> {
|
| +};
|
| +
|
| +TEST_P(SpeechRecognitionAudioReceiverTest, CheckScenario) {
|
| + set_scenario(GetParam());
|
| + SendMessages(kMessages);
|
| +
|
| + EXPECT_CALL(*this, SendCalled())
|
| + .Times(kMessages);
|
| + EXPECT_CALL(*this, OnDataCalled())
|
| + .Times(kMessages);
|
| + EXPECT_CALL(*this, OnErrorCalled())
|
| + .Times((scenario() & EXPECT_ERROR) ? 1 : 0);
|
| +
|
| + message_loop()->RunUntilIdle();
|
| +};
|
| +
|
| +// We don't destroy the receiver after all sends were issued which will cause
|
| +// timeouts on the socket receive and an |OnError()| callback is issued to
|
| +// indicate there is a problem with receiving data. The session is then aborted
|
| +// by the speech recognizer in production.
|
| +INSTANTIATE_TEST_CASE_P(
|
| + SendAndFreezeTriggersError,
|
| + SpeechRecognitionAudioReceiverTest,
|
| + testing::Values(TestScenario(
|
| + NOT & CLOSE_SOCKET | NOT & DESTROY_RECEIVER | EXPECT_ERROR)));
|
| +
|
| +// Here we destroy the receiver after sending the messages which should stop the
|
| +// receiving thread cleanly - without an error callback. This is a desired
|
| +// behavior since the speech recognizer should destroy the receiver when the
|
| +// session is to be stopped.
|
| +INSTANTIATE_TEST_CASE_P(
|
| + SendAndDestroyTriggersNoError,
|
| + SpeechRecognitionAudioReceiverTest,
|
| + testing::Values(TestScenario(
|
| + NOT & CLOSE_SOCKET | DESTROY_RECEIVER | NOT & EXPECT_ERROR)));
|
| +
|
| +// We close the socket after sending |kMessages| messages expecting same
|
| +// behavior as when a timeout occurs.
|
| +INSTANTIATE_TEST_CASE_P(
|
| + SendAndCloseSocketTriggersError,
|
| + SpeechRecognitionAudioReceiverTest,
|
| + testing::Values(TestScenario(
|
| + CLOSE_SOCKET | NOT & DESTROY_RECEIVER | EXPECT_ERROR)));
|
| +
|
| +// We close the socket and destroy the receiver expecting same behavior as when
|
| +// a timeout occurs.
|
| +INSTANTIATE_TEST_CASE_P(
|
| + SendCloseDestroyTriggersNoError,
|
| + SpeechRecognitionAudioReceiverTest,
|
| + testing::Values(TestScenario(
|
| + CLOSE_SOCKET | DESTROY_RECEIVER | NOT & EXPECT_ERROR)));
|
| +
|
| +} // namespace content
|
|
|