Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1472)

Unified Diff: content/browser/speech/speech_recognition_audio_receiver_unittest.cc

Issue 549373003: Binding media stream audio track to speech recognition [browser] Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Audio Receiver unit test Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..ec531b5926bd47ac6b67bfd9aeeac92f745e7704
--- /dev/null
+++ b/content/browser/speech/speech_recognition_audio_receiver_unittest.cc
@@ -0,0 +1,345 @@
+// 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.
+enum TestScenario {
+
+ NO_TRIGGERS_ERROR = 0,
+
+ // Socket remains open for the entire fixture life time.
+ NO_CLOSE_SOCKET = 0,
+
+ // Receiver is preserved for the entire fixture life time.
+ NO_DESTROY_RECEIVER = 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,
+
+ TRIGGERS_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_.reset(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.
+ virtual void OnCreated(media::AudioInputController* controller) override {}
+ virtual void OnRecording(media::AudioInputController* controller) override {}
+ virtual void OnLog(media::AudioInputController* controller,
+ const std::string& message) override {}
+
+ virtual 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() & TRIGGERS_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(NO_CLOSE_SOCKET | NO_DESTROY_RECEIVER | TRIGGERS_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(NO_CLOSE_SOCKET | DESTROY_RECEIVER | NO_TRIGGERS_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 | NO_DESTROY_RECEIVER | TRIGGERS_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 | NO_TRIGGERS_ERROR)));
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698